1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

Merge pull request #34155 from poettering/gmtime-safe

handle gmtime_r() errors more robustly
This commit is contained in:
Yu Watanabe 2024-09-06 09:07:48 +09:00 committed by GitHub
commit ccd62e6d4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 301 additions and 174 deletions

View File

@ -536,8 +536,8 @@ static int write_to_syslog(
char header_priority[2 + DECIMAL_STR_MAX(int) + 1],
header_time[64],
header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1];
time_t t;
struct tm tm;
int r;
if (syslog_fd < 0)
return 0;
@ -547,9 +547,9 @@ static int write_to_syslog(
xsprintf(header_priority, "<%i>", level);
t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
if (!localtime_r(&t, &tm))
return -EINVAL;
r = localtime_or_gmtime_usec(now(CLOCK_REALTIME), /* utc= */ false, &tm);
if (r < 0)
return r;
if (strftime(header_time, sizeof(header_time), "%h %e %T ", &tm) <= 0)
return -EINVAL;

View File

@ -450,24 +450,29 @@ int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eo
support_end = _support_end_alloc;
}
if (isempty(support_end)) /* An empty string is a explicit way to say "no EOL exists" */
if (isempty(support_end)) { /* An empty string is a explicit way to say "no EOL exists" */
if (ret_eol)
*ret_eol = USEC_INFINITY;
return false; /* no end date defined */
}
struct tm tm = {};
const char *k = strptime(support_end, "%Y-%m-%d", &tm);
if (!k || *k)
return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL),
"Failed to parse SUPPORT_END= in os-release file, ignoring: %m");
"Failed to parse SUPPORT_END= from os-release file, ignoring: %s", support_end);
time_t eol = timegm(&tm);
if (eol == (time_t) -1)
return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL),
"Failed to convert SUPPORT_END= in os-release file, ignoring: %m");
usec_t eol;
r = mktime_or_timegm_usec(&tm, /* utc= */ true, &eol);
if (r < 0)
return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
"Failed to convert SUPPORT_END= time from os-release file, ignoring: %m");
if (ret_eol)
*ret_eol = eol * USEC_PER_SEC;
*ret_eol = eol;
return DIV_ROUND_UP(now(CLOCK_REALTIME), USEC_PER_SEC) > (usec_t) eol;
return now(CLOCK_REALTIME) > eol;
}
const char* os_release_pretty_name(const char *pretty_name, const char *name) {

View File

@ -332,7 +332,6 @@ char* format_timestamp_style(
struct tm tm;
bool utc, us;
time_t sec;
size_t n;
assert(buf);
@ -375,9 +374,7 @@ char* format_timestamp_style(
return strcpy(buf, xxx[style]);
}
sec = (time_t) (t / USEC_PER_SEC); /* Round down */
if (!localtime_or_gmtime_r(&sec, &tm, utc))
if (localtime_or_gmtime_usec(t, utc, &tm) < 0)
return NULL;
/* Start with the week day */
@ -665,7 +662,6 @@ static int parse_timestamp_impl(
unsigned fractional = 0;
const char *k;
struct tm tm, copy;
time_t sec;
/* Allowed syntaxes:
*
@ -778,10 +774,9 @@ static int parse_timestamp_impl(
}
}
sec = (time_t) (usec / USEC_PER_SEC);
if (!localtime_or_gmtime_r(&sec, &tm, utc))
return -EINVAL;
r = localtime_or_gmtime_usec(usec, utc, &tm);
if (r < 0)
return r;
tm.tm_isdst = isdst;
@ -939,11 +934,11 @@ from_tm:
} else
minus = gmtoff * USEC_PER_SEC;
sec = mktime_or_timegm(&tm, utc);
if (sec < 0)
return -EINVAL;
r = mktime_or_timegm_usec(&tm, utc, &usec);
if (r < 0)
return r;
usec = usec_add(sec * USEC_PER_SEC, fractional);
usec = usec_add(usec, fractional);
finish:
usec = usec_add(usec, plus);
@ -1625,17 +1620,54 @@ int get_timezone(char **ret) {
return strdup_to(ret, e);
}
time_t mktime_or_timegm(struct tm *tm, bool utc) {
int mktime_or_timegm_usec(
struct tm *tm, /* input + normalized output */
bool utc,
usec_t *ret) {
time_t t;
assert(tm);
return utc ? timegm(tm) : mktime(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;
}
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
assert(t);
assert(tm);
int localtime_or_gmtime_usec(
usec_t t,
bool utc,
struct tm *ret) {
return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
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) {

View File

@ -177,8 +177,8 @@ usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
int get_timezone(char **ret);
time_t mktime_or_timegm(struct tm *tm, bool utc);
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);
int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret);
int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret);
uint32_t usec_to_jiffies(usec_t usec);
usec_t jiffies_to_usec(uint32_t jiffies);

View File

@ -342,14 +342,15 @@ static int get_firmware_date(usec_t *ret) {
.tm_mon = m,
.tm_year = y,
};
time_t v = timegm(&tm);
if (v == (time_t) -1)
return -errno;
usec_t v;
r = mktime_or_timegm_usec(&tm, /* utc= */ true, &v);
if (r < 0)
return r;
if (tm.tm_mday != (int) d || tm.tm_mon != (int) m || tm.tm_year != (int) y)
return -EINVAL; /* date was not normalized? (e.g. "30th of feb") */
*ret = (usec_t) v * USEC_PER_SEC;
*ret = v;
return 0;
}

