From f78098afd8597c883b9e411d552ebc476c7daeb0 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 20 Nov 2012 18:09:19 +1030 Subject: [PATCH] Prototype code for new config parser --- config.h | 63 +++++++++ config_schema.h | 16 +++ config_test.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 440 insertions(+) create mode 100644 config.h create mode 100644 config_schema.h create mode 100644 config_test.c diff --git a/config.h b/config.h new file mode 100644 index 00000000..69fb5306 --- /dev/null +++ b/config.h @@ -0,0 +1,63 @@ +typedef unsigned long debugflags_t; + +struct config_node { + const char *source; // = parse_config() 'source' arg + unsigned int line_number; + const char *fullkey; // malloc() + const char *key; // points inside fullkey, do not free() + const char *text; // malloc() + size_t nodc; + struct config_node *nodv[10]; // malloc() +}; + +struct config_node *parse_config(const char *source, const char *buf, size_t len); +int get_child(const struct config_node *parent, const char *key); +void unsupported_node(const struct config_node *node); +void unsupported_tree(const struct config_node *node); + +int opt_boolean(int *booleanp, const struct config_node *node); +int opt_absolute_path(const char **pathp, const struct config_node *node); +void opt_debugflags(debugflags_t *flagsp, const struct config_node *node); + +// Generate value structs, struct config_SECTION +#define CONFIG_SECTION(__sect) struct config_##__sect { +#define CONFIG_ITEM(__name, __type, __default, __parser, __comment) __type __name; +#define CONFIG_STRUCT(__name, __sect) struct config_##__sect __name; +#define CONFIG_SECTION_END }; +#include "config_schema.h" +#undef CONFIG_SECTION +#undef CONFIG_ITEM +#undef CONFIG_STRUCT +#undef CONFIG_SECTION_END + +// Generate default functions, dfl_config_SECTION() +#define CONFIG_SECTION(__sect) \ + void dfl_config_##__sect(struct config_##__sect *s) { +#define CONFIG_ITEM(__name, __type, __default, __parser, __comment) s->__name = __default; +#define CONFIG_STRUCT(__name, __sect) dfl_config_##__sect(&s->__name); +#define CONFIG_SECTION_END } +#include "config_schema.h" +#undef CONFIG_SECTION +#undef CONFIG_ITEM +#undef CONFIG_STRUCT +#undef CONFIG_SECTION_END + +// Generate parsing functions, opt_config_SECTION() +#define CONFIG_SECTION(__sect) \ + void opt_config_##__sect(struct config_##__sect *s, const struct config_node *node) { \ + int i; \ + char used[node->nodc]; \ + for (i = 0; i < node->nodc; ++i) used[i] = 0; +#define CONFIG_ITEM(__name, __type, __default, __parser, __comment) \ + if ((i = get_child(node, #__name)) != -1) { used[i] = 1; __parser(&s->__name, node->nodv[i]); } +#define CONFIG_STRUCT(__name, __sect) \ + if ((i = get_child(node, #__name)) != -1) { used[i] = 1; opt_config_##__sect(&s->__name, node->nodv[i]); } +#define CONFIG_SECTION_END \ + for (i = 0; i < node->nodc; ++i) { if (!used[i]) unsupported_tree(node->nodv[i]); } \ + } +#include "config_schema.h" +#undef CONFIG_SECTION +#undef CONFIG_ITEM +#undef CONFIG_STRUCT +#undef CONFIG_SECTION_END + diff --git a/config_schema.h b/config_schema.h new file mode 100644 index 00000000..3ca56af2 --- /dev/null +++ b/config_schema.h @@ -0,0 +1,16 @@ +CONFIG_SECTION( log) +CONFIG_ITEM( file, const char *, NULL, opt_absolute_path, "Absolute path of log file") +CONFIG_ITEM( show_pid, int, 1, opt_boolean, "If true, all log lines contain PID of logging process") +CONFIG_ITEM( show_time, int, 1, opt_boolean, "If true, all log lines contain time stamp") +CONFIG_SECTION_END + +CONFIG_SECTION( rhizome) +CONFIG_ITEM( path, const char *, NULL, opt_absolute_path, "Absolute path of rhizome directory") +CONFIG_ITEM( enabled, int, 1, opt_boolean, "If true, Rhizome HTTP server is started") +CONFIG_SECTION_END + +CONFIG_SECTION( main) +CONFIG_STRUCT( log, log) +CONFIG_ITEM( debug, debugflags_t, 0, opt_debugflags, "Debug flags") +CONFIG_STRUCT( rhizome, rhizome) +CONFIG_SECTION_END diff --git a/config_test.c b/config_test.c new file mode 100644 index 00000000..206d46dd --- /dev/null +++ b/config_test.c @@ -0,0 +1,361 @@ +#include +#include +#include +#include +#include +#include + +#include "str.h" +#include "strbuf_helpers.h" +#include "config.h" + +#define NELS(a) (sizeof (a) / sizeof *(a)) +#define DEBUGF(F,...) fprintf(stderr, "DEBUG: " F "\n", ##__VA_ARGS__) +#define WARNF(F,...) fprintf(stderr, "WARN: " F "\n", ##__VA_ARGS__) +#define WHYF(F,...) fprintf(stderr, "ERROR: " F "\n", ##__VA_ARGS__) +#define WHYF_perror(F,...) fprintf(stderr, "ERROR: " F ": %s [errno=%d]\n", ##__VA_ARGS__, strerror(errno), errno) + +struct config_main config; +struct config_main default_config; + +const char *find_keyend(const char *const fullkey, const char *const fullkeyend) +{ + const char *s; + for (s = fullkey; s < fullkeyend && (isalnum(*s) || *s == '_'); ++s) + ; + if (s == fullkey || (s < fullkeyend && *s != '.')) + return NULL; + return s; +} + +char *strn_malloc(const char *str, size_t len) +{ + char *new = malloc(len + 1); + if (!new) { + WHYF_perror("malloc(%lu)", (long)len + 1); + return NULL; + } + strncpy(new, str, len); + new[len] = '\0'; + return new; +} + +char *str_malloc(const char *str) +{ + return strn_malloc(str, strlen(str)); +} + +int make_child(struct config_node **const parentp, const char *const key, size_t keylen) +{ + // TODO: search using binary chop and insert in key lexical order. + int i; + struct config_node *child; + for (i = 0; i < (*parentp)->nodc; ++i) { + child = (*parentp)->nodv[i]; + if (strncmp(child->key, key, keylen) == 0 && child->key[keylen] == '\0') + return i; + } + child = (struct config_node *) calloc(1, sizeof *child); + if (child == NULL) { + WHYF_perror("calloc(1, %u)", sizeof(struct config_node)); + return -1; + } + i = (*parentp)->nodc++; + if ((*parentp)->nodc > NELS((*parentp)->nodv)) + *parentp = realloc(*parentp, sizeof(**parentp) + sizeof((*parentp)->nodv[0]) * ((*parentp)->nodc - NELS((*parentp)->nodv))); + (*parentp)->nodv[i] = child; + return i; +} + +struct config_node *parse_config(const char *source, const char *buf, size_t len) +{ + struct config_node *root = calloc(1, sizeof(struct config_node)); + const char *end = buf + len; + const char *line = buf; + const char *nextline; + unsigned lineno = 1; + 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)); + 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 -- ignored", source, lineno); + continue; + } + struct config_node **parentp = &root; + const char *fullkey = line; + const char *fullkeyend = p; + const char *key = fullkey; + const char *keyend = NULL; + int nodi; + struct config_node *node = NULL; + while (key < fullkeyend && (keyend = find_keyend(key, fullkeyend)) && (nodi = make_child(parentp, key, keyend - key)) != -1) { + node = (*parentp)->nodv[nodi]; + if (node->text) { + WARNF("%s:%u: duplicate configuration option %s -- ignored (original is at %s:%u)", + source, lineno, alloca_toprint(-1, fullkey, fullkeyend - fullkey), + node->source, node->line_number + ); + break; + } + if (node->line_number == 0) { + node->source = source; + node->line_number = lineno; + } + if (!node->fullkey) { + if (!(node->fullkey = strn_malloc(fullkey, keyend - fullkey))) + break; // out of memory + node->key = node->fullkey + (key - fullkey); + } + node->text = NULL; + key = keyend + 1; + parentp = &(*parentp)->nodv[nodi]; + } + if (keyend == NULL) { + WARNF("%s:%u: malformed configuration option %s -- ignored", + source, lineno, alloca_toprint(-1, fullkey, fullkeyend - fullkey) + ); + break; + } + if (nodi == -1) + break; // out of memory + for (++p; p < lend && isspace(*p); ++p) + ; + if (!(node->text = strn_malloc(p, lend - p))) + break; // out of memory + } + return root; +} + +void free_config_node(struct config_node *node) +{ + while (node->nodc) + free_config_node(node->nodv[--node->nodc]); + if (node->fullkey) { + free((char *)node->fullkey); + node->fullkey = node->key = NULL; + } + if (node->text) { + free((char *)node->text); + node->text = NULL; + } + free(node); +} + +void dump_config_node(const struct config_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, + node->fullkey ? alloca_str_toprint(node->fullkey) : "NULL", + node->key ? alloca_str_toprint(node->key) : "NULL", + node->text ? alloca_str_toprint(node->text) : "NULL" + ); + int i; + for (i = 0; i < node->nodc; ++i) + dump_config_node(node->nodv[i], indent + 1); + } +} + +int get_child(const struct config_node *parent, const char *key) +{ + int i; + for (i = 0; i < parent->nodc; ++i) + if (strcmp(parent->nodv[i]->key, key) == 0) + return i; + return -1; +} + +void invalid_text(const struct config_node *node) +{ + WARNF("%s:%u: invalid configuration option %s=%s -- ignored", + node->source, node->line_number, + alloca_str_toprint(node->fullkey), + alloca_str_toprint(node->text) + ); +} + +void unsupported_node(const struct config_node *node) +{ + WARNF("%s:%u: unsupported configuration option %s -- ignored", + node->source, node->line_number, alloca_str_toprint(node->fullkey) + ); +} + +void unsupported_children(const struct config_node *parent) +{ + int i; + for (i = 0; i < parent->nodc; ++i) + unsupported_tree(parent->nodv[i]); +} + +void unsupported_tree(const struct config_node *node) +{ + if (node->text) + unsupported_node(node); + unsupported_children(node); +} + +void unused_config_node(const struct config_node *node) +{ + if (node->text) + unsupported_node(node); + int i; + for (i = 0; i < node->nodc; ++i) + unused_config_node(node->nodv[i]); +} + +int opt_boolean(int *booleanp, const struct config_node *node) +{ + unsupported_children(node); + if (node->text) { + if (!strcasecmp(node->text, "true") || !strcasecmp(node->text, "yes") || !strcasecmp(node->text, "on") || !strcasecmp(node->text, "1")) + return (*booleanp = 1); + else if (!strcasecmp(node->text, "false") || !strcasecmp(node->text, "no") || !strcasecmp(node->text, "off") || !strcasecmp(node->text, "0")) + return (*booleanp = 0); + else + invalid_text(node); + } + return -1; +} + +int opt_absolute_path(const char **pathp, const struct config_node *node) +{ + DEBUGF("%s", __FUNCTION__); + dump_config_node(node, 1); + unsupported_children(node); + if (node->text) { + if (node->text[0] != '/') + invalid_text(node); + else { + if (*pathp) + free((char *) *pathp); + if ((*pathp = str_malloc(node->text))) + return 0; + } + } + return -1; +} + +debugflags_t debugFlagMask(const char *flagname) +{ + if (!strcasecmp(flagname,"all")) return ~0; + else if (!strcasecmp(flagname,"interfaces")) return 1 << 0; + else if (!strcasecmp(flagname,"rx")) return 1 << 1; + else if (!strcasecmp(flagname,"tx")) return 1 << 2; + else if (!strcasecmp(flagname,"verbose")) return 1 << 3; + else if (!strcasecmp(flagname,"verbio")) return 1 << 4; + else if (!strcasecmp(flagname,"peers")) return 1 << 5; + else if (!strcasecmp(flagname,"dnaresponses")) return 1 << 6; + else if (!strcasecmp(flagname,"dnahelper")) return 1 << 7; + else if (!strcasecmp(flagname,"vomp")) return 1 << 8; + else if (!strcasecmp(flagname,"packetformats")) return 1 << 9; + else if (!strcasecmp(flagname,"packetconstruction")) return 1 << 10; + else if (!strcasecmp(flagname,"gateway")) return 1 << 11; + else if (!strcasecmp(flagname,"keyring")) return 1 << 12; + else if (!strcasecmp(flagname,"sockio")) return 1 << 13; + else if (!strcasecmp(flagname,"frames")) return 1 << 14; + else if (!strcasecmp(flagname,"abbreviations")) return 1 << 15; + else if (!strcasecmp(flagname,"routing")) return 1 << 16; + else if (!strcasecmp(flagname,"security")) return 1 << 17; + else if (!strcasecmp(flagname,"rhizome")) return 1 << 18; + else if (!strcasecmp(flagname,"rhizometx")) return 1 << 19; + else if (!strcasecmp(flagname,"rhizomerx")) return 1 << 20; + else if (!strcasecmp(flagname,"rhizomeads")) return 1 << 21; + else if (!strcasecmp(flagname,"monitorroutes")) return 1 << 22; + else if (!strcasecmp(flagname,"queues")) return 1 << 23; + else if (!strcasecmp(flagname,"broadcasts")) return 1 << 24; + else if (!strcasecmp(flagname,"manifests")) return 1 << 25; + else if (!strcasecmp(flagname,"mdprequests")) return 1 << 26; + else if (!strcasecmp(flagname,"timing")) return 1 << 27; + return 0; +} + +void opt_debugflags(debugflags_t *flagsp, const struct config_node *node) +{ + DEBUGF("%s", __FUNCTION__); + dump_config_node(node, 1); + if (node->text) + unsupported_tree(node); + debugflags_t setmask = 0; + debugflags_t clearmask = 0; + int setall = 0; + int clearall = 0; + int i; + for (i = 0; i < node->nodc; ++i) { + const struct config_node *child = node->nodv[i]; + debugflags_t mask = debugFlagMask(child->key); + int flag = -1; + if (!mask) + unsupported_tree(child); + else if (opt_boolean(&flag, child) != -1) { + if (mask == ~0) { + if (flag) + setall = 1; + else + clearall = 1; + } else { + if (flag) + setmask |= mask; + else + clearmask |= mask; + } + } + } + if (setall) + *flagsp = ~0; + else if (clearall) + *flagsp = 0; + *flagsp &= ~clearmask; + *flagsp |= setmask; +} + +int main(int argc, char **argv) +{ + int i; + for (i = 1; i < argc; ++i) { + int fd = open(argv[i], O_RDONLY); + if (fd == -1) { + perror("open"); + exit(1); + } + struct stat st; + fstat(fd, &st); + char *buf = malloc(st.st_size); + if (!buf) { + perror("malloc"); + exit(1); + } + if (read(fd, buf, st.st_size) != st.st_size) { + perror("read"); + exit(1); + } + struct config_node *root = parse_config(argv[i], buf, st.st_size); + close(fd); + //dump_config_node(root, 0); + struct config_main config; + dfl_config_main(&config); + opt_config_main(&config, root); + free_config_node(root); + free(buf); + DEBUGF("config.log.file = %s", config.log.file ? alloca_str_toprint(config.log.file) : "NULL"); + DEBUGF("config.log.show_pid = %d", config.log.show_pid); + DEBUGF("config.log.show_time = %d", config.log.show_time); + DEBUGF("config.debug = %llx", (unsigned long long) config.debug); + } + exit(0); +}