From 5cd5f48f765f00b81786c6f569314474a91a06b8 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Sun, 16 Sep 2018 15:38:45 +0200 Subject: [PATCH] Add helper library for cron configuration Signed-off-by: David Oberhollenzer --- lib/Makemodule.am | 11 +- lib/cron/cronscan.c | 77 +++++++ lib/cron/crontab.c | 50 +++++ lib/cron/delcron.c | 38 ++++ lib/cron/rdcron.c | 503 ++++++++++++++++++++++++++++++++++++++++++ lib/include/crontab.h | 56 +++++ lib/include/libcfg.h | 24 ++ lib/libcfg/rdcfg.c | 83 +++++++ lib/util/rdsvc.c | 128 ++++------- 9 files changed, 882 insertions(+), 88 deletions(-) create mode 100644 lib/cron/cronscan.c create mode 100644 lib/cron/crontab.c create mode 100644 lib/cron/delcron.c create mode 100644 lib/cron/rdcron.c create mode 100644 lib/include/crontab.h create mode 100644 lib/libcfg/rdcfg.c diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 817a2a5..7620e63 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -9,11 +9,16 @@ 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 +libcfg_a_SOURCES += lib/libcfg/splitkv.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) -EXTRA_DIST += $(HEADRS) lib/include/libcfg.h +libcron_a_SOURCES = lib/cron/rdcron.c lib/cron/delcron.c lib/cron/crontab.c +libcron_a_SOURCES += lib/cron/cronscan.c lib/include/crontab.h +libcron_a_CPPFLAGS = $(AM_CPPFLAGS) +libcron_a_CFLAGS = $(AM_CFLAGS) -noinst_LIBRARIES += libinit.a libcfg.a +EXTRA_DIST += $(HEADRS) lib/include/libcfg.h lib/include/crontab.h + +noinst_LIBRARIES += libinit.a libcfg.a libcron.a diff --git a/lib/cron/cronscan.c b/lib/cron/cronscan.c new file mode 100644 index 0000000..e28fb03 --- /dev/null +++ b/lib/cron/cronscan.c @@ -0,0 +1,77 @@ +/* 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 +#include +#include +#include +#include +#include +#include + +#include "crontab.h" + +int cronscan(const char *directory, crontab_t **list) +{ + struct dirent *ent; + int dfd, ret = 0; + crontab_t *cron; + DIR *dir; + + dir = opendir(directory); + if (dir == NULL) { + perror(directory); + return -1; + } + + dfd = dirfd(dir); + if (dfd < 0) { + perror(directory); + closedir(dir); + return -1; + } + + for (;;) { + errno = 0; + ent = readdir(dir); + + if (ent == NULL) { + if (errno != 0) { + perror(directory); + ret = -1; + } + break; + } + + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + continue; + + cron = rdcron(dfd, ent->d_name); + if (cron == NULL) { + ret = -1; + continue; + } + + cron->next = *list; + *list = cron; + } + + closedir(dir); + return ret; +} diff --git a/lib/cron/crontab.c b/lib/cron/crontab.c new file mode 100644 index 0000000..f761ec0 --- /dev/null +++ b/lib/cron/crontab.c @@ -0,0 +1,50 @@ +/* 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 "crontab.h" + +void cron_tm_to_mask(crontab_t *out, struct tm *t) +{ + memset(out, 0, sizeof(*out)); + out->minute = 1UL << ((unsigned long)t->tm_min); + out->hour = 1 << t->tm_hour; + out->dayofmonth = 1 << (t->tm_mday - 1); + out->month = 1 << t->tm_mon; + out->dayofweek = 1 << t->tm_wday; +} + +bool cron_should_run(const crontab_t *t, const crontab_t *mask) +{ + if ((t->minute & mask->minute) == 0) + return false; + + if ((t->hour & mask->hour) == 0) + return false; + + if ((t->dayofmonth & mask->dayofmonth) == 0) + return false; + + if ((t->month & mask->month) == 0) + return false; + + if ((t->dayofweek & mask->dayofweek) == 0) + return false; + + return true; +} diff --git a/lib/cron/delcron.c b/lib/cron/delcron.c new file mode 100644 index 0000000..1877db1 --- /dev/null +++ b/lib/cron/delcron.c @@ -0,0 +1,38 @@ +/* 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 "crontab.h" + +void delcron(crontab_t *cron) +{ + exec_t *e; + + if (cron == NULL) + return; + + while (cron->exec != NULL) { + e = cron->exec; + cron->exec = e->next; + + free(e); + } + + free(cron->ctty); + free(cron); +} diff --git a/lib/cron/rdcron.c b/lib/cron/rdcron.c new file mode 100644 index 0000000..42936ce --- /dev/null +++ b/lib/cron/rdcron.c @@ -0,0 +1,503 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crontab.h" +#include "libcfg.h" +#include "util.h" + + +static const enum_map_t weekday[] = { + { "MON", 1 }, + { "TUE", 2 }, + { "WED", 3 }, + { "THU", 4 }, + { "FRI", 5 }, + { "SAT", 6 }, + { "SUN", 0 }, +}; + +static const enum_map_t month[] = { + { "JAN", 1 }, + { "FEB", 2 }, + { "MAR", 3 }, + { "APR", 4 }, + { "MAY", 5 }, + { "JUN", 6 }, + { "JUL", 7 }, + { "AUG", 8 }, + { "SEP", 9 }, + { "OCT", 10 }, + { "NOV", 11 }, + { "DEC", 12 }, +}; + +static const struct { + const char *macro; + crontab_t tab; +} intervals[] = { + { + .macro = "yearly", + .tab = { + .minute = 0x01, + .hour = 0x01, + .dayofmonth = 0x01, + .month = 0x01, + .dayofweek = 0xFF + }, + }, { + .macro = "annually", + .tab = { + .minute = 0x01, + .hour = 0x01, + .dayofmonth = 0x01, + .month = 0x01, + .dayofweek = 0xFF + }, + }, { + .macro = "monthly", + .tab = { + .minute = 0x01, + .hour = 0x01, + .dayofmonth = 0x01, + .month = 0xFFFF, + .dayofweek = 0xFF + }, + }, { + .macro = "weekly", + .tab = { + .minute = 0x01, + .hour = 0x01, + .dayofmonth = 0xFFFFFFFF, + .month = 0xFFFF, + .dayofweek = 0x01 + }, + }, { + .macro = "daily", + .tab = { + .minute = 0x01, + .hour = 0x01, + .dayofmonth = 0xFFFFFFFF, + .month = 0xFFFF, + .dayofweek = 0xFF + }, + }, { + .macro = "hourly", + .tab = { + .minute = 0x01, + .hour = 0xFFFFFFFF, + .dayofmonth = 0xFFFFFFFF, + .month = 0xFFFF, + .dayofweek = 0xFF + }, + }, +}; + +/*****************************************************************************/ + +static int try_unescape(char *arg, rdline_t *rd) +{ + if (unescape(arg)) { + fprintf(stderr, "%s: %zu: malformed string constant\n", + rd->filename, rd->lineno); + return -1; + } + return 0; +} + +static char *try_strdup(const char *str, rdline_t *rd) +{ + char *out = strdup(str); + + if (out == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", + rd->filename, rd->lineno); + } + return out; +} + +static char *readnum(char *line, int *out, int minval, int maxval, + const enum_map_t *mnemonic, rdline_t *rd) +{ + int i, temp, value = 0; + const enum_map_t *ev; + + if (!isdigit(*line)) { + if (!mnemonic) + goto fail_mn; + + for (i = 0; isalnum(line[i]); ++i) + ; + if (i == 0) + goto fail_mn; + + temp = line[i]; + line[i] = '\0'; + ev = enum_by_name(mnemonic, line); + if (!ev) { + fprintf(stderr, "%s: %zu: unexpected '%s'", + rd->filename, rd->lineno, line); + } + line[i] = temp; + if (!ev) + return NULL; + *out = ev->value; + return line + i; + } + + while (isdigit(*line)) { + i = ((*(line++)) - '0'); + if (value > (maxval - i) / 10) + goto fail_of; + value = value * 10 + i; + } + + if (value < minval) + goto fail_uf; + + *out = value; + return line; +fail_of: + fprintf(stderr, "%s: %zu: value exceeds maximum (%d > %d)\n", + rd->filename, rd->lineno, value, maxval); + return NULL; +fail_uf: + fprintf(stderr, "%s: %zu: value too small (%d < %d)\n", + rd->filename, rd->lineno, value, minval); + return NULL; +fail_mn: + fprintf(stderr, "%s: %zu: expected numeric value", + rd->filename, rd->lineno); + return NULL; +} + +static char *readfield(char *line, uint64_t *out, int minval, int maxval, + const enum_map_t *mnemonic, rdline_t *rd) +{ + int value, endvalue, step; + uint64_t v = 0; +next: + if (*line == '*') { + ++line; + value = minval; + endvalue = maxval; + } else { + line = readnum(line, &value, minval, maxval, mnemonic, rd); + if (!line) + goto fail; + + if (*line == '-') { + line = readnum(line + 1, &endvalue, minval, maxval, + mnemonic, rd); + if (!line) + goto fail; + } else { + endvalue = value; + } + } + + if (endvalue < value) + goto fail; + + if (*line == '/') { + line = readnum(line + 1, &step, 1, maxval + 1, NULL, rd); + if (!line) + goto fail; + } else { + step = 1; + } + + while (value <= endvalue) { + v |= 1UL << (unsigned long)(value - minval); + value += step; + } + + if (*line == ',' || *line == ' ') { + ++line; + goto next; + } + + if (*line != '\0') + goto fail; + + *out = v; + return line; +fail: + fprintf(stderr, "%s: %zu: invalid time range expression\n", + rd->filename, rd->lineno); + return NULL; +} + +/*****************************************************************************/ + +static int cron_exec(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + exec_t *e, *end; + (void)flags; + + e = calloc(1, sizeof(*e) + strlen(arg) + 1); + if (e == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", + rd->filename, rd->lineno); + return -1; + } + + strcpy(e->args, arg); + + e->argc = pack_argv(e->args); + if (e->argc < 0) { + fprintf(stderr, "%s: %zu: malformed string constant\n", + rd->filename, rd->lineno); + return -1; + } + + if (cron->exec == NULL) { + cron->exec = e; + } else { + for (end = cron->exec; end->next != NULL; end = end->next) + ; + end->next = e; + } + return 0; +} + +static int cron_hour(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + uint64_t value; + (void)flags; + + if (!readfield(arg, &value, 0, 23, NULL, rd)) + return -1; + + cron->hour = value; + return 0; +} + +static int cron_minute(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + uint64_t value; + (void)flags; + + if (!readfield(arg, &value, 0, 59, NULL, rd)) + return -1; + + cron->minute = value; + return 0; +} + +static int cron_dayofmonth(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + uint64_t value; + (void)flags; + + if (!readfield(arg, &value, 1, 31, NULL, rd)) + return -1; + + cron->dayofmonth = value; + return 0; +} + +static int cron_dayofweek(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + uint64_t value; + (void)flags; + + if (!readfield(arg, &value, 0, 6, weekday, rd)) + return -1; + + cron->dayofweek = value; + return 0; +} + +static int cron_month(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + uint64_t value; + (void)flags; + + if (!readfield(arg, &value, 1, 12, month, rd)) + return -1; + + cron->month = value; + return 0; +} + +static int cron_interval(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + size_t i; + (void)flags; + + for (i = 0; i < ARRAY_SIZE(intervals); ++i) { + if (!strcmp(intervals[i].macro, arg)) { + cron->minute = intervals[i].tab.minute; + cron->hour = intervals[i].tab.hour; + cron->dayofmonth = intervals[i].tab.dayofmonth; + cron->month = intervals[i].tab.month; + cron->dayofweek = intervals[i].tab.dayofweek; + return 0; + } + } + + fprintf(stderr, "%s: %zu: unknown interval '%s'\n", + rd->filename, rd->lineno, arg); + return -1; +} + +static int cron_user(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + struct passwd *pwd; + bool isnumeric; + char *ptr; + int value; + (void)flags; + + for (ptr = arg; isdigit(*ptr); ++ptr) + ; + + isnumeric = (*ptr == '\0'); + pwd = getpwnam(arg); + + if (pwd == NULL && !isnumeric) { + fprintf(stderr, "%s: %zu: unknown user '%s'\n", + rd->filename, rd->lineno, arg); + return -1; + } + + if (pwd != NULL) { + cron->uid = pwd->pw_uid; + } else { + if (readnum(arg, &value, 0, INT_MAX, NULL, rd)) + return -1; + cron->uid = value; + } + return 0; +} + +static int cron_group(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + struct group *group; + bool isnumeric; + char *ptr; + int value; + (void)flags; + + for (ptr = arg; isdigit(*ptr); ++ptr) + ; + + isnumeric = (*ptr == '\0'); + group = getgrnam(arg); + + if (group == NULL && !isnumeric) { + fprintf(stderr, "%s: %zu: unknown group '%s'\n", + rd->filename, rd->lineno, arg); + return -1; + } + + if (group != NULL) { + cron->gid = group->gr_gid; + } else { + if (readnum(arg, &value, 0, INT_MAX, NULL, rd)) + return -1; + cron->gid = value; + } + return 0; +} + +static int cron_tty(void *user, char *arg, rdline_t *rd, int flags) +{ + crontab_t *cron = user; + (void)flags; + + if (strncmp(arg, "truncate", 8) == 0 && isspace(arg[8])) { + cron->tty_truncate = 1; + arg += 8; + while (isspace(*arg)) + ++arg; + } + + if (try_unescape(arg, rd)) + return -1; + + cron->ctty = try_strdup(arg, rd); + return cron->ctty == NULL ? -1 : 0; +} + + + +static const cfg_param_t cron_params[] = { + { "hour", 0, cron_hour }, + { "minute", 0, cron_minute }, + { "dayofmonth", 0, cron_dayofmonth }, + { "dayofweek", 0, cron_dayofweek }, + { "month", 0, cron_month }, + { "interval", 0, cron_interval }, + { "user", 0, cron_user }, + { "group", 0, cron_group }, + { "tty", 0, cron_tty }, + { "exec", 1, cron_exec }, +}; + +crontab_t *rdcron(int dirfd, const char *filename) +{ + crontab_t *cron; + rdline_t rd; + int fd, ret; + + fd = openat(dirfd, filename, O_RDONLY); + if (fd < 0) { + perror(filename); + return NULL; + } + + cron = calloc(1, sizeof(*cron)); + if (cron == NULL) { + fputs("out of memory\n", stderr); + goto out; + } + + cron->pid = -1; + + rdline_init(&rd, fd, filename, 0, NULL); + ret = rdcfg(cron, &rd, cron_params, ARRAY_SIZE(cron_params), 0); + if (ret) { + delcron(cron); + cron = NULL; + } +out: + close(fd); + return cron; +} diff --git a/lib/include/crontab.h b/lib/include/crontab.h new file mode 100644 index 0000000..408d276 --- /dev/null +++ b/lib/include/crontab.h @@ -0,0 +1,56 @@ +/* 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 . + */ +#ifndef CRONTAB_H +#define CRONTAB_H + +#include +#include +#include +#include + +#include "service.h" + +typedef struct crontab_t { + struct crontab_t *next; + exec_t *exec; + char *ctty; + + uid_t uid; + gid_t gid; + pid_t pid; + + uint64_t minute; + uint32_t hour; + uint32_t dayofmonth; + uint16_t month; + uint8_t dayofweek; + + unsigned int tty_truncate : 1; +} crontab_t; + +crontab_t *rdcron(int dirfd, const char *filename); + +void delcron(crontab_t *cron); + +int cronscan(const char *directory, crontab_t **list); + +void cron_tm_to_mask(crontab_t *out, struct tm *t); + +bool cron_should_run(const crontab_t *t, const crontab_t *mask); + +#endif /* CRONTAB_H */ diff --git a/lib/include/libcfg.h b/lib/include/libcfg.h index 83b9213..8096f1b 100644 --- a/lib/include/libcfg.h +++ b/lib/include/libcfg.h @@ -19,6 +19,7 @@ #define LIBCONFIG_H #include +#include typedef struct { int fd; /* input file descriptor */ @@ -38,6 +39,20 @@ typedef struct { bool comment; /* inside a comment */ } rdline_t; +typedef struct { + /* keyword to map the callback to */ + const char *key; + + /* + If set, allow grouping repetitions of the keyword in a single + multi line '{' ... '}' block. The callback is called for each + line. + */ + unsigned int allow_block : 1; + + int (*handle)(void *obj, char *arg, rdline_t *rd, int flags); +} cfg_param_t; + /* Initialize the config line scanner. @@ -104,4 +119,13 @@ int pack_argv(char *str); */ 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. + + Returns zero on success. + */ +int rdcfg(void *cfgobj, rdline_t *rd, const cfg_param_t *params, size_t count, + int flags); + #endif /* LIBCONFIG_H */ diff --git a/lib/libcfg/rdcfg.c b/lib/libcfg/rdcfg.c new file mode 100644 index 0000000..71a994f --- /dev/null +++ b/lib/libcfg/rdcfg.c @@ -0,0 +1,83 @@ +/* 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 "libcfg.h" + +#include +#include + +static const cfg_param_t *find_param(rdline_t *rd, const char *name, + const cfg_param_t *params, size_t count) +{ + size_t i; + + for (i = 0; i < count; ++i) { + if (!strcmp(params[i].key, name)) + return params + i; + } + + fprintf(stderr, "%s: %zu: unknown keyword '%s'\n", + rd->filename, rd->lineno, name); + return NULL; +} + +int rdcfg(void *cfgobj, rdline_t *rd, const cfg_param_t *params, size_t count, + int flags) +{ + const cfg_param_t *p; + char *key, *value; + int ret; + + while ((ret = rdline(rd)) == 0) { + if (splitkv(rd, &key, &value)) + return -1; + + p = find_param(rd, key, params, count); + if (p == NULL) + return -1; + + if (p->allow_block && *value == '{') { + for (++value; *value == ' '; ++value) + ; + + if (*value != '\0') { + ret = p->handle(cfgobj, value, rd, flags); + if (ret) + return -1; + } + + while ((ret = rdline(rd)) == 0) { + if (strcmp(rd->buffer, "}") == 0) + break; + if (p->handle(cfgobj, rd->buffer, rd, flags)) + return -1; + } + + if (ret < 0) + return -1; + if (ret > 0) + goto fail_bra; + } else if (p->handle(cfgobj, value, rd, flags)) { + return -1; + } + } + + return ret < 0 ? -1 : 0; +fail_bra: + fprintf(stderr, "%s: missing '}' before end-of-file\n", rd->filename); + return -1; +} diff --git a/lib/util/rdsvc.c b/lib/util/rdsvc.c index 3e146a2..747d753 100644 --- a/lib/util/rdsvc.c +++ b/lib/util/rdsvc.c @@ -60,16 +60,24 @@ static int try_pack_argv(char *str, rdline_t *rd) return count; } -static int svc_desc(service_t *svc, char *arg, rdline_t *rd) +static int svc_desc(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; + (void)flags; + if (try_unescape(arg, rd)) return -1; svc->desc = try_strdup(arg, rd); return svc->desc == NULL ? -1 : 0; } -static int svc_tty(service_t *svc, char *arg, rdline_t *rd) +static int svc_tty(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; + + if (flags & RDSVC_NO_CTTY) + return 0; + if (strncmp(arg, "truncate", 8) == 0 && isspace(arg[8])) { svc->flags |= SVC_FLAG_TRUNCATE_OUT; arg += 8; @@ -84,10 +92,14 @@ static int svc_tty(service_t *svc, char *arg, rdline_t *rd) return svc->ctty == NULL ? -1 : 0; } -static int svc_exec(service_t *svc, char *arg, rdline_t *rd) +static int svc_exec(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; exec_t *e, *end; + if (flags & RDSVC_NO_EXEC) + return 0; + e = calloc(1, sizeof(*e) + strlen(arg) + 1); if (e == NULL) { fprintf(stderr, "%s: %zu: out of memory\n", @@ -111,8 +123,13 @@ static int svc_exec(service_t *svc, char *arg, rdline_t *rd) return 0; } -static int svc_before(service_t *svc, char *arg, rdline_t *rd) +static int svc_before(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; + + if (flags & RDSVC_NO_DEPS) + return 0; + if (svc->before != NULL) { fprintf(stderr, "%s: %zu: 'before' dependencies respecified\n", rd->filename, rd->lineno); @@ -127,8 +144,13 @@ static int svc_before(service_t *svc, char *arg, rdline_t *rd) return (svc->num_before < 0) ? -1 : 0; } -static int svc_after(service_t *svc, char *arg, rdline_t *rd) +static int svc_after(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; + + if (flags & RDSVC_NO_DEPS) + return 0; + if (svc->after != NULL) { fprintf(stderr, "%s: %zu: 'after' dependencies respecified\n", rd->filename, rd->lineno); @@ -143,9 +165,11 @@ static int svc_after(service_t *svc, char *arg, rdline_t *rd) return (svc->num_after < 0) ? -1 : 0; } -static int svc_type(service_t *svc, char *arg, rdline_t *rd) +static int svc_type(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; int count = try_pack_argv(arg, rd); + (void)flags; if (count < 1) return -1; @@ -189,9 +213,11 @@ fail_limit: return -1; } -static int svc_target(service_t *svc, char *arg, rdline_t *rd) +static int svc_target(void *user, char *arg, rdline_t *rd, int flags) { + service_t *svc = user; int target; + (void)flags; if (try_unescape(arg, rd)) return -1; @@ -208,48 +234,23 @@ static int svc_target(service_t *svc, char *arg, rdline_t *rd) return 0; } -static const struct svc_param { - const char *key; - - unsigned int allow_block : 1; - - int flags; - - int (*handle)(service_t *svc, char *arg, rdline_t *rd); -} svc_params[] = { - { "description", 0, 0, svc_desc }, - { "exec", 1, RDSVC_NO_EXEC, svc_exec }, - { "type", 0, 0, svc_type }, - { "target", 0, 0, svc_target }, - { "tty", 0, RDSVC_NO_CTTY, svc_tty }, - { "before", 0, RDSVC_NO_DEPS, svc_before }, - { "after", 0, RDSVC_NO_DEPS, svc_after }, +static const cfg_param_t svc_params[] = { + { "description", 0, svc_desc }, + { "exec", 1, svc_exec }, + { "type", 0, svc_type }, + { "target", 0, svc_target }, + { "tty", 0, svc_tty }, + { "before", 0, svc_before }, + { "after", 0, svc_after }, }; -static const struct svc_param *find_param(rdline_t *rd, const char *name) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(svc_params); ++i) { - if (!strcmp(svc_params[i].key, name)) - return svc_params + i; - } - - fprintf(stderr, "%s: %zu: unknown keyword '%s'\n", - rd->filename, rd->lineno, name); - return NULL; -} - - service_t *rdsvc(int dirfd, const char *filename, int flags) { - const struct svc_param *p; const char *arg, *args[1]; service_t *svc = NULL; - char *key, *value; size_t argc, nlen; rdline_t rd; - int fd, ret; + int fd; fd = openat(dirfd, filename, O_RDONLY); if (fd < 0) { @@ -281,54 +282,11 @@ service_t *rdsvc(int dirfd, const char *filename, int flags) memcpy(svc->name, filename, nlen); - while ((ret = rdline(&rd)) == 0) { - if (splitkv(&rd, &key, &value)) - goto fail; - - p = find_param(&rd, key); - if (p == NULL) - goto fail; - - if (p->allow_block && *value == '{') { - for (++value; *value == ' '; ++value) - ; - - if (!(flags & p->flags)) { - if (*value != '\0' && - p->handle(svc, value, &rd)) { - goto fail; - } - } - - while ((ret = rdline(&rd)) == 0) { - if (strcmp(rd.buffer, "}") == 0) - break; - if (flags & p->flags) - continue; - if (p->handle(svc, rd.buffer, &rd)) - goto fail; - } - - if (ret < 0) - goto fail; - if (ret > 0) - goto fail_bra; - } else { - if (flags & p->flags) - continue; - if (p->handle(svc, value, &rd)) - goto fail; - } - } - - if (ret < 0) + if (rdcfg(svc, &rd, svc_params, ARRAY_SIZE(svc_params), flags)) goto fail; close(fd); return svc; -fail_bra: - fprintf(stderr, "%s: missing '}' before end-of-file\n", filename); - goto fail; fail_oom: fputs("out of memory\n", stderr); fail: