]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
timedatectl: show both current actual timezone name and UTC distance in TImezone...
[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
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 <stdlib.h>
23#include <stdbool.h>
24#include <unistd.h>
25#include <getopt.h>
a9cdc94f 26#include <locale.h>
6d0274f1
LP
27#include <string.h>
28#include <sys/timex.h>
29
30#include "dbus-common.h"
31#include "util.h"
32#include "spawn-polkit-agent.h"
33#include "build.h"
34#include "hwclock.h"
35#include "strv.h"
36#include "pager.h"
f18ca9dc 37#include "time-dst.h"
6d0274f1 38
c9783430 39static bool arg_adjust_system_clock = false;
6d0274f1
LP
40static bool arg_no_pager = false;
41static enum transport {
42 TRANSPORT_NORMAL,
43 TRANSPORT_SSH,
44 TRANSPORT_POLKIT
45} arg_transport = TRANSPORT_NORMAL;
46static bool arg_ask_password = true;
47static const char *arg_host = NULL;
48
49static void pager_open_if_enabled(void) {
50
51 if (arg_no_pager)
52 return;
53
54 pager_open();
55}
56
57static void polkit_agent_open_if_enabled(void) {
58
59 /* Open the polkit agent as a child process if necessary */
60
61 if (!arg_ask_password)
62 return;
63
64 polkit_agent_open();
65}
66
67typedef struct StatusInfo {
68 const char *timezone;
69 bool local_rtc;
70 bool ntp;
71} StatusInfo;
72
73static bool ntp_synced(void) {
6d0274f1
LP
74 struct timex txc;
75
76 zero(txc);
6d0274f1
LP
77 if (adjtimex(&txc) < 0)
78 return false;
79
80 if (txc.status & STA_UNSYNC)
81 return false;
82
83 return true;
84}
85
2311eb2f
KS
86static const char *jump_str(int delta_minutes, char *s, size_t size) {
87 if (delta_minutes == 60)
88 return "one hour forward";
89 if (delta_minutes == -60)
90 return "one hour backwards";
91 if (delta_minutes < 0) {
92 snprintf(s, size, "%i minutes backwards", -delta_minutes);
93 return s;
94 }
95 if (delta_minutes > 0) {
96 snprintf(s, size, "%i minutes forward", delta_minutes);
97 return s;
98 }
99 return "";
100}
101
6d0274f1
LP
102static void print_status_info(StatusInfo *i) {
103 usec_t n;
f18ca9dc 104 char a[FORMAT_TIMESTAMP_MAX];
6d0274f1 105 char b[FORMAT_TIMESTAMP_MAX];
2311eb2f 106 char s[32];
6d0274f1
LP
107 struct tm tm;
108 time_t sec;
f18ca9dc
KS
109 char *zc, *zn;
110 time_t t, tc, tn;
2311eb2f 111 int dn;
f18ca9dc 112 bool is_dstc, is_dstn;
6d0274f1
LP
113 int r;
114
59965986
LP
115 assert(i);
116
2311eb2f
KS
117 /* enforce the values of /etc/localtime */
118 if (getenv("TZ")) {
119 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
120 unsetenv("TZ");
121 }
122
6d0274f1
LP
123 n = now(CLOCK_REALTIME);
124 sec = (time_t) (n / USEC_PER_SEC);
125
126 zero(tm);
2af32104 127 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
f18ca9dc
KS
128 char_array_0(a);
129 printf(" Local time: %s\n", a);
6d0274f1
LP
130
131 zero(tm);
2af32104 132 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
f18ca9dc
KS
133 char_array_0(a);
134 printf(" Universal time: %s\n", a);
6d0274f1
LP
135
136 zero(tm);
137 r = hwclock_get_time(&tm);
138 if (r >= 0) {
139 /* Calculcate the week-day */
140 mktime(&tm);
141
2af32104 142 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
f18ca9dc
KS
143 char_array_0(a);
144 printf(" RTC time: %s\n", a);
6d0274f1
LP
145 }
146
f18ca9dc 147 zero(tm);
bd5ce8e9 148 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
f18ca9dc 149 char_array_0(a);
bd5ce8e9 150 printf(" Timezone: %s (%s)\n"
6d0274f1
LP
151 " NTP enabled: %s\n"
152 "NTP synchronized: %s\n"
153 " RTC in local TZ: %s\n",
154 strna(i->timezone),
f18ca9dc 155 a,
6d0274f1
LP
156 yes_no(i->ntp),
157 yes_no(ntp_synced()),
158 yes_no(i->local_rtc));
159
f18ca9dc
KS
160 r = time_get_dst(sec, "/etc/localtime",
161 &tc, &zc, &is_dstc,
2311eb2f 162 &tn, &dn, &zn, &is_dstn);
e2fd5e5b
KS
163 if (r < 0)
164 printf(" DST active: n/a\n");
165 else {
f18ca9dc
KS
166 printf(" DST active: %s\n", yes_no(is_dstc));
167
168 t = tc - 1;
169 zero(tm);
2af32104 170 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
f18ca9dc
KS
171 char_array_0(a);
172
173 zero(tm);
2af32104 174 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
f18ca9dc 175 char_array_0(b);
27985805 176 printf(" Last DST change: DST became %s\n"
f18ca9dc
KS
177 " %s\n"
178 " %s\n",
324dfd5c 179 is_dstc ? "active" : "inactive", a, b);
f18ca9dc
KS
180
181 t = tn - 1;
182 zero(tm);
2af32104 183 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
f18ca9dc
KS
184 char_array_0(a);
185
186 zero(tm);
2af32104 187 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
f18ca9dc 188 char_array_0(b);
27985805 189 printf(" Next DST change: DST will become %s, the clock will jump %s\n"
f18ca9dc
KS
190 " %s\n"
191 " %s\n",
324dfd5c 192 is_dstn ? "active" : "inactive", jump_str(dn, s, sizeof(s)), a, b);
f18ca9dc
KS
193
194 free(zc);
195 free(zn);
196 }
197
6d0274f1
LP
198 if (i->local_rtc)
199 fputs("\n" ANSI_HIGHLIGHT_ON
200 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
201 " mode is not fully supported and will create various problems with time\n"
202 " zone changes and daylight saving adjustments. If at all possible use\n"
203 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
204}
205
206static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
207 assert(name);
208 assert(iter);
209
210 switch (dbus_message_iter_get_arg_type(iter)) {
211
212 case DBUS_TYPE_STRING: {
213 const char *s;
214
215 dbus_message_iter_get_basic(iter, &s);
216 if (!isempty(s)) {
217 if (streq(name, "Timezone"))
218 i->timezone = s;
219 }
220 break;
221 }
222
223 case DBUS_TYPE_BOOLEAN: {
224 dbus_bool_t b;
225
226 dbus_message_iter_get_basic(iter, &b);
227 if (streq(name, "LocalRTC"))
228 i->local_rtc = b;
229 else if (streq(name, "NTP"))
230 i->ntp = b;
231 }
232 }
233
234 return 0;
235}
236
237static int show_status(DBusConnection *bus, char **args, unsigned n) {
238 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
239 const char *interface = "";
240 int r;
241 DBusMessageIter iter, sub, sub2, sub3;
242 StatusInfo info;
243
244 assert(args);
245
246 r = bus_method_call_with_reply(
247 bus,
248 "org.freedesktop.timedate1",
249 "/org/freedesktop/timedate1",
250 "org.freedesktop.DBus.Properties",
251 "GetAll",
252 &reply,
253 NULL,
254 DBUS_TYPE_STRING, &interface,
255 DBUS_TYPE_INVALID);
256 if (r < 0)
257 return r;
258
259 if (!dbus_message_iter_init(reply, &iter) ||
260 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
261 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
262 log_error("Failed to parse reply.");
263 return -EIO;
264 }
265
59965986 266 zero(info);
6d0274f1
LP
267 dbus_message_iter_recurse(&iter, &sub);
268
269 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
270 const char *name;
271
272 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
273 log_error("Failed to parse reply.");
274 return -EIO;
275 }
276
277 dbus_message_iter_recurse(&sub, &sub2);
278
279 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
280 log_error("Failed to parse reply.");
281 return -EIO;
282 }
283
284 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
285 log_error("Failed to parse reply.");
286 return -EIO;
287 }
288
289 dbus_message_iter_recurse(&sub2, &sub3);
290
291 r = status_property(name, &sub3, &info);
292 if (r < 0) {
293 log_error("Failed to parse reply.");
294 return r;
295 }
296
297 dbus_message_iter_next(&sub);
298 }
299
300 print_status_info(&info);
301 return 0;
302}
303
304static int set_time(DBusConnection *bus, char **args, unsigned n) {
305 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
306 dbus_bool_t relative = false, interactive = true;
307 usec_t t;
308 dbus_int64_t u;
309 int r;
310
311 assert(args);
312 assert(n == 2);
313
314 polkit_agent_open_if_enabled();
315
316 r = parse_timestamp(args[1], &t);
317 if (r < 0) {
318 log_error("Failed to parse time specification: %s", args[1]);
319 return r;
320 }
321
322 u = (dbus_uint64_t) t;
323
324 return bus_method_call_with_reply(
325 bus,
326 "org.freedesktop.timedate1",
327 "/org/freedesktop/timedate1",
328 "org.freedesktop.timedate1",
329 "SetTime",
330 &reply,
331 NULL,
332 DBUS_TYPE_INT64, &u,
333 DBUS_TYPE_BOOLEAN, &relative,
334 DBUS_TYPE_BOOLEAN, &interactive,
335 DBUS_TYPE_INVALID);
336}
337
338static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
339 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
340 dbus_bool_t interactive = true;
341
342 assert(args);
343 assert(n == 2);
344
345 polkit_agent_open_if_enabled();
346
347 return bus_method_call_with_reply(
348 bus,
349 "org.freedesktop.timedate1",
350 "/org/freedesktop/timedate1",
351 "org.freedesktop.timedate1",
352 "SetTimezone",
353 &reply,
354 NULL,
355 DBUS_TYPE_STRING, &args[1],
356 DBUS_TYPE_BOOLEAN, &interactive,
357 DBUS_TYPE_INVALID);
358}
359
360static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
361 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
362 dbus_bool_t interactive = true, b, q;
363 int r;
364
365 assert(args);
366 assert(n == 2);
367
368 polkit_agent_open_if_enabled();
369
370 r = parse_boolean(args[1]);
371 if (r < 0) {
372 log_error("Failed to parse local RTC setting: %s", args[1]);
373 return r;
374 }
375
376 b = r;
c9783430 377 q = arg_adjust_system_clock;
6d0274f1
LP
378
379 return bus_method_call_with_reply(
380 bus,
381 "org.freedesktop.timedate1",
382 "/org/freedesktop/timedate1",
383 "org.freedesktop.timedate1",
384 "SetLocalRTC",
385 &reply,
386 NULL,
387 DBUS_TYPE_BOOLEAN, &b,
388 DBUS_TYPE_BOOLEAN, &q,
389 DBUS_TYPE_BOOLEAN, &interactive,
390 DBUS_TYPE_INVALID);
391}
392
393static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
394 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
395 dbus_bool_t interactive = true, b;
396 int r;
397
398 assert(args);
399 assert(n == 2);
400
401 polkit_agent_open_if_enabled();
402
403 r = parse_boolean(args[1]);
404 if (r < 0) {
405 log_error("Failed to parse NTP setting: %s", args[1]);
406 return r;
407 }
408
409 b = r;
410
411 return bus_method_call_with_reply(
412 bus,
413 "org.freedesktop.timedate1",
414 "/org/freedesktop/timedate1",
415 "org.freedesktop.timedate1",
416 "SetNTP",
417 &reply,
418 NULL,
419 DBUS_TYPE_BOOLEAN, &b,
420 DBUS_TYPE_BOOLEAN, &interactive,
421 DBUS_TYPE_INVALID);
422}
423
6d0274f1
LP
424static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
425 _cleanup_fclose_ FILE *f = NULL;
426 _cleanup_strv_free_ char **zones = NULL;
f75cb30b 427 size_t n_zones = 0;
6d0274f1
LP
428 char **i;
429
430 assert(args);
431 assert(n == 1);
432
433 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
434 if (!f) {
435 log_error("Failed to open timezone database: %m");
436 return -errno;
437 }
438
439 for (;;) {
440 char l[LINE_MAX], *p, **z, *w;
441 size_t k;
442
443 if (!fgets(l, sizeof(l), f)) {
444 if (feof(f))
445 break;
446
447 log_error("Failed to read timezone database: %m");
448 return -errno;
449 }
450
451 p = strstrip(l);
452
453 if (isempty(p) || *p == '#')
454 continue;
455
456
457 /* Skip over country code */
458 p += strcspn(p, WHITESPACE);
459 p += strspn(p, WHITESPACE);
460
461 /* Skip over coordinates */
462 p += strcspn(p, WHITESPACE);
463 p += strspn(p, WHITESPACE);
464
465 /* Found timezone name */
466 k = strcspn(p, WHITESPACE);
467 if (k <= 0)
468 continue;
469
470 w = strndup(p, k);
471 if (!w)
472 return log_oom();
473
474 z = realloc(zones, sizeof(char*) * (n_zones + 2));
475 if (!z) {
476 free(w);
477 return log_oom();
478 }
479
480 zones = z;
481 zones[n_zones++] = w;
482 }
483
484 if (zones)
857a493d 485 zones[n_zones] = NULL;
6d0274f1
LP
486
487 pager_open_if_enabled();
488
857a493d 489 strv_sort(zones);
6d0274f1
LP
490 STRV_FOREACH(i, zones)
491 puts(*i);
492
493 return 0;
494}
495
496static int help(void) {
497
7591abd4
LP
498 printf("%s [OPTIONS...] COMMAND ...\n\n"
499 "Query or change system time and date settings.\n\n"
6d0274f1
LP
500 " -h --help Show this help\n"
501 " --version Show package version\n"
c9783430
LP
502 " --adjust-system-clock\n"
503 " Adjust system clock when changing local RTC mode\n"
6d0274f1
LP
504 " --no-pager Do not pipe output into a pager\n"
505 " --no-ask-password Do not prompt for password\n"
506 " -H --host=[USER@]HOST Operate on remote host\n\n"
507 "Commands:\n"
7591abd4
LP
508 " status Show current time settings\n"
509 " set-time TIME Set system time\n"
510 " set-timezone ZONE Set system timezone\n"
511 " list-timezones Show known timezones\n"
512 " set-local-rtc BOOL Control whether RTC is in local time\n"
513 " set-ntp BOOL Control whether NTP is enabled\n",
6d0274f1
LP
514 program_invocation_short_name);
515
516 return 0;
517}
518
519static int parse_argv(int argc, char *argv[]) {
520
521 enum {
522 ARG_VERSION = 0x100,
523 ARG_NO_PAGER,
c9783430 524 ARG_ADJUST_SYSTEM_CLOCK,
6d0274f1
LP
525 ARG_NO_ASK_PASSWORD
526 };
527
528 static const struct option options[] = {
c9783430
LP
529 { "help", no_argument, NULL, 'h' },
530 { "version", no_argument, NULL, ARG_VERSION },
531 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
532 { "host", required_argument, NULL, 'H' },
533 { "privileged", no_argument, NULL, 'P' },
534 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
535 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
536 { NULL, 0, NULL, 0 }
6d0274f1
LP
537 };
538
539 int c;
540
541 assert(argc >= 0);
542 assert(argv);
543
7591abd4 544 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
6d0274f1
LP
545
546 switch (c) {
547
548 case 'h':
549 help();
550 return 0;
551
552 case ARG_VERSION:
553 puts(PACKAGE_STRING);
554 puts(DISTRIBUTION);
555 puts(SYSTEMD_FEATURES);
556 return 0;
557
558 case 'P':
559 arg_transport = TRANSPORT_POLKIT;
560 break;
561
562 case 'H':
563 arg_transport = TRANSPORT_SSH;
564 arg_host = optarg;
565 break;
566
c9783430
LP
567 case ARG_ADJUST_SYSTEM_CLOCK:
568 arg_adjust_system_clock = true;
6d0274f1
LP
569 break;
570
571 case ARG_NO_PAGER:
572 arg_no_pager = true;
573 break;
574
575 case '?':
576 return -EINVAL;
577
578 default:
579 log_error("Unknown option code %c", c);
580 return -EINVAL;
581 }
582 }
583
584 return 1;
585}
586
587static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
588
589 static const struct {
590 const char* verb;
591 const enum {
592 MORE,
593 LESS,
594 EQUAL
595 } argc_cmp;
596 const int argc;
597 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
598 } verbs[] = {
599 { "status", LESS, 1, show_status },
600 { "set-time", EQUAL, 2, set_time },
601 { "set-timezone", EQUAL, 2, set_timezone },
602 { "list-timezones", EQUAL, 1, list_timezones },
603 { "set-local-rtc", EQUAL, 2, set_local_rtc },
604 { "set-ntp", EQUAL, 2, set_ntp, },
605 };
606
607 int left;
608 unsigned i;
609
610 assert(argc >= 0);
611 assert(argv);
612 assert(error);
613
614 left = argc - optind;
615
616 if (left <= 0)
617 /* Special rule: no arguments means "status" */
618 i = 0;
619 else {
620 if (streq(argv[optind], "help")) {
621 help();
622 return 0;
623 }
624
625 for (i = 0; i < ELEMENTSOF(verbs); i++)
626 if (streq(argv[optind], verbs[i].verb))
627 break;
628
629 if (i >= ELEMENTSOF(verbs)) {
630 log_error("Unknown operation %s", argv[optind]);
631 return -EINVAL;
632 }
633 }
634
635 switch (verbs[i].argc_cmp) {
636
637 case EQUAL:
638 if (left != verbs[i].argc) {
639 log_error("Invalid number of arguments.");
640 return -EINVAL;
641 }
642
643 break;
644
645 case MORE:
646 if (left < verbs[i].argc) {
647 log_error("Too few arguments.");
648 return -EINVAL;
649 }
650
651 break;
652
653 case LESS:
654 if (left > verbs[i].argc) {
655 log_error("Too many arguments.");
656 return -EINVAL;
657 }
658
659 break;
660
661 default:
662 assert_not_reached("Unknown comparison operator.");
663 }
664
665 if (!bus) {
666 log_error("Failed to get D-Bus connection: %s", error->message);
667 return -EIO;
668 }
669
670 return verbs[i].dispatch(bus, argv + optind, left);
671}
672
673int main(int argc, char *argv[]) {
674 int r, retval = EXIT_FAILURE;
675 DBusConnection *bus = NULL;
676 DBusError error;
677
678 dbus_error_init(&error);
679
a9cdc94f 680 setlocale(LC_ALL, "");
6d0274f1
LP
681 log_parse_environment();
682 log_open();
683
684 r = parse_argv(argc, argv);
685 if (r < 0)
686 goto finish;
687 else if (r == 0) {
688 retval = EXIT_SUCCESS;
689 goto finish;
690 }
691
692 if (arg_transport == TRANSPORT_NORMAL)
693 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
694 else if (arg_transport == TRANSPORT_POLKIT)
695 bus_connect_system_polkit(&bus, &error);
696 else if (arg_transport == TRANSPORT_SSH)
697 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
698 else
699 assert_not_reached("Uh, invalid transport...");
700
701 r = timedatectl_main(bus, argc, argv, &error);
702 retval = r < 0 ? EXIT_FAILURE : r;
703
704finish:
705 if (bus) {
706 dbus_connection_flush(bus);
707 dbus_connection_close(bus);
708 dbus_connection_unref(bus);
709 }
710
711 dbus_error_free(&error);
712 dbus_shutdown();
713
714 pager_close();
715
716 return retval;
717}