diff options
Diffstat (limited to 'rdcron.c')
-rw-r--r-- | rdcron.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/rdcron.c b/rdcron.c new file mode 100644 index 0000000..7e39763 --- /dev/null +++ b/rdcron.c @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: ISC */ +#include "gcrond.h" + +typedef struct { + const char *name; + int value; +} enum_map_t; + +static const enum_map_t weekday[] = { + { "MON", 1 }, + { "TUE", 2 }, + { "WED", 3 }, + { "THU", 4 }, + { "FRI", 5 }, + { "SAT", 6 }, + { "SUN", 0 }, + { NULL, 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 }, + { NULL, 0 }, +}; + +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 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'; + + for (ev = mnemonic; ev->name != NULL; ++ev) { + if (!strcmp(line, mnemonic->name) == 0) + break; + } + + if (ev->name == NULL) { + rdline_complain(rd, "unexpected '%s'", line); + return NULL; + } + line[i] = temp; + *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: + rdline_complain(rd, "value exceeds maximum (%d > %d)", value, maxval); + return NULL; +fail_uf: + rdline_complain(rd, "value too small (%d < %d)", value, minval); + return NULL; +fail_mn: + rdline_complain(rd, "expected numeric value"); + 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; + goto next; + } + + if (*line != '\0' && !isspace(*line)) + goto fail; + while (isspace(*line)) + ++line; + + *out = v; + return line; +fail: + rdline_complain(rd, "invalid time range expression"); + return NULL; +} + +/*****************************************************************************/ + +static char *cron_interval(crontab_t *cron, rdline_t *rd) +{ + char *arg = rd->line; + size_t i, j; + + if (*(arg++) != '@') + goto fail; + for (j = 0; isalpha(arg[j]); ++j) + ; + if (j == 0 || !isspace(arg[j])) + goto fail; + + for (i = 0; i < ARRAY_SIZE(intervals); ++i) { + if (strlen(intervals[i].macro) != j) + continue; + if (strncmp(intervals[i].macro, arg, j) == 0) + break; + } + + if (i == ARRAY_SIZE(intervals)) + goto fail; + + 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 arg + j; +fail: + rdline_complain(rd, "unknown interval '%s'", arg); + return NULL; +} + +static char *cron_fields(crontab_t *cron, rdline_t *rd) +{ + char *arg = rd->line; + uint64_t value; + + if ((arg = readfield(arg, &value, 0, 59, NULL, rd)) == NULL) + return NULL; + cron->minute = value; + + if ((arg = readfield(arg, &value, 0, 23, NULL, rd)) == NULL) + return NULL; + cron->hour = value; + + if ((arg = readfield(arg, &value, 1, 31, NULL, rd)) == NULL) + return NULL; + cron->dayofmonth = value; + + if ((arg = readfield(arg, &value, 1, 12, month, rd)) == NULL) + return NULL; + cron->month = value; + + if ((arg = readfield(arg, &value, 0, 6, weekday, rd)) == NULL) + return NULL; + cron->dayofweek = value; + + return arg; +} + +crontab_t *rdcron(int dirfd, const char *filename) +{ + crontab_t *cron, *list = NULL; + rdline_t rd; + char *ptr; + + if (rdline_init(&rd, dirfd, filename)) + return NULL; + + while (rdline(&rd) == 0) { + cron = calloc(1, sizeof(*cron)); + if (cron == NULL) { + rdline_complain(&rd, strerror(errno)); + break; + } + + if (rd.line[0] == '@') { + ptr = cron_interval(cron, &rd); + } else { + ptr = cron_fields(cron, &rd); + } + + if (ptr == NULL) { + free(cron); + continue; + } + + while (isspace(*ptr)) + ++ptr; + + cron->exec = strdup(ptr); + if (cron->exec == NULL) { + rdline_complain(&rd, strerror(errno)); + free(cron); + continue; + } + + cron->next = list; + list = cron; + } + + rdline_cleanup(&rd); + return list; +} |