diff --git a/man/systemd-timesyncd.service.xml b/man/systemd-timesyncd.service.xml index 9ab4af9763b..34e0a674720 100644 --- a/man/systemd-timesyncd.service.xml +++ b/man/systemd-timesyncd.service.xml @@ -75,10 +75,11 @@ /var/lib/systemd/timesync/clock - The modification time ("mtime") of this file indicates the timestamp of the last successful - synchronization (or at least the systemd build date, in case synchronization was not possible). It - is used to ensure that the system clock remains roughly monotonic across reboots, in case no local - RTC is available. + The modification time ("mtime") of this file is updated on each successful NTP synchronization + or after each SaveIntervalSec= time interval, as specified in + timesyncd.conf5. + At the minimum, it will be set to the systemd build date. It is used to ensure that the system clock + remains roughly monotonic across reboots, in case no local RTC is available. diff --git a/man/timesyncd.conf.xml b/man/timesyncd.conf.xml index 3fd11cea04c..07472cdb39b 100644 --- a/man/timesyncd.conf.xml +++ b/man/timesyncd.conf.xml @@ -103,6 +103,18 @@ Defaults to 30 seconds and must not be smaller than 1 second. + + SaveIntervalSec= + The interval at which the current time is periodically saved to disk, in the absence + of any recent synchronisation from an NTP server. This is especially useful for offline systems + with no local RTC, as it will guarantee that the system clock remains roughly monotonic across + reboots. + + Takes a time interval value. The default unit is seconds, but other units may be specified, see + systemd.time5. + Defaults to 60 seconds. + + diff --git a/src/timesync/timesyncd-gperf.gperf b/src/timesync/timesyncd-gperf.gperf index 556a2e9ba86..731dea12e3a 100644 --- a/src/timesync/timesyncd-gperf.gperf +++ b/src/timesync/timesyncd-gperf.gperf @@ -25,3 +25,4 @@ Time.RootDistanceMaxSec, config_parse_sec, 0, offs Time.PollIntervalMinSec, config_parse_sec, 0, offsetof(Manager, poll_interval_min_usec) Time.PollIntervalMaxSec, config_parse_sec, 0, offsetof(Manager, poll_interval_max_usec) Time.ConnectionRetrySec, config_parse_sec, 0, offsetof(Manager, connection_retry_usec) +Time.SaveIntervalSec, config_parse_sec, 0, offsetof(Manager, save_time_interval_usec) diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index cb5d42b1d3f..1c284f31e3e 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -59,6 +59,7 @@ static int manager_arm_timer(Manager *m, usec_t next); static int manager_clock_watch_setup(Manager *m); static int manager_listen_setup(Manager *m); static void manager_listen_stop(Manager *m); +static int manager_save_time_and_rearm(Manager *m); static double ntp_ts_short_to_d(const struct ntp_ts_short *ts) { return be16toh(ts->sec) + (be16toh(ts->frac) / 65536.0); @@ -303,8 +304,11 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { if (r < 0) return -errno; + r = manager_save_time_and_rearm(m); + if (r < 0) + return r; + /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ - (void) touch("/var/lib/systemd/timesync/clock"); (void) touch("/run/systemd/timesync/synchronized"); m->drift_freq = tmx.freq; @@ -591,7 +595,6 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re m->poll_interval_usec / USEC_PER_SEC); if (!spike) { - m->sync = true; r = manager_adjust_clock(m, offset, leap_sec); if (r < 0) log_error_errno(r, "Failed to call clock_adjtime(): %m"); @@ -942,6 +945,8 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->network_event_source); sd_network_monitor_unref(m->network_monitor); + sd_event_source_unref(m->event_save_time); + sd_resolve_unref(m->resolve); sd_event_unref(m->event); @@ -1104,6 +1109,8 @@ int manager_new(Manager **ret) { m->ratelimit = (RateLimit) { RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST }; + m->save_time_interval_usec = DEFAULT_SAVE_TIME_INTERVAL_USEC; + r = sd_event_default(&m->event); if (r < 0) return r; @@ -1131,3 +1138,60 @@ int manager_new(Manager **ret) { return 0; } + +static int manager_save_time_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *m = userdata; + + assert(m); + + (void) manager_save_time_and_rearm(m); + return 0; +} + +int manager_setup_save_time_event(Manager *m) { + int r; + + assert(m); + assert(!m->event_save_time); + + if (m->save_time_interval_usec == USEC_INFINITY) + return 0; + + /* NB: we'll accumulate scheduling latencies here, but this doesn't matter */ + r = sd_event_add_time_relative( + m->event, &m->event_save_time, + clock_boottime_or_monotonic(), + m->save_time_interval_usec, + 10 * USEC_PER_SEC, + manager_save_time_handler, m); + if (r < 0) + return log_error_errno(r, "Failed to add save time event: %m"); + + (void) sd_event_source_set_description(m->event_save_time, "save-time"); + + return 0; +} + +static int manager_save_time_and_rearm(Manager *m) { + int r; + + assert(m); + + r = touch(CLOCK_FILE); + if (r < 0) + log_debug_errno(r, "Failed to update " CLOCK_FILE ", ignoring: %m"); + + m->save_on_exit = true; + + if (m->save_time_interval_usec != USEC_INFINITY) { + r = sd_event_source_set_time_relative(m->event_save_time, m->save_time_interval_usec); + if (r < 0) + return log_error_errno(r, "Failed to rearm save time event: %m"); + + r = sd_event_source_set_enabled(m->event_save_time, SD_EVENT_ONESHOT); + if (r < 0) + return log_error_errno(r, "Failed to enable save time event: %m"); + } + + return 0; +} diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index 4aa7575a802..aceb0098cef 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -27,7 +27,12 @@ typedef struct Manager Manager; #define NTP_RETRY_INTERVAL_MIN_USEC (15 * USEC_PER_SEC) #define NTP_RETRY_INTERVAL_MAX_USEC (6 * 60 * USEC_PER_SEC) /* 6 minutes */ -#define DEFAULT_CONNECTION_RETRY_USEC (30*USEC_PER_SEC) +#define DEFAULT_CONNECTION_RETRY_USEC (30 * USEC_PER_SEC) + +#define DEFAULT_SAVE_TIME_INTERVAL_USEC (60 * USEC_PER_SEC) + +#define STATE_DIR "/var/lib/systemd/timesync" +#define CLOCK_FILE STATE_DIR "/clock" struct Manager { sd_bus *bus; @@ -83,7 +88,6 @@ struct Manager { /* last change */ bool jumped; - bool sync; int64_t drift_freq; /* watch for time changes */ @@ -100,6 +104,11 @@ struct Manager { struct ntp_msg ntpmsg; struct timespec origin_time, dest_time; bool spike; + + /* save time event */ + sd_event_source *event_save_time; + usec_t save_time_interval_usec; + bool save_on_exit; }; int manager_new(Manager **ret); @@ -113,3 +122,5 @@ void manager_flush_server_names(Manager *m, ServerType t); int manager_connect(Manager *m); void manager_disconnect(Manager *m); + +int manager_setup_save_time_event(Manager *m); diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index e6a2b066873..179562696d2 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -21,9 +21,6 @@ #include "timesyncd-manager.h" #include "user-util.h" -#define STATE_DIR "/var/lib/systemd/timesync" -#define CLOCK_FILE STATE_DIR "/clock" - static int load_clock_timestamp(uid_t uid, gid_t gid) { _cleanup_close_ int fd = -1; usec_t min = TIME_EPOCH * USEC_PER_SEC; @@ -155,6 +152,10 @@ static int run(int argc, char *argv[]) { "STATUS=Daemon is running", NOTIFY_STOPPING); + r = manager_setup_save_time_event(m); + if (r < 0) + return r; + if (network_is_online()) { r = manager_connect(m); if (r < 0) @@ -166,10 +167,10 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to run event loop: %m"); /* if we got an authoritative time, store it in the file system */ - if (m->sync) { + if (m->save_on_exit) { r = touch(CLOCK_FILE); if (r < 0) - log_debug_errno(r, "Failed to touch %s, ignoring: %m", CLOCK_FILE); + log_debug_errno(r, "Failed to touch " CLOCK_FILE ", ignoring: %m"); } return 0; diff --git a/src/timesync/timesyncd.conf.in b/src/timesync/timesyncd.conf.in index d5f29e1598c..df02cb5bd60 100644 --- a/src/timesync/timesyncd.conf.in +++ b/src/timesync/timesyncd.conf.in @@ -18,3 +18,4 @@ #RootDistanceMaxSec=5 #PollIntervalMinSec=32 #PollIntervalMaxSec=2048 +#SaveIntervalSec=60