Split out recovery confing-writing code from pg_basebackup
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 25 Sep 2019 17:35:24 +0000 (14:35 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 25 Sep 2019 17:35:24 +0000 (14:35 -0300)
... into a new file, fe_utils/recovery_gen.c.

This can later be used by pg_rewind.

Authors: Paul Guo, Jimmy Yih, Ashwin Agrawal.  A few tweaks by Álvaro Herrera
Reviewed-by: Michaël Paquier
Discussion: https://postgr.es/m/CAEET0ZEffUkXc48pg2iqARQgGRYDiiVxDu+yYek_bTwJF+q=Uw@mail.gmail.com

src/bin/pg_basebackup/pg_basebackup.c
src/fe_utils/Makefile
src/fe_utils/recovery_gen.c [new file with mode: 0644]
src/include/fe_utils/recovery_gen.h [new file with mode: 0644]
src/tools/msvc/Mkvcbuild.pm

index 7986872f106c1232a3010767d586709e10d783fe..55ef13926da67620d5e1aa6844921e33297bf5ce 100644 (file)
@@ -31,6 +31,7 @@
 #include "common/file_utils.h"
 #include "common/logging.h"
 #include "common/string.h"
+#include "fe_utils/recovery_gen.h"
 #include "fe_utils/string_utils.h"
 #include "getopt_long.h"
 #include "libpq-fe.h"
@@ -67,11 +68,6 @@ typedef struct TablespaceList
  */
 #define MINIMUM_VERSION_FOR_TEMP_SLOTS 100000
 
-/*
- * recovery.conf is integrated into postgresql.conf from version 12.
- */
-#define MINIMUM_VERSION_FOR_RECOVERY_GUC 120000
-
 /*
  * Different ways to include WAL
  */
@@ -147,8 +143,6 @@ static void progress_report(int tablespacenum, const char *filename, bool force)
 
 static void ReceiveTarFile(PGconn *conn, PGresult *res, int rownum);
 static void ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum);
-static void GenerateRecoveryConf(PGconn *conn);
-static void WriteRecoveryConf(void);
 static void BaseBackup(void);
 
 static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline,
@@ -1629,7 +1623,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
        PQfreemem(copybuf);
 
    if (basetablespace && writerecoveryconf)
-       WriteRecoveryConf();
+       WriteRecoveryConfig(conn, basedir, recoveryconfcontents);
 
    /*
     * No data is synced here, everything is done for all tablespaces at the
@@ -1637,156 +1631,6 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
     */
 }
 
-/*
- * Escape a string so that it can be used as a value in a key-value pair
- * a configuration file.
- */
-static char *
-escape_quotes(const char *src)
-{
-   char       *result = escape_single_quotes_ascii(src);
-
-   if (!result)
-   {
-       pg_log_error("out of memory");
-       exit(1);
-   }
-   return result;
-}
-
-/*
- * Create a configuration file in memory using a PQExpBuffer
- */
-static void
-GenerateRecoveryConf(PGconn *conn)
-{
-   PQconninfoOption *connOptions;
-   PQconninfoOption *option;
-   PQExpBufferData conninfo_buf;
-   char       *escaped;
-
-   recoveryconfcontents = createPQExpBuffer();
-   if (!recoveryconfcontents)
-   {
-       pg_log_error("out of memory");
-       exit(1);
-   }
-
-   /*
-    * In PostgreSQL 12 and newer versions, standby_mode is gone, replaced by
-    * standby.signal to trigger a standby state at recovery.
-    */
-   if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
-       appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n");
-
-   connOptions = PQconninfo(conn);
-   if (connOptions == NULL)
-   {
-       pg_log_error("out of memory");
-       exit(1);
-   }
-
-   initPQExpBuffer(&conninfo_buf);
-   for (option = connOptions; option && option->keyword; option++)
-   {
-       /* Omit empty settings and those libpqwalreceiver overrides. */
-       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;
-
-       /* Separate key-value pairs with spaces */
-       if (conninfo_buf.len != 0)
-           appendPQExpBufferChar(&conninfo_buf, ' ');
-
-       /*
-        * Write "keyword=value" pieces, the value string is escaped and/or
-        * quoted if necessary.
-        */
-       appendPQExpBuffer(&conninfo_buf, "%s=", option->keyword);
-       appendConnStrVal(&conninfo_buf, option->val);
-   }
-
-   /*
-    * Escape the connection string, so that it can be put in the config file.
-    * Note that this is different from the escaping of individual connection
-    * options above!
-    */
-   escaped = escape_quotes(conninfo_buf.data);
-   appendPQExpBuffer(recoveryconfcontents, "primary_conninfo = '%s'\n", escaped);
-   free(escaped);
-
-   if (replication_slot)
-   {
-       /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
-       appendPQExpBuffer(recoveryconfcontents, "primary_slot_name = '%s'\n",
-                         replication_slot);
-   }
-
-   if (PQExpBufferBroken(recoveryconfcontents) ||
-       PQExpBufferDataBroken(conninfo_buf))
-   {
-       pg_log_error("out of memory");
-       exit(1);
-   }
-
-   termPQExpBuffer(&conninfo_buf);
-
-   PQconninfoFree(connOptions);
-}
-
-
-/*
- * Write the configuration file into the directory specified in basedir,
- * with the contents already collected in memory appended.  Then write
- * the signal file into the basedir.  If the server does not support
- * recovery parameters as GUCs, the signal file is not necessary, and
- * configuration is written to recovery.conf.
- */
-static void
-WriteRecoveryConf(void)
-{
-   char        filename[MAXPGPATH];
-   FILE       *cf;
-   bool        is_recovery_guc_supported = true;
-
-   if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
-       is_recovery_guc_supported = false;
-
-   snprintf(filename, MAXPGPATH, "%s/%s", basedir,
-            is_recovery_guc_supported ? "postgresql.auto.conf" : "recovery.conf");
-
-   cf = fopen(filename, is_recovery_guc_supported ? "a" : "w");
-   if (cf == NULL)
-   {
-       pg_log_error("could not open file \"%s\": %m", filename);
-       exit(1);
-   }
-
-   if (fwrite(recoveryconfcontents->data, recoveryconfcontents->len, 1, cf) != 1)
-   {
-       pg_log_error("could not write to file \"%s\": %m", filename);
-       exit(1);
-   }
-
-   fclose(cf);
-
-   if (is_recovery_guc_supported)
-   {
-       snprintf(filename, MAXPGPATH, "%s/%s", basedir, "standby.signal");
-       cf = fopen(filename, "w");
-       if (cf == NULL)
-       {
-           pg_log_error("could not create file \"%s\": %m", filename);
-           exit(1);
-       }
-
-       fclose(cf);
-   }
-}
-
 
 static void
 BaseBackup(void)
