mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-22 13:33:56 +03:00
time-sync-wait: add service (#8494)
This one-shot service waits until the kernel time has been set to synchronized.
This commit is contained in:
parent
d9a43665eb
commit
5c3376efcd
@ -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',
|
||||
|
91
man/systemd-time-wait-sync.service.xml
Normal file
91
man/systemd-time-wait-sync.service.xml
Normal file
@ -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>
|
@ -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
|
||||
|
191
src/time-wait-sync/time-wait-sync.c
Normal file
191
src/time-wait-sync/time-wait-sync.c
Normal file
@ -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;
|
||||
}
|
@ -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/'],
|
||||
|
25
units/systemd-time-wait-sync.service.in
Normal file
25
units/systemd-time-wait-sync.service.in
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user