Hello systemd community, I am an undergraduate student working on a fix for an epoch timestamp issue in /run/systemd/systemd-units-load that occurs on devices lacking a hardware RTC (e.g., Raspberry Pi). Due to my current setup limitations, I am unable to build and test systemd with this patch, so I would appreciate feedback on its correctness.
***** Problem description ***** The issue is that the function now() and now_nsec() rely on clock_gettime() to fetch the RTC timestamp. This works, but on aforementioned devices clock_gettime() returns an epoch timestamp when called at the earliest boot stage when systemd has just initialized itself and no time synchronization services have been loaded yet. Such a call is made from src/core/manager.c in function manager_ready() where the file /run/systemd/systemd-units-load is created and its timestamp is set using now(), giving the file an extremely old timestamp instead of the actual realtime value. This causes issues with utilities such as mount (which relies on the modification time of /run/systemd/systemd-units-load to track changes to /etc/fstab) where a warning is issued every reboot unless systemd daemon-reload is run. ***** Proposed fix ***** My patch creates three additional functions: - static usec_t get_timesyncd_clock_timestamp(void): returns the modification time of /var/lib/systemd/timesync/clock, or USEC_INFINITY on failure; - static usec_t get_journald_lastlog_timestamp(void): returns the last log timestamp from journald, or USEC_INFINITY on failure; - static usec_t get_recent_timestamp(void): returns one result from either of the above two functions, with preference to the timesyncd timestamp, before falling back to the epoch timestamp. The functions now() and now_nsec() have also been modified to use the timestamp returned by get_recent_timestamp() when clock_id == CLOCK_REALTIME. The system time is not modified in any of the functions, though this may be implemented if deemed necessary by the maintainers. Although this patch doesn't resolve the issue that now() and now_nsec() return an incorrect time during early boot, it mitigates the problem by modifying them to return a more recent value instead. This should remove the false warning produced by mount and other similar programs relying on /run/systemd/systemd-units-load. ***** Request for feedback ***** I’d appreciate it if you could inspect my code and provide feedback on its correctness. Please let me know if any refinements are needed. Patch file has been attached. Best regards, Arijit Kumar Das
From 903acc19492fc9f22db0b6ac3f71a4ce96f39925 Mon Sep 17 00:00:00 2001 From: ArijitKD <arijitkdgit.offic...@gmail.com> Date: Mon, 24 Mar 2025 15:04:17 +0530 Subject: [PATCH] Fixed an issue in time-util.c where the timestamp of epoch is returned by now() and now_nsec() in systems lacking RTC module. Most embedded systems (like the Raspberry Pi) lack a hardware RTC module. In such systems, the file /run/systemd/systemd-units-load has an extremely old timestamp due to the time being reset on a reboot. Since no time synchronization services are loaded at the earliest boot stage when this file is created, now() and now_nsec() returns the epoch timestamp because it uses clock_gettime() to query for the time of the real-time clock, which is reset in this case. This patch ensures that in such cases, a recent timestamp from either the modification time of /var/lib/systemd/timesync/clock or the last log time of journald is used instead of the RTC timestamp, before falling back to the epoch timestamp. The patch fixes a problem where utilities such as mount issue a warning notifying the user that /etc/fstab has been modified, but no such modification has been made since the last boot. Limitations: If /var/lib/systemd/timesync/clock is missing (when, for example, timesyncd is not installed) or there are no journald logs (possible on a freshly installed system), now() and now_nsec() may still fall back to the epoch timestamp, until the system clock is synchronized. This shouldn't be a major issue unless the system completely operates offline. --- src/basic/time-util.c | 92 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index fcbedf9e9d..03fa31d30b 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -5,6 +5,7 @@ #include <limits.h> #include <stdlib.h> #include <sys/mman.h> +#include <sys/stat.h> #include <sys/time.h> #include <sys/timerfd.h> #include <sys/types.h> @@ -21,6 +22,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "sd-journal.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" @@ -47,20 +49,102 @@ static clockid_t map_clock_id(clockid_t c) { } } +static usec_t get_timesyncd_clock_timestamp(void) { + struct stat st; + + if (stat("/var/lib/systemd/timesync/clock", &st) == -1) + return USEC_INFINITY; + + return (usec_t)(st.st_mtim.tv_sec * 1000000ULL + + st.st_mtim.tv_nsec / 1000ULL); +} + +static usec_t get_journald_lastlog_timestamp(void) { + sd_journal *j = NULL; + usec_t timestamp; + int ret; + + ret = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (ret < 0) + goto error; + + ret = sd_journal_seek_tail(j); + if (ret < 0) + goto error; + + ret = sd_journal_previous(j); + if (ret < 0) + goto error; + + ret = sd_journal_get_realtime_usec(j, ×tamp); + if (ret < 0) + goto error; + + sd_journal_close(j); + return timestamp; + +error: + sd_journal_close(j); + return USEC_INFINITY; + +} + +static usec_t get_recent_timestamp(void) { + usec_t timesyncd_ts = get_timesyncd_clock_timestamp(); + + return (timesyncd_ts != USEC_INFINITY) ? + timesyncd_ts : get_journald_lastlog_timestamp(); +} + usec_t now(clockid_t clock_id) { struct timespec ts; + clockid_t clock_id_mapped = map_clock_id(clock_id); + + assert_se(clock_gettime(clock_id_mapped, &ts) == 0); + + usec_t now_timestamp = timespec_load(&ts); + + /* Devices that lack a hardware RTC will yield an incorrect time when clock_gettime() is + * called without the system clock being synced by time-sync services. This is especially + * a problem during the early boot phase when systemd has just initiated itself and no such + * services have been loaded yet. This may cause /run/systemd/systemd-units-load to have + * an extremely old epoch timestamp. The function get_recent_timestamp() loads a recent + * timestamp from (1) /var/lib/systemd/timesync/clock, and if it is absent then (2) from + * the timestamp of the last journald log. Only after these two cases fail (possible when + * the system boots for the first time), the ancient timestamp is used. + */ - assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); + if (clock_id_mapped == CLOCK_REALTIME) { + usec_t recent_timestamp = get_recent_timestamp(); - return timespec_load(&ts); + if (recent_timestamp != USEC_INFINITY && + now_timestamp < recent_timestamp) + now_timestamp = recent_timestamp; + } + + return now_timestamp; } nsec_t now_nsec(clockid_t clock_id) { struct timespec ts; + clockid_t clock_id_mapped = map_clock_id(clock_id); + + assert_se(clock_gettime(clock_id_mapped, &ts) == 0); + + nsec_t now_nsec_timestamp = timespec_load_nsec(&ts); - assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); + if (clock_id_mapped == CLOCK_REALTIME) { + usec_t recent_timestamp = get_recent_timestamp(); + + nsec_t recent_nsec_timestamp = (recent_timestamp != USEC_INFINITY) ? + (nsec_t)(recent_timestamp * 1000ULL) : NSEC_INFINITY; + + if (recent_nsec_timestamp != NSEC_INFINITY && + now_nsec_timestamp < recent_nsec_timestamp) + now_nsec_timestamp = recent_nsec_timestamp; + } - return timespec_load_nsec(&ts); + return now_nsec_timestamp; } dual_timestamp* dual_timestamp_now(dual_timestamp *ts) { -- 2.48.1