/* Serval DNA configuration Copyright (C) 2012 Serval Project Inc. This program 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. This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "mem.h" #include "str.h" #include "strbuf.h" #include "log.h" #include "conf.h" static const char *cf_find_keyend(const char *const key, const char *const fullkeyend) { const char *s = key; if (s < fullkeyend && (isalpha(*s) || *s == '_')) ++s; while (s < fullkeyend && (isalnum(*s) || *s == '_')) ++s; if (s == key || (s < fullkeyend && *s != '.')) return NULL; return s; } static const char *cf_find_keypattern_end(const char *const key, const char *const fullkeyend) { const char *s = cf_find_keyend(key, fullkeyend); if (s == NULL) { s = key; if (s < fullkeyend && *s == '*') ++s; if (s + 1 == fullkeyend && *s == '*') ++s; if (s == key || (s < fullkeyend && *s != '.')) return NULL; } return s; } /* This predicate function defines the constraints on configuration option names. * * OPTION_NAME ::= ( KEY "." )* LASTKEY * KEY ::= ( ALPHA | "_") ( ALPHANUM | "_" )* * LASTKEY ::= KEY * ALPHA ::= "A" .. "Z" | "a" .. "z" * ALPHANUM ::= ALPHA | "0" .. "9" * * Valid examples: * foo * foo.bar * foo.bar.chow * _word * word1 * word_1 * Invalid: * foo. * .foo * 1foo * foo.bar. * 12 * 1.2.3 * foo bar * @author Andrew Bettison */ int is_configvarname(const char *text) { const char *const textend = text + strlen(text); const char *key = text; const char *keyend = NULL; while (key <= textend && (keyend = cf_find_keyend(key, textend)) != NULL) key = keyend + 1; return keyend != NULL; } /* This predicate function defines the constraints on configuration option patterns. * Similar to is_configvarname(). * * OPTION_PATTERN ::= ( KEY_PATTERN "." )* LASTKEY_PATTERN * KEY_PATTERN ::= "*" | KEY * LASTKEY_PATTERN ::= "**" | KEY_PATTERN * * @author Andrew Bettison */ int is_configvarpattern(const char *text) { const char *const textend = text + strlen(text); const char *key = text; const char *keyend = NULL; while (key <= textend && (keyend = cf_find_keypattern_end(key, textend)) != NULL) key = keyend + 1; return keyend != NULL; } static int cf_om_make_child(struct cf_om_node **const parentp, const char *const fullkey, const char *const key, const char *const keyend) { // Allocate parent node if it is not present. if (!*parentp && (*parentp = emalloc_zero(sizeof **parentp)) == NULL) return -1; size_t keylen = keyend - key; unsigned i = 0; struct cf_om_node *child; if ((*parentp)->nodc) { // Binary search for matching child. int m = 0; int n = (*parentp)->nodc - 1; int c; do { i = (m + n) / 2; child = (*parentp)->nodv[i]; c = strncmp(key, child->key, keylen); if (c == 0 && child->key[keylen]) c = -1; //DEBUGF(" m=%u n=%u i=%u child->key=%s c=%d", m, n, i, alloca_str_toprint(child->key), c); if (c == 0) { //DEBUGF(" found i=%u", i); return i; } if (c > 0) m = ++i; else n = i - 1; } while (m <= n); } // At this point, i is the index where a new child should be inserted. assert(i <= (*parentp)->nodc); if ((child = emalloc_zero(sizeof *child)) == NULL) return -1; if (!(child->fullkey = strn_edup(fullkey, keyend - fullkey))) { free(child); return -1; } child->key = child->fullkey + (key - fullkey); ++(*parentp)->nodc; if ((*parentp)->nodc > NELS((*parentp)->nodv)) *parentp = realloc(*parentp, sizeof(**parentp) + sizeof((*parentp)->nodv[0]) * ((*parentp)->nodc - NELS((*parentp)->nodv))); unsigned j; for (j = (*parentp)->nodc - 1; j > i; --j) (*parentp)->nodv[j] = (*parentp)->nodv[j-1]; (*parentp)->nodv[i] = child; //DEBUGF(" insert i=%u", i); return i; } int cf_om_add_child(struct cf_om_node **const parentp, const char *const key) { size_t parent_fullkey_len = (parentp && *parentp && (*parentp)->fullkey) ? strlen((*parentp)->fullkey) : 0; size_t fullkey_len = parent_fullkey_len + 1 + strlen(key); char fullkey[fullkey_len + 1]; char *pkey = fullkey; if (parent_fullkey_len) { strcpy(fullkey, (*parentp)->fullkey); pkey = fullkey + parent_fullkey_len; *pkey++ = '.'; } strcpy(pkey, key); return cf_om_make_child(parentp, fullkey, pkey, fullkey + fullkey_len); } int cf_om_get_child(const struct cf_om_node *parent, const char *key, const char *keyend) { if (keyend == NULL) keyend = key + strlen(key); // TODO: use binary search, since child nodes are already sorted by key unsigned i; for (i = 0; i < parent->nodc; ++i) if (memcmp(parent->nodv[i]->key, key, keyend - key) == 0 && parent->nodv[i]->key[keyend - key] == '\0') return i; return -1; } int cf_om_remove_null_child(struct cf_om_node **parentp, unsigned n) { assert(n < (*parentp)->nodc); if ((*parentp)->nodv[n] == NULL) { cf_om_remove_child(parentp, n); return 1; } return 0; } int cf_om_remove_empty_child(struct cf_om_node **parentp, unsigned n) { assert(n < (*parentp)->nodc); if ((*parentp)->nodv[n] && (*parentp)->nodv[n]->text == NULL && (*parentp)->nodv[n]->nodc == 0) { cf_om_remove_child(parentp, n); return 1; } return 0; } void cf_om_remove_child(struct cf_om_node **parentp, unsigned n) { assert(n < (*parentp)->nodc); cf_om_free_node(&(*parentp)->nodv[n]); --(*parentp)->nodc; for (; n < (*parentp)->nodc; ++n) (*parentp)->nodv[n] = (*parentp)->nodv[n+1]; } int cf_om_parse(const char *source, const char *buf, size_t len, struct cf_om_node **rootp) { const char *end = buf + len; const char *line = buf; const char *nextline; unsigned lineno = 1; int result = CFEMPTY; for (lineno = 1; line < end; line = nextline, ++lineno) { const char *lend = line; while (lend < end && *lend != '\n') ++lend; nextline = lend + 1; if (lend > line && lend[-1] == '\r') --lend; //DEBUGF("lineno=%u %s", lineno, alloca_toprint(-1, line, lend - line)); if (line[0] == '#') continue; // skip comment lines const char *p; for (p = line; p < lend && isspace(*p); ++p) ; if (p == lend) continue; // skip empty and blank lines for (p = line; p < lend && *p != '='; ++p) ; if (p == line || p == lend) { WARNF("%s:%u: malformed configuration line", source, lineno); result |= CFINVALID; continue; } struct cf_om_node **nodep = rootp; const char *fullkey = line; const char *fullkeyend = p; const char *key = fullkey; const char *keyend = NULL; int nodi = -1; while (key <= fullkeyend && (keyend = cf_find_keyend(key, fullkeyend)) && (nodi = cf_om_make_child(nodep, fullkey, key, keyend)) != -1) { key = keyend + 1; nodep = &(*nodep)->nodv[nodi]; } if (keyend == NULL) { WARNF("%s:%u: malformed configuration option %s", source, lineno, alloca_toprint(-1, fullkey, fullkeyend - fullkey) ); result |= CFINVALID; continue; } if (nodi == -1) return CFERROR; // out of memory struct cf_om_node *node = *nodep; if (node->text) { WARNF("%s:%u: duplicate configuration option %s (original is at %s:%u)", source, lineno, alloca_toprint(-1, fullkey, fullkeyend - fullkey), node->source, node->line_number ); result |= CFDUPLICATE; continue; } ++p; if (!(node->text = strn_edup(p, lend - p))) return CFERROR; // out of memory node->source = source; node->line_number = lineno; result &= ~CFEMPTY; } return result; } void cf_om_free_node(struct cf_om_node **nodep) { if (*nodep) { //DEBUGF("%s text=%s nodc=%d", (*nodep)->fullkey, alloca_str_toprint((*nodep)->text), (*nodep)->nodc); while ((*nodep)->nodc) cf_om_free_node(&(*nodep)->nodv[--(*nodep)->nodc]); if ((*nodep)->fullkey) { free((char *)(*nodep)->fullkey); (*nodep)->fullkey = (*nodep)->key = NULL; } if ((*nodep)->text) { free((char *)(*nodep)->text); (*nodep)->text = NULL; } free(*nodep); *nodep = NULL; } } void cf_om_dump_node(const struct cf_om_node *node, int indent) { if (node == NULL) DEBUGF("%*sNULL", indent * 3, ""); else { DEBUGF("%*s%s:%u fullkey=%s key=%s text=%s", indent * 3, "", node->source ? node->source : "NULL", node->line_number, alloca_str_toprint(node->fullkey), alloca_str_toprint(node->key), alloca_str_toprint(node->text) ); unsigned i; for (i = 0; i < node->nodc; ++i) cf_om_dump_node(node->nodv[i], indent + 1); } } int cf_om_match(const char *pattern, const struct cf_om_node *node) { if (node == NULL) { //DEBUGF("pattern='%s' node=NULL", pattern); return 0; } if (node->fullkey == NULL) { //DEBUGF("pattern='%s' node->fullkey=NULL", pattern); return 0; } /* DEBUGF("pattern='%s' node->fullkey=%s node->nodc=%d node->text=%s", pattern, alloca_str_toprint(node->fullkey), node->nodc, alloca_str_toprint(node->text) ); */ if (!pattern[0]) return -1; const char *const pattern_end = pattern + strlen(pattern); const char *pat = pattern; const char *key = node->fullkey; const char *const fullkeyend = node->fullkey + strlen(node->fullkey); const char *keyend = NULL; const char *patend = pat; //DEBUGF(" pat=%s key=%s", alloca_str_toprint(pat), alloca_str_toprint(key)); while (pat < pattern_end && key <= fullkeyend && (keyend = cf_find_keyend(key, fullkeyend)) && (patend = cf_find_keypattern_end(pat, pattern_end))) { if (pat[0] == '*') { if (pat[1] == '*') return 1; pat = patend; key = keyend; } else { while (pat < patend && key < fullkeyend && *pat == *key) ++pat, ++key; if (pat != patend || key != keyend) return 0; } if (*pat) ++pat; if (*key) ++key; //DEBUGF(" pat=%s key=%s", alloca_str_toprint(pat), alloca_str_toprint(key)); } //DEBUGF(" patend=%s keyend=%s", alloca_str_toprint(patend), alloca_str_toprint(keyend)); return patend == NULL ? -1 : keyend && keyend == fullkeyend && pat == pattern_end; } const char *cf_om_get(const struct cf_om_node *node, const char *fullkey) { if (node == NULL) return NULL; const char *fullkeyend = fullkey + strlen(fullkey); const char *key = fullkey; const char *keyend = NULL; int nodi = -1; while (key <= fullkeyend && (keyend = cf_find_keyend(key, fullkeyend)) && (nodi = cf_om_get_child(node, key, keyend)) != -1) { key = keyend + 1; node = node->nodv[nodi]; } if (keyend == NULL) { WARNF("malformed configuration option %s", alloca_toprint(-1, fullkey, fullkeyend - fullkey)); return NULL; } if (nodi == -1) return NULL; return node->text; } int cf_om_set(struct cf_om_node **nodep, const char *fullkey, const char *text) { const char *fullkeyend = fullkey + strlen(fullkey); const char *key = fullkey; const char *keyend = NULL; int nodi = -1; while (key <= fullkeyend && (keyend = cf_find_keyend(key, fullkeyend)) && (nodi = cf_om_make_child(nodep, fullkey, key, keyend)) != -1) { key = keyend + 1; nodep = &(*nodep)->nodv[nodi]; } if (keyend == NULL) { WARNF("malformed configuration option %s", alloca_toprint(-1, fullkey, fullkeyend - fullkey)); return CFINVALID; } if (nodi == -1) return CFERROR; // out of memory struct cf_om_node *node = *nodep; free((char *)node->text); if (text == NULL) node->text = NULL; else if (!(node->text = str_edup(text))) return CFERROR; // out of memory return CFOK; } void cf_om_iter_start(struct cf_om_iterator *it, const struct cf_om_node *root) { it->sp = 0; it->stack[0].node = it->node = root; it->stack[0].index = 0; } #if 0 static void cf_om_iter_dump(struct cf_om_iterator *it) { strbuf b = strbuf_alloca(1024); strbuf_sprintf(b, "node=%p sp=%d", it->node, it->sp); int i; for (i = 0; i <= it->sp; ++i) strbuf_sprintf(b, " %p[%d]", it->stack[i].node, it->stack[i].index); DEBUG(strbuf_str(b)); } #endif int cf_om_iter_next(struct cf_om_iterator *it) { //cf_om_iter_dump(it); if (!it->node) return 0; while (1) { const struct cf_om_node *parent = it->stack[it->sp].node; unsigned i = it->stack[it->sp].index++; if (i < parent->nodc) { it->node = parent->nodv[i]; if (it->node == NULL) return WHY("null node"); if (it->sp >= NELS(it->stack)) return WHY("stack overflow"); ++it->sp; it->stack[it->sp].node = it->node; it->stack[it->sp].index = 0; return 0; } else if (it->sp) { --it->sp; } else { it->node = NULL; return 0; } } } void _cf_warn_nodev(struct __sourceloc __whence, const struct cf_om_node *node, const char *key, const char *fmt, va_list ap) { strbuf b = strbuf_alloca(1024); if (node) { if (node->source && node->line_number) strbuf_sprintf(b, "%s:%u: ", node->source, node->line_number); strbuf_puts(b, "configuration option \""); if (node->fullkey && node->fullkey[0]) strbuf_puts(b, node->fullkey); if (key && key[0]) { if (node->fullkey && node->fullkey[0]) strbuf_putc(b, '.'); strbuf_puts(b, key); } strbuf_puts(b, "\" "); } strbuf_vsprintf(b, fmt, ap); WARN(strbuf_str(b)); } void _cf_warn_childrenv(struct __sourceloc __whence, const struct cf_om_node *parent, const char *fmt, va_list ap) { unsigned i; for (i = 0; i < parent->nodc; ++i) { _cf_warn_nodev(__whence, parent->nodv[i], NULL, fmt, ap); _cf_warn_childrenv(__whence, parent->nodv[i], fmt, ap); } } void _cf_warn_node(struct __sourceloc __whence, const struct cf_om_node *node, const char *key, const char *fmt, ...) { va_list ap; va_start(ap, fmt); _cf_warn_nodev(__whence, node, key, fmt, ap); va_end(ap); } void _cf_warn_children(struct __sourceloc __whence, const struct cf_om_node *node, const char *fmt, ...) { va_list ap; va_start(ap, fmt); _cf_warn_childrenv(__whence, node, fmt, ap); va_end(ap); } void _cf_warn_duplicate_node(struct __sourceloc __whence, const struct cf_om_node *parent, const char *key) { _cf_warn_node(__whence, parent, key, "is duplicate"); } void _cf_warn_missing_node(struct __sourceloc __whence, const struct cf_om_node *parent, const char *key) { _cf_warn_node(__whence, parent, key, "is missing"); } void _cf_warn_incompatible(struct __sourceloc __whence, const struct cf_om_node *node, const struct cf_om_node *orig) { assert(node != orig); strbuf b = strbuf_alloca(180); if (orig) { strbuf_sprintf(b, "\"%s\"", orig->fullkey); if (orig->source && orig->line_number) strbuf_sprintf(b, " at %s:%u", orig->source, orig->line_number); } else { strbuf_puts(b, "other option(s)"); } _cf_warn_node(__whence, node, NULL, "is incompatible with %s", strbuf_str(b)); } void _cf_warn_incompatible_children(struct __sourceloc __whence, const struct cf_om_node *parent) { struct cf_om_iterator it; for (cf_om_iter_start(&it, parent); it.node; cf_om_iter_next(&it)) if (it.node != parent && it.node->text) _cf_warn_incompatible(__whence, parent, it.node); } void _cf_warn_unsupported_node(struct __sourceloc __whence, const struct cf_om_node *node) { _cf_warn_node(__whence, node, NULL, "not supported"); } void _cf_warn_unsupported_children(struct __sourceloc __whence, const struct cf_om_node *parent) { unsigned i; for (i = 0; i < parent->nodc; ++i) { if (parent->nodv[i]->text) _cf_warn_unsupported_node(__whence, parent->nodv[i]); _cf_warn_unsupported_children(__whence, parent->nodv[i]); } } strbuf strbuf_cf_flags(strbuf sb, int flags) { if (flags == CFERROR) return strbuf_puts(sb, "CFERROR"); size_t n = strbuf_len(sb); static struct { int flag; const char *name; } flagdefs[] = { { CFEMPTY, "CFEMPTY" }, { CFDUPLICATE, "CFDUPLICATE" }, { CFSTRINGOVERFLOW, "CFSTRINGOVERFLOW" }, { CFARRAYOVERFLOW, "CFARRAYOVERFLOW" }, { CFINCOMPLETE, "CFINCOMPLETE" }, { CFINCOMPATIBLE, "CFINCOMPATIBLE" }, { CFINVALID, "CFINVALID" }, { CFUNSUPPORTED, "CFUNSUPPORTED" }, }; unsigned i; for (i = 0; i < NELS(flagdefs); ++i) { if (flags & flagdefs[i].flag) { if (strbuf_len(sb) != n) strbuf_putc(sb, ' '); strbuf_puts(sb, flagdefs[i].name); flags &= ~flagdefs[i].flag; } } for (i = 0; i < NELS(flagdefs); ++i) { if (flags & CFSUB(flagdefs[i].flag)) { if (strbuf_len(sb) != n) strbuf_putc(sb, ' '); strbuf_puts(sb, "CFSUB("); strbuf_puts(sb, flagdefs[i].name); strbuf_putc(sb, ')'); flags &= ~CFSUB(flagdefs[i].flag); } } if (flags) { if (strbuf_len(sb) != n) strbuf_putc(sb, ' '); strbuf_sprintf(sb, "%#x", flags); } if (strbuf_len(sb) == n) strbuf_puts(sb, "CFOK"); return sb; } strbuf strbuf_cf_flag_reason(strbuf sb, int flags) { if (flags == CFERROR) return strbuf_puts(sb, "unrecoverable error"); size_t n = strbuf_len(sb); static struct { int flag; const char *reason; } flagdefs[] = { { CFEMPTY, "empty" }, { CFDUPLICATE, "duplicate element" }, { CFSTRINGOVERFLOW, "string overflow" }, { CFARRAYOVERFLOW, "array overflow" }, { CFINCOMPLETE, "incomplete" }, { CFINCOMPATIBLE, "incompatible" }, { CFINVALID, "invalid" }, { CFUNSUPPORTED, "not supported" }, { CFSUB(CFEMPTY), "contains empty element" }, { CFSUB(CFDUPLICATE), "contains element with duplicate" }, { CFSUB(CFSTRINGOVERFLOW), "contains string overflow" }, { CFSUB(CFARRAYOVERFLOW), "contains array overflow" }, { CFSUB(CFINCOMPLETE), "contains incomplete element" }, { CFSUB(CFINCOMPATIBLE), "contains incompatible element" }, { CFSUB(CFINVALID), "contains invalid element" }, { CFSUB(CFUNSUPPORTED), "contains unsupported element" }, }; unsigned i; for (i = 0; i < NELS(flagdefs); ++i) { if (flags & flagdefs[i].flag) { if (strbuf_len(sb) != n) strbuf_puts(sb, ", "); strbuf_puts(sb, flagdefs[i].reason); flags &= ~flagdefs[i].flag; } } if (strbuf_len(sb) == n) strbuf_puts(sb, "no reason"); return sb; } void _cf_warn_node_value(struct __sourceloc __whence, const struct cf_om_node *node, int reason) { strbuf b = strbuf_alloca(180); strbuf_cf_flag_reason(b, reason); _cf_warn_node(__whence, node, NULL, "value %s %s", alloca_str_toprint(node->text), strbuf_str(b)); } void _cf_warn_no_array(struct __sourceloc __whence, const struct cf_om_node *node, int reason) { strbuf b = strbuf_alloca(180); strbuf_cf_flag_reason(b, reason); _cf_warn_node(__whence, node, NULL, "array discarded -- %s", strbuf_str(b)); } void _cf_warn_array_key(struct __sourceloc __whence, const struct cf_om_node *node, int reason) { strbuf b = strbuf_alloca(180); strbuf_cf_flag_reason(b, reason); _cf_warn_node(__whence, node, NULL, "array key %s -- %s", alloca_str_toprint(node->key), strbuf_str(b)); } void _cf_warn_array_value(struct __sourceloc __whence, const struct cf_om_node *node, int reason) { strbuf b = strbuf_alloca(180); strbuf_cf_flag_reason(b, reason); if (node->text) _cf_warn_node(__whence, node, NULL, "array value %s -- %s", alloca_str_toprint(node->text), strbuf_str(b)); else _cf_warn_node(__whence, node, NULL, "array element -- %s", strbuf_str(b)); } void _cf_warn_list_overflow(struct __sourceloc __whence, const struct cf_om_node *node) { _cf_warn_node(__whence, node, NULL, "list overflow"); _cf_warn_children(__whence, node, "list overflow"); }