1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <ctype.h>
4 #include <errno.h>
5 #include <limits.h>
6 #include <stddef.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/mman.h>
10 #include <unistd.h>
11 
12 #include "alloc-util.h"
13 #include "calendarspec.h"
14 #include "errno-util.h"
15 #include "fileio.h"
16 #include "macro.h"
17 #include "parse-util.h"
18 #include "process-util.h"
19 #include "sort-util.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "time-util.h"
23 
24 #define BITS_WEEKDAYS 127
25 #define MIN_YEAR 1970
26 #define MAX_YEAR 2199
27 
28 /* An arbitrary limit on the length of the chains of components. We don't want to
29  * build a very long linked list, which would be slow to iterate over and might cause
30  * our stack to overflow. It's unlikely that legitimate uses require more than a few
31  * linked components anyway. */
32 #define CALENDARSPEC_COMPONENTS_MAX 240
33 
34 /* Let's make sure that the microsecond component is safe to be stored in an 'int' */
35 assert_cc(INT_MAX >= USEC_PER_SEC);
36 
chain_free(CalendarComponent * c)37 static CalendarComponent* chain_free(CalendarComponent *c) {
38         while (c) {
39                 CalendarComponent *n = c->next;
40                 free(c);
41                 c = n;
42         }
43         return NULL;
44 }
45 
46 DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarComponent*, chain_free);
47 
calendar_spec_free(CalendarSpec * c)48 CalendarSpec* calendar_spec_free(CalendarSpec *c) {
49 
50         if (!c)
51                 return NULL;
52 
53         chain_free(c->year);
54         chain_free(c->month);
55         chain_free(c->day);
56         chain_free(c->hour);
57         chain_free(c->minute);
58         chain_free(c->microsecond);
59         free(c->timezone);
60 
61         return mfree(c);
62 }
63 
component_compare(CalendarComponent * const * a,CalendarComponent * const * b)64 static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
65         int r;
66 
67         r = CMP((*a)->start, (*b)->start);
68         if (r != 0)
69                 return r;
70 
71         r = CMP((*a)->stop, (*b)->stop);
72         if (r != 0)
73                 return r;
74 
75         return CMP((*a)->repeat, (*b)->repeat);
76 }
77 
normalize_chain(CalendarComponent ** c)78 static void normalize_chain(CalendarComponent **c) {
79         assert(c);
80 
81         size_t n = 0;
82         for (CalendarComponent *i = *c; i; i = i->next) {
83                 n++;
84 
85                 /* While we're counting the chain, also normalize 'stop'
86                  * so the length of the range is a multiple of 'repeat'. */
87                 if (i->stop > i->start && i->repeat > 0)
88                         i->stop -= (i->stop - i->start) % i->repeat;
89 
90                 /* If a repeat value is specified, but it cannot even be triggered once, let's suppress it.
91                  *
92                  * Similarly, if the stop value is the same as the start value, then let's just make this a
93                  * non-repeating chain element. */
94                 if ((i->stop > i->start && i->repeat > 0 && i->start + i->repeat > i->stop) ||
95                     i->start == i->stop) {
96                         i->repeat = 0;
97                         i->stop = -1;
98                 }
99         }
100 
101         if (n <= 1)
102                 return;
103 
104         CalendarComponent **b, **j;
105         b = j = newa(CalendarComponent*, n);
106         for (CalendarComponent *i = *c; i; i = i->next)
107                 *(j++) = i;
108 
109         typesafe_qsort(b, n, component_compare);
110 
111         b[n-1]->next = NULL;
112         CalendarComponent *next = b[n-1];
113 
114         /* Drop non-unique entries */
115         for (size_t k = n-1; k > 0; k--) {
116                 if (component_compare(&b[k-1], &next) == 0) {
117                         free(b[k-1]);
118                         continue;
119                 }
120 
121                 b[k-1]->next = next;
122                 next = b[k-1];
123         }
124 
125         *c = next;
126 }
127 
fix_year(CalendarComponent * c)128 static void fix_year(CalendarComponent *c) {
129         /* Turns 12 → 2012, 89 → 1989 */
130 
131         while (c) {
132                 if (c->start >= 0 && c->start < 70)
133                         c->start += 2000;
134 
135                 if (c->stop >= 0 && c->stop < 70)
136                         c->stop += 2000;
137 
138                 if (c->start >= 70 && c->start < 100)
139                         c->start += 1900;
140 
141                 if (c->stop >= 70 && c->stop < 100)
142                         c->stop += 1900;
143 
144                 c = c->next;
145         }
146 }
147 
calendar_spec_normalize(CalendarSpec * c)148 static void calendar_spec_normalize(CalendarSpec *c) {
149         assert(c);
150 
151         if (streq_ptr(c->timezone, "UTC")) {
152                 c->utc = true;
153                 c->timezone = mfree(c->timezone);
154         }
155 
156         if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
157                 c->weekdays_bits = -1;
158 
159         if (c->end_of_month && !c->day)
160                 c->end_of_month = false;
161 
162         fix_year(c->year);
163 
164         normalize_chain(&c->year);
165         normalize_chain(&c->month);
166         normalize_chain(&c->day);
167         normalize_chain(&c->hour);
168         normalize_chain(&c->minute);
169         normalize_chain(&c->microsecond);
170 }
171 
chain_valid(CalendarComponent * c,int from,int to,bool end_of_month)172 static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
173         assert(to >= from);
174 
175         if (!c)
176                 return true;
177 
178         /* Forbid dates more than 28 days from the end of the month */
179         if (end_of_month)
180                 to -= 3;
181 
182         if (c->start < from || c->start > to)
183                 return false;
184 
185         /* Avoid overly large values that could cause overflow */
186         if (c->repeat > to - from)
187                 return false;
188 
189         /*
190          * c->repeat must be short enough so at least one repetition may
191          * occur before the end of the interval.  For dates scheduled
192          * relative to the end of the month, c->start and c->stop
193          * correspond to the Nth last day of the month.
194          */
195         if (c->stop >= 0) {
196                 if (c->stop < from || c ->stop > to)
197                         return false;
198 
199                 if (c->start + c->repeat > c->stop)
200                         return false;
201         } else {
202                 if (end_of_month && c->start - c->repeat < from)
203                         return false;
204 
205                 if (!end_of_month && c->start + c->repeat > to)
206                         return false;
207         }
208 
209         if (c->next)
210                 return chain_valid(c->next, from, to, end_of_month);
211 
212         return true;
213 }
214 
calendar_spec_valid(CalendarSpec * c)215 _pure_ bool calendar_spec_valid(CalendarSpec *c) {
216         assert(c);
217 
218         if (c->weekdays_bits > BITS_WEEKDAYS)
219                 return false;
220 
221         if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
222                 return false;
223 
224         if (!chain_valid(c->month, 1, 12, false))
225                 return false;
226 
227         if (!chain_valid(c->day, 1, 31, c->end_of_month))
228                 return false;
229 
230         if (!chain_valid(c->hour, 0, 23, false))
231                 return false;
232 
233         if (!chain_valid(c->minute, 0, 59, false))
234                 return false;
235 
236         if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
237                 return false;
238 
239         return true;
240 }
241 
format_weekdays(FILE * f,const CalendarSpec * c)242 static void format_weekdays(FILE *f, const CalendarSpec *c) {
243         static const char *const days[] = {
244                 "Mon",
245                 "Tue",
246                 "Wed",
247                 "Thu",
248                 "Fri",
249                 "Sat",
250                 "Sun",
251         };
252 
253         int l, x;
254         bool need_comma = false;
255 
256         assert(f);
257         assert(c);
258         assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
259 
260         for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
261 
262                 if (c->weekdays_bits & (1 << x)) {
263 
264                         if (l < 0) {
265                                 if (need_comma)
266                                         fputc(',', f);
267                                 else
268                                         need_comma = true;
269 
270                                 fputs(days[x], f);
271                                 l = x;
272                         }
273 
274                 } else if (l >= 0) {
275 
276                         if (x > l + 1) {
277                                 fputs(x > l + 2 ? ".." : ",", f);
278                                 fputs(days[x-1], f);
279                         }
280 
281                         l = -1;
282                 }
283         }
284 
285         if (l >= 0 && x > l + 1) {
286                 fputs(x > l + 2 ? ".." : ",", f);
287                 fputs(days[x-1], f);
288         }
289 }
290 
chain_is_star(const CalendarComponent * c,bool usec)291 static bool chain_is_star(const CalendarComponent *c, bool usec) {
292         /* Return true if the whole chain can be replaced by '*'.
293          * This happens when the chain is empty or one of the components covers all. */
294         if (!c)
295                 return true;
296         if (usec)
297                 for (; c; c = c->next)
298                         if (c->start == 0 && c->stop < 0 && c->repeat == USEC_PER_SEC)
299                                 return true;
300         return false;
301 }
302 
_format_chain(FILE * f,int space,const CalendarComponent * c,bool start,bool usec)303 static void _format_chain(FILE *f, int space, const CalendarComponent *c, bool start, bool usec) {
304         int d = usec ? (int) USEC_PER_SEC : 1;
305 
306         assert(f);
307 
308         if (start && chain_is_star(c, usec)) {
309                 fputc('*', f);
310                 return;
311         }
312 
313         assert(c->start >= 0);
314 
315         fprintf(f, "%0*i", space, c->start / d);
316         if (c->start % d > 0)
317                 fprintf(f, ".%06i", c->start % d);
318 
319         if (c->stop > 0)
320                 fprintf(f, "..%0*i", space, c->stop / d);
321         if (c->stop % d > 0)
322                 fprintf(f, ".%06i", c->stop % d);
323 
324         if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
325                 fprintf(f, "/%i", c->repeat / d);
326         if (c->repeat % d > 0)
327                 fprintf(f, ".%06i", c->repeat % d);
328 
329         if (c->next) {
330                 fputc(',', f);
331                 _format_chain(f, space, c->next, false, usec);
332         }
333 }
334 
format_chain(FILE * f,int space,const CalendarComponent * c,bool usec)335 static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
336         _format_chain(f, space, c, /* start = */ true, usec);
337 }
338 
calendar_spec_to_string(const CalendarSpec * c,char ** p)339 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
340         char *buf = NULL;
341         size_t sz = 0;
342         FILE *f;
343         int r;
344 
345         assert(c);
346         assert(p);
347 
348         f = open_memstream_unlocked(&buf, &sz);
349         if (!f)
350                 return -ENOMEM;
351 
352         if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
353                 format_weekdays(f, c);
354                 fputc(' ', f);
355         }
356 
357         format_chain(f, 4, c->year, false);
358         fputc('-', f);
359         format_chain(f, 2, c->month, false);
360         fputc(c->end_of_month ? '~' : '-', f);
361         format_chain(f, 2, c->day, false);
362         fputc(' ', f);
363         format_chain(f, 2, c->hour, false);
364         fputc(':', f);
365         format_chain(f, 2, c->minute, false);
366         fputc(':', f);
367         format_chain(f, 2, c->microsecond, true);
368 
369         if (c->utc)
370                 fputs(" UTC", f);
371         else if (c->timezone) {
372                 fputc(' ', f);
373                 fputs(c->timezone, f);
374         } else if (IN_SET(c->dst, 0, 1)) {
375 
376                 /* If daylight saving is explicitly on or off, let's show the used timezone. */
377 
378                 tzset();
379 
380                 if (!isempty(tzname[c->dst])) {
381                         fputc(' ', f);
382                         fputs(tzname[c->dst], f);
383                 }
384         }
385 
386         r = fflush_and_check(f);
387         fclose(f);
388 
389         if (r < 0) {
390                 free(buf);
391                 return r;
392         }
393 
394         *p = buf;
395         return 0;
396 }
397 
parse_weekdays(const char ** p,CalendarSpec * c)398 static int parse_weekdays(const char **p, CalendarSpec *c) {
399         static const struct {
400                 const char *name;
401                 const int nr;
402         } day_nr[] = {
403                 { "Monday",    0 },
404                 { "Mon",       0 },
405                 { "Tuesday",   1 },
406                 { "Tue",       1 },
407                 { "Wednesday", 2 },
408                 { "Wed",       2 },
409                 { "Thursday",  3 },
410                 { "Thu",       3 },
411                 { "Friday",    4 },
412                 { "Fri",       4 },
413                 { "Saturday",  5 },
414                 { "Sat",       5 },
415                 { "Sunday",    6 },
416                 { "Sun",       6 },
417         };
418 
419         int l = -1;
420         bool first = true;
421 
422         assert(p);
423         assert(*p);
424         assert(c);
425 
426         for (;;) {
427                 size_t i;
428 
429                 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
430                         size_t skip;
431 
432                         if (!startswith_no_case(*p, day_nr[i].name))
433                                 continue;
434 
435                         skip = strlen(day_nr[i].name);
436 
437                         if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
438                                 return -EINVAL;
439 
440                         c->weekdays_bits |= 1 << day_nr[i].nr;
441 
442                         if (l >= 0) {
443                                 if (l > day_nr[i].nr)
444                                         return -EINVAL;
445 
446                                 for (int j = l + 1; j < day_nr[i].nr; j++)
447                                         c->weekdays_bits |= 1 << j;
448                         }
449 
450                         *p += skip;
451                         break;
452                 }
453 
454                 /* Couldn't find this prefix, so let's assume the
455                    weekday was not specified and let's continue with
456                    the date */
457                 if (i >= ELEMENTSOF(day_nr))
458                         return first ? 0 : -EINVAL;
459 
460                 /* We reached the end of the string */
461                 if (**p == 0)
462                         return 0;
463 
464                 /* We reached the end of the weekday spec part */
465                 if (**p == ' ') {
466                         *p += strspn(*p, " ");
467                         return 0;
468                 }
469 
470                 if (**p == '.') {
471                         if (l >= 0)
472                                 return -EINVAL;
473 
474                         if ((*p)[1] != '.')
475                                 return -EINVAL;
476 
477                         l = day_nr[i].nr;
478                         *p += 2;
479 
480                 /* Support ranges with "-" for backwards compatibility */
481                 } else if (**p == '-') {
482                         if (l >= 0)
483                                 return -EINVAL;
484 
485                         l = day_nr[i].nr;
486                         *p += 1;
487 
488                 } else if (**p == ',') {
489                         l = -1;
490                         *p += 1;
491                 }
492 
493                 /* Allow a trailing comma but not an open range */
494                 if (IN_SET(**p, 0, ' ')) {
495                         *p += strspn(*p, " ");
496                         return l < 0 ? 0 : -EINVAL;
497                 }
498 
499                 first = false;
500         }
501 }
502 
parse_one_number(const char * p,const char ** e,unsigned long * ret)503 static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
504         char *ee = NULL;
505         unsigned long value;
506 
507         errno = 0;
508         value = strtoul(p, &ee, 10);
509         if (errno > 0)
510                 return -errno;
511         if (ee == p)
512                 return -EINVAL;
513 
514         *ret = value;
515         *e = ee;
516         return 0;
517 }
518 
parse_component_decimal(const char ** p,bool usec,int * res)519 static int parse_component_decimal(const char **p, bool usec, int *res) {
520         unsigned long value;
521         const char *e = NULL;
522         int r;
523 
524         if (!isdigit(**p))
525                 return -EINVAL;
526 
527         r = parse_one_number(*p, &e, &value);
528         if (r < 0)
529                 return r;
530 
531         if (usec) {
532                 if (value * USEC_PER_SEC / USEC_PER_SEC != value)
533                         return -ERANGE;
534 
535                 value *= USEC_PER_SEC;
536 
537                 /* One "." is a decimal point, but ".." is a range separator */
538                 if (e[0] == '.' && e[1] != '.') {
539                         unsigned add;
540 
541                         e++;
542                         r = parse_fractional_part_u(&e, 6, &add);
543                         if (r < 0)
544                                 return r;
545 
546                         if (add + value < value)
547                                 return -ERANGE;
548                         value += add;
549                 }
550         }
551 
552         if (value > INT_MAX)
553                 return -ERANGE;
554 
555         *p = e;
556         *res = value;
557 
558         return 0;
559 }
560 
const_chain(int value,CalendarComponent ** c)561 static int const_chain(int value, CalendarComponent **c) {
562         CalendarComponent *cc = NULL;
563 
564         assert(c);
565 
566         cc = new(CalendarComponent, 1);
567         if (!cc)
568                 return -ENOMEM;
569 
570         *cc = (CalendarComponent) {
571                 .start = value,
572                 .stop = -1,
573                 .repeat = 0,
574                 .next = *c,
575         };
576 
577         *c = cc;
578 
579         return 0;
580 }
581 
calendarspec_from_time_t(CalendarSpec * c,time_t time)582 static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
583         _cleanup_(chain_freep) CalendarComponent
584                 *year = NULL, *month = NULL, *day = NULL,
585                 *hour = NULL, *minute = NULL, *us = NULL;
586         struct tm tm;
587         int r;
588 
589         if (!gmtime_r(&time, &tm))
590                 return -ERANGE;
591 
592         if (tm.tm_year > INT_MAX - 1900)
593                 return -ERANGE;
594 
595         r = const_chain(tm.tm_year + 1900, &year);
596         if (r < 0)
597                 return r;
598 
599         r = const_chain(tm.tm_mon + 1, &month);
600         if (r < 0)
601                 return r;
602 
603         r = const_chain(tm.tm_mday, &day);
604         if (r < 0)
605                 return r;
606 
607         r = const_chain(tm.tm_hour, &hour);
608         if (r < 0)
609                 return r;
610 
611         r = const_chain(tm.tm_min, &minute);
612         if (r < 0)
613                 return r;
614 
615         r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
616         if (r < 0)
617                 return r;
618 
619         c->utc = true;
620         c->year = TAKE_PTR(year);
621         c->month = TAKE_PTR(month);
622         c->day = TAKE_PTR(day);
623         c->hour = TAKE_PTR(hour);
624         c->minute = TAKE_PTR(minute);
625         c->microsecond = TAKE_PTR(us);
626         return 0;
627 }
628 
prepend_component(const char ** p,bool usec,unsigned nesting,CalendarComponent ** c)629 static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
630         int r, start, stop = -1, repeat = 0;
631         CalendarComponent *cc;
632         const char *e = *p;
633 
634         assert(p);
635         assert(c);
636 
637         if (nesting > CALENDARSPEC_COMPONENTS_MAX)
638                 return -ENOBUFS;
639 
640         r = parse_component_decimal(&e, usec, &start);
641         if (r < 0)
642                 return r;
643 
644         if (e[0] == '.' && e[1] == '.') {
645                 e += 2;
646                 r = parse_component_decimal(&e, usec, &stop);
647                 if (r < 0)
648                         return r;
649 
650                 repeat = usec ? USEC_PER_SEC : 1;
651         }
652 
653         if (*e == '/') {
654                 e++;
655                 r = parse_component_decimal(&e, usec, &repeat);
656                 if (r < 0)
657                         return r;
658 
659                 if (repeat == 0)
660                         return -ERANGE;
661         } else {
662                 /* If no repeat value is specified for the µs component, then let's explicitly refuse ranges
663                  * below 1s because our default repeat granularity is beyond that. */
664 
665                 /* Overflow check */
666                 if (start > INT_MAX - repeat)
667                         return -ERANGE;
668 
669                 if (usec && stop >= 0 && start + repeat > stop)
670                         return -EINVAL;
671         }
672 
673         if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
674                 return -EINVAL;
675 
676         cc = new(CalendarComponent, 1);
677         if (!cc)
678                 return -ENOMEM;
679 
680         *cc = (CalendarComponent) {
681                 .start = start,
682                 .stop = stop,
683                 .repeat = repeat,
684                 .next = *c,
685         };
686 
687         *p = e;
688         *c = cc;
689 
690         if (*e ==',') {
691                 *p += 1;
692                 return prepend_component(p, usec, nesting + 1, c);
693         }
694 
695         return 0;
696 }
697 
parse_chain(const char ** p,bool usec,CalendarComponent ** c)698 static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
699         _cleanup_(chain_freep) CalendarComponent *cc = NULL;
700         const char *t;
701         int r;
702 
703         assert(p);
704         assert(c);
705 
706         t = *p;
707 
708         if (t[0] == '*') {
709                 if (usec) {
710                         r = const_chain(0, c);
711                         if (r < 0)
712                                 return r;
713                         (*c)->repeat = USEC_PER_SEC;
714                 } else
715                         *c = NULL;
716 
717                 *p = t + 1;
718                 return 0;
719         }
720 
721         r = prepend_component(&t, usec, 0, &cc);
722         if (r < 0)
723                 return r;
724 
725         *p = t;
726         *c = TAKE_PTR(cc);
727         return 0;
728 }
729 
parse_date(const char ** p,CalendarSpec * c)730 static int parse_date(const char **p, CalendarSpec *c) {
731         _cleanup_(chain_freep) CalendarComponent *first = NULL, *second = NULL, *third = NULL;
732         const char *t;
733         int r;
734 
735         assert(p);
736         assert(*p);
737         assert(c);
738 
739         t = *p;
740 
741         if (*t == 0)
742                 return 0;
743 
744         /* @TIMESTAMP — UNIX time in seconds since the epoch */
745         if (*t == '@') {
746                 unsigned long value;
747                 time_t time;
748 
749                 r = parse_one_number(t + 1, &t, &value);
750                 if (r < 0)
751                         return r;
752 
753                 time = value;
754                 if ((unsigned long) time != value)
755                         return -ERANGE;
756 
757                 r = calendarspec_from_time_t(c, time);
758                 if (r < 0)
759                         return r;
760 
761                 *p = t;
762                 return 1; /* finito, don't parse H:M:S after that */
763         }
764 
765         r = parse_chain(&t, false, &first);
766         if (r < 0)
767                 return r;
768 
769         /* Already the end? A ':' as separator? In that case this was a time, not a date */
770         if (IN_SET(*t, 0, ':'))
771                 return 0;
772 
773         if (*t == '~')
774                 c->end_of_month = true;
775         else if (*t != '-')
776                 return -EINVAL;
777 
778         t++;
779         r = parse_chain(&t, false, &second);
780         if (r < 0)
781                 return r;
782 
783         /* Got two parts, hence it's month and day */
784         if (IN_SET(*t, 0, ' ')) {
785                 *p = t + strspn(t, " ");
786                 c->month = TAKE_PTR(first);
787                 c->day = TAKE_PTR(second);
788                 return 0;
789         } else if (c->end_of_month)
790                 return -EINVAL;
791 
792         if (*t == '~')
793                 c->end_of_month = true;
794         else if (*t != '-')
795                 return -EINVAL;
796 
797         t++;
798         r = parse_chain(&t, false, &third);
799         if (r < 0)
800                 return r;
801 
802         if (!IN_SET(*t, 0, ' '))
803                 return -EINVAL;
804 
805         /* Got three parts, hence it is year, month and day */
806         *p = t + strspn(t, " ");
807         c->year = TAKE_PTR(first);
808         c->month = TAKE_PTR(second);
809         c->day = TAKE_PTR(third);
810         return 0;
811 }
812 
parse_calendar_time(const char ** p,CalendarSpec * c)813 static int parse_calendar_time(const char **p, CalendarSpec *c) {
814         _cleanup_(chain_freep) CalendarComponent *h = NULL, *m = NULL, *s = NULL;
815         const char *t;
816         int r;
817 
818         assert(p);
819         assert(*p);
820         assert(c);
821 
822         t = *p;
823 
824         /* If no time is specified at all, then this means 00:00:00 */
825         if (*t == 0)
826                 goto null_hour;
827 
828         r = parse_chain(&t, false, &h);
829         if (r < 0)
830                 return r;
831 
832         if (*t != ':')
833                 return -EINVAL;
834 
835         t++;
836         r = parse_chain(&t, false, &m);
837         if (r < 0)
838                 return r;
839 
840         /* Already at the end? Then it's hours and minutes, and seconds are 0 */
841         if (*t == 0)
842                 goto null_second;
843 
844         if (*t != ':')
845                 return -EINVAL;
846 
847         t++;
848         r = parse_chain(&t, true, &s);
849         if (r < 0)
850                 return r;
851 
852         /* At the end? Then it's hours, minutes and seconds */
853         if (*t == 0)
854                 goto finish;
855 
856         return -EINVAL;
857 
858 null_hour:
859         r = const_chain(0, &h);
860         if (r < 0)
861                 return r;
862 
863         r = const_chain(0, &m);
864         if (r < 0)
865                 return r;
866 
867 null_second:
868         r = const_chain(0, &s);
869         if (r < 0)
870                 return r;
871 
872 finish:
873         *p = t;
874         c->hour = TAKE_PTR(h);
875         c->minute = TAKE_PTR(m);
876         c->microsecond = TAKE_PTR(s);
877 
878         return 0;
879 }
880 
calendar_spec_from_string(const char * p,CalendarSpec ** spec)881 int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
882         const char *utc;
883         _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
884         _cleanup_free_ char *p_tmp = NULL;
885         int r;
886 
887         assert(p);
888 
889         c = new(CalendarSpec, 1);
890         if (!c)
891                 return -ENOMEM;
892 
893         *c = (CalendarSpec) {
894                 .dst = -1,
895                 .timezone = NULL,
896         };
897 
898         utc = endswith_no_case(p, " UTC");
899         if (utc) {
900                 c->utc = true;
901                 p = p_tmp = strndup(p, utc - p);
902                 if (!p)
903                         return -ENOMEM;
904         } else {
905                 const char *e = NULL;
906                 int j;
907 
908                 tzset();
909 
910                 /* Check if the local timezone was specified? */
911                 for (j = 0; j <= 1; j++) {
912                         if (isempty(tzname[j]))
913                                 continue;
914 
915                         e = endswith_no_case(p, tzname[j]);
916                         if (!e)
917                                 continue;
918                         if (e == p)
919                                 continue;
920                         if (e[-1] != ' ')
921                                 continue;
922 
923                         break;
924                 }
925 
926                 /* Found one of the two timezones specified? */
927                 if (IN_SET(j, 0, 1)) {
928                         p = p_tmp = strndup(p, e - p - 1);
929                         if (!p)
930                                 return -ENOMEM;
931 
932                         c->dst = j;
933                 } else {
934                         const char *last_space;
935 
936                         last_space = strrchr(p, ' ');
937                         if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
938                                 c->timezone = strdup(last_space + 1);
939                                 if (!c->timezone)
940                                         return -ENOMEM;
941 
942                                 p = p_tmp = strndup(p, last_space - p);
943                                 if (!p)
944                                         return -ENOMEM;
945                         }
946                 }
947         }
948 
949         if (isempty(p))
950                 return -EINVAL;
951 
952         if (strcaseeq(p, "minutely")) {
953                 r = const_chain(0, &c->microsecond);
954                 if (r < 0)
955                         return r;
956 
957         } else if (strcaseeq(p, "hourly")) {
958                 r = const_chain(0, &c->minute);
959                 if (r < 0)
960                         return r;
961                 r = const_chain(0, &c->microsecond);
962                 if (r < 0)
963                         return r;
964 
965         } else if (strcaseeq(p, "daily")) {
966                 r = const_chain(0, &c->hour);
967                 if (r < 0)
968                         return r;
969                 r = const_chain(0, &c->minute);
970                 if (r < 0)
971                         return r;
972                 r = const_chain(0, &c->microsecond);
973                 if (r < 0)
974                         return r;
975 
976         } else if (strcaseeq(p, "monthly")) {
977                 r = const_chain(1, &c->day);
978                 if (r < 0)
979                         return r;
980                 r = const_chain(0, &c->hour);
981                 if (r < 0)
982                         return r;
983                 r = const_chain(0, &c->minute);
984                 if (r < 0)
985                         return r;
986                 r = const_chain(0, &c->microsecond);
987                 if (r < 0)
988                         return r;
989 
990         } else if (STRCASE_IN_SET(p,
991                                   "annually",
992                                   "yearly",
993                                   "anually") /* backwards compatibility */ ) {
994 
995                 r = const_chain(1, &c->month);
996                 if (r < 0)
997                         return r;
998                 r = const_chain(1, &c->day);
999                 if (r < 0)
1000                         return r;
1001                 r = const_chain(0, &c->hour);
1002                 if (r < 0)
1003                         return r;
1004                 r = const_chain(0, &c->minute);
1005                 if (r < 0)
1006                         return r;
1007                 r = const_chain(0, &c->microsecond);
1008                 if (r < 0)
1009                         return r;
1010 
1011         } else if (strcaseeq(p, "weekly")) {
1012 
1013                 c->weekdays_bits = 1;
1014 
1015                 r = const_chain(0, &c->hour);
1016                 if (r < 0)
1017                         return r;
1018                 r = const_chain(0, &c->minute);
1019                 if (r < 0)
1020                         return r;
1021                 r = const_chain(0, &c->microsecond);
1022                 if (r < 0)
1023                         return r;
1024 
1025         } else if (strcaseeq(p, "quarterly")) {
1026 
1027                 r = const_chain(1, &c->month);
1028                 if (r < 0)
1029                         return r;
1030                 r = const_chain(4, &c->month);
1031                 if (r < 0)
1032                         return r;
1033                 r = const_chain(7, &c->month);
1034                 if (r < 0)
1035                         return r;
1036                 r = const_chain(10, &c->month);
1037                 if (r < 0)
1038                         return r;
1039                 r = const_chain(1, &c->day);
1040                 if (r < 0)
1041                         return r;
1042                 r = const_chain(0, &c->hour);
1043                 if (r < 0)
1044                         return r;
1045                 r = const_chain(0, &c->minute);
1046                 if (r < 0)
1047                         return r;
1048                 r = const_chain(0, &c->microsecond);
1049                 if (r < 0)
1050                         return r;
1051 
1052         } else if (STRCASE_IN_SET(p,
1053                                   "biannually",
1054                                   "bi-annually",
1055                                   "semiannually",
1056                                   "semi-annually")) {
1057 
1058                 r = const_chain(1, &c->month);
1059                 if (r < 0)
1060                         return r;
1061                 r = const_chain(7, &c->month);
1062                 if (r < 0)
1063                         return r;
1064                 r = const_chain(1, &c->day);
1065                 if (r < 0)
1066                         return r;
1067                 r = const_chain(0, &c->hour);
1068                 if (r < 0)
1069                         return r;
1070                 r = const_chain(0, &c->minute);
1071                 if (r < 0)
1072                         return r;
1073                 r = const_chain(0, &c->microsecond);
1074                 if (r < 0)
1075                         return r;
1076 
1077         } else {
1078                 r = parse_weekdays(&p, c);
1079                 if (r < 0)
1080                         return r;
1081 
1082                 r = parse_date(&p, c);
1083                 if (r < 0)
1084                         return r;
1085 
1086                 if (r == 0) {
1087                         r = parse_calendar_time(&p, c);
1088                         if (r < 0)
1089                                 return r;
1090                 }
1091 
1092                 if (*p != 0)
1093                         return -EINVAL;
1094         }
1095 
1096         calendar_spec_normalize(c);
1097 
1098         if (!calendar_spec_valid(c))
1099                 return -EINVAL;
1100 
1101         if (spec)
1102                 *spec = TAKE_PTR(c);
1103         return 0;
1104 }
1105 
find_end_of_month(const struct tm * tm,bool utc,int day)1106 static int find_end_of_month(const struct tm *tm, bool utc, int day) {
1107         struct tm t = *tm;
1108 
1109         t.tm_mon++;
1110         t.tm_mday = 1 - day;
1111 
1112         if (mktime_or_timegm(&t, utc) < 0 ||
1113             t.tm_mon != tm->tm_mon)
1114                 return -1;
1115 
1116         return t.tm_mday;
1117 }
1118 
find_matching_component(const CalendarSpec * spec,const CalendarComponent * c,const struct tm * tm,int * val)1119 static int find_matching_component(
1120                 const CalendarSpec *spec,
1121                 const CalendarComponent *c,
1122                 const struct tm *tm,           /* tm is only used for end-of-month calculations */
1123                 int *val) {
1124 
1125         int d = -1, r;
1126         bool d_set = false;
1127 
1128         assert(val);
1129 
1130         /* Finds the *earliest* matching time specified by one of the CalendarCompoment items in chain c.
1131          * If no matches can be found, returns -ENOENT.
1132          * Otherwise, updates *val to the matching time. 1 is returned if *val was changed, 0 otherwise.
1133          */
1134 
1135         if (!c)
1136                 return 0;
1137 
1138         bool end_of_month = spec->end_of_month && c == spec->day;
1139 
1140         while (c) {
1141                 int start, stop;
1142 
1143                 if (end_of_month) {
1144                         start = find_end_of_month(tm, spec->utc, c->start);
1145                         stop = find_end_of_month(tm, spec->utc, c->stop);
1146 
1147                         if (stop > 0)
1148                                 SWAP_TWO(start, stop);
1149                 } else {
1150                         start = c->start;
1151                         stop = c->stop;
1152                 }
1153 
1154                 if (start >= *val) {
1155 
1156                         if (!d_set || start < d) {
1157                                 d = start;
1158                                 d_set = true;
1159                         }
1160 
1161                 } else if (c->repeat > 0) {
1162                         int k;
1163 
1164                         k = start + c->repeat * DIV_ROUND_UP(*val - start, c->repeat);
1165 
1166                         if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
1167                                 d = k;
1168                                 d_set = true;
1169                         }
1170                 }
1171 
1172                 c = c->next;
1173         }
1174 
1175         if (!d_set)
1176                 return -ENOENT;
1177 
1178         r = *val != d;
1179         *val = d;
1180         return r;
1181 }
1182 
tm_within_bounds(struct tm * tm,bool utc)1183 static int tm_within_bounds(struct tm *tm, bool utc) {
1184         struct tm t;
1185         int cmp;
1186         assert(tm);
1187 
1188         /*
1189          * Set an upper bound on the year so impossible dates like "*-02-31"
1190          * don't cause find_next() to loop forever. tm_year contains years
1191          * since 1900, so adjust it accordingly.
1192          */
1193         if (tm->tm_year + 1900 > MAX_YEAR)
1194                 return -ERANGE;
1195 
1196         t = *tm;
1197         if (mktime_or_timegm(&t, utc) < 0)
1198                 return negative_errno();
1199 
1200         /*
1201          * Did any normalization take place? If so, it was out of bounds before.
1202          * Normalization could skip next elapse, e.g. result of normalizing 3-33
1203          * is 4-2. This skips 4-1. So reset the sub time unit if upper unit was
1204          * out of bounds. Normalization has occurred implies find_matching_component() > 0,
1205          * other sub time units are already reset in find_next().
1206          */
1207         if ((cmp = CMP(t.tm_year, tm->tm_year)) != 0)
1208                 t.tm_mon = 0;
1209         else if ((cmp = CMP(t.tm_mon, tm->tm_mon)) != 0)
1210                 t.tm_mday = 1;
1211         else if ((cmp = CMP(t.tm_mday, tm->tm_mday)) != 0)
1212                 t.tm_hour = 0;
1213         else if ((cmp = CMP(t.tm_hour, tm->tm_hour)) != 0)
1214                 t.tm_min = 0;
1215         else if ((cmp = CMP(t.tm_min, tm->tm_min)) != 0)
1216                 t.tm_sec = 0;
1217         else
1218                 cmp = CMP(t.tm_sec, tm->tm_sec);
1219 
1220         if (cmp < 0)
1221                 return -EDEADLK; /* Refuse to go backward */
1222         if (cmp > 0)
1223                 *tm = t;
1224         return cmp == 0;
1225 }
1226 
matches_weekday(int weekdays_bits,const struct tm * tm,bool utc)1227 static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
1228         struct tm t;
1229         int k;
1230 
1231         if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
1232                 return true;
1233 
1234         t = *tm;
1235         if (mktime_or_timegm(&t, utc) < 0)
1236                 return false;
1237 
1238         k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
1239         return (weekdays_bits & (1 << k));
1240 }
1241 
1242 /* A safety valve: if we get stuck in the calculation, return an error.
1243  * C.f. https://bugzilla.redhat.com/show_bug.cgi?id=1941335. */
1244 #define MAX_CALENDAR_ITERATIONS 1000
1245 
find_next(const CalendarSpec * spec,struct tm * tm,usec_t * usec)1246 static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
1247         struct tm c;
1248         int tm_usec;
1249         int r;
1250 
1251         /* Returns -ENOENT if the expression is not going to elapse anymore */
1252 
1253         assert(spec);
1254         assert(tm);
1255 
1256         c = *tm;
1257         tm_usec = *usec;
1258 
1259         for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) {
1260                 /* Normalize the current date */
1261                 (void) mktime_or_timegm(&c, spec->utc);
1262                 c.tm_isdst = spec->dst;
1263 
1264                 c.tm_year += 1900;
1265                 r = find_matching_component(spec, spec->year, &c, &c.tm_year);
1266                 c.tm_year -= 1900;
1267 
1268                 if (r > 0) {
1269                         c.tm_mon = 0;
1270                         c.tm_mday = 1;
1271                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1272                 }
1273                 if (r < 0)
1274                         return r;
1275                 if (tm_within_bounds(&c, spec->utc) <= 0)
1276                         return -ENOENT;
1277 
1278                 c.tm_mon += 1;
1279                 r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
1280                 c.tm_mon -= 1;
1281 
1282                 if (r > 0) {
1283                         c.tm_mday = 1;
1284                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1285                 }
1286                 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1287                         c.tm_year++;
1288                         c.tm_mon = 0;
1289                         c.tm_mday = 1;
1290                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1291                         continue;
1292                 }
1293                 if (r == 0)
1294                         continue;
1295 
1296                 r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
1297                 if (r > 0)
1298                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1299                 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1300                         c.tm_mon++;
1301                         c.tm_mday = 1;
1302                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1303                         continue;
1304                 }
1305                 if (r == 0)
1306                         continue;
1307 
1308                 if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
1309                         c.tm_mday++;
1310                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1311                         continue;
1312                 }
1313 
1314                 r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
1315                 if (r > 0)
1316                         c.tm_min = c.tm_sec = tm_usec = 0;
1317                 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1318                         c.tm_mday++;
1319                         c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
1320                         continue;
1321                 }
1322                 if (r == 0)
1323                         /* The next hour we set might be missing if there
1324                          * are time zone changes. Let's try again starting at
1325                          * normalized time. */
1326                         continue;
1327 
1328                 r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
1329                 if (r > 0)
1330                         c.tm_sec = tm_usec = 0;
1331                 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1332                         c.tm_hour++;
1333                         c.tm_min = c.tm_sec = tm_usec = 0;
1334                         continue;
1335                 }
1336                 if (r == 0)
1337                         continue;
1338 
1339                 c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
1340                 r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
1341                 tm_usec = c.tm_sec % USEC_PER_SEC;
1342                 c.tm_sec /= USEC_PER_SEC;
1343 
1344                 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1345                         c.tm_min++;
1346                         c.tm_sec = tm_usec = 0;
1347                         continue;
1348                 }
1349                 if (r == 0)
1350                         continue;
1351 
1352                 *tm = c;
1353                 *usec = tm_usec;
1354                 return 0;
1355         }
1356 
1357         /* It seems we entered an infinite loop. Let's gracefully return an error instead of hanging or
1358          * aborting. This code is also exercised when timers.target is brought up during early boot, so
1359          * aborting here is problematic and hard to diagnose for users. */
1360         _cleanup_free_ char *s = NULL;
1361         (void) calendar_spec_to_string(spec, &s);
1362         return log_warning_errno(SYNTHETIC_ERRNO(EDEADLK),
1363                                  "Infinite loop in calendar calculation: %s", strna(s));
1364 }
1365 
calendar_spec_next_usec_impl(const CalendarSpec * spec,usec_t usec,usec_t * ret_next)1366 static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
1367         struct tm tm;
1368         time_t t;
1369         int r;
1370         usec_t tm_usec;
1371 
1372         assert(spec);
1373 
1374         if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
1375                 return -EINVAL;
1376 
1377         usec++;
1378         t = (time_t) (usec / USEC_PER_SEC);
1379         assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
1380         tm_usec = usec % USEC_PER_SEC;
1381 
1382         r = find_next(spec, &tm, &tm_usec);
1383         if (r < 0)
1384                 return r;
1385 
1386         t = mktime_or_timegm(&tm, spec->utc);
1387         if (t < 0)
1388                 return -EINVAL;
1389 
1390         if (ret_next)
1391                 *ret_next = (usec_t) t * USEC_PER_SEC + tm_usec;
1392 
1393         return 0;
1394 }
1395 
1396 typedef struct SpecNextResult {
1397         usec_t next;
1398         int return_value;
1399 } SpecNextResult;
1400 
calendar_spec_next_usec(const CalendarSpec * spec,usec_t usec,usec_t * ret_next)1401 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
1402         SpecNextResult *shared, tmp;
1403         int r;
1404 
1405         assert(spec);
1406 
1407         if (isempty(spec->timezone))
1408                 return calendar_spec_next_usec_impl(spec, usec, ret_next);
1409 
1410         shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1411         if (shared == MAP_FAILED)
1412                 return negative_errno();
1413 
1414         r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
1415         if (r < 0) {
1416                 (void) munmap(shared, sizeof *shared);
1417                 return r;
1418         }
1419         if (r == 0) {
1420                 char *colon_tz;
1421 
1422                 /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
1423                 colon_tz = strjoina(":", spec->timezone);
1424 
1425                 if (setenv("TZ", colon_tz, 1) != 0) {
1426                         shared->return_value = negative_errno();
1427                         _exit(EXIT_FAILURE);
1428                 }
1429 
1430                 tzset();
1431 
1432                 shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
1433 
1434                 _exit(EXIT_SUCCESS);
1435         }
1436 
1437         tmp = *shared;
1438         if (munmap(shared, sizeof *shared) < 0)
1439                 return negative_errno();
1440 
1441         if (tmp.return_value == 0 && ret_next)
1442                 *ret_next = tmp.next;
1443 
1444         return tmp.return_value;
1445 }
1446