1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
28 #include <sys/timex.h>
32 #include "bus-error.h"
34 #include "spawn-polkit-agent.h"
41 static bool arg_adjust_system_clock = false;
42 static bool arg_no_pager = false;
43 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
44 static bool arg_ask_password = true;
45 static char *arg_host = NULL;
47 static void pager_open_if_enabled(void) {
55 static void polkit_agent_open_if_enabled(void) {
57 /* Open the polkit agent as a child process if necessary */
59 if (!arg_ask_password)
65 typedef struct StatusInfo {
72 static const char *jump_str(int delta_minutes, char *s, size_t size) {
73 if (delta_minutes == 60)
74 return "one hour forward";
75 if (delta_minutes == -60)
76 return "one hour backwards";
77 if (delta_minutes < 0) {
78 snprintf(s, size, "%i minutes backwards", -delta_minutes);
81 if (delta_minutes > 0) {
82 snprintf(s, size, "%i minutes forward", delta_minutes);
88 static void print_status_info(StatusInfo *i) {
90 char a[FORMAT_TIMESTAMP_MAX];
91 char b[FORMAT_TIMESTAMP_MAX];
98 bool is_dstc, is_dstn;
103 /* enforce the values of /etc/localtime */
105 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
109 n = now(CLOCK_REALTIME);
110 sec = (time_t) (n / USEC_PER_SEC);
113 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
115 printf(" Local time: %s\n", a);
118 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
120 printf(" Universal time: %s\n", a);
123 r = hwclock_get_time(&tm);
125 /* Calculate the week-day */
128 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
130 printf(" RTC time: %s\n", a);
134 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
136 printf(" Timezone: %s (%s)\n"
138 "NTP synchronized: %s\n"
139 " RTC in local TZ: %s\n",
142 i->can_ntp ? yes_no(i->ntp) : "n/a",
143 yes_no(ntp_synced()),
144 yes_no(i->local_rtc));
146 r = time_get_dst(sec, "/etc/localtime",
148 &tn, &dn, &zn, &is_dstn);
150 printf(" DST active: n/a\n");
152 printf(" DST active: %s\n", yes_no(is_dstc));
156 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
160 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
162 printf(" Last DST change: DST %s at\n"
165 is_dstc ? "began" : "ended", a, b);
169 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
173 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
175 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
178 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
185 fputs("\n" ANSI_HIGHLIGHT_ON
186 "Warning: The RTC is configured to maintain time in the local timezone. This\n"
187 " mode is not fully supported and will create various problems with time\n"
188 " zone changes and daylight saving adjustments. If at all possible use\n"
189 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
192 static int get_timedate_property_bool(sd_bus *bus, const char *name, bool *target) {
193 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
194 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
199 r = sd_bus_get_property(bus,
200 "org.freedesktop.timedate1",
201 "/org/freedesktop/timedate1",
202 "org.freedesktop.timedate1",
208 log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
212 r = sd_bus_message_read(reply, "b", target);
214 log_error("Failed to parse reply.");
221 static int show_status(sd_bus *bus, char **args, unsigned n) {
222 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
223 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
225 StatusInfo info = {};
229 r = sd_bus_get_property(bus,
230 "org.freedesktop.timedate1",
231 "/org/freedesktop/timedate1",
232 "org.freedesktop.timedate1",
238 log_error("Failed to get property: Timezone %s", bus_error_message(&error, -r));
242 r = sd_bus_message_read(reply, "s", &info.timezone);
246 r = get_timedate_property_bool(bus, "LocalRTC", &info.local_rtc);
250 r = get_timedate_property_bool(bus, "NTP", &info.ntp);
254 r = get_timedate_property_bool(bus, "CanNTP", &info.can_ntp);
258 print_status_info(&info);
262 static int set_time(sd_bus *bus, char **args, unsigned n) {
263 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
264 bool relative = false, interactive = arg_ask_password;
271 polkit_agent_open_if_enabled();
273 r = parse_timestamp(args[1], &t);
275 log_error("Failed to parse time specification: %s", args[1]);
279 r = sd_bus_call_method(bus,
280 "org.freedesktop.timedate1",
281 "/org/freedesktop/timedate1",
282 "org.freedesktop.timedate1",
286 "xbb", (int64_t)t, relative, interactive);
288 log_error("Failed to set time: %s", bus_error_message(&error, -r));
293 static int set_timezone(sd_bus *bus, char **args, unsigned n) {
294 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
295 bool interactive = arg_ask_password;
301 polkit_agent_open_if_enabled();
303 r = sd_bus_call_method(bus,
304 "org.freedesktop.timedate1",
305 "/org/freedesktop/timedate1",
306 "org.freedesktop.timedate1",
310 "sb", args[1], interactive);
312 log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
317 static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
318 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
319 bool interactive = arg_ask_password, b, q;
325 polkit_agent_open_if_enabled();
327 r = parse_boolean(args[1]);
329 log_error("Failed to parse local RTC setting: %s", args[1]);
334 q = arg_adjust_system_clock;
336 r = sd_bus_call_method(bus,
337 "org.freedesktop.timedate1",
338 "/org/freedesktop/timedate1",
339 "org.freedesktop.timedate1",
343 "bbb", b, q, interactive);
345 log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
350 static int set_ntp(sd_bus *bus, char **args, unsigned n) {
351 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
352 bool interactive = arg_ask_password, b;
358 polkit_agent_open_if_enabled();
360 r = parse_boolean(args[1]);
362 log_error("Failed to parse NTP setting: %s", args[1]);
368 r = sd_bus_call_method(bus,
369 "org.freedesktop.timedate1",
370 "/org/freedesktop/timedate1",
371 "org.freedesktop.timedate1",
375 "bb", b, interactive);
377 log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
382 static int list_timezones(sd_bus *bus, char **args, unsigned n) {
383 _cleanup_fclose_ FILE *f = NULL;
384 _cleanup_strv_free_ char **zones = NULL;
390 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
392 log_error("Failed to open timezone database: %m");
397 char l[LINE_MAX], *p, **z, *w;
400 if (!fgets(l, sizeof(l), f)) {
404 log_error("Failed to read timezone database: %m");
410 if (isempty(p) || *p == '#')
414 /* Skip over country code */
415 p += strcspn(p, WHITESPACE);
416 p += strspn(p, WHITESPACE);
418 /* Skip over coordinates */
419 p += strcspn(p, WHITESPACE);
420 p += strspn(p, WHITESPACE);
422 /* Found timezone name */
423 k = strcspn(p, WHITESPACE);
431 z = realloc(zones, sizeof(char*) * (n_zones + 2));
438 zones[n_zones++] = w;
442 zones[n_zones] = NULL;
444 pager_open_if_enabled();
452 static int help(void) {
454 printf("%s [OPTIONS...] COMMAND ...\n\n"
455 "Query or change system time and date settings.\n\n"
456 " -h --help Show this help\n"
457 " --version Show package version\n"
458 " --adjust-system-clock\n"
459 " Adjust system clock when changing local RTC mode\n"
460 " --no-pager Do not pipe output into a pager\n"
461 " --no-ask-password Do not prompt for password\n"
462 " -H --host=[USER@]HOST Operate on remote host\n"
463 " -M --machine=CONTAINER Operate on local container\n\n"
465 " status Show current time settings\n"
466 " set-time TIME Set system time\n"
467 " set-timezone ZONE Set system timezone\n"
468 " list-timezones Show known timezones\n"
469 " set-local-rtc BOOL Control whether RTC is in local time\n"
470 " set-ntp BOOL Control whether NTP is enabled\n",
471 program_invocation_short_name);
476 static int parse_argv(int argc, char *argv[]) {
481 ARG_ADJUST_SYSTEM_CLOCK,
485 static const struct option options[] = {
486 { "help", no_argument, NULL, 'h' },
487 { "version", no_argument, NULL, ARG_VERSION },
488 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
489 { "host", required_argument, NULL, 'H' },
490 { "machine", required_argument, NULL, 'M' },
491 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
492 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
501 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
510 puts(PACKAGE_STRING);
511 puts(SYSTEMD_FEATURES);
515 arg_transport = BUS_TRANSPORT_REMOTE;
520 arg_transport = BUS_TRANSPORT_CONTAINER;
524 case ARG_NO_ASK_PASSWORD:
525 arg_ask_password = false;
528 case ARG_ADJUST_SYSTEM_CLOCK:
529 arg_adjust_system_clock = true;
540 log_error("Unknown option code %c", c);
548 static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
550 static const struct {
558 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
560 { "status", LESS, 1, show_status },
561 { "set-time", EQUAL, 2, set_time },
562 { "set-timezone", EQUAL, 2, set_timezone },
563 { "list-timezones", EQUAL, 1, list_timezones },
564 { "set-local-rtc", EQUAL, 2, set_local_rtc },
565 { "set-ntp", EQUAL, 2, set_ntp, },
574 left = argc - optind;
577 /* Special rule: no arguments means "status" */
580 if (streq(argv[optind], "help")) {
585 for (i = 0; i < ELEMENTSOF(verbs); i++)
586 if (streq(argv[optind], verbs[i].verb))
589 if (i >= ELEMENTSOF(verbs)) {
590 log_error("Unknown operation %s", argv[optind]);
595 switch (verbs[i].argc_cmp) {
598 if (left != verbs[i].argc) {
599 log_error("Invalid number of arguments.");
606 if (left < verbs[i].argc) {
607 log_error("Too few arguments.");
614 if (left > verbs[i].argc) {
615 log_error("Too many arguments.");
622 assert_not_reached("Unknown comparison operator.");
625 return verbs[i].dispatch(bus, argv + optind, left);
628 int main(int argc, char *argv[]) {
629 int r, ret = EXIT_FAILURE;
630 _cleanup_bus_unref_ sd_bus *bus = NULL;
632 setlocale(LC_ALL, "");
633 log_parse_environment();
636 r = parse_argv(argc, argv);
644 r = bus_open_transport(arg_transport, arg_host, false, &bus);
646 log_error("Failed to create bus connection: %s", strerror(-r));
651 r = timedatectl_main(bus, argc, argv);
652 ret = r < 0 ? EXIT_FAILURE : r;