On Mon, 13 Apr 2020 21:45:21 -0600, Bob Beck wrote:

> Like this one plenty.  I think it's ok the values change on reload. 

Here's a cleaned up version that includes the man page.  The random
interval can now be one of "~", "low~high", "low~", or "~high" where
if low and/or high are omitted, the appropriate value for the field
is used.  Multiple random intervals can be used, separated by a
comma.

 - todd

Index: usr.sbin/cron/crontab.5
===================================================================
RCS file: /cvs/src/usr.sbin/cron/crontab.5,v
retrieving revision 1.37
diff -u -p -u -r1.37 crontab.5
--- usr.sbin/cron/crontab.5     6 Jan 2020 19:44:09 -0000       1.37
+++ usr.sbin/cron/crontab.5     14 Apr 2020 19:01:33 -0000
@@ -144,7 +144,18 @@ For example,
 .Ar hour
 entry specifies execution at hours 8, 9, 10 and 11.
 .Pp
-Step values can be used in conjunction with ranges.
+A random value (within the legal range) may be obtained by using the
+.Ql ~
+character in a field.
+The interval of the random value may be specified explicitly, for example
+.Dq 0~30
+will result in a random value between 0 and 30 inclusive.
+If either (or both) of the numbers on either side of the
+.Ql ~
+are omitted, the appropriate limit (low or high) for the field will be used.
+.Pp
+Step values can be used in conjunction with ranges (but not random ranges
+which represent a single number).
 Following a range with
 .No / Ns Ar number
 specifies skips of
@@ -318,6 +329,9 @@ MAILTO=paul
 23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
 
 5 4 * * sun     echo "run at 5 after 4 every sunday"
+
+# run hourly at a random time within the first 30 minutes of the hour
+0~30 * * * *   /usr/libexec/spamd-setup
 .Ed
 .Sh SEE ALSO
 .Xr crontab 1 ,
@@ -337,6 +351,10 @@ field may use 7 to represent Sunday.
 .It
 Ranges may include
 .Dq steps .
+.It
+Random intervals are supported using the
+.Dq ~
+character.
 .It
 Months or days of the week can be specified by name.
 .It
Index: usr.sbin/cron/entry.c
===================================================================
RCS file: /cvs/src/usr.sbin/cron/entry.c,v
retrieving revision 1.49
diff -u -p -u -r1.49 entry.c
--- usr.sbin/cron/entry.c       13 Jun 2018 11:27:30 -0000      1.49
+++ usr.sbin/cron/entry.c       14 Apr 2020 19:03:47 -0000
@@ -450,33 +450,29 @@ static int
 get_range(bitstr_t *bits, int low, int high, const char *names[],
          int ch, FILE *file)
 {
-       /* range = number | number "-" number [ "/" number ]
+       /* range = number | number* "~" number* | number "-" number ["/" number]
         */
 
        int i, num1, num2, num3;
 
+       num1 = low;
+       num2 = high;
+
        if (ch == '*') {
-               /* '*' means "first-last" but can still be modified by /step
+               /* '*' means [low, high] but can still be modified by /step
                 */
-               num1 = low;
-               num2 = high;
                ch = get_char(file);
                if (ch == EOF)
                        return (EOF);
        } else {
-               ch = get_number(&num1, low, names, ch, file, ",- \t\n");
-               if (ch == EOF)
-                       return (EOF);
-
-               if (ch != '-') {
-                       /* not a range, it's a single number.
-                        */
-                       if (EOF == set_element(bits, low, high, num1)) {
-                               unget_char(ch, file);
+               if (ch != '~') {
+                       ch = get_number(&num1, low, names, ch, file, ",-~ 
\t\n");
+                       if (ch == EOF)
                                return (EOF);
-                       }
-                       return (ch);
-               } else {
+               }
+
+               switch (ch) {
+               case '-':
                        /* eat the dash
                         */
                        ch = get_char(file);
@@ -488,6 +484,37 @@ get_range(bitstr_t *bits, int low, int h
                        ch = get_number(&num2, low, names, ch, file, "/, \t\n");
                        if (ch == EOF || num1 > num2)
                                return (EOF);
+                       break;
+               case '~':
+                       /* eat the tilde
+                        */
+                       ch = get_char(file);
+                       if (ch == EOF)
+                               return (EOF);
+
+                       /* get the (optional) number following the tilde
+                        */
+                       ch = get_number(&num2, low, names, ch, file, ", \t\n");
+                       if (ch == EOF)
+                               ch = get_char(file);
+                       if (ch == EOF || num1 > num2) {
+                               unget_char(ch, file);
+                               return (EOF);
+                       }
+
+                       /* get a random number in the interval [num1, num2]
+                        */
+                       num3 = num1;
+                       num1 = arc4random_uniform(num2 - num3 + 1) + num3;
+                       /* FALLTHROUGH */
+               default:
+                       /* not a range, it's a single number.
+                        */
+                       if (EOF == set_element(bits, low, high, num1)) {
+                               unget_char(ch, file);
+                               return (EOF);
+                       }
+                       return (ch);
                }
        }
 

Reply via email to