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