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) {