2012-10-14 22:31 keltezéssel, Boszormenyi Zoltan írta:
2012-10-14 22:26 keltezéssel, Boszormenyi Zoltan írta:
2012-10-14 22:23 keltezéssel, Boszormenyi Zoltan írta:
Hi,

2012-10-14 18:41 keltezéssel, Boszormenyi Zoltan írta:
2012-10-14 18:02 keltezéssel, Fujii Masao írta:
Thanks for updating the patch!

On Sun, Oct 14, 2012 at 8:41 PM, Boszormenyi Zoltan <z...@cybertec.at> wrote:
Backing up a standby server without -R preserves the original recovery.conf
of the
standby, it points to the standby's source server.

Backing up a standby server with -R overwrites the original recovery.conf
with the new
one pointing to the standby instead of the standby's source server. Without
-Ft, it is
obvious. With -Ft, there are two recovery.conf files in the tar file and
upon extracting it,
the last written one (the one generated via -R) overwrites the original.
The tar file is always extracted such way in all platform which PostgreSQL
supports? I'm just concerned about that some tool in some platform might
prefer the original recovery.conf when extracting tar file. If the spec of tar
format specifies such behavior (i.e., the last written file of the same name
is always preferred), it's OK.

Since tar is a sequential archive format, I think this is the behaviour of
every tar extractor. But I will look at adding code to skip the original
recovery.conf if it exists in the tar file.

I found the bug that recovery.conf is included in the tar file of the tablespace
instead of base.tar, when there are tablespaces in the server.

You are right, I am looking into this. But I don't know how it got there,
I check for (rownum == 0 && writerecoveryconf) and rownum == 0
supposedly means that it's the base.tar. Looking again.

I made a mistake in the previous check, rownum is not reliable.
The tablespaces are sent first and base backup as the last.
Now recovery.conf is written into base.tar.

Maybe this is nitpicky problem,,,, but...
If port number is not explicitly specified in pg_basebackup, the port
number is not
included to primary_conninfo in recovery.conf which is created during
the backup.
That is, the standby server using such recovery.conf tries to connect
to the default
port number because the port number is not supplied in primary_conninfo. This
assumes that the default port number is the same between the master and standby.
But this is not true. The default port number can be changed in --with-pgport
configure option, so the default port number might be different
between the master
and standby. To avoid this uncertainty, pg_basebackup -R should always include
the port number in primary_conninfo?

I think you are right. But, I wouldn't restrict it only to the port setting.
Any of the values that are set and equal to the compiled-in default,
it should be written into recovery.conf.

Now all values that are set (even those being equal to the compiled-in default)
are put into recovery.conf.

When the password is required to connect to the server, pg_basebackup -R
always writes the password setting into primary_conninfo in recovery.conf.
But if the password is supplied from .pgpass, ISTM that the password setting
doesn't need to be written into primary_conninfo. Right?

How can you deduce it from the PQconninfoOption structure?

Also, if the machine you take the base backup on is different
from the one where you actually use the backup on, it can be
different not only in the --with-pgport compilation option but
in the presence of .pgpass or the PGPASSWORD envvar, too.
The administrator is there for a reason or there is no .pgpass
or PGPASSWORD at all.

+        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.

Is it difficult to make pg_basebackup escape the special characters in the
password? It's better if we can remove this restriction.

It's not difficult. What other characters need to be escaped besides single 
quotes?

All written values are escaped.

Other changes: the recovery.conf in base.tar is correctly skipped if it exists
and -R is given. The new recovery.conf is written with padding to round up to
512, the TAR chunk size.

Also, the check for conflict between -R and -x/-X is now removed.

The documentation for option -R has changed to reflect this and
there is no different error code 2 now: it would make the behaviour
inconsistent between -Fp and -Ft.

The PQconninfo patch is also attached but didn't change since the last mail.

Best regards,
Zoltán Böszörményi

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
     http://www.postgresql.at/

diff -durpN postgresql/doc/src/sgml/libpq.sgml postgresql.1/doc/src/sgml/libpq.sgml
--- postgresql/doc/src/sgml/libpq.sgml	2012-08-03 09:39:30.114266570 +0200
+++ postgresql.1/doc/src/sgml/libpq.sgml	2012-10-14 11:12:07.049196259 +0200
@@ -496,6 +496,37 @@ typedef struct
      </listitem>
     </varlistentry>
 
