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