View File

@ -384,7 +384,6 @@ int curl_parse_http_time(const char *t, usec_t *ret) {
_cleanup_(freelocalep) locale_t loc = (locale_t) 0;
const char *e;
struct tm tm;
time_t v;
assert(t);
assert(ret);
@ -404,10 +403,5 @@ int curl_parse_http_time(const char *t, usec_t *ret) {
if (!e || *e != 0)
return -EINVAL;
v = timegm(&tm);
if (v == (time_t) -1)
return -EINVAL;
*ret = (usec_t) v * USEC_PER_SEC;
return 0;
return mktime_or_timegm_usec(&tm, /* usec= */ true, ret);
}

View File

@ -127,7 +127,6 @@ void server_forward_syslog(Server *s, int priority, const char *identifier, cons
char header_priority[DECIMAL_STR_MAX(priority) + 3], header_time[64],
header_pid[STRLEN("[]: ") + DECIMAL_STR_MAX(pid_t) + 1];
int n = 0;
time_t t;
struct tm tm;
_cleanup_free_ char *ident_buf = NULL;
@ -144,8 +143,7 @@ void server_forward_syslog(Server *s, int priority, const char *identifier, cons
iovec[n++] = IOVEC_MAKE_STRING(header_priority);
/* Second: timestamp */
t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
if (!localtime_r(&t, &tm))
if (localtime_or_gmtime_usec(tv ? tv->tv_sec * USEC_PER_SEC : now(CLOCK_REALTIME), /* utc= */ false, &tm) < 0)
return;
if (strftime(header_time, sizeof(header_time), "%h %e %T ", &tm) <= 0)
return;

View File

@ -793,12 +793,14 @@ static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t alt
static int format_timestamp_dns(char *buf, size_t l, time_t sec) {
struct tm tm;
int r;
assert(buf);
assert(l > STRLEN("YYYYMMDDHHmmSS"));
if (!gmtime_r(&sec, &tm))
return -EINVAL;
r = localtime_or_gmtime_usec(sec * USEC_PER_SEC, /* utc= */ true, &tm);
if (r < 0)
return r;
if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0)
return -EINVAL;

View File

@ -576,9 +576,13 @@ static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
struct tm tm;
int r;
if (!gmtime_r(&time, &tm))
if ((usec_t) time > USEC_INFINITY / USEC_PER_SEC)
return -ERANGE;
r = localtime_or_gmtime_usec((usec_t) time * USEC_PER_SEC, /* utc= */ true, &tm);
if (r < 0)
return r;
if (tm.tm_year > INT_MAX - 1900)
return -ERANGE;
@ -1094,12 +1098,12 @@ int calendar_spec_from_string(const char *p, CalendarSpec **ret) {
}
static int find_end_of_month(const struct tm *tm, bool utc, int day) {
struct tm t = *tm;
struct tm t = *ASSERT_PTR(tm);
t.tm_mon++;
t.tm_mday = 1 - day;
if (mktime_or_timegm(&t, utc) < 0 ||
if (mktime_or_timegm_usec(&t, utc, /* ret= */ NULL) < 0 ||
t.tm_mon != tm->tm_mon)
return -1;
@ -1171,8 +1175,8 @@ static int find_matching_component(
}
static int tm_within_bounds(struct tm *tm, bool utc) {
struct tm t;
int cmp;
int r;
assert(tm);
/*
@ -1183,9 +1187,10 @@ static int tm_within_bounds(struct tm *tm, bool utc) {
if (tm->tm_year + 1900 > MAX_YEAR)
return -ERANGE;
t = *tm;
if (mktime_or_timegm(&t, utc) < 0)
return negative_errno();
struct tm t = *tm;
r = mktime_or_timegm_usec(&t, utc, /* ret= */ NULL);
if (r < 0)
return r;
/*
* Did any normalization take place? If so, it was out of bounds before.
@ -1194,6 +1199,7 @@ static int tm_within_bounds(struct tm *tm, bool utc) {
* out of bounds. Normalization has occurred implies find_matching_component() > 0,
* other sub time units are already reset in find_next().
*/
int cmp;
if ((cmp = CMP(t.tm_year, tm->tm_year)) != 0)
t.tm_mon = 0;
else if ((cmp = CMP(t.tm_mon, tm->tm_mon)) != 0)
@ -1222,7 +1228,7 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
return true;
t = *tm;
if (mktime_or_timegm(&t, utc) < 0)
if (mktime_or_timegm_usec(&t, utc, /* ret= */ NULL) < 0)
return false;
k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
@ -1248,7 +1254,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) {
/* Normalize the current date */
(void) mktime_or_timegm(&c, spec->utc);
(void) mktime_or_timegm_usec(&c, spec->utc, /* ret= */ NULL);
c.tm_isdst = spec->dst;
c.tm_year += 1900;
@ -1354,10 +1360,9 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
}
static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
struct tm tm;
time_t t;
int r;
usec_t tm_usec;
struct tm tm;
int r;
assert(spec);
@ -1365,20 +1370,22 @@ static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, u
return -EINVAL;
usec++;
t = (time_t) (usec / USEC_PER_SEC);
assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
r = localtime_or_gmtime_usec(usec, spec->utc, &tm);
if (r < 0)
return r;
tm_usec = usec % USEC_PER_SEC;
r = find_next(spec, &tm, &tm_usec);
if (r < 0)
return r;
t = mktime_or_timegm(&tm, spec->utc);
if (t < 0)
return -EINVAL;
usec_t t;
r = mktime_or_timegm_usec(&tm, spec->utc, &t);
if (r < 0)
return r;
if (ret_next)
*ret_next = (usec_t) t * USEC_PER_SEC + tm_usec;
*ret_next = t + tm_usec;
return 0;
}

View File

@ -53,11 +53,13 @@ int clock_is_localtime(const char *adjtime_path) {
}
int clock_set_timezone(int *ret_minutesdelta) {
struct timespec ts;
struct tm tm;
int r;
r = localtime_or_gmtime_usec(now(CLOCK_REALTIME), /* utc= */ false, &tm);
if (r < 0)
return r;
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
assert_se(localtime_r(&ts.tv_sec, &tm));
int minutesdelta = tm.tm_gmtoff / 60;
struct timezone tz = {

View File

@ -368,6 +368,7 @@ static int output_timestamp_realtime(
usec_t usec) {
char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)];
int r;
assert(f);
assert(j);
@ -375,65 +376,76 @@ static int output_timestamp_realtime(
if (!VALID_REALTIME(usec))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available.");
if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) {
const char *k;
switch (mode) {
if (flags & OUTPUT_UTC)
k = format_timestamp_style(buf, sizeof(buf), usec, TIMESTAMP_UTC);
else
k = format_timestamp(buf, sizeof(buf), usec);
if (!k)
case OUTPUT_SHORT_FULL:
case OUTPUT_WITH_UNIT: {
if (!format_timestamp_style(buf, sizeof(buf), usec, flags & OUTPUT_UTC ? TIMESTAMP_UTC : TIMESTAMP_PRETTY))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Failed to format timestamp: %" PRIu64, usec);
break;
}
} else {
case OUTPUT_SHORT_UNIX:
xsprintf(buf, "%10" PRI_USEC ".%06" PRI_USEC, usec / USEC_PER_SEC, usec % USEC_PER_SEC);
break;
case OUTPUT_SHORT:
case OUTPUT_SHORT_PRECISE:
case OUTPUT_SHORT_ISO:
case OUTPUT_SHORT_ISO_PRECISE: {
struct tm tm;
time_t t;
size_t tail = 0;
t = (time_t) (usec / USEC_PER_SEC);
switch (mode) {
case OUTPUT_SHORT_UNIX:
xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, usec % USEC_PER_SEC);
break;
case OUTPUT_SHORT_ISO:
case OUTPUT_SHORT_ISO_PRECISE: {
size_t tail = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S",
localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC));
if (tail == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format ISO time.");
/* No usec in strftime, need to append */
if (mode == OUTPUT_SHORT_ISO_PRECISE) {
assert_se(snprintf_ok(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, usec % USEC_PER_SEC));
tail += 7;
}
int h = tm.tm_gmtoff / 60 / 60;
int m = labs((tm.tm_gmtoff / 60) % 60);
snprintf(buf + tail, ELEMENTSOF(buf) - tail, "%+03d:%02d", h, m);
break;
r = localtime_or_gmtime_usec(usec, flags & OUTPUT_UTC, &tm);
if (r < 0)
log_debug_errno(r, "Failed to convert timestamp to calendar time, generating fallback timestamp: %m");
else {
tail = strftime(
buf, sizeof(buf),
IN_SET(mode, OUTPUT_SHORT_ISO, OUTPUT_SHORT_ISO_PRECISE) ? "%Y-%m-%dT%H:%M:%S" : "%b %d %H:%M:%S",
&tm);
if (tail <= 0)
log_debug("Failed to format calendar time, generating fallback timestamp.");
}
case OUTPUT_SHORT:
case OUTPUT_SHORT_PRECISE:
if (tail <= 0) {
/* Generate fallback timestamp if regular formatting didn't work. (This might happen on systems where time_t is 32bit) */
if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S",
localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format syslog time.");
static const char *const xxx[_OUTPUT_MODE_MAX] = {
[OUTPUT_SHORT] = "XXX XX XX:XX:XX",
[OUTPUT_SHORT_PRECISE] = "XXX XX XX:XX:XX.XXXXXX",
[OUTPUT_SHORT_ISO] = "XXXX-XX-XXTXX:XX:XX+XX:XX",
[OUTPUT_SHORT_ISO_PRECISE] = "XXXX-XX-XXTXX:XX:XX.XXXXXX+XX:XX",
};
if (mode == OUTPUT_SHORT_PRECISE) {
assert(sizeof(buf) > strlen(buf));
if (!snprintf_ok(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06"PRIu64, usec % USEC_PER_SEC))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format precise time.");
}
break;
default:
assert_not_reached();
fputs(xxx[mode], f);
return strlen(xxx[mode]);
}
assert(tail <= sizeof(buf));
/* No usec in strftime, need to append */
if (IN_SET(mode, OUTPUT_SHORT_ISO_PRECISE, OUTPUT_SHORT_PRECISE)) {
assert_se(snprintf_ok(buf + tail, sizeof(buf) - tail, ".%06" PRI_USEC, usec % USEC_PER_SEC));
tail += 7;
assert(tail <= sizeof(buf));
}
if (IN_SET(mode, OUTPUT_SHORT_ISO, OUTPUT_SHORT_ISO_PRECISE)) {
int h = tm.tm_gmtoff / 60 / 60,
m = abs((int) ((tm.tm_gmtoff / 60) % 60));
assert_se(snprintf_ok(buf + tail, sizeof(buf) - tail, "%+03d:%02d", h, m));
}
break;
}
default:
assert_not_reached();
}
fputs(buf, f);

View File

@ -58,6 +58,8 @@ int talk_initctl(char rl) {
}
int parse_shutdown_time_spec(const char *t, usec_t *ret) {
int r;
assert(t);
assert(ret);
@ -73,9 +75,6 @@ int parse_shutdown_time_spec(const char *t, usec_t *ret) {
} else {
char *e = NULL;
long hour, minute;
struct tm tm = {};
time_t s;
usec_t n;
errno = 0;
hour = strtol(t, &e, 10);
@ -86,22 +85,26 @@ int parse_shutdown_time_spec(const char *t, usec_t *ret) {
if (errno > 0 || *e != 0 || minute < 0 || minute > 59)
return -EINVAL;
n = now(CLOCK_REALTIME);
s = (time_t) (n / USEC_PER_SEC);
usec_t n = now(CLOCK_REALTIME);
struct tm tm = {};
assert_se(localtime_r(&s, &tm));
r = localtime_or_gmtime_usec(n, /* utc= */ false, &tm);
if (r < 0)
return r;
tm.tm_hour = (int) hour;
tm.tm_min = (int) minute;
tm.tm_sec = 0;
s = mktime(&tm);
assert(s >= 0);
usec_t s;
r = mktime_or_timegm_usec(&tm, /* utc= */ false, &s);
if (r < 0)
return r;
*ret = (usec_t) s * USEC_PER_SEC;
while (s <= n)
s += USEC_PER_DAY;
while (*ret <= n)
*ret += USEC_PER_DAY;
*ret = s;
}
return 0;

View File

@ -721,7 +721,7 @@ static void test_parse_timestamp_impl(const char *tz) {
test_parse_timestamp_one("1996-12-20T00:39:57Z", 0, 851042397 * USEC_PER_SEC + 000000);
test_parse_timestamp_one("1990-12-31T23:59:60Z", 0, 662688000 * USEC_PER_SEC + 000000);
test_parse_timestamp_one("1990-12-31T15:59:60-08:00", 0, 662688000 * USEC_PER_SEC + 000000);
assert_se(parse_timestamp("1937-01-01T12:00:27.87+00:20", NULL) == -EINVAL); /* we don't support pre-epoch timestamps */
assert_se(parse_timestamp("1937-01-01T12:00:27.87+00:20", NULL) == -ERANGE); /* we don't support pre-epoch timestamps */
/* We accept timestamps without seconds as well */
test_parse_timestamp_one("1996-12-20T00:39Z", 0, (851042397 - 57) * USEC_PER_SEC + 000000);
test_parse_timestamp_one("1990-12-31T15:59-08:00", 0, (662688000-60) * USEC_PER_SEC + 000000);
@ -1170,6 +1170,33 @@ TEST(timezone_offset_change) {
tzset();
}
static usec_t absdiff(usec_t a, usec_t b) {
return a > b ? a - b : b - a;
}
TEST(mktime_or_timegm_usec) {
usec_t n = now(CLOCK_REALTIME), m;
struct tm tm;
assert_se(localtime_or_gmtime_usec(n, /* utc= */ false, &tm) >= 0);
assert_se(mktime_or_timegm_usec(&tm, /* utc= */ false, &m) >= 0);
assert_se(absdiff(n, m) < 2 * USEC_PER_DAY);
assert_se(localtime_or_gmtime_usec(n, /* utc= */ true, &tm) >= 0);
assert_se(mktime_or_timegm_usec(&tm, /* utc= */ true, &m) >= 0);
assert_se(absdiff(n, m) < USEC_PER_SEC);
/* This definitely should fail, because we refuse dates before the UNIX epoch */
tm = (struct tm) {
.tm_mday = 15,
.tm_mon = 11,
.tm_year = 1969 - 1900,
};
assert_se(mktime_or_timegm_usec(&tm, /* utc= */ true, NULL) == -ERANGE);
}
static int intro(void) {
/* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */
assert_se(unsetenv("TZ") >= 0);

View File

@ -9,24 +9,23 @@
#include "fd-util.h"
#include "hwclock-util.h"
int hwclock_get(struct tm *ret) {
int hwclock_get(struct tm *tm /* input + output! */) {
_cleanup_close_ int fd = -EBADF;
assert(ret);
assert(tm);
fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC);
if (fd < 0)
return -errno;
/* This leaves the timezone fields of struct ret uninitialized! */
if (ioctl(fd, RTC_RD_TIME, ret) < 0)
if (ioctl(fd, RTC_RD_TIME, tm) < 0)
/* Some drivers return -EINVAL in case the time could not be kept, i.e. power loss
* happened. Let's turn that into a clearly recognizable error */
return errno == EINVAL ? -ENODATA : -errno;
/* We don't know daylight saving, so we reset this in order not
* to confuse mktime(). */
ret->tm_isdst = -1;
/* We don't know daylight saving, so we reset this in order not to confuse mktime(). */
tm->tm_isdst = -1;
return 0;
}

View File

@ -3,5 +3,5 @@
#include <time.h>
int hwclock_get(struct tm *ret);
int hwclock_get(struct tm *tm);
int hwclock_set(const struct tm *tm);

View File

@ -55,7 +55,7 @@ static int print_status_info(const StatusInfo *i) {
char a[LINE_MAX];
TableCell *cell;
struct tm tm;
time_t sec;
usec_t t;
size_t n;
int r;
@ -84,22 +84,38 @@ static int print_status_info(const StatusInfo *i) {
tzset();
if (i->time != 0) {
sec = (time_t) (i->time / USEC_PER_SEC);
t = i->time;
have_time = true;
} else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) {
sec = time(NULL);
t = now(CLOCK_REALTIME);
have_time = true;
} else
log_warning("Could not get time from timedated and not operating locally, ignoring.");
n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) : 0;
if (have_time) {
r = localtime_or_gmtime_usec(t, /* utc= */ false, &tm);
if (r < 0) {
log_warning_errno(r, "Failed to convert system time to local time, ignoring: %m");
n = 0;
} else
n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", &tm);
} else
n = 0;
r = table_add_many(table,
TABLE_FIELD, "Local time",
TABLE_STRING, n > 0 ? a : "n/a");
if (r < 0)
return table_log_add_error(r);
n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) : 0;
if (have_time) {
r = localtime_or_gmtime_usec(t, /* utc= */ true, &tm);
if (r < 0) {
log_warning_errno(r, "Failed to convert system time to universal time, ignoring: %m");
n = 0;
} else
n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", &tm);
} else
n = 0;
r = table_add_many(table,
TABLE_FIELD, "Universal time",
TABLE_STRING, n > 0 ? a : "n/a");
@ -107,10 +123,12 @@ static int print_status_info(const StatusInfo *i) {
return table_log_add_error(r);
if (i->rtc_time > 0) {
time_t rtc_sec;
rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC);
n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm));
r = localtime_or_gmtime_usec(i->rtc_time, /* utc= */ true, &tm);
if (r < 0) {
log_warning_errno(r, "Failed to convert RTC time to universal time, ignoring: %m");
n = 0;
} else
n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", &tm);
} else
n = 0;
r = table_add_many(table,
@ -122,8 +140,15 @@ static int print_status_info(const StatusInfo *i) {
r = table_add_cell(table, NULL, TABLE_FIELD, "Time zone");
if (r < 0)
return table_log_add_error(r);
n = have_time ? strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm)) : 0;
if (have_time) {
r = localtime_or_gmtime_usec(t, /* utc= */ false, &tm);
if (r < 0) {
log_warning_errno(r, "Failed to determine timezone from system time, ignoring: %m");
n = 0;
} else
n = strftime(a, sizeof a, "%Z, %z", &tm);
} else
n = 0;
r = table_add_cell_stringf(table, NULL, "%s (%s)", strna(i->timezone), n > 0 ? a : "n/a");
if (r < 0)
return table_log_add_error(r);

View File

@ -601,8 +601,11 @@ static int property_get_rtc_time(
log_debug("/dev/rtc has no valid time, power loss probably occurred?");
else if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
else
t = (usec_t) timegm(&tm) * USEC_PER_SEC;
else {
r = mktime_or_timegm_usec(&tm, /* utc= */ true, &t);
if (r < 0)
log_warning_errno(r, "Failed to convert RTC time to UNIX time, ignoring: %m");
}
return sd_bus_message_append(reply, "t", t);
}
@ -713,16 +716,17 @@ static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *
log_debug_errno(r, "Failed to tell kernel about timezone, ignoring: %m");
if (c->local_rtc) {
struct timespec ts;
struct tm tm;
/* 4. Sync RTC from system clock, with the new delta */
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
assert_se(localtime_r(&ts.tv_sec, &tm));
r = hwclock_set(&tm);
r = localtime_or_gmtime_usec(now(CLOCK_REALTIME), /* utc= */ false, &tm);
if (r < 0)
log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
log_debug_errno(r, "Failed to convert system time to calendar time, ignoring: %m");
else {
r = hwclock_set(&tm);
if (r < 0)
log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
}
}
log_struct(LOG_INFO,
@ -787,32 +791,46 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error
assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
if (fix_system) {
struct tm tm;
struct tm tm = {
.tm_isdst = -1,
};
/* Sync system clock from RTC; first, initialize the timezone fields of struct tm. */
localtime_or_gmtime_r(&ts.tv_sec, &tm, !c->local_rtc);
r = localtime_or_gmtime_usec(timespec_load(&ts), !c->local_rtc, &tm);
if (r < 0)
log_debug_errno(r, "Failed to determine current timezone, ignoring: %m");
/* Override the main fields of struct tm, but not the timezone fields */
r = hwclock_get(&tm);
if (r < 0)
log_debug_errno(r, "Failed to get hardware clock, ignoring: %m");
else {
usec_t t;
/* And set the system clock with this */
ts.tv_sec = mktime_or_timegm(&tm, !c->local_rtc);
if (clock_settime(CLOCK_REALTIME, &ts) < 0)
log_debug_errno(errno, "Failed to update system clock, ignoring: %m");
r = mktime_or_timegm_usec(&tm, !c->local_rtc, &t);
if (r < 0)
log_debug_errno(r, "Failed to convert calendar time to system time, ignoring: %m");
else {
/* We leave the subsecond offset as is! */
ts.tv_sec = t / USEC_PER_SEC;
if (clock_settime(CLOCK_REALTIME, &ts) < 0)
log_debug_errno(errno, "Failed to update system clock, ignoring: %m");
}
}
} else {
struct tm tm;
/* Sync RTC from system clock */
localtime_or_gmtime_r(&ts.tv_sec, &tm, !c->local_rtc);
r = hwclock_set(&tm);
r = localtime_or_gmtime_usec(timespec_load(&ts), !c->local_rtc, &tm);
if (r < 0)
log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
log_debug_errno(r, "Failed to convert time to calendar time, ignoring: %m");
else {
r = hwclock_set(&tm);
if (r < 0)
log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
}
}
log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
@ -826,7 +844,6 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error
static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
sd_bus *bus = sd_bus_message_get_bus(m);
char buf[FORMAT_TIMESTAMP_MAX];
int relative, interactive, r;
Context *c = ASSERT_PTR(userdata);
int64_t utc;
@ -901,16 +918,19 @@ static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *erro
}
/* Sync down to RTC */
localtime_or_gmtime_r(&ts.tv_sec, &tm, !c->local_rtc);
r = hwclock_set(&tm);
r = localtime_or_gmtime_usec(timespec_load(&ts), !c->local_rtc, &tm);
if (r < 0)
log_debug_errno(r, "Failed to update hardware clock, ignoring: %m");
log_debug_errno(r, "Failed to convert timestamp to calendar time, ignoring: %m");
else {
r = hwclock_set(&tm);
if (r < 0)
log_debug_errno(r, "Failed to update hardware clock, ignoring: %m");
}
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR,
"REALTIME="USEC_FMT, timespec_load(&ts),
LOG_MESSAGE("Changed local time to %s", strnull(format_timestamp(buf, sizeof(buf), timespec_load(&ts)))));
LOG_MESSAGE("Changed local time to %s", strnull(FORMAT_TIMESTAMP(timespec_load(&ts)))));
return sd_bus_reply_method_return(m, NULL);
}