]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/calendarspec.c
log: minimize includes in log.h
[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
584 assert_se(gmtime_r(&time, &tm));
585
586 r = const_chain(tm.tm_year + 1900, &year);
587 if (r < 0)
588 return r;
589
590 r = const_chain(tm.tm_mon + 1, &month);
591 if (r < 0)
592 return r;
593
594 r = const_chain(tm.tm_mday, &day);
595 if (r < 0)
596 return r;
597
598 r = const_chain(tm.tm_hour, &hour);
599 if (r < 0)
600 return r;
601
602 r = const_chain(tm.tm_min, &minute);
603 if (r < 0)
604 return r;
605
606 r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
607 if (r < 0)
608 return r;
609
610 c->utc = true;
611 c->year = year;
612 c->month = month;
613 c->day = day;
614 c->hour = hour;
615 c->minute = minute;
616 c->microsecond = us;
617 return 0;
618}
619
436dd70f 620static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
c0aebb4b 621 int r, start, stop = -1, repeat = 0;
436dd70f 622 CalendarComponent *cc;
436dd70f
HV
623 const char *e;
624
625 assert(p);
626 assert(c);
627
628 e = *p;
629
e78fce48 630 r = parse_component_decimal(&e, usec, &start);
436dd70f
HV
631 if (r < 0)
632 return r;
633
a2eb5ea7
DC
634 if (e[0] == '.' && e[1] == '.') {
635 e += 2;
e78fce48 636 r = parse_component_decimal(&e, usec, &stop);
a2eb5ea7
DC
637 if (r < 0)
638 return r;
639
640 repeat = usec ? USEC_PER_SEC : 1;
641 }
642
436dd70f
HV
643 if (*e == '/') {
644 e++;
645 r = parse_component_decimal(&e, usec, &repeat);
646 if (r < 0)
647 return r;
648
649 if (repeat == 0)
650 return -ERANGE;
36697dc0
LP
651 }
652
4c701096 653 if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
36697dc0
LP
654 return -EINVAL;
655
656 cc = new0(CalendarComponent, 1);
657 if (!cc)
658 return -ENOMEM;
659
e78fce48
DC
660 cc->start = start;
661 cc->stop = stop;
36697dc0
LP
662 cc->repeat = repeat;
663 cc->next = *c;
664
665 *p = e;
666 *c = cc;
667
668 if (*e ==',') {
669 *p += 1;
436dd70f 670 return prepend_component(p, usec, c);
36697dc0
LP
671 }
672
673 return 0;
674}
675
436dd70f 676static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
36697dc0
LP
677 const char *t;
678 CalendarComponent *cc = NULL;
679 int r;
680
681 assert(p);
682 assert(c);
683
684 t = *p;
685
686 if (t[0] == '*') {
436dd70f
HV
687 if (usec) {
688 r = const_chain(0, c);
689 if (r < 0)
690 return r;
691 (*c)->repeat = USEC_PER_SEC;
692 } else
693 *c = NULL;
694
36697dc0 695 *p = t + 1;
36697dc0
LP
696 return 0;
697 }
698
436dd70f 699 r = prepend_component(&t, usec, &cc);
36697dc0
LP
700 if (r < 0) {
701 free_chain(cc);
702 return r;
703 }
704
705 *p = t;
706 *c = cc;
707 return 0;
708}
709
36697dc0
LP
710static int parse_date(const char **p, CalendarSpec *c) {
711 const char *t;
712 int r;
713 CalendarComponent *first, *second, *third;
714
715 assert(p);
716 assert(*p);
717 assert(c);
718
719 t = *p;
720
721 if (*t == 0)
722 return 0;
723
d80e5b74
ZJS
724 /* @TIMESTAMP — UNIX time in seconds since the epoch */
725 if (*t == '@') {
726 unsigned long value;
727 time_t time;
728
729 r = parse_one_number(t + 1, &t, &value);
730 if (r < 0)
731 return r;
732
733 time = value;
734 if ((unsigned long) time != value)
735 return -ERANGE;
736
737 r = calendarspec_from_time_t(c, time);
738 if (r < 0)
739 return r;
740
741 *p = t;
742 return 1; /* finito, don't parse H:M:S after that */
743 }
744
436dd70f 745 r = parse_chain(&t, false, &first);
36697dc0
LP
746 if (r < 0)
747 return r;
748
749 /* Already the end? A ':' as separator? In that case this was a time, not a date */
4c701096 750 if (IN_SET(*t, 0, ':')) {
36697dc0
LP
751 free_chain(first);
752 return 0;
753 }
754
8ea80351
DC
755 if (*t == '~')
756 c->end_of_month = true;
757 else if (*t != '-') {
36697dc0
LP
758 free_chain(first);
759 return -EINVAL;
760 }
761
762 t++;
436dd70f 763 r = parse_chain(&t, false, &second);
36697dc0
LP
764 if (r < 0) {
765 free_chain(first);
766 return r;
767 }
768
769 /* Got two parts, hence it's month and day */
4c701096 770 if (IN_SET(*t, 0, ' ')) {
36697dc0
LP
771 *p = t + strspn(t, " ");
772 c->month = first;
773 c->day = second;
774 return 0;
fc2371c7
DC
775 } else if (c->end_of_month) {
776 free_chain(first);
777 free_chain(second);
8ea80351 778 return -EINVAL;
fc2371c7 779 }
36697dc0 780
8ea80351
DC
781 if (*t == '~')
782 c->end_of_month = true;
783 else if (*t != '-') {
36697dc0
LP
784 free_chain(first);
785 free_chain(second);
786 return -EINVAL;
787 }
788
789 t++;
436dd70f 790 r = parse_chain(&t, false, &third);
36697dc0
LP
791 if (r < 0) {
792 free_chain(first);
793 free_chain(second);
794 return r;
795 }
796
8ea80351 797 /* Got three parts, hence it is year, month and day */
4c701096 798 if (IN_SET(*t, 0, ' ')) {
36697dc0
LP
799 *p = t + strspn(t, " ");
800 c->year = first;
801 c->month = second;
802 c->day = third;
803 return 0;
804 }
805
806 free_chain(first);
807 free_chain(second);
808 free_chain(third);
809 return -EINVAL;
810}
811
519cffec 812static int parse_calendar_time(const char **p, CalendarSpec *c) {
36697dc0
LP
813 CalendarComponent *h = NULL, *m = NULL, *s = NULL;
814 const char *t;
815 int r;
816
817 assert(p);
818 assert(*p);
819 assert(c);
820
821 t = *p;
822
408a51e1
DC
823 /* If no time is specified at all, then this means 00:00:00 */
824 if (*t == 0)
825 goto null_hour;
36697dc0 826
436dd70f 827 r = parse_chain(&t, false, &h);
36697dc0
LP
828 if (r < 0)
829 goto fail;
830
831 if (*t != ':') {
832 r = -EINVAL;
833 goto fail;
834 }
835
836 t++;
436dd70f 837 r = parse_chain(&t, false, &m);
36697dc0
LP
838 if (r < 0)
839 goto fail;
840
841 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
c0df71fa
DC
842 if (*t == 0)
843 goto null_second;
36697dc0
LP
844
845 if (*t != ':') {
846 r = -EINVAL;
847 goto fail;
848 }
849
850 t++;
436dd70f 851 r = parse_chain(&t, true, &s);
36697dc0
LP
852 if (r < 0)
853 goto fail;
854
855 /* At the end? Then it's hours, minutes and seconds */
856 if (*t == 0)
857 goto finish;
858
859 r = -EINVAL;
860 goto fail;
861
862null_hour:
863 r = const_chain(0, &h);
864 if (r < 0)
865 goto fail;
866
867 r = const_chain(0, &m);
868 if (r < 0)
869 goto fail;
870
871null_second:
872 r = const_chain(0, &s);
873 if (r < 0)
874 goto fail;
875
876finish:
877 *p = t;
878 c->hour = h;
879 c->minute = m;
436dd70f
HV
880 c->microsecond = s;
881
36697dc0
LP
882 return 0;
883
884fail:
885 free_chain(h);
886 free_chain(m);
887 free_chain(s);
888 return r;
889}
890
891int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
21b3a0fc 892 const char *utc;
36697dc0
LP
893 CalendarSpec *c;
894 int r;
895
896 assert(p);
897 assert(spec);
898
36697dc0
LP
899 c = new0(CalendarSpec, 1);
900 if (!c)
901 return -ENOMEM;
21b3a0fc 902 c->dst = -1;
48d26c01 903 c->timezone = NULL;
36697dc0 904
078efddd
HV
905 utc = endswith_no_case(p, " UTC");
906 if (utc) {
907 c->utc = true;
908 p = strndupa(p, utc - p);
21b3a0fc
LP
909 } else {
910 const char *e = NULL;
911 int j;
912
913 tzset();
914
915 /* Check if the local timezone was specified? */
916 for (j = 0; j <= 1; j++) {
917 if (isempty(tzname[j]))
918 continue;
919
920 e = endswith_no_case(p, tzname[j]);
d80e5b74 921 if (!e)
21b3a0fc
LP
922 continue;
923 if (e == p)
924 continue;
925 if (e[-1] != ' ')
926 continue;
927
928 break;
929 }
930
931 /* Found one of the two timezones specified? */
932 if (IN_SET(j, 0, 1)) {
933 p = strndupa(p, e - p - 1);
934 c->dst = j;
48d26c01
IK
935 } else {
936 const char *last_space;
48d26c01 937
a2932a0d
ZJS
938 last_space = strrchr(p, ' ');
939 if (last_space != NULL && timezone_is_valid(last_space + 1)) {
940 c->timezone = strdup(last_space + 1);
941 if (!c->timezone) {
942 r = -ENOMEM;
943 goto fail;
48d26c01 944 }
a2932a0d
ZJS
945
946 p = strndupa(p, last_space - p);
48d26c01 947 }
21b3a0fc 948 }
078efddd 949 }
51ffa239 950
04773cb5
DC
951 if (isempty(p)) {
952 r = -EINVAL;
953 goto fail;
954 }
955
272ac205 956 if (strcaseeq(p, "minutely")) {
436dd70f 957 r = const_chain(0, &c->microsecond);
272ac205
DM
958 if (r < 0)
959 goto fail;
960
961 } else if (strcaseeq(p, "hourly")) {
36697dc0
LP
962 r = const_chain(0, &c->minute);
963 if (r < 0)
964 goto fail;
436dd70f 965 r = const_chain(0, &c->microsecond);
36697dc0
LP
966 if (r < 0)
967 goto fail;
968
b43d1d01 969 } else if (strcaseeq(p, "daily")) {
36697dc0
LP
970 r = const_chain(0, &c->hour);
971 if (r < 0)
972 goto fail;
973 r = const_chain(0, &c->minute);
974 if (r < 0)
975 goto fail;
436dd70f 976 r = const_chain(0, &c->microsecond);
36697dc0
LP
977 if (r < 0)
978 goto fail;
979
b43d1d01 980 } else if (strcaseeq(p, "monthly")) {
36697dc0
LP
981 r = const_chain(1, &c->day);
982 if (r < 0)
983 goto fail;
984 r = const_chain(0, &c->hour);
985 if (r < 0)
986 goto fail;
987 r = const_chain(0, &c->minute);
988 if (r < 0)
989 goto fail;
436dd70f 990 r = const_chain(0, &c->microsecond);
36697dc0
LP
991 if (r < 0)
992 goto fail;
993
dbfd41e2
LP
994 } else if (strcaseeq(p, "annually") ||
995 strcaseeq(p, "yearly") ||
996 strcaseeq(p, "anually") /* backwards compatibility */ ) {
997
13516818
LP
998 r = const_chain(1, &c->month);
999 if (r < 0)
1000 goto fail;
1001 r = const_chain(1, &c->day);
1002 if (r < 0)
1003 goto fail;
1004 r = const_chain(0, &c->hour);
1005 if (r < 0)
1006 goto fail;
1007 r = const_chain(0, &c->minute);
1008 if (r < 0)
1009 goto fail;
436dd70f 1010 r = const_chain(0, &c->microsecond);
13516818
LP
1011 if (r < 0)
1012 goto fail;
1013
b43d1d01 1014 } else if (strcaseeq(p, "weekly")) {
36697dc0
LP
1015
1016 c->weekdays_bits = 1;
1017
1018 r = const_chain(0, &c->hour);
1019 if (r < 0)
1020 goto fail;
1021 r = const_chain(0, &c->minute);
1022 if (r < 0)
1023 goto fail;
436dd70f 1024 r = const_chain(0, &c->microsecond);
dbfd41e2
LP
1025 if (r < 0)
1026 goto fail;
1027
1028 } else if (strcaseeq(p, "quarterly")) {
1029
1030 r = const_chain(1, &c->month);
1031 if (r < 0)
1032 goto fail;
1033 r = const_chain(4, &c->month);
1034 if (r < 0)
1035 goto fail;
1036 r = const_chain(7, &c->month);
1037 if (r < 0)
1038 goto fail;
1039 r = const_chain(10, &c->month);
1040 if (r < 0)
1041 goto fail;
1042 r = const_chain(1, &c->day);
1043 if (r < 0)
1044 goto fail;
1045 r = const_chain(0, &c->hour);
1046 if (r < 0)
1047 goto fail;
1048 r = const_chain(0, &c->minute);
1049 if (r < 0)
1050 goto fail;
436dd70f 1051 r = const_chain(0, &c->microsecond);
dbfd41e2
LP
1052 if (r < 0)
1053 goto fail;
1054
1055 } else if (strcaseeq(p, "biannually") ||
1056 strcaseeq(p, "bi-annually") ||
1057 strcaseeq(p, "semiannually") ||
1058 strcaseeq(p, "semi-annually")) {
1059
1060 r = const_chain(1, &c->month);
1061 if (r < 0)
1062 goto fail;
1063 r = const_chain(7, &c->month);
1064 if (r < 0)
1065 goto fail;
1066 r = const_chain(1, &c->day);
1067 if (r < 0)
1068 goto fail;
1069 r = const_chain(0, &c->hour);
1070 if (r < 0)
1071 goto fail;
1072 r = const_chain(0, &c->minute);
1073 if (r < 0)
1074 goto fail;
436dd70f 1075 r = const_chain(0, &c->microsecond);
36697dc0
LP
1076 if (r < 0)
1077 goto fail;
1078
1079 } else {
1080 r = parse_weekdays(&p, c);
1081 if (r < 0)
1082 goto fail;
1083
1084 r = parse_date(&p, c);
1085 if (r < 0)
1086 goto fail;
1087
d80e5b74
ZJS
1088 if (r == 0) {
1089 r = parse_calendar_time(&p, c);
1090 if (r < 0)
1091 goto fail;
1092 }
36697dc0
LP
1093
1094 if (*p != 0) {
1095 r = -EINVAL;
1096 goto fail;
1097 }
1098 }
1099
1100 r = calendar_spec_normalize(c);
1101 if (r < 0)
1102 goto fail;
1103
1104 if (!calendar_spec_valid(c)) {
1105 r = -EINVAL;
1106 goto fail;
1107 }
1108
1109 *spec = c;
1110 return 0;
1111
1112fail:
1113 calendar_spec_free(c);
1114 return r;
1115}
1116
60bf5836 1117static int find_end_of_month(struct tm *tm, bool utc, int day) {
a2eb5ea7
DC
1118 struct tm t = *tm;
1119
1120 t.tm_mon++;
1121 t.tm_mday = 1 - day;
1122
c477ff14 1123 if (mktime_or_timegm(&t, utc) < 0 ||
a2eb5ea7
DC
1124 t.tm_mon != tm->tm_mon)
1125 return -1;
1126
1127 return t.tm_mday;
1128}
1129
8ea80351
DC
1130static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
1131 struct tm *tm, int *val) {
482f3b54 1132 const CalendarComponent *p = c;
e78fce48 1133 int start, stop, d = -1;
36697dc0
LP
1134 bool d_set = false;
1135 int r;
1136
1137 assert(val);
1138
1139 if (!c)
1140 return 0;
1141
1142 while (c) {
e78fce48
DC
1143 start = c->start;
1144 stop = c->stop;
a2eb5ea7 1145
8ea80351 1146 if (spec->end_of_month && p == spec->day) {
e78fce48
DC
1147 start = find_end_of_month(tm, spec->utc, start);
1148 stop = find_end_of_month(tm, spec->utc, stop);
a2eb5ea7 1149
e78fce48
DC
1150 if (stop > 0)
1151 SWAP_TWO(start, stop);
a2eb5ea7 1152 }
8ea80351 1153
e78fce48 1154 if (start >= *val) {
36697dc0 1155
e78fce48
DC
1156 if (!d_set || start < d) {
1157 d = start;
36697dc0
LP
1158 d_set = true;
1159 }
1160
1161 } else if (c->repeat > 0) {
1162 int k;
1163
e78fce48 1164 k = start + c->repeat * ((*val - start + c->repeat - 1) / c->repeat);
36697dc0 1165
e78fce48 1166 if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
36697dc0
LP
1167 d = k;
1168 d_set = true;
1169 }
1170 }
1171
482f3b54 1172 c = c->next;
36697dc0
LP
1173 }
1174
1175 if (!d_set)
1176 return -ENOENT;
1177
1178 r = *val != d;
1179 *val = d;
1180 return r;
1181}
1182
51ffa239 1183static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
36697dc0
LP
1184 struct tm t;
1185 assert(tm);
1186
1187 t = *tm;
1188
c477ff14 1189 if (mktime_or_timegm(&t, utc) < 0)
36697dc0
LP
1190 return true;
1191
f6e7d66b
DC
1192 /*
1193 * Set an upper bound on the year so impossible dates like "*-02-31"
1194 * don't cause find_next() to loop forever. tm_year contains years
1195 * since 1900, so adjust it accordingly.
1196 */
1197 if (tm->tm_year + 1900 > MAX_YEAR)
1198 return true;
1199
36697dc0
LP
1200 /* Did any normalization take place? If so, it was out of bounds before */
1201 return
1202 t.tm_year != tm->tm_year ||
1203 t.tm_mon != tm->tm_mon ||
1204 t.tm_mday != tm->tm_mday ||
1205 t.tm_hour != tm->tm_hour ||
1206 t.tm_min != tm->tm_min ||
1207 t.tm_sec != tm->tm_sec;
1208}
1209
51ffa239 1210static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
36697dc0
LP
1211 struct tm t;
1212 int k;
1213
489464d0 1214 if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
36697dc0
LP
1215 return true;
1216
1217 t = *tm;
c477ff14 1218 if (mktime_or_timegm(&t, utc) < 0)
36697dc0
LP
1219 return false;
1220
1221 k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
1222 return (weekdays_bits & (1 << k));
1223}
1224
436dd70f 1225static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
36697dc0 1226 struct tm c;
436dd70f 1227 int tm_usec;
36697dc0
LP
1228 int r;
1229
1230 assert(spec);
1231 assert(tm);
1232
1233 c = *tm;
436dd70f 1234 tm_usec = *usec;
36697dc0
LP
1235
1236 for (;;) {
1237 /* Normalize the current date */
ea3894c1 1238 (void) mktime_or_timegm(&c, spec->utc);
21b3a0fc 1239 c.tm_isdst = spec->dst;
36697dc0
LP
1240
1241 c.tm_year += 1900;
8ea80351 1242 r = find_matching_component(spec, spec->year, &c, &c.tm_year);
36697dc0
LP
1243 c.tm_year -= 1900;
1244
1245 if (r > 0) {
1246 c.tm_mon = 0;
1247 c.tm_mday = 1;
436dd70f 1248 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0 1249 }
e308ddca 1250 if (r < 0)
36697dc0 1251 return r;
e308ddca
LP
1252 if (tm_out_of_bounds(&c, spec->utc))
1253 return -ENOENT;
36697dc0
LP
1254
1255 c.tm_mon += 1;
8ea80351 1256 r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
36697dc0
LP
1257 c.tm_mon -= 1;
1258
1259 if (r > 0) {
1260 c.tm_mday = 1;
436dd70f 1261 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0 1262 }
51ffa239 1263 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
313cefa1 1264 c.tm_year++;
36697dc0
LP
1265 c.tm_mon = 0;
1266 c.tm_mday = 1;
436dd70f 1267 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1268 continue;
1269 }
1270
8ea80351 1271 r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
36697dc0 1272 if (r > 0)
436dd70f 1273 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
51ffa239 1274 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
313cefa1 1275 c.tm_mon++;
36697dc0 1276 c.tm_mday = 1;
436dd70f 1277 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1278 continue;
1279 }
1280
51ffa239 1281 if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
36697dc0 1282 c.tm_mday++;
436dd70f 1283 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1284 continue;
1285 }
1286
8ea80351 1287 r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
36697dc0 1288 if (r > 0)
a022d76e 1289 c.tm_min = c.tm_sec = tm_usec = 0;
51ffa239 1290 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
313cefa1 1291 c.tm_mday++;
436dd70f 1292 c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1293 continue;
1294 }
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;
51ffa239 1299 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
313cefa1 1300 c.tm_hour++;
436dd70f 1301 c.tm_min = c.tm_sec = tm_usec = 0;
36697dc0
LP
1302 continue;
1303 }
1304
436dd70f 1305 c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
8ea80351 1306 r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
436dd70f
HV
1307 tm_usec = c.tm_sec % USEC_PER_SEC;
1308 c.tm_sec /= USEC_PER_SEC;
1309
51ffa239 1310 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
313cefa1 1311 c.tm_min++;
436dd70f 1312 c.tm_sec = tm_usec = 0;
36697dc0
LP
1313 continue;
1314 }
1315
36697dc0 1316 *tm = c;
436dd70f 1317 *usec = tm_usec;
36697dc0
LP
1318 return 0;
1319 }
1320}
1321
48d26c01 1322static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
36697dc0
LP
1323 struct tm tm;
1324 time_t t;
1325 int r;
436dd70f 1326 usec_t tm_usec;
36697dc0
LP
1327
1328 assert(spec);
1329 assert(next);
1330
1bb4b028
LP
1331 if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
1332 return -EINVAL;
1333
436dd70f
HV
1334 usec++;
1335 t = (time_t) (usec / USEC_PER_SEC);
51ffa239 1336 assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
436dd70f 1337 tm_usec = usec % USEC_PER_SEC;
36697dc0 1338
436dd70f 1339 r = find_next(spec, &tm, &tm_usec);
36697dc0
LP
1340 if (r < 0)
1341 return r;
1342
51ffa239 1343 t = mktime_or_timegm(&tm, spec->utc);
c477ff14 1344 if (t < 0)
36697dc0
LP
1345 return -EINVAL;
1346
436dd70f 1347 *next = (usec_t) t * USEC_PER_SEC + tm_usec;
36697dc0
LP
1348 return 0;
1349}
48d26c01
IK
1350
1351typedef struct SpecNextResult {
1352 usec_t next;
1353 int return_value;
1354} SpecNextResult;
1355
1356int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
4c253ed1 1357 SpecNextResult *shared, tmp;
48d26c01
IK
1358 int r;
1359
1360 if (isempty(spec->timezone))
1361 return calendar_spec_next_usec_impl(spec, usec, next);
1362
1363 shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1364 if (shared == MAP_FAILED)
1365 return negative_errno();
1366
1f5d1e02 1367 r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
4c253ed1 1368 if (r < 0) {
48d26c01 1369 (void) munmap(shared, sizeof *shared);
4c253ed1 1370 return r;
48d26c01 1371 }
4c253ed1 1372 if (r == 0) {
48d26c01
IK
1373 if (setenv("TZ", spec->timezone, 1) != 0) {
1374 shared->return_value = negative_errno();
1375 _exit(EXIT_FAILURE);
1376 }
1377
1378 tzset();
1379
1380 shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
1381
1382 _exit(EXIT_SUCCESS);
1383 }
1384
48d26c01 1385 tmp = *shared;
1f5d1e02 1386 if (munmap(shared, sizeof *shared) < 0)
48d26c01
IK
1387 return negative_errno();
1388
1389 if (tmp.return_value == 0)
1390 *next = tmp.next;
1391
1392 return tmp.return_value;
1393}