Files
systemd/src/basic/time-util.c
Mike Yuan 6dd412bc50 time-util: do not carry musl-specific fallback logic on glibc systems
Follow-up for 3ac4d68498

We have no sensible way to detect why strptime() fails, hence
the fallback path as it is now would fire on glibc systems too,
pointlessly. Let's guard it behind ifdeffery.
2025-11-13 09:40:20 +01:00

1870 lines
58 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <threads.h>
#include <unistd.h>
#include "alloc-util.h"
#include "env-util.h"
#include "errno-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
static clockid_t map_clock_id(clockid_t c) {
/* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus,
* clock_gettime() will fail for them. Since they are essentially the same as their non-ALARM
* pendants (their only difference is when timers are set on them), let's just map them
* accordingly. This way, we can get the correct time even on those archs. */
switch (c) {
case CLOCK_BOOTTIME_ALARM:
return CLOCK_BOOTTIME;
case CLOCK_REALTIME_ALARM:
return CLOCK_REALTIME;
default:
return c;
}
}
usec_t now(clockid_t clock_id) {
struct timespec ts;
assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
return timespec_load(&ts);
}
nsec_t now_nsec(clockid_t clock_id) {
struct timespec ts;
assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
return timespec_load_nsec(&ts);
}
dual_timestamp* dual_timestamp_now(dual_timestamp *ts) {
assert(ts);
ts->realtime = now(CLOCK_REALTIME);
ts->monotonic = now(CLOCK_MONOTONIC);
return ts;
}
triple_timestamp* triple_timestamp_now(triple_timestamp *ts) {
assert(ts);
ts->realtime = now(CLOCK_REALTIME);
ts->monotonic = now(CLOCK_MONOTONIC);
ts->boottime = now(CLOCK_BOOTTIME);
return ts;
}
usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base) {
/* Maps the time 'from' between two clocks, based on a common reference point where the first clock
* is at 'from_base' and the second clock at 'to_base'. Basically calculates:
*
* from - from_base + to_base
*
* But takes care of overflows/underflows and avoids signed operations. */
if (from >= from_base) { /* In the future */
usec_t delta = from - from_base;
if (to_base >= USEC_INFINITY - delta) /* overflow? */
return USEC_INFINITY;
return to_base + delta;
} else { /* In the past */
usec_t delta = from_base - from;
if (to_base <= delta) /* underflow? */
return 0;
return to_base - delta;
}
}
usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock) {
/* Try to avoid any inaccuracy needlessly added in case we convert from effectively the same clock
* onto itself */
if (map_clock_id(from_clock) == map_clock_id(to_clock))
return from;
/* Keep infinity as is */
if (from == USEC_INFINITY)
return from;
return map_clock_usec_raw(from, now(from_clock), now(to_clock));
}
dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
assert(ts);
if (!timestamp_is_set(u)) {
ts->realtime = ts->monotonic = u;
return ts;
}
ts->realtime = u;
ts->monotonic = map_clock_usec(u, CLOCK_REALTIME, CLOCK_MONOTONIC);
return ts;
}
triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
usec_t nowr;
assert(ts);
if (!timestamp_is_set(u)) {
ts->realtime = ts->monotonic = ts->boottime = u;
return ts;
}
nowr = now(CLOCK_REALTIME);
ts->realtime = u;
ts->monotonic = map_clock_usec_raw(u, nowr, now(CLOCK_MONOTONIC));
ts->boottime = map_clock_usec_raw(u, nowr, now(CLOCK_BOOTTIME));
return ts;
}
triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) {
usec_t nowb;
assert(ts);
if (u == USEC_INFINITY) {
ts->realtime = ts->monotonic = ts->boottime = u;
return ts;
}
nowb = now(CLOCK_BOOTTIME);
ts->boottime = u;
ts->monotonic = map_clock_usec_raw(u, nowb, now(CLOCK_MONOTONIC));
ts->realtime = map_clock_usec_raw(u, nowb, now(CLOCK_REALTIME));
return ts;
}
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
assert(ts);
if (u == USEC_INFINITY) {
ts->realtime = ts->monotonic = USEC_INFINITY;
return ts;
}
ts->monotonic = u;
ts->realtime = map_clock_usec(u, CLOCK_MONOTONIC, CLOCK_REALTIME);
return ts;
}
dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
usec_t nowm;
assert(ts);
if (u == USEC_INFINITY) {
ts->realtime = ts->monotonic = USEC_INFINITY;
return ts;
}
nowm = now(CLOCK_BOOTTIME);
ts->monotonic = map_clock_usec_raw(u, nowm, now(CLOCK_MONOTONIC));
ts->realtime = map_clock_usec_raw(u, nowm, now(CLOCK_REALTIME));
return ts;
}
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
assert(ts);
switch (clock) {
case CLOCK_REALTIME:
case CLOCK_REALTIME_ALARM:
return ts->realtime;
case CLOCK_MONOTONIC:
return ts->monotonic;
case CLOCK_BOOTTIME:
case CLOCK_BOOTTIME_ALARM:
return ts->boottime;
default:
return USEC_INFINITY;
}
}
usec_t timespec_load(const struct timespec *ts) {
assert(ts);
if (ts->tv_sec < 0 || ts->tv_nsec < 0)
return USEC_INFINITY;
if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
return USEC_INFINITY;
return
(usec_t) ts->tv_sec * USEC_PER_SEC +
(usec_t) ts->tv_nsec / NSEC_PER_USEC;
}
nsec_t timespec_load_nsec(const struct timespec *ts) {
assert(ts);
if (ts->tv_sec < 0 || ts->tv_nsec < 0)
return NSEC_INFINITY;
if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
return NSEC_INFINITY;
return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
}
struct timespec *timespec_store(struct timespec *ts, usec_t u) {
assert(ts);
if (u == USEC_INFINITY ||
u / USEC_PER_SEC >= TIME_T_MAX) {
ts->tv_sec = (time_t) -1;
ts->tv_nsec = -1L;
return ts;
}
ts->tv_sec = (time_t) (u / USEC_PER_SEC);
ts->tv_nsec = (long) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
return ts;
}
struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
assert(ts);
if (n == NSEC_INFINITY ||
n / NSEC_PER_SEC >= TIME_T_MAX) {
ts->tv_sec = (time_t) -1;
ts->tv_nsec = -1L;
return ts;
}
ts->tv_sec = (time_t) (n / NSEC_PER_SEC);
ts->tv_nsec = (long) (n % NSEC_PER_SEC);
return ts;
}
usec_t timeval_load(const struct timeval *tv) {
assert(tv);
if (tv->tv_sec < 0 || tv->tv_usec < 0)
return USEC_INFINITY;
if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
return USEC_INFINITY;
return
(usec_t) tv->tv_sec * USEC_PER_SEC +
(usec_t) tv->tv_usec;
}
struct timeval *timeval_store(struct timeval *tv, usec_t u) {
assert(tv);
if (u == USEC_INFINITY ||
u / USEC_PER_SEC > TIME_T_MAX) {
tv->tv_sec = (time_t) -1;
tv->tv_usec = (suseconds_t) -1;
} else {
tv->tv_sec = (time_t) (u / USEC_PER_SEC);
tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
}
return tv;
}
char* format_timestamp_style(
char *buf,
size_t l,
usec_t t,
TimestampStyle style) {
/* The weekdays in non-localized (English) form. We use this instead of the localized form, so that
* our generated timestamps may be parsed with parse_timestamp(), and always read the same. */
static const char * const weekdays[] = {
[0] = "Sun",
[1] = "Mon",
[2] = "Tue",
[3] = "Wed",
[4] = "Thu",
[5] = "Fri",
[6] = "Sat",
};
struct tm tm;
bool utc, us;
size_t n;
assert(buf);
assert(style >= 0);
assert(style < _TIMESTAMP_STYLE_MAX);
if (!timestamp_is_set(t))
return NULL; /* Timestamp is unset */
if (style == TIMESTAMP_UNIX) {
if (l < (size_t) (1 + 1 + 1))
return NULL; /* not enough space for even the shortest of forms */
return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down μs → s */
}
utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE);
us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC);
if (l < (size_t) (3 + /* week day */
1 + 10 + /* space and date */
style == TIMESTAMP_DATE ? 0 :
(1 + 8 + /* space and time */
(us ? 1 + 6 : 0) + /* "." and microsecond part */
1 + (utc ? 3 : 1)) + /* space and shortest possible zone */
1))
return NULL; /* Not enough space even for the shortest form. */
/* Let's not format times with years > 9999 */
if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
static const char* const xxx[_TIMESTAMP_STYLE_MAX] = {
[TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX",
[TIMESTAMP_US] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX",
[TIMESTAMP_UTC] = "--- XXXX-XX-XX XX:XX:XX UTC",
[TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC",
[TIMESTAMP_DATE] = "--- XXXX-XX-XX",
};
assert(l >= strlen(xxx[style]) + 1);
return strcpy(buf, xxx[style]);
}
if (localtime_or_gmtime_usec(t, utc, &tm) < 0)
return NULL;
/* Start with the week day */
assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
memcpy(buf, weekdays[tm.tm_wday], 4);
if (style == TIMESTAMP_DATE) {
/* Special format string if only date should be shown. */
if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0)
return NULL; /* Doesn't fit */
return buf;
}
/* Add the main components */
if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
return NULL; /* Doesn't fit */
/* Append the microseconds part, if that's requested */
if (us) {
n = strlen(buf);
if (n + 8 > l)
return NULL; /* Microseconds part doesn't fit. */
sprintf(buf + n, ".%06"PRI_USEC, t % USEC_PER_SEC);
}
/* Append the timezone */
n = strlen(buf);
if (utc) {
/* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r()
* normally uses the obsolete "GMT" instead. */
if (n + 5 > l)
return NULL; /* "UTC" doesn't fit. */
strcpy(buf + n, " UTC");
} else if (!isempty(tm.tm_zone)) {
size_t tn;
/* An explicit timezone is specified, let's use it, if it fits */
tn = strlen(tm.tm_zone);
if (n + 1 + tn + 1 > l) {
/* The full time zone does not fit in. Yuck. */
if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that
* case, complain that it doesn't fit. */
/* So the time zone doesn't fit in fully, but the caller passed enough space for the
* POSIX minimum time zone length. In this case suppress the timezone entirely, in
* order not to dump an overly long, hard to read string on the user. This should be
* safe, because the user will assume the local timezone anyway if none is shown. And
* so does parse_timestamp(). */
} else {
buf[n++] = ' ';
strcpy(buf + n, tm.tm_zone);
}
}
return buf;
}
char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) {
const char *s;
usec_t n, d;
assert(buf);
if (!timestamp_is_set(t))
return NULL;
n = now(clock);
if (n > t) {
d = n - t;
s = " ago";
} else {
d = t - n;
s = implicit_left ? "" : " left";
}
if (d >= USEC_PER_YEAR) {
usec_t years = d / USEC_PER_YEAR;
usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
(void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
years,
years == 1 ? "year" : "years",
months,
months == 1 ? "month" : "months",
s);
} else if (d >= USEC_PER_MONTH) {
usec_t months = d / USEC_PER_MONTH;
usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
(void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
months,
months == 1 ? "month" : "months",
days,
days == 1 ? "day" : "days",
s);
} else if (d >= USEC_PER_WEEK) {
usec_t weeks = d / USEC_PER_WEEK;
usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
(void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
weeks,
weeks == 1 ? "week" : "weeks",
days,
days == 1 ? "day" : "days",
s);
} else if (d >= 2*USEC_PER_DAY)
(void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s);
else if (d >= 25*USEC_PER_HOUR)
(void) snprintf(buf, l, "1 day " USEC_FMT "h%s",
(d - USEC_PER_DAY) / USEC_PER_HOUR, s);
else if (d >= 6*USEC_PER_HOUR)
(void) snprintf(buf, l, USEC_FMT "h%s",
d / USEC_PER_HOUR, s);
else if (d >= USEC_PER_HOUR)
(void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min%s",
d / USEC_PER_HOUR,
(d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
else if (d >= 5*USEC_PER_MINUTE)
(void) snprintf(buf, l, USEC_FMT "min%s",
d / USEC_PER_MINUTE, s);
else if (d >= USEC_PER_MINUTE)
(void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s%s",
d / USEC_PER_MINUTE,
(d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
else if (d >= USEC_PER_SEC)
(void) snprintf(buf, l, USEC_FMT "s%s",
d / USEC_PER_SEC, s);
else if (d >= USEC_PER_MSEC)
(void) snprintf(buf, l, USEC_FMT "ms%s",
d / USEC_PER_MSEC, s);
else if (d > 0)
(void) snprintf(buf, l, USEC_FMT"us%s",
d, s);
else
(void) snprintf(buf, l, "now");
buf[l-1] = 0;
return buf;
}
char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
static const struct {
const char *suffix;
usec_t usec;
} table[] = {
{ "y", USEC_PER_YEAR },
{ "month", USEC_PER_MONTH },
{ "w", USEC_PER_WEEK },
{ "d", USEC_PER_DAY },
{ "h", USEC_PER_HOUR },
{ "min", USEC_PER_MINUTE },
{ "s", USEC_PER_SEC },
{ "ms", USEC_PER_MSEC },
{ "us", 1 },
};
char *p = ASSERT_PTR(buf);
bool something = false;
assert(l > 0);
if (t == USEC_INFINITY) {
strncpy(p, "infinity", l-1);
p[l-1] = 0;
return p;
}
if (t <= 0) {
strncpy(p, "0", l-1);
p[l-1] = 0;
return p;
}
/* The result of this function can be parsed with parse_sec */
FOREACH_ELEMENT(i, table) {
int k = 0;
size_t n;
bool done = false;
usec_t a, b;
if (t <= 0)
break;
if (t < accuracy && something)
break;
if (t < i->usec)
continue;
if (l <= 1)
break;
a = t / i->usec;
b = t % i->usec;
/* Let's see if we should shows this in dot notation */
if (t < USEC_PER_MINUTE && b > 0) {
signed char j = 0;
for (usec_t cc = i->usec; cc > 1; cc /= 10)
j++;
for (usec_t cc = accuracy; cc > 1; cc /= 10) {
b /= 10;
j--;
}
if (j > 0) {
k = snprintf(p, l,
"%s"USEC_FMT".%0*"PRI_USEC"%s",
p > buf ? " " : "",
a,
j,
b,
i->suffix);
t = 0;
done = true;
}
}
/* No? Then let's show it normally */
if (!done) {
k = snprintf(p, l,
"%s"USEC_FMT"%s",
p > buf ? " " : "",
a,
i->suffix);
t = b;
}
n = MIN((size_t) k, l-1);
l -= n;
p += n;
something = true;
}
*p = 0;
return buf;
}
const char* get_tzname(bool dst) {
/* musl leaves the DST timezone name unset if there is no DST, map this back to no DST */
if (dst && isempty(tzname[1]))
dst = false;
return empty_to_null(tzname[dst]);
}
int parse_gmtoff(const char *t, long *ret) {
assert(t);
struct tm tm;
const char *k = strptime(t, "%z", &tm);
if (k && *k == '\0') {
/* Success! */
if (ret)
*ret = tm.tm_gmtoff;
return 0;
}
#ifdef __GLIBC__
return -EINVAL;
#else
int r;
/* musl v1.2.5 does not support %z specifier in strptime(). Since
* https://github.com/kraj/musl/commit/fced99e93daeefb0192fd16304f978d4401d1d77
* %z is supported, but it only supports strict RFC-822/ISO 8601 format, that is, 4 digits with sign
* (e.g. +0900 or -1400), but does not support extended format: 2 digits or colon separated 4 digits
* (e.g. +09 or -14:00). Let's add fallback logic to make it support the extended timezone spec. */
bool positive;
switch (*t) {
case '+':
positive = true;
break;
case '-':
positive = false;
break;
default:
return -EINVAL;
}
t++;
r = undecchar(*t);
if (r < 0)
return r;
usec_t u = r * 10 * USEC_PER_HOUR;
t++;
r = undecchar(*t);
if (r < 0)
return r;
u += r * USEC_PER_HOUR;
t++;
if (*t == '\0') /* 2 digits case */
goto finalize;
if (*t == ':') /* skip colon */
t++;
r = undecchar(*t);
if (r < 0)
return r;
if (r >= 6) /* refuse minutes equal to or larger than 60 */
return -EINVAL;
u += r * 10 * USEC_PER_MINUTE;
t++;
r = undecchar(*t);
if (r < 0)
return r;
u += r * USEC_PER_MINUTE;
t++;
if (*t != '\0')
return -EINVAL;
finalize:
if (u > USEC_PER_DAY) /* refuse larger than one day */
return -EINVAL;
if (ret) {
long gmtoff = u / USEC_PER_SEC;
*ret = positive ? gmtoff : -gmtoff;
}
return 0;
#endif
}
static int parse_timestamp_impl(
const char *t,
size_t max_len,
bool utc,
int isdst,
long gmtoff,
usec_t *ret) {
static const struct {
const char *name;
const int nr;
} day_nr[] = {
{ "Sunday", 0 },
{ "Sun", 0 },
{ "Monday", 1 },
{ "Mon", 1 },
{ "Tuesday", 2 },
{ "Tue", 2 },
{ "Wednesday", 3 },
{ "Wed", 3 },
{ "Thursday", 4 },
{ "Thu", 4 },
{ "Friday", 5 },
{ "Fri", 5 },
{ "Saturday", 6 },
{ "Sat", 6 },
};
_cleanup_free_ char *t_alloc = NULL;
usec_t usec, plus = 0, minus = 0;
bool with_tz = false;
int r, weekday = -1;
unsigned fractional = 0;
const char *k;
struct tm tm, copy;
/* Allowed syntaxes:
*
* 2012-09-22 16:34:22.1[2[3[4[5[6]]]]]
* 2012-09-22 16:34:22 (µsec will be set to 0)
* 2012-09-22 16:34 (seconds will be set to 0)
* 2012-09-22T16:34:22.1[2[3[4[5[6]]]]]
* 2012-09-22T16:34:22 (µsec will be set to 0)
* 2012-09-22T16:34 (seconds will be set to 0)
* 2012-09-22 (time will be set to 00:00:00)
* 16:34:22 (date will be set to today)
* 16:34 (date will be set to today, seconds to 0)
* now
* yesterday (time is set to 00:00:00)
* today (time is set to 00:00:00)
* tomorrow (time is set to 00:00:00)
* +5min
* -5days
* @2147483647 (seconds since epoch)
*
* Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted.
* E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT".
*
* A simplified strptime-spelled RFC3339 ABNF looks like
* "%Y-%m-%d" "T" "%H" ":" "%M" ":" "%S" [".%N"] ("Z" / (("+" / "-") "%H:%M"))
* We additionally allow no seconds and inherited timezone
* for symmetry with our other syntaxes and improved interactive usability:
* "%Y-%m-%d" "T" "%H" ":" "%M" ":" ["%S" [".%N"]] ["Z" / (("+" / "-") "%H:%M")]
* RFC3339 defines time-secfrac to as "." 1*DIGIT, but we limit to 6 digits,
* since we're limited to 1µs resolution.
* We also accept "Sat 2012-09-22T16:34:22", RFC3339 warns against it.
*/
assert(t);
if (max_len != SIZE_MAX) {
/* If the input string contains timezone, then cut it here. */
if (max_len == 0) /* Can't be the only field */
return -EINVAL;
t_alloc = strndup(t, max_len);
if (!t_alloc)
return -ENOMEM;
t = t_alloc;
with_tz = true;
}
if (utc) {
/* glibc accepts gmtoff more than 24 hours, but we refuse it. */
if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY)
return -EINVAL;
} else {
if (gmtoff != 0)
return -EINVAL;
}
if (t[0] == '@' && !with_tz)
return parse_sec(t + 1, ret);
usec = now(CLOCK_REALTIME);
if (!with_tz) {
if (streq(t, "now"))
goto finish;
if (t[0] == '+') {
r = parse_sec(t+1, &plus);
if (r < 0)
return r;
goto finish;
}
if (t[0] == '-') {
r = parse_sec(t+1, &minus);
if (r < 0)
return r;
goto finish;
}
if ((k = endswith(t, " ago"))) {
_cleanup_free_ char *buf = NULL;
buf = strndup(t, k - t);
if (!buf)
return -ENOMEM;
r = parse_sec(buf, &minus);
if (r < 0)
return r;
goto finish;
}
if ((k = endswith(t, " left"))) {
_cleanup_free_ char *buf = NULL;
buf = strndup(t, k - t);
if (!buf)
return -ENOMEM;
r = parse_sec(buf, &plus);
if (r < 0)
return r;
goto finish;
}
}
r = localtime_or_gmtime_usec(usec, utc, &tm);
if (r < 0)
return r;
tm.tm_isdst = isdst;
if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto from_tm;
} else if (streq(t, "yesterday")) {
tm.tm_mday--;
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto from_tm;
} else if (streq(t, "tomorrow")) {
tm.tm_mday++;
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto from_tm;
}
FOREACH_ELEMENT(day, day_nr) {
k = startswith_no_case(t, day->name);
if (!k || *k != ' ')
continue;
#ifdef __GLIBC__
/* musl does not set tm_wday field and set 0 unless it is explicitly requested by %w or so.
* In the below, let's only check tm_wday field only when built with glibc. */
weekday = day->nr;
#endif
t = k + 1;
break;
}
copy = tm;
k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
if (k) {
if (*k == '.')
goto parse_usec;
else if (*k == 0)
goto from_tm;
}
/* Our "canonical" RFC3339 syntax variant */
tm = copy;
k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
if (k) {
if (*k == '.')
goto parse_usec;
else if (*k == 0)
goto from_tm;
}
/* RFC3339 syntax */
tm = copy;
k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
if (k) {
if (*k == '.')
goto parse_usec;
else if (*k == 0)
goto from_tm;
}
/* Support OUTPUT_SHORT and OUTPUT_SHORT_PRECISE formats */
tm = copy;
k = strptime(t, "%b %d %H:%M:%S", &tm);
if (k) {
if (*k == '.')
goto parse_usec;
else if (*k == 0)
goto from_tm;
}
tm = copy;
k = strptime(t, "%y-%m-%d %H:%M", &tm);
if (k && *k == 0) {
tm.tm_sec = 0;
goto from_tm;
}
/* Our "canonical" RFC3339 syntax variant without seconds */
tm = copy;
k = strptime(t, "%Y-%m-%d %H:%M", &tm);
if (k && *k == 0) {
tm.tm_sec = 0;
goto from_tm;
}
/* RFC3339 syntax without seconds */
tm = copy;
k = strptime(t, "%Y-%m-%dT%H:%M", &tm);
if (k && *k == 0) {
tm.tm_sec = 0;
goto from_tm;
}
tm = copy;
k = strptime(t, "%y-%m-%d", &tm);
if (k && *k == 0) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto from_tm;
}
tm = copy;
k = strptime(t, "%Y-%m-%d", &tm);
if (k && *k == 0) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto from_tm;
}
tm = copy;
k = strptime(t, "%H:%M:%S", &tm);
if (k) {
if (*k == '.')
goto parse_usec;
else if (*k == 0)
goto from_tm;
}
tm = copy;
k = strptime(t, "%H:%M", &tm);
if (k && *k == 0) {
tm.tm_sec = 0;
goto from_tm;
}
return -EINVAL;
parse_usec:
k++;
r = parse_fractional_part_u(&k, 6, &fractional);
if (r < 0)
return -EINVAL;
if (*k != '\0')
return -EINVAL;
from_tm:
assert(plus == 0);
assert(minus == 0);
if (weekday >= 0 && tm.tm_wday != weekday)
return -EINVAL;
if (gmtoff < 0) {
plus = -gmtoff * USEC_PER_SEC;
/* If gmtoff is negative, the string may be too old to be parsed as UTC.
* E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
* We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
* handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
* later. */
if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
/* Thu 1970-01-01-00:00:00 */
tm.tm_year = 70;
tm.tm_mon = 0;
tm.tm_mday = 1;
tm.tm_wday = 4;
tm.tm_yday = 0;
minus = USEC_PER_DAY;
}
} else
minus = gmtoff * USEC_PER_SEC;
r = mktime_or_timegm_usec(&tm, utc, &usec);
if (r < 0)
return r;
usec = usec_add(usec, fractional);
finish:
usec = usec_add(usec, plus);
if (usec < minus)
return -EINVAL;
usec = usec_sub_unsigned(usec, minus);
if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL;
if (ret)
*ret = usec;
return 0;
}
int parse_timestamp(const char *t, usec_t *ret) {
long gmtoff;
int r;
assert(t);
size_t t_len = strlen(t);
if (t_len > 2 && t[t_len - 1] == 'Z') {
/* Try to parse as RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */
r = parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
if (r >= 0)
return r;
}
/* RFC3339-style welded offset: "1990-12-31T15:59:60-08:00" */
if (t_len > 7 && IN_SET(t[t_len - 6], '+', '-') && t[t_len - 7] != ' ' && parse_gmtoff(&t[t_len - 6], &gmtoff) >= 0)
return parse_timestamp_impl(t, t_len - 6, /* utc = */ true, /* isdst = */ -1, gmtoff, ret);
const char *tz = strrchr(t, ' ');
if (!tz)
return parse_timestamp_impl(t, /* max_len = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
size_t max_len = tz - t;
tz++;
/* Shortcut, parse the string as UTC. */
if (streq(tz, "UTC"))
return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
/* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
* UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as
* tzname[] may be in the same format. */
if (parse_gmtoff(tz, &gmtoff) >= 0)
return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, gmtoff, ret);
/* Check if the last word matches tzname[] of the local timezone. Note, this must be done earlier
* than the check by timezone_is_valid() below, as some short timezone specifications have their own
* timezone files (e.g. WET has its own timezone file, but JST does not), but using such files does
* not follow the timezone change in the current area. */
tzset();
for (int j = 0; j <= 1; j++) {
if (!streq_ptr(tz, get_tzname(j)))
continue;
/* The specified timezone matches tzname[] of the local timezone. */
return parse_timestamp_impl(t, max_len, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
}
/* If the last word is a valid timezone file (e.g. Asia/Tokyo), then save the current timezone, apply
* the specified timezone, and parse the remaining string as a local time. */
if (timezone_is_valid(tz, LOG_DEBUG)) {
SAVE_TIMEZONE;
if (setenv("TZ", tz, /* overwrite = */ true) < 0)
return negative_errno();
return parse_timestamp_impl(t, max_len, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
}
/* Otherwise, assume that the last word is a part of the time and try to parse the whole string as a
* local time. */
return parse_timestamp_impl(t, SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
}
static const char* extract_multiplier(const char *p, usec_t *ret) {
static const struct {
const char *suffix;
usec_t usec;
} table[] = {
{ "seconds", USEC_PER_SEC },
{ "second", USEC_PER_SEC },
{ "sec", USEC_PER_SEC },
{ "s", USEC_PER_SEC },
{ "minutes", USEC_PER_MINUTE },
{ "minute", USEC_PER_MINUTE },
{ "min", USEC_PER_MINUTE },
{ "months", USEC_PER_MONTH },
{ "month", USEC_PER_MONTH },
{ "M", USEC_PER_MONTH },
{ "msec", USEC_PER_MSEC },
{ "ms", USEC_PER_MSEC },
{ "m", USEC_PER_MINUTE },
{ "hours", USEC_PER_HOUR },
{ "hour", USEC_PER_HOUR },
{ "hr", USEC_PER_HOUR },
{ "h", USEC_PER_HOUR },
{ "days", USEC_PER_DAY },
{ "day", USEC_PER_DAY },
{ "d", USEC_PER_DAY },
{ "weeks", USEC_PER_WEEK },
{ "week", USEC_PER_WEEK },
{ "w", USEC_PER_WEEK },
{ "years", USEC_PER_YEAR },
{ "year", USEC_PER_YEAR },
{ "y", USEC_PER_YEAR },
{ "usec", 1ULL },
{ "us", 1ULL },
{ "μs", 1ULL }, /* U+03bc (aka GREEK SMALL LETTER MU) */
{ "µs", 1ULL }, /* U+b5 (aka MICRO SIGN) */
};
assert(p);
assert(ret);
FOREACH_ELEMENT(i, table) {
char *e;
e = startswith(p, i->suffix);
if (e) {
*ret = i->usec;
return e;
}
}
return p;
}
int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
const char *p, *s;
assert(t);
assert(default_unit > 0);
p = skip_leading_chars(t, /* bad = */ NULL);
s = startswith(p, "infinity");
if (s) {
if (!in_charset(s, WHITESPACE))
return -EINVAL;
if (ret)
*ret = USEC_INFINITY;
return 0;
}
usec_t usec = 0;
for (bool something = false;;) {
usec_t multiplier = default_unit, k;
long long l;
char *e;
p = skip_leading_chars(p, /* bad = */ NULL);
if (*p == 0) {
if (!something)
return -EINVAL;
break;
}
if (*p == '-') /* Don't allow "-0" */
return -ERANGE;
errno = 0;
l = strtoll(p, &e, 10);
if (errno > 0)
return -errno;
if (l < 0)
return -ERANGE;
if (*e == '.') {
p = e + 1;
p += strspn(p, DIGITS);
} else if (e == p)
return -EINVAL;
else
p = e;
s = extract_multiplier(p + strspn(p, WHITESPACE), &multiplier);
if (s == p && *s != '\0')
/* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */
return -EINVAL;
p = s;
if ((usec_t) l >= USEC_INFINITY / multiplier)
return -ERANGE;
k = (usec_t) l * multiplier;
if (k >= USEC_INFINITY - usec)
return -ERANGE;
usec += k;
something = true;
if (*e == '.') {
usec_t m = multiplier / 10;
const char *b;
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (usec_t) (*b - '0') * m;
if (k >= USEC_INFINITY - usec)
return -ERANGE;
usec += k;
}
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
if (b == e + 1)
return -EINVAL;
}
}
if (ret)
*ret = usec;
return 0;
}
int parse_sec(const char *t, usec_t *ret) {
return parse_time(t, ret, USEC_PER_SEC);
}
int parse_sec_fix_0(const char *t, usec_t *ret) {
usec_t k;
int r;
assert(t);
assert(ret);
r = parse_sec(t, &k);
if (r < 0)
return r;
*ret = k == 0 ? USEC_INFINITY : k;
return r;
}
int parse_sec_def_infinity(const char *t, usec_t *ret) {
assert(t);
assert(ret);
t += strspn(t, WHITESPACE);
if (isempty(t)) {
*ret = USEC_INFINITY;
return 0;
}
return parse_sec(t, ret);
}
static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
static const struct {
const char *suffix;
nsec_t nsec;
} table[] = {
{ "seconds", NSEC_PER_SEC },
{ "second", NSEC_PER_SEC },
{ "sec", NSEC_PER_SEC },
{ "s", NSEC_PER_SEC },
{ "minutes", NSEC_PER_MINUTE },
{ "minute", NSEC_PER_MINUTE },
{ "min", NSEC_PER_MINUTE },
{ "months", NSEC_PER_MONTH },
{ "month", NSEC_PER_MONTH },
{ "M", NSEC_PER_MONTH },
{ "msec", NSEC_PER_MSEC },
{ "ms", NSEC_PER_MSEC },
{ "m", NSEC_PER_MINUTE },
{ "hours", NSEC_PER_HOUR },
{ "hour", NSEC_PER_HOUR },
{ "hr", NSEC_PER_HOUR },
{ "h", NSEC_PER_HOUR },
{ "days", NSEC_PER_DAY },
{ "day", NSEC_PER_DAY },
{ "d", NSEC_PER_DAY },
{ "weeks", NSEC_PER_WEEK },
{ "week", NSEC_PER_WEEK },
{ "w", NSEC_PER_WEEK },
{ "years", NSEC_PER_YEAR },
{ "year", NSEC_PER_YEAR },
{ "y", NSEC_PER_YEAR },
{ "usec", NSEC_PER_USEC },
{ "us", NSEC_PER_USEC },
{ "μs", NSEC_PER_USEC }, /* U+03bc (aka GREEK LETTER MU) */
{ "µs", NSEC_PER_USEC }, /* U+b5 (aka MICRO SIGN) */
{ "nsec", 1ULL },
{ "ns", 1ULL },
{ "", 1ULL }, /* default is nsec */
};
assert(p);
assert(ret);
FOREACH_ELEMENT(i, table) {
char *e;
e = startswith(p, i->suffix);
if (e) {
*ret = i->nsec;
return e;
}
}
return p;
}
int parse_nsec(const char *t, nsec_t *ret) {
const char *p, *s;
nsec_t nsec = 0;
bool something = false;
assert(t);
assert(ret);
p = t;
p += strspn(p, WHITESPACE);
s = startswith(p, "infinity");
if (s) {
s += strspn(s, WHITESPACE);
if (*s != 0)
return -EINVAL;
*ret = NSEC_INFINITY;
return 0;
}
for (;;) {
nsec_t multiplier = 1, k;
long long l;
char *e;
p += strspn(p, WHITESPACE);
if (*p == 0) {
if (!something)
return -EINVAL;
break;
}
if (*p == '-') /* Don't allow "-0" */
return -ERANGE;
errno = 0;
l = strtoll(p, &e, 10);
if (errno > 0)
return -errno;
if (l < 0)
return -ERANGE;
if (*e == '.') {
p = e + 1;
p += strspn(p, DIGITS);
} else if (e == p)
return -EINVAL;
else
p = e;
s = extract_nsec_multiplier(p + strspn(p, WHITESPACE), &multiplier);
if (s == p && *s != '\0')
/* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */
return -EINVAL;
p = s;
if ((nsec_t) l >= NSEC_INFINITY / multiplier)
return -ERANGE;
k = (nsec_t) l * multiplier;
if (k >= NSEC_INFINITY - nsec)
return -ERANGE;
nsec += k;
something = true;
if (*e == '.') {
nsec_t m = multiplier / 10;
const char *b;
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (nsec_t) (*b - '0') * m;
if (k >= NSEC_INFINITY - nsec)
return -ERANGE;
nsec += k;
}
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
if (b == e + 1)
return -EINVAL;
}
}
*ret = nsec;
return 0;
}
static int get_timezones_from_zone1970_tab(char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
int r;
assert(ret);
f = fopen("/usr/share/zoneinfo/zone1970.tab", "re");
if (!f)
return -errno;
for (;;) {
_cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
const char *p = line;
/* Line format is:
* 'country codes' 'coordinates' 'timezone' 'comments' */
r = extract_many_words(&p, NULL, 0, &cc, &co, &tz);
if (r < 0)
continue;
/* Lines that start with # are comments. */
if (*cc == '#')
continue;
if (!timezone_is_valid(tz, LOG_DEBUG))
/* Don't list unusable timezones. */
continue;
r = strv_extend(&zones, tz);
if (r < 0)
return r;
}
*ret = TAKE_PTR(zones);
return 0;
}
static int get_timezones_from_tzdata_zi(char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
int r;
assert(ret);
f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
if (!f)
return -errno;
for (;;) {
_cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
const char *p = line;
/* The only lines we care about are Zone and Link lines.
* Zone line format is:
* 'Zone' 'timezone' ...
* Link line format is:
* 'Link' 'target' 'alias'
* See 'man zic' for more detail. */
r = extract_many_words(&p, NULL, 0, &type, &f1, &f2);
if (r < 0)
continue;
char *tz;
if (IN_SET(*type, 'Z', 'z'))
/* Zone lines have timezone in field 1. */
tz = f1;
else if (IN_SET(*type, 'L', 'l'))
/* Link lines have timezone in field 2. */
tz = f2;
else
/* Not a line we care about. */
continue;
if (!timezone_is_valid(tz, LOG_DEBUG))
/* Don't list unusable timezones. */
continue;
r = strv_extend(&zones, tz);
if (r < 0)
return r;
}
*ret = TAKE_PTR(zones);
return 0;
}
int get_timezones(char ***ret) {
_cleanup_strv_free_ char **zones = NULL;
int r;
assert(ret);
r = get_timezones_from_tzdata_zi(&zones);
if (r == -ENOENT) {
log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m");
r = get_timezones_from_zone1970_tab(&zones);
if (r == -ENOENT)
log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m");
}
if (r < 0 && r != -ENOENT)
return r;
/* Always include UTC */
r = strv_extend(&zones, "UTC");
if (r < 0)
return r;
strv_sort_uniq(zones);
*ret = TAKE_PTR(zones);
return 0;
}
int verify_timezone(const char *name, int log_level) {
bool slash = false;
const char *p, *t;
_cleanup_close_ int fd = -EBADF;
char buf[4];
int r;
if (isempty(name))
return -EINVAL;
/* Always accept "UTC" as valid timezone, since it's the fallback, even if user has no timezones installed. */
if (streq(name, "UTC"))
return 0;
if (name[0] == '/')
return -EINVAL;
for (p = name; *p; p++) {
if (!ascii_isdigit(*p) &&
!ascii_isalpha(*p) &&
!IN_SET(*p, '-', '_', '+', '/'))
return -EINVAL;
if (*p == '/') {
if (slash)
return -EINVAL;
slash = true;
} else
slash = false;
}
if (slash)
return -EINVAL;
if (p - name >= PATH_MAX)
return -ENAMETOOLONG;
t = strjoina("/usr/share/zoneinfo/", name);
fd = open(t, O_RDONLY|O_CLOEXEC);
if (fd < 0)
return log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
r = fd_verify_regular(fd);
if (r < 0)
return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
r = loop_read_exact(fd, buf, 4, false);
if (r < 0)
return log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
/* Magic from tzfile(5) */
if (memcmp(buf, "TZif", 4) != 0)
return log_full_errno(log_level, SYNTHETIC_ERRNO(EBADMSG),
"Timezone file '%s' has wrong magic bytes", t);
return 0;
}
void reset_timezonep(char **p) {
assert(p);
(void) set_unset_env("TZ", *p, /* overwrite = */ true);
tzset();
*p = mfree(*p);
}
char* save_timezone(void) {
const char *e = getenv("TZ");
if (!e)
return NULL;
char *s = strdup(e);
if (!s)
log_debug("Failed to save $TZ=%s, unsetting the environment variable.", e);
return s;
}
bool clock_supported(clockid_t clock) {
struct timespec ts;
switch (clock) {
case CLOCK_MONOTONIC:
case CLOCK_REALTIME:
case CLOCK_BOOTTIME:
/* These three are always available in our baseline, and work in timerfd, as of kernel 3.15 */
return true;
default:
/* For everything else, check properly */
return clock_gettime(clock, &ts) >= 0;
}
}
int get_timezone(char **ret) {
_cleanup_free_ char *t = NULL;
int r;
assert(ret);
r = readlink_malloc(etc_localtime(), &t);
if (r == -ENOENT)
/* If the symlink does not exist, assume "UTC", like glibc does */
return strdup_to(ret, "UTC");
if (r < 0)
return r; /* Return EINVAL if not a symlink */
const char *e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/");
if (!e)
return -EINVAL;
if (!timezone_is_valid(e, LOG_DEBUG))
return -EINVAL;
return strdup_to(ret, e);
}
const char* etc_localtime(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_LOCALTIME") ?: "/etc/localtime";
return cached;
}
int mktime_or_timegm_usec(
struct tm *tm, /* input + normalized output */
bool utc,
usec_t *ret) {
time_t t;
assert(tm);
if (tm->tm_year < 69) /* early check for negative (i.e. before 1970) time_t (Note that in some timezones the epoch is in the year 1969!) */
return -ERANGE;
if ((usec_t) tm->tm_year > CONST_MIN(USEC_INFINITY / USEC_PER_YEAR, (usec_t) TIME_T_MAX / (365U * 24U * 60U * 60U)) - 1900) /* early check for possible overrun of usec_t or time_t */
return -ERANGE;
/* timegm()/mktime() is a bit weird to use, since it returns -1 in two cases: on error as well as a
* valid time indicating one second before the UNIX epoch. Let's treat both cases the same here, and
* return -ERANGE for anything negative, since usec_t is unsigned, and we can thus not express
* negative times anyway. */
t = utc ? timegm(tm) : mktime(tm);
if (t < 0) /* Refuse negative times and errors */
return -ERANGE;
if ((usec_t) t >= USEC_INFINITY / USEC_PER_SEC) /* Never return USEC_INFINITY by accident (or overflow) */
return -ERANGE;
if (ret)
*ret = (usec_t) t * USEC_PER_SEC;
return 0;
}
int localtime_or_gmtime_usec(
usec_t t,
bool utc,
struct tm *ret) {
t /= USEC_PER_SEC; /* Round down */
if (t > (usec_t) TIME_T_MAX)
return -ERANGE;
time_t sec = (time_t) t;
struct tm buf = {};
if (!(utc ? gmtime_r(&sec, &buf) : localtime_r(&sec, &buf)))
return -EINVAL;
if (ret)
*ret = buf;
return 0;
}
static uint32_t sysconf_clock_ticks_cached(void) {
static thread_local uint32_t hz = 0;
long r;
if (hz == 0) {
r = sysconf(_SC_CLK_TCK);
assert(r > 0);
hz = r;
}
return hz;
}
uint32_t usec_to_jiffies(usec_t u) {
uint32_t hz = sysconf_clock_ticks_cached();
return DIV_ROUND_UP(u, USEC_PER_SEC / hz);
}
usec_t jiffies_to_usec(uint32_t j) {
uint32_t hz = sysconf_clock_ticks_cached();
return DIV_ROUND_UP(j * USEC_PER_SEC, hz);
}
usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
usec_t a, b;
if (x == USEC_INFINITY)
return USEC_INFINITY;
if (map_clock_id(from) == map_clock_id(to))
return x;
a = now(from);
b = now(to);
if (x > a)
/* x lies in the future */
return usec_add(b, usec_sub_unsigned(x, a));
else
/* x lies in the past */
return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
}
bool in_utc_timezone(void) {
tzset();
return timezone == 0 && daylight == 0;
}
int time_change_fd(void) {
/* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
static const struct itimerspec its = {
.it_value.tv_sec = TIME_T_MAX,
};
_cleanup_close_ int fd = -EBADF;
assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
/* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
* CLOCK_MONOTONIC. */
fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
if (fd < 0)
return -errno;
if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0)
return TAKE_FD(fd);
/* So apparently there are systems where time_t is 64-bit, but the kernel actually doesn't support
* 64-bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a
* similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit
* of a black magic thing though, but what can we do?
*
* We don't want this code on x86-64, hence let's conditionalize this for systems with 64-bit time_t
* but where "long" is shorter than 64-bit, i.e. 32-bit archs.
*
* See: https://github.com/systemd/systemd/issues/14362 */
#if SIZEOF_TIME_T == 8 && ULONG_MAX < UINT64_MAX
if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EOVERFLOW) {
static const struct itimerspec its32 = {
.it_value.tv_sec = INT32_MAX,
};
if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its32, NULL) >= 0)
return TAKE_FD(fd);
}
#endif
return -errno;
}
static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
[TIMESTAMP_PRETTY] = "pretty",
[TIMESTAMP_US] = "us",
[TIMESTAMP_UTC] = "utc",
[TIMESTAMP_US_UTC] = "us+utc",
[TIMESTAMP_UNIX] = "unix",
};
/* Use the macro for enum → string to allow for aliases */
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
/* For the string → enum mapping we use the generic implementation, but also support two aliases */
TimestampStyle timestamp_style_from_string(const char *s) {
TimestampStyle t;
t = (TimestampStyle) string_table_lookup_from_string(timestamp_style_table, ELEMENTSOF(timestamp_style_table), s);
if (t >= 0)
return t;
if (STRPTR_IN_SET(s, "µs", "μs")) /* accept both µ symbols in unicode, i.e. micro symbol + Greek small letter mu. */
return TIMESTAMP_US;
if (STRPTR_IN_SET(s, "µs+utc", "μs+utc"))
return TIMESTAMP_US_UTC;
return t;
}