]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
6d0274f1 LP |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2012 Lennart Poettering | |
2f6a5907 | 6 | Copyright 2013 Kay Sievers |
6d0274f1 LP |
7 | |
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
6d0274f1 | 22 | #include <getopt.h> |
a9cdc94f | 23 | #include <locale.h> |
3f6fd1ba LP |
24 | #include <stdbool.h> |
25 | #include <stdlib.h> | |
6d0274f1 | 26 | |
a281d9c7 | 27 | #include "sd-bus.h" |
3f6fd1ba | 28 | |
a281d9c7 | 29 | #include "bus-error.h" |
3f6fd1ba LP |
30 | #include "bus-util.h" |
31 | #include "pager.h" | |
6bedfcbb | 32 | #include "parse-util.h" |
6d0274f1 | 33 | #include "spawn-polkit-agent.h" |
6d0274f1 | 34 | #include "strv.h" |
288a74cc | 35 | #include "terminal-util.h" |
3f6fd1ba | 36 | #include "util.h" |
be90a886 | 37 | #include "verbs.h" |
6d0274f1 | 38 | |
6d0274f1 | 39 | static bool arg_no_pager = false; |
6d0274f1 | 40 | static bool arg_ask_password = true; |
e1636421 | 41 | static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; |
7085053a | 42 | static char *arg_host = NULL; |
e1636421 | 43 | static bool arg_adjust_system_clock = false; |
6d0274f1 | 44 | |
6d0274f1 | 45 | typedef struct StatusInfo { |
2f6a5907 | 46 | usec_t time; |
f37f8a61 | 47 | const char *timezone; |
2f6a5907 KS |
48 | |
49 | usec_t rtc_time; | |
f37f8a61 | 50 | bool rtc_local; |
2f6a5907 | 51 | |
f37f8a61 YW |
52 | bool ntp_enabled; |
53 | bool ntp_capable; | |
54 | bool ntp_synced; | |
6d0274f1 LP |
55 | } StatusInfo; |
56 | ||
ffc06c35 | 57 | static void print_status_info(const StatusInfo *i) { |
14ce0c25 | 58 | char a[LINE_MAX]; |
6d0274f1 LP |
59 | struct tm tm; |
60 | time_t sec; | |
9ff09bcb | 61 | bool have_time = false; |
d95a74ed | 62 | const char *old_tz = NULL, *tz; |
6d0274f1 | 63 | int r; |
14ce0c25 | 64 | size_t n; |
6d0274f1 | 65 | |
59965986 LP |
66 | assert(i); |
67 | ||
d95a74ed LP |
68 | /* Save the old $TZ */ |
69 | tz = getenv("TZ"); | |
70 | if (tz) | |
71 | old_tz = strdupa(tz); | |
2311eb2f | 72 | |
d95a74ed | 73 | /* Set the new $TZ */ |
bdeb9e60 | 74 | if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) |
d95a74ed LP |
75 | log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); |
76 | else | |
77 | tzset(); | |
3e5e74d5 | 78 | |
9ff09bcb SL |
79 | if (i->time != 0) { |
80 | sec = (time_t) (i->time / USEC_PER_SEC); | |
81 | have_time = true; | |
d95a74ed | 82 | } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { |
9ff09bcb SL |
83 | sec = time(NULL); |
84 | have_time = true; | |
85 | } else | |
d95a74ed | 86 | log_warning("Could not get time from timedated and not operating locally, ignoring."); |
6d0274f1 | 87 | |
9ff09bcb | 88 | if (have_time) { |
14ce0c25 ZJS |
89 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); |
90 | printf(" Local time: %s\n", n > 0 ? a : "n/a"); | |
5ffa8c81 | 91 | |
14ce0c25 ZJS |
92 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); |
93 | printf(" Universal time: %s\n", n > 0 ? a : "n/a"); | |
9ff09bcb | 94 | } else { |
3ec530a1 ZJS |
95 | printf(" Local time: %s\n", "n/a"); |
96 | printf(" Universal time: %s\n", "n/a"); | |
9ff09bcb | 97 | } |
6d0274f1 | 98 | |
2f6a5907 KS |
99 | if (i->rtc_time > 0) { |
100 | time_t rtc_sec; | |
6d0274f1 | 101 | |
d95a74ed | 102 | rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); |
14ce0c25 ZJS |
103 | n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); |
104 | printf(" RTC time: %s\n", n > 0 ? a : "n/a"); | |
2f6a5907 | 105 | } else |
3ec530a1 | 106 | printf(" RTC time: %s\n", "n/a"); |
6d0274f1 | 107 | |
5ffa8c81 | 108 | if (have_time) |
14ce0c25 | 109 | n = strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm)); |
2667cc25 | 110 | |
d95a74ed LP |
111 | /* Restore the $TZ */ |
112 | if (old_tz) | |
113 | r = setenv("TZ", old_tz, true); | |
114 | else | |
115 | r = unsetenv("TZ"); | |
116 | if (r < 0) | |
117 | log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); | |
118 | else | |
119 | tzset(); | |
120 | ||
14ce0c25 | 121 | printf(" Time zone: %s (%s)\n" |
3ec530a1 ZJS |
122 | " System clock synchronized: %s\n" |
123 | "systemd-timesyncd.service active: %s\n" | |
124 | " RTC in local TZ: %s\n", | |
14ce0c25 | 125 | strna(i->timezone), have_time && n > 0 ? a : "n/a", |
2f6a5907 | 126 | yes_no(i->ntp_synced), |
a5198615 | 127 | i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a", |
2f6a5907 | 128 | yes_no(i->rtc_local)); |
6d0274f1 | 129 | |
2f6a5907 | 130 | if (i->rtc_local) |
54f8c958 LP |
131 | printf("\n%s" |
132 | "Warning: The system is configured to read the RTC time in the local time zone.\n" | |
87ac8d99 | 133 | " This mode cannot be fully supported. It will create various problems\n" |
54f8c958 LP |
134 | " with time zone changes and daylight saving time adjustments. The RTC\n" |
135 | " time is never updated, it relies on external facilities to maintain it.\n" | |
136 | " If at all possible, use RTC in UTC by calling\n" | |
137 | " 'timedatectl set-local-rtc 0'.%s\n", ansi_highlight(), ansi_normal()); | |
6d0274f1 LP |
138 | } |
139 | ||
be90a886 | 140 | static int show_status(int argc, char **argv, void *userdata) { |
f37f8a61 | 141 | StatusInfo info = {}; |
9f6eb1cd KS |
142 | static const struct bus_properties_map map[] = { |
143 | { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, | |
144 | { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, | |
145 | { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) }, | |
146 | { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, | |
147 | { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, | |
148 | { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, | |
149 | { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, | |
ffc06c35 KS |
150 | {} |
151 | }; | |
f9e0eefc LP |
152 | |
153 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
f37f8a61 | 154 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
be90a886 | 155 | sd_bus *bus = userdata; |
ffc06c35 | 156 | int r; |
6d0274f1 | 157 | |
a281d9c7 | 158 | assert(bus); |
6d0274f1 | 159 | |
ffc06c35 KS |
160 | r = bus_map_all_properties(bus, |
161 | "org.freedesktop.timedate1", | |
162 | "/org/freedesktop/timedate1", | |
9f6eb1cd | 163 | map, |
f9e0eefc | 164 | &error, |
f37f8a61 | 165 | &m, |
9f6eb1cd | 166 | &info); |
e7e55dbd | 167 | if (r < 0) |
f9e0eefc | 168 | return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r)); |
6d0274f1 LP |
169 | |
170 | print_status_info(&info); | |
ffc06c35 | 171 | |
ffc06c35 | 172 | return r; |
6d0274f1 LP |
173 | } |
174 | ||
be90a886 | 175 | static int set_time(int argc, char **argv, void *userdata) { |
4afd3348 | 176 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
a281d9c7 | 177 | bool relative = false, interactive = arg_ask_password; |
be90a886 | 178 | sd_bus *bus = userdata; |
6d0274f1 | 179 | usec_t t; |
6d0274f1 LP |
180 | int r; |
181 | ||
8a4b13c5 | 182 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 183 | |
be90a886 YW |
184 | r = parse_timestamp(argv[1], &t); |
185 | if (r < 0) | |
186 | return log_error_errno(r, "Failed to parse time specification '%s': %m", argv[1]); | |
6d0274f1 | 187 | |
a281d9c7 TA |
188 | r = sd_bus_call_method(bus, |
189 | "org.freedesktop.timedate1", | |
190 | "/org/freedesktop/timedate1", | |
191 | "org.freedesktop.timedate1", | |
192 | "SetTime", | |
193 | &error, | |
194 | NULL, | |
be90a886 | 195 | "xbb", (int64_t) t, relative, interactive); |
a281d9c7 | 196 | if (r < 0) |
be90a886 | 197 | log_error("Failed to set time: %s", bus_error_message(&error, r)); |
a281d9c7 TA |
198 | |
199 | return r; | |
6d0274f1 LP |
200 | } |
201 | ||
be90a886 | 202 | static int set_timezone(int argc, char **argv, void *userdata) { |
4afd3348 | 203 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 204 | sd_bus *bus = userdata; |
a281d9c7 | 205 | int r; |
6d0274f1 | 206 | |
8a4b13c5 | 207 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 208 | |
a281d9c7 TA |
209 | r = sd_bus_call_method(bus, |
210 | "org.freedesktop.timedate1", | |
211 | "/org/freedesktop/timedate1", | |
212 | "org.freedesktop.timedate1", | |
213 | "SetTimezone", | |
214 | &error, | |
215 | NULL, | |
be90a886 | 216 | "sb", argv[1], arg_ask_password); |
a281d9c7 | 217 | if (r < 0) |
be90a886 | 218 | log_error("Failed to set time zone: %s", bus_error_message(&error, r)); |
a281d9c7 TA |
219 | |
220 | return r; | |
6d0274f1 LP |
221 | } |
222 | ||
be90a886 | 223 | static int set_local_rtc(int argc, char **argv, void *userdata) { |
4afd3348 | 224 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 225 | sd_bus *bus = userdata; |
e5609878 | 226 | int r, b; |
6d0274f1 | 227 | |
8a4b13c5 | 228 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 229 | |
be90a886 YW |
230 | b = parse_boolean(argv[1]); |
231 | if (b < 0) | |
232 | return log_error_errno(b, "Failed to parse local RTC setting '%s': %m", argv[1]); | |
6d0274f1 | 233 | |
a281d9c7 TA |
234 | r = sd_bus_call_method(bus, |
235 | "org.freedesktop.timedate1", | |
236 | "/org/freedesktop/timedate1", | |
237 | "org.freedesktop.timedate1", | |
238 | "SetLocalRTC", | |
239 | &error, | |
240 | NULL, | |
e5609878 | 241 | "bbb", b, arg_adjust_system_clock, arg_ask_password); |
a281d9c7 | 242 | if (r < 0) |
be90a886 | 243 | log_error("Failed to set local RTC: %s", bus_error_message(&error, r)); |
a281d9c7 TA |
244 | |
245 | return r; | |
6d0274f1 LP |
246 | } |
247 | ||
be90a886 | 248 | static int set_ntp(int argc, char **argv, void *userdata) { |
4afd3348 | 249 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
be90a886 | 250 | sd_bus *bus = userdata; |
e5609878 | 251 | int b, r; |
6d0274f1 | 252 | |
8a4b13c5 | 253 | polkit_agent_open_if_enabled(arg_transport, arg_ask_password); |
6d0274f1 | 254 | |
be90a886 YW |
255 | b = parse_boolean(argv[1]); |
256 | if (b < 0) | |
257 | return log_error_errno(b, "Failed to parse NTP setting '%s': %m", argv[1]); | |
6d0274f1 | 258 | |
a281d9c7 TA |
259 | r = sd_bus_call_method(bus, |
260 | "org.freedesktop.timedate1", | |
261 | "/org/freedesktop/timedate1", | |
262 | "org.freedesktop.timedate1", | |
263 | "SetNTP", | |
264 | &error, | |
265 | NULL, | |
e5609878 | 266 | "bb", b, arg_ask_password); |
a281d9c7 | 267 | if (r < 0) |
be90a886 | 268 | log_error("Failed to set ntp: %s", bus_error_message(&error, r)); |
a281d9c7 TA |
269 | |
270 | return r; | |
6d0274f1 LP |
271 | } |
272 | ||
be90a886 | 273 | static int list_timezones(int argc, char **argv, void *userdata) { |
6d0274f1 | 274 | _cleanup_strv_free_ char **zones = NULL; |
75683450 | 275 | int r; |
6d0274f1 | 276 | |
75683450 | 277 | r = get_timezones(&zones); |
f647962d MS |
278 | if (r < 0) |
279 | return log_error_errno(r, "Failed to read list of time zones: %m"); | |
6d0274f1 | 280 | |
ee5324aa | 281 | (void) pager_open(arg_no_pager, false); |
7c2d8094 | 282 | strv_print(zones); |
6d0274f1 LP |
283 | |
284 | return 0; | |
285 | } | |
286 | ||
be90a886 | 287 | static int help(void) { |
7591abd4 LP |
288 | printf("%s [OPTIONS...] COMMAND ...\n\n" |
289 | "Query or change system time and date settings.\n\n" | |
07a062a7 | 290 | " -h --help Show this help message\n" |
4f8f66cb ZJS |
291 | " --version Show package version\n" |
292 | " --no-pager Do not pipe output into a pager\n" | |
293 | " --no-ask-password Do not prompt for password\n" | |
294 | " -H --host=[USER@]HOST Operate on remote host\n" | |
295 | " -M --machine=CONTAINER Operate on local container\n" | |
296 | " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" | |
6d0274f1 | 297 | "Commands:\n" |
4f8f66cb ZJS |
298 | " status Show current time settings\n" |
299 | " set-time TIME Set system time\n" | |
07a062a7 JSJ |
300 | " set-timezone ZONE Set system time zone\n" |
301 | " list-timezones Show known time zones\n" | |
4f8f66cb | 302 | " set-local-rtc BOOL Control whether RTC is in local time\n" |
3906ab4a | 303 | " set-ntp BOOL Enable or disable network time synchronization\n", |
6d0274f1 | 304 | program_invocation_short_name); |
be90a886 YW |
305 | |
306 | return 0; | |
307 | } | |
308 | ||
309 | static int verb_help(int argc, char **argv, void *userdata) { | |
310 | return help(); | |
6d0274f1 LP |
311 | } |
312 | ||
313 | static int parse_argv(int argc, char *argv[]) { | |
314 | ||
315 | enum { | |
316 | ARG_VERSION = 0x100, | |
317 | ARG_NO_PAGER, | |
c9783430 | 318 | ARG_ADJUST_SYSTEM_CLOCK, |
6d0274f1 LP |
319 | ARG_NO_ASK_PASSWORD |
320 | }; | |
321 | ||
322 | static const struct option options[] = { | |
c9783430 LP |
323 | { "help", no_argument, NULL, 'h' }, |
324 | { "version", no_argument, NULL, ARG_VERSION }, | |
325 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
326 | { "host", required_argument, NULL, 'H' }, | |
a281d9c7 | 327 | { "machine", required_argument, NULL, 'M' }, |
c9783430 LP |
328 | { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, |
329 | { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, | |
eb9da376 | 330 | {} |
6d0274f1 LP |
331 | }; |
332 | ||
333 | int c; | |
334 | ||
335 | assert(argc >= 0); | |
336 | assert(argv); | |
337 | ||
601185b4 | 338 | while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) |
6d0274f1 LP |
339 | |
340 | switch (c) { | |
341 | ||
342 | case 'h': | |
be90a886 | 343 | return help(); |
6d0274f1 LP |
344 | |
345 | case ARG_VERSION: | |
3f6fd1ba | 346 | return version(); |
6d0274f1 | 347 | |
a281d9c7 TA |
348 | case 'H': |
349 | arg_transport = BUS_TRANSPORT_REMOTE; | |
350 | arg_host = optarg; | |
6d0274f1 LP |
351 | break; |
352 | ||
a281d9c7 | 353 | case 'M': |
de33fc62 | 354 | arg_transport = BUS_TRANSPORT_MACHINE; |
a281d9c7 | 355 | arg_host = optarg; |
6d0274f1 LP |
356 | break; |
357 | ||
546158bc JJ |
358 | case ARG_NO_ASK_PASSWORD: |
359 | arg_ask_password = false; | |
360 | break; | |
361 | ||
c9783430 LP |
362 | case ARG_ADJUST_SYSTEM_CLOCK: |
363 | arg_adjust_system_clock = true; | |
6d0274f1 LP |
364 | break; |
365 | ||
366 | case ARG_NO_PAGER: | |
367 | arg_no_pager = true; | |
368 | break; | |
369 | ||
370 | case '?': | |
371 | return -EINVAL; | |
372 | ||
373 | default: | |
eb9da376 | 374 | assert_not_reached("Unhandled option"); |
6d0274f1 | 375 | } |
6d0274f1 LP |
376 | |
377 | return 1; | |
378 | } | |
379 | ||
a281d9c7 | 380 | static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { |
6d0274f1 | 381 | |
be90a886 YW |
382 | static const Verb verbs[] = { |
383 | { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, | |
384 | { "set-time", 2, 2, 0, set_time }, | |
385 | { "set-timezone", 2, 2, 0, set_timezone }, | |
386 | { "list-timezones", VERB_ANY, 1, 0, list_timezones }, | |
387 | { "set-local-rtc", 2, 2, 0, set_local_rtc }, | |
388 | { "set-ntp", 2, 2, 0, set_ntp }, | |
389 | { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ | |
390 | {} | |
6d0274f1 LP |
391 | }; |
392 | ||
be90a886 | 393 | return dispatch_verb(argc, argv, verbs, bus); |
6d0274f1 LP |
394 | } |
395 | ||
396 | int main(int argc, char *argv[]) { | |
a3c56345 | 397 | sd_bus *bus = NULL; |
84f6181c | 398 | int r; |
6d0274f1 | 399 | |
a9cdc94f | 400 | setlocale(LC_ALL, ""); |
6d0274f1 LP |
401 | log_parse_environment(); |
402 | log_open(); | |
403 | ||
404 | r = parse_argv(argc, argv); | |
84f6181c | 405 | if (r <= 0) |
6d0274f1 | 406 | goto finish; |
6d0274f1 | 407 | |
266f3e26 | 408 | r = bus_connect_transport(arg_transport, arg_host, false, &bus); |
a281d9c7 | 409 | if (r < 0) { |
da927ba9 | 410 | log_error_errno(r, "Failed to create bus connection: %m"); |
a281d9c7 | 411 | goto finish; |
6d0274f1 LP |
412 | } |
413 | ||
a281d9c7 | 414 | r = timedatectl_main(bus, argc, argv); |
6d0274f1 | 415 | |
a281d9c7 | 416 | finish: |
0a84daa5 FB |
417 | /* make sure we terminate the bus connection first, and then close the |
418 | * pager, see issue #3543 for the details. */ | |
a3c56345 | 419 | sd_bus_flush_close_unref(bus); |
6d0274f1 LP |
420 | pager_close(); |
421 | ||
84f6181c | 422 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; |
6d0274f1 | 423 | } |