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>
30 #include "dbus-common.h"
32 #include "spawn-polkit-agent.h"
39 static bool arg_adjust_system_clock = false;
40 static bool arg_no_pager = false;
41 static enum transport {
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static char *arg_host = NULL;
48 static char *arg_user = NULL;
50 static void pager_open_if_enabled(void) {
58 static void polkit_agent_open_if_enabled(void) {
60 /* Open the polkit agent as a child process if necessary */
62 if (!arg_ask_password)
68 typedef struct StatusInfo {
75 static const char *jump_str(int delta_minutes, char *s, size_t size) {
76 if (delta_minutes == 60)
77 return "one hour forward";
78 if (delta_minutes == -60)
79 return "one hour backwards";
80 if (delta_minutes < 0) {
81 snprintf(s, size, "%i minutes backwards", -delta_minutes);
84 if (delta_minutes > 0) {
85 snprintf(s, size, "%i minutes forward", delta_minutes);
91 static void print_status_info(StatusInfo *i) {
93 char a[FORMAT_TIMESTAMP_MAX];
94 char b[FORMAT_TIMESTAMP_MAX];
101 bool is_dstc, is_dstn;
106 /* enforce the values of /etc/localtime */
108 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
112 n = now(CLOCK_REALTIME);
113 sec = (time_t) (n / USEC_PER_SEC);
116 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
118 printf(" Local time: %s\n", a);
121 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
123 printf(" Universal time: %s\n", a);
126 r = hwclock_get_time(&tm);
128 /* Calculcate the week-day */
131 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
133 printf(" RTC time: %s\n", a);
137 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
139 printf(" Timezone: %s (%s)\n"
141 "NTP synchronized: %s\n"
142 " RTC in local TZ: %s\n",
145 i->can_ntp ? yes_no(i->ntp) : "n/a",
146 yes_no(ntp_synced()),
147 yes_no(i->local_rtc));
149 r = time_get_dst(sec, "/etc/localtime",
151 &tn, &dn, &zn, &is_dstn);
153 printf(" DST active: n/a\n");
155 printf(" DST active: %s\n", yes_no(is_dstc));
159 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
163 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
165 printf(" Last DST change: DST %s at\n"
168 is_dstc ? "began" : "ended", a, b);
172 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
176 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
178 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
181 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
188 fputs("\n" ANSI_HIGHLIGHT_ON
189 "Warning: The RTC is configured to maintain time in the local timezone. This\n"
190 " mode is not fully supported and will create various problems with time\n"
191 " zone changes and daylight saving adjustments. If at all possible use\n"
192 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
195 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
199 switch (dbus_message_iter_get_arg_type(iter)) {
201 case DBUS_TYPE_STRING: {
204 dbus_message_iter_get_basic(iter, &s);
206 if (streq(name, "Timezone"))
212 case DBUS_TYPE_BOOLEAN: {
215 dbus_message_iter_get_basic(iter, &b);
216 if (streq(name, "LocalRTC"))
218 else if (streq(name, "NTP"))
220 else if (streq(name, "CanNTP"))
228 static int show_status(DBusConnection *bus, char **args, unsigned n) {
229 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
230 const char *interface = "";
232 DBusMessageIter iter, sub, sub2, sub3;
233 StatusInfo info = {};
237 r = bus_method_call_with_reply(
239 "org.freedesktop.timedate1",
240 "/org/freedesktop/timedate1",
241 "org.freedesktop.DBus.Properties",
245 DBUS_TYPE_STRING, &interface,
250 if (!dbus_message_iter_init(reply, &iter) ||
251 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
252 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
253 log_error("Failed to parse reply.");
257 dbus_message_iter_recurse(&iter, &sub);
259 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
262 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
263 log_error("Failed to parse reply.");
267 dbus_message_iter_recurse(&sub, &sub2);
269 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
270 log_error("Failed to parse reply.");
274 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
275 log_error("Failed to parse reply.");
279 dbus_message_iter_recurse(&sub2, &sub3);
281 r = status_property(name, &sub3, &info);
283 log_error("Failed to parse reply.");
287 dbus_message_iter_next(&sub);
290 print_status_info(&info);
294 static int set_time(DBusConnection *bus, char **args, unsigned n) {
295 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
296 dbus_bool_t relative = false, interactive = arg_ask_password;
304 polkit_agent_open_if_enabled();
306 r = parse_timestamp(args[1], &t);
308 log_error("Failed to parse time specification: %s", args[1]);
312 u = (dbus_uint64_t) t;
314 return bus_method_call_with_reply(
316 "org.freedesktop.timedate1",
317 "/org/freedesktop/timedate1",
318 "org.freedesktop.timedate1",
323 DBUS_TYPE_BOOLEAN, &relative,
324 DBUS_TYPE_BOOLEAN, &interactive,
328 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
329 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
330 dbus_bool_t interactive = arg_ask_password;
335 polkit_agent_open_if_enabled();
337 return bus_method_call_with_reply(
339 "org.freedesktop.timedate1",
340 "/org/freedesktop/timedate1",
341 "org.freedesktop.timedate1",
345 DBUS_TYPE_STRING, &args[1],
346 DBUS_TYPE_BOOLEAN, &interactive,
350 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
351 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
352 dbus_bool_t interactive = arg_ask_password, b, q;
358 polkit_agent_open_if_enabled();
360 r = parse_boolean(args[1]);
362 log_error("Failed to parse local RTC setting: %s", args[1]);
367 q = arg_adjust_system_clock;
369 return bus_method_call_with_reply(
371 "org.freedesktop.timedate1",
372 "/org/freedesktop/timedate1",
373 "org.freedesktop.timedate1",
377 DBUS_TYPE_BOOLEAN, &b,
378 DBUS_TYPE_BOOLEAN, &q,
379 DBUS_TYPE_BOOLEAN, &interactive,
383 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
384 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
385 dbus_bool_t interactive = arg_ask_password, b;
391 polkit_agent_open_if_enabled();
393 r = parse_boolean(args[1]);
395 log_error("Failed to parse NTP setting: %s", args[1]);
401 return bus_method_call_with_reply(
403 "org.freedesktop.timedate1",
404 "/org/freedesktop/timedate1",
405 "org.freedesktop.timedate1",
409 DBUS_TYPE_BOOLEAN, &b,
410 DBUS_TYPE_BOOLEAN, &interactive,
414 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
415 _cleanup_fclose_ FILE *f = NULL;
416 _cleanup_strv_free_ char **zones = NULL;
422 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
424 log_error("Failed to open timezone database: %m");
429 char l[LINE_MAX], *p, **z, *w;
432 if (!fgets(l, sizeof(l), f)) {
436 log_error("Failed to read timezone database: %m");
442 if (isempty(p) || *p == '#')
446 /* Skip over country code */
447 p += strcspn(p, WHITESPACE);
448 p += strspn(p, WHITESPACE);
450 /* Skip over coordinates */
451 p += strcspn(p, WHITESPACE);
452 p += strspn(p, WHITESPACE);
454 /* Found timezone name */
455 k = strcspn(p, WHITESPACE);
463 z = realloc(zones, sizeof(char*) * (n_zones + 2));
470 zones[n_zones++] = w;
474 zones[n_zones] = NULL;
476 pager_open_if_enabled();
484 static int help(void) {
486 printf("%s [OPTIONS...] COMMAND ...\n\n"
487 "Query or change system time and date settings.\n\n"
488 " -h --help Show this help\n"
489 " --version Show package version\n"
490 " --adjust-system-clock\n"
491 " Adjust system clock when changing local RTC mode\n"
492 " --no-pager Do not pipe output into a pager\n"
493 " -P --privileged Acquire privileges before execution\n"
494 " --no-ask-password Do not prompt for password\n"
495 " -H --host=[USER@]HOST Operate on remote host\n\n"
497 " status Show current time settings\n"
498 " set-time TIME Set system time\n"
499 " set-timezone ZONE Set system timezone\n"
500 " list-timezones Show known timezones\n"
501 " set-local-rtc BOOL Control whether RTC is in local time\n"
502 " set-ntp BOOL Control whether NTP is enabled\n",
503 program_invocation_short_name);
508 static int parse_argv(int argc, char *argv[]) {
513 ARG_ADJUST_SYSTEM_CLOCK,
517 static const struct option options[] = {
518 { "help", no_argument, NULL, 'h' },
519 { "version", no_argument, NULL, ARG_VERSION },
520 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
521 { "host", required_argument, NULL, 'H' },
522 { "privileged", no_argument, NULL, 'P' },
523 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
524 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
533 while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
542 puts(PACKAGE_STRING);
543 puts(SYSTEMD_FEATURES);
547 arg_transport = TRANSPORT_POLKIT;
551 arg_transport = TRANSPORT_SSH;
552 parse_user_at_host(optarg, &arg_user, &arg_host);
555 case ARG_NO_ASK_PASSWORD:
556 arg_ask_password = false;
559 case ARG_ADJUST_SYSTEM_CLOCK:
560 arg_adjust_system_clock = true;
571 log_error("Unknown option code %c", c);
579 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
581 static const struct {
589 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
591 { "status", LESS, 1, show_status },
592 { "set-time", EQUAL, 2, set_time },
593 { "set-timezone", EQUAL, 2, set_timezone },
594 { "list-timezones", EQUAL, 1, list_timezones },
595 { "set-local-rtc", EQUAL, 2, set_local_rtc },
596 { "set-ntp", EQUAL, 2, set_ntp, },
606 left = argc - optind;
609 /* Special rule: no arguments means "status" */
612 if (streq(argv[optind], "help")) {
617 for (i = 0; i < ELEMENTSOF(verbs); i++)
618 if (streq(argv[optind], verbs[i].verb))
621 if (i >= ELEMENTSOF(verbs)) {
622 log_error("Unknown operation %s", argv[optind]);
627 switch (verbs[i].argc_cmp) {
630 if (left != verbs[i].argc) {
631 log_error("Invalid number of arguments.");
638 if (left < verbs[i].argc) {
639 log_error("Too few arguments.");
646 if (left > verbs[i].argc) {
647 log_error("Too many arguments.");
654 assert_not_reached("Unknown comparison operator.");
658 log_error("Failed to get D-Bus connection: %s", error->message);
662 return verbs[i].dispatch(bus, argv + optind, left);
665 int main(int argc, char *argv[]) {
666 int r, retval = EXIT_FAILURE;
667 DBusConnection *bus = NULL;
670 dbus_error_init(&error);
672 setlocale(LC_ALL, "");
673 log_parse_environment();
676 r = parse_argv(argc, argv);
680 retval = EXIT_SUCCESS;
684 if (arg_transport == TRANSPORT_NORMAL)
685 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
686 else if (arg_transport == TRANSPORT_POLKIT)
687 bus_connect_system_polkit(&bus, &error);
688 else if (arg_transport == TRANSPORT_SSH)
689 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
691 assert_not_reached("Uh, invalid transport...");
693 r = timedatectl_main(bus, argc, argv, &error);
694 retval = r < 0 ? EXIT_FAILURE : r;
698 dbus_connection_flush(bus);
699 dbus_connection_close(bus);
700 dbus_connection_unref(bus);
703 dbus_error_free(&error);