]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/time-wait-sync/time-wait-sync.c
time-wait-sync: use watchfile to coordinate with timesyncd
[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/inotify.h>
29 #include <sys/timerfd.h>
30 #include <sys/timex.h>
31 #include <unistd.h>
32
33 #include "sd-event.h"
34
35 #include "fd-util.h"
36 #include "fs-util.h"
37 #include "missing.h"
38 #include "signal-util.h"
39 #include "time-util.h"
40
41 typedef struct ClockState {
42 int timerfd_fd; /* non-negative is descriptor from timerfd_create */
43 int adjtime_state; /* return value from last adjtimex(2) call */
44 sd_event_source *timerfd_event_source; /* non-null is the active io event source */
45 int inotify_fd;
46 sd_event_source *inotify_event_source;
47 int run_systemd_wd;
48 int run_systemd_timesync_wd;
49 bool has_watchfile;
50 } ClockState;
51
52 static void clock_state_release_timerfd(ClockState *sp) {
53 sp->timerfd_event_source = sd_event_source_unref(sp->timerfd_event_source);
54 sp->timerfd_fd = safe_close(sp->timerfd_fd);
55 }
56
57 static void clock_state_release(ClockState *sp) {
58 clock_state_release_timerfd(sp);
59 sp->inotify_event_source = sd_event_source_unref(sp->inotify_event_source);
60 sp->inotify_fd = safe_close(sp->inotify_fd);
61 }
62
63 static int clock_state_update(ClockState *sp, sd_event *event);
64
65 static int update_notify_run_systemd_timesync(ClockState *sp) {
66 sp->run_systemd_timesync_wd = inotify_add_watch(sp->inotify_fd, "/run/systemd/timesync", IN_CREATE|IN_DELETE_SELF);
67 return sp->run_systemd_timesync_wd;
68 }
69
70 static int timerfd_handler(sd_event_source *s,
71 int fd,
72 uint32_t revents,
73 void *userdata) {
74 ClockState *sp = userdata;
75
76 return clock_state_update(sp, sd_event_source_get_event(s));
77 }
78
79 static void process_inotify_event(sd_event *event, ClockState *sp, struct inotify_event *e) {
80 if (e->wd == sp->run_systemd_wd) {
81 /* Only thing we care about is seeing if we can start watching /run/systemd/timesync. */
82 if (sp->run_systemd_timesync_wd < 0)
83 update_notify_run_systemd_timesync(sp);
84 } else if (e->wd == sp->run_systemd_timesync_wd) {
85 if (e->mask & IN_DELETE_SELF) {
86 /* Somebody removed /run/systemd/timesync. */
87 (void) inotify_rm_watch(sp->inotify_fd, sp->run_systemd_timesync_wd);
88 sp->run_systemd_timesync_wd = -1;
89 } else
90 /* Somebody might have created /run/systemd/timesync/synchronized. */
91 clock_state_update(sp, event);
92 }
93 }
94
95 static int inotify_handler(sd_event_source *s,
96 int fd,
97 uint32_t revents,
98 void *userdata) {
99 sd_event *event = sd_event_source_get_event(s);
100 ClockState *sp = userdata;
101 union inotify_event_buffer buffer;
102 struct inotify_event *e;
103 ssize_t l;
104
105 l = read(fd, &buffer, sizeof(buffer));
106 if (l < 0) {
107 if (IN_SET(errno, EAGAIN, EINTR))
108 return 0;
109
110 return log_warning_errno(errno, "Lost access to inotify: %m");
111 }
112 FOREACH_INOTIFY_EVENT(e, buffer, l)
113 process_inotify_event(event, sp, e);
114
115 return 0;
116 }
117
118 static int clock_state_update(ClockState *sp,
119 sd_event *event) {
120 static const struct itimerspec its = {
121 .it_value.tv_sec = TIME_T_MAX,
122 };
123 int r;
124 struct timex tx = {};
125 char buf[MAX((size_t)FORMAT_TIMESTAMP_MAX, STRLEN("unrepresentable"))];
126 usec_t t;
127 const char * ts;
128
129 clock_state_release_timerfd(sp);
130
131 /* The kernel supports cancelling timers whenever its realtime clock is "set" (which can happen in a variety of
132 * ways, generally adjustments of at least 500 ms). The way this module works is we set up a timerfd that will
133 * wake when the clock is set, and when that happens we read the clock synchronization state from the return
134 * value of adjtimex(2), which supports the NTP time adjustment protocol.
135 *
136 * The kernel determines whether the clock is synchronized using driver-specific tests, based on time
137 * information passed by an application, generally through adjtimex(2). If the application asserts the clock is
138 * synchronized, but does not also do something that "sets the clock", the timer will not be cancelled and
139 * synchronization will not be detected.
140 *
141 * Similarly, this service will never complete if the application sets the time without also providing
142 * information that adjtimex(2) can use to determine that the clock is synchronized. This generally doesn't
143 * happen, but can if the system has a hardware clock that is accurate enough that the adjustment is too small
144 * to be a "set".
145 *
146 * Both these failure-to-detect situations are covered by having the presence/creation of
147 * /run/systemd/timesync/synchronized, which is considered sufficient to indicate a synchronized clock even if
148 * the kernel has not been updated.
149 *
150 * For timesyncd the initial setting of the time uses settimeofday(2), which sets the clock but does not mark
151 * it synchronized. When an NTP source is selected it sets the clock again with clock_adjtime(2) which marks it
152 * synchronized and also touches /run/systemd/timesync/synchronized which covers the case when the clock wasn't
153 * "set". */
154 r = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
155 if (r < 0) {
156 log_error_errno(errno, "Failed to create timerfd: %m");
157 goto finish;
158 }
159 sp->timerfd_fd = r;
160
161 r = timerfd_settime(sp->timerfd_fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &its, NULL);
162 if (r < 0) {
163 log_error_errno(errno, "Failed to set timerfd conditions: %m");
164 goto finish;
165 }
166
167 r = adjtimex(&tx);
168 if (r < 0) {
169 log_error_errno(errno, "Failed to read adjtimex state: %m");
170 goto finish;
171 }
172 sp->adjtime_state = r;
173
174 if (tx.status & STA_NANO)
175 tx.time.tv_usec /= 1000;
176 t = timeval_load(&tx.time);
177 ts = format_timestamp_us_utc(buf, sizeof(buf), t);
178 if (!ts)
179 strcpy(buf, "unrepresentable");
180 log_info("adjtime state %d status %x time %s", sp->adjtime_state, tx.status, ts);
181
182 sp->has_watchfile = access("/run/systemd/timesync/synchronized", F_OK) >= 0;
183 if (sp->has_watchfile)
184 /* Presence of watch file overrides adjtime_state */
185 r = 0;
186 else if (sp->adjtime_state == TIME_ERROR) {
187 /* Not synchronized. Do a one-shot wait on the descriptor and inform the caller we need to keep
188 * running. */
189 r = sd_event_add_io(event, &sp->timerfd_event_source, sp->timerfd_fd,
190 EPOLLIN, timerfd_handler, sp);
191 if (r < 0) {
192 log_error_errno(r, "Failed to create time change monitor source: %m");
193 goto finish;
194 }
195 r = 1;
196 } else
197 /* Synchronized; we can exit. */
198 r = 0;
199
200 finish:
201 if (r <= 0)
202 (void) sd_event_exit(event, r);
203 return r;
204 }
205
206 int main(int argc, char * argv[]) {
207 int r;
208 _cleanup_(sd_event_unrefp) sd_event *event;
209 ClockState state = {
210 .timerfd_fd = -1,
211 .inotify_fd = -1,
212 .run_systemd_wd = -1,
213 .run_systemd_timesync_wd = -1,
214 };
215
216 assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
217
218 r = sd_event_default(&event);
219 if (r < 0) {
220 log_error_errno(r, "Failed to allocate event loop: %m");
221 goto finish;
222 }
223
224 r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
225 if (r < 0) {
226 log_error_errno(r, "Failed to create sigterm event source: %m");
227 goto finish;
228 }
229
230 r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
231 if (r < 0) {
232 log_error_errno(r, "Failed to create sigint event source: %m");
233 goto finish;
234 }
235
236 r = sd_event_set_watchdog(event, true);
237 if (r < 0) {
238 log_error_errno(r, "Failed to create watchdog event source: %m");
239 goto finish;
240 }
241
242 r = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
243 if (r < 0) {
244 log_error_errno(errno, "Failed to create inotify descriptor: %m");
245 goto finish;
246 }
247 state.inotify_fd = r;
248
249 r = sd_event_add_io(event, &state.inotify_event_source, state.inotify_fd,
250 EPOLLIN, inotify_handler, &state);
251 if (r < 0) {
252 log_error_errno(r, "Failed to create notify event source: %m");
253 goto finish;
254 }
255
256 r = inotify_add_watch(state.inotify_fd, "/run/systemd/", IN_CREATE);
257 if (r < 0) {
258 log_error_errno(errno, "Failed to watch /run/systemd/: %m");
259 goto finish;
260 }
261 state.run_systemd_wd = r;
262
263 (void) update_notify_run_systemd_timesync(&state);
264
265 r = clock_state_update(&state, event);
266 if (r > 0) {
267 r = sd_event_loop(event);
268 if (r < 0)
269 log_error_errno(r, "Failed in event loop: %m");
270 }
271
272 if (state.has_watchfile)
273 log_debug("Exit enabled by: /run/systemd/timesync/synchonized");
274
275 if (state.adjtime_state == TIME_ERROR)
276 log_info("Exit without adjtimex synchronized.");
277
278 finish:
279 clock_state_release(&state);
280 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
281 }