0001-pg_rewind-options-to-use-restore_command-v2.1.patch
text/x-diff
Filename: 0001-pg_rewind-options-to-use-restore_command-v2.1.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: pg_rewind: options to use restore_command from postgresql.conf or command line.
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/pg_rewind.sgml | 28 | 2 |
| src/backend/commands/extension.c | 1 | 0 |
| src/backend/Makefile | 2 | 2 |
| src/backend/utils/misc/guc.c | 400 | 34 |
| src/backend/utils/misc/Makefile | 0 | 8 |
| src/bin/pg_rewind/Makefile | 1 | 1 |
| src/bin/pg_rewind/parsexlog.c | 175 | 5 |
| src/bin/pg_rewind/pg_rewind.c | 96 | 4 |
| src/bin/pg_rewind/pg_rewind.h | 7 | 5 |
| src/bin/pg_rewind/RewindTest.pm | 95 | 1 |
| src/bin/pg_rewind/t/001_basic.pl | 3 | 1 |
| src/bin/pg_rewind/t/002_databases.pl | 3 | 1 |
| src/bin/pg_rewind/t/003_extrafiles.pl | 3 | 1 |
| src/common/guc-file.l | 114 | 400 |
| src/common/Makefile | 6 | 3 |
| src/include/common/guc-file.h | 50 | 0 |
| src/include/utils/guc.h | 1 | 38 |
| src/tools/msvc/clean.bat | 1 | 1 |
| src/tools/msvc/Mkvcbuild.pm | 1 | 1 |
From cc64d7943e220ba6434b76db63a579ccb547517a 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/Makefile | 8 -
src/backend/utils/misc/guc.c | 434 +++++++++++++--
src/bin/pg_rewind/Makefile | 2 +-
src/bin/pg_rewind/RewindTest.pm | 96 +++-
src/bin/pg_rewind/parsexlog.c | 180 +++++-
src/bin/pg_rewind/pg_rewind.c | 100 +++-
src/bin/pg_rewind/pg_rewind.h | 12 +-
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/common/Makefile | 9 +-
src/{backend/utils/misc => common}/guc-file.l | 514 ++++--------------
src/include/common/guc-file.h | 50 ++
src/include/utils/guc.h | 39 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
src/tools/msvc/clean.bat | 2 +-
19 files changed, 987 insertions(+), 508 deletions(-)
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.sgml
index 53a64ee29e..34046d6404 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> options are 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/Makefile
index 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.c
index 4fe71196c4..b64dece236 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -49,6 +49,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/Makefile b/src/backend/utils/misc/Makefile
index 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.c
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c216ed0922..c52d1bc7e5 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"
@@ -211,7 +212,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);
@@ -4467,7 +4467,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);
@@ -4944,37 +4943,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.
*
@@ -11348,4 +11316,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/Makefile
index 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/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 85cae7e47b..b07ca3617f 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/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,98 @@ 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;
+
+ print "Using restore_command=$restore_command\n";
+ print "Conf path $master_conf_path\n";
+
+ # 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/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index e19c265cbb..537d50e231 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,7 +46,10 @@ static char xlogfpath[MAXPGPATH];
typedef struct XLogPageReadPrivate
{
const char *datadir;
+ const char *restoreCommand;
int tliIndex;
+ XLogRecPtr oldrecptr;
+ TimeLineID oldtli;
} XLogPageReadPrivate;
static int SimpleXLogPageRead(XLogReaderState *xlogreader,
@@ -53,6 +57,10 @@ 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,
+ const char *lastRestartPointFname);
+
/*
* 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 +68,19 @@ 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;
+ private.oldrecptr = targetCF->checkPointCopy.redo;
+ private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
&private);
if (xlogreader == NULL)
@@ -154,9 +166,9 @@ readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex)
* Find the previous checkpoint preceding given WAL location.
*/
void
-findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
+findLastCheckpoint(const char *datadir, ControlFileData *targetCF, 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 +193,9 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
private.datadir = datadir;
private.tliIndex = tliIndex;
+ private.restoreCommand = restoreCommand;
+ private.oldrecptr = targetCF->checkPointCopy.redo;
+ private.oldtli = targetCF->checkPointCopy.ThisTimeLineID;
xlogreader = XLogReaderAllocate(WalSegSz, &SimpleXLogPageRead,
&private);
if (xlogreader == NULL)
@@ -291,9 +306,53 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
if (xlogreadfd < 0)
{
- printf(_("could not open file \"%s\": %s\n"), xlogfpath,
+ bool restore_ok;
+ char lastRestartPointFname[MAXFNAMELEN];
+ XLogSegNo restartSegNo;
+
+ /*
+ * 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;
+ return -1;
+ }
+
+ XLByteToSeg(private->oldrecptr, restartSegNo, WalSegSz);
+ XLogFileName(lastRestartPointFname, private->oldtli, restartSegNo,
+ WalSegSz);
+
+ /*
+ * 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,
+ lastRestartPointFname);
+
+ 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 +468,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, const char *lastRestartPointFname)
+{
+ 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.c
index 7ccde5c87f..abe593a588 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,8 +24,10 @@
#include "access/xlog_internal.h"
#include "catalog/catversion.h"
#include "catalog/pg_control.h"
+#include "common/controldata_utils.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 +54,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 +79,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 +101,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 +139,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 +151,10 @@ main(int argc, char **argv)
showprogress = true;
break;
+ case 'r':
+ restore_wals = true;
+ break;
+
case 'n':
dry_run = true;
break;
@@ -157,6 +171,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 +241,80 @@ main(int argc, char **argv)
umask(pg_mode_mask);
+ if (restore_command != NULL)
+ {
+ if (restore_wals)
+ {
+ fprintf(stderr, _("%s: redundant 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 +386,9 @@ main(int argc, char **argv)
exit(0);
}
- findLastCheckpoint(datadir_target, divergerec,
+ findLastCheckpoint(datadir_target, &ControlFile_target, divergerec,
lastcommontliIndex,
- &chkptrec, &chkpttli, &chkptredo);
+ &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 +411,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.h
index 83b2898b8b..5dc6f5b1ba 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);
-extern void findLastCheckpoint(const char *datadir, XLogRecPtr searchptr,
- int tliIndex,
- XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
- XLogRecPtr *lastchkptredo);
+ int tliIndex, ControlFileData *targetCF, const char *restoreCommand);
+extern void findLastCheckpoint(const char *datadir, ControlFileData *targetCF, XLogRecPtr searchptr,
+ 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.pl
index 53dbf45be2..1860ce2fe3 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 RewindTest;
@@ -103,5 +103,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.pl
index 2c9e427831..42d708dfbd 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 RewindTest;
@@ -59,5 +59,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.pl
index 496f38c457..96665ac2f4 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;
@@ -87,5 +87,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/common/Makefile b/src/common/Makefile
index 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.c
diff --git a/src/backend/utils/misc/guc-file.l b/src/common/guc-file.l
similarity index 60%
rename from src/backend/utils/misc/guc-file.l
rename to src/common/guc-file.l
index 1c8b5f7d84..4117a1dcbc 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/common/guc-file.l
@@ -4,21 +4,33 @@
*
* Copyright (c) 2000-2019, PostgreSQL Global Development Group
*
- * src/backend/utils/misc/guc-file.l
+ * src/common/guc-file.l
*/
%{
-#include "postgres.h"
-
#include <ctype.h>
#include <unistd.h>
+#include <sys/stat.h>
+
+#ifndef FRONTEND
+#include "postgres.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/guc.h"
+#include "utils/elog.h"
+#else
+#include <dirent.h>
+#include <setjmp.h>
+
+#include "postgres_fe.h"
+#include "common/fe_memutils.h"
+#endif
+
+#include "common/guc-file.h"
/*
* flex emits a yy_fatal_error() function that it calls in response to
@@ -48,12 +60,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 +117,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;
+ 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;
}
-
- /*
- * 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;
+ if (*namea)
+ return 1; /* a is longer */
+ if (*nameb)
+ return -1; /* b is longer */
+ return 0;
}
/*
@@ -525,6 +162,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 +176,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 +218,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 +234,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 +259,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 +274,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 +291,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 +383,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 +528,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,12 +566,21 @@ 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)
+ #else
+ if (errorcount >= 100)
+ #endif
{
+ #ifndef FRONTEND
ereport(elevel,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many syntax errors found, abandoning file \"%s\"",
config_file)));
+ #else
+ printf(_("too many syntax errors found, abandoning file \"%s\"\n"),
+ config_file);
+ #endif
break;
}
@@ -934,13 +626,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 +657,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 +702,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 +740,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.h
new file mode 100644
index 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.h
index 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.pm
index 56192f1b20..6952e60550 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -172,7 +172,7 @@ 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/common', '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.bat
index 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
--
2.19.1