|
@@ -0,0 +1,964 @@
|
|
|
+/*
|
|
|
+ * $Id$
|
|
|
+ * Standalone Configuration File Parser
|
|
|
+ *
|
|
|
+ * Copyright (C) 2008 iptelorg GmbH
|
|
|
+ * Written by Jan Janak <[email protected]>
|
|
|
+ *
|
|
|
+ * 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
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 1: Options without values
|
|
|
+ *
|
|
|
+ * 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 (cfg_parse(parser) < 0) {
|
|
|
+ * ERR("Error while parsing configuration file\n");
|
|
|
+ * cfg_parser_close(parser);
|
|
|
+ * return -1;
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * cfg_parser_close(parser);
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 2: Options with integer values
|
|
|
+ * 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}
|
|
|
+ * };
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 3: Enum options
|
|
|
+ * 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}
|
|
|
+ * };
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 4: Options with string values
|
|
|
+ * 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
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 5: Custom value parsing
|
|
|
+ * TBD
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 6: Parsing Sections
|
|
|
+ * TBD
|
|
|
+ */
|
|
|
+
|
|
|
+/** Example 7: Default Options
|
|
|
+ * TBD
|
|
|
+ */
|
|
|
+
|
|
|
+/** 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.
|
|
|
+ */
|
|
|
+
|
|
|
+/** Run-time Allocated Destination Variables
|
|
|
+ * - 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).
|
|
|
+ */
|
|
|
+
|
|
|
+/** Built-in parsing functions
|
|
|
+ * *_val functions parse the whole option body (including =)
|
|
|
+ */
|
|
|
+
|
|
|
+#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>
|
|
|
+
|
|
|
+
|
|
|
+/* 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 */
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/* Test for alphanumeric characters */
|
|
|
+#define IS_ALPHA(c) \
|
|
|
+ (((c) >= 'a' && (c) <= 'z') || \
|
|
|
+ ((c) >= 'A' && (c) <= 'Z') || \
|
|
|
+ ((c) >= '0' && (c) <= '9') || \
|
|
|
+ (c) == '_')
|
|
|
+
|
|
|
+
|
|
|
+/* 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) == '\'')
|
|
|
+
|
|
|
+
|
|
|
+/* 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 */
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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);
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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 1;
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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)
|
|
|
+{
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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 0;
|
|
|
+
|
|
|
+ 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;
|
|
|
+ str* name;
|
|
|
+
|
|
|
+ name = (str*)param;
|
|
|
+ if (name == NULL) {
|
|
|
+ BUG("cfg_parser: Invalid parameter value to cfg_parse_section\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, 0);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Section name missing.\n", st->file, st->line, st->col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.type != CFG_TOKEN_ALPHA) {
|
|
|
+ ERR("%s:%d:%d: Invalid table name %d:'%.*s'\n",
|
|
|
+ st->file, t.start.line, t.start.col,
|
|
|
+ t.type, STR_FMT(&t.val));
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((name->s = as_asciiz(&t.val)) == NULL) {
|
|
|
+ ERR("%s:%d:%d: Out of memory\n", st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ name->len = t.val.len;
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, 0);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ error:
|
|
|
+ if (name->s) pkg_free(name->s);
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+cfg_parser_t* cfg_parser_init(str* filename)
|
|
|
+{
|
|
|
+ cfg_parser_t* st;
|
|
|
+ char* pathname, *base;
|
|
|
+
|
|
|
+ pathname = NULL;
|
|
|
+ st = NULL;
|
|
|
+ base = NULL;
|
|
|
+
|
|
|
+ if ((pathname = get_abs_pathname(NULL, filename)) == NULL) {
|
|
|
+ ERR("cfg_parser: Error while converting %.*s to absolute pathname\n",
|
|
|
+ STR_FMT(filename));
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ free(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 (pathname) free(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 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;
|
|
|
+
|
|
|
+ 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) return -1;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Skip EOL */
|
|
|
+ ret = cfg_get_token(&t, st, 0);
|
|
|
+ if (ret <= 0) return ret;
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+cfg_option_t* cfg_lookup_token(cfg_option_t* table, str* token)
|
|
|
+{
|
|
|
+ int len, i;
|
|
|
+ int (*cmp)(const char* s12, 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)
|
|
|
+{
|
|
|
+ cfg_token_t t;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, 0);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, st->line, st->col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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_parse_enum_val(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;
|
|
|
+
|
|
|
+ if (cfg_eat_equal(st)) return -1;
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, st->line, st->col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
|
|
|
+ ERR("%s:%d:%d: Invalid option 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 option 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_str_val(void* param, cfg_parser_t* st, unsigned int flags)
|
|
|
+{
|
|
|
+ str* val;
|
|
|
+ int ret;
|
|
|
+ char* buf;
|
|
|
+ cfg_token_t t;
|
|
|
+
|
|
|
+ if (cfg_eat_equal(st)) return -1;
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
|
|
|
+ ERR("%s:%d:%d: Invalid option value '%.*s'\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_int_val(void* param, cfg_parser_t* st, unsigned int flags)
|
|
|
+{
|
|
|
+ int* val;
|
|
|
+ int ret, tmp;
|
|
|
+ cfg_token_t t;
|
|
|
+
|
|
|
+ val = (int*)param;
|
|
|
+
|
|
|
+ if (cfg_eat_equal(st)) return -1;
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
|
|
|
+ ERR("%s:%d:%d: Invalid option value '%.*s', integer expected\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_bool_val(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, 0);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.type != '=') {
|
|
|
+ ERR("%s:%d:%d: Syntax error, '=' expected\n",
|
|
|
+ st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
|
|
|
+ if (ret < 0) return -1;
|
|
|
+ if (ret == 0) {
|
|
|
+ ERR("%s:%d:%d: Option value missing\n",
|
|
|
+ st->file, t.start.line, t.start.col);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+}
|