1
0
mirror of git://sourceware.org/git/lvm2.git synced 2025-02-10 17:57:54 +03:00
lvm2/lib/config/config.c

1380 lines
35 KiB
C
Raw Normal View History

/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
2011-05-24 13:53:26 +00:00
* Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved.
*
2004-03-30 19:35:44 +00:00
* This file is part of LVM2.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License v.2.1.
2004-03-30 19:35:44 +00:00
*
* You should have received a copy of the GNU Lesser General Public License
2004-03-30 19:35:44 +00:00
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
2002-11-18 14:01:16 +00:00
#include "lib.h"
2002-11-18 14:01:16 +00:00
#include "config.h"
#include "crc.h"
#include "device.h"
2004-05-04 18:28:15 +00:00
#include "str_list.h"
#include "toolcontext.h"
#include "lvm-file.h"
#include "memlock.h"
2002-11-18 14:01:16 +00:00
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
config: use config checks and add support for creating trees from config definition (config_def_create_tree fn) Configuration checking is initiated during config load/processing (_process_config fn) which is part of the command context creation/refresh. This patch also defines 5 types of trees that could be created from the configuration definition (config_settings.h), the cfg_def_tree_t: - CFG_DEF_TREE_CURRENT that denotes a tree of all the configuration nodes that are explicitly defined in lvm.conf/--config - CFG_DEF_TREE_MISSING that denotes a tree of all missing configuration nodes for which default valus are used since they're not explicitly used in lvm.conf/--config - CFG_DEF_TREE_DEFAULT that denotes a tree of all possible configuration nodes with default values assigned, no matter what the actual lvm.conf/--config is - CFG_DEF_TREE_NEW that denotes a tree of all new configuration nodes that appeared in given version - CFG_DEF_TREE_COMPLETE that denotes a tree of the whole configuration tree that is used in LVM2 (a combination of CFG_DEF_TREE_CURRENT + CFG_DEF_TREE_MISSING). This is not implemented yet, it will be added later... The function that creates the definition tree of given type: struct dm_config_tree *config_def_create_tree(struct config_def_tree_spec *spec); Where the "spec" specifies the tree type to be created: struct config_def_tree_spec { cfg_def_tree_t type; /* tree type */ uint16_t version; /* tree at this LVM2 version */ int ignoreadvanced; /* do not include advanced configs */ int ignoreunsupported; /* do not include unsupported configs */ }; This tree can be passed to already existing functions that write the tree on output (like we already do with cmd->cft). There is a new lvm.conf section called "config" with two new options: - config/checks which enables/disables checking (enabled by default) - config/abort_on_errors which enables/disables aborts on any type of mismatch found in the config (disabled by default)
2013-03-05 17:36:10 +01:00
#include <ctype.h>
static const char *_config_source_names[] = {
[CONFIG_UNDEFINED] = "undefined",
[CONFIG_FILE] = "file",
[CONFIG_MERGED_FILES] = "merged files",
[CONFIG_STRING] = "string",
[CONFIG_PROFILE] = "profile"
};
struct config_file {
off_t st_size;
char *filename;
int exists;
int keep_open;
struct device *dev;
};
struct config_source {
config_source_t type;
time_t timestamp;
union {
struct config_file *file;
struct config_file *profile;
} source;
};
static char _cfg_path[CFG_PATH_MAX_LEN];
/*
* Map each ID to respective definition of the configuration item.
*/
static struct cfg_def_item _cfg_def_items[CFG_COUNT + 1] = {
#define cfg_section(id, name, parent, flags, since_version, comment) {id, parent, name, CFG_TYPE_SECTION, {0}, flags, since_version, comment},
#define cfg(id, name, parent, flags, type, default_value, since_version, comment) {id, parent, name, type, {.v_##type = default_value}, flags, since_version, comment},
#define cfg_array(id, name, parent, flags, types, default_value, since_version, comment) {id, parent, name, CFG_TYPE_ARRAY | types, {.v_CFG_TYPE_STRING = default_value}, flags, since_version, comment},
#include "config_settings.h"
#undef cfg_section
#undef cfg
#undef cfg_array
};
config_source_t config_get_source_type(struct dm_config_tree *cft)
{
struct config_source *cs = dm_config_get_custom(cft);
return cs ? cs->type : CONFIG_UNDEFINED;
}
/*
* public interface
*/
struct dm_config_tree *config_open(config_source_t source,
const char *filename,
int keep_open)
2002-01-07 10:23:52 +00:00
{
struct dm_config_tree *cft = dm_config_create();
struct config_source *cs;
struct config_file *cf;
if (!cft)
return NULL;
if (!(cs = dm_pool_zalloc(cft->mem, sizeof(struct config_source)))) {
log_error("Failed to allocate config source.");
goto fail;
}
2002-01-07 10:23:52 +00:00
if ((source == CONFIG_FILE) || (source == CONFIG_PROFILE)) {
if (!(cf = dm_pool_zalloc(cft->mem, sizeof(struct config_file)))) {
log_error("Failed to allocate config file.");
goto fail;
}
cf->keep_open = keep_open;
if (filename &&
!(cf->filename = dm_pool_strdup(cft->mem, filename))) {
log_error("Failed to duplicate filename.");
goto fail;
}
cs->source.file = cf;
}
cs->type = source;
dm_config_set_custom(cft, cs);
return cft;
fail:
dm_config_destroy(cft);
return NULL;
}
/*
* Doesn't populate filename if the file is empty.
*/
int config_file_check(struct dm_config_tree *cft, const char **filename, struct stat *info)
{
struct config_source *cs = dm_config_get_custom(cft);
struct config_file *cf;
struct stat _info;
if ((cs->type != CONFIG_FILE) && (cs->type != CONFIG_PROFILE)) {
log_error(INTERNAL_ERROR "config_file_check: expected file or profile config source, "
"found %s config source.", _config_source_names[cs->type]);
return 0;
}
if (!info)
info = &_info;
cf = cs->source.file;
if (stat(cf->filename, info)) {
log_sys_error("stat", cf->filename);
cf->exists = 0;
return 0;
}
if (!S_ISREG(info->st_mode)) {
log_error("%s is not a regular file", cf->filename);
cf->exists = 0;
return 0;
}
cs->timestamp = info->st_ctime;
cf->exists = 1;
cf->st_size = info->st_size;
if (info->st_size == 0)
log_verbose("%s is empty", cf->filename);
else if (filename)
*filename = cf->filename;
return 1;
}
/*
* Return 1 if config files ought to be reloaded
*/
int config_file_changed(struct dm_config_tree *cft)
{
struct config_source *cs = dm_config_get_custom(cft);
struct config_file *cf;
struct stat info;
if (cs->type != CONFIG_FILE) {
log_error(INTERNAL_ERROR "config_file_changed: expected file config source, "
"found %s config source.", _config_source_names[cs->type]);
return 0;
}
cf = cs->source.file;
if (!cf->filename)
return 0;
if (stat(cf->filename, &info) == -1) {
/* Ignore a deleted config file: still use original data */
if (errno == ENOENT) {
if (!cf->exists)
return 0;
log_very_verbose("Config file %s has disappeared!",
cf->filename);
goto reload;
}
log_sys_error("stat", cf->filename);
log_error("Failed to reload configuration files");
return 0;
}
if (!S_ISREG(info.st_mode)) {
log_error("Configuration file %s is not a regular file",
cf->filename);
goto reload;
}
/* Unchanged? */
if (cs->timestamp == info.st_ctime && cf->st_size == info.st_size)
return 0;
reload:
log_verbose("Detected config file change to %s", cf->filename);
return 1;
}
void config_destroy(struct dm_config_tree *cft)
{
struct config_source *cs;
struct config_file *cf;
if (!cft)
return;
cs = dm_config_get_custom(cft);
if ((cs->type == CONFIG_FILE) || (cs->type == CONFIG_PROFILE)) {
cf = cs->source.file;
if (cf && cf->dev)
if (!dev_close(cf->dev))
stack;
}
2002-01-07 10:23:52 +00:00
dm_config_destroy(cft);
}
struct dm_config_tree *config_file_open_and_read(const char *config_file,
config_source_t source)
{
struct dm_config_tree *cft;
struct stat info;
if (!(cft = config_open(source, config_file, 0))) {
log_error("config_tree allocation failed");
return NULL;
}
/* Is there a config file? */
if (stat(config_file, &info) == -1) {
/* Profile file must be present! */
if (errno == ENOENT && (source != CONFIG_PROFILE))
return cft;
log_sys_error("stat", config_file);
goto bad;
}
log_very_verbose("Loading config file: %s", config_file);
if (!config_file_read(cft)) {
log_error("Failed to load config file %s", config_file);
goto bad;
}
return cft;
bad:
config_destroy(cft);
return NULL;
}
/*
* Returns config tree if it was removed.
*/
struct dm_config_tree *remove_config_tree_by_source(struct cmd_context *cmd,
config_source_t source)
{
struct dm_config_tree *previous_cft = NULL;
struct dm_config_tree *cft = cmd->cft;
struct config_source *cs;
while (cft) {
cs = dm_config_get_custom(cft);
if (cs && (cs->type == source)) {
if (previous_cft) {
previous_cft->cascade = cft->cascade;
cmd->cft = previous_cft;
} else
cmd->cft = cft->cascade;
cft->cascade = NULL;
break;
}
previous_cft = cft;
cft = cft->cascade;
}
return cft;
}
int override_config_tree_from_string(struct cmd_context *cmd,
const char *config_settings)
{
struct dm_config_tree *cft_new;
struct config_source *cs = dm_config_get_custom(cmd->cft);
/*
* Follow this sequence:
* CONFIG_STRING -> CONFIG_FILE/CONFIG_MERGED_FILES
*/
if (cs->type == CONFIG_STRING) {
log_error(INTERNAL_ERROR "override_config_tree_from_string: "
"config cascade already contains a string config.");
return 0;
}
if (!(cft_new = dm_config_from_string(config_settings))) {
log_error("Failed to set overridden configuration entries.");
return 0;
}
if (!(cs = dm_pool_zalloc(cft_new->mem, sizeof(struct config_source)))) {
log_error("Failed to allocate config source.");
dm_config_destroy(cft_new);
return 0;
}
cs->type = CONFIG_STRING;
dm_config_set_custom(cft_new, cs);
cmd->cft = dm_config_insert_cascaded_tree(cft_new, cmd->cft);
return 1;
}
int config_file_read_fd(struct dm_config_tree *cft, struct device *dev,
off_t offset, size_t size, off_t offset2, size_t size2,
checksum_fn_t checksum_fn, uint32_t checksum)
{
char *fb, *fe;
2002-11-18 14:01:16 +00:00
int r = 0;
int use_mmap = 1;
off_t mmap_offset = 0;
char *buf = NULL;
struct config_source *cs = dm_config_get_custom(cft);
if ((cs->type != CONFIG_FILE) && (cs->type != CONFIG_PROFILE)) {
log_error(INTERNAL_ERROR "config_file_read_fd: expected file or profile config source, "
"found %s config source.", _config_source_names[cs->type]);
return 0;
}
/* Only use mmap with regular files */
if (!(dev->flags & DEV_REGULAR) || size2)
use_mmap = 0;
if (use_mmap) {
2006-08-17 18:23:44 +00:00
mmap_offset = offset % lvm_getpagesize();
2002-11-18 14:01:16 +00:00
/* memory map the file */
fb = mmap((caddr_t) 0, size + mmap_offset, PROT_READ,
MAP_PRIVATE, dev_fd(dev), offset - mmap_offset);
if (fb == (caddr_t) (-1)) {
log_sys_error("mmap", dev_name(dev));
2002-11-18 14:01:16 +00:00
goto out;
}
fb = fb + mmap_offset;
} else {
if (!(buf = dm_malloc(size + size2))) {
log_error("Failed to allocate circular buffer.");
return 0;
}
if (!dev_read_circular(dev, (uint64_t) offset, size,
(uint64_t) offset2, size2, buf)) {
goto out;
}
fb = buf;
2002-11-18 14:01:16 +00:00
}
if (checksum_fn && checksum !=
(checksum_fn(checksum_fn(INITIAL_CRC, (const uint8_t *)fb, size),
(const uint8_t *)(fb + size), size2))) {
log_error("%s: Checksum error", dev_name(dev));
2002-11-18 14:01:16 +00:00
goto out;
}
fe = fb + size + size2;
if (!dm_config_parse(cft, fb, fe))
2008-01-30 13:19:47 +00:00
goto_out;
2002-11-18 14:01:16 +00:00
r = 1;
out:
if (!use_mmap)
dm_free(buf);
2002-11-18 14:01:16 +00:00
else {
/* unmap the file */
if (munmap(fb - mmap_offset, size + mmap_offset)) {
log_sys_error("munmap", dev_name(dev));
2002-11-18 14:01:16 +00:00
r = 0;
}
}
return r;
}
int config_file_read(struct dm_config_tree *cft)
2002-11-18 14:01:16 +00:00
{
const char *filename = NULL;
struct config_source *cs = dm_config_get_custom(cft);
struct config_file *cf;
2002-11-18 14:01:16 +00:00
struct stat info;
int r;
if (!config_file_check(cft, &filename, &info))
2011-09-01 21:04:14 +00:00
return_0;
2004-05-04 18:28:15 +00:00
/* Nothing to do. E.g. empty file. */
if (!filename)
return 1;
cf = cs->source.file;
if (!cf->dev) {
if (!(cf->dev = dev_create_file(filename, NULL, NULL, 1)))
return_0;
if (!dev_open_readonly_buffered(cf->dev))
return_0;
}
r = config_file_read_fd(cft, cf->dev, 0, (size_t) info.st_size, 0, 0,
(checksum_fn_t) NULL, 0);
2002-11-18 14:01:16 +00:00
if (!cf->keep_open) {
if (!dev_close(cf->dev))
stack;
cf->dev = NULL;
}
2002-11-18 14:01:16 +00:00
return r;
}
time_t config_file_timestamp(struct dm_config_tree *cft)
{
struct config_source *cs = dm_config_get_custom(cft);
return cs->timestamp;
}
#define cfg_def_get_item_p(id) (&_cfg_def_items[id])
#define cfg_def_get_default_value(item,type) item->default_value.v_##type
#define cfg_def_get_path(item) (_cfg_def_make_path(_cfg_path,CFG_PATH_MAX_LEN,item->id,item),_cfg_path)
static int _cfg_def_make_path(char *buf, size_t buf_size, int id, cfg_def_item_t *item)
{
int parent_id = item->parent;
int count, n;
if (id == parent_id)
return 0;
count = _cfg_def_make_path(buf, buf_size, parent_id, cfg_def_get_item_p(parent_id));
if ((n = dm_snprintf(buf + count, buf_size - count, "%s%s",
count ? "/" : "",
item->flags & CFG_NAME_VARIABLE ? "#" : item->name)) < 0) {
log_error(INTERNAL_ERROR "_cfg_def_make_path: supplied buffer too small for %s/%s",
cfg_def_get_item_p(parent_id)->name, item->name);
buf[0] = '\0';
return 0;
}
return count + n;
}
int config_def_get_path(char *buf, size_t buf_size, int id)
{
return _cfg_def_make_path(buf, buf_size, id, cfg_def_get_item_p(id));
}
static void _get_type_name(char *buf, size_t buf_size, cfg_def_type_t type)
{
(void) dm_snprintf(buf, buf_size, "%s%s%s%s%s%s",
(type & CFG_TYPE_ARRAY) ?
((type & ~CFG_TYPE_ARRAY) ?
" array with values of type:" : " array") : "",
(type & CFG_TYPE_SECTION) ? " section" : "",
(type & CFG_TYPE_BOOL) ? " boolean" : "",
(type & CFG_TYPE_INT) ? " integer" : "",
(type & CFG_TYPE_FLOAT) ? " float" : "",
(type & CFG_TYPE_STRING) ? " string" : "");
}
static void _log_type_error(const char *path, cfg_def_type_t actual,
cfg_def_type_t expected, int suppress_messages)
{
static char actual_type_name[128];
static char expected_type_name[128];
_get_type_name(actual_type_name, sizeof(actual_type_name), actual);
_get_type_name(expected_type_name, sizeof(expected_type_name), expected);
log_warn_suppress(suppress_messages, "Configuration setting \"%s\" has invalid type. "
"Found%s, expected%s.", path,
actual_type_name, expected_type_name);
}
static int _config_def_check_node_single_value(const char *rp, const struct dm_config_value *v,
const cfg_def_item_t *def, int suppress_messages)
{
/* Check empty array first if present. */
if (v->type == DM_CFG_EMPTY_ARRAY) {
if (!(def->type & CFG_TYPE_ARRAY)) {
_log_type_error(rp, CFG_TYPE_ARRAY, def->type, suppress_messages);
return 0;
}
if (!(def->flags & CFG_ALLOW_EMPTY)) {
log_warn_suppress(suppress_messages,
"Configuration setting \"%s\" invalid. Empty value not allowed.", rp);
return 0;
}
return 1;
}
switch (v->type) {
case DM_CFG_INT:
if (!(def->type & CFG_TYPE_INT) && !(def->type & CFG_TYPE_BOOL)) {
_log_type_error(rp, CFG_TYPE_INT, def->type, suppress_messages);
return 0;
}
break;
case DM_CFG_FLOAT:
if (!(def->type & CFG_TYPE_FLOAT)) {
_log_type_error(rp, CFG_TYPE_FLOAT, def->type, suppress_messages);
return 0;
}
break;
case DM_CFG_STRING:
if (def->type & CFG_TYPE_BOOL) {
if (!dm_config_value_is_bool(v)) {
log_warn_suppress(suppress_messages,
"Configuration setting \"%s\" invalid. "
"Found string value \"%s\", "
"expected boolean value: 0/1, \"y/n\", "
"\"yes/no\", \"on/off\", "
"\"true/false\".", rp, v->v.str);
return 0;
}
} else if (!(def->type & CFG_TYPE_STRING)) {
_log_type_error(rp, CFG_TYPE_STRING, def->type, suppress_messages);
return 0;
}
break;
default: ;
}
return 1;
}
static int _config_def_check_node_value(const char *rp, const struct dm_config_value *v,
const cfg_def_item_t *def, int suppress_messages)
{
if (!v) {
if (def->type != CFG_TYPE_SECTION) {
_log_type_error(rp, CFG_TYPE_SECTION, def->type, suppress_messages);
return 0;
}
return 1;
}
if (v->next) {
if (!(def->type & CFG_TYPE_ARRAY)) {
_log_type_error(rp, CFG_TYPE_ARRAY, def->type, suppress_messages);
return 0;
}
}
do {
if (!_config_def_check_node_single_value(rp, v, def, suppress_messages))
return 0;
v = v->next;
} while (v);
return 1;
}
static int _config_def_check_node(const char *vp, char *pvp, char *rp, char *prp,
size_t buf_size, struct dm_config_node *cn,
struct dm_hash_table *ht, int suppress_messages)
{
cfg_def_item_t *def;
int sep = vp != pvp; /* don't use '/' separator for top-level node */
if (dm_snprintf(pvp, buf_size, "%s%s", sep ? "/" : "", cn->key) < 0 ||
dm_snprintf(prp, buf_size, "%s%s", sep ? "/" : "", cn->key) < 0) {
log_error("Failed to construct path for configuration node %s.", cn->key);
return 0;
}
if (!(def = (cfg_def_item_t *) dm_hash_lookup(ht, vp))) {
/* If the node is not a section but a setting, fail now. */
if (cn->v) {
log_warn_suppress(suppress_messages,
"Configuration setting \"%s\" unknown.", rp);
config: add support for enhanced config node output There's a possibility to interconnect the dm_config_node with an ID, which in our case is used to reference the configuration definition ID from config_settings.h. So simply interconnecting struct dm_config_node with struct cfg_def_item. This patch also adds support for enhanced config node output besides existing "output line by line". This patch adds a possibility to register a callback that gets called *before* the config node is processed line by line (for example to include any headers on output) and *after* the config node is processed line by line (to include any footers on output). Also, it adds the config node reference itself as the callback arg in addition to have a possibility to extract more information from the config node itself if needed when processing the output callback (e.g. the key name, the id, or whether this is a section or a value etc...). If the config node from lvm.conf/--config tree is recognized and valid, it's always coupled with the config node definition ID from config_settings.h: struct dm_config_node { int id; const char *key; struct dm_config_node *parent, *sib, *child; struct dm_config_value *v; } For example if the dm_config_node *cn holds "devices/dev" configuration, then the cn->id holds "devices_dev_CFG" ID from config_settings.h, -1 if not found in config_settings.h and 0 if matching has not yet been done. To support the enhanced config node output, a new structure has been defined in libdevmapper to register it: struct dm_config_node_out_spec { dm_config_node_out_fn prefix_fn; /* called before processing config node lines */ dm_config_node_out_fn line_fn; /* called for each config node line */ dm_config_node_out_fn suffix_fn; /* called after processing config node lines */ }; Where dm_config_node_out_fn is: typedef int (*dm_config_node_out_fn)(const struct dm_config_node *cn, const char *line, void *baton); (so in comparison to existing callbacks for config node output, it has an extra dm_config_node *cn arg in addition) This patch also adds these functions to libdevmapper: - dm_config_write_node_out - dm_config_write_one_node_out ...which have exactly the same functionality as their counterparts without the "out" suffix. The "*_out" functions adds the extra hooks for enhanced config output (prefix_fn and suffix_fn mentioned above). One can still use the old interface for config node output, this is just an enhancement for those who'd like to modify the output more extensively.
2013-03-05 18:02:13 +01:00
cn->id = -1;
return 0;
}
/* If the node is a section, try if the section name is variable. */
/* Modify virtual path vp in situ and replace the key name with a '#'. */
/* The real path without '#' is still stored in rp variable. */
pvp[sep] = '#', pvp[sep + 1] = '\0';
if (!(def = (cfg_def_item_t *) dm_hash_lookup(ht, vp))) {
log_warn_suppress(suppress_messages,
"Configuration section \"%s\" unknown.", rp);
config: add support for enhanced config node output There's a possibility to interconnect the dm_config_node with an ID, which in our case is used to reference the configuration definition ID from config_settings.h. So simply interconnecting struct dm_config_node with struct cfg_def_item. This patch also adds support for enhanced config node output besides existing "output line by line". This patch adds a possibility to register a callback that gets called *before* the config node is processed line by line (for example to include any headers on output) and *after* the config node is processed line by line (to include any footers on output). Also, it adds the config node reference itself as the callback arg in addition to have a possibility to extract more information from the config node itself if needed when processing the output callback (e.g. the key name, the id, or whether this is a section or a value etc...). If the config node from lvm.conf/--config tree is recognized and valid, it's always coupled with the config node definition ID from config_settings.h: struct dm_config_node { int id; const char *key; struct dm_config_node *parent, *sib, *child; struct dm_config_value *v; } For example if the dm_config_node *cn holds "devices/dev" configuration, then the cn->id holds "devices_dev_CFG" ID from config_settings.h, -1 if not found in config_settings.h and 0 if matching has not yet been done. To support the enhanced config node output, a new structure has been defined in libdevmapper to register it: struct dm_config_node_out_spec { dm_config_node_out_fn prefix_fn; /* called before processing config node lines */ dm_config_node_out_fn line_fn; /* called for each config node line */ dm_config_node_out_fn suffix_fn; /* called after processing config node lines */ }; Where dm_config_node_out_fn is: typedef int (*dm_config_node_out_fn)(const struct dm_config_node *cn, const char *line, void *baton); (so in comparison to existing callbacks for config node output, it has an extra dm_config_node *cn arg in addition) This patch also adds these functions to libdevmapper: - dm_config_write_node_out - dm_config_write_one_node_out ...which have exactly the same functionality as their counterparts without the "out" suffix. The "*_out" functions adds the extra hooks for enhanced config output (prefix_fn and suffix_fn mentioned above). One can still use the old interface for config node output, this is just an enhancement for those who'd like to modify the output more extensively.
2013-03-05 18:02:13 +01:00
cn->id = -1;
return 0;
}
}
def->flags |= CFG_USED;
config: add support for enhanced config node output There's a possibility to interconnect the dm_config_node with an ID, which in our case is used to reference the configuration definition ID from config_settings.h. So simply interconnecting struct dm_config_node with struct cfg_def_item. This patch also adds support for enhanced config node output besides existing "output line by line". This patch adds a possibility to register a callback that gets called *before* the config node is processed line by line (for example to include any headers on output) and *after* the config node is processed line by line (to include any footers on output). Also, it adds the config node reference itself as the callback arg in addition to have a possibility to extract more information from the config node itself if needed when processing the output callback (e.g. the key name, the id, or whether this is a section or a value etc...). If the config node from lvm.conf/--config tree is recognized and valid, it's always coupled with the config node definition ID from config_settings.h: struct dm_config_node { int id; const char *key; struct dm_config_node *parent, *sib, *child; struct dm_config_value *v; } For example if the dm_config_node *cn holds "devices/dev" configuration, then the cn->id holds "devices_dev_CFG" ID from config_settings.h, -1 if not found in config_settings.h and 0 if matching has not yet been done. To support the enhanced config node output, a new structure has been defined in libdevmapper to register it: struct dm_config_node_out_spec { dm_config_node_out_fn prefix_fn; /* called before processing config node lines */ dm_config_node_out_fn line_fn; /* called for each config node line */ dm_config_node_out_fn suffix_fn; /* called after processing config node lines */ }; Where dm_config_node_out_fn is: typedef int (*dm_config_node_out_fn)(const struct dm_config_node *cn, const char *line, void *baton); (so in comparison to existing callbacks for config node output, it has an extra dm_config_node *cn arg in addition) This patch also adds these functions to libdevmapper: - dm_config_write_node_out - dm_config_write_one_node_out ...which have exactly the same functionality as their counterparts without the "out" suffix. The "*_out" functions adds the extra hooks for enhanced config output (prefix_fn and suffix_fn mentioned above). One can still use the old interface for config node output, this is just an enhancement for those who'd like to modify the output more extensively.
2013-03-05 18:02:13 +01:00
cn->id = def->id;
if (!_config_def_check_node_value(rp, cn->v, def, suppress_messages))
return 0;
def->flags |= CFG_VALID;
return 1;
}
static int _config_def_check_tree(const char *vp, char *pvp, char *rp, char *prp,
size_t buf_size, struct dm_config_node *root,
struct dm_hash_table *ht, int suppress_messages)
{
struct dm_config_node *cn;
int valid, r = 1;
size_t len;
for (cn = root->child; cn; cn = cn->sib) {
if ((valid = _config_def_check_node(vp, pvp, rp, prp, buf_size,
cn, ht, suppress_messages)) && !cn->v) {
len = strlen(rp);
valid = _config_def_check_tree(vp, pvp + strlen(pvp),
rp, prp + len, buf_size - len, cn, ht,
suppress_messages);
}
if (!valid)
r = 0;
}
return r;
}
int config_def_check(struct cmd_context *cmd, int force, int skip, int suppress_messages)
{
cfg_def_item_t *def;
struct dm_config_node *cn;
char *vp = _cfg_path, rp[CFG_PATH_MAX_LEN];
size_t rplen;
int id, r = 1;
/*
* vp = virtual path, it might contain substitutes for variable parts
* of the path, used while working with the hash
* rp = real path, the real path of the config element as found in the
* configuration, used for message output
*/
/*
* If the check has already been done and 'skip' is set,
* skip the actual check and use last result if available.
* If not available, we must do the check. The global status
* is stored in root node.
*/
def = cfg_def_get_item_p(root_CFG_SECTION);
if (skip && (def->flags & CFG_USED))
return def->flags & CFG_VALID;
/* Clear 'used' and 'valid' status flags. */
for (id = 0; id < CFG_COUNT; id++) {
def = cfg_def_get_item_p(id);
def->flags &= ~(CFG_USED | CFG_VALID);
}
if (!force && !find_config_tree_bool(cmd, config_checks_CFG)) {
if (cmd->cft_def_hash) {
dm_hash_destroy(cmd->cft_def_hash);
cmd->cft_def_hash = NULL;
}
return 1;
}
/*
* Create a hash of all possible configuration
* sections and settings with full path as a key.
* If section name is variable, use '#' as a substitute.
*/
if (!cmd->cft_def_hash) {
if (!(cmd->cft_def_hash = dm_hash_create(64))) {
log_error("Failed to create configuration definition hash.");
r = 0; goto out;
}
for (id = 1; id < CFG_COUNT; id++) {
def = cfg_def_get_item_p(id);
if (!cfg_def_get_path(def)) {
dm_hash_destroy(cmd->cft_def_hash);
cmd->cft_def_hash = NULL;
r = 0; goto out;
}
if (!dm_hash_insert(cmd->cft_def_hash, vp, def)) {
log_error("Failed to insert configuration to hash.");
r = 0;
goto out;
}
}
}
cfg_def_get_item_p(root_CFG_SECTION)->flags |= CFG_USED;