+    <varlistentry id="libpq-pqconninfo">
+     <term><function>PQconninfo</function><indexterm><primary>PQconninfo</></></term>
+     <listitem>
+      <para>
+       Returns the connection options used by a live connection.
+<synopsis>
+PQconninfoOption *PQconninfo(PGconn *conn, bool for_replication);
+</synopsis>
+      </para>
+
+      <para>
+       Returns a connection options array.  This can be used to determine
+       all possible <function>PQconnectdb</function> options and their
+       current values that were used to connect to the server. The return
+       value points to an array of <structname>PQconninfoOption</structname>
+       structures, which ends with an entry having a null <structfield>keyword</>
+       pointer.  Every notes above for <function>PQconndefaults</function> also apply.
+      </para>
+
+      <para>
+       The <literal>for_replication</> argument can be used to exclude some
+       options from the list which are used by the walreceiver module.
+       <application>pg_basebackup</application> uses this pre-filtered list
+       to construct <literal>primary_conninfo</> in the automatically generated
+       recovery.conf file.
+      </para>
+
+     </listitem>
+    </varlistentry>
+
+
     <varlistentry id="libpq-pqconninfoparse">
      <term><function>PQconninfoParse</function><indexterm><primary>PQconninfoParse</></></term>
      <listitem>
diff -durpN postgresql/src/interfaces/libpq/exports.txt postgresql.1/src/interfaces/libpq/exports.txt
--- postgresql/src/interfaces/libpq/exports.txt	2012-10-09 09:58:14.342782974 +0200
+++ postgresql.1/src/interfaces/libpq/exports.txt	2012-10-14 11:12:07.049196259 +0200
@@ -164,3 +164,4 @@ PQsetSingleRowMode        161
 lo_lseek64                162
 lo_tell64                 163
 lo_truncate64             164
+PQconninfo                165
diff -durpN postgresql/src/interfaces/libpq/fe-connect.c postgresql.1/src/interfaces/libpq/fe-connect.c
--- postgresql/src/interfaces/libpq/fe-connect.c	2012-09-09 08:11:09.470401480 +0200
+++ postgresql.1/src/interfaces/libpq/fe-connect.c	2012-10-14 11:12:07.053196284 +0200
@@ -137,6 +137,9 @@ static int ldapServiceLookup(const char
  * PQconninfoOptions[] *must* be NULL.	In a working copy, non-null "val"
  * fields point to malloc'd strings that should be freed when the working
  * array is freed (see PQconninfoFree).
+ *
+ * If you add a new connection option to this list, remember to add it to
+ * PQconninfoMappings[] below.
  * ----------
  */
 static const PQconninfoOption PQconninfoOptions[] = {
@@ -264,6 +267,66 @@ static const PQconninfoOption PQconninfo
 	NULL, NULL, 0}
 };
 
