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

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