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