1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include <stdio_ext.h>
33 #include "alloc-util.h"
34 #include "calendarspec.h"
37 #include "parse-util.h"
38 #include "string-util.h"
39 #include "time-util.h"
41 #define BITS_WEEKDAYS 127
45 static void free_chain(CalendarComponent
*c
) {
55 CalendarSpec
* calendar_spec_free(CalendarSpec
*c
) {
64 free_chain(c
->minute
);
65 free_chain(c
->microsecond
);
71 static int component_compare(const void *_a
, const void *_b
) {
72 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
74 if ((*a
)->start
< (*b
)->start
)
76 if ((*a
)->start
> (*b
)->start
)
79 if ((*a
)->stop
< (*b
)->stop
)
81 if ((*a
)->stop
> (*b
)->stop
)
84 if ((*a
)->repeat
< (*b
)->repeat
)
86 if ((*a
)->repeat
> (*b
)->repeat
)
92 static void normalize_chain(CalendarComponent
**c
) {
94 CalendarComponent
**b
, *i
, **j
, *next
;
98 for (i
= *c
; i
; i
= i
->next
) {
102 * While we're counting the chain, also normalize `stop`
103 * so the length of the range is a multiple of `repeat`
105 if (i
->stop
> i
->start
&& i
->repeat
> 0)
106 i
->stop
-= (i
->stop
- i
->start
) % i
->repeat
;
113 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
114 for (i
= *c
; i
; i
= i
->next
)
117 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
122 /* Drop non-unique entries */
123 for (k
= n
-1; k
> 0; k
--) {
124 if (component_compare(&b
[k
-1], &next
) == 0) {
136 static void fix_year(CalendarComponent
*c
) {
137 /* Turns 12 → 2012, 89 → 1989 */
140 if (c
->start
>= 0 && c
->start
< 70)
143 if (c
->stop
>= 0 && c
->stop
< 70)
146 if (c
->start
>= 70 && c
->start
< 100)
149 if (c
->stop
>= 70 && c
->stop
< 100)
156 int calendar_spec_normalize(CalendarSpec
*c
) {
159 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
160 c
->weekdays_bits
= -1;
162 if (c
->end_of_month
&& !c
->day
)
163 c
->end_of_month
= false;
167 normalize_chain(&c
->year
);
168 normalize_chain(&c
->month
);
169 normalize_chain(&c
->day
);
170 normalize_chain(&c
->hour
);
171 normalize_chain(&c
->minute
);
172 normalize_chain(&c
->microsecond
);
177 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
, bool end_of_month
) {
181 /* Forbid dates more than 28 days from the end of the month */
185 if (c
->start
< from
|| c
->start
> to
)
189 * c->repeat must be short enough so at least one repetition may
190 * occur before the end of the interval. For dates scheduled
191 * relative to the end of the month, c->start and c->stop
192 * correspond to the Nth last day of the month.
195 if (c
->stop
< from
|| c
->stop
> to
)
198 if (c
->start
+ c
->repeat
> c
->stop
)
201 if (end_of_month
&& c
->start
- c
->repeat
< from
)
204 if (!end_of_month
&& c
->start
+ c
->repeat
> to
)
209 return chain_valid(c
->next
, from
, to
, end_of_month
);
214 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
217 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
220 if (!chain_valid(c
->year
, MIN_YEAR
, MAX_YEAR
, false))
223 if (!chain_valid(c
->month
, 1, 12, false))
226 if (!chain_valid(c
->day
, 1, 31, c
->end_of_month
))
229 if (!chain_valid(c
->hour
, 0, 23, false))
232 if (!chain_valid(c
->minute
, 0, 59, false))
235 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1, false))
241 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
242 static const char *const days
[] = {
253 bool need_comma
= false;
257 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
259 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
261 if (c
->weekdays_bits
& (1 << x
)) {
276 fputs(x
> l
+ 2 ? ".." : ",", f
);
284 if (l
>= 0 && x
> l
+ 1) {
285 fputs(x
> l
+ 2 ? ".." : ",", f
);
290 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
291 int d
= usec
? (int) USEC_PER_SEC
: 1;
300 if (usec
&& c
->start
== 0 && c
->repeat
== USEC_PER_SEC
&& !c
->next
) {
305 assert(c
->start
>= 0);
307 fprintf(f
, "%0*i", space
, c
->start
/ d
);
308 if (c
->start
% d
> 0)
309 fprintf(f
, ".%06i", c
->start
% d
);
312 fprintf(f
, "..%0*i", space
, c
->stop
/ d
);
314 fprintf(f
, ".%06i", c
->stop
% d
);
316 if (c
->repeat
> 0 && !(c
->stop
> 0 && c
->repeat
== d
))
317 fprintf(f
, "/%i", c
->repeat
/ d
);
318 if (c
->repeat
% d
> 0)
319 fprintf(f
, ".%06i", c
->repeat
% d
);
323 format_chain(f
, space
, c
->next
, usec
);
327 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
336 f
= open_memstream(&buf
, &sz
);
340 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
342 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
343 format_weekdays(f
, c
);
347 format_chain(f
, 4, c
->year
, false);
349 format_chain(f
, 2, c
->month
, false);
350 fputc(c
->end_of_month
? '~' : '-', f
);
351 format_chain(f
, 2, c
->day
, false);
353 format_chain(f
, 2, c
->hour
, false);
355 format_chain(f
, 2, c
->minute
, false);
357 format_chain(f
, 2, c
->microsecond
, true);
361 else if (c
->timezone
!= NULL
) {
363 fputs(c
->timezone
, f
);
364 } else if (IN_SET(c
->dst
, 0, 1)) {
366 /* If daylight saving is explicitly on or off, let's show the used timezone. */
370 if (!isempty(tzname
[c
->dst
])) {
372 fputs(tzname
[c
->dst
], f
);
376 r
= fflush_and_check(f
);
389 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
390 static const struct {
420 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
423 if (!startswith_no_case(*p
, day_nr
[i
].name
))
426 skip
= strlen(day_nr
[i
].name
);
428 if (!IN_SET((*p
)[skip
], 0, '-', '.', ',', ' '))
431 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
436 if (l
> day_nr
[i
].nr
)
439 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
440 c
->weekdays_bits
|= 1 << j
;
447 /* Couldn't find this prefix, so let's assume the
448 weekday was not specified and let's continue with
450 if (i
>= ELEMENTSOF(day_nr
))
451 return first
? 0 : -EINVAL
;
453 /* We reached the end of the string */
457 /* We reached the end of the weekday spec part */
459 *p
+= strspn(*p
, " ");
473 /* Support ranges with "-" for backwards compatibility */
474 } else if (**p
== '-') {
481 } else if (**p
== ',') {
486 /* Allow a trailing comma but not an open range */
487 if (IN_SET(**p
, 0, ' ')) {
488 *p
+= strspn(*p
, " ");
489 return l
< 0 ? 0 : -EINVAL
;
496 static int parse_one_number(const char *p
, const char **e
, unsigned long *ret
) {
501 value
= strtoul(p
, &ee
, 10);
512 static int parse_component_decimal(const char **p
, bool usec
, int *res
) {
514 const char *e
= NULL
;
520 r
= parse_one_number(*p
, &e
, &value
);
525 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
528 value
*= USEC_PER_SEC
;
530 /* One "." is a decimal point, but ".." is a range separator */
531 if (e
[0] == '.' && e
[1] != '.') {
535 r
= parse_fractional_part_u(&e
, 6, &add
);
539 if (add
+ value
< value
)
554 static int const_chain(int value
, CalendarComponent
**c
) {
555 CalendarComponent
*cc
= NULL
;
559 cc
= new0(CalendarComponent
, 1);
573 static int calendarspec_from_time_t(CalendarSpec
*c
, time_t time
) {
575 CalendarComponent
*year
= NULL
, *month
= NULL
, *day
= NULL
, *hour
= NULL
, *minute
= NULL
, *us
= NULL
;
578 assert_se(gmtime_r(&time
, &tm
));
580 r
= const_chain(tm
.tm_year
+ 1900, &year
);
584 r
= const_chain(tm
.tm_mon
+ 1, &month
);
588 r
= const_chain(tm
.tm_mday
, &day
);
592 r
= const_chain(tm
.tm_hour
, &hour
);
596 r
= const_chain(tm
.tm_min
, &minute
);
600 r
= const_chain(tm
.tm_sec
* USEC_PER_SEC
, &us
);
614 static int prepend_component(const char **p
, bool usec
, CalendarComponent
**c
) {
615 int r
, start
, stop
= -1, repeat
= 0;
616 CalendarComponent
*cc
;
624 r
= parse_component_decimal(&e
, usec
, &start
);
628 if (e
[0] == '.' && e
[1] == '.') {
630 r
= parse_component_decimal(&e
, usec
, &stop
);
634 repeat
= usec
? USEC_PER_SEC
: 1;
639 r
= parse_component_decimal(&e
, usec
, &repeat
);
647 if (!IN_SET(*e
, 0, ' ', ',', '-', '~', ':'))
650 cc
= new0(CalendarComponent
, 1);
664 return prepend_component(p
, usec
, c
);
670 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
672 CalendarComponent
*cc
= NULL
;
682 r
= const_chain(0, c
);
685 (*c
)->repeat
= USEC_PER_SEC
;
693 r
= prepend_component(&t
, usec
, &cc
);
704 static int parse_date(const char **p
, CalendarSpec
*c
) {
707 CalendarComponent
*first
, *second
, *third
;
718 /* @TIMESTAMP — UNIX time in seconds since the epoch */
723 r
= parse_one_number(t
+ 1, &t
, &value
);
728 if ((unsigned long) time
!= value
)
731 r
= calendarspec_from_time_t(c
, time
);
736 return 1; /* finito, don't parse H:M:S after that */
739 r
= parse_chain(&t
, false, &first
);
743 /* Already the end? A ':' as separator? In that case this was a time, not a date */
744 if (IN_SET(*t
, 0, ':')) {
750 c
->end_of_month
= true;
751 else if (*t
!= '-') {
757 r
= parse_chain(&t
, false, &second
);
763 /* Got two parts, hence it's month and day */
764 if (IN_SET(*t
, 0, ' ')) {
765 *p
= t
+ strspn(t
, " ");
769 } else if (c
->end_of_month
) {
776 c
->end_of_month
= true;
777 else if (*t
!= '-') {
784 r
= parse_chain(&t
, false, &third
);
791 /* Got three parts, hence it is year, month and day */
792 if (IN_SET(*t
, 0, ' ')) {
793 *p
= t
+ strspn(t
, " ");
806 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
807 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
817 /* If no time is specified at all, then this means 00:00:00 */
821 r
= parse_chain(&t
, false, &h
);
831 r
= parse_chain(&t
, false, &m
);
835 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
845 r
= parse_chain(&t
, true, &s
);
849 /* At the end? Then it's hours, minutes and seconds */
857 r
= const_chain(0, &h
);
861 r
= const_chain(0, &m
);
866 r
= const_chain(0, &s
);
885 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
893 c
= new0(CalendarSpec
, 1);
899 utc
= endswith_no_case(p
, " UTC");
902 p
= strndupa(p
, utc
- p
);
904 const char *e
= NULL
;
909 /* Check if the local timezone was specified? */
910 for (j
= 0; j
<= 1; j
++) {
911 if (isempty(tzname
[j
]))
914 e
= endswith_no_case(p
, tzname
[j
]);
925 /* Found one of the two timezones specified? */
926 if (IN_SET(j
, 0, 1)) {
927 p
= strndupa(p
, e
- p
- 1);
930 const char *last_space
;
932 last_space
= strrchr(p
, ' ');
933 if (last_space
!= NULL
&& timezone_is_valid(last_space
+ 1)) {
934 c
->timezone
= strdup(last_space
+ 1);
940 p
= strndupa(p
, last_space
- p
);
950 if (strcaseeq(p
, "minutely")) {
951 r
= const_chain(0, &c
->microsecond
);
955 } else if (strcaseeq(p
, "hourly")) {
956 r
= const_chain(0, &c
->minute
);
959 r
= const_chain(0, &c
->microsecond
);
963 } else if (strcaseeq(p
, "daily")) {
964 r
= const_chain(0, &c
->hour
);
967 r
= const_chain(0, &c
->minute
);
970 r
= const_chain(0, &c
->microsecond
);
974 } else if (strcaseeq(p
, "monthly")) {
975 r
= const_chain(1, &c
->day
);
978 r
= const_chain(0, &c
->hour
);
981 r
= const_chain(0, &c
->minute
);
984 r
= const_chain(0, &c
->microsecond
);
988 } else if (strcaseeq(p
, "annually") ||
989 strcaseeq(p
, "yearly") ||
990 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
992 r
= const_chain(1, &c
->month
);
995 r
= const_chain(1, &c
->day
);
998 r
= const_chain(0, &c
->hour
);
1001 r
= const_chain(0, &c
->minute
);
1004 r
= const_chain(0, &c
->microsecond
);
1008 } else if (strcaseeq(p
, "weekly")) {
1010 c
->weekdays_bits
= 1;
1012 r
= const_chain(0, &c
->hour
);
1015 r
= const_chain(0, &c
->minute
);
1018 r
= const_chain(0, &c
->microsecond
);
1022 } else if (strcaseeq(p
, "quarterly")) {
1024 r
= const_chain(1, &c
->month
);
1027 r
= const_chain(4, &c
->month
);
1030 r
= const_chain(7, &c
->month
);
1033 r
= const_chain(10, &c
->month
);
1036 r
= const_chain(1, &c
->day
);
1039 r
= const_chain(0, &c
->hour
);
1042 r
= const_chain(0, &c
->minute
);
1045 r
= const_chain(0, &c
->microsecond
);
1049 } else if (strcaseeq(p
, "biannually") ||
1050 strcaseeq(p
, "bi-annually") ||
1051 strcaseeq(p
, "semiannually") ||
1052 strcaseeq(p
, "semi-annually")) {
1054 r
= const_chain(1, &c
->month
);
1057 r
= const_chain(7, &c
->month
);
1060 r
= const_chain(1, &c
->day
);
1063 r
= const_chain(0, &c
->hour
);
1066 r
= const_chain(0, &c
->minute
);
1069 r
= const_chain(0, &c
->microsecond
);
1074 r
= parse_weekdays(&p
, c
);
1078 r
= parse_date(&p
, c
);
1083 r
= parse_calendar_time(&p
, c
);
1094 r
= calendar_spec_normalize(c
);
1098 if (!calendar_spec_valid(c
)) {
1107 calendar_spec_free(c
);
1111 static int find_end_of_month(struct tm
*tm
, bool utc
, int day
) {
1115 t
.tm_mday
= 1 - day
;
1117 if (mktime_or_timegm(&t
, utc
) < 0 ||
1118 t
.tm_mon
!= tm
->tm_mon
)
1124 static int find_matching_component(const CalendarSpec
*spec
, const CalendarComponent
*c
,
1125 struct tm
*tm
, int *val
) {
1126 const CalendarComponent
*p
= c
;
1127 int start
, stop
, d
= -1;
1140 if (spec
->end_of_month
&& p
== spec
->day
) {
1141 start
= find_end_of_month(tm
, spec
->utc
, start
);
1142 stop
= find_end_of_month(tm
, spec
->utc
, stop
);
1145 SWAP_TWO(start
, stop
);
1148 if (start
>= *val
) {
1150 if (!d_set
|| start
< d
) {
1155 } else if (c
->repeat
> 0) {
1158 k
= start
+ c
->repeat
* ((*val
- start
+ c
->repeat
- 1) / c
->repeat
);
1160 if ((!d_set
|| k
< d
) && (stop
< 0 || k
<= stop
)) {
1177 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
1183 if (mktime_or_timegm(&t
, utc
) < 0)
1187 * Set an upper bound on the year so impossible dates like "*-02-31"
1188 * don't cause find_next() to loop forever. tm_year contains years
1189 * since 1900, so adjust it accordingly.
1191 if (tm
->tm_year
+ 1900 > MAX_YEAR
)
1194 /* Did any normalization take place? If so, it was out of bounds before */
1196 t
.tm_year
!= tm
->tm_year
||
1197 t
.tm_mon
!= tm
->tm_mon
||
1198 t
.tm_mday
!= tm
->tm_mday
||
1199 t
.tm_hour
!= tm
->tm_hour
||
1200 t
.tm_min
!= tm
->tm_min
||
1201 t
.tm_sec
!= tm
->tm_sec
;
1204 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
1208 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
1212 if (mktime_or_timegm(&t
, utc
) < 0)
1215 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
1216 return (weekdays_bits
& (1 << k
));
1219 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
1231 /* Normalize the current date */
1232 (void) mktime_or_timegm(&c
, spec
->utc
);
1233 c
.tm_isdst
= spec
->dst
;
1236 r
= find_matching_component(spec
, spec
->year
, &c
, &c
.tm_year
);
1242 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1246 if (tm_out_of_bounds(&c
, spec
->utc
))
1250 r
= find_matching_component(spec
, spec
->month
, &c
, &c
.tm_mon
);
1255 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1257 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1261 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1265 r
= find_matching_component(spec
, spec
->day
, &c
, &c
.tm_mday
);
1267 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1268 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1271 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1275 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
1277 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1281 r
= find_matching_component(spec
, spec
->hour
, &c
, &c
.tm_hour
);
1283 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1284 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1286 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1290 r
= find_matching_component(spec
, spec
->minute
, &c
, &c
.tm_min
);
1292 c
.tm_sec
= tm_usec
= 0;
1293 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1295 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1299 c
.tm_sec
= c
.tm_sec
* USEC_PER_SEC
+ tm_usec
;
1300 r
= find_matching_component(spec
, spec
->microsecond
, &c
, &c
.tm_sec
);
1301 tm_usec
= c
.tm_sec
% USEC_PER_SEC
;
1302 c
.tm_sec
/= USEC_PER_SEC
;
1304 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1306 c
.tm_sec
= tm_usec
= 0;
1316 static int calendar_spec_next_usec_impl(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1325 if (usec
> USEC_TIMESTAMP_FORMATTABLE_MAX
)
1329 t
= (time_t) (usec
/ USEC_PER_SEC
);
1330 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1331 tm_usec
= usec
% USEC_PER_SEC
;
1333 r
= find_next(spec
, &tm
, &tm_usec
);
1337 t
= mktime_or_timegm(&tm
, spec
->utc
);
1341 *next
= (usec_t
) t
* USEC_PER_SEC
+ tm_usec
;
1345 typedef struct SpecNextResult
{
1350 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1352 SpecNextResult
*shared
;
1356 if (isempty(spec
->timezone
))
1357 return calendar_spec_next_usec_impl(spec
, usec
, next
);
1359 shared
= mmap(NULL
, sizeof *shared
, PROT_READ
|PROT_WRITE
, MAP_SHARED
|MAP_ANONYMOUS
, -1, 0);
1360 if (shared
== MAP_FAILED
)
1361 return negative_errno();
1366 int fork_errno
= errno
;
1367 (void) munmap(shared
, sizeof *shared
);
1372 if (setenv("TZ", spec
->timezone
, 1) != 0) {
1373 shared
->return_value
= negative_errno();
1374 _exit(EXIT_FAILURE
);
1379 shared
->return_value
= calendar_spec_next_usec_impl(spec
, usec
, &shared
->next
);
1381 _exit(EXIT_SUCCESS
);
1384 r
= wait_for_terminate(pid
, NULL
);
1386 (void) munmap(shared
, sizeof *shared
);
1391 if (munmap(shared
, sizeof *shared
) != 0)
1392 return negative_errno();
1394 if (tmp
.return_value
== 0)
1397 return tmp
.return_value
;