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