+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
#include <time.h>
#include "alloc-util.h"
#include "macro.h"
#include "parse-util.h"
#include "string-util.h"
+#include "time-util.h"
#define BITS_WEEKDAYS 127
#define MIN_YEAR 1970
free_chain(c->hour);
free_chain(c->minute);
free_chain(c->microsecond);
+ free(c->timezone);
free(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;
}
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;
}
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);
}
}
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;
}
fprintf(f, ".%06i", c->repeat % d);
if (c->next) {
- fputc(',', f);
+ fputc_unlocked(',', f);
format_chain(f, space, c->next, usec);
}
}
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);
}
}
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;
}
/* 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;
}
}
}
+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)
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;
return -ERANGE;
}
- if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != '~' && *e != ':')
+ if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
return -EINVAL;
cc = new0(CalendarComponent, 1);
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;
}
}
/* 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;
}
/* 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;
if (!c)
return -ENOMEM;
c->dst = -1;
+ c->timezone = NULL;
utc = endswith_no_case(p, " UTC");
if (utc) {
continue;
e = endswith_no_case(p, tzname[j]);
- if(!e)
+ if (!e)
continue;
if (e == p)
continue;
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);
+ }
}
}
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;
}
}
-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;
*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;
+}