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:
M | cron.1 | | | 8 | ++++++++ |
M | cron.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) {