]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedatectl.c
Merge pull request #7540 from fbuihuu/systemd-delta-tweaks
[thirdparty/systemd.git] / src / timedate / timedatectl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Kay Sievers
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
22 #include <getopt.h>
23 #include <locale.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26
27 #include "sd-bus.h"
28
29 #include "bus-error.h"
30 #include "bus-util.h"
31 #include "pager.h"
32 #include "parse-util.h"
33 #include "spawn-polkit-agent.h"
34 #include "strv.h"
35 #include "terminal-util.h"
36 #include "util.h"
37
38 static bool arg_no_pager = false;
39 static bool arg_ask_password = true;
40 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
41 static char *arg_host = NULL;
42 static bool arg_adjust_system_clock = false;
43
44 typedef struct StatusInfo {
45 usec_t time;
46 char *timezone;
47
48 usec_t rtc_time;
49 int rtc_local;
50
51 int ntp_enabled;
52 int ntp_capable;
53 int ntp_synced;
54 } StatusInfo;
55
56 static void status_info_clear(StatusInfo *info) {
57 if (info) {
58 free(info->timezone);
59 zero(*info);
60 }
61 }
62
63 static void print_status_info(const StatusInfo *i) {
64 char a[LINE_MAX];
65 struct tm tm;
66 time_t sec;
67 bool have_time = false;
68 const char *old_tz = NULL, *tz;
69 int r;
70 size_t n;
71
72 assert(i);
73
74 /* Save the old $TZ */
75 tz = getenv("TZ");
76 if (tz)
77 old_tz = strdupa(tz);
78
79 /* Set the new $TZ */
80 if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0)
81 log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m");
82 else
83 tzset();
84
85 if (i->time != 0) {
86 sec = (time_t) (i->time / USEC_PER_SEC);
87 have_time = true;
88 } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) {
89 sec = time(NULL);
90 have_time = true;
91 } else
92 log_warning("Could not get time from timedated and not operating locally, ignoring.");
93
94 if (have_time) {
95 n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm));
96 printf(" Local time: %s\n", n > 0 ? a : "n/a");
97
98 n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm));
99 printf(" Universal time: %s\n", n > 0 ? a : "n/a");
100 } else {
101 printf(" Local time: %s\n", "n/a");
102 printf(" Universal time: %s\n", "n/a");
103 }
104
105 if (i->rtc_time > 0) {
106 time_t rtc_sec;
107
108 rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC);
109 n = strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm));
110 printf(" RTC time: %s\n", n > 0 ? a : "n/a");
111 } else
112 printf(" RTC time: %s\n", "n/a");
113
114 if (have_time)
115 n = strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm));
116
117 /* Restore the $TZ */
118 if (old_tz)
119 r = setenv("TZ", old_tz, true);
120 else
121 r = unsetenv("TZ");
122 if (r < 0)
123 log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m");
124 else
125 tzset();
126
127 printf(" Time zone: %s (%s)\n"
128 " System clock synchronized: %s\n"
129 "systemd-timesyncd.service active: %s\n"
130 " RTC in local TZ: %s\n",
131 strna(i->timezone), have_time && n > 0 ? a : "n/a",
132 yes_no(i->ntp_synced),
133 i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
134 yes_no(i->rtc_local));
135
136 if (i->rtc_local)
137 printf("\n%s"
138 "Warning: The system is configured to read the RTC time in the local time zone.\n"
139 " This mode can not be fully supported. It will create various problems\n"
140 " with time zone changes and daylight saving time adjustments. The RTC\n"
141 " time is never updated, it relies on external facilities to maintain it.\n"
142 " If at all possible, use RTC in UTC by calling\n"
143 " 'timedatectl set-local-rtc 0'.%s\n", ansi_highlight(), ansi_normal());
144 }
145
146 static int show_status(sd_bus *bus, char **args, unsigned n) {
147 _cleanup_(status_info_clear) StatusInfo info = {};
148 static const struct bus_properties_map map[] = {
149 { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) },
150 { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) },
151 { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) },
152 { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) },
153 { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) },
154 { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) },
155 { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) },
156 {}
157 };
158
159 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
160 int r;
161
162 assert(bus);
163
164 r = bus_map_all_properties(bus,
165 "org.freedesktop.timedate1",
166 "/org/freedesktop/timedate1",
167 map,
168 &error,
169 &info);
170 if (r < 0)
171 return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r));
172
173 print_status_info(&info);
174
175 return r;
176 }
177
178 static int set_time(sd_bus *bus, char **args, unsigned n) {
179 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
180 bool relative = false, interactive = arg_ask_password;
181 usec_t t;
182 int r;
183
184 assert(args);
185 assert(n == 2);
186
187 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
188
189 r = parse_timestamp(args[1], &t);
190 if (r < 0) {
191 log_error("Failed to parse time specification: %s", args[1]);
192 return r;
193 }
194
195 r = sd_bus_call_method(bus,
196 "org.freedesktop.timedate1",
197 "/org/freedesktop/timedate1",
198 "org.freedesktop.timedate1",
199 "SetTime",
200 &error,
201 NULL,
202 "xbb", (int64_t)t, relative, interactive);
203 if (r < 0)
204 log_error("Failed to set time: %s", bus_error_message(&error, -r));
205
206 return r;
207 }
208
209 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
210 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
211 int r;
212
213 assert(args);
214 assert(n == 2);
215
216 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
217
218 r = sd_bus_call_method(bus,
219 "org.freedesktop.timedate1",
220 "/org/freedesktop/timedate1",
221 "org.freedesktop.timedate1",
222 "SetTimezone",
223 &error,
224 NULL,
225 "sb", args[1], arg_ask_password);
226 if (r < 0)
227 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
228
229 return r;
230 }
231
232 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
233 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
234 int r, b;
235
236 assert(args);
237 assert(n == 2);
238
239 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
240
241 b = parse_boolean(args[1]);
242 if (b < 0) {
243 log_error("Failed to parse local RTC setting: %s", args[1]);
244 return b;
245 }
246
247 r = sd_bus_call_method(bus,
248 "org.freedesktop.timedate1",
249 "/org/freedesktop/timedate1",
250 "org.freedesktop.timedate1",
251 "SetLocalRTC",
252 &error,
253 NULL,
254 "bbb", b, arg_adjust_system_clock, arg_ask_password);
255 if (r < 0)
256 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
257
258 return r;
259 }
260
261 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
262 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
263 int b, r;
264
265 assert(args);
266 assert(n == 2);
267
268 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
269
270 b = parse_boolean(args[1]);
271 if (b < 0) {
272 log_error("Failed to parse NTP setting: %s", args[1]);
273 return b;
274 }
275
276 r = sd_bus_call_method(bus,
277 "org.freedesktop.timedate1",
278 "/org/freedesktop/timedate1",
279 "org.freedesktop.timedate1",
280 "SetNTP",
281 &error,
282 NULL,
283 "bb", b, arg_ask_password);
284 if (r < 0)
285 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
286
287 return r;
288 }
289
290 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
291 _cleanup_strv_free_ char **zones = NULL;
292 int r;
293
294 assert(args);
295 assert(n == 1);
296
297 r = get_timezones(&zones);
298 if (r < 0)
299 return log_error_errno(r, "Failed to read list of time zones: %m");
300
301 pager_open(arg_no_pager, false);
302 strv_print(zones);
303
304 return 0;
305 }
306
307 static void help(void) {
308 printf("%s [OPTIONS...] COMMAND ...\n\n"
309 "Query or change system time and date settings.\n\n"
310 " -h --help Show this help message\n"
311 " --version Show package version\n"
312 " --no-pager Do not pipe output into a pager\n"
313 " --no-ask-password Do not prompt for password\n"
314 " -H --host=[USER@]HOST Operate on remote host\n"
315 " -M --machine=CONTAINER Operate on local container\n"
316 " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
317 "Commands:\n"
318 " status Show current time settings\n"
319 " set-time TIME Set system time\n"
320 " set-timezone ZONE Set system time zone\n"
321 " list-timezones Show known time zones\n"
322 " set-local-rtc BOOL Control whether RTC is in local time\n"
323 " set-ntp BOOL Enable or disable network time synchronization\n",
324 program_invocation_short_name);
325 }
326
327 static int parse_argv(int argc, char *argv[]) {
328
329 enum {
330 ARG_VERSION = 0x100,
331 ARG_NO_PAGER,
332 ARG_ADJUST_SYSTEM_CLOCK,
333 ARG_NO_ASK_PASSWORD
334 };
335
336 static const struct option options[] = {
337 { "help", no_argument, NULL, 'h' },
338 { "version", no_argument, NULL, ARG_VERSION },
339 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
340 { "host", required_argument, NULL, 'H' },
341 { "machine", required_argument, NULL, 'M' },
342 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
343 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
344 {}
345 };
346
347 int c;
348
349 assert(argc >= 0);
350 assert(argv);
351
352 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
353
354 switch (c) {
355
356 case 'h':
357 help();
358 return 0;
359
360 case ARG_VERSION:
361 return version();
362
363 case 'H':
364 arg_transport = BUS_TRANSPORT_REMOTE;
365 arg_host = optarg;
366 break;
367
368 case 'M':
369 arg_transport = BUS_TRANSPORT_MACHINE;
370 arg_host = optarg;
371 break;
372
373 case ARG_NO_ASK_PASSWORD:
374 arg_ask_password = false;
375 break;
376
377 case ARG_ADJUST_SYSTEM_CLOCK:
378 arg_adjust_system_clock = true;
379 break;
380
381 case ARG_NO_PAGER:
382 arg_no_pager = true;
383 break;
384
385 case '?':
386 return -EINVAL;
387
388 default:
389 assert_not_reached("Unhandled option");
390 }
391
392 return 1;
393 }
394
395 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
396
397 static const struct {
398 const char* verb;
399 const enum {
400 MORE,
401 LESS,
402 EQUAL
403 } argc_cmp;
404 const int argc;
405 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
406 } verbs[] = {
407 { "status", LESS, 1, show_status },
408 { "set-time", EQUAL, 2, set_time },
409 { "set-timezone", EQUAL, 2, set_timezone },
410 { "list-timezones", EQUAL, 1, list_timezones },
411 { "set-local-rtc", EQUAL, 2, set_local_rtc },
412 { "set-ntp", EQUAL, 2, set_ntp, },
413 };
414
415 int left;
416 unsigned i;
417
418 assert(argc >= 0);
419 assert(argv);
420
421 left = argc - optind;
422
423 if (left <= 0)
424 /* Special rule: no arguments means "status" */
425 i = 0;
426 else {
427 if (streq(argv[optind], "help")) {
428 help();
429 return 0;
430 }
431
432 for (i = 0; i < ELEMENTSOF(verbs); i++)
433 if (streq(argv[optind], verbs[i].verb))
434 break;
435
436 if (i >= ELEMENTSOF(verbs)) {
437 log_error("Unknown operation %s", argv[optind]);
438 return -EINVAL;
439 }
440 }
441
442 switch (verbs[i].argc_cmp) {
443
444 case EQUAL:
445 if (left != verbs[i].argc) {
446 log_error("Invalid number of arguments.");
447 return -EINVAL;
448 }
449
450 break;
451
452 case MORE:
453 if (left < verbs[i].argc) {
454 log_error("Too few arguments.");
455 return -EINVAL;
456 }
457
458 break;
459
460 case LESS:
461 if (left > verbs[i].argc) {
462 log_error("Too many arguments.");
463 return -EINVAL;
464 }
465
466 break;
467
468 default:
469 assert_not_reached("Unknown comparison operator.");
470 }
471
472 return verbs[i].dispatch(bus, argv + optind, left);
473 }
474
475 int main(int argc, char *argv[]) {
476 sd_bus *bus = NULL;
477 int r;
478
479 setlocale(LC_ALL, "");
480 log_parse_environment();
481 log_open();
482
483 r = parse_argv(argc, argv);
484 if (r <= 0)
485 goto finish;
486
487 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
488 if (r < 0) {
489 log_error_errno(r, "Failed to create bus connection: %m");
490 goto finish;
491 }
492
493 r = timedatectl_main(bus, argc, argv);
494
495 finish:
496 /* make sure we terminate the bus connection first, and then close the
497 * pager, see issue #3543 for the details. */
498 sd_bus_flush_close_unref(bus);
499 pager_close();
500
501 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
502 }