summaryrefslogtreecommitdiff
path: root/src/bin/pg_basebackup/pg_basebackup.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_basebackup/pg_basebackup.c')
-rw-r--r--src/bin/pg_basebackup/pg_basebackup.c208
1 files changed, 166 insertions, 42 deletions
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 2a58be638a5..ec3b4f3c174 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -115,7 +115,7 @@ typedef enum
static char *basedir = NULL;
static TablespaceList tablespace_dirs = {NULL, NULL};
static char *xlog_dir = NULL;
-static char format = 'p'; /* p(lain)/t(ar) */
+static char format = '\0'; /* p(lain)/t(ar) */
static char *label = "pg_basebackup base backup";
static bool noclean = false;
static bool checksum_failure = false;
@@ -132,6 +132,7 @@ static pg_time_t last_progress_report = 0;
static int32 maxrate = 0; /* no limit by default */
static char *replication_slot = NULL;
static bool temp_replication_slot = true;
+static char *backup_target = NULL;
static bool create_slot = false;
static bool no_slot = false;
static bool verify_checksums = true;
@@ -364,6 +365,8 @@ usage(void)
printf(_("Usage:\n"));
printf(_(" %s [OPTION]...\n"), progname);
printf(_("\nOptions controlling the output:\n"));
+ printf(_(" -t, --target=TARGET[:DETAIL]\n"
+ " backup target (if other than client)\n"));
printf(_(" -D, --pgdata=DIRECTORY receive base backup into directory\n"));
printf(_(" -F, --format=p|t output format (plain (default), tar)\n"));
printf(_(" -r, --max-rate=RATE maximum transfer rate to transfer data directory\n"
@@ -1232,15 +1235,22 @@ ReceiveArchiveStreamChunk(size_t r, char *copybuf, void *callback_data)
}
/*
- * Create an appropriate backup streamer. We know that
- * recovery GUCs are supported, because this protocol can only
- * be used on v15+.
+ * Create an appropriate backup streamer, unless a backup
+ * target was specified. In that case, it's up to the server
+ * to put the backup wherever it needs to go.
*/
- state->streamer =
- CreateBackupStreamer(archive_name,
- spclocation,
- &state->manifest_inject_streamer,
- true, false);
+ if (backup_target == NULL)
+ {
+ /*
+ * We know that recovery GUCs are supported, because this
+ * protocol can only be used on v15+.
+ */
+ state->streamer =
+ CreateBackupStreamer(archive_name,
+ spclocation,
+ &state->manifest_inject_streamer,
+ true, false);
+ }
break;
}
@@ -1312,24 +1322,32 @@ ReceiveArchiveStreamChunk(size_t r, char *copybuf, void *callback_data)
GetCopyDataEnd(r, copybuf, cursor);
/*
- * If we're supposed inject the manifest into the archive, we
- * prepare to buffer it in memory; otherwise, we prepare to
- * write it to a temporary file.
+ * If a backup target was specified, figuring out where to put
+ * the manifest is the server's problem. Otherwise, we need to
+ * deal with it.
*/
- if (state->manifest_inject_streamer != NULL)
- state->manifest_buffer = createPQExpBuffer();
- else
+ if (backup_target == NULL)
{
- snprintf(state->manifest_filename,
- sizeof(state->manifest_filename),
- "%s/backup_manifest.tmp", basedir);
- state->manifest_file =
- fopen(state->manifest_filename, "wb");
- if (state->manifest_file == NULL)
+ /*
+ * If we're supposed inject the manifest into the archive,
+ * we prepare to buffer it in memory; otherwise, we
+ * prepare to write it to a temporary file.
+ */
+ if (state->manifest_inject_streamer != NULL)
+ state->manifest_buffer = createPQExpBuffer();
+ else
{
- pg_log_error("could not create file \"%s\": %m",
- state->manifest_filename);
- exit(1);
+ snprintf(state->manifest_filename,
+ sizeof(state->manifest_filename),
+ "%s/backup_manifest.tmp", basedir);
+ state->manifest_file =
+ fopen(state->manifest_filename, "wb");
+ if (state->manifest_file == NULL)
+ {
+ pg_log_error("could not create file \"%s\": %m",
+ state->manifest_filename);
+ exit(1);
+ }
}
}
break;
@@ -1698,13 +1716,41 @@ BaseBackup(void)
if (manifest)
{
AppendStringCommandOption(&buf, use_new_option_syntax, "MANIFEST",
- manifest_force_encode ? "force-encode" : "yes");
+ manifest_force_encode ? "force-encode" : "yes");
if (manifest_checksums != NULL)
AppendStringCommandOption(&buf, use_new_option_syntax,
- "MANIFEST_CHECKSUMS", manifest_checksums);
+ "MANIFEST_CHECKSUMS", manifest_checksums);
}
- if (serverMajor >= 1500)
+ if (backup_target != NULL)
+ {
+ char *colon;
+
+ if (serverMajor < 1500)
+ {
+ pg_log_error("backup targets are not supported by this server version");
+ exit(1);
+ }
+
+ AppendPlainCommandOption(&buf, use_new_option_syntax, "TABLESPACE_MAP");
+
+ if ((colon = strchr(backup_target, ':')) == NULL)
+ {
+ AppendStringCommandOption(&buf, use_new_option_syntax,
+ "TARGET", backup_target);
+ }
+ else
+ {
+ char *target;
+
+ target = pnstrdup(backup_target, colon - backup_target);
+ AppendStringCommandOption(&buf, use_new_option_syntax,
+ "TARGET", target);
+ AppendStringCommandOption(&buf, use_new_option_syntax,
+ "TARGET_DETAIL", colon + 1);
+ }
+ }
+ else if (serverMajor >= 1500)
AppendStringCommandOption(&buf, use_new_option_syntax,
"TARGET", "client");
@@ -1799,8 +1845,13 @@ BaseBackup(void)
* Verify tablespace directories are empty. Don't bother with the
* first once since it can be relocated, and it will be checked before
* we do anything anyway.
+ *
+ * Note that this is skipped for tar format backups and backups that
+ * the server is storing to a target location, since in that case
+ * we won't be storing anything into these directories and thus should
+ * not create them.
*/
- if (format == 'p' && !PQgetisnull(res, i, 1))
+ if (backup_target == NULL && format == 'p' && !PQgetisnull(res, i, 1))
{
char *path = unconstify(char *, get_tablespace_mapping(PQgetvalue(res, i, 1)));
@@ -1811,7 +1862,8 @@ BaseBackup(void)
/*
* When writing to stdout, require a single tablespace
*/
- writing_to_stdout = format == 't' && strcmp(basedir, "-") == 0;
+ writing_to_stdout = format == 't' && basedir != NULL &&
+ strcmp(basedir, "-") == 0;
if (writing_to_stdout && PQntuples(res) > 1)
{
pg_log_error("can only write single tablespace to stdout, database has %d",
@@ -1894,7 +1946,7 @@ BaseBackup(void)
res = PQgetResult(conn);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
- pg_log_error("could not get write-ahead log end position from server: %s",
+ pg_log_error("backup failed: %s",
PQerrorMessage(conn));
exit(1);
}
@@ -2028,8 +2080,11 @@ BaseBackup(void)
* synced after being completed. In plain format, all the data of the
* base directory is synced, taking into account all the tablespaces.
* Errors are not considered fatal.
+ *
+ * If, however, there's a backup target, we're not writing anything
+ * locally, so in that case we skip this step.
*/
- if (do_sync)
+ if (do_sync && backup_target == NULL)
{
if (verbose)
pg_log_info("syncing data to disk ...");
@@ -2051,7 +2106,7 @@ BaseBackup(void)
* without a backup_manifest file, decreasing the chances that a directory
* we leave behind will be mistaken for a valid backup.
*/
- if (!writing_to_stdout && manifest)
+ if (!writing_to_stdout && manifest && backup_target == NULL)
{
char tmp_filename[MAXPGPATH];
char filename[MAXPGPATH];
@@ -2085,6 +2140,7 @@ main(int argc, char **argv)
{"max-rate", required_argument, NULL, 'r'},
{"write-recovery-conf", no_argument, NULL, 'R'},
{"slot", required_argument, NULL, 'S'},
+ {"target", required_argument, NULL, 't'},
{"tablespace-mapping", required_argument, NULL, 'T'},
{"wal-method", required_argument, NULL, 'X'},
{"gzip", no_argument, NULL, 'z'},
@@ -2135,7 +2191,7 @@ main(int argc, char **argv)
atexit(cleanup_directories_atexit);
- while ((c = getopt_long(argc, argv, "CD:F:r:RS:T:X:l:nNzZ:d:c:h:p:U:s:wWkvP",
+ while ((c = getopt_long(argc, argv, "CD:F:r:RS:t:T:X:l:nNzZ:d:c:h:p:U:s:wWkvP",
long_options, &option_index)) != -1)
{
switch (c)
@@ -2176,6 +2232,9 @@ main(int argc, char **argv)
case 2:
no_slot = true;
break;
+ case 't':
+ backup_target = pg_strdup(optarg);
+ break;
case 'T':
tablespace_list_append(optarg);
break;
@@ -2308,27 +2367,72 @@ main(int argc, char **argv)
}
/*
- * Required arguments
+ * Setting the backup target to 'client' is equivalent to leaving out the
+ * option. This logic allows us to assume elsewhere that the backup is
+ * being stored locally if and only if backup_target == NULL.
+ */
+ if (backup_target != NULL && strcmp(backup_target, "client") == 0)
+ {
+ pg_free(backup_target);
+ backup_target = NULL;
+ }
+
+ /*
+ * Can't use --format with --target. Without --target, default format is
+ * tar.
+ */
+ if (backup_target != NULL && format != '\0')
+ {
+ pg_log_error("cannot specify both format and backup target");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ if (format == '\0')
+ format = 'p';
+
+ /*
+ * Either directory or backup target should be specified, but not both
*/
- if (basedir == NULL)
+ if (basedir == NULL && backup_target == NULL)
{
- pg_log_error("no target directory specified");
+ pg_log_error("must specify output directory or backup target");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ if (basedir != NULL && backup_target != NULL)
+ {
+ pg_log_error("cannot specify both output directory and backup target");
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
progname);
exit(1);
}
/*
- * Mutually exclusive arguments
+ * Compression doesn't make sense unless tar format is in use.
*/
if (format == 'p' && compresslevel != 0)
{
- pg_log_error("only tar mode backups can be compressed");
+ if (backup_target == NULL)
+ pg_log_error("only tar mode backups can be compressed");
+ else
+ pg_log_error("client-side compression is not possible when a backup target is specfied");
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
progname);
exit(1);
}
+ /*
+ * Sanity checks for WAL method.
+ */
+ if (backup_target != NULL && includewal == STREAM_WAL)
+ {
+ pg_log_error("WAL cannot be streamed when a backup target is specified");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
if (format == 't' && includewal == STREAM_WAL && strcmp(basedir, "-") == 0)
{
pg_log_error("cannot stream write-ahead logs in tar mode to stdout");
@@ -2345,6 +2449,9 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Sanity checks for replication slot options.
+ */
if (no_slot)
{
if (replication_slot)
@@ -2378,8 +2485,18 @@ main(int argc, char **argv)
}
}
+ /*
+ * Sanity checks on WAL directory.
+ */
if (xlog_dir)
{
+ if (backup_target != NULL)
+ {
+ pg_log_error("WAL directory location cannot be specified along with a backup target");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
if (format != 'p')
{
pg_log_error("WAL directory location can only be specified in plain mode");
@@ -2400,6 +2517,7 @@ main(int argc, char **argv)
}
#ifndef HAVE_LIBZ
+ /* Sanity checks for compression level. */
if (compresslevel != 0)
{
pg_log_error("this build does not support compression");
@@ -2407,6 +2525,9 @@ main(int argc, char **argv)
}
#endif
+ /*
+ * Sanity checks for progress reporting options.
+ */
if (showprogress && !estimatesize)
{
pg_log_error("%s and %s are incompatible options",
@@ -2416,6 +2537,9 @@ main(int argc, char **argv)
exit(1);
}
+ /*
+ * Sanity checks for backup manifest options.
+ */
if (!manifest && manifest_checksums != NULL)
{
pg_log_error("%s and %s are incompatible options",
@@ -2458,11 +2582,11 @@ main(int argc, char **argv)
manifest = false;
/*
- * Verify that the target directory exists, or create it. For plaintext
- * backups, always require the directory. For tar backups, require it
- * unless we are writing to stdout.
+ * If an output directory was specified, verify that it exists, or create
+ * it. Note that for a tar backup, an output directory of "-" means we are
+ * writing to stdout, so do nothing in that case.
*/
- if (format == 'p' || strcmp(basedir, "-") != 0)
+ if (basedir != NULL && (format == 'p' || strcmp(basedir, "-") != 0))
verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
/* determine remote server's xlog segment size */