]> granicus.if.org Git - postgresql/commitdiff
Support include directives in postgresql.conf.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 4 Mar 2006 22:19:31 +0000 (22:19 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 4 Mar 2006 22:19:31 +0000 (22:19 +0000)
Patch by Joachim Wieland, somewhat reworked for clarity and portability.

doc/src/sgml/config.sgml
src/backend/utils/misc/guc-file.l

index 710f1b93d16d3bf66005182fc140154532857844..77a742e0450bac42c9a64e33c3457b38e0a02350 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.48 2006/03/03 22:02:07 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.49 2006/03/04 22:19:31 tgl Exp $
 -->
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -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.
-  </para>
+   </para>
+
+   <para>
+    <indexterm>
+     <primary><literal>include</></primary>
+     <secondary>in configuration file</secondary>
+    </indexterm>
+    In addition to parameter settings, the <filename>postgresql.conf</>
+    file can contain <firstterm>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
+<programlisting>
+include 'filename'
+</programlisting>
+    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.
+   </para>
 
    <para>
     <indexterm>
index cab0d164d2015f575e78d49de55ad15ed187a382..62f028afde8f7d3456d7bd4dbc685ccc6d848398 100644 (file)
@@ -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 <unistd.h>
 #include <ctype.h>
+#include <unistd.h>
 
 #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