commit 5bc533663dcad382ec65bef4a599a17272dfdd0d Author: Steeve Lennmark Date: Thu Jan 9 20:45:26 2014 +0000 Add support for relocating tablespaces This is done by mapping old and new tablespace location using the format old_dir:new_dir. This also accounts for directory names containing colons. diff --git doc/src/sgml/ref/pg_basebackup.sgml doc/src/sgml/ref/pg_basebackup.sgml index c379df5..b345154 100644 --- doc/src/sgml/ref/pg_basebackup.sgml +++ doc/src/sgml/ref/pg_basebackup.sgml @@ -138,6 +138,19 @@ PostgreSQL documentation + + + + + Relocates the tablespace in directory olddir + to newdir. newdir + has to be an absolute path. This options can be specified multiple times + for multiple tablespaces. + + + + + @@ -530,7 +543,7 @@ PostgreSQL documentation The way PostgreSQL manages tablespaces, the path for all additional tablespaces must be identical whenever a backup is - restored. The main data directory, however, is relocatable to any location. + restored, if --tablespace isn't specified. @@ -570,6 +583,14 @@ PostgreSQL documentation (This command will fail if there are multiple tablespaces in the database.) + + + To create a backup of a two-tablespace local database where tablespace + /opt/ts is relocated to ./backup/archive + +$ pg_basebackup -D $(pwd)/backup/data -T /opt/ts:$(pwd)/backup/archive + + diff --git src/bin/pg_basebackup/pg_basebackup.c src/bin/pg_basebackup/pg_basebackup.c index 9d13d57..cbc9f87 100644 --- src/bin/pg_basebackup/pg_basebackup.c +++ src/bin/pg_basebackup/pg_basebackup.c @@ -33,8 +33,24 @@ #include "streamutil.h" +#define atooid(x) ((Oid) strtoul((x), NULL, 10)) + +typedef struct TablespaceListCell +{ + struct TablespaceListCell *next; + char old_dir[MAXPGPATH]; + char new_dir[MAXPGPATH]; +} TablespaceListCell; + +typedef struct TablespaceList +{ + TablespaceListCell *head; + TablespaceListCell *tail; +} TablespaceList; + /* Global options */ static char *basedir = NULL; +static TablespaceList tablespace_dirs = {NULL, NULL}; static char *xlog_dir = ""; static char format = 'p'; /* p(lain)/t(ar) */ static char *label = "pg_basebackup base backup"; @@ -86,6 +102,54 @@ static void BaseBackup(void); static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline, bool segment_finished); +static const char *get_tablespace_dir(const char *dir); +static void update_tablespace_symlink(Oid oid, const char *old_dir); +static bool tablespace_list_append(char *arg); + +/* + * Split tablespace argument into old_dir and new_dir, this accounts for + * directory name containing a colon. + */ +static bool +tablespace_list_append(char *arg) +{ + TablespaceListCell *cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell)); + char *dst = cell->old_dir; + const char *dst_head = dst; + const char *arg_head = arg; + + cell->next = NULL; + + for (; *arg; arg++) + { + /* Check for overflow */ + if (dst - dst_head >= MAXPGPATH) + return false; + + /* Split on colon not trailing a slash */ + if (*arg == '\\' && *(arg + 1) == ':') + ; + else if (*arg != ':' || (arg != arg_head && *(arg - 1) == '\\')) + *dst++ = *arg; + else if (!*cell->old_dir || *cell->new_dir || *(arg + 1) != '/') + return false; + else + dst_head = dst = cell->new_dir; + } + + if (!(*cell->old_dir && *cell->new_dir)) + return false; + + if (tablespace_dirs.tail) + tablespace_dirs.tail->next = cell; + else + tablespace_dirs.head = cell; + tablespace_dirs.tail = cell; + + return true; +} + + #ifdef HAVE_LIBZ static const char * get_gz_error(gzFile gzf) @@ -110,6 +174,8 @@ usage(void) printf(_(" %s [OPTION]...\n"), progname); printf(_("\nOptions controlling the output:\n")); printf(_(" -D, --pgdata=DIRECTORY receive base backup into directory\n")); + printf(_(" -T, --tablespace=OLDDIR:NEWDIR\n" + " relocate tablespace olddir to newdir\n")); printf(_(" -F, --format=p|t output format (plain (default), tar)\n")); printf(_(" -R, --write-recovery-conf\n" " write recovery.conf after backup\n")); @@ -861,14 +927,56 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) } /* + * Retrieve tablespace path, either relocated or original depending on + * whether -T old_dir:new_dir was passed or not. + */ +static const char * +get_tablespace_dir(const char *dir) +{ + TablespaceListCell *cell; + + for (cell = tablespace_dirs.head; cell; cell = cell->next) + if (strcmp(dir, cell->old_dir) == 0) + return cell->new_dir; + + return dir; +} + +/* + * Update symlinks to reflect relocated tablespace, only applied if + * tablespace isn't in its original location. + */ +static void +update_tablespace_symlink(Oid oid, const char *old_dir) +{ + const char *new_dir = get_tablespace_dir(old_dir); + if (strcmp(old_dir, new_dir) != 0) + { + char linkloc[MAXPGPATH]; + snprintf(linkloc, sizeof(linkloc), "%s/pg_tblspc/%d", basedir, oid); + if (unlink(linkloc) < 0 && errno != ENOENT) + { + fprintf(stderr, _("%s: unable to remove \"%s\": %s"), + progname, linkloc, strerror(errno)); + disconnect_and_exit(1); + } + if (symlink(new_dir, linkloc) < 0) + { + fprintf(stderr, _("%s: unable to create symlink \"%s\": %s"), + progname, linkloc, strerror(errno)); + disconnect_and_exit(1); + } + } +} + +/* * Receive a tar format stream from the connection to the server, and unpack * the contents of it into a directory. Only files, directories and * symlinks are supported, no other kinds of special files. * * If the data is for the main data directory, it will be restored in the * specified directory. If it's for another tablespace, it will be restored - * in the original directory, since relocation of tablespaces is not - * supported. + * in the original directory, if tablespace relocation is not enabled. */ static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) @@ -884,7 +992,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) if (basetablespace) strcpy(current_path, basedir); else - strcpy(current_path, PQgetvalue(res, rownum, 1)); + strcpy(current_path, get_tablespace_dir(PQgetvalue(res, rownum, 1))); /* * Get the COPY data @@ -1465,7 +1573,10 @@ BaseBackup(void) * we do anything anyway. */ if (format == 'p' && !PQgetisnull(res, i, 1)) - verify_dir_is_empty_or_create(PQgetvalue(res, i, 1)); + { + char *path = (char *) get_tablespace_dir(PQgetvalue(res, i, 1)); + verify_dir_is_empty_or_create(path); + } } /* @@ -1507,6 +1618,22 @@ BaseBackup(void) progress_report(PQntuples(res), NULL); fprintf(stderr, "\n"); /* Need to move to next line */ } + + if (format == 'p' && tablespace_dirs.head != NULL) + { +#ifdef HAVE_SYMLINK + for (i = 0; i < PQntuples(res); i++) + { + Oid tblspc_oid = atooid(PQgetvalue(res, i, 0)); + if (tblspc_oid) + update_tablespace_symlink(tblspc_oid, PQgetvalue(res, i, 1)); + } +#else + fprintf(stderr, _("%s: not updating pg_tblspc with new tablespace relocation\n"), + progname); +#endif + } + PQclear(res); /* @@ -1655,6 +1782,7 @@ main(int argc, char **argv) {"help", no_argument, NULL, '?'}, {"version", no_argument, NULL, 'V'}, {"pgdata", required_argument, NULL, 'D'}, + {"tablespace", required_argument, NULL, 'T'}, {"format", required_argument, NULL, 'F'}, {"checkpoint", required_argument, NULL, 'c'}, {"write-recovery-conf", no_argument, NULL, 'R'}, @@ -1697,7 +1825,7 @@ main(int argc, char **argv) } } - while ((c = getopt_long(argc, argv, "D:F:RxX:l:zZ:d:c:h:p:U:s:wWvP", + while ((c = getopt_long(argc, argv, "D:T:F:RxX:l:zZ:d:c:h:p:U:s:wWvP", long_options, &option_index)) != -1) { switch (c) @@ -1705,6 +1833,15 @@ main(int argc, char **argv) case 'D': basedir = pg_strdup(optarg); break; + case 'T': + if (!tablespace_list_append(optarg)) + { + fprintf(stderr, + _("%s: invalid tablespace format \"%s\", must be \"old_dir:/absolute-path\"\n"), + progname, optarg); + exit(1); + } + break; case 'F': if (strcmp(optarg, "p") == 0 || strcmp(optarg, "plain") == 0) format = 'p';