mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
efd0c45a8b
In tini, allow_empty_value=false causes the parser to ignore the lines without '=' sign, but lines with nothing after '=' sign are allowed and cause empty string ("") to be passed as a value. This is counter-intuitive, so conf requires special handling for empty values (which are treated as invalid). Signed-off-by: Amitay Isaacs <amitay@gmail.com> Reviewed-by: Martin Schwenke <martin@meltin.net>
1392 lines
26 KiB
C
1392 lines
26 KiB
C
/*
|
|
Configuration file handling on top of tini
|
|
|
|
Copyright (C) Amitay Isaacs 2017
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "replace.h"
|
|
#include "system/locale.h"
|
|
|
|
#include <talloc.h>
|
|
|
|
#include "lib/util/dlinklist.h"
|
|
#include "lib/util/tini.h"
|
|
#include "lib/util/debug.h"
|
|
|
|
#include "common/conf.h"
|
|
|
|
struct conf_value {
|
|
enum conf_type type;
|
|
union {
|
|
const char *string;
|
|
int integer;
|
|
bool boolean;
|
|
} data;
|
|
};
|
|
|
|
union conf_pointer {
|
|
const char **string;
|
|
int *integer;
|
|
bool *boolean;
|
|
};
|
|
|
|
struct conf_option {
|
|
struct conf_option *prev, *next;
|
|
|
|
const char *name;
|
|
enum conf_type type;
|
|
void *validate;
|
|
|
|
struct conf_value default_value;
|
|
bool default_set;
|
|
|
|
struct conf_value *value, *new_value;
|
|
union conf_pointer ptr;
|
|
bool temporary_modified;
|
|
};
|
|
|
|
struct conf_section {
|
|
struct conf_section *prev, *next;
|
|
|
|
const char *name;
|
|
conf_validate_section_fn validate;
|
|
struct conf_option *option;
|
|
};
|
|
|
|
struct conf_context {
|
|
const char *filename;
|
|
struct conf_section *section;
|
|
bool define_failed;
|
|
bool ignore_unknown;
|
|
bool reload;
|
|
bool validation_active;
|
|
};
|
|
|
|
/*
|
|
* Functions related to conf_value
|
|
*/
|
|
|
|
static int string_to_string(TALLOC_CTX *mem_ctx,
|
|
const char *str,
|
|
const char **str_val)
|
|
{
|
|
char *t;
|
|
|
|
if (str == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
t = talloc_strdup(mem_ctx, str);
|
|
if (t == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
*str_val = t;
|
|
return 0;
|
|
}
|
|
|
|
static int string_to_integer(const char *str, int *int_val)
|
|
{
|
|
long t;
|
|
char *endptr = NULL;
|
|
|
|
if (str == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
t = strtol(str, &endptr, 0);
|
|
if (*str != '\0' || endptr == NULL) {
|
|
if (t < 0 || t > INT_MAX) {
|
|
return EINVAL;
|
|
}
|
|
|
|
*int_val = (int)t;
|
|
return 0;
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
static int string_to_boolean(const char *str, bool *bool_val)
|
|
{
|
|
if (strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0) {
|
|
*bool_val = true;
|
|
return 0;
|
|
}
|
|
|
|
if (strcasecmp(str, "false") == 0 || strcasecmp(str, "no") == 0) {
|
|
*bool_val = false;
|
|
return 0;
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
static int conf_value_from_string(TALLOC_CTX *mem_ctx,
|
|
const char *str,
|
|
struct conf_value *value)
|
|
{
|
|
int ret;
|
|
|
|
switch (value->type) {
|
|
case CONF_STRING:
|
|
ret = string_to_string(mem_ctx, str, &value->data.string);
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
ret = string_to_integer(str, &value->data.integer);
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
ret = string_to_boolean(str, &value->data.boolean);
|
|
break;
|
|
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool conf_value_compare(struct conf_value *old, struct conf_value *new)
|
|
{
|
|
if (old == NULL || new == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (old->type != new->type) {
|
|
return false;
|
|
}
|
|
|
|
switch (old->type) {
|
|
case CONF_STRING:
|
|
if (old->data.string == NULL && new->data.string == NULL) {
|
|
return true;
|
|
}
|
|
if (old->data.string != NULL && new->data.string != NULL) {
|
|
if (strcmp(old->data.string, new->data.string) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
if (old->data.integer == new->data.integer) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
if (old->data.boolean == new->data.boolean) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int conf_value_copy(TALLOC_CTX *mem_ctx,
|
|
struct conf_value *src,
|
|
struct conf_value *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
return EINVAL;
|
|
}
|
|
|
|
switch (src->type) {
|
|
case CONF_STRING:
|
|
if (dst->data.string != NULL) {
|
|
talloc_free(discard_const(dst->data.string));
|
|
}
|
|
if (src->data.string == NULL) {
|
|
dst->data.string = NULL;
|
|
} else {
|
|
dst->data.string = talloc_strdup(
|
|
mem_ctx, src->data.string);
|
|
if (dst->data.string == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
dst->data.integer = src->data.integer;
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
dst->data.boolean = src->data.boolean;
|
|
break;
|
|
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conf_value_dump(const char *key,
|
|
struct conf_value *value,
|
|
bool is_default,
|
|
bool is_temporary,
|
|
FILE *fp)
|
|
{
|
|
if ((value->type == CONF_STRING && value->data.string == NULL) ||
|
|
is_default) {
|
|
fprintf(fp, "\t# %s = ", key);
|
|
} else {
|
|
fprintf(fp, "\t%s = ", key);
|
|
}
|
|
|
|
switch (value->type) {
|
|
case CONF_STRING:
|
|
if (value->data.string != NULL) {
|
|
fprintf(fp, "%s", value->data.string);
|
|
}
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
fprintf(fp, "%d", value->data.integer);
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
fprintf(fp, "%s", (value->data.boolean ? "true" : "false"));
|
|
break;
|
|
}
|
|
|
|
if (is_temporary) {
|
|
fprintf(fp, " # temporary");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Functions related to conf_option
|
|
*/
|
|
|
|
static struct conf_option *conf_option_find(struct conf_section *s,
|
|
const char *key)
|
|
{
|
|
struct conf_option *opt;
|
|
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
if (strcmp(opt->name, key) == 0) {
|
|
return opt;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void conf_option_set_ptr_value(struct conf_option *opt)
|
|
{
|
|
switch (opt->type) {
|
|
case CONF_STRING:
|
|
if (opt->ptr.string != NULL) {
|
|
*(opt->ptr.string) = opt->value->data.string;
|
|
}
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
if (opt->ptr.integer != NULL) {
|
|
*(opt->ptr.integer) = opt->value->data.integer;
|
|
}
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
if (opt->ptr.boolean != NULL) {
|
|
*(opt->ptr.boolean) = opt->value->data.boolean;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void conf_option_default(struct conf_option *opt);
|
|
|
|
static int conf_option_add(struct conf_section *s,
|
|
const char *key,
|
|
enum conf_type type,
|
|
void *validate,
|
|
struct conf_option **popt)
|
|
{
|
|
struct conf_option *opt;
|
|
|
|
opt = conf_option_find(s, key);
|
|
if (opt != NULL) {
|
|
D_ERR("conf: option \"%s\" already exists\n", key);
|
|
return EEXIST;
|
|
}
|
|
|
|
opt = talloc_zero(s, struct conf_option);
|
|
if (opt == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
opt->name = talloc_strdup(opt, key);
|
|
if (opt->name == NULL) {
|
|
talloc_free(opt);
|
|
return ENOMEM;
|
|
}
|
|
|
|
opt->type = type;
|
|
opt->validate = validate;
|
|
|
|
DLIST_ADD_END(s->option, opt);
|
|
|
|
if (popt != NULL) {
|
|
*popt = opt;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int conf_option_set_default(struct conf_option *opt,
|
|
struct conf_value *default_value)
|
|
{
|
|
int ret;
|
|
|
|
opt->default_value.type = opt->type;
|
|
|
|
ret = conf_value_copy(opt, default_value, &opt->default_value);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
opt->default_set = true;
|
|
opt->temporary_modified = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conf_option_set_ptr(struct conf_option *opt,
|
|
union conf_pointer *ptr)
|
|
{
|
|
opt->ptr = *ptr;
|
|
}
|
|
|
|
static bool conf_option_validate_string(struct conf_option *opt,
|
|
struct conf_value *value,
|
|
enum conf_update_mode mode)
|
|
{
|
|
conf_validate_string_option_fn validate =
|
|
(conf_validate_string_option_fn)opt->validate;
|
|
|
|
return validate(opt->name,
|
|
opt->value->data.string,
|
|
value->data.string,
|
|
mode);
|
|
}
|
|
|
|
static bool conf_option_validate_integer(struct conf_option *opt,
|
|
struct conf_value *value,
|
|
enum conf_update_mode mode)
|
|
{
|
|
conf_validate_integer_option_fn validate =
|
|
(conf_validate_integer_option_fn)opt->validate;
|
|
|
|
return validate(opt->name,
|
|
opt->value->data.integer,
|
|
value->data.integer,
|
|
mode);
|
|
}
|
|
|
|
static bool conf_option_validate_boolean(struct conf_option *opt,
|
|
struct conf_value *value,
|
|
enum conf_update_mode mode)
|
|
{
|
|
conf_validate_boolean_option_fn validate =
|
|
(conf_validate_boolean_option_fn)opt->validate;
|
|
|
|
return validate(opt->name,
|
|
opt->value->data.boolean,
|
|
value->data.boolean,
|
|
mode);
|
|
}
|
|
|
|
static bool conf_option_validate(struct conf_option *opt,
|
|
struct conf_value *value,
|
|
enum conf_update_mode mode)
|
|
{
|
|
int ret;
|
|
|
|
if (opt->validate == NULL) {
|
|
return true;
|
|
}
|
|
|
|
switch (opt->type) {
|
|
case CONF_STRING:
|
|
ret = conf_option_validate_string(opt, value, mode);
|
|
break;
|
|
|
|
case CONF_INTEGER:
|
|
ret = conf_option_validate_integer(opt, value, mode);
|
|
break;
|
|
|
|
case CONF_BOOLEAN:
|
|
ret = conf_option_validate_boolean(opt, value, mode);
|
|
break;
|
|
|
|
default:
|
|
ret = EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool conf_option_same_value(struct conf_option *opt,
|
|
struct conf_value *new_value)
|
|
{
|
|
return conf_value_compare(opt->value, new_value);
|
|
}
|
|
|
|
static int conf_option_new_value(struct conf_option *opt,
|
|
struct conf_value *new_value,
|
|
enum conf_update_mode mode)
|
|
{
|
|
int ret;
|
|
bool ok;
|
|
|
|
if (opt->new_value != &opt->default_value) {
|
|
TALLOC_FREE(opt->new_value);
|
|
}
|
|
|
|
if (new_value == &opt->default_value) {
|
|
/*
|
|
* This happens only during load/reload. Set the value to
|
|
* default value, so if the config option is dropped from
|
|
* config file, then it get's reset to default.
|
|
*/
|
|
opt->new_value = &opt->default_value;
|
|
} else {
|
|
ok = conf_option_validate(opt, new_value, mode);
|
|
if (!ok) {
|
|
D_ERR("conf: validation for option \"%s\" failed\n",
|
|
opt->name);
|
|
return EINVAL;
|
|
}
|
|
|
|
opt->new_value = talloc_zero(opt, struct conf_value);
|
|
if (opt->new_value == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
opt->new_value->type = opt->value->type;
|
|
ret = conf_value_copy(opt, new_value, opt->new_value);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
conf_option_set_ptr_value(opt);
|
|
|
|
if (new_value != &opt->default_value) {
|
|
if (mode == CONF_MODE_API) {
|
|
opt->temporary_modified = true;
|
|
} else {
|
|
opt->temporary_modified = false;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int conf_option_new_default_value(struct conf_option *opt,
|
|
enum conf_update_mode mode)
|
|
{
|
|
return conf_option_new_value(opt, &opt->default_value, mode);
|
|
}
|
|
|
|
static void conf_option_default(struct conf_option *opt)
|
|
{
|
|
if (! opt->default_set) {
|
|
return;
|
|
}
|
|
|
|
if (opt->value != &opt->default_value) {
|
|
TALLOC_FREE(opt->value);
|
|
}
|
|
|
|
opt->value = &opt->default_value;
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
static void conf_option_reset(struct conf_option *opt)
|
|
{
|
|
if (opt->new_value != &opt->default_value) {
|
|
TALLOC_FREE(opt->new_value);
|
|
}
|
|
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
static void conf_option_update(struct conf_option *opt)
|
|
{
|
|
if (opt->new_value == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (opt->value != &opt->default_value) {
|
|
TALLOC_FREE(opt->value);
|
|
}
|
|
|
|
opt->value = opt->new_value;
|
|
opt->new_value = NULL;
|
|
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
static void conf_option_reset_temporary(struct conf_option *opt)
|
|
{
|
|
opt->temporary_modified = false;
|
|
}
|
|
|
|
static bool conf_option_is_default(struct conf_option *opt)
|
|
{
|
|
return (opt->value == &opt->default_value);
|
|
}
|
|
|
|
static void conf_option_dump(struct conf_option *opt, FILE *fp)
|
|
{
|
|
bool is_default;
|
|
|
|
is_default = conf_option_is_default(opt);
|
|
|
|
conf_value_dump(opt->name,
|
|
opt->value,
|
|
is_default,
|
|
opt->temporary_modified,
|
|
fp);
|
|
}
|
|
|
|
/*
|
|
* Functions related to conf_section
|
|
*/
|
|
|
|
static struct conf_section *conf_section_find(struct conf_context *conf,
|
|
const char *section)
|
|
{
|
|
struct conf_section *s;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
if (strcasecmp(s->name, section) == 0) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int conf_section_add(struct conf_context *conf,
|
|
const char *section,
|
|
conf_validate_section_fn validate)
|
|
{
|
|
struct conf_section *s;
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s != NULL) {
|
|
return EEXIST;
|
|
}
|
|
|
|
s = talloc_zero(conf, struct conf_section);
|
|
if (s == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
s->name = talloc_strdup(s, section);
|
|
if (s->name == NULL) {
|
|
talloc_free(s);
|
|
return ENOMEM;
|
|
}
|
|
|
|
s->validate = validate;
|
|
|
|
DLIST_ADD_END(conf->section, s);
|
|
return 0;
|
|
}
|
|
|
|
static bool conf_section_validate(struct conf_context *conf,
|
|
struct conf_section *s,
|
|
enum conf_update_mode mode)
|
|
{
|
|
bool ok;
|
|
|
|
if (s->validate == NULL) {
|
|
return true;
|
|
}
|
|
|
|
ok = s->validate(conf, s->name, mode);
|
|
if (!ok) {
|
|
D_ERR("conf: validation for section [%s] failed\n", s->name);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static void conf_section_dump(struct conf_section *s, FILE *fp)
|
|
{
|
|
fprintf(fp, "[%s]\n", s->name);
|
|
}
|
|
|
|
/*
|
|
* Functions related to conf_context
|
|
*/
|
|
|
|
static void conf_all_default(struct conf_context *conf)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
conf_option_default(opt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int conf_all_temporary_default(struct conf_context *conf,
|
|
enum conf_update_mode mode)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
int ret;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
ret = conf_option_new_default_value(opt, mode);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conf_all_reset(struct conf_context *conf)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
conf_option_reset(opt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void conf_all_update(struct conf_context *conf)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
conf_option_update(opt);
|
|
conf_option_reset_temporary(opt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* API functions
|
|
*/
|
|
|
|
int conf_init(TALLOC_CTX *mem_ctx, struct conf_context **result)
|
|
{
|
|
struct conf_context *conf;
|
|
|
|
conf = talloc_zero(mem_ctx, struct conf_context);
|
|
if (conf == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
conf->define_failed = false;
|
|
|
|
*result = conf;
|
|
return 0;
|
|
}
|
|
|
|
void conf_define_section(struct conf_context *conf,
|
|
const char *section,
|
|
conf_validate_section_fn validate)
|
|
{
|
|
int ret;
|
|
|
|
if (conf->define_failed) {
|
|
return;
|
|
}
|
|
|
|
if (section == NULL) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
ret = conf_section_add(conf, section, validate);
|
|
if (ret != 0) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static struct conf_option *conf_define(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
enum conf_type type,
|
|
conf_validate_string_option_fn validate)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
int ret;
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s == NULL) {
|
|
D_ERR("conf: unknown section [%s]\n", section);
|
|
return NULL;
|
|
}
|
|
|
|
if (key == NULL) {
|
|
D_ERR("conf: option name null in section [%s]\n", section);
|
|
return NULL;
|
|
}
|
|
|
|
ret = conf_option_add(s, key, type, validate, &opt);
|
|
if (ret != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
return opt;
|
|
}
|
|
|
|
static void conf_define_post(struct conf_context *conf,
|
|
struct conf_option *opt,
|
|
struct conf_value *default_value)
|
|
{
|
|
int ret;
|
|
|
|
ret = conf_option_set_default(opt, default_value);
|
|
if (ret != 0) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
conf_option_default(opt);
|
|
}
|
|
|
|
void conf_define_string(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const char *default_str_val,
|
|
conf_validate_string_option_fn validate)
|
|
{
|
|
struct conf_option *opt;
|
|
struct conf_value default_value;
|
|
|
|
if (! conf_valid(conf)) {
|
|
return;
|
|
}
|
|
|
|
opt = conf_define(conf, section, key, CONF_STRING, validate);
|
|
if (opt == NULL) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
default_value.type = CONF_STRING;
|
|
default_value.data.string = default_str_val;
|
|
|
|
conf_define_post(conf, opt, &default_value);
|
|
}
|
|
|
|
void conf_define_integer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const int default_int_val,
|
|
conf_validate_integer_option_fn validate)
|
|
{
|
|
struct conf_option *opt;
|
|
struct conf_value default_value;
|
|
|
|
if (! conf_valid(conf)) {
|
|
return;
|
|
}
|
|
|
|
opt = conf_define(conf, section, key, CONF_INTEGER, (void *)validate);
|
|
if (opt == NULL) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
default_value.type = CONF_INTEGER;
|
|
default_value.data.integer = default_int_val;
|
|
|
|
conf_define_post(conf, opt, &default_value);
|
|
}
|
|
|
|
|
|
void conf_define_boolean(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const bool default_bool_val,
|
|
conf_validate_boolean_option_fn validate)
|
|
{
|
|
struct conf_option *opt;
|
|
struct conf_value default_value;
|
|
|
|
if (! conf_valid(conf)) {
|
|
return;
|
|
}
|
|
|
|
opt = conf_define(conf, section, key, CONF_BOOLEAN, (void *)validate);
|
|
if (opt == NULL) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
default_value.type = CONF_BOOLEAN;
|
|
default_value.data.boolean = default_bool_val;
|
|
|
|
conf_define_post(conf, opt, &default_value);
|
|
}
|
|
|
|
static struct conf_option *_conf_option(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
opt = conf_option_find(s, key);
|
|
return opt;
|
|
}
|
|
|
|
void conf_assign_string_pointer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const char **str_ptr)
|
|
{
|
|
struct conf_option *opt;
|
|
union conf_pointer ptr;
|
|
|
|
opt = _conf_option(conf, section, key);
|
|
if (opt == NULL) {
|
|
D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
if (opt->type != CONF_STRING) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
ptr.string = str_ptr;
|
|
conf_option_set_ptr(opt, &ptr);
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
void conf_assign_integer_pointer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
int *int_ptr)
|
|
{
|
|
struct conf_option *opt;
|
|
union conf_pointer ptr;
|
|
|
|
opt = _conf_option(conf, section, key);
|
|
if (opt == NULL) {
|
|
D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
if (opt->type != CONF_INTEGER) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
ptr.integer = int_ptr;
|
|
conf_option_set_ptr(opt, &ptr);
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
void conf_assign_boolean_pointer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
bool *bool_ptr)
|
|
{
|
|
struct conf_option *opt;
|
|
union conf_pointer ptr;
|
|
|
|
opt = _conf_option(conf, section, key);
|
|
if (opt == NULL) {
|
|
D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
if (opt->type != CONF_BOOLEAN) {
|
|
conf->define_failed = true;
|
|
return;
|
|
}
|
|
|
|
ptr.boolean = bool_ptr;
|
|
conf_option_set_ptr(opt, &ptr);
|
|
conf_option_set_ptr_value(opt);
|
|
}
|
|
|
|
bool conf_query(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
enum conf_type *type)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
if (! conf_valid(conf)) {
|
|
return false;
|
|
}
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s == NULL) {
|
|
return false;
|
|
}
|
|
|
|
opt = conf_option_find(s, key);
|
|
if (opt == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (type != NULL) {
|
|
*type = opt->type;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool conf_valid(struct conf_context *conf)
|
|
{
|
|
if (conf->define_failed) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void conf_set_defaults(struct conf_context *conf)
|
|
{
|
|
conf_all_default(conf);
|
|
}
|
|
|
|
struct conf_load_state {
|
|
struct conf_context *conf;
|
|
struct conf_section *s;
|
|
enum conf_update_mode mode;
|
|
int err;
|
|
};
|
|
|
|
static bool conf_load_section(const char *section, void *private_data);
|
|
static bool conf_load_option(const char *name,
|
|
const char *value_str,
|
|
void *private_data);
|
|
|
|
static int conf_load_internal(struct conf_context *conf)
|
|
{
|
|
struct conf_load_state state;
|
|
FILE *fp;
|
|
int ret;
|
|
bool ok;
|
|
|
|
state = (struct conf_load_state) {
|
|
.conf = conf,
|
|
.mode = (conf->reload ? CONF_MODE_RELOAD : CONF_MODE_LOAD),
|
|
};
|
|
|
|
ret = conf_all_temporary_default(conf, state.mode);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
fp = fopen(conf->filename, "r");
|
|
if (fp == NULL) {
|
|
return errno;
|
|
}
|
|
|
|
ok = tini_parse(fp,
|
|
false,
|
|
conf_load_section,
|
|
conf_load_option,
|
|
&state);
|
|
fclose(fp);
|
|
if (!ok) {
|
|
goto fail;
|
|
}
|
|
|
|
/* Process the last section */
|
|
if (state.s != NULL) {
|
|
ok = conf_section_validate(conf, state.s, state.mode);
|
|
if (!ok) {
|
|
state.err = EINVAL;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (state.err != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
conf_all_update(conf);
|
|
return 0;
|
|
|
|
fail:
|
|
conf_all_reset(conf);
|
|
return state.err;
|
|
}
|
|
|
|
static bool conf_load_section(const char *section, void *private_data)
|
|
{
|
|
struct conf_load_state *state =
|
|
(struct conf_load_state *)private_data;
|
|
bool ok;
|
|
|
|
if (state->s != NULL) {
|
|
ok = conf_section_validate(state->conf, state->s, state->mode);
|
|
if (!ok) {
|
|
state->err = EINVAL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
state->s = conf_section_find(state->conf, section);
|
|
if (state->s == NULL) {
|
|
if (state->conf->ignore_unknown) {
|
|
D_DEBUG("conf: ignoring unknown section [%s]\n",
|
|
section);
|
|
} else {
|
|
D_ERR("conf: unknown section [%s]\n", section);
|
|
state->err = EINVAL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool conf_load_option(const char *name,
|
|
const char *value_str,
|
|
void *private_data)
|
|
{
|
|
struct conf_load_state *state =
|
|
(struct conf_load_state *)private_data;
|
|
struct conf_option *opt;
|
|
TALLOC_CTX *tmp_ctx;
|
|
struct conf_value value;
|
|
int ret;
|
|
bool ok;
|
|
|
|
if (state->s == NULL) {
|
|
if (state->conf->ignore_unknown) {
|
|
D_DEBUG("conf: unknown section for option \"%s\"\n",
|
|
name);
|
|
return true;
|
|
} else {
|
|
D_ERR("conf: unknown section for option \"%s\"\n",
|
|
name);
|
|
state->err = EINVAL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
opt = conf_option_find(state->s, name);
|
|
if (opt == NULL) {
|
|
if (state->conf->ignore_unknown) {
|
|
D_DEBUG("conf: unknown option [%s] -> \"%s\"\n",
|
|
state->s->name,
|
|
name);
|
|
return true;
|
|
} else {
|
|
D_ERR("conf: unknown option [%s] -> \"%s\"\n",
|
|
state->s->name,
|
|
name);
|
|
state->err = EINVAL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (strlen(value_str) == 0) {
|
|
D_ERR("conf: empty value [%s] -> \"%s\"\n",
|
|
state->s->name,
|
|
name);
|
|
state->err = EINVAL;
|
|
return true;
|
|
}
|
|
|
|
tmp_ctx = talloc_new(state->conf);
|
|
if (tmp_ctx == NULL) {
|
|
state->err = ENOMEM;
|
|
return false;
|
|
}
|
|
|
|
value.type = opt->type;
|
|
ret = conf_value_from_string(tmp_ctx, value_str, &value);
|
|
if (ret != 0) {
|
|
D_ERR("conf: invalid value [%s] -> \"%s\" = \"%s\"\n",
|
|
state->s->name,
|
|
name,
|
|
value_str);
|
|
talloc_free(tmp_ctx);
|
|
state->err = ret;
|
|
return true;
|
|
}
|
|
|
|
ok = conf_option_same_value(opt, &value);
|
|
if (ok) {
|
|
goto done;
|
|
}
|
|
|
|
ret = conf_option_new_value(opt, &value, state->mode);
|
|
if (ret != 0) {
|
|
talloc_free(tmp_ctx);
|
|
state->err = ret;
|
|
return true;
|
|
}
|
|
|
|
done:
|
|
talloc_free(tmp_ctx);
|
|
return true;
|
|
|
|
}
|
|
|
|
int conf_load(struct conf_context *conf,
|
|
const char *filename,
|
|
bool ignore_unknown)
|
|
{
|
|
conf->filename = talloc_strdup(conf, filename);
|
|
if (conf->filename == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
conf->ignore_unknown = ignore_unknown;
|
|
|
|
D_NOTICE("Reading config file %s\n", filename);
|
|
|
|
return conf_load_internal(conf);
|
|
}
|
|
|
|
int conf_reload(struct conf_context *conf)
|
|
{
|
|
int ret;
|
|
|
|
if (conf->filename == NULL) {
|
|
return EPERM;
|
|
}
|
|
|
|
D_NOTICE("Re-reading config file %s\n", conf->filename);
|
|
|
|
conf->reload = true;
|
|
ret = conf_load_internal(conf);
|
|
conf->reload = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int conf_set(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
struct conf_value *value)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
int ret;
|
|
bool ok;
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
opt = conf_option_find(s, key);
|
|
if (opt == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if (opt->type != value->type) {
|
|
return EINVAL;
|
|
}
|
|
|
|
ok = conf_option_same_value(opt, value);
|
|
if (ok) {
|
|
return 0;
|
|
}
|
|
|
|
ret = conf_option_new_value(opt, value, CONF_MODE_API);
|
|
if (ret != 0) {
|
|
conf_option_reset(opt);
|
|
return ret;
|
|
}
|
|
|
|
ok = conf_section_validate(conf, s, CONF_MODE_API);
|
|
if (!ok) {
|
|
conf_option_reset(opt);
|
|
return EINVAL;
|
|
}
|
|
|
|
conf_option_update(opt);
|
|
return 0;
|
|
}
|
|
|
|
int conf_set_string(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const char *str_val)
|
|
{
|
|
struct conf_value value;
|
|
|
|
value.type = CONF_STRING;
|
|
value.data.string = str_val;
|
|
|
|
return conf_set(conf, section, key, &value);
|
|
}
|
|
|
|
int conf_set_integer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
int int_val)
|
|
{
|
|
struct conf_value value;
|
|
|
|
value.type = CONF_INTEGER;
|
|
value.data.integer = int_val;
|
|
|
|
return conf_set(conf, section, key, &value);
|
|
}
|
|
|
|
int conf_set_boolean(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
bool bool_val)
|
|
{
|
|
struct conf_value value;
|
|
|
|
value.type = CONF_BOOLEAN;
|
|
value.data.boolean = bool_val;
|
|
|
|
return conf_set(conf, section, key, &value);
|
|
}
|
|
|
|
static int conf_get(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
enum conf_type type,
|
|
const struct conf_value **value,
|
|
bool *is_default)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
s = conf_section_find(conf, section);
|
|
if (s == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
opt = conf_option_find(s, key);
|
|
if (opt == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if (opt->type != type) {
|
|
return EINVAL;
|
|
}
|
|
|
|
*value = opt->value;
|
|
if (is_default != NULL) {
|
|
*is_default = conf_option_is_default(opt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_get_string(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
const char **str_val,
|
|
bool *is_default)
|
|
{
|
|
const struct conf_value *value;
|
|
int ret;
|
|
|
|
ret = conf_get(conf, section, key, CONF_STRING, &value, is_default);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
*str_val = value->data.string;
|
|
return 0;
|
|
}
|
|
|
|
int conf_get_integer(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
int *int_val,
|
|
bool *is_default)
|
|
{
|
|
const struct conf_value *value;
|
|
int ret;
|
|
|
|
ret = conf_get(conf, section, key, CONF_INTEGER, &value, is_default);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
*int_val = value->data.integer;
|
|
return 0;
|
|
}
|
|
|
|
int conf_get_boolean(struct conf_context *conf,
|
|
const char *section,
|
|
const char *key,
|
|
bool *bool_val,
|
|
bool *is_default)
|
|
{
|
|
const struct conf_value *value;
|
|
int ret;
|
|
|
|
ret = conf_get(conf, section, key, CONF_BOOLEAN, &value, is_default);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
*bool_val = value->data.boolean;
|
|
return 0;
|
|
}
|
|
|
|
void conf_dump(struct conf_context *conf, FILE *fp)
|
|
{
|
|
struct conf_section *s;
|
|
struct conf_option *opt;
|
|
|
|
for (s = conf->section; s != NULL; s = s->next) {
|
|
conf_section_dump(s, fp);
|
|
for (opt = s->option; opt != NULL; opt = opt->next) {
|
|
conf_option_dump(opt, fp);
|
|
}
|
|
}
|
|
}
|