]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ | |
2 | ||
3 | #include <getopt.h> | |
4 | #include <locale.h> | |
5 | #include <math.h> | |
6 | #include <stdbool.h> | |
7 | #include <stdlib.h> | |
8 | ||
9 | #include "sd-bus.h" | |
10 | ||
11 | #include "bus-error.h" | |
12 | #include "bus-util.h" | |
13 | #include "in-addr-util.h" | |
14 | #include "main-func.h" | |
15 | #include "pager.h" | |
16 | #include "parse-util.h" | |
17 | #include "pretty-print.h" | |
18 | #include "spawn-polkit-agent.h" | |
19 | #include "sparse-endian.h" | |
20 | #include "string-table.h" | |
21 | #include "strv.h" | |
22 | #include "terminal-util.h" | |
23 | #include "util.h" | |
24 | #include "verbs.h" | |
25 | ||
26 | static PagerFlags arg_pager_flags = 0; | |
27 | static bool arg_ask_password = true; | |
28 | static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; | |
29 | static char *arg_host = NULL; | |
30 | static bool arg_adjust_system_clock = false; | |
31 | static bool arg_monitor = false; | |
32 | static char **arg_property = NULL; | |
33 | static bool arg_value = false; | |
34 | static bool arg_all = false; | |
35 | ||
36 | typedef struct StatusInfo { | |
37 | usec_t time; | |
38 | const char *timezone; | |
39 | ||
40 | usec_t rtc_time; | |
41 | bool rtc_local; | |
42 | ||
43 | bool ntp_capable; | |
44 | bool ntp_active; | |
45 | bool ntp_synced; | |
46 | } StatusInfo; | |
47 | ||
48 | static void print_status_info(const StatusInfo *i) { | |
49 | const char *old_tz = NULL, *tz; | |
50 | bool have_time = false; | |
51 | char a[LINE_MAX]; | |
52 | struct tm tm; | |
53 | time_t sec; | |
54 | size_t n; | |
55 | int r; | |
56 | ||
57 | assert(i); | |
58 | ||
59 | /* Save the old $TZ */ | |
60 | tz = getenv("TZ"); | |
61 | if (tz) | |
62 | old_tz = strdupa(tz); | |
63 | ||
64 | /* Set the new $TZ */ | |
65 | if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) | |
66 | log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); | |
67 | else | |
68 | tzset(); | |
69 | ||
70 | if (i->time != 0) { | |
71 | sec = (time_t) (i->time / USEC_PER_SEC); | |
72 | have_time = true; | |
73 | } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { | |
74 | sec = time(NULL); | |
75 | have_time = true; | |
76 | } else | |
77 | log_warning("Could not get time from timedated and not operating locally, ignoring."); | |
78 | ||
79 | if (have_time) { | |
80 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); | |
81 | printf(" Local time: %s\n", n > 0 ? a : "n/a"); | |
82 | ||
83 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); | |
84 | printf(" Universal time: %s\n", n > 0 ? a : "n/a"); | |
85 | } else { | |
86 | printf(" Local time: %s\n", "n/a"); | |
87 | printf(" Universal time: %s\n", "n/a"); | |
88 | } | |
89 | ||
90 | if (i->rtc_time > 0) { | |
91 | time_t rtc_sec; | |
92 | ||
93 | rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); | |
94 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); | |
95 | printf(" RTC time: %s\n", n > 0 ? a : "n/a"); | |
96 | } else | |
97 | printf(" RTC time: %s\n", "n/a"); | |
98 | ||
99 | if (have_time) | |
100 | n = strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm)); | |
101 | ||
102 | /* Restore the $TZ */ | |
103 | if (old_tz) | |
104 | r = setenv("TZ", old_tz, true); | |
105 | else | |
106 | r = unsetenv("TZ"); | |
107 | if (r < 0) | |
108 | log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); | |
109 | else | |
110 | tzset(); | |
111 | ||
112 | printf(" Time zone: %s (%s)\n" | |
113 | "System clock synchronized: %s\n" | |
114 | " NTP service: %s\n" | |
115 | " RTC in local TZ: %s\n", | |
116 | strna(i->timezone), have_time && n > 0 ? a : "n/a", | |
117 | yes_no(i->ntp_synced), | |
118 | i->ntp_capable ? (i->ntp_active ? "active" : "inactive") : "n/a", | |
119 | yes_no(i->rtc_local)); | |
120 | ||
121 | if (i->rtc_local) | |
122 | printf("\n%s" | |
123 | "Warning: The system is configured to read the RTC time in the local time zone.\n" | |
124 | " This mode cannot be fully supported. It will create various problems\n" | |
125 | " with time zone changes and daylight saving time adjustments. The RTC\n" | |
126 | " time is never updated, it relies on external facilities to maintain it.\n" | |
127 | " If at all possible, use RTC in UTC by calling\n" | |
128 | " 'timedatectl set-local-rtc 0'.%s\n", ansi_highlight(), ansi_normal()); | |
129 | } | |
130 | ||
131 | static int show_status(int argc, char **argv, void *userdata) { | |
132 | StatusInfo info = {}; | |
133 | static const struct bus_properties_map map[] = { | |
134 | { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, | |
135 | { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, | |
136 | { "NTP", "b", NULL, offsetof(StatusInfo, ntp_active) }, | |
137 | { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, | |
138 | { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, | |
139 | { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, | |
140 | { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, | |
141 | {} | |
142 | }; | |
143 | ||
144 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
145 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
146 | sd_bus *bus = userdata; | |
147 | int r; | |
148 | ||
149 | assert(bus); | |
150 | ||
151 | r = bus_map_all_properties(bus, | |
152 | "org.freedesktop.timedate1", | |
153 | "/org/freedesktop/timedate1", | |
154 | map, | |
155 | BUS_MAP_BOOLEAN_AS_BOOL, | |
156 | &error, | |
157 | &m, | |
158 | &info); | |
159 | if (r < 0) | |
160 | return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r)); | |
161 | ||
162 | print_status_info(&info); | |
163 | ||
164 | return r; | |
165 | } | |
166 | ||
167 | static int show_properties(int argc, char **argv, void *userdata) { | |
168 | sd_bus *bus = userdata; | |
169 | int r; | |
170 | ||
171 | assert(bus); | |
172 | ||
173 | r = bus_print_all_properties(bus, | |
174 | "org.freedesktop.timedate1", | |
175 | "/org/freedesktop/timedate1", | |
176 | NULL, | |
177 | arg_property, | |
178 | arg_value, | |
179 | arg_all, | |
180 | NULL); | |
181 | if (r < 0) | |
182 | return bus_log_parse_error(r); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int set_time(int argc, char **argv, void *userdata) { | |
188 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
189 | bool relative = false, interactive = arg_ask_password; | |
190 | sd_bus *bus = userdata; | |
191 | usec_t t; | |
192 | int r; | |
193 | ||
194 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); | |
195 | ||
196 | r = parse_timestamp(argv[1], &t); | |
197 | if (r < 0) | |
198 | return log_error_errno(r, "Failed to parse time specification '%s': %m", argv[1]); | |
199 | ||
200 | r = sd_bus_call_method(bus, | |
201 | "org.freedesktop.timedate1", | |
202 | "/org/freedesktop/timedate1", | |
203 | "org.freedesktop.timedate1", | |
204 | "SetTime", | |
205 | &error, | |
206 | NULL, | |
207 | "xbb", (int64_t) t, relative, interactive); | |
208 | if (r < 0) | |
209 | return log_error_errno(r, "Failed to set time: %s", bus_error_message(&error, r)); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | static int set_timezone(int argc, char **argv, void *userdata) { | |
215 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
216 | sd_bus *bus = userdata; | |
217 | int r; | |
218 | ||
219 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); | |
220 | ||
221 | r = sd_bus_call_method(bus, | |
222 | "org.freedesktop.timedate1", | |
223 | "/org/freedesktop/timedate1", | |
224 | "org.freedesktop.timedate1", | |
225 | "SetTimezone", | |
226 | &error, | |
227 | NULL, | |
228 | "sb", argv[1], arg_ask_password); | |
229 | if (r < 0) | |
230 | return log_error_errno(r, "Failed to set time zone: %s", bus_error_message(&error, r)); | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
235 | static int set_local_rtc(int argc, char **argv, void *userdata) { | |
236 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
237 | sd_bus *bus = userdata; | |
238 | int r, b; | |
239 | ||
240 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); | |
241 | ||
242 | b = parse_boolean(argv[1]); | |
243 | if (b < 0) | |
244 | return log_error_errno(b, "Failed to parse local RTC setting '%s': %m", argv[1]); | |
245 | ||
246 | r = sd_bus_call_method(bus, | |
247 | "org.freedesktop.timedate1", | |
248 | "/org/freedesktop/timedate1", | |
249 | "org.freedesktop.timedate1", | |
250 | "SetLocalRTC", | |
251 | &error, | |
252 | NULL, | |
253 | "bbb", b, arg_adjust_system_clock, arg_ask_password); | |
254 | if (r < 0) | |
255 | return log_error_errno(r, "Failed to set local RTC: %s", bus_error_message(&error, r)); | |
256 | ||
257 | return 0; | |
258 | } | |
259 | ||
260 | static int set_ntp(int argc, char **argv, void *userdata) { | |
261 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
262 | sd_bus *bus = userdata; | |
263 | int b, r; | |
264 | ||
265 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); | |
266 | ||
267 | b = parse_boolean(argv[1]); | |
268 | if (b < 0) | |
269 | return log_error_errno(b, "Failed to parse NTP setting '%s': %m", argv[1]); | |
270 | ||
271 | r = sd_bus_call_method(bus, | |
272 | "org.freedesktop.timedate1", | |
273 | "/org/freedesktop/timedate1", | |
274 | "org.freedesktop.timedate1", | |
275 | "SetNTP", | |
276 | &error, | |
277 | NULL, | |
278 | "bb", b, arg_ask_password); | |
279 | if (r < 0) | |
280 | return log_error_errno(r, "Failed to set ntp: %s", bus_error_message(&error, r)); | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | static int list_timezones(int argc, char **argv, void *userdata) { | |
286 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
287 | sd_bus *bus = userdata; | |
288 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
289 | int r; | |
290 | char** zones; | |
291 | ||
292 | r = sd_bus_call_method(bus, | |
293 | "org.freedesktop.timedate1", | |
294 | "/org/freedesktop/timedate1", | |
295 | "org.freedesktop.timedate1", | |
296 | "ListTimezones", | |
297 | &error, | |
298 | &reply, | |
299 | NULL); | |
300 | if (r < 0) | |
301 | return log_error_errno(r, "Failed to request list of time zones: %s", | |
302 | bus_error_message(&error, r)); | |
303 | ||
304 | r = sd_bus_message_read_strv(reply, &zones); | |
305 | if (r < 0) | |
306 | return bus_log_parse_error(r); | |
307 | ||
308 | (void) pager_open(arg_pager_flags); | |
309 | strv_print(zones); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | typedef struct NTPStatusInfo { | |
315 | const char *server_name; | |
316 | char *server_address; | |
317 | usec_t poll_interval, poll_max, poll_min; | |
318 | usec_t root_distance_max; | |
319 | ||
320 | uint32_t leap, version, mode, stratum; | |
321 | int32_t precision; | |
322 | usec_t root_delay, root_dispersion; | |
323 | union { | |
324 | char str[5]; | |
325 | uint32_t val; | |
326 | } reference; | |
327 | usec_t origin, recv, trans, dest; | |
328 | ||
329 | bool spike; | |
330 | uint64_t packet_count; | |
331 | usec_t jitter; | |
332 | ||
333 | int64_t freq; | |
334 | } NTPStatusInfo; | |
335 | ||
336 | static void ntp_status_info_clear(NTPStatusInfo *p) { | |
337 | p->server_address = mfree(p->server_address); | |
338 | } | |
339 | ||
340 | static const char * const ntp_leap_table[4] = { | |
341 | [0] = "normal", | |
342 | [1] = "last minute of the day has 61 seconds", | |
343 | [2] = "last minute of the day has 59 seconds", | |
344 | [3] = "not synchronized", | |
345 | }; | |
346 | ||
347 | #pragma GCC diagnostic push | |
348 | #pragma GCC diagnostic ignored "-Wtype-limits" | |
349 | DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ntp_leap, uint32_t); | |
350 | #pragma GCC diagnostic pop | |
351 | ||
352 | static void print_ntp_status_info(NTPStatusInfo *i) { | |
353 | char ts[FORMAT_TIMESPAN_MAX], tmin[FORMAT_TIMESPAN_MAX], tmax[FORMAT_TIMESPAN_MAX]; | |
354 | usec_t delay, t14, t23, offset, root_distance; | |
355 | bool offset_sign; | |
356 | ||
357 | assert(i); | |
358 | ||
359 | /* | |
360 | * "Timestamp Name ID When Generated | |
361 | * ------------------------------------------------------------ | |
362 | * Originate Timestamp T1 time request sent by client | |
363 | * Receive Timestamp T2 time request received by server | |
364 | * Transmit Timestamp T3 time reply sent by server | |
365 | * Destination Timestamp T4 time reply received by client | |
366 | * | |
367 | * The round-trip delay, d, and system clock offset, t, are defined as: | |
368 | * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" | |
369 | */ | |
370 | ||
371 | printf(" Server: %s (%s)\n", | |
372 | i->server_address, i->server_name); | |
373 | printf("Poll interval: %s (min: %s; max %s)\n", | |
374 | format_timespan(ts, sizeof(ts), i->poll_interval, 0), | |
375 | format_timespan(tmin, sizeof(tmin), i->poll_min, 0), | |
376 | format_timespan(tmax, sizeof(tmax), i->poll_max, 0)); | |
377 | ||
378 | if (i->packet_count == 0) { | |
379 | printf(" Packet count: 0\n"); | |
380 | return; | |
381 | } | |
382 | ||
383 | if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) { | |
384 | log_error("Invalid NTP response"); | |
385 | return; | |
386 | } | |
387 | ||
388 | delay = (i->dest - i->origin) - (i->trans - i->recv); | |
389 | ||
390 | t14 = i->origin + i->dest; | |
391 | t23 = i->recv + i->trans; | |
392 | offset_sign = t14 < t23; | |
393 | offset = (offset_sign ? t23 - t14 : t14 - t23) / 2; | |
394 | ||
395 | root_distance = i->root_delay / 2 + i->root_dispersion; | |
396 | ||
397 | printf(" Leap: %s\n" | |
398 | " Version: %" PRIu32 "\n" | |
399 | " Stratum: %" PRIu32 "\n", | |
400 | ntp_leap_to_string(i->leap), | |
401 | i->version, | |
402 | i->stratum); | |
403 | if (i->stratum <= 1) | |
404 | printf(" Reference: %s\n", i->reference.str); | |
405 | else | |
406 | printf(" Reference: %" PRIX32 "\n", be32toh(i->reference.val)); | |
407 | printf(" Precision: %s (%" PRIi32 ")\n", | |
408 | format_timespan(ts, sizeof(ts), DIV_ROUND_UP((nsec_t) (exp2(i->precision) * NSEC_PER_SEC), NSEC_PER_USEC), 0), | |
409 | i->precision); | |
410 | printf("Root distance: %s (max: %s)\n", | |
411 | format_timespan(ts, sizeof(ts), root_distance, 0), | |
412 | format_timespan(tmax, sizeof(tmax), i->root_distance_max, 0)); | |
413 | printf(" Offset: %s%s\n", | |
414 | offset_sign ? "+" : "-", | |
415 | format_timespan(ts, sizeof(ts), offset, 0)); | |
416 | printf(" Delay: %s\n", | |
417 | format_timespan(ts, sizeof(ts), delay, 0)); | |
418 | printf(" Jitter: %s\n", | |
419 | format_timespan(ts, sizeof(ts), i->jitter, 0)); | |
420 | printf(" Packet count: %" PRIu64 "\n", i->packet_count); | |
421 | ||
422 | if (!i->spike) | |
423 | printf(" Frequency: %+.3fppm\n", | |
424 | (double) i->freq / 0x10000); | |
425 | } | |
426 | ||
427 | static int map_server_address(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { | |
428 | char **p = (char **) userdata; | |
429 | const void *d; | |
430 | int family, r; | |
431 | size_t sz; | |
432 | ||
433 | assert(p); | |
434 | ||
435 | r = sd_bus_message_enter_container(m, 'r', "iay"); | |
436 | if (r < 0) | |
437 | return r; | |
438 | ||
439 | r = sd_bus_message_read(m, "i", &family); | |
440 | if (r < 0) | |
441 | return r; | |
442 | ||
443 | r = sd_bus_message_read_array(m, 'y', &d, &sz); | |
444 | if (r < 0) | |
445 | return r; | |
446 | ||
447 | r = sd_bus_message_exit_container(m); | |
448 | if (r < 0) | |
449 | return r; | |
450 | ||
451 | if (sz == 0 && family == AF_UNSPEC) { | |
452 | *p = mfree(*p); | |
453 | return 0; | |
454 | } | |
455 | ||
456 | if (!IN_SET(family, AF_INET, AF_INET6)) | |
457 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
458 | "Unknown address family %i", family); | |
459 | ||
460 | if (sz != FAMILY_ADDRESS_SIZE(family)) | |
461 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
462 | "Invalid address size"); | |
463 | ||
464 | r = in_addr_to_string(family, d, p); | |
465 | if (r < 0) | |
466 | return r; | |
467 | ||
468 | return 0; | |
469 | } | |
470 | ||
471 | static int map_ntp_message(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { | |
472 | NTPStatusInfo *p = userdata; | |
473 | const void *d; | |
474 | size_t sz; | |
475 | int32_t b; | |
476 | int r; | |
477 | ||
478 | assert(p); | |
479 | ||
480 | r = sd_bus_message_enter_container(m, 'r', "uuuuittayttttbtt"); | |
481 | if (r < 0) | |
482 | return r; | |
483 | ||
484 | r = sd_bus_message_read(m, "uuuuitt", | |
485 | &p->leap, &p->version, &p->mode, &p->stratum, &p->precision, | |
486 | &p->root_delay, &p->root_dispersion); | |
487 | if (r < 0) | |
488 | return r; | |
489 | ||
490 | r = sd_bus_message_read_array(m, 'y', &d, &sz); | |
491 | if (r < 0) | |
492 | return r; | |
493 | ||
494 | r = sd_bus_message_read(m, "ttttbtt", | |
495 | &p->origin, &p->recv, &p->trans, &p->dest, | |
496 | &b, &p->packet_count, &p->jitter); | |
497 | if (r < 0) | |
498 | return r; | |
499 | ||
500 | r = sd_bus_message_exit_container(m); | |
501 | if (r < 0) | |
502 | return r; | |
503 | ||
504 | if (sz != 4) | |
505 | return -EINVAL; | |
506 | ||
507 | memcpy(p->reference.str, d, sz); | |
508 | ||
509 | p->spike = b; | |
510 | ||
511 | return 0; | |
512 | } | |
513 | ||
514 | static int show_timesync_status_once(sd_bus *bus) { | |
515 | static const struct bus_properties_map map_timesync[] = { | |
516 | { "ServerName", "s", NULL, offsetof(NTPStatusInfo, server_name) }, | |
517 | { "ServerAddress", "(iay)", map_server_address, offsetof(NTPStatusInfo, server_address) }, | |
518 | { "PollIntervalUSec", "t", NULL, offsetof(NTPStatusInfo, poll_interval) }, | |
519 | { "PollIntervalMinUSec", "t", NULL, offsetof(NTPStatusInfo, poll_min) }, | |
520 | { "PollIntervalMaxUSec", "t", NULL, offsetof(NTPStatusInfo, poll_max) }, | |
521 | { "RootDistanceMaxUSec", "t", NULL, offsetof(NTPStatusInfo, root_distance_max) }, | |
522 | { "NTPMessage", "(uuuuittayttttbtt)", map_ntp_message, 0 }, | |
523 | { "Frequency", "x", NULL, offsetof(NTPStatusInfo, freq) }, | |
524 | {} | |
525 | }; | |
526 | _cleanup_(ntp_status_info_clear) NTPStatusInfo info = {}; | |
527 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
528 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
529 | int r; | |
530 | ||
531 | assert(bus); | |
532 | ||
533 | r = bus_map_all_properties(bus, | |
534 | "org.freedesktop.timesync1", | |
535 | "/org/freedesktop/timesync1", | |
536 | map_timesync, | |
537 | BUS_MAP_BOOLEAN_AS_BOOL, | |
538 | &error, | |
539 | &m, | |
540 | &info); | |
541 | if (r < 0) | |
542 | return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r)); | |
543 | ||
544 | if (arg_monitor && !terminal_is_dumb()) | |
545 | fputs(ANSI_HOME_CLEAR, stdout); | |
546 | ||
547 | print_ntp_status_info(&info); | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
552 | static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { | |
553 | const char *name; | |
554 | int r; | |
555 | ||
556 | assert(m); | |
557 | ||
558 | r = sd_bus_message_read(m, "s", &name); | |
559 | if (r < 0) | |
560 | return log_error_errno(r, "Failed to read interface name: %m"); | |
561 | ||
562 | if (!streq_ptr(name, "org.freedesktop.timesync1.Manager")) | |
563 | return 0; | |
564 | ||
565 | return show_timesync_status_once(sd_bus_message_get_bus(m)); | |
566 | } | |
567 | ||
568 | static int show_timesync_status(int argc, char **argv, void *userdata) { | |
569 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
570 | sd_bus *bus = userdata; | |
571 | int r; | |
572 | ||
573 | assert(bus); | |
574 | ||
575 | r = show_timesync_status_once(bus); | |
576 | if (r < 0) | |
577 | return r; | |
578 | ||
579 | if (!arg_monitor) | |
580 | return 0; | |
581 | ||
582 | r = sd_event_default(&event); | |
583 | if (r < 0) | |
584 | return log_error_errno(r, "Failed to get event loop: %m"); | |
585 | ||
586 | r = sd_bus_match_signal(bus, | |
587 | NULL, | |
588 | "org.freedesktop.timesync1", | |
589 | "/org/freedesktop/timesync1", | |
590 | "org.freedesktop.DBus.Properties", | |
591 | "PropertiesChanged", | |
592 | on_properties_changed, NULL); | |
593 | if (r < 0) | |
594 | return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m"); | |
595 | ||
596 | r = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL); | |
597 | if (r < 0) | |
598 | return log_error_errno(r, "Failed to attach bus to event loop: %m"); | |
599 | ||
600 | r = sd_event_loop(event); | |
601 | if (r < 0) | |
602 | return log_error_errno(r, "Failed to run event loop: %m"); | |
603 | ||
604 | return 0; | |
605 | } | |
606 | ||
607 | static int print_timesync_property(const char *name, const char *expected_value, sd_bus_message *m, bool value, bool all) { | |
608 | char type; | |
609 | const char *contents; | |
610 | int r; | |
611 | ||
612 | assert(name); | |
613 | assert(m); | |
614 | ||
615 | r = sd_bus_message_peek_type(m, &type, &contents); | |
616 | if (r < 0) | |
617 | return r; | |
618 | ||
619 | switch (type) { | |
620 | ||
621 | case SD_BUS_TYPE_STRUCT: | |
622 | if (streq(name, "NTPMessage")) { | |
623 | _cleanup_(ntp_status_info_clear) NTPStatusInfo i = {}; | |
624 | char ts[FORMAT_TIMESPAN_MAX], stamp[FORMAT_TIMESTAMP_MAX]; | |
625 | ||
626 | r = map_ntp_message(NULL, NULL, m, NULL, &i); | |
627 | if (r < 0) | |
628 | return r; | |
629 | ||
630 | if (i.packet_count == 0) | |
631 | return 1; | |
632 | ||
633 | if (!value) { | |
634 | fputs(name, stdout); | |
635 | fputc('=', stdout); | |
636 | } | |
637 | ||
638 | printf("{ Leap=%u, Version=%u, Mode=%u, Stratum=%u, Precision=%i,", | |
639 | i.leap, i.version, i.mode, i.stratum, i.precision); | |
640 | printf(" RootDelay=%s,", | |
641 | format_timespan(ts, sizeof(ts), i.root_delay, 0)); | |
642 | printf(" RootDispersion=%s,", | |
643 | format_timespan(ts, sizeof(ts), i.root_dispersion, 0)); | |
644 | ||
645 | if (i.stratum == 1) | |
646 | printf(" Reference=%s,", i.reference.str); | |
647 | else | |
648 | printf(" Reference=%" PRIX32 ",", be32toh(i.reference.val)); | |
649 | ||
650 | printf(" OriginateTimestamp=%s,", | |
651 | format_timestamp(stamp, sizeof(stamp), i.origin)); | |
652 | printf(" ReceiveTimestamp=%s,", | |
653 | format_timestamp(stamp, sizeof(stamp), i.recv)); | |
654 | printf(" TransmitTimestamp=%s,", | |
655 | format_timestamp(stamp, sizeof(stamp), i.trans)); | |
656 | printf(" DestinationTimestamp=%s,", | |
657 | format_timestamp(stamp, sizeof(stamp), i.dest)); | |
658 | printf(" Ignored=%s PacketCount=%" PRIu64 ",", | |
659 | yes_no(i.spike), i.packet_count); | |
660 | printf(" Jitter=%s }\n", | |
661 | format_timespan(ts, sizeof(ts), i.jitter, 0)); | |
662 | ||
663 | return 1; | |
664 | ||
665 | } else if (streq(name, "ServerAddress")) { | |
666 | _cleanup_free_ char *str = NULL; | |
667 | ||
668 | r = map_server_address(NULL, NULL, m, NULL, &str); | |
669 | if (r < 0) | |
670 | return r; | |
671 | ||
672 | if (arg_all || !isempty(str)) | |
673 | bus_print_property_value(name, expected_value, value, "%s", str); | |
674 | ||
675 | return 1; | |
676 | } | |
677 | break; | |
678 | } | |
679 | ||
680 | return 0; | |
681 | } | |
682 | ||
683 | static int show_timesync(int argc, char **argv, void *userdata) { | |
684 | sd_bus *bus = userdata; | |
685 | int r; | |
686 | ||
687 | assert(bus); | |
688 | ||
689 | r = bus_print_all_properties(bus, | |
690 | "org.freedesktop.timesync1", | |
691 | "/org/freedesktop/timesync1", | |
692 | print_timesync_property, | |
693 | arg_property, | |
694 | arg_value, | |
695 | arg_all, | |
696 | NULL); | |
697 | if (r < 0) | |
698 | return bus_log_parse_error(r); | |
699 | ||
700 | return 0; | |
701 | } | |
702 | ||
703 | static int help(void) { | |
704 | _cleanup_free_ char *link = NULL; | |
705 | int r; | |
706 | ||
707 | r = terminal_urlify_man("timedatectl", "1", &link); | |
708 | if (r < 0) | |
709 | return log_oom(); | |
710 | ||
711 | printf("%s [OPTIONS...] COMMAND ...\n\n" | |
712 | "Query or change system time and date settings.\n\n" | |
713 | " -h --help Show this help message\n" | |
714 | " --version Show package version\n" | |
715 | " --no-pager Do not pipe output into a pager\n" | |
716 | " --no-ask-password Do not prompt for password\n" | |
717 | " -H --host=[USER@]HOST Operate on remote host\n" | |
718 | " -M --machine=CONTAINER Operate on local container\n" | |
719 | " --adjust-system-clock Adjust system clock when changing local RTC mode\n" | |
720 | " --monitor Monitor status of systemd-timesyncd\n" | |
721 | " -p --property=NAME Show only properties by this name\n" | |
722 | " -a --all Show all properties, including empty ones\n" | |
723 | " --value When showing properties, only print the value\n" | |
724 | "\n" | |
725 | "Commands:\n" | |
726 | " status Show current time settings\n" | |
727 | " show Show properties of systemd-timedated\n" | |
728 | " set-time TIME Set system time\n" | |
729 | " set-timezone ZONE Set system time zone\n" | |
730 | " list-timezones Show known time zones\n" | |
731 | " set-local-rtc BOOL Control whether RTC is in local time\n" | |
732 | " set-ntp BOOL Enable or disable network time synchronization\n" | |
733 | "\n" | |
734 | "systemd-timesyncd Commands:\n" | |
735 | " timesync-status Show status of systemd-timesyncd\n" | |
736 | " show-timesync Show properties of systemd-timesyncd\n" | |
737 | "\nSee the %s for details.\n" | |
738 | , program_invocation_short_name | |
739 | , link | |
740 | ); | |
741 | ||
742 | return 0; | |
743 | } | |
744 | ||
745 | static int verb_help(int argc, char **argv, void *userdata) { | |
746 | return help(); | |
747 | } | |
748 | ||
749 | static int parse_argv(int argc, char *argv[]) { | |
750 | ||
751 | enum { | |
752 | ARG_VERSION = 0x100, | |
753 | ARG_NO_PAGER, | |
754 | ARG_ADJUST_SYSTEM_CLOCK, | |
755 | ARG_NO_ASK_PASSWORD, | |
756 | ARG_MONITOR, | |
757 | ARG_VALUE, | |
758 | }; | |
759 | ||
760 | static const struct option options[] = { | |
761 | { "help", no_argument, NULL, 'h' }, | |
762 | { "version", no_argument, NULL, ARG_VERSION }, | |
763 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
764 | { "host", required_argument, NULL, 'H' }, | |
765 | { "machine", required_argument, NULL, 'M' }, | |
766 | { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, | |
767 | { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, | |
768 | { "monitor", no_argument, NULL, ARG_MONITOR }, | |
769 | { "property", required_argument, NULL, 'p' }, | |
770 | { "all", no_argument, NULL, 'a' }, | |
771 | { "value", no_argument, NULL, ARG_VALUE }, | |
772 | {} | |
773 | }; | |
774 | ||
775 | int c, r; | |
776 | ||
777 | assert(argc >= 0); | |
778 | assert(argv); | |
779 | ||
780 | while ((c = getopt_long(argc, argv, "hH:M:p:a", options, NULL)) >= 0) | |
781 | ||
782 | switch (c) { | |
783 | ||
784 | case 'h': | |
785 | return help(); | |
786 | ||
787 | case ARG_VERSION: | |
788 | return version(); | |
789 | ||
790 | case 'H': | |
791 | arg_transport = BUS_TRANSPORT_REMOTE; | |
792 | arg_host = optarg; | |
793 | break; | |
794 | ||
795 | case 'M': | |
796 | arg_transport = BUS_TRANSPORT_MACHINE; | |
797 | arg_host = optarg; | |
798 | break; | |
799 | ||
800 | case ARG_NO_ASK_PASSWORD: | |
801 | arg_ask_password = false; | |
802 | break; | |
803 | ||
804 | case ARG_ADJUST_SYSTEM_CLOCK: | |
805 | arg_adjust_system_clock = true; | |
806 | break; | |
807 | ||
808 | case ARG_NO_PAGER: | |
809 | arg_pager_flags |= PAGER_DISABLE; | |
810 | break; | |
811 | ||
812 | case ARG_MONITOR: | |
813 | arg_monitor = true; | |
814 | break; | |
815 | ||
816 | case 'p': { | |
817 | r = strv_extend(&arg_property, optarg); | |
818 | if (r < 0) | |
819 | return log_oom(); | |
820 | ||
821 | /* If the user asked for a particular | |
822 | * property, show it to him, even if it is | |
823 | * empty. */ | |
824 | arg_all = true; | |
825 | break; | |
826 | } | |
827 | ||
828 | case 'a': | |
829 | arg_all = true; | |
830 | break; | |
831 | ||
832 | case ARG_VALUE: | |
833 | arg_value = true; | |
834 | break; | |
835 | ||
836 | case '?': | |
837 | return -EINVAL; | |
838 | ||
839 | default: | |
840 | assert_not_reached("Unhandled option"); | |
841 | } | |
842 | ||
843 | return 1; | |
844 | } | |
845 | ||
846 | static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { | |
847 | static const Verb verbs[] = { | |
848 | { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, | |
849 | { "show", VERB_ANY, 1, 0, show_properties }, | |
850 | { "set-time", 2, 2, 0, set_time }, | |
851 | { "set-timezone", 2, 2, 0, set_timezone }, | |
852 | { "list-timezones", VERB_ANY, 1, 0, list_timezones }, | |
853 | { "set-local-rtc", 2, 2, 0, set_local_rtc }, | |
854 | { "set-ntp", 2, 2, 0, set_ntp }, | |
855 | { "timesync-status", VERB_ANY, 1, 0, show_timesync_status }, | |
856 | { "show-timesync", VERB_ANY, 1, 0, show_timesync }, | |
857 | { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ | |
858 | {} | |
859 | }; | |
860 | ||
861 | return dispatch_verb(argc, argv, verbs, bus); | |
862 | } | |
863 | ||
864 | static int run(int argc, char *argv[]) { | |
865 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
866 | int r; | |
867 | ||
868 | setlocale(LC_ALL, ""); | |
869 | log_parse_environment(); | |
870 | log_open(); | |
871 | ||
872 | r = parse_argv(argc, argv); | |
873 | if (r <= 0) | |
874 | return r; | |
875 | ||
876 | r = bus_connect_transport(arg_transport, arg_host, false, &bus); | |
877 | if (r < 0) | |
878 | return log_error_errno(r, "Failed to create bus connection: %m"); | |
879 | ||
880 | return timedatectl_main(bus, argc, argv); | |
881 | } | |
882 | ||
883 | DEFINE_MAIN_FUNCTION(run); |