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