From 24c90b7700e18d0668799f8f343bc854a42dea20 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Wed, 10 Oct 2018 11:28:46 +0200 Subject: [PATCH] Configuration parser cleanup - Do a getline() & process in rdline instead of doing a read per character and feeding it through a state machine. - Move splitkv to rdcfg.c, the only place where it is used Signed-off-by: David Oberhollenzer --- cmd/runsvc/env.c | 6 +- lib/Makemodule.am | 3 +- lib/cron/rdcron.c | 6 +- lib/include/libcfg.h | 29 ++--- lib/libcfg/rdcfg.c | 32 +++++- lib/libcfg/rdline.c | 257 +++++++++++++++++++++---------------------- lib/libcfg/splitkv.c | 48 -------- lib/util/rdsvc.c | 13 ++- 8 files changed, 176 insertions(+), 218 deletions(-) delete mode 100644 lib/libcfg/splitkv.c diff --git a/cmd/runsvc/env.c b/cmd/runsvc/env.c index 4fe2368..1e73ee4 100644 --- a/cmd/runsvc/env.c +++ b/cmd/runsvc/env.c @@ -41,7 +41,7 @@ static struct entry *parse_list(rdline_t *rd) char *ptr; while (rdline(rd) == 0) { - ptr = rd->buffer; + ptr = rd->line; while (*ptr != '\0' && *ptr != ' ' && *ptr != '=') ++ptr; @@ -66,11 +66,11 @@ static struct entry *parse_list(rdline_t *rd) continue; } - e = calloc(1, sizeof(*e) + strlen(rd->buffer) + 1); + e = calloc(1, sizeof(*e) + strlen(rd->line) + 1); if (e == NULL) goto fail_oom; - strcpy(e->data, rd->buffer); + strcpy(e->data, rd->line); e->next = list; list = e; } diff --git a/lib/Makemodule.am b/lib/Makemodule.am index fb6ca92..51bce72 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -8,8 +8,7 @@ libinit_a_SOURCES += lib/util/print_version.c lib/util/argv_exec.c $(HEADRS) libinit_a_CPPFLAGS = $(AM_CPPFLAGS) libinit_a_CFLAGS = $(AM_CFLAGS) -libcfg_a_SOURCES = lib/libcfg/rdline.c lib/libcfg/unescape.c -libcfg_a_SOURCES += lib/libcfg/splitkv.c lib/libcfg/rdcfg.c +libcfg_a_SOURCES = lib/libcfg/rdline.c lib/libcfg/unescape.c lib/libcfg/rdcfg.c libcfg_a_SOURCES += lib/libcfg/pack_argv.c lib/include/libcfg.h libcfg_a_CPPFLAGS = $(AM_CPPFLAGS) libcfg_a_CFLAGS = $(AM_CFLAGS) diff --git a/lib/cron/rdcron.c b/lib/cron/rdcron.c index 1c11cbb..520f969 100644 --- a/lib/cron/rdcron.c +++ b/lib/cron/rdcron.c @@ -486,7 +486,8 @@ crontab_t *rdcron(int dirfd, const char *filename) cron = calloc(1, sizeof(*cron)); if (cron == NULL) { fputs("out of memory\n", stderr); - goto out; + close(fd); + return NULL; } cron->minute = 0xFFFFFFFFFFFFFFFFUL; @@ -501,7 +502,6 @@ crontab_t *rdcron(int dirfd, const char *filename) delcron(cron); cron = NULL; } -out: - close(fd); + rdline_cleanup(&rd); return cron; } diff --git a/lib/include/libcfg.h b/lib/include/libcfg.h index 8096f1b..95f91a8 100644 --- a/lib/include/libcfg.h +++ b/lib/include/libcfg.h @@ -20,23 +20,16 @@ #include #include +#include typedef struct { - int fd; /* input file descriptor */ - const char *argstr; /* if not NULL, read from this instead */ - const char *filename; /* input file name */ size_t lineno; /* current line number */ - - size_t i; /* buffer offset */ - char buffer[256]; /* current line, null-terminated */ + FILE *fp; + char *line; int argc; const char *const *argv; - - bool string; /* inside a string? */ - bool escape; /* reading an escape sequence? */ - bool comment; /* inside a comment */ } rdline_t; typedef struct { @@ -63,6 +56,8 @@ typedef struct { void rdline_init(rdline_t *t, int fd, const char *filename, int argc, const char *const *argv); +void rdline_cleanup(rdline_t *t); + /* Read from file until end-of-file or a line feed is encountered. @@ -84,9 +79,8 @@ void rdline_init(rdline_t *t, int fd, const char *filename, outside the bounds set by argc, processing fails. On success, the argv value is inserted and processed as described above. - A '%' character can be escaped by writing '%%' or, if inside - a double quite string, by writing \%. - - An attempt to use such an indexed argument inside an argument - expansion, results in failure. + a double quoted string, by writing \%. + - Arguments are pasted as is. Substitution is not recursive. - If the resulting line is empty, processing is restarted. */ int rdline(rdline_t *t); @@ -110,15 +104,6 @@ int unescape(char *src); */ int pack_argv(char *str); -/* - Split the current input line into a space seperted keyword - (alphabetical characters only) and a value (the rest of the line). - - If errors are encounted, prints a diagnostic message to stderr and - returns -1. On success, zero is returned. - */ -int splitkv(rdline_t *rd, char **k, char **v); - /* Parse a configuration file containing ' [arguments...]' lines. The cfgobj and flags are passed to the callback in the params array. diff --git a/lib/libcfg/rdcfg.c b/lib/libcfg/rdcfg.c index 71a994f..ccbcf7b 100644 --- a/lib/libcfg/rdcfg.c +++ b/lib/libcfg/rdcfg.c @@ -19,6 +19,7 @@ #include #include +#include static const cfg_param_t *find_param(rdline_t *rd, const char *name, const cfg_param_t *params, size_t count) @@ -35,6 +36,33 @@ static const cfg_param_t *find_param(rdline_t *rd, const char *name, return NULL; } +static int splitkv(rdline_t *rd, char **k, char **v) +{ + char *key = rd->line, *value = rd->line; + + while (*value != ' ' && *value != '\0') { + if (!isalpha(*value)) { + fprintf(stderr, + "%s: %zu: unexpected '%c' in keyword\n", + rd->filename, rd->lineno, *value); + return -1; + } + ++value; + } + + if (*value != ' ') { + fprintf(stderr, "%s: %zu: expected argument after '%s'\n", + rd->filename, rd->lineno, key); + return -1; + } + + *(value++) = '\0'; + + *k = key; + *v = value; + return 0; +} + int rdcfg(void *cfgobj, rdline_t *rd, const cfg_param_t *params, size_t count, int flags) { @@ -61,9 +89,9 @@ int rdcfg(void *cfgobj, rdline_t *rd, const cfg_param_t *params, size_t count, } while ((ret = rdline(rd)) == 0) { - if (strcmp(rd->buffer, "}") == 0) + if (strcmp(rd->line, "}") == 0) break; - if (p->handle(cfgobj, rd->buffer, rd, flags)) + if (p->handle(cfgobj, rd->line, rd, flags)) return -1; } diff --git a/lib/libcfg/rdline.c b/lib/libcfg/rdline.c index ffbfd89..16ca5cf 100644 --- a/lib/libcfg/rdline.c +++ b/lib/libcfg/rdline.c @@ -24,151 +24,144 @@ #include "libcfg.h" -static int rdline_getc(rdline_t *t) -{ - int ret; - char c; - - if (t->argstr != NULL) { - c = *(t->argstr++); - if (c != '\0') - goto out; - - t->argstr = NULL; - } - - do { - ret = read(t->fd, &c, 1); - } while (ret < 0 && errno == EINTR); - - if (ret < 0) - return -1; - - if (ret == 0) { - if (t->i == 0) { - errno = 0; - return -1; - } - c = '\0'; - } -out: - return (c == '\n') ? '\0' : c; -} - -static int rdline_append(rdline_t *t, int c) -{ - if (t->comment) { - if (c != '\0') - return 0; - } else if (t->string) { - if (t->escape) { - t->escape = false; - } else { - if (c == '\\') - t->escape = true; - if (c == '"') - t->string = false; - } - } else { - if (isspace(c)) - c = ' '; - if (c == ' ' && (t->i == 0 || t->buffer[t->i - 1] == ' ')) - return 0; - if (c == '#') { - t->comment = true; - return 0; - } - if (c == '"') - t->string = true; - } - - if (c == '\0') { - while (t->i > 0 && t->buffer[t->i - 1] == ' ') - t->i -= 1; - } - - if (t->i == sizeof(t->buffer)) - return -1; - - t->buffer[t->i++] = c; - return 0; -} - void rdline_init(rdline_t *t, int fd, const char *filename, int argc, const char *const *argv) { memset(t, 0, sizeof(*t)); - t->fd = fd; + t->fp = fdopen(fd, "r"); t->filename = filename; t->argc = argc; t->argv = argv; } -int rdline(rdline_t *t) +void rdline_cleanup(rdline_t *t) { - const char *errstr; - int c; -retry: - t->i = 0; - t->argstr = NULL; - t->string = t->escape = t->comment = false; + free(t->line); + fclose(t->fp); +} + +static int read_raw_line(rdline_t *t) +{ + size_t len = 0; + + free(t->line); + t->line = NULL; + + errno = 0; + + if (getline(&t->line, &len, t->fp) < 0) { + if (errno) { + fprintf(stderr, "%s: %zu: %s\n", t->filename, + t->lineno, strerror(errno)); + return -1; + } + return 1; + } + t->lineno += 1; - - do { - errno = 0; - c = rdline_getc(t); - if (c < 0) { - if (errno == 0) - return 1; - errstr = strerror(errno); - goto fail; - } - if (c == 0 && t->string) { - errstr = "missing \""; - goto fail; - } - - if (c == '%') { - c = rdline_getc(t); - if (c == 0) { - errstr = "unexpected end of line after '%%'"; - goto fail; - } - if (c < 0) { - errstr = strerror(errno); - goto fail; - } - - if (c != '%') { - if (!isdigit(c)) { - errstr = "exptected digit after '%%'"; - goto fail; - } - if ((c - '0') >= t->argc) { - errstr = "argument out of range"; - goto fail; - } - if (t->argstr != NULL) { - errstr = "recursive argument " - "expansion"; - goto fail; - } - t->argstr = t->argv[c - '0']; - continue; - } - } - - if (rdline_append(t, c)) { - errstr = "line too long"; - goto fail; - } - } while (c != '\0'); - - if (t->buffer[0] == '\0') - goto retry; - return 0; +} + +static int normalize_line(rdline_t *t) +{ + char *dst = t->line, *src = t->line; + bool string = false; + const char *errstr; + int c, ret = 0; + + while (isspace(*src)) + ++src; + + while (*src != '\0' && (string || *src != '#')) { + c = *(src++); + + if (c == '"') { + string = !string; + } else if (!string && isspace(c)) { + c = ' '; + if (dst > t->line && dst[-1] == ' ') + continue; + } else if (c == '%') { + *(dst++) = c; + c = *(src++); + if (c != '%' && !isdigit(c)) { + errstr = "expected digit after '%%'"; + goto fail; + } + if (isdigit(c) && (c - '0') >= t->argc) { + errstr = "argument out of range"; + goto fail; + } + ret += strlen(t->argv[c - '0']); + } else if (string && c == '\\' && *src != '\0') { + *(dst++) = c; + c = *(src++); + } + + *(dst++) = c; + } + + if (string) { + errstr = "missing \""; + goto fail; + } + + while (dst > t->line && dst[-1] == ' ') + --dst; + *dst = '\0'; + return ret; fail: fprintf(stderr, "%s: %zu: %s\n", t->filename, t->lineno, errstr); return -1; } + +static void substitute(rdline_t *t, char *dst, char *src) +{ + bool string = false; + + while (*src != '\0') { + if (src[0] == '%' && isdigit(src[1])) { + strcpy(dst, t->argv[src[1] - '0']); + src += 2; + while (*dst != '\0') + ++dst; + } else { + if (*src == '"') + string = !string; + if (string && *src == '\\') + *(dst++) = *(src++); + *(dst++) = *(src++); + } + } +} + +int rdline(rdline_t *t) +{ + char *buffer = NULL; + int ret; + + do { + if ((ret = read_raw_line(t))) + goto out; + if ((ret = normalize_line(t)) < 0) + goto out; + } while (t->line[0] == '\0'); + + if (ret == 0) + return 0; + + buffer = calloc(1, strlen(t->line) + ret + 1); + if (buffer == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", + t->filename, t->lineno); + ret = -1; + goto out; + } + + substitute(t, buffer, t->line); + ret = 0; +out: + free(t->line); + t->line = buffer; + return ret; +} diff --git a/lib/libcfg/splitkv.c b/lib/libcfg/splitkv.c deleted file mode 100644 index 49f8ebe..0000000 --- a/lib/libcfg/splitkv.c +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * Copyright (C) 2018 - David Oberhollenzer - * - * 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 3 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, see . - */ -#include -#include - -#include "libcfg.h" - -int splitkv(rdline_t *rd, char **k, char **v) -{ - char *key = rd->buffer, *value = rd->buffer; - - while (*value != ' ' && *value != '\0') { - if (!isalpha(*value)) { - fprintf(stderr, - "%s: %zu: unexpected '%c' in keyword\n", - rd->filename, rd->lineno, *value); - return -1; - } - ++value; - } - - if (*value != ' ') { - fprintf(stderr, "%s: %zu: expected argument after '%s'\n", - rd->filename, rd->lineno, key); - return -1; - } - - *(value++) = '\0'; - - *k = key; - *v = value; - return 0; -} diff --git a/lib/util/rdsvc.c b/lib/util/rdsvc.c index 747d753..ca60731 100644 --- a/lib/util/rdsvc.c +++ b/lib/util/rdsvc.c @@ -266,8 +266,6 @@ service_t *rdsvc(int dirfd, const char *filename, int flags) argc = 0; } - rdline_init(&rd, fd, filename, argc, args); - nlen = (arg != NULL) ? (size_t)(arg - filename) : strlen(filename); svc = calloc(1, sizeof(*svc) + nlen + 1); @@ -282,14 +280,17 @@ service_t *rdsvc(int dirfd, const char *filename, int flags) memcpy(svc->name, filename, nlen); - if (rdcfg(svc, &rd, svc_params, ARRAY_SIZE(svc_params), flags)) - goto fail; + rdline_init(&rd, fd, filename, argc, args); - close(fd); + if (rdcfg(svc, &rd, svc_params, ARRAY_SIZE(svc_params), flags)) { + delsvc(svc); + svc = NULL; + } + + rdline_cleanup(&rd); return svc; fail_oom: fputs("out of memory\n", stderr); -fail: delsvc(svc); close(fd); return NULL;