]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / timedate / timedatectl.c
CommitLineData
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"
6d0274f1 37
6d0274f1 38static bool arg_no_pager = false;
6d0274f1 39static bool arg_ask_password = true;
e1636421 40static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 41static char *arg_host = NULL;
e1636421 42static bool arg_adjust_system_clock = false;
6d0274f1 43
6d0274f1 44typedef struct StatusInfo {
2f6a5907 45 usec_t time;
ffc06c35 46 char *timezone;
2f6a5907
KS
47
48 usec_t rtc_time;
43dcc86a 49 int rtc_local;
2f6a5907 50
43dcc86a
LP
51 int ntp_enabled;
52 int ntp_capable;
53 int ntp_synced;
6d0274f1
LP
54} StatusInfo;
55
e7e55dbd
DH
56static void status_info_clear(StatusInfo *info) {
57 if (info) {
58 free(info->timezone);
59 zero(*info);
60 }
61}
62
ffc06c35 63static void print_status_info(const StatusInfo *i) {
14ce0c25 64 char a[LINE_MAX];
6d0274f1
LP
65 struct tm tm;
66 time_t sec;
9ff09bcb 67 bool have_time = false;
d95a74ed 68 const char *old_tz = NULL, *tz;
6d0274f1 69 int r;
14ce0c25 70 size_t n;
6d0274f1 71
59965986
LP
72 assert(i);
73
d95a74ed
LP
74 /* Save the old $TZ */
75 tz = getenv("TZ");
76 if (tz)
77 old_tz = strdupa(tz);
2311eb2f 78
d95a74ed 79 /* Set the new $TZ */
bdeb9e60 80 if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0)
d95a74ed
LP
81 log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m");
82 else
83 tzset();
3e5e74d5 84
9ff09bcb
SL
85 if (i->time != 0) {
86 sec = (time_t) (i->time / USEC_PER_SEC);
87 have_time = true;
d95a74ed 88 } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) {
9ff09bcb
SL
89 sec = time(NULL);
90 have_time = true;
91 } else
d95a74ed 92 log_warning("Could not get time from timedated and not operating locally, ignoring.");
6d0274f1 93
9ff09bcb 94 if (have_time) {
14ce0c25
ZJS
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");
5ffa8c81 97
14ce0c25
ZJS
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");
9ff09bcb 100 } else {
3ec530a1
ZJS
101 printf(" Local time: %s\n", "n/a");
102 printf(" Universal time: %s\n", "n/a");
9ff09bcb 103 }
6d0274f1 104
2f6a5907
KS
105 if (i->rtc_time > 0) {
106 time_t rtc_sec;
6d0274f1 107
d95a74ed 108 rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC);
14ce0c25
ZJS
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");
2f6a5907 111 } else
3ec530a1 112 printf(" RTC time: %s\n", "n/a");
6d0274f1 113
5ffa8c81 114 if (have_time)
14ce0c25 115 n = strftime(a, sizeof a, "%Z, %z", localtime_r(&sec, &tm));
2667cc25 116
d95a74ed
LP
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
14ce0c25 127 printf(" Time zone: %s (%s)\n"
3ec530a1
ZJS
128 " System clock synchronized: %s\n"
129 "systemd-timesyncd.service active: %s\n"
130 " RTC in local TZ: %s\n",
14ce0c25 131 strna(i->timezone), have_time && n > 0 ? a : "n/a",
2f6a5907
KS
132 i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
133 yes_no(i->ntp_synced),
134 yes_no(i->rtc_local));
6d0274f1 135
2f6a5907 136 if (i->rtc_local)
54f8c958
LP
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());
6d0274f1
LP
144}
145
a281d9c7 146static int show_status(sd_bus *bus, char **args, unsigned n) {
e7e55dbd 147 _cleanup_(status_info_clear) StatusInfo info = {};
9f6eb1cd
KS
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) },
ffc06c35
KS
156 {}
157 };
f9e0eefc
LP
158
159 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
ffc06c35 160 int r;
6d0274f1 161
a281d9c7 162 assert(bus);
6d0274f1 163
ffc06c35
KS
164 r = bus_map_all_properties(bus,
165 "org.freedesktop.timedate1",
166 "/org/freedesktop/timedate1",
9f6eb1cd 167 map,
f9e0eefc 168 &error,
9f6eb1cd 169 &info);
e7e55dbd 170 if (r < 0)
f9e0eefc 171 return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r));
6d0274f1
LP
172
173 print_status_info(&info);
ffc06c35 174
ffc06c35 175 return r;
6d0274f1
LP
176}
177
a281d9c7 178static int set_time(sd_bus *bus, char **args, unsigned n) {
4afd3348 179 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
a281d9c7 180 bool relative = false, interactive = arg_ask_password;
6d0274f1 181 usec_t t;
6d0274f1
LP
182 int r;
183
184 assert(args);
185 assert(n == 2);
186
8a4b13c5 187 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
6d0274f1
LP
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
a281d9c7
TA
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;
6d0274f1
LP
207}
208
a281d9c7 209static int set_timezone(sd_bus *bus, char **args, unsigned n) {
4afd3348 210 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
a281d9c7 211 int r;
6d0274f1
LP
212
213 assert(args);
214 assert(n == 2);
215
8a4b13c5 216 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
6d0274f1 217
a281d9c7
TA
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,
e5609878 225 "sb", args[1], arg_ask_password);
a281d9c7 226 if (r < 0)
07a062a7 227 log_error("Failed to set time zone: %s", bus_error_message(&error, -r));
a281d9c7
TA
228
229 return r;
6d0274f1
LP
230}
231
a281d9c7 232static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
4afd3348 233 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 234 int r, b;
6d0274f1
LP
235
236 assert(args);
237 assert(n == 2);
238
8a4b13c5 239 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
6d0274f1 240
e5609878
LP
241 b = parse_boolean(args[1]);
242 if (b < 0) {
6d0274f1 243 log_error("Failed to parse local RTC setting: %s", args[1]);
e5609878 244 return b;
6d0274f1
LP
245 }
246
a281d9c7
TA
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,
e5609878 254 "bbb", b, arg_adjust_system_clock, arg_ask_password);
a281d9c7
TA
255 if (r < 0)
256 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
257
258 return r;
6d0274f1
LP
259}
260
a281d9c7 261static int set_ntp(sd_bus *bus, char **args, unsigned n) {
4afd3348 262 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 263 int b, r;
6d0274f1
LP
264
265 assert(args);
266 assert(n == 2);
267
8a4b13c5 268 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
6d0274f1 269
e5609878
LP
270 b = parse_boolean(args[1]);
271 if (b < 0) {
6d0274f1 272 log_error("Failed to parse NTP setting: %s", args[1]);
e5609878 273 return b;
6d0274f1
LP
274 }
275
a281d9c7
TA
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,
e5609878 283 "bb", b, arg_ask_password);
a281d9c7
TA
284 if (r < 0)
285 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
286
287 return r;
6d0274f1
LP
288}
289
a281d9c7 290static int list_timezones(sd_bus *bus, char **args, unsigned n) {
6d0274f1 291 _cleanup_strv_free_ char **zones = NULL;
75683450 292 int r;
6d0274f1
LP
293
294 assert(args);
295 assert(n == 1);
296
75683450 297 r = get_timezones(&zones);
f647962d
MS
298 if (r < 0)
299 return log_error_errno(r, "Failed to read list of time zones: %m");
6d0274f1 300
ea4b98e6 301 pager_open(arg_no_pager, false);
7c2d8094 302 strv_print(zones);
6d0274f1
LP
303
304 return 0;
305}
306
601185b4 307static void help(void) {
7591abd4
LP
308 printf("%s [OPTIONS...] COMMAND ...\n\n"
309 "Query or change system time and date settings.\n\n"
07a062a7 310 " -h --help Show this help message\n"
4f8f66cb
ZJS
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"
6d0274f1 317 "Commands:\n"
4f8f66cb
ZJS
318 " status Show current time settings\n"
319 " set-time TIME Set system time\n"
07a062a7
JSJ
320 " set-timezone ZONE Set system time zone\n"
321 " list-timezones Show known time zones\n"
4f8f66cb 322 " set-local-rtc BOOL Control whether RTC is in local time\n"
3906ab4a 323 " set-ntp BOOL Enable or disable network time synchronization\n",
6d0274f1 324 program_invocation_short_name);
6d0274f1
LP
325}
326
327static int parse_argv(int argc, char *argv[]) {
328
329 enum {
330 ARG_VERSION = 0x100,
331 ARG_NO_PAGER,
c9783430 332 ARG_ADJUST_SYSTEM_CLOCK,
6d0274f1
LP
333 ARG_NO_ASK_PASSWORD
334 };
335
336 static const struct option options[] = {
c9783430
LP
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' },
a281d9c7 341 { "machine", required_argument, NULL, 'M' },
c9783430
LP
342 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
343 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
eb9da376 344 {}
6d0274f1
LP
345 };
346
347 int c;
348
349 assert(argc >= 0);
350 assert(argv);
351
601185b4 352 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
6d0274f1
LP
353
354 switch (c) {
355
356 case 'h':
601185b4
ZJS
357 help();
358 return 0;
6d0274f1
LP
359
360 case ARG_VERSION:
3f6fd1ba 361 return version();
6d0274f1 362
a281d9c7
TA
363 case 'H':
364 arg_transport = BUS_TRANSPORT_REMOTE;
365 arg_host = optarg;
6d0274f1
LP
366 break;
367
a281d9c7 368 case 'M':
de33fc62 369 arg_transport = BUS_TRANSPORT_MACHINE;
a281d9c7 370 arg_host = optarg;
6d0274f1
LP
371 break;
372
546158bc
JJ
373 case ARG_NO_ASK_PASSWORD:
374 arg_ask_password = false;
375 break;
376
c9783430
LP
377 case ARG_ADJUST_SYSTEM_CLOCK:
378 arg_adjust_system_clock = true;
6d0274f1
LP
379 break;
380
381 case ARG_NO_PAGER:
382 arg_no_pager = true;
383 break;
384
385 case '?':
386 return -EINVAL;
387
388 default:
eb9da376 389 assert_not_reached("Unhandled option");
6d0274f1 390 }
6d0274f1
LP
391
392 return 1;
393}
394
a281d9c7 395static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
6d0274f1
LP
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;
a281d9c7 405 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
6d0274f1
LP
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);
6d0274f1
LP
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
6d0274f1
LP
472 return verbs[i].dispatch(bus, argv + optind, left);
473}
474
475int main(int argc, char *argv[]) {
0b3c84eb 476 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
84f6181c 477 int r;
6d0274f1 478
a9cdc94f 479 setlocale(LC_ALL, "");
6d0274f1
LP
480 log_parse_environment();
481 log_open();
482
483 r = parse_argv(argc, argv);
84f6181c 484 if (r <= 0)
6d0274f1 485 goto finish;
6d0274f1 486
266f3e26 487 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
a281d9c7 488 if (r < 0) {
da927ba9 489 log_error_errno(r, "Failed to create bus connection: %m");
a281d9c7 490 goto finish;
6d0274f1
LP
491 }
492
a281d9c7 493 r = timedatectl_main(bus, argc, argv);
6d0274f1 494
a281d9c7 495finish:
6d0274f1
LP
496 pager_close();
497
84f6181c 498 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
6d0274f1 499}