]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/calendarspec.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / calendarspec.c
index 3fa1c51ace0496d3f7646dfed54ec842e092e7b1..8e406aa643f9112d390202d867a6a12dcb05a9b1 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -25,6 +26,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <time.h>
 
 #include "alloc-util.h"
@@ -33,6 +35,7 @@
 #include "macro.h"
 #include "parse-util.h"
 #include "string-util.h"
+#include "time-util.h"
 
 #define BITS_WEEKDAYS 127
 #define MIN_YEAR 1970
@@ -59,6 +62,7 @@ void calendar_spec_free(CalendarSpec *c) {
         free_chain(c->hour);
         free_chain(c->minute);
         free_chain(c->microsecond);
+        free(c->timezone);
 
         free(c);
 }
@@ -116,8 +120,7 @@ static void normalize_chain(CalendarComponent **c) {
 
         /* Drop non-unique entries */
         for (k = n-1; k > 0; k--) {
-                if (b[k-1]->start == next->start &&
-                    b[k-1]->repeat == next->repeat) {
+                if (component_compare(&b[k-1], &next) == 0) {
                         free(b[k-1]);
                         continue;
                 }
@@ -258,19 +261,19 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
 
                         if (l < 0) {
                                 if (need_comma)
-                                        fputc(',', f);
+                                        fputc_unlocked(',', f);
                                 else
                                         need_comma = true;
 
-                                fputs(days[x], f);
+                                fputs_unlocked(days[x], f);
                                 l = x;
                         }
 
                 } else if (l >= 0) {
 
                         if (x > l + 1) {
-                                fputs(x > l + 2 ? ".." : ",", f);
-                                fputs(days[x-1], f);
+                                fputs_unlocked(x > l + 2 ? ".." : ",", f);
+                                fputs_unlocked(days[x-1], f);
                         }
 
                         l = -1;
@@ -278,8 +281,8 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
         }
 
         if (l >= 0 && x > l + 1) {
-                fputs(x > l + 2 ? ".." : ",", f);
-                fputs(days[x-1], f);
+                fputs_unlocked(x > l + 2 ? ".." : ",", f);
+                fputs_unlocked(days[x-1], f);
         }
 }
 
@@ -289,12 +292,12 @@ static void format_chain(FILE *f, int space, const CalendarComponent *c, bool us
         assert(f);
 
         if (!c) {
-                fputc('*', f);
+                fputc_unlocked('*', f);
                 return;
         }
 
         if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) {
-                fputc('*', f);
+                fputc_unlocked('*', f);
                 return;
         }
 
@@ -315,7 +318,7 @@ static void format_chain(FILE *f, int space, const CalendarComponent *c, bool us
                 fprintf(f, ".%06i", c->repeat % d);
 
         if (c->next) {
-                fputc(',', f);
+                fputc_unlocked(',', f);
                 format_chain(f, space, c->next, usec);
         }
 }
@@ -335,32 +338,35 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
 
         if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
                 format_weekdays(f, c);
-                fputc(' ', f);
+                fputc_unlocked(' ', f);
         }
 
         format_chain(f, 4, c->year, false);
-        fputc('-', f);
+        fputc_unlocked('-', f);
         format_chain(f, 2, c->month, false);
-        fputc(c->end_of_month ? '~' : '-', f);
+        fputc_unlocked(c->end_of_month ? '~' : '-', f);
         format_chain(f, 2, c->day, false);
-        fputc(' ', f);
+        fputc_unlocked(' ', f);
         format_chain(f, 2, c->hour, false);
-        fputc(':', f);
+        fputc_unlocked(':', f);
         format_chain(f, 2, c->minute, false);
-        fputc(':', f);
+        fputc_unlocked(':', f);
         format_chain(f, 2, c->microsecond, true);
 
         if (c->utc)
-                fputs(" UTC", f);
-        else if (IN_SET(c->dst, 0, 1)) {
+                fputs_unlocked(" UTC", f);
+        else if (c->timezone != NULL) {
+                fputc_unlocked(' ', f);
+                fputs_unlocked(c->timezone, f);
+        } else if (IN_SET(c->dst, 0, 1)) {
 
                 /* If daylight saving is explicitly on or off, let's show the used timezone. */
 
                 tzset();
 
                 if (!isempty(tzname[c->dst])) {
-                        fputc(' ', f);
-                        fputs(tzname[c->dst], f);
+                        fputc_unlocked(' ', f);
+                        fputs_unlocked(tzname[c->dst], f);
                 }
         }
 
@@ -416,11 +422,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
 
                         skip = strlen(day_nr[i].name);
 
-                        if ((*p)[skip] != '-' &&
-                            (*p)[skip] != '.' &&
-                            (*p)[skip] != ',' &&
-                            (*p)[skip] != ' ' &&
-                            (*p)[skip] != 0)
+                        if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
                                 return -EINVAL;
 
                         c->weekdays_bits |= 1 << day_nr[i].nr;
@@ -479,7 +481,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
                 }
 
                 /* Allow a trailing comma but not an open range */
-                if (**p == 0 || **p == ' ') {
+                if (IN_SET(**p, 0, ' ')) {
                         *p += strspn(*p, " ");
                         return l < 0 ? 0 : -EINVAL;
                 }
@@ -488,22 +490,33 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
         }
 }
 
