From 6f5cf41570776f489967d1a7de18260b2bc9acf9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Aug 2024 14:10:01 +0200 Subject: [PATCH] time-util: rework localtime_or_gmtime() into localtime_or_gmtime_usec() We typically want to deal in usec_t, hence let's change the prototype accordingly, and do proper range checks. Also, make sure are not confused by negative times. Do something similar for mktime_or_timegm(). This is a more comprehensive alternative to #34065 Replaces: #34065 --- src/basic/log.c | 8 +-- src/basic/os-util.c | 21 +++--- src/basic/time-util.c | 70 +++++++++++++----- src/basic/time-util.h | 4 +- src/hostname/hostnamed.c | 11 +-- src/import/curl-util.c | 8 +-- src/journal/journald-syslog.c | 4 +- src/resolve/resolved-dns-rr.c | 6 +- src/shared/calendarspec.c | 43 ++++++----- src/shared/clock-util.c | 8 ++- src/shared/logs-show.c | 100 ++++++++++++++------------ src/systemctl/systemctl-sysv-compat.c | 25 ++++--- src/test/test-time-util.c | 29 +++++++- src/timedate/timedatectl.c | 47 +++++++++--- src/timedate/timedated.c | 68 +++++++++++------- 15 files changed, 290 insertions(+), 162 deletions(-) diff --git a/src/basic/log.c b/src/basic/log.c index c3e61ab5d4..b99f37385c 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -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; diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 9b31a0d325..4eec2f6014 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -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) { diff --git a/src/basic/time-util.c b/src/basic/time-util.c index ed6fdc3e49..ac2b078225 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -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) { diff --git a/src/basic/time-util.h b/src/basic/time-util.h index f273770233..7d5a1b7b78 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -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); diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 74ed6301d9..df0ebe8daa 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -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; } diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 4b65f4619f..cc97ce6b55 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -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); } diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 4ad73ed387..811ff1a522 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -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; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index d04af90cbc..6a4bd6aecd 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -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; diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 039080f052..5fdae23536 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -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) { + usec_t tm_usec; struct tm tm; - time_t t; int r; - usec_t tm_usec; 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; } diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c index 9bbfdbae2e..8ab2d4ca95 100644 --- a/src/shared/clock-util.c +++ b/src/shared/clock-util.c @@ -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 = { diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 96955497a2..ec91b43ad5 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -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 { - struct tm tm; - time_t t; + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10" PRI_USEC ".%06" PRI_USEC, usec / USEC_PER_SEC, usec % USEC_PER_SEC); + break; - t = (time_t) (usec / USEC_PER_SEC); + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + case OUTPUT_SHORT_ISO: + case OUTPUT_SHORT_ISO_PRECISE: { + struct tm tm; + size_t tail = 0; - switch (mode) { + 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_UNIX: - xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, usec % USEC_PER_SEC); - break; + if (tail <= 0) { + /* Generate fallback timestamp if regular formatting didn't work. (This might happen on systems where time_t is 32bit) */ - 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; - } + 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", + }; - 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; + fputs(xxx[mode], f); + return strlen(xxx[mode]); } - case OUTPUT_SHORT: - case OUTPUT_SHORT_PRECISE: + assert(tail <= sizeof(buf)); - 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."); + /* 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)); - 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; + 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)); - default: - assert_not_reached(); + assert_se(snprintf_ok(buf + tail, sizeof(buf) - tail, "%+03d:%02d", h, m)); } + + break; + } + + default: + assert_not_reached(); } fputs(buf, f); diff --git a/src/systemctl/systemctl-sysv-compat.c b/src/systemctl/systemctl-sysv-compat.c index 8ee16eb13f..cb9c43e3dc 100644 --- a/src/systemctl/systemctl-sysv-compat.c +++ b/src/systemctl/systemctl-sysv-compat.c @@ -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; diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 9943923be3..e37a7ff610 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -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); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 1a1371030a..4dd00b667d 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -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); diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index d8b509d134..58e8a0a0df 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -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); } -- 2.25.1