]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/time-wait-sync/time-wait-sync.c
9b558532ce8eff327747e350f25f553d95fc19f5
[thirdparty/systemd.git] / src / time-wait-sync / time-wait-sync.c
1 /*
2 * systemd service to wait until kernel realtime clock is synchronized
3 *
4 * Copyright 2018 Peter A. Bigot
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20 */
21
22 #include <errno.h>
23 #include <signal.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/timerfd.h>
29 #include <sys/timex.h>
30 #include <unistd.h>
31
32 #include "sd-event.h"
33
34 #include "fd-util.h"
35 #include "missing.h"
36 #include "signal-util.h"
37 #include "time-util.h"
38
39 typedef struct ClockState {
40 int fd; /* non-negative is descriptor from timerfd_create */
41 int adjtime_state; /* return value from last adjtimex(2) call */
42 sd_event_source *event_source; /* non-null is the active io event source */
43 } ClockState;
44
45 static void clock_state_release(ClockState *sp) {
46 sp->event_source = sd_event_source_unref(sp->event_source);
47 sp->fd = safe_close(sp->fd);
48 }
49
50 static int clock_state_update(ClockState *sp, sd_event *event);
51
52 static int io_handler(sd_event_source * s,
53 int fd,
54 uint32_t revents,
55 void *userdata) {
56 ClockState *sp = userdata;
57
58 return clock_state_update(sp, sd_event_source_get_event(s));
59 }
60
61 static int clock_state_update(ClockState *sp,
62 sd_event *event) {
63 static const struct itimerspec its = {
64 .it_value.tv_sec = TIME_T_MAX,
65 };
66 int r;
67 struct timex tx = {};
68 char buf[MAX((size_t)FORMAT_TIMESTAMP_MAX, STRLEN("unrepresentable"))];
69 usec_t t;
70 const char * ts;
71
72 clock_state_release(sp);
73
74 /* The kernel supports cancelling timers whenever its realtime clock is "set" (which can happen in a variety of
75 * ways, generally adjustments of at least 500 ms). The way this module works is we set up a timer that will
76 * wake when the clock is set, and when that happens we read the clock synchronization state from the return
77 * value of adjtimex(2), which supports the NTP time adjustment protocol.
78 *
79 * The kernel determines whether the clock is synchronized using driver-specific tests, based on time
80 * information passed by an application, generally through adjtimex(2). If the application asserts the clock
81 * is synchronized, but does not also do something that "sets the clock", the timer will not be cancelled and
82 * synchronization will not be detected. Should this behavior be observed with a time synchronization provider
83 * this code might be reworked to do a periodic check as well.
84 *
85 * Similarly, this service will never complete if the application sets the time without also providing
86 * information that adjtimex(2) can use to determine that the clock is synchronized.
87 *
88 * Well-behaved implementations including systemd-timesyncd should not produce either situation. For timesyncd
89 * the initial setting of the time uses settimeofday(2), which sets the clock but does not mark it
90 * synchronized. When an NTP source is selected it sets the clock again with clock_adjtime(2) which does mark
91 * it synchronized. */
92 r = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
93 if (r < 0) {
94 log_error_errno(errno, "Failed to create timerfd: %m");
95 goto finish;
96 }
97 sp->fd = r;
98
99 r = timerfd_settime(sp->fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &its, NULL);
100 if (r < 0) {
101 log_error_errno(errno, "Failed to set timerfd conditions: %m");
102 goto finish;
103 }
104
105 r = adjtimex(&tx);
106 if (r < 0) {
107 log_error_errno(errno, "Failed to read adjtimex state: %m");
108 goto finish;
109 }
110 sp->adjtime_state = r;
111
112 if (tx.status & STA_NANO)
113 tx.time.tv_usec /= 1000;
114 t = timeval_load(&tx.time);
115 ts = format_timestamp_us_utc(buf, sizeof(buf), t);
116 if (!ts)
117 strcpy(buf, "unrepresentable");
118 log_info("adjtime state %d status %x time %s", sp->adjtime_state, tx.status, ts);
119
120 if (sp->adjtime_state == TIME_ERROR) {
121 /* Not synchronized. Do a one-shot wait on the descriptor and inform the caller we need to keep
122 * running. */
123 r = sd_event_add_io(event, &sp->event_source, sp->fd,
124 EPOLLIN, io_handler, sp);
125 if (r < 0) {
126 log_error_errno(r, "Failed to create time change monitor source: %m");
127 goto finish;
128 }
129 r = 1;
130 } else {
131 /* Synchronized; we can exit. */
132 (void) sd_event_exit(event, 0);
133 r = 0;
134 }
135
136 finish:
137 if (r < 0)
138 (void) sd_event_exit(event, r);
139 return r;
140 }
141
142 int main(int argc, char * argv[]) {
143 int r;
144 _cleanup_(sd_event_unrefp) sd_event *event;
145 ClockState state = {
146 .fd = -1,
147 };
148
149 assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
150
151 r = sd_event_default(&event);
152 if (r < 0) {
153 log_error_errno(r, "Failed to allocate event loop: %m");
154 goto finish;
155 }
156
157 r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
158 if (r < 0) {
159 log_error_errno(r, "Failed to create sigterm event source: %m");
160 goto finish;
161 }
162
163 r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
164 if (r < 0) {
165 log_error_errno(r, "Failed to create sigint event source: %m");
166 goto finish;
167 }
168
169 r = sd_event_set_watchdog(event, true);
170 if (r < 0) {
171 log_error_errno(r, "Failed to create watchdog event source: %m");
172 goto finish;
173 }
174
175 r = clock_state_update(&state, event);
176 if (r > 0) {
177 r = sd_event_loop(event);
178 if (r < 0)
179 log_error_errno(r, "Failed in event loop: %m");
180 else if (state.adjtime_state == TIME_ERROR) {
181 log_error("Event loop terminated without synchronizing");
182 r = -ECANCELED;
183 }
184 }
185
186 finish:
187 clock_state_release(&state);
188 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
189 }