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
@@ -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
@@ -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;
@@ -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)
 {
@@ -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);
 }
@@ -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');
@@ -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




Reply via email to