diff options
Diffstat (limited to 'src/backend/utils/misc/guc-file.l')
| -rw-r--r-- | src/backend/utils/misc/guc-file.l | 369 |
1 files changed, 242 insertions, 127 deletions
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index 5c0a9655fd..9ca6fc8dd5 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(); @@ -344,54 +413,6 @@ ProcessConfigFile(GucContext context) } /* - * 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. */ for (item = head; item; item = item->next) @@ -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 @@ -580,6 +619,35 @@ cleanup: } /* + * 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 * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of @@ -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); } |
