On 21.01.2019 23:50, a.kondra...@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
>From 99c6d94f37a797400d41545a271ff111b92e9361 Mon Sep 17 00:00:00 2001 From: Alexey Kondratov <alex.lu...@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.sgml index 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/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 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/.gitignore deleted file mode 100644 index 495b1aec76..0000000000 --- a/src/backend/utils/misc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/guc-file.c 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 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/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/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 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.c index 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.h index 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.pl index 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.pl index 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.pl index 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.pm index 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/.gitignore index ffa3284fbf..424beacbc9 100644 --- a/src/common/.gitignore +++ b/src/common/.gitignore @@ -1 +1,2 @@ /kwlist_d.h +/guc-file.c 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..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.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 <pete...@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..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.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 base-commit: 46682705986207795fa3eec9459df0b975c5ce03 -- 2.17.1