chiark / gitweb /
conf-parser: warn when we open configuration files with weird access bits
[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, "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 }