04-pg_basebackup-v17.patch

text/x-patch

Filename: 04-pg_basebackup-v17.patch
Type: text/x-patch
Part: 2
Message: Re: [PATCH] Make pg_basebackup configure and start standby [Review]

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 v17
File+
doc/src/sgml/ref/pg_basebackup.sgml 15 0
src/bin/pg_basebackup/pg_basebackup.c 328 43
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 10:44:09.232437587 +0100
@@ -189,6 +189,21 @@ 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.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><option>-x</option></term>
       <term><option>--xlog</option></term>
       <listitem>
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 10:27:33.854681991 +0100
+++ postgresql.4/src/bin/pg_basebackup/pg_basebackup.c	2012-11-22 10:57:35.291135926 +0100
@@ -19,6 +19,7 @@
 #define FRONTEND 1
 #include "postgres.h"
 #include "libpq-fe.h"
+#include "pqexpbuffer.h"
 
 #include <unistd.h>
 #include <dirent.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,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", &current_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,112 @@ 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_REPLICATION);
+	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 ((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 +1229,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 +1501,9 @@ BaseBackup(void)
 #endif
 	}
 
+	/* Free the recovery.conf contents */
+	destroyPQExpBuffer(rcExpBuf);
+
 	/*
 	 * End of copy data. Final result is already checked inside the loop.
 	 */
@@ -1266,6 +1547,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'},
@@ -1305,7 +1587,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)
@@ -1326,6 +1608,9 @@ main(int argc, char **argv)
 					exit(1);
 				}
 				break;
+			case 'R':
+				writerecoveryconf = true;
+				break;
 			case 'x':
 				if (includewal)
 				{