/
niceSOFT
/
libconfuse
Обзор
Документация
Войти
/
niceSOFT
/
libconfuse
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/confuse.c
3 586 строк
76 KB
Joachim Wiberg
Add optional JSON-style [ ] lists, CFGF_JSON_LISTS
31 июл 2026, 23:49
Не верифицирован
31 июл 2026, 23:49
333aaa6
Код
Авторство
О чём код?
/* * 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> #include <limits.h> #include <inttypes.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 = NULL; 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); extern void cfg_scan_list_begin(void); extern void cfg_scan_list_end(void); extern void cfg_raw_begin(void); extern char *cfg_raw_end(void); extern int cfg_raw_active(void); extern size_t cfg_raw_tell(void); extern void cfg_raw_seek(size_t pos); 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, int json); #define STATE_CONTINUE 0 #define STATE_EOF -1 #define STATE_ERROR 1 /* Guard against stack exhaustion from deeply/infinitely nested sections, * similar in spirit to MAX_INCLUDE_DEPTH in lexer.l for include() nesting. */ #define CFG_MAX_NESTING_DEPTH 500 #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, unsigned 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->type != CFGT_RAWSEC)) { 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; } errno = 0; i = strtoul(title, &endptr, 0); if (*endptr != '\0' || errno || i > UINT_MAX) i = -1; } while(0); if (index) *index = i >= 0 ? i : UINT_MAX; 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) && !is_set(CFGF_KEYSTRVAL, sec->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_getraw(cfg_t *cfg) { if (cfg) return cfg->raw; 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 int64_t cfg_opt_getnint64(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_INT64) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->i64; if (opt->simple_value.i64) return *opt->simple_value.i64; return 0; } DLLIMPORT int64_t cfg_getnint64(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnint64(cfg_getopt(cfg, name), index); } DLLIMPORT int64_t cfg_getint64(cfg_t *cfg, const char *name) { return cfg_getnint64(cfg, name, 0); } DLLIMPORT uint32_t cfg_opt_getnuint32(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_UINT32) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->u32; if (opt->simple_value.u32) return *opt->simple_value.u32; return 0; } DLLIMPORT uint32_t cfg_getnuint32(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnuint32(cfg_getopt(cfg, name), index); } DLLIMPORT uint32_t cfg_getuint32(cfg_t *cfg, const char *name) { return cfg_getnuint32(cfg, name, 0); } DLLIMPORT uint64_t cfg_opt_getnuint64(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_UINT64) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->u64; if (opt->simple_value.u64) return *opt->simple_value.u64; return 0; } DLLIMPORT uint64_t cfg_getnuint64(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnuint64(cfg_getopt(cfg, name), index); } DLLIMPORT uint64_t cfg_getuint64(cfg_t *cfg, const char *name) { return cfg_getnuint64(cfg, name, 0); } DLLIMPORT int8_t cfg_opt_getnint8(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_INT8) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->i8; if (opt->simple_value.i8) return *opt->simple_value.i8; return 0; } DLLIMPORT int8_t cfg_getnint8(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnint8(cfg_getopt(cfg, name), index); } DLLIMPORT int8_t cfg_getint8(cfg_t *cfg, const char *name) { return cfg_getnint8(cfg, name, 0); } DLLIMPORT int16_t cfg_opt_getnint16(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_INT16) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->i16; if (opt->simple_value.i16) return *opt->simple_value.i16; return 0; } DLLIMPORT int16_t cfg_getnint16(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnint16(cfg_getopt(cfg, name), index); } DLLIMPORT int16_t cfg_getint16(cfg_t *cfg, const char *name) { return cfg_getnint16(cfg, name, 0); } DLLIMPORT int32_t cfg_opt_getnint32(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_INT32) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->i32; if (opt->simple_value.i32) return *opt->simple_value.i32; return 0; } DLLIMPORT int32_t cfg_getnint32(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnint32(cfg_getopt(cfg, name), index); } DLLIMPORT int32_t cfg_getint32(cfg_t *cfg, const char *name) { return cfg_getnint32(cfg, name, 0); } DLLIMPORT uint8_t cfg_opt_getnuint8(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_UINT8) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->u8; if (opt->simple_value.u8) return *opt->simple_value.u8; return 0; } DLLIMPORT uint8_t cfg_getnuint8(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnuint8(cfg_getopt(cfg, name), index); } DLLIMPORT uint8_t cfg_getuint8(cfg_t *cfg, const char *name) { return cfg_getnuint8(cfg, name, 0); } DLLIMPORT uint16_t cfg_opt_getnuint16(cfg_opt_t *opt, unsigned int index) { if (!opt || opt->type != CFGT_UINT16) { errno = EINVAL; return 0; } if (opt->values && index < opt->nvalues) return opt->values[index]->u16; if (opt->simple_value.u16) return *opt->simple_value.u16; return 0; } DLLIMPORT uint16_t cfg_getnuint16(cfg_t *cfg, const char *name, unsigned int index) { return cfg_opt_getnuint16(cfg_getopt(cfg, name), index); } DLLIMPORT uint16_t cfg_getuint16(cfg_t *cfg, const char *name) { return cfg_getnuint16(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 && opt->type != CFGT_RAWSEC)) { 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; unsigned 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; if (n) 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].type != CFGT_RAWSEC) { 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_INT8: cfg_opt_setnint8(&cfg->opts[i], (int8_t)cfg->opts[i].def.number, 0); break; case CFGT_INT16: cfg_opt_setnint16(&cfg->opts[i], (int16_t)cfg->opts[i].def.number, 0); break; case CFGT_INT32: cfg_opt_setnint32(&cfg->opts[i], (int32_t)cfg->opts[i].def.number, 0); break; case CFGT_INT64: cfg_opt_setnint64(&cfg->opts[i], cfg->opts[i].def.number, 0); break; case CFGT_UINT8: cfg_opt_setnuint8(&cfg->opts[i], (uint8_t)cfg->opts[i].def.number, 0); break; case CFGT_UINT16: cfg_opt_setnuint16(&cfg->opts[i], (uint16_t)cfg->opts[i].def.number, 0); break; case CFGT_UINT32: cfg_opt_setnuint32(&cfg->opts[i], (uint32_t)cfg->opts[i].def.number, 0); break; case CFGT_UINT64: cfg_opt_setnuint64(&cfg->opts[i], (uint64_t)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], NULL); cfg->opts[i].flags |= CFGF_DEFINIT; } } } static cfg_opt_t rawsec_no_opts[] = { CFG_END() }; /* Append an include() function to a section's (already duplicated) option * array, so that CFGF_USE_INCLUDE_FUNCTION sections gain include support. */ static int cfg_section_add_include(cfg_t *sec) { int num = cfg_numopts(sec->opts); cfg_opt_t *opts; opts = reallocarray(sec->opts, num + 2, sizeof(cfg_opt_t)); if (!opts) return CFG_FAIL; sec->opts = opts; memset(&sec->opts[num], 0, sizeof(cfg_opt_t)); sec->opts[num].name = strdup("include"); if (!sec->opts[num].name) return CFG_FAIL; sec->opts[num].type = CFGT_FUNC; sec->opts[num].func = &cfg_include; memset(&sec->opts[num + 1], 0, sizeof(cfg_opt_t)); /* new CFG_END() */ return CFG_SUCCESS; } /* * Detect an integer radix prefix, like strtol() base 0 but also handling * binary '0b' (see issue #145). Points *digits at the first digit after * any prefix and returns the radix. */ static int cfg_int_radix(const char *value, const char **digits) { int radix = 0; *digits = value; if (value[0] == '0') { switch (value[1]) { case 'b': radix = 2; *digits = &value[2]; break; case 'x': radix = 16; *digits = &value[2]; break; default: radix = 8; *digits = &value[1]; } } return radix; } DLLIMPORT cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, const char *value) { cfg_value_t *val = NULL; const char *s; char *endptr; long int i; int64_t i64; uint64_t u64; double f; void *p; int b; 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 || opt->type == CFGT_RAWSEC) && 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 { const char *str; int radix; if (!value) { errno = EINVAL; return NULL; } radix = cfg_int_radix(value, &str); i = strtol(str, &endptr, radix); 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_INT8: case CFGT_INT16: case CFGT_INT32: case CFGT_INT64: if (opt->parsecb) { if ((*opt->parsecb) (cfg, opt, value, &i64) != 0) return NULL; } else { const char *str; int radix; if (!value) { errno = EINVAL; return NULL; } radix = cfg_int_radix(value, &str); errno = 0; i64 = strtoll(str, &endptr, radix); 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; } } { int64_t lo = INT64_MIN, hi = INT64_MAX; switch (opt->type) { case CFGT_INT8: lo = INT8_MIN; hi = INT8_MAX; break; case CFGT_INT16: lo = INT16_MIN; hi = INT16_MAX; break; case CFGT_INT32: lo = INT32_MIN; hi = INT32_MAX; break; default: break; } if (i64 < lo || i64 > hi) { cfg_error(cfg, _("integer value for option '%s' is out of range"), opt->name); return NULL; } switch (opt->type) { case CFGT_INT8: val->i8 = (int8_t)i64; break; case CFGT_INT16: val->i16 = (int16_t)i64; break; case CFGT_INT32: val->i32 = (int32_t)i64; break; default: val->i64 = i64; break; } } break; case CFGT_UINT8: case CFGT_UINT16: case CFGT_UINT32: case CFGT_UINT64: if (opt->parsecb) { if ((*opt->parsecb) (cfg, opt, value, &u64) != 0) return NULL; } else { const char *str; int radix; if (!value) { errno = EINVAL; return NULL; } if (value[0] == '-') { cfg_error(cfg, _("negative value for unsigned option '%s'"), opt->name); return NULL; } radix = cfg_int_radix(value, &str); errno = 0; u64 = strtoull(str, &endptr, radix); 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; } } { uint64_t max = UINT64_MAX; switch (opt->type) { case CFGT_UINT8: max = UINT8_MAX; break; case CFGT_UINT16: max = UINT16_MAX; break; case CFGT_UINT32: max = UINT32_MAX; break; default: break; } if (u64 > max) { cfg_error(cfg, _("integer value for option '%s' is out of range"), opt->name); return NULL; } switch (opt->type) { case CFGT_UINT8: val->u8 = (uint8_t)u64; break; case CFGT_UINT16: val->u16 = (uint16_t)u64; break; case CFGT_UINT32: val->u32 = (uint32_t)u64; break; default: val->u64 = u64; break; } } 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 == NULL) { 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_USE_INCLUDE_FUNCTION, opt->flags) && cfg_section_add_include(val->section) != CFG_SUCCESS) return NULL; } if (!is_set(CFGF_DEFINIT, opt->flags)) cfg_init_defaults(val->section); break; case CFGT_RAWSEC: if (is_set(CFGF_MULTI, opt->flags) || val->section == NULL) { 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; } /* The body is walked by the mini discard parser, so it must ignore. */ val->section->flags = cfg->flags | CFGF_IGNORE_UNKNOWN; 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; } /* A raw section carries no sub-options, unless it opts in * to include() via CFGF_USE_INCLUDE_FUNCTION. */ val->section->opts = cfg_dupopt_array(rawsec_no_opts); if (!val->section->opts) { free(val->section->title); free(val->section->filename); free(val->section->name); free(val->section); return NULL; } if (is_set(CFGF_USE_INCLUDE_FUNCTION, opt->flags) && cfg_section_add_include(val->section) != CFG_SUCCESS) return NULL; } 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 = NULL; 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); } /** An entry in the configuration include search path, see cfg_add_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(NULL, NULL, 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 list_close = '}'; /* closing token expected for the current list, '}' or ']' */ int rc; size_t rawmark = 0; /* capture offset before a func, for include() expansion */ if (force_state != -1) state = force_state; if (force_opt) opt = force_opt; if (level > CFG_MAX_NESTING_DEPTH) { cfg_error(cfg, _("too deeply nested sections (max %d), " "bailing out to avoid stack exhaustion"), CFG_MAX_NESTING_DEPTH); goto error; } 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; } /* * A comment may appear between any two tokens, see issue #150. * Handle it once here (capture when CFGF_COMMENTS is set, else * skip); the unknown-option discard states (>= 10) do their own. */ if (tok == CFGT_COMMENT && state < 10) { if (is_set(CFGF_COMMENTS, cfg->flags)) { if (comment) free(comment); comment = strdup(cfg_yylval); } continue; } 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; 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 || opt->type == CFGT_RAWSEC) { if (is_set(CFGF_TITLE, opt->flags)) state = 6; else state = 5; } else if (opt->type == CFGT_FUNC) { if (cfg_raw_active()) rawmark = cfg_raw_tell() - strlen(cfg_yylval); 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; /* opener read in list mode so '[' is a token; * every exit from states 2-4 must cfg_scan_list_end() */ if (is_set(CFGF_JSON_LISTS, cfg->flags)) cfg_scan_list_begin(); } else { state = 2; } break; case 2: /* expecting an option value */ if (opt && is_set(CFGF_LIST, opt->flags) && tok == list_close) { cfg_scan_list_end(); 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) == NULL) 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 == '[') { /* JSON list: keep scanning in list mode */ list_close = ']'; state = 2; break; } cfg_scan_list_end(); /* a { } body or bare value scans as usual */ if (tok == '{') { list_close = '}'; state = 2; } else { if (tok != CFGT_STR) { cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); goto error; } if (cfg_setopt(cfg, opt, cfg_yylval) == NULL) goto error; if (opt && opt->validcb && (*opt->validcb) (cfg, opt) != 0) goto error; ++num_values; state = 0; } break; case 4: /* expecting a separator for a list option, or closing (list) brace */ if (tok == ',') { state = 2; } else if (tok == list_close) { cfg_scan_list_end(); 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; if (opt->type == CFGT_RAWSEC) cfg_raw_begin(); rc = cfg_parse_internal(val->section, level + 1, -1, NULL); if (rc != STATE_EOF) { if (opt->type == CFGT_RAWSEC) cfg_raw_end(); goto error; } if (opt->type == CFGT_RAWSEC) { free(val->section->raw); val->section->raw = strdup(cfg_raw_end()); if (!val->section->raw) 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; /* Replace an include() call * by its expanded content * in the raw capture*/ if (cfg_raw_active() && opt && opt->func == cfg_include) cfg_raw_seek(rawmark); 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; /* Replace an include() call * by its expanded content * in the raw capture*/ if (cfg_raw_active() && opt && opt->func == cfg_include) cfg_raw_seek(rawmark); 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 == '}') { if (comment) free(comment); return force_state >= 10 ? STATE_CONTINUE : STATE_EOF; } 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 */ if (tok == '}') { state = force_state >= 10 ? 15 : 0; break; } else if (tok == CFGT_COMMENT) { state = 15; rc = cfg_parse_internal(cfg, level + 1, 15, NULL); if (rc != STATE_CONTINUE) goto error; break; } else { rc = cfg_parse_internal(cfg, level + 1, 10, NULL); if (rc != STATE_CONTINUE) goto error; state = force_state >= 10 ? 15 : 0; } break; case 13: /* unknown option, consume tokens silently until end of func/list */ if (tok != ignore) break; if (ignore == '=') { ignore = 0; state = 14; break; } ignore = 0; state = force_state >= 10 ? 15 : 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; state = force_state >= 10 ? 15 : 0; break; case 15: /* unknown option, dummy read of next parameter in sub-section */ if (tok == CFGT_COMMENT) continue; else if (tok == '}') return force_state >= 10 ? STATE_CONTINUE : STATE_EOF; 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: cfg_scan_list_end(); /* reset scanner if we bailed mid-list */ 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 (file[0] == '/') { fullpath = strdup(file); if (!fullpath) return NULL; goto check; } if ((fullpath = cfg_searchpath(p->next, file)) != NULL) return fullpath; if ((fullpath = cfg_make_fullpath(p->dir, file)) == NULL) return NULL; check: #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; FILE *fp; if (!cfg) { errno = EINVAL; return CFG_PARSE_ERROR; } if (!buf) return CFG_SUCCESS; if (!cfg->filename) cfg->filename = strdup("[buf]"); if (!cfg->filename) return CFG_PARSE_ERROR; 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 = NULL; cfg->line = 0; cfg->errfunc = NULL; #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 = NULL; #ifndef _WIN32 /* Do tilde expansion */ if (filename[0] == '~') { struct passwd *passwd = NULL; const char *file = NULL; if (filename[1] == '/' || filename[1] == 0) { /* ~ or ~/path */ passwd = getpwuid(geteuid()); file = filename + 1; } else { char *user; /* ~user or ~user/path */ size_t len; file = strchr(filename, '/'); if (file == NULL) file = filename + strlen(filename); len = file - filename - 1; user = malloc(len + 1); if (!user) return NULL; strncpy(user, &filename[1], len); user[len] = 0; 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->type == CFGT_RAWSEC) { 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); if (cfg->raw) free(cfg->raw); 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 = NULL; 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_setnint64(cfg_opt_t *opt, int64_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_INT64) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->i64 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnint64(cfg_t *cfg, const char *name, int64_t 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_setnint64(opt, value, index); } DLLIMPORT int cfg_setint64(cfg_t *cfg, const char *name, int64_t value) { return cfg_setnint64(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnuint32(cfg_opt_t *opt, uint32_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_UINT32) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->u32 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnuint32(cfg_t *cfg, const char *name, uint32_t 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_setnuint32(opt, value, index); } DLLIMPORT int cfg_setuint32(cfg_t *cfg, const char *name, uint32_t value) { return cfg_setnuint32(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnuint64(cfg_opt_t *opt, uint64_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_UINT64) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->u64 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnuint64(cfg_t *cfg, const char *name, uint64_t 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_setnuint64(opt, value, index); } DLLIMPORT int cfg_setuint64(cfg_t *cfg, const char *name, uint64_t value) { return cfg_setnuint64(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnint8(cfg_opt_t *opt, int8_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_INT8) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->i8 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnint8(cfg_t *cfg, const char *name, int8_t 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_setnint8(opt, value, index); } DLLIMPORT int cfg_setint8(cfg_t *cfg, const char *name, int8_t value) { return cfg_setnint8(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnint16(cfg_opt_t *opt, int16_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_INT16) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->i16 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnint16(cfg_t *cfg, const char *name, int16_t 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_setnint16(opt, value, index); } DLLIMPORT int cfg_setint16(cfg_t *cfg, const char *name, int16_t value) { return cfg_setnint16(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnint32(cfg_opt_t *opt, int32_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_INT32) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->i32 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnint32(cfg_t *cfg, const char *name, int32_t 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_setnint32(opt, value, index); } DLLIMPORT int cfg_setint32(cfg_t *cfg, const char *name, int32_t value) { return cfg_setnint32(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnuint8(cfg_opt_t *opt, uint8_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_UINT8) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->u8 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnuint8(cfg_t *cfg, const char *name, uint8_t 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_setnuint8(opt, value, index); } DLLIMPORT int cfg_setuint8(cfg_t *cfg, const char *name, uint8_t value) { return cfg_setnuint8(cfg, name, value, 0); } DLLIMPORT int cfg_opt_setnuint16(cfg_opt_t *opt, uint16_t value, unsigned int index) { cfg_value_t *val; if (!opt || opt->type != CFGT_UINT16) { errno = EINVAL; return CFG_FAIL; } val = cfg_opt_getval(opt, index); if (!val) return CFG_FAIL; val->u16 = value; opt->flags |= CFGF_MODIFIED; return CFG_SUCCESS; } DLLIMPORT int cfg_setnuint16(cfg_t *cfg, const char *name, uint16_t 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_setnuint16(opt, value, index); } DLLIMPORT int cfg_setuint16(cfg_t *cfg, const char *name, uint16_t value) { return cfg_setnuint16(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_INT8: result = cfg_opt_setnint8(opt, (int8_t)va_arg(ap, int), opt->nvalues); break; case CFGT_INT16: result = cfg_opt_setnint16(opt, (int16_t)va_arg(ap, int), opt->nvalues); break; case CFGT_INT32: result = cfg_opt_setnint32(opt, va_arg(ap, int32_t), opt->nvalues); break; case CFGT_INT64: result = cfg_opt_setnint64(opt, va_arg(ap, int64_t), opt->nvalues); break; case CFGT_UINT8: result = cfg_opt_setnuint8(opt, (uint8_t)va_arg(ap, int), opt->nvalues); break; case CFGT_UINT16: result = cfg_opt_setnuint16(opt, (uint16_t)va_arg(ap, int), opt->nvalues); break; case CFGT_UINT32: result = cfg_opt_setnuint32(opt, va_arg(ap, uint32_t), opt->nvalues); break; case CFGT_UINT64: result = cfg_opt_setnuint64(opt, va_arg(ap, uint64_t), 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; } opt->flags &= ~CFGF_RESET; 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_t *sec; cfg_opt_t *opt; cfg_value_t *val; sec = cfg_gettsec(cfg, name, title); if (sec) return sec; 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; unsigned 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_INT8: fprintf(fp, "%" PRId8, cfg_opt_getnint8(opt, index)); break; case CFGT_INT16: fprintf(fp, "%" PRId16, cfg_opt_getnint16(opt, index)); break; case CFGT_INT32: fprintf(fp, "%" PRId32, cfg_opt_getnint32(opt, index)); break; case CFGT_INT64: fprintf(fp, "%" PRId64, cfg_opt_getnint64(opt, index)); break; case CFGT_UINT8: fprintf(fp, "%" PRIu8, cfg_opt_getnuint8(opt, index)); break; case CFGT_UINT16: fprintf(fp, "%" PRIu16, cfg_opt_getnuint16(opt, index)); break; case CFGT_UINT32: fprintf(fp, "%" PRIu32, cfg_opt_getnuint32(opt, index)); break; case CFGT_UINT64: fprintf(fp, "%" PRIu64, cfg_opt_getnuint64(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_RAWSEC: 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, int json) { 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, json); cfg_indent(fp, indent); fprintf(fp, "}\n"); } } else if (opt->type == CFGT_RAWSEC) { 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\" {%s}\n", opt->name, cfg_title(sec), sec->raw ? sec->raw : ""); else fprintf(fp, "%s {%s}\n", opt->name, sec->raw ? sec->raw : ""); } } else if (opt->type != CFGT_FUNC && opt->type != CFGT_NONE) { if (is_set(CFGF_LIST, opt->flags)) { cfg_indent(fp, indent); fprintf(fp, "%s = %c", opt->name, json ? '[' : '{'); 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, "%c", json ? ']' : '}'); } 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, NULL, indent, 0); } DLLIMPORT int cfg_opt_print(cfg_opt_t *opt, FILE *fp) { return cfg_opt_print_pff_indent(opt, fp, NULL, 0, 0); } static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp, cfg_print_filter_func_t fb_pff, int indent, int json) { 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, json); } return result; } DLLIMPORT int cfg_print_indent(cfg_t *cfg, FILE *fp, int indent) { return cfg_print_pff_indent(cfg, fp, NULL, indent, is_set(CFGF_JSON_LISTS, cfg->flags)); } DLLIMPORT int cfg_print(cfg_t *cfg, FILE *fp) { return cfg_print_pff_indent(cfg, fp, NULL, 0, is_set(CFGF_JSON_LISTS, cfg->flags)); } 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)) != NULL) 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: */