chiark / gitweb /
Add __attribute__((const, pure, format)) in various places
[elogind.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, "weekly")) {
690
691                 c->weekdays_bits = 1;
692
693                 r = const_chain(0, &c->hour);
694                 if (r < 0)
695                         goto fail;
696                 r = const_chain(0, &c->minute);
697                 if (r < 0)
698                         goto fail;
699                 r = const_chain(0, &c->second);
700                 if (r < 0)
701                         goto fail;
702
703         } else {
704                 r = parse_weekdays(&p, c);
705                 if (r < 0)
706                         goto fail;
707
708                 r = parse_date(&p, c);
709                 if (r < 0)
710                         goto fail;
711
712                 r = parse_time(&p, c);
713                 if (r < 0)
714                         goto fail;
715
716                 if (*p != 0) {
717                         r = -EINVAL;
718                         goto fail;
719                 }
720         }
721
722         r = calendar_spec_normalize(c);
723         if (r < 0)
724                 goto fail;
725
726         if (!calendar_spec_valid(c)) {
727                 r = -EINVAL;
728                 goto fail;
729         }
730
731         *spec = c;
732         return 0;
733
734 fail:
735         calendar_spec_free(c);
736         return r;
737 }
738
739 static int find_matching_component(const CalendarComponent *c, int *val) {
740         const CalendarComponent *n;
741         int d = -1;
742         bool d_set = false;
743         int r;
744
745         assert(val);
746
747         if (!c)
748                 return 0;
749
750         while (c) {
751                 n = c->next;
752
753                 if (c->value >= *val) {
754
755                         if (!d_set || c->value < d) {
756                                 d = c->value;
757                                 d_set = true;
758                         }
759
760                 } else if (c->repeat > 0) {
761                         int k;
762
763                         k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
764
765                         if (!d_set || k < d) {
766                                 d = k;
767                                 d_set = true;
768                         }
769                 }
770
771                 c = n;
772         }
773
774         if (!d_set)
775                 return -ENOENT;
776
777         r = *val != d;
778         *val = d;
779         return r;
780 }
781
782 static bool tm_out_of_bounds(const struct tm *tm) {
783         struct tm t;
784         assert(tm);
785
786         t = *tm;
787
788         if (mktime(&t) == (time_t) -1)
789                 return true;
790
791         /* Did any normalization take place? If so, it was out of bounds before */
792         return
793                 t.tm_year != tm->tm_year ||
794                 t.tm_mon != tm->tm_mon ||
795                 t.tm_mday != tm->tm_mday ||
796                 t.tm_hour != tm->tm_hour ||
797                 t.tm_min != tm->tm_min ||
798                 t.tm_sec != tm->tm_sec;
799 }
800
801 static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
802         struct tm t;
803         int k;
804
805         if (weekdays_bits < 0 || weekdays_bits >= 127)
806                 return true;
807
808         t = *tm;
809         if (mktime(&t) == (time_t) -1)
810                 return false;
811
812         k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
813         return (weekdays_bits & (1 << k));
814 }
815
816 static int find_next(const CalendarSpec *spec, struct tm *tm) {
817         struct tm c;
818         int r;
819
820         assert(spec);
821         assert(tm);
822
823         c = *tm;
824
825         for (;;) {
826                 /* Normalize the current date */
827                 mktime(&c);
828                 c.tm_isdst = -1;
829
830                 c.tm_year += 1900;
831                 r = find_matching_component(spec->year, &c.tm_year);
832                 c.tm_year -= 1900;
833
834                 if (r > 0) {
835                         c.tm_mon = 0;
836                         c.tm_mday = 1;
837                         c.tm_hour = c.tm_min = c.tm_sec = 0;
838                 }
839                 if (r < 0 || tm_out_of_bounds(&c))
840                         return r;
841
842                 c.tm_mon += 1;
843                 r = find_matching_component(spec->month, &c.tm_mon);
844                 c.tm_mon -= 1;
845
846                 if (r > 0) {
847                         c.tm_mday = 1;
848                         c.tm_hour = c.tm_min = c.tm_sec = 0;
849                 }
850                 if (r < 0 || tm_out_of_bounds(&c)) {
851                         c.tm_year ++;
852                         c.tm_mon = 0;
853                         c.tm_mday = 1;
854                         c.tm_hour = c.tm_min = c.tm_sec = 0;
855                         continue;
856                 }
857
858                 r = find_matching_component(spec->day, &c.tm_mday);
859                 if (r > 0)
860                         c.tm_hour = c.tm_min = c.tm_sec = 0;
861                 if (r < 0 || tm_out_of_bounds(&c)) {
862                         c.tm_mon ++;
863                         c.tm_mday = 1;
864                         c.tm_hour = c.tm_min = c.tm_sec = 0;
865                         continue;
866                 }
867
868                 if (!matches_weekday(spec->weekdays_bits, &c)) {
869                         c.tm_mday++;
870                         c.tm_hour = c.tm_min = c.tm_sec = 0;
871                         continue;
872                 }
873
874                 r = find_matching_component(spec->hour, &c.tm_hour);
875                 if (r > 0)
876                         c.tm_min = c.tm_sec = 0;
877                 if (r < 0 || tm_out_of_bounds(&c)) {
878                         c.tm_mday ++;
879                         c.tm_hour = c.tm_min = c.tm_sec = 0;
880                         continue;
881                 }
882
883                 r = find_matching_component(spec->minute, &c.tm_min);
884                 if (r > 0)
885                         c.tm_sec = 0;
886                 if (r < 0 || tm_out_of_bounds(&c)) {
887                         c.tm_hour ++;
888                         c.tm_min = c.tm_sec = 0;
889                         continue;
890                 }
891
892                 r = find_matching_component(spec->second, &c.tm_sec);
893                 if (r < 0 || tm_out_of_bounds(&c)) {
894                         c.tm_min ++;
895                         c.tm_sec = 0;
896                         continue;
897                 }
898
899
900                 *tm = c;
901                 return 0;
902         }
903 }
904
905 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
906         struct tm tm;
907         time_t t;
908         int r;
909
910         assert(spec);
911         assert(next);
912
913         t = (time_t) (usec / USEC_PER_SEC) + 1;
914         assert_se(localtime_r(&t, &tm));
915
916         r = find_next(spec, &tm);
917         if (r < 0)
918                 return r;
919
920         t = mktime(&tm);
921         if (t == (time_t) -1)
922                 return -EINVAL;
923
924
925         *next = (usec_t) t * USEC_PER_SEC;
926         return 0;
927 }