[hackers] [sbase][PATCH] cron: heavy refactor of parsefield() and matchentry()

From: Elie Le Vaillant <eolien55_AT_disroot.org>
Date: Wed, 28 Feb 2024 00:47:27 +0100

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.
---
 cron.1 |   8 ++
 cron.c | 259 ++++++++++++++++++++++++++++++---------------------------
 2 files changed, 144 insertions(+), 123 deletions(-)
diff --git a/cron.1 b/cron.1
index 4553b46..2f62fde 100644
--- a/cron.1
+++ b/cron.1
_AT_@ -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
index 77304cc..5d940cd 100644
--- a/cron.c
+++ b/cron.c
_AT_@ -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;
_AT_@ -202,143 +196,159 @@ 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)
 {
_AT_@ -346,15 +356,15 @@ freecte(struct ctabentry *cte, int 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);
 }
_AT_@ -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');
_AT_@ -525,6 +536,8 @@ main(int argc, char *argv[])
 	sigaction(SIGHUP, &sa, NULL);
 	sigaction(SIGTERM, &sa, NULL);
 
+	random_seed();
+
 	loadentries();
 
 	while (1) {
-- 
2.44.0
Received on Wed Feb 28 2024 - 00:47:27 CET

This archive was generated by hypermail 2.3.0 : Wed Feb 28 2024 - 01:36:53 CET