]>
Commit | Line | Data |
---|---|---|
5c3376ef PB |
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> | |
2dd79846 | 28 | #include <sys/inotify.h> |
5c3376ef PB |
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" | |
2dd79846 | 36 | #include "fs-util.h" |
5c3376ef PB |
37 | #include "missing.h" |
38 | #include "signal-util.h" | |
39 | #include "time-util.h" | |
40 | ||
41 | typedef struct ClockState { | |
2dd79846 PB |
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; | |
5c3376ef PB |
50 | } ClockState; |
51 | ||
2dd79846 PB |
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 | ||
5c3376ef | 57 | static void clock_state_release(ClockState *sp) { |
2dd79846 PB |
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); | |
5c3376ef PB |
61 | } |
62 | ||
0fad9daf | 63 | static int clock_state_update(ClockState *sp, sd_event *event); |
5c3376ef | 64 | |
2dd79846 PB |
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) { | |
5c3376ef PB |
74 | ClockState *sp = userdata; |
75 | ||
76 | return clock_state_update(sp, sd_event_source_get_event(s)); | |
77 | } | |
78 | ||
2dd79846 PB |
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 | ||
4f811d27 LP |
118 | static int clock_state_update( |
119 | ClockState *sp, | |
120 | sd_event *event) { | |
121 | ||
5c3376ef | 122 | char buf[MAX((size_t)FORMAT_TIMESTAMP_MAX, STRLEN("unrepresentable"))]; |
4f811d27 | 123 | struct timex tx = {}; |
5c3376ef | 124 | const char * ts; |
4f811d27 LP |
125 | usec_t t; |
126 | int r; | |
5c3376ef | 127 | |
2dd79846 | 128 | clock_state_release_timerfd(sp); |
5c3376ef PB |
129 | |
130 | /* The kernel supports cancelling timers whenever its realtime clock is "set" (which can happen in a variety of | |
2dd79846 | 131 | * ways, generally adjustments of at least 500 ms). The way this module works is we set up a timerfd that will |
0fad9daf | 132 | * wake when the clock is set, and when that happens we read the clock synchronization state from the return |
5c3376ef PB |
133 | * value of adjtimex(2), which supports the NTP time adjustment protocol. |
134 | * | |
135 | * The kernel determines whether the clock is synchronized using driver-specific tests, based on time | |
2dd79846 PB |
136 | * information passed by an application, generally through adjtimex(2). If the application asserts the clock is |
137 | * synchronized, but does not also do something that "sets the clock", the timer will not be cancelled and | |
138 | * synchronization will not be detected. | |
5c3376ef PB |
139 | * |
140 | * Similarly, this service will never complete if the application sets the time without also providing | |
2dd79846 PB |
141 | * information that adjtimex(2) can use to determine that the clock is synchronized. This generally doesn't |
142 | * happen, but can if the system has a hardware clock that is accurate enough that the adjustment is too small | |
143 | * to be a "set". | |
144 | * | |
145 | * Both these failure-to-detect situations are covered by having the presence/creation of | |
146 | * /run/systemd/timesync/synchronized, which is considered sufficient to indicate a synchronized clock even if | |
147 | * the kernel has not been updated. | |
5c3376ef | 148 | * |
2dd79846 PB |
149 | * For timesyncd the initial setting of the time uses settimeofday(2), which sets the clock but does not mark |
150 | * it synchronized. When an NTP source is selected it sets the clock again with clock_adjtime(2) which marks it | |
151 | * synchronized and also touches /run/systemd/timesync/synchronized which covers the case when the clock wasn't | |
152 | * "set". */ | |
5c3376ef | 153 | |
4f811d27 | 154 | r = time_change_fd(); |
5c3376ef | 155 | if (r < 0) { |
4f811d27 | 156 | log_error_errno(r, "Failed to create timerfd: %m"); |
5c3376ef PB |
157 | goto finish; |
158 | } | |
4f811d27 | 159 | sp->timerfd_fd = r; |
5c3376ef PB |
160 | |
161 | r = adjtimex(&tx); | |
162 | if (r < 0) { | |
163 | log_error_errno(errno, "Failed to read adjtimex state: %m"); | |
164 | goto finish; | |
165 | } | |
166 | sp->adjtime_state = r; | |
167 | ||
168 | if (tx.status & STA_NANO) | |
169 | tx.time.tv_usec /= 1000; | |
170 | t = timeval_load(&tx.time); | |
171 | ts = format_timestamp_us_utc(buf, sizeof(buf), t); | |
172 | if (!ts) | |
173 | strcpy(buf, "unrepresentable"); | |
174 | log_info("adjtime state %d status %x time %s", sp->adjtime_state, tx.status, ts); | |
175 | ||
2dd79846 PB |
176 | sp->has_watchfile = access("/run/systemd/timesync/synchronized", F_OK) >= 0; |
177 | if (sp->has_watchfile) | |
178 | /* Presence of watch file overrides adjtime_state */ | |
179 | r = 0; | |
180 | else if (sp->adjtime_state == TIME_ERROR) { | |
181 | /* Not synchronized. Do a one-shot wait on the descriptor and inform the caller we need to keep | |
5c3376ef | 182 | * running. */ |
2dd79846 PB |
183 | r = sd_event_add_io(event, &sp->timerfd_event_source, sp->timerfd_fd, |
184 | EPOLLIN, timerfd_handler, sp); | |
5c3376ef PB |
185 | if (r < 0) { |
186 | log_error_errno(r, "Failed to create time change monitor source: %m"); | |
187 | goto finish; | |
188 | } | |
189 | r = 1; | |
2dd79846 | 190 | } else |
5c3376ef | 191 | /* Synchronized; we can exit. */ |
5c3376ef | 192 | r = 0; |
5c3376ef PB |
193 | |
194 | finish: | |
2dd79846 | 195 | if (r <= 0) |
5c3376ef PB |
196 | (void) sd_event_exit(event, r); |
197 | return r; | |
198 | } | |
199 | ||
0fad9daf | 200 | int main(int argc, char * argv[]) { |
5c3376ef PB |
201 | int r; |
202 | _cleanup_(sd_event_unrefp) sd_event *event; | |
203 | ClockState state = { | |
2dd79846 PB |
204 | .timerfd_fd = -1, |
205 | .inotify_fd = -1, | |
206 | .run_systemd_wd = -1, | |
207 | .run_systemd_timesync_wd = -1, | |
5c3376ef PB |
208 | }; |
209 | ||
210 | assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); | |
211 | ||
212 | r = sd_event_default(&event); | |
213 | if (r < 0) { | |
214 | log_error_errno(r, "Failed to allocate event loop: %m"); | |
215 | goto finish; | |
216 | } | |
217 | ||
218 | r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); | |
219 | if (r < 0) { | |
220 | log_error_errno(r, "Failed to create sigterm event source: %m"); | |
221 | goto finish; | |
222 | } | |
223 | ||
224 | r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); | |
225 | if (r < 0) { | |
226 | log_error_errno(r, "Failed to create sigint event source: %m"); | |
227 | goto finish; | |
228 | } | |
229 | ||
230 | r = sd_event_set_watchdog(event, true); | |
231 | if (r < 0) { | |
232 | log_error_errno(r, "Failed to create watchdog event source: %m"); | |
233 | goto finish; | |
234 | } | |
235 | ||
2dd79846 PB |
236 | r = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); |
237 | if (r < 0) { | |
238 | log_error_errno(errno, "Failed to create inotify descriptor: %m"); | |
239 | goto finish; | |
240 | } | |
241 | state.inotify_fd = r; | |
242 | ||
243 | r = sd_event_add_io(event, &state.inotify_event_source, state.inotify_fd, | |
244 | EPOLLIN, inotify_handler, &state); | |
245 | if (r < 0) { | |
246 | log_error_errno(r, "Failed to create notify event source: %m"); | |
247 | goto finish; | |
248 | } | |
249 | ||
250 | r = inotify_add_watch(state.inotify_fd, "/run/systemd/", IN_CREATE); | |
251 | if (r < 0) { | |
252 | log_error_errno(errno, "Failed to watch /run/systemd/: %m"); | |
253 | goto finish; | |
254 | } | |
255 | state.run_systemd_wd = r; | |
256 | ||
257 | (void) update_notify_run_systemd_timesync(&state); | |
258 | ||
5c3376ef PB |
259 | r = clock_state_update(&state, event); |
260 | if (r > 0) { | |
261 | r = sd_event_loop(event); | |
0fad9daf | 262 | if (r < 0) |
5c3376ef | 263 | log_error_errno(r, "Failed in event loop: %m"); |
5c3376ef PB |
264 | } |
265 | ||
2dd79846 PB |
266 | if (state.has_watchfile) |
267 | log_debug("Exit enabled by: /run/systemd/timesync/synchonized"); | |
268 | ||
269 | if (state.adjtime_state == TIME_ERROR) | |
270 | log_info("Exit without adjtimex synchronized."); | |
271 | ||
5c3376ef PB |
272 | finish: |
273 | clock_state_release(&state); | |
274 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
275 | } |