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">
<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>

View 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