Support include directives in postgresql.conf.

Patch by Joachim Wieland, somewhat reworked for clarity and portability.
This commit is contained in:
Tom Lane 2006-03-04 22:19:31 +00:00
parent 60d3c9fdf4
commit 5b8ac71042
2 changed files with 205 additions and 79 deletions

View 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"> <chapter Id="runtime-config">
<title>Server Configuration</title> <title>Server Configuration</title>
@ -47,7 +47,24 @@ search_path = '"$user", public'
anywhere. Parameter values that are not simple identifiers or anywhere. Parameter values that are not simple identifiers or
numbers must be single-quoted. To embed a single quote in a parameter numbers must be single-quoted. To embed a single quote in a parameter
value, write either two quotes (preferred) or backslash-quote. 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> <para>
<indexterm> <indexterm>

View File

@ -4,26 +4,25 @@
* *
* Copyright (c) 2000-2005, PostgreSQL Global Development Group * 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 "postgres.h"
#include <unistd.h>
#include <ctype.h> #include <ctype.h>
#include <unistd.h>
#include "miscadmin.h" #include "miscadmin.h"
#include "storage/fd.h" #include "storage/fd.h"
#include "utils/guc.h" #include "utils/guc.h"
/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
#undef fprintf #undef fprintf
#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg))) #define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
static unsigned ConfigFileLineno;
enum { enum {
GUC_ID = 1, GUC_ID = 1,
GUC_STRING = 2, GUC_STRING = 2,
@ -36,9 +35,25 @@ enum {
GUC_ERROR = 100 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); 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); static char *GUC_scanstr(const char *s);
%} %}
%option 8bit %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 * Exported function to read and process the configuration file. The
*/
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
* parameter indicates in what context the file is being read --- either * parameter indicates in what context the file is being read --- either
* postmaster startup (including standalone-backend startup) or SIGHUP. * postmaster startup (including standalone-backend startup) or SIGHUP.
* All options mentioned in the configuration file are set to new values. * All options mentioned in the configuration file are set to new values.
@ -126,10 +112,7 @@ void
ProcessConfigFile(GucContext context) ProcessConfigFile(GucContext context)
{ {
int elevel; int elevel;
int token;
char *opt_name, *opt_value;
struct name_value_pair *item, *head, *tail; struct name_value_pair *item, *head, *tail;
FILE *fp;
Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP);
@ -144,27 +127,124 @@ ProcessConfigFile(GucContext context)
else else
elevel = ERROR; 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) if (!fp)
{ {
ereport(elevel, ereport(elevel,
(errcode_for_file_access(), (errcode_for_file_access(),
errmsg("could not open configuration file \"%s\": %m", errmsg("could not open configuration file \"%s\": %m",
ConfigFileName))); config_file)));
return; return false;
} }
/* /*
* Parse * Parse
*/ */
yyrestart(fp); lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE);
head = tail = NULL; yy_switch_to_buffer(lex_buffer);
opt_name = opt_value = NULL;
ConfigFileLineno = 1; ConfigFileLineno = 1;
/* This loop iterates once per logical line */ /* This loop iterates once per logical line */
while ((token = yylex())) while ((token = yylex()))
{ {
char *opt_name, *opt_value;
if (token == GUC_EOL) /* empty or comment line */ if (token == GUC_EOL) /* empty or comment line */
continue; continue;
@ -195,8 +275,30 @@ ProcessConfigFile(GucContext context)
if (token != GUC_EOL && token != 0) if (token != GUC_EOL && token != 0)
goto parse_error; goto parse_error;
/* OK, save the option name and value */ /* OK, process the option name and value */
if (strcmp(opt_name, "custom_variable_classes") == 0) 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 * This variable must be processed first as it controls
@ -207,7 +309,8 @@ ProcessConfigFile(GucContext context)
{ {
pfree(opt_name); pfree(opt_name);
pfree(opt_value); pfree(opt_value);
FreeFile(fp); /* we assume error message was logged already */
OK = false;
goto cleanup_exit; goto cleanup_exit;
} }
pfree(opt_name); pfree(opt_name);
@ -216,15 +319,17 @@ ProcessConfigFile(GucContext context)
else else
{ {
/* append to list */ /* append to list */
struct name_value_pair *item;
item = palloc(sizeof *item); item = palloc(sizeof *item);
item->name = opt_name; item->name = opt_name;
item->value = opt_value; item->value = opt_value;
item->next = NULL; item->next = NULL;
if (!head) if (*head_p == NULL)
head = item; *head_p = item;
else else
tail->next = item; (*tail_p)->next = item;
tail = item; *tail_p = item;
} }
/* break out of loop if read EOF, else loop for next line */ /* break out of loop if read EOF, else loop for next line */
@ -232,45 +337,49 @@ ProcessConfigFile(GucContext context)
break; break;
} }
FreeFile(fp); /* successful completion of parsing */
goto cleanup_exit;
/*
* 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;
parse_error: parse_error:
FreeFile(fp);
free_name_value_list(head);
if (token == GUC_EOL || token == 0) if (token == GUC_EOL || token == 0)
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near end of line", errmsg("syntax error in file \"%s\" line %u, near end of line",
ConfigFileName, ConfigFileLineno - 1))); config_file, ConfigFileLineno - 1)));
else else
ereport(elevel, ereport(elevel,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", 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 * scanstr