]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedatectl.c
pager: introduce "jump to end" option
[thirdparty/systemd.git] / src / timedate / timedatectl.c
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>
26 #include <locale.h>
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"
37 #include "time-dst.h"
38
39 static bool arg_adjust_system_clock = false;
40 static bool arg_no_pager = false;
41 static enum transport {
42 TRANSPORT_NORMAL,
43 TRANSPORT_SSH,
44 TRANSPORT_POLKIT
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static const char *arg_host = NULL;
48
49 static void pager_open_if_enabled(void) {
50
51 if (arg_no_pager)
52 return;
53
54 pager_open(false);
55 }
56
57 static 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
67 typedef struct StatusInfo {
68 const char *timezone;
69 bool local_rtc;
70 bool ntp;
71 } StatusInfo;
72
73 static bool ntp_synced(void) {
74 struct timex txc;
75
76 zero(txc);
77 if (adjtimex(&txc) < 0)
78 return false;
79
80 if (txc.status & STA_UNSYNC)
81 return false;
82
83 return true;
84 }
85
86 static 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
102 static void print_status_info(StatusInfo *i) {
103 usec_t n;
104 char a[FORMAT_TIMESTAMP_MAX];
105 char b[FORMAT_TIMESTAMP_MAX];
106 char s[32];
107 struct tm tm;
108 time_t sec;
109 char *zc, *zn;
110 time_t t, tc, tn;
111 int dn;
112 bool is_dstc, is_dstn;
113 int r;
114
115 assert(i);
116
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
123 n = now(CLOCK_REALTIME);
124 sec = (time_t) (n / USEC_PER_SEC);
125
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);
130
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
136 zero(tm);
137 r = hwclock_get_time(&tm);
138 if (r >= 0) {
139 /* Calculcate the week-day */
140 mktime(&tm);
141
142 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
143 char_array_0(a);
144 printf(" RTC time: %s\n", a);
145 }
146
147 zero(tm);
148 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
149 char_array_0(a);
150 printf(" Timezone: %s (%s)\n"
151 " NTP enabled: %s\n"
152 "NTP synchronized: %s\n"
153 " RTC in local TZ: %s\n",
154 strna(i->timezone),
155 a,
156 yes_no(i->ntp),
157 yes_no(ntp_synced()),
158 yes_no(i->local_rtc));
159
160 r = time_get_dst(sec, "/etc/localtime",
161 &tc, &zc, &is_dstc,
162 &tn, &dn, &zn, &is_dstn);
163 if (r < 0)
164 printf(" DST active: n/a\n");
165 else {
166 printf(" DST active: %s\n", yes_no(is_dstc));
167
168 t = tc - 1;
169 zero(tm);
170 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
171 char_array_0(a);
172
173 zero(tm);
174 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
175 char_array_0(b);
176 printf(" Last DST change: DST %s at\n"
177 " %s\n"
178 " %s\n",
179 is_dstc ? "began" : "ended", a, b);
180
181 t = tn - 1;
182 zero(tm);
183 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
184 char_array_0(a);
185
186 zero(tm);
187 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
188 char_array_0(b);
189 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
190 " %s\n"
191 " %s\n",
192 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
193
194 free(zc);
195 free(zn);
196 }
197
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
206 static 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
237 static 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
266 zero(info);
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
304 static 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
338 static 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
360 static 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;
377 q = arg_adjust_system_clock;
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
393 static 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
424 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
425 _cleanup_fclose_ FILE *f = NULL;
426 _cleanup_strv_free_ char **zones = NULL;
427 size_t n_zones = 0;
428
429 assert(args);
430 assert(n == 1);
431
432 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
433 if (!f) {
434 log_error("Failed to open timezone database: %m");
435 return -errno;
436 }
437
438 for (;;) {
439 char l[LINE_MAX], *p, **z, *w;
440 size_t k;
441
442 if (!fgets(l, sizeof(l), f)) {
443 if (feof(f))
444 break;
445
446 log_error("Failed to read timezone database: %m");
447 return -errno;
448 }
449
450 p = strstrip(l);
451
452 if (isempty(p) || *p == '#')
453 continue;
454
455
456 /* Skip over country code */
457 p += strcspn(p, WHITESPACE);
458 p += strspn(p, WHITESPACE);
459
460 /* Skip over coordinates */
461 p += strcspn(p, WHITESPACE);
462 p += strspn(p, WHITESPACE);
463
464 /* Found timezone name */
465 k = strcspn(p, WHITESPACE);
466 if (k <= 0)
467 continue;
468
469 w = strndup(p, k);
470 if (!w)
471 return log_oom();
472
473 z = realloc(zones, sizeof(char*) * (n_zones + 2));
474 if (!z) {
475 free(w);
476 return log_oom();
477 }
478
479 zones = z;
480 zones[n_zones++] = w;
481 }
482
483 if (zones)
484 zones[n_zones] = NULL;
485
486 pager_open_if_enabled();
487
488 strv_sort(zones);
489 strv_print(zones);
490
491 return 0;
492 }
493
494 static int help(void) {
495
496 printf("%s [OPTIONS...] COMMAND ...\n\n"
497 "Query or change system time and date settings.\n\n"
498 " -h --help Show this help\n"
499 " --version Show package version\n"
500 " --adjust-system-clock\n"
501 " Adjust system clock when changing local RTC mode\n"
502 " --no-pager Do not pipe output into a pager\n"
503 " --no-ask-password Do not prompt for password\n"
504 " -H --host=[USER@]HOST Operate on remote host\n\n"
505 "Commands:\n"
506 " status Show current time settings\n"
507 " set-time TIME Set system time\n"
508 " set-timezone ZONE Set system timezone\n"
509 " list-timezones Show known timezones\n"
510 " set-local-rtc BOOL Control whether RTC is in local time\n"
511 " set-ntp BOOL Control whether NTP is enabled\n",
512 program_invocation_short_name);
513
514 return 0;
515 }
516
517 static int parse_argv(int argc, char *argv[]) {
518
519 enum {
520 ARG_VERSION = 0x100,
521 ARG_NO_PAGER,
522 ARG_ADJUST_SYSTEM_CLOCK,
523 ARG_NO_ASK_PASSWORD
524 };
525
526 static const struct option options[] = {
527 { "help", no_argument, NULL, 'h' },
528 { "version", no_argument, NULL, ARG_VERSION },
529 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
530 { "host", required_argument, NULL, 'H' },
531 { "privileged", no_argument, NULL, 'P' },
532 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
533 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
534 { NULL, 0, NULL, 0 }
535 };
536
537 int c;
538
539 assert(argc >= 0);
540 assert(argv);
541
542 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
543
544 switch (c) {
545
546 case 'h':
547 help();
548 return 0;
549
550 case ARG_VERSION:
551 puts(PACKAGE_STRING);
552 puts(SYSTEMD_FEATURES);
553 return 0;
554
555 case 'P':
556 arg_transport = TRANSPORT_POLKIT;
557 break;
558
559 case 'H':
560 arg_transport = TRANSPORT_SSH;
561 arg_host = optarg;
562 break;
563
564 case ARG_ADJUST_SYSTEM_CLOCK:
565 arg_adjust_system_clock = true;
566 break;
567
568 case ARG_NO_PAGER:
569 arg_no_pager = true;
570 break;
571
572 case '?':
573 return -EINVAL;
574
575 default:
576 log_error("Unknown option code %c", c);
577 return -EINVAL;
578 }
579 }
580
581 return 1;
582 }
583
584 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
585
586 static const struct {
587 const char* verb;
588 const enum {
589 MORE,
590 LESS,
591 EQUAL
592 } argc_cmp;
593 const int argc;
594 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
595 } verbs[] = {
596 { "status", LESS, 1, show_status },
597 { "set-time", EQUAL, 2, set_time },
598 { "set-timezone", EQUAL, 2, set_timezone },
599 { "list-timezones", EQUAL, 1, list_timezones },
600 { "set-local-rtc", EQUAL, 2, set_local_rtc },
601 { "set-ntp", EQUAL, 2, set_ntp, },
602 };
603
604 int left;
605 unsigned i;
606
607 assert(argc >= 0);
608 assert(argv);
609 assert(error);
610
611 left = argc - optind;
612
613 if (left <= 0)
614 /* Special rule: no arguments means "status" */
615 i = 0;
616 else {
617 if (streq(argv[optind], "help")) {
618 help();
619 return 0;
620 }
621
622 for (i = 0; i < ELEMENTSOF(verbs); i++)
623 if (streq(argv[optind], verbs[i].verb))
624 break;
625
626 if (i >= ELEMENTSOF(verbs)) {
627 log_error("Unknown operation %s", argv[optind]);
628 return -EINVAL;
629 }
630 }
631
632 switch (verbs[i].argc_cmp) {
633
634 case EQUAL:
635 if (left != verbs[i].argc) {
636 log_error("Invalid number of arguments.");
637 return -EINVAL;
638 }
639
640 break;
641
642 case MORE:
643 if (left < verbs[i].argc) {
644 log_error("Too few arguments.");
645 return -EINVAL;
646 }
647
648 break;
649
650 case LESS:
651 if (left > verbs[i].argc) {
652 log_error("Too many arguments.");
653 return -EINVAL;
654 }
655
656 break;
657
658 default:
659 assert_not_reached("Unknown comparison operator.");
660 }
661
662 if (!bus) {
663 log_error("Failed to get D-Bus connection: %s", error->message);
664 return -EIO;
665 }
666
667 return verbs[i].dispatch(bus, argv + optind, left);
668 }
669
670 int main(int argc, char *argv[]) {
671 int r, retval = EXIT_FAILURE;
672 DBusConnection *bus = NULL;
673 DBusError error;
674
675 dbus_error_init(&error);
676
677 setlocale(LC_ALL, "");
678 log_parse_environment();
679 log_open();
680
681 r = parse_argv(argc, argv);
682 if (r < 0)
683 goto finish;
684 else if (r == 0) {
685 retval = EXIT_SUCCESS;
686 goto finish;
687 }
688
689 if (arg_transport == TRANSPORT_NORMAL)
690 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
691 else if (arg_transport == TRANSPORT_POLKIT)
692 bus_connect_system_polkit(&bus, &error);
693 else if (arg_transport == TRANSPORT_SSH)
694 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
695 else
696 assert_not_reached("Uh, invalid transport...");
697
698 r = timedatectl_main(bus, argc, argv, &error);
699 retval = r < 0 ? EXIT_FAILURE : r;
700
701 finish:
702 if (bus) {
703 dbus_connection_flush(bus);
704 dbus_connection_close(bus);
705 dbus_connection_unref(bus);
706 }
707
708 dbus_error_free(&error);
709 dbus_shutdown();
710
711 pager_close();
712
713 return retval;
714 }