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 bool ntp_synced(void) {
76 struct timex txc = {};
78 if (adjtimex(&txc) < 0)
81 if (txc.status & STA_UNSYNC)
87 static const char *jump_str(int delta_minutes, char *s, size_t size) {
88 if (delta_minutes == 60)
89 return "one hour forward";
90 if (delta_minutes == -60)
91 return "one hour backwards";
92 if (delta_minutes < 0) {
93 snprintf(s, size, "%i minutes backwards", -delta_minutes);
96 if (delta_minutes > 0) {
97 snprintf(s, size, "%i minutes forward", delta_minutes);
103 static void print_status_info(StatusInfo *i) {
105 char a[FORMAT_TIMESTAMP_MAX];
106 char b[FORMAT_TIMESTAMP_MAX];
113 bool is_dstc, is_dstn;
118 /* enforce the values of /etc/localtime */
120 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
124 n = now(CLOCK_REALTIME);
125 sec = (time_t) (n / USEC_PER_SEC);
128 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
130 printf(" Local time: %s\n", a);
133 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
135 printf(" Universal time: %s\n", a);
138 r = hwclock_get_time(&tm);
140 /* Calculcate the week-day */
143 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
145 printf(" RTC time: %s\n", a);
149 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
151 printf(" Timezone: %s (%s)\n"
153 "NTP synchronized: %s\n"
154 " RTC in local TZ: %s\n",
157 i->can_ntp ? yes_no(i->ntp) : "n/a",
158 yes_no(ntp_synced()),
159 yes_no(i->local_rtc));
161 r = time_get_dst(sec, "/etc/localtime",
163 &tn, &dn, &zn, &is_dstn);
165 printf(" DST active: n/a\n");
167 printf(" DST active: %s\n", yes_no(is_dstc));
171 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
175 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
177 printf(" Last DST change: DST %s at\n"
180 is_dstc ? "began" : "ended", a, b);
184 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
188 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
190 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
193 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
200 fputs("\n" ANSI_HIGHLIGHT_ON
201 "Warning: The RTC is configured to maintain time in the local timezone. This\n"
202 " mode is not fully supported and will create various problems with time\n"
203 " zone changes and daylight saving adjustments. If at all possible use\n"
204 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
207 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
211 switch (dbus_message_iter_get_arg_type(iter)) {
213 case DBUS_TYPE_STRING: {
216 dbus_message_iter_get_basic(iter, &s);
218 if (streq(name, "Timezone"))
224 case DBUS_TYPE_BOOLEAN: {
227 dbus_message_iter_get_basic(iter, &b);
228 if (streq(name, "LocalRTC"))
230 else if (streq(name, "NTP"))
232 else if (streq(name, "CanNTP"))
240 static int show_status(DBusConnection *bus, char **args, unsigned n) {
241 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
242 const char *interface = "";
244 DBusMessageIter iter, sub, sub2, sub3;
245 StatusInfo info = {};
249 r = bus_method_call_with_reply(
251 "org.freedesktop.timedate1",
252 "/org/freedesktop/timedate1",
253 "org.freedesktop.DBus.Properties",
257 DBUS_TYPE_STRING, &interface,
262 if (!dbus_message_iter_init(reply, &iter) ||
263 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
264 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
265 log_error("Failed to parse reply.");
269 dbus_message_iter_recurse(&iter, &sub);
271 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
274 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
275 log_error("Failed to parse reply.");
279 dbus_message_iter_recurse(&sub, &sub2);
281 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
282 log_error("Failed to parse reply.");
286 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
287 log_error("Failed to parse reply.");
291 dbus_message_iter_recurse(&sub2, &sub3);
293 r = status_property(name, &sub3, &info);
295 log_error("Failed to parse reply.");
299 dbus_message_iter_next(&sub);
302 print_status_info(&info);
306 static int set_time(DBusConnection *bus, char **args, unsigned n) {
307 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
308 dbus_bool_t relative = false, interactive = arg_ask_password;
316 polkit_agent_open_if_enabled();
318 r = parse_timestamp(args[1], &t);
320 log_error("Failed to parse time specification: %s", args[1]);
324 u = (dbus_uint64_t) t;
326 return bus_method_call_with_reply(
328 "org.freedesktop.timedate1",
329 "/org/freedesktop/timedate1",
330 "org.freedesktop.timedate1",
335 DBUS_TYPE_BOOLEAN, &relative,
336 DBUS_TYPE_BOOLEAN, &interactive,
340 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
341 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
342 dbus_bool_t interactive = arg_ask_password;
347 polkit_agent_open_if_enabled();
349 return bus_method_call_with_reply(
351 "org.freedesktop.timedate1",
352 "/org/freedesktop/timedate1",
353 "org.freedesktop.timedate1",
357 DBUS_TYPE_STRING, &args[1],
358 DBUS_TYPE_BOOLEAN, &interactive,
362 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
363 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
364 dbus_bool_t interactive = arg_ask_password, b, q;
370 polkit_agent_open_if_enabled();
372 r = parse_boolean(args[1]);
374 log_error("Failed to parse local RTC setting: %s", args[1]);
379 q = arg_adjust_system_clock;
381 return bus_method_call_with_reply(
383 "org.freedesktop.timedate1",
384 "/org/freedesktop/timedate1",
385 "org.freedesktop.timedate1",
389 DBUS_TYPE_BOOLEAN, &b,
390 DBUS_TYPE_BOOLEAN, &q,
391 DBUS_TYPE_BOOLEAN, &interactive,
395 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
396 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
397 dbus_bool_t interactive = arg_ask_password, b;
403 polkit_agent_open_if_enabled();
405 r = parse_boolean(args[1]);
407 log_error("Failed to parse NTP setting: %s", args[1]);
413 return bus_method_call_with_reply(
415 "org.freedesktop.timedate1",
416 "/org/freedesktop/timedate1",
417 "org.freedesktop.timedate1",
421 DBUS_TYPE_BOOLEAN, &b,
422 DBUS_TYPE_BOOLEAN, &interactive,
426 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
427 _cleanup_fclose_ FILE *f = NULL;
428 _cleanup_strv_free_ char **zones = NULL;
434 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
436 log_error("Failed to open timezone database: %m");
441 char l[LINE_MAX], *p, **z, *w;
444 if (!fgets(l, sizeof(l), f)) {
448 log_error("Failed to read timezone database: %m");
454 if (isempty(p) || *p == '#')
458 /* Skip over country code */
459 p += strcspn(p, WHITESPACE);
460 p += strspn(p, WHITESPACE);
462 /* Skip over coordinates */
463 p += strcspn(p, WHITESPACE);
464 p += strspn(p, WHITESPACE);
466 /* Found timezone name */
467 k = strcspn(p, WHITESPACE);
475 z = realloc(zones, sizeof(char*) * (n_zones + 2));
482 zones[n_zones++] = w;
486 zones[n_zones] = NULL;
488 pager_open_if_enabled();
496 static int help(void) {
498 printf("%s [OPTIONS...] COMMAND ...\n\n"
499 "Query or change system time and date settings.\n\n"
500 " -h --help Show this help\n"
501 " --version Show package version\n"
502 " --adjust-system-clock\n"
503 " Adjust system clock when changing local RTC mode\n"
504 " --no-pager Do not pipe output into a pager\n"
505 " -P --privileged Acquire privileges before execution\n"
506 " --no-ask-password Do not prompt for password\n"
507 " -H --host=[USER@]HOST Operate on remote host\n\n"
509 " status Show current time settings\n"
510 " set-time TIME Set system time\n"
511 " set-timezone ZONE Set system timezone\n"
512 " list-timezones Show known timezones\n"
513 " set-local-rtc BOOL Control whether RTC is in local time\n"
514 " set-ntp BOOL Control whether NTP is enabled\n",
515 program_invocation_short_name);
520 static int parse_argv(int argc, char *argv[]) {
525 ARG_ADJUST_SYSTEM_CLOCK,
529 static const struct option options[] = {
530 { "help", no_argument, NULL, 'h' },
531 { "version", no_argument, NULL, ARG_VERSION },
532 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
533 { "host", required_argument, NULL, 'H' },
534 { "privileged", no_argument, NULL, 'P' },
535 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
536 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
545 while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
554 puts(PACKAGE_STRING);
555 puts(SYSTEMD_FEATURES);
559 arg_transport = TRANSPORT_POLKIT;
563 arg_transport = TRANSPORT_SSH;
564 parse_user_at_host(optarg, &arg_user, &arg_host);
567 case ARG_NO_ASK_PASSWORD:
568 arg_ask_password = false;
571 case ARG_ADJUST_SYSTEM_CLOCK:
572 arg_adjust_system_clock = true;
583 log_error("Unknown option code %c", c);
591 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
593 static const struct {
601 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
603 { "status", LESS, 1, show_status },
604 { "set-time", EQUAL, 2, set_time },
605 { "set-timezone", EQUAL, 2, set_timezone },
606 { "list-timezones", EQUAL, 1, list_timezones },
607 { "set-local-rtc", EQUAL, 2, set_local_rtc },
608 { "set-ntp", EQUAL, 2, set_ntp, },
618 left = argc - optind;
621 /* Special rule: no arguments means "status" */
624 if (streq(argv[optind], "help")) {
629 for (i = 0; i < ELEMENTSOF(verbs); i++)
630 if (streq(argv[optind], verbs[i].verb))
633 if (i >= ELEMENTSOF(verbs)) {
634 log_error("Unknown operation %s", argv[optind]);
639 switch (verbs[i].argc_cmp) {
642 if (left != verbs[i].argc) {
643 log_error("Invalid number of arguments.");
650 if (left < verbs[i].argc) {
651 log_error("Too few arguments.");
658 if (left > verbs[i].argc) {
659 log_error("Too many arguments.");
666 assert_not_reached("Unknown comparison operator.");
670 log_error("Failed to get D-Bus connection: %s", error->message);
674 return verbs[i].dispatch(bus, argv + optind, left);
677 int main(int argc, char *argv[]) {
678 int r, retval = EXIT_FAILURE;
679 DBusConnection *bus = NULL;
682 dbus_error_init(&error);
684 setlocale(LC_ALL, "");
685 log_parse_environment();
688 r = parse_argv(argc, argv);
692 retval = EXIT_SUCCESS;
696 if (arg_transport == TRANSPORT_NORMAL)
697 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
698 else if (arg_transport == TRANSPORT_POLKIT)
699 bus_connect_system_polkit(&bus, &error);
700 else if (arg_transport == TRANSPORT_SSH)
701 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
703 assert_not_reached("Uh, invalid transport...");
705 r = timedatectl_main(bus, argc, argv, &error);
706 retval = r < 0 ? EXIT_FAILURE : r;
710 dbus_connection_flush(bus);
711 dbus_connection_close(bus);
712 dbus_connection_unref(bus);
715 dbus_error_free(&error);