sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

commit 6c8dc1522ca47f65260f9605b6003fae547f9454
parent 279cec88898c2386430d701847739209fabf6208
Author: Elie Le Vaillant <eolien55@disroot.org>
Date:   Fri,  6 Dec 2024 10:37:35 +0100

cron: heavy refactor of parsefield() and matchentry()

This patch heavily simplifies the parsing logic of parsefield(), and
makes the grammar more standards-compliant.  Before, this cron
implementation would only recognize repeats ("/n" at the end of a
range, or of a wildcar) for wildcars, and list elements could only
be numbers.  Now, the basic type is a range, which also includes and
simplifies numbers, wildcars and "repeats".  A field is thus a list of
this basic types.  So now we can have things such as "*/3,14-18" as a
field.  Every range (except for numbers) allow the "/n" syntax.

Also added: random fields, using ~.  This is a non-standard addition,
but which matches many cron implementations, like OpenBSD's for
instance.  Its usefulness is debatable.

Diffstat:
Mcron.1 | 8++++++++
Mcron.c | 259+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 144 insertions(+), 123 deletions(-)

diff --git a/cron.1 b/cron.1 @@ -21,3 +21,11 @@ instead of the default .It Fl n Do not daemonize. .El +.Sh NOTES +This +.Nm +accepts normal, standard syntax. +It also includes the '~' syntax for random ranges, +which is non-standard. +.Sh SEE ALSO +.Xf crontab 5 diff --git a/cron.c b/cron.c @@ -17,19 +17,13 @@ #include "queue.h" #include "util.h" -struct field { - enum { - ERROR, - WILDCARD, - NUMBER, - RANGE, - REPEAT, - LIST - } type; - long *val; - int len; +struct range { + long low, high, repeat, random; + TAILQ_ENTRY(range) entry; }; +TAILQ_HEAD(field, range); + struct ctabentry { struct field min; struct field hour; @@ -202,159 +196,175 @@ matchentry(struct ctabentry *cte, struct tm *tm) { .f = &cte->wday, .tm = tm->tm_wday, .len = 7 }, }; size_t i; - int j; + int found, t; + long low; + struct range *r; for (i = 0; i < LEN(matchtbl); i++) { - switch (matchtbl[i].f->type) { - case WILDCARD: - continue; - case NUMBER: - if (matchtbl[i].f->val[0] == matchtbl[i].tm) - continue; - break; - case RANGE: - if (matchtbl[i].f->val[0] <= matchtbl[i].tm) - if (matchtbl[i].f->val[1] >= matchtbl[i].tm) - continue; - break; - case REPEAT: - if (matchtbl[i].tm > 0) { - if (matchtbl[i].tm % matchtbl[i].f->val[0] == 0) - continue; - } else { - if (matchtbl[i].len % matchtbl[i].f->val[0] == 0) - continue; + found = 0; + t = matchtbl[i].tm; + TAILQ_FOREACH(r, matchtbl[i].f, entry) { + if (r->random) + low = r->random; + else + low = r->low; + if (low <= t && r->high >= t && t % r->repeat == 0) { + found = 1; + break; } - break; - case LIST: - for (j = 0; j < matchtbl[i].f->len; j++) - if (matchtbl[i].f->val[j] == matchtbl[i].tm) - break; - if (j < matchtbl[i].f->len) - continue; - break; - default: - break; } - break; + if (!found) + break; } if (i != LEN(matchtbl)) return 0; + + for (i = 0; i < LEN(matchtbl); i++) { /* only if entry is matched */ + TAILQ_FOREACH(r, matchtbl[i].f, entry) { + if (r->random) + r->random = random_uniform(r->high - r->low) + r->low; + } + } + return 1; } + static int -parsefield(const char *field, long low, long high, struct field *f) +parserange(char *str, long low, long high, struct range *r) { - int i; - char *e1, *e2; - const char *p; - - p = field; - while (isdigit(*p)) - p++; - - f->type = ERROR; - - switch (*p) { - case '*': - if (strcmp(field, "*") == 0) { - f->val = NULL; - f->len = 0; - f->type = WILDCARD; - } else if (strncmp(field, "*/", 2) == 0) { - f->val = emalloc(sizeof(*f->val)); - f->len = 1; - - errno = 0; - f->val[0] = strtol(field + 2, &e1, 10); - if (e1[0] != '\0' || errno != 0 || f->val[0] == 0) - break; - - f->type = REPEAT; - } - break; - case '\0': - f->val = emalloc(sizeof(*f->val)); - f->len = 1; - - errno = 0; - f->val[0] = strtol(field, &e1, 10); - if (e1[0] != '\0' || errno != 0) - break; + /* range = number | + * [number] "~" [number] ["/" number] | + * number "-" number ["/" number] + */ + char *range, *repeat, *strlow, *strhigh; + char *e; + + r->random = 0; + + range = strsep(&str, "/"); + repeat = strsep(&str, "/"); + if (!range || !*range) + return -1; - f->type = NUMBER; + switch (*range) { + case '~': + r->random = 1; + case '*': /* fallthru */ + if (range[1] != '\0') + return -1; + r->low = low; + r->high = high; break; - case '-': - f->val = emalloc(2 * sizeof(*f->val)); - f->len = 2; - - errno = 0; - f->val[0] = strtol(field, &e1, 10); - if (e1[0] != '-' || errno != 0) - break; + ARGNUM: + strlow = strsep(&range, "-"); + strhigh = strsep(&range, "-"); + if (!*strlow) /* i.e. - */ + return -1; errno = 0; - f->val[1] = strtol(e1 + 1, &e2, 10); - if (e2[0] != '\0' || errno != 0) - break; - - f->type = RANGE; + r->low = strtol(strlow, &e, 10); + if (*e || errno != 0) + return -1; + if (strhigh) { + if (!*strhigh || range != NULL) /* i.e. N- or N-M-... */ + return -1; + errno = 0; + r->high = strtol(strhigh, &e, 10); + if (*e || errno != 0) + return -1; + } else { + e = strsep(&strlow, "~"); + if (!strlow) /* i.e. N */ + r->high = r->low; + strhigh = strsep(&strlow, "-"); + if (strhigh) { + if (!*strhigh || strlow != NULL) /* i.e. N~ or N~M~... */ + return -1; + r->random = 1; + + errno = 0; + r->high = strtol(strhigh, &e, 10); + if (*e || errno != 0) + return -1; + } + } break; - case ',': - for (i = 1; isdigit(*p) || *p == ','; p++) - if (*p == ',') - i++; - f->val = emalloc(i * sizeof(*f->val)); - f->len = i; + } + if (repeat) { + if (!*repeat || range != NULL) + return -1; errno = 0; - f->val[0] = strtol(field, &e1, 10); - if (f->val[0] < low || f->val[0] > high) - break; + r->repeat = strtol(repeat, &e, 10); + if (*e || errno != 0 || r->repeat == 0) + return -1; + } else { + r->repeat = 1; + } - for (i = 1; *e1 == ',' && errno == 0; i++) { - errno = 0; - f->val[i] = strtol(e1 + 1, &e2, 10); - e1 = e2; - } - if (e1[0] != '\0' || errno != 0) - break; + if (r->random) { + /* random replaces low in matchentry(), if it is >0 */ + r->random = random_uniform(r->high - r->low) + r->low; + } - f->type = LIST; - break; - default: + if (r->low < low || r->low > high || r->high < low || r->high > high || r->repeat < low || r->repeat > high) { return -1; } - for (i = 0; i < f->len; i++) - if (f->val[i] < low || f->val[i] > high) - f->type = ERROR; + return 0; +} - if (f->type == ERROR) { - free(f->val); - return -1; +static int +parsefield(char *field, long low, long high, struct field *f) +{ + char *elem; + struct range *r; + + int first = 1; + while ((elem = strsep(&field, ",")) != NULL) { + if (*elem == '\0') + return -1; + first = 0; + + r = emalloc(sizeof(*r)); + if (parserange(elem, low, high, r)) + return -1; + TAILQ_INSERT_TAIL(f, r, entry); } + if (first) + return -1; + return 0; } static void +freefield(struct field *f) +{ + struct range *r; + while ((r = TAILQ_FIRST(f))) { + TAILQ_REMOVE(f, r, entry); + free(r); + } +} + +static void freecte(struct ctabentry *cte, int nfields) { switch (nfields) { case 6: free(cte->cmd); case 5: - free(cte->wday.val); + freefield(&cte->wday); case 4: - free(cte->mon.val); + freefield(&cte->mon); case 3: - free(cte->mday.val); + freefield(&cte->mday); case 2: - free(cte->hour.val); + freefield(&cte->hour); case 1: - free(cte->min.val); + freefield(&cte->min); } free(cte); } @@ -412,6 +422,7 @@ loadentries(void) flim[4].f = &cte->wday; for (x = 0; x < LEN(flim); x++) { + TAILQ_INIT(flim[x].f); do col = strsep(&p, "\t\n "); while (col && col[0] == '\0'); @@ -525,6 +536,8 @@ main(int argc, char *argv[]) sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); + random_seed(); + loadentries(); while (1) {