#include <sys/cdefs.h>
#include <assert.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
static nvlist_t *config_root;
void
init_config(void)
{
config_root = nvlist_create(0);
if (config_root == NULL)
err(4, "Failed to create configuration root nvlist");
}
static nvlist_t *
_lookup_config_node(nvlist_t *parent, const char *path, bool create)
{
char *copy, *name, *tofree;
nvlist_t *nvl, *new_nvl;
copy = strdup(path);
if (copy == NULL)
errx(4, "Failed to allocate memory");
tofree = copy;
nvl = parent;
while ((name = strsep(©, ".")) != NULL) {
if (*name == '\0') {
warnx("Invalid configuration node: %s", path);
nvl = NULL;
break;
}
if (nvlist_exists_nvlist(nvl, name))
nvl = __DECONST(nvlist_t *,
nvlist_get_nvlist(nvl, name));
else if (nvlist_exists(nvl, name)) {
for (copy = tofree; copy < name; copy++)
if (*copy == '\0')
*copy = '.';
warnx(
"Configuration node %s is a child of existing variable %s",
path, tofree);
nvl = NULL;
break;
} else if (create) {
new_nvl = nvlist_create(0);
if (new_nvl == NULL)
errx(4, "Failed to allocate memory");
nvlist_move_nvlist(nvl, name, new_nvl);
nvl = new_nvl;
} else {
nvl = NULL;
break;
}
}
free(tofree);
return (nvl);
}
nvlist_t *
create_config_node(const char *path)
{
return (_lookup_config_node(config_root, path, true));
}
nvlist_t *
find_config_node(const char *path)
{
return (_lookup_config_node(config_root, path, false));
}
nvlist_t *
create_relative_config_node(nvlist_t *parent, const char *path)
{
return (_lookup_config_node(parent, path, true));
}
nvlist_t *
find_relative_config_node(nvlist_t *parent, const char *path)
{
return (_lookup_config_node(parent, path, false));
}
void
set_config_value_node(nvlist_t *parent, const char *name, const char *value)
{
if (strchr(name, '.') != NULL)
errx(4, "Invalid config node name %s", name);
if (parent == NULL)
parent = config_root;
if (nvlist_exists_string(parent, name))
nvlist_free_string(parent, name);
else if (nvlist_exists(parent, name))
errx(4,
"Attempting to add value %s to existing node %s of list %p",
value, name, parent);
nvlist_add_string(parent, name, value);
}
void
set_config_value_node_if_unset(nvlist_t *const parent, const char *const name,
const char *const value)
{
if (get_config_value_node(parent, name) != NULL) {
return;
}
set_config_value_node(parent, name, value);
}
void
set_config_value(const char *path, const char *value)
{
const char *name;
char *node_name;
nvlist_t *nvl;
name = strrchr(path, '.');
if (name == NULL) {
nvl = config_root;
name = path;
} else {
node_name = strndup(path, name - path);
if (node_name == NULL)
errx(4, "Failed to allocate memory");
nvl = create_config_node(node_name);
if (nvl == NULL)
errx(4, "Failed to create configuration node %s",
node_name);
free(node_name);
name++;
}
if (nvlist_exists_nvlist(nvl, name))
errx(4, "Attempting to add value %s to existing node %s",
value, path);
set_config_value_node(nvl, name, value);
}
void
set_config_value_if_unset(const char *const path, const char *const value)
{
if (get_config_value(path) != NULL) {
return;
}
set_config_value(path, value);
}
static const char *
get_raw_config_value(const char *path)
{
const char *name;
char *node_name;
nvlist_t *nvl;
name = strrchr(path, '.');
if (name == NULL) {
nvl = config_root;
name = path;
} else {
node_name = strndup(path, name - path);
if (node_name == NULL)
errx(4, "Failed to allocate memory");
nvl = find_config_node(node_name);
free(node_name);
if (nvl == NULL)
return (NULL);
name++;
}
if (nvlist_exists_string(nvl, name))
return (nvlist_get_string(nvl, name));
if (nvlist_exists_nvlist(nvl, name))
warnx("Attempting to fetch value of node %s", path);
return (NULL);
}
static char *
_expand_config_value(const char *value, int depth)
{
FILE *valfp;
const char *cp, *vp;
char *nestedval, *path, *valbuf;
size_t valsize;
valfp = open_memstream(&valbuf, &valsize);
if (valfp == NULL)
errx(4, "Failed to allocate memory");
vp = value;
while (*vp != '\0') {
switch (*vp) {
case '%':
if (depth > 15) {
warnx(
"Too many recursive references in configuration value");
fputc('%', valfp);
vp++;
break;
}
if (vp[1] != '(' || vp[2] == '\0')
cp = NULL;
else
cp = strchr(vp + 2, ')');
if (cp == NULL) {
warnx(
"Invalid reference in configuration value \"%s\"",
value);
fputc('%', valfp);
vp++;
break;
}
vp += 2;
if (cp == vp) {
warnx(
"Empty reference in configuration value \"%s\"",
value);
vp++;
break;
}
path = strndup(vp, cp - vp);
if (path == NULL)
errx(4, "Failed to allocate memory");
vp = cp + 1;
cp = get_raw_config_value(path);
if (cp == NULL)
warnx(
"Failed to fetch referenced configuration variable %s",
path);
else {
nestedval = _expand_config_value(cp, depth + 1);
fputs(nestedval, valfp);
free(nestedval);
}
free(path);
break;
case '\\':
vp++;
if (*vp == '\0') {
warnx(
"Trailing \\ in configuration value \"%s\"",
value);
break;
}
default:
fputc(*vp, valfp);
vp++;
break;
}
}
fclose(valfp);
return (valbuf);
}
static const char *
expand_config_value(const char *value)
{
static char *valbuf;
if (strchr(value, '%') == NULL)
return (value);
free(valbuf);
valbuf = _expand_config_value(value, 0);
return (valbuf);
}
const char *
get_config_value(const char *path)
{
const char *value;
value = get_raw_config_value(path);
if (value == NULL)
return (NULL);
return (expand_config_value(value));
}
const char *
get_config_value_node(const nvlist_t *parent, const char *name)
{
if (strchr(name, '.') != NULL)
errx(4, "Invalid config node name %s", name);
if (parent == NULL)
parent = config_root;
if (nvlist_exists_nvlist(parent, name))
warnx("Attempt to fetch value of node %s of list %p", name,
parent);
if (!nvlist_exists_string(parent, name))
return (NULL);
return (expand_config_value(nvlist_get_string(parent, name)));
}
static bool
_bool_value(const char *name, const char *value)
{
if (strcasecmp(value, "true") == 0 ||
strcasecmp(value, "on") == 0 ||
strcasecmp(value, "yes") == 0 ||
strcmp(value, "1") == 0)
return (true);
if (strcasecmp(value, "false") == 0 ||
strcasecmp(value, "off") == 0 ||
strcasecmp(value, "no") == 0 ||
strcmp(value, "0") == 0)
return (false);
err(4, "Invalid value %s for boolean variable %s", value, name);
}
bool
get_config_bool(const char *path)
{
const char *value;
value = get_config_value(path);
if (value == NULL)
err(4, "Failed to fetch boolean variable %s", path);
return (_bool_value(path, value));
}
bool
get_config_bool_default(const char *path, bool def)
{
const char *value;
value = get_config_value(path);
if (value == NULL)
return (def);
return (_bool_value(path, value));
}
bool
get_config_bool_node(const nvlist_t *parent, const char *name)
{
const char *value;
value = get_config_value_node(parent, name);
if (value == NULL)
err(4, "Failed to fetch boolean variable %s", name);
return (_bool_value(name, value));
}
bool
get_config_bool_node_default(const nvlist_t *parent, const char *name,
bool def)
{
const char *value;
value = get_config_value_node(parent, name);
if (value == NULL)
return (def);
return (_bool_value(name, value));
}
void
set_config_bool(const char *path, bool value)
{
set_config_value(path, value ? "true" : "false");
}
void
set_config_bool_node(nvlist_t *parent, const char *name, bool value)
{
set_config_value_node(parent, name, value ? "true" : "false");
}
int
walk_config_nodes(const char *prefix, const nvlist_t *parent, void *arg,
int (*cb)(const char *, const nvlist_t *, const char *, int, void *))
{
void *cookie = NULL;
const char *name;
int type;
while ((name = nvlist_next(parent, &type, &cookie)) != NULL) {
int ret;
ret = cb(prefix, parent, name, type, arg);
if (ret != 0)
return (ret);
}
return (0);
}
static int
dump_node_cb(const char *prefix, const nvlist_t *parent, const char *name,
int type, void *arg)
{
if (type == NV_TYPE_NVLIST) {
char *new_prefix;
int ret;
asprintf(&new_prefix, "%s%s.", prefix, name);
ret = walk_config_nodes(new_prefix,
nvlist_get_nvlist(parent, name), arg, dump_node_cb);
free(new_prefix);
return (ret);
}
assert(type == NV_TYPE_STRING);
printf("%s%s=%s\n", prefix, name, nvlist_get_string(parent, name));
return (0);
}
void
dump_config(void)
{
(void)walk_config_nodes("", config_root, NULL, dump_node_cb);
}