mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
Add support for include_dir in config file.
This allows easily splitting configuration into many files, deployed in a directory. Magnus Hagander, Greg Smith, Selena Deckelmann, reviewed by Noah Misch.
This commit is contained in:
parent
ce9eee39d1
commit
2a0c81a12c
@ -79,38 +79,6 @@ shared_buffers = 128MB
|
||||
value, write either two quotes (preferred) or backslash-quote.
|
||||
</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. This feature allows a configuration
|
||||
file to be divided into physically separate parts.
|
||||
Include directives simply look like:
|
||||
<programlisting>
|
||||
include 'filename'
|
||||
</programlisting>
|
||||
If the file name 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>
|
||||
<primary><literal>include_if_exists</></primary>
|
||||
<secondary>in configuration file</secondary>
|
||||
</indexterm>
|
||||
There is also an <literal>include_if_exists</> directive, which acts
|
||||
the same as the <literal>include</> directive, except for the behavior
|
||||
when the referenced file does not exist or cannot be read. A regular
|
||||
<literal>include</> will consider this an error condition, but
|
||||
<literal>include_if_exists</> merely logs a message and continues
|
||||
processing the referencing configuration file.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<indexterm>
|
||||
<primary>SIGHUP</primary>
|
||||
@ -213,7 +181,123 @@ SET ENABLE_SEQSCAN TO OFF;
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect2 id="config-includes">
|
||||
<title>Configuration File Includes</title>
|
||||
|
||||
<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. This feature allows a configuration
|
||||
file to be divided into physically separate parts.
|
||||
Include directives simply look like:
|
||||
<programlisting>
|
||||
include 'filename'
|
||||
</programlisting>
|
||||
If the file name 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>
|
||||
<primary><literal>include_if_exists</></primary>
|
||||
<secondary>in configuration file</secondary>
|
||||
</indexterm>
|
||||
There is also an <literal>include_if_exists</> directive, which acts
|
||||
the same as the <literal>include</> directive, except for the behavior
|
||||
when the referenced file does not exist or cannot be read. A regular
|
||||
<literal>include</> will consider this an error condition, but
|
||||
<literal>include_if_exists</> merely logs a message and continues
|
||||
processing the referencing configuration file.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<indexterm>
|
||||
<primary><literal>include_dir</></primary>
|
||||
<secondary>in configuration file</secondary>
|
||||
</indexterm>
|
||||
The <filename>postgresql.conf</> file can also contain
|
||||
<firstterm>include_dir directives</>, which specify an entire directory
|
||||
of configuration files to include. It is used similarly:
|
||||
<programlisting>
|
||||
include_dir 'directory'
|
||||
</programlisting>
|
||||
Non-absolute directory names follow the same rules as single file include
|
||||
directives: they are relative to the directory containing the referencing
|
||||
configuration file. Within that directory, only non-directory files whose
|
||||
names end with the suffix <literal>.conf</literal> will be included. File
|
||||
names that start with the <literal>.</literal> character are also excluded,
|
||||
to prevent mistakes as they are hidden on some platforms. Multiple files
|
||||
within an include directory are processed in filename order. The filenames
|
||||
are ordered by C locale rules, ie. numbers before letters, and uppercase
|
||||
letters before lowercase ones.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Include files or directories can be used to logically separate portions
|
||||
of the database configuration, rather than having a single large
|
||||
<filename>postgresql.conf</> file. Consider a company that has two
|
||||
database servers, each with a different amount of memory. There are likely
|
||||
elements of the configuration both will share, for things such as logging.
|
||||
But memory-related parameters on the server will vary between the two. And
|
||||
there might be server specific customizations, too. One way to manage this
|
||||
situation is to break the custom configuration changes for your site into
|
||||
three files. You could add this to the end of your
|
||||
<filename>postgresql.conf</> file to include them:
|
||||
<programlisting>
|
||||
include 'shared.conf'
|
||||
include 'memory.conf'
|
||||
include 'server.conf'
|
||||
</programlisting>
|
||||
All systems would have the same <filename>shared.conf</>. Each server
|
||||
with a particular amount of memory could share the same
|
||||
<filename>memory.conf</>; you might have one for all servers with 8GB of RAM,
|
||||
another for those having 16GB. And finally <filename>server.conf</> could
|
||||
have truly server-specific configuration information in it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Another possibility is to create a configuration file directory and
|
||||
put this information into files there. For example, a <filename>conf.d</>
|
||||
directory could be referenced at the end of<filename>postgresql.conf</>:
|
||||
<screen>
|
||||
include_dir 'conf.d'
|
||||
</screen>
|
||||
Then you could name the files in the <filename>conf.d</> directory like this:
|
||||
<screen>
|
||||
00shared.conf
|
||||
01memory.conf
|
||||
02server.conf
|
||||
</screen>
|
||||
This shows a clear order in which these files will be loaded. This is
|
||||
important because only the last setting encountered when the server is
|
||||
reading its configuration will be used. Something set in
|
||||
<filename>conf.d/02server.conf</> in this example would override a value
|
||||
set in <filename>conf.d/01memory.conf</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
You might instead use this configuration directory approach while naming
|
||||
these files more descriptively:
|
||||
<screen>
|
||||
00shared.conf
|
||||
01memory-8GB.conf
|
||||
02server-foo.conf
|
||||
</screen>
|
||||
This sort of arrangement gives a unique name for each configuration file
|
||||
variation. This can help eliminate ambiguity when several servers have
|
||||
their configurations all stored in one place, such as in a version
|
||||
control repository. (Storing database configuration files under version
|
||||
control is another good practice to consider).
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="runtime-config-file-locations">
|
||||
<title>File Locations</title>
|
||||
|
@ -362,6 +362,39 @@ ProcessConfigFile(GucContext context)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a configuration file or directory location that may be a relative
|
||||
* path, return an absolute one. We consider the location to be relative to
|
||||
* the directory holding the calling file.
|
||||
*/
|
||||
static char *
|
||||
AbsoluteConfigLocation(const char *location, const char *calling_file)
|
||||
{
|
||||
char abs_path[MAXPGPATH];
|
||||
|
||||
if (is_absolute_path(location))
|
||||
return pstrdup(location);
|
||||
else
|
||||
{
|
||||
if (calling_file != NULL)
|
||||
{
|
||||
strlcpy(abs_path, calling_file, sizeof(abs_path));
|
||||
get_parent_directory(abs_path);
|
||||
join_path_components(abs_path, abs_path, location);
|
||||
canonicalize_path(abs_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* calling_file is NULL, we make an absolute path from $PGDATA
|
||||
*/
|
||||
join_path_components(abs_path, data_directory, location);
|
||||
canonicalize_path(abs_path);
|
||||
}
|
||||
return pstrdup(abs_path);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and parse a single configuration file. This function recurses
|
||||
* to handle "include" directives.
|
||||
@ -378,7 +411,6 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
|
||||
{
|
||||
bool OK = true;
|
||||
FILE *fp;
|
||||
char abs_path[MAXPGPATH];
|
||||
|
||||
/*
|
||||
* Reject too-deep include nesting depth. This is just a safety check
|
||||
@ -394,31 +426,7 @@ ParseConfigFile(const char *config_file, const char *calling_file, bool strict,
|
||||
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))
|
||||
{
|
||||
if (calling_file != NULL)
|
||||
{
|
||||
strlcpy(abs_path, calling_file, sizeof(abs_path));
|
||||
get_parent_directory(abs_path);
|
||||
join_path_components(abs_path, abs_path, config_file);
|
||||
canonicalize_path(abs_path);
|
||||
config_file = abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* calling_file is NULL, we make an absolute path from $PGDATA
|
||||
*/
|
||||
join_path_components(abs_path, data_directory, config_file);
|
||||
canonicalize_path(abs_path);
|
||||
config_file = abs_path;
|
||||
}
|
||||
}
|
||||
|
||||
config_file = AbsoluteConfigLocation(config_file,calling_file);
|
||||
fp = AllocateFile(config_file, "r");
|
||||
if (!fp)
|
||||
{
|
||||
@ -563,7 +571,22 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
|
||||
}
|
||||
|
||||
/* OK, process the option name and value */
|
||||
if (guc_name_compare(opt_name, "include_if_exists") == 0)
|
||||
if (guc_name_compare(opt_name, "include_dir") == 0)
|
||||
{
|
||||
/*
|
||||
* 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))
|
||||
OK = false;
|
||||
yy_switch_to_buffer(lex_buffer);
|
||||
ConfigFileLineno = save_ConfigFileLineno;
|
||||
pfree(opt_name);
|
||||
pfree(opt_value);
|
||||
}
|
||||
else if (guc_name_compare(opt_name, "include_if_exists") == 0)
|
||||
{
|
||||
/*
|
||||
* An include_if_exists directive isn't a variable and should be
|
||||
@ -573,9 +596,9 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
|
||||
depth + 1, elevel,
|
||||
head_p, tail_p))
|
||||
OK = false;
|
||||
yy_switch_to_buffer(lex_buffer);
|
||||
pfree(opt_name);
|
||||
pfree(opt_value);
|
||||
yy_switch_to_buffer(lex_buffer);
|
||||
pfree(opt_name);
|
||||
pfree(opt_value);
|
||||
}
|
||||
else if (guc_name_compare(opt_name, "include") == 0)
|
||||
{
|
||||
@ -665,6 +688,111 @@ cleanup:
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read and parse all config files in a subdirectory in alphabetical order
|
||||
*/
|
||||
bool
|
||||
ParseConfigDirectory(const char *includedir,
|
||||
const char *calling_file,
|
||||
int depth, int elevel,
|
||||
ConfigVariable **head_p,
|
||||
ConfigVariable **tail_p)
|
||||
{
|
||||
char *directory;
|
||||
DIR *d;
|
||||
struct dirent *de;
|
||||
char **filenames = NULL;
|
||||
int num_filenames = 0;
|
||||
int size_filenames = 0;
|
||||
bool status;
|
||||
|
||||
directory = AbsoluteConfigLocation(includedir, calling_file);
|
||||
d = AllocateDir(directory);
|
||||
if (d == NULL)
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open configuration directory \"%s\": %m",
|
||||
directory)));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the directory and put the filenames in an array, so we can sort
|
||||
* them prior to processing the contents.
|
||||
*/
|
||||
while ((de = ReadDir(d, directory)) != NULL)
|
||||
{
|
||||
struct stat st;
|
||||
char filename[MAXPGPATH];
|
||||
|
||||
/*
|
||||
* Only parse files with names ending in ".conf". Explicitly reject
|
||||
* files starting with ".". This excludes things like "." and "..",
|
||||
* as well as typical hidden files, backup files, and editor debris.
|
||||
*/
|
||||
if (strlen(de->d_name) < 6)
|
||||
continue;
|
||||
if (de->d_name[0] == '.')
|
||||
continue;
|
||||
if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0)
|
||||
continue;
|
||||
|
||||
join_path_components(filename, directory, de->d_name);
|
||||
canonicalize_path(filename);
|
||||
if (stat(filename, &st) == 0)
|
||||
{
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
{
|
||||
/* Add file to list, 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[num_filenames] = pstrdup(filename);
|
||||
num_filenames++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* stat does not care about permissions, so the most likely reason
|
||||
* a file can't be accessed now is if it was removed between the
|
||||
* directory listing and now.
|
||||
*/
|
||||
ereport(elevel,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not stat file \"%s\": %m",
|
||||
filename)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_filenames > 0)
|
||||
{
|
||||
int i;
|
||||
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))
|
||||
{
|
||||
status = false;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
status = true;
|
||||
|
||||
cleanup:
|
||||
FreeDir(d);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a list of ConfigVariables, including the names and the values
|
||||
|
@ -566,6 +566,17 @@
|
||||
#exit_on_error = off # terminate session on any error?
|
||||
#restart_after_crash = on # reinitialize after backend crash?
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONFIG FILE INCLUDES
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# These options allow settings to be loaded from files other than the
|
||||
# default postgresql.conf
|
||||
|
||||
#include_dir = 'conf.d' # include files ending in '.conf' from
|
||||
# directory 'conf.d'
|
||||
#include_if_exists = 'exists.conf' # include file only if it exists
|
||||
#include = 'special.conf' # include file
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CUSTOMIZED OPTIONS
|
||||
|
@ -116,6 +116,11 @@ extern bool ParseConfigFile(const char *config_file, const char *calling_file,
|
||||
extern bool ParseConfigFp(FILE *fp, const char *config_file,
|
||||
int depth, int elevel,
|
||||
ConfigVariable **head_p, ConfigVariable **tail_p);
|
||||
extern bool ParseConfigDirectory(const char *includedir,
|
||||
const char *calling_file,
|
||||
int depth, int elevel,
|
||||
ConfigVariable **head_p,
|
||||
ConfigVariable **tail_p);
|
||||
extern void FreeConfigVariables(ConfigVariable *list);
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user