]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedatectl.c
systemctl: remove extra padding from status output
[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
1b12a7b5 54 pager_open(false);
6d0274f1
LP
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;
1a561326 71 bool can_ntp;
6d0274f1
LP
72} StatusInfo;
73
74static bool ntp_synced(void) {
b92bea5d 75 struct timex txc = {};
6d0274f1 76
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,
1a561326 156 i->can_ntp ? yes_no(i->ntp) : "n/a",
6d0274f1
LP
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);
3062c151 176 printf(" Last DST change: DST %s at\n"
f18ca9dc
KS
177 " %s\n"
178 " %s\n",
3062c151 179 is_dstc ? "began" : "ended", 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);
3062c151 189 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
f18ca9dc
KS
190 " %s\n"
191 " %s\n",
3062c151 192 is_dstn ? "begins" : "ends", 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;
1a561326
LP
231 else if (streq(name, "CanNTP"))
232 i->can_ntp = b;
6d0274f1
LP
233 }
234 }
235
236 return 0;
237}
238
239static int show_status(DBusConnection *bus, char **args, unsigned n) {
240 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
241 const char *interface = "";
242 int r;
243 DBusMessageIter iter, sub, sub2, sub3;
b92bea5d 244 StatusInfo info = {};
6d0274f1
LP
245
246 assert(args);
247
248 r = bus_method_call_with_reply(
249 bus,
250 "org.freedesktop.timedate1",
251 "/org/freedesktop/timedate1",
252 "org.freedesktop.DBus.Properties",
253 "GetAll",
254 &reply,
255 NULL,
256 DBUS_TYPE_STRING, &interface,
257 DBUS_TYPE_INVALID);
258 if (r < 0)
259 return r;
260
261 if (!dbus_message_iter_init(reply, &iter) ||
262 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
263 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
264 log_error("Failed to parse reply.");
265 return -EIO;
266 }
267
268 dbus_message_iter_recurse(&iter, &sub);
269
270 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
271 const char *name;
272
273 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
274 log_error("Failed to parse reply.");
275 return -EIO;
276 }
277
278 dbus_message_iter_recurse(&sub, &sub2);
279
280 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
281 log_error("Failed to parse reply.");
282 return -EIO;
283 }
284
285 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
286 log_error("Failed to parse reply.");
287 return -EIO;
288 }
289
290 dbus_message_iter_recurse(&sub2, &sub3);
291
292 r = status_property(name, &sub3, &info);
293 if (r < 0) {
294 log_error("Failed to parse reply.");
295 return r;
296 }
297
298 dbus_message_iter_next(&sub);
299 }
300
301 print_status_info(&info);
302 return 0;
303}
304
305static int set_time(DBusConnection *bus, char **args, unsigned n) {
306 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
546158bc 307 dbus_bool_t relative = false, interactive = arg_ask_password;
6d0274f1
LP
308 usec_t t;
309 dbus_int64_t u;
310 int r;
311
312 assert(args);
313 assert(n == 2);
314
315 polkit_agent_open_if_enabled();
316
317 r = parse_timestamp(args[1], &t);
318 if (r < 0) {
319 log_error("Failed to parse time specification: %s", args[1]);
320 return r;
321 }
322
323 u = (dbus_uint64_t) t;
324
325 return bus_method_call_with_reply(
326 bus,
327 "org.freedesktop.timedate1",
328 "/org/freedesktop/timedate1",
329 "org.freedesktop.timedate1",
330 "SetTime",
331 &reply,
332 NULL,
333 DBUS_TYPE_INT64, &u,
334 DBUS_TYPE_BOOLEAN, &relative,
335 DBUS_TYPE_BOOLEAN, &interactive,
336 DBUS_TYPE_INVALID);
337}
338
339static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
340 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
546158bc 341 dbus_bool_t interactive = arg_ask_password;
6d0274f1
LP
342
343 assert(args);
344 assert(n == 2);
345
346 polkit_agent_open_if_enabled();
347
348 return bus_method_call_with_reply(
349 bus,
350 "org.freedesktop.timedate1",
351 "/org/freedesktop/timedate1",
352 "org.freedesktop.timedate1",
353 "SetTimezone",
354 &reply,
355 NULL,
356 DBUS_TYPE_STRING, &args[1],
357 DBUS_TYPE_BOOLEAN, &interactive,
358 DBUS_TYPE_INVALID);
359}
360
361static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
362 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
546158bc 363 dbus_bool_t interactive = arg_ask_password, b, q;
6d0274f1
LP
364 int r;
365
366 assert(args);
367 assert(n == 2);
368
369 polkit_agent_open_if_enabled();
370
371 r = parse_boolean(args[1]);
372 if (r < 0) {
373 log_error("Failed to parse local RTC setting: %s", args[1]);
374 return r;
375 }
376
377 b = r;
c9783430 378 q = arg_adjust_system_clock;
6d0274f1
LP
379
380 return bus_method_call_with_reply(
381 bus,
382 "org.freedesktop.timedate1",
383 "/org/freedesktop/timedate1",
384 "org.freedesktop.timedate1",
385 "SetLocalRTC",
386 &reply,
387 NULL,
388 DBUS_TYPE_BOOLEAN, &b,
389 DBUS_TYPE_BOOLEAN, &q,
390 DBUS_TYPE_BOOLEAN, &interactive,
391 DBUS_TYPE_INVALID);
392}
393
394static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
395 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
546158bc 396 dbus_bool_t interactive = arg_ask_password, b;
6d0274f1
LP
397 int r;
398
399 assert(args);
400 assert(n == 2);
401
402 polkit_agent_open_if_enabled();
403
404 r = parse_boolean(args[1]);
405 if (r < 0) {
406 log_error("Failed to parse NTP setting: %s", args[1]);
407 return r;
408 }
409
410 b = r;
411
412 return bus_method_call_with_reply(
413 bus,
414 "org.freedesktop.timedate1",
415 "/org/freedesktop/timedate1",
416 "org.freedesktop.timedate1",
417 "SetNTP",
418 &reply,
419 NULL,
420 DBUS_TYPE_BOOLEAN, &b,
421 DBUS_TYPE_BOOLEAN, &interactive,
422 DBUS_TYPE_INVALID);
423}
424
6d0274f1
LP
425static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
426 _cleanup_fclose_ FILE *f = NULL;
427 _cleanup_strv_free_ char **zones = NULL;
f75cb30b 428 size_t n_zones = 0;
6d0274f1
LP
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);
7c2d8094 490 strv_print(zones);
6d0274f1
LP
491
492 return 0;
493}
494
495static int help(void) {
496
7591abd4
LP
497 printf("%s [OPTIONS...] COMMAND ...\n\n"
498 "Query or change system time and date settings.\n\n"
6d0274f1
LP
499 " -h --help Show this help\n"
500 " --version Show package version\n"
c9783430
LP
501 " --adjust-system-clock\n"
502 " Adjust system clock when changing local RTC mode\n"
6d0274f1 503 " --no-pager Do not pipe output into a pager\n"
2927b326 504 " -P --privileged Acquire privileges before execution\n"
6d0274f1
LP
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
546158bc 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);
6d0274f1
LP
554 puts(SYSTEMD_FEATURES);
555 return 0;
556
557 case 'P':
558 arg_transport = TRANSPORT_POLKIT;
559 break;
560
561 case 'H':
562 arg_transport = TRANSPORT_SSH;
563 arg_host = optarg;
564 break;
565
546158bc
JJ
566 case ARG_NO_ASK_PASSWORD:
567 arg_ask_password = false;
568 break;
569
c9783430
LP
570 case ARG_ADJUST_SYSTEM_CLOCK:
571 arg_adjust_system_clock = true;
6d0274f1
LP
572 break;
573
574 case ARG_NO_PAGER:
575 arg_no_pager = true;
576 break;
577
578 case '?':
579 return -EINVAL;
580
581 default:
582 log_error("Unknown option code %c", c);
583 return -EINVAL;
584 }
585 }
586
587 return 1;
588}
589
590static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
591
592 static const struct {
593 const char* verb;
594 const enum {
595 MORE,
596 LESS,
597 EQUAL
598 } argc_cmp;
599 const int argc;
600 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
601 } verbs[] = {
602 { "status", LESS, 1, show_status },
603 { "set-time", EQUAL, 2, set_time },
604 { "set-timezone", EQUAL, 2, set_timezone },
605 { "list-timezones", EQUAL, 1, list_timezones },
606 { "set-local-rtc", EQUAL, 2, set_local_rtc },
607 { "set-ntp", EQUAL, 2, set_ntp, },
608 };
609
610 int left;
611 unsigned i;
612
613 assert(argc >= 0);
614 assert(argv);
615 assert(error);
616
617 left = argc - optind;
618
619 if (left <= 0)
620 /* Special rule: no arguments means "status" */
621 i = 0;
622 else {
623 if (streq(argv[optind], "help")) {
624 help();
625 return 0;
626 }
627
628 for (i = 0; i < ELEMENTSOF(verbs); i++)
629 if (streq(argv[optind], verbs[i].verb))
630 break;
631
632 if (i >= ELEMENTSOF(verbs)) {
633 log_error("Unknown operation %s", argv[optind]);
634 return -EINVAL;
635 }
636 }
637
638 switch (verbs[i].argc_cmp) {
639
640 case EQUAL:
641 if (left != verbs[i].argc) {
642 log_error("Invalid number of arguments.");
643 return -EINVAL;
644 }
645
646 break;
647
648 case MORE:
649 if (left < verbs[i].argc) {
650 log_error("Too few arguments.");
651 return -EINVAL;
652 }
653
654 break;
655
656 case LESS:
657 if (left > verbs[i].argc) {
658 log_error("Too many arguments.");
659 return -EINVAL;
660 }
661
662 break;
663
664 default:
665 assert_not_reached("Unknown comparison operator.");
666 }
667
668 if (!bus) {
669 log_error("Failed to get D-Bus connection: %s", error->message);
670 return -EIO;
671 }
672
673 return verbs[i].dispatch(bus, argv + optind, left);
674}
675
676int main(int argc, char *argv[]) {
677 int r, retval = EXIT_FAILURE;
678 DBusConnection *bus = NULL;
679 DBusError error;
680
681 dbus_error_init(&error);
682
a9cdc94f 683 setlocale(LC_ALL, "");
6d0274f1
LP
684 log_parse_environment();
685 log_open();
686
687 r = parse_argv(argc, argv);
688 if (r < 0)
689 goto finish;
690 else if (r == 0) {
691 retval = EXIT_SUCCESS;
692 goto finish;
693 }
694
695 if (arg_transport == TRANSPORT_NORMAL)
696 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
697 else if (arg_transport == TRANSPORT_POLKIT)
698 bus_connect_system_polkit(&bus, &error);
699 else if (arg_transport == TRANSPORT_SSH)
700 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
701 else
702 assert_not_reached("Uh, invalid transport...");
703
704 r = timedatectl_main(bus, argc, argv, &error);
705 retval = r < 0 ? EXIT_FAILURE : r;
706
707finish:
708 if (bus) {
709 dbus_connection_flush(bus);
710 dbus_connection_close(bus);
711 dbus_connection_unref(bus);
712 }
713
714 dbus_error_free(&error);
715 dbus_shutdown();
716
717 pager_close();
718
719 return retval;
720}