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