+/*
+ * We need a mapping between the PQconninfoOptions[] array
+ * and PGconn members. We have to keep it separate from
+ * PQconninfoOptions[] to not leak info about PGconn members
+ * to clients.
+ */
+typedef struct PQconninfoMapping {
+	char		*keyword;
+	size_t		member_offset;
+	bool		for_replication;
+	/*
+	 * Special and simplistic value mapping between
+	 * PQconninfoOption and PGconn. Only used by "requiressl".
+	 */
+	char		*conninfoValue;
+	char		*connValue;
+} PQconninfoMapping;
+#define PGCONNMEMBERADDR(conn, mapping) ((char **)((char *)conn + mapping->member_offset))
+
+static const PQconninfoMapping PQconninfoMappings[] =
+{
+	/* "authtype" is not used anymore, there is no mapping to PGconn */
+	/* there is no mapping of "service" to PGconn */
+	{ "user", offsetof(struct pg_conn, pguser), true, NULL, NULL },
+	{ "password", offsetof(struct pg_conn, pgpass), true, NULL, NULL },
+	{ "connect_timeout", offsetof(struct pg_conn, connect_timeout), true, NULL, NULL },
+	{ "dbname", offsetof(struct pg_conn, dbName), false, NULL, NULL },
+	{ "host", offsetof(struct pg_conn, pghost), true, NULL, NULL },
+	{ "hostaddr", offsetof(struct pg_conn, pghostaddr), true, NULL, NULL },
+	{ "port", offsetof(struct pg_conn, pgport), true, NULL, NULL },
+	{ "client_encoding", offsetof(struct pg_conn, client_encoding_initial), false, NULL, NULL },
+	{ "tty", offsetof(struct pg_conn, pgtty), false, NULL, NULL },
+	{ "options", offsetof(struct pg_conn, pgoptions), true, NULL, NULL },
+	{ "application_name", offsetof(struct pg_conn, appname), false, NULL, NULL },
+	{ "fallback_application_name", offsetof(struct pg_conn, fbappname), false, NULL, NULL },
+	{ "keepalives", offsetof(struct pg_conn, keepalives), true, NULL, NULL },
+	{ "keepalives_idle", offsetof(struct pg_conn, keepalives_idle), true, NULL, NULL },
+	{ "keepalives_interval", offsetof(struct pg_conn, keepalives_interval), true, NULL, NULL },
+	{ "keepalives_count", offsetof(struct pg_conn, keepalives_count), true, NULL, NULL },
+#ifdef USE_SSL
+	{ "requiressl", offsetof(struct pg_conn, sslmode), false, "1", "require" },
+#endif
+	{ "sslmode", offsetof(struct pg_conn, sslmode), true, NULL, NULL },
+	{ "sslcompression", offsetof(struct pg_conn, sslcompression), true, NULL, NULL },
+	{ "sslcert", offsetof(struct pg_conn, sslcert), true, NULL, NULL },
+	{ "sslkey", offsetof(struct pg_conn, sslkey), true, NULL, NULL },
+	{ "sslrootcert", offsetof(struct pg_conn, sslrootcert), true, NULL, NULL },
+	{ "sslcrl", offsetof(struct pg_conn, sslcrl), true, NULL, NULL },
+	{ "requirepeer", offsetof(struct pg_conn, requirepeer), true, NULL, NULL },
+#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+	{ "krbsrvname", offsetof(struct pg_conn, krbsrvname), true, NULL, NULL },
+#endif
+#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
+	{ "gsslib", offsetof(struct pg_conn, requirepeer), true, NULL, NULL },
+#endif
+	{ "replication", offsetof(struct pg_conn, replication), false, NULL, NULL },
+	/* Terminating entry --- MUST BE LAST */
+	{ NULL, 0, false, NULL, NULL }
+};
+
 static const PQEnvironmentOption EnvironmentOptions[] =
 {
 	/* common user-interface settings */
@@ -295,7 +358,8 @@ static PGconn *makeEmptyPGconn(void);
 static void fillPGconn(PGconn *conn, PQconninfoOption *connOptions);
 static void freePGconn(PGconn *conn);
 static void closePGconn(PGconn *conn);
-static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage);
+static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage,
+						bool for_replication);
 static PQconninfoOption *parse_connection_string(const char *conninfo,
 						PQExpBuffer errorMessage, bool use_defaults);
 static int	uri_prefix_length(const char *connstr);
