]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/calendarspec.c
Merge pull request #18886 from anitazha/shutdownconsole
[thirdparty/systemd.git] / src / shared / calendarspec.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
36697dc0 2
9dfa81a0 3#include <ctype.h>
11c3a366 4#include <errno.h>
c0aebb4b 5#include <limits.h>
11c3a366
TA
6#include <stddef.h>
7#include <stdio.h>
36697dc0 8#include <stdlib.h>
48d26c01 9#include <sys/mman.h>
36697dc0 10
b5efdb8a 11#include "alloc-util.h"
36697dc0 12#include "calendarspec.h"
2b2fec7d 13#include "errno-util.h"
0d39fa9c 14#include "fileio.h"
11c3a366 15#include "macro.h"
93cc7779 16#include "parse-util.h"
dccca82b 17#include "process-util.h"
760877e9 18#include "sort-util.h"
cf0fbc49 19#include "string-util.h"
ddd6a22a 20#include "strv.h"
48d26c01 21#include "time-util.h"
36697dc0 22
a2eb5ea7
DC
23#define BITS_WEEKDAYS 127
24#define MIN_YEAR 1970
25#define MAX_YEAR 2199
489464d0 26
74353158
ZJS
27/* An arbitrary limit on the length of the chains of components. We don't want to
28 * build a very long linked list, which would be slow to iterate over and might cause
29 * our stack to overflow. It's unlikely that legitimate uses require more than a few
28423d9a 30 * linked components anyway. */
74353158
ZJS
31#define CALENDARSPEC_COMPONENTS_MAX 240
32
a4d6d711
LP
33/* Let's make sure that the microsecond component is safe to be stored in an 'int' */
34assert_cc(INT_MAX >= USEC_PER_SEC);
35
75db809a 36static CalendarComponent* chain_free(CalendarComponent *c) {
36697dc0 37 while (c) {
75db809a 38 CalendarComponent *n = c->next;
36697dc0
LP
39 free(c);
40 c = n;
41 }
75db809a 42 return NULL;
36697dc0
LP
43}
44
fb3ba5ec
YW
45DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarComponent*, chain_free);
46
7c123d49 47CalendarSpec* calendar_spec_free(CalendarSpec *c) {
0b76b4d8
LP
48
49 if (!c)
7c123d49 50 return NULL;
36697dc0 51
9eef82e5
YW
52 chain_free(c->year);
53 chain_free(c->month);
54 chain_free(c->day);
55 chain_free(c->hour);
56 chain_free(c->minute);
57 chain_free(c->microsecond);
48d26c01 58 free(c->timezone);
36697dc0 59
7c123d49 60 return mfree(c);
36697dc0
LP
61}
62
93bab288
YW
63static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
64 int r;
36697dc0 65
93bab288
YW
66 r = CMP((*a)->start, (*b)->start);
67 if (r != 0)
68 return r;
36697dc0 69
93bab288
YW
70 r = CMP((*a)->stop, (*b)->stop);
71 if (r != 0)
72 return r;
a2eb5ea7 73
93bab288 74 return CMP((*a)->repeat, (*b)->repeat);
36697dc0
LP
75}
76
a2eb5ea7 77static void normalize_chain(CalendarComponent **c) {
36697dc0 78 CalendarComponent **b, *i, **j, *next;
da6053d0 79 size_t n = 0, k;
36697dc0
LP
80
81 assert(c);
82
a2eb5ea7 83 for (i = *c; i; i = i->next) {
36697dc0
LP
84 n++;
85
a2eb5ea7 86 /*
e78fce48 87 * While we're counting the chain, also normalize `stop`
a2eb5ea7
DC
88 * so the length of the range is a multiple of `repeat`
89 */
c0aebb4b 90 if (i->stop > i->start && i->repeat > 0)
e78fce48 91 i->stop -= (i->stop - i->start) % i->repeat;
a2eb5ea7 92
c9c9f6f4
LP
93 /* If a repeat value is specified, but it cannot even be triggered once, let's suppress
94 * it.
95 *
96 * Similar, if the stop value is the same as the start value, then let's just make this a
97 * non-repeating chain element */
98 if ((i->stop > i->start && i->repeat > 0 && i->start + i->repeat > i->stop) ||
99 i->start == i->stop) {
100 i->repeat = 0;
101 i->stop = -1;
102 }
a2eb5ea7
DC
103 }
104
36697dc0
LP
105 if (n <= 1)
106 return;
107
cf409d15 108 j = b = newa(CalendarComponent*, n);
36697dc0
LP
109 for (i = *c; i; i = i->next)
110 *(j++) = i;
111
93bab288 112 typesafe_qsort(b, n, component_compare);
36697dc0
LP
113
114 b[n-1]->next = NULL;
115 next = b[n-1];
116
117 /* Drop non-unique entries */
118 for (k = n-1; k > 0; k--) {
963e3d83 119 if (component_compare(&b[k-1], &next) == 0) {
36697dc0
LP
120 free(b[k-1]);
121 continue;
122 }
123
124 b[k-1]->next = next;
125 next = b[k-1];
126 }
127
128 *c = next;
129}
130
131static void fix_year(CalendarComponent *c) {
132 /* Turns 12 → 2012, 89 → 1989 */
133
9ed794a3 134 while (c) {
e78fce48
DC
135 if (c->start >= 0 && c->start < 70)
136 c->start += 2000;
36697dc0 137
e78fce48
DC
138 if (c->stop >= 0 && c->stop < 70)
139 c->stop += 2000;
a2eb5ea7 140
e78fce48
DC
141 if (c->start >= 70 && c->start < 100)
142 c->start += 1900;
36697dc0 143
e78fce48
DC
144 if (c->stop >= 70 && c->stop < 100)
145 c->stop += 1900;
a2eb5ea7 146
482f3b54 147 c = c->next;
36697dc0
LP
148 }
149}
150
151int calendar_spec_normalize(CalendarSpec *c) {
152 assert(c);
153
10e859a2
LP
154 if (streq_ptr(c->timezone, "UTC")) {
155 c->utc = true;
156 c->timezone = mfree(c->timezone);
157 }
158
489464d0 159 if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
36697dc0
LP
160 c->weekdays_bits = -1;
161
8ea80351
DC
162 if (c->end_of_month && !c->day)
163 c->end_of_month = false;
164
36697dc0
LP
165 fix_year(c->year);
166
a2eb5ea7
DC
167 normalize_chain(&c->year);
168 normalize_chain(&c->month);
169 normalize_chain(&c->day);
170 normalize_chain(&c->hour);
171 normalize_chain(&c->minute);
172 normalize_chain(&c->microsecond);
36697dc0
LP
173
174 return 0;
175}
176
3c6f0300 177static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
e127f26b
ZJS
178 assert(to >= from);
179
36697dc0
LP
180 if (!c)
181 return true;
182
a2eb5ea7
DC
183 /* Forbid dates more than 28 days from the end of the month */
184 if (end_of_month)
185 to -= 3;
186
e78fce48 187 if (c->start < from || c->start > to)
36697dc0
LP
188 return false;
189
e127f26b
ZJS
190 /* Avoid overly large values that could cause overflow */
191 if (c->repeat > to - from)
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
2fe21124 342 f = open_memstream_unlocked(&buf, &sz);
36697dc0
LP
343 if (!f)
344 return -ENOMEM;
345
489464d0 346 if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
36697dc0 347 format_weekdays(f, c);
0d536673 348 fputc(' ', f);
36697dc0
LP
349 }
350
436dd70f 351 format_chain(f, 4, c->year, false);
0d536673 352 fputc('-', f);
436dd70f 353 format_chain(f, 2, c->month, false);
0d536673 354 fputc(c->end_of_month ? '~' : '-', f);
436dd70f 355 format_chain(f, 2, c->day, false);
0d536673 356 fputc(' ', f);
436dd70f 357 format_chain(f, 2, c->hour, false);
0d536673 358 fputc(':', f);
436dd70f 359 format_chain(f, 2, c->minute, false);
0d536673 360 fputc(':', f);
482f3b54 361 format_chain(f, 2, c->microsecond, true);
36697dc0 362
51ffa239 363 if (c->utc)
0d536673 364 fputs(" UTC", f);
1d6cc5d0 365 else if (c->timezone) {
0d536673
LP
366 fputc(' ', f);
367 fputs(c->timezone, f);
48d26c01 368 } else if (IN_SET(c->dst, 0, 1)) {
21b3a0fc
LP
369
370 /* If daylight saving is explicitly on or off, let's show the used timezone. */
371
372 tzset();
373
374 if (!isempty(tzname[c->dst])) {
0d536673
LP
375 fputc(' ', f);
376 fputs(tzname[c->dst], f);
21b3a0fc
LP
377 }
378 }
51ffa239 379
dacd6cee 380 r = fflush_and_check(f);
05851cb9
LP
381 fclose(f);
382
dacd6cee 383 if (r < 0) {
36697dc0 384 free(buf);
dacd6cee 385 return r;
36697dc0
LP
386 }
387
36697dc0
LP
388 *p = buf;
389 return 0;
390}
391
392static int parse_weekdays(const char **p, CalendarSpec *c) {
393 static const struct {
394 const char *name;
395 const int nr;
396 } day_nr[] = {
397 { "Monday", 0 },
398 { "Mon", 0 },
399 { "Tuesday", 1 },
400 { "Tue", 1 },
401 { "Wednesday", 2 },
402 { "Wed", 2 },
403 { "Thursday", 3 },
404 { "Thu", 3 },
405 { "Friday", 4 },
406 { "Fri", 4 },
407 { "Saturday", 5 },
408 { "Sat", 5 },
409 { "Sunday", 6 },
410 { "Sun", 6 }
411 };
412
413 int l = -1;
414 bool first = true;
415
416 assert(p);
417 assert(*p);
418 assert(c);
419
420 for (;;) {
da6053d0 421 size_t i;
36697dc0 422
36697dc0
LP
423 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
424 size_t skip;
425
426 if (!startswith_no_case(*p, day_nr[i].name))
427 continue;
428
429 skip = strlen(day_nr[i].name);
430
4c701096 431 if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
36697dc0
LP
432 return -EINVAL;
433
434 c->weekdays_bits |= 1 << day_nr[i].nr;
435
436 if (l >= 0) {
437 int j;
438
439 if (l > day_nr[i].nr)
440 return -EINVAL;
441
442 for (j = l + 1; j < day_nr[i].nr; j++)
443 c->weekdays_bits |= 1 << j;
444 }
445
446 *p += skip;
447 break;
448 }
449
450 /* Couldn't find this prefix, so let's assume the
451 weekday was not specified and let's continue with
452 the date */
453 if (i >= ELEMENTSOF(day_nr))
454 return first ? 0 : -EINVAL;
455
456 /* We reached the end of the string */
457 if (**p == 0)
458 return 0;
459
460 /* We reached the end of the weekday spec part */
461 if (**p == ' ') {
462 *p += strspn(*p, " ");
463 return 0;
464 }
465
e638d050
DC
466 if (**p == '.') {
467 if (l >= 0)
468 return -EINVAL;
469
470 if ((*p)[1] != '.')
471 return -EINVAL;
472
473 l = day_nr[i].nr;
6bae2fd4 474 *p += 2;
e638d050
DC
475
476 /* Support ranges with "-" for backwards compatibility */
477 } else if (**p == '-') {
36697dc0
LP
478 if (l >= 0)
479 return -EINVAL;
480
481 l = day_nr[i].nr;
6bae2fd4
DC
482 *p += 1;
483
484 } else if (**p == ',') {
36697dc0 485 l = -1;
6bae2fd4
DC
486 *p += 1;
487 }
488
482f3b54 489 /* Allow a trailing comma but not an open range */
4c701096 490 if (IN_SET(**p, 0, ' ')) {
6bae2fd4
DC
491 *p += strspn(*p, " ");
492 return l < 0 ? 0 : -EINVAL;
493 }
36697dc0 494
36697dc0
LP
495 first = false;
496 }
497}
498
d80e5b74
ZJS
499static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
500 char *ee = NULL;
501 unsigned long value;
502
503 errno = 0;
504 value = strtoul(p, &ee, 10);
505 if (errno > 0)
506 return -errno;
507 if (ee == p)
508 return -EINVAL;
509
510 *ret = value;
511 *e = ee;
512 return 0;
513}
514
c0aebb4b 515static int parse_component_decimal(const char **p, bool usec, int *res) {
436dd70f
HV
516 unsigned long value;
517 const char *e = NULL;
436dd70f 518 int r;
36697dc0 519
9dfa81a0
DC
520 if (!isdigit(**p))
521 return -EINVAL;
522
d80e5b74
ZJS
523 r = parse_one_number(*p, &e, &value);
524 if (r < 0)
525 return r;
36697dc0 526
436dd70f
HV
527 if (usec) {
528 if (value * USEC_PER_SEC / USEC_PER_SEC != value)
36697dc0
LP
529 return -ERANGE;
530
436dd70f 531 value *= USEC_PER_SEC;
436dd70f 532
60bf5836
DC
533 /* One "." is a decimal point, but ".." is a range separator */
534 if (e[0] == '.' && e[1] != '.') {
535 unsigned add;
36ff0c97 536
436dd70f
HV
537 e++;
538 r = parse_fractional_part_u(&e, 6, &add);
539 if (r < 0)
540 return r;
541
542 if (add + value < value)
543 return -ERANGE;
544 value += add;
545 }
546 }
547
c0aebb4b
DC
548 if (value > INT_MAX)
549 return -ERANGE;
550
436dd70f
HV
551 *p = e;
552 *res = value;
553
554 return 0;
555}
556
32b52369
DC
557static int const_chain(int value, CalendarComponent **c) {
558 CalendarComponent *cc = NULL;
559
560 assert(c);
561
4122b14b 562 cc = new(CalendarComponent, 1);
32b52369
DC
563 if (!cc)
564 return -ENOMEM;
565
4122b14b
YW
566 *cc = (CalendarComponent) {
567 .start = value,
568 .stop = -1,
569 .repeat = 0,
570 .next = *c,
571 };
32b52369
DC
572
573 *c = cc;
574
575 return 0;
576}
577
d80e5b74 578static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
fb3ba5ec
YW
579 _cleanup_(chain_freep) CalendarComponent
580 *year = NULL, *month = NULL, *day = NULL,
581 *hour = NULL, *minute = NULL, *us = NULL;
d80e5b74 582 struct tm tm;
d80e5b74
ZJS
583 int r;
584
55a30fd4
ZJS
585 if (!gmtime_r(&time, &tm))
586 return -ERANGE;
d80e5b74 587
daa4aca1
YW
588 if (tm.tm_year > INT_MAX - 1900)
589 return -ERANGE;
590
d80e5b74
ZJS
591 r = const_chain(tm.tm_year + 1900, &year);
592 if (r < 0)
593 return r;
594
595 r = const_chain(tm.tm_mon + 1, &month);
596 if (r < 0)
597 return r;
598
599 r = const_chain(tm.tm_mday, &day);
600 if (r < 0)
601 return r;
602
603 r = const_chain(tm.tm_hour, &hour);
604 if (r < 0)
605 return r;
606
607 r = const_chain(tm.tm_min, &minute);
608 if (r < 0)
609 return r;
610
611 r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
612 if (r < 0)
613 return r;
614
615 c->utc = true;
fb3ba5ec
YW
616 c->year = TAKE_PTR(year);
617 c->month = TAKE_PTR(month);
618 c->day = TAKE_PTR(day);
619 c->hour = TAKE_PTR(hour);
620 c->minute = TAKE_PTR(minute);
621 c->microsecond = TAKE_PTR(us);
d80e5b74
ZJS
622 return 0;
623}
624
74353158 625static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
c0aebb4b 626 int r, start, stop = -1, repeat = 0;
436dd70f 627 CalendarComponent *cc;
74353158 628 const char *e = *p;
436dd70f
HV
629
630 assert(p);
631 assert(c);
632
74353158
ZJS
633 if (nesting > CALENDARSPEC_COMPONENTS_MAX)
634 return -ENOBUFS;
436dd70f 635
e78fce48 636 r = parse_component_decimal(&e, usec, &start);
436dd70f
HV
637 if (r < 0)
638 return r;
639
a2eb5ea7
DC
640 if (e[0] == '.' && e[1] == '.') {
641 e += 2;
e78fce48 642 r = parse_component_decimal(&e, usec, &stop);
a2eb5ea7
DC
643 if (r < 0)
644 return r;
645
646 repeat = usec ? USEC_PER_SEC : 1;
647 }
648
436dd70f
HV
649 if (*e == '/') {
650 e++;
651 r = parse_component_decimal(&e, usec, &repeat);
652 if (r < 0)
653 return r;
654
655 if (repeat == 0)
656 return -ERANGE;
c9c9f6f4
LP
657 } else {
658 /* If no repeat value is specified for the µs component, then let's explicitly refuse ranges
659 * below 1s because our default repeat granularity is beyond that. */
660
c07f18ff
FS
661 /* Overflow check */
662 if (start > INT_MAX - repeat)
663 return -ERANGE;
664
c9c9f6f4
LP
665 if (usec && stop >= 0 && start + repeat > stop)
666 return -EINVAL;
36697dc0
LP
667 }
668
4c701096 669 if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
36697dc0
LP
670 return -EINVAL;
671
4122b14b 672 cc = new(CalendarComponent, 1);
36697dc0
LP
673 if (!cc)
674 return -ENOMEM;
675
4122b14b
YW
676 *cc = (CalendarComponent) {
677 .start = start,
678 .stop = stop,
679 .repeat = repeat,
680 .next = *c,
681 };
36697dc0
LP
682
683 *p = e;
684 *c = cc;
685
686 if (*e ==',') {
687 *p += 1;
74353158 688 return prepend_component(p, usec, nesting + 1, c);
36697dc0
LP
689 }
690
691 return 0;
692}
693
436dd70f 694static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
fb3ba5ec 695 _cleanup_(chain_freep) CalendarComponent *cc = NULL;
36697dc0 696 const char *t;
36697dc0
LP
697 int r;
698
699 assert(p);
700 assert(c);
701
702 t = *p;
703
704 if (t[0] == '*') {
436dd70f
HV
705 if (usec) {
706 r = const_chain(0, c);
707 if (r < 0)
708 return r;
709 (*c)->repeat = USEC_PER_SEC;
710 } else
711 *c = NULL;
712
36697dc0 713 *p = t + 1;
36697dc0
LP
714 return 0;
715 }
716
74353158 717 r = prepend_component(&t, usec, 0, &cc);
fb3ba5ec 718 if (r < 0)
36697dc0 719 return r;
36697dc0
LP
720
721 *p = t;
fb3ba5ec 722 *c = TAKE_PTR(cc);
36697dc0
LP
723 return 0;
724}
725
36697dc0 726static int parse_date(const char **p, CalendarSpec *c) {
fb3ba5ec 727 _cleanup_(chain_freep) CalendarComponent *first = NULL, *second = NULL, *third = NULL;
36697dc0
LP
728 const char *t;
729 int r;
36697dc0
LP
730
731 assert(p);
732 assert(*p);
733 assert(c);
734
735 t = *p;
736
737 if (*t == 0)
738 return 0;
739
d80e5b74
ZJS
740 /* @TIMESTAMP — UNIX time in seconds since the epoch */
741 if (*t == '@') {
742 unsigned long value;
743 time_t time;
744
745 r = parse_one_number(t + 1, &t, &value);
746 if (r < 0)
747 return r;
748
749 time = value;
750 if ((unsigned long) time != value)
751 return -ERANGE;
752
753 r = calendarspec_from_time_t(c, time);
754 if (r < 0)
755 return r;
756
757 *p = t;
758 return 1; /* finito, don't parse H:M:S after that */
759 }
760
436dd70f 761 r = parse_chain(&t, false, &first);
36697dc0
LP
762 if (r < 0)
763 return r;
764
765 /* Already the end? A ':' as separator? In that case this was a time, not a date */
fb3ba5ec 766 if (IN_SET(*t, 0, ':'))
36697dc0 767 return 0;
36697dc0 768
8ea80351
DC
769 if (*t == '~')
770 c->end_of_month = true;
fb3ba5ec 771 else if (*t != '-')
36697dc0 772 return -EINVAL;
36697dc0
LP
773
774 t++;
436dd70f 775 r = parse_chain(&t, false, &second);
fb3ba5ec 776 if (r < 0)
36697dc0 777 return r;
36697dc0
LP
778
779 /* Got two parts, hence it's month and day */
4c701096 780 if (IN_SET(*t, 0, ' ')) {
36697dc0 781 *p = t + strspn(t, " ");
fb3ba5ec
YW
782 c->month = TAKE_PTR(first);
783 c->day = TAKE_PTR(second);
36697dc0 784 return 0;
fb3ba5ec 785 } else if (c->end_of_month)
8ea80351 786 return -EINVAL;
36697dc0 787
8ea80351
DC
788 if (*t == '~')
789 c->end_of_month = true;
fb3ba5ec 790 else if (*t != '-')
36697dc0 791 return -EINVAL;
36697dc0
LP
792
793 t++;
436dd70f 794 r = parse_chain(&t, false, &third);
fb3ba5ec 795 if (r < 0)
36697dc0 796 return r;
36697dc0 797
fb3ba5ec
YW
798 if (!IN_SET(*t, 0, ' '))
799 return -EINVAL;
36697dc0 800
fb3ba5ec
YW
801 /* Got three parts, hence it is year, month and day */
802 *p = t + strspn(t, " ");
803 c->year = TAKE_PTR(first);
804 c->month = TAKE_PTR(second);
805 c->day = TAKE_PTR(third);
806 return 0;
36697dc0
LP
807}
808
519cffec 809static int parse_calendar_time(const char **p, CalendarSpec *c) {
fb3ba5ec 810 _cleanup_(chain_freep) CalendarComponent *h = NULL, *m = NULL, *s = NULL;
36697dc0
LP
811 const char *t;
812 int r;
813
814 assert(p);
815 assert(*p);
816 assert(c);
817
818 t = *p;
819
408a51e1
DC
820 /* If no time is specified at all, then this means 00:00:00 */
821 if (*t == 0)
822 goto null_hour;
36697dc0 823
436dd70f 824 r = parse_chain(&t, false, &h);
36697dc0 825 if (r < 0)
fb3ba5ec 826 return r;
36697dc0 827
fb3ba5ec
YW
828 if (*t != ':')
829 return -EINVAL;
36697dc0
LP
830
831 t++;
436dd70f 832 r = parse_chain(&t, false, &m);
36697dc0 833 if (r < 0)
fb3ba5ec 834 return r;
36697dc0
LP
835
836 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
c0df71fa
DC
837 if (*t == 0)
838 goto null_second;
36697dc0 839
fb3ba5ec
YW
840 if (*t != ':')
841 return -EINVAL;
36697dc0
LP
842
843 t++;
436dd70f 844 r = parse_chain(&t, true, &s);
36697dc0 845 if (r < 0)
fb3ba5ec 846 return r;
36697dc0
LP
847
848 /* At the end? Then it's hours, minutes and seconds */
849 if (*t == 0)
850 goto finish;
851
fb3ba5ec 852 return -EINVAL;
36697dc0
LP
853
854null_hour:
855 r = const_chain(0, &h);
856 if (r < 0)
fb3ba5ec 857 return r;
36697dc0
LP
858
859 r = const_chain(0, &m);
860 if (r < 0)
fb3ba5ec 861 return r;
36697dc0
LP
862
863null_second:
864 r = const_chain(0, &s);
865 if (r < 0)
fb3ba5ec 866 return r;
36697dc0
LP
867
868finish:
869 *p = t;
fb3ba5ec
YW
870 c->hour = TAKE_PTR(h);
871 c->minute = TAKE_PTR(m);
872 c->microsecond = TAKE_PTR(s);
436dd70f 873
36697dc0 874 return 0;
36697dc0
LP
875}
876
877int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
21b3a0fc 878 const char *utc;
921b5987 879 _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
ea53cfd1 880 _cleanup_free_ char *p_tmp = NULL;
36697dc0
LP
881 int r;
882
883 assert(p);
36697dc0 884
4122b14b 885 c = new(CalendarSpec, 1);
36697dc0
LP
886 if (!c)
887 return -ENOMEM;
4122b14b
YW
888
889 *c = (CalendarSpec) {
890 .dst = -1,
891 .timezone = NULL,
892 };
36697dc0 893
078efddd
HV
894 utc = endswith_no_case(p, " UTC");
895 if (utc) {
896 c->utc = true;
ea53cfd1
ZJS
897 p = p_tmp = strndup(p, utc - p);
898 if (!p)
899 return -ENOMEM;
21b3a0fc
LP
900 } else {
901 const char *e = NULL;
902 int j;
903
904 tzset();
905
906 /* Check if the local timezone was specified? */
907 for (j = 0; j <= 1; j++) {
908 if (isempty(tzname[j]))
909 continue;
910
911 e = endswith_no_case(p, tzname[j]);
d80e5b74 912 if (!e)
21b3a0fc
LP
913 continue;
914 if (e == p)
915 continue;
916 if (e[-1] != ' ')
917 continue;
918
919 break;
920 }
921
922 /* Found one of the two timezones specified? */
923 if (IN_SET(j, 0, 1)) {
ea53cfd1
ZJS
924 p = p_tmp = strndup(p, e - p - 1);
925 if (!p)
926 return -ENOMEM;
927
21b3a0fc 928 c->dst = j;
48d26c01
IK
929 } else {
930 const char *last_space;
48d26c01 931
a2932a0d 932 last_space = strrchr(p, ' ');
089fb865 933 if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
a2932a0d 934 c->timezone = strdup(last_space + 1);
921b5987
DT
935 if (!c->timezone)
936 return -ENOMEM;
a2932a0d 937
ea53cfd1
ZJS
938 p = p_tmp = strndup(p, last_space - p);
939 if (!p)
940 return -ENOMEM;
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
ddd6a22a
LP
986 } else if (STRCASE_IN_SET(p,
987 "annually",
988 "yearly",
989 "anually") /* backwards compatibility */ ) {
dbfd41e2 990
13516818
LP
991 r = const_chain(1, &c->month);
992 if (r < 0)
921b5987 993 return r;
13516818
LP
994 r = const_chain(1, &c->day);
995 if (r < 0)
921b5987 996 return r;
13516818
LP
997 r = const_chain(0, &c->hour);
998 if (r < 0)
921b5987 999 return r;
13516818
LP
1000 r = const_chain(0, &c->minute);
1001 if (r < 0)
921b5987 1002 return r;
436dd70f 1003 r = const_chain(0, &c->microsecond);
13516818 1004 if (r < 0)
921b5987 1005 return r;
13516818 1006
b43d1d01 1007 } else if (strcaseeq(p, "weekly")) {
36697dc0
LP
1008
1009 c->weekdays_bits = 1;
1010
1011 r = const_chain(0, &c->hour);
1012 if (r < 0)
921b5987 1013 return r;
36697dc0
LP
1014 r = const_chain(0, &c->minute);
1015 if (r < 0)
921b5987 1016 return r;
436dd70f 1017 r = const_chain(0, &c->microsecond);
dbfd41e2 1018 if (r < 0)
921b5987 1019 return r;
dbfd41e2
LP
1020
1021 } else if (strcaseeq(p, "quarterly")) {
1022
1023 r = const_chain(1, &c->month);
1024 if (r < 0)
921b5987 1025 return r;
dbfd41e2
LP
1026 r = const_chain(4, &c->month);
1027 if (r < 0)
921b5987 1028 return r;
dbfd41e2
LP
1029 r = const_chain(7, &c->month);
1030 if (r < 0)
921b5987 1031 return r;
dbfd41e2
LP
1032 r = const_chain(10, &c->month);
1033 if (r < 0)
921b5987 1034 return r;
dbfd41e2
LP
1035 r = const_chain(1, &c->day);
1036 if (r < 0)
921b5987 1037 return r;
dbfd41e2
LP
1038 r = const_chain(0, &c->hour);
1039 if (r < 0)
921b5987 1040 return r;
dbfd41e2
LP
1041 r = const_chain(0, &c->minute);
1042 if (r < 0)
921b5987 1043 return r;
436dd70f 1044 r = const_chain(0, &c->microsecond);
dbfd41e2 1045 if (r < 0)
921b5987 1046 return r;
dbfd41e2 1047
ddd6a22a
LP
1048 } else if (STRCASE_IN_SET(p,
1049 "biannually",
1050 "bi-annually",
1051 "semiannually",
1052 "semi-annually")) {
dbfd41e2
LP
1053
1054 r = const_chain(1, &c->month);
1055 if (r < 0)
921b5987 1056 return r;
dbfd41e2
LP
1057 r = const_chain(7, &c->month);
1058 if (r < 0)
921b5987 1059 return r;
dbfd41e2
LP
1060 r = const_chain(1, &c->day);
1061 if (r < 0)
921b5987 1062 return r;
dbfd41e2
LP
1063 r = const_chain(0, &c->hour);
1064 if (r < 0)
921b5987 1065 return r;
dbfd41e2
LP
1066 r = const_chain(0, &c->minute);
1067 if (r < 0)
921b5987 1068 return r;
436dd70f 1069 r = const_chain(0, &c->microsecond);
36697dc0 1070 if (r < 0)
921b5987 1071 return r;
36697dc0
LP
1072
1073 } else {
1074 r = parse_weekdays(&p, c);
1075 if (r < 0)
921b5987 1076 return r;
36697dc0
LP
1077
1078 r = parse_date(&p, c);
1079 if (r < 0)
921b5987 1080 return r;
36697dc0 1081
d80e5b74
ZJS
1082 if (r == 0) {
1083 r = parse_calendar_time(&p, c);
1084 if (r < 0)
921b5987 1085 return r;
d80e5b74 1086 }
36697dc0 1087
921b5987
DT
1088 if (*p != 0)
1089 return -EINVAL;
36697dc0
LP
1090 }
1091
1092 r = calendar_spec_normalize(c);
1093 if (r < 0)
921b5987 1094 return r;
36697dc0 1095
921b5987
DT
1096 if (!calendar_spec_valid(c))
1097 return -EINVAL;
36697dc0 1098
9a2b3d3d
ZJS
1099 if (spec)
1100 *spec = TAKE_PTR(c);
36697dc0 1101 return 0;
36697dc0
LP
1102}
1103
462f15d9 1104static int find_end_of_month(const 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
462f15d9
ZJS
1117static int find_matching_component(
1118 const CalendarSpec *spec,
1119 const CalendarComponent *c,
1120 const struct tm *tm, /* tm is only used for end-of-month calculations */
1121 int *val) {
1122
1123 int d = -1, r;
36697dc0 1124 bool d_set = false;
36697dc0
LP
1125
1126 assert(val);
1127
462f15d9
ZJS
1128 /* Finds the *earliest* matching time specified by one of the CalendarCompoment items in chain c.
1129 * If no matches can be found, returns -ENOENT.
1130 * Otherwise, updates *val to the matching time. 1 is returned if *val was changed, 0 otherwise.
1131 */
1132
36697dc0
LP
1133 if (!c)
1134 return 0;
1135
462f15d9
ZJS
1136 bool end_of_month = spec->end_of_month && c == spec->day;
1137
36697dc0 1138 while (c) {
462f15d9 1139 int start, stop;
a2eb5ea7 1140
462f15d9
ZJS
1141 if (end_of_month) {
1142 start = find_end_of_month(tm, spec->utc, c->start);
1143 stop = find_end_of_month(tm, spec->utc, c->stop);
a2eb5ea7 1144
e78fce48
DC
1145 if (stop > 0)
1146 SWAP_TWO(start, stop);
462f15d9
ZJS
1147 } else {
1148 start = c->start;
1149 stop = c->stop;
a2eb5ea7 1150 }
8ea80351 1151
e78fce48 1152 if (start >= *val) {
36697dc0 1153
e78fce48
DC
1154 if (!d_set || start < d) {
1155 d = start;
36697dc0
LP
1156 d_set = true;
1157 }
1158
1159 } else if (c->repeat > 0) {
1160 int k;
1161
be6b0c21 1162 k = start + c->repeat * DIV_ROUND_UP(*val - start, c->repeat);
36697dc0 1163
e78fce48 1164 if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
36697dc0
LP
1165 d = k;
1166 d_set = true;
1167 }
1168 }
1169
482f3b54 1170 c = c->next;
36697dc0
LP
1171 }
1172
1173 if (!d_set)
1174 return -ENOENT;
1175
1176 r = *val != d;
1177 *val = d;
1178 return r;
1179}
1180
be75c86d 1181static int tm_within_bounds(struct tm *tm, bool utc) {
36697dc0
LP
1182 struct tm t;
1183 assert(tm);
1184
f6e7d66b
DC
1185 /*
1186 * Set an upper bound on the year so impossible dates like "*-02-31"
1187 * don't cause find_next() to loop forever. tm_year contains years
1188 * since 1900, so adjust it accordingly.
1189 */
1190 if (tm->tm_year + 1900 > MAX_YEAR)
be75c86d
ZJS
1191 return -ERANGE;
1192
1193 t = *tm;
1194 if (mktime_or_timegm(&t, utc) < 0)
1195 return negative_errno();
f6e7d66b 1196
36697dc0 1197 /* Did any normalization take place? If so, it was out of bounds before */
129cb6e2
ZJS
1198 int cmp = CMP(t.tm_year, tm->tm_year) ?:
1199 CMP(t.tm_mon, tm->tm_mon) ?:
1200 CMP(t.tm_mday, tm->tm_mday) ?:
1201 CMP(t.tm_hour, tm->tm_hour) ?:
1202 CMP(t.tm_min, tm->tm_min) ?:
1203 CMP(t.tm_sec, tm->tm_sec);
1204
1205 if (cmp < 0)
1206 return -EDEADLK; /* Refuse to go backward */
1207 if (cmp > 0)
be75c86d 1208 *tm = t;
129cb6e2 1209 return cmp == 0;
36697dc0
LP
1210}
1211
51ffa239 1212static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
36697dc0
LP
1213 struct tm t;
1214 int k;
1215
489464d0 1216 if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
36697dc0
LP
1217 return true;
1218
1219 t = *tm;
c477ff14 1220 if (mktime_or_timegm(&t, utc) < 0)
36697dc0
LP
1221 return false;
1222
1223 k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
1224 return (weekdays_bits & (1 << k));
1225}
1226
169615c9
ZJS
1227/* A safety valve: if we get stuck in the calculation, return an error.
1228 * C.f. https://bugzilla.redhat.com/show_bug.cgi?id=1941335. */
1229#define MAX_CALENDAR_ITERATIONS 1000
1230
436dd70f 1231static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
36697dc0 1232 struct tm c;
436dd70f 1233 int tm_usec;
36697dc0
LP
1234 int r;
1235
9bb656bd
LP
1236 /* Returns -ENOENT if the expression is not going to elapse anymore */
1237
36697dc0
LP
1238 assert(spec);
1239 assert(tm);
1240
1241 c = *tm;
436dd70f 1242 tm_usec = *usec;
36697dc0 1243
169615c9 1244 for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) {
36697dc0 1245 /* Normalize the current date */
ea3894c1 1246 (void) mktime_or_timegm(&c, spec->utc);
21b3a0fc 1247 c.tm_isdst = spec->dst;
36697dc0
LP
1248
1249 c.tm_year += 1900;
8ea80351 1250 r = find_matching_component(spec, spec->year, &c, &c.tm_year);
36697dc0
LP
1251 c.tm_year -= 1900;
1252
1253 if (r > 0) {
1254 c.tm_mon = 0;
1255 c.tm_mday = 1;
436dd70f 1256 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0 1257 }
e308ddca 1258 if (r < 0)
36697dc0 1259 return r;
be75c86d 1260 if (tm_within_bounds(&c, spec->utc) <= 0)
e308ddca 1261 return -ENOENT;
36697dc0
LP
1262
1263 c.tm_mon += 1;
8ea80351 1264 r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
36697dc0
LP
1265 c.tm_mon -= 1;
1266
1267 if (r > 0) {
1268 c.tm_mday = 1;
436dd70f 1269 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0 1270 }
be75c86d 1271 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
313cefa1 1272 c.tm_year++;
36697dc0
LP
1273 c.tm_mon = 0;
1274 c.tm_mday = 1;
436dd70f 1275 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1276 continue;
1277 }
be75c86d
ZJS
1278 if (r == 0)
1279 continue;
36697dc0 1280
8ea80351 1281 r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
36697dc0 1282 if (r > 0)
436dd70f 1283 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
be75c86d 1284 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
313cefa1 1285 c.tm_mon++;
36697dc0 1286 c.tm_mday = 1;
436dd70f 1287 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1288 continue;
1289 }
be75c86d
ZJS
1290 if (r == 0)
1291 continue;
36697dc0 1292
51ffa239 1293 if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
36697dc0 1294 c.tm_mday++;
436dd70f 1295 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1296 continue;
1297 }
1298
8ea80351 1299 r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
36697dc0 1300 if (r > 0)
a022d76e 1301 c.tm_min = c.tm_sec = tm_usec = 0;
be75c86d 1302 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
313cefa1 1303 c.tm_mday++;
436dd70f 1304 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1305 continue;
1306 }
be75c86d
ZJS
1307 if (r == 0)
1308 /* The next hour we set might be missing if there
1309 * are time zone changes. Let's try again starting at
1310 * normalized time. */
1311 continue;
36697dc0 1312
8ea80351 1313 r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
36697dc0 1314 if (r > 0)
a022d76e 1315 c.tm_sec = tm_usec = 0;
be75c86d 1316 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
313cefa1 1317 c.tm_hour++;
436dd70f 1318 c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1319 continue;
1320 }
be75c86d
ZJS
1321 if (r == 0)
1322 continue;
36697dc0 1323
436dd70f 1324 c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
8ea80351 1325 r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
436dd70f
HV
1326 tm_usec = c.tm_sec % USEC_PER_SEC;
1327 c.tm_sec /= USEC_PER_SEC;
1328
be75c86d 1329 if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
313cefa1 1330 c.tm_min++;
436dd70f 1331 c.tm_sec = tm_usec = 0;
36697dc0
LP
1332 continue;
1333 }
be75c86d
ZJS
1334 if (r == 0)
1335 continue;
36697dc0 1336
36697dc0 1337 *tm = c;
436dd70f 1338 *usec = tm_usec;
36697dc0
LP
1339 return 0;
1340 }
169615c9
ZJS
1341
1342 /* It seems we entered an infinite loop. Let's gracefully return an error instead of hanging or
1343 * aborting. This code is also exercised when timers.target is brought up during early boot, so
1344 * aborting here is problematic and hard to diagnose for users. */
1345 _cleanup_free_ char *s = NULL;
1346 (void) calendar_spec_to_string(spec, &s);
1347 return log_warning_errno(SYNTHETIC_ERRNO(EDEADLK),
1348 "Infinite loop in calendar calculation: %s", strna(s));
36697dc0
LP
1349}
1350
9bb656bd 1351static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
36697dc0
LP
1352 struct tm tm;
1353 time_t t;
1354 int r;
436dd70f 1355 usec_t tm_usec;
36697dc0
LP
1356
1357 assert(spec);
36697dc0 1358
1bb4b028
LP
1359 if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
1360 return -EINVAL;
1361
436dd70f
HV
1362 usec++;
1363 t = (time_t) (usec / USEC_PER_SEC);
51ffa239 1364 assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
436dd70f 1365 tm_usec = usec % USEC_PER_SEC;
36697dc0 1366
436dd70f 1367 r = find_next(spec, &tm, &tm_usec);
36697dc0
LP
1368 if (r < 0)
1369 return r;
1370
51ffa239 1371 t = mktime_or_timegm(&tm, spec->utc);
c477ff14 1372 if (t < 0)
36697dc0
LP
1373 return -EINVAL;
1374
9bb656bd
LP
1375 if (ret_next)
1376 *ret_next = (usec_t) t * USEC_PER_SEC + tm_usec;
1377
36697dc0
LP
1378 return 0;
1379}
48d26c01
IK
1380
1381typedef struct SpecNextResult {
1382 usec_t next;
1383 int return_value;
1384} SpecNextResult;
1385
9bb656bd 1386int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
4c253ed1 1387 SpecNextResult *shared, tmp;
48d26c01
IK
1388 int r;
1389
9bb656bd
LP
1390 assert(spec);
1391
48d26c01 1392 if (isempty(spec->timezone))
9bb656bd 1393 return calendar_spec_next_usec_impl(spec, usec, ret_next);
48d26c01
IK
1394
1395 shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1396 if (shared == MAP_FAILED)
1397 return negative_errno();
1398
1f5d1e02 1399 r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
4c253ed1 1400 if (r < 0) {
48d26c01 1401 (void) munmap(shared, sizeof *shared);
4c253ed1 1402 return r;
48d26c01 1403 }
4c253ed1 1404 if (r == 0) {
437f48a4
LP
1405 char *colon_tz;
1406
1407 /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
1408 colon_tz = strjoina(":", spec->timezone);
1409
1410 if (setenv("TZ", colon_tz, 1) != 0) {
48d26c01
IK
1411 shared->return_value = negative_errno();
1412 _exit(EXIT_FAILURE);
1413 }
1414
1415 tzset();
1416
1417 shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
1418
1419 _exit(EXIT_SUCCESS);
1420 }
1421
48d26c01 1422 tmp = *shared;
1f5d1e02 1423 if (munmap(shared, sizeof *shared) < 0)
48d26c01
IK
1424 return negative_errno();
1425
9bb656bd
LP
1426 if (tmp.return_value == 0 && ret_next)
1427 *ret_next = tmp.next;
48d26c01
IK
1428
1429 return tmp.return_value;
1430}