1610 lines
44 KiB
C
1610 lines
44 KiB
C
![]() |
/* Configuration file parser -*- tab-width: 4; -*-
|
||
|
*
|
||
|
* $Id: confuse.c,v 1.27 2004/10/17 16:57:00 mhe Exp $
|
||
|
*
|
||
|
* Copyright (c) 2002-2003, Martin Hedenfalk <mhe@home.se>
|
||
|
*
|
||
|
* This library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
* License along with this library; if not, write to the Free Software
|
||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
# include <config.h>
|
||
|
#endif
|
||
|
|
||
|
#ifdef HAVE_STRING_H
|
||
|
# define _GNU_SOURCE /* FIXME! */
|
||
|
# include <string.h>
|
||
|
#endif
|
||
|
#ifdef HAVE_STDLIB_H
|
||
|
# include <stdlib.h>
|
||
|
#endif
|
||
|
#include <assert.h>
|
||
|
#include <errno.h>
|
||
|
#ifndef _WIN32
|
||
|
# include <pwd.h>
|
||
|
#endif
|
||
|
#include <sys/types.h>
|
||
|
#ifdef HAVE_UNISTD_H
|
||
|
# include <unistd.h>
|
||
|
#endif
|
||
|
#include <ctype.h>
|
||
|
|
||
|
#include "confuse.h"
|
||
|
|
||
|
#define is_set(f, x) (((f) & (x)) == (f))
|
||
|
|
||
|
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
|
||
|
# include <libintl.h>
|
||
|
# define _(str) dgettext(PACKAGE, str)
|
||
|
#else
|
||
|
# define _(str) str
|
||
|
#endif
|
||
|
#define N_(str) str
|
||
|
|
||
|
extern FILE *cfg_yyin;
|
||
|
extern int cfg_yylex(cfg_t *cfg);
|
||
|
extern int cfg_lexer_include(cfg_t *cfg, const char *fname);
|
||
|
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;
|
||
|
|
||
|
const char confuse_version[] = PACKAGE_VERSION;
|
||
|
const char confuse_copyright[] = PACKAGE_STRING" by Martin Hedenfalk <mhe@home.se>";
|
||
|
const char confuse_author[] = "Martin Hedenfalk <mhe@home.se>";
|
||
|
|
||
|
static int cfg_parse_internal(cfg_t *cfg, int level,
|
||
|
int force_state, cfg_opt_t *force_opt);
|
||
|
static cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, char *value);
|
||
|
|
||
|
#define STATE_CONTINUE 0
|
||
|
#define STATE_EOF -1
|
||
|
#define STATE_ERROR 1
|
||
|
|
||
|
#ifndef HAVE_STRDUP
|
||
|
# ifdef HAVE__STRDUP
|
||
|
# define strdup _strdup
|
||
|
# else
|
||
|
static char *strdup(const char *s)
|
||
|
{
|
||
|
char *r;
|
||
|
|
||
|
if(s == 0 || *s == 0)
|
||
|
return 0;
|
||
|
|
||
|
r = (char *)malloc(strlen(s) + 1);
|
||
|
strcpy(r, s);
|
||
|
return r;
|
||
|
}
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
#ifndef HAVE_STRNDUP
|
||
|
static char *strndup(const char *s, size_t n)
|
||
|
{
|
||
|
char *r;
|
||
|
|
||
|
if(s == 0)
|
||
|
return 0;
|
||
|
|
||
|
r = (char *)malloc(n + 1);
|
||
|
strncpy(r, s, n);
|
||
|
r[n] = 0;
|
||
|
return r;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifndef HAVE_STRCASECMP
|
||
|
/* Implementation from GNU glibc 2.3.1
|
||
|
* Copyright (C) 1991,1992,1995,1996,1997,2001,2002
|
||
|
* Free Software Foundation, Inc.
|
||
|
* License: GNU LGPL
|
||
|
*/
|
||
|
static int strcasecmp(const char *a, const char *b)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
if(a == b)
|
||
|
return 0;
|
||
|
|
||
|
while((r = tolower(*a) - tolower(*b++)) == 0)
|
||
|
{
|
||
|
if(*a++ == '\0')
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
DLLIMPORT cfg_opt_t *cfg_getopt(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
cfg_t *sec = cfg;
|
||
|
|
||
|
assert(cfg && cfg->name && name);
|
||
|
|
||
|
while(name && *name)
|
||
|
{
|
||
|
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++)
|
||
|
{
|
||
|
if(is_set(CFGF_NOCASE, sec->flags))
|
||
|
{
|
||
|
if(strcasecmp(sec->opts[i].name, name) == 0)
|
||
|
return &sec->opts[i];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(strcmp(sec->opts[i].name, name) == 0)
|
||
|
return &sec->opts[i];
|
||
|
}
|
||
|
}
|
||
|
cfg_error(cfg, _("no such option '%s'"), name);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT const char *cfg_title(cfg_t *cfg)
|
||
|
{
|
||
|
if(cfg)
|
||
|
return cfg->title;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT const char *cfg_name(cfg_t *cfg)
|
||
|
{
|
||
|
if(cfg)
|
||
|
return cfg->name;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt)
|
||
|
{
|
||
|
if(opt)
|
||
|
return opt->name;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT unsigned int cfg_opt_size(cfg_opt_t *opt)
|
||
|
{
|
||
|
if(opt)
|
||
|
return opt->nvalues;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT unsigned int cfg_size(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_opt_size(cfg_getopt(cfg, name));
|
||
|
}
|
||
|
|
||
|
DLLIMPORT signed long cfg_opt_getnint(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_INT);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->number;
|
||
|
else if(opt->simple_value)
|
||
|
return *(signed long *)opt->simple_value;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT signed long cfg_getnint(cfg_t *cfg, const char *name,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnint(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT signed long cfg_getint(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnint(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT double cfg_opt_getnfloat(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_FLOAT);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->fpnumber;
|
||
|
else if(opt->simple_value)
|
||
|
return *(double *)opt->simple_value;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT double cfg_getnfloat(cfg_t *cfg, const char *name,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnfloat(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT double cfg_getfloat(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnfloat(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_bool_t cfg_opt_getnbool(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_BOOL);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->boolean;
|
||
|
else if(opt->simple_value)
|
||
|
return *(cfg_bool_t *)opt->simple_value;
|
||
|
else
|
||
|
return cfg_false;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_bool_t cfg_getnbool(cfg_t *cfg, const char *name,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnbool(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_bool_t cfg_getbool(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnbool(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT char *cfg_opt_getnstr(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_STR);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->string;
|
||
|
else if(opt->simple_value)
|
||
|
return *(char **)opt->simple_value;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT char *cfg_getnstr(cfg_t *cfg, const char *name, unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnstr(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT char *cfg_getstr(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnstr(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void *cfg_opt_getnptr(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_PTR);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->ptr;
|
||
|
else if(opt->simple_value)
|
||
|
return *(void **)opt->simple_value;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void *cfg_getnptr(cfg_t *cfg, const char *name, unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnptr(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void *cfg_getptr(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnptr(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_opt_getnsec(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
assert(opt && opt->type == CFGT_SEC);
|
||
|
if(opt->values && index < opt->nvalues)
|
||
|
return opt->values[index]->section;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_getnsec(cfg_t *cfg, const char *name, unsigned int index)
|
||
|
{
|
||
|
return cfg_opt_getnsec(cfg_getopt(cfg, name), index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_opt_gettsec(cfg_opt_t *opt, const char *title)
|
||
|
{
|
||
|
unsigned int i, n;
|
||
|
|
||
|
assert(opt && title);
|
||
|
if(!is_set(CFGF_TITLE, opt->flags))
|
||
|
return 0;
|
||
|
n = cfg_opt_size(opt);
|
||
|
for(i = 0; i < n; i++)
|
||
|
{
|
||
|
cfg_t *sec = cfg_opt_getnsec(opt, i);
|
||
|
assert(sec && sec->title);
|
||
|
if(is_set(CFGF_NOCASE, opt->flags))
|
||
|
{
|
||
|
if(strcasecmp(title, sec->title) == 0)
|
||
|
return sec;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(strcmp(title, sec->title) == 0)
|
||
|
return sec;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_gettsec(cfg_t *cfg, const char *name, const char *title)
|
||
|
{
|
||
|
return cfg_opt_gettsec(cfg_getopt(cfg, name), title);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_getsec(cfg_t *cfg, const char *name)
|
||
|
{
|
||
|
return cfg_getnsec(cfg, name, 0);
|
||
|
}
|
||
|
|
||
|
static cfg_value_t *cfg_addval(cfg_opt_t *opt)
|
||
|
{
|
||
|
opt->values = (cfg_value_t **)realloc(opt->values,
|
||
|
(opt->nvalues+1) * sizeof(cfg_value_t *));
|
||
|
assert(opt->values);
|
||
|
opt->values[opt->nvalues] = (cfg_value_t *)malloc(sizeof(cfg_value_t));
|
||
|
memset(opt->values[opt->nvalues], 0, sizeof(cfg_value_t));
|
||
|
return opt->values[opt->nvalues++];
|
||
|
}
|
||
|
|
||
|
int cfg_numopts(cfg_opt_t *opts)
|
||
|
{
|
||
|
int n;
|
||
|
|
||
|
for(n = 0; opts[n].name; n++)
|
||
|
/* do nothing */ ;
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static cfg_opt_t *cfg_dupopt_array(cfg_opt_t *opts)
|
||
|
{
|
||
|
int i;
|
||
|
cfg_opt_t *dupopts;
|
||
|
int n = cfg_numopts(opts);
|
||
|
|
||
|
dupopts = (cfg_opt_t *)malloc((n+1) * sizeof(cfg_opt_t));
|
||
|
memset(dupopts, 0, (n+1) * sizeof(cfg_opt_t));
|
||
|
memcpy(dupopts, opts, n * sizeof(cfg_opt_t));
|
||
|
|
||
|
for(i = 0; i < n; i++)
|
||
|
{
|
||
|
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)
|
||
|
dupopts[i].def.parsed = opts[i].def.parsed ? strdup(opts[i].def.parsed) : 0;
|
||
|
else if(opts[i].type == CFGT_STR)
|
||
|
dupopts[i].def.string = opts[i].def.string ? strdup(opts[i].def.string) : 0;
|
||
|
}
|
||
|
|
||
|
return dupopts;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT int cfg_parse_boolean(const char *s)
|
||
|
{
|
||
|
if(strcasecmp(s, "true") == 0
|
||
|
|| strcasecmp(s, "on") == 0
|
||
|
|| strcasecmp(s, "yes") == 0)
|
||
|
return 1;
|
||
|
else if(strcasecmp(s, "false") == 0
|
||
|
|| strcasecmp(s, "off") == 0
|
||
|
|| strcasecmp(s, "no") == 0)
|
||
|
return 0;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static void cfg_init_defaults(cfg_t *cfg)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for(i = 0; cfg->opts[i].name; i++)
|
||
|
{
|
||
|
/* 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)
|
||
|
{
|
||
|
cfg->opts[i].flags |= CFGF_DEFINIT;
|
||
|
|
||
|
if(is_set(CFGF_LIST, cfg->opts[i].flags) ||
|
||
|
cfg->opts[i].def.parsed)
|
||
|
{
|
||
|
int xstate, ret;
|
||
|
|
||
|
/* If it's a list, but no default value was given,
|
||
|
* keep the option uninitialized.
|
||
|
*/
|
||
|
if(cfg->opts[i].def.parsed == 0 ||
|
||
|
cfg->opts[i].def.parsed[0] == 0)
|
||
|
continue;
|
||
|
|
||
|
/* setup scanning from the string specified for the
|
||
|
* "default" value, force the correct state and option
|
||
|
*/
|
||
|
|
||
|
if(is_set(CFGF_LIST, cfg->opts[i].flags))
|
||
|
/* lists must be surrounded by {braces} */
|
||
|
xstate = 3;
|
||
|
else if(cfg->opts[i].type == CFGT_FUNC)
|
||
|
xstate = 0;
|
||
|
else
|
||
|
xstate = 2;
|
||
|
|
||
|
cfg_scan_string_begin(cfg->opts[i].def.parsed);
|
||
|
do
|
||
|
{
|
||
|
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
|
||
|
* 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;
|
||
|
} /* end if cfg->opts[i].type != CFGT_SEC */
|
||
|
else if(!is_set(CFGF_MULTI, cfg->opts[i].flags))
|
||
|
{
|
||
|
cfg_setopt(cfg, &cfg->opts[i], 0);
|
||
|
cfg->opts[i].flags |= CFGF_DEFINIT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, char *value)
|
||
|
{
|
||
|
cfg_value_t *val = 0;
|
||
|
int b;
|
||
|
char *s;
|
||
|
double f;
|
||
|
long int i;
|
||
|
void *p;
|
||
|
char *endptr;
|
||
|
|
||
|
assert(cfg && opt);
|
||
|
|
||
|
if(opt->simple_value)
|
||
|
{
|
||
|
assert(opt->type != CFGT_SEC);
|
||
|
val = (cfg_value_t *)opt->simple_value;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(is_set(CFGF_RESET, opt->flags))
|
||
|
{
|
||
|
cfg_free_value(opt);
|
||
|
opt->flags &= ~CFGF_RESET;
|
||
|
}
|
||
|
|
||
|
if(opt->nvalues == 0 || is_set(CFGF_MULTI, opt->flags) ||
|
||
|
is_set(CFGF_LIST, opt->flags))
|
||
|
{
|
||
|
val = 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
|
||
|
*/
|
||
|
assert(value);
|
||
|
for(i = 0; i < opt->nvalues; i++)
|
||
|
{
|
||
|
cfg_t *sec = opt->values[i]->section;
|
||
|
if(is_set(CFGF_NOCASE, cfg->flags))
|
||
|
{
|
||
|
if(strcasecmp(value, sec->title) == 0)
|
||
|
val = opt->values[i];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(strcmp(value, sec->title) == 0)
|
||
|
val = opt->values[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(val == 0)
|
||
|
val = cfg_addval(opt);
|
||
|
}
|
||
|
else
|
||
|
val = opt->values[0];
|
||
|
}
|
||
|
|
||
|
switch(opt->type)
|
||
|
{
|
||
|
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(opt->parsecb)
|
||
|
{
|
||
|
if((*opt->parsecb)(cfg, opt, value, &f) != 0)
|
||
|
return 0;
|
||
|
val->fpnumber = f;
|
||
|
}
|
||
|
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:
|
||
|
free(val->string);
|
||
|
if(opt->parsecb)
|
||
|
{
|
||
|
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(is_set(CFGF_MULTI, opt->flags) || val->section == 0)
|
||
|
{
|
||
|
cfg_free(val->section);
|
||
|
val->section = (cfg_t *)malloc(sizeof(cfg_t));
|
||
|
assert(val->section);
|
||
|
memset(val->section, 0, sizeof(cfg_t));
|
||
|
val->section->name = strdup(opt->name);
|
||
|
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:
|
||
|
if(opt->parsecb)
|
||
|
{
|
||
|
if((*opt->parsecb)(cfg, opt, value, &b) != 0)
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
b = cfg_parse_boolean(value);
|
||
|
if(b == -1)
|
||
|
{
|
||
|
cfg_error(cfg, _("invalid boolean value for option '%s'"),
|
||
|
opt->name);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
val->boolean = (cfg_bool_t)b;
|
||
|
break;
|
||
|
|
||
|
case CFGT_PTR:
|
||
|
assert(opt->parsecb);
|
||
|
if((*opt->parsecb)(cfg, opt, value, &p) != 0)
|
||
|
return 0;
|
||
|
val->ptr = p;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
cfg_error(cfg, "internal error in cfg_setopt(%s, %s)",
|
||
|
opt->name, value);
|
||
|
assert(0);
|
||
|
break;
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_errfunc_t cfg_set_error_function(cfg_t *cfg,
|
||
|
cfg_errfunc_t errfunc)
|
||
|
{
|
||
|
cfg_errfunc_t old;
|
||
|
|
||
|
assert(cfg);
|
||
|
old = cfg->errfunc;
|
||
|
cfg->errfunc = errfunc;
|
||
|
return old;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_error(cfg_t *cfg, const char *fmt, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
|
||
|
va_start(ap, fmt);
|
||
|
|
||
|
if(cfg && cfg->errfunc)
|
||
|
(*cfg->errfunc)(cfg, fmt, ap);
|
||
|
else
|
||
|
{
|
||
|
if(cfg && cfg->filename && cfg->line)
|
||
|
fprintf(stderr, "%s:%d: ", cfg->filename, cfg->line);
|
||
|
else if(cfg && cfg->filename)
|
||
|
fprintf(stderr, "%s: ", cfg->filename);
|
||
|
vfprintf(stderr, fmt, ap);
|
||
|
fprintf(stderr, "\n");
|
||
|
}
|
||
|
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
static int call_function(cfg_t *cfg, cfg_opt_t *opt, cfg_opt_t *funcopt)
|
||
|
{
|
||
|
int ret;
|
||
|
const char **argv;
|
||
|
unsigned int i;
|
||
|
|
||
|
/* create a regular argv string vector and call
|
||
|
* the registered function
|
||
|
*/
|
||
|
argv = (const char **)malloc(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);
|
||
|
cfg_free_value(funcopt);
|
||
|
free((char **)argv);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int cfg_parse_internal(cfg_t *cfg, int level,
|
||
|
int force_state, cfg_opt_t *force_opt)
|
||
|
{
|
||
|
int state = 0;
|
||
|
char *opttitle = 0;
|
||
|
cfg_opt_t *opt = 0;
|
||
|
cfg_value_t *val = 0;
|
||
|
cfg_opt_t funcopt = CFG_STR(0, 0, 0);
|
||
|
int num_values = 0; /* number of values found for a list option */
|
||
|
int rc;
|
||
|
|
||
|
if(force_state != -1)
|
||
|
state = force_state;
|
||
|
if(force_opt)
|
||
|
opt = force_opt;
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
int tok = cfg_yylex(cfg);
|
||
|
|
||
|
if(tok == 0)
|
||
|
{
|
||
|
/* lexer.l should have called cfg_error */
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
|
||
|
if(tok == EOF)
|
||
|
{
|
||
|
if(state != 0)
|
||
|
{
|
||
|
cfg_error(cfg, _("premature end of file"));
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
return STATE_EOF;
|
||
|
}
|
||
|
|
||
|
switch(state)
|
||
|
{
|
||
|
case 0: /* expecting an option name */
|
||
|
if(tok == '}')
|
||
|
{
|
||
|
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(tok == '+')
|
||
|
{
|
||
|
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(tok == '}' && 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);
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
|
||
|
if(cfg_setopt(cfg, opt, cfg_yylval) == 0)
|
||
|
return STATE_ERROR;
|
||
|
if(opt->validcb && (*opt->validcb)(cfg, opt) != 0)
|
||
|
return STATE_ERROR;
|
||
|
if(is_set(CFGF_LIST, opt->flags))
|
||
|
{
|
||
|
state = 4;
|
||
|
++num_values;
|
||
|
}
|
||
|
else
|
||
|
state = 0;
|
||
|
break;
|
||
|
|
||
|
case 3: /* expecting an opening brace for a list option */
|
||
|
if(tok != '{')
|
||
|
{
|
||
|
cfg_error(cfg, _("missing opening brace for option '%s'"),
|
||
|
opt->name);
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
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->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 */
|
||
|
if(tok != '{')
|
||
|
{
|
||
|
cfg_error(cfg, _("missing opening brace for section '%s'"),
|
||
|
opt->name);
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
|
||
|
val = cfg_setopt(cfg, opt, opttitle);
|
||
|
opttitle = 0;
|
||
|
if(!val)
|
||
|
return STATE_ERROR;
|
||
|
|
||
|
val->section->line = cfg->line;
|
||
|
rc = cfg_parse_internal(val->section, level+1,-1,0);
|
||
|
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 */
|
||
|
if(tok != CFGT_STR)
|
||
|
{
|
||
|
cfg_error(cfg, _("missing title for section '%s'"),
|
||
|
opt->name);
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
else
|
||
|
opttitle = strdup(cfg_yylval);
|
||
|
state = 5;
|
||
|
break;
|
||
|
|
||
|
case 7: /* expecting an opening parenthesis for a function */
|
||
|
if(tok != '(')
|
||
|
{
|
||
|
cfg_error(cfg, _("missing parenthesis for function '%s'"),
|
||
|
opt->name);
|
||
|
return STATE_ERROR;
|
||
|
}
|
||
|
state = 8;
|
||
|
break;
|
||
|
|
||
|
case 8: /* expecting a function parameter or a closing paren */
|
||
|
if(tok == ')')
|
||
|
{
|
||
|
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 */
|
||
|
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:
|
||
|
/* missing state, internal error, abort */
|
||
|
assert(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return STATE_EOF;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT int cfg_parse_fp(cfg_t *cfg, FILE *fp)
|
||
|
{
|
||
|
int ret;
|
||
|
assert(cfg && fp);
|
||
|
|
||
|
if(cfg->filename == 0)
|
||
|
cfg->filename = strdup("FILE");
|
||
|
cfg->line = 1;
|
||
|
|
||
|
cfg_yyin = fp;
|
||
|
cfg_scan_fp_begin(cfg_yyin);
|
||
|
ret = cfg_parse_internal(cfg, 0, -1, 0);
|
||
|
cfg_scan_fp_end();
|
||
|
if(ret == STATE_ERROR)
|
||
|
return CFG_PARSE_ERROR;
|
||
|
return CFG_SUCCESS;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT int cfg_parse(cfg_t *cfg, const char *filename)
|
||
|
{
|
||
|
int ret;
|
||
|
FILE *fp;
|
||
|
|
||
|
assert(cfg && filename);
|
||
|
|
||
|
free(cfg->filename);
|
||
|
cfg->filename = cfg_tilde_expand(filename);
|
||
|
fp = fopen(cfg->filename, "r");
|
||
|
if(fp == 0)
|
||
|
return CFG_FILE_ERROR;
|
||
|
ret = cfg_parse_fp(cfg, fp);
|
||
|
fclose(fp);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT int cfg_parse_buf(cfg_t *cfg, const char *buf)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
assert(cfg);
|
||
|
if(buf == 0)
|
||
|
return CFG_SUCCESS;
|
||
|
|
||
|
free(cfg->filename);
|
||
|
cfg->filename = strdup("[buf]");
|
||
|
cfg->line = 1;
|
||
|
|
||
|
cfg_scan_string_begin(buf);
|
||
|
ret = cfg_parse_internal(cfg, 0, -1, 0);
|
||
|
cfg_scan_string_end();
|
||
|
if(ret == STATE_ERROR)
|
||
|
return CFG_PARSE_ERROR;
|
||
|
return CFG_SUCCESS;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_t *cfg_init(cfg_opt_t *opts, cfg_flag_t flags)
|
||
|
{
|
||
|
cfg_t *cfg;
|
||
|
|
||
|
cfg = (cfg_t *)malloc(sizeof(cfg_t));
|
||
|
assert(cfg);
|
||
|
memset(cfg, 0, sizeof(cfg_t));
|
||
|
|
||
|
cfg->name = strdup("root");
|
||
|
cfg->opts = cfg_dupopt_array(opts);
|
||
|
cfg->flags = flags;
|
||
|
cfg->filename = 0;
|
||
|
cfg->line = 0;
|
||
|
cfg->errfunc = 0;
|
||
|
|
||
|
cfg_init_defaults(cfg);
|
||
|
|
||
|
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
|
||
|
setlocale(LC_MESSAGES, "");
|
||
|
setlocale(LC_CTYPE, "");
|
||
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
||
|
#endif
|
||
|
|
||
|
return cfg;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT char *cfg_tilde_expand(const char *filename)
|
||
|
{
|
||
|
char *expanded = 0;
|
||
|
|
||
|
#ifndef _WIN32
|
||
|
/* do tilde expansion
|
||
|
*/
|
||
|
if(filename[0] == '~')
|
||
|
{
|
||
|
struct passwd *passwd = 0;
|
||
|
const char *file = 0;
|
||
|
|
||
|
if(filename[1] == '/' || filename[1] == 0)
|
||
|
{
|
||
|
/* ~ or ~/path */
|
||
|
passwd = getpwuid(geteuid());
|
||
|
file = filename + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* ~user or ~user/path */
|
||
|
char *user;
|
||
|
|
||
|
file = strchr(filename, '/');
|
||
|
if(file == 0)
|
||
|
file = filename + strlen(filename);
|
||
|
user = (char *)malloc(file - filename);
|
||
|
strncpy(user, filename + 1, file - filename - 1);
|
||
|
passwd = getpwnam(user);
|
||
|
free(user);
|
||
|
}
|
||
|
|
||
|
if(passwd)
|
||
|
{
|
||
|
expanded = (char *)malloc(strlen(passwd->pw_dir) + strlen(file) + 1);
|
||
|
strcpy(expanded, passwd->pw_dir);
|
||
|
strcat(expanded, file);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
if(!expanded)
|
||
|
expanded = strdup(filename);
|
||
|
return expanded;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_free_value(cfg_opt_t *opt)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
if(opt == 0)
|
||
|
return;
|
||
|
|
||
|
if(opt->values)
|
||
|
{
|
||
|
for(i = 0; i < opt->nvalues; i++)
|
||
|
{
|
||
|
if(opt->type == CFGT_STR)
|
||
|
free(opt->values[i]->string);
|
||
|
else if(opt->type == CFGT_SEC)
|
||
|
cfg_free(opt->values[i]->section);
|
||
|
else if(opt->type == CFGT_PTR && opt->freecb && opt->values[i]->ptr)
|
||
|
(opt->freecb)(opt->values[i]->ptr);
|
||
|
free(opt->values[i]);
|
||
|
}
|
||
|
free(opt->values);
|
||
|
}
|
||
|
opt->values = 0;
|
||
|
opt->nvalues = 0;
|
||
|
}
|
||
|
|
||
|
static void cfg_free_opt_array(cfg_opt_t *opts)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for(i = 0; opts[i].name; ++i)
|
||
|
{
|
||
|
free(opts[i].name);
|
||
|
if(opts[i].type == CFGT_STR)
|
||
|
free(opts[i].def.string);
|
||
|
else if(opts[i].type == CFGT_SEC)
|
||
|
cfg_free_opt_array(opts[i].subopts);
|
||
|
else if(opts[i].type == CFGT_FUNC || is_set(CFGF_LIST, opts[i].flags))
|
||
|
free(opts[i].def.parsed);
|
||
|
}
|
||
|
free(opts);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_free(cfg_t *cfg)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if(cfg == 0)
|
||
|
return;
|
||
|
|
||
|
for(i = 0; cfg->opts[i].name; ++i)
|
||
|
cfg_free_value(&cfg->opts[i]);
|
||
|
|
||
|
cfg_free_opt_array(cfg->opts);
|
||
|
|
||
|
free(cfg->name);
|
||
|
free(cfg->title);
|
||
|
free(cfg->filename);
|
||
|
|
||
|
free(cfg);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT int cfg_include(cfg_t *cfg, cfg_opt_t *opt, int argc,
|
||
|
const char **argv)
|
||
|
{
|
||
|
if(argc != 1)
|
||
|
{
|
||
|
cfg_error(cfg, _("wrong number of arguments to cfg_include()"));
|
||
|
return 1;
|
||
|
}
|
||
|
return cfg_lexer_include(cfg, argv[0]);
|
||
|
}
|
||
|
|
||
|
static cfg_value_t *cfg_opt_getval(cfg_opt_t *opt, unsigned int index)
|
||
|
{
|
||
|
cfg_value_t *val = 0;
|
||
|
|
||
|
assert(index == 0 || is_set(CFGF_LIST, opt->flags));
|
||
|
|
||
|
if(opt->simple_value)
|
||
|
val = (cfg_value_t *)opt->simple_value;
|
||
|
else
|
||
|
{
|
||
|
if(is_set(CFGF_RESET, opt->flags))
|
||
|
{
|
||
|
cfg_free_value(opt);
|
||
|
opt->flags &= ~CFGF_RESET;
|
||
|
}
|
||
|
|
||
|
if(index >= opt->nvalues)
|
||
|
val = cfg_addval(opt);
|
||
|
else
|
||
|
val = opt->values[index];
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_setnint(cfg_opt_t *opt, long int value,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
cfg_value_t *val;
|
||
|
assert(opt && opt->type == CFGT_INT);
|
||
|
val = cfg_opt_getval(opt, index);
|
||
|
val->number = value;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setnint(cfg_t *cfg, const char *name,
|
||
|
long int value, unsigned int index)
|
||
|
{
|
||
|
cfg_opt_setnint(cfg_getopt(cfg, name), value, index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setint(cfg_t *cfg, const char *name, long int value)
|
||
|
{
|
||
|
cfg_setnint(cfg, name, value, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_setnfloat(cfg_opt_t *opt, double value,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
cfg_value_t *val;
|
||
|
assert(opt && opt->type == CFGT_FLOAT);
|
||
|
val = cfg_opt_getval(opt, index);
|
||
|
val->fpnumber = value;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setnfloat(cfg_t *cfg, const char *name,
|
||
|
double value, unsigned int index)
|
||
|
{
|
||
|
cfg_opt_setnfloat(cfg_getopt(cfg, name), value, index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setfloat(cfg_t *cfg, const char *name, double value)
|
||
|
{
|
||
|
cfg_setnfloat(cfg, name, value, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_setnbool(cfg_opt_t *opt, cfg_bool_t value,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
cfg_value_t *val;
|
||
|
assert(opt && opt->type == CFGT_BOOL);
|
||
|
val = cfg_opt_getval(opt, index);
|
||
|
val->boolean = value;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setnbool(cfg_t *cfg, const char *name,
|
||
|
cfg_bool_t value, unsigned int index)
|
||
|
{
|
||
|
cfg_opt_setnbool(cfg_getopt(cfg, name), value, index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setbool(cfg_t *cfg, const char *name, cfg_bool_t value)
|
||
|
{
|
||
|
cfg_setnbool(cfg, name, value, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_setnstr(cfg_opt_t *opt, const char *value,
|
||
|
unsigned int index)
|
||
|
{
|
||
|
cfg_value_t *val;
|
||
|
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,
|
||
|
const char *value, unsigned int index)
|
||
|
{
|
||
|
cfg_opt_setnstr(cfg_getopt(cfg, name), value, index);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_setstr(cfg_t *cfg, const char *name, const char *value)
|
||
|
{
|
||
|
cfg_setnstr(cfg, name, value, 0);
|
||
|
}
|
||
|
|
||
|
static void cfg_addlist_internal(cfg_opt_t *opt,
|
||
|
unsigned int nvalues, va_list ap)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for(i = 0; i < nvalues; i++)
|
||
|
{
|
||
|
switch(opt->type)
|
||
|
{
|
||
|
case CFGT_INT:
|
||
|
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,
|
||
|
unsigned int nvalues, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
cfg_opt_t *opt = cfg_getopt(cfg, name);
|
||
|
|
||
|
assert(opt && is_set(CFGF_LIST, opt->flags));
|
||
|
|
||
|
cfg_free_value(opt);
|
||
|
va_start(ap, nvalues);
|
||
|
cfg_addlist_internal(opt, nvalues, ap);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_addlist(cfg_t *cfg, const char *name,
|
||
|
unsigned int nvalues, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
cfg_opt_t *opt = cfg_getopt(cfg, name);
|
||
|
|
||
|
assert(opt && is_set(CFGF_LIST, opt->flags));
|
||
|
|
||
|
va_start(ap, nvalues);
|
||
|
cfg_addlist_internal(opt, nvalues, ap);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_nprint_var(cfg_opt_t *opt, unsigned int index, FILE *fp)
|
||
|
{
|
||
|
const char *str;
|
||
|
|
||
|
assert(opt && fp);
|
||
|
switch(opt->type)
|
||
|
{
|
||
|
case CFGT_INT:
|
||
|
fprintf(fp, "%ld", cfg_opt_getnint(opt, index));
|
||
|
break;
|
||
|
case CFGT_FLOAT:
|
||
|
fprintf(fp, "%lf", cfg_opt_getnfloat(opt, index));
|
||
|
break;
|
||
|
case CFGT_STR:
|
||
|
str = cfg_opt_getnstr(opt, index);
|
||
|
fprintf(fp, "\"%s\"", str ? str : "");
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void cfg_indent(FILE *fp, int indent)
|
||
|
{
|
||
|
while(indent--)
|
||
|
fprintf(fp, " ");
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_print_indent(cfg_opt_t *opt, FILE *fp, int indent)
|
||
|
{
|
||
|
assert(opt && fp);
|
||
|
|
||
|
if(opt->type == CFGT_SEC)
|
||
|
{
|
||
|
cfg_t *sec;
|
||
|
unsigned int i;
|
||
|
|
||
|
for(i = 0; i < cfg_opt_size(opt); i++)
|
||
|
{
|
||
|
sec = cfg_opt_getnsec(opt, i);
|
||
|
cfg_indent(fp, indent);
|
||
|
if(is_set(CFGF_TITLE, opt->flags))
|
||
|
fprintf(fp, "%s \"%s\" {\n", opt->name, cfg_title(sec));
|
||
|
else
|
||
|
fprintf(fp, "%s {\n", opt->name);
|
||
|
cfg_print_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);
|
||
|
fprintf(fp, "%s = {", opt->name);
|
||
|
|
||
|
if(opt->nvalues)
|
||
|
{
|
||
|
if(opt->pf)
|
||
|
opt->pf(opt, 0, fp);
|
||
|
else
|
||
|
cfg_opt_nprint_var(opt, 0, fp);
|
||
|
for(i = 1; i < opt->nvalues; i++)
|
||
|
{
|
||
|
fprintf(fp, ", ");
|
||
|
if(opt->pf)
|
||
|
opt->pf(opt, i, fp);
|
||
|
else
|
||
|
cfg_opt_nprint_var(opt, i, fp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fprintf(fp, "}");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cfg_indent(fp, indent);
|
||
|
/* comment out the option if is not set */
|
||
|
if(opt->simple_value)
|
||
|
{
|
||
|
if(opt->type == CFGT_STR && *((char **)opt->simple_value) == 0)
|
||
|
fprintf(fp, "# ");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(cfg_opt_size(opt) == 0 || (
|
||
|
opt->type == CFGT_STR && (opt->values[0]->string == 0 ||
|
||
|
opt->values[0]->string[0] == 0)))
|
||
|
fprintf(fp, "# ");
|
||
|
}
|
||
|
fprintf(fp, "%s = ", opt->name);
|
||
|
if(opt->pf)
|
||
|
opt->pf(opt, 0, fp);
|
||
|
else
|
||
|
cfg_opt_nprint_var(opt, 0, fp);
|
||
|
}
|
||
|
|
||
|
fprintf(fp, "\n");
|
||
|
}
|
||
|
else if(opt->pf)
|
||
|
{
|
||
|
cfg_indent(fp, indent);
|
||
|
opt->pf(opt, 0, fp);
|
||
|
fprintf(fp, "\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_opt_print(cfg_opt_t *opt, FILE *fp)
|
||
|
{
|
||
|
cfg_opt_print_indent(opt, fp, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_print_indent(cfg_t *cfg, FILE *fp, int indent)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for(i = 0; cfg->opts[i].name; i++)
|
||
|
cfg_opt_print_indent(&cfg->opts[i], fp, indent);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT void cfg_print(cfg_t *cfg, FILE *fp)
|
||
|
{
|
||
|
cfg_print_indent(cfg, fp, 0);
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_print_func_t cfg_opt_set_print_func(cfg_opt_t *opt,
|
||
|
cfg_print_func_t pf)
|
||
|
{
|
||
|
cfg_print_func_t oldpf;
|
||
|
|
||
|
assert(opt);
|
||
|
oldpf = opt->pf;
|
||
|
opt->pf = pf;
|
||
|
|
||
|
return oldpf;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_print_func_t cfg_set_print_func(cfg_t *cfg, const char *name,
|
||
|
cfg_print_func_t pf)
|
||
|
{
|
||
|
return cfg_opt_set_print_func(cfg_getopt(cfg, name), pf);
|
||
|
}
|
||
|
|
||
|
static cfg_opt_t *cfg_getopt_array(cfg_opt_t *rootopts, int cfg_flags, const char *name)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
cfg_opt_t *opts = rootopts;
|
||
|
|
||
|
assert(rootopts && name);
|
||
|
|
||
|
while(name && *name)
|
||
|
{
|
||
|
cfg_t *seccfg;
|
||
|
char *secname;
|
||
|
size_t len = strcspn(name, "|");
|
||
|
if(name[len] == 0 /*len == strlen(name)*/)
|
||
|
/* no more subsections */
|
||
|
break;
|
||
|
if(len)
|
||
|
{
|
||
|
cfg_opt_t *secopt;
|
||
|
secname = strndup(name, len);
|
||
|
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) && (seccfg = cfg_opt_getnsec(secopt, 0)) != 0)
|
||
|
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(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 0;
|
||
|
}
|
||
|
|
||
|
DLLIMPORT cfg_validate_callback_t cfg_set_validate_func(cfg_t *cfg,
|
||
|
const char *name,
|
||
|
cfg_validate_callback_t vf)
|
||
|
{
|
||
|
cfg_opt_t *opt = cfg_getopt_array(cfg->opts, cfg->flags, name);
|
||
|
cfg_validate_callback_t oldvf;
|
||
|
assert(opt);
|
||
|
oldvf = opt->validcb;
|
||
|
opt->validcb = vf;
|
||
|
return oldvf;
|
||
|
}
|
||
|
|