diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5cd8b115cac..2c2190f13d3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7626,7 +7626,7 @@
pg_file_settings
- file location of parameter settings
+ summary of configuration file contents
@@ -8007,13 +8007,26 @@
- The view pg_file_settings provides the file
- name, line number and value of all parameters which are set through
- configuration files.
- In contrast to pg_settings, a row is provided for
- each occurrence of the parameter across all configuration files. This is helpful
- for discovering why one value may have been used in preference to another
- when the parameters were loaded.
+ The view pg_file_settings provides a summary of
+ the contents of the server's configuration file(s). A row appears in
+ this view for each name = value> entry appearing in the files,
+ with annotations indicating whether the value could be applied
+ successfully. Additional row(s) may appear for problems not linked to
+ a name = value> entry, such as syntax errors in the files.
+
+
+
+ This view is helpful for checking whether planned changes in the
+ configuration files will work, or for diagnosing a previous failure.
+ Note that this view reports on the current> contents of the
+ files, not on what was last applied by the server. (The
+ pg_settings
+ view is usually sufficient to determine that.)
+
+
+
+ The pg_file_settings view can be read only by
+ superusers.
@@ -8031,43 +8044,64 @@
sourcefiletext
- Path to and name of the configration file
+ Full pathname of the configuration filesourcelineinteger
- Line number within the configuration file where the value was set
+ Line number within the configuration file where the entry appears
seqnointeger
- Order in which the setting was loaded
+ Order in which the entries are processed (1..n>)nametext
- Run-time configuration parameter name
+ Configuration parameter namesettingtext
- value of the parameter
+ Value to be assigned to the parameter
+
+
+ applied
+ boolean
+ True if the value can be applied successfully
+
+
+ error
+ text
+ If not null, an error message indicating why this entry could
+ not be applied
-
+
- See for more information about the various
- ways to change these parameters.
+ If the configuration file contains syntax errors or invalid parameter
+ names, the server will not attempt to apply any settings from it, and
+ therefore all the applied> fields will read as false.
+ In such a case there will be one or more rows with
+ non-null error fields indicating the
+ problem(s). Otherwise, individual settings will be applied if possible.
+ If an individual setting cannot be applied (e.g., invalid value, or the
+ setting cannot be changed after server start) it will have an appropriate
+ message in the error field. Another way that
+ an entry might have applied> = false is that it is
+ overridden by a later entry for the same parameter name; this case is not
+ considered an error so nothing appears in
+ the error field.
- The pg_file_settings view cannot be modified
- directly as it represents information, as read in at server start or
- reload time, about all parameter settings across all configuration files.
+ See for more information about the various
+ ways to change run-time parameters.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 1da7dfbc5cd..4b7bd8a86e6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -175,6 +175,14 @@ shared_buffers = 128MB
effect in the same way. Settings in postgresql.auto.conf>
override those in postgresql.conf>.
+
+
+ The system view
+ pg_file_settings
+ can be helpful for pre-testing changes to the configuration file, or for
+ diagnosing problems if a SIGHUP> signal did not have the
+ desired effects.
+
diff --git a/doc/src/sgml/release-9.5.sgml b/doc/src/sgml/release-9.5.sgml
index 5b0d109e35c..ffb2f5d7b6e 100644
--- a/doc/src/sgml/release-9.5.sgml
+++ b/doc/src/sgml/release-9.5.sgml
@@ -6,8 +6,8 @@
Release Date
- AS OF 2015-06-01
- 2015-XX-XX
+ 2015-??-??
+ Current as of 2015-06-01
@@ -23,6 +23,7 @@
+ ... to be filled in ...
@@ -480,10 +481,10 @@
- Add function and view pg_file_settings>>
- to show the source of GUC> values set in configuration
- files (Sawada Masahiko)
+ Add system view pg_file_settings>>
+ to show the contents of the server's configuration files
+ (Sawada Masahiko)
@@ -503,7 +504,7 @@
- This removes the setting from postgresql.auto.conf>.
+ This command removes the setting from postgresql.auto.conf>.
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index 5c0a9655fd8..9ca6fc8dd50 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -47,6 +47,12 @@ 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);
@@ -107,20 +113,15 @@ STRING \'([^'\\\n]|\\.|\'\')*\'
* 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 an error occurs, no values will be changed.
+ * 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)
{
- bool error = false;
- bool apply = false;
int elevel;
- const char *ConfFileWithError;
- ConfigVariable *item,
- *head,
- *tail;
- int i;
- int file_variables_count = 0;
+ MemoryContext config_cxt;
+ MemoryContext caller_cxt;
/*
* Config files are processed on startup (by the postmaster only)
@@ -135,15 +136,58 @@ ProcessConfigFile(GucContext context)
*/
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_MINSIZE,
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ 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, NULL, true, 0, elevel, &head, &tail))
+ if (!ParseConfigFile(ConfigFileName, true,
+ NULL, 0, 0, elevel,
+ &head, &tail))
{
/* Syntax error(s) detected in the file, so bail out */
error = true;
- goto cleanup_list;
+ goto bail_out;
}
/*
@@ -154,13 +198,14 @@ ProcessConfigFile(GucContext context)
*/
if (DataDir)
{
- if (!ParseConfigFile(PG_AUTOCONF_FILENAME, NULL, false, 0, elevel,
+ 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 cleanup_list;
+ goto bail_out;
}
}
else
@@ -173,28 +218,22 @@ ProcessConfigFile(GucContext context)
* will be read later. OTOH, since data_directory isn't allowed in the
* PG_AUTOCONF_FILENAME file, it will never be overwritten later.
*/
- ConfigVariable *prev = NULL;
+ ConfigVariable *newlist = NULL;
- /* Prune all items except "data_directory" from the list */
- for (item = head; item;)
+ /*
+ * Prune all items except the last "data_directory" from the list.
+ */
+ for (item = head; item; item = item->next)
{
- ConfigVariable *ptr = item;
-
- item = item->next;
- if (strcmp(ptr->name, "data_directory") != 0)
- {
- if (prev == NULL)
- head = ptr->next;
- else
- prev->next = ptr->next;
- if (ptr->next == NULL)
- tail = prev;
- FreeConfigVariable(ptr);
- }
- else
- prev = ptr;
+ 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.
*
@@ -203,7 +242,7 @@ ProcessConfigFile(GucContext context)
* the config file.
*/
if (head == NULL)
- return;
+ goto bail_out;
}
/*
@@ -228,12 +267,17 @@ ProcessConfigFile(GucContext context)
* 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.
+ * 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.
@@ -242,7 +286,24 @@ ProcessConfigFile(GucContext context)
if (record)
{
- /* Found, so mark it as present in file */
+ /* 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)
@@ -253,10 +314,10 @@ ProcessConfigFile(GucContext context)
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;
}
- file_variables_count++;
}
/*
@@ -264,10 +325,10 @@ ProcessConfigFile(GucContext context)
* any changes.
*/
if (error)
- goto cleanup_list;
+ goto bail_out;
/* Otherwise, set flag that we're beginning to apply changes */
- apply = true;
+ applying = true;
/*
* Check for variables having been removed from the config file, and
@@ -289,10 +350,18 @@ ProcessConfigFile(GucContext context)
(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.
@@ -334,7 +403,7 @@ ProcessConfigFile(GucContext context)
* 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)
+ if (context == PGC_SIGHUP && applySettings)
{
InitializeGUCOptionsFromEnvironment();
pg_timezone_abbrev_initialize();
@@ -343,54 +412,6 @@ ProcessConfigFile(GucContext context)
PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);
}
- /*
- * Check if we have allocated the array yet.
- *
- * If not, allocate it based on the number of file variables we have seen.
- */
- if (!guc_file_variables)
- {
- /* For the first call */
- num_guc_file_variables = file_variables_count;
- guc_file_variables = (ConfigFileVariable *) guc_malloc(FATAL,
- num_guc_file_variables * sizeof(struct ConfigFileVariable));
- }
- else
- {
- int i;
-
- /* Free all of the previously allocated entries */
- for (i = 0; i < num_guc_file_variables; i++)
- {
- free(guc_file_variables[i].name);
- free(guc_file_variables[i].value);
- free(guc_file_variables[i].filename);
- }
-
- /* Update the global count and realloc based on the new size */
- num_guc_file_variables = file_variables_count;
- guc_file_variables = (ConfigFileVariable *) guc_realloc(FATAL,
- guc_file_variables,
- num_guc_file_variables * sizeof(struct ConfigFileVariable));
- }
-
- /*
- * Copy the settings which came from the files read into the
- * guc_file_variables array which backs the pg_show_file_settings()
- * function.
- */
- for (item = head, i = 0; item && i < num_guc_file_variables;
- item = item->next, i++)
- {
- guc_file_variables[i].name = guc_strdup(FATAL, item->name);
- guc_file_variables[i].value = guc_strdup(FATAL, item->value);
- guc_file_variables[i].filename = guc_strdup(FATAL, item->filename);
- guc_file_variables[i].sourceline = item->sourceline;
- }
-
- /* We had better have made it through the loop above to a clean ending. */
- Assert(!item && i == num_guc_file_variables);
-
/*
* Now apply the values from the config file.
*/
@@ -399,8 +420,12 @@ ProcessConfigFile(GucContext context)
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 && !IsUnderPostmaster)
+ if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster)
{
const char *preval = GetConfigOption(item->name, true, false);
@@ -413,7 +438,7 @@ ProcessConfigFile(GucContext context)
scres = set_config_option(item->name, item->value,
context, PGC_S_FILE,
- GUC_ACTION_SET, true, 0, false);
+ GUC_ACTION_SET, applySettings, 0, false);
if (scres > 0)
{
/* variable was updated, so log the change if appropriate */
@@ -428,13 +453,19 @@ ProcessConfigFile(GucContext context)
(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 */
+ 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
@@ -442,7 +473,7 @@ ProcessConfigFile(GucContext context)
* (In the postmaster, there won't be a difference, but it does matter
* in backends.)
*/
- if (scres != 0)
+ if (scres != 0 && applySettings)
set_config_sourcefile(item->name, item->filename,
item->sourceline);
@@ -451,10 +482,11 @@ ProcessConfigFile(GucContext context)
}
/* Remember when we last successfully loaded the config file. */
- PgReloadTime = GetCurrentTimestamp();
+ if (applySettings)
+ PgReloadTime = GetCurrentTimestamp();
- cleanup_list:
- if (error)
+ bail_out:
+ if (error && applySettings)
{
/* During postmaster startup, any error is fatal */
if (context == PGC_POSTMASTER)
@@ -462,7 +494,7 @@ ProcessConfigFile(GucContext context)
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors",
ConfFileWithError)));
- else if (apply)
+ else if (applying)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
@@ -474,12 +506,8 @@ ProcessConfigFile(GucContext context)
ConfFileWithError)));
}
- /*
- * Calling FreeConfigVariables() any earlier than this can cause problems,
- * because ConfFileWithError could be pointing to a string that will be
- * freed here.
- */
- FreeConfigVariables(head);
+ /* Successful or otherwise, return the collected data list */
+ return head;
}
/*
@@ -520,12 +548,16 @@ AbsoluteConfigLocation(const char *location, const char *calling_file)
* If "strict" is true, treat failure to open the config file as an error,
* otherwise just skip the file.
*
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
* See ParseConfigFp for further details. This one merely adds opening the
* config file rather than working from a caller-supplied file descriptor,
* and absolute-ifying the path name if necessary.
*/
bool
-ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
+ParseConfigFile(const char *config_file, bool strict,
+ const char *calling_file, int calling_lineno,
int depth, int elevel,
ConfigVariable **head_p,
ConfigVariable **tail_p)
@@ -545,6 +577,9 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
config_file)));
+ record_config_file_error("nesting depth exceeded",
+ calling_file, calling_lineno,
+ head_p, tail_p);
return false;
}
@@ -558,6 +593,10 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
(errcode_for_file_access(),
errmsg("could not open configuration file \"%s\": %m",
abs_path)));
+ record_config_file_error(psprintf("could not open file \"%s\"",
+ abs_path),
+ calling_file, calling_lineno,
+ head_p, tail_p);
OK = false;
}
else
@@ -579,6 +618,35 @@ cleanup:
return OK;
}
+/*
+ * Capture an error message in the ConfigVariable list returned by
+ * config file parsing.
+ */
+static void
+record_config_file_error(const char *errmsg,
+ const char *config_file,
+ int lineno,
+ ConfigVariable **head_p,
+ ConfigVariable **tail_p)
+{
+ ConfigVariable *item;
+
+ item = palloc(sizeof *item);
+ item->name = NULL;
+ item->value = NULL;
+ item->errmsg = pstrdup(errmsg);
+ item->filename = config_file ? pstrdup(config_file) : NULL;
+ item->sourceline = lineno;
+ item->ignore = true;
+ item->applied = false;
+ item->next = NULL;
+ if (*head_p == NULL)
+ *head_p = item;
+ else
+ (*tail_p)->next = item;
+ *tail_p = item;
+}
+
/*
* Flex fatal errors bring us here. Stash the error message and jump back to
* ParseConfigFp(). Assume all msg arguments point to string constants; this
@@ -607,9 +675,10 @@ GUC_flex_fatal(const char *msg)
* Input/Output parameters:
* head_p, tail_p: head and tail of linked list of name/value pairs
*
- * *head_p and *tail_p must either be initialized to NULL or valid pointers
- * to a ConfigVariable list before calling the outer recursion level. Any
- * name-value pairs read from the input file(s) will be added to the list.
+ * *head_p and *tail_p must be initialized, either to NULL or valid pointers
+ * to a ConfigVariable list, before calling the outer recursion level. Any
+ * name-value pairs read from the input file(s) will be appended to the list.
+ * Error reports will also be appended to the list, if elevel < ERROR.
*
* Returns TRUE if successful, FALSE if an error occurred. The error has
* already been ereport'd, it is only necessary for the caller to clean up
@@ -617,6 +686,12 @@ GUC_flex_fatal(const char *msg)
*
* Note: if elevel >= ERROR then an error will not return control to the
* caller, so there is no need to check the return value in that case.
+ *
+ * Note: this function is used to parse not only postgresql.conf, but
+ * various other configuration files that use the same "name = value"
+ * syntax. Hence, do not do anything here or in the subsidiary routines
+ * ParseConfigFile/ParseConfigDirectory that assumes we are processing
+ * GUCs specifically.
*/
bool
ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
@@ -641,7 +716,9 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
*/
elog(elevel, "%s at file \"%s\" line %u",
GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
-
+ record_config_file_error(GUC_flex_fatal_errmsg,
+ config_file, ConfigFileLineno,
+ head_p, tail_p);
OK = false;
goto cleanup;
}
@@ -704,12 +781,12 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
* An include_dir directive isn't a variable and should be
* processed immediately.
*/
- if (!ParseConfigDirectory(opt_value, config_file,
- depth + 1, elevel,
- head_p, tail_p))
+ if (!ParseConfigDirectory(opt_value,
+ config_file, ConfigFileLineno - 1,
+ depth + 1, elevel,
+ head_p, tail_p))
OK = false;
yy_switch_to_buffer(lex_buffer);
- ConfigFileLineno = save_ConfigFileLineno;
pfree(opt_name);
pfree(opt_value);
}
@@ -719,7 +796,8 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
* An include_if_exists directive isn't a variable and should be
* processed immediately.
*/
- if (!ParseConfigFile(opt_value, config_file, false,
+ if (!ParseConfigFile(opt_value, false,
+ config_file, ConfigFileLineno - 1,
depth + 1, elevel,
head_p, tail_p))
OK = false;
@@ -733,7 +811,8 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
* An include directive isn't a variable and should be processed
* immediately.
*/
- if (!ParseConfigFile(opt_value, config_file, true,
+ if (!ParseConfigFile(opt_value, true,
+ config_file, ConfigFileLineno - 1,
depth + 1, elevel,
head_p, tail_p))
OK = false;
@@ -747,8 +826,11 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
item = palloc(sizeof *item);
item->name = opt_name;
item->value = opt_value;
+ item->errmsg = NULL;
item->filename = pstrdup(config_file);
item->sourceline = ConfigFileLineno-1;
+ item->ignore = false;
+ item->applied = false;
item->next = NULL;
if (*head_p == NULL)
*head_p = item;
@@ -771,15 +853,25 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
/* report the error */
if (token == GUC_EOL || token == 0)
+ {
ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near end of line",
config_file, ConfigFileLineno - 1)));
+ record_config_file_error("syntax error",
+ config_file, ConfigFileLineno - 1,
+ head_p, tail_p);
+ }
else
+ {
ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
config_file, ConfigFileLineno, yytext)));
+ record_config_file_error("syntax error",
+ config_file, ConfigFileLineno,
+ head_p, tail_p);
+ }
OK = false;
errorcount++;
@@ -817,10 +909,17 @@ cleanup:
/*
* Read and parse all config files in a subdirectory in alphabetical order
+ *
+ * includedir is the absolute or relative path to the subdirectory to scan.
+ *
+ * calling_file/calling_lineno identify the source of the request.
+ * Pass NULL/0 if not recursing from an inclusion request.
+ *
+ * See ParseConfigFp for further details.
*/
bool
ParseConfigDirectory(const char *includedir,
- const char *calling_file,
+ const char *calling_file, int calling_lineno,
int depth, int elevel,
ConfigVariable **head_p,
ConfigVariable **tail_p)
@@ -828,9 +927,9 @@ ParseConfigDirectory(const char *includedir,
char *directory;
DIR *d;
struct dirent *de;
- char **filenames = NULL;
- int num_filenames = 0;
- int size_filenames = 0;
+ char **filenames;
+ int num_filenames;
+ int size_filenames;
bool status;
directory = AbsoluteConfigLocation(includedir, calling_file);
@@ -841,6 +940,10 @@ ParseConfigDirectory(const char *includedir,
(errcode_for_file_access(),
errmsg("could not open configuration directory \"%s\": %m",
directory)));
+ record_config_file_error(psprintf("could not open directory \"%s\"",
+ directory),
+ calling_file, calling_lineno,
+ head_p, tail_p);
status = false;
goto cleanup;
}
@@ -849,6 +952,10 @@ ParseConfigDirectory(const char *includedir,
* Read the directory and put the filenames in an array, so we can sort
* them prior to processing the contents.
*/
+ size_filenames = 32;
+ filenames = (char **) palloc(size_filenames * sizeof(char *));
+ num_filenames = 0;
+
while ((de = ReadDir(d, directory)) != NULL)
{
struct stat st;
@@ -872,15 +979,12 @@ ParseConfigDirectory(const char *includedir,
{
if (!S_ISDIR(st.st_mode))
{
- /* Add file to list, increasing its size in blocks of 32 */
- if (num_filenames == size_filenames)
+ /* Add file to array, increasing its size in blocks of 32 */
+ if (num_filenames >= size_filenames)
{
size_filenames += 32;
- if (num_filenames == 0)
- /* Must initialize, repalloc won't take NULL input */
- filenames = palloc(size_filenames * sizeof(char *));
- else
- filenames = repalloc(filenames, size_filenames * sizeof(char *));
+ filenames = (char **) repalloc(filenames,
+ size_filenames * sizeof(char *));
}
filenames[num_filenames] = pstrdup(filename);
num_filenames++;
@@ -897,6 +1001,10 @@ ParseConfigDirectory(const char *includedir,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m",
filename)));
+ record_config_file_error(psprintf("could not stat file \"%s\"",
+ filename),
+ calling_file, calling_lineno,
+ head_p, tail_p);
status = false;
goto cleanup;
}
@@ -908,8 +1016,10 @@ ParseConfigDirectory(const char *includedir,
qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp);
for (i = 0; i < num_filenames; i++)
{
- if (!ParseConfigFile(filenames[i], NULL, true,
- depth, elevel, head_p, tail_p))
+ if (!ParseConfigFile(filenames[i], true,
+ calling_file, calling_lineno,
+ depth, elevel,
+ head_p, tail_p))
{
status = false;
goto cleanup;
@@ -949,9 +1059,14 @@ FreeConfigVariables(ConfigVariable *list)
static void
FreeConfigVariable(ConfigVariable *item)
{
- pfree(item->name);
- pfree(item->value);
- pfree(item->filename);
+ if (item->name)
+ pfree(item->name);
+ if (item->value)
+ pfree(item->value);
+ if (item->errmsg)
+ pfree(item->errmsg);
+ if (item->filename)
+ pfree(item->filename);
pfree(item);
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 230c5cc0ef2..0356ecb4821 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -182,6 +182,10 @@ static bool check_cluster_name(char **newval, void **extra, GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
+/* Private functions in guc-file.l that need to be called from guc.c */
+static ConfigVariable *ProcessConfigFileInternal(GucContext context,
+ bool applySettings, int elevel);
+
/*
* Options for enum values defined in this module.
@@ -3678,22 +3682,6 @@ static struct config_generic **guc_variables;
/* Current number of variables contained in the vector */
static int num_guc_variables;
-/*
- * Lookup of variables for pg_file_settings view.
- * guc_file_variables is an array of length num_guc_file_variables.
- */
-typedef struct ConfigFileVariable
-{
- char *name;
- char *value;
- char *filename;
- int sourceline;
-} ConfigFileVariable;
-static struct ConfigFileVariable *guc_file_variables;
-
-/* Number of file variables */
-static int num_guc_file_variables;
-
/* Vector capacity */
static int size_guc_variables;
@@ -6781,8 +6769,11 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
item = palloc(sizeof *item);
item->name = pstrdup(name);
item->value = pstrdup(value);
+ item->errmsg = NULL;
item->filename = pstrdup(""); /* new item has no location */
item->sourceline = 0;
+ item->ignore = false;
+ item->applied = false;
item->next = NULL;
if (*head_p == NULL)
@@ -6931,7 +6922,10 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
AutoConfFileName)));
/* parse it */
- ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail);
+ if (!ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail))
+ ereport(ERROR,
+ (errmsg("could not parse contents of file \"%s\"",
+ AutoConfFileName)));
FreeFile(infile);
}
@@ -8181,10 +8175,12 @@ show_all_settings(PG_FUNCTION_ARGS)
/*
* show_all_file_settings
*
- * returns a table of all parameter settings in all configuration files
- * which includes the config file path/name, the line number, a sequence number
- * indicating when we loaded it, the parameter name, and the value it is
- * set to.
+ * Returns a table of all parameter settings in all configuration files
+ * which includes the config file pathname, the line number, a sequence number
+ * indicating the order in which the settings were encountered, the parameter
+ * name and value, a bool showing if the value could be applied, and possibly
+ * an associated error message. (For problems such as syntax errors, the
+ * parameter name/value might be NULL.)
*
* Note: no filtering is done here, instead we depend on the GRANT system
* to prevent unprivileged users from accessing this function or the view
@@ -8193,92 +8189,111 @@ show_all_settings(PG_FUNCTION_ARGS)
Datum
show_all_file_settings(PG_FUNCTION_ARGS)
{
-#define NUM_PG_FILE_SETTINGS_ATTS 5
- FuncCallContext *funcctx;
+#define NUM_PG_FILE_SETTINGS_ATTS 7
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
TupleDesc tupdesc;
- int call_cntr;
- int max_calls;
- AttInMetadata *attinmeta;
+ Tuplestorestate *tupstore;
+ ConfigVariable *conf;
+ int seqno;
+ MemoryContext per_query_ctx;
MemoryContext oldcontext;
- if (SRF_IS_FIRSTCALL())
+ /* Check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ /* Scan the config files using current context as workspace */
+ conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3);
+
+ /* Switch into long-lived context to construct returned data structures */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ /* Build a tuple descriptor for our result type */
+ tupdesc = CreateTemplateTupleDesc(NUM_PG_FILE_SETTINGS_ATTS, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "sourcefile",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sourceline",
+ INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 3, "seqno",
+ INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 4, "name",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 5, "setting",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 6, "applied",
+ BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 7, "error",
+ TEXTOID, -1, 0);
+
+ /* Build a tuplestore to return our results in */
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ /* The rest can be done in short-lived context */
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Process the results and create a tuplestore */
+ for (seqno = 1; conf != NULL; conf = conf->next, seqno++)
{
- funcctx = SRF_FIRSTCALL_INIT();
+ Datum values[NUM_PG_FILE_SETTINGS_ATTS];
+ bool nulls[NUM_PG_FILE_SETTINGS_ATTS];
- oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
- /*
- * need a tuple descriptor representing NUM_PG_FILE_SETTINGS_ATTS
- * columns of the appropriate types
- */
-
- tupdesc = CreateTemplateTupleDesc(NUM_PG_FILE_SETTINGS_ATTS, false);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "sourcefile",
- TEXTOID, -1, 0);
- TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sourceline",
- INT4OID, -1, 0);
- TupleDescInitEntry(tupdesc, (AttrNumber) 3, "seqno",
- INT4OID, -1, 0);
- TupleDescInitEntry(tupdesc, (AttrNumber) 4, "name",
- TEXTOID, -1, 0);
- TupleDescInitEntry(tupdesc, (AttrNumber) 5, "setting",
- TEXTOID, -1, 0);
-
- attinmeta = TupleDescGetAttInMetadata(tupdesc);
- funcctx->attinmeta = attinmeta;
- funcctx->max_calls = num_guc_file_variables;
- MemoryContextSwitchTo(oldcontext);
- }
-
- funcctx = SRF_PERCALL_SETUP();
-
- call_cntr = funcctx->call_cntr;
- max_calls = funcctx->max_calls;
- attinmeta = funcctx->attinmeta;
-
- if (call_cntr < max_calls)
- {
- char *values[NUM_PG_FILE_SETTINGS_ATTS];
- HeapTuple tuple;
- Datum result;
- ConfigFileVariable conf;
- char buffer[12]; /* must be at least 12, per pg_ltoa */
-
- /* Check to avoid going past end of array */
- if (call_cntr > num_guc_file_variables)
- SRF_RETURN_DONE(funcctx);
-
- conf = guc_file_variables[call_cntr];
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
/* sourcefile */
- values[0] = conf.filename;
+ if (conf->filename)
+ values[0] = PointerGetDatum(cstring_to_text(conf->filename));
+ else
+ nulls[0] = true;
- /* sourceline */
- pg_ltoa(conf.sourceline, buffer);
- values[1] = pstrdup(buffer);
+ /* sourceline (not meaningful if no sourcefile) */
+ if (conf->filename)
+ values[1] = Int32GetDatum(conf->sourceline);
+ else
+ nulls[1] = true;
/* seqno */
- pg_ltoa(call_cntr + 1, buffer);
- values[2] = pstrdup(buffer);
+ values[2] = Int32GetDatum(seqno);
/* name */
- values[3] = conf.name;
+ if (conf->name)
+ values[3] = PointerGetDatum(cstring_to_text(conf->name));
+ else
+ nulls[3] = true;
/* setting */
- values[4] = conf.value;
+ if (conf->value)
+ values[4] = PointerGetDatum(cstring_to_text(conf->value));
+ else
+ nulls[4] = true;
- /* build a tuple */
- tuple = BuildTupleFromCStrings(attinmeta, values);
+ /* applied */
+ values[5] = BoolGetDatum(conf->applied);
- /* make the tuple into a datum */
- result = HeapTupleGetDatum(tuple);
+ /* error */
+ if (conf->errmsg)
+ values[6] = PointerGetDatum(cstring_to_text(conf->errmsg));
+ else
+ nulls[6] = true;
- SRF_RETURN_NEXT(funcctx, result);
- }
- else
- {
- SRF_RETURN_DONE(funcctx);
+ /* shove row into tuplestore */
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
+
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
}
static char *
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 3515b6436ae..965a53cbfda 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201506281
+#define CATALOG_VERSION_NO 201506282
#endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d1cc8907d7c..3a40fa69c0a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3071,7 +3071,7 @@ DATA(insert OID = 2078 ( set_config PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 2
DESCR("SET X as a function");
DATA(insert OID = 2084 ( pg_show_all_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline,pending_restart}" _null_ _null_ show_all_settings _null_ _null_ _null_ ));
DESCR("SHOW ALL as a function");
-DATA(insert OID = 3329 ( pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,23,23,25,25}" "{o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting}" _null_ _null_ show_all_file_settings _null_ _null_ _null_ ));
+DATA(insert OID = 3329 ( pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,23,23,25,25,16,25}" "{o,o,o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting,applied,error}" _null_ _null_ show_all_file_settings _null_ _null_ _null_ ));
DESCR("show config file settings");
DATA(insert OID = 1371 ( pg_lock_status PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ _null_ pg_lock_status _null_ _null_ _null_ ));
DESCR("view system lock information");
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ffe1168ccc1..49ec3840af3 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -121,26 +121,36 @@ typedef enum
} GucSource;
/*
- * Parsing the configuration file will return a list of name-value pairs
- * with source location info.
+ * 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, const char *calling_file,
- bool strict, int depth, int elevel,
+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,
+ const char *calling_file, int calling_lineno,
int depth, int elevel,
ConfigVariable **head_p,
ConfigVariable **tail_p);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 60c1f408fcc..cd5337531d4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1316,8 +1316,10 @@ pg_file_settings| SELECT a.sourcefile,
a.sourceline,
a.seqno,
a.name,
- a.setting
- FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting);
+ a.setting,
+ a.applied,
+ a.error
+ FROM pg_show_all_file_settings() a(sourcefile, sourceline, seqno, name, setting, applied, error);
pg_group| SELECT pg_authid.rolname AS groname,
pg_authid.oid AS grosysid,
ARRAY( SELECT pg_auth_members.member