On Sat, Jan 15, 2011 at 23:10, Dimitri Fontaine <dimi...@2ndquadrant.fr> wrote:
>>> That should be -D --pgdata, for consistency with pg_dump.
>>
>> pg_dump doesn't have a -D. I assume you mean pg_ctl / initdb?
>
> Yes, sorry, been too fast.

Ok. Updated patch that includes this change attached. I also changed
the tar directory from -t to -T, for consistency.

It also includes the change to take -h host, -U user, -w/-W for
password -p port instead of a conninfo string.


>> Another option, I think Heikki mentioned this on IM at some point, is
>> to do something like name it <oid>-<name>.tar. That would give us best
>> of both worlds?
>
> Well I'd think we know the pg_tablespace columns encoding, so the
> problem might be the filesystem encodings, right?  Well there's also the

Do we really? That's one of the global catalogs that don't really have
an encoding, isn't it?


> option of creating <oid>.tar and have a symlink to it called <name>.tar
> but that's pushing it.  I don't think naming after OIDs is a good
> service for users, but if that's all we can reasonably do…

Yeah, symlink seems to be making things way too complex. <oid>-<name>
seems is perhaps a reasonable compromise?


> Will continue reviewing and post something more polished and
> comprehensive next week — mainly wanted to see if you wanted to include
> pg_ctl <command> in the patch already.

Ok, thanks.

-- 
 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..dc8e2f4
--- /dev/null
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -0,0 +1,313 @@
+<!--
+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>-D <replaceable class="parameter">directory</replaceable></option></term>
+      <term><option>--pgdata=<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>
+    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>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..bb9dea0
--- /dev/null
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -0,0 +1,999 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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	   *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;
+
+/* 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:\n"));
+	printf(_("  -D, --pgdata=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(_("\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(_("\nOther options:\n"));
+	printf(_("  -?, --help                show this help, then exit\n"));
+	printf(_("  -V, --version             output version information, then exit\n"));
+	printf(_("\nReport bugs to <pgsql-b...@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));
+				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, &copybuf, 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, &copybuf, 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", &current_len_left) != 1)
+			{
+				fprintf(stderr, _("%s: could not parse file size!\n"),
+						progname);
+				exit(1);
+			}
+
+			/* Set permissions on the file */
+			if (sscanf(&copybuf[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(&copybuf[157], fn) != 0)
+					{
+						fprintf(stderr,
+								_("%s: could not create symbolic link from %s to %s: %m\n"),
+								progname, fn, &copybuf[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)
+{
+	PGconn	   *conn;
+	int			argcount = 3;	/* dbname, replication, 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";
+	i = 2;
+	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;
+		}
+
+		conn = PQconnectdbParams(keywords, values, true);
+		if (password)
+			free(password);
+
+		if (PQstatus(conn) == CONNECTION_BAD &&
+			PQconnectionNeedsPassword(conn) &&
+			dbgetpassword != -1)
+		{
+			dbgetpassword = 1;	/* ask for password next time */
+			continue;
+		}
+
+		if (PQstatus(conn) != CONNECTION_OK)
+		{
+			fprintf(stderr, _("%s: could not connect to server: %s\n"),
+					progname, PQerrorMessage(conn));
+			exit(1);
+		}
+
+		/* Connection ok! */
+		free(values);
+		free(keywords);
+		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'},
+		{"pgdata", required_argument, NULL, 'D'},
+		{"tardir", required_argument, NULL, 'T'},
+		{"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], "-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, "D:T:l:Z:h:p:U:wWvP",
+							long_options, &option_index)) != -1)
+	{
+		switch (c)
+		{
+			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 'h':
+				dbhost = xstrdup(optarg);
+				break;
+			case 'p':
+				if (atoi(optarg) == 0)
+				{
+					fprintf(stderr, _("%s: invalid port number \"%s\""),
+							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 + 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);
+	}
+
+	/*
+	 * 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

Reply via email to