From 5c3376efcde4ebc0ce38328956371a35e550515b Mon Sep 17 00:00:00 2001 From: "Peter A. Bigot" Date: Wed, 21 Mar 2018 06:42:04 -0500 Subject: [PATCH] time-sync-wait: add service (#8494) This one-shot service waits until the kernel time has been set to synchronized. --- man/rules/meson.build | 1 + man/systemd-time-wait-sync.service.xml | 91 +++++++++++ meson.build | 9 ++ src/time-wait-sync/time-wait-sync.c | 191 ++++++++++++++++++++++++ units/meson.build | 2 + units/systemd-time-wait-sync.service.in | 25 ++++ 6 files changed, 319 insertions(+) create mode 100644 man/systemd-time-wait-sync.service.xml create mode 100644 src/time-wait-sync/time-wait-sync.c create mode 100644 units/systemd-time-wait-sync.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index f5c21c2e446..878f560681f 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 00000000000..cec6689213b --- /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 a381db8ebf6..642ccd4491a 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 00000000000..1cd29736aac --- /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 0bef71d82ba..7977d076905 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 00000000000..352705d1a12 --- /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 -- 2.39.2