You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/cfg_parser.c

1043 lines
23 KiB

/*
* $Id$
* Standalone Configuration File Parser
*
* Copyright (C) 2008 iptelorg GmbH
* Written by Jan Janak <jan@iptel.org>
*
* This file is part of SER, a free SIP server.
*
* SER is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* SER is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*!
* \file
* \brief SIP-router core ::
* \ingroup core
*
* Module: \ref core
*
* See \ref ConfigEngine
*
* \page ConfigEngine
* In file \ref cfg_parser.c
* Configuration examples
* - \ref ConfigExample1
* - \ref ConfigExample2
* - \ref ConfigExample3
* - \ref ConfigExample4
* - \ref ConfigExample5
* - \ref ConfigExample6
* - \ref ConfigExample7
* - \ref ConfigExample8
*
* <b>Run-time Allocated Destination Variables</b>
* - the destination variable pointers in arrays are assigned at compile time
* - The address of variables allocated on the heap (typically in dynamically allocated
* structures) is not know and thus we need to assign NULL initially and change the pointer
* at runtime.
* (provide an example).
*
* <b>Built-in parsing functions</b>
*
* *_val functions parse the whole option body (including =)
*/
/*! \page ConfigExample1 Configuration engine Example 1: Options without values
*
\verbatim
* str file = STR_STATIC_INIT("test.cfg");
* cfg_parser_t* parser;
* int feature_a = 0, feature_b = 0;
*
* cfg_option_t options[] = {
* {"enable_feature_a", .param = &feature_a, .val = 1},
* {"disable_feature_a", .param = &feature_a, .val = 0},
* {"feature_b", .param = &feature_b, .val = 1},
* {0}
* };
*
* if ((parser = cfg_parser_init(&file)) == NULL) {
* ERR("Error while creating parser\n");
* return -1;
* }
*
* cfg_set_options(parser, options);
*
* if (sr_cfg_parse(parser) < 0) {
* ERR("Error while parsing configuration file\n");
* cfg_parser_close(parser);
* return -1;
* }
*
* cfg_parser_close(parser);
\endverbatim
*/
/*! \page ConfigExample2 Configuration engine Example 2: Options with integer values
\verbatim
* cfg_option_t options[] = {
* {"max_number", .param = &max_number, .f = cfg_parse_int_val },
* {"extra_checks", .param = &extra_checks, .f = cfg_parse_bool_val},
* {0}
* };
\endverbatim
*/
/*! \page ConfigExample3 Configuration engine Example 3: Enum options
\verbatim
* int scope;
*
* cfg_option_t scopes[] = {
* {"base", .param = &scope, .val = 1},
* {"onelevel", .param = &scope, .val = 2},
* {"one", .param = &scope, .val = 3},
* {"subtree", .param = &scope, .val = 4},
* {"sub", .param = &scope, .val = 5},
* {"children", .param = &scope, .val = 6},
* {0}
* };
*
* cfg_option_t options[] = {
* {"scope", .param = scopes, .f = cfg_parse_enum_val},
* {0}
* };
\endverbatim
*/
/*! \page ConfigExample4 Configuration engine Example 4: Options with string values
\verbatim
* str filename = STR_NULL;
*
* cfg_option_t options[] = {
* {"filename", .param = &filename, .f = cfg_parse_str_val},
* {0}
* };
*
* - By default the function returns a pointer to an internal buffer which will be destroyed
* by a subsequent call
* - There are flags to tell the function to copy the resuting string in a pkg, shm, glibc,
* or static buffers
\endverbatim
*/
/*! \page ConfigExample5 Configuration engine Example 5: Custom value parsing
* TBD
*/
/*! \page ConfigExample6 Configuration engine Example 6: Parsing Sections
* TBD
*/
/*! \page ConfigExample7 Configuration engine Example 7: Default Options
* TBD
*/
/*! \page ConfigExample8 Configuration engine Example 8: Memory management of strings
*
* Data types with fixed size are easy, they can be copied into a pre-allocated memory
* buffer, strings cannot because their length is unknown.
*/
#include "cfg_parser.h"
#include "mem/mem.h"
#include "mem/shm_mem.h"
#include "dprint.h"
#include "trim.h"
#include "ut.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
/*! \brief The states of the lexical scanner */
enum st {
ST_S, /*!< Begin */
ST_A, /*!< Alphanumeric */
ST_AE, /*!< Alphanumeric escaped */
ST_Q, /*!< Quoted */
ST_QE, /*!< Quoted escaped */
ST_C, /*!< Comment */
ST_CE, /*!< Comment escaped */
ST_E, /*!< Escaped */
};
/*! \brief Test for alphanumeric characters */
#define IS_ALPHA(c) \
(((c) >= 'a' && (c) <= 'z') || \
((c) >= 'A' && (c) <= 'Z') || \
((c) >= '0' && (c) <= '9') || \
(c) == '_')
/*! \brief Test for delimiter characters */
#define IS_DELIM(c) \
((c) == '=' || \
(c) == ':' || \
(c) == ';' || \
(c) == '.' || \
(c) == ',' || \
(c) == '?' || \
(c) == '[' || \
(c) == ']' || \
(c) == '/' || \
(c) == '@' || \
(c) == '!' || \
(c) == '$' || \
(c) == '%' || \
(c) == '&' || \
(c) == '*' || \
(c) == '(' || \
(c) == ')' || \
(c) == '-' || \
(c) == '+' || \
(c) == '|' || \
(c) == '\'')
/*! \brief Whitespace characters */
#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r')
#define IS_QUOTE(c) ((c) == '\"') /* Quote characters */
#define IS_COMMENT(c) ((c) == '#') /* Characters that start comments */
#define IS_ESCAPE(c) ((c) == '\\') /* Escape characters */
#define IS_EOL(c) ((c) == '\n') /* End of line */
/*! \brief
* Append character to the value of current token
*/
#define PUSH(c) \
if (token->val.len >= MAX_TOKEN_LEN) { \
ERR("%s:%d:%d: Token too long\n", \
st->file, st->line, st->col); \
return -1; \
} \
if (token->val.len == 0) { \
token->start.line = st->line; \
token->start.col = st->col; \
} \
token->val.s[token->val.len++] = (c);
/*! \brief
* Return current token from the lexical analyzer
*/
#define RETURN(c) \
token->end.line = st->line; \
token->end.col = st->col; \
token->type = (c); \
print_token(token); \
return 0;
/*! \brief
* Get next character and update counters
*/
#define READ_CHAR \
c = fgetc(st->f); \
if (IS_EOL(c)) { \
st->line++; \
st->col = 0; \
} else { \
st->col++; \
}
cfg_option_t cfg_bool_values[] = {
{"yes", .val = 1},
{"true", .val = 1},
{"enable", .val = 1},
{"enabled", .val = 1},
{"1", .val = 1},
{"on", .val = 1},
{"no", .val = 0},
{"false", .val = 0},
{"disable", .val = 0},
{"disabled", .val = 0},
{"0", .val = 0},
{"off", .val = 0},
{0}
};
static void print_token(cfg_token_t* token)
{
#ifdef EXTRA_DEBUG
int i, j;
char* buf;
if ((buf = pkg_malloc(token->val.len * 2)) == NULL) {
DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n",
token->type, STR_FMT(&token->val),
token->start.line, token->start.col,
token->end.line, token->end.col);
} else {
for(i = 0, j = 0; i < token->val.len; i++) {
switch(token->val.s[i]) {
case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break;
case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break;
case '\t': buf[j++] = '\\'; buf[j++] = 't'; break;
case '\0': buf[j++] = '\\'; buf[j++] = '0'; break;
case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break;
default: buf[j++] = token->val.s[i];
}
}
DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n",
token->type, j, buf,
token->start.line, token->start.col,
token->end.line, token->end.col);
pkg_free(buf);
}
#endif /* EXTRA_DEBUG */
}
int cfg_get_token(cfg_token_t* token, cfg_parser_t* st, unsigned int flags)
{
static int look_ahead = EOF;
int c;
enum st state;
state = ST_S;
token->val.s = token->buf;
token->val.len = 0;
if (look_ahead != EOF) {
c = look_ahead;
look_ahead = EOF;
} else {
READ_CHAR;
}
while(c != EOF) {
switch(state) {
case ST_S:
if (flags & CFG_EXTENDED_ALPHA) {
if (IS_WHITESPACE(c)) {
/* Do nothing */
} else if (IS_ALPHA(c) ||
IS_ESCAPE(c) ||
IS_DELIM(c)) {
PUSH(c);
state = ST_A;
} else if (IS_QUOTE(c)) {
state = ST_Q;
} else if (IS_COMMENT(c)) {
state = ST_C;
} else if (IS_EOL(c)) {
PUSH(c);
RETURN(c);
} else {
ERR("%s:%d:%d: Invalid character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
} else {
if (IS_WHITESPACE(c)) {
/* Do nothing */
} else if (IS_ALPHA(c)) {
PUSH(c);
state = ST_A;
} else if (IS_QUOTE(c)) {
state = ST_Q;
} else if (IS_COMMENT(c)) {
state = ST_C;
} else if (IS_ESCAPE(c)) {
state = ST_E;
} else if (IS_DELIM(c) || IS_EOL(c)) {
PUSH(c);
RETURN(c);
} else {
ERR("%s:%d:%d: Invalid character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
}
break;
case ST_A:
if (flags & CFG_EXTENDED_ALPHA) {
if (IS_ALPHA(c) ||
IS_DELIM(c) ||
IS_QUOTE(c)) {
PUSH(c);
} else if (IS_ESCAPE(c)) {
state = ST_AE;
} else if (IS_COMMENT(c) || IS_EOL(c) || IS_WHITESPACE(c)) {
look_ahead = c;
RETURN(CFG_TOKEN_ALPHA);
} else {
ERR("%s:%d:%d: Invalid character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
} else {
if (IS_ALPHA(c)) {
PUSH(c);
} else if (IS_ESCAPE(c)) {
state = ST_AE;
} else if (IS_WHITESPACE(c) ||
IS_DELIM(c) ||
IS_QUOTE(c) ||
IS_COMMENT(c) ||
IS_EOL(c)) {
look_ahead = c;
RETURN(CFG_TOKEN_ALPHA);
} else {
ERR("%s:%d:%d: Invalid character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
}
break;
case ST_AE:
if (IS_COMMENT(c) ||
IS_QUOTE(c) ||
IS_ESCAPE(c)) {
PUSH(c);
} else if (c == 'r') {
PUSH('\r');
} else if (c == 'n') {
PUSH('\n');
} else if (c == 't') {
PUSH('\t');
} else if (c == ' ') {
PUSH(' ');
} else if (IS_EOL(c)) {
/* Do nothing */
} else {
ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
state = ST_A;
break;
case ST_Q:
if (IS_QUOTE(c)) {
RETURN(CFG_TOKEN_STRING);
} else if (IS_ESCAPE(c)) {
state = ST_QE;
break;
} else {
PUSH(c);
}
break;
case ST_QE:
if (IS_ESCAPE(c) ||
IS_QUOTE(c)) {
PUSH(c);
} else if (c == 'n') {
PUSH('\n');
} else if (c == 'r') {
PUSH('\r');
} else if (c == 't') {
PUSH('\t');
} else if (IS_EOL(c)) {
/* Do nothing */
} else {
ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
state = ST_Q;
break;
case ST_C:
if (IS_ESCAPE(c)) {
state = ST_CE;
} else if (IS_EOL(c)) {
state = ST_S;
continue; /* Do not read a new char, return EOL */
} else {
/* Do nothing */
}
break;
case ST_CE:
state = ST_C;
break;
case ST_E:
if (IS_COMMENT(c) ||
IS_QUOTE(c) ||
IS_ESCAPE(c)) {
PUSH(c);
RETURN(c);
} else if (c == 'r') {
PUSH('\r');
RETURN('\r');
} else if (c == 'n') {
PUSH('\n');
RETURN('\n');
} else if (c == 't') {
PUSH('\t');
RETURN('\t');
} else if (c == ' ') {
PUSH(' ');
RETURN(' ');
} else if (IS_EOL(c)) {
/* Escped eol means no eol */
state = ST_S;
} else {
ERR("%s:%d:%d: Unsupported escape character 0x%x\n",
st->file, st->line, st->col, c);
return -1;
}
break;
}
READ_CHAR;
};
switch(state) {
case ST_S:
case ST_C:
case ST_CE:
return 1;
case ST_A:
RETURN(CFG_TOKEN_ALPHA);
case ST_Q:
ERR("%s:%d:%d: Premature end of file, missing closing quote in"
" string constant\n", st->file, st->line, st->col);
return -1;
case ST_QE:
case ST_E:
case ST_AE:
ERR("%s:%d:%d: Premature end of file, missing escaped character\n",
st->file, st->line, st->col);
return -1;
}
BUG("%s:%d:%d: Invalid state %d\n",
st->file, st->line, st->col, state);
return -1;
}
int cfg_parse_section(void* param, cfg_parser_t* st, unsigned int flags)
{
cfg_token_t t;
int ret;
ret = cfg_parse_str(param, st, flags);
if (ret < 0) return ret;
if (ret > 0) {
ERR("%s:%d:%d: Section name missing.\n",
st->file, st->line, st->col);
return ret;
}
ret = cfg_get_token(&t, st, flags);
if (ret < 0) goto error;
if (ret > 0) {
ERR("%s:%d:%d: Closing ']' missing\n", st->file, st->line, st->col);
goto error;
}
if (t.type != ']') {
ERR("%s:%d:%d: Syntax error, ']' expected\n",
st->file, t.start.line, t.start.col);
goto error;
}
if (cfg_eat_eol(st, flags)) goto error;
return 0;
error:
if (param && ((str*)param)->s) {
if (flags & CFG_STR_PKGMEM) {
pkg_free(((str*)param)->s);
((str*)param)->s = NULL;
} else if (flags & CFG_STR_SHMMEM) {
shm_free(((str*)param)->s);
((str*)param)->s = NULL;
} else if (flags & CFG_STR_MALLOC) {
free(((str*)param)->s);
((str*)param)->s = NULL;
}
}
return -1;
}
static char* get_base_name(str* filename)
{
char* tmp1, *tmp2, *res;
int len;
res = NULL;
if ((tmp1 = as_asciiz(filename)) == NULL) {
ERR("cfg_parser: No memory left\n");
goto error;
}
if ((tmp2 = basename(tmp1)) == NULL) {
ERR("cfg_parser: Error in basename\n");
goto error;
}
len = strlen(tmp2);
if ((res = pkg_malloc(len + 1)) == NULL) {
ERR("cfg_parser: No memory left");
goto error;
}
memcpy(res, tmp2, len + 1);
pkg_free(tmp1);
return res;
error:
if (tmp1) pkg_free(tmp1);
return NULL;
}
/** intialize the config parser.
* @param basedir - path to the config file name. If 0 the path
* (base directory) of the main ser.cfg file will be used, else
* basedir will be concatenated to the filename. It will be
* used only if filename is not an absolute path.
* @param filename - config filename (can include path elements).
* @return 0 on error, !=0 on success.
*/
cfg_parser_t* cfg_parser_init(str* basedir, str* filename)
{
cfg_parser_t* st;
char* pathname, *base, *abs_pathname;
abs_pathname = NULL;
pathname = filename->s;
st = NULL;
base = NULL;
/* if basedir == 0 or != "" get_abs_pathname */
if (basedir == 0 || basedir->len != 0) {
if ((abs_pathname = get_abs_pathname(basedir, filename)) == NULL) {
ERR("cfg_parser: Error while converting %.*s to absolute"
" pathname\n", STR_FMT(filename));
goto error;
}
pathname = abs_pathname;
}
if ((base = get_base_name(filename)) == NULL) goto error;
if ((st = (cfg_parser_t*)pkg_malloc(sizeof(*st))) == NULL) {
ERR("cfg_parser: No memory left\n");
goto error;
}
memset(st, '\0', sizeof(*st));
if ((st->f = fopen(pathname, "r")) == NULL) {
ERR("cfg_parser: Unable to open file '%s'\n", pathname);
goto error;
}
if (abs_pathname) pkg_free(abs_pathname);
st->file = base;
st->line = 1;
st->col = 0;
return st;
error:
if (st) {
if (st->f) fclose(st->f);
pkg_free(st);
}
if (base) pkg_free(base);
if (abs_pathname) pkg_free(abs_pathname);
return NULL;
}
void cfg_parser_close(cfg_parser_t* st)
{
if (!st) return;
if (st->f) fclose(st->f);
if (st->file) pkg_free(st->file);
pkg_free(st);
}
void cfg_section_parser(cfg_parser_t* st, cfg_func_f parser, void* param)
{
if (st == NULL) return;
st->section.parser = parser;
st->section.param = param;
}
void cfg_set_options(cfg_parser_t* st, cfg_option_t* options)
{
if (st) st->options = options;
}
static int process_option(cfg_parser_t* st, cfg_option_t* opt)
{
if (opt->f) {
/* We have a function so store it and pass opt->dst to it */
if (opt->f(opt->param, st, opt->flags) < 0) return -1;
} else {
/* We have no function, so if we have a pointer to some
* variable in opt->param then store the value of opt->i
* there, the variable is assumed to be of type i
*/
if (opt->param) *(int*)opt->param = opt->val;
}
return 0;
}
int sr_cfg_parse(cfg_parser_t* st)
{
int ret;
cfg_token_t t;
cfg_option_t* opt;
while(1) {
ret = cfg_get_token(&t, st, 0);
if (ret < 0) return ret;
if (ret > 0) break;
switch(t.type) {
case CFG_TOKEN_ALPHA:
/* Lookup the option name */
if ((opt = cfg_lookup_token(st->options, &t.val)) == NULL) {
ERR("%s:%d:%d: Unsupported option '%.*s'\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
st->cur_opt = &t;
if (process_option(st, opt) < 0) { st->cur_opt = 0; return -1; }
st->cur_opt = 0;
break;
case '[':
if (st->section.parser == NULL) {
ERR("%s:%d:%d: Syntax error\n", st->file,
t.start.line, t.start.col);
return -1;
}
if (st->section.parser(st->section.param, st, 0) < 0) return -1;
break;
/* Empty line */
case '\n': continue;
default:
ERR("%s:%d:%d: Syntax error\n",
st->file, t.start.line, t.start.col);
return -1;
}
}
return 0;
}
cfg_option_t* cfg_lookup_token(cfg_option_t* table, str* token)
{
int len, i;
int (*cmp)(const char* s1, const char* s2, size_t n) = NULL;
if (table == NULL) return NULL;
for(i = 0; table[i].name; i++) {
len = strlen(table[i].name);
if (table[i].flags & CFG_PREFIX) {
if (token->len < len) continue;
} else {
if (token->len != len) continue;
}
if (table[i].flags & CFG_CASE_SENSITIVE) cmp = strncmp;
else cmp = strncasecmp;
if (cmp(token->s, table[i].name, len)) continue;
return table + i;
}
if (table[i].flags & CFG_DEFAULT) {
return table + i;
}
return NULL;
}
int cfg_eat_equal(cfg_parser_t* st, unsigned int flags)
{
cfg_token_t t;
int ret;
ret = cfg_get_token(&t, st, flags);
if (ret < 0) return ret;
if (ret > 0) {
ERR("%s:%d:%d: Delimiter '=' missing\n",
st->file, st->line, st->col);
return ret;
}
if (t.type != '=') {
ERR("%s:%d:%d: Syntax error, '=' expected\n",
st->file, t.start.line, t.start.col);
return -1;
}
return 0;
}
int cfg_eat_eol(cfg_parser_t* st, unsigned int flags)
{
cfg_token_t t;
int ret;
/* Skip EOL */
ret = cfg_get_token(&t, st, 0);
if (ret < 0) return ret;
if (ret > 0) return 0;
if (t.type != '\n') {
ERR("%s:%d:%d: End of line expected\n",
st->file, t.start.line, t.start.col);
return -1;
}
return 0;
}
int cfg_parse_enum(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret;
cfg_token_t t;
cfg_option_t* values, *val;
values = (cfg_option_t*)param;
ret = cfg_get_token(&t, st, flags);
if (ret != 0) return ret;
if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
ERR("%s:%d:%d: Invalid enum value '%.*s'\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if (values) {
if ((val = cfg_lookup_token(values, &t.val)) == NULL) {
ERR("%s:%d:%d Unsupported enum value '%.*s'\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
return process_option(st, val);
} else {
return 0;
}
}
int cfg_parse_enum_opt(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret;
if (cfg_eat_equal(st, flags)) return -1;
ret = cfg_parse_enum(param, st, CFG_EXTENDED_ALPHA | flags);
if (ret > 0) {
ERR("%s:%d:%d: Option value missing\n",
st->file, st->line, st->col);
return ret;
} else if (ret < 0) return ret;
if (cfg_eat_eol(st, flags)) return -1;
return 0;
}
int cfg_parse_str(void* param, cfg_parser_t* st, unsigned int flags)
{
str* val;
int ret;
char* buf;
cfg_token_t t;
ret = cfg_get_token(&t, st, flags);
if (ret != 0) return ret;
if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
ERR("%s:%d:%d: Invalid string value '%.*s', a string expected.\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if (!param) return 0;
val = (str*)param;
if (flags & CFG_STR_STATIC) {
if (!val->s || val->len <= t.val.len) {
ERR("%s:%d:%d: Destination string buffer too small\n",
st->file, t.start.line, t.start.col);
return -1;
}
buf = val->s;
} else if (flags & CFG_STR_SHMMEM) {
if ((buf = shm_malloc(t.val.len + 1)) == NULL) {
ERR("%s:%d:%d: Out of shared memory\n", st->file,
t.start.line, t.start.col);
return -1;
}
if (val->s) shm_free(val->s);
} else if (flags & CFG_STR_MALLOC) {
if ((buf = malloc(t.val.len + 1)) == NULL) {
ERR("%s:%d:%d: Out of malloc memory\n", st->file,
t.start.line, t.start.col);
return -1;
}
if (val->s) free(val->s);
} else if (flags & CFG_STR_PKGMEM) {
if ((buf = pkg_malloc(t.val.len + 1)) == NULL) {
ERR("%s:%d:%d: Out of private memory\n", st->file,
t.start.line, t.start.col);
return -1;
}
if (val->s) pkg_free(val->s);
} else {
*val = t.val;
return 0;
}
memcpy(buf, t.val.s, t.val.len);
buf[t.val.len] = '\0';
val->s = buf;
val->len = t.val.len;
return 0;
}
int cfg_parse_str_opt(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret;
if (cfg_eat_equal(st, flags)) return -1;
ret = cfg_parse_str(param, st, flags | CFG_EXTENDED_ALPHA);
if (ret > 0) {
ERR("%s:%d:%d: Option value missing\n",
st->file, st->line, st->col);
} else if (ret < 0) return ret;
if (cfg_eat_eol(st, flags)) return -1;
return 0;
}
int cfg_parse_int(void* param, cfg_parser_t* st, unsigned int flags)
{
int* val;
int ret, tmp;
cfg_token_t t;
val = (int*)param;
ret = cfg_get_token(&t, st, flags);
if (ret != 0) return ret;
if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
ERR("%s:%d:%d: Invalid integer value '%.*s'\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if (str2sint(&t.val, &tmp) < 0) {
ERR("%s:%d:%d: Invalid integer value '%.*s'\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if (val) *val = tmp;
return 0;
}
int cfg_parse_int_opt(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret;
if (cfg_eat_equal(st, flags)) return -1;
ret = cfg_parse_int(param, st, flags);
if (ret > 0) {
ERR("%s:%d:%d: Option value missing\n",
st->file, st->line, st->col);
} else if (ret < 0) return ret;
if (cfg_eat_eol(st, flags)) return -1;
return 0;
}
int cfg_parse_bool(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret, *val;
cfg_token_t t;
cfg_option_t* map;
val = (int*)param;
ret = cfg_get_token(&t, st, flags);
if (ret != 0) return ret;
if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if ((map = cfg_lookup_token(cfg_bool_values, &t.val)) == NULL) {
ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n",
st->file, t.start.line, t.start.col, STR_FMT(&t.val));
return -1;
}
if (val) *val = map->val;
return 0;
}
int cfg_parse_bool_opt(void* param, cfg_parser_t* st, unsigned int flags)
{
int ret;
if (cfg_eat_equal(st, flags)) return -1;
ret = cfg_parse_bool(param, st, CFG_EXTENDED_ALPHA | flags);
if (ret > 0) {
ERR("%s:%d:%d: Option value missing\n",
st->file, st->line, st->col);
} else if (ret < 0) return ret;
if (cfg_eat_eol(st, flags)) return -1;
return 0;
}