Line data Source code
1 : /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2 :
3 : /***
4 : This file is part of systemd.
5 :
6 : Copyright 2012 Lennart Poettering
7 :
8 : systemd is free software; you can redistribute it and/or modify it
9 : under the terms of the GNU Lesser General Public License as published by
10 : the Free Software Foundation; either version 2.1 of the License, or
11 : (at your option) any later version.
12 :
13 : systemd is distributed in the hope that it will be useful, but
14 : WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : Lesser General Public License for more details.
17 :
18 : You should have received a copy of the GNU Lesser General Public License
19 : along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 : ***/
21 :
22 : #include <stdlib.h>
23 : #include <string.h>
24 :
25 : #include "calendarspec.h"
26 :
27 : #define BITS_WEEKDAYS 127
28 :
29 378 : static void free_chain(CalendarComponent *c) {
30 : CalendarComponent *n;
31 :
32 1020 : while (c) {
33 264 : n = c->next;
34 264 : free(c);
35 264 : c = n;
36 : }
37 378 : }
38 :
39 61 : void calendar_spec_free(CalendarSpec *c) {
40 :
41 61 : if (!c)
42 0 : return;
43 :
44 61 : free_chain(c->year);
45 61 : free_chain(c->month);
46 61 : free_chain(c->day);
47 61 : free_chain(c->hour);
48 61 : free_chain(c->minute);
49 61 : free_chain(c->second);
50 :
51 61 : free(c);
52 : }
53 :
54 34 : static int component_compare(const void *_a, const void *_b) {
55 34 : CalendarComponent * const *a = _a, * const *b = _b;
56 :
57 34 : if ((*a)->value < (*b)->value)
58 6 : return -1;
59 28 : if ((*a)->value > (*b)->value)
60 27 : return 1;
61 :
62 1 : if ((*a)->repeat < (*b)->repeat)
63 0 : return -1;
64 1 : if ((*a)->repeat > (*b)->repeat)
65 0 : return 1;
66 :
67 1 : return 0;
68 : }
69 :
70 354 : static void sort_chain(CalendarComponent **c) {
71 354 : unsigned n = 0, k;
72 : CalendarComponent **b, *i, **j, *next;
73 :
74 354 : assert(c);
75 :
76 608 : for (i = *c; i; i = i->next)
77 254 : n++;
78 :
79 354 : if (n <= 1)
80 340 : return;
81 :
82 14 : j = b = alloca(sizeof(CalendarComponent*) * n);
83 53 : for (i = *c; i; i = i->next)
84 39 : *(j++) = i;
85 :
86 14 : qsort(b, n, sizeof(CalendarComponent*), component_compare);
87 :
88 14 : b[n-1]->next = NULL;
89 14 : next = b[n-1];
90 :
91 : /* Drop non-unique entries */
92 39 : for (k = n-1; k > 0; k--) {
93 26 : if (b[k-1]->value == next->value &&
94 1 : b[k-1]->repeat == next->repeat) {
95 1 : free(b[k-1]);
96 1 : continue;
97 : }
98 :
99 24 : b[k-1]->next = next;
100 24 : next = b[k-1];
101 : }
102 :
103 14 : *c = next;
104 : }
105 :
106 59 : static void fix_year(CalendarComponent *c) {
107 : /* Turns 12 → 2012, 89 → 1989 */
108 :
109 126 : while(c) {
110 8 : CalendarComponent *n = c->next;
111 :
112 8 : if (c->value >= 0 && c->value < 70)
113 2 : c->value += 2000;
114 :
115 8 : if (c->value >= 70 && c->value < 100)
116 0 : c->value += 1900;
117 :
118 8 : c = n;
119 : }
120 59 : }
121 :
122 59 : int calendar_spec_normalize(CalendarSpec *c) {
123 59 : assert(c);
124 :
125 59 : if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
126 35 : c->weekdays_bits = -1;
127 :
128 59 : fix_year(c->year);
129 :
130 59 : sort_chain(&c->year);
131 59 : sort_chain(&c->month);
132 59 : sort_chain(&c->day);
133 59 : sort_chain(&c->hour);
134 59 : sort_chain(&c->minute);
135 59 : sort_chain(&c->second);
136 :
137 59 : return 0;
138 : }
139 :
140 376 : _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
141 376 : if (!c)
142 125 : return true;
143 :
144 251 : if (c->value < from || c->value > to)
145 1 : return false;
146 :
147 250 : if (c->value + c->repeat > to)
148 0 : return false;
149 :
150 250 : if (c->next)
151 24 : return chain_valid(c->next, from, to);
152 :
153 226 : return true;
154 : }
155 :
156 59 : _pure_ bool calendar_spec_valid(CalendarSpec *c) {
157 59 : assert(c);
158 :
159 59 : if (c->weekdays_bits > BITS_WEEKDAYS)
160 0 : return false;
161 :
162 59 : if (!chain_valid(c->year, 1970, 2199))
163 0 : return false;
164 :
165 59 : if (!chain_valid(c->month, 1, 12))
166 0 : return false;
167 :
168 59 : if (!chain_valid(c->day, 1, 31))
169 0 : return false;
170 :
171 59 : if (!chain_valid(c->hour, 0, 23))
172 1 : return false;
173 :
174 58 : if (!chain_valid(c->minute, 0, 59))
175 0 : return false;
176 :
177 58 : if (!chain_valid(c->second, 0, 59))
178 0 : return false;
179 :
180 58 : return true;
181 : }
182 :
183 24 : static void format_weekdays(FILE *f, const CalendarSpec *c) {
184 : static const char *const days[] = {
185 : "Mon",
186 : "Tue",
187 : "Wed",
188 : "Thu",
189 : "Fri",
190 : "Sat",
191 : "Sun"
192 : };
193 :
194 : int l, x;
195 24 : bool need_colon = false;
196 :
197 24 : assert(f);
198 24 : assert(c);
199 24 : assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
200 :
201 192 : for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
202 :
203 168 : if (c->weekdays_bits & (1 << x)) {
204 :
205 52 : if (l < 0) {
206 32 : if (need_colon)
207 8 : fputc(',', f);
208 : else
209 24 : need_colon = true;
210 :
211 32 : fputs(days[x], f);
212 32 : l = x;
213 : }
214 :
215 116 : } else if (l >= 0) {
216 :
217 24 : if (x > l + 1) {
218 4 : fputc(x > l + 2 ? '-' : ',', f);
219 4 : fputs(days[x-1], f);
220 : }
221 :
222 24 : l = -1;
223 : }
224 : }
225 :
226 24 : if (l >= 0 && x > l + 1) {
227 6 : fputc(x > l + 2 ? '-' : ',', f);
228 6 : fputs(days[x-1], f);
229 : }
230 24 : }
231 :
232 372 : static void format_chain(FILE *f, int space, const CalendarComponent *c) {
233 372 : assert(f);
234 :
235 372 : if (!c) {
236 122 : fputc('*', f);
237 122 : return;
238 : }
239 :
240 250 : assert(c->value >= 0);
241 250 : fprintf(f, "%0*i", space, c->value);
242 :
243 250 : if (c->repeat > 0)
244 4 : fprintf(f, "/%i", c->repeat);
245 :
246 250 : if (c->next) {
247 24 : fputc(',', f);
248 24 : format_chain(f, space, c->next);
249 : }
250 : }
251 :
252 58 : int calendar_spec_to_string(const CalendarSpec *c, char **p) {
253 58 : char *buf = NULL;
254 58 : size_t sz = 0;
255 : FILE *f;
256 :
257 58 : assert(c);
258 58 : assert(p);
259 :
260 58 : f = open_memstream(&buf, &sz);
261 58 : if (!f)
262 0 : return -ENOMEM;
263 :
264 58 : if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
265 24 : format_weekdays(f, c);
266 24 : fputc(' ', f);
267 : }
268 :
269 58 : format_chain(f, 4, c->year);
270 58 : fputc('-', f);
271 58 : format_chain(f, 2, c->month);
272 58 : fputc('-', f);
273 58 : format_chain(f, 2, c->day);
274 58 : fputc(' ', f);
275 58 : format_chain(f, 2, c->hour);
276 58 : fputc(':', f);
277 58 : format_chain(f, 2, c->minute);
278 58 : fputc(':', f);
279 58 : format_chain(f, 2, c->second);
280 :
281 58 : fflush(f);
282 :
283 58 : if (ferror(f)) {
284 0 : free(buf);
285 0 : fclose(f);
286 0 : return -ENOMEM;
287 : }
288 :
289 58 : fclose(f);
290 :
291 58 : *p = buf;
292 58 : return 0;
293 : }
294 :
295 53 : static int parse_weekdays(const char **p, CalendarSpec *c) {
296 : static const struct {
297 : const char *name;
298 : const int nr;
299 : } day_nr[] = {
300 : { "Monday", 0 },
301 : { "Mon", 0 },
302 : { "Tuesday", 1 },
303 : { "Tue", 1 },
304 : { "Wednesday", 2 },
305 : { "Wed", 2 },
306 : { "Thursday", 3 },
307 : { "Thu", 3 },
308 : { "Friday", 4 },
309 : { "Fri", 4 },
310 : { "Saturday", 5 },
311 : { "Sat", 5 },
312 : { "Sunday", 6 },
313 : { "Sun", 6 }
314 : };
315 :
316 53 : int l = -1;
317 53 : bool first = true;
318 :
319 53 : assert(p);
320 53 : assert(*p);
321 53 : assert(c);
322 :
323 : for (;;) {
324 : unsigned i;
325 :
326 77 : if (!first && **p == ' ')
327 1 : return 0;
328 :
329 1630 : for (i = 0; i < ELEMENTSOF(day_nr); i++) {
330 : size_t skip;
331 :
332 785 : if (!startswith_no_case(*p, day_nr[i].name))
333 739 : continue;
334 :
335 46 : skip = strlen(day_nr[i].name);
336 :
337 86 : if ((*p)[skip] != '-' &&
338 62 : (*p)[skip] != ',' &&
339 23 : (*p)[skip] != ' ' &&
340 1 : (*p)[skip] != 0)
341 0 : return -EINVAL;
342 :
343 46 : c->weekdays_bits |= 1 << day_nr[i].nr;
344 :
345 46 : if (l >= 0) {
346 : int j;
347 :
348 6 : if (l > day_nr[i].nr)
349 0 : return -EINVAL;
350 :
351 14 : for (j = l + 1; j < day_nr[i].nr; j++)
352 8 : c->weekdays_bits |= 1 << j;
353 : }
354 :
355 46 : *p += skip;
356 46 : break;
357 : }
358 :
359 : /* Couldn't find this prefix, so let's assume the
360 : weekday was not specified and let's continue with
361 : the date */
362 76 : if (i >= ELEMENTSOF(day_nr))
363 30 : return first ? 0 : -EINVAL;
364 :
365 : /* We reached the end of the string */
366 46 : if (**p == 0)
367 1 : return 0;
368 :
369 : /* We reached the end of the weekday spec part */
370 45 : if (**p == ' ') {
371 21 : *p += strspn(*p, " ");
372 21 : return 0;
373 : }
374 :
375 24 : if (**p == '-') {
376 6 : if (l >= 0)
377 0 : return -EINVAL;
378 :
379 6 : l = day_nr[i].nr;
380 : } else
381 18 : l = -1;
382 :
383 24 : *p += 1;
384 24 : first = false;
385 24 : }
386 : }
387 :
388 209 : static int prepend_component(const char **p, CalendarComponent **c) {
389 209 : unsigned long value, repeat = 0;
390 209 : char *e = NULL, *ee = NULL;
391 : CalendarComponent *cc;
392 :
393 209 : assert(p);
394 209 : assert(c);
395 :
396 209 : errno = 0;
397 209 : value = strtoul(*p, &e, 10);
398 209 : if (errno > 0)
399 0 : return -errno;
400 209 : if (e == *p)
401 1 : return -EINVAL;
402 208 : if ((unsigned long) (int) value != value)
403 0 : return -ERANGE;
404 :
405 208 : if (*e == '/') {
406 4 : repeat = strtoul(e+1, &ee, 10);
407 4 : if (errno > 0)
408 0 : return -errno;
409 4 : if (ee == e+1)
410 0 : return -EINVAL;
411 4 : if ((unsigned long) (int) repeat != repeat)
412 0 : return -ERANGE;
413 4 : if (repeat <= 0)
414 0 : return -ERANGE;
415 :
416 4 : e = ee;
417 : }
418 :
419 208 : if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
420 0 : return -EINVAL;
421 :
422 208 : cc = new0(CalendarComponent, 1);
423 208 : if (!cc)
424 0 : return -ENOMEM;
425 :
426 208 : cc->value = value;
427 208 : cc->repeat = repeat;
428 208 : cc->next = *c;
429 :
430 208 : *p = e;
431 208 : *c = cc;
432 :
433 208 : if (*e ==',') {
434 24 : *p += 1;
435 24 : return prepend_component(p, c);
436 : }
437 :
438 184 : return 0;
439 : }
440 :
441 261 : static int parse_chain(const char **p, CalendarComponent **c) {
442 : const char *t;
443 261 : CalendarComponent *cc = NULL;
444 : int r;
445 :
446 261 : assert(p);
447 261 : assert(c);
448 :
449 261 : t = *p;
450 :
451 261 : if (t[0] == '*') {
452 76 : *p = t + 1;
453 76 : *c = NULL;
454 76 : return 0;
455 : }
456 :
457 185 : r = prepend_component(&t, &cc);
458 185 : if (r < 0) {
459 1 : free_chain(cc);
460 1 : return r;
461 : }
462 :
463 184 : *p = t;
464 184 : *c = cc;
465 184 : return 0;
466 : }
467 :
468 57 : static int const_chain(int value, CalendarComponent **c) {
469 57 : CalendarComponent *cc = NULL;
470 :
471 57 : assert(c);
472 :
473 57 : cc = new0(CalendarComponent, 1);
474 57 : if (!cc)
475 0 : return -ENOMEM;
476 :
477 57 : cc->value = value;
478 57 : cc->repeat = 0;
479 57 : cc->next = *c;
480 :
481 57 : *c = cc;
482 :
483 57 : return 0;
484 : }
485 :
486 53 : static int parse_date(const char **p, CalendarSpec *c) {
487 : const char *t;
488 : int r;
489 : CalendarComponent *first, *second, *third;
490 :
491 53 : assert(p);
492 53 : assert(*p);
493 53 : assert(c);
494 :
495 53 : t = *p;
496 :
497 53 : if (*t == 0)
498 1 : return 0;
499 :
500 52 : r = parse_chain(&t, &first);
501 52 : if (r < 0)
502 1 : return r;
503 :
504 : /* Already the end? A ':' as separator? In that case this was a time, not a date */
505 51 : if (*t == 0 || *t == ':') {
506 8 : free_chain(first);
507 8 : return 0;
508 : }
509 :
510 43 : if (*t != '-') {
511 0 : free_chain(first);
512 0 : return -EINVAL;
513 : }
514 :
515 43 : t++;
516 43 : r = parse_chain(&t, &second);
517 43 : if (r < 0) {
518 0 : free_chain(first);
519 0 : return r;
520 : }
521 :
522 : /* Got two parts, hence it's month and day */
523 43 : if (*t == ' ' || *t == 0) {
524 6 : *p = t + strspn(t, " ");
525 6 : c->month = first;
526 6 : c->day = second;
527 6 : return 0;
528 : }
529 :
530 37 : if (*t != '-') {
531 0 : free_chain(first);
532 0 : free_chain(second);
533 0 : return -EINVAL;
534 : }
535 :
536 37 : t++;
537 37 : r = parse_chain(&t, &third);
538 37 : if (r < 0) {
539 0 : free_chain(first);
540 0 : free_chain(second);
541 0 : return r;
542 : }
543 :
544 : /* Got tree parts, hence it is year, month and day */
545 37 : if (*t == ' ' || *t == 0) {
546 37 : *p = t + strspn(t, " ");
547 37 : c->year = first;
548 37 : c->month = second;
549 37 : c->day = third;
550 37 : return 0;
551 : }
552 :
553 0 : free_chain(first);
554 0 : free_chain(second);
555 0 : free_chain(third);
556 0 : return -EINVAL;
557 : }
558 :
559 52 : static int parse_time(const char **p, CalendarSpec *c) {
560 52 : CalendarComponent *h = NULL, *m = NULL, *s = NULL;
561 : const char *t;
562 : int r;
563 :
564 52 : assert(p);
565 52 : assert(*p);
566 52 : assert(c);
567 :
568 52 : t = *p;
569 :
570 52 : if (*t == 0) {
571 : /* If no time is specified at all, but a date of some
572 : * kind, then this means 00:00:00 */
573 6 : if (c->day || c->weekdays_bits > 0)
574 : goto null_hour;
575 :
576 0 : goto finish;
577 : }
578 :
579 46 : r = parse_chain(&t, &h);
580 46 : if (r < 0)
581 0 : goto fail;
582 :
583 46 : if (*t != ':') {
584 1 : r = -EINVAL;
585 1 : goto fail;
586 : }
587 :
588 45 : t++;
589 45 : r = parse_chain(&t, &m);
590 45 : if (r < 0)
591 0 : goto fail;
592 :
593 : /* Already at the end? Then it's hours and minutes, and seconds are 0 */
594 45 : if (*t == 0) {
595 7 : if (m != NULL)
596 7 : goto null_second;
597 :
598 0 : goto finish;
599 : }
600 :
601 38 : if (*t != ':') {
602 0 : r = -EINVAL;
603 0 : goto fail;
604 : }
605 :
606 38 : t++;
607 38 : r = parse_chain(&t, &s);
608 38 : if (r < 0)
609 0 : goto fail;
610 :
611 : /* At the end? Then it's hours, minutes and seconds */
612 38 : if (*t == 0)
613 38 : goto finish;
614 :
615 0 : r = -EINVAL;
616 0 : goto fail;
617 :
618 : null_hour:
619 6 : r = const_chain(0, &h);
620 6 : if (r < 0)
621 0 : goto fail;
622 :
623 6 : r = const_chain(0, &m);
624 6 : if (r < 0)
625 0 : goto fail;
626 :
627 : null_second:
628 13 : r = const_chain(0, &s);
629 13 : if (r < 0)
630 0 : goto fail;
631 :
632 : finish:
633 51 : *p = t;
634 51 : c->hour = h;
635 51 : c->minute = m;
636 51 : c->second = s;
637 51 : return 0;
638 :
639 : fail:
640 1 : free_chain(h);
641 1 : free_chain(m);
642 1 : free_chain(s);
643 1 : return r;
644 : }
645 :
646 62 : int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
647 : CalendarSpec *c;
648 : int r;
649 :
650 62 : assert(p);
651 62 : assert(spec);
652 :
653 62 : if (isempty(p))
654 1 : return -EINVAL;
655 :
656 61 : c = new0(CalendarSpec, 1);
657 61 : if (!c)
658 0 : return -ENOMEM;
659 :
660 61 : if (strcaseeq(p, "minutely")) {
661 1 : r = const_chain(0, &c->second);
662 1 : if (r < 0)
663 0 : goto fail;
664 :
665 60 : } else if (strcaseeq(p, "hourly")) {
666 1 : r = const_chain(0, &c->minute);
667 1 : if (r < 0)
668 0 : goto fail;
669 1 : r = const_chain(0, &c->second);
670 1 : if (r < 0)
671 0 : goto fail;
672 :
673 59 : } else if (strcaseeq(p, "daily")) {
674 1 : r = const_chain(0, &c->hour);
675 1 : if (r < 0)
676 0 : goto fail;
677 1 : r = const_chain(0, &c->minute);
678 1 : if (r < 0)
679 0 : goto fail;
680 1 : r = const_chain(0, &c->second);
681 1 : if (r < 0)
682 0 : goto fail;
683 :
684 58 : } else if (strcaseeq(p, "monthly")) {
685 1 : r = const_chain(1, &c->day);
686 1 : if (r < 0)
687 0 : goto fail;
688 1 : r = const_chain(0, &c->hour);
689 1 : if (r < 0)
690 0 : goto fail;
691 1 : r = const_chain(0, &c->minute);
692 1 : if (r < 0)
693 0 : goto fail;
694 1 : r = const_chain(0, &c->second);
695 1 : if (r < 0)
696 0 : goto fail;
697 :
698 113 : } else if (strcaseeq(p, "annually") ||
699 112 : strcaseeq(p, "yearly") ||
700 56 : strcaseeq(p, "anually") /* backwards compatibility */ ) {
701 :
702 1 : r = const_chain(1, &c->month);
703 1 : if (r < 0)
704 0 : goto fail;
705 1 : r = const_chain(1, &c->day);
706 1 : if (r < 0)
707 0 : goto fail;
708 1 : r = const_chain(0, &c->hour);
709 1 : if (r < 0)
710 0 : goto fail;
711 1 : r = const_chain(0, &c->minute);
712 1 : if (r < 0)
713 0 : goto fail;
714 1 : r = const_chain(0, &c->second);
715 2 : if (r < 0)
716 0 : goto fail;
717 :
718 56 : } else if (strcaseeq(p, "weekly")) {
719 :
720 1 : c->weekdays_bits = 1;
721 :
722 1 : r = const_chain(0, &c->hour);
723 1 : if (r < 0)
724 0 : goto fail;
725 1 : r = const_chain(0, &c->minute);
726 1 : if (r < 0)
727 0 : goto fail;
728 1 : r = const_chain(0, &c->second);
729 1 : if (r < 0)
730 0 : goto fail;
731 :
732 55 : } else if (strcaseeq(p, "quarterly")) {
733 :
734 1 : r = const_chain(1, &c->month);
735 1 : if (r < 0)
736 0 : goto fail;
737 1 : r = const_chain(4, &c->month);
738 1 : if (r < 0)
739 0 : goto fail;
740 1 : r = const_chain(7, &c->month);
741 1 : if (r < 0)
742 0 : goto fail;
743 1 : r = const_chain(10, &c->month);
744 1 : if (r < 0)
745 0 : goto fail;
746 1 : r = const_chain(1, &c->day);
747 1 : if (r < 0)
748 0 : goto fail;
749 1 : r = const_chain(0, &c->hour);
750 1 : if (r < 0)
751 0 : goto fail;
752 1 : r = const_chain(0, &c->minute);
753 1 : if (r < 0)
754 0 : goto fail;
755 1 : r = const_chain(0, &c->second);
756 1 : if (r < 0)
757 0 : goto fail;
758 :
759 108 : } else if (strcaseeq(p, "biannually") ||
760 108 : strcaseeq(p, "bi-annually") ||
761 108 : strcaseeq(p, "semiannually") ||
762 54 : strcaseeq(p, "semi-annually")) {
763 :
764 1 : r = const_chain(1, &c->month);
765 1 : if (r < 0)
766 0 : goto fail;
767 1 : r = const_chain(7, &c->month);
768 1 : if (r < 0)
769 0 : goto fail;
770 1 : r = const_chain(1, &c->day);
771 1 : if (r < 0)
772 0 : goto fail;
773 1 : r = const_chain(0, &c->hour);
774 1 : if (r < 0)
775 0 : goto fail;
776 1 : r = const_chain(0, &c->minute);
777 1 : if (r < 0)
778 0 : goto fail;
779 1 : r = const_chain(0, &c->second);
780 2 : if (r < 0)
781 0 : goto fail;
782 :
783 : } else {
784 53 : r = parse_weekdays(&p, c);
785 53 : if (r < 0)
786 0 : goto fail;
787 :
788 53 : r = parse_date(&p, c);
789 53 : if (r < 0)
790 1 : goto fail;
791 :
792 52 : r = parse_time(&p, c);
793 52 : if (r < 0)
794 1 : goto fail;
795 :
796 51 : if (*p != 0) {
797 0 : r = -EINVAL;
798 0 : goto fail;
799 : }
800 : }
801 :
802 59 : r = calendar_spec_normalize(c);
803 59 : if (r < 0)
804 0 : goto fail;
805 :
806 59 : if (!calendar_spec_valid(c)) {
807 1 : r = -EINVAL;
808 1 : goto fail;
809 : }
810 :
811 58 : *spec = c;
812 58 : return 0;
813 :
814 : fail:
815 3 : calendar_spec_free(c);
816 3 : return r;
817 : }
818 :
819 427 : static int find_matching_component(const CalendarComponent *c, int *val) {
820 : const CalendarComponent *n;
821 427 : int d = -1;
822 427 : bool d_set = false;
823 : int r;
824 :
825 427 : assert(val);
826 :
827 427 : if (!c)
828 248 : return 0;
829 :
830 568 : while (c) {
831 210 : n = c->next;
832 :
833 210 : if (c->value >= *val) {
834 :
835 136 : if (!d_set || c->value < d) {
836 124 : d = c->value;
837 124 : d_set = true;
838 : }
839 :
840 74 : } else if (c->repeat > 0) {
841 : int k;
842 :
843 9 : k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
844 :
845 9 : if (!d_set || k < d) {
846 9 : d = k;
847 9 : d_set = true;
848 : }
849 : }
850 :
851 210 : c = n;
852 : }
853 :
854 179 : if (!d_set)
855 46 : return -ENOENT;
856 :
857 133 : r = *val != d;
858 133 : *val = d;
859 133 : return r;
860 : }
861 :
862 381 : static bool tm_out_of_bounds(const struct tm *tm) {
863 : struct tm t;
864 381 : assert(tm);
865 :
866 381 : t = *tm;
867 :
868 381 : if (mktime(&t) == (time_t) -1)
869 0 : return true;
870 :
871 : /* Did any normalization take place? If so, it was out of bounds before */
872 : return
873 1142 : t.tm_year != tm->tm_year ||
874 760 : t.tm_mon != tm->tm_mon ||
875 760 : t.tm_mday != tm->tm_mday ||
876 760 : t.tm_hour != tm->tm_hour ||
877 1141 : t.tm_min != tm->tm_min ||
878 380 : t.tm_sec != tm->tm_sec;
879 : }
880 :
881 79 : static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
882 : struct tm t;
883 : int k;
884 :
885 79 : if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
886 21 : return true;
887 :
888 58 : t = *tm;
889 58 : if (mktime(&t) == (time_t) -1)
890 0 : return false;
891 :
892 58 : k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
893 58 : return (weekdays_bits & (1 << k));
894 : }
895 :
896 29 : static int find_next(const CalendarSpec *spec, struct tm *tm) {
897 : struct tm c;
898 : int r;
899 :
900 29 : assert(spec);
901 29 : assert(tm);
902 :
903 29 : c = *tm;
904 :
905 : for (;;) {
906 : /* Normalize the current date */
907 118 : mktime(&c);
908 118 : c.tm_isdst = -1;
909 :
910 118 : c.tm_year += 1900;
911 118 : r = find_matching_component(spec->year, &c.tm_year);
912 118 : c.tm_year -= 1900;
913 :
914 118 : if (r > 0) {
915 0 : c.tm_mon = 0;
916 0 : c.tm_mday = 1;
917 0 : c.tm_hour = c.tm_min = c.tm_sec = 0;
918 : }
919 118 : if (r < 0 || tm_out_of_bounds(&c))
920 4 : return r;
921 :
922 114 : c.tm_mon += 1;
923 114 : r = find_matching_component(spec->month, &c.tm_mon);
924 114 : c.tm_mon -= 1;
925 :
926 114 : if (r > 0) {
927 9 : c.tm_mday = 1;
928 9 : c.tm_hour = c.tm_min = c.tm_sec = 0;
929 : }
930 114 : if (r < 0 || tm_out_of_bounds(&c)) {
931 5 : c.tm_year ++;
932 5 : c.tm_mon = 0;
933 5 : c.tm_mday = 1;
934 5 : c.tm_hour = c.tm_min = c.tm_sec = 0;
935 5 : continue;
936 : }
937 :
938 109 : r = find_matching_component(spec->day, &c.tm_mday);
939 109 : if (r > 0)
940 7 : c.tm_hour = c.tm_min = c.tm_sec = 0;
941 109 : if (r < 0 || tm_out_of_bounds(&c)) {
942 30 : c.tm_mon ++;
943 30 : c.tm_mday = 1;
944 30 : c.tm_hour = c.tm_min = c.tm_sec = 0;
945 30 : continue;
946 : }
947 :
948 79 : if (!matches_weekday(spec->weekdays_bits, &c)) {
949 46 : c.tm_mday++;
950 46 : c.tm_hour = c.tm_min = c.tm_sec = 0;
951 46 : continue;
952 : }
953 :
954 33 : r = find_matching_component(spec->hour, &c.tm_hour);
955 33 : if (r > 0)
956 8 : c.tm_min = c.tm_sec = 0;
957 33 : if (r < 0 || tm_out_of_bounds(&c)) {
958 6 : c.tm_mday ++;
959 6 : c.tm_hour = c.tm_min = c.tm_sec = 0;
960 6 : continue;
961 : }
962 :
963 27 : r = find_matching_component(spec->minute, &c.tm_min);
964 27 : if (r > 0)
965 10 : c.tm_sec = 0;
966 27 : if (r < 0 || tm_out_of_bounds(&c)) {
967 1 : c.tm_hour ++;
968 1 : c.tm_min = c.tm_sec = 0;
969 1 : continue;
970 : }
971 :
972 26 : r = find_matching_component(spec->second, &c.tm_sec);
973 26 : if (r < 0 || tm_out_of_bounds(&c)) {
974 1 : c.tm_min ++;
975 1 : c.tm_sec = 0;
976 1 : continue;
977 : }
978 :
979 :
980 25 : *tm = c;
981 25 : return 0;
982 89 : }
983 : }
984 :
985 29 : int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
986 : struct tm tm;
987 : time_t t;
988 : int r;
989 :
990 29 : assert(spec);
991 29 : assert(next);
992 :
993 29 : t = (time_t) (usec / USEC_PER_SEC) + 1;
994 29 : assert_se(localtime_r(&t, &tm));
995 :
996 29 : r = find_next(spec, &tm);
997 29 : if (r < 0)
998 4 : return r;
999 :
1000 25 : t = mktime(&tm);
1001 25 : if (t == (time_t) -1)
1002 0 : return -EINVAL;
1003 :
1004 25 : *next = (usec_t) t * USEC_PER_SEC;
1005 25 : return 0;
1006 : }
|