This patch creates pg_basebackup in bin/, being a client program for the streaming base backup feature.
I think it's more or less done now. I've again split it out of pg_streamrecv, because it had very little shared code with that (basically just the PQconnectdb() wrapper). One thing I'm thinking about - right now the tool just takes -c <conninfo> to connect to the database. Should it instead be taught to take the connection parameters that for example pg_dump does - one for each of host, port, user, password? (shouldn't be hard to do..) -- Magnus Hagander Me: http://www.hagander.net/ Work: http://www.redpill-linpro.com/
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index db7c834..c14ae43 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -813,6 +813,16 @@ SELECT pg_stop_backup(); </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. When taking a base backup of an active database, this situation is normal diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index f40fa9d..c44d11e 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 0000000..42a714b --- /dev/null +++ b/doc/src/sgml/ref/pg_basebackup.sgml @@ -0,0 +1,249 @@ +<!-- +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 + server (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">). + </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> + <variablelist> + <varlistentry> + <term><option>-c <replaceable class="parameter">conninfo</replaceable></option></term> + <term><option>--conninfo=<replaceable class="parameter">conninfo</replaceable></option></term> + <listitem> + <para> + Specify the conninfo string used to connect to the server. For example: +<programlisting> +$ <userinput>pg_basebackup -c "host=192.168.0.2 user=backup"</userinput> +</programlisting> + See <xref linkend="libpq-connect"> for more information on all the + available connection options. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-d <replaceable class="parameter">directory</replaceable></option></term> + <term><option>--basedir=<replaceable class="parameter">directory</replaceable></option></term> + <listitem> + <para> + Directory to restore the base data directory to. When the cluster has + no additional tablespaces, the whole database will be placed in this + directory. If the cluster contains additional tablespaces, the main + data directory will be placed in this directory, but all other + tablespaces will be placed in the same absolute path as they have + on the server. + </para> + <para> + Only one of <literal>-d</> and <literal>-t</> can be specified. + </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>-t <replaceable class="parameter">directory</replaceable></option></term> + <term><option>--tardir=<replaceable class="parameter">directory</replaceable></option></term> + <listitem> + <para> + Directory to place tar format files in. When this is specified, the + backup will consist of a number of tar files, one for each tablespace + in the database, stored in this directory. The tar file for the main + data directory will be named <filename>base.tar</>, and all other + tablespaces will be named after the tablespace oid. + </para> + <para> + If the value <literal>-</> (dash) is specified as tar directory, + the tar contents will be written to standard output, suitable for + piping to for example <productname>gzip</>. This is only possible if + the cluster has no additional tablespaces. + </para> + <para> + Only one of <literal>-d</> and <literal>-t</> can be specified. + </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> + + <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> + 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>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 84babf6..6ee8e5b 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/bin/Makefile b/src/bin/Makefile index c18c05c..3809412 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 0000000..ccb1502 --- /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 0000000..760ee1d --- /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 0000000..7fcb20a --- /dev/null +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -0,0 +1,893 @@ +/*------------------------------------------------------------------------- + * + * pg_basebackup.c - receive a base backup using streaming replication protocol + * + * Author: Magnus Hagander <mag...@hagander.net> + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/bin/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 *tardir = NULL; +char *label = "pg_basebackup base backup"; +bool showprogress = false; +int verbose = 0; +int compresslevel = 0; +char *conninfo = NULL; + +/* Progress counters */ +static uint64 totalsize; +static uint64 totaldone; +static int tablespacecount; + +/* Function headers */ +static char *xstrdup(const char *s); +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() replacement that prints an error and exists 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 +usage(void) +{ + printf(_("%s takes base backups of running PostgreSQL servers\n\n"), + progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]...\n"), progname); + printf(_("\nOptions:\n")); + printf(_(" -c, --conninfo=conninfo connection info string to server\n")); + printf(_(" -d, --basedir=directory receive base backup into directory\n")); + printf(_(" -t, --tardir=directory receive base backup into tar files\n" + " stored in specified directory\n")); + printf(_(" -Z, --compress=0-9 compress tar output\n")); + printf(_(" -l, --label=label set backup label\n")); + printf(_(" -p, --progress show progress information\n")); + printf(_(" -v, --verbose output verbose messages\n")); + printf(_("\nOther options:\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_(" -V, --version output version information, then exit\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)); + 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); + exit(1); + case -1: + + /* + * Access problem + */ + fprintf(stderr, _("%s: could not access directory \"%s\": %s\n"), + progname, dirname, strerror(errno)); + exit(1); + } +} + + +/* + * Print a progress report based on the global variables. If verbose output + * is disabled, 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(tardir, "-") == 0) + tarfile = stdout; + else + { +#ifdef HAVE_LIBZ + if (compresslevel > 0) + { + snprintf(fn, sizeof(fn), "%s/base.tar.gz", tardir); + ztarfile = gzopen(fn, "wb"); + if (gzsetparams(ztarfile, compresslevel, Z_DEFAULT_STRATEGY) != Z_OK) + { + fprintf(stderr, _("%s: could not set compression level %i\n"), + progname, compresslevel); + exit(1); + } + } + else +#endif + { + snprintf(fn, sizeof(fn), "%s/base.tar", tardir); + tarfile = fopen(fn, "wb"); + } + } + else + { + /* + * Specific tablespace + */ +#ifdef HAVE_LIBZ + if (compresslevel > 0) + { + snprintf(fn, sizeof(fn), "%s/%s.tar.gz", tardir, PQgetvalue(res, rownum, 0)); + ztarfile = gzopen(fn, "wb"); + } + else +#endif + { + snprintf(fn, sizeof(fn), "%s/%s.tar", tardir, PQgetvalue(res, rownum, 0)); + tarfile = fopen(fn, "wb"); + } + } + +#ifdef HAVE_LIBZ + if (!tarfile && !ztarfile) +#else + if (!tarfile) +#endif + { + fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), + progname, fn, strerror(errno)); + exit(1); + } + + /* + * Get the COPY data stream + */ + res = PQgetResult(conn); + if (!res || PQresultStatus(res) != PGRES_COPY_OUT) + { + fprintf(stderr, _("%s: could not get COPY data stream: %s\n"), + progname, PQerrorMessage(conn)); + 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': %m\n"), + progname, fn); + exit(1); + } + } + + if (strcmp(tardir, "-") != 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)); + 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': %m\n"), + progname, fn); + 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 (!res || PQresultStatus(res) != PGRES_COPY_OUT) + { + fprintf(stderr, _("%s: could not get COPY data stream: %s\n"), + progname, PQerrorMessage(conn)); + 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)); + 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); + exit(1); + } + totaldone += 512; + + if (sscanf(copybuf + 124, "%11o", ¤t_len_left) != 1) + { + fprintf(stderr, _("%s: could not parse file size!\n"), + progname); + exit(1); + } + + /* Set permissions on the file */ + if (sscanf(©buf[100], "%07o ", &filemode) != 1) + { + fprintf(stderr, _("%s: could not parse file mode!\n"), + progname); + 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\": %m\n"), + progname, fn); + exit(1); + } +#ifndef WIN32 + if (chmod(fn, filemode)) + fprintf(stderr, _("%s: could not set permissions on directory '%s': %m\n"), + progname, fn); +#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: %m\n"), + progname, fn, ©buf[157]); + exit(1); + } + } + else + { + fprintf(stderr, _("%s: unknown link indicator '%c'\n"), + progname, copybuf[156]); + exit(1); + } + continue; /* directory or link handled */ + } + + /* + * regular file + */ + file = fopen(fn, "wb"); + if (!file) + { + fprintf(stderr, _("%s: could not create file '%s': %m\n"), + progname, fn); + exit(1); + } + +#ifndef WIN32 + if (chmod(fn, filemode)) + fprintf(stderr, _("%s: could not set permissions on file '%s': %m\n"), + progname, fn); +#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': %m\n"), + progname, fn); + 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); + exit(1); + } + + if (copybuf != NULL) + PQfreemem(copybuf); +} + + +static PGconn * +GetConnection(void) +{ + char buf[MAXPGPATH]; + PGconn *conn; + + snprintf(buf, sizeof(buf), "%s dbname=replication replication=true", conninfo); + + if (verbose) + fprintf(stderr, _("%s: Connecting to \"%s\"\n"), progname, buf); + + conn = PQconnectdb(buf); + if (!conn || PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, _("%s: could not connect to server: %s\n"), + progname, PQerrorMessage(conn)); + exit(1); + } + + return conn; +} + +static void +BaseBackup() +{ + PGconn *conn; + 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", + escaped_label, + showprogress ? "PROGRESS" : ""); + + if (PQsendQuery(conn, current_path) == 0) + { + fprintf(stderr, _("%s: coult not start base backup: %s\n"), + progname, PQerrorMessage(conn)); + PQfinish(conn); + exit(1); + } + + /* + * Get the header + */ + res = PQgetResult(conn); + if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, _("%s: could not initiate base backup: %s\n"), + progname, PQerrorMessage(conn)); + PQfinish(conn); + exit(1); + } + if (PQntuples(res) < 1) + { + fprintf(stderr, _("%s: no data returned from server.\n"), progname); + PQfinish(conn); + 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 (basedir != NULL && i > 0) + verify_dir_is_empty_or_create(PQgetvalue(res, i, 1)); + } + + /* + * When writing to stdout, require a single tablespace + */ + if (tardir != NULL && strcmp(tardir, "-") == 0 && PQntuples(res) > 1) + { + fprintf(stderr, _("%s: can only write single tablespace to stdout, database has %i.\n"), + progname, PQntuples(res)); + PQfinish(conn); + exit(1); + } + + /* + * Start receiving chunks + */ + for (i = 0; i < PQntuples(res); i++) + { + if (tardir != NULL) + 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 (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, _("%s: final receive failed: %s\n"), + progname, PQerrorMessage(conn)); + 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'}, + {"conninfo", required_argument, NULL, 'c'}, + {"basedir", required_argument, NULL, 'd'}, + {"tardir", required_argument, NULL, 't'}, + {"compress", required_argument, NULL, 'Z'}, + {"label", required_argument, NULL, 'l'}, + {"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], "-h") == 0 || 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, "c:d:t:l:Z:vp", + long_options, &option_index)) != -1) + { + switch (c) + { + case 'c': + conninfo = xstrdup(optarg); + break; + case 'd': + basedir = xstrdup(optarg); + break; + case 't': + tardir = xstrdup(optarg); + break; + case 'l': + label = xstrdup(optarg); + break; + case 'Z': + compresslevel = atoi(optarg); + 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 + 1]); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + /* + * Required arguments + */ + if (basedir == NULL && tardir == NULL) + { + fprintf(stderr, _("%s: no target directory specified\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + if (conninfo == NULL) + { + fprintf(stderr, _("%s: no conninfo string specified\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + /* + * Mutually exclusive arguments + */ + if (basedir != NULL && tardir != NULL) + { + fprintf(stderr, + _("%s: both directory mode and tar mode cannot be specified\n"), + progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + if (basedir != NULL && 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(tardir, "-") == 0) + { + fprintf(stderr, + _("%s: compression is not supported on standard output\n"), + progname); + exit(1); + } +#endif + + /* + * Verify directories + */ + if (basedir) + verify_dir_is_empty_or_create(basedir); + else if (strcmp(tardir, "-") != 0) + verify_dir_is_empty_or_create(tardir); + + + + BaseBackup(); + + return 0; +} diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 29c3c77..40fb130 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -273,6 +273,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');
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers