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