@@ -318,6 +382,8 @@ static char *conninfo_uri_decode(const c
 static bool get_hexdigit(char digit, int *value);
 static const char *conninfo_getval(PQconninfoOption *connOptions,
 				const char *keyword);
+static void conninfo_setval(PQconninfoOption *connOptions,
+				const char *keyword, const char *val);
 static PQconninfoOption *conninfo_storeval(PQconninfoOption *connOptions,
 				  const char *keyword, const char *value,
 			  PQExpBuffer errorMessage, bool ignoreMissing, bool uri_decode);
@@ -627,7 +693,9 @@ PQconnectStart(const char *conninfo)
 static void
 fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 {
+	const PQconninfoMapping *mapping;
 	const char *tmp;
+	char	   **memberaddr;
 
 	/*
 	 * Move option values into conn structure
@@ -637,72 +705,24 @@ fillPGconn(PGconn *conn, PQconninfoOptio
 	 *
 	 * XXX: probably worth checking strdup() return value here...
 	 */
-	tmp = conninfo_getval(connOptions, "hostaddr");
-	conn->pghostaddr = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "host");
-	conn->pghost = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "port");
-	conn->pgport = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "tty");
-	conn->pgtty = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "options");
-	conn->pgoptions = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "application_name");
-	conn->appname = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "fallback_application_name");
-	conn->fbappname = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "dbname");
-	conn->dbName = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "user");
-	conn->pguser = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "password");
-	conn->pgpass = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "connect_timeout");
-	conn->connect_timeout = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "client_encoding");
-	conn->client_encoding_initial = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "keepalives");
-	conn->keepalives = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "keepalives_idle");
-	conn->keepalives_idle = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "keepalives_interval");
-	conn->keepalives_interval = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "keepalives_count");
-	conn->keepalives_count = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslmode");
-	conn->sslmode = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslcompression");
-	conn->sslcompression = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslkey");
-	conn->sslkey = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslcert");
-	conn->sslcert = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslrootcert");
-	conn->sslrootcert = tmp ? strdup(tmp) : NULL;
-	tmp = conninfo_getval(connOptions, "sslcrl");
-	conn->sslcrl = tmp ? strdup(tmp) : NULL;
-#ifdef USE_SSL
-	tmp = conninfo_getval(connOptions, "requiressl");
-	if (tmp && tmp[0] == '1')
+	for (mapping = PQconninfoMappings; mapping->keyword; mapping++)
 	{
-		/* here warn that the requiressl option is deprecated? */
-		if (conn->sslmode)
-			free(conn->sslmode);
-		conn->sslmode = strdup("require");
+		tmp = conninfo_getval(connOptions, mapping->keyword);
+		memberaddr = PGCONNMEMBERADDR(conn, mapping);
+
+		if (mapping->conninfoValue && mapping->connValue)
+		{
+			size_t	len = strlen(mapping->conninfoValue);
+			if (tmp && strncmp(tmp, mapping->conninfoValue, len) == 0)
+			{
+				if (*memberaddr)
+					free(*memberaddr);
+				*memberaddr = strdup(mapping->connValue);
+			}
+		}
+		else
+			*memberaddr = tmp ? strdup(tmp) : NULL;
 	}
-#endif
-	tmp = conninfo_getval(connOptions, "requirepeer");
-	conn->requirepeer = tmp ? strdup(tmp) : NULL;
-#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-	tmp = conninfo_getval(connOptions, "krbsrvname");
-	conn->krbsrvname = tmp ? strdup(tmp) : NULL;
-#endif
-#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
-	tmp = conninfo_getval(connOptions, "gsslib");
-	conn->gsslib = tmp ? strdup(tmp) : NULL;
-#endif
-	tmp = conninfo_getval(connOptions, "replication");
-	conn->replication = tmp ? strdup(tmp) : NULL;
 }
 
 /*
@@ -884,7 +904,7 @@ PQconndefaults(void)
 	if (PQExpBufferDataBroken(errorBuf))
 		return NULL;			/* out of memory already :-( */
 
-	connOptions = conninfo_init(&errorBuf);
+	connOptions = conninfo_init(&errorBuf, false);
 	if (connOptions != NULL)
 	{
 		if (!conninfo_add_defaults(connOptions, &errorBuf))
@@ -4006,9 +4026,11 @@ PQconninfoParse(const char *conninfo, ch
 
 /*
  * Build a working copy of the constant PQconninfoOptions array.
+ * If for_replication is true, only return the options that are
+ * not added by libpqwalreceiver. 
  */
 static PQconninfoOption *
-conninfo_init(PQExpBuffer errorMessage)
+conninfo_init(PQExpBuffer errorMessage, bool for_replication)
 {
 	PQconninfoOption *options;
 
@@ -4019,7 +4041,28 @@ conninfo_init(PQExpBuffer errorMessage)
 						  libpq_gettext("out of memory\n"));
 		return NULL;
 	}
-	memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions));
+	if (for_replication)
+	{
+		const PQconninfoMapping	*mapping = PQconninfoMappings;
+		PQconninfoOption *opt_dest = options;
+
+		while (mapping->keyword)
+		{
+			PQconninfoOption *opt_src = conninfo_find((PQconninfoOption *)PQconninfoOptions, mapping->keyword);
+
+			if (opt_src && mapping->for_replication)
+			{
+				memcpy(opt_dest, opt_src, sizeof(PQconninfoOption));
+				opt_dest++;
+			}
+
+			opt_src++;
+			mapping++;
+		}
+		MemSet(opt_dest, 0, sizeof(PQconninfoOption));
+	}
+	else
+		memcpy(options, PQconninfoOptions, sizeof(PQconninfoOptions));
 
 	return options;
 }
