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

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