Preparatory refactoring for compiling guc-file.c standalone

Mostly this involves moving ProcessConfigFileInternal() to guc.c
and fixing the shared API to match.

Reviewed by Andres Freund
Discussion: https://www.postgresql.org/message-id/20220810171935.7k5zgnjwqzalzmtm%40awork3.anarazel.de
Discussion: https://www.postgresql.org/message-id/CAFBsxsF8Gc2StS3haXofshHCzqNMRXiSxvQEYGwnFsTmsdwNeg@mail.gmail.com
This commit is contained in:
John Naylor 2022-08-16 10:42:19 +07:00
parent 73b9d051c6
commit 1b188ea792
3 changed files with 364 additions and 365 deletions

View File

@ -49,12 +49,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);
/* LCOV_EXCL_START */
@ -160,358 +154,6 @@ ProcessConfigFile(GucContext context)
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, true, 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 (!valid_custom_variable_name(item->name))
{
/* Invalid non-custom variable, so complain */
ereport(elevel,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %d",
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)
{
/* The removal can't be effective without a restart */
gconf->status |= GUC_PENDING_RESTART;
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 non-PGC_INTERNAL variable that
* could potentially have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source.
*/
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;
}
/*
* Given a configuration file or directory location that may be a relative
* path, return an absolute one. We consider the location to be relative to
@ -660,7 +302,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,

View File

@ -243,10 +243,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);
/*
* Track whether there were any deferred checks for custom resource managers
* specified in wal_consistency_checking.
@ -5160,8 +5156,8 @@ static bool report_needed; /* true if any GUC_REPORT reports are needed */
static int GUCNestLevel = 0; /* 1 when in main transaction */
static struct config_generic *find_option(const char *name, bool create_placeholders, bool skip_errors, int elevel);
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);
@ -5180,7 +5176,359 @@ static bool validate_option_array_item(const char *name, const char *value,
static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head_p);
static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
const char *name, const char *value);
static bool valid_custom_variable_name(const char *name);
/*
* 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().
*/
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, true, 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 (!valid_custom_variable_name(item->name))
{
/* Invalid non-custom variable, so complain */
ereport(elevel,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %d",
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)
{
/* The removal can't be effective without a restart */
gconf->status |= GUC_PENDING_RESTART;
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 non-PGC_INTERNAL variable that
* could potentially have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source.
*/
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;
}
/*
* Some infrastructure for checking malloc/strdup/realloc calls
@ -5737,7 +6085,7 @@ guc_var_compare(const void *a, const void *b)
/*
* the bare comparison function for GUC names
*/
static int
int
guc_name_compare(const char *namea, const char *nameb)
{
/*

View File

@ -442,6 +442,15 @@ extern void GUC_check_errcode(int sqlerrcode);
pre_format_elog_string(errno, TEXTDOMAIN), \
GUC_check_errhint_string = format_elog_string
/* functions shared between guc.c and guc-file.l */
extern int guc_name_compare(const char *namea, const char *nameb);
extern ConfigVariable *ProcessConfigFileInternal(GucContext context,
bool applySettings, int elevel);
extern void record_config_file_error(const char *errmsg,
const char *config_file,
int lineno,
ConfigVariable **head_p,
ConfigVariable **tail_p);
/*
* The following functions are not in guc.c, but are declared here to avoid