02-pg_basebackup-v13.patch
text/x-patch
Filename: 02-pg_basebackup-v13.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 v13
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/pg_basebackup.sgml | 31 | 0 |
| src/bin/pg_basebackup/pg_basebackup.c | 543 | 14 |
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 22:29:07.243890976 +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,11 @@ static int has_xlogendptr = 0;
static volatile LONG has_xlogendptr = 0;
#endif
+#define TARCHUNKSZ (512)
+#define RCBUFSZ TARCHUNKSZ
+static char *recoveryconf = NULL;
+static size_t recoveryconflen = 0;
+
/* Function headers */
static void usage(void);
static void verify_dir_is_empty_or_create(char *dirname);
@@ -77,6 +84,15 @@ 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,
+ char *keyword, char *value,
+ 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 scan_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 +123,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"));
@@ -467,6 +485,11 @@ ReceiveTarFile(PGconn *conn, PGresult *r
char filename[MAXPGPATH];
char *copybuf = NULL;
FILE *tarfile = NULL;
+ char tarhdr[512];
+ bool basebackup;
+ bool in_tarhdr, skip_file;
+ size_t tarhdrsz;
+ uint64 filesz;
#ifdef HAVE_LIBZ
gzFile ztarfile = NULL;
@@ -519,6 +542,8 @@ ReceiveTarFile(PGconn *conn, PGresult *r
tarfile = fopen(filename, "wb");
}
}
+
+ basebackup = true;
}
else
{
@@ -547,6 +572,8 @@ ReceiveTarFile(PGconn *conn, PGresult *r
PQgetvalue(res, rownum, 0));
tarfile = fopen(filename, "wb");
}
+
+ basebackup = false;
}
#ifdef HAVE_LIBZ
@@ -584,6 +611,10 @@ ReceiveTarFile(PGconn *conn, PGresult *r
disconnect_and_exit(1);
}
+ in_tarhdr = true;
+ skip_file = false;
+ tarhdrsz = filesz = 0;
+
while (1)
{
int r;
@@ -598,13 +629,69 @@ 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 (basebackup && writerecoveryconf)
+ {
+ char header[512];
+ int lastchunk;
+
+ _tarCreateHeader(header);
+
+ lastchunk = recoveryconflen % TARCHUNKSZ;
+ if (lastchunk > 0)
+ recoveryconflen += (TARCHUNKSZ - lastchunk);
+#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, recoveryconflen) !=
+ recoveryconflen)
+ {
+ 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, recoveryconflen, 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 into '%s'\n"), progname, filename);
+
+ free(recoveryconf);
+ }
+
MemSet(zerobuf, 0, sizeof(zerobuf));
#ifdef HAVE_LIBZ
if (ztarfile != NULL)
@@ -665,25 +752,130 @@ ReceiveTarFile(PGconn *conn, PGresult *r
disconnect_and_exit(1);
}
-#ifdef HAVE_LIBZ
- if (ztarfile != NULL)
+ if (basebackup && writerecoveryconf)
{
- if (gzwrite(ztarfile, copybuf, r) != r)
+ int rr = r;
+ int pos = 0;
+
+ while (rr > 0)
{
- fprintf(stderr,
- _("%s: could not write to compressed file \"%s\": %s\n"),
- progname, filename, get_gz_error(ztarfile));
- disconnect_and_exit(1);
+ if (in_tarhdr)
+ {
+ if (tarhdrsz < 512)
+ {
+ int hdrleft, bytes2copy;
+
+ hdrleft = TARCHUNKSZ - tarhdrsz;
+ bytes2copy = (rr > hdrleft ? hdrleft : rr);
+
+ memcpy(&tarhdr[tarhdrsz], copybuf + pos, bytes2copy);
+
+ rr -= bytes2copy;
+ pos += bytes2copy;
+ tarhdrsz += bytes2copy;
+ }
+ else
+ {
+ int lastchunk;
+
+ skip_file = (strcmp(&tarhdr[0], "recovery.conf") == 0);
+
+ scan_val(&tarhdr[124], &filesz, 8, 11);
+ lastchunk = filesz % TARCHUNKSZ;
+ if (lastchunk > 0)
+ filesz += (TARCHUNKSZ - lastchunk);
+
+ in_tarhdr = false;
+ tarhdrsz = 0;
+
+#ifdef HAVE_LIBZ
+ if (ztarfile != NULL)
+ {
+ if (!skip_file && gzwrite(ztarfile, tarhdr, TARCHUNKSZ) != TARCHUNKSZ)
+ {
+ 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 (!skip_file && fwrite(tarhdr, TARCHUNKSZ, 1, tarfile) != 1)
+ {
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ }
+ }
+ }
+ else /* inside a file */
+ {
+ if (filesz > 0)
+ {
+ int bytes2write;
+
+ bytes2write = (filesz > rr ? rr : filesz);
+
+#ifdef HAVE_LIBZ
+ if (ztarfile != NULL)
+ {
+ if (!skip_file && gzwrite(ztarfile, copybuf + pos, bytes2write) != bytes2write)
+ {
+ 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 (!skip_file && fwrite(copybuf + pos, bytes2write, 1, tarfile) != 1)
+ {
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
+ }
+
+ rr -= bytes2write;
+ pos += bytes2write;
+ filesz -= bytes2write;
+ }
+ else
+ {
+ in_tarhdr = true;
+ skip_file = false;
+ tarhdrsz = filesz = 0;
+ }
+ }
}
}
else
-#endif
{
- if (fwrite(copybuf, r, 1, tarfile) != 1)
+#ifdef HAVE_LIBZ
+ if (ztarfile != NULL)
{
- fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
- progname, filename, strerror(errno));
- disconnect_and_exit(1);
+ 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);
+ }
+ }
+ else
+#endif
+ {
+ if (fwrite(copybuf, r, 1, tarfile) != 1)
+ {
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"),
+ progname, filename, strerror(errno));
+ disconnect_and_exit(1);
+ }
}
}
totaldone += r;
@@ -712,13 +904,20 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
char filename[MAXPGPATH];
int current_len_left;
int current_padding = 0;
+ bool basebackup;
char *copybuf = NULL;
FILE *file = NULL;
if (PQgetisnull(res, rownum, 0))
+ {
strcpy(current_path, basedir);
+ basebackup = true;
+ }
else
+ {
strcpy(current_path, PQgetvalue(res, rownum, 1));
+ basebackup = false;
+ }
/*
* Get the COPY data
@@ -937,6 +1136,330 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG
if (copybuf != NULL)
PQfreemem(copybuf);
+
+ if (basebackup)
+ 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;
+ }
+}
+
+
+/*
+ * Inverse for print_val()
+ */
+static void
+scan_val(char *s, uint64 *val, unsigned int base, size_t len)
+{
+ uint64 tmp = 0;
+ int i;
+
+ for (i = 0; i < len; i++)
+ {
+ int digit = s[i] - '0';
+
+ tmp = tmp * base + digit;
+ }
+
+ *val = tmp;
+}
+
+
+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], recoveryconflen, 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));
+}
+
+/*
+ * Escape single quotes in a string
+ */
+static char *
+escape_string(char *value)
+{
+ char *tmp, *escaped, *esc;
+ int num_quotes;
+
+ tmp = value;
+ num_quotes = 0;
+ while (*tmp)
+ {
+ if (*tmp == '\'')
+ num_quotes++;
+ tmp++;
+ }
+
+ escaped = malloc((tmp - value) + num_quotes + 1);
+ if (escaped == NULL)
+ {
+ fprintf(stderr, _("%s: out of memory"), progname);
+ disconnect_and_exit(1)
+ }
+
+ tmp = value;
+ esc = escaped;
+ while (*tmp)
+ {
+ if (*tmp == '\'')
+ *esc++ = '\\';
+ *esc++ = *tmp++;
+ }
+ *esc = '\0';
+
+ return escaped;
+}
+
+/*
+ * Reallocate the buffer for recovery.conf if needed
+ */
+static void
+_reallocRCBuffer(char **buf, int *bufsz, int currsz, char *keyword, char *value, int extrasz)
+{
+ int len = extrasz;
+
+ if (keyword != NULL)
+ len += strlen(keyword);
+ if (value != NULL)
+ len += strlen(value);
+
+ if (*bufsz < currsz + len)
+ {
+ 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;
+ memset(tmp + currsz, 0, *bufsz - currsz);
+ }
+}
+
+/*
+ * 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++)
+ {
+ char *escaped;
+ /*
+ * Do not emit this setting if not set or empty.
+ * 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'))
+ continue;
+
+ /*
+ * Write "keyword='value'" pieces, the value string is escaped
+ * if necessary and doubled single quotes around the value string.
+ */
+ escaped = escape_string(option->val);
+
+ _reallocRCBuffer(&buf, &bufsz, total, option->keyword, escaped, 6);
+
+ written = sprintf(&buf[total], "%s=''%s'' ", option->keyword, option->val);
+
+ free(escaped);
+
+ 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, 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;
+ recoveryconflen = 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, recoveryconflen, 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.\n"), progname);
+
+ free(recoveryconf);
}
@@ -960,6 +1483,8 @@ BaseBackup(void)
/* Error message already written in GetConnection() */
exit(1);
+ CreateRecoveryConf(conn);
+
/*
* Run IDENTIFY_SYSTEM so we can get the timeline
*/
@@ -1243,6 +1768,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 +1806,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 +1827,9 @@ main(int argc, char **argv)
exit(1);
}
break;
+ case 'R':
+ writerecoveryconf = true;
+ break;
case 'x':
if (includewal)
{