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