*** doc/src/sgml/ref/pg_basebackup.sgml --- doc/src/sgml/ref/pg_basebackup.sgml *************** *** 138,143 **** PostgreSQL documentation --- 138,155 ---- + + + + + 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,536 **** 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. --- 542,548 ---- The way PostgreSQL manages tablespaces, the path for all additional tablespaces must be identical whenever a backup is ! restored, if --tablespace isn't specified. *************** *** 570,575 **** PostgreSQL documentation --- 582,595 ---- (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 + + *** src/bin/pg_basebackup/pg_basebackup.c --- src/bin/pg_basebackup/pg_basebackup.c *************** *** 33,40 **** --- 33,54 ---- #include "streamutil.h" + #define atooid(x) ((Oid) strtoul((x), NULL, 10)) + + typedef struct TablespaceListCell { + struct TablespaceListCell *next; + Oid oid; + char path[1]; + } TablespaceListCell; + + typedef struct TablespaceList { + TablespaceListCell *head; + TablespaceListCell *tail; + } TablespaceList; + /* Global options */ static char *basedir = NULL; + static TablespaceList tablespace_locations = {NULL, NULL}; static char *xlog_dir = ""; static char format = 'p'; /* p(lain)/t(ar) */ static char *label = "pg_basebackup base backup"; *************** *** 86,91 **** static void BaseBackup(void); --- 100,140 ---- static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline, bool segment_finished); + static const char *get_tablespace_location(Oid oid, const char *location); + static void update_tablespace_symlink(Oid oid, const char *location); + static bool tablespace_list_append(TablespaceList *list, const char *tablespace); + + static bool + tablespace_list_append(TablespaceList *list, const char *tablespace) + { + TablespaceListCell *cell; + char *path; + Oid oid = atooid(tablespace); + + if (!oid) + return false; + + path = strstr(tablespace, ":/"); + if (!path) + return false; + path++; + + cell = (TablespaceListCell *) pg_malloc(sizeof(TablespaceListCell) + strlen(path)); + + cell->next = NULL; + cell->oid = oid; + strcpy(cell->path, path); + + 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,115 **** usage(void) --- 159,166 ---- printf(_(" %s [OPTION]...\n"), progname); printf(_("\nOptions controlling the output:\n")); printf(_(" -D, --pgdata=DIRECTORY receive base backup into directory\n")); + printf(_(" -T, --tablespace=OID:LOCATION\n" + " Specify tablespace oid and its absolute location\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,874 **** ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) } /* * 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. */ static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) --- 912,967 ---- } /* + * Retrieve tablespace location, either relocated or original depending on + * whether -T oid:tablespacedir was passed or not. + */ + static const char * + get_tablespace_location(Oid oid, const char *location) + { + TablespaceListCell *cell; + + for (cell = tablespace_locations.head; cell; cell = cell->next) + if (oid == cell->oid) + return cell->path; + + return location; + } + + /* + * 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 *location) + { + const char *new_location = get_tablespace_location(oid, location); + if (strcmp(new_location, location) != 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_location, 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, if tablespace relocation is not enabled. */ static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) *************** *** 884,890 **** ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum) if (basetablespace) strcpy(current_path, basedir); else ! strcpy(current_path, PQgetvalue(res, rownum, 1)); /* * Get the COPY data --- 977,987 ---- if (basetablespace) strcpy(current_path, basedir); else ! { ! strcpy(current_path, ! get_tablespace_location(atooid(PQgetvalue(res, rownum, 0)), ! PQgetvalue(res, rownum, 1))); ! } /* * Get the COPY data *************** *** 1465,1471 **** BaseBackup(void) * we do anything anyway. */ if (format == 'p' && !PQgetisnull(res, i, 1)) ! verify_dir_is_empty_or_create(PQgetvalue(res, i, 1)); } /* --- 1562,1572 ---- * we do anything anyway. */ if (format == 'p' && !PQgetisnull(res, i, 1)) ! { ! char *path = (char *) get_tablespace_location(atooid(PQgetvalue(res, i, 0)), ! PQgetvalue(res, i, 1)); ! verify_dir_is_empty_or_create(path); ! } } /* *************** *** 1507,1512 **** BaseBackup(void) --- 1608,1629 ---- progress_report(PQntuples(res), NULL); fprintf(stderr, "\n"); /* Need to move to next line */ } + + if (format == 'p' && tablespace_locations.head != NULL) + { + #ifdef HAVE_SYMLINK + for (i = 0; i < PQntuples(res); i++) + { + int 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,1660 **** main(int argc, char **argv) --- 1772,1778 ---- {"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,1703 **** main(int argc, char **argv) } } ! while ((c = getopt_long(argc, argv, "D:F:RxX:l:zZ:d:c:h:p:U:s:wWvP", long_options, &option_index)) != -1) { switch (c) --- 1815,1821 ---- } } ! 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,1710 **** main(int argc, char **argv) --- 1823,1837 ---- case 'D': basedir = pg_strdup(optarg); break; + case 'T': + if (!tablespace_list_append(&tablespace_locations, optarg)) + { + fprintf(stderr, + _("%s: invalid tablespace format \"%s\", must be \"oid:/absolute-path\"\n"), + progname, optarg); + exit(1); + } + break; case 'F': if (strcmp(optarg, "p") == 0 || strcmp(optarg, "plain") == 0) format = 'p';