]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
time-util: rework localtime_or_gmtime() into localtime_or_gmtime_usec() 34155/head
authorLennart Poettering <lennart@poettering.net>
Wed, 28 Aug 2024 12:10:01 +0000 (14:10 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 5 Sep 2024 15:40:25 +0000 (17:40 +0200)
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

15 files changed:
src/basic/log.c
src/basic/os-util.c
src/basic/time-util.c
src/basic/time-util.h
src/hostname/hostnamed.c
src/import/curl-util.c
src/journal/journald-syslog.c
src/resolve/resolved-dns-rr.c
src/shared/calendarspec.c
src/shared/clock-util.c
src/shared/logs-show.c
src/systemctl/systemctl-sysv-compat.c
src/test/test-time-util.c
src/timedate/timedatectl.c
src/timedate/timedated.c

index c3e61ab5d4f33955263053373acf8f00b3650d95..b99f37385cec64dad81df63659188d817992af94 100644 (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;
index 9b31a0d32576a6aba9ce202b5ce82aa4446bf52a..4eec2f60149356a6f9f63474f4d066e4f2832a4b 100644 (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) {
index ed6fdc3e4910f3102678e7a7b5149640a61f90cb..ac2b078225f9ea78a493deba8cfffc261de3ceba 100644 (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) {
index f273770233ad9b6db3b2b177de703f4456ffb598..7d5a1b7b78a923a31864f65393a40fd376bb8f75 100644 (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);
index 74ed6301d901c9b92250c32369e39bf01e0ccc7b..df0ebe8daab210409b9234836f3d100990c956d9 100644 (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;
 }
 
index 4b65f4619fbd1bd114f09686291779ed8946ea1d..cc97ce6b5562209361d3a11919225dbca1f331f6 100644 (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);
 }
index 4ad73ed38717edc421fcc727c3e076b8881caccc..811ff1a52247fc5301a45275c79cea9bf48290d7 100644 (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;
index d04af90cbcd36eb1ea39fac4daf34062976ac9e6..6a4bd6aecdd6a2c26296546689f3eb4ef06b4a98 100644 (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;
index 039080f05213988ec3aa721bd5964885113ff314..5fdae2353669b2861a42090778aa899ced9118c7 100644 (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) {
+        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;
 }
index 9bbfdbae2ebba4d7cf4c19122f8635da45ae5341..8ab2d4ca95f3f9b01aa19d59abc8a73dc12fa90c 100644 (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 = {
index 96955497a292ff854a77c62f608593bc0189ee6a..ec91b43ad56f56704f269fddbac7db61072c0fa3 100644 (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 {
-                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);
index 8ee16eb13f88d9f211f7597709166920da2e2ddd..cb9c43e3dc0a2e310eb6d95dc105293d502b6a8f 100644 (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;
index 9943923be30ac2bbbaf15f62d396806661eb118b..e37a7ff6103bc071699d5f3e7d8c9f7679c7530a 100644 (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);
index 1a1371030ac2e2bfc5a5eb10f0e63bf62d3cd7d5..4dd00b667d9ddf7336045010b0d59be10876e4c2 100644 (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);
index d8b509d13414747b68c86ced25229a6f1b181d16..58e8a0a0dfc3ee8e3ed8fe1baf7f7aaade5a240a 100644 (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);
 }