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