diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | conf-parser.c | 321 | ||||
-rw-r--r-- | conf-parser.h | 33 |
3 files changed, 355 insertions, 1 deletions
@@ -1,7 +1,7 @@ CFLAGS=-Wall -Wextra -O0 -g -pipe -D_GNU_SOURCE -fdiagnostics-show-option -Wno-unused-parameter LIBS=-lrt -systemd: main.o name.o util.o set.o hashmap.o strv.o job.o manager.o +systemd: main.o name.o util.o set.o hashmap.o strv.o job.o manager.o conf-parser.o $(CC) $(CFLAGS) -o $@ $^ $(LIBS) clean: diff --git a/conf-parser.c b/conf-parser.c new file mode 100644 index 0000000000..c45acd7702 --- /dev/null +++ b/conf-parser.c @@ -0,0 +1,321 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <stdlib.h> + +#include "conf-parser.h" +#include "util.h" +#include "macro.h" + +#define WHITESPACE " \t\n" +#define COMMENTS "#;\n" +#define LINE_MAX 4096 + +/* Run the user supplied parser for an assignment */ +static int next_assignment( + const char *filename, + unsigned line, + const char *section, + const ConfigItem *t, + const char *lvalue, + const char *rvalue, + void *userdata) { + + assert(filename); + assert(t); + assert(lvalue); + assert(rvalue); + + for (; t->parse; t++) { + + if (t->lvalue && !streq(lvalue, t->lvalue)) + continue; + + if (t->section && !section) + continue; + + if (t->section && !streq(section, t->section)) + continue; + + return t->parse(filename, line, section, lvalue, rvalue, t->data, userdata); + } + + fprintf(stderr, "[%s:%u] Unknown lvalue '%s' in section '%s'.", filename, line, lvalue, strna(section)); + return -EBADMSG; +} + +/* Returns non-zero when c is contained in s */ +static int in_string(char c, const char *s) { + assert(s); + + for (; *s; s++) + if (*s == c) + return 1; + + return 0; +} + +/* Remove all whitepsapce from the beginning and the end of *s. *s may + * be modified. */ +static char *strip(char *s) { + char *b = s+strspn(s, WHITESPACE); + char *e, *l = NULL; + + for (e = b; *e; e++) + if (!in_string(*e, WHITESPACE)) + l = e; + + if (l) + *(l+1) = 0; + + return b; +} + +/* Parse a variable assignment line */ +static int parse_line(const char *filename, unsigned line, char **section, const ConfigItem *t, char *l, void *userdata) { + char *e, *c, *b; + + b = l+strspn(l, WHITESPACE); + + if ((c = strpbrk(b, COMMENTS))) + *c = 0; + + if (!*b) + return 0; + + if (startswith(b, ".include ")) { + char *path = NULL, *fn; + int r; + + fn = strip(b+9); + if (!is_path_absolute(fn)) { + const char *k; + + if ((k = strrchr(filename, '/'))) { + char *dir; + + if (!(dir = strndup(filename, k-filename))) + return -ENOMEM; + + if (asprintf(&path, "%s/%s", dir, fn) < 0) + return -errno; + + fn = path; + free(dir); + } + } + + r = config_parse(fn, t, userdata); + free(path); + return r; + } + + if (*b == '[') { + size_t k; + char *n; + + k = strlen(b); + assert(k > 0); + + if (b[k-1] != ']') { + fprintf(stderr, "[%s:%u] Invalid section header.", filename, line); + return -EBADMSG; + } + + if (!(n = strndup(b+1, k-2))) + return -ENOMEM; + + free(*section); + *section = n; + + return 0; + } + + if (!(e = strchr(b, '='))) { + fprintf(stderr, "[%s:%u] Missing '='.", filename, line); + return -EBADMSG; + } + + *e = 0; + e++; + + return next_assignment(filename, line, *section, t, strip(b), strip(e), userdata); +} + +/* Go through the file and parse each line */ +int config_parse(const char *filename, const ConfigItem *t, void *userdata) { + unsigned line = 0; + char *section = NULL; + FILE *f; + int r; + + assert(filename); + assert(t); + + if (!(f = fopen(filename, "re"))) { + r = -errno; + fprintf(stderr, "Failed to open configuration file '%s': %s", filename, strerror(-r)); + goto finish; + } + + while (!feof(f)) { + char l[LINE_MAX]; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + r = -errno; + fprintf(stderr, "Failed to read configuration file '%s': %s", filename, strerror(-r)); + goto finish; + } + + if ((r = parse_line(filename, ++line, §ion, t, l, userdata)) < 0) + goto finish; + } + + r = 0; + +finish: + free(section); + + if (f) + fclose(f); + + return r; +} + +int config_parse_int( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + int *i = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atoi(rvalue, i)) < 0) { + fprintf(stderr, "[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue); + return r; + } + + return 0; +} + +int config_parse_unsigned( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + unsigned *u = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atou(rvalue, u)) < 0) { + fprintf(stderr, "[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue); + return r; + } + + return 0; +} + +int config_parse_size( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + size_t *sz = data; + unsigned u; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((r = safe_atou(rvalue, &u)) < 0) { + fprintf(stderr, "[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue); + return r; + } + + *sz = (size_t) u; + return 0; +} + +int config_parse_bool( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + int k; + bool *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if ((k = parse_boolean(rvalue)) < 0) { + fprintf(stderr, "[%s:%u] Failed to parse boolean value: %s", filename, line, rvalue); + return k; + } + + *b = !!k; + return 0; +} + +int config_parse_string( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + char **s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (*rvalue) { + if (!(n = strdup(rvalue))) + return -ENOMEM; + } else + n = NULL; + + free(*s); + *s = n; + + return 0; +} diff --git a/conf-parser.h b/conf-parser.h new file mode 100644 index 0000000000..11f537918f --- /dev/null +++ b/conf-parser.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooconfparserhfoo +#define fooconfparserhfoo + +#include <stdio.h> + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +typedef int (*config_parser_cb_t)(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); + +/* Wraps info for parsing a specific configuration variable */ +typedef struct ConfigItem { + const char *lvalue; /* name of the variable */ + config_parser_cb_t parse; /* Function that is called to parse the variable's value */ + void *data; /* Where to store the variable's data */ + const char *section; +} ConfigItem; + +/* The configuration file parsing routine. Expects a table of + * config_items in *t that is terminated by an item where lvalue is + * NULL */ +int config_parse(const char *filename, const ConfigItem *t, void *userdata); + +/* Generic parsers */ +int config_parse_int(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); +int config_parse_unsigned(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); +int config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); +int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); +int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata); + +#endif |