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