]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
udev: make sure we always return a valid error code in trie_store()
[thirdparty/systemd.git] / src / timedate / timedatectl.c
CommitLineData
6d0274f1
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
2f6a5907 7 Copyright 2013 Kay Sievers
6d0274f1
LP
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
23#include <stdlib.h>
24#include <stdbool.h>
25#include <unistd.h>
26#include <getopt.h>
a9cdc94f 27#include <locale.h>
6d0274f1
LP
28#include <string.h>
29#include <sys/timex.h>
30
a281d9c7
TA
31#include "sd-bus.h"
32#include "bus-util.h"
33#include "bus-error.h"
6d0274f1
LP
34#include "util.h"
35#include "spawn-polkit-agent.h"
36#include "build.h"
6d0274f1
LP
37#include "strv.h"
38#include "pager.h"
f18ca9dc 39#include "time-dst.h"
6d0274f1 40
6d0274f1 41static bool arg_no_pager = false;
6d0274f1 42static bool arg_ask_password = true;
e1636421 43static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 44static char *arg_host = NULL;
e1636421 45static bool arg_adjust_system_clock = false;
6d0274f1
LP
46
47static void pager_open_if_enabled(void) {
48
49 if (arg_no_pager)
50 return;
51
1b12a7b5 52 pager_open(false);
6d0274f1
LP
53}
54
55static void polkit_agent_open_if_enabled(void) {
56
57 /* Open the polkit agent as a child process if necessary */
6d0274f1
LP
58 if (!arg_ask_password)
59 return;
60
46e65dcc
LP
61 if (arg_transport != BUS_TRANSPORT_LOCAL)
62 return;
63
6d0274f1
LP
64 polkit_agent_open();
65}
66
67typedef struct StatusInfo {
2f6a5907 68 usec_t time;
ffc06c35 69 char *timezone;
2f6a5907
KS
70
71 usec_t rtc_time;
72 bool rtc_local;
73
74 bool ntp_enabled;
75 bool ntp_capable;
76 bool ntp_synced;
6d0274f1
LP
77} StatusInfo;
78
2311eb2f
KS
79static const char *jump_str(int delta_minutes, char *s, size_t size) {
80 if (delta_minutes == 60)
81 return "one hour forward";
82 if (delta_minutes == -60)
83 return "one hour backwards";
84 if (delta_minutes < 0) {
85 snprintf(s, size, "%i minutes backwards", -delta_minutes);
86 return s;
87 }
88 if (delta_minutes > 0) {
89 snprintf(s, size, "%i minutes forward", delta_minutes);
90 return s;
91 }
92 return "";
93}
94
ffc06c35 95static void print_status_info(const StatusInfo *i) {
f18ca9dc 96 char a[FORMAT_TIMESTAMP_MAX];
6d0274f1 97 char b[FORMAT_TIMESTAMP_MAX];
2311eb2f 98 char s[32];
6d0274f1
LP
99 struct tm tm;
100 time_t sec;
9ff09bcb 101 bool have_time = false;
f18ca9dc
KS
102 char *zc, *zn;
103 time_t t, tc, tn;
2311eb2f 104 int dn;
f18ca9dc 105 bool is_dstc, is_dstn;
6d0274f1
LP
106 int r;
107
59965986
LP
108 assert(i);
109
2f6a5907 110 /* Enforce the values of /etc/localtime */
2311eb2f
KS
111 if (getenv("TZ")) {
112 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
113 unsetenv("TZ");
114 }
115
9ff09bcb
SL
116 if (i->time != 0) {
117 sec = (time_t) (i->time / USEC_PER_SEC);
118 have_time = true;
119 } else if (arg_transport == BUS_TRANSPORT_LOCAL) {
120 sec = time(NULL);
121 have_time = true;
122 } else
123 fprintf(stderr, "Warning: could not get time from timedated and not operating locally.\n\n");
6d0274f1 124
9ff09bcb
SL
125 if (have_time) {
126 zero(tm);
127 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
128 char_array_0(a);
129 printf(" Local time: %s\n", a);
6d0274f1 130
9ff09bcb
SL
131 zero(tm);
132 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
133 char_array_0(a);
134 printf(" Universal time: %s\n", a);
135 } else {
136 printf(" Local time: %s\n", "n/a");
137 printf(" Universal time: %s\n", "n/a");
138 }
6d0274f1 139
2f6a5907
KS
140 if (i->rtc_time > 0) {
141 time_t rtc_sec;
6d0274f1 142
2f6a5907
KS
143 rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
144 zero(tm);
7f35b7bc 145 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)) > 0);
f18ca9dc
KS
146 char_array_0(a);
147 printf(" RTC time: %s\n", a);
2f6a5907 148 } else
9ff09bcb 149 printf(" RTC time: %s\n", "n/a");
6d0274f1 150
f18ca9dc 151 zero(tm);
bd5ce8e9 152 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
f18ca9dc 153 char_array_0(a);
bd5ce8e9 154 printf(" Timezone: %s (%s)\n"
6d0274f1
LP
155 " NTP enabled: %s\n"
156 "NTP synchronized: %s\n"
157 " RTC in local TZ: %s\n",
2f6a5907
KS
158 strna(i->timezone), a,
159 i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
160 yes_no(i->ntp_synced),
161 yes_no(i->rtc_local));
6d0274f1 162
f18ca9dc
KS
163 r = time_get_dst(sec, "/etc/localtime",
164 &tc, &zc, &is_dstc,
2311eb2f 165 &tn, &dn, &zn, &is_dstn);
e2fd5e5b 166 if (r < 0)
9ff09bcb
SL
167 printf(" DST active: %s\n", "n/a");
168 else if (have_time) {
f18ca9dc
KS
169 printf(" DST active: %s\n", yes_no(is_dstc));
170
171 t = tc - 1;
172 zero(tm);
2af32104 173 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
f18ca9dc
KS
174 char_array_0(a);
175
176 zero(tm);
2af32104 177 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
f18ca9dc 178 char_array_0(b);
3062c151 179 printf(" Last DST change: DST %s at\n"
f18ca9dc
KS
180 " %s\n"
181 " %s\n",
3062c151 182 is_dstc ? "began" : "ended", a, b);
f18ca9dc
KS
183
184 t = tn - 1;
185 zero(tm);
2af32104 186 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
f18ca9dc
KS
187 char_array_0(a);
188
189 zero(tm);
2af32104 190 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
f18ca9dc 191 char_array_0(b);
3062c151 192 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
f18ca9dc
KS
193 " %s\n"
194 " %s\n",
3062c151 195 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
f18ca9dc
KS
196
197 free(zc);
198 free(zn);
9ff09bcb
SL
199 } else
200 printf(" DST active: %s\n", yes_no(is_dstc));
f18ca9dc 201
2f6a5907 202 if (i->rtc_local)
6d0274f1 203 fputs("\n" ANSI_HIGHLIGHT_ON
e9dd9f95 204 "Warning: The RTC is configured to maintain time in the local timezone. This\n"
6d0274f1
LP
205 " mode is not fully supported and will create various problems with time\n"
206 " zone changes and daylight saving adjustments. If at all possible use\n"
207 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
208}
209
a281d9c7 210static int show_status(sd_bus *bus, char **args, unsigned n) {
b92bea5d 211 StatusInfo info = {};
9f6eb1cd
KS
212 static const struct bus_properties_map map[] = {
213 { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) },
214 { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) },
215 { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) },
216 { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) },
217 { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) },
218 { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) },
219 { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) },
ffc06c35
KS
220 {}
221 };
222 int r;
6d0274f1 223
a281d9c7 224 assert(bus);
6d0274f1 225
ffc06c35
KS
226 r = bus_map_all_properties(bus,
227 "org.freedesktop.timedate1",
228 "/org/freedesktop/timedate1",
9f6eb1cd
KS
229 map,
230 &info);
adacb957
LP
231 if (r < 0) {
232 log_error("Failed to query server: %s", strerror(-r));
ffc06c35 233 goto fail;
adacb957 234 }
6d0274f1
LP
235
236 print_status_info(&info);
ffc06c35
KS
237
238fail:
239 free(info.timezone);
240 return r;
6d0274f1
LP
241}
242
a281d9c7
TA
243static int set_time(sd_bus *bus, char **args, unsigned n) {
244 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
245 bool relative = false, interactive = arg_ask_password;
6d0274f1 246 usec_t t;
6d0274f1
LP
247 int r;
248
249 assert(args);
250 assert(n == 2);
251
252 polkit_agent_open_if_enabled();
253
254 r = parse_timestamp(args[1], &t);
255 if (r < 0) {
256 log_error("Failed to parse time specification: %s", args[1]);
257 return r;
258 }
259
a281d9c7
TA
260 r = sd_bus_call_method(bus,
261 "org.freedesktop.timedate1",
262 "/org/freedesktop/timedate1",
263 "org.freedesktop.timedate1",
264 "SetTime",
265 &error,
266 NULL,
267 "xbb", (int64_t)t, relative, interactive);
268 if (r < 0)
269 log_error("Failed to set time: %s", bus_error_message(&error, -r));
270
271 return r;
6d0274f1
LP
272}
273
a281d9c7
TA
274static int set_timezone(sd_bus *bus, char **args, unsigned n) {
275 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
a281d9c7 276 int r;
6d0274f1
LP
277
278 assert(args);
279 assert(n == 2);
280
281 polkit_agent_open_if_enabled();
282
a281d9c7
TA
283 r = sd_bus_call_method(bus,
284 "org.freedesktop.timedate1",
285 "/org/freedesktop/timedate1",
286 "org.freedesktop.timedate1",
287 "SetTimezone",
288 &error,
289 NULL,
e5609878 290 "sb", args[1], arg_ask_password);
a281d9c7
TA
291 if (r < 0)
292 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
293
294 return r;
6d0274f1
LP
295}
296
a281d9c7
TA
297static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
298 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 299 int r, b;
6d0274f1
LP
300
301 assert(args);
302 assert(n == 2);
303
304 polkit_agent_open_if_enabled();
305
e5609878
LP
306 b = parse_boolean(args[1]);
307 if (b < 0) {
6d0274f1 308 log_error("Failed to parse local RTC setting: %s", args[1]);
e5609878 309 return b;
6d0274f1
LP
310 }
311
a281d9c7
TA
312 r = sd_bus_call_method(bus,
313 "org.freedesktop.timedate1",
314 "/org/freedesktop/timedate1",
315 "org.freedesktop.timedate1",
316 "SetLocalRTC",
317 &error,
318 NULL,
e5609878 319 "bbb", b, arg_adjust_system_clock, arg_ask_password);
a281d9c7
TA
320 if (r < 0)
321 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
322
323 return r;
6d0274f1
LP
324}
325
a281d9c7
TA
326static int set_ntp(sd_bus *bus, char **args, unsigned n) {
327 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
e5609878 328 int b, r;
6d0274f1
LP
329
330 assert(args);
331 assert(n == 2);
332
333 polkit_agent_open_if_enabled();
334
e5609878
LP
335 b = parse_boolean(args[1]);
336 if (b < 0) {
6d0274f1 337 log_error("Failed to parse NTP setting: %s", args[1]);
e5609878 338 return b;
6d0274f1
LP
339 }
340
a281d9c7
TA
341 r = sd_bus_call_method(bus,
342 "org.freedesktop.timedate1",
343 "/org/freedesktop/timedate1",
344 "org.freedesktop.timedate1",
345 "SetNTP",
346 &error,
347 NULL,
e5609878 348 "bb", b, arg_ask_password);
a281d9c7
TA
349 if (r < 0)
350 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
351
352 return r;
6d0274f1
LP
353}
354
a281d9c7 355static int list_timezones(sd_bus *bus, char **args, unsigned n) {
6d0274f1
LP
356 _cleanup_fclose_ FILE *f = NULL;
357 _cleanup_strv_free_ char **zones = NULL;
f75cb30b 358 size_t n_zones = 0;
6d0274f1
LP
359
360 assert(args);
361 assert(n == 1);
362
363 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
364 if (!f) {
365 log_error("Failed to open timezone database: %m");
366 return -errno;
367 }
368
369 for (;;) {
370 char l[LINE_MAX], *p, **z, *w;
371 size_t k;
372
373 if (!fgets(l, sizeof(l), f)) {
374 if (feof(f))
375 break;
376
377 log_error("Failed to read timezone database: %m");
378 return -errno;
379 }
380
381 p = strstrip(l);
382
383 if (isempty(p) || *p == '#')
384 continue;
385
386
387 /* Skip over country code */
388 p += strcspn(p, WHITESPACE);
389 p += strspn(p, WHITESPACE);
390
391 /* Skip over coordinates */
392 p += strcspn(p, WHITESPACE);
393 p += strspn(p, WHITESPACE);
394
395 /* Found timezone name */
396 k = strcspn(p, WHITESPACE);
397 if (k <= 0)
398 continue;
399
400 w = strndup(p, k);
401 if (!w)
402 return log_oom();
403
404 z = realloc(zones, sizeof(char*) * (n_zones + 2));
405 if (!z) {
406 free(w);
407 return log_oom();
408 }
409
410 zones = z;
411 zones[n_zones++] = w;
412 }
413
414 if (zones)
857a493d 415 zones[n_zones] = NULL;
6d0274f1
LP
416
417 pager_open_if_enabled();
418
857a493d 419 strv_sort(zones);
7c2d8094 420 strv_print(zones);
6d0274f1
LP
421
422 return 0;
423}
424
425static int help(void) {
426
7591abd4
LP
427 printf("%s [OPTIONS...] COMMAND ...\n\n"
428 "Query or change system time and date settings.\n\n"
4f8f66cb
ZJS
429 " -h --help Show this help\n"
430 " --version Show package version\n"
431 " --no-pager Do not pipe output into a pager\n"
432 " --no-ask-password Do not prompt for password\n"
433 " -H --host=[USER@]HOST Operate on remote host\n"
434 " -M --machine=CONTAINER Operate on local container\n"
435 " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n"
6d0274f1 436 "Commands:\n"
4f8f66cb
ZJS
437 " status Show current time settings\n"
438 " set-time TIME Set system time\n"
439 " set-timezone ZONE Set system timezone\n"
440 " list-timezones Show known timezones\n"
441 " set-local-rtc BOOL Control whether RTC is in local time\n"
442 " set-ntp BOOL Control whether NTP is enabled\n",
6d0274f1
LP
443 program_invocation_short_name);
444
445 return 0;
446}
447
448static int parse_argv(int argc, char *argv[]) {
449
450 enum {
451 ARG_VERSION = 0x100,
452 ARG_NO_PAGER,
c9783430 453 ARG_ADJUST_SYSTEM_CLOCK,
6d0274f1
LP
454 ARG_NO_ASK_PASSWORD
455 };
456
457 static const struct option options[] = {
c9783430
LP
458 { "help", no_argument, NULL, 'h' },
459 { "version", no_argument, NULL, ARG_VERSION },
460 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
461 { "host", required_argument, NULL, 'H' },
a281d9c7 462 { "machine", required_argument, NULL, 'M' },
c9783430
LP
463 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
464 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
eb9da376 465 {}
6d0274f1
LP
466 };
467
468 int c;
469
470 assert(argc >= 0);
471 assert(argv);
472
a281d9c7 473 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
6d0274f1
LP
474
475 switch (c) {
476
477 case 'h':
eb9da376 478 return help();
6d0274f1
LP
479
480 case ARG_VERSION:
481 puts(PACKAGE_STRING);
6d0274f1
LP
482 puts(SYSTEMD_FEATURES);
483 return 0;
484
a281d9c7
TA
485 case 'H':
486 arg_transport = BUS_TRANSPORT_REMOTE;
487 arg_host = optarg;
6d0274f1
LP
488 break;
489
a281d9c7
TA
490 case 'M':
491 arg_transport = BUS_TRANSPORT_CONTAINER;
492 arg_host = optarg;
6d0274f1
LP
493 break;
494
546158bc
JJ
495 case ARG_NO_ASK_PASSWORD:
496 arg_ask_password = false;
497 break;
498
c9783430
LP
499 case ARG_ADJUST_SYSTEM_CLOCK:
500 arg_adjust_system_clock = true;
6d0274f1
LP
501 break;
502
503 case ARG_NO_PAGER:
504 arg_no_pager = true;
505 break;
506
507 case '?':
508 return -EINVAL;
509
510 default:
eb9da376 511 assert_not_reached("Unhandled option");
6d0274f1
LP
512 }
513 }
514
515 return 1;
516}
517
a281d9c7 518static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
6d0274f1
LP
519
520 static const struct {
521 const char* verb;
522 const enum {
523 MORE,
524 LESS,
525 EQUAL
526 } argc_cmp;
527 const int argc;
a281d9c7 528 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
6d0274f1
LP
529 } verbs[] = {
530 { "status", LESS, 1, show_status },
531 { "set-time", EQUAL, 2, set_time },
532 { "set-timezone", EQUAL, 2, set_timezone },
533 { "list-timezones", EQUAL, 1, list_timezones },
534 { "set-local-rtc", EQUAL, 2, set_local_rtc },
535 { "set-ntp", EQUAL, 2, set_ntp, },
536 };
537
538 int left;
539 unsigned i;
540
541 assert(argc >= 0);
542 assert(argv);
6d0274f1
LP
543
544 left = argc - optind;
545
546 if (left <= 0)
547 /* Special rule: no arguments means "status" */
548 i = 0;
549 else {
550 if (streq(argv[optind], "help")) {
551 help();
552 return 0;
553 }
554
555 for (i = 0; i < ELEMENTSOF(verbs); i++)
556 if (streq(argv[optind], verbs[i].verb))
557 break;
558
559 if (i >= ELEMENTSOF(verbs)) {
560 log_error("Unknown operation %s", argv[optind]);
561 return -EINVAL;
562 }
563 }
564
565 switch (verbs[i].argc_cmp) {
566
567 case EQUAL:
568 if (left != verbs[i].argc) {
569 log_error("Invalid number of arguments.");
570 return -EINVAL;
571 }
572
573 break;
574
575 case MORE:
576 if (left < verbs[i].argc) {
577 log_error("Too few arguments.");
578 return -EINVAL;
579 }
580
581 break;
582
583 case LESS:
584 if (left > verbs[i].argc) {
585 log_error("Too many arguments.");
586 return -EINVAL;
587 }
588
589 break;
590
591 default:
592 assert_not_reached("Unknown comparison operator.");
593 }
594
6d0274f1
LP
595 return verbs[i].dispatch(bus, argv + optind, left);
596}
597
598int main(int argc, char *argv[]) {
a281d9c7 599 _cleanup_bus_unref_ sd_bus *bus = NULL;
84f6181c 600 int r;
6d0274f1 601
a9cdc94f 602 setlocale(LC_ALL, "");
6d0274f1
LP
603 log_parse_environment();
604 log_open();
605
606 r = parse_argv(argc, argv);
84f6181c 607 if (r <= 0)
6d0274f1 608 goto finish;
6d0274f1 609
a281d9c7
TA
610 r = bus_open_transport(arg_transport, arg_host, false, &bus);
611 if (r < 0) {
612 log_error("Failed to create bus connection: %s", strerror(-r));
a281d9c7 613 goto finish;
6d0274f1
LP
614 }
615
a281d9c7 616 r = timedatectl_main(bus, argc, argv);
6d0274f1 617
a281d9c7 618finish:
6d0274f1
LP
619 pager_close();
620
84f6181c 621 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
6d0274f1 622}