- At present, the only supported value for this parameter is
- client .
+ If the target is client , the backup data is
+ sent to the client. If it is server , the backup
+ data is written to the server at the pathname specified by the
+ TARGET_DETAIL option. If it is
+ blackhole , the backup data is not sent
+ anywhere; it is simply discarded.
+
+
+
+
+
+ TARGET_DETAIL 'detail'
+
+ Provides additional information about the backup target.
+
+
+ Currently, this option can only be used when the backup target is
+ server . It specifies the server directory
+ to which the backup should be written.
+
+ -t target
+ --target=target
+
+
+ Instructs the server where to place the base backup. The default target
+ is client , which specifies that the backup should
+ be sent to the machine where
pg_basebackup
+ is running. If the target is instead set to
+ server:/some/path , the backup will be stored on
+ the machine where the server is running in the
+ /some/path directory. Storing a backup on the
+ server requires superuser privileges. If the target is set to
+ blackhole , the contents are discarded and not
+ stored anywhere. This should only be used for testing purposes, as you
+ will not end up with an actual backup.
+
+
+ Since WAL streaming is implemented by
+
pg_basebackup rather than by the server,
+ this option cannot be used together with -Xstream .
+ Since that is the default, when this option is specified, you must also
+ specify either -Xfetch or -Xnone .
+
+
+
+
+
-T olddir =newdir
--tablespace-mapping=olddir =newdir
basebackup.o \
basebackup_copy.o \
basebackup_progress.o \
+ basebackup_server.o \
basebackup_sink.o \
basebackup_throttle.o \
repl_gram.o \
typedef enum
{
+ BACKUP_TARGET_BLACKHOLE,
BACKUP_TARGET_COMPAT,
- BACKUP_TARGET_CLIENT
+ BACKUP_TARGET_CLIENT,
+ BACKUP_TARGET_SERVER
} backup_target_type;
typedef struct
uint32 maxrate;
bool sendtblspcmapfile;
backup_target_type target;
+ char *target_detail;
backup_manifest_option manifest;
pg_checksum_type manifest_checksum_type;
} basebackup_options;
bool o_manifest = false;
bool o_manifest_checksums = false;
bool o_target = false;
+ bool o_target_detail = false;
+ char *target_str = "compat"; /* placate compiler */
MemSet(opt, 0, sizeof(*opt));
opt->target = BACKUP_TARGET_COMPAT;
}
else if (strcmp(defel->defname, "target") == 0)
{
- char *optval = defGetString(defel);
+ target_str = defGetString(defel);
if (o_target)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("duplicate option \"%s\"", defel->defname)));
- if (strcmp(optval, "client") == 0)
+ if (strcmp(target_str, "blackhole") == 0)
+ opt->target = BACKUP_TARGET_BLACKHOLE;
+ else if (strcmp(target_str, "client") == 0)
opt->target = BACKUP_TARGET_CLIENT;
+ else if (strcmp(target_str, "server") == 0)
+ opt->target = BACKUP_TARGET_SERVER;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("unrecognized target: \"%s\"", optval )));
+ errmsg("unrecognized target: \"%s\"", target_str )));
o_target = true;
}
- else
- ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("option \"%s\" not recognized",
- defel->defname));
+ else if (strcmp(defel->defname, "target_detail") == 0)
+ {
+ char *optval = defGetString(defel);
+
+ if (o_target_detail)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("duplicate option \"%s\"", defel->defname)));
+ opt->target_detail = optval;
+ o_target_detail = true;
+ }
}
if (opt->label == NULL)
opt->label = "base backup";
errmsg("manifest checksums require a backup manifest")));
opt->manifest_checksum_type = CHECKSUM_TYPE_NONE;
}
+ if (opt->target == BACKUP_TARGET_SERVER)
+ {
+ if (opt->target_detail == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("target '%s' requires a target detail",
+ target_str)));
+ }
+ else
+ {
+ if (opt->target_detail != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("target '%s' does not accept a target detail",
+ target_str)));
+ }
}
/*
* If the TARGET option was specified, then we can use the new copy-stream
- * protocol. If not, we must fall back to the old and less capable
- * copy-tablespace protocol.
+ * protocol. If the target is specifically 'client' then set up to stream
+ * the backup to the client; otherwise, it's being sent someplace else and
+ * should not be sent to the client.
+ *
+ * If the TARGET option was not specified, we must fall back to the older
+ * and less capable copy-tablespace protocol.
*/
- if (opt.target != BACKUP_TARGET_COMPAT)
- sink = bbsink_copystream_new();
+ if (opt.target == BACKUP_TARGET_CLIENT)
+ sink = bbsink_copystream_new(true);
+ else if (opt.target != BACKUP_TARGET_COMPAT)
+ sink = bbsink_copystream_new(false);
else
sink = bbsink_copytblspc_new();
+ /*
+ * If a non-default backup target is in use, arrange to send the data
+ * wherever it needs to go.
+ */
+ switch (opt.target)
+ {
+ case BACKUP_TARGET_BLACKHOLE:
+ /* Nothing to do, just discard data. */
+ break;
+ case BACKUP_TARGET_COMPAT:
+ case BACKUP_TARGET_CLIENT:
+ /* Nothing to do, handling above is sufficient. */
+ break;
+ case BACKUP_TARGET_SERVER:
+ sink = bbsink_server_new(sink, opt.target_detail);
+ break;
+ }
+
/* Set up network throttling, if client requested it */
if (opt.maxrate > 0)
sink = bbsink_throttle_new(sink, opt.maxrate);
/* Common information for all types of sink. */
bbsink base;
+ /* Are we sending the archives to the client, or somewhere else? */
+ bool send_to_client;
+
/*
* Protocol message buffer. We assemble CopyData protocol messages by
* setting the first character of this buffer to 'd' (archive or manifest
* Create a new 'copystream' bbsink.
*/
bbsink *
-bbsink_copystream_new(void )
+bbsink_copystream_new(bool send_to_client )
{
bbsink_copystream *sink = palloc0(sizeof(bbsink_copystream));
*((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_copystream_ops;
+ sink->send_to_client = send_to_client;
/* Set up for periodic progress reporting. */
sink->last_progress_report_time = GetCurrentTimestamp();
StringInfoData buf;
uint64 targetbytes;
- /* Send the archive content to the client (with leading type byte). */
- pq_putmessage('d', mysink->msgbuffer, len + 1);
+ /* Send the archive content to the client, if appropriate. */
+ if (mysink->send_to_client)
+ {
+ /* Add one because we're also sending a leading type byte. */
+ pq_putmessage('d', mysink->msgbuffer, len + 1);
+ }
/* Consider whether to send a progress report to the client. */
targetbytes = mysink->bytes_done_at_last_time_check
{
bbsink_copystream *mysink = (bbsink_copystream *) sink;
- /* Send the manifest content to the client (with leading type byte). */
- pq_putmessage('d', mysink->msgbuffer, len + 1);
+ if (mysink->send_to_client)
+ {
+ /* Add one because we're also sending a leading type byte. */
+ pq_putmessage('d', mysink->msgbuffer, len + 1);
+ }
}
/*
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * basebackup_server.c
+ * store basebackup archives on the server
+ *
+ * IDENTIFICATION
+ * src/backend/replication/basebackup_server.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "replication/basebackup.h"
+#include "replication/basebackup_sink.h"
+#include "storage/fd.h"
+#include "utils/timestamp.h"
+#include "utils/wait_event.h"
+
+typedef struct bbsink_server
+{
+ /* Common information for all types of sink. */
+ bbsink base;
+
+ /* Directory in which backup is to be stored. */
+ char *pathname;
+
+ /* Currently open file (or 0 if nothing open). */
+ File file;
+
+ /* Current file position. */
+ off_t filepos;
+} bbsink_server;
+
+static void bbsink_server_begin_archive(bbsink *sink,
+ const char *archive_name);
+static void bbsink_server_archive_contents(bbsink *sink, size_t len);
+static void bbsink_server_end_archive(bbsink *sink);
+static void bbsink_server_begin_manifest(bbsink *sink);
+static void bbsink_server_manifest_contents(bbsink *sink, size_t len);
+static void bbsink_server_end_manifest(bbsink *sink);
+
+const bbsink_ops bbsink_server_ops = {
+ .begin_backup = bbsink_forward_begin_backup,
+ .begin_archive = bbsink_server_begin_archive,
+ .archive_contents = bbsink_server_archive_contents,
+ .end_archive = bbsink_server_end_archive,
+ .begin_manifest = bbsink_server_begin_manifest,
+ .manifest_contents = bbsink_server_manifest_contents,
+ .end_manifest = bbsink_server_end_manifest,
+ .end_backup = bbsink_forward_end_backup,
+ .cleanup = bbsink_forward_cleanup
+};
+
+/*
+ * Create a new 'server' bbsink.
+ */
+bbsink *
+bbsink_server_new(bbsink *next, char *pathname)
+{
+ bbsink_server *sink = palloc0(sizeof(bbsink_server));
+
+ *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_server_ops;
+ sink->pathname = pathname;
+ sink->base.bbs_next = next;
+
+ /* Replication permission is not sufficient in this case. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create server backup")));
+
+ /*
+ * It's not a good idea to store your backups in the same directory that
+ * you're backing up. If we allowed a relative path here, that could easily
+ * happen accidentally, so we don't. The user could still accomplish the
+ * same thing by including the absolute path to $PGDATA in the pathname,
+ * but that's likely an intentional bad decision rather than an accident.
+ */
+ if (!is_absolute_path(pathname))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("relative path not allowed for server backup")));
+
+ switch (pg_check_dir(pathname))
+ {
+ case 0:
+ /*
+ * Does not exist, so create it using the same permissions we'd use
+ * for a new subdirectory of the data directory itself.
+ */
+ if (MakePGDirectory(pathname) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create directory \"%s\": %m", pathname)));
+ break;
+
+ case 1:
+ /* Exists, empty. */
+ break;
+
+ case 2:
+ case 3:
+ case 4:
+ /* Exists, not empty. */
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_FILE),
+ errmsg("directory \"%s\" exists but is not empty",
+ pathname)));
+ break;
+
+ default:
+ /* Access problem. */
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not access directory \"%s\": %m",
+ pathname)));
+ }
+
+ return &sink->base;
+}
+
+/*
+ * Open the correct output file for this archive.
+ */
+static void
+bbsink_server_begin_archive(bbsink *sink, const char *archive_name)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+ char *filename;
+
+ Assert(mysink->file == 0);
+ Assert(mysink->filepos == 0);
+
+ filename = psprintf("%s/%s", mysink->pathname, archive_name);
+
+ mysink->file = PathNameOpenFile(filename,
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
+ if (mysink->file <= 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", filename)));
+
+ pfree(filename);
+
+ bbsink_forward_begin_archive(sink, archive_name);
+}
+
+/*
+ * Write the data to the output file.
+ */
+static void
+bbsink_server_archive_contents(bbsink *sink, size_t len)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+ int nbytes;
+
+ nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len,
+ mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);
+
+ if (nbytes != len)
+ {
+ if (nbytes < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write file \"%s\": %m",
+ FilePathName(mysink->file)),
+ errhint("Check free disk space.")));
+ /* short write: complain appropriately */
+ ereport(ERROR,
+ (errcode(ERRCODE_DISK_FULL),
+ errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
+ FilePathName(mysink->file),
+ nbytes, (int) len, (unsigned) mysink->filepos),
+ errhint("Check free disk space.")));
+ }
+
+ mysink->filepos += nbytes;
+
+ bbsink_forward_archive_contents(sink, len);
+}
+
+/*
+ * fsync and close the current output file.
+ */
+static void
+bbsink_server_end_archive(bbsink *sink)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+
+ /*
+ * We intentionally don't use data_sync_elevel here, because the server
+ * shouldn't PANIC just because we can't guarantee the the backup has been
+ * written down to disk. Running recovery won't fix anything in this case
+ * anyway.
+ */
+ if (FileSync(mysink->file, WAIT_EVENT_BASEBACKUP_SYNC) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m",
+ FilePathName(mysink->file))));
+
+
+ /* We're done with this file now. */
+ FileClose(mysink->file);
+ mysink->file = 0;
+ mysink->filepos = 0;
+
+ bbsink_forward_end_archive(sink);
+}
+
+/*
+ * Open the output file to which we will write the manifest.
+ *
+ * Just like pg_basebackup, we write the manifest first under a temporary
+ * name and then rename it into place after fsync. That way, if the manifest
+ * is there and under the correct name, the user can be sure that the backup
+ * completed.
+ */
+static void
+bbsink_server_begin_manifest(bbsink *sink)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+ char *tmp_filename;
+
+ Assert(mysink->file == 0);
+
+ tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname);
+
+ mysink->file = PathNameOpenFile(tmp_filename,
+ O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
+ if (mysink->file <= 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", tmp_filename)));
+
+ pfree(tmp_filename);
+
+ bbsink_forward_begin_manifest(sink);
+}
+
+/*
+ * Each chunk of manifest data is sent using a CopyData message.
+ */
+static void
+bbsink_server_manifest_contents(bbsink *sink, size_t len)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+ int nbytes;
+
+ nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len,
+ mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);
+
+ if (nbytes != len)
+ {
+ if (nbytes < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write file \"%s\": %m",
+ FilePathName(mysink->file)),
+ errhint("Check free disk space.")));
+ /* short write: complain appropriately */
+ ereport(ERROR,
+ (errcode(ERRCODE_DISK_FULL),
+ errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
+ FilePathName(mysink->file),
+ nbytes, (int) len, (unsigned) mysink->filepos),
+ errhint("Check free disk space.")));
+ }
+
+ mysink->filepos += nbytes;
+
+ bbsink_forward_manifest_contents(sink, len);
+}
+
+/*
+ * fsync the backup manifest, close the file, and then rename it into place.
+ */
+static void
+bbsink_server_end_manifest(bbsink *sink)
+{
+ bbsink_server *mysink = (bbsink_server *) sink;
+ char *tmp_filename;
+ char *filename;
+
+ /* We're done with this file now. */
+ FileClose(mysink->file);
+ mysink->file = 0;
+
+ /*
+ * Rename it into place. This also fsyncs the temporary file, so we don't
+ * need to do that here. We don't use data_sync_elevel here for the same
+ * reasons as in bbsink_server_end_archive.
+ */
+ tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname);
+ filename = psprintf("%s/backup_manifest", mysink->pathname);
+ durable_rename(tmp_filename, filename, ERROR);
+ pfree(filename);
+ pfree(tmp_filename);
+
+ bbsink_forward_end_manifest(sink);
+}
case WAIT_EVENT_BASEBACKUP_READ:
event_name = "BaseBackupRead";
break;
+ case WAIT_EVENT_BASEBACKUP_SYNC:
+ event_name = "BaseBackupSync";
+ break;
+ case WAIT_EVENT_BASEBACKUP_WRITE:
+ event_name = "BaseBackupWrite";
+ break;
case WAIT_EVENT_BUFFILE_READ:
event_name = "BufFileRead";
break;
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;
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;
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"
}
/*
- * 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;
}
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;
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");
* 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)));
/*
* 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",
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);
}
* 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 ...");
* 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];
{"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'},
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)
case 2:
no_slot = true;
break;
+ case 't':
+ backup_target = pg_strdup(optarg);
+ break;
case 'T':
tablespace_list_append(optarg);
break;
}
/*
- * 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");
exit(1);
}
+ /*
+ * Sanity checks for replication slot options.
+ */
if (no_slot)
{
if (replication_slot)
}
}
+ /*
+ * 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");
}
#ifndef HAVE_LIBZ
+ /* Sanity checks for compression level. */
if (compresslevel != 0)
{
pg_log_error("this build does not support compression");
}
#endif
+ /*
+ * Sanity checks for progress reporting options.
+ */
if (showprogress && !estimatesize)
{
pg_log_error("%s and %s are incompatible options",
exit(1);
}
+ /*
+ * Sanity checks for backup manifest options.
+ */
if (!manifest && manifest_checksums != NULL)
{
pg_log_error("%s and %s are incompatible options",
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 */
use Fcntl qw(:seek);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 11 5;
+use Test::More tests => 13 5;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
],
'pg_basebackup -X stream runs with --no-slot');
rmtree("$tempdir/backupnoslot");
+$node->command_ok(
+ [ @pg_basebackup_defs, '-D', "$tempdir/backupxf", '-X', 'fetch' ],
+ 'pg_basebackup -X fetch runs');
+
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'blackhole' ],
+ qr/WAL cannot be streamed when a backup target is specified/,
+ 'backup target requires -X');
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'blackhole', '-X', 'stream' ],
+ qr/WAL cannot be streamed when a backup target is specified/,
+ 'backup target requires -X other than -X stream');
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'bogus', '-X', 'none' ],
+ qr/unrecognized target/,
+ 'backup target unrecognized');
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'blackhole', '-X', 'none', '-D', "$tempdir/blackhole" ],
+ qr/cannot specify both output directory and backup target/,
+ 'backup target and output directory');
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'blackhole', '-X', 'none', '-Ft' ],
+ qr/cannot specify both format and backup target/,
+ 'backup target and output directory');
+$node->command_ok(
+ [ @pg_basebackup_defs, '--target', 'blackhole', '-X', 'none' ],
+ 'backup target blackhole');
+$node->command_ok(
+ [ @pg_basebackup_defs, '--target', "server:$tempdir/backuponserver", '-X', 'none' ],
+ 'backup target server');
+ok(-f "$tempdir/backuponserver/base.tar", 'backup tar was created');
+rmtree("$tempdir/backuponserver");
+
+$node->command_fails(
+ [
+ @pg_basebackup_defs, '-D',
+ "$tempdir/backupxs_sl_fail", '-X',
+ 'stream', '-S',
+ 'slot0'
+ ],
+ 'pg_basebackup fails with nonexistent replication slot');
+
+$node->command_fails(
+ [ @pg_basebackup_defs, '-D', "$tempdir/backupxs_slot", '-C' ],
+ 'pg_basebackup -C fails without slot name');
+
+$node->command_fails(
+ [
+ @pg_basebackup_defs, '-D',
+ "$tempdir/backupxs_slot", '-C',
+ '-S', 'slot0',
+ '--no-slot'
+ ],
+ 'pg_basebackup fails with -C -S --no-slot');
+$node->command_fails_like(
+ [ @pg_basebackup_defs, '--target', 'blackhole', '-D', "$tempdir/blackhole" ],
+ qr/cannot specify both output directory and backup target/,
+ 'backup target and output directory');
+
+$node->command_ok(
+ [ @pg_basebackup_defs, '-D', "$tempdir/backuptr/co", '-X', 'none' ],
+ 'pg_basebackup -X fetch runs');
$node->command_fails(
[
extern void bbsink_forward_cleanup(bbsink *sink);
/* Constructors for various types of sinks. */
-extern bbsink *bbsink_copystream_new(void );
+extern bbsink *bbsink_copystream_new(bool send_to_client );
extern bbsink *bbsink_copytblspc_new(void);
extern bbsink *bbsink_progress_new(bbsink *next, bool estimate_backup_size);
+extern bbsink *bbsink_server_new(bbsink *next, char *pathname);
extern bbsink *bbsink_throttle_new(bbsink *next, uint32 maxrate);
/* Extra interface functions for progress reporting. */
typedef enum
{
WAIT_EVENT_BASEBACKUP_READ = PG_WAIT_IO,
+ WAIT_EVENT_BASEBACKUP_SYNC,
+ WAIT_EVENT_BASEBACKUP_WRITE,
WAIT_EVENT_BUFFILE_READ,
WAIT_EVENT_BUFFILE_WRITE,
WAIT_EVENT_BUFFILE_TRUNCATE,