From: Alexey Kondratov <a.kondratov@postgrespro.ru>
To: Andrey Borodin <x4mmm@yandex-team.ru>
Cc: PostgreSQL-development <pgsql-hackers@postgresql.org>
Cc: Dmitry Dolgov <9erthalion6@gmail.com>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: Alvaro Herrera <alvherre@2ndquadrant.com>
Cc: vladimirlesk@yandex-team.ru
Cc: dsarafan@yandex-team.ru
Subject: Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line
Date: Fri, 8 Feb 2019 18:30:18 +0300
Message-ID: <541a55c5-4c9b-a4a9-ad77-4b5b6fa4dacb@postgrespro.ru> (raw)
In-Reply-To: <ecb60bb6eb9c7fc22e6002fa80e1dbe6@postgrespro.ru>
References: <50b94dcb-c744-ff2c-d6a4-3c371f8a7f9a@postgrespro.ru>
<20181022171907.ri2tkqtt7n6jm4dh@alvherre.pgsql>
<20181029070600.GJ14242@paquier.xyz>
<527530a0-3460-7c32-d223-3014ddaeee8e@postgrespro.ru>
<20181030030123.GA18366@paquier.xyz>
<3e432374-d21b-9ab7-06e0-a0785e5e3fd0@postgrespro.ru>
<CA+q6zcUw9KaV-L3khD9axOHU8x+sxota_pQP8-LqqFiOtgQmTA@mail.gmail.com>
<13ec67dc-2c6b-777e-902a-1141fdb8302e@postgrespro.ru>
<8a8d3069-129e-18c4-aab9-707825eca365@postgrespro.ru>
<A5DF5C14-AA25-43ED-90E3-5FEB01590B6D@yandex-team.ru>
<ecb60bb6eb9c7fc22e6002fa80e1dbe6@postgrespro.ru>
On 21.01.2019 23:50, a.kondratov@postgrespro.ru wrote:
> Thank you for the review! I have updated the patch according to your
> comments and remarks. Please, find new version attached.
During the self-reviewing of the code and tests, I discovered some
problems with build on Windows. New version of the patch is attached and
it fixes this issue as well as includes some minor code revisions.
Regards
--
Alexey Kondratov
Postgres Professional https://www.postgrespro.com
Russian Postgres Company
Attachments:0001-pg_rewind-options-to-use-restore_command-v2.2.patch (text/x-patch, 67.4K) download
From 99c6d94f37a797400d41545a271ff111b92e9361 Mon Sep 17 00:00:00 2001
From: Alexey Kondratov <alex.lumir@gmail.com>
Date: Fri, 21 Dec 2018 14:00:30 +0300
Subject: [PATCH] pg_rewind: options to use restore_command from
postgresql.conf or command line.
---
doc/src/sgml/ref/pg_rewind.sgml | 30 +-
src/backend/Makefile | 4 +-
src/backend/commands/extension.c | 1 +
src/backend/utils/misc/.gitignore | 1 -
src/backend/utils/misc/Makefile | 8 -
src/backend/utils/misc/guc.c | 434 +++++++++++++--
src/bin/pg_rewind/Makefile | 2 +-
src/bin/pg_rewind/parsexlog.c | 166 +++++-
src/bin/pg_rewind/pg_rewind.c | 100 +++-
src/bin/pg_rewind/pg_rewind.h | 10 +-
src/bin/pg_rewind/t/001_basic.pl | 4 +-
src/bin/pg_rewind/t/002_databases.pl | 4 +-
src/bin/pg_rewind/t/003_extrafiles.pl | 4 +-
src/bin/pg_rewind/t/RewindTest.pm | 93 +++-
src/common/.gitignore | 1 +
src/common/Makefile | 9 +-
src/{backend/utils/misc => common}/guc-file.l | 518 ++++--------------
src/include/common/guc-file.h | 50 ++
src/include/utils/guc.h | 39 +-
src/tools/msvc/Mkvcbuild.pm | 7 +-
src/tools/msvc/clean.bat | 2 +-
21 files changed, 973 insertions(+), 514 deletions(-)
delete mode 100644 src/backend/utils/misc/.gitignore
rename src/{backend/utils/misc => common}/guc-file.l (60%)
create mode 100644 src/include/common/guc-file.h
diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgmlindex 53a64ee29e..0c2441afa7 100644--- a/doc/src/sgml/ref/pg_rewind.sgml+++ b/doc/src/sgml/ref/pg_rewind.sgml@@ -67,8 +67,10 @@ PostgreSQL documentation
ancestor. In the typical failover scenario where the target cluster was
shut down soon after the divergence, this is not a problem, but if the
target cluster ran for a long time after the divergence, the old WAL
- files might no longer be present. In that case, they can be manually- copied from the WAL archive to the <filename>pg_wal</filename> directory, or+ files might no longer be present. In that case, they can be automatically+ copied by <application>pg_rewind</application> from the WAL archive to the + <filename>pg_wal</filename> directory if either <literal>-r</literal> or+ <literal>-R</literal> option is specified, or
fetched on startup by configuring <xref linkend="guc-primary-conninfo"/> or
<xref linkend="guc-restore-command"/>. The use of
<application>pg_rewind</application> is not limited to failover, e.g. a standby
@@ -200,6 +202,30 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>+ <term><option>-r</option></term>+ <term><option>--use-postgresql-conf</option></term>+ <listitem>+ <para>+ Use restore_command in the <filename>postgresql.conf</filename> to+ retreive missing in the target <filename>pg_wal</filename> directory+ WAL files from the WAL archive.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>+ <term><option>-R <replaceable class="parameter">restore_command</replaceable></option></term>+ <term><option>--restore-command=<replaceable class="parameter">restore_command</replaceable></option></term>+ <listitem>+ <para>+ Specifies the restore_command to use for retrieval of the missing+ in the target <filename>pg_wal</filename> directory WAL files from+ the WAL archive.+ </para>+ </listitem>+ </varlistentry>+
<varlistentry>
<term><option>--debug</option></term>
<listitem>
diff --git a/src/backend/Makefile b/src/backend/Makefileindex 478a96db9b..721cb57e89 100644--- a/src/backend/Makefile+++ b/src/backend/Makefile@@ -186,7 +186,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c
$(MAKE) -C utils distprep
- $(MAKE) -C utils/misc guc-file.c+ $(MAKE) -C common guc-file.c
$(MAKE) -C utils/sort qsort_tuple.c
@@ -307,7 +307,7 @@ maintainer-clean: distclean
replication/syncrep_scanner.c \
storage/lmgr/lwlocknames.c \
storage/lmgr/lwlocknames.h \
- utils/misc/guc-file.c \+ common/guc-file.c \
utils/sort/qsort_tuple.c
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.cindex daf3f51636..195eb8a821 100644--- a/src/backend/commands/extension.c+++ b/src/backend/commands/extension.c@@ -50,6 +50,7 @@
#include "commands/defrem.h"
#include "commands/extension.h"
#include "commands/schemacmds.h"
+#include "common/guc-file.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
diff --git a/src/backend/utils/misc/.gitignore b/src/backend/utils/misc/.gitignoredeleted file mode 100644index 495b1aec76..0000000000--- a/src/backend/utils/misc/.gitignore+++ /dev/null@@ -1 +0,0 @@-/guc-file.cdiff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefileindex ec7ec131e5..2205956a9f 100644--- a/src/backend/utils/misc/Makefile+++ b/src/backend/utils/misc/Makefile@@ -25,11 +25,3 @@ override CPPFLAGS += -DPG_KRB_SRVTAB='"$(krb_srvtab)"'
endif
include $(top_srcdir)/src/backend/common.mk
--# guc-file is compiled as part of guc-guc.o: guc-file.c--# Note: guc-file.c is not deleted by 'make clean',-# since we want to ship it in distribution tarballs.-clean:- @rm -f lex.yy.cdiff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.cindex ea5444c6f1..eff5b5aefa 100644--- a/src/backend/utils/misc/guc.c+++ b/src/backend/utils/misc/guc.c@@ -41,6 +41,7 @@
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "commands/trigger.h"
+#include "common/guc-file.h"
#include "common/string.h"
#include "funcapi.h"
#include "jit/jit.h"
@@ -212,7 +213,6 @@ static void assign_recovery_target_lsn(const char *newval, void *extra);
static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
-/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
bool applySettings, int elevel);
@@ -4491,7 +4491,6 @@ static int GUCNestLevel = 0; /* 1 when in main transaction */
static int guc_var_compare(const void *a, const void *b);
-static int guc_name_compare(const char *namea, const char *nameb);
static void InitializeGUCOptionsFromEnvironment(void);
static void InitializeOneGUCOption(struct config_generic *gconf);
static void push_old_value(struct config_generic *gconf, GucAction action);
@@ -4968,37 +4967,6 @@ guc_var_compare(const void *a, const void *b)
return guc_name_compare(confa->name, confb->name);
}
-/*- * the bare comparison function for GUC names- */-static int-guc_name_compare(const char *namea, const char *nameb)-{- /*- * The temptation to use strcasecmp() here must be resisted, because the- * array ordering has to remain stable across setlocale() calls. So, build- * our own with a simple ASCII-only downcasing.- */- while (*namea && *nameb)- {- char cha = *namea++;- char chb = *nameb++;-- if (cha >= 'A' && cha <= 'Z')- cha += 'a' - 'A';- if (chb >= 'A' && chb <= 'Z')- chb += 'a' - 'A';- if (cha != chb)- return cha - chb;- }- if (*namea)- return 1; /* a is longer */- if (*nameb)- return -1; /* b is longer */- return 0;-}--
/*
* Initialize GUC options during program startup.
*
@@ -11372,4 +11340,402 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
return true;
}
-#include "guc-file.c"+/*+ * Exported function to read and process the configuration file. The+ * parameter indicates in what context the file is being read --- either+ * postmaster startup (including standalone-backend startup) or SIGHUP.+ * All options mentioned in the configuration file are set to new values.+ * If a hard error occurs, no values will be changed. (There can also be+ * errors that prevent just one value from being changed.)+ */+void+ProcessConfigFile(GucContext context)+{+ int elevel;+ MemoryContext config_cxt;+ MemoryContext caller_cxt;++ /*+ * Config files are processed on startup (by the postmaster only) and on+ * SIGHUP (by the postmaster and its children)+ */+ Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||+ context == PGC_SIGHUP);++ /*+ * To avoid cluttering the log, only the postmaster bleats loudly about+ * problems with the config file.+ */+ elevel = IsUnderPostmaster ? DEBUG2 : LOG;++ /*+ * This function is usually called within a process-lifespan memory+ * context. To ensure that any memory leaked during GUC processing does+ * not accumulate across repeated SIGHUP cycles, do the work in a private+ * context that we can free at exit.+ */+ config_cxt = AllocSetContextCreate(CurrentMemoryContext,+ "config file processing",+ ALLOCSET_DEFAULT_SIZES);+ caller_cxt = MemoryContextSwitchTo(config_cxt);++ /*+ * Read and apply the config file. We don't need to examine the result.+ */+ (void) ProcessConfigFileInternal(context, true, elevel);++ /* Clean up */+ MemoryContextSwitchTo(caller_cxt);+ MemoryContextDelete(config_cxt);+}++/*+ * This function handles both actual config file (re)loads and execution of+ * show_all_file_settings() (i.e., the pg_file_settings view). In the latter+ * case we don't apply any of the settings, but we make all the usual validity+ * checks, and we return the ConfigVariable list so that it can be printed out+ * by show_all_file_settings().+ */+static ConfigVariable *+ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)+{+ bool error = false;+ bool applying = false;+ const char *ConfFileWithError;+ ConfigVariable *item,+ *head,+ *tail;+ int i;++ /* Parse the main config file into a list of option names and values */+ ConfFileWithError = ConfigFileName;+ head = tail = NULL;++ if (!ParseConfigFile(ConfigFileName, true,+ NULL, 0, 0, elevel,+ &head, &tail))+ {+ /* Syntax error(s) detected in the file, so bail out */+ error = true;+ goto bail_out;+ }++ /*+ * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to+ * replace any parameters set by ALTER SYSTEM command. Because this file+ * is in the data directory, we can't read it until the DataDir has been+ * set.+ */+ if (DataDir)+ {+ if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,+ NULL, 0, 0, elevel,+ &head, &tail))+ {+ /* Syntax error(s) detected in the file, so bail out */+ error = true;+ ConfFileWithError = PG_AUTOCONF_FILENAME;+ goto bail_out;+ }+ }+ else+ {+ /*+ * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be+ * read. In this case, we don't want to accept any settings but+ * data_directory from postgresql.conf, because they might be+ * overwritten with settings in the PG_AUTOCONF_FILENAME file which+ * will be read later. OTOH, since data_directory isn't allowed in the+ * PG_AUTOCONF_FILENAME file, it will never be overwritten later.+ */+ ConfigVariable *newlist = NULL;++ /*+ * Prune all items except the last "data_directory" from the list.+ */+ for (item = head; item; item = item->next)+ {+ if (!item->ignore &&+ strcmp(item->name, "data_directory") == 0)+ newlist = item;+ }++ if (newlist)+ newlist->next = NULL;+ head = tail = newlist;++ /*+ * Quick exit if data_directory is not present in file.+ *+ * We need not do any further processing, in particular we don't set+ * PgReloadTime; that will be set soon by subsequent full loading of+ * the config file.+ */+ if (head == NULL)+ goto bail_out;+ }++ /*+ * Mark all extant GUC variables as not present in the config file. We+ * need this so that we can tell below which ones have been removed from+ * the file since we last processed it.+ */+ for (i = 0; i < num_guc_variables; i++)+ {+ struct config_generic *gconf = guc_variables[i];++ gconf->status &= ~GUC_IS_IN_FILE;+ }++ /*+ * Check if all the supplied option names are valid, as an additional+ * quasi-syntactic check on the validity of the config file. It is+ * important that the postmaster and all backends agree on the results of+ * this phase, else we will have strange inconsistencies about which+ * processes accept a config file update and which don't. Hence, unknown+ * custom variable names have to be accepted without complaint. For the+ * same reason, we don't attempt to validate the options' values here.+ *+ * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC+ * variable mentioned in the file; and we detect duplicate entries in the+ * file and mark the earlier occurrences as ignorable.+ */+ for (item = head; item; item = item->next)+ {+ struct config_generic *record;++ /* Ignore anything already marked as ignorable */+ if (item->ignore)+ continue;++ /*+ * Try to find the variable; but do not create a custom placeholder if+ * it's not there already.+ */+ record = find_option(item->name, false, elevel);++ if (record)+ {+ /* If it's already marked, then this is a duplicate entry */+ if (record->status & GUC_IS_IN_FILE)+ {+ /*+ * Mark the earlier occurrence(s) as dead/ignorable. We could+ * avoid the O(N^2) behavior here with some additional state,+ * but it seems unlikely to be worth the trouble.+ */+ ConfigVariable *pitem;++ for (pitem = head; pitem != item; pitem = pitem->next)+ {+ if (!pitem->ignore &&+ strcmp(pitem->name, item->name) == 0)+ pitem->ignore = true;+ }+ }+ /* Now mark it as present in file */+ record->status |= GUC_IS_IN_FILE;+ }+ else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)+ {+ /* Invalid non-custom variable, so complain */+ ereport(elevel,+ (errcode(ERRCODE_UNDEFINED_OBJECT),+ errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",+ item->name,+ item->filename, item->sourceline)));+ item->errmsg = pstrdup("unrecognized configuration parameter");+ error = true;+ ConfFileWithError = item->filename;+ }+ }++ /*+ * If we've detected any errors so far, we don't want to risk applying any+ * changes.+ */+ if (error)+ goto bail_out;++ /* Otherwise, set flag that we're beginning to apply changes */+ applying = true;++ /*+ * Check for variables having been removed from the config file, and+ * revert their reset values (and perhaps also effective values) to the+ * boot-time defaults. If such a variable can't be changed after startup,+ * report that and continue.+ */+ for (i = 0; i < num_guc_variables; i++)+ {+ struct config_generic *gconf = guc_variables[i];+ GucStack *stack;++ if (gconf->reset_source != PGC_S_FILE ||+ (gconf->status & GUC_IS_IN_FILE))+ continue;+ if (gconf->context < PGC_SIGHUP)+ {+ ereport(elevel,+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),+ errmsg("parameter \"%s\" cannot be changed without restarting the server",+ gconf->name)));+ record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",+ gconf->name),+ NULL, 0,+ &head, &tail);+ error = true;+ continue;+ }++ /* No more to do if we're just doing show_all_file_settings() */+ if (!applySettings)+ continue;++ /*+ * Reset any "file" sources to "default", else set_config_option will+ * not override those settings.+ */+ if (gconf->reset_source == PGC_S_FILE)+ gconf->reset_source = PGC_S_DEFAULT;+ if (gconf->source == PGC_S_FILE)+ gconf->source = PGC_S_DEFAULT;+ for (stack = gconf->stack; stack; stack = stack->prev)+ {+ if (stack->source == PGC_S_FILE)+ stack->source = PGC_S_DEFAULT;+ }++ /* Now we can re-apply the wired-in default (i.e., the boot_val) */+ if (set_config_option(gconf->name, NULL,+ context, PGC_S_DEFAULT,+ GUC_ACTION_SET, true, 0, false) > 0)+ {+ /* Log the change if appropriate */+ if (context == PGC_SIGHUP)+ ereport(elevel,+ (errmsg("parameter \"%s\" removed from configuration file, reset to default",+ gconf->name)));+ }+ }++ /*+ * Restore any variables determined by environment variables or+ * dynamically-computed defaults. This is a no-op except in the case+ * where one of these had been in the config file and is now removed.+ *+ * In particular, we *must not* do this during the postmaster's initial+ * loading of the file, since the timezone functions in particular should+ * be run only after initialization is complete.+ *+ * XXX this is an unmaintainable crock, because we have to know how to set+ * (or at least what to call to set) every variable that could potentially+ * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no+ * time to redesign it for 9.1.+ */+ if (context == PGC_SIGHUP && applySettings)+ {+ InitializeGUCOptionsFromEnvironment();+ pg_timezone_abbrev_initialize();+ /* this selects SQL_ASCII in processes not connected to a database */+ SetConfigOption("client_encoding", GetDatabaseEncodingName(),+ PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);+ }++ /*+ * Now apply the values from the config file.+ */+ for (item = head; item; item = item->next)+ {+ char *pre_value = NULL;+ int scres;++ /* Ignore anything marked as ignorable */+ if (item->ignore)+ continue;++ /* In SIGHUP cases in the postmaster, we want to report changes */+ if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)+ {+ const char *preval = GetConfigOption(item->name, true, false);++ /* If option doesn't exist yet or is NULL, treat as empty string */+ if (!preval)+ preval = "";+ /* must dup, else might have dangling pointer below */+ pre_value = pstrdup(preval);+ }++ scres = set_config_option(item->name, item->value,+ context, PGC_S_FILE,+ GUC_ACTION_SET, applySettings, 0, false);+ if (scres > 0)+ {+ /* variable was updated, so log the change if appropriate */+ if (pre_value)+ {+ const char *post_value = GetConfigOption(item->name, true, false);++ if (!post_value)+ post_value = "";+ if (strcmp(pre_value, post_value) != 0)+ ereport(elevel,+ (errmsg("parameter \"%s\" changed to \"%s\"",+ item->name, item->value)));+ }+ item->applied = true;+ }+ else if (scres == 0)+ {+ error = true;+ item->errmsg = pstrdup("setting could not be applied");+ ConfFileWithError = item->filename;+ }+ else+ {+ /* no error, but variable's active value was not changed */+ item->applied = true;+ }++ /*+ * We should update source location unless there was an error, since+ * even if the active value didn't change, the reset value might have.+ * (In the postmaster, there won't be a difference, but it does matter+ * in backends.)+ */+ if (scres != 0 && applySettings)+ set_config_sourcefile(item->name, item->filename,+ item->sourceline);++ if (pre_value)+ pfree(pre_value);+ }++ /* Remember when we last successfully loaded the config file. */+ if (applySettings)+ PgReloadTime = GetCurrentTimestamp();++bail_out:+ if (error && applySettings)+ {+ /* During postmaster startup, any error is fatal */+ if (context == PGC_POSTMASTER)+ ereport(ERROR,+ (errcode(ERRCODE_CONFIG_FILE_ERROR),+ errmsg("configuration file \"%s\" contains errors",+ ConfFileWithError)));+ else if (applying)+ ereport(elevel,+ (errcode(ERRCODE_CONFIG_FILE_ERROR),+ errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",+ ConfFileWithError)));+ else+ ereport(elevel,+ (errcode(ERRCODE_CONFIG_FILE_ERROR),+ errmsg("configuration file \"%s\" contains errors; no changes were applied",+ ConfFileWithError)));+ }++ /* Successful or otherwise, return the collected data list */+ return head;+}diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefileindex 04f3b8f520..65ebf3c78c 100644--- a/src/bin/pg_rewind/Makefile+++ b/src/bin/pg_rewind/Makefile@@ -15,7 +15,7 @@ subdir = src/bin/pg_rewind
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
LDFLAGS_INTERNAL += $(libpq_pgport)
OBJS = pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.cindex e19c265cbb..39cbe78f84 100644--- a/src/bin/pg_rewind/parsexlog.c+++ b/src/bin/pg_rewind/parsexlog.c@@ -12,6 +12,7 @@
#include "postgres_fe.h"
#include <unistd.h>
+#include <sys/stat.h>
#include "pg_rewind.h"
#include "filemap.h"
@@ -45,6 +46,7 @@ static char xlogfpath[MAXPGPATH];
typedef struct XLogPageReadPrivate
{
const char *datadir;
+ const char *restoreCommand;
int tliIndex;
} XLogPageReadPrivate;
@@ -53,6 +55,9 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
int reqLen, XLogRecPtr targetRecPtr, char *readBuf,
TimeLineID *pageTLI);
+static bool RestoreArchivedWAL(const char *path, const char *xlogfname,+ off_t expectedSize, const char *restoreCommand);+
/*
* Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
* index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
@@ -60,15 +65,17 @@ static int SimpleXLogPageRead(XLogReaderState *xlogreader,
*/
void
extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
- XLogRecPtr endpoint)+ ControlFileData *targetCF, const char *restore_command)
{
XLogRecord *record;
+ XLogRecPtr endpoint = targetCF->checkPoint;
XLogReaderState *xlogreader;
char *errormsg;
XLogPageReadPrivate private;
private.datadir = datadir;
private.tliIndex = tliIndex;
+ private.restoreCommand = restore_command;
xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
&private);
if (xlogreader == NULL)
@@ -156,7 +163,7 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
void
findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
- XLogRecPtr *lastchkptredo)+ XLogRecPtr *lastchkptredo, const char *restoreCommand)
{
/* Walk backwards, starting from the given record */
XLogRecord *record;
@@ -181,6 +188,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
private.datadir = datadir;
private.tliIndex = tliIndex;
+ private.restoreCommand = restoreCommand;
xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
&private);
if (xlogreader == NULL)
@@ -291,9 +299,46 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
- printf(_("could not open file \"%s\": %s\n"), xlogfpath,- strerror(errno));- return -1;+ bool restore_ok;++ /*+ * If we have no restore_command to execute, then exit.+ */+ if (private->restoreCommand == NULL)+ {+ printf(_("could not open file \"%s\": %s\n"), xlogfpath,+ strerror(errno));+ return -1;+ }++ /*+ * Since we have restore_command to execute, then try to retreive+ * missing WAL file from the archive.+ */+ restore_ok = RestoreArchivedWAL(private->datadir,+ xlogfname,+ WalSegSz,+ private->restoreCommand);++ if (restore_ok)+ {+ xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);++ if (xlogreadfd < 0)+ {+ printf(_("could not open restored from archive file \"%s\": %s\n"), xlogfpath,+ strerror(errno));+ return -1;+ }+ else+ pg_log(PG_DEBUG, "using restored from archive version of file \"%s\"\n", xlogfpath);+ }+ else+ {+ printf(_("could not restore file \"%s\" from archive: %s\n"), xlogfname,+ strerror(errno));+ return -1;+ }
}
}
@@ -409,3 +454,114 @@ extractPageInfo(XLogReaderState *record)
process_block_change(forknum, rnode, blkno);
}
}
++/*+ * Attempt to retrieve the specified file from off-line archival storage.+ * If successful return true.+ *+ * For fixed-size files, the caller may pass the expected size as an+ * additional crosscheck on successful recovery. If the file size is not+ * known, set expectedSize = 0.+ *+ * This is a simplified and adapted to frontend version of+ * RestoreArchivedFile function from transam/xlogarchive.c+ */+bool+RestoreArchivedWAL(const char *path, const char *xlogfname, off_t expectedSize,+ const char *restoreCommand)+{+ char xlogpath[MAXPGPATH];+ char xlogRestoreCmd[MAXPGPATH];+ char *dp;+ char *endp;+ const char *sp;+ int rc;+ struct stat stat_buf;++ snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);++ /*+ * Construct the command to be executed.+ */+ dp = xlogRestoreCmd;+ endp = xlogRestoreCmd + MAXPGPATH - 1;+ *endp = '\0';++ for (sp = restoreCommand; *sp; sp++)+ {+ if (*sp == '%')+ {+ switch (sp[1])+ {+ case 'p':+ /* %p: relative path of target file */+ sp++;+ StrNCpy(dp, xlogpath, endp - dp);+ make_native_path(dp);+ dp += strlen(dp);+ break;+ case 'f':+ /* %f: filename of desired file */+ sp++;+ StrNCpy(dp, xlogfname, endp - dp);+ dp += strlen(dp);+ break;+ case 'r':+ /* %r: filename of last restartpoint */+ pg_fatal("restore_command with %%r cannot be used during rewind process.\n");+ break;+ case '%':+ /* convert %% to a single % */+ sp++;+ if (dp < endp)+ *dp++ = *sp;+ break;+ default:+ /* otherwise treat the % as not special */+ if (dp < endp)+ *dp++ = *sp;+ break;+ }+ }+ else+ {+ if (dp < endp)+ *dp++ = *sp;+ }+ }+ *dp = '\0';++ /*+ * Execute restore_command, which should copy+ * the missing WAL file from archival storage.+ */+ rc = system(xlogRestoreCmd);++ if (rc == 0)+ {+ /*+ * Command apparently succeeded, but let's make sure the file is+ * really there now and has the correct size.+ */+ if (stat(xlogpath, &stat_buf) == 0)+ {+ if (expectedSize > 0 && stat_buf.st_size != expectedSize)+ {+ printf(_("archive file \"%s\" has wrong size: %lu instead of %lu, %s"),+ xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize,+ strerror(errno));+ }+ else+ return true;+ }+ else+ {+ /* Stat failed */+ printf(_("could not stat file \"%s\": %s"),+ xlogpath,+ strerror(errno));+ }+ }++ return false;+}diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.cindex 7ccde5c87f..a89fdde7cc 100644--- a/src/bin/pg_rewind/pg_rewind.c+++ b/src/bin/pg_rewind/pg_rewind.c@@ -26,6 +26,7 @@
#include "catalog/pg_control.h"
#include "common/file_perm.h"
#include "common/file_utils.h"
+#include "common/guc-file.h"
#include "common/restricted_token.h"
#include "getopt_long.h"
#include "storage/bufpage.h"
@@ -52,11 +53,13 @@ int WalSegSz;
char *datadir_target = NULL;
char *datadir_source = NULL;
char *connstr_source = NULL;
+char *restore_command = NULL;
bool debug = false;
bool showprogress = false;
bool dry_run = false;
bool do_sync = true;
+bool restore_wals = false;
/* Target history */
TimeLineHistoryEntry *targetHistory;
@@ -75,6 +78,9 @@ usage(const char *progname)
printf(_(" -N, --no-sync do not wait for changes to be written\n"));
printf(_(" safely to disk\n"));
printf(_(" -P, --progress write progress messages\n"));
+ printf(_(" -r, --use-postgresql-conf use restore_command in the postgresql.conf to\n"));+ printf(_(" retreive WALs from archive\n"));+ printf(_(" -R, --restore-command=COMMAND restore_command\n"));
printf(_(" --debug write a lot of debug messages\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -?, --help show this help, then exit\n"));
@@ -94,9 +100,12 @@ main(int argc, char **argv)
{"dry-run", no_argument, NULL, 'n'},
{"no-sync", no_argument, NULL, 'N'},
{"progress", no_argument, NULL, 'P'},
+ {"use-postgresql-conf", no_argument, NULL, 'r'},+ {"restore-command", required_argument, NULL, 'R'},
{"debug", no_argument, NULL, 3},
{NULL, 0, NULL, 0}
};
+ char recfile_fullpath[MAXPGPATH];
int option_index;
int c;
XLogRecPtr divergerec;
@@ -129,7 +138,7 @@ main(int argc, char **argv)
}
}
- while ((c = getopt_long(argc, argv, "D:nNP", long_options, &option_index)) != -1)+ while ((c = getopt_long(argc, argv, "D:nNPR:r", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -141,6 +150,10 @@ main(int argc, char **argv)
showprogress = true;
break;
+ case 'r':+ restore_wals = true;+ break;+
case 'n':
dry_run = true;
break;
@@ -157,6 +170,10 @@ main(int argc, char **argv)
datadir_target = pg_strdup(optarg);
break;
+ case 'R':+ restore_command = pg_strdup(optarg);+ break;+
case 1: /* --source-pgdata */
datadir_source = pg_strdup(optarg);
break;
@@ -223,6 +240,80 @@ main(int argc, char **argv)
umask(pg_mode_mask);
+ if (restore_command != NULL)+ {+ if (restore_wals)+ {+ fprintf(stderr, _("%s: conflicting options: both -r and -R are specified\n"),+ progname);+ fprintf(stderr, _("You must run %s with either -r/--use-postgresql-conf or -R/--restore-command.\n"),+ progname);+ exit(1);+ }++ pg_log(PG_DEBUG, "using command line restore_command=\'%s\'.\n", restore_command);+ }+ else if (restore_wals)+ {+ FILE *conf_file;++ /*+ * Look for configuration file in the target data directory and+ * try to get restore_command from there.+ */+ snprintf(recfile_fullpath, sizeof(recfile_fullpath), "%s/%s", datadir_target, RESTORE_COMMAND_FILE);+ conf_file = fopen(recfile_fullpath, "r");++ if (conf_file == NULL)+ {+ fprintf(stderr, _("%s: option -r/--use-postgresql-conf is specified, but postgreslq.conf is absent in the target directory\n"),+ progname);+ fprintf(stderr, _("You have to add postgresql.conf or pass restore_command with -R/--restore-command option.\n"));+ exit(1);+ }+ else+ {+ ConfigVariable *item,+ *head = NULL,+ *tail = NULL;+ bool config_is_parsed;++ /*+ * We pass a fullpath to the configuration file as calling_file here, since+ * parser will use its parent directory as base for all further includes+ * if any exist.+ */+ config_is_parsed = ParseConfigFile(RESTORE_COMMAND_FILE, true,+ recfile_fullpath, 0, 0,+ PG_WARNING, &head, &tail);+ fclose(conf_file);++ if (config_is_parsed)+ {+ for (item = head; item; item = item->next)+ {+ if (strcmp(item->name, "restore_command") == 0)+ {+ if (restore_command != NULL)+ {+ pfree(restore_command);+ restore_command = NULL;+ }+ restore_command = pstrdup(item->value);+ pg_log(PG_DEBUG, "using restore_command=\'%s\' from %s.\n", restore_command, RESTORE_COMMAND_FILE);+ }+ }++ if (restore_command == NULL)+ pg_fatal("could not find restore_command in %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);+ }+ else+ pg_fatal("could not parse %s file %s\n", RESTORE_COMMAND_FILE, recfile_fullpath);++ FreeConfigVariables(head);+ }+ }+
/* Connect to remote server */
if (connstr_source)
libpqConnect(connstr_source);
@@ -294,9 +385,8 @@ main(int argc, char **argv)
exit(0);
}
- findLastCheckpoint(datadir_target, divergerec,- lastcommontliIndex,- &chkptrec, &chkpttli, &chkptredo);+ findLastCheckpoint(datadir_target, divergerec, lastcommontliIndex,+ &chkptrec, &chkpttli, &chkptredo, restore_command);
printf(_("rewinding from last common checkpoint at %X/%X on timeline %u\n"),
(uint32) (chkptrec >> 32), (uint32) chkptrec,
chkpttli);
@@ -319,7 +409,7 @@ main(int argc, char **argv)
*/
pg_log(PG_PROGRESS, "reading WAL in target\n");
extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
- ControlFile_target.checkPoint);+ &ControlFile_target, restore_command);
filemap_finalize();
if (showprogress)
diff --git a/src/bin/pg_rewind/pg_rewind.h b/src/bin/pg_rewind/pg_rewind.hindex 83b2898b8b..0df8637e6d 100644--- a/src/bin/pg_rewind/pg_rewind.h+++ b/src/bin/pg_rewind/pg_rewind.h@@ -14,9 +14,12 @@
#include "datapagemap.h"
#include "access/timeline.h"
+#include "catalog/pg_control.h"
#include "storage/block.h"
#include "storage/relfilenode.h"
+#define RESTORE_COMMAND_FILE "postgresql.conf"+
/* Configuration options */
extern char *datadir_target;
extern char *datadir_source;
@@ -32,11 +35,10 @@ extern int targetNentries;
/* in parsexlog.c */
extern void extractPageMap(const char *datadir, XLogRecPtr startpoint,
- int tliIndex, XLogRecPtr endpoint);+ int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
- int tliIndex,- XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,- XLogRecPtr *lastchkptredo);+ int tliIndex, XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,+ XLogRecPtr *lastchkptredo, const char *restoreCommand);
extern XLogRecPtr readOneRecord(const char *datadir, XLogRecPtr ptr,
int tliIndex);
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.plindex 115192170e..8a6fa33016 100644--- a/src/bin/pg_rewind/t/001_basic.pl+++ b/src/bin/pg_rewind/t/001_basic.pl@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 10;+use Test::More tests => 20;
use FindBin;
use lib $FindBin::RealBin;
@@ -106,5 +106,7 @@ in master, before promotion
# Run the test in both modes
run_test('local');
run_test('remote');
+run_test('archive');+run_test('archive_conf');
exit(0);
diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.plindex 6dc05720a1..99ea821cfe 100644--- a/src/bin/pg_rewind/t/002_databases.pl+++ b/src/bin/pg_rewind/t/002_databases.pl@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 6;+use Test::More tests => 12;
use FindBin;
use lib $FindBin::RealBin;
@@ -62,5 +62,7 @@ template1
# Run the test in both modes.
run_test('local');
run_test('remote');
+run_test('archive');+run_test('archive_conf');
exit(0);
diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.plindex c4040bd562..24cec256de 100644--- a/src/bin/pg_rewind/t/003_extrafiles.pl+++ b/src/bin/pg_rewind/t/003_extrafiles.pl@@ -3,7 +3,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 4;+use Test::More tests => 8;
use File::Find;
@@ -90,5 +90,7 @@ sub run_test
# Run the test in both modes.
run_test('local');
run_test('remote');
+run_test('archive');+run_test('archive_conf');
exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pmindex 85cae7e47b..79bd6434b4 100644--- a/src/bin/pg_rewind/t/RewindTest.pm+++ b/src/bin/pg_rewind/t/RewindTest.pm@@ -39,7 +39,9 @@ use Carp;
use Config;
use Exporter 'import';
use File::Copy;
-use File::Path qw(rmtree);+use File::Glob ':bsd_glob';+use File::Path qw(remove_tree make_path);+use File::Spec::Functions qw(catpath catfile);
use IPC::Run qw(run);
use PostgresNode;
use TestLib;
@@ -249,6 +251,95 @@ sub run_pg_rewind
],
'pg_rewind remote');
}
+ elsif ($test_mode eq "archive")+ {++ # Do rewind using a local pgdata as source and+ # specified directory with target WALs archive.+ my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');+ my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');+ my $restore_command;++ remove_tree($wals_archive_dir);+ make_path($wals_archive_dir) or die;++ # Move all old master WAL files to the archive.+ # Old master should be stopped at this point.+ foreach my $wal_file (@wal_files)+ {+ move($wal_file, "$wals_archive_dir") or die;+ }++ if ($windows_os)+ {+ $restore_command = "copy $wals_archive_dir\\\%f \%p";+ }+ else+ {+ $restore_command = "cp $wals_archive_dir/\%f \%p";+ }++ # Stop the new master and be ready to perform the rewind.+ $node_standby->stop;+ command_ok(+ [+ 'pg_rewind',+ "--debug",+ "--source-pgdata=$standby_pgdata",+ "--target-pgdata=$master_pgdata",+ "--no-sync",+ "--restore-command=$restore_command"+ ],+ 'pg_rewind archive');+ }+ elsif ($test_mode eq "archive_conf")+ {++ # Do rewind using a local pgdata as source and+ # specified directory with target WALs archive.+ my $wals_archive_dir = catpath(${TestLib::tmp_check}, 'master_wals_archive');+ my @wal_files = bsd_glob catpath($master_pgdata, 'pg_wal', '0000000*');+ my $master_conf_path = catfile($master_pgdata, 'postgresql.conf');+ my $restore_command;++ remove_tree($wals_archive_dir);+ make_path($wals_archive_dir) or die;++ # Move all old master WAL files to the archive.+ # Old master should be stopped at this point.+ foreach my $wal_file (@wal_files)+ {+ move($wal_file, "$wals_archive_dir") or die;+ }++ if ($windows_os)+ {+ $restore_command = "copy $wals_archive_dir\\\%f \%p";+ }+ else+ {+ $restore_command = "cp $wals_archive_dir/\%f \%p";+ }++ # Stop the new master and be ready to perform the rewind.+ $node_standby->stop;++ # Add restore_command to postgresql.conf of target cluster.+ open(my $conf_fd, ">>", $master_conf_path) or die;+ print $conf_fd "\nrestore_command='$restore_command'";+ close $conf_fd;++ command_ok(+ [+ 'pg_rewind',+ "--debug",+ "--source-pgdata=$standby_pgdata",+ "--target-pgdata=$master_pgdata",+ "--no-sync",+ "-r"+ ],+ 'pg_rewind archive_conf');+ }
else
{
diff --git a/src/common/.gitignore b/src/common/.gitignoreindex ffa3284fbf..424beacbc9 100644--- a/src/common/.gitignore+++ b/src/common/.gitignore@@ -1 +1,2 @@
/kwlist_d.h
+/guc-file.cdiff --git a/src/common/Makefile b/src/common/Makefileindex d0c2b970eb..3d7da47f56 100644--- a/src/common/Makefile+++ b/src/common/Makefile@@ -48,7 +48,7 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
ip.o keywords.o kwlookup.o link-canary.o md5.o pg_lzcompress.o \
pgfnames.o psprintf.o relpath.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
- username.o wait_error.o+ username.o wait_error.o guc-file.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
@@ -70,7 +70,7 @@ GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
-distprep: kwlist_d.h+distprep: kwlist_d.h guc-file.c
# libpgcommon is needed by some contrib
install: all installdirs
@@ -130,10 +130,13 @@ kwlist_d.h: $(top_srcdir)/src/include/parser/kwlist.h $(GEN_KEYWORDLIST_DEPS)
# that you don't get broken parsing code, even in a non-enable-depend build.
keywords.o keywords_shlib.o keywords_srv.o: kwlist_d.h
-# kwlist_d.h is in the distribution tarball, so it is not cleaned here.+# Note: guc-file.c and kwlist_d.h are not deleted by 'make clean',+# since we want to ship them in distribution tarballs.
clean distclean:
rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+ @rm -f lex.yy.c
maintainer-clean: distclean
rm -f kwlist_d.h
+ rm -f guc-file.cdiff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.lsimilarity index 60%rename from src/backend/utils/misc/guc-file.lrename to src/common/guc-file.lindex 1c8b5f7d84..175685c9b5 100644--- a/src/backend/utils/misc/guc-file.l+++ b/src/common/guc-file.l@@ -4,21 +4,31 @@
*
* Copyright (c) 2000-2019, PostgreSQL Global Development Group
*
- * src/backend/utils/misc/guc-file.l+ * src/common/guc-file.l
*/
%{
+#ifndef FRONTEND
#include "postgres.h"
-#include <ctype.h>-#include <unistd.h>-
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/guc.h"
+#include "utils/elog.h"+#else+#include "postgres_fe.h"++#include <dirent.h>+#include <setjmp.h>+#endif++#include <ctype.h>+#include <unistd.h>+#include <sys/stat.h>+#include "common/guc-file.h"
/*
* flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +58,6 @@ static sigjmp_buf *GUC_flex_fatal_jmp;
static void FreeConfigVariable(ConfigVariable *item);
-static void record_config_file_error(const char *errmsg,- const char *config_file,- int lineno,- ConfigVariable **head_p,- ConfigVariable **tail_p);-
static int GUC_flex_fatal(const char *msg);
static char *GUC_scanstr(const char *s);
@@ -111,404 +115,35 @@ STRING \'([^'\\\n]|\\.|\'\')*\'
/* LCOV_EXCL_STOP */
-/*- * Exported function to read and process the configuration file. The- * parameter indicates in what context the file is being read --- either- * postmaster startup (including standalone-backend startup) or SIGHUP.- * All options mentioned in the configuration file are set to new values.- * If a hard error occurs, no values will be changed. (There can also be- * errors that prevent just one value from being changed.)- */-void-ProcessConfigFile(GucContext context)-{- int elevel;- MemoryContext config_cxt;- MemoryContext caller_cxt;-- /*- * Config files are processed on startup (by the postmaster only) and on- * SIGHUP (by the postmaster and its children)- */- Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||- context == PGC_SIGHUP);-- /*- * To avoid cluttering the log, only the postmaster bleats loudly about- * problems with the config file.- */- elevel = IsUnderPostmaster ? DEBUG2 : LOG;-- /*- * This function is usually called within a process-lifespan memory- * context. To ensure that any memory leaked during GUC processing does- * not accumulate across repeated SIGHUP cycles, do the work in a private- * context that we can free at exit.- */- config_cxt = AllocSetContextCreate(CurrentMemoryContext,- "config file processing",- ALLOCSET_DEFAULT_SIZES);- caller_cxt = MemoryContextSwitchTo(config_cxt);-- /*- * Read and apply the config file. We don't need to examine the result.- */- (void) ProcessConfigFileInternal(context, true, elevel);-- /* Clean up */- MemoryContextSwitchTo(caller_cxt);- MemoryContextDelete(config_cxt);-}
/*
- * This function handles both actual config file (re)loads and execution of- * show_all_file_settings() (i.e., the pg_file_settings view). In the latter- * case we don't apply any of the settings, but we make all the usual validity- * checks, and we return the ConfigVariable list so that it can be printed out- * by show_all_file_settings().+ * The bare comparison function for GUC names.
*/
-static ConfigVariable *-ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)+int+guc_name_compare(const char *namea, const char *nameb)
{
- bool error = false;- bool applying = false;- const char *ConfFileWithError;- ConfigVariable *item,- *head,- *tail;- int i;-- /* Parse the main config file into a list of option names and values */- ConfFileWithError = ConfigFileName;- head = tail = NULL;-- if (!ParseConfigFile(ConfigFileName, true,- NULL, 0, 0, elevel,- &head, &tail))- {- /* Syntax error(s) detected in the file, so bail out */- error = true;- goto bail_out;- }-- /*- * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to- * replace any parameters set by ALTER SYSTEM command. Because this file- * is in the data directory, we can't read it until the DataDir has been- * set.- */- if (DataDir)- {- if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false,- NULL, 0, 0, elevel,- &head, &tail))- {- /* Syntax error(s) detected in the file, so bail out */- error = true;- ConfFileWithError = PG_AUTOCONF_FILENAME;- goto bail_out;- }- }- else- {- /*- * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be- * read. In this case, we don't want to accept any settings but- * data_directory from postgresql.conf, because they might be- * overwritten with settings in the PG_AUTOCONF_FILENAME file which- * will be read later. OTOH, since data_directory isn't allowed in the- * PG_AUTOCONF_FILENAME file, it will never be overwritten later.- */- ConfigVariable *newlist = NULL;-- /*- * Prune all items except the last "data_directory" from the list.- */- for (item = head; item; item = item->next)- {- if (!item->ignore &&- strcmp(item->name, "data_directory") == 0)- newlist = item;- }-- if (newlist)- newlist->next = NULL;- head = tail = newlist;-- /*- * Quick exit if data_directory is not present in file.- *- * We need not do any further processing, in particular we don't set- * PgReloadTime; that will be set soon by subsequent full loading of- * the config file.- */- if (head == NULL)- goto bail_out;- }-
/*
- * Mark all extant GUC variables as not present in the config file. We- * need this so that we can tell below which ones have been removed from- * the file since we last processed it.+ * The temptation to use strcasecmp() here must be resisted, because the+ * array ordering has to remain stable across setlocale() calls. So, build+ * our own with a simple ASCII-only downcasing.
*/
- for (i = 0; i < num_guc_variables; i++)+ while (*namea && *nameb)
{
- struct config_generic *gconf = guc_variables[i];-- gconf->status &= ~GUC_IS_IN_FILE;- }-- /*- * Check if all the supplied option names are valid, as an additional- * quasi-syntactic check on the validity of the config file. It is- * important that the postmaster and all backends agree on the results of- * this phase, else we will have strange inconsistencies about which- * processes accept a config file update and which don't. Hence, unknown- * custom variable names have to be accepted without complaint. For the- * same reason, we don't attempt to validate the options' values here.- *- * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC- * variable mentioned in the file; and we detect duplicate entries in the- * file and mark the earlier occurrences as ignorable.- */- for (item = head; item; item = item->next)- {- struct config_generic *record;-- /* Ignore anything already marked as ignorable */- if (item->ignore)- continue;-- /*- * Try to find the variable; but do not create a custom placeholder if- * it's not there already.- */- record = find_option(item->name, false, elevel);-- if (record)- {- /* If it's already marked, then this is a duplicate entry */- if (record->status & GUC_IS_IN_FILE)- {- /*- * Mark the earlier occurrence(s) as dead/ignorable. We could- * avoid the O(N^2) behavior here with some additional state,- * but it seems unlikely to be worth the trouble.- */- ConfigVariable *pitem;-- for (pitem = head; pitem != item; pitem = pitem->next)- {- if (!pitem->ignore &&- strcmp(pitem->name, item->name) == 0)- pitem->ignore = true;- }- }- /* Now mark it as present in file */- record->status |= GUC_IS_IN_FILE;- }- else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)- {- /* Invalid non-custom variable, so complain */- ereport(elevel,- (errcode(ERRCODE_UNDEFINED_OBJECT),- errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %u",- item->name,- item->filename, item->sourceline)));- item->errmsg = pstrdup("unrecognized configuration parameter");- error = true;- ConfFileWithError = item->filename;- }+ char cha = *namea++;+ char chb = *nameb++;++ if (cha >= 'A' && cha <= 'Z')+ cha += 'a' - 'A';+ if (chb >= 'A' && chb <= 'Z')+ chb += 'a' - 'A';+ if (cha != chb)+ return cha - chb;
}
-- /*- * If we've detected any errors so far, we don't want to risk applying any- * changes.- */- if (error)- goto bail_out;-- /* Otherwise, set flag that we're beginning to apply changes */- applying = true;-- /*- * Check for variables having been removed from the config file, and- * revert their reset values (and perhaps also effective values) to the- * boot-time defaults. If such a variable can't be changed after startup,- * report that and continue.- */- for (i = 0; i < num_guc_variables; i++)- {- struct config_generic *gconf = guc_variables[i];- GucStack *stack;-- if (gconf->reset_source != PGC_S_FILE ||- (gconf->status & GUC_IS_IN_FILE))- continue;- if (gconf->context < PGC_SIGHUP)- {- ereport(elevel,- (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),- errmsg("parameter \"%s\" cannot be changed without restarting the server",- gconf->name)));- record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server",- gconf->name),- NULL, 0,- &head, &tail);- error = true;- continue;- }-- /* No more to do if we're just doing show_all_file_settings() */- if (!applySettings)- continue;-- /*- * Reset any "file" sources to "default", else set_config_option will- * not override those settings.- */- if (gconf->reset_source == PGC_S_FILE)- gconf->reset_source = PGC_S_DEFAULT;- if (gconf->source == PGC_S_FILE)- gconf->source = PGC_S_DEFAULT;- for (stack = gconf->stack; stack; stack = stack->prev)- {- if (stack->source == PGC_S_FILE)- stack->source = PGC_S_DEFAULT;- }-- /* Now we can re-apply the wired-in default (i.e., the boot_val) */- if (set_config_option(gconf->name, NULL,- context, PGC_S_DEFAULT,- GUC_ACTION_SET, true, 0, false) > 0)- {- /* Log the change if appropriate */- if (context == PGC_SIGHUP)- ereport(elevel,- (errmsg("parameter \"%s\" removed from configuration file, reset to default",- gconf->name)));- }- }-- /*- * Restore any variables determined by environment variables or- * dynamically-computed defaults. This is a no-op except in the case- * where one of these had been in the config file and is now removed.- *- * In particular, we *must not* do this during the postmaster's initial- * loading of the file, since the timezone functions in particular should- * be run only after initialization is complete.- *- * XXX this is an unmaintainable crock, because we have to know how to set- * (or at least what to call to set) every variable that could potentially- * have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. However, there's no- * time to redesign it for 9.1.- */- if (context == PGC_SIGHUP && applySettings)- {- InitializeGUCOptionsFromEnvironment();- pg_timezone_abbrev_initialize();- /* this selects SQL_ASCII in processes not connected to a database */- SetConfigOption("client_encoding", GetDatabaseEncodingName(),- PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);- }-- /*- * Now apply the values from the config file.- */- for (item = head; item; item = item->next)- {- char *pre_value = NULL;- int scres;-- /* Ignore anything marked as ignorable */- if (item->ignore)- continue;-- /* In SIGHUP cases in the postmaster, we want to report changes */- if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)- {- const char *preval = GetConfigOption(item->name, true, false);-- /* If option doesn't exist yet or is NULL, treat as empty string */- if (!preval)- preval = "";- /* must dup, else might have dangling pointer below */- pre_value = pstrdup(preval);- }-- scres = set_config_option(item->name, item->value,- context, PGC_S_FILE,- GUC_ACTION_SET, applySettings, 0, false);- if (scres > 0)- {- /* variable was updated, so log the change if appropriate */- if (pre_value)- {- const char *post_value = GetConfigOption(item->name, true, false);-- if (!post_value)- post_value = "";- if (strcmp(pre_value, post_value) != 0)- ereport(elevel,- (errmsg("parameter \"%s\" changed to \"%s\"",- item->name, item->value)));- }- item->applied = true;- }- else if (scres == 0)- {- error = true;- item->errmsg = pstrdup("setting could not be applied");- ConfFileWithError = item->filename;- }- else- {- /* no error, but variable's active value was not changed */- item->applied = true;- }-- /*- * We should update source location unless there was an error, since- * even if the active value didn't change, the reset value might have.- * (In the postmaster, there won't be a difference, but it does matter- * in backends.)- */- if (scres != 0 && applySettings)- set_config_sourcefile(item->name, item->filename,- item->sourceline);-- if (pre_value)- pfree(pre_value);- }-- /* Remember when we last successfully loaded the config file. */- if (applySettings)- PgReloadTime = GetCurrentTimestamp();--bail_out:- if (error && applySettings)- {- /* During postmaster startup, any error is fatal */- if (context == PGC_POSTMASTER)- ereport(ERROR,- (errcode(ERRCODE_CONFIG_FILE_ERROR),- errmsg("configuration file \"%s\" contains errors",- ConfFileWithError)));- else if (applying)- ereport(elevel,- (errcode(ERRCODE_CONFIG_FILE_ERROR),- errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",- ConfFileWithError)));- else- ereport(elevel,- (errcode(ERRCODE_CONFIG_FILE_ERROR),- errmsg("configuration file \"%s\" contains errors; no changes were applied",- ConfFileWithError)));- }-- /* Successful or otherwise, return the collected data list */- return head;+ if (*namea)+ return 1; /* a is longer */+ if (*nameb)+ return -1; /* b is longer */+ return 0;
}
/*
@@ -525,6 +160,7 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
return pstrdup(location);
else
{
+ #ifndef FRONTEND
if (calling_file != NULL)
{
strlcpy(abs_path, calling_file, sizeof(abs_path));
@@ -538,6 +174,12 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
join_path_components(abs_path, DataDir, location);
canonicalize_path(abs_path);
}
+ #else+ strlcpy(abs_path, calling_file, sizeof(abs_path));+ get_parent_directory(abs_path);+ join_path_components(abs_path, abs_path, location);+ canonicalize_path(abs_path);+ #endif
return pstrdup(abs_path);
}
}
@@ -574,10 +216,15 @@ ParseConfigFile(const char *config_file, bool strict,
*/
if (depth > 10)
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
config_file)));
+ #else+ printf(_("could not open configuration file \"%s\": maximum nesting depth exceeded\n"),+ config_file);+ #endif
record_config_file_error("nesting depth exceeded",
calling_file, calling_lineno,
head_p, tail_p);
@@ -585,15 +232,23 @@ ParseConfigFile(const char *config_file, bool strict,
}
abs_path = AbsoluteConfigLocation(config_file, calling_file);
+ #ifndef FRONTEND
fp = AllocateFile(abs_path, "r");
+ #else+ fp = fopen(abs_path, "r");+ #endif
if (!fp)
{
if (strict)
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode_for_file_access(),
errmsg("could not open configuration file \"%s\": %m",
abs_path)));
+ #else+ printf(_("could not open configuration file \"%s\"\n"), abs_path);+ #endif
record_config_file_error(psprintf("could not open file \"%s\"",
abs_path),
calling_file, calling_lineno,
@@ -602,9 +257,13 @@ ParseConfigFile(const char *config_file, bool strict,
}
else
{
+ #ifndef FRONTEND
ereport(LOG,
(errmsg("skipping missing configuration file \"%s\"",
abs_path)));
+ #else+ printf(_("skipping missing configuration file \"%s\"\n"), abs_path);+ #endif
}
goto cleanup;
}
@@ -613,7 +272,14 @@ ParseConfigFile(const char *config_file, bool strict,
cleanup:
if (fp)
+ {+ #ifndef FRONTEND
FreeFile(fp);
+ #else+ fclose(fp);+ #endif+ }+
pfree(abs_path);
return OK;
@@ -623,7 +289,7 @@ cleanup:
* Capture an error message in the ConfigVariable list returned by
* config file parsing.
*/
-static void+void
record_config_file_error(const char *errmsg,
const char *config_file,
int lineno,
@@ -715,8 +381,13 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
* corrupted parser state. Consequently, abandon the file, but trust
* that the state remains sane enough for yy_delete_buffer().
*/
+ #ifndef FRONTEND
elog(elevel, "%s at file \"%s\" line %u",
GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
+ #else+ printf(_("%s at file \"%s\" line %u\n"),+ GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);+ #endif
record_config_file_error(GUC_flex_fatal_errmsg,
config_file, ConfigFileLineno,
head_p, tail_p);
@@ -855,20 +526,30 @@ parse_error:
/* report the error */
if (token == GUC_EOL || token == 0)
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near end of line",
config_file, ConfigFileLineno - 1)));
+ #else+ printf(_("syntax error in file \"%s\" line %u, near end of line\n"),+ config_file, ConfigFileLineno - 1);+ #endif
record_config_file_error("syntax error",
config_file, ConfigFileLineno - 1,
head_p, tail_p);
}
else
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
config_file, ConfigFileLineno, yytext)));
+ #else+ printf(_("syntax error in file \"%s\" line %u, near token \"%s\"\n"),+ config_file, ConfigFileLineno, yytext);+ #endif
record_config_file_error("syntax error",
config_file, ConfigFileLineno,
head_p, tail_p);
@@ -883,14 +564,23 @@ parse_error:
* as well give up immediately. (This prevents postmaster children
* from bloating the logs with duplicate complaints.)
*/
+ #ifndef FRONTEND
if (errorcount >= 100 || elevel <= DEBUG1)
{
ereport(elevel,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("too many syntax errors found, abandoning file \"%s\"",- config_file)));+ errmsg("too many syntax errors found, abandoning file \"%s\"",+ config_file)));
break;
}
+ #else+ if (errorcount >= 100)+ {+ printf(_("too many syntax errors found, abandoning file \"%s\"\n"),+ config_file);+ break;+ }+ #endif
/* resync to next end-of-line or EOF */
while (token != GUC_EOL && token != 0)
@@ -934,13 +624,21 @@ ParseConfigDirectory(const char *includedir,
bool status;
directory = AbsoluteConfigLocation(includedir, calling_file);
+ #ifndef FRONTEND
d = AllocateDir(directory);
+ #else+ d = opendir(directory);+ #endif
if (d == NULL)
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode_for_file_access(),
errmsg("could not open configuration directory \"%s\": %m",
directory)));
+ #else+ printf(_("could not open configuration directory \"%s\"\n"), directory);+ #endif
record_config_file_error(psprintf("could not open directory \"%s\"",
directory),
calling_file, calling_lineno,
@@ -957,7 +655,11 @@ ParseConfigDirectory(const char *includedir,
filenames = (char **) palloc(size_filenames * sizeof(char *));
num_filenames = 0;
+ #ifndef FRONTEND
while ((de = ReadDir(d, directory)) != NULL)
+ #else+ while ((de = readdir(d)) != NULL)+ #endif
{
struct stat st;
char filename[MAXPGPATH];
@@ -998,10 +700,14 @@ ParseConfigDirectory(const char *includedir,
* a file can't be accessed now is if it was removed between the
* directory listing and now.
*/
+ #ifndef FRONTEND
ereport(elevel,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m",
filename)));
+ #else+ printf(_("could not stat file \"%s\"\n"), filename);+ #endif
record_config_file_error(psprintf("could not stat file \"%s\"",
filename),
calling_file, calling_lineno,
@@ -1032,7 +738,13 @@ ParseConfigDirectory(const char *includedir,
cleanup:
if (d)
+ {+ #ifndef FRONTEND
FreeDir(d);
+ #else+ closedir(d);+ #endif+ }
pfree(directory);
return status;
}
diff --git a/src/include/common/guc-file.h b/src/include/common/guc-file.hnew file mode 100644index 0000000000..ca969e2aed--- /dev/null+++ b/src/include/common/guc-file.h@@ -0,0 +1,50 @@+#ifndef GUC_FILE_H+#define GUC_FILE_H++#include "c.h"++/*+ * Parsing the configuration file(s) will return a list of name-value pairs+ * with source location info. We also abuse this data structure to carry+ * error reports about the config files. An entry reporting an error will+ * have errmsg != NULL, and might have NULLs for name, value, and/or filename.+ *+ * If "ignore" is true, don't attempt to apply the item (it might be an error+ * report, or an item we determined to be duplicate). "applied" is set true+ * if we successfully applied, or could have applied, the setting.+ */+typedef struct ConfigVariable+{+ char *name;+ char *value;+ char *errmsg;+ char *filename;+ int sourceline;+ bool ignore;+ bool applied;+ struct ConfigVariable *next;+} ConfigVariable;++extern bool ParseConfigFile(const char *config_file, bool strict,+ const char *calling_file, int calling_lineno,+ int depth, int elevel,+ ConfigVariable **head_p, ConfigVariable **tail_p);+extern bool ParseConfigFp(FILE *fp, const char *config_file,+ int depth, int elevel,+ ConfigVariable **head_p, ConfigVariable **tail_p);+extern bool ParseConfigDirectory(const char *includedir,+ const char *calling_file, int calling_lineno,+ int depth, int elevel,+ ConfigVariable **head_p,+ ConfigVariable **tail_p);+extern void FreeConfigVariables(ConfigVariable *list);++extern int guc_name_compare(const char *namea, const char *nameb);++extern void record_config_file_error(const char *errmsg,+ const char *config_file,+ int lineno,+ ConfigVariable **head_p,+ ConfigVariable **tail_p);++#endif /* GUC_FILE_H */diff --git a/src/include/utils/guc.h b/src/include/utils/guc.hindex c07e7b945e..0b3391004c 100644--- a/src/include/utils/guc.h+++ b/src/include/utils/guc.h@@ -1,8 +1,7 @@
/*--------------------------------------------------------------------
* guc.h
*
- * External declarations pertaining to backend/utils/misc/guc.c and- * backend/utils/misc/guc-file.l+ * External declarations pertaining to backend/utils/misc/guc.c
*
* Copyright (c) 2000-2019, PostgreSQL Global Development Group
* Written by Peter Eisentraut <peter_e@gmx.net>.
@@ -120,42 +119,6 @@ typedef enum
PGC_S_SESSION /* SET command */
} GucSource;
-/*- * Parsing the configuration file(s) will return a list of name-value pairs- * with source location info. We also abuse this data structure to carry- * error reports about the config files. An entry reporting an error will- * have errmsg != NULL, and might have NULLs for name, value, and/or filename.- *- * If "ignore" is true, don't attempt to apply the item (it might be an error- * report, or an item we determined to be duplicate). "applied" is set true- * if we successfully applied, or could have applied, the setting.- */-typedef struct ConfigVariable-{- char *name;- char *value;- char *errmsg;- char *filename;- int sourceline;- bool ignore;- bool applied;- struct ConfigVariable *next;-} ConfigVariable;--extern bool ParseConfigFile(const char *config_file, bool strict,- const char *calling_file, int calling_lineno,- int depth, int elevel,- ConfigVariable **head_p, ConfigVariable **tail_p);-extern bool ParseConfigFp(FILE *fp, const char *config_file,- int depth, int elevel,- ConfigVariable **head_p, ConfigVariable **tail_p);-extern bool ParseConfigDirectory(const char *includedir,- const char *calling_file, int calling_lineno,- int depth, int elevel,- ConfigVariable **head_p,- ConfigVariable **tail_p);-extern void FreeConfigVariables(ConfigVariable *list);-
/*
* The possible values of an enum variable are specified by an array of
* name-value pairs. The "hidden" flag means the value is accepted but
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pmindex 56192f1b20..dd83329681 100644--- a/src/tools/msvc/Mkvcbuild.pm+++ b/src/tools/msvc/Mkvcbuild.pm@@ -117,8 +117,8 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c- keywords.c kwlookup.c link-canary.c md5.c+ base64.c config_info.c controldata_utils.c exec.c file_perm.c+ guc-file.c ip.c keywords.c kwlookup.c link-canary.c md5.c
pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c unicode_norm.c username.c
wait_error.c);
@@ -147,6 +147,7 @@ sub mkvcbuild
$libpgcommon = $solution->AddProject('libpgcommon', 'lib', 'misc');
$libpgcommon->AddDefine('FRONTEND');
+ $libpgcommon->AddFiles('src/common', 'guc-file.l');
$libpgcommon->AddFiles('src/common', @pgcommonfrontendfiles);
$libpgfeutils = $solution->AddProject('libpgfeutils', 'lib', 'misc');
@@ -163,6 +164,7 @@ sub mkvcbuild
$postgres->ReplaceFile('src/backend/port/pg_shmem.c',
'src/backend/port/win32_shmem.c');
$postgres->AddFiles('src/port', @pgportfiles);
+ $postgres->AddFiles('src/common', 'guc-file.l');
$postgres->AddFiles('src/common', @pgcommonbkndfiles);
$postgres->AddDir('src/timezone');
@@ -172,7 +174,6 @@ sub mkvcbuild
$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
$postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l',
'bootparse.y');
- $postgres->AddFiles('src/backend/utils/misc', 'guc-file.l');
$postgres->AddFiles(
'src/backend/replication', 'repl_scanner.l',
'repl_gram.y', 'syncrep_scanner.l',
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.batindex 069d6eb569..53d77aaf0a 100755--- a/src/tools/msvc/clean.bat+++ b/src/tools/msvc/clean.bat@@ -80,7 +80,7 @@ if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c
if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c
if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c
if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c
-if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c+if %DIST%==1 if exist src\common\guc-file.c del /q src\common\guc-file.c
if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c
if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c
if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c
base-commit: 46682705986207795fa3eec9459df0b975c5ce03
--
2.17.1
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: a.kondratov@postgrespro.ru, x4mmm@yandex-team.ru, 9erthalion6@gmail.com, michael@paquier.xyz, alvherre@2ndquadrant.com, vladimirlesk@yandex-team.ru, dsarafan@yandex-team.ru
Subject: Re: [Patch] pg_rewind: options to use restore_command from recovery.conf or command line
In-Reply-To: <541a55c5-4c9b-a4a9-ad77-4b5b6fa4dacb@postgrespro.ru>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox