mirror of https://github.com/sipwise/kamailio.git
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.
1043 lines
23 KiB
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;
|
|
}
|