@@ -4095,7 +4138,7 @@ conninfo_parse(const char *conninfo, PQE
 	PQconninfoOption *options;
 
 	/* Make a working copy of PQconninfoOptions */
-	options = conninfo_init(errorMessage);
+	options = conninfo_init(errorMessage, false);
 	if (options == NULL)
 		return NULL;
 
@@ -4295,7 +4338,7 @@ conninfo_array_parse(const char *const *
 	}
 
 	/* Make a working copy of PQconninfoOptions */
-	options = conninfo_init(errorMessage);
+	options = conninfo_init(errorMessage, false);
 	if (options == NULL)
 	{
 		PQconninfoFree(dbname_options);
@@ -4485,7 +4528,7 @@ conninfo_uri_parse(const char *uri, PQEx
 	PQconninfoOption *options;
 
 	/* Make a working copy of PQconninfoOptions */
-	options = conninfo_init(errorMessage);
+	options = conninfo_init(errorMessage, false);
 	if (options == NULL)
 		return NULL;
 
@@ -4985,6 +5028,24 @@ conninfo_getval(PQconninfoOption *connOp
 }
 
 /*
+ * Set an option value corresponding to the keyword in the connOptions array.
+ */
+static void
+conninfo_setval(PQconninfoOption *connOptions, const char *keyword,
+						const char *val)
+{
+	PQconninfoOption *option;
+
+	option = conninfo_find(connOptions, keyword);
+	if (option)
+	{
+		if (option->val)
+			free(option->val);
+		option->val = val ? strdup(val) : NULL;
+	}
+}
+
+/*
  * Store a (new) value for an option corresponding to the keyword in
  * connOptions array.
  *
@@ -5066,6 +5127,50 @@ conninfo_find(PQconninfoOption *connOpti
 }
 
 
+/*
+ * Return the connection options used for the connections
+ */
+PQconninfoOption *
+PQconninfo(PGconn *conn, bool for_replication)
+{
+	PQExpBufferData errorBuf;
+	PQconninfoOption *connOptions;
+
+	if (conn == NULL)
+		return NULL;
+
+	/* We don't actually report any errors here, but callees want a buffer */
+	initPQExpBuffer(&errorBuf);
+	if (PQExpBufferDataBroken(errorBuf))
+		return NULL;		/* out of memory already :-( */
+
+	connOptions = conninfo_init(&errorBuf, for_replication);
+
+	termPQExpBuffer(&errorBuf);
+
+	if (connOptions != NULL)
+	{
+		const PQconninfoMapping *mapping;
+
+		for (mapping = PQconninfoMappings; mapping->keyword; mapping++)
+		{
+			char **memberaddr = PGCONNMEMBERADDR(conn, mapping);
+
+			if (mapping->conninfoValue && mapping->connValue)
+			{
+				size_t	len = strlen(mapping->connValue);
+				if (*memberaddr && strncmp(*memberaddr, mapping->connValue, len) == 0)
+					conninfo_setval(connOptions, mapping->keyword, mapping->conninfoValue);
+			}
+			else
+				conninfo_setval(connOptions, mapping->keyword, *memberaddr);
+		}
+	}
+
+	return connOptions;
+}
+
+
 void
 PQconninfoFree(PQconninfoOption *connOptions)
 {
diff -durpN postgresql/src/interfaces/libpq/libpq-fe.h postgresql.1/src/interfaces/libpq/libpq-fe.h
--- postgresql/src/interfaces/libpq/libpq-fe.h	2012-10-09 09:58:14.343782980 +0200
+++ postgresql.1/src/interfaces/libpq/libpq-fe.h	2012-10-14 11:12:07.054196291 +0200
@@ -262,6 +262,9 @@ extern PQconninfoOption *PQconndefaults(
 /* parse connection options in same way as PQconnectdb */
 extern PQconninfoOption *PQconninfoParse(const char *conninfo, char **errmsg);
 
+/* return the connection options used by a live connection */
+extern PQconninfoOption *PQconninfo(PGconn *conn, bool for_replication);
+
 /* free the data structure returned by PQconndefaults() or PQconninfoParse() */
 extern void PQconninfoFree(PQconninfoOption *connOptions);
 
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-15 09:42:35.293396090 +0200
@@ -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.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-15 09:42:39.078417914 +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);
+
+	free(recoveryconf);
+
+	fprintf(stderr, _("%s: recovery.conf written.\n"), progname);
 }
 
 
@@ -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)
 				{
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to