diff --git a/src/dir.c b/src/dir.c index b636e00f..6bd0c682 100644 --- a/src/dir.c +++ b/src/dir.c @@ -124,10 +124,13 @@ static void dir_list_file_internal(parray *files, const char *root, static void list_data_directories(parray *files, const char *path, bool is_root, bool exclude); +static void free_extra_remap_list(void *cell); /* Tablespace mapping */ static TablespaceList tablespace_dirs = {NULL, NULL}; static TablespaceCreatedList tablespace_created_dirs = {NULL, NULL}; +/* Extra directories mapping */ +static parray *extra_remap_list = NULL; /* * Create directory, also create parent directories if necessary. @@ -949,6 +952,57 @@ opt_tablespace_map(ConfigOption *opt, const char *arg) tablespace_dirs.tail = cell; } +void +opt_extradir_map(ConfigOption *opt, const char *arg) +{ + TablespaceListCell *cell = pgut_new(TablespaceListCell); + char *dst; + char *dst_ptr; + const char *arg_ptr; + + extra_remap_list = parray_new(); + dst_ptr = dst = cell->old_dir; + for (arg_ptr = arg; *arg_ptr; arg_ptr++) + { + if (dst_ptr - dst >= MAXPGPATH) + elog(ERROR, "directory name too long"); + + if (*arg_ptr == '\\' && *(arg_ptr + 1) == '=') + ; /* skip backslash escaping = */ + else if (*arg_ptr == '=' && (arg_ptr == arg || *(arg_ptr - 1) != '\\')) + { + if (*cell->new_dir) + elog(ERROR, "multiple \"=\" signs in extra directory mapping\n"); + else + dst = dst_ptr = cell->new_dir; + } + else + *dst_ptr++ = *arg_ptr; + } + + if (!*cell->old_dir || !*cell->new_dir) + elog(ERROR, "invalid extra directory mapping format \"%s\", " + "must be \"OLDDIR=NEWDIR\"", arg); + + /* + * This check isn't absolutely necessary. But all tablespaces are created + * with absolute directories, so specifying a non-absolute path here would + * just never match, possibly confusing users. It's also good to be + * consistent with the new_dir check. + */ + if (!is_absolute_path(cell->old_dir)) + elog(ERROR, "old directory is not an absolute path " + "in extra directory mapping: %s\n", + cell->old_dir); + + if (!is_absolute_path(cell->new_dir)) + elog(ERROR, "new directory is not an absolute path " + "in extra directory mapping: %s\n", + cell->new_dir); + + parray_append(extra_remap_list, cell); +} + /* * Create backup directories from **backup_dir** to **data_dir**. Doesn't raise * an error if target directories exist. @@ -1219,6 +1273,43 @@ check_tablespace_mapping(pgBackup *backup) parray_free(links); } +char * +check_extra_dir_mapping(char *current_dir) +{ + if (!extra_remap_list) + return current_dir; + + for (int i = 0; i < parray_num(extra_remap_list); i++) + { + TablespaceListCell *cell = parray_get(extra_remap_list, i); + char *old_dir = cell->old_dir; + + if (strcmp(old_dir, current_dir) == 0) + return cell->new_dir; + } + return current_dir; +} + +static void +free_extra_remap_list(void *cell) +{ + TablespaceListCell *cell_ptr; + if (cell == NULL) + return; + cell_ptr = (TablespaceListCell *)cell; + pfree(cell_ptr); +} + +void +clean_extra_dirs_remap_list(void) +{ + if (extra_remap_list) + { + parray_walk(extra_remap_list, free_extra_remap_list); + parray_free(extra_remap_list); + } +} + /* * Print backup content list. */ diff --git a/src/pg_probackup.c b/src/pg_probackup.c index 7a2c6216..5fea590a 100644 --- a/src/pg_probackup.c +++ b/src/pg_probackup.c @@ -137,7 +137,8 @@ static ConfigOption cmd_options[] = { 's', 'S', "slot", &replication_slot, SOURCE_CMD_STRICT }, { 'b', 134, "delete-wal", &delete_wal, SOURCE_CMD_STRICT }, { 'b', 135, "delete-expired", &delete_expired, SOURCE_CMD_STRICT }, - { 's', 155, "extra-directory", &extradir, SOURCE_CMD_STRICT }, + { 's', 155, "extra-directory", &extradir, SOURCE_CMD_STRICT }, + { 'f', 'E', "extra-mapping", opt_extradir_map, SOURCE_CMD_STRICT }, /* TODO not completed feature. Make it unavailiable from user level { 'b', 18, "remote", &is_remote_backup, SOURCE_CMD_STRICT, }, */ /* restore options */ diff --git a/src/pg_probackup.h b/src/pg_probackup.h index e472ffac..670d852d 100644 --- a/src/pg_probackup.h +++ b/src/pg_probackup.h @@ -509,7 +509,10 @@ extern void create_data_directories(const char *data_dir, extern void read_tablespace_map(parray *files, const char *backup_dir); extern void opt_tablespace_map(ConfigOption *opt, const char *arg); +extern void opt_extradir_map(ConfigOption *opt, const char *arg); extern void check_tablespace_mapping(pgBackup *backup); +extern char* check_extra_dir_mapping(char *current_dir); +extern void clean_extra_dirs_remap_list(void); extern void print_file_list(FILE *out, const parray *files, const char *root, const char *extra_prefix, parray *extra_list); diff --git a/src/restore.c b/src/restore.c index 8d2b7c0c..43133ff4 100644 --- a/src/restore.c +++ b/src/restore.c @@ -463,7 +463,9 @@ restore_backup(pgBackup *backup, const char *extra_dir_str) requested_extra_dirs = make_extra_directory_list(extra_dir_str); for (int i = 0; i < parray_num(requested_extra_dirs); i++) { - dir_create_dir(parray_get(requested_extra_dirs, i), DIR_PERMISSION); + char *extra_path = parray_get(requested_extra_dirs, i); + extra_path = check_extra_dir_mapping(extra_path); + dir_create_dir(extra_path, DIR_PERMISSION); } } @@ -504,6 +506,8 @@ restore_backup(pgBackup *backup, const char *extra_dir_str) if (backup_contains_extra(extra_path, requested_extra_dirs)) { char container_dir[MAXPGPATH]; + + extra_path = check_extra_dir_mapping(extra_path); makeExtraDirPathByNum(container_dir, extra_prefix, file->extra_dir_num); dir_name = GetRelativePath(file->path, container_dir); @@ -554,6 +558,8 @@ restore_backup(pgBackup *backup, const char *extra_dir_str) parray_walk(files, pgFileFree); parray_free(files); + clean_extra_dirs_remap_list(); + if (logger_config.log_level_console <= LOG || logger_config.log_level_file <= LOG) elog(LOG, "restore %s backup completed", base36enc(backup->start_time)); @@ -693,7 +699,10 @@ restore_files(void *arg) char *extra_path = parray_get(arguments->cur_extra_dirs, file->extra_dir_num - 1); if (backup_contains_extra(extra_path, arguments->req_extra_dirs)) + { + extra_path = check_extra_dir_mapping(extra_path); copy_file(arguments->extra_prefix, extra_path, file); + } } else copy_file(from_root, instance_config.pgdata, file);