From 5b8ac710423bcb88948d36c8679e4a48a73ae72e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 4 Mar 2006 22:19:31 +0000 Subject: [PATCH] Support include directives in postgresql.conf. Patch by Joachim Wieland, somewhat reworked for clarity and portability. --- doc/src/sgml/config.sgml | 21 ++- src/backend/utils/misc/guc-file.l | 263 +++++++++++++++++++++--------- 2 files changed, 205 insertions(+), 79 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 710f1b93d1..77a742e045 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,5 +1,5 @@ Server Configuration @@ -47,7 +47,24 @@ search_path = '"$user", public' anywhere. Parameter values that are not simple identifiers or numbers must be single-quoted. To embed a single quote in a parameter value, write either two quotes (preferred) or backslash-quote. - + + + + + include + in configuration file + + In addition to parameter settings, the postgresql.conf + file can contain include directives, which specify + another file to read and process as if it were inserted into the + configuration file at this point. Include directives simply look like + +include 'filename' + + If the filename is not an absolute path, it is taken as relative to + the directory containing the referencing configuration file. + Inclusions can be nested. + diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index cab0d164d2..62f028afde 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -4,26 +4,25 @@ * * Copyright (c) 2000-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/utils/misc/guc-file.l,v 1.34 2006/01/02 19:55:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc-file.l,v 1.35 2006/03/04 22:19:31 tgl Exp $ */ %{ #include "postgres.h" -#include #include +#include #include "miscadmin.h" #include "storage/fd.h" #include "utils/guc.h" + /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ #undef fprintf #define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg))) -static unsigned ConfigFileLineno; - enum { GUC_ID = 1, GUC_STRING = 2, @@ -36,9 +35,25 @@ enum { GUC_ERROR = 100 }; -/* prototype, so compiler is happy with our high warnings setting */ +struct name_value_pair +{ + char *name; + char *value; + struct name_value_pair *next; +}; + +static unsigned int ConfigFileLineno; + +/* flex fails to supply a prototype for yylex, so provide one */ int GUC_yylex(void); + +static bool ParseConfigFile(const char *config_file, const char *calling_file, + int depth, GucContext context, int elevel, + struct name_value_pair **head_p, + struct name_value_pair **tail_p); +static void free_name_value_list(struct name_value_pair * list); static char *GUC_scanstr(const char *s); + %} %option 8bit @@ -85,38 +100,9 @@ STRING \'([^'\\\n]|\\.|\'\')*\' %% -struct name_value_pair -{ - char *name; - char *value; - struct name_value_pair *next; -}; - /* - * Free a list of name/value pairs, including the names and the values - */ -static void -free_name_value_list(struct name_value_pair * list) -{ - struct name_value_pair *item; - - item = list; - while (item) - { - struct name_value_pair *save; - - save = item->next; - pfree(item->name); - pfree(item->value); - pfree(item); - item = save; - } -} - - -/* - * Official function to read and process the configuration file. The + * 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. @@ -126,10 +112,7 @@ void ProcessConfigFile(GucContext context) { int elevel; - int token; - char *opt_name, *opt_value; struct name_value_pair *item, *head, *tail; - FILE *fp; Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); @@ -144,27 +127,124 @@ ProcessConfigFile(GucContext context) else elevel = ERROR; - fp = AllocateFile(ConfigFileName, "r"); + head = tail = NULL; + + if (!ParseConfigFile(ConfigFileName, NULL, + 0, context, elevel, + &head, &tail)) + goto cleanup_list; + + /* Check if all options are valid */ + for (item = head; item; item = item->next) + { + if (!set_config_option(item->name, item->value, context, + PGC_S_FILE, false, false)) + goto cleanup_list; + } + + /* If we got here all the options checked out okay, so apply them. */ + for (item = head; item; item = item->next) + { + set_config_option(item->name, item->value, context, + PGC_S_FILE, false, true); + } + + cleanup_list: + free_name_value_list(head); +} + + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * Input parameters: + * config_file: absolute or relative path of file to read + * calling_file: absolute path of file containing the "include" directive, + * or NULL at outer level (config_file must be absolute at outer level) + * depth: recursion depth (used only to prevent infinite recursion) + * context: GucContext passed to ProcessConfigFile() + * elevel: error logging level determined by ProcessConfigFile() + * Output parameters: + * head_p, tail_p: head and tail of linked list of name/value pairs + * + * *head_p and *tail_p must be initialized to NULL before calling the outer + * recursion level. On exit, they contain a list of name-value pairs read + * from the input file(s). + * + * 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 + * its own state and release the name/value pairs list. + * + * Note: if elevel >= ERROR then an error will not return control to the + * caller, and internal state such as open files will not be cleaned up. + * This case occurs only during postmaster or standalone-backend startup, + * where an error will lead to immediate process exit anyway; so there is + * no point in contorting the code so it can clean up nicely. + */ +static bool +ParseConfigFile(const char *config_file, const char *calling_file, + int depth, GucContext context, int elevel, + struct name_value_pair **head_p, + struct name_value_pair **tail_p) +{ + bool OK = true; + char abs_path[MAXPGPATH]; + FILE *fp; + YY_BUFFER_STATE lex_buffer; + int token; + + /* + * Reject too-deep include nesting depth. This is just a safety check + * to avoid dumping core due to stack overflow if an include file loops + * back to itself. The maximum nesting depth is pretty arbitrary. + */ + if (depth > 10) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded", + config_file))); + return false; + } + + /* + * If config_file is a relative path, convert to absolute. We consider + * it to be relative to the directory holding the calling file. + */ + if (!is_absolute_path(config_file)) + { + Assert(calling_file != NULL); + StrNCpy(abs_path, calling_file, MAXPGPATH); + get_parent_directory(abs_path); + join_path_components(abs_path, abs_path, config_file); + canonicalize_path(abs_path); + config_file = abs_path; + } + + fp = AllocateFile(config_file, "r"); if (!fp) { ereport(elevel, (errcode_for_file_access(), errmsg("could not open configuration file \"%s\": %m", - ConfigFileName))); - return; + config_file))); + return false; } /* * Parse */ - yyrestart(fp); - head = tail = NULL; - opt_name = opt_value = NULL; + lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE); + yy_switch_to_buffer(lex_buffer); + ConfigFileLineno = 1; /* This loop iterates once per logical line */ while ((token = yylex())) { + char *opt_name, *opt_value; + if (token == GUC_EOL) /* empty or comment line */ continue; @@ -195,8 +275,30 @@ ProcessConfigFile(GucContext context) if (token != GUC_EOL && token != 0) goto parse_error; - /* OK, save the option name and value */ - if (strcmp(opt_name, "custom_variable_classes") == 0) + /* OK, process the option name and value */ + if (pg_strcasecmp(opt_name, "include") == 0) + { + /* + * An include directive isn't a variable and should be processed + * immediately. + */ + unsigned int save_ConfigFileLineno = ConfigFileLineno; + + if (!ParseConfigFile(opt_value, config_file, + depth + 1, context, elevel, + head_p, tail_p)) + { + pfree(opt_name); + pfree(opt_value); + OK = false; + goto cleanup_exit; + } + yy_switch_to_buffer(lex_buffer); + ConfigFileLineno = save_ConfigFileLineno; + pfree(opt_name); + pfree(opt_value); + } + else if (pg_strcasecmp(opt_name, "custom_variable_classes") == 0) { /* * This variable must be processed first as it controls @@ -207,7 +309,8 @@ ProcessConfigFile(GucContext context) { pfree(opt_name); pfree(opt_value); - FreeFile(fp); + /* we assume error message was logged already */ + OK = false; goto cleanup_exit; } pfree(opt_name); @@ -216,15 +319,17 @@ ProcessConfigFile(GucContext context) else { /* append to list */ + struct name_value_pair *item; + item = palloc(sizeof *item); item->name = opt_name; item->value = opt_value; item->next = NULL; - if (!head) - head = item; + if (*head_p == NULL) + *head_p = item; else - tail->next = item; - tail = item; + (*tail_p)->next = item; + *tail_p = item; } /* break out of loop if read EOF, else loop for next line */ @@ -232,45 +337,49 @@ ProcessConfigFile(GucContext context) break; } - FreeFile(fp); - - /* - * Check if all options are valid - */ - for(item = head; item; item=item->next) - { - if (!set_config_option(item->name, item->value, context, - PGC_S_FILE, false, false)) - goto cleanup_exit; - } - - /* If we got here all the options parsed okay, so apply them. */ - for(item = head; item; item=item->next) - { - set_config_option(item->name, item->value, context, - PGC_S_FILE, false, true); - } - - cleanup_exit: - free_name_value_list(head); - return; + /* successful completion of parsing */ + goto cleanup_exit; parse_error: - FreeFile(fp); - free_name_value_list(head); if (token == GUC_EOL || token == 0) ereport(elevel, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax error in file \"%s\" line %u, near end of line", - ConfigFileName, ConfigFileLineno - 1))); + config_file, ConfigFileLineno - 1))); else ereport(elevel, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", - ConfigFileName, ConfigFileLineno, yytext))); + config_file, ConfigFileLineno, yytext))); + OK = false; + +cleanup_exit: + yy_delete_buffer(lex_buffer); + FreeFile(fp); + return OK; } +/* + * Free a list of name/value pairs, including the names and the values + */ +static void +free_name_value_list(struct name_value_pair *list) +{ + struct name_value_pair *item; + + item = list; + while (item) + { + struct name_value_pair *next = item->next; + + pfree(item->name); + pfree(item->value); + pfree(item); + item = next; + } +} + /* * scanstr