@@ -1843,7 +1687,7 @@ BaseBackup(void)
     * Build contents of configuration file if requested
     */
    if (writerecoveryconf)
-       GenerateRecoveryConf(conn);
+       recoveryconfcontents = GenerateRecoveryConfig(conn, replication_slot);
 
    /*
     * Run IDENTIFY_SYSTEM so we can get the timeline
index 7d738003237161b097d8d8ea83d41f2e1c43feb0..f2e516a2aa32c3b0ed9482927c0f3dd651eec783 100644 (file)
@@ -19,7 +19,8 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
 
-OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o conditional.o
+OBJS = conditional.o mbprint.o print.o psqlscan.o recovery_gen.o \
+       simple_list.o string_utils.o
 
 all: libpgfeutils.a
 
diff --git a/src/fe_utils/recovery_gen.c b/src/fe_utils/recovery_gen.c
new file mode 100644 (file)
index 0000000..6641f95
--- /dev/null
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * recovery_gen.c
+ *     Generator for recovery configuration
+ *
+ * Portions Copyright (c) 2011-2019, PostgreSQL Global Development Group
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include "common/logging.h"
+#include "fe_utils/string_utils.h"
+#include "fe_utils/recovery_gen.h"
+
+
+static char *escape_quotes(const char *src);
+
+/*
+ * Write recovery configuration contents into a fresh PQExpBuffer, and
+ * return it.
+ */
+PQExpBuffer
+GenerateRecoveryConfig(PGconn *pgconn, char *replication_slot)
+{
+   PQconninfoOption *connOptions;
+   PQExpBufferData conninfo_buf;
+   char       *escaped;
+   PQExpBuffer contents;
+
+   Assert(pgconn != NULL);
+
+   contents = createPQExpBuffer();
+   if (!contents)
+   {
+       pg_log_error("out of memory");
+       exit(1);
+   }
+
+   /*
+    * In PostgreSQL 12 and newer versions, standby_mode is gone, replaced by
+    * standby.signal to trigger a standby state at recovery.
+    */
+   if (PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
+       appendPQExpBufferStr(contents, "standby_mode = 'on'\n");
+
+   connOptions = PQconninfo(pgconn);
+   if (connOptions == NULL)
+   {
+       pg_log_error("out of memory");
+       exit(1);
+   }
+
+   initPQExpBuffer(&conninfo_buf);
+   for (PQconninfoOption *opt = connOptions; opt && opt->keyword; opt++)
+   {
+       /* Omit empty settings and those libpqwalreceiver overrides. */
+       if (strcmp(opt->keyword, "replication") == 0 ||
+           strcmp(opt->keyword, "dbname") == 0 ||
+           strcmp(opt->keyword, "fallback_application_name") == 0 ||
+           (opt->val == NULL) ||
+           (opt->val != NULL && opt->val[0] == '\0'))
+           continue;
+
+       /* Separate key-value pairs with spaces */
+       if (conninfo_buf.len != 0)
+           appendPQExpBufferChar(&conninfo_buf, ' ');
+
+       /*
+        * Write "keyword=value" pieces, the value string is escaped and/or
+        * quoted if necessary.
+        */
+       appendPQExpBuffer(&conninfo_buf, "%s=", opt->keyword);
+       appendConnStrVal(&conninfo_buf, opt->val);
+   }
+   if (PQExpBufferDataBroken(conninfo_buf))
+   {
+       pg_log_error("out of memory");
+       exit(1);
+   }
+
+   /*
+    * Escape the connection string, so that it can be put in the config file.
+    * Note that this is different from the escaping of individual connection
+    * options above!
+    */
+   escaped = escape_quotes(conninfo_buf.data);
+   termPQExpBuffer(&conninfo_buf);
+   appendPQExpBuffer(contents, "primary_conninfo = '%s'\n", escaped);
+   free(escaped);
+
+   if (replication_slot)
+   {
+       /* unescaped: ReplicationSlotValidateName allows [a-z0-9_] only */
+       appendPQExpBuffer(contents, "primary_slot_name = '%s'\n",
+                         replication_slot);
+   }
+
+   if (PQExpBufferBroken(contents))
+   {
+       pg_log_error("out of memory");
+       exit(1);
+   }
+
+   PQconninfoFree(connOptions);
+
+   return contents;
+}
+
+/*
+ * Write the configuration file in the directory specified in target_dir,
+ * with the contents already collected in memory appended.  Then write
+ * the signal file into the target_dir.  If the server does not support
+ * recovery parameters as GUCs, the signal file is not necessary, and
+ * configuration is written to recovery.conf.
+ */
+void
+WriteRecoveryConfig(PGconn *pgconn, char *target_dir, PQExpBuffer contents)
+{
+   char        filename[MAXPGPATH];
+   FILE       *cf;
+   bool        use_recovery_conf;
+
+   Assert(pgconn != NULL);
+
+   use_recovery_conf =
+       PQserverVersion(pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;
+
+   snprintf(filename, MAXPGPATH, "%s/%s", target_dir,
+            use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");
+
+   cf = fopen(filename, use_recovery_conf ? "a" : "w");
+   if (cf == NULL)
+   {
+       pg_log_error("could not open file \"%s\": %m", filename);
+       exit(1);
+   }
+
+   if (fwrite(contents->data, contents->len, 1, cf) != 1)
+   {
+       pg_log_error("could not write to file \"%s\": %m", filename);
+       exit(1);
+   }
+
+   fclose(cf);
+
+   if (!use_recovery_conf)
+   {
+       snprintf(filename, MAXPGPATH, "%s/%s", target_dir, "standby.signal");
+       cf = fopen(filename, "w");
+       if (cf == NULL)
+       {
+           pg_log_error("could not create file \"%s\": %m", filename);
+           exit(1);
+       }
+
+       fclose(cf);
+   }
+}
+
+/*
+ * Escape a string so that it can be used as a value in a key-value pair
+ * a configuration file.
+ */
+static char *
+escape_quotes(const char *src)
+{
+   char       *result = escape_single_quotes_ascii(src);
+
+   if (!result)
+   {
+       pg_log_error("out of memory");
+       exit(1);
+   }
+   return result;
+}
diff --git a/src/include/fe_utils/recovery_gen.h b/src/include/fe_utils/recovery_gen.h
new file mode 100644 (file)
index 0000000..8b15307
--- /dev/null
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * Generator for recovery configuration
+ *
+ * Portions Copyright (c) 2011-2019, PostgreSQL Global Development Group
+ *
+ * src/include/fe_utils/recovery_gen.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RECOVERY_GEN_H
+#define RECOVERY_GEN_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+/*
+ * recovery configuration is part of postgresql.conf in version 12 and up, and
+ * in recovery.conf before that.
+ */
+#define MINIMUM_VERSION_FOR_RECOVERY_GUC 120000
+
+extern PQExpBuffer GenerateRecoveryConfig(PGconn *pgconn,
+                                         char *pg_replication_slot);
+extern void WriteRecoveryConfig(PGconn *pgconn, char *target_dir,
+                               PQExpBuffer contents);
+
+#endif                         /* RECOVERY_GEN_H */
index 00b2bc25e50524bc483a9dedf7afc9b57843fcee..7a103e61406e53e691ffea2dea760e59be5257d6 100644 (file)
@@ -142,7 +142,8 @@ sub mkvcbuild
    our @pgcommonbkndfiles = @pgcommonallfiles;
 
    our @pgfeutilsfiles = qw(
-     conditional.c mbprint.c print.c psqlscan.l psqlscan.c simple_list.c string_utils.c);
+     conditional.c mbprint.c print.c psqlscan.l psqlscan.c
+     simple_list.c string_utils.c recovery_gen.c);
 
    $libpgport = $solution->AddProject('libpgport', 'lib', 'misc');
    $libpgport->AddDefine('FRONTEND');