diff -durpN postgresql.3/doc/src/sgml/ref/pg_basebackup.sgml postgresql.4/doc/src/sgml/ref/pg_basebackup.sgml
--- postgresql.3/doc/src/sgml/ref/pg_basebackup.sgml 2012-11-08 13:13:04.151630632 +0100
+++ postgresql.4/doc/src/sgml/ref/pg_basebackup.sgml 2012-11-22 14:57:20.353717277 +0100
@@ -189,6 +189,21 @@ PostgreSQL documentation
+
+
+
+
+
+ Write a minimal recovery.conf into the output directory (or into
+ the base archive file if was specified)
+ using the connection parameters from the command line to ease
+ setting up the standby.
+
+
+
+
+
+
diff -durpN postgresql.3/src/bin/pg_basebackup/pg_basebackup.c postgresql.4/src/bin/pg_basebackup/pg_basebackup.c
--- postgresql.3/src/bin/pg_basebackup/pg_basebackup.c 2012-11-22 14:55:52.199178432 +0100
+++ postgresql.4/src/bin/pg_basebackup/pg_basebackup.c 2012-11-22 14:57:20.355717289 +0100
@@ -19,6 +19,7 @@
#define FRONTEND 1
#include "postgres.h"
#include "libpq-fe.h"
+#include "pqexpbuffer.h"
#include
#include
@@ -46,6 +47,7 @@ int compresslevel = 0;
bool includewal = false;
bool streamwal = false;
bool fastcheckpoint = false;
+bool writerecoveryconf = false;
int standby_message_timeout = 10 * 1000; /* 10 sec = default */
/* Progress counters */
@@ -70,6 +72,8 @@ static int has_xlogendptr = 0;
static volatile LONG has_xlogendptr = 0;
#endif
+PQExpBuffer rcExpBuf = NULL;
+
/* Function headers */
static void usage(void);
static void verify_dir_is_empty_or_create(char *dirname);
@@ -77,6 +81,8 @@ static void progress_report(int tablespa
static void ReceiveTarFile(PGconn *conn, PGresult *res, int rownum);
static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum);
+static void CreateRecoveryConf(PGconn *conn);
+static void WriteRecoveryConf(void);
static void BaseBackup(void);
static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline,
@@ -107,6 +113,8 @@ usage(void)
printf(_("\nOptions controlling the output:\n"));
printf(_(" -D, --pgdata=DIRECTORY receive base backup into directory\n"));
printf(_(" -F, --format=p|t output format (plain (default), tar)\n"));
+ printf(_(" -R, --write-recovery-conf\n"
+ " write recovery.conf after backup\n"));
printf(_(" -x, --xlog include required WAL files in backup (fetch mode)\n"));
printf(_(" -X, --xlog-method=fetch|stream\n"
" include required WAL files with specified method\n"));
@@ -452,6 +460,45 @@ progress_report(int tablespacenum, const
/*
+ * Write a piece of tar data
+ */
+static void
+writeTarData(
+#ifdef HAVE_LIBZ
+ gzFile ztarfile,
+#endif
+ FILE *tarfile, char *buf, int r, char *current_file)
+{
+#ifdef HAVE_LIBZ
+ if (ztarfile != NULL)
+ {
+ if (gzwrite(ztarfile, buf, r) != r)
+ {
+ fprintf(stderr,
+ _("%s: could not write to compressed file \"%s\": %s\n"),
+ progname, current_file, get_gz_error(ztarfile));
+ disconnect_and_exit(1);
+ }
+ }
+ else
+#endif
+ {
+ if (fwrite(buf, r, 1, tarfile) != 1)
+ {
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
+ progname, current_file, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ }
+}
+
+#ifdef HAVE_LIBZ
+#define WRITE_TAR_DATA(buf, sz) writeTarData(ztarfile, tarfile, buf, sz, filename)
+#else
+#define WRITE_TAR_DATA(buf, sz) writeTarData(tarfile, buf, sz, filename)
+#endif
+
+/*
* 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.
@@ -467,12 +514,17 @@ ReceiveTarFile(PGconn *conn, PGresult *r
char filename[MAXPGPATH];
char *copybuf = NULL;
FILE *tarfile = NULL;
+ char tarhdr[TARCHUNKSZ];
+ bool basetablespace = PQgetisnull(res, rownum, 0);
+ bool in_tarhdr, skip_file;
+ size_t tarhdrsz;
+ uint64 filesz;
#ifdef HAVE_LIBZ
gzFile ztarfile = NULL;
#endif
- if (PQgetisnull(res, rownum, 0))
+ if (basetablespace)
{
/*
* Base tablespaces
@@ -584,6 +636,16 @@ ReceiveTarFile(PGconn *conn, PGresult *r
disconnect_and_exit(1);
}
+ /*
+ * Initialize our variables for tracking
+ * individual files inside the TAR stream.
+ * For more detailed explanation, see below.
+ */
+ in_tarhdr = true;
+ skip_file = false;
+ tarhdrsz = 0;
+ filesz = 0;
+
while (1)
{
int r;
@@ -598,38 +660,39 @@ ReceiveTarFile(PGconn *conn, PGresult *r
if (r == -1)
{
/*
- * End of chunk. Close file (but not stdout).
+ * End of chunk. Write recovery.conf into the tar file (if it
+ * was requested) and 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];
+ char zerobuf[2*TARCHUNKSZ];
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, filename, get_gz_error(ztarfile));
- disconnect_and_exit(1);
- }
- }
- else
-#endif
+
+ if (basetablespace && writerecoveryconf)
{
- if (fwrite(zerobuf, sizeof(zerobuf), 1, tarfile) != 1)
- {
- fprintf(stderr,
- _("%s: could not write to file \"%s\": %s\n"),
- progname, filename, strerror(errno));
- disconnect_and_exit(1);
- }
+ char header[TARCHUNKSZ];
+ int padding;
+
+ _tarCreateHeader(header, "recovery.conf", NULL,
+ rcExpBuf->len,
+ 0600, 04000, 02000,
+ time(NULL));
+
+ padding = ((rcExpBuf->len + (TARCHUNKSZ-1)) & ~(TARCHUNKSZ-1)) - rcExpBuf->len;
+
+ WRITE_TAR_DATA(header, sizeof(header));
+ WRITE_TAR_DATA(rcExpBuf->data, rcExpBuf->len);
+ if (padding)
+ WRITE_TAR_DATA(zerobuf, padding);
+
+ if (verbose)
+ fprintf(stderr, _("%s: recovery.conf written into '%s'\n"), progname, filename);
}
+ WRITE_TAR_DATA(zerobuf, sizeof(zerobuf));
+
#ifdef HAVE_LIBZ
if (ztarfile != NULL)
{
@@ -665,25 +728,124 @@ ReceiveTarFile(PGconn *conn, PGresult *r
disconnect_and_exit(1);
}
-#ifdef HAVE_LIBZ
- if (ztarfile != NULL)
+ if (!writerecoveryconf || !basetablespace)
{
- if (gzwrite(ztarfile, copybuf, r) != r)
- {
- fprintf(stderr,
- _("%s: could not write to compressed file \"%s\": %s\n"),
- progname, filename, get_gz_error(ztarfile));
- disconnect_and_exit(1);
- }
+ /*
+ * If --write-recovery-conf was not requested or this
+ * is not the base tablespace, simply pass the received
+ * data into the TAR file, either compressed or not.
+ */
+
+ WRITE_TAR_DATA(copybuf, r);
}
else
-#endif
{
- if (fwrite(copybuf, r, 1, tarfile) != 1)
+ /*
+ * If --write-recovery-conf was requested AND this
+ * is the base tablespace, the TAR stream may contain
+ * a recovery.conf file if the backup is coming from
+ * a standby server. We have to skip this file in
+ * the stream and add a new one constructed by
+ * CreateRecoveryConf() at the end of the stream.
+ *
+ * To do this, we have to process the individual files
+ * inside the TAR stream. The stream consists of a header
+ * and zero or more chunks, all 512 bytes long. The stream
+ * from the server is broken up into smaller pieces, so
+ * we have to track the size of the files to find the next
+ * header structure.
+ */
+ int rr = r; /* Save the value returned by PQgetCopyData */
+ int pos = 0;
+
+ while (rr > 0)
{
- fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
- progname, filename, strerror(errno));
- disconnect_and_exit(1);
+ if (in_tarhdr)
+ {
+ /*
+ * We're currently reading a header structure
+ * inside the TAR stream, i.e. the file metadata.
+ */
+ if (tarhdrsz < TARCHUNKSZ)
+ {
+ /*
+ * Copy the header structure into tarhdr[]
+ * in case the header is not aligned to 512 bytes
+ * or it's not returned in whole by the last
+ * PQgetCopyData call.
+ */
+ int hdrleft, bytes2copy;
+
+ hdrleft = TARCHUNKSZ - tarhdrsz;
+ bytes2copy = (rr > hdrleft ? hdrleft : rr);
+
+ memcpy(&tarhdr[tarhdrsz], copybuf + pos, bytes2copy);
+
+ rr -= bytes2copy;
+ pos += bytes2copy;
+ tarhdrsz += bytes2copy;
+ }
+ else
+ {
+ /*
+ * We have the whole header structure in tarhdr[],
+ * look at the file metadata:
+ * - the subsequent file contents have to be skipped
+ * if the filename is recovery.conf
+ * - find out the size of the file padded to the next
+ * multiple of 512
+ */
+ int64 padding;
+
+ skip_file = (strcmp(&tarhdr[0], "recovery.conf") == 0);
+
+ scan_val(&tarhdr[124], &filesz, 8, 11);
+
+ padding = ((filesz + (TARCHUNKSZ-1)) & ~(TARCHUNKSZ-1)) - filesz;
+ filesz += padding;
+
+ /* Indicate that the subsequent data is the file content. */
+ in_tarhdr = false;
+
+ if (!skip_file)
+ WRITE_TAR_DATA(tarhdr, TARCHUNKSZ);
+ }
+ }
+ else
+ {
+ /*
+ * We're processing a file's contents.
+ */
+ if (filesz > 0)
+ {
+ /*
+ * We still have data to read (and possibly write).
+ */
+ int bytes2write;
+
+ bytes2write = (filesz > rr ? rr : filesz);
+
+ if (!skip_file)
+ WRITE_TAR_DATA(copybuf + pos, bytes2write);
+
+ rr -= bytes2write;
+ pos += bytes2write;
+ filesz -= bytes2write;
+ }
+ else
+ {
+ /*
+ * No more data in the current file,
+ * the next piece of data (if any) will
+ * be a new file header structure.
+ * Reinitialize all our variables.
+ */
+ in_tarhdr = true;
+ skip_file = false;
+ tarhdrsz = 0;
+ filesz = 0;
+ }
+ }
}
}
totaldone += r;
@@ -712,10 +874,11 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
char filename[MAXPGPATH];
int current_len_left;
int current_padding = 0;
+ bool basetablespace = PQgetisnull(res, rownum, 0);
char *copybuf = NULL;
FILE *file = NULL;
- if (PQgetisnull(res, rownum, 0))
+ if (basetablespace)
strcpy(current_path, basedir);
else
strcpy(current_path, PQgetvalue(res, rownum, 1));
@@ -767,13 +930,13 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
/*
* No current file, so this must be the header for a new file
*/
- if (r != 512)
+ if (r != TARCHUNKSZ)
{
fprintf(stderr, _("%s: invalid tar block header size: %d\n"),
progname, r);
disconnect_and_exit(1);
}
- totaldone += 512;
+ totaldone += TARCHUNKSZ;
if (sscanf(copybuf + 124, "%11o", ¤t_len_left) != 1)
{
@@ -794,7 +957,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
* All files are padded up to 512 bytes
*/
current_padding =
- ((current_len_left + 511) & ~511) - current_len_left;
+ ((current_len_left + (TARCHUNKSZ-1)) & ~(TARCHUNKSZ-1)) - current_len_left;
/*
* First part of header is zero terminated filename
@@ -937,6 +1100,114 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
if (copybuf != NULL)
PQfreemem(copybuf);
+
+ if (basetablespace)
+ WriteRecoveryConf();
+}
+
+/*
+ * Create the new recovery.conf in memory
+ */
+static void
+CreateRecoveryConf(PGconn *conn)
+{
+ PQconninfoOption *connOptions;
+ PQconninfoOption *option;
+
+ if (!writerecoveryconf)
+ return;
+
+ connOptions = PQconninfo(conn, PG_CONNINFO_NORMAL | PG_CONNINFO_PASSWORD);
+ if (connOptions == NULL)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ appendPQExpBufferStr(rcExpBuf, "standby_mode = 'on'\n");
+ if (PQExpBufferBroken(rcExpBuf))
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ appendPQExpBufferStr(rcExpBuf, "primary_conninfo = '");
+ if (PQExpBufferBroken(rcExpBuf))
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ for (option = connOptions; option && option->keyword; option++)
+ {
+ char *escaped;
+
+ /*
+ * Do not emit this setting if not set or empty.
+ */
+ if (strcmp(option->keyword, "dbname") != 0 ||
+ strcmp(option->keyword, "fallback_application_name") != 0 ||
+ (option->val == NULL) ||
+ (option->val != NULL && option->val[0] == '\0'))
+ continue;
+
+ /*
+ * Write "keyword='value'" pieces, the value string is escaped
+ * if necessary and doubled single quotes around the value string.
+ */
+ escaped = escape_quotes(option->val);
+
+ appendPQExpBuffer(rcExpBuf, "%s=''%s'' ", option->keyword, escaped);
+ if (PQExpBufferBroken(rcExpBuf))
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ free(escaped);
+ }
+
+ appendPQExpBufferStr(rcExpBuf, "'\n");
+ if (PQExpBufferBroken(rcExpBuf))
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ PQconninfoFree(connOptions);
+}
+
+
+static void
+WriteRecoveryConf(void)
+{
+ char filename[MAXPGPATH];
+ FILE *cf;
+
+ if (!writerecoveryconf)
+ return;
+
+ sprintf(filename, "%s/recovery.conf", basedir);
+
+ cf = fopen(filename, "w");
+ if (cf == NULL)
+ {
+ fprintf(stderr, _("%s: cannot create %s: %s"), progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+
+ if (fwrite(rcExpBuf->data, rcExpBuf->len, 1, cf) != 1)
+ {
+ fprintf(stderr,
+ _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+
+ fclose(cf);
+
+ if (verbose)
+ fprintf(stderr, _("%s: recovery.conf written.\n"), progname);
}
@@ -960,6 +1231,15 @@ BaseBackup(void)
/* Error message already written in GetConnection() */
exit(1);
+ rcExpBuf = createPQExpBuffer();
+ if (!rcExpBuf)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ CreateRecoveryConf(conn);
+
/*
* Run IDENTIFY_SYSTEM so we can get the timeline
*/
@@ -1223,6 +1503,9 @@ BaseBackup(void)
#endif
}
+ /* Free the recovery.conf contents */
+ destroyPQExpBuffer(rcExpBuf);
+
/*
* End of copy data. Final result is already checked inside the loop.
*/
@@ -1243,6 +1526,7 @@ main(int argc, char **argv)
{"pgdata", required_argument, NULL, 'D'},
{"format", required_argument, NULL, 'F'},
{"checkpoint", required_argument, NULL, 'c'},
+ {"write-recovery-conf", no_argument, NULL, 'R'},
{"xlog", no_argument, NULL, 'x'},
{"xlog-method", required_argument, NULL, 'X'},
{"gzip", no_argument, NULL, 'z'},
@@ -1282,7 +1566,7 @@ main(int argc, char **argv)
}
}
- while ((c = getopt_long(argc, argv, "D:F:xX:l:zZ:c:h:p:U:s:wWvP",
+ while ((c = getopt_long(argc, argv, "D:F:RxX:l:zZ:c:h:p:U:s:wWvP",
long_options, &option_index)) != -1)
{
switch (c)
@@ -1303,6 +1587,9 @@ main(int argc, char **argv)
exit(1);
}
break;
+ case 'R':
+ writerecoveryconf = true;
+ break;
case 'x':
if (includewal)
{