diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index d1e628f..5df214e 100644 *** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** shared_buffers = 128MB *** 74,96 **** - 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 file name is not an absolute path, it is taken as relative to - the directory containing the referencing configuration file. - Inclusions can be nested. - - - - SIGHUP The configuration file is reread whenever the main server process receives a --- 74,79 ---- *************** SET ENABLE_SEQSCAN TO OFF; *** 178,184 **** any desired selection condition. It also contains more information about what values are allowed for the parameters. ! File Locations --- 161,273 ---- any desired selection condition. It also contains more information about what values are allowed for the parameters. ! ! ! Configuration file includes ! ! ! 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 file name is not an absolute path, it is taken as relative to ! the directory containing the referencing configuration file. ! All types of inclusion directives can be nested. ! ! ! ! ! include_dir ! in configuration file ! ! The postgresql.conf file can also contain ! include_dir directives, which specify an entire directory ! of configuration files to include. It is used similarly: ! ! include_dir 'directory' ! ! 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 files whose names end ! with the suffix .conf will be included. File names ! that start with the . character are also excluded, ! to prevent mistakes as they are hidden on some platforms. Multiple files ! within an include directory are ordered by an alphanumeric sorting, so ! that ones beginning with numbers are considered before those starting ! with letters. ! ! ! ! Include files or directories can be used to logically separate portions ! of the database configuration, rather than having a single large ! 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 ! postgresql.conf file to include them: ! ! include 'shared.conf' ! include 'memory.conf' ! include 'server.conf' ! ! All systems would have the same shared.conf. Each server ! with a particular amount of memory could share the same ! memory.conf; you might have one for all servers with 8GB of RAM, ! another for those having 16GB. And finally server.conf could ! have truly server-specific configuration information in it. ! ! ! ! Another possibility for this same sort of organization is to create a ! configuration file directory and put this information into files there. ! Other programs such as Apache use a ! conf.d directory for this purpose. And using numbered names ! to enforce an ordering is common in UNIX startup script directories named ! like /etc/rcN.d, where N gives the ! associated system runlevel. Both of these ideas might be combined by ! adding a conf.d directory where the ! postgresql.conf file is at, then referencing it at the end: ! ! include_dir 'conf.d' ! ! Then you could name the files in the conf.d directory like this: ! ! include '00shared.conf' ! include '01memory.conf' ! include '02server.conf' ! ! 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 ! conf.d/02server.conf in this example would override a value ! set in conf.d/01memory.conf. ! ! ! ! You might instead use this configuration directory approach while naming ! these files more descriptively: ! ! 00shared.conf ! 01memory-8GB.conf ! 02server-foo.conf ! ! 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). ! ! ! File Locations diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index a094c7a..ae33fdd 100644 *** a/src/backend/utils/misc/guc-file.l --- b/src/backend/utils/misc/guc-file.l *************** ProcessConfigFile(GucContext context) *** 355,360 **** --- 355,396 ---- } /* + * 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. * *************** ParseConfigFile(const char *config_file, *** 370,376 **** { bool OK = true; FILE *fp; - char abs_path[MAXPGPATH]; /* * Reject too-deep include nesting depth. This is just a safety check --- 406,411 ---- *************** ParseConfigFile(const char *config_file, *** 386,416 **** 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; ! } ! } ! fp = AllocateFile(config_file, "r"); if (!fp) { --- 421,427 ---- return false; } ! config_file=AbsoluteConfigLocation(config_file,calling_file); fp = AllocateFile(config_file, "r"); if (!fp) { *************** ParseConfigFp(FILE *fp, const char *conf *** 512,518 **** } /* OK, process the option name and value */ ! if (guc_name_compare(opt_name, "include") == 0) { /* * An include directive isn't a variable and should be processed --- 523,546 ---- } /* OK, process the option name and value */ ! if (guc_name_compare(opt_name, "include_dir") == 0) ! { ! /* ! * An include_dir directive isn't a variable and should be processed ! * immediately. ! */ ! unsigned int save_ConfigFileLineno = ConfigFileLineno; ! ! 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") == 0) { /* * An include directive isn't a variable and should be processed *************** ParseConfigFp(FILE *fp, const char *conf *** 599,604 **** --- 627,747 ---- return OK; } + /* + * String comparison code for use by qsort. Borrowed from + * src/backend/tsearch/ts_utils.c + */ + static int + comparestr(const void *a, const void *b) + { + return strcmp(*(char **) a, *(char **) b); + } + + /* + * 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 (strncmp(de->d_name, ".", 1) == 0) + continue; + if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0) + continue; + + join_path_components(filename, directory, de->d_name); + 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("configuration file \"%s\" can no longer be accessed: %m", + filename))); + return false; + } + } + } + + if (num_filenames > 0) + { + int i; + qsort(filenames, num_filenames, sizeof(char *), comparestr); + for (i = 0; i < num_filenames; i++) + { + if (!ParseConfigFile(filenames[i], NULL, + 0, 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 diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 8e3057a..2005ce9 100644 *** a/src/include/utils/guc.h --- b/src/include/utils/guc.h *************** extern bool ParseConfigFile(const char * *** 116,121 **** --- 116,126 ---- 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); /*