+static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
+        char *ee = NULL;
+        unsigned long value;
+
+        errno = 0;
+        value = strtoul(p, &ee, 10);
+        if (errno > 0)
+                return -errno;
+        if (ee == p)
+                return -EINVAL;
+
+        *ret = value;
+        *e = ee;
+        return 0;
+}
+
 static int parse_component_decimal(const char **p, bool usec, int *res) {
         unsigned long value;
         const char *e = NULL;
-        char *ee = NULL;
         int r;
 
         if (!isdigit(**p))
                 return -EINVAL;
 
-        errno = 0;
-        value = strtoul(*p, &ee, 10);
-        if (errno > 0)
-                return -errno;
-        if (ee == *p)
-                return -EINVAL;
-        e = ee;
+        r = parse_one_number(*p, &e, &value);
+        if (r < 0)
+                return r;
 
         if (usec) {
                 if (value * USEC_PER_SEC / USEC_PER_SEC != value)
@@ -554,6 +567,47 @@ static int const_chain(int value, CalendarComponent **c) {
         return 0;
 }
 
+static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
+        struct tm tm;
+        CalendarComponent *year = NULL, *month = NULL, *day = NULL, *hour = NULL, *minute = NULL, *us = NULL;
+        int r;
+
+        assert_se(gmtime_r(&time, &tm));
+
+        r = const_chain(tm.tm_year + 1900, &year);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_mon + 1, &month);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_mday, &day);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_hour, &hour);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_min, &minute);
+        if (r < 0)
+                return r;
+
+        r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
+        if (r < 0)
+                return r;
+
+        c->utc = true;
+        c->year = year;
+        c->month = month;
+        c->day = day;
+        c->hour = hour;
+        c->minute = minute;
+        c->microsecond = us;
+        return 0;
+}
+
 static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
         int r, start, stop = -1, repeat = 0;
         CalendarComponent *cc;
@@ -587,7 +641,7 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
                         return -ERANGE;
         }
 
-        if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != '~' && *e != ':')
+        if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
                 return -EINVAL;
 
         cc = new0(CalendarComponent, 1);
@@ -658,12 +712,33 @@ static int parse_date(const char **p, CalendarSpec *c) {
         if (*t == 0)
                 return 0;
 
+        /* @TIMESTAMP — UNIX time in seconds since the epoch */
+        if (*t == '@') {
+                unsigned long value;
+                time_t time;
+
+                r = parse_one_number(t + 1, &t, &value);
+                if (r < 0)
+                        return r;
+
+                time = value;
+                if ((unsigned long) time != value)
+                        return -ERANGE;
+
+                r = calendarspec_from_time_t(c, time);
+                if (r < 0)
+                        return r;
+
+                *p = t;
+                return 1; /* finito, don't parse H:M:S after that */
+        }
+
         r = parse_chain(&t, false, &first);
         if (r < 0)
                 return r;
 
         /* Already the end? A ':' as separator? In that case this was a time, not a date */
-        if (*t == 0 || *t == ':') {
+        if (IN_SET(*t, 0, ':')) {
                 free_chain(first);
                 return 0;
         }
@@ -683,7 +758,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
         }
 
         /* Got two parts, hence it's month and day */
-        if (*t == ' ' || *t == 0) {
+        if (IN_SET(*t, 0, ' ')) {
                 *p = t + strspn(t, " ");
                 c->month = first;
                 c->day = second;
@@ -711,7 +786,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
         }
 
         /* Got three parts, hence it is year, month and day */
-        if (*t == ' ' || *t == 0) {
+        if (IN_SET(*t, 0, ' ')) {
                 *p = t + strspn(t, " ");
                 c->year = first;
                 c->month = second;
@@ -816,6 +891,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
         if (!c)
                 return -ENOMEM;
         c->dst = -1;
+        c->timezone = NULL;
 
         utc = endswith_no_case(p, " UTC");
         if (utc) {
@@ -833,7 +909,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                                 continue;
 
                         e = endswith_no_case(p, tzname[j]);
-                        if(!e)
+                        if (!e)
                                 continue;
                         if (e == p)
                                 continue;
@@ -847,6 +923,19 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 if (IN_SET(j, 0, 1)) {
                         p = strndupa(p, e - p - 1);
                         c->dst = j;
+                } else {
+                        const char *last_space;
+
+                        last_space = strrchr(p, ' ');
+                        if (last_space != NULL && timezone_is_valid(last_space + 1)) {
+                                c->timezone = strdup(last_space + 1);
+                                if (!c->timezone) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                p = strndupa(p, last_space - p);
+                        }
                 }
         }
 
@@ -987,9 +1076,11 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 if (r < 0)
                         goto fail;
 
-                r = parse_calendar_time(&p, c);
-                if (r < 0)
-                        goto fail;
+                if (r == 0) {
+                        r = parse_calendar_time(&p, c);
+                        if (r < 0)
+                                goto fail;
+                }
 
                 if (*p != 0) {
                         r = -EINVAL;
@@ -1219,7 +1310,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
         }
 }
 
-int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
         struct tm tm;
         time_t t;
         int r;
@@ -1247,3 +1338,58 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
         *next = (usec_t) t * USEC_PER_SEC + tm_usec;
         return 0;
 }
+
+typedef struct SpecNextResult {
+        usec_t next;
+        int return_value;
+} SpecNextResult;
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+        pid_t pid;
+        SpecNextResult *shared;
+        SpecNextResult tmp;
+        int r;
+
+        if (isempty(spec->timezone))
+                return calendar_spec_next_usec_impl(spec, usec, next);
+
+        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+        if (shared == MAP_FAILED)
+                return negative_errno();
+
+        pid = fork();
+
+        if (pid == -1) {
+                int fork_errno = errno;
+                (void) munmap(shared, sizeof *shared);
+                return -fork_errno;
+        }
+
+        if (pid == 0) {
+                if (setenv("TZ", spec->timezone, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        r = wait_for_terminate(pid, NULL);
+        if (r < 0) {
+                (void) munmap(shared, sizeof *shared);
+                return r;
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) != 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *next = tmp.next;
+
+        return tmp.return_value;
+}