02-pg_basebackup-v10.patch
text/x-patch
Filename: 02-pg_basebackup-v10.patch
Type: text/x-patch
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v10
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/pg_basebackup.sgml | 31 | 0 |
| src/bin/pg_basebackup/pg_basebackup.c | 341 | 2 |
diff -durpN postgresql.1/doc/src/sgml/ref/pg_basebackup.sgml postgresql.2/doc/src/sgml/ref/pg_basebackup.sgml
--- postgresql.1/doc/src/sgml/ref/pg_basebackup.sgml 2012-08-24 09:49:22.960530329 +0200
+++ postgresql.2/doc/src/sgml/ref/pg_basebackup.sgml 2012-10-14 13:09:58.913513268 +0200
@@ -189,6 +189,37 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>-R</option></term>
+ <term><option>--write-recovery-conf</option></term>
+ <listitem>
+
+ <para>
+ Write a minimal recovery.conf into the output directory (or into
+ the base archive file if <option>--format=tar</option> was specified)
+ using the connection parameters from the command line to ease
+ setting up the standby. Since creating a backup for a standalone
+ server with <option>-x</option> or <option>-X</option> and adding
+ a recovery.conf to it wouldn't make a working standby, these options
+ naturally conflict.
+ </para>
+
+ <para>
+ The password written into recovery.conf is not escaped even if special
+ characters appear in it. The administrator must review recovery.conf
+ to ensure proper escaping.
+ </para>
+
+ <para>
+ When this option is specified and taking the base backup succeeded,
+ failing to write the <filename>recovery.conf</filename> results
+ in the error code 2. This way, scripts can distinguish between different
+ failure cases of <application>pg_basebackup</application>.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-x</option></term>
<term><option>--xlog</option></term>
<listitem>
diff -durpN postgresql.1/src/bin/pg_basebackup/pg_basebackup.c postgresql.2/src/bin/pg_basebackup/pg_basebackup.c
--- postgresql.1/src/bin/pg_basebackup/pg_basebackup.c 2012-10-03 10:40:48.297207389 +0200
+++ postgresql.2/src/bin/pg_basebackup/pg_basebackup.c 2012-10-14 13:07:10.303472940 +0200
@@ -25,6 +25,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <time.h>
#ifdef HAVE_LIBZ
#include <zlib.h>
@@ -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,10 @@ static int has_xlogendptr = 0;
static volatile LONG has_xlogendptr = 0;
#endif
+#define RCBUFSZ (1024)
+static size_t recoveryconf_length = 0;
+static char *recoveryconf = NULL;
+
/* Function headers */
static void usage(void);
static void verify_dir_is_empty_or_create(char *dirname);
@@ -77,6 +83,13 @@ 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 _reallocRCBuffer(char **buf, int *bufsz, int currsz,
+ PQconninfoOption *option, int extrasz);
+static void CreateRecoveryConf(PGconn *conn);
+static void WriteRecoveryConf(void);
+static int _tarChecksum(char *header);
+static void print_val(char *s, uint64 val, unsigned int base, size_t len);
+static void _tarCreateHeader(char *header);
static void BaseBackup(void);
static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline,
@@ -107,6 +120,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"));
@@ -598,13 +613,66 @@ 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];
+ if (rownum == 0 && writerecoveryconf)
+ {
+ char header[512];
+
+ _tarCreateHeader(header);
+#ifdef HAVE_LIBZ
+ if (ztarfile != NULL)
+ {
+ if (gzwrite(ztarfile, header, sizeof(header)) !=
+ sizeof(header))
+ {
+ fprintf(stderr,
+ _("%s: could not write to compressed file \"%s\": %s\n"),
+ progname, filename, get_gz_error(ztarfile));
+ disconnect_and_exit(1);
+ }
+
+ if (gzwrite(ztarfile, recoveryconf, recoveryconf_length) !=
+ recoveryconf_length)
+ {
+ 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 (fwrite(header, sizeof(header), 1, tarfile) != 1)
+ {
+ fprintf(stderr,
+ _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+
+ if (fwrite(recoveryconf, recoveryconf_length, 1, tarfile) != 1)
+ {
+ fprintf(stderr,
+ _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ }
+ fprintf(stderr, _("%s: recovery.conf written in to the base backup. "
+ "Please, add escaping to the password field in "
+ "\"primary_conninfo\" if needed.\n"), progname);
+
+ free(recoveryconf);
+ }
+
MemSet(zerobuf, 0, sizeof(zerobuf));
#ifdef HAVE_LIBZ
if (ztarfile != NULL)
@@ -937,6 +1005,264 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
if (copybuf != NULL)
PQfreemem(copybuf);
+
+ if (rownum == 0)
+ WriteRecoveryConf();
+}
+
+static int
+_tarChecksum(char *header)
+{
+ int i,
+ sum;
+
+ /*
+ * Per POSIX, the checksum is the simple sum of all bytes in the header,
+ * treating the bytes as unsigned, and treating the checksum field (at
+ * offset 148) as though it contained 8 spaces.
+ */
+ sum = 8 * ' '; /* presumed value for checksum field */
+ for (i = 0; i < 512; i++)
+ if (i < 148 || i >= 156)
+ sum += 0xFF & header[i];
+ return sum;
+}
+
+
+/*
+ * Utility routine to print possibly larger than 32 bit integers in a
+ * portable fashion. Filled with zeros.
+ */
+static void
+print_val(char *s, uint64 val, unsigned int base, size_t len)
+{
+ int i;
+
+ for (i = len; i > 0; i--)
+ {
+ int digit = val % base;
+
+ s[i - 1] = '0' + digit;
+ val = val / base;
+ }
+}
+
+
+static void
+_tarCreateHeader(char *header)
+{
+ /*
+ * Note: most of the fields in a tar header are not supposed to be
+ * null-terminated. We use sprintf, which will write a null after the
+ * required bytes; that null goes into the first byte of the next field.
+ * This is okay as long as we fill the fields in order.
+ */
+ memset(header, 0, 512 /* sizeof the tar header */);
+
+ /* Name 100 */
+ sprintf(&header[0], "%.99s", "recovery.conf");
+
+ /* Mode 8 */
+ sprintf(&header[100], "0000600 ");
+
+ /* User ID 8 */
+ sprintf(&header[108], "0004000 ");
+
+ /* Group 8 */
+ sprintf(&header[116], "0002000 ");
+
+ /* File size 12 - 11 digits, 1 space; use print_val for 64 bit support */
+ print_val(&header[124], recoveryconf_length, 8, 11);
+ sprintf(&header[135], " ");
+
+ /* Mod Time 12 */
+ sprintf(&header[136], "%011o ", (int) time(NULL));
+
+ /* Checksum 8 cannot be calculated until we've filled all other fields */
+
+ /* Type - regular file */
+ sprintf(&header[156], "0");
+
+ /* Link Name 100 (leave as nulls) */
+
+ /* Magic 6 */
+ sprintf(&header[257], "ustar");
+
+ /* Version 2 */
+ sprintf(&header[263], "00");
+
+ /* User 32 */
+ /* XXX: Do we need to care about setting correct username? */
+ sprintf(&header[265], "%.31s", "postgres");
+
+ /* Group 32 */
+ /* XXX: Do we need to care about setting correct group name? */
+ sprintf(&header[297], "%.31s", "postgres");
+
+ /* Major Dev 8 */
+ sprintf(&header[329], "%07o ", 0);
+
+ /* Minor Dev 8 */
+ sprintf(&header[337], "%07o ", 0);
+
+ /* Prefix 155 - not used, leave as nulls */
+
+ /*
+ * We mustn't overwrite the next field while inserting the checksum.
+ * Fortunately, the checksum can't exceed 6 octal digits, so we just write
+ * 6 digits, a space, and a null, which is legal per POSIX.
+ */
+ sprintf(&header[148], "%06o ", _tarChecksum(header));
+}
+
+/*
+ * Reallocate the buffer for recovery.conf if needed
+ */
+static void
+_reallocRCBuffer(char **buf, int *bufsz, int currsz, PQconninfoOption *option, int extrasz)
+{
+ int kw_val_len;
+
+ if (option != NULL)
+ kw_val_len = strlen(option->keyword) + strlen(option->val);
+ else
+ kw_val_len = 0;
+
+ if (*bufsz < currsz + kw_val_len + extrasz)
+ {
+ char *tmp;
+
+ tmp = realloc(*buf, *bufsz + RCBUFSZ);
+ if (tmp == NULL)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1)
+ }
+ *buf = tmp;
+ *bufsz += RCBUFSZ;
+ }
+}
+
+/*
+ * Try to create recovery.conf in memory and set the length to write later.
+ */
+static void
+CreateRecoveryConf(PGconn *conn)
+{
+ char *buf = NULL;
+ PQconninfoOption *connOptions;
+ PQconninfoOption *option;
+ int bufsz;
+ int total = 0;
+ int written;
+
+ if (!writerecoveryconf)
+ return;
+
+ connOptions = PQconninfo(conn, true);
+ if (connOptions == NULL)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+
+ bufsz = RCBUFSZ;
+ buf = malloc(bufsz);
+ if (buf == NULL)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1);
+ }
+
+ written = sprintf(&buf[total], "standby_mode = 'on'\n");
+ if (written < 0)
+ {
+ fprintf(stderr, _("%s: cannot write to string: %s"), progname, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ total += written;
+
+ written = sprintf(&buf[total], "primary_conninfo = '");
+ if (written < 0)
+ {
+ fprintf(stderr, _("%s: cannot write to string: %s"), progname, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ total += written;
+
+ for (option = connOptions; option && option->keyword; option++)
+ {
+ /*
+ * Do not emit this setting if not set, empty or default.
+ * The list of options was already pre-filtered for options
+ * usable for replication with PQconninfo(conn, true).
+ */
+ if (option->val == NULL ||
+ (option->val != NULL && option->val[0] == '\0') ||
+ (option->val &&
+ option->compiled &&
+ strcmp(option->val, option->compiled) == 0))
+ continue;
+
+ /* Write "keyword='value'" pieces, single quotes doubled */
+ _reallocRCBuffer(&buf, &bufsz, total, option, 6);
+ written = sprintf(&buf[total], "%s=''%s'' ", option->keyword, option->val);
+ if (written < 0)
+ {
+ fprintf(stderr, _("%s: cannot write to string: %s"), progname, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ total += written;
+ }
+
+ _reallocRCBuffer(&buf, &bufsz, total, NULL, 2);
+ written = sprintf(&buf[total], "'\n");
+ if (written < 0)
+ {
+ fprintf(stderr, _("%s: cannot write to string: %s"), progname, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ total += written;
+
+ recoveryconf = buf;
+ recoveryconf_length = total;
+
+ 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(recoveryconf, recoveryconf_length, 1, cf) != 1)
+ {
+ fprintf(stderr,
+ _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+
+ fclose(cf);
+
+ fprintf(stderr, _("%s: recovery.conf written. "
+ "Please, add escaping to the password field in "
+ "\"primary_conninfo\" if needed.\n"), progname);
}
@@ -960,6 +1286,8 @@ BaseBackup(void)
/* Error message already written in GetConnection() */
exit(1);
+ CreateRecoveryConf(conn);
+
/*
* Run IDENTIFY_SYSTEM so we can get the timeline
*/
@@ -1243,6 +1571,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'},
@@ -1280,7 +1609,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)
@@ -1301,6 +1630,9 @@ main(int argc, char **argv)
exit(1);
}
break;
+ case 'R':
+ writerecoveryconf = true;
+ break;
case 'x':
if (includewal)
{
@@ -1466,6 +1798,13 @@ main(int argc, char **argv)
}
#endif
+ if (writerecoveryconf && includewal)
+ {
+ fprintf(stderr,
+ _("--xlog and --writerecoveryconf are mutually exclusive\n"));
+ exit(1);
+ }
+
/*
* Verify that the target directory exists, or create it. For plaintext
* backups, always require the directory. For tar backups, require it