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