diff --git a/man/rules/meson.build b/man/rules/meson.build
index f5c21c2e44..878f560681 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -639,6 +639,7 @@ manpages = [
''],
['systemd-sysusers', '8', ['systemd-sysusers.service'], ''],
['systemd-sysv-generator', '8', [], 'HAVE_SYSV_COMPAT'],
+ ['systemd-time-wait-sync.service', '8', ['systemd-time-wait-sync'], 'ENABLE_TIMESYNCD'],
['systemd-timedated.service', '8', ['systemd-timedated'], 'ENABLE_TIMEDATED'],
['systemd-timesyncd.service', '8', ['systemd-timesyncd'], 'ENABLE_TIMESYNCD'],
['systemd-tmpfiles',
diff --git a/man/systemd-time-wait-sync.service.xml b/man/systemd-time-wait-sync.service.xml
new file mode 100644
index 0000000000..cec6689213
--- /dev/null
+++ b/man/systemd-time-wait-sync.service.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+ systemd-time-wait-sync.service
+ systemd
+
+
+
+ Developer
+ Peter
+ Bigot
+ pab@pabigot.com
+
+
+
+
+
+ systemd-time-wait-sync.service
+ 8
+
+
+
+ systemd-time-wait-sync.service
+ systemd-time-wait-sync
+ Wait Until Kernel Time Synchronized
+
+
+
+ systemd-time-wait-sync.service
+ /usr/lib/systemd/systemd-time-wait-sync
+
+
+
+ Description
+
+ systemd-time-wait-sync is a system service that delays the start of units that depend on
+ time-sync.target until systemd-timesyncd.service or something else has
+ set the system time and marked it as synchronized. Reaching this state generally requires synchronization with an
+ external source, such as an NTP server.
+
+ When this unit is not enabled the time-sync.target synchronization point may be reached
+ as soon as the system time is advanced by systemd-timesyncd.service to the time stored at the
+ last shutdown. That time may not meet the expectations of dependent services that require an accurate
+ clock.
+
+
+
+
+ Notes
+
+ This service works correctly with a time synchronization service like
+ systemd-timesyncd.service that uses the same protocol as NTP to set the time from a
+ synchronized source. When used with time synchronization services that follow a different protocol the event of
+ setting synchronized time may not be detected in which case this service will not complete.
+
+
+
+ See Also
+
+ systemd1,
+ systemd.special7,
+ systemd-timesyncd.service8,
+
+
+
+
diff --git a/meson.build b/meson.build
index a381db8ebf..642ccd4491 100644
--- a/meson.build
+++ b/meson.build
@@ -1303,6 +1303,7 @@ includes = include_directories('src/basic',
'src/journal',
'src/resolve',
'src/timesync',
+ 'src/time-wait-sync',
'src/login',
'src/udev',
'src/libudev',
@@ -1885,6 +1886,14 @@ if conf.get('ENABLE_TIMESYNCD') == 1
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
+
+ executable('systemd-time-wait-sync',
+ 'src/time-wait-sync/time-wait-sync.c',
+ include_directories : includes,
+ link_with : [libshared],
+ install_rpath : rootlibexecdir,
+ install : true,
+ install_dir : rootlibexecdir)
endif
if conf.get('ENABLE_MACHINED') == 1
diff --git a/src/time-wait-sync/time-wait-sync.c b/src/time-wait-sync/time-wait-sync.c
new file mode 100644
index 0000000000..1cd29736aa
--- /dev/null
+++ b/src/time-wait-sync/time-wait-sync.c
@@ -0,0 +1,191 @@
+/*
+ * systemd service to wait until kernel realtime clock is synchronized
+ *
+ * Copyright 2018 Peter A. Bigot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "sd-event.h"
+
+#include "fd-util.h"
+#include "missing.h"
+#include "signal-util.h"
+#include "time-util.h"
+
+typedef struct ClockState {
+ int fd; /* non-negative is descriptor from timerfd_create */
+ int adjtime_state; /* return value from last adjtimex(2) call */
+ sd_event_source *event_source; /* non-null is the active io event source */
+} ClockState;
+
+static void clock_state_release(ClockState *sp) {
+ sp->event_source = sd_event_source_unref(sp->event_source);
+ sp->fd = safe_close(sp->fd);
+}
+
+static int clock_state_update(ClockState *sp,
+ sd_event *event);
+
+static int io_handler(sd_event_source * s,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ ClockState *sp = userdata;
+
+ return clock_state_update(sp, sd_event_source_get_event(s));
+}
+
+static int clock_state_update(ClockState *sp,
+ sd_event *event) {
+ static const struct itimerspec its = {
+ .it_value.tv_sec = TIME_T_MAX,
+ };
+ int r;
+ struct timex tx = {};
+ char buf[MAX((size_t)FORMAT_TIMESTAMP_MAX, STRLEN("unrepresentable"))];
+ usec_t t;
+ const char * ts;
+
+ clock_state_release(sp);
+
+ /* The kernel supports cancelling timers whenever its realtime clock is "set" (which can happen in a variety of
+ * ways, generally adjustments of at least 500 ms). The way this module works is we set up a timer that will
+ * wake when it the clock is set, and when that happens we read the clock synchronization state from the return
+ * value of adjtimex(2), which supports the NTP time adjustment protocol.
+ *
+ * The kernel determines whether the clock is synchronized using driver-specific tests, based on time
+ * information passed by an application, generally through adjtimex(2). If the application asserts the clock
+ * is synchronized, but does not also do something that "sets the clock", the timer will not be cancelled and
+ * synchronization will not be detected. Should this behavior be observed with a time synchronization provider
+ * this code might be reworked to do a periodic check as well.
+ *
+ * Similarly, this service will never complete if the application sets the time without also providing
+ * information that adjtimex(2) can use to determine that the clock is synchronized.
+ *
+ * Well-behaved implementations including systemd-timesyncd should not produce either situation. For timesyncd
+ * the initial set of the timestamp uses settimeofday(2), which sets the clock but does not mark it
+ * synchronized. When an NTP source is selected it sets the clock again with clock_adjtime(2) which does mark
+ * it synchronized. */
+ r = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
+ if (r < 0) {
+ log_error_errno(errno, "Failed to create timerfd: %m");
+ goto finish;
+ }
+ sp->fd = r;
+
+ r = timerfd_settime(sp->fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &its, NULL);
+ if (r < 0) {
+ log_error_errno(errno, "Failed to set timerfd conditions: %m");
+ goto finish;
+ }
+
+ r = adjtimex(&tx);
+ if (r < 0) {
+ log_error_errno(errno, "Failed to read adjtimex state: %m");
+ goto finish;
+ }
+ sp->adjtime_state = r;
+
+ if (tx.status & STA_NANO)
+ tx.time.tv_usec /= 1000;
+ t = timeval_load(&tx.time);
+ ts = format_timestamp_us_utc(buf, sizeof(buf), t);
+ if (!ts)
+ strcpy(buf, "unrepresentable");
+ log_info("adjtime state %d status %x time %s", sp->adjtime_state, tx.status, ts);
+
+ if (sp->adjtime_state == TIME_ERROR) {
+ /* Not synchronized. Do a one-shot wait on the descriptor and inform the caller we need to keep
+ * running. */
+ r = sd_event_add_io(event, &sp->event_source, sp->fd,
+ EPOLLIN, io_handler, sp);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create time change monitor source: %m");
+ goto finish;
+ }
+ r = 1;
+ } else {
+ /* Synchronized; we can exit. */
+ (void) sd_event_exit(event, 0);
+ r = 0;
+ }
+
+ finish:
+ if (r < 0)
+ (void) sd_event_exit(event, r);
+ return r;
+}
+
+int main(int argc,
+ char * argv[]) {
+ int r;
+ _cleanup_(sd_event_unrefp) sd_event *event;
+ ClockState state = {
+ .fd = -1,
+ };
+
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
+
+ r = sd_event_default(&event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate event loop: %m");
+ goto finish;
+ }
+
+ r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create sigterm event source: %m");
+ goto finish;
+ }
+
+ r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create sigint event source: %m");
+ goto finish;
+ }
+
+ r = sd_event_set_watchdog(event, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create watchdog event source: %m");
+ goto finish;
+ }
+
+ r = clock_state_update(&state, event);
+ if (r > 0) {
+ r = sd_event_loop(event);
+ if (0 > r)
+ log_error_errno(r, "Failed in event loop: %m");
+ else if (state.adjtime_state == TIME_ERROR) {
+ log_error("Event loop terminated without synchronizing");
+ r = -ECANCELED;
+ }
+ }
+
+ finish:
+ clock_state_release(&state);
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/units/meson.build b/units/meson.build
index 0bef71d82b..7977d07690 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -210,6 +210,8 @@ in_units = [
'dbus-org.freedesktop.timedate1.service'],
['systemd-timesyncd.service', 'ENABLE_TIMESYNCD',
join_paths(pkgsysconfdir, 'system/sysinit.target.wants/')],
+ ['systemd-time-wait-sync.service', '',
+ join_paths(pkgsysconfdir, 'system/sysinit.target.wants/')],
['systemd-tmpfiles-clean.service', 'ENABLE_TMPFILES'],
['systemd-tmpfiles-setup-dev.service', 'ENABLE_TMPFILES',
'sysinit.target.wants/'],
diff --git a/units/systemd-time-wait-sync.service.in b/units/systemd-time-wait-sync.service.in
new file mode 100644
index 0000000000..352705d1a1
--- /dev/null
+++ b/units/systemd-time-wait-sync.service.in
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: LGPL-2.1+
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Wait Until Kernel Time Synchronized
+Documentation=man:systemd-time-wait-sync.service(8)
+DefaultDependencies=no
+Before=time-sync.target shutdown.target
+Wants=time-sync.target
+Conflicts=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-time-wait-sync
+TimeoutStartSec=infinity
+RemainAfterExit=yes
+
+[Install]
+WantedBy=sysinit.target