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-11-08 13:13:04.151630632 +0100 +++ postgresql.2/doc/src/sgml/ref/pg_basebackup.sgml 2013-01-02 10:46:41.642608888 +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.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 2013-01-02 09:19:03.856521815 +0100 +++ postgresql.2/src/bin/pg_basebackup/pg_basebackup.c 2013-01-02 11:29:05.353701197 +0100 @@ -13,12 +13,14 @@ #include "postgres_fe.h" #include "libpq-fe.h" +#include "pqexpbuffer.h" #include #include #include #include #include +#include #ifdef HAVE_LIBZ #include @@ -40,6 +42,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 */ @@ -64,13 +67,18 @@ 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); static void progress_report(int tablespacenum, const char *filename); +static void scan_val(char *s, uint64 *val, unsigned int base, size_t len); 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, @@ -101,6 +109,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")); @@ -446,6 +456,65 @@ progress_report(int tablespacenum, const /* + * Inverse of 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; +} + + +/* + * 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. @@ -461,12 +530,17 @@ ReceiveTarFile(PGconn *conn, PGresult *r char filename[MAXPGPATH]; char *copybuf = NULL; FILE *tarfile = NULL; + char tarhdr[512]; + 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 @@ -578,6 +652,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; @@ -592,7 +676,8 @@ 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. @@ -600,30 +685,30 @@ ReceiveTarFile(PGconn *conn, PGresult *r 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, 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[512]; + int padding; + + tarCreateHeader(header, "recovery.conf", NULL, + rcExpBuf->len, + 0600, 04000, 02000, + time(NULL)); + + padding = ((rcExpBuf->len + 511) & ~511) - 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) { @@ -659,25 +744,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 < 512) + { + /* + * 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 = 512 - 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 + 511) & ~511) - filesz; + filesz += padding; + + /* Indicate that the subsequent data is the file content. */ + in_tarhdr = false; + + if (!skip_file) + WRITE_TAR_DATA(tarhdr, 512); + } + } + 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; @@ -706,10 +890,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)); @@ -931,6 +1116,131 @@ ReceiveAndUnpackTarFile(PGconn *conn, PG if (copybuf != NULL) PQfreemem(copybuf); + + if (basetablespace) + WriteRecoveryConf(); +} + +static char * +escape_quotes(const char *src) +{ + char *result = escape_single_quotes_ascii(src); + if (!result) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + exit(1); + } + return result; +} + +/* + * Create the new recovery.conf in memory + */ +static void +CreateRecoveryConf(PGconn *conn) +{ + PQconninfoOption *connOptions; + PQconninfoOption *option; + + if (!writerecoveryconf) + return; + + connOptions = PQconninfo(conn); + 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: + * - the setting is "replication", "dbname" or + * "fallback_application_name", since these would be + * overridden by the libpqwalreceiver module anyway. + * - not set or empty. + */ + if (strcmp(option->keyword, "replication") == 0 || + 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); } @@ -954,6 +1264,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 */ @@ -1217,6 +1536,9 @@ BaseBackup(void) #endif } + /* Free the recovery.conf contents */ + destroyPQExpBuffer(rcExpBuf); + /* * End of copy data. Final result is already checked inside the loop. */ @@ -1237,6 +1559,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'}, @@ -1274,7 +1597,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) @@ -1295,6 +1618,9 @@ main(int argc, char **argv) exit(1); } break; + case 'R': + writerecoveryconf = true; + break; case 'x': if (includewal) {