3535#include "streamutil.h"
3636
3737
38+ #define atooid (x ) ((Oid) strtoul((x), NULL, 10))
39+
40+ typedef struct TablespaceListCell
41+ {
42+ struct TablespaceListCell * next ;
43+ char old_dir [MAXPGPATH ];
44+ char new_dir [MAXPGPATH ];
45+ } TablespaceListCell ;
46+
47+ typedef struct TablespaceList
48+ {
49+ TablespaceListCell * head ;
50+ TablespaceListCell * tail ;
51+ } TablespaceList ;
52+
3853/* Global options */
3954static char * basedir = NULL ;
55+ static TablespaceList tablespace_dirs = {NULL , NULL };
4056static char * xlog_dir = "" ;
4157static char format = 'p' ; /* p(lain)/t(ar) */
4258static char * label = "pg_basebackup base backup" ;
@@ -90,6 +106,10 @@ static void BaseBackup(void);
90106static bool reached_end_position (XLogRecPtr segendpos , uint32 timeline ,
91107 bool segment_finished );
92108
109+ static const char * get_tablespace_mapping (const char * dir );
110+ static void update_tablespace_symlink (Oid oid , const char * old_dir );
111+ static void tablespace_list_append (const char * arg );
112+
93113
94114static void disconnect_and_exit (int code )
95115{
@@ -110,6 +130,77 @@ static void disconnect_and_exit(int code)
110130}
111131
112132
133+ /*
134+ * Split argument into old_dir and new_dir and append to tablespace mapping
135+ * list.
136+ */
137+ static void
138+ tablespace_list_append (const char * arg )
139+ {
140+ TablespaceListCell * cell = (TablespaceListCell * ) pg_malloc0 (sizeof (TablespaceListCell ));
141+ char * dst ;
142+ char * dst_ptr ;
143+ const char * arg_ptr ;
144+
145+ dst_ptr = dst = cell -> old_dir ;
146+ for (arg_ptr = arg ; * arg_ptr ; arg_ptr ++ )
147+ {
148+ if (dst_ptr - dst >= MAXPGPATH )
149+ {
150+ fprintf (stderr , _ ("%s: directory name too long\n" ), progname );
151+ exit (1 );
152+ }
153+
154+ if (* arg_ptr == '\\' && * (arg_ptr + 1 ) == '=' )
155+ ; /* skip backslash escaping = */
156+ else if (* arg_ptr == '=' && (arg_ptr == arg || * (arg_ptr - 1 ) != '\\' ))
157+ {
158+ if (* cell -> new_dir )
159+ {
160+ fprintf (stderr , _ ("%s: multiple \"=\" signs in tablespace mapping\n" ), progname );
161+ exit (1 );
162+ }
163+ else
164+ dst = dst_ptr = cell -> new_dir ;
165+ }
166+ else
167+ * dst_ptr ++ = * arg_ptr ;
168+ }
169+
170+ if (!* cell -> old_dir || !* cell -> new_dir )
171+ {
172+ fprintf (stderr ,
173+ _ ("%s: invalid tablespace mapping format \"%s\", must be \"OLDDIR=NEWDIR\"\n" ),
174+ progname , arg );
175+ exit (1 );
176+ }
177+
178+ /* This check isn't absolutely necessary. But all tablespaces are created
179+ * with absolute directories, so specifying a non-absolute path here would
180+ * just never match, possibly confusing users. It's also good to be
181+ * consistent with the new_dir check. */
182+ if (!is_absolute_path (cell -> old_dir ))
183+ {
184+ fprintf (stderr , _ ("%s: old directory not absolute in tablespace mapping: %s\n" ),
185+ progname , cell -> old_dir );
186+ exit (1 );
187+ }
188+
189+ if (!is_absolute_path (cell -> new_dir ))
190+ {
191+ fprintf (stderr , _ ("%s: new directory not absolute in tablespace mapping: %s\n" ),
192+ progname , cell -> new_dir );
193+ exit (1 );
194+ }
195+
196+ if (tablespace_dirs .tail )
197+ tablespace_dirs .tail -> next = cell ;
198+ else
199+ tablespace_dirs .head = cell ;
200+ tablespace_dirs .tail = cell ;
201+ }
202+
203+
113204#ifdef HAVE_LIBZ
114205static const char *
115206get_gz_error (gzFile gzf )
@@ -137,6 +228,8 @@ usage(void)
137228 printf (_ (" -F, --format=p|t output format (plain (default), tar)\n" ));
138229 printf (_ (" -R, --write-recovery-conf\n"
139230 " write recovery.conf after backup\n" ));
231+ printf (_ (" -T, --tablespace-mapping=OLDDIR=NEWDIR\n"
232+ " relocate tablespace in OLDDIR to NEWDIR\n" ));
140233 printf (_ (" -x, --xlog include required WAL files in backup (fetch mode)\n" ));
141234 printf (_ (" -X, --xlog-method=fetch|stream\n"
142235 " include required WAL files with specified method\n" ));
@@ -899,15 +992,60 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
899992 PQfreemem (copybuf );
900993}
901994
995+
996+ /*
997+ * Retrieve tablespace path, either relocated or original depending on whether
998+ * -T was passed or not.
999+ */
1000+ static const char *
1001+ get_tablespace_mapping (const char * dir )
1002+ {
1003+ TablespaceListCell * cell ;
1004+
1005+ for (cell = tablespace_dirs .head ; cell ; cell = cell -> next )
1006+ if (strcmp (dir , cell -> old_dir ) == 0 )
1007+ return cell -> new_dir ;
1008+
1009+ return dir ;
1010+ }
1011+
1012+
1013+ /*
1014+ * Update symlinks to reflect relocated tablespace.
1015+ */
1016+ static void
1017+ update_tablespace_symlink (Oid oid , const char * old_dir )
1018+ {
1019+ const char * new_dir = get_tablespace_mapping (old_dir );
1020+
1021+ if (strcmp (old_dir , new_dir ) != 0 )
1022+ {
1023+ char * linkloc = psprintf ("%s/pg_tblspc/%d" , basedir , oid );
1024+
1025+ if (unlink (linkloc ) != 0 && errno != ENOENT )
1026+ {
1027+ fprintf (stderr , _ ("%s: could not remove symbolic link \"%s\": %s" ),
1028+ progname , linkloc , strerror (errno ));
1029+ disconnect_and_exit (1 );
1030+ }
1031+ if (symlink (new_dir , linkloc ) != 0 )
1032+ {
1033+ fprintf (stderr , _ ("%s: could not create symbolic link \"%s\": %s" ),
1034+ progname , linkloc , strerror (errno ));
1035+ disconnect_and_exit (1 );
1036+ }
1037+ }
1038+ }
1039+
1040+
9021041/*
9031042 * Receive a tar format stream from the connection to the server, and unpack
9041043 * the contents of it into a directory. Only files, directories and
9051044 * symlinks are supported, no other kinds of special files.
9061045 *
9071046 * If the data is for the main data directory, it will be restored in the
9081047 * specified directory. If it's for another tablespace, it will be restored
909- * in the original directory, since relocation of tablespaces is not
910- * supported.
1048+ * in the original or mapped directory.
9111049 */
9121050static void
9131051ReceiveAndUnpackTarFile (PGconn * conn , PGresult * res , int rownum )
@@ -923,7 +1061,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
9231061 if (basetablespace )
9241062 strlcpy (current_path , basedir , sizeof (current_path ));
9251063 else
926- strlcpy (current_path , PQgetvalue (res , rownum , 1 ), sizeof (current_path ));
1064+ strlcpy (current_path , get_tablespace_mapping ( PQgetvalue (res , rownum , 1 ) ), sizeof (current_path ));
9271065
9281066 /*
9291067 * Get the COPY data
@@ -1503,7 +1641,10 @@ BaseBackup(void)
15031641 * we do anything anyway.
15041642 */
15051643 if (format == 'p' && !PQgetisnull (res , i , 1 ))
1506- verify_dir_is_empty_or_create (PQgetvalue (res , i , 1 ));
1644+ {
1645+ char * path = (char * ) get_tablespace_mapping (PQgetvalue (res , i , 1 ));
1646+ verify_dir_is_empty_or_create (path );
1647+ }
15071648 }
15081649
15091650 /*
@@ -1545,6 +1686,17 @@ BaseBackup(void)
15451686 progress_report (PQntuples (res ), NULL , true);
15461687 fprintf (stderr , "\n" ); /* Need to move to next line */
15471688 }
1689+
1690+ if (format == 'p' && tablespace_dirs .head != NULL )
1691+ {
1692+ for (i = 0 ; i < PQntuples (res ); i ++ )
1693+ {
1694+ Oid tblspc_oid = atooid (PQgetvalue (res , i , 0 ));
1695+ if (tblspc_oid )
1696+ update_tablespace_symlink (tblspc_oid , PQgetvalue (res , i , 1 ));
1697+ }
1698+ }
1699+
15481700 PQclear (res );
15491701
15501702 /*
@@ -1696,6 +1848,7 @@ main(int argc, char **argv)
16961848 {"format" , required_argument , NULL , 'F' },
16971849 {"checkpoint" , required_argument , NULL , 'c' },
16981850 {"write-recovery-conf" , no_argument , NULL , 'R' },
1851+ {"tablespace-mapping" , required_argument , NULL , 'T' },
16991852 {"xlog" , no_argument , NULL , 'x' },
17001853 {"xlog-method" , required_argument , NULL , 'X' },
17011854 {"gzip" , no_argument , NULL , 'z' },
@@ -1735,7 +1888,7 @@ main(int argc, char **argv)
17351888 }
17361889 }
17371890
1738- while ((c = getopt_long (argc , argv , "D:F:RxX :l:zZ:d:c:h:p:U:s:wWvP" ,
1891+ while ((c = getopt_long (argc , argv , "D:F:RT:xX :l:zZ:d:c:h:p:U:s:wWvP" ,
17391892 long_options , & option_index )) != -1 )
17401893 {
17411894 switch (c )
@@ -1759,6 +1912,9 @@ main(int argc, char **argv)
17591912 case 'R' :
17601913 writerecoveryconf = true;
17611914 break ;
1915+ case 'T' :
1916+ tablespace_list_append (optarg );
1917+ break ;
17621918 case 'x' :
17631919 if (includewal )
17641920 {
0 commit comments