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