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