]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
6d0274f1 | 2 | |
6d0274f1 | 3 | #include <getopt.h> |
a9cdc94f | 4 | #include <locale.h> |
6129ec85 | 5 | #include <math.h> |
3f6fd1ba LP |
6 | #include <stdbool.h> |
7 | #include <stdlib.h> | |
6d0274f1 | 8 | |
a281d9c7 | 9 | #include "sd-bus.h" |
3f6fd1ba | 10 | |
a281d9c7 | 11 | #include "bus-error.h" |
3f6fd1ba | 12 | #include "bus-util.h" |
6129ec85 | 13 | #include "in-addr-util.h" |
50378e5d | 14 | #include "main-func.h" |
3f6fd1ba | 15 | #include "pager.h" |
6bedfcbb | 16 | #include "parse-util.h" |
294bf0c3 | 17 | #include "pretty-print.h" |
6d0274f1 | 18 | #include "spawn-polkit-agent.h" |
6129ec85 YW |
19 | #include "sparse-endian.h" |
20 | #include "string-table.h" | |
6d0274f1 | 21 | #include "strv.h" |
288a74cc | 22 | #include "terminal-util.h" |
3f6fd1ba | 23 | #include "util.h" |
be90a886 | 24 | #include "verbs.h" |
6d0274f1 | 25 | |
0221d68a | 26 | static PagerFlags arg_pager_flags = 0; |
6d0274f1 | 27 | static bool arg_ask_password = true; |
e1636421 | 28 | static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; |
7085053a | 29 | static char *arg_host = NULL; |
e1636421 | 30 | static bool arg_adjust_system_clock = false; |
6129ec85 YW |
31 | static bool arg_monitor = false; |
32 | static char **arg_property = NULL; | |
33 | static bool arg_value = false; | |
34 | static bool arg_all = false; | |
6d0274f1 | 35 | |
6d0274f1 | 36 | typedef struct StatusInfo { |
2f6a5907 | 37 | usec_t time; |
f37f8a61 | 38 | const char *timezone; |
2f6a5907 KS |
39 | |
40 | usec_t rtc_time; | |
f37f8a61 | 41 | bool rtc_local; |
2f6a5907 | 42 | |
f37f8a61 | 43 | bool ntp_capable; |
f567f086 | 44 | bool ntp_active; |
f37f8a61 | 45 | bool ntp_synced; |
6d0274f1 LP |
46 | } StatusInfo; |
47 | ||
ffc06c35 | 48 | static void print_status_info(const StatusInfo *i) { |
f567f086 YW |
49 | const char *old_tz = NULL, *tz; |
50 | bool have_time = false; | |
14ce0c25 | 51 | char a[LINE_MAX]; |
6d0274f1 LP |
52 | struct tm tm; |
53 | time_t sec; | |
14ce0c25 | 54 | size_t n; |
f567f086 | 55 | int r; |
6d0274f1 | 56 | |
59965986 LP |
57 | assert(i); |
58 | ||
d95a74ed LP |
59 | /* Save the old $TZ */ |
60 | tz = getenv("TZ"); | |
61 | if (tz) | |
62 | old_tz = strdupa(tz); | |
2311eb2f | 63 | |
d95a74ed | 64 | /* Set the new $TZ */ |
bdeb9e60 | 65 | if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) |
d95a74ed LP |
66 | log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); |
67 | else | |
68 | tzset(); | |
3e5e74d5 | 69 | |
9ff09bcb SL |
70 | if (i->time != 0) { |
71 | sec = (time_t) (i->time / USEC_PER_SEC); | |
72 | have_time = true; | |
d95a74ed | 73 | } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { |
9ff09bcb SL |
74 | sec = time(NULL); |
75 | have_time = true; | |
76 | } else | |
d95a74ed | 77 | log_warning("Could not get time from timedated and not operating locally, ignoring."); |
6d0274f1 | 78 | |
9ff09bcb | 79 | if (have_time) { |
14ce0c25 | 80 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); |
f567f086 | 81 | printf(" Local time: %s\n", n > 0 ? a : "n/a"); |
5ffa8c81 | 82 | |
14ce0c25 | 83 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); |
f567f086 | 84 | printf(" Universal time: %s\n", n > 0 ? a : "n/a"); |
9ff09bcb | 85 | } else { |
f567f086 YW |
86 | printf(" Local time: %s\n", "n/a"); |
87 | printf(" Universal time: %s\n", "n/a"); | |
9ff09bcb | 88 | } |
6d0274f1 | 89 | |
2f6a5907 KS |
90 | if (i->rtc_time > 0) { |
91 | time_t rtc_sec; | |
6d0274f1 | 92 | |
d95a74ed | 93 | rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); |
14ce0c25 | 94 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); |
f567f086 | 95 | printf(" RTC time: %s\n", n > 0 ? a : "n/a"); |
2f6a5907 | 96 | } else |
f567f086 | 97 | printf(" RTC time: %s\n", "n/a"); |
6d0274f1 | 98 | |
5ffa8c81 | 99 | if (have_time) |
14ce0c25 | 100 | n = strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm)); |
2667cc25 | 101 | |
d95a74ed LP |
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 | ||
f567f086 YW |
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", | |
14ce0c25 | 116 | strna(i->timezone), have_time && n > 0 ? a : "n/a", |
2f6a5907 | 117 | yes_no(i->ntp_synced), |
f567f086 | 118 | i->ntp_capable ? (i->ntp_active ? "active" : "inactive") : "n/a", |
2f6a5907 | 119 | yes_no(i->rtc_local)); |
6d0274f1 | 120 | |
2f6a5907 | 121 | if (i->rtc_local) |
54f8c958 LP |
122 | printf("\n%s" |
123 | "Warning: The system is configured to read the RTC time in the local time zone.\n" | |
87ac8d99 | 124 | " This mode cannot be fully supported. It will create various problems\n" |
54f8c958 LP |
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()); | |
6d0274f1 LP |
129 | } |
130 | ||
be90a886 | 131 | static int show_status(int argc, char **argv, void *userdata) { |
f37f8a61 | 132 | StatusInfo info = {}; |
9f6eb1cd | 133 | static const struct bus_properties_map map[] = { |
f567f086 YW |
134 | { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, |
135 | { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, | |
136 | { "NTP", "b", NULL, offsetof(StatusInfo, ntp_active) }, | |
9f6eb1cd | 137 | { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, |
f567f086 YW |
138 | { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, |
139 | { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, | |
140 | { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, | |
ffc06c35 KS |
141 | {} |
142 | }; | |
f9e0eefc LP |
143 | |
144 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
f37f8a61 | 145 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
be90a886 | 146 | sd_bus *bus = userdata; |
ffc06c35 | 147 | int r; |
6d0274f1 | 148 | |
a281d9c7 | 149 | assert(bus); |
6d0274f1 | 150 | |
ffc06c35 KS |
151 | r = bus_map_all_properties(bus, |
152 | "org.freedesktop.timedate1", | |
153 | "/org/freedesktop/timedate1", | |
9f6eb1cd | 154 | map, |
a7e4861c | 155 | BUS_MAP_BOOLEAN_AS_BOOL, |
f9e0eefc | 156 | &error, |
f37f8a61 | 157 | &m, |
9f6eb1cd | 158 | &info); |
e7e55dbd | 159 | if (r < 0) |
f9e0eefc | 160 | return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r)); |
6d0274f1 LP |
161 | |
162 | print_status_info(&info); | |
ffc06c35 | 163 | |
ffc06c35 | 164 | return r; |
6d0274f1 LP |
165 | } |
166 | ||
ead0adb1 YW |
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 | ||
be90a886 | 187 | static int set_time(int argc, char **argv, void *userdata) { |
4afd3348 | 188 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
a281d9c7 | 189 | bool relative = false, interactive = arg_ask_password; |
be90a886 | 190 | sd_bus *bus = userdata; |
6d0274f1 | 191 | usec_t t; |
6d0274f1 LP |
192 | int r; |
193 | ||
8a4b13c5 | 194 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 195 | |
be90a886 YW |
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]); | |
6d0274f1 | 199 | |
a281d9c7 TA |
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, | |
be90a886 | 207 | "xbb", (int64_t) t, relative, interactive); |
a281d9c7 | 208 | if (r < 0) |
4ae25393 | 209 | return log_error_errno(r, "Failed to set time: %s", bus_error_message(&error, r)); |
a281d9c7 | 210 | |
4ae25393 | 211 | return 0; |
6d0274f1 LP |
212 | } |
213 | ||
be90a886 | 214 | static int set_timezone(int argc, char **argv, void *userdata) { |
4afd3348 | 215 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 216 | sd_bus *bus = userdata; |
a281d9c7 | 217 | int r; |
6d0274f1 | 218 | |
8a4b13c5 | 219 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 220 | |
a281d9c7 TA |
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, | |
be90a886 | 228 | "sb", argv[1], arg_ask_password); |
a281d9c7 | 229 | if (r < 0) |
4ae25393 | 230 | return log_error_errno(r, "Failed to set time zone: %s", bus_error_message(&error, r)); |
a281d9c7 | 231 | |
4ae25393 | 232 | return 0; |
6d0274f1 LP |
233 | } |
234 | ||
be90a886 | 235 | static int set_local_rtc(int argc, char **argv, void *userdata) { |
4afd3348 | 236 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 237 | sd_bus *bus = userdata; |
e5609878 | 238 | int r, b; |
6d0274f1 | 239 | |
8a4b13c5 | 240 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 241 | |
be90a886 YW |
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]); | |
6d0274f1 | 245 | |
a281d9c7 TA |
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, | |
e5609878 | 253 | "bbb", b, arg_adjust_system_clock, arg_ask_password); |
a281d9c7 | 254 | if (r < 0) |
4ae25393 | 255 | return log_error_errno(r, "Failed to set local RTC: %s", bus_error_message(&error, r)); |
a281d9c7 | 256 | |
4ae25393 | 257 | return 0; |
6d0274f1 LP |
258 | } |
259 | ||
be90a886 | 260 | static int set_ntp(int argc, char **argv, void *userdata) { |
4afd3348 | 261 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 262 | sd_bus *bus = userdata; |
e5609878 | 263 | int b, r; |
6d0274f1 | 264 | |
8a4b13c5 | 265 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 266 | |
be90a886 YW |
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]); | |
6d0274f1 | 270 | |
a281d9c7 TA |
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, | |
e5609878 | 278 | "bb", b, arg_ask_password); |
a281d9c7 | 279 | if (r < 0) |
4ae25393 | 280 | return log_error_errno(r, "Failed to set ntp: %s", bus_error_message(&error, r)); |
a281d9c7 | 281 | |
4ae25393 | 282 | return 0; |
6d0274f1 LP |
283 | } |
284 | ||
be90a886 | 285 | static int list_timezones(int argc, char **argv, void *userdata) { |
2cf0b2fe NT |
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; | |
75683450 | 289 | int r; |
2cf0b2fe NT |
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)); | |
6d0274f1 | 303 | |
2cf0b2fe | 304 | r = sd_bus_message_read_strv(reply, &zones); |
f647962d | 305 | if (r < 0) |
2cf0b2fe | 306 | return bus_log_parse_error(r); |
6d0274f1 | 307 | |
0221d68a | 308 | (void) pager_open(arg_pager_flags); |
7c2d8094 | 309 | strv_print(zones); |
6d0274f1 LP |
310 | |
311 | return 0; | |
312 | } | |
313 | ||
6129ec85 YW |
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 | ||
baaa35ad ZJS |
456 | if (!IN_SET(family, AF_INET, AF_INET6)) |
457 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
458 | "Unknown address family %i", family); | |
6129ec85 | 459 | |
baaa35ad ZJS |
460 | if (sz != FAMILY_ADDRESS_SIZE(family)) |
461 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
462 | "Invalid address size"); | |
6129ec85 YW |
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 | ||
5d904a6a | 509 | p->spike = b; |
6129ec85 YW |
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 | ||
eda19357 | 607 | static int print_timesync_property(const char *name, const char *expected_value, sd_bus_message *m, bool value, bool all) { |
6129ec85 YW |
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)) | |
eda19357 | 673 | bus_print_property_value(name, expected_value, value, "%s", str); |
6129ec85 YW |
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 | ||
be90a886 | 703 | static int help(void) { |
37ec0fdd LP |
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 | ||
7591abd4 LP |
711 | printf("%s [OPTIONS...] COMMAND ...\n\n" |
712 | "Query or change system time and date settings.\n\n" | |
07a062a7 | 713 | " -h --help Show this help message\n" |
4f8f66cb ZJS |
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" | |
6129ec85 YW |
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" | |
6d0274f1 | 725 | "Commands:\n" |
4f8f66cb | 726 | " status Show current time settings\n" |
ead0adb1 | 727 | " show Show properties of systemd-timedated\n" |
4f8f66cb | 728 | " set-time TIME Set system time\n" |
07a062a7 JSJ |
729 | " set-timezone ZONE Set system time zone\n" |
730 | " list-timezones Show known time zones\n" | |
4f8f66cb | 731 | " set-local-rtc BOOL Control whether RTC is in local time\n" |
6129ec85 YW |
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" | |
37ec0fdd LP |
737 | "\nSee the %s for details.\n" |
738 | , program_invocation_short_name | |
739 | , link | |
740 | ); | |
be90a886 YW |
741 | |
742 | return 0; | |
743 | } | |
744 | ||
745 | static int verb_help(int argc, char **argv, void *userdata) { | |
746 | return help(); | |
6d0274f1 LP |
747 | } |
748 | ||
749 | static int parse_argv(int argc, char *argv[]) { | |
750 | ||
751 | enum { | |
752 | ARG_VERSION = 0x100, | |
753 | ARG_NO_PAGER, | |
c9783430 | 754 | ARG_ADJUST_SYSTEM_CLOCK, |
6129ec85 YW |
755 | ARG_NO_ASK_PASSWORD, |
756 | ARG_MONITOR, | |
757 | ARG_VALUE, | |
6d0274f1 LP |
758 | }; |
759 | ||
760 | static const struct option options[] = { | |
c9783430 LP |
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' }, | |
a281d9c7 | 765 | { "machine", required_argument, NULL, 'M' }, |
c9783430 LP |
766 | { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, |
767 | { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, | |
6129ec85 YW |
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 }, | |
eb9da376 | 772 | {} |
6d0274f1 LP |
773 | }; |
774 | ||
6129ec85 | 775 | int c, r; |
6d0274f1 LP |
776 | |
777 | assert(argc >= 0); | |
778 | assert(argv); | |
779 | ||
6129ec85 | 780 | while ((c = getopt_long(argc, argv, "hH:M:p:a", options, NULL)) >= 0) |
6d0274f1 LP |
781 | |
782 | switch (c) { | |
783 | ||
784 | case 'h': | |
be90a886 | 785 | return help(); |
6d0274f1 LP |
786 | |
787 | case ARG_VERSION: | |
3f6fd1ba | 788 | return version(); |
6d0274f1 | 789 | |
a281d9c7 TA |
790 | case 'H': |
791 | arg_transport = BUS_TRANSPORT_REMOTE; | |
792 | arg_host = optarg; | |
6d0274f1 LP |
793 | break; |
794 | ||
a281d9c7 | 795 | case 'M': |
de33fc62 | 796 | arg_transport = BUS_TRANSPORT_MACHINE; |
a281d9c7 | 797 | arg_host = optarg; |
6d0274f1 LP |
798 | break; |
799 | ||
546158bc JJ |
800 | case ARG_NO_ASK_PASSWORD: |
801 | arg_ask_password = false; | |
802 | break; | |
803 | ||
c9783430 LP |
804 | case ARG_ADJUST_SYSTEM_CLOCK: |
805 | arg_adjust_system_clock = true; | |
6d0274f1 LP |
806 | break; |
807 | ||
808 | case ARG_NO_PAGER: | |
0221d68a | 809 | arg_pager_flags |= PAGER_DISABLE; |
6d0274f1 LP |
810 | break; |
811 | ||
6129ec85 YW |
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 | ||
6d0274f1 LP |
836 | case '?': |
837 | return -EINVAL; | |
838 | ||
839 | default: | |
eb9da376 | 840 | assert_not_reached("Unhandled option"); |
6d0274f1 | 841 | } |
6d0274f1 LP |
842 | |
843 | return 1; | |
844 | } | |
845 | ||
a281d9c7 | 846 | static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { |
be90a886 | 847 | static const Verb verbs[] = { |
6129ec85 | 848 | { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, |
ead0adb1 | 849 | { "show", VERB_ANY, 1, 0, show_properties }, |
6129ec85 YW |
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. */ | |
be90a886 | 858 | {} |
6d0274f1 LP |
859 | }; |
860 | ||
be90a886 | 861 | return dispatch_verb(argc, argv, verbs, bus); |
6d0274f1 LP |
862 | } |
863 | ||
50378e5d ZJS |
864 | static int run(int argc, char *argv[]) { |
865 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
84f6181c | 866 | int r; |
6d0274f1 | 867 | |
a9cdc94f | 868 | setlocale(LC_ALL, ""); |
6d0274f1 LP |
869 | log_parse_environment(); |
870 | log_open(); | |
871 | ||
872 | r = parse_argv(argc, argv); | |
84f6181c | 873 | if (r <= 0) |
50378e5d | 874 | return r; |
6d0274f1 | 875 | |
266f3e26 | 876 | r = bus_connect_transport(arg_transport, arg_host, false, &bus); |
50378e5d ZJS |
877 | if (r < 0) |
878 | return log_error_errno(r, "Failed to create bus connection: %m"); | |
6d0274f1 | 879 | |
50378e5d | 880 | return timedatectl_main(bus, argc, argv); |
6d0274f1 | 881 | } |
50378e5d ZJS |
882 | |
883 | DEFINE_MAIN_FUNCTION(run); |