diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index db7c8349148e5e4bcee68eea30c726d94bb143de..c14ae4306237717cb4c9a46d9653c9bd18bcc7b1 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -812,6 +812,16 @@ SELECT pg_stop_backup(); </orderedlist> </para> + <para> + You can also use the <xref linkend="app-pgbasebackup"> tool to take + the backup, instead of manually copying the files. This tool will take + care of the <function>pg_start_backup()</>, copy and + <function>pg_stop_backup()</> steps automatically, and transfers the + backup over a regular <productname>PostgreSQL</productname> connection + using the replication protocol, instead of requiring filesystem level + access. + </para> + <para> Some file system backup tools emit warnings or errors if the files they are trying to copy change while the copy proceeds. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 570c7c3b7de3b339eef4063823f94c892e32ca39..bbfe86a6922a8986e210cc4fa34821d2e710f487 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1921,7 +1921,8 @@ SET ENABLE_SEQSCAN TO OFF; <listitem> <para> Specifies the maximum number of concurrent connections from standby - servers (i.e., the maximum number of simultaneously running WAL sender + servers or streaming base backup clients (i.e., the maximum number of + simultaneously running WAL sender processes). The default is zero. This parameter can only be set at server start. <varname>wal_level</> must be set to <literal>archive</> or <literal>hot_standby</> to allow connections from standby servers. diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 76c062fd5162f4a12ca78f70575cca431773fa08..73f26b432da0943ecbc9477fce139f9f832acca3 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1460,7 +1460,7 @@ The commands accepted in walsender mode are: </varlistentry> <varlistentry> - <term>BASE_BACKUP [<literal>LABEL</literal> <replaceable>'label'</replaceable>] [<literal>PROGRESS</literal>]</term> + <term>BASE_BACKUP [<literal>LABEL</literal> <replaceable>'label'</replaceable>] [<literal>PROGRESS</literal>] [<literal>FAST</literal>]</term> <listitem> <para> Instructs the server to start streaming a base backup. @@ -1496,6 +1496,15 @@ The commands accepted in walsender mode are: </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>FAST</></term> + <listitem> + <para> + Request a fast checkpoint. + </para> + </listitem> + </varlistentry> </variablelist> </para> <para> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index f40fa9dd8b26267d7910d090e0360d547b01b8c5..c44d11ef91b916523502454dfc18b20d92f1aab8 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -160,6 +160,7 @@ Complete list of usable sgml source files in this directory. <!entity dropuser system "dropuser.sgml"> <!entity ecpgRef system "ecpg-ref.sgml"> <!entity initdb system "initdb.sgml"> +<!entity pgBasebackup system "pg_basebackup.sgml"> <!entity pgConfig system "pg_config-ref.sgml"> <!entity pgControldata system "pg_controldata.sgml"> <!entity pgCtl system "pg_ctl-ref.sgml"> diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml new file mode 100644 index 0000000000000000000000000000000000000000..321c8cade1cccad5d4e10b1f2e863e53a1bb213a --- /dev/null +++ b/doc/src/sgml/ref/pg_basebackup.sgml @@ -0,0 +1,397 @@ +<!-- +doc/src/sgml/ref/pg_basebackup.sgml +PostgreSQL documentation +--> + +<refentry id="app-pgbasebackup"> + <refmeta> + <refentrytitle>pg_basebackup</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo>Application</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>pg_basebackup</refname> + <refpurpose>take a base backup of a <productname>PostgreSQL</productname> cluster</refpurpose> + </refnamediv> + + <indexterm zone="app-pgbasebackup"> + <primary>pg_basebackup</primary> + </indexterm> + + <refsynopsisdiv> + <cmdsynopsis> + <command>pg_basebackup</command> + <arg rep="repeat"><replaceable>option</></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title> + Description + </title> + <para> + <application>pg_basebackup</application> is used to take base backups of + a running <productname>PostgreSQL</productname> database cluster. These + are taken without affecting other clients to the database, and can be used + both for point-in-time recovery (see <xref linkend="continuous-archiving">) + and as the starting point for a log shipping or streaming replication standby + servers (see <xref linkend="warm-standby">). + </para> + + <para> + <application>pg_basebackup</application> makes a binary copy of the database + cluster files, while making sure the system is automatically put in and + out of backup mode automatically. Backups are always taken of the entire + database cluster, it is not possible to back up individual databases or + database objects. For individual database backups, a tool such as + <xref linkend="APP-PGDUMP"> must be used. + </para> + + <para> + The backup is made over a regular <productname>PostgreSQL</productname> + connection, and uses the replication protocol. The connection must be + made with a user having <literal>REPLICATION</literal> permissions (see + <xref linkend="role-attributes">), and the user must be granted explicit + permissions in <filename>pg_hba.conf</filename>. The server must also + be configured with <xref linkend="guc-max-wal-senders"> set high enough + to leave at least one session available for the backup. + </para> + + <para> + Only one backup can be concurrently active in + <productname>PostgreSQL</productname>, meaning that only one instance of + <application>pg_basebackup</application> can run at the same time + against a single database cluster. + </para> + </refsect1> + + <refsect1> + <title>Options</title> + + <para> + The following command-line options control the location and format of the + output. + + <variablelist> + <varlistentry> + <term><option>-D <replaceable class="parameter">directory</replaceable></option></term> + <term><option>--pgdata=<replaceable class="parameter">directory</replaceable></option></term> + <listitem> + <para> + Directory to write the output to. + </para> + <para> + When the backup is in tar mode, and the directory is specified as + <literal>-</literal> (dash), the tar file will be written to + <literal>stdout</literal>. + </para> + <para> + This parameter is required. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-F <replaceable class="parameter">format</replaceable></option></term> + <term><option>--format=<replaceable class="parameter">format</replaceable></option></term> + <listitem> + <para> + Selects the format for the output. <replaceable>format</replaceable> + can be one of the following: + + <variablelist> + <varlistentry> + <term><literal>p</literal></term> + <term><literal>plain</literal></term> + <listitem> + <para> + Write the output as plain files, with the same layout as the + current data directory and tablespaces. When the cluster has + no additional tablespaces, the whole database will be placed in + the target directory. If the cluster contains additional + tablespaces, the main data directory will be placed in the + target directory, but all other tablespaces will be placed + in the same absolute path as they have on the server. + </para> + <para> + This is the default format. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>t</literal></term> + <term><literal>tar</literal></term> + <listitem> + <para> + Write the output as tar files in the target directory. The main + data directory will be written to a file named + <filename>base.tar</filename>, and all other tablespaces will + be named after the tablespace oid. + </para> + <para> + If the value <literal>-</literal> (dash) is specified as + target directory, the tar contents will be written to + standard output, suitable for piping to for example + <productname>gzip</productname>. This is only possible if + the cluster has no additional tablespaces. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-Z <replaceable class="parameter">level</replaceable></option></term> + <term><option>--compress=<replaceable class="parameter">level</replaceable></option></term> + <listitem> + <para> + Enables gzip compression of tar file output. Compression is only + available when generating tar files, and is not available when sending + output to standard output. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + <para> + The following command-line options control the generation of the + backup and the running of the program. + + <variablelist> + <varlistentry> + <term><option>-c <replaceable class="parameter">fast|spread</replaceable></option></term> + <term><option>--checkpoint <replaceable class="parameter">fast|spread</replaceable></option></term> + <listitem> + <para> + Sets checkpoint mode to fast or spread (default). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-l <replaceable class="parameter">label</replaceable></option></term> + <term><option>--label=<replaceable class="parameter">label</replaceable></option></term> + <listitem> + <para> + Sets the label for the backup. If none is specified, a default value of + <literal>pg_basebackup base backup</literal> will be used. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-P</option></term> + <term><option>--progress</option></term> + <listitem> + <para> + Enables progress reporting. Turning this on will deliver an approximate + progress report during the backup. Since the database may change during + the backup, this is only an approximation and may not end at exactly + <literal>100%</literal>. + </para> + <para> + When this is enabled, the backup will start by enumerating the size of + the entire database, and then go back and send the actual contents. + This may make the backup take slightly longer, and in particular it + will take longer before the first data is sent. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-v</option></term> + <term><option>--verbose</option></term> + <listitem> + <para> + Enables verbose mode. Will output some extra steps during startup and + shutdown, as well as show the exact filename that is currently being + processed if progress reporting is also enabled. + </para> + </listitem> + </varlistentry> + + </variablelist> + </para> + + <para> + The following command-line options control the database connection parameters. + + <variablelist> + <varlistentry> + <term><option>-h <replaceable class="parameter">host</replaceable></option></term> + <term><option>--host=<replaceable class="parameter">host</replaceable></option></term> + <listitem> + <para> + Specifies the host name of the machine on which the server is + running. If the value begins with a slash, it is used as the + directory for the Unix domain socket. The default is taken + from the <envar>PGHOST</envar> environment variable, if set, + else a Unix domain socket connection is attempted. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-p <replaceable class="parameter">port</replaceable></option></term> + <term><option>--port=<replaceable class="parameter">port</replaceable></option></term> + <listitem> + <para> + Specifies the TCP port or local Unix domain socket file + extension on which the server is listening for connections. + Defaults to the <envar>PGPORT</envar> environment variable, if + set, or a compiled-in default. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-U <replaceable>username</replaceable></option></term> + <term><option>--username=<replaceable class="parameter">username</replaceable></option></term> + <listitem> + <para> + User name to connect as. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-w</></term> + <term><option>--no-password</></term> + <listitem> + <para> + Never issue a password prompt. If the server requires + password authentication and a password is not available by + other means such as a <filename>.pgpass</filename> file, the + connection attempt will fail. This option can be useful in + batch jobs and scripts where no user is present to enter a + password. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-W</option></term> + <term><option>--password</option></term> + <listitem> + <para> + Force <application>pg_basebackup</application> to prompt for a + password before connecting to a database. + </para> + + <para> + This option is never essential, since + <application>pg_bsaebackup</application> will automatically prompt + for a password if the server demands password authentication. + However, <application>pg_basebackup</application> will waste a + connection attempt finding out that the server wants a password. + In some cases it is worth typing <option>-W</> to avoid the extra + connection attempt. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Other, less commonly used, parameters are also available: + + <variablelist> + <varlistentry> + <term><option>-V</></term> + <term><option>--version</></term> + <listitem> + <para> + Print the <application>pg_basebackup</application> version and exit. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-?</></term> + <term><option>--help</></term> + <listitem> + <para> + Show help about <application>pg_basebackup</application> command line + arguments, and exit. + </para> + </listitem> + </varlistentry> + + </variablelist> + </para> + + </refsect1> + + <refsect1> + <title>Environment</title> + + <para> + This utility, like most other <productname>PostgreSQL</> utilities, + uses the environment variables supported by <application>libpq</> + (see <xref linkend="libpq-envars">). + </para> + + </refsect1> + + <refsect1> + <title>Notes</title> + + <para> + The backup will include all files in the data directory and tablespaces, + including the configuration files and any additional files placed in the + directory by third parties. Only regular files and directories are allowed + in the data directory, no symbolic links or special device files. + </para> + + <para> + The way <productname>PostgreSQL</productname> 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. + </para> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para> + To create a base backup of the server at <literal>mydbserver</literal> + and store it in the local directory + <filename>/usr/local/pgsql/data</filename>: + <screen> + <prompt>$</prompt> <userinput>pg_basebackup -h mydbserver -D /usr/local/pgsql/data</userinput> + </screen> + </para> + + <para> + To create a backup of the local server with one maximum compressed + tar file for each tablespace, and store it in the directory + <filename>backup</filename>, showing a progress report while running: + <screen> + <prompt>$</prompt> <userinput>pg_basebackup -D backup -Ft -Z9 -P</userinput> + </screen> + </para> + + <para> + To create a backup of a single-tablespace local database and compress + this with <productname>bzip2</productname>: + <screen> + <prompt>$</prompt> <userinput>pg_basebackup -D - -Ft | bzip2 > backup.tar.bz2</userinput> + </screen> + (this command will fail if there are multiple tablespaces in the + database) + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="APP-PGDUMP"></member> + </simplelist> + </refsect1> + +</refentry> diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 84babf61c00c70d5b6a5f19c65fd9956dc1cb821..6ee8e5bcff82dab89d70cd483aa5c47866e52dc7 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -202,6 +202,7 @@ &droplang; &dropuser; &ecpgRef; + &pgBasebackup; &pgConfig; &pgDump; &pgDumpall; diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index b4d5bbe412e7791b36f509b7a27077b020fe0d2b..943d80470bf1ff5a7adf609126a66e13ed0475d6 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -40,7 +40,7 @@ static void send_int8_string(StringInfoData *buf, int64 intval); static void SendBackupHeader(List *tablespaces); static void SendBackupDirectory(char *location, char *spcoid); static void base_backup_cleanup(int code, Datum arg); -static void perform_base_backup(const char *backup_label, bool progress, DIR *tblspcdir); +static void perform_base_backup(const char *backup_label, bool progress, DIR *tblspcdir, bool fastcheckpoint); typedef struct { @@ -67,9 +67,9 @@ base_backup_cleanup(int code, Datum arg) * clobbered by longjmp" from stupider versions of gcc. */ static void -perform_base_backup(const char *backup_label, bool progress, DIR *tblspcdir) +perform_base_backup(const char *backup_label, bool progress, DIR *tblspcdir, bool fastcheckpoint) { - do_pg_start_backup(backup_label, true); + do_pg_start_backup(backup_label, fastcheckpoint); PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0); { @@ -135,7 +135,7 @@ perform_base_backup(const char *backup_label, bool progress, DIR *tblspcdir) * pg_stop_backup() for the user. */ void -SendBaseBackup(const char *backup_label, bool progress) +SendBaseBackup(const char *backup_label, bool progress, bool fastcheckpoint) { DIR *dir; MemoryContext backup_context; @@ -168,7 +168,7 @@ SendBaseBackup(const char *backup_label, bool progress) ereport(ERROR, (errmsg("unable to open directory pg_tblspc: %m"))); - perform_base_backup(backup_label, progress, dir); + perform_base_backup(backup_label, progress, dir, fastcheckpoint); FreeDir(dir); @@ -333,7 +333,16 @@ sendDir(char *path, int basepathlen, bool sizeonly) if (strcmp(pathbuf, "./pg_xlog") == 0) { if (!sizeonly) + { + /* If pg_xlog is a symlink, write it as a directory anyway */ +#ifndef WIN32 + if (S_ISLNK(statbuf.st_mode)) +#else + if (pgwin32_is_junction(pathbuf)) +#endif + statbuf.st_mode = S_IFDIR | S_IRWXU; _tarWriteHeader(pathbuf + basepathlen + 1, NULL, &statbuf); + } size += 512; /* Size of the header just added */ continue; /* don't recurse into pg_xlog */ } diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y index 0ef33ddb4f9c494922552d68f2bb19f6368520f4..e4f4c4742f68783c964c0da8f8450819935ca9ac 100644 --- a/src/backend/replication/repl_gram.y +++ b/src/backend/replication/repl_gram.y @@ -66,11 +66,12 @@ Node *replication_parse_result; %token K_IDENTIFY_SYSTEM %token K_LABEL %token K_PROGRESS +%token K_FAST %token K_START_REPLICATION %type <node> command %type <node> base_backup start_replication identify_system -%type <boolval> opt_progress +%type <boolval> opt_progress opt_fast %type <str> opt_label %% @@ -102,15 +103,16 @@ identify_system: ; /* - * BASE_BACKUP [LABEL <label>] [PROGRESS] + * BASE_BACKUP [LABEL <label>] [PROGRESS] [FAST] */ base_backup: - K_BASE_BACKUP opt_label opt_progress + K_BASE_BACKUP opt_label opt_progress opt_fast { BaseBackupCmd *cmd = (BaseBackupCmd *) makeNode(BaseBackupCmd); cmd->label = $2; cmd->progress = $3; + cmd->fastcheckpoint = $4; $$ = (Node *) cmd; } @@ -123,6 +125,9 @@ opt_label: K_LABEL SCONST { $$ = $2; } opt_progress: K_PROGRESS { $$ = true; } | /* EMPTY */ { $$ = false; } ; +opt_fast: K_FAST { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; /* * START_REPLICATION %X/%X diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l index 014a72059a48693ff58583fcf9f063fd718d2054..e6dfb041b6ad5445fc048dc8a84d265a7f5865f3 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -57,6 +57,7 @@ quotestop {quote} %% BASE_BACKUP { return K_BASE_BACKUP; } +FAST { return K_FAST; } IDENTIFY_SYSTEM { return K_IDENTIFY_SYSTEM; } LABEL { return K_LABEL; } PROGRESS { return K_PROGRESS; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 0ad6804e55204355849faa3da273ec3fac7eb6b7..14b43d855bade3979442d03de31b820d06136b79 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -402,7 +402,7 @@ HandleReplicationCommand(const char *cmd_string) { BaseBackupCmd *cmd = (BaseBackupCmd *) cmd_node; - SendBaseBackup(cmd->label, cmd->progress); + SendBaseBackup(cmd->label, cmd->progress, cmd->fastcheckpoint); /* Send CommandComplete and ReadyForQuery messages */ EndCommand("SELECT", DestRemote); diff --git a/src/bin/Makefile b/src/bin/Makefile index c18c05c6c53482ba30151109cf3f18991bdce0e5..3809412a2d0885b261c353c0e63aaf4ecbb454a8 100644 --- a/src/bin/Makefile +++ b/src/bin/Makefile @@ -14,7 +14,7 @@ top_builddir = ../.. include $(top_builddir)/src/Makefile.global SUBDIRS = initdb pg_ctl pg_dump \ - psql scripts pg_config pg_controldata pg_resetxlog + psql scripts pg_config pg_controldata pg_resetxlog pg_basebackup ifeq ($(PORTNAME), win32) SUBDIRS+=pgevent endif diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ccb15025ef5fa5cfd371965ed9eb226eb82eeeed --- /dev/null +++ b/src/bin/pg_basebackup/Makefile @@ -0,0 +1,38 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/bin/pg_basebackup +# +# Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/bin/pg_basebackup/Makefile +# +#------------------------------------------------------------------------- + +PGFILEDESC = "pg_basebackup - takes a streaming base backup of a PostgreSQL instance" +PGAPPICON=win32 + +subdir = src/bin/pg_basebackup +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) + +OBJS= pg_basebackup.o $(WIN32RES) + +all: pg_basebackup + +pg_basebackup: $(OBJS) | submake-libpq submake-libpgport + $(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +install: all installdirs + $(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(bindir)/pg_basebackup$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(bindir)' + +uninstall: + rm -f '$(DESTDIR)$(bindir)/pg_basebackup$(X)' + +clean distclean maintainer-clean: + rm -f pg_basebackup$(X) $(OBJS) diff --git a/src/bin/pg_basebackup/nls.mk b/src/bin/pg_basebackup/nls.mk new file mode 100644 index 0000000000000000000000000000000000000000..760ee1d70a1c44dd55b9932e5735fc5d012dbb63 --- /dev/null +++ b/src/bin/pg_basebackup/nls.mk @@ -0,0 +1,5 @@ +# src/bin/pg_basebackup/nls.mk +CATALOG_NAME := pg_basebackup +AVAIL_LANGUAGES := +GETTEXT_FILES := pg_basebackup.c +GETTEXT_TRIGGERS:= _ diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c new file mode 100644 index 0000000000000000000000000000000000000000..5baea4cf77bf62318b0d4ccb9971f40545907aa0 --- /dev/null +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -0,0 +1,1035 @@ +/*------------------------------------------------------------------------- + * + * pg_basebackup.c - receive a base backup using streaming replication protocol + * + * Author: Magnus Hagander <magnus@hagander.net> + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/bin/pg_basebackup/pg_basebackup.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" +#include "libpq-fe.h" + +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +#ifdef HAVE_LIBZ +#include <zlib.h> +#endif + +#include "getopt_long.h" + + +/* Global options */ +static const char *progname; +char *basedir = NULL; +char format = 'p'; /* p(lain)/t(ar) */ +char *label = "pg_basebackup base backup"; +bool showprogress = false; +int verbose = 0; +int compresslevel = 0; +bool fastcheckpoint = false; +char *dbhost = NULL; +char *dbuser = NULL; +char *dbport = NULL; +int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */ + +/* Progress counters */ +static uint64 totalsize; +static uint64 totaldone; +static int tablespacecount; + +/* Connection kept global so we can disconnect easily */ +static PGconn *conn = NULL; + +#define disconnect_and_exit(code) \ + { \ + if (conn != NULL) PQfinish(conn); \ + exit(code); \ + } + +/* Function headers */ +static char *xstrdup(const char *s); +static void *xmalloc0(int size); +static void usage(void); +static void verify_dir_is_empty_or_create(char *dirname); +static void progress_report(int tablespacenum, char *fn); +static PGconn *GetConnection(void); + +static void ReceiveTarFile(PGconn *conn, PGresult *res, int rownum); +static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum); +static void BaseBackup(); + +#ifdef HAVE_LIBZ +static const char * +get_gz_error(gzFile *gzf) +{ + int errnum; + const char *errmsg; + + errmsg = gzerror(gzf, &errnum); + if (errnum == Z_ERRNO) + return strerror(errno); + else + return errmsg; +} +#endif + +/* + * strdup() and malloc() replacements that prints an error and exits + * if something goes wrong. Can never return NULL. + */ +static char * +xstrdup(const char *s) +{ + char *result; + + result = strdup(s); + if (!result) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + exit(1); + } + return result; +} + +static void * +xmalloc0(int size) +{ + void *result; + + result = malloc(size); + if (!result) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + exit(1); + } + MemSet(result, 0, size); + return result; +} + + +static void +usage(void) +{ + printf(_("%s takes base backups of running PostgreSQL servers\n\n"), + progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]...\n"), progname); + printf(_("\nOptions controlling the output:\n")); + printf(_(" -D, --pgdata=directory receive base backup into directory\n")); + printf(_(" -F, --format=p|t output format (plain, tar)\n")); + printf(_(" -Z, --compress=0-9 compress tar output\n")); + printf(_("\nGeneral options:\n")); + printf(_(" -c, --checkpoint=fast|spread\n" + " set fast or spread checkpointing\n")); + printf(_(" -l, --label=label set backup label\n")); + printf(_(" -P, --progress show progress information\n")); + printf(_(" -v, --verbose output verbose messages\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_("\nConnection options:\n")); + printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); + printf(_(" -p, --port=PORT database server port number\n")); + printf(_(" -U, --username=NAME connect as specified database user\n")); + printf(_(" -w, --no-password never prompt for password\n")); + printf(_(" -W, --password force password prompt (should happen automatically)\n")); + printf(_("\nReport bugs to <pgsql-bugs@postgresql.org>.\n")); +} + + +/* + * Verify that the given directory exists and is empty. If it does not + * exist, it is created. If it exists but is not empty, an error will + * be give and the process ended. + */ +static void +verify_dir_is_empty_or_create(char *dirname) +{ + switch (pg_check_dir(dirname)) + { + case 0: + + /* + * Does not exist, so create + */ + if (pg_mkdir_p(dirname, S_IRWXU) == -1) + { + fprintf(stderr, + _("%s: could not create directory \"%s\": %s\n"), + progname, dirname, strerror(errno)); + disconnect_and_exit(1); + } + return; + case 1: + + /* + * Exists, empty + */ + return; + case 2: + + /* + * Exists, not empty + */ + fprintf(stderr, + _("%s: directory \"%s\" exists but is not empty\n"), + progname, dirname); + disconnect_and_exit(1); + case -1: + + /* + * Access problem + */ + fprintf(stderr, _("%s: could not access directory \"%s\": %s\n"), + progname, dirname, strerror(errno)); + disconnect_and_exit(1); + } +} + + +/* + * Print a progress report based on the global variables. If verbose output + * is enabled, also print the current file name. + */ +static void +progress_report(int tablespacenum, char *fn) +{ + if (verbose) + fprintf(stderr, + INT64_FORMAT "/" INT64_FORMAT " kB (%i%%) %i/%i tablespaces (%-30s)\r", + totaldone / 1024, totalsize, + (int) ((totaldone / 1024) * 100 / totalsize), + tablespacenum, tablespacecount, fn); + else + fprintf(stderr, INT64_FORMAT "/" INT64_FORMAT " kB (%i%%) %i/%i tablespaces\r", + totaldone / 1024, totalsize, + (int) ((totaldone / 1024) * 100 / totalsize), + tablespacenum, tablespacecount); +} + + +/* + * Receive a tar format file from the connection to the server, and write + * the data from this file directly into a tar file. If compression is + * enabled, the data will be compressed while written to the file. + * + * The file will be named base.tar[.gz] if it's for the main data directory + * or <tablespaceoid>.tar[.gz] if it's for another tablespace. + * + * No attempt to inspect or validate the contents of the file is done. + */ +static void +ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) +{ + char fn[MAXPGPATH]; + char *copybuf = NULL; + FILE *tarfile = NULL; + +#ifdef HAVE_LIBZ + gzFile *ztarfile = NULL; +#endif + + if (PQgetisnull(res, rownum, 0)) + + /* + * Base tablespaces + */ + if (strcmp(basedir, "-") == 0) + tarfile = stdout; + else + { +#ifdef HAVE_LIBZ + if (compresslevel > 0) + { + snprintf(fn, sizeof(fn), "%s/base.tar.gz", basedir); + ztarfile = gzopen(fn, "wb"); + if (gzsetparams(ztarfile, compresslevel, Z_DEFAULT_STRATEGY) != Z_OK) + { + fprintf(stderr, _("%s: could not set compression level %i: %s\n"), + progname, compresslevel, get_gz_error(ztarfile)); + disconnect_and_exit(1); + } + } + else +#endif + { + snprintf(fn, sizeof(fn), "%s/base.tar", basedir); + tarfile = fopen(fn, "wb"); + } + } + else + { + /* + * Specific tablespace + */ +#ifdef HAVE_LIBZ + if (compresslevel > 0) + { + snprintf(fn, sizeof(fn), "%s/%s.tar.gz", basedir, PQgetvalue(res, rownum, 0)); + ztarfile = gzopen(fn, "wb"); + if (gzsetparams(ztarfile, compresslevel, Z_DEFAULT_STRATEGY) != Z_OK) + { + fprintf(stderr, _("%s: could not set compression level %i: %s\n"), + progname, compresslevel, get_gz_error(ztarfile)); + disconnect_and_exit(1); + } + } + else +#endif + { + snprintf(fn, sizeof(fn), "%s/%s.tar", basedir, PQgetvalue(res, rownum, 0)); + tarfile = fopen(fn, "wb"); + } + } + +#ifdef HAVE_LIBZ + if (compresslevel > 0) + { + if (!ztarfile) + { + /* Compression is in use */ + fprintf(stderr, _("%s: could not create compressed file \"%s\": %s\n"), + progname, fn, get_gz_error(ztarfile)); + disconnect_and_exit(1); + } + } + else +#endif + { + /* Either no zlib support, or zlib support but compresslevel = 0 */ + if (!tarfile) + { + fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } + } + + /* + * Get the COPY data stream + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_COPY_OUT) + { + fprintf(stderr, _("%s: could not get COPY data stream: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + + while (1) + { + int r; + + if (copybuf != NULL) + { + PQfreemem(copybuf); + copybuf = NULL; + } + + r = PQgetCopyData(conn, ©buf, 0); + if (r == -1) + { + /* + * End of chunk. Close file (but not stdout). + * + * Also, write two completely empty blocks at the end of the tar + * file, as required by some tar programs. + */ + char zerobuf[1024]; + + MemSet(zerobuf, 0, sizeof(zerobuf)); +#ifdef HAVE_LIBZ + if (ztarfile != NULL) + { + if (gzwrite(ztarfile, zerobuf, sizeof(zerobuf)) != sizeof(zerobuf)) + { + fprintf(stderr, _("%s: could not write to compressed file \"%s\": %s\n"), + progname, fn, get_gz_error(ztarfile)); + } + } + else +#endif + { + if (fwrite(zerobuf, sizeof(zerobuf), 1, tarfile) != 1) + { + fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } + } + + if (strcmp(basedir, "-") != 0) + { +#ifdef HAVE_LIBZ + if (ztarfile != NULL) + gzclose(ztarfile); +#endif + if (tarfile != NULL) + fclose(tarfile); + } + + break; + } + else if (r == -2) + { + fprintf(stderr, _("%s: could not read COPY data: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + +#ifdef HAVE_LIBZ + if (ztarfile != NULL) + { + if (gzwrite(ztarfile, copybuf, r) != r) + { + fprintf(stderr, _("%s: could not write to compressed file \"%s\": %s\n"), + progname, fn, get_gz_error(ztarfile)); + } + } + else +#endif + { + if (fwrite(copybuf, r, 1, tarfile) != 1) + { + fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } + } + totaldone += r; + if (showprogress) + progress_report(rownum, fn); + } /* while (1) */ + + if (copybuf != NULL) + PQfreemem(copybuf); +} + +/* + * 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) +{ + char current_path[MAXPGPATH]; + char fn[MAXPGPATH]; + int current_len_left; + int current_padding; + char *copybuf = NULL; + FILE *file = NULL; + + if (PQgetisnull(res, rownum, 0)) + strcpy(current_path, basedir); + else + strcpy(current_path, PQgetvalue(res, rownum, 1)); + + /* + * Make sure we're unpacking into an empty directory + */ + verify_dir_is_empty_or_create(current_path); + + /* + * Get the COPY data + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_COPY_OUT) + { + fprintf(stderr, _("%s: could not get COPY data stream: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + + while (1) + { + int r; + + if (copybuf != NULL) + { + PQfreemem(copybuf); + copybuf = NULL; + } + + r = PQgetCopyData(conn, ©buf, 0); + + if (r == -1) + { + /* + * End of chunk + */ + if (file) + fclose(file); + + break; + } + else if (r == -2) + { + fprintf(stderr, _("%s: could not read COPY data: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + + if (file == NULL) + { +#ifndef WIN32 + mode_t filemode; +#endif + + /* + * No current file, so this must be the header for a new file + */ + if (r != 512) + { + fprintf(stderr, _("%s: Invalid tar block header size: %i\n"), + progname, r); + disconnect_and_exit(1); + } + totaldone += 512; + + if (sscanf(copybuf + 124, "%11o", ¤t_len_left) != 1) + { + fprintf(stderr, _("%s: could not parse file size!\n"), + progname); + disconnect_and_exit(1); + } + + /* Set permissions on the file */ + if (sscanf(©buf[100], "%07o ", &filemode) != 1) + { + fprintf(stderr, _("%s: could not parse file mode!\n"), + progname); + disconnect_and_exit(1); + } + + /* + * All files are padded up to 512 bytes + */ + current_padding = + ((current_len_left + 511) & ~511) - current_len_left; + + /* + * First part of header is zero terminated filename + */ + snprintf(fn, sizeof(fn), "%s/%s", current_path, copybuf); + if (fn[strlen(fn) - 1] == '/') + { + /* + * Ends in a slash means directory or symlink to directory + */ + if (copybuf[156] == '5') + { + /* + * Directory + */ + fn[strlen(fn) - 1] = '\0'; /* Remove trailing slash */ + if (mkdir(fn, S_IRWXU) != 0) + { + fprintf(stderr, + _("%s: could not create directory \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } +#ifndef WIN32 + if (chmod(fn, filemode)) + fprintf(stderr, _("%s: could not set permissions on directory \"%s\": %s\n"), + progname, fn, strerror(errno)); +#endif + } + else if (copybuf[156] == '2') + { + /* + * Symbolic link + */ + fn[strlen(fn) - 1] = '\0'; /* Remove trailing slash */ + if (symlink(©buf[157], fn) != 0) + { + fprintf(stderr, + _("%s: could not create symbolic link from %s to %s: %s\n"), + progname, fn, ©buf[157], strerror(errno)); + disconnect_and_exit(1); + } + } + else + { + fprintf(stderr, _("%s: unknown link indicator \"%c\"\n"), + progname, copybuf[156]); + disconnect_and_exit(1); + } + continue; /* directory or link handled */ + } + + /* + * regular file + */ + file = fopen(fn, "wb"); + if (!file) + { + fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } + +#ifndef WIN32 + if (chmod(fn, filemode)) + fprintf(stderr, _("%s: could not set permissions on file \"%s\": %s\n"), + progname, fn, strerror(errno)); +#endif + + if (current_len_left == 0) + { + /* + * Done with this file, next one will be a new tar header + */ + fclose(file); + file = NULL; + continue; + } + } /* new file */ + else + { + /* + * Continuing blocks in existing file + */ + if (current_len_left == 0 && r == current_padding) + { + /* + * Received the padding block for this file, ignore it and + * close the file, then move on to the next tar header. + */ + fclose(file); + file = NULL; + totaldone += r; + continue; + } + + if (fwrite(copybuf, r, 1, file) != 1) + { + fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), + progname, fn, strerror(errno)); + disconnect_and_exit(1); + } + totaldone += r; + if (showprogress) + progress_report(rownum, fn); + + current_len_left -= r; + if (current_len_left == 0 && current_padding == 0) + { + /* + * Received the last block, and there is no padding to be + * expected. Close the file and move on to the next tar + * header. + */ + fclose(file); + file = NULL; + continue; + } + } /* continuing data in existing file */ + } /* loop over all data blocks */ + + if (file != NULL) + { + fprintf(stderr, _("%s: last file was never finsihed!\n"), progname); + disconnect_and_exit(1); + } + + if (copybuf != NULL) + PQfreemem(copybuf); +} + + +static PGconn * +GetConnection(void) +{ + PGconn *tmpconn; + int argcount = 4; /* dbname, replication, fallback_app_name, + * password */ + int i; + const char **keywords; + const char **values; + char *password = NULL; + + if (dbhost) + argcount++; + if (dbuser) + argcount++; + if (dbport) + argcount++; + + keywords = xmalloc0((argcount + 1) * sizeof(*keywords)); + values = xmalloc0((argcount + 1) * sizeof(*values)); + + keywords[0] = "dbname"; + values[0] = "replication"; + keywords[1] = "replication"; + values[1] = "true"; + keywords[2] = "fallback_application_name"; + values[2] = progname; + i = 3; + if (dbhost) + { + keywords[i] = "host"; + values[i] = dbhost; + i++; + } + if (dbuser) + { + keywords[i] = "user"; + values[i] = dbuser; + i++; + } + if (dbport) + { + keywords[i] = "port"; + values[i] = dbport; + i++; + } + + while (true) + { + if (dbgetpassword == 1) + { + /* Prompt for a password */ + password = simple_prompt(_("Password: "), 100, false); + keywords[argcount - 1] = "password"; + values[argcount - 1] = password; + } + + tmpconn = PQconnectdbParams(keywords, values, true); + if (password) + free(password); + + if (PQstatus(tmpconn) == CONNECTION_BAD && + PQconnectionNeedsPassword(tmpconn) && + dbgetpassword != -1) + { + dbgetpassword = 1; /* ask for password next time */ + PQfinish(tmpconn); + continue; + } + + if (PQstatus(tmpconn) != CONNECTION_OK) + { + fprintf(stderr, _("%s: could not connect to server: %s\n"), + progname, PQerrorMessage(tmpconn)); + exit(1); + } + + /* Connection ok! */ + free(values); + free(keywords); + return tmpconn; + } +} + +static void +BaseBackup() +{ + PGresult *res; + char current_path[MAXPGPATH]; + char escaped_label[MAXPGPATH]; + int i; + + /* + * Connect in replication mode to the server + */ + conn = GetConnection(); + + PQescapeStringConn(conn, escaped_label, label, sizeof(escaped_label), &i); + snprintf(current_path, sizeof(current_path), "BASE_BACKUP LABEL '%s' %s %s", + escaped_label, + showprogress ? "PROGRESS" : "", + fastcheckpoint ? "FAST" : ""); + + if (PQsendQuery(conn, current_path) == 0) + { + fprintf(stderr, _("%s: could not start base backup: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + + /* + * Get the header + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, _("%s: could not initiate base backup: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + if (PQntuples(res) < 1) + { + fprintf(stderr, _("%s: no data returned from server.\n"), progname); + disconnect_and_exit(1); + } + + /* + * Sum up the total size, for progress reporting + */ + totalsize = totaldone = 0; + tablespacecount = PQntuples(res); + for (i = 0; i < PQntuples(res); i++) + { + if (showprogress) + totalsize += atol(PQgetvalue(res, i, 2)); + + /* + * 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. + */ + if (format == 'p' && i > 0) + verify_dir_is_empty_or_create(PQgetvalue(res, i, 1)); + } + + /* + * When writing to stdout, require a single tablespace + */ + if (format == 't' && strcmp(basedir, "-") == 0 && PQntuples(res) > 1) + { + fprintf(stderr, _("%s: can only write single tablespace to stdout, database has %i.\n"), + progname, PQntuples(res)); + disconnect_and_exit(1); + } + + /* + * Start receiving chunks + */ + for (i = 0; i < PQntuples(res); i++) + { + if (format == 't') + ReceiveTarFile(conn, res, i); + else + ReceiveAndUnpackTarFile(conn, res, i); + } /* Loop over all tablespaces */ + + if (showprogress) + { + progress_report(PQntuples(res), ""); + fprintf(stderr, "\n"); /* Need to move to next line */ + } + PQclear(res); + + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, _("%s: final receive failed: %s\n"), + progname, PQerrorMessage(conn)); + disconnect_and_exit(1); + } + + /* + * End of copy data. Final result is already checked inside the loop. + */ + PQfinish(conn); + + if (verbose) + fprintf(stderr, "%s: base backup completed.\n", progname); +} + + +int +main(int argc, char **argv) +{ + static struct option long_options[] = { + {"help", no_argument, NULL, '?'}, + {"version", no_argument, NULL, 'V'}, + {"pgdata", required_argument, NULL, 'D'}, + {"format", required_argument, NULL, 'F'}, + {"checkpoint", required_argument, NULL, 'c'}, + {"compress", required_argument, NULL, 'Z'}, + {"label", required_argument, NULL, 'l'}, + {"host", required_argument, NULL, 'h'}, + {"port", required_argument, NULL, 'p'}, + {"username", required_argument, NULL, 'U'}, + {"no-password", no_argument, NULL, 'w'}, + {"password", no_argument, NULL, 'W'}, + {"verbose", no_argument, NULL, 'v'}, + {"progress", no_argument, NULL, 'P'}, + {NULL, 0, NULL, 0} + }; + int c; + + int option_index; + + progname = get_progname(argv[0]); + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup")); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(); + exit(0); + } + else if (strcmp(argv[1], "-V") == 0 + || strcmp(argv[1], "--version") == 0) + { + puts("pg_basebackup (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + while ((c = getopt_long(argc, argv, "D:F:l:Z:c:h:p:U:wWvP", + long_options, &option_index)) != -1) + { + switch (c) + { + case 'D': + basedir = xstrdup(optarg); + break; + case 'F': + if (strcmp(optarg, "p") == 0 || strcmp(optarg, "plain") == 0) + format = 'p'; + else if (strcmp(optarg, "t") == 0 || strcmp(optarg, "tar") == 0) + format = 't'; + else + { + fprintf(stderr, _("%s: invalid output format \"%s\", must be \"plain\" or \"tar\"\n"), + progname, optarg); + exit(1); + } + break; + case 'l': + label = xstrdup(optarg); + break; + case 'Z': + compresslevel = atoi(optarg); + if (compresslevel <= 0 || compresslevel > 9) + { + fprintf(stderr, _("%s: invalid compression level \"%s\"\n"), + progname, optarg); + exit(1); + } + break; + case 'c': + if (strcasecmp(optarg, "fast") == 0) + fastcheckpoint = true; + else if (strcasecmp(optarg, "spread") == 0) + fastcheckpoint = false; + else + { + fprintf(stderr, _("%s: invalid checkpoint argument \"%s\", must be \"fast\" or \"spread\"\n"), + progname, optarg); + exit(1); + } + break; + case 'h': + dbhost = xstrdup(optarg); + break; + case 'p': + if (atoi(optarg) <= 0) + { + fprintf(stderr, _("%s: invalid port number \"%s\"\n"), + progname, optarg); + exit(1); + } + dbport = xstrdup(optarg); + break; + case 'U': + dbuser = xstrdup(optarg); + break; + case 'w': + dbgetpassword = -1; + break; + case 'W': + dbgetpassword = 1; + break; + case 'v': + verbose++; + break; + case 'P': + showprogress = true; + break; + default: + + /* + * getopt_long already emitted a complaint + */ + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + } + + /* + * Any non-option arguments? + */ + if (optind < argc) + { + fprintf(stderr, + _("%s: too many command-line arguments (first is \"%s\")\n"), + progname, argv[optind]); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + /* + * Required arguments + */ + if (basedir == NULL) + { + fprintf(stderr, _("%s: no target directory specified\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + /* + * Mutually exclusive arguments + */ + if (format == 'p' && compresslevel > 0) + { + fprintf(stderr, + _("%s: only tar mode backups can be compressed\n"), + progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + +#ifndef HAVE_LIBZ + if (compresslevel > 0) + { + fprintf(stderr, + _("%s: this build does not support compression\n"), + progname); + exit(1); + } +#else + if (compresslevel > 0 && strcmp(basedir, "-") == 0) + { + fprintf(stderr, + _("%s: compression is not supported on standard output\n"), + progname); + exit(1); + } +#endif + + /* + * 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 (format == 'p' || strcmp(basedir, "-") != 0) + verify_dir_is_empty_or_create(basedir); + + + BaseBackup(); + + return 0; +} diff --git a/src/include/replication/basebackup.h b/src/include/replication/basebackup.h index eb2e1601768a50745f409d3f9b5dcaee63e08106..80f814b2e7c6e6b11d4c5a1ddf815cb831fc3ed5 100644 --- a/src/include/replication/basebackup.h +++ b/src/include/replication/basebackup.h @@ -12,6 +12,6 @@ #ifndef _BASEBACKUP_H #define _BASEBACKUP_H -extern void SendBaseBackup(const char *backup_label, bool progress); +extern void SendBaseBackup(const char *backup_label, bool progress, bool fastcheckpoint); #endif /* _BASEBACKUP_H */ diff --git a/src/include/replication/replnodes.h b/src/include/replication/replnodes.h index 4f4a1a3bac31f7a6b240c48e2c4cd37b22f5b417..fc814146baf895e7674cb51996143b4fc4b46085 100644 --- a/src/include/replication/replnodes.h +++ b/src/include/replication/replnodes.h @@ -47,6 +47,7 @@ typedef struct BaseBackupCmd NodeTag type; char *label; bool progress; + bool fastcheckpoint; } BaseBackupCmd; diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 310378b0ddcaf86bb46b71697ca37634ad50ffdb..b73271e71222919e7356cd0ccd869e20863510d2 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -275,6 +275,8 @@ sub mkvcbuild $initdb->AddLibrary('wsock32.lib'); $initdb->AddLibrary('ws2_32.lib'); + my $pgbasebackup = AddSimpleFrontend('pg_basebackup', 1); + my $pgconfig = AddSimpleFrontend('pg_config'); my $pgcontrol = AddSimpleFrontend('pg_controldata');