version 1.1.1.1, 2017/01/24 14:48:56
|
version 1.1.1.2, 2021/03/17 00:49:17
|
Line 1
|
Line 1
|
/* |
/* |
* Copyright (c) 2002,2003,2007 Martin Hedenfalk <martin@bzero.se> | * Copyright (c) 2002-2017 Martin Hedenfalk <martin@bzero.se> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and/or distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
* copyright notice and this permission notice appear in all copies. |
* copyright notice and this permission notice appear in all copies. |
* |
* |
Line 18
|
Line 18
|
# include <config.h> |
# include <config.h> |
#endif |
#endif |
|
|
#define _GNU_SOURCE |
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <string.h> |
#include <string.h> |
|
#ifdef HAVE_STRINGS_H |
|
# include <strings.h> |
|
#endif |
#include <stdlib.h> |
#include <stdlib.h> |
#include <assert.h> |
#include <assert.h> |
#include <errno.h> |
#include <errno.h> |
Line 32
|
Line 34
|
#endif |
#endif |
#include <ctype.h> |
#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" |
#include "confuse.h" |
|
|
#define is_set(f, x) (((f) & (x)) == (f)) |
#define is_set(f, x) (((f) & (x)) == (f)) |
Line 45
|
Line 55
|
#endif |
#endif |
#define N_(str) str |
#define N_(str) str |
|
|
extern FILE *cfg_yyin; | const char confuse_version[] = PACKAGE_VERSION; |
extern int cfg_yylex(cfg_t *cfg); | const char confuse_copyright[] = PACKAGE_STRING " by Martin Hedenfalk <martin@bzero.se>"; |
extern int cfg_lexer_include(cfg_t *cfg, const char *fname); | const char confuse_author[] = "Martin Hedenfalk <martin@bzero.se>"; |
extern void cfg_scan_string_begin(const char *buf); | |
extern void cfg_scan_string_end(void); | |
extern void cfg_scan_fp_begin(FILE *fp); | |
extern void cfg_scan_fp_end(void); | |
extern char *cfg_qstring; | |
|
|
char *cfg_yylval = 0; |
char *cfg_yylval = 0; |
|
|
const char confuse_version[] = PACKAGE_VERSION; | extern int cfg_yylex(cfg_t *cfg); |
const char confuse_copyright[] = PACKAGE_STRING" by Martin Hedenfalk <martin@bzero.se>"; | extern void cfg_yylex_destroy(void); |
const char confuse_author[] = "Martin Hedenfalk <martin@bzero.se>"; | 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, | static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t *force_opt); |
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_CONTINUE 0 |
#define STATE_EOF -1 |
#define STATE_EOF -1 |
#define STATE_ERROR 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 |
#ifndef HAVE_STRDUP |
# ifdef HAVE__STRDUP | /* |
# define strdup _strdup | * Copyright (c) 1988, 1993 |
# else | * The Regents of the University of California. All rights reserved. |
static char *strdup(const char *s) | * |
| * 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) |
{ |
{ |
char *r; | size_t len; |
| char *dup; |
|
|
if(s == 0 || *s == 0) | len = strlen(str) + 1; |
return 0; | dup = calloc(len, sizeof(char)); |
| if (!dup) |
| return NULL; |
|
|
r = malloc(strlen(s) + 1); | memcpy(dup, str, len); |
assert(r); | |
strcpy(r, s); | return dup; |
return r; | |
} |
} |
# endif |
|
#endif |
#endif |
|
|
#ifndef HAVE_STRNDUP |
#ifndef HAVE_STRNDUP |
static char *strndup(const char *s, size_t n) |
static char *strndup(const char *s, size_t n) |
{ |
{ |
char *r; | char *r; |
|
|
if(s == 0) | r = malloc(n + 1); |
return 0; | if (!r) |
| return NULL; |
|
|
r = malloc(n + 1); | strncpy(r, s, n); |
assert(r); | r[n] = 0; |
strncpy(r, s, n); | |
r[n] = 0; | return r; |
return r; | |
} |
} |
#endif |
#endif |
|
|
#ifndef HAVE_STRCASECMP |
#ifndef HAVE_STRCASECMP |
int strcasecmp(const char *s1, const char *s2) |
int strcasecmp(const char *s1, const char *s2) |
{ |
{ |
assert(s1); | assert(s1); |
assert(s2); | assert(s2); |
|
|
while(*s1) | while (*s1) { |
{ | int c1 = tolower(*(const unsigned char *)s1); |
int c1 = tolower(*(const unsigned char *)s1); | int c2 = tolower(*(const unsigned char *)s2); |
int c2 = tolower(*(const unsigned char *)s2); | |
if(c1 < c2) | |
return -1; | |
if(c1 > c2) | |
return +1; | |
|
|
++s1; | if (c1 < c2) |
++s2; | return -1; |
} | if (c1 > c2) |
| return +1; |
|
|
if(*s2 != 0) | ++s1; |
return -1; | ++s2; |
| } |
|
|
return 0; | if (*s2 != 0) |
| return -1; |
| |
| return 0; |
} |
} |
#endif |
#endif |
|
|
DLLIMPORT cfg_opt_t *cfg_getopt(cfg_t *cfg, const char *name) | static cfg_opt_t *cfg_getopt_leaf(cfg_t *cfg, const char *name) |
{ |
{ |
unsigned int i; | unsigned int i; |
cfg_t *sec = cfg; | |
|
|
assert(cfg && cfg->name && name); | 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]; |
| } |
| } |
|
|
while(name && *name) | return NULL; |
{ | } |
char *secname; | |
size_t len = strcspn(name, "|"); | |
if(name[len] == 0 /*len == strlen(name)*/) | |
/* no more subsections */ | |
break; | |
if(len) | |
{ | |
secname = strndup(name, len); | |
sec = cfg_getsec(sec, secname); | |
if(sec == 0) | |
cfg_error(cfg, _("no such option '%s'"), secname); | |
free(secname); | |
if(sec == 0) | |
return 0; | |
} | |
name += len; | |
name += strspn(name, "|"); | |
} | |
|
|
for(i = 0; sec->opts[i].name; i++) | static char *parse_title(const char *name, size_t *len) |
{ | { |
if(is_set(CFGF_NOCASE, sec->flags)) | const char *escapes = "'\\"; |
{ | char *title; |
if(strcasecmp(sec->opts[i].name, name) == 0) | char *end; |
return &sec->opts[i]; | char *ch; |
} | |
else | if (*name != '\'') { |
{ | *len = strcspn(name, "|"); |
if(strcmp(sec->opts[i].name, name) == 0) | if (!*len) |
return &sec->opts[i]; | return NULL; |
} | return strndup(name, *len); |
} | } |
cfg_error(cfg, _("no such option '%s'"), name); | |
return 0; | 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) |
DLLIMPORT const char *cfg_title(cfg_t *cfg) |
{ |
{ |
if(cfg) | if (cfg) |
return cfg->title; | return cfg->title; |
return 0; | return NULL; |
} |
} |
|
|
DLLIMPORT const char *cfg_name(cfg_t *cfg) |
DLLIMPORT const char *cfg_name(cfg_t *cfg) |
{ |
{ |
if(cfg) | if (cfg) |
return cfg->name; | return cfg->name; |
return 0; | return NULL; |
} |
} |
|
|
DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt) |
DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt) |
{ |
{ |
if(opt) | if (opt) |
return opt->name; | return opt->name; |
return 0; | 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) |
DLLIMPORT unsigned int cfg_opt_size(cfg_opt_t *opt) |
{ |
{ |
if(opt) | if (opt) |
return opt->nvalues; | return opt->nvalues; |
return 0; | return 0; |
} |
} |
|
|
DLLIMPORT unsigned int cfg_size(cfg_t *cfg, const char *name) |
DLLIMPORT unsigned int cfg_size(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_opt_size(cfg_getopt(cfg, 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) |
DLLIMPORT signed long cfg_opt_getnint(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_INT); | if (!opt || opt->type != CFGT_INT) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->number; | return 0; |
else if(opt->simple_value) | } |
return *(signed long *)opt->simple_value; | |
else | if (opt->values && index < opt->nvalues) |
return 0; | 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, | DLLIMPORT signed long cfg_getnint(cfg_t *cfg, const char *name, unsigned int index) |
unsigned int index) | |
{ |
{ |
return cfg_opt_getnint(cfg_getopt(cfg, name), index); | return cfg_opt_getnint(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT signed long cfg_getint(cfg_t *cfg, const char *name) |
DLLIMPORT signed long cfg_getint(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnint(cfg, name, 0); | return cfg_getnint(cfg, name, 0); |
} |
} |
|
|
DLLIMPORT double cfg_opt_getnfloat(cfg_opt_t *opt, unsigned int index) |
DLLIMPORT double cfg_opt_getnfloat(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_FLOAT); | if (!opt || opt->type != CFGT_FLOAT) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->fpnumber; | return 0; |
else if(opt->simple_value) | } |
return *(double *)opt->simple_value; | |
else | if (opt->values && index < opt->nvalues) |
return 0; | 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, | DLLIMPORT double cfg_getnfloat(cfg_t *cfg, const char *name, unsigned int index) |
unsigned int index) | |
{ |
{ |
return cfg_opt_getnfloat(cfg_getopt(cfg, name), index); | return cfg_opt_getnfloat(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT double cfg_getfloat(cfg_t *cfg, const char *name) |
DLLIMPORT double cfg_getfloat(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnfloat(cfg, name, 0); | return cfg_getnfloat(cfg, name, 0); |
} |
} |
|
|
DLLIMPORT cfg_bool_t cfg_opt_getnbool(cfg_opt_t *opt, unsigned int index) |
DLLIMPORT cfg_bool_t cfg_opt_getnbool(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_BOOL); | if (!opt || opt->type != CFGT_BOOL) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->boolean; | return cfg_false; |
else if(opt->simple_value) | } |
return *(cfg_bool_t *)opt->simple_value; | |
else | if (opt->values && index < opt->nvalues) |
return cfg_false; | 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, | DLLIMPORT cfg_bool_t cfg_getnbool(cfg_t *cfg, const char *name, unsigned int index) |
unsigned int index) | |
{ |
{ |
return cfg_opt_getnbool(cfg_getopt(cfg, name), index); | return cfg_opt_getnbool(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT cfg_bool_t cfg_getbool(cfg_t *cfg, const char *name) |
DLLIMPORT cfg_bool_t cfg_getbool(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnbool(cfg, name, 0); | return cfg_getnbool(cfg, name, 0); |
} |
} |
|
|
DLLIMPORT char *cfg_opt_getnstr(cfg_opt_t *opt, unsigned int index) |
DLLIMPORT char *cfg_opt_getnstr(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_STR); | if (!opt || opt->type != CFGT_STR) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->string; | return NULL; |
else if(opt->simple_value) | } |
return *(char **)opt->simple_value; | |
else | if (opt->values && index < opt->nvalues) |
return 0; | 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) |
DLLIMPORT char *cfg_getnstr(cfg_t *cfg, const char *name, unsigned int index) |
{ |
{ |
return cfg_opt_getnstr(cfg_getopt(cfg, name), index); | return cfg_opt_getnstr(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT char *cfg_getstr(cfg_t *cfg, const char *name) |
DLLIMPORT char *cfg_getstr(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnstr(cfg, name, 0); | return cfg_getnstr(cfg, name, 0); |
} |
} |
|
|
DLLIMPORT void *cfg_opt_getnptr(cfg_opt_t *opt, unsigned int index) |
DLLIMPORT void *cfg_opt_getnptr(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_PTR); | if (!opt || opt->type != CFGT_PTR) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->ptr; | return NULL; |
else if(opt->simple_value) | } |
return *(void **)opt->simple_value; | |
else | if (opt->values && index < opt->nvalues) |
return 0; | 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) |
DLLIMPORT void *cfg_getnptr(cfg_t *cfg, const char *name, unsigned int index) |
{ |
{ |
return cfg_opt_getnptr(cfg_getopt(cfg, name), index); | return cfg_opt_getnptr(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT void *cfg_getptr(cfg_t *cfg, const char *name) |
DLLIMPORT void *cfg_getptr(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnptr(cfg, name, 0); | return cfg_getnptr(cfg, name, 0); |
} |
} |
|
|
DLLIMPORT cfg_t *cfg_opt_getnsec(cfg_opt_t *opt, unsigned int index) |
DLLIMPORT cfg_t *cfg_opt_getnsec(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
assert(opt && opt->type == CFGT_SEC); | if (!opt || opt->type != CFGT_SEC) { |
if(opt->values && index < opt->nvalues) | errno = EINVAL; |
return opt->values[index]->section; | return NULL; |
return 0; | } |
| |
| 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) |
DLLIMPORT cfg_t *cfg_getnsec(cfg_t *cfg, const char *name, unsigned int index) |
{ |
{ |
return cfg_opt_getnsec(cfg_getopt(cfg, name), index); | return cfg_opt_getnsec(cfg_getopt(cfg, name), index); |
} |
} |
|
|
DLLIMPORT cfg_t *cfg_opt_gettsec(cfg_opt_t *opt, const char *title) |
DLLIMPORT cfg_t *cfg_opt_gettsec(cfg_opt_t *opt, const char *title) |
{ |
{ |
unsigned int i, n; | long int i; |
|
|
assert(opt && title); | if (!opt || !title) { |
if(!is_set(CFGF_TITLE, opt->flags)) | errno = EINVAL; |
return 0; | return NULL; |
n = cfg_opt_size(opt); | } |
for(i = 0; i < n; i++) | |
{ | if (!is_set(CFGF_TITLE, opt->flags)) { |
cfg_t *sec = cfg_opt_getnsec(opt, i); | errno = EINVAL; |
assert(sec && sec->title); | return NULL; |
if(is_set(CFGF_NOCASE, opt->flags)) | } |
{ | |
if(strcasecmp(title, sec->title) == 0) | i = cfg_opt_gettsecidx(opt, title); |
return sec; | if (i >= 0) |
} | return cfg_opt_getnsec(opt, i); |
else | |
{ | errno = ENOENT; |
if(strcmp(title, sec->title) == 0) | return NULL; |
return sec; | |
} | |
} | |
return 0; | |
} |
} |
|
|
DLLIMPORT cfg_t *cfg_gettsec(cfg_t *cfg, const char *name, const char *title) |
DLLIMPORT cfg_t *cfg_gettsec(cfg_t *cfg, const char *name, const char *title) |
{ |
{ |
return cfg_opt_gettsec(cfg_getopt(cfg, name), title); | return cfg_opt_gettsec(cfg_getopt(cfg, name), title); |
} |
} |
|
|
DLLIMPORT cfg_t *cfg_getsec(cfg_t *cfg, const char *name) |
DLLIMPORT cfg_t *cfg_getsec(cfg_t *cfg, const char *name) |
{ |
{ |
return cfg_getnsec(cfg, name, 0); | 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) |
static cfg_value_t *cfg_addval(cfg_opt_t *opt) |
{ |
{ |
opt->values = realloc(opt->values, | void *ptr; |
(opt->nvalues+1) * sizeof(cfg_value_t *)); | |
assert(opt->values); | ptr = realloc(opt->values, (opt->nvalues + 1) * sizeof(cfg_value_t *)); |
opt->values[opt->nvalues] = malloc(sizeof(cfg_value_t)); | if (!ptr) |
memset(opt->values[opt->nvalues], 0, sizeof(cfg_value_t)); | return NULL; |
return opt->values[opt->nvalues++]; | |
| 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) |
DLLIMPORT int cfg_numopts(cfg_opt_t *opts) |
{ |
{ |
int n; | int n; |
|
|
for(n = 0; opts[n].name; n++) | for (n = 0; opts && opts[n].name; n++) |
/* do nothing */ ; | /* do nothing */ ; |
return n; | 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) |
static cfg_opt_t *cfg_dupopt_array(cfg_opt_t *opts) |
{ |
{ |
int i; | int i; |
cfg_opt_t *dupopts; | cfg_opt_t *dupopts; |
int n = cfg_numopts(opts); | int n = cfg_numopts(opts); |
|
|
dupopts = calloc(n+1, sizeof(cfg_opt_t)); | dupopts = calloc(n + 1, sizeof(cfg_opt_t)); |
memcpy(dupopts, opts, n * sizeof(cfg_opt_t)); | if (!dupopts) |
| return NULL; |
|
|
for(i = 0; i < n; i++) | memcpy(dupopts, opts, n * sizeof(cfg_opt_t)); |
{ | |
dupopts[i].name = strdup(opts[i].name); | |
if(opts[i].type == CFGT_SEC && opts[i].subopts) | |
dupopts[i].subopts = cfg_dupopt_array(opts[i].subopts); | |
|
|
if(is_set(CFGF_LIST, opts[i].flags) || opts[i].type == CFGT_FUNC) | for (i = 0; i < n; i++) { |
dupopts[i].def.parsed = opts[i].def.parsed ? strdup(opts[i].def.parsed) : 0; | /* Clear dynamic ptrs, protecting the original on failure */ |
else if(opts[i].type == CFGT_STR) | dupopts[i].name = NULL; |
dupopts[i].def.string = opts[i].def.string ? strdup(opts[i].def.string) : 0; | dupopts[i].subopts = NULL; |
} | dupopts[i].def.parsed = NULL; |
| dupopts[i].def.string = NULL; |
| dupopts[i].comment = NULL; |
| } |
|
|
return dupopts; | 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) |
DLLIMPORT int cfg_parse_boolean(const char *s) |
{ |
{ |
if(strcasecmp(s, "true") == 0 | if (!s) { |
|| strcasecmp(s, "on") == 0 | errno = EINVAL; |
|| strcasecmp(s, "yes") == 0) | return CFG_FAIL; |
return 1; | } |
else if(strcasecmp(s, "false") == 0 | |
|| strcasecmp(s, "off") == 0 | if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0) |
|| strcasecmp(s, "no") == 0) | return 1; |
return 0; | if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0) |
return -1; | return 0; |
| |
| return CFG_FAIL; |
} |
} |
|
|
static void cfg_init_defaults(cfg_t *cfg) |
static void cfg_init_defaults(cfg_t *cfg) |
{ |
{ |
int i; | int i; |
|
|
for(i = 0; cfg->opts[i].name; i++) | for (i = 0; cfg->opts && cfg->opts[i].name; i++) { |
{ | int j; |
/* libConfuse doesn't handle default values for "simple" options */ | |
if(cfg->opts[i].simple_value || is_set(CFGF_NODEFAULT, cfg->opts[i].flags)) | |
continue; | |
|
|
if(cfg->opts[i].type != CFGT_SEC) | for (j = 0; j < i; ++j) { |
{ | if (is_set(CFGF_NOCASE, cfg->opts[i].flags | cfg->opts[j].flags)) { |
cfg->opts[i].flags |= CFGF_DEFINIT; | 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; |
| } |
|
|
if(is_set(CFGF_LIST, cfg->opts[i].flags) || | /* libConfuse doesn't handle default values for "simple" options */ |
cfg->opts[i].def.parsed) | if (cfg->opts[i].simple_value.ptr || is_set(CFGF_NODEFAULT, cfg->opts[i].flags)) |
{ | continue; |
int xstate, ret; | |
|
|
/* If it's a list, but no default value was given, | if (cfg->opts[i].type != CFGT_SEC) { |
* keep the option uninitialized. | cfg->opts[i].flags |= CFGF_DEFINIT; |
*/ | |
if(cfg->opts[i].def.parsed == 0 || | |
cfg->opts[i].def.parsed[0] == 0) | |
continue; | |
|
|
/* setup scanning from the string specified for the | if (is_set(CFGF_LIST, cfg->opts[i].flags) || cfg->opts[i].def.parsed) { |
* "default" value, force the correct state and option | int xstate, ret = 0; |
*/ | char *buf; |
| FILE *fp; |
|
|
if(is_set(CFGF_LIST, cfg->opts[i].flags)) | /* If it's a list, but no default value was given, |
/* lists must be surrounded by {braces} */ | * keep the option uninitialized. |
xstate = 3; | */ |
else if(cfg->opts[i].type == CFGT_FUNC) | buf = cfg->opts[i].def.parsed; |
xstate = 0; | if (!buf || !buf[0]) |
else | continue; |
xstate = 2; | |
|
|
cfg_scan_string_begin(cfg->opts[i].def.parsed); | /* setup scanning from the string specified for the |
do | * "default" value, force the correct state and option |
{ | */ |
ret = cfg_parse_internal(cfg, 1, xstate, &cfg->opts[i]); | |
xstate = -1; | |
} while(ret == STATE_CONTINUE); | |
cfg_scan_string_end(); | |
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 | if (is_set(CFGF_LIST, cfg->opts[i].flags)) |
* is given in the configuration file, so we set the RESET | /* lists must be surrounded by {braces} */ |
* flag here. When/If cfg_setopt() is called, the value(s) | xstate = 3; |
* will be freed and the flag unset. | else if (cfg->opts[i].type == CFGT_FUNC) |
*/ | xstate = 0; |
cfg->opts[i].flags |= CFGF_RESET; | else |
} /* end if cfg->opts[i].type != CFGT_SEC */ | xstate = 2; |
else if(!is_set(CFGF_MULTI, cfg->opts[i].flags)) | |
{ | fp = fmemopen(buf, strlen(buf), "r"); |
cfg_setopt(cfg, &cfg->opts[i], 0); | if (!fp) { |
cfg->opts[i].flags |= CFGF_DEFINIT; | /* |
} | * 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 * | DLLIMPORT cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, const char *value) |
cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, char *value) | |
{ |
{ |
cfg_value_t *val = 0; | cfg_value_t *val = NULL; |
int b; | int b; |
char *s; | const char *s; |
double f; | double f; |
long int i; | long int i; |
void *p; | void *p; |
char *endptr; | char *endptr; |
|
|
assert(cfg && opt); | if (!cfg || !opt) { |
| errno = EINVAL; |
| return NULL; |
| } |
|
|
if(opt->simple_value) | if (opt->simple_value.ptr) { |
{ | if (opt->type == CFGT_SEC) { |
assert(opt->type != CFGT_SEC); | errno = EINVAL; |
val = (cfg_value_t *)opt->simple_value; | return NULL; |
} | } |
else | val = (cfg_value_t *)opt->simple_value.ptr; |
{ | } else { |
if(is_set(CFGF_RESET, opt->flags)) | if (is_set(CFGF_RESET, opt->flags)) { |
{ | cfg_free_value(opt); |
cfg_free_value(opt); | opt->flags &= ~CFGF_RESET; |
opt->flags &= ~CFGF_RESET; | } |
} | |
|
|
if(opt->nvalues == 0 || is_set(CFGF_MULTI, opt->flags) || | if (opt->nvalues == 0 || is_set(CFGF_MULTI, opt->flags) || is_set(CFGF_LIST, opt->flags)) { |
is_set(CFGF_LIST, opt->flags)) | val = NULL; |
{ | |
val = 0; | |
if(opt->type == CFGT_SEC && is_set(CFGF_TITLE, opt->flags)) | |
{ | |
unsigned int i; | |
|
|
/* Check if there already is a section with the same title. | if (opt->type == CFGT_SEC && is_set(CFGF_TITLE, opt->flags)) { |
*/ | unsigned int i; |
|
|
/* Assert that there are either no sections at all, or a | /* XXX: Check if there already is a section with the same title. */ |
* non-NULL section title. */ | |
assert(opt->nvalues == 0 || value); | |
|
|
for(i = 0; i < opt->nvalues && val == NULL; i++) | /* |
{ | * Check there are either no sections at |
cfg_t *sec = opt->values[i]->section; | * all, or a non-NULL section title. |
if(is_set(CFGF_NOCASE, cfg->flags)) | */ |
{ | if (opt->nvalues != 0 && !value) { |
if(strcasecmp(value, sec->title) == 0) | errno = EINVAL; |
val = opt->values[i]; | return NULL; |
} | } |
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 0; | |
} | |
} | |
if(val == NULL) | |
val = cfg_addval(opt); | |
} | |
else | |
val = opt->values[0]; | |
} | |
|
|
switch(opt->type) | for (i = 0; i < opt->nvalues && val == NULL; i++) { |
{ | cfg_t *sec = opt->values[i]->section; |
case CFGT_INT: | |
if(opt->parsecb) | |
{ | |
if((*opt->parsecb)(cfg, opt, value, &i) != 0) | |
return 0; | |
val->number = i; | |
} | |
else | |
{ | |
val->number = strtol(value, &endptr, 0); | |
if(*endptr != '\0') | |
{ | |
cfg_error(cfg, _("invalid integer value for option '%s'"), | |
opt->name); | |
return 0; | |
} | |
if(errno == ERANGE) | |
{ | |
cfg_error(cfg, | |
_("integer value for option '%s' is out of range"), | |
opt->name); | |
return 0; | |
} | |
} | |
break; | |
|
|
case CFGT_FLOAT: | if (is_set(CFGF_NOCASE, cfg->flags)) { |
if(opt->parsecb) | if (strcasecmp(value, sec->title) == 0) |
{ | val = opt->values[i]; |
if((*opt->parsecb)(cfg, opt, value, &f) != 0) | } else { |
return 0; | if (strcmp(value, sec->title) == 0) |
val->fpnumber = f; | val = opt->values[i]; |
} | } |
else | } |
{ | |
val->fpnumber = strtod(value, &endptr); | |
if(*endptr != '\0') | |
{ | |
cfg_error(cfg, | |
_("invalid floating point value for option '%s'"), | |
opt->name); | |
return 0; | |
} | |
if(errno == ERANGE) | |
{ | |
cfg_error(cfg, | |
_("floating point value for option '%s' is out of range"), | |
opt->name); | |
return 0; | |
} | |
} | |
break; | |
|
|
case CFGT_STR: | if (val && is_set(CFGF_NO_TITLE_DUPES, opt->flags)) { |
free(val->string); | cfg_error(cfg, _("found duplicate title '%s'"), value); |
if(opt->parsecb) | return NULL; |
{ | } |
s = 0; | } |
if((*opt->parsecb)(cfg, opt, value, &s) != 0) | |
return 0; | |
val->string = strdup(s); | |
} | |
else | |
val->string = strdup(value); | |
break; | |
|
|
case CFGT_SEC: | if (!val) { |
if(is_set(CFGF_MULTI, opt->flags) || val->section == 0) | val = cfg_addval(opt); |
{ | if (!val) |
cfg_free(val->section); | return NULL; |
val->section = calloc(1, sizeof(cfg_t)); | } |
assert(val->section); | } else { |
val->section->name = strdup(opt->name); | val = opt->values[0]; |
val->section->opts = cfg_dupopt_array(opt->subopts); | } |
val->section->flags = cfg->flags; | } |
val->section->filename = cfg->filename ? strdup(cfg->filename) : 0; | |
val->section->line = cfg->line; | |
val->section->errfunc = cfg->errfunc; | |
val->section->title = value; | |
} | |
if(!is_set(CFGF_DEFINIT, opt->flags)) | |
cfg_init_defaults(val->section); | |
break; | |
|
|
case CFGT_BOOL: | switch (opt->type) { |
if(opt->parsecb) | case CFGT_INT: |
{ | if (opt->parsecb) { |
if((*opt->parsecb)(cfg, opt, value, &b) != 0) | if ((*opt->parsecb) (cfg, opt, value, &i) != 0) |
return 0; | return NULL; |
} | } else { |
else | if (!value) { |
{ | errno = EINVAL; |
b = cfg_parse_boolean(value); | return NULL; |
if(b == -1) | } |
{ | i = strtol(value, &endptr, 0); |
cfg_error(cfg, _("invalid boolean value for option '%s'"), | if (*endptr != '\0') { |
opt->name); | cfg_error(cfg, _("invalid integer value for option '%s'"), opt->name); |
return 0; | return NULL; |
} | } |
} | if (errno == ERANGE) { |
val->boolean = (cfg_bool_t)b; | cfg_error(cfg, _("integer value for option '%s' is out of range"), opt->name); |
break; | return NULL; |
| } |
| } |
| val->number = i; |
| break; |
|
|
case CFGT_PTR: | case CFGT_FLOAT: |
assert(opt->parsecb); | if (opt->parsecb) { |
if((*opt->parsecb)(cfg, opt, value, &p) != 0) | if ((*opt->parsecb) (cfg, opt, value, &f) != 0) |
return 0; | return NULL; |
val->ptr = p; | } else { |
break; | 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; |
|
|
default: | case CFGT_STR: |
cfg_error(cfg, "internal error in cfg_setopt(%s, %s)", | if (opt->parsecb) { |
opt->name, value); | s = NULL; |
assert(0); | if ((*opt->parsecb) (cfg, opt, value, &s) != 0) |
break; | return NULL; |
} | } else { |
return val; | 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 cfg_errfunc_t cfg_set_error_function(cfg_t *cfg, | DLLIMPORT int cfg_opt_setmulti(cfg_t *cfg, cfg_opt_t *opt, unsigned int nvalues, char **values) |
cfg_errfunc_t errfunc) | |
{ |
{ |
cfg_errfunc_t old; | cfg_opt_t old; |
| unsigned int i; |
|
|
assert(cfg); | if (!opt || !nvalues) { |
old = cfg->errfunc; | errno = EINVAL; |
cfg->errfunc = errfunc; | return CFG_FAIL; |
return old; | } |
| |
| 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, ...) |
DLLIMPORT void cfg_error(cfg_t *cfg, const char *fmt, ...) |
{ |
{ |
va_list ap; | va_list ap; |
|
|
va_start(ap, fmt); | va_start(ap, fmt); |
|
|
if(cfg && cfg->errfunc) | if (cfg && cfg->errfunc) |
(*cfg->errfunc)(cfg, fmt, ap); | (*cfg->errfunc) (cfg, fmt, ap); |
else | else { |
{ | if (cfg && cfg->filename && cfg->line) |
if(cfg && cfg->filename && cfg->line) | fprintf(stderr, "%s:%d: ", cfg->filename, cfg->line); |
fprintf(stderr, "%s:%d: ", cfg->filename, cfg->line); | else if (cfg && cfg->filename) |
else if(cfg && cfg->filename) | fprintf(stderr, "%s: ", cfg->filename); |
fprintf(stderr, "%s: ", cfg->filename); | vfprintf(stderr, fmt, ap); |
vfprintf(stderr, fmt, ap); | fprintf(stderr, "\n"); |
fprintf(stderr, "\n"); | } |
} | |
|
|
va_end(ap); | va_end(ap); |
} |
} |
|
|
static int call_function(cfg_t *cfg, cfg_opt_t *opt, cfg_opt_t *funcopt) |
static int call_function(cfg_t *cfg, cfg_opt_t *opt, cfg_opt_t *funcopt) |
{ |
{ |
int ret; | int ret; |
const char **argv; | const char **argv; |
unsigned int i; | unsigned int i; |
|
|
/* create a regular argv string vector and call | if (!cfg || !opt ||!funcopt) { |
* the registered function | errno = EINVAL; |
*/ | return CFG_FAIL; |
argv = calloc(funcopt->nvalues, sizeof(char *)); | } |
for(i = 0; i < funcopt->nvalues; i++) | |
argv[i] = funcopt->values[i]->string; | /* |
ret = (*opt->func)(cfg, opt, funcopt->nvalues, argv); | * create am argv string vector and call the registered function |
cfg_free_value(funcopt); | */ |
free(argv); | argv = calloc(funcopt->nvalues, sizeof(char *)); |
return ret; | 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 int cfg_parse_internal(cfg_t *cfg, int level, | static void cfg_handle_deprecated(cfg_t *cfg, cfg_opt_t *opt) |
int force_state, cfg_opt_t *force_opt) | |
{ |
{ |
int state = 0; | if (is_set(CFGF_DROP, opt->flags)) { |
char *opttitle = 0; | cfg_error(cfg, _("dropping deprecated configuration option '%s'"), opt->name); |
cfg_opt_t *opt = 0; | cfg_free_value(opt); |
cfg_value_t *val = 0; | } else { |
cfg_opt_t funcopt = CFG_STR(0, 0, 0); | cfg_error(cfg, _("found deprecated option '%s', please update configuration file."), opt->name); |
int num_values = 0; /* number of values found for a list option */ | } |
int rc; | } |
|
|
if(force_state != -1) | static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t *force_opt) |
state = force_state; | { |
if(force_opt) | int state = 0; |
opt = force_opt; | 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; |
|
|
while(1) | if (force_state != -1) |
{ | state = force_state; |
int tok = cfg_yylex(cfg); | if (force_opt) |
| opt = force_opt; |
|
|
if(tok == 0) | while (1) { |
{ | int tok = cfg_yylex(cfg); |
/* lexer.l should have called cfg_error */ | |
return STATE_ERROR; | |
} | |
|
|
if(tok == EOF) | if (tok == 0) { |
{ | /* lexer.l should have called cfg_error() */ |
if(state != 0) | goto error; |
{ | } |
cfg_error(cfg, _("premature end of file")); | |
return STATE_ERROR; | |
} | |
return STATE_EOF; | |
} | |
|
|
switch(state) | if (tok == EOF) { |
{ | if (state != 0) { |
case 0: /* expecting an option name */ | cfg_error(cfg, _("premature end of file")); |
if(tok == '}') | goto error; |
{ | } |
if(level == 0) | |
{ | |
cfg_error(cfg, _("unexpected closing brace")); | |
return STATE_ERROR; | |
} | |
return STATE_EOF; | |
} | |
if(tok != CFGT_STR) | |
{ | |
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); | |
return STATE_ERROR; | |
} | |
opt = cfg_getopt(cfg, cfg_yylval); | |
if(opt == 0) | |
return STATE_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 && is_set(CFGF_DEPRECATED, opt->flags)) |
if(tok == '+') | cfg_handle_deprecated(cfg, opt); |
{ | |
if(!is_set(CFGF_LIST, opt->flags)) | |
{ | |
cfg_error(cfg, | |
_("attempt to append to non-list option '%s'"), | |
opt->name); | |
return STATE_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); | |
return STATE_ERROR; | |
} | |
if(is_set(CFGF_LIST, opt->flags)) | |
{ | |
state = 3; | |
num_values = 0; | |
} else | |
state = 2; | |
break; | |
|
|
case 2: /* expecting an option value */ | if (comment) |
if(tok == '}' && is_set(CFGF_LIST, opt->flags)) | free(comment); |
{ | |
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) | return STATE_EOF; |
{ | } |
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); | |
return STATE_ERROR; | |
} | |
|
|
if(cfg_setopt(cfg, opt, cfg_yylval) == 0) | switch (state) { |
return STATE_ERROR; | case 0: /* expecting an option name */ |
if(opt->validcb && (*opt->validcb)(cfg, opt) != 0) | if (opt && is_set(CFGF_DEPRECATED, opt->flags)) |
return STATE_ERROR; | cfg_handle_deprecated(cfg, opt); |
if(is_set(CFGF_LIST, opt->flags)) | |
{ | |
++num_values; | |
state = 4; | |
} | |
else | |
state = 0; | |
break; | |
|
|
case 3: /* expecting an opening brace for a list option */ | switch (tok) { |
if(tok != '{') | case '}': |
{ | if (level == 0) { |
if(tok != CFGT_STR) | cfg_error(cfg, _("unexpected closing brace")); |
{ | goto error; |
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); | } |
return STATE_ERROR; | if (comment) |
} | free(comment); |
|
|
if(cfg_setopt(cfg, opt, cfg_yylval) == 0) | return STATE_EOF; |
return STATE_ERROR; | |
if(opt->validcb && (*opt->validcb)(cfg, opt) != 0) | |
return STATE_ERROR; | |
++num_values; | |
state = 0; | |
} | |
else | |
state = 2; | |
break; | |
|
|
case 4: /* expecting a separator for a list option, or | case CFGT_STR: |
* closing (list) brace */ | break; |
if(tok == ',') | |
state = 2; | |
else if(tok == '}') | |
{ | |
state = 0; | |
if(opt->validcb && (*opt->validcb)(cfg, opt) != 0) | |
return STATE_ERROR; | |
} | |
else | |
{ | |
cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); | |
return STATE_ERROR; | |
} | |
break; | |
|
|
case 5: /* expecting an opening brace for a section */ | case CFGT_COMMENT: |
if(tok != '{') | if (!is_set(CFGF_COMMENTS, cfg->flags)) |
{ | continue; |
cfg_error(cfg, _("missing opening brace for section '%s'"), | |
opt->name); | |
return STATE_ERROR; | |
} | |
|
|
val = cfg_setopt(cfg, opt, opttitle); | if (comment) |
opttitle = 0; | free(comment); |
if(!val) | comment = strdup(cfg_yylval); |
return STATE_ERROR; | continue; |
|
|
val->section->line = cfg->line; | default: |
val->section->errfunc = cfg->errfunc; | cfg_error(cfg, _("unexpected token '%s'"), cfg_yylval); |
rc = cfg_parse_internal(val->section, level+1,-1,0); | goto error; |
cfg->line = val->section->line; | } |
if(rc != STATE_EOF) | |
return STATE_ERROR; | |
if(opt->validcb && (*opt->validcb)(cfg, opt) != 0) | |
return STATE_ERROR; | |
state = 0; | |
break; | |
|
|
case 6: /* expecting a title for a section */ | opt = cfg_getopt(cfg, cfg_yylval); |
if(tok != CFGT_STR) | if (!opt) { |
{ | if (is_set(CFGF_IGNORE_UNKNOWN, cfg->flags)) { |
cfg_error(cfg, _("missing title for section '%s'"), | state = 10; |
opt->name); | break; |
return STATE_ERROR; | } |
} | |
else | |
opttitle = strdup(cfg_yylval); | |
state = 5; | |
break; | |
|
|
case 7: /* expecting an opening parenthesis for a function */ | /* Not found, is it a dynamic key-value section? */ |
if(tok != '(') | if (is_set(CFGF_KEYSTRVAL, cfg->flags)) { |
{ | opt = cfg_addopt(cfg, cfg_yylval); |
cfg_error(cfg, _("missing parenthesis for function '%s'"), | if (!opt) |
opt->name); | goto error; |
return STATE_ERROR; | |
} | |
state = 8; | |
break; | |
|
|
case 8: /* expecting a function parameter or a closing paren */ | state = 1; |
if(tok == ')') | break; |
{ | } |
int ret = call_function(cfg, opt, &funcopt); | |
if(ret != 0) | |
return STATE_ERROR; | |
state = 0; | |
} | |
else if(tok == CFGT_STR) | |
{ | |
val = cfg_addval(&funcopt); | |
val->string = strdup(cfg_yylval); | |
state = 9; | |
} | |
else | |
{ | |
cfg_error(cfg, _("syntax error in call of function '%s'"), | |
opt->name); | |
return STATE_ERROR; | |
} | |
break; | |
|
|
case 9: /* expecting a comma in a function or a closing paren */ | goto error; |
if(tok == ')') | } |
{ | |
int ret = call_function(cfg, opt, &funcopt); | |
if(ret != 0) | |
return STATE_ERROR; | |
state = 0; | |
} | |
else if(tok == ',') | |
state = 8; | |
else | |
{ | |
cfg_error(cfg, _("syntax error in call of function '%s'"), | |
opt->name); | |
return STATE_ERROR; | |
} | |
break; | |
|
|
default: | if (opt->type == CFGT_SEC) { |
/* missing state, internal error, abort */ | if (is_set(CFGF_TITLE, opt->flags)) |
assert(0); | state = 6; |
} | else |
} | state = 5; |
| } else if (opt->type == CFGT_FUNC) { |
| state = 7; |
| } else { |
| state = 1; |
| } |
| break; |
|
|
return STATE_EOF; | 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) |
DLLIMPORT int cfg_parse_fp(cfg_t *cfg, FILE *fp) |
{ |
{ |
int ret; | int ret; |
assert(cfg && fp); | |
|
|
if(cfg->filename == 0) | if (!cfg || !fp) { |
cfg->filename = strdup("FILE"); | errno = EINVAL; |
cfg->line = 1; | return CFG_PARSE_ERROR; |
| } |
|
|
cfg_yyin = fp; | if (!cfg->filename) |
cfg_scan_fp_begin(cfg_yyin); | cfg->filename = strdup("FILE"); |
ret = cfg_parse_internal(cfg, 0, -1, 0); | if (!cfg->filename) |
cfg_scan_fp_end(); | return CFG_PARSE_ERROR; |
if(ret == STATE_ERROR) | |
return CFG_PARSE_ERROR; | cfg->line = 1; |
return CFG_SUCCESS; | 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) |
DLLIMPORT int cfg_parse(cfg_t *cfg, const char *filename) |
{ |
{ |
int ret; | int ret; |
FILE *fp; | char *fn; |
| FILE *fp; |
|
|
assert(cfg && filename); | if (!cfg || !filename) { |
| errno = EINVAL; |
| return CFG_FILE_ERROR; |
| } |
|
|
free(cfg->filename); | if (cfg->path) |
cfg->filename = cfg_tilde_expand(filename); | fn = cfg_searchpath(cfg->path, filename); |
fp = fopen(cfg->filename, "r"); | else |
if(fp == 0) | fn = cfg_tilde_expand(filename); |
return CFG_FILE_ERROR; | if (!fn) |
ret = cfg_parse_fp(cfg, fp); | return CFG_FILE_ERROR; |
fclose(fp); | |
return ret; | 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) |
DLLIMPORT int cfg_parse_buf(cfg_t *cfg, const char *buf) |
{ |
{ |
int ret; | int ret; |
| char *fn; |
| FILE *fp; |
|
|
assert(cfg); | if (!cfg) { |
if(buf == 0) | errno = EINVAL; |
return CFG_SUCCESS; | return CFG_PARSE_ERROR; |
| } |
|
|
free(cfg->filename); | if (!buf) |
cfg->filename = strdup("[buf]"); | return CFG_SUCCESS; |
cfg->line = 1; | |
|
|
cfg_scan_string_begin(buf); | fn = strdup("[buf]"); |
ret = cfg_parse_internal(cfg, 0, -1, 0); | if (!fn) |
cfg_scan_string_end(); | return CFG_PARSE_ERROR; |
if(ret == STATE_ERROR) | |
return CFG_PARSE_ERROR; | free(cfg->filename); |
return CFG_SUCCESS; | 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) |
DLLIMPORT cfg_t *cfg_init(cfg_opt_t *opts, cfg_flag_t flags) |
{ |
{ |
cfg_t *cfg; | cfg_t *cfg; |
|
|
cfg = calloc(1, sizeof(cfg_t)); | cfg = calloc(1, sizeof(cfg_t)); |
assert(cfg); | if (!cfg) |
| return NULL; |
|
|
cfg->name = strdup("root"); | cfg->name = strdup("root"); |
cfg->opts = cfg_dupopt_array(opts); | if (!cfg->name) { |
cfg->flags = flags; | free(cfg); |
cfg->filename = 0; | return NULL; |
cfg->line = 0; | } |
cfg->errfunc = 0; | |
|
|
cfg_init_defaults(cfg); | 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) |
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT) |
setlocale(LC_MESSAGES, ""); | bindtextdomain(PACKAGE, LOCALEDIR); |
setlocale(LC_CTYPE, ""); | |
bindtextdomain(PACKAGE, LOCALEDIR); | |
#endif |
#endif |
|
|
return cfg; | cfg_init_defaults(cfg); |
| |
| return cfg; |
} |
} |
|
|
DLLIMPORT char *cfg_tilde_expand(const char *filename) |
DLLIMPORT char *cfg_tilde_expand(const char *filename) |
{ |
{ |
char *expanded = 0; | char *expanded = 0; |
|
|
#ifndef _WIN32 |
#ifndef _WIN32 |
/* do tilde expansion | /* Do tilde expansion */ |
*/ | if (filename[0] == '~') { |
if(filename[0] == '~') | struct passwd *passwd = 0; |
{ | const char *file = 0; |
struct passwd *passwd = 0; | |
const char *file = 0; | |
|
|
if(filename[1] == '/' || filename[1] == 0) | if (filename[1] == '/' || filename[1] == 0) { |
{ | /* ~ or ~/path */ |
/* ~ or ~/path */ | passwd = getpwuid(geteuid()); |
passwd = getpwuid(geteuid()); | file = filename + 1; |
file = filename + 1; | } else { |
} | /* ~user or ~user/path */ |
else | char *user; |
{ | |
/* ~user or ~user/path */ | |
char *user; | |
|
|
file = strchr(filename, '/'); | file = strchr(filename, '/'); |
if(file == 0) | if (file == 0) |
file = filename + strlen(filename); | file = filename + strlen(filename); |
user = malloc(file - filename); | |
strncpy(user, filename + 1, file - filename - 1); | |
passwd = getpwnam(user); | |
free(user); | |
} | |
|
|
if(passwd) | user = malloc(file - filename); |
{ | if (!user) |
expanded = malloc(strlen(passwd->pw_dir) + strlen(file) + 1); | return NULL; |
strcpy(expanded, passwd->pw_dir); | |
strcat(expanded, file); | 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 |
#endif |
if(!expanded) | if (!expanded) |
expanded = strdup(filename); | expanded = strdup(filename); |
return expanded; | |
| return expanded; |
} |
} |
|
|
DLLIMPORT void cfg_free_value(cfg_opt_t *opt) | DLLIMPORT int cfg_free_value(cfg_opt_t *opt) |
{ |
{ |
unsigned int i; | if (!opt) { |
| errno = EINVAL; |
| return CFG_FAIL; |
| } |
|
|
if(opt == 0) | if (opt->comment && !is_set(CFGF_RESET, opt->flags)) { |
return; | free(opt->comment); |
| opt->comment = NULL; |
| } |
|
|
if(opt->values) | if (opt->values) { |
{ | unsigned int i; |
for(i = 0; i < opt->nvalues; i++) | |
{ | for (i = 0; i < opt->nvalues; i++) { |
if(opt->type == CFGT_STR) | if (opt->type == CFGT_STR) { |
free(opt->values[i]->string); | free((void *)opt->values[i]->string); |
else if(opt->type == CFGT_SEC) | } else if (opt->type == CFGT_SEC) { |
cfg_free(opt->values[i]->section); | opt->values[i]->section->path = NULL; /* Global search path */ |
else if(opt->type == CFGT_PTR && opt->freecb && opt->values[i]->ptr) | cfg_free(opt->values[i]->section); |
(opt->freecb)(opt->values[i]->ptr); | } else if (opt->type == CFGT_PTR && opt->freecb && opt->values[i]->ptr) { |
free(opt->values[i]); | (opt->freecb) (opt->values[i]->ptr); |
} | } |
free(opt->values); | free(opt->values[i]); |
} | } |
opt->values = 0; | free(opt->values); |
opt->nvalues = 0; | } |
| |
| opt->values = NULL; |
| opt->nvalues = 0; |
| |
| return CFG_SUCCESS; |
} |
} |
|
|
static void cfg_free_opt_array(cfg_opt_t *opts) |
static void cfg_free_opt_array(cfg_opt_t *opts) |
{ |
{ |
int i; | int i; |
|
|
for(i = 0; opts[i].name; ++i) | for (i = 0; opts[i].name; ++i) { |
{ | free((void *)opts[i].name); |
free(opts[i].name); | if (opts[i].comment) |
if(opts[i].type == CFGT_FUNC || is_set(CFGF_LIST, opts[i].flags)) | free(opts[i].comment); |
free(opts[i].def.parsed); | if (opts[i].def.parsed) |
else if(opts[i].type == CFGT_STR) | free(opts[i].def.parsed); |
free(opts[i].def.string); | if (opts[i].def.string) |
else if(opts[i].type == CFGT_SEC) | free((void *)opts[i].def.string); |
cfg_free_opt_array(opts[i].subopts); | if (opts[i].subopts) |
} | cfg_free_opt_array(opts[i].subopts); |
free(opts); | } |
| free(opts); |
} |
} |
|
|
DLLIMPORT void cfg_free(cfg_t *cfg) | static int cfg_free_searchpath(cfg_searchpath_t *p) |
{ |
{ |
int i; | if (p) { |
| cfg_free_searchpath(p->next); |
| free(p->dir); |
| free(p); |
| } |
|
|
if(cfg == 0) | return CFG_SUCCESS; |
return; | } |
|
|
for(i = 0; cfg->opts[i].name; ++i) | DLLIMPORT int cfg_free(cfg_t *cfg) |
cfg_free_value(&cfg->opts[i]); | { |
| int i; |
| int isroot = 0; |
|
|
cfg_free_opt_array(cfg->opts); | if (!cfg) { |
| errno = EINVAL; |
| return CFG_FAIL; |
| } |
|
|
free(cfg->name); | if (cfg->comment) |
free(cfg->title); | free(cfg->comment); |
free(cfg->filename); | |
|
|
free(cfg); | 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, | DLLIMPORT int cfg_include(cfg_t *cfg, cfg_opt_t *opt, int argc, const char **argv) |
const char **argv) | |
{ |
{ |
opt = NULL; | (void)opt; /* Unused in this predefined include FUNC */ |
if(argc != 1) | |
{ | if (!cfg || !argv) { |
cfg_error(cfg, _("wrong number of arguments to cfg_include()")); | errno = EINVAL; |
return 1; | return CFG_FAIL; |
} | } |
return cfg_lexer_include(cfg, argv[0]); | |
| 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) |
static cfg_value_t *cfg_opt_getval(cfg_opt_t *opt, unsigned int index) |
{ |
{ |
cfg_value_t *val = 0; | cfg_value_t *val = 0; |
|
|
assert(index == 0 || is_set(CFGF_LIST, opt->flags)); | if (index != 0 && !is_set(CFGF_LIST, opt->flags) && !is_set(CFGF_MULTI, opt->flags)) { |
| errno = EINVAL; |
| return NULL; |
| } |
|
|
if(opt->simple_value) | if (opt->simple_value.ptr) |
val = (cfg_value_t *)opt->simple_value; | val = (cfg_value_t *)opt->simple_value.ptr; |
else | else { |
{ | if (is_set(CFGF_RESET, opt->flags)) { |
if(is_set(CFGF_RESET, opt->flags)) | cfg_free_value(opt); |
{ | opt->flags &= ~CFGF_RESET; |
cfg_free_value(opt); | } |
opt->flags &= ~CFGF_RESET; | |
} | |
|
|
if(index >= opt->nvalues) | if (index >= opt->nvalues) |
val = cfg_addval(opt); | val = cfg_addval(opt); |
else | else |
val = opt->values[index]; | val = opt->values[index]; |
} | } |
return val; | |
| return val; |
} |
} |
|
|
DLLIMPORT void cfg_opt_setnint(cfg_opt_t *opt, long int value, | DLLIMPORT int cfg_opt_setcomment(cfg_opt_t *opt, char *comment) |
unsigned int index) | |
{ |
{ |
cfg_value_t *val; | char *oldcomment, *newcomment; |
assert(opt && opt->type == CFGT_INT); | |
val = cfg_opt_getval(opt, index); | if (!opt || !comment) { |
val->number = value; | 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 void cfg_setnint(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setcomment(cfg_t *cfg, const char *name, char *comment) |
long int value, unsigned int index) | |
{ |
{ |
cfg_opt_setnint(cfg_getopt(cfg, name), value, index); | return cfg_opt_setcomment(cfg_getopt(cfg, name), comment); |
} |
} |
|
|
DLLIMPORT void cfg_setint(cfg_t *cfg, const char *name, long int value) | DLLIMPORT int cfg_opt_setnint(cfg_opt_t *opt, long int value, unsigned int index) |
{ |
{ |
cfg_setnint(cfg, name, value, 0); | 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 void cfg_opt_setnfloat(cfg_opt_t *opt, double value, | DLLIMPORT int cfg_setnint(cfg_t *cfg, const char *name, long int value, unsigned int index) |
unsigned int index) | |
{ |
{ |
cfg_value_t *val; | cfg_opt_t *opt; |
assert(opt && opt->type == CFGT_FLOAT); | |
val = cfg_opt_getval(opt, index); | opt = cfg_getopt(cfg, name); |
val->fpnumber = value; | if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)&value) != 0) |
| return CFG_FAIL; |
| |
| return cfg_opt_setnint(opt, value, index); |
} |
} |
|
|
DLLIMPORT void cfg_setnfloat(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setint(cfg_t *cfg, const char *name, long int value) |
double value, unsigned int index) | |
{ |
{ |
cfg_opt_setnfloat(cfg_getopt(cfg, name), value, index); | return cfg_setnint(cfg, name, value, 0); |
} |
} |
|
|
DLLIMPORT void cfg_setfloat(cfg_t *cfg, const char *name, double value) | DLLIMPORT int cfg_opt_setnfloat(cfg_opt_t *opt, double value, unsigned int index) |
{ |
{ |
cfg_setnfloat(cfg, name, value, 0); | 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 void cfg_opt_setnbool(cfg_opt_t *opt, cfg_bool_t value, | DLLIMPORT int cfg_setnfloat(cfg_t *cfg, const char *name, double value, unsigned int index) |
unsigned int index) | |
{ |
{ |
cfg_value_t *val; | cfg_opt_t *opt; |
assert(opt && opt->type == CFGT_BOOL); | |
val = cfg_opt_getval(opt, index); | opt = cfg_getopt(cfg, name); |
val->boolean = value; | if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)&value) != 0) |
| return CFG_FAIL; |
| |
| return cfg_opt_setnfloat(opt, value, index); |
} |
} |
|
|
DLLIMPORT void cfg_setnbool(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setfloat(cfg_t *cfg, const char *name, double value) |
cfg_bool_t value, unsigned int index) | |
{ |
{ |
cfg_opt_setnbool(cfg_getopt(cfg, name), value, index); | return cfg_setnfloat(cfg, name, value, 0); |
} |
} |
|
|
DLLIMPORT void cfg_setbool(cfg_t *cfg, const char *name, cfg_bool_t value) | DLLIMPORT int cfg_opt_setnbool(cfg_opt_t *opt, cfg_bool_t value, unsigned int index) |
{ |
{ |
cfg_setnbool(cfg, name, value, 0); | 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 void cfg_opt_setnstr(cfg_opt_t *opt, const char *value, | DLLIMPORT int cfg_setnbool(cfg_t *cfg, const char *name, cfg_bool_t value, unsigned int index) |
unsigned int index) | |
{ |
{ |
cfg_value_t *val; | return cfg_opt_setnbool(cfg_getopt(cfg, name), value, index); |
assert(opt && opt->type == CFGT_STR); | |
val = cfg_opt_getval(opt, index); | |
free(val->string); | |
val->string = value ? strdup(value) : 0; | |
} |
} |
|
|
DLLIMPORT void cfg_setnstr(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setbool(cfg_t *cfg, const char *name, cfg_bool_t value) |
const char *value, unsigned int index) | |
{ |
{ |
cfg_opt_setnstr(cfg_getopt(cfg, name), value, index); | return cfg_setnbool(cfg, name, value, 0); |
} |
} |
|
|
DLLIMPORT void cfg_setstr(cfg_t *cfg, const char *name, const char *value) | DLLIMPORT int cfg_opt_setnstr(cfg_opt_t *opt, const char *value, unsigned int index) |
{ |
{ |
cfg_setnstr(cfg, name, value, 0); | 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; |
} |
} |
|
|
static void cfg_addlist_internal(cfg_opt_t *opt, | DLLIMPORT int cfg_setnstr(cfg_t *cfg, const char *name, const char *value, unsigned int index) |
unsigned int nvalues, va_list ap) | |
{ |
{ |
unsigned int i; | cfg_opt_t *opt; |
|
|
for(i = 0; i < nvalues; i++) | opt = cfg_getopt(cfg, name); |
{ | if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)value) != 0) |
switch(opt->type) | return CFG_FAIL; |
{ | |
case CFGT_INT: | return cfg_opt_setnstr(opt, value, index); |
cfg_opt_setnint(opt, va_arg(ap, int), opt->nvalues); | |
break; | |
case CFGT_FLOAT: | |
cfg_opt_setnfloat(opt, va_arg(ap, double), | |
opt->nvalues); | |
break; | |
case CFGT_BOOL: | |
cfg_opt_setnbool(opt, va_arg(ap, cfg_bool_t), | |
opt->nvalues); | |
break; | |
case CFGT_STR: | |
cfg_opt_setnstr(opt, va_arg(ap, char*), opt->nvalues); | |
break; | |
case CFGT_FUNC: | |
case CFGT_SEC: | |
default: | |
break; | |
} | |
} | |
} |
} |
|
|
DLLIMPORT void cfg_setlist(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setstr(cfg_t *cfg, const char *name, const char *value) |
unsigned int nvalues, ...) | |
{ |
{ |
va_list ap; | return cfg_setnstr(cfg, name, value, 0); |
cfg_opt_t *opt = cfg_getopt(cfg, name); | } |
|
|
assert(opt && is_set(CFGF_LIST, opt->flags)); | static int cfg_addlist_internal(cfg_opt_t *opt, unsigned int nvalues, va_list ap) |
| { |
| int result = CFG_FAIL; |
| unsigned int i; |
|
|
cfg_free_value(opt); | for (i = 0; i < nvalues; i++) { |
va_start(ap, nvalues); | switch (opt->type) { |
cfg_addlist_internal(opt, nvalues, ap); | case CFGT_INT: |
va_end(ap); | 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 void cfg_addlist(cfg_t *cfg, const char *name, | DLLIMPORT int cfg_setlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...) |
unsigned int nvalues, ...) | |
{ |
{ |
va_list ap; | va_list ap; |
cfg_opt_t *opt = cfg_getopt(cfg, name); | cfg_opt_t *opt = cfg_getopt(cfg, name); |
|
|
assert(opt && is_set(CFGF_LIST, opt->flags)); | if (!opt || !is_set(CFGF_LIST, opt->flags)) { |
| errno = EINVAL; |
| return CFG_FAIL; |
| } |
|
|
va_start(ap, nvalues); | cfg_free_value(opt); |
cfg_addlist_internal(opt, nvalues, ap); | va_start(ap, nvalues); |
va_end(ap); | cfg_addlist_internal(opt, nvalues, ap); |
| va_end(ap); |
| |
| return CFG_SUCCESS; |
} |
} |
|
|
DLLIMPORT void cfg_opt_nprint_var(cfg_opt_t *opt, unsigned int index, FILE *fp) | DLLIMPORT int cfg_addlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...) |
{ |
{ |
const char *str; | va_list ap; |
| cfg_opt_t *opt = cfg_getopt(cfg, name); |
|
|
assert(opt && fp); | if (!opt || !is_set(CFGF_LIST, opt->flags)) { |
switch(opt->type) | errno = EINVAL; |
{ | return CFG_FAIL; |
case CFGT_INT: | } |
fprintf(fp, "%ld", cfg_opt_getnint(opt, index)); | |
break; | va_start(ap, nvalues); |
case CFGT_FLOAT: | cfg_addlist_internal(opt, nvalues, ap); |
fprintf(fp, "%lf", cfg_opt_getnfloat(opt, index)); | va_end(ap); |
break; | |
case CFGT_STR: | return CFG_SUCCESS; |
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: | |
break; | |
} | |
} |
} |
|
|
|
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) |
static void cfg_indent(FILE *fp, int indent) |
{ |
{ |
while(indent--) | while (indent--) |
fprintf(fp, " "); | fprintf(fp, " "); |
} |
} |
|
|
DLLIMPORT void cfg_opt_print_indent(cfg_opt_t *opt, FILE *fp, int indent) | static int cfg_opt_print_pff_indent(cfg_opt_t *opt, FILE *fp, |
| cfg_print_filter_func_t pff, int indent) |
{ |
{ |
assert(opt && fp); | if (!opt || !fp) { |
| errno = EINVAL; |
| return CFG_FAIL; |
| } |
|
|
if(opt->type == CFGT_SEC) | if (is_set(CFGF_COMMENTS, opt->flags) && opt->comment) { |
{ | cfg_indent(fp, indent); |
cfg_t *sec; | fprintf(fp, "/* %s */\n", opt->comment); |
unsigned int i; | } |
|
|
for(i = 0; i < cfg_opt_size(opt); i++) | if (opt->type == CFGT_SEC) { |
{ | cfg_t *sec; |
sec = cfg_opt_getnsec(opt, i); | unsigned int 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_indent(sec, fp, 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)) | |
{ | |
unsigned int i; | |
|
|
cfg_indent(fp, indent); | for (i = 0; i < cfg_opt_size(opt); i++) { |
fprintf(fp, "%s = {", opt->name); | 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) | 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, "}"); | if (opt->pf) |
} | opt->pf(opt, 0, fp); |
else | else |
{ | cfg_opt_nprint_var(opt, 0, fp); |
cfg_indent(fp, indent); | for (i = 1; i < opt->nvalues; i++) { |
/* comment out the option if is not set */ | fprintf(fp, ", "); |
if(opt->simple_value) | if (opt->pf) |
{ | opt->pf(opt, i, fp); |
if(opt->type == CFGT_STR && *((char **)opt->simple_value) == 0) | else |
fprintf(fp, "# "); | cfg_opt_nprint_var(opt, i, fp); |
} | } |
else | } |
{ | |
if(cfg_opt_size(opt) == 0 || ( | fprintf(fp, "}"); |
opt->type == CFGT_STR && (opt->values[0]->string == 0 || | } else { |
opt->values[0]->string[0] == 0))) | cfg_indent(fp, indent); |
fprintf(fp, "# "); | /* comment out the option if is not set */ |
} | if (cfg_opt_size(opt) == 0 || |
fprintf(fp, "%s = ", opt->name); | (opt->type == CFGT_STR && !cfg_opt_getnstr(opt, 0))) |
if(opt->pf) | fprintf(fp, "# "); |
opt->pf(opt, 0, fp); | fprintf(fp, "%s=", opt->name); |
else | if (opt->pf) |
cfg_opt_nprint_var(opt, 0, fp); | opt->pf(opt, 0, fp); |
} | else |
| cfg_opt_nprint_var(opt, 0, fp); |
fprintf(fp, "\n"); | } |
} | |
else if(opt->pf) | fprintf(fp, "\n"); |
{ | } else if (opt->pf) { |
cfg_indent(fp, indent); | cfg_indent(fp, indent); |
opt->pf(opt, 0, fp); | opt->pf(opt, 0, fp); |
fprintf(fp, "\n"); | fprintf(fp, "\n"); |
} | } |
| |
| return CFG_SUCCESS; |
} |
} |
|
|
DLLIMPORT void cfg_opt_print(cfg_opt_t *opt, FILE *fp) | DLLIMPORT int cfg_opt_print_indent(cfg_opt_t *opt, FILE *fp, int indent) |
{ |
{ |
cfg_opt_print_indent(opt, fp, 0); | return cfg_opt_print_pff_indent(opt, fp, 0, indent); |
} |
} |
|
|
DLLIMPORT void cfg_print_indent(cfg_t *cfg, FILE *fp, int indent) | DLLIMPORT int cfg_opt_print(cfg_opt_t *opt, FILE *fp) |
{ |
{ |
int i; | return cfg_opt_print_pff_indent(opt, fp, 0, 0); |
| } |
|
|
for(i = 0; cfg->opts[i].name; i++) | static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp, |
cfg_opt_print_indent(&cfg->opts[i], fp, indent); | 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 void cfg_print(cfg_t *cfg, FILE *fp) | DLLIMPORT int cfg_print_indent(cfg_t *cfg, FILE *fp, int indent) |
{ |
{ |
cfg_print_indent(cfg, fp, 0); | return cfg_print_pff_indent(cfg, fp, 0, indent); |
} |
} |
|
|
DLLIMPORT cfg_print_func_t cfg_opt_set_print_func(cfg_opt_t *opt, | DLLIMPORT int cfg_print(cfg_t *cfg, FILE *fp) |
cfg_print_func_t pf) | |
{ |
{ |
cfg_print_func_t oldpf; | return cfg_print_pff_indent(cfg, fp, 0, 0); |
| } |
|
|
assert(opt); | DLLIMPORT cfg_print_func_t cfg_opt_set_print_func(cfg_opt_t *opt, cfg_print_func_t pf) |
oldpf = opt->pf; | { |
opt->pf = pf; | cfg_print_func_t oldpf; |
|
|
return 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, | DLLIMPORT cfg_print_func_t cfg_set_print_func(cfg_t *cfg, const char *name, cfg_print_func_t pf) |
cfg_print_func_t pf) | |
{ |
{ |
return cfg_opt_set_print_func(cfg_getopt(cfg, name), 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) |
static cfg_opt_t *cfg_getopt_array(cfg_opt_t *rootopts, int cfg_flags, const char *name) |
{ |
{ |
unsigned int i; | unsigned int i; |
cfg_opt_t *opts = rootopts; | cfg_opt_t *opts = rootopts; |
|
|
assert(rootopts && name); | if (!rootopts || !name) { |
| errno = EINVAL; |
| return NULL; |
| } |
|
|
while(name && *name) | while (name && *name) { |
{ | cfg_t *seccfg; |
cfg_t *seccfg; | char *secname; |
char *secname; | size_t len = strcspn(name, "|"); |
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); | |
secopt = cfg_getopt_array(opts, cfg_flags, secname); | |
free(secname); | |
if(secopt == 0) | |
{ | |
/*fprintf(stderr, "section not found\n");*/ | |
return 0; | |
} | |
if(secopt->type != CFGT_SEC) | |
{ | |
/*fprintf(stderr, "not a section!\n");*/ | |
return 0; | |
} | |
|
|
if(!is_set(CFGF_MULTI, secopt->flags) && | if (name[len] == 0 /*len == strlen(name) */ ) |
(seccfg = cfg_opt_getnsec(secopt, 0)) != 0) | /* no more subsections */ |
{ | break; |
opts = seccfg->opts; | |
} | |
else | |
opts = secopt->subopts; | |
if(opts == 0) | |
{ | |
/*fprintf(stderr, "section have no subopts!?\n");*/ | |
return 0; | |
} | |
} | |
name += len; | |
name += strspn(name, "|"); | |
} | |
|
|
for(i = 0; opts[i].name; i++) | if (len) { |
{ | cfg_opt_t *secopt; |
if(is_set(CFGF_NOCASE, cfg_flags)) | |
{ | secname = strndup(name, len); |
if(strcasecmp(opts[i].name, name) == 0) | if (!secname) |
return &opts[i]; | return NULL; |
} | |
else | secopt = cfg_getopt_array(opts, cfg_flags, secname); |
{ | free(secname); |
if(strcmp(opts[i].name, name) == 0) | if (!secopt) { |
return &opts[i]; | /*fprintf(stderr, "section not found\n"); */ |
} | return NULL; |
} | } |
return 0; | 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, | DLLIMPORT cfg_validate_callback_t cfg_set_validate_func(cfg_t *cfg, const char *name, cfg_validate_callback_t vf) |
const char *name, | |
cfg_validate_callback_t vf) | |
{ |
{ |
cfg_opt_t *opt = cfg_getopt_array(cfg->opts, cfg->flags, name); | cfg_opt_t *opt; |
cfg_validate_callback_t oldvf; | cfg_validate_callback_t oldvf; |
assert(opt); | |
oldvf = opt->validcb; | opt = cfg_getopt_array(cfg->opts, cfg->flags, name); |
opt->validcb = vf; | if (!opt) |
return oldvf; | 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: |
|
*/ |