commit 8d649148205cf16cee008b4a118654af86b67a0a 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. diff --git doc/src/sgml/ref/pg_basebackup.sgml doc/src/sgml/ref/pg_basebackup.sgml index c379df5..cad57ba 100644 --- doc/src/sgml/ref/pg_basebackup.sgml +++ doc/src/sgml/ref/pg_basebackup.sgml @@ -138,6 +138,18 @@ PostgreSQL documentation + + + + + Specifies the location where tablespace with oid + is written, tablespacedir must be an absolute path. + This options can be specified multiple times for multiple tablespaces. + + + + + @@ -530,7 +542,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 +582,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 + archive is written to ./backup/archive + +$ pg_basebackup -D $(pwd)/backup/data -T archive:$(pwd)/backup/archive + + diff --git src/bin/pg_basebackup/pg_basebackup.c src/bin/pg_basebackup/pg_basebackup.c index 9d13d57..498ba78 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; + char *new_dir; +} 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,37 @@ 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 *new_dir); +static bool tablespace_list_append(TablespaceList *list, char *tablespace); + +static bool +tablespace_list_append(TablespaceList *list, char *tablespace) +{ + TablespaceListCell *cell; + char *token; + + if (!(token = strtok(tablespace, ":"))) + return false; + + cell = (TablespaceListCell *) pg_malloc(sizeof(TablespaceListCell)); + cell->next = NULL; + cell->old_dir = pg_strdup(token); + + if (!(token = strtok(NULL, ":")) || token[0] != '/') + return false; + cell->new_dir = pg_strdup(token); + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; + + return true; +} + + #ifdef HAVE_LIBZ static const char * get_gz_error(gzFile gzf) @@ -110,6 +157,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 +910,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 +975,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 +1556,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 +1601,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 +1765,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 +1808,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 +1816,15 @@ main(int argc, char **argv) case 'D': basedir = pg_strdup(optarg); break; + case 'T': + if (!tablespace_list_append(&tablespace_dirs, 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';