]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/calendarspec.c
calendarspec: fix duplicate detection (#5310)
[thirdparty/systemd.git] / src / basic / calendarspec.c
index 514587d23728f4d3a5f5f0d630110679faaa16cc..2323eb85556b24df4adc1a87a7337eb9e20ad5cb 100644 (file)
@@ -20,6 +20,7 @@
 #include <alloca.h>
 #include <ctype.h>
 #include <errno.h>
+#include <limits.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "parse-util.h"
 #include "string-util.h"
 
-/* Longest valid date/time range is 1970..2199 */
-#define MAX_RANGE_LEN   230
-#define MIN_YEAR       1970
-#define MAX_YEAR       2199
-#define BITS_WEEKDAYS   127
+#define BITS_WEEKDAYS 127
+#define MIN_YEAR 1970
+#define MAX_YEAR 2199
 
 static void free_chain(CalendarComponent *c) {
         CalendarComponent *n;
@@ -67,9 +66,14 @@ void calendar_spec_free(CalendarSpec *c) {
 static int component_compare(const void *_a, const void *_b) {
         CalendarComponent * const *a = _a, * const *b = _b;
 
-        if ((*a)->value < (*b)->value)
+        if ((*a)->start < (*b)->start)
                 return -1;
-        if ((*a)->value > (*b)->value)
+        if ((*a)->start > (*b)->start)
+                return 1;
+
+        if ((*a)->stop < (*b)->stop)
+                return -1;
+        if ((*a)->stop > (*b)->stop)
                 return 1;
 
         if ((*a)->repeat < (*b)->repeat)
@@ -80,15 +84,24 @@ static int component_compare(const void *_a, const void *_b) {
         return 0;
 }
 
-static void sort_chain(CalendarComponent **c) {
+static void normalize_chain(CalendarComponent **c) {
         unsigned n = 0, k;
         CalendarComponent **b, *i, **j, *next;
 
         assert(c);
 
-        for (i = *c; i; i = i->next)
+        for (i = *c; i; i = i->next) {
                 n++;
 
+                /*
+                 * While we're counting the chain, also normalize `stop`
+                 * so the length of the range is a multiple of `repeat`
+                 */
+                if (i->stop > i->start && i->repeat > 0)
+                        i->stop -= (i->stop - i->start) % i->repeat;
+
+        }
+
         if (n <= 1)
                 return;
 
@@ -103,8 +116,7 @@ static void sort_chain(CalendarComponent **c) {
 
         /* Drop non-unique entries */
         for (k = n-1; k > 0; k--) {
-                if (b[k-1]->value == next->value &&
-                    b[k-1]->repeat == next->repeat) {
+                if (component_compare(&b[k-1], &next) == 0) {
                         free(b[k-1]);
                         continue;
                 }
@@ -120,15 +132,19 @@ static void fix_year(CalendarComponent *c) {
         /* Turns 12 → 2012, 89 → 1989 */
 
         while (c) {
-                CalendarComponent *n = c->next;
+                if (c->start >= 0 && c->start < 70)
+                        c->start += 2000;
 
-                if (c->value >= 0 && c->value < 70)
-                        c->value += 2000;
+                if (c->stop >= 0 && c->stop < 70)
+                        c->stop += 2000;
 
-                if (c->value >= 70 && c->value < 100)
-                        c->value += 1900;
+                if (c->start >= 70 && c->start < 100)
+                        c->start += 1900;
 
-                c = n;
+                if (c->stop >= 70 && c->stop < 100)
+                        c->stop += 1900;
+
+                c = c->next;
         }
 }
 
@@ -143,12 +159,12 @@ int calendar_spec_normalize(CalendarSpec *c) {
 
         fix_year(c->year);
 
-        sort_chain(&c->year);
-        sort_chain(&c->month);
-        sort_chain(&c->day);
-        sort_chain(&c->hour);
-        sort_chain(&c->minute);
-        sort_chain(&c->microsecond);
+        normalize_chain(&c->year);
+        normalize_chain(&c->month);
+        normalize_chain(&c->day);
+        normalize_chain(&c->hour);
+        normalize_chain(&c->minute);
+        normalize_chain(&c->microsecond);
 
         return 0;
 }
@@ -157,20 +173,32 @@ _pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool end_
         if (!c)
                 return true;
 
-        if (c->value < from || c->value > to)
+        /* Forbid dates more than 28 days from the end of the month */
+        if (end_of_month)
+                to -= 3;
+
+        if (c->start < from || c->start > to)
                 return false;
 
         /*
          * c->repeat must be short enough so at least one repetition may
          * occur before the end of the interval.  For dates scheduled
-         * relative to the end of the month, c->value corresponds to the
-         * Nth last day of the month.
+         * relative to the end of the month, c->start and c->stop
+         * correspond to the Nth last day of the month.
          */
-        if (end_of_month && c->value - c->repeat < from)
-                return false;
+        if (c->stop >= 0) {
+                if (c->stop < from || c ->stop > to)
+                        return false;
 
-        if (!end_of_month && c->value + c->repeat > to)
-                return false;
+                if (c->start + c->repeat > c->stop)
+                        return false;
+        } else {
+                if (end_of_month && c->start - c->repeat < from)
+                        return false;
+
+                if (!end_of_month && c->start + c->repeat > to)
+                        return false;
+        }
 
         if (c->next)
                 return chain_valid(c->next, from, to, end_of_month);
@@ -255,7 +283,6 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
 }
 
 static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
-        const CalendarComponent *n, *p;
         int d = usec ? (int) USEC_PER_SEC : 1;
 
         assert(f);
@@ -265,41 +292,34 @@ static void format_chain(FILE *f, int space, const CalendarComponent *c, bool us
                 return;
         }
 
-        assert(c->value >= 0);
-
-        fprintf(f, "%0*i", space, c->value / d);
-        if (c->value % d != 0)
-                fprintf(f, ".%06i", c->value % d);
-
-        if (c->repeat != 0)
-                fprintf(f, "/%i", c->repeat / d);
-        if (c->repeat % d != 0)
-                fprintf(f, ".%06i", c->repeat % d);
+        if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) {
+                fputc('*', f);
+                return;
+        }
 
-        p = c;
-        for (;;) {
-                n = p->next;
+        assert(c->start >= 0);
 
-                if (!n || n->repeat || p->repeat)
-                        break;
+        fprintf(f, "%0*i", space, c->start / d);
+        if (c->start % d > 0)
+                fprintf(f, ".%06i", c->start % d);
 
-                if (n->value - p->value != d)
-                       break;
+        if (c->stop > 0)
+                fprintf(f, "..%0*i", space, c->stop / d);
+        if (c->stop % d > 0)
+                fprintf(f, ".%06i", c->stop % d);
 
-                p = n;
-        }
+        if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
+                fprintf(f, "/%i", c->repeat / d);
+        if (c->repeat % d > 0)
+                fprintf(f, ".%06i", c->repeat % d);
 
-        if (p->value - c->value >= 2 * d) {
-                fputs("..", f);
-                format_chain(f, space, p, usec);
-        } else if (c->next) {
+        if (c->next) {
                 fputc(',', f);
                 format_chain(f, space, c->next, usec);
         }
 }
 
 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
-        CalendarComponent *cc;
         char *buf = NULL;
         size_t sz = 0;
         FILE *f;
@@ -327,12 +347,7 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
         fputc(':', f);
         format_chain(f, 2, c->minute, false);
         fputc(':', f);
-
-        cc = c->microsecond;
-        if (cc && !cc->value && cc->repeat == USEC_PER_SEC && !cc->next)
-                fputc('*', f);
-        else
-                format_chain(f, 2, c->microsecond, true);
+        format_chain(f, 2, c->microsecond, true);
 
         if (c->utc)
                 fputs(" UTC", f);
@@ -462,7 +477,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
                         *p += 1;
                 }
 
-                /* Allow  a trailing comma but not an open range */
+                /* Allow a trailing comma but not an open range */
                 if (**p == 0 || **p == ' ') {
                         *p += strspn(*p, " ");
                         return l < 0 ? 0 : -EINVAL;
@@ -472,7 +487,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
         }
 }
 
-static int parse_component_decimal(const char **p, bool usec, unsigned long *res) {
+static int parse_component_decimal(const char **p, bool usec, int *res) {
         unsigned long value;
         const char *e = NULL;
         char *ee = NULL;
@@ -487,8 +502,6 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res
                 return -errno;
         if (ee == *p)
                 return -EINVAL;
-        if ((unsigned long) (int) value != value)
-                return -ERANGE;
         e = ee;
 
         if (usec) {
@@ -496,12 +509,10 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res
                         return -ERANGE;
 
                 value *= USEC_PER_SEC;
-                if (*e == '.') {
-                        unsigned add;
 
-                        /* This is the start of a range, not a fractional part */
-                        if (e[1] == '.')
-                                goto finish;
+                /* One "." is a decimal point, but ".." is a range separator */
+                if (e[0] == '.' && e[1] != '.') {
+                        unsigned add;
 
                         e++;
                         r = parse_fractional_part_u(&e, 6, &add);
@@ -514,7 +525,9 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res
                 }
         }
 
-finish:
+        if (value > INT_MAX)
+                return -ERANGE;
+
         *p = e;
         *res = value;
 
@@ -530,7 +543,8 @@ static int const_chain(int value, CalendarComponent **c) {
         if (!cc)
                 return -ENOMEM;
 
-        cc->value = value;
+        cc->start = value;
+        cc->stop = -1;
         cc->repeat = 0;
         cc->next = *c;
 
@@ -540,9 +554,8 @@ static int const_chain(int value, CalendarComponent **c) {
 }
 
 static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
-        unsigned long i, value, range_end, range_inc, repeat = 0;
+        int r, start, stop = -1, repeat = 0;
         CalendarComponent *cc;
-        int r;
         const char *e;
 
         assert(p);
@@ -550,10 +563,19 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
 
         e = *p;
 
-        r = parse_component_decimal(&e, usec, &value);
+        r = parse_component_decimal(&e, usec, &start);
         if (r < 0)
                 return r;
 
+        if (e[0] == '.' && e[1] == '.') {
+                e += 2;
+                r = parse_component_decimal(&e, usec, &stop);
+                if (r < 0)
+                        return r;
+
+                repeat = usec ? USEC_PER_SEC : 1;
+        }
+
         if (*e == '/') {
                 e++;
                 r = parse_component_decimal(&e, usec, &repeat);
@@ -562,30 +584,6 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
 
                 if (repeat == 0)
                         return -ERANGE;
-        } else if (e[0] == '.' && e[1] == '.') {
-                e += 2;
-                r = parse_component_decimal(&e, usec, &range_end);
-                if (r < 0)
-                        return r;
-
-                if (value >= range_end)
-                        return -EINVAL;
-
-                range_inc = usec ? USEC_PER_SEC : 1;
-
-                /* Don't allow impossibly large ranges... */
-                if (range_end - value >= MAX_RANGE_LEN * range_inc)
-                        return -EINVAL;
-
-                /* ...or ranges with only a single element */
-                if (range_end - value < range_inc)
-                        return -EINVAL;
-
-                for (i = value; i <= range_end; i += range_inc) {
-                        r = const_chain(i, c);
-                        if (r < 0)
-                                return r;
-                }
         }
 
         if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != '~' && *e != ':')
@@ -595,7 +593,8 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
         if (!cc)
                 return -ENOMEM;
 
-        cc->value = value;
+        cc->start = start;
+        cc->stop = stop;
         cc->repeat = repeat;
         cc->next = *c;
 
@@ -688,8 +687,11 @@ static int parse_date(const char **p, CalendarSpec *c) {
                 c->month = first;
                 c->day = second;
                 return 0;
-        } else if (c->end_of_month)
+        } else if (c->end_of_month) {
+                free_chain(first);
+                free_chain(second);
                 return -EINVAL;
+        }
 
         if (*t == '~')
                 c->end_of_month = true;
@@ -1011,11 +1013,23 @@ fail:
         return r;
 }
 
+static int find_end_of_month(struct tm *tm, bool utc, int day) {
+        struct tm t = *tm;
+
+        t.tm_mon++;
+        t.tm_mday = 1 - day;
+
+        if (mktime_or_timegm(&t, utc) < 0 ||
+            t.tm_mon != tm->tm_mon)
+                return -1;
+
+        return t.tm_mday;
+}
+
 static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
                                    struct tm *tm, int *val) {
-        const CalendarComponent *n, *p = c;
-        struct tm t;
-        int v, d = -1;
+        const CalendarComponent *p = c;
+        int start, stop, d = -1;
         bool d_set = false;
         int r;
 
@@ -1025,40 +1039,36 @@ static int find_matching_component(const CalendarSpec *spec, const CalendarCompo
                 return 0;
 
         while (c) {
-                n = c->next;
+                start = c->start;
+                stop = c->stop;
 
                 if (spec->end_of_month && p == spec->day) {
-                        t = *tm;
-                        t.tm_mon++;
-                        t.tm_mday = 1 - c->value;
-
-                        if (mktime_or_timegm(&t, spec->utc) == (time_t) -1 ||
-                            t.tm_mon != tm->tm_mon)
-                                v = -1;
-                        else
-                                v = t.tm_mday;
-                } else
-                        v = c->value;
+                        start = find_end_of_month(tm, spec->utc, start);
+                        stop = find_end_of_month(tm, spec->utc, stop);
+
+                        if (stop > 0)
+                                SWAP_TWO(start, stop);
+                }
 
-                if (v >= *val) {
+                if (start >= *val) {
 
-                        if (!d_set || v < d) {
-                                d = v;
+                        if (!d_set || start < d) {
+                                d = start;
                                 d_set = true;
                         }
 
                 } else if (c->repeat > 0) {
                         int k;
 
-                        k = v + c->repeat * ((*val - v + c->repeat -1) / c->repeat);
+                        k = start + c->repeat * ((*val - start + c->repeat - 1) / c->repeat);
 
-                        if (!d_set || k < d) {
+                        if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
                                 d = k;
                                 d_set = true;
                         }
                 }
 
-                c = n;
+                c = c->next;
         }
 
         if (!d_set)
@@ -1075,7 +1085,7 @@ static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
 
         t = *tm;
 
-        if (mktime_or_timegm(&t, utc) == (time_t) -1)
+        if (mktime_or_timegm(&t, utc) < 0)
                 return true;
 
         /*
@@ -1104,7 +1114,7 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
                 return true;
 
         t = *tm;
-        if (mktime_or_timegm(&t, utc) == (time_t) -1)
+        if (mktime_or_timegm(&t, utc) < 0)
                 return false;
 
         k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
@@ -1217,6 +1227,9 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
         assert(spec);
         assert(next);
 
+        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
+                return -EINVAL;
+
         usec++;
         t = (time_t) (usec / USEC_PER_SEC);
         assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
@@ -1227,7 +1240,7 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
                 return r;
 
         t = mktime_or_timegm(&tm, spec->utc);
-        if (t == (time_t) -1)
+        if (t < 0)
                 return -EINVAL;
 
         *next = (usec_t) t * USEC_PER_SEC + tm_usec;