MXS-304: Support nested configuration files
Given a config file "config.cnf", we look for the directory "config.cnf.d" and recursively in that hierarhcy load all files whose suffix is ".cnf"; other files are ignored. Currently duplicate sections are checked on a file by file basis. That will be changed so that duplicate sections are not allowed across all the files.
This commit is contained in:
@ -46,6 +46,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <maxscale/config.h>
|
#include <maxscale/config.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <ftw.h>
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -62,6 +63,7 @@
|
|||||||
#include <maxscale/notification.h>
|
#include <maxscale/notification.h>
|
||||||
#include <maxscale/pcre2.h>
|
#include <maxscale/pcre2.h>
|
||||||
#include <maxscale/service.h>
|
#include <maxscale/service.h>
|
||||||
|
#include <maxscale/spinlock.h>
|
||||||
#include <maxscale/utils.h>
|
#include <maxscale/utils.h>
|
||||||
|
|
||||||
extern int setipaddress(struct in_addr *, char *);
|
extern int setipaddress(struct in_addr *, char *);
|
||||||
@ -373,37 +375,108 @@ static bool config_load_single_file(const char* file, CONFIG_CONTEXT* context)
|
|||||||
if (rval > 0)
|
if (rval > 0)
|
||||||
{
|
{
|
||||||
snprintf(errorbuffer, sizeof(errorbuffer),
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
||||||
"Failed to parse configuration file. Error on line %d.", rval);
|
"Failed to parse configuration file %s. Error on line %d.", file, rval);
|
||||||
}
|
}
|
||||||
else if (rval == -1)
|
else if (rval == -1)
|
||||||
{
|
{
|
||||||
snprintf(errorbuffer, sizeof(errorbuffer),
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
||||||
"Failed to parse configuration file. Failed to open file.");
|
"Failed to parse configuration file %s. Could not open file.", file);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
snprintf(errorbuffer, sizeof(errorbuffer),
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
||||||
"Failed to parse configuration file. Memory allocation failed.");
|
"Failed to parse configuration file %s. Memory allocation failed.", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
MXS_ERROR("%s", errorbuffer);
|
MXS_ERROR("%s", errorbuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rval == 0)
|
||||||
|
{
|
||||||
|
MXS_NOTICE("Loaded %s.", file);
|
||||||
|
}
|
||||||
|
|
||||||
return rval == 0;
|
return rval == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current parsing contex, must be managed explicitly since the ftw callback
|
||||||
|
* can not have user data.
|
||||||
|
*/
|
||||||
|
static CONFIG_CONTEXT *current_context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nftw callback.
|
||||||
|
*
|
||||||
|
* @see man ftw
|
||||||
|
*/
|
||||||
|
int config_cb(const char* fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
|
||||||
|
{
|
||||||
|
int rval = 0;
|
||||||
|
|
||||||
|
if (typeflag == FTW_F) // We are only interested in files,
|
||||||
|
{
|
||||||
|
const char* filename = fpath + ftwbuf->base;
|
||||||
|
const char* dot = strrchr(filename, '.');
|
||||||
|
|
||||||
|
if (dot) // that must have a suffix,
|
||||||
|
{
|
||||||
|
const char* suffix = dot + 1;
|
||||||
|
|
||||||
|
if (strcmp(suffix, "cnf") == 0) // that is ".cnf".
|
||||||
|
{
|
||||||
|
if (!config_load_single_file(fpath, current_context))
|
||||||
|
{
|
||||||
|
rval = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all configuration files in a directory hierarchy.
|
||||||
|
*
|
||||||
|
* Only files with the suffix ".cnf" are considered to be configuration files.
|
||||||
|
*
|
||||||
|
* @param dir The directory.
|
||||||
|
* @param context The configuration context.
|
||||||
|
*
|
||||||
|
* @return True, if all configuration files in the directory hierarchy could be loaded,
|
||||||
|
* otherwise false.
|
||||||
|
*/
|
||||||
|
static bool config_load_dir(const char *dir, CONFIG_CONTEXT *context)
|
||||||
|
{
|
||||||
|
// Since there is no way to pass userdata to the callback, we need to store
|
||||||
|
// the current context into a static variable. Consequently, we need lock.
|
||||||
|
// Should not matter since config_load() is called once at startup.
|
||||||
|
static SPINLOCK lock = SPINLOCK_INIT;
|
||||||
|
|
||||||
|
int nopenfd = 5; // Maximum concurrently opened directory descriptors
|
||||||
|
|
||||||
|
spinlock_acquire(&lock);
|
||||||
|
current_context = context;
|
||||||
|
int rv = nftw(dir, config_cb, nopenfd, FTW_PHYS);
|
||||||
|
current_context = NULL;
|
||||||
|
spinlock_release(&lock);
|
||||||
|
|
||||||
|
return rv == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Load the configuration file for the MaxScale
|
* @brief Load the configuration file for the MaxScale
|
||||||
*
|
*
|
||||||
* This function will parse the configuration file, check for duplicate sections,
|
* This function will parse the configuration file, check for duplicate sections,
|
||||||
* validate the module parameters and finally turn it into a set of objects.
|
* validate the module parameters and finally turn it into a set of objects.
|
||||||
*
|
*
|
||||||
* @param file The filename of the configuration file
|
* @param filename The filename of the configuration file
|
||||||
* @return True on success, false on fatal error
|
* @return True on success, false on fatal error
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
config_load(const char *file)
|
config_load(const char *filename)
|
||||||
{
|
{
|
||||||
bool rval = false;
|
bool rval = false;
|
||||||
|
|
||||||
@ -416,15 +489,51 @@ config_load(const char *file)
|
|||||||
|
|
||||||
CONFIG_CONTEXT config = {.object = ""};
|
CONFIG_CONTEXT config = {.object = ""};
|
||||||
|
|
||||||
if (config_load_single_file(file, &config))
|
if (config_load_single_file(filename, &config))
|
||||||
{
|
{
|
||||||
config_file = file;
|
const char DIR_SUFFIX[] = ".d";
|
||||||
|
|
||||||
|
char dir[strlen(filename) + sizeof(DIR_SUFFIX)];
|
||||||
|
strcpy(dir, filename);
|
||||||
|
strcat(dir, DIR_SUFFIX);
|
||||||
|
|
||||||
|
rval = true;
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(dir, &st) == -1)
|
||||||
|
{
|
||||||
|
if (errno == ENOENT)
|
||||||
|
{
|
||||||
|
MXS_NOTICE("%s does not exist, not reading.", dir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char errbuf[MXS_STRERROR_BUFLEN];
|
||||||
|
MXS_WARNING("Could not access %s, not reading: %s",
|
||||||
|
dir, strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (S_ISDIR(st.st_mode))
|
||||||
|
{
|
||||||
|
rval = config_load_dir(dir, &config);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_WARNING("%s exists, but it is not a directory. Ignoring.", dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rval)
|
||||||
|
{
|
||||||
if (check_config_objects(config.next) && process_config_context(config.next))
|
if (check_config_objects(config.next) && process_config_context(config.next))
|
||||||
{
|
{
|
||||||
|
config_file = filename;
|
||||||
rval = true;
|
rval = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
free_config_context(config.next);
|
free_config_context(config.next);
|
||||||
return rval;
|
return rval;
|
||||||
|
|||||||
Reference in New Issue
Block a user