]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
time-sync-wait: add service (#8494)
authorPeter A. Bigot <pab@pabigot.com>
Wed, 21 Mar 2018 11:42:04 +0000 (06:42 -0500)
committerLennart Poettering <lennart@poettering.net>
Wed, 21 Mar 2018 11:42:04 +0000 (12:42 +0100)
This one-shot service waits until the kernel time has been set to
synchronized.

man/rules/meson.build
man/systemd-time-wait-sync.service.xml [new file with mode: 0644]
meson.build
src/time-wait-sync/time-wait-sync.c [new file with mode: 0644]
units/meson.build
units/systemd-time-wait-sync.service.in [new file with mode: 0644]

index f5c21c2e4468771b54e48a1c64aad370244d43dc..878f560681f40536d6c18980601821a7471398ae 100644 (file)
@@ -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 (file)
index 0000000..cec6689
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  SPDX-License-Identifier: LGPL-2.1+
+
+  This file is part of systemd.
+
+  Copyright 2018 Peter A. Bigot
+
+  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.
+
+  systemd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-time-wait-sync.service" conditional='ENABLE_TIMESYNCD'>
+
+  <refentryinfo>
+    <title>systemd-time-wait-sync.service</title>
+    <productname>systemd</productname>
+
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Peter</firstname>
+        <surname>Bigot</surname>
+        <email>pab@pabigot.com</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-time-wait-sync.service</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-time-wait-sync.service</refname>
+    <refname>systemd-time-wait-sync</refname>
+    <refpurpose>Wait Until Kernel Time Synchronized</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><filename>systemd-time-wait-sync.service</filename></para>
+    <para><filename>/usr/lib/systemd/systemd-time-wait-sync</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><filename>systemd-time-wait-sync</filename> is a system service that delays the start of units that depend on
+    <filename>time-sync.target</filename> until <filename>systemd-timesyncd.service</filename> 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.</para>
+
+    <para>When this unit is not enabled the <filename>time-sync.target</filename> synchronization point may be reached
+    as soon as the system time is advanced by <filename>systemd-timesyncd.service</filename> to the time stored at the
+    last shutdown.  That time may not meet the expectations of dependent services that require an accurate
+    clock.</para>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Notes</title>
+
+    <para>This service works correctly with a time synchronization service like
+    <filename>systemd-timesyncd.service</filename> 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.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-timesyncd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+    </para>
+  </refsect1>
+
+</refentry>
index a381db8ebf6849c3abade38cd6fc57044c6d135e..642ccd4491ae3d0cfc826459af98a33e9096de07 100644 (file)
@@ -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 (file)
index 0000000..1cd2973
--- /dev/null
@@ -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 <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/timerfd.h>
+#include <sys/timex.h>
+#include <unistd.h>
+
+#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;
+}
index 0bef71d82ba8dace49587c6b559b616b70247bba..7977d076905c64048dc74422439294e789ad002d 100644 (file)
@@ -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 (file)
index 0000000..352705d
--- /dev/null
@@ -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