Blame SOURCES/0001-Add-random-within-range-operator.patch

66d457
From 0f1704a0f8c5fd2a4da6f530694bdd93a7ca3226 Mon Sep 17 00:00:00 2001
66d457
From: =?UTF-8?q?Ond=C5=99ej=20Poho=C5=99elsk=C3=BD?=
66d457
 <35430604+opohorel@users.noreply.github.com>
66d457
Date: Mon, 8 Nov 2021 16:20:09 +0100
66d457
Subject: [PATCH] Add random within range '~' operator
66d457
66d457
With the operator one can specify for a job a random time or date within
66d457
a specified range for a field.
66d457
The random value is generated when the crontab where the job is
66d457
specified, is loaded.
66d457
---
66d457
 man/crontab.5 |   9 ++
66d457
 src/entry.c   | 262 ++++++++++++++++++++++++++++++++------------------
66d457
 2 files changed, 176 insertions(+), 95 deletions(-)
66d457
66d457
diff --git a/man/crontab.5 b/man/crontab.5
66d457
index a011c89..ba8f0c3 100644
66d457
--- a/man/crontab.5
66d457
+++ b/man/crontab.5
66d457
@@ -199,6 +199,15 @@ hyphen.  The specified range is inclusive.  For example, 8-11 for
66d457
 an 'hours' entry specifies execution at hours 8, 9, 10, and 11. The first
66d457
 number must be less than or equal to the second one.
66d457
 .PP
66d457
+Randomization of the execution time within a range can be used.
66d457
+A random number within a range specified as two numbers separated with
66d457
+a tilde is picked.  The specified range is inclusive.
66d457
+For example, 6~15 for a 'minutes' entry picks a random minute
66d457
+within 6 to 15 range.  The random number is picked when crontab file is parsed.
66d457
+The first number must be less than or equal to the second one. You might omit
66d457
+one or both of the numbers specifying the range.  For example, ~ for a 'minutes'
66d457
+entry picks a random minute within 0 to 59 range.
66d457
+.PP
66d457
 Lists are allowed.  A list is a set of numbers (or ranges) separated by
66d457
 commas.  Examples: "1,2,5,9", "0-4,8-12".
66d457
 .PP
66d457
diff --git a/src/entry.c b/src/entry.c
66d457
index 92b55f5..9276f47 100644
66d457
--- a/src/entry.c
66d457
+++ b/src/entry.c
66d457
@@ -62,9 +62,22 @@ static const char *ecodes[] = {
66d457
 	"out of memory"
66d457
 };
66d457
 
66d457
+typedef enum {
66d457
+	R_START,
66d457
+	R_AST,
66d457
+	R_STEP,
66d457
+	R_TERMS,
66d457
+	R_NUM1,
66d457
+	R_RANGE,
66d457
+	R_RANGE_NUM2,
66d457
+	R_RANDOM,
66d457
+	R_RANDOM_NUM2,
66d457
+	R_FINISH,
66d457
+} range_state_t;
66d457
+
66d457
 static int get_list(bitstr_t *, int, int, const char *[], int, FILE *),
66d457
-get_range(bitstr_t *, int, int, const char *[], int, FILE *),
66d457
-get_number(int *, int, const char *[], int, FILE *, const char *),
66d457
+get_range(bitstr_t *, int, int, const char *[], FILE *),
66d457
+get_number(int *, int, const char *[], FILE *),
66d457
 set_element(bitstr_t *, int, int, int);
66d457
 
66d457
 void free_entry(entry * e) {
66d457
@@ -449,11 +462,14 @@ get_list(bitstr_t * bits, int low, int high, const char *names[],
66d457
 	/* process all ranges
66d457
 	 */
66d457
 	done = FALSE;
66d457
+	/* unget ch to allow get_range() to process it properly 
66d457
+	 */
66d457
+	unget_char(ch, file);
66d457
 	while (!done) {
66d457
-		if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
66d457
+		if (EOF == (ch = get_range(bits, low, high, names, file)))
66d457
 			return (EOF);
66d457
 		if (ch == ',')
66d457
-			ch = get_char(file);
66d457
+			continue;
66d457
 		else
66d457
 			done = TRUE;
66d457
 	}
66d457
@@ -468,137 +484,193 @@ get_list(bitstr_t * bits, int low, int high, const char *names[],
66d457
 	return (ch);
66d457
 }
66d457
 
66d457
+inline static int is_separator(int ch) {
66d457
+	switch (ch) {
66d457
+		case '\t':
66d457
+		case '\n':
66d457
+		case ' ':
66d457
+		case ',':
66d457
+			return 1;
66d457
+		default:
66d457
+			return 0;
66d457
+	}
66d457
+}
66d457
+
66d457
+
66d457
 
66d457
 static int
66d457
 get_range(bitstr_t * bits, int low, int high, const char *names[],
66d457
-	int ch, FILE * file) {
66d457
+		FILE * file) {
66d457
 	/* range = number | number "-" number [ "/" number ]
66d457
+	 *         | [number] "~" [number]
66d457
 	 */
66d457
+	
66d457
+	int ch, i, num1, num2, num3;
66d457
 
66d457
-	int i, num1, num2, num3;
66d457
+	/* default value for step
66d457
+	 */
66d457
+	num3 = 1;
66d457
+	range_state_t state = R_START;
66d457
+
66d457
+	while (state != R_FINISH && ((ch = get_char(file)) != EOF)) {
66d457
+		switch (state) {
66d457
+			case R_START:
66d457
+				if (ch == '*') {
66d457
+					num1 = low;
66d457
+					num2 = high;
66d457
+					state = R_AST;
66d457
+					break;
66d457
+				}
66d457
+				if (ch == '~') {
66d457
+					num1 = low;
66d457
+					state = R_RANDOM;
66d457
+					break;
66d457
+				}
66d457
+				unget_char(ch, file);
66d457
+				if (get_number(&num1, low, names, file) != EOF) {
66d457
+					state = R_NUM1;
66d457
+					break;
66d457
+				}
66d457
+				return (EOF);
66d457
 
66d457
-	Debug(DPARS | DEXT, ("get_range()...entering, exit won't show\n"));
66d457
+			case R_AST:
66d457
+				if (ch == '/') {
66d457
+					state = R_STEP;
66d457
+					break;
66d457
+				}
66d457
+				if (is_separator(ch)) {
66d457
+					state = R_FINISH;
66d457
+					break;
66d457
+				}
66d457
+				return (EOF);
66d457
 
66d457
-		if (ch == '*') {
66d457
-		/* '*' means "first-last" but can still be modified by /step
66d457
-		 */
66d457
-		num1 = low;
66d457
-		num2 = high;
66d457
-		ch = get_char(file);
66d457
-		if (ch == EOF)
66d457
-			return (EOF);
66d457
-	}
66d457
-	else {
66d457
-		ch = get_number(&num1, low, names, ch, file, ",- \t\n");
66d457
-		if (ch == EOF)
66d457
-			return (EOF);
66d457
+			case R_STEP:
66d457
+				if (get_number(&num3, 0, PPC_NULL, file) != EOF) {
66d457
+					state = R_TERMS;
66d457
+					break;
66d457
+				}
66d457
+				return (EOF);
66d457
 
66d457
-		if (ch != '-') {
66d457
-			/* not a range, it's a single number.
66d457
-			 */
66d457
-			if (EOF == set_element(bits, low, high, num1)) {
66d457
-				unget_char(ch, file);
66d457
+			case R_TERMS:
66d457
+				if (is_separator(ch)) {
66d457
+					state = R_FINISH;
66d457
+					break;
66d457
+				}
66d457
 				return (EOF);
66d457
-			}
66d457
-			return (ch);
66d457
-		}
66d457
-		else {
66d457
-			/* eat the dash
66d457
-			 */
66d457
-			ch = get_char(file);
66d457
-			if (ch == EOF)
66d457
+
66d457
+			case R_NUM1:
66d457
+				if (ch == '-') {
66d457
+					state = R_RANGE;
66d457
+					break;
66d457
+				}
66d457
+				if (ch == '~') {
66d457
+					state = R_RANDOM;
66d457
+					break;
66d457
+				}
66d457
+				if (is_separator(ch)) {
66d457
+					num2 = num1;
66d457
+					state = R_FINISH;
66d457
+					break;
66d457
+				}
66d457
 				return (EOF);
66d457
 
66d457
-			/* get the number following the dash
66d457
-			 */
66d457
-			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
66d457
-			if (ch == EOF || num1 > num2)
66d457
+			case R_RANGE:
66d457
+				if (get_number(&num2, low, names, file) != EOF) {
66d457
+					state = R_RANGE_NUM2;
66d457
+					break;
66d457
+				}
66d457
 				return (EOF);
66d457
-		}
66d457
-	}
66d457
 
66d457
-	/* check for step size
66d457
-	 */
66d457
-	if (ch == '/') {
66d457
-		/* eat the slash
66d457
-		 */
66d457
-		ch = get_char(file);
66d457
-		if (ch == EOF)
66d457
-			return (EOF);
66d457
+			case R_RANGE_NUM2:
66d457
+				if (ch == '/') {
66d457
+					state = R_STEP;
66d457
+					break;
66d457
+				}
66d457
+				if (is_separator(ch)) {
66d457
+					state = R_FINISH;
66d457
+					break;
66d457
+				}
66d457
+				return (EOF);
66d457
 
66d457
-		/* get the step size -- note: we don't pass the
66d457
-		 * names here, because the number is not an
66d457
-		 * element id, it's a step size.  'low' is
66d457
-		 * sent as a 0 since there is no offset either.
66d457
-		 */
66d457
-		ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n");
66d457
-		if (ch == EOF || num3 == 0)
66d457
-			return (EOF);
66d457
-	}
66d457
-	else {
66d457
-		/* no step.  default==1.
66d457
-		 */
66d457
-		num3 = 1;
66d457
+			case R_RANDOM:
66d457
+				if (is_separator(ch)) {
66d457
+					num2 = high;
66d457
+					state = R_FINISH;
66d457
+				}
66d457
+				else if (unget_char(ch, file),
66d457
+						get_number(&num2, low, names, file) != EOF) {
66d457
+					state = R_TERMS;
66d457
+				}
66d457
+				/* fail if couldn't find match on previous term
66d457
+				 */
66d457
+				else
66d457
+					return (EOF);
66d457
+
66d457
+				/* if invalid random range was selected */
66d457
+				if (num1 > num2)
66d457
+					return (EOF);
66d457
+
66d457
+				/* select random number in range <num1, num2>
66d457
+				 */
66d457
+				num1 = num2 = random() % (num2 - num1 + 1) + num1;
66d457
+				break;
66d457
+
66d457
+
66d457
+			default:
66d457
+				/* We should never get here
66d457
+				 */
66d457
+				return (EOF);
66d457
+		}
66d457
 	}
66d457
+	if (state != R_FINISH || ch == EOF)
66d457
+		return (EOF);
66d457
 
66d457
-	/* range. set all elements from num1 to num2, stepping
66d457
-	 * by num3.  (the step is a downward-compatible extension
66d457
-	 * proposed conceptually by bob@acornrc, syntactically
66d457
-	 * designed then implemented by paul vixie).
66d457
-	 */
66d457
 	for (i = num1; i <= num2; i += num3)
66d457
 		if (EOF == set_element(bits, low, high, i)) {
66d457
 			unget_char(ch, file);
66d457
 			return (EOF);
66d457
 		}
66d457
-
66d457
-	return (ch);
66d457
+	return ch;
66d457
 }
66d457
 
66d457
 static int
66d457
-get_number(int *numptr, int low, const char *names[], int ch, FILE * file,
66d457
-	const char *terms) {
66d457
+get_number(int *numptr, int low, const char *names[], FILE * file) {
66d457
 	char temp[MAX_TEMPSTR], *pc;
66d457
-	int len, i;
66d457
+	int len, i, ch;
66d457
+	char *endptr;
66d457
 
66d457
 	pc = temp;
66d457
 	len = 0;
66d457
 
66d457
-	/* first look for a number */
66d457
-	while (isdigit((unsigned char) ch)) {
66d457
+	/* get all alnum characters available */
66d457
+	while (isalnum((ch = get_char(file)))) {
66d457
 		if (++len >= MAX_TEMPSTR)
66d457
 			goto bad;
66d457
 		*pc++ = (char)ch;
66d457
-		ch = get_char(file);
66d457
 	}
66d457
-	*pc = '\0';
66d457
-	if (len != 0) {
66d457
-		/* got a number, check for valid terminator */
66d457
-		if (!strchr(terms, ch))
66d457
-			goto bad;
66d457
-		*numptr = atoi(temp);
66d457
-		return (ch);
66d457
+	if (len == 0)
66d457
+		goto bad;
66d457
+
66d457
+	unget_char(ch, file);
66d457
+
66d457
+	/* try to get number */
66d457
+	*numptr = (int) strtol(temp, &endptr, 10);
66d457
+	if (*endptr == '\0' && temp != endptr) {
66d457
+		/* We have a number */
66d457
+		return 0;
66d457
 	}
66d457
 
66d457
 	/* no numbers, look for a string if we have any */
66d457
 	if (names) {
66d457
-		while (isalpha((unsigned char) ch)) {
66d457
-			if (++len >= MAX_TEMPSTR)
66d457
-				goto bad;
66d457
-			*pc++ = (char)ch;
66d457
-			ch = get_char(file);
66d457
-		}
66d457
-		*pc = '\0';
66d457
-		if (len != 0 && strchr(terms, ch)) {
66d457
-			for (i = 0; names[i] != NULL; i++) {
66d457
-				Debug(DPARS | DEXT,
66d457
-					("get_num, compare(%s,%s)\n", names[i], temp));
66d457
-					if (!strcasecmp(names[i], temp)) {
66d457
-					*numptr = i + low;
66d457
-					return (ch);
66d457
-				}
66d457
+		for (i = 0; names[i] != NULL; i++) {
66d457
+			Debug(DPARS | DEXT, ("get_num, compare(%s,%s)\n", names[i], temp));
66d457
+			if (strcasecmp(names[i], temp) == 0) {
66d457
+				*numptr = i + low;
66d457
+				return 0;
66d457
 			}
66d457
 		}
66d457
+	} else {
66d457
+		goto bad;
66d457
 	}
66d457
 
66d457
   bad:
66d457
-- 
66d457
2.35.1
66d457