/*
* Copyright (c) 2002-2017 Martin Hedenfalk <martin@bzero.se>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#ifndef _WIN32
# include <pwd.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <ctype.h>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
# ifndef S_ISREG
# define S_ISREG(mode) ((mode) & S_IFREG)
# endif
#endif
#include "compat.h"
#include "confuse.h"
#define is_set(f, x) (((f) & (x)) == (f))
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
# include <locale.h>
# include <libintl.h>
# define _(str) dgettext(PACKAGE, str)
#else
# define _(str) str
#endif
#define N_(str) str
const char confuse_version[] = PACKAGE_VERSION;
const char confuse_copyright[] = PACKAGE_STRING " by Martin Hedenfalk <martin@bzero.se>";
const char confuse_author[] = "Martin Hedenfalk <martin@bzero.se>";
char *cfg_yylval = 0;
extern int cfg_yylex(cfg_t *cfg);
extern void cfg_yylex_destroy(void);
extern int cfg_lexer_include(cfg_t *cfg, const char *fname);
extern void cfg_scan_fp_begin(FILE *fp);
extern void cfg_scan_fp_end(void);
static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t *force_opt);
static void cfg_free_opt_array(cfg_opt_t *opts);
static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp,
cfg_print_filter_func_t fb_pff, int indent);
#define STATE_CONTINUE 0
#define STATE_EOF -1
#define STATE_ERROR 1
#ifndef HAVE_FMEMOPEN
extern FILE *fmemopen(void *buf, size_t size, const char *type);
#endif
#ifndef HAVE_REALLOCARRAY
extern void *reallocarray(void *optr, size_t nmemb, size_t size);
#endif
#ifndef HAVE_STRDUP
/*
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
static char *strdup(const char *str)
{
size_t len;
char *dup;
len = strlen(str) + 1;
dup = calloc(len, sizeof(char));
if (!dup)
return NULL;
memcpy(dup, str, len);
return dup;
}
#endif
#ifndef HAVE_STRNDUP
static char *strndup(const char *s, size_t n)
{
char *r;
r = malloc(n + 1);
if (!r)
return NULL;
strncpy(r, s, n);
r[n] = 0;
return r;
}
#endif
#ifndef HAVE_STRCASECMP
int strcasecmp(const char *s1, const char *s2)
{
assert(s1);
assert(s2);
while (*s1) {
int c1 = tolower(*(const unsigned char *)s1);
int c2 = tolower(*(const unsigned char *)s2);
if (c1 < c2)
return -1;
if (c1 > c2)
return +1;
++s1;
++s2;
}
if (*s2 != 0)
return -1;
return 0;
}
#endif
static cfg_opt_t *cfg_getopt_leaf(cfg_t *cfg, const char *name)
{
unsigned int i;
for (i = 0; cfg->opts && cfg->opts[i].name; i++) {
if (is_set(CFGF_NOCASE, cfg->flags)) {
if (strcasecmp(cfg->opts[i].name, name) == 0)
return &cfg->opts[i];
} else {
if (strcmp(cfg->opts[i].name, name) == 0)
return &cfg->opts[i];
}
}
return NULL;
}
static char *parse_title(const char *name, size_t *len)
{
const char *escapes = "'\\";
char *title;
char *end;
char *ch;
if (*name != '\'') {
*len = strcspn(name, "|");
if (!*len)
return NULL;
return strndup(name, *len);
}
title = strdup(name + 1);
if (!title)
return NULL;
*len = 1;
ch = title;
end = title + strlen(title);
while (ch < end) {
size_t l = strcspn(ch, escapes);
*len += l + 1;
ch += l;
switch (*ch) {
case '\'':
*ch = 0;
return title;
case '\\':
if (!ch[1] || strcspn(ch + 1, escapes)) {
free(title);
return NULL;
}
memmove(ch, ch + 1, strlen(ch));
ch++;
(*len)++;
break;
default:
free(title);
return NULL;
}
}
free(title);
return NULL;
}
static long int cfg_opt_gettsecidx(cfg_opt_t *opt, const char *title)
{
unsigned int i, n;
n = cfg_opt_size(opt);
for (i = 0; i < n; i++) {
cfg_t *sec = cfg_opt_getnsec(opt, i);
if (!sec || !sec->title)
return -1;
if (is_set(CFGF_NOCASE, opt->flags)) {
if (strcasecmp(title, sec->title) == 0)
return i;
} else {
if (strcmp(title, sec->title) == 0)
return i;
}
}
return -1;
}
static cfg_opt_t *cfg_getopt_secidx(cfg_t *cfg, const char *name,
long int *index)
{
cfg_opt_t *opt = NULL;
cfg_t *sec = cfg;
if (!cfg || !cfg->name || !name || !*name) {
errno = EINVAL;
return NULL;
}
while (name && *name) {
char *title = NULL;
long int i = -1;
char *secname;
size_t len;
len = strcspn(name, "|=");
if (!index && name[len] == 0 /*len == strlen(name) */ )
/* no more subsections */
break;
if (!len)
break;
secname = strndup(name, len);
if (!secname)
return NULL;
do {
char *endptr;
opt = cfg_getopt_leaf(sec, secname);
if (!opt || opt->type != CFGT_SEC) {
opt = NULL;
break;
}
if (name[len] != '=') {
/* non-multi, and backwards compat */
i = 0;
break;
}
if (!is_set(CFGF_MULTI, opt->flags))
break;
name += len + 1;
title = parse_title(name, &len);
if (!title)
break;
if (is_set(CFGF_TITLE, opt->flags)) {
i = cfg_opt_gettsecidx(opt, title);
break;
}
i = strtol(title, &endptr, 0);
if (*endptr != '\0')
i = -1;
} while(0);
if (index)
*index = i;
sec = i >= 0 ? cfg_opt_getnsec(opt, i) : NULL;
if (!sec && !is_set(CFGF_IGNORE_UNKNOWN, cfg->flags)) {
if (opt && !is_set(CFGF_MULTI, opt->flags))
cfg_error(cfg, _("no such option '%s'"), secname);
else if (title)
cfg_error(cfg, _("no sub-section '%s' in '%s'"), title, secname);
else
cfg_error(cfg, _("no sub-section title/index for '%s'"), secname);
}
free(secname);
if (title)
free(title);
if (!sec)
return NULL;
name += len;
name += strspn(name, "|");
}
if (!index) {
opt = cfg_getopt_leaf(sec, name);
if (!opt && !is_set(CFGF_IGNORE_UNKNOWN, cfg->flags))
cfg_error(cfg, _("no such option '%s'"), name);
}
return opt;
}
DLLIMPORT cfg_opt_t *cfg_getnopt(cfg_t *cfg, unsigned int index)
{
unsigned int i;
if (!cfg)
return NULL;
for (i = 0; cfg->opts && cfg->opts[i].name; i++) {
if (i == index)
return &cfg->opts[i];
}
return NULL;
}
DLLIMPORT cfg_opt_t *cfg_getopt(cfg_t *cfg, const char *name)
{
return cfg_getopt_secidx(cfg, name, NULL);
}
DLLIMPORT const char *cfg_title(cfg_t *cfg)
{
if (cfg)
return cfg->title;
return NULL;
}
DLLIMPORT const char *cfg_name(cfg_t *cfg)
{
if (cfg)
return cfg->name;
return NULL;
}
DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt)
{
if (opt)
return opt->name;
return NULL;
}
DLLIMPORT const char *cfg_opt_getstr(cfg_opt_t *opt)
{
return cfg_opt_getnstr(opt, 0);
}
DLLIMPORT unsigned int cfg_opt_size(cfg_opt_t *opt)
{
if (opt)
return opt->nvalues;
return 0;
}
DLLIMPORT unsigned int cfg_size(cfg_t *cfg, const char *name)
{
return cfg_opt_size(cfg_getopt(cfg, name));
}
DLLIMPORT char *cfg_opt_getcomment(cfg_opt_t *opt)
{
if (opt)
return opt->comment;
return NULL;
}
DLLIMPORT char *cfg_getcomment(cfg_t *cfg, const char *name)
{
return cfg_opt_getcomment(cfg_getopt(cfg, name));
}
DLLIMPORT signed long cfg_opt_getnint(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_INT) {
errno = EINVAL;
return 0;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->number;
if (opt->simple_value.number)
return *opt->simple_value.number;
return 0;
}
DLLIMPORT signed long cfg_getnint(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnint(cfg_getopt(cfg, name), index);
}
DLLIMPORT signed long cfg_getint(cfg_t *cfg, const char *name)
{
return cfg_getnint(cfg, name, 0);
}
DLLIMPORT double cfg_opt_getnfloat(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_FLOAT) {
errno = EINVAL;
return 0;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->fpnumber;
if (opt->simple_value.fpnumber)
return *opt->simple_value.fpnumber;
return 0;
}
DLLIMPORT double cfg_getnfloat(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnfloat(cfg_getopt(cfg, name), index);
}
DLLIMPORT double cfg_getfloat(cfg_t *cfg, const char *name)
{
return cfg_getnfloat(cfg, name, 0);
}
DLLIMPORT cfg_bool_t cfg_opt_getnbool(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_BOOL) {
errno = EINVAL;
return cfg_false;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->boolean;
if (opt->simple_value.boolean)
return *opt->simple_value.boolean;
return cfg_false;
}
DLLIMPORT cfg_bool_t cfg_getnbool(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnbool(cfg_getopt(cfg, name), index);
}
DLLIMPORT cfg_bool_t cfg_getbool(cfg_t *cfg, const char *name)
{
return cfg_getnbool(cfg, name, 0);
}
DLLIMPORT char *cfg_opt_getnstr(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_STR) {
errno = EINVAL;
return NULL;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->string;
if (opt->simple_value.string)
return *opt->simple_value.string;
return NULL;
}
DLLIMPORT char *cfg_getnstr(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnstr(cfg_getopt(cfg, name), index);
}
DLLIMPORT char *cfg_getstr(cfg_t *cfg, const char *name)
{
return cfg_getnstr(cfg, name, 0);
}
DLLIMPORT void *cfg_opt_getnptr(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_PTR) {
errno = EINVAL;
return NULL;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->ptr;
if (opt->simple_value.ptr)
return *opt->simple_value.ptr;
return NULL;
}
DLLIMPORT void *cfg_getnptr(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnptr(cfg_getopt(cfg, name), index);
}
DLLIMPORT void *cfg_getptr(cfg_t *cfg, const char *name)
{
return cfg_getnptr(cfg, name, 0);
}
DLLIMPORT cfg_t *cfg_opt_getnsec(cfg_opt_t *opt, unsigned int index)
{
if (!opt || opt->type != CFGT_SEC) {
errno = EINVAL;
return NULL;
}
if (opt->values && index < opt->nvalues)
return opt->values[index]->section;
errno = ENOENT;
return NULL;
}
DLLIMPORT cfg_t *cfg_getnsec(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_getnsec(cfg_getopt(cfg, name), index);
}
DLLIMPORT cfg_t *cfg_opt_gettsec(cfg_opt_t *opt, const char *title)
{
long int i;
if (!opt || !title) {
errno = EINVAL;
return NULL;
}
if (!is_set(CFGF_TITLE, opt->flags)) {
errno = EINVAL;
return NULL;
}
i = cfg_opt_gettsecidx(opt, title);
if (i >= 0)
return cfg_opt_getnsec(opt, i);
errno = ENOENT;
return NULL;
}
DLLIMPORT cfg_t *cfg_gettsec(cfg_t *cfg, const char *name, const char *title)
{
return cfg_opt_gettsec(cfg_getopt(cfg, name), title);
}
DLLIMPORT cfg_t *cfg_getsec(cfg_t *cfg, const char *name)
{
cfg_opt_t *opt;
long int index;
opt = cfg_getopt_secidx(cfg, name, &index);
return cfg_opt_getnsec(opt, index);
}
static cfg_value_t *cfg_addval(cfg_opt_t *opt)
{
void *ptr;
ptr = realloc(opt->values, (opt->nvalues + 1) * sizeof(cfg_value_t *));
if (!ptr)
return NULL;
opt->values = ptr;
opt->values[opt->nvalues] = calloc(1, sizeof(cfg_value_t));
if (!opt->values[opt->nvalues])
return NULL;
opt->flags |= CFGF_MODIFIED;
return opt->values[opt->nvalues++];
}
static cfg_opt_t *cfg_addopt(cfg_t *cfg, char *key)
{
int num = cfg_num(cfg);
cfg_opt_t *opts;
opts = reallocarray(cfg->opts, num + 2, sizeof(cfg_opt_t));
if (!opts)
return NULL;
/* Write new opt to previous CFG_END() marker */
cfg->opts = opts;
cfg->opts[num].name = strdup(key);
cfg->opts[num].type = CFGT_STR;
if (!cfg->opts[num].name) {
free(opts);
return NULL;
}
/* Set new CFG_END() */
memset(&cfg->opts[num + 1], 0, sizeof(cfg_opt_t));
return &cfg->opts[num];
}
DLLIMPORT int cfg_numopts(cfg_opt_t *opts)
{
int n;
for (n = 0; opts && opts[n].name; n++)
/* do nothing */ ;
return n;
}
DLLIMPORT unsigned int cfg_num(cfg_t *cfg)
{
if (!cfg)
return 0;
return (unsigned int)cfg_numopts(cfg->opts);
}
static cfg_opt_t *cfg_dupopt_array(cfg_opt_t *opts)
{
int i;
cfg_opt_t *dupopts;
int n = cfg_numopts(opts);
dupopts = calloc(n + 1, sizeof(cfg_opt_t));
if (!dupopts)
return NULL;
memcpy(dupopts, opts, n * sizeof(cfg_opt_t));
for (i = 0; i < n; i++) {
/* Clear dynamic ptrs, protecting the original on failure */
dupopts[i].name = NULL;
dupopts[i].subopts = NULL;
dupopts[i].def.parsed = NULL;
dupopts[i].def.string = NULL;
dupopts[i].comment = NULL;
}
for (i = 0; i < n; i++) {
dupopts[i].name = strdup(opts[i].name);
if (!dupopts[i].name)
goto err;
if (opts[i].subopts) {
dupopts[i].subopts = cfg_dupopt_array(opts[i].subopts);
if (!dupopts[i].subopts)
goto err;
}
if (opts[i].def.parsed) {
dupopts[i].def.parsed = strdup(opts[i].def.parsed);
if (!dupopts[i].def.parsed)
goto err;
}
if (opts[i].def.string) {
dupopts[i].def.string = strdup(opts[i].def.string);
if (!dupopts[i].def.string)
goto err;
}
if (opts[i].comment) {
dupopts[i].comment = strdup(opts[i].comment);
if (!dupopts[i].comment)
goto err;
}
}
return dupopts;
err:
cfg_free_opt_array(dupopts);
return NULL;
}
DLLIMPORT int cfg_parse_boolean(const char *s)
{
if (!s) {
errno = EINVAL;
return CFG_FAIL;
}
if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0)
return 1;
if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0)
return 0;
return CFG_FAIL;
}
static void cfg_init_defaults(cfg_t *cfg)
{
int i;
for (i = 0; cfg->opts && cfg->opts[i].name; i++) {
int j;
for (j = 0; j < i; ++j) {
if (is_set(CFGF_NOCASE, cfg->opts[i].flags | cfg->opts[j].flags)) {
if (strcasecmp(cfg->opts[i].name, cfg->opts[j].name))
continue;
} else {
if (strcmp(cfg->opts[i].name, cfg->opts[j].name))
continue;
}
/*
* There are two definitions of the same option name.
* What to do? It's a programming error and not an end
* user input error. Lets print a message and abort...
*/
cfg_error(cfg, _("duplicate option '%s' not allowed"),
cfg->opts[i].name);
break;
}
/* libConfuse doesn't handle default values for "simple" options */
if (cfg->opts[i].simple_value.ptr || is_set(CFGF_NODEFAULT, cfg->opts[i].flags))
continue;
if (cfg->opts[i].type != CFGT_SEC) {
cfg->opts[i].flags |= CFGF_DEFINIT;
if (is_set(CFGF_LIST, cfg->opts[i].flags) || cfg->opts[i].def.parsed) {
int xstate, ret = 0;
char *buf;
FILE *fp;
/* If it's a list, but no default value was given,
* keep the option uninitialized.
*/
buf = cfg->opts[i].def.parsed;
if (!buf || !buf[0])
continue;
/* setup scanning from the string specified for the
* "default" value, force the correct state and option
*/
if (is_set(CFGF_LIST, cfg->opts[i].flags))
/* lists must be surrounded by {braces} */
xstate = 3;
else if (cfg->opts[i].type == CFGT_FUNC)
xstate = 0;
else
xstate = 2;
fp = fmemopen(buf, strlen(buf), "r");
if (!fp) {
/*
* fmemopen() on older GLIBC versions do not accept zero
* length buffers for some reason. This is a workaround.
*/
if (strlen(buf) > 0)
ret = STATE_ERROR;
} else {
cfg_scan_fp_begin(fp);
do {
ret = cfg_parse_internal(cfg, 1, xstate, &cfg->opts[i]);
xstate = -1;
} while (ret == STATE_CONTINUE);
cfg_scan_fp_end();
fclose(fp);
}
if (ret == STATE_ERROR) {
/*
* If there was an error parsing the default string,
* the initialization of the default value could be
* inconsistent or empty. What to do? It's a
* programming error and not an end user input
* error. Lets print a message and abort...
*/
fprintf(stderr, "Parse error in default value '%s'"
" for option '%s'\n", cfg->opts[i].def.parsed, cfg->opts[i].name);
fprintf(stderr, "Check your initialization macros and the" " libConfuse documentation\n");
abort();
}
} else {
switch (cfg->opts[i].type) {
case CFGT_INT:
cfg_opt_setnint(&cfg->opts[i], cfg->opts[i].def.number, 0);
break;
case CFGT_FLOAT:
cfg_opt_setnfloat(&cfg->opts[i], cfg->opts[i].def.fpnumber, 0);
break;
case CFGT_BOOL:
cfg_opt_setnbool(&cfg->opts[i], cfg->opts[i].def.boolean, 0);
break;
case CFGT_STR:
cfg_opt_setnstr(&cfg->opts[i], cfg->opts[i].def.string, 0);
break;
case CFGT_FUNC:
case CFGT_PTR:
break;
default:
cfg_error(cfg, "internal error in cfg_init_defaults(%s)", cfg->opts[i].name);
break;
}
}
/* The default value should only be returned if no value
* is given in the configuration file, so we set the RESET
* flag here. When/If cfg_setopt() is called, the value(s)
* will be freed and the flag unset.
*/
cfg->opts[i].flags |= CFGF_RESET;
cfg->opts[i].flags &= ~CFGF_MODIFIED;
} else if (!is_set(CFGF_MULTI, cfg->opts[i].flags)) {
cfg_setopt(cfg, &cfg->opts[i], 0);
cfg->opts[i].flags |= CFGF_DEFINIT;
}
}
}
DLLIMPORT cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, const char *value)
{
cfg_value_t *val = NULL;
int b;
const char *s;
double f;
long int i;
void *p;
char *endptr;
if (!cfg || !opt) {
errno = EINVAL;
return NULL;
}
if (opt->simple_value.ptr) {
if (opt->type == CFGT_SEC) {
errno = EINVAL;
return NULL;
}
val = (cfg_value_t *)opt->simple_value.ptr;
} else {
if (is_set(CFGF_RESET, opt->flags)) {
cfg_free_value(opt);
opt->flags &= ~CFGF_RESET;
}
if (opt->nvalues == 0 || is_set(CFGF_MULTI, opt->flags) || is_set(CFGF_LIST, opt->flags)) {
val = NULL;
if (opt->type == CFGT_SEC && is_set(CFGF_TITLE, opt->flags)) {
unsigned int i;
/* XXX: Check if there already is a section with the same title. */
/*
* Check there are either no sections at
* all, or a non-NULL section title.
*/
if (opt->nvalues != 0 && !value) {
errno = EINVAL;
return NULL;
}
for (i = 0; i < opt->nvalues && val == NULL; i++) {
cfg_t *sec = opt->values[i]->section;
if (is_set(CFGF_NOCASE, cfg->flags)) {
if (strcasecmp(value, sec->title) == 0)
val = opt->values[i];
} else {
if (strcmp(value, sec->title) == 0)
val = opt->values[i];
}
}
if (val && is_set(CFGF_NO_TITLE_DUPES, opt->flags)) {
cfg_error(cfg, _("found duplicate title '%s'"), value);
return NULL;
}
}
if (!val) {
val = cfg_addval(opt);
if (!val)
return NULL;
}
} else {
val = opt->values[0];
}
}
switch (opt->type) {
case CFGT_INT:
if (opt->parsecb) {
if ((*opt->parsecb) (cfg, opt, value, &i) != 0)
return NULL;
} else {
if (!value) {
errno = EINVAL;
return NULL;
}
i = strtol(value, &endptr, 0);
if (*endptr != '\0') {
cfg_error(cfg, _("invalid integer value for option '%s'"), opt->name);
return NULL;
}
if (errno == ERANGE) {
cfg_error(cfg, _("integer value for option '%s' is out of range"), opt->name);
return NULL;
}
}
val->number = i;
break;
case CFGT_FLOAT:
if (opt->parsecb) {
if ((*opt->parsecb) (cfg, opt, value, &f) != 0)
return NULL;
} else {
if (!value) {
errno = EINVAL;
return NULL;
}
f = strtod(value, &endptr);
if (*endptr != '\0') {
cfg_error(cfg, _("invalid floating point value for option '%s'"), opt->name);
return NULL;
}
if (errno == ERANGE) {
cfg_error(cfg, _("floating point value for option '%s' is out of range"), opt->name);
return NULL;
}
}
val->fpnumber = f;
break;
case CFGT_STR:
if (opt->parsecb) {
s = NULL;
if ((*opt->parsecb) (cfg, opt, value, &s) != 0)
return NULL;
} else {
s = value;
}
if (!s) {
errno = EINVAL;
return NULL;
}
free(val->string);
val->string = strdup(s);
if (!val->string)
return NULL;
break;
case CFGT_SEC:
if (is_set(CFGF_MULTI, opt->flags) || val->section == 0) {
if (val->section) {
val->section->path = NULL; /* Global search path */
cfg_free(val->section);
}
val->section = calloc(1, sizeof(cfg_t));
if (!val->section)
return NULL;
val->section->name = strdup(opt->name);
if (!val->section->name) {
free(val->section);
return NULL;
}
val->section->flags = cfg->flags;
if (is_set(CFGF_KEYSTRVAL, opt->flags))
val->section->flags |= CFGF_KEYSTRVAL;
val->section->filename = cfg->filename ? strdup(cfg->filename) : NULL;
if (cfg->filename && !val->section->filename) {
free(val->section->name);
free(val->section);
return NULL;
}
val->section->line = cfg->line;
val->section->errfunc = cfg->errfunc;
val->section->title = value ? strdup(value) : NULL;
if (value && !val->section->title) {
free(val->section->filename);
free(val->section->name);
free(val->section);
return NULL;
}
val->section->opts = cfg_dupopt_array(opt->subopts);
if (!val->section->opts) {
if (val->section->title)
free(val->section->title);
if (val->section->filename)
free(val->section->filename);
free(val->section->name);
free(val->section);
return NULL;
}
}
if (!is_set(CFGF_DEFINIT, opt->flags))
cfg_init_defaults(val->section);
break;
case CFGT_BOOL:
if (opt->parsecb) {
if ((*opt->parsecb) (cfg, opt, value, &b) != 0)
return NULL;
} else {
b = cfg_parse_boolean(value);
if (b == -1) {
cfg_error(cfg, _("invalid boolean value for option '%s'"), opt->name);
return NULL;
}
}
val->boolean = (cfg_bool_t)b;
break;
case CFGT_PTR:
if (!opt->parsecb) {
errno = EINVAL;
return NULL;
}
if ((*opt->parsecb) (cfg, opt, value, &p) != 0)
return NULL;
if (val->ptr && opt->freecb)
opt->freecb(val->ptr);
val->ptr = p;
break;
default:
cfg_error(cfg, "internal error in cfg_setopt(%s, %s)", opt->name, (value) ? (value) : "NULL");
return NULL;
}
opt->flags |= CFGF_MODIFIED;
return val;
}
DLLIMPORT int cfg_opt_setmulti(cfg_t *cfg, cfg_opt_t *opt, unsigned int nvalues, char **values)
{
cfg_opt_t old;
unsigned int i;
if (!opt || !nvalues) {
errno = EINVAL;
return CFG_FAIL;
}
old = *opt;
opt->nvalues = 0;
opt->values = 0;
for (i = 0; i < nvalues; i++) {
if (cfg_setopt(cfg, opt, values[i]))
continue;
/* ouch, revert */
cfg_free_value(opt);
opt->nvalues = old.nvalues;
opt->values = old.values;
opt->flags &= ~(CFGF_RESET | CFGF_MODIFIED);
opt->flags |= old.flags & (CFGF_RESET | CFGF_MODIFIED);
return CFG_FAIL;
}
cfg_free_value(&old);
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setmulti(cfg_t *cfg, const char *name, unsigned int nvalues, char **values)
{
cfg_opt_t *opt;
if (!cfg || !name || !values) {
errno = EINVAL;
return CFG_FAIL;
}
opt = cfg_getopt(cfg, name);
if (!opt) {
errno = ENOENT;
return CFG_FAIL;
}
return cfg_opt_setmulti(cfg, opt, nvalues, values);
}
/* searchpath */
struct cfg_searchpath_t {
char *dir; /**< directory to search */
cfg_searchpath_t *next; /**< next in list */
};
/* prepend a new cfg_searchpath_t to the linked list */
DLLIMPORT int cfg_add_searchpath(cfg_t *cfg, const char *dir)
{
cfg_searchpath_t *p;
char *d;
if (!cfg || !dir) {
errno = EINVAL;
return CFG_FAIL;
}
d = cfg_tilde_expand(dir);
if (!d)
return CFG_FAIL;
p = malloc(sizeof(cfg_searchpath_t));
if (!p) {
free(d);
return CFG_FAIL;
}
p->next = cfg->path;
p->dir = d;
cfg->path = p;
return CFG_SUCCESS;
}
DLLIMPORT cfg_errfunc_t cfg_set_error_function(cfg_t *cfg, cfg_errfunc_t errfunc)
{
cfg_errfunc_t old;
if (!cfg) {
errno = EINVAL;
return NULL;
}
old = cfg->errfunc;
cfg->errfunc = errfunc;
return old;
}
DLLIMPORT cfg_print_filter_func_t cfg_set_print_filter_func(cfg_t *cfg, cfg_print_filter_func_t pff)
{
cfg_print_filter_func_t old;
if (!cfg) {
errno = EINVAL;
return NULL;
}
old = cfg->pff;
cfg->pff = pff;
return old;
}
DLLIMPORT void cfg_error(cfg_t *cfg, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (cfg && cfg->errfunc)
(*cfg->errfunc) (cfg, fmt, ap);
else {
if (cfg && cfg->filename && cfg->line)
fprintf(stderr, "%s:%d: ", cfg->filename, cfg->line);
else if (cfg && cfg->filename)
fprintf(stderr, "%s: ", cfg->filename);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
}
va_end(ap);
}
static int call_function(cfg_t *cfg, cfg_opt_t *opt, cfg_opt_t *funcopt)
{
int ret;
const char **argv;
unsigned int i;
if (!cfg || !opt ||!funcopt) {
errno = EINVAL;
return CFG_FAIL;
}
/*
* create am argv string vector and call the registered function
*/
argv = calloc(funcopt->nvalues, sizeof(char *));
if (!argv)
return CFG_FAIL;
for (i = 0; i < funcopt->nvalues; i++)
argv[i] = funcopt->values[i]->string;
ret = (*opt->func) (cfg, opt, funcopt->nvalues, argv);
cfg_free_value(funcopt);
free(argv);
return ret;
}
static void cfg_handle_deprecated(cfg_t *cfg, cfg_opt_t *opt)
{
if (is_set(CFGF_DROP, opt->flags)) {
cfg_error(cfg, _("dropping deprecated configuration option '%s'"), opt->name);
cfg_free_value(opt);
} else {
cfg_error(cfg, _("found deprecated option '%s', please update configuration file."), opt->name);
}
}
static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t *force_opt)
{
int state = 0;
char *comment = NULL;
char *opttitle = NULL;
cfg_opt_t *opt = NULL;
cfg_value_t *val = NULL;
cfg_opt_t funcopt = CFG_STR(0, 0, 0);
int ignore = 0; /* ignore until this token, traverse parser w/o error */
int num_values = 0; /* number of values found for a list option */
int rc;
if (force_state != -1)
state = force_state;
if (force_opt)
opt = force_opt;
while (1) {
int tok = cfg_yylex(cfg);
if (tok == 0) {
/* lexer.l should have called cfg_error() */
goto error;
}
if (tok == EOF) {
if (state != 0) {
cfg_error(cfg, _("premature end of file"));
goto error;
}
if (opt && is_set(CFGF_DEPRECATED, opt->flags))
cfg_handle_deprecated(cfg, opt);
if (comment)
free(comment);
return STATE_EOF;
}
switch (state) {
case 0: /* expecting an option name */
if (opt && is_set(CFGF_DEPRECATED, opt->flags))
cfg_handle_deprecated(cfg, opt);
switch (tok) {
case '}':
if (level == 0) {
cfg_error(cfg, _("unexpected closing brace"));
goto error;
}
if (comment)
free(comment);
return STATE_EOF;
case CFGT_STR:
break;
case CFGT_COMMENT:
if (!is_set(CFGF_COMMENTS, cfg->flags))
continue;
if (comment)
free(comment);
comment = strdup(cfg_yylval);
continue;
default:
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
opt = cfg_getopt(cfg, cfg_yylval);
if (!opt) {
if (is_set(CFGF_IGNORE_UNKNOWN, cfg->flags)) {
state = 10;
break;
}
/* Not found, is it a dynamic key-value section? */
if (is_set(CFGF_KEYSTRVAL, cfg->flags)) {
opt = cfg_addopt(cfg, cfg_yylval);
if (!opt)
goto error;
state = 1;
break;
}
goto error;
}
if (opt->type == CFGT_SEC) {
if (is_set(CFGF_TITLE, opt->flags))
state = 6;
else
state = 5;
} else if (opt->type == CFGT_FUNC) {
state = 7;
} else {
state = 1;
}
break;
case 1: /* expecting an equal sign or plus-equal sign */
if (!opt)
goto error;
if (tok == '+') {
if (!is_set(CFGF_LIST, opt->flags)) {
cfg_error(cfg, _("attempt to append to non-list option '%s'"), opt->name);
goto error;
}
/* Even if the reset flag was set by
* cfg_init_defaults, appending to the defaults
* should be ok.
*/
opt->flags &= ~CFGF_RESET;
} else if (tok == '=') {
/* set the (temporary) reset flag to clear the old
* values, since we obviously didn't want to append */
opt->flags |= CFGF_RESET;
} else {
cfg_error(cfg, _("missing equal sign after option '%s'"), opt->name);
goto error;
}
opt->flags |= CFGF_MODIFIED;
if (is_set(CFGF_LIST, opt->flags)) {
state = 3;
num_values = 0;
} else {
state = 2;
}
break;
case 2: /* expecting an option value */
if (tok == '}' && opt && is_set(CFGF_LIST, opt->flags)) {
state = 0;
if (num_values == 0 && is_set(CFGF_RESET, opt->flags))
/* Reset flags was set, and the empty list was
* specified. Free all old values. */
cfg_free_value(opt);
break;
}
if (tok != CFGT_STR) {
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
if (cfg_setopt(cfg, opt, cfg_yylval) == 0)
goto error;
if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
goto error;
/* Inherit last read comment */
cfg_opt_setcomment(opt, comment);
if (comment)
free(comment);
comment = NULL;
if (opt && is_set(CFGF_LIST, opt->flags)) {
++num_values;
state = 4;
} else {
state = 0;
}
break;
case 3: /* expecting an opening brace for a list option */
if (tok != '{') {
if (tok != CFGT_STR) {
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
if (cfg_setopt(cfg, opt, cfg_yylval) == 0)
goto error;
if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
goto error;
++num_values;
state = 0;
} else {
state = 2;
}
break;
case 4: /* expecting a separator for a list option, or closing (list) brace */
if (tok == ',') {
state = 2;
} else if (tok == '}') {
state = 0;
if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
goto error;
} else {
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
break;
case 5: /* expecting an opening brace for a section */
if (tok != '{') {
cfg_error(cfg, _("missing opening brace for section '%s'"), opt ? opt->name : "");
goto error;
}
val = cfg_setopt(cfg, opt, opttitle);
if (!val)
goto error;
if (opttitle)
free(opttitle);
opttitle = NULL;
val->section->path = cfg->path; /* Remember global search path */
val->section->line = cfg->line;
val->section->errfunc = cfg->errfunc;
rc = cfg_parse_internal(val->section, level + 1, -1, 0);
if (rc != STATE_EOF)
goto error;
cfg->line = val->section->line;
if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0)
goto error;
state = 0;
break;
case 6: /* expecting a title for a section */
if (tok != CFGT_STR) {
cfg_error(cfg, _("missing title for section '%s'"), opt ? opt->name : "");
goto error;
} else {
opttitle = strdup(cfg_yylval);
if (!opttitle)
goto error;
}
state = 5;
break;
case 7: /* expecting an opening parenthesis for a function */
if (tok != '(') {
cfg_error(cfg, _("missing parenthesis for function '%s'"), opt ? opt->name : "");
goto error;
}
state = 8;
break;
case 8: /* expecting a function parameter or a closing paren */
if (tok == ')') {
if (call_function(cfg, opt, &funcopt))
goto error;
state = 0;
} else if (tok == CFGT_STR) {
val = cfg_addval(&funcopt);
if (!val)
goto error;
val->string = strdup(cfg_yylval);
if (!val->string)
goto error;
state = 9;
} else {
cfg_error(cfg, _("syntax error in call of function '%s'"), opt ? opt->name : "");
goto error;
}
break;
case 9: /* expecting a comma in a function or a closing paren */
if (tok == ')') {
if (call_function(cfg, opt, &funcopt))
goto error;
state = 0;
} else if (tok == ',') {
state = 8;
} else {
cfg_error(cfg, _("syntax error in call of function '%s'"), opt ? opt->name : "");
goto error;
}
break;
case 10: /* unknown option, mini-discard parser states: 10-15 */
if (comment) {
free(comment);
comment = NULL;
}
if (tok == '+') {
ignore = '=';
state = 13; /* Append to list, should be followed by '=' */
} else if (tok == '=') {
ignore = 0;
state = 14; /* Assignment, regular handling */
} else if (tok == '(') {
ignore = ')';
state = 13; /* Function, ignore until end of param list */
} else if (tok == '{') {
state = 12; /* Section, ignore all until closing brace */
} else if (tok == CFGT_STR) {
state = 11; /* No '=' ... must be a titled section */
} else if (tok == '}' && force_state == 10) {
if (comment)
free(comment);
return STATE_CONTINUE;
}
break;
case 11: /* unknown option, expecting start of title section */
if (tok != '{') {
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
state = 12;
break;
case 12: /* unknown option, recursively ignore entire sub-section */
rc = cfg_parse_internal(cfg, level + 1, 10, NULL);
if (rc != STATE_CONTINUE)
goto error;
ignore = '}';
state = 13;
break;
case 13: /* unknown option, consume tokens silently until end of func/list */
if (tok != ignore)
break;
if (ignore == '=') {
ignore = 0;
state = 14;
break;
}
/* Are we done with recursive ignore of sub-section? */
if (force_state == 10) {
if (comment)
free(comment);
return STATE_CONTINUE;
}
ignore = 0;
state = 0;
break;
case 14: /* unknown option, assuming value or start of list */
if (tok == '{') {
ignore = '}';
state = 13;
break;
}
if (tok != CFGT_STR) {
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval);
goto error;
}
ignore = 0;
if (force_state == 10)
state = 15;
else
state = 0;
break;
case 15: /* unknown option, dummy read of next parameter in sub-section */
state = 10;
break;
default:
cfg_error(cfg, _("Internal error in cfg_parse_internal(), unknown state %d"), state);
goto error;
}
}
if (comment)
free(comment);
return STATE_EOF;
error:
if (opttitle)
free(opttitle);
if (comment)
free(comment);
return STATE_ERROR;
}
DLLIMPORT int cfg_parse_fp(cfg_t *cfg, FILE *fp)
{
int ret;
if (!cfg || !fp) {
errno = EINVAL;
return CFG_PARSE_ERROR;
}
if (!cfg->filename)
cfg->filename = strdup("FILE");
if (!cfg->filename)
return CFG_PARSE_ERROR;
cfg->line = 1;
cfg_scan_fp_begin(fp);
ret = cfg_parse_internal(cfg, 0, -1, NULL);
cfg_scan_fp_end();
if (ret == STATE_ERROR)
return CFG_PARSE_ERROR;
return CFG_SUCCESS;
}
static char *cfg_make_fullpath(const char *dir, const char *file)
{
int np;
char *path;
size_t len;
if (!dir || !file) {
errno = EINVAL;
return NULL;
}
len = strlen(dir) + strlen(file) + 2;
path = malloc(len);
if (!path)
return NULL;
np = snprintf(path, len, "%s/%s", dir, file);
/*
* np is the number of characters that would have
* been printed if there was enough room in path.
* if np >= n then the snprintf() was truncated
* (which must be a bug).
*/
assert(np < (int)len);
return path;
}
DLLIMPORT char *cfg_searchpath(cfg_searchpath_t *p, const char *file)
{
char *fullpath;
#ifdef HAVE_SYS_STAT_H
struct stat st;
int err;
#endif
if (!p || !file) {
errno = EINVAL;
return NULL;
}
if ((fullpath = cfg_searchpath(p->next, file)) != NULL)
return fullpath;
if ((fullpath = cfg_make_fullpath(p->dir, file)) == NULL)
return NULL;
#ifdef HAVE_SYS_STAT_H
err = stat((const char *)fullpath, &st);
if ((!err) && S_ISREG(st.st_mode))
return fullpath;
#else
/* needs an alternative check here for win32 */
#endif
free(fullpath);
return NULL;
}
DLLIMPORT int cfg_parse(cfg_t *cfg, const char *filename)
{
int ret;
char *fn;
FILE *fp;
if (!cfg || !filename) {
errno = EINVAL;
return CFG_FILE_ERROR;
}
if (cfg->path)
fn = cfg_searchpath(cfg->path, filename);
else
fn = cfg_tilde_expand(filename);
if (!fn)
return CFG_FILE_ERROR;
free(cfg->filename);
cfg->filename = fn;
fp = fopen(cfg->filename, "r");
if (!fp)
return CFG_FILE_ERROR;
ret = cfg_parse_fp(cfg, fp);
fclose(fp);
return ret;
}
DLLIMPORT int cfg_parse_buf(cfg_t *cfg, const char *buf)
{
int ret;
char *fn;
FILE *fp;
if (!cfg) {
errno = EINVAL;
return CFG_PARSE_ERROR;
}
if (!buf)
return CFG_SUCCESS;
fn = strdup("[buf]");
if (!fn)
return CFG_PARSE_ERROR;
free(cfg->filename);
cfg->filename = fn;
fp = fmemopen((void *)buf, strlen(buf), "r");
if (!fp) {
/*
* fmemopen() on older GLIBC versions do not accept zero
* length buffers for some reason. This is a workaround.
*/
if (strlen(buf) > 0)
return CFG_FILE_ERROR;
return CFG_SUCCESS;
}
ret = cfg_parse_fp(cfg, fp);
fclose(fp);
return ret;
}
DLLIMPORT cfg_t *cfg_init(cfg_opt_t *opts, cfg_flag_t flags)
{
cfg_t *cfg;
cfg = calloc(1, sizeof(cfg_t));
if (!cfg)
return NULL;
cfg->name = strdup("root");
if (!cfg->name) {
free(cfg);
return NULL;
}
cfg->opts = cfg_dupopt_array(opts);
if (!cfg->opts) {
free(cfg->name);
free(cfg);
return NULL;
}
cfg->flags = flags;
cfg->filename = 0;
cfg->line = 0;
cfg->errfunc = 0;
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
bindtextdomain(PACKAGE, LOCALEDIR);
#endif
cfg_init_defaults(cfg);
return cfg;
}
DLLIMPORT char *cfg_tilde_expand(const char *filename)
{
char *expanded = 0;
#ifndef _WIN32
/* Do tilde expansion */
if (filename[0] == '~') {
struct passwd *passwd = 0;
const char *file = 0;
if (filename[1] == '/' || filename[1] == 0) {
/* ~ or ~/path */
passwd = getpwuid(geteuid());
file = filename + 1;
} else {
/* ~user or ~user/path */
char *user;
file = strchr(filename, '/');
if (file == 0)
file = filename + strlen(filename);
user = malloc(file - filename);
if (!user)
return NULL;
strncpy(user, filename + 1, file - filename - 1);
passwd = getpwnam(user);
free(user);
}
if (passwd) {
expanded = malloc(strlen(passwd->pw_dir) + strlen(file) + 1);
if (!expanded)
return NULL;
strcpy(expanded, passwd->pw_dir);
strcat(expanded, file);
}
}
#endif
if (!expanded)
expanded = strdup(filename);
return expanded;
}
DLLIMPORT int cfg_free_value(cfg_opt_t *opt)
{
if (!opt) {
errno = EINVAL;
return CFG_FAIL;
}
if (opt->comment && !is_set(CFGF_RESET, opt->flags)) {
free(opt->comment);
opt->comment = NULL;
}
if (opt->values) {
unsigned int i;
for (i = 0; i < opt->nvalues; i++) {
if (opt->type == CFGT_STR) {
free((void *)opt->values[i]->string);
} else if (opt->type == CFGT_SEC) {
opt->values[i]->section->path = NULL; /* Global search path */
cfg_free(opt->values[i]->section);
} else if (opt->type == CFGT_PTR && opt->freecb && opt->values[i]->ptr) {
(opt->freecb) (opt->values[i]->ptr);
}
free(opt->values[i]);
}
free(opt->values);
}
opt->values = NULL;
opt->nvalues = 0;
return CFG_SUCCESS;
}
static void cfg_free_opt_array(cfg_opt_t *opts)
{
int i;
for (i = 0; opts[i].name; ++i) {
free((void *)opts[i].name);
if (opts[i].comment)
free(opts[i].comment);
if (opts[i].def.parsed)
free(opts[i].def.parsed);
if (opts[i].def.string)
free((void *)opts[i].def.string);
if (opts[i].subopts)
cfg_free_opt_array(opts[i].subopts);
}
free(opts);
}
static int cfg_free_searchpath(cfg_searchpath_t *p)
{
if (p) {
cfg_free_searchpath(p->next);
free(p->dir);
free(p);
}
return CFG_SUCCESS;
}
DLLIMPORT int cfg_free(cfg_t *cfg)
{
int i;
int isroot = 0;
if (!cfg) {
errno = EINVAL;
return CFG_FAIL;
}
if (cfg->comment)
free(cfg->comment);
for (i = 0; cfg->opts[i].name; ++i)
cfg_free_value(&cfg->opts[i]);
cfg_free_opt_array(cfg->opts);
cfg_free_searchpath(cfg->path);
if (cfg->name) {
isroot = !strcmp(cfg->name, "root");
free(cfg->name);
}
if (cfg->title)
free(cfg->title);
if (cfg->filename)
free(cfg->filename);
free(cfg);
if (isroot)
cfg_yylex_destroy();
return CFG_SUCCESS;
}
DLLIMPORT int cfg_include(cfg_t *cfg, cfg_opt_t *opt, int argc, const char **argv)
{
(void)opt; /* Unused in this predefined include FUNC */
if (!cfg || !argv) {
errno = EINVAL;
return CFG_FAIL;
}
if (argc != 1) {
cfg_error(cfg, _("wrong number of arguments to cfg_include()"));
return 1;
}
return cfg_lexer_include(cfg, argv[0]);
}
static cfg_value_t *cfg_opt_getval(cfg_opt_t *opt, unsigned int index)
{
cfg_value_t *val = 0;
if (index != 0 && !is_set(CFGF_LIST, opt->flags) && !is_set(CFGF_MULTI, opt->flags)) {
errno = EINVAL;
return NULL;
}
if (opt->simple_value.ptr)
val = (cfg_value_t *)opt->simple_value.ptr;
else {
if (is_set(CFGF_RESET, opt->flags)) {
cfg_free_value(opt);
opt->flags &= ~CFGF_RESET;
}
if (index >= opt->nvalues)
val = cfg_addval(opt);
else
val = opt->values[index];
}
return val;
}
DLLIMPORT int cfg_opt_setcomment(cfg_opt_t *opt, char *comment)
{
char *oldcomment, *newcomment;
if (!opt || !comment) {
errno = EINVAL;
return CFG_FAIL;
}
oldcomment = opt->comment;
newcomment = strdup(comment);
if (!newcomment)
return CFG_FAIL;
if (oldcomment)
free(oldcomment);
opt->comment = newcomment;
opt->flags |= CFGF_COMMENTS;
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setcomment(cfg_t *cfg, const char *name, char *comment)
{
return cfg_opt_setcomment(cfg_getopt(cfg, name), comment);
}
DLLIMPORT int cfg_opt_setnint(cfg_opt_t *opt, long int value, unsigned int index)
{
cfg_value_t *val;
if (!opt || opt->type != CFGT_INT) {
errno = EINVAL;
return CFG_FAIL;
}
val = cfg_opt_getval(opt, index);
if (!val)
return CFG_FAIL;
val->number = value;
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setnint(cfg_t *cfg, const char *name, long int value, unsigned int index)
{
cfg_opt_t *opt;
opt = cfg_getopt(cfg, name);
if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)&value) != 0)
return CFG_FAIL;
return cfg_opt_setnint(opt, value, index);
}
DLLIMPORT int cfg_setint(cfg_t *cfg, const char *name, long int value)
{
return cfg_setnint(cfg, name, value, 0);
}
DLLIMPORT int cfg_opt_setnfloat(cfg_opt_t *opt, double value, unsigned int index)
{
cfg_value_t *val;
if (!opt || opt->type != CFGT_FLOAT) {
errno = EINVAL;
return CFG_FAIL;
}
val = cfg_opt_getval(opt, index);
if (!val)
return CFG_FAIL;
val->fpnumber = value;
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setnfloat(cfg_t *cfg, const char *name, double value, unsigned int index)
{
cfg_opt_t *opt;
opt = cfg_getopt(cfg, name);
if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)&value) != 0)
return CFG_FAIL;
return cfg_opt_setnfloat(opt, value, index);
}
DLLIMPORT int cfg_setfloat(cfg_t *cfg, const char *name, double value)
{
return cfg_setnfloat(cfg, name, value, 0);
}
DLLIMPORT int cfg_opt_setnbool(cfg_opt_t *opt, cfg_bool_t value, unsigned int index)
{
cfg_value_t *val;
if (!opt || opt->type != CFGT_BOOL) {
errno = EINVAL;
return CFG_FAIL;
}
val = cfg_opt_getval(opt, index);
if (!val)
return CFG_FAIL;
val->boolean = value;
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setnbool(cfg_t *cfg, const char *name, cfg_bool_t value, unsigned int index)
{
return cfg_opt_setnbool(cfg_getopt(cfg, name), value, index);
}
DLLIMPORT int cfg_setbool(cfg_t *cfg, const char *name, cfg_bool_t value)
{
return cfg_setnbool(cfg, name, value, 0);
}
DLLIMPORT int cfg_opt_setnstr(cfg_opt_t *opt, const char *value, unsigned int index)
{
char *newstr, *oldstr = NULL;
cfg_value_t *val;
if (!opt || opt->type != CFGT_STR) {
errno = EINVAL;
return CFG_FAIL;
}
val = cfg_opt_getval(opt, index);
if (!val)
return CFG_FAIL;
if (val->string)
oldstr = val->string;
if (value) {
newstr = strdup(value);
if (!newstr)
return CFG_FAIL;
val->string = newstr;
} else {
val->string = NULL;
}
if (oldstr)
free(oldstr);
opt->flags |= CFGF_MODIFIED;
return CFG_SUCCESS;
}
DLLIMPORT int cfg_setnstr(cfg_t *cfg, const char *name, const char *value, unsigned int index)
{
cfg_opt_t *opt;
opt = cfg_getopt(cfg, name);
if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)value) != 0)
return CFG_FAIL;
return cfg_opt_setnstr(opt, value, index);
}
DLLIMPORT int cfg_setstr(cfg_t *cfg, const char *name, const char *value)
{
return cfg_setnstr(cfg, name, value, 0);
}
static int cfg_addlist_internal(cfg_opt_t *opt, unsigned int nvalues, va_list ap)
{
int result = CFG_FAIL;
unsigned int i;
for (i = 0; i < nvalues; i++) {
switch (opt->type) {
case CFGT_INT:
result = cfg_opt_setnint(opt, va_arg(ap, int), opt->nvalues);
break;
case CFGT_FLOAT:
result = cfg_opt_setnfloat(opt, va_arg(ap, double), opt->nvalues);
break;
case CFGT_BOOL:
result = cfg_opt_setnbool(opt, va_arg(ap, cfg_bool_t), opt->nvalues);
break;
case CFGT_STR:
result = cfg_opt_setnstr(opt, va_arg(ap, char *), opt->nvalues);
break;
case CFGT_FUNC:
case CFGT_SEC:
default:
result = CFG_SUCCESS;
break;
}
}
return result;
}
DLLIMPORT int cfg_setlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...)
{
va_list ap;
cfg_opt_t *opt = cfg_getopt(cfg, name);
if (!opt || !is_set(CFGF_LIST, opt->flags)) {
errno = EINVAL;
return CFG_FAIL;
}
cfg_free_value(opt);
va_start(ap, nvalues);
cfg_addlist_internal(opt, nvalues, ap);
va_end(ap);
return CFG_SUCCESS;
}
DLLIMPORT int cfg_addlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...)
{
va_list ap;
cfg_opt_t *opt = cfg_getopt(cfg, name);
if (!opt || !is_set(CFGF_LIST, opt->flags)) {
errno = EINVAL;
return CFG_FAIL;
}
va_start(ap, nvalues);
cfg_addlist_internal(opt, nvalues, ap);
va_end(ap);
return CFG_SUCCESS;
}
DLLIMPORT cfg_t *cfg_addtsec(cfg_t *cfg, const char *name, const char *title)
{
cfg_opt_t *opt;
cfg_value_t *val;
if (cfg_gettsec(cfg, name, title))
return NULL;
opt = cfg_getopt(cfg, name);
if (!opt) {
cfg_error(cfg, _("no such option '%s'"), name);
return NULL;
}
val = cfg_setopt(cfg, opt, title);
if (!val)
return NULL;
val->section->path = cfg->path; /* Remember global search path. */
val->section->line = 1;
val->section->errfunc = cfg->errfunc;
return val->section;
}
DLLIMPORT int cfg_opt_rmnsec(cfg_opt_t *opt, unsigned int index)
{
unsigned int n;
cfg_value_t *val;
if (!opt || opt->type != CFGT_SEC) {
errno = EINVAL;
return CFG_FAIL;
}
n = cfg_opt_size(opt);
if (index >= n)
return CFG_FAIL;
val = cfg_opt_getval(opt, index);
if (!val)
return CFG_FAIL;
if (index + 1 != n) {
/* not removing last, move the tail */
memmove(&opt->values[index], &opt->values[index + 1], sizeof(opt->values[index]) * (n - index - 1));
}
--opt->nvalues;
cfg_free(val->section);
free(val);
return CFG_SUCCESS;
}
DLLIMPORT int cfg_rmnsec(cfg_t *cfg, const char *name, unsigned int index)
{
return cfg_opt_rmnsec(cfg_getopt(cfg, name), index);
}
DLLIMPORT int cfg_rmsec(cfg_t *cfg, const char *name)
{
cfg_opt_t *opt;
long int index;
opt = cfg_getopt_secidx(cfg, name, &index);
return cfg_opt_rmnsec(opt, index);
}
DLLIMPORT int cfg_opt_rmtsec(cfg_opt_t *opt, const char *title)
{
unsigned int i, n;
if (!opt || !title) {
errno = EINVAL;
return CFG_FAIL;
}
if (!is_set(CFGF_TITLE, opt->flags))
return CFG_FAIL;
n = cfg_opt_size(opt);
for (i = 0; i < n; i++) {
cfg_t *sec = cfg_opt_getnsec(opt, i);
if (!sec || !sec->title)
return CFG_FAIL;
if (is_set(CFGF_NOCASE, opt->flags)) {
if (strcasecmp(title, sec->title) == 0)
break;
} else {
if (strcmp(title, sec->title) == 0)
break;
}
}
if (i == n)
return CFG_FAIL;
return cfg_opt_rmnsec(opt, i);
}
DLLIMPORT int cfg_rmtsec(cfg_t *cfg, const char *name, const char *title)
{
return cfg_opt_rmtsec(cfg_getopt(cfg, name), title);
}
DLLIMPORT int cfg_opt_nprint_var(cfg_opt_t *opt, unsigned int index, FILE *fp)
{
const char *str;
if (!opt || !fp) {
errno = EINVAL;
return CFG_FAIL;
}
switch (opt->type) {
case CFGT_INT:
fprintf(fp, "%ld", cfg_opt_getnint(opt, index));
break;
case CFGT_FLOAT:
fprintf(fp, "%f", cfg_opt_getnfloat(opt, index));
break;
case CFGT_STR:
str = cfg_opt_getnstr(opt, index);
fprintf(fp, "\"");
while (str && *str) {
if (*str == '"')
fprintf(fp, "\\\"");
else if (*str == '\\')
fprintf(fp, "\\\\");
else
fprintf(fp, "%c", *str);
str++;
}
fprintf(fp, "\"");
break;
case CFGT_BOOL:
fprintf(fp, "%s", cfg_opt_getnbool(opt, index) ? "true" : "false");
break;
case CFGT_NONE:
case CFGT_SEC:
case CFGT_FUNC:
case CFGT_PTR:
case CFGT_COMMENT:
break;
}
return CFG_SUCCESS;
}
static void cfg_indent(FILE *fp, int indent)
{
while (indent--)
fprintf(fp, " ");
}
static int cfg_opt_print_pff_indent(cfg_opt_t *opt, FILE *fp,
cfg_print_filter_func_t pff, int indent)
{
if (!opt || !fp) {
errno = EINVAL;
return CFG_FAIL;
}
if (is_set(CFGF_COMMENTS, opt->flags) && opt->comment) {
cfg_indent(fp, indent);
fprintf(fp, "/* %s */\n", opt->comment);
}
if (opt->type == CFGT_SEC) {
cfg_t *sec;
unsigned int i;
for (i = 0; i < cfg_opt_size(opt); i++) {
sec = cfg_opt_getnsec(opt, i);
cfg_indent(fp, indent);
if (is_set(CFGF_TITLE, opt->flags))
fprintf(fp, "%s \"%s\" {\n", opt->name, cfg_title(sec));
else
fprintf(fp, "%s {\n", opt->name);
cfg_print_pff_indent(sec, fp, pff, indent + 1);
cfg_indent(fp, indent);
fprintf(fp, "}\n");
}
} else if (opt->type != CFGT_FUNC && opt->type != CFGT_NONE) {
if (is_set(CFGF_LIST, opt->flags)) {
cfg_indent(fp, indent);
fprintf(fp, "%s = {", opt->name);
if (opt->nvalues) {
unsigned int i;
if (opt->pf)
opt->pf(opt, 0, fp);
else
cfg_opt_nprint_var(opt, 0, fp);
for (i = 1; i < opt->nvalues; i++) {
fprintf(fp, ", ");
if (opt->pf)
opt->pf(opt, i, fp);
else
cfg_opt_nprint_var(opt, i, fp);
}
}
fprintf(fp, "}");
} else {
cfg_indent(fp, indent);
/* comment out the option if is not set */
if (cfg_opt_size(opt) == 0 ||
(opt->type == CFGT_STR && !cfg_opt_getnstr(opt, 0)))
fprintf(fp, "# ");
fprintf(fp, "%s=", opt->name);
if (opt->pf)
opt->pf(opt, 0, fp);
else
cfg_opt_nprint_var(opt, 0, fp);
}
fprintf(fp, "\n");
} else if (opt->pf) {
cfg_indent(fp, indent);
opt->pf(opt, 0, fp);
fprintf(fp, "\n");
}
return CFG_SUCCESS;
}
DLLIMPORT int cfg_opt_print_indent(cfg_opt_t *opt, FILE *fp, int indent)
{
return cfg_opt_print_pff_indent(opt, fp, 0, indent);
}
DLLIMPORT int cfg_opt_print(cfg_opt_t *opt, FILE *fp)
{
return cfg_opt_print_pff_indent(opt, fp, 0, 0);
}
static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp,
cfg_print_filter_func_t fb_pff, int indent)
{
int i, result = CFG_SUCCESS;
for (i = 0; cfg->opts[i].name; i++) {
cfg_print_filter_func_t pff = cfg->pff ? cfg->pff : fb_pff;
if (pff && pff(cfg, &cfg->opts[i]))
continue;
result += cfg_opt_print_pff_indent(&cfg->opts[i], fp, pff, indent);
}
return result;
}
DLLIMPORT int cfg_print_indent(cfg_t *cfg, FILE *fp, int indent)
{
return cfg_print_pff_indent(cfg, fp, 0, indent);
}
DLLIMPORT int cfg_print(cfg_t *cfg, FILE *fp)
{
return cfg_print_pff_indent(cfg, fp, 0, 0);
}
DLLIMPORT cfg_print_func_t cfg_opt_set_print_func(cfg_opt_t *opt, cfg_print_func_t pf)
{
cfg_print_func_t oldpf;
if (!opt) {
errno = EINVAL;
return NULL;
}
oldpf = opt->pf;
opt->pf = pf;
return oldpf;
}
DLLIMPORT cfg_print_func_t cfg_set_print_func(cfg_t *cfg, const char *name, cfg_print_func_t pf)
{
return cfg_opt_set_print_func(cfg_getopt(cfg, name), pf);
}
static cfg_opt_t *cfg_getopt_array(cfg_opt_t *rootopts, int cfg_flags, const char *name)
{
unsigned int i;
cfg_opt_t *opts = rootopts;
if (!rootopts || !name) {
errno = EINVAL;
return NULL;
}
while (name && *name) {
cfg_t *seccfg;
char *secname;
size_t len = strcspn(name, "|");
if (name[len] == 0 /*len == strlen(name) */ )
/* no more subsections */
break;
if (len) {
cfg_opt_t *secopt;
secname = strndup(name, len);
if (!secname)
return NULL;
secopt = cfg_getopt_array(opts, cfg_flags, secname);
free(secname);
if (!secopt) {
/*fprintf(stderr, "section not found\n"); */
return NULL;
}
if (secopt->type != CFGT_SEC) {
/*fprintf(stderr, "not a section!\n"); */
return NULL;
}
if (!is_set(CFGF_MULTI, secopt->flags) && (seccfg = cfg_opt_getnsec(secopt, 0)) != 0)
opts = seccfg->opts;
else
opts = secopt->subopts;
if (!opts) {
/*fprintf(stderr, "section have no subopts!?\n"); */
return NULL;
}
}
name += len;
name += strspn(name, "|");
}
for (i = 0; opts[i].name; i++) {
if (is_set(CFGF_NOCASE, cfg_flags)) {
if (strcasecmp(opts[i].name, name) == 0)
return &opts[i];
} else {
if (strcmp(opts[i].name, name) == 0)
return &opts[i];
}
}
return NULL;
}
DLLIMPORT cfg_validate_callback_t cfg_set_validate_func(cfg_t *cfg, const char *name, cfg_validate_callback_t vf)
{
cfg_opt_t *opt;
cfg_validate_callback_t oldvf;
opt = cfg_getopt_array(cfg->opts, cfg->flags, name);
if (!opt)
return NULL;
oldvf = opt->validcb;
opt->validcb = vf;
return oldvf;
}
DLLIMPORT cfg_validate_callback2_t cfg_set_validate_func2(cfg_t *cfg, const char *name, cfg_validate_callback2_t vf)
{
cfg_opt_t *opt;
cfg_validate_callback2_t oldvf;
opt = cfg_getopt_array(cfg->opts, cfg->flags, name);
if (!opt)
return NULL;
oldvf = opt->validcb2;
opt->validcb2 = vf;
return oldvf;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>