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/>.
27 #include <sys/timex.h>
29 #include "dbus-common.h"
31 #include "spawn-polkit-agent.h"
38 static bool arg_adjust_system_clock = false;
39 static bool arg_no_pager = false;
40 static enum transport {
44 } arg_transport = TRANSPORT_NORMAL;
45 static bool arg_ask_password = true;
46 static const char *arg_host = NULL;
48 static void pager_open_if_enabled(void) {
56 static void polkit_agent_open_if_enabled(void) {
58 /* Open the polkit agent as a child process if necessary */
60 if (!arg_ask_password)
66 typedef struct StatusInfo {
72 static bool ntp_synced(void) {
76 if (adjtimex(&txc) < 0)
79 if (txc.status & STA_UNSYNC)
85 static void print_status_info(StatusInfo *i) {
87 char a[FORMAT_TIMESTAMP_MAX];
88 char b[FORMAT_TIMESTAMP_MAX];
93 bool is_dstc, is_dstn;
98 n = now(CLOCK_REALTIME);
99 sec = (time_t) (n / USEC_PER_SEC);
102 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
104 printf(" Local time: %s\n", a);
107 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
109 printf(" Universal time: %s\n", a);
112 r = hwclock_get_time(&tm);
114 /* Calculcate the week-day */
117 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S", &tm) > 0);
119 printf(" RTC time: %s\n", a);
123 assert_se(strftime(a, sizeof(a), "%z", localtime_r(&sec, &tm)) > 0);
125 printf(" Timezone: %s\n"
128 "NTP synchronized: %s\n"
129 " RTC in local TZ: %s\n",
133 yes_no(ntp_synced()),
134 yes_no(i->local_rtc));
136 r = time_get_dst(sec, "/etc/localtime",
140 printf(" DST active: %s\n", yes_no(is_dstc));
144 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
148 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
150 printf(" Last DST change: %s → %s, one hour %s\n"
153 strna(zn), strna(zc), is_dstc ? "forward" : "backwards", a, b);
157 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
161 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
163 printf(" Next DST change: %s → %s, one hour %s\n"
166 strna(zc), strna(zn), is_dstn ? "forward" : "backwards", a, b);
173 fputs("\n" ANSI_HIGHLIGHT_ON
174 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
175 " mode is not fully supported and will create various problems with time\n"
176 " zone changes and daylight saving adjustments. If at all possible use\n"
177 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
180 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
184 switch (dbus_message_iter_get_arg_type(iter)) {
186 case DBUS_TYPE_STRING: {
189 dbus_message_iter_get_basic(iter, &s);
191 if (streq(name, "Timezone"))
197 case DBUS_TYPE_BOOLEAN: {
200 dbus_message_iter_get_basic(iter, &b);
201 if (streq(name, "LocalRTC"))
203 else if (streq(name, "NTP"))
211 static int show_status(DBusConnection *bus, char **args, unsigned n) {
212 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
213 const char *interface = "";
215 DBusMessageIter iter, sub, sub2, sub3;
220 r = bus_method_call_with_reply(
222 "org.freedesktop.timedate1",
223 "/org/freedesktop/timedate1",
224 "org.freedesktop.DBus.Properties",
228 DBUS_TYPE_STRING, &interface,
233 if (!dbus_message_iter_init(reply, &iter) ||
234 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
235 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
236 log_error("Failed to parse reply.");
241 dbus_message_iter_recurse(&iter, &sub);
243 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
246 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
247 log_error("Failed to parse reply.");
251 dbus_message_iter_recurse(&sub, &sub2);
253 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
254 log_error("Failed to parse reply.");
258 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
259 log_error("Failed to parse reply.");
263 dbus_message_iter_recurse(&sub2, &sub3);
265 r = status_property(name, &sub3, &info);
267 log_error("Failed to parse reply.");
271 dbus_message_iter_next(&sub);
274 print_status_info(&info);
278 static int set_time(DBusConnection *bus, char **args, unsigned n) {
279 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
280 dbus_bool_t relative = false, interactive = true;
288 polkit_agent_open_if_enabled();
290 r = parse_timestamp(args[1], &t);
292 log_error("Failed to parse time specification: %s", args[1]);
296 u = (dbus_uint64_t) t;
298 return bus_method_call_with_reply(
300 "org.freedesktop.timedate1",
301 "/org/freedesktop/timedate1",
302 "org.freedesktop.timedate1",
307 DBUS_TYPE_BOOLEAN, &relative,
308 DBUS_TYPE_BOOLEAN, &interactive,
312 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
313 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
314 dbus_bool_t interactive = true;
319 polkit_agent_open_if_enabled();
321 return bus_method_call_with_reply(
323 "org.freedesktop.timedate1",
324 "/org/freedesktop/timedate1",
325 "org.freedesktop.timedate1",
329 DBUS_TYPE_STRING, &args[1],
330 DBUS_TYPE_BOOLEAN, &interactive,
334 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
335 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
336 dbus_bool_t interactive = true, b, q;
342 polkit_agent_open_if_enabled();
344 r = parse_boolean(args[1]);
346 log_error("Failed to parse local RTC setting: %s", args[1]);
351 q = arg_adjust_system_clock;
353 return bus_method_call_with_reply(
355 "org.freedesktop.timedate1",
356 "/org/freedesktop/timedate1",
357 "org.freedesktop.timedate1",
361 DBUS_TYPE_BOOLEAN, &b,
362 DBUS_TYPE_BOOLEAN, &q,
363 DBUS_TYPE_BOOLEAN, &interactive,
367 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
368 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
369 dbus_bool_t interactive = true, b;
375 polkit_agent_open_if_enabled();
377 r = parse_boolean(args[1]);
379 log_error("Failed to parse NTP setting: %s", args[1]);
385 return bus_method_call_with_reply(
387 "org.freedesktop.timedate1",
388 "/org/freedesktop/timedate1",
389 "org.freedesktop.timedate1",
393 DBUS_TYPE_BOOLEAN, &b,
394 DBUS_TYPE_BOOLEAN, &interactive,
398 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
399 _cleanup_fclose_ FILE *f = NULL;
400 _cleanup_strv_free_ char **zones = NULL;
407 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
409 log_error("Failed to open timezone database: %m");
414 char l[LINE_MAX], *p, **z, *w;
417 if (!fgets(l, sizeof(l), f)) {
421 log_error("Failed to read timezone database: %m");
427 if (isempty(p) || *p == '#')
431 /* Skip over country code */
432 p += strcspn(p, WHITESPACE);
433 p += strspn(p, WHITESPACE);
435 /* Skip over coordinates */
436 p += strcspn(p, WHITESPACE);
437 p += strspn(p, WHITESPACE);
439 /* Found timezone name */
440 k = strcspn(p, WHITESPACE);
448 z = realloc(zones, sizeof(char*) * (n_zones + 2));
455 zones[n_zones++] = w;
459 zones[n_zones] = NULL;
461 pager_open_if_enabled();
464 STRV_FOREACH(i, zones)
470 static int help(void) {
472 printf("%s [OPTIONS...] COMMAND ...\n\n"
473 "Query or change system time and date settings.\n\n"
474 " -h --help Show this help\n"
475 " --version Show package version\n"
476 " --adjust-system-clock\n"
477 " Adjust system clock when changing local RTC mode\n"
478 " --no-pager Do not pipe output into a pager\n"
479 " --no-ask-password Do not prompt for password\n"
480 " -H --host=[USER@]HOST Operate on remote host\n\n"
482 " status Show current time settings\n"
483 " set-time TIME Set system time\n"
484 " set-timezone ZONE Set system timezone\n"
485 " list-timezones Show known timezones\n"
486 " set-local-rtc BOOL Control whether RTC is in local time\n"
487 " set-ntp BOOL Control whether NTP is enabled\n",
488 program_invocation_short_name);
493 static int parse_argv(int argc, char *argv[]) {
498 ARG_ADJUST_SYSTEM_CLOCK,
502 static const struct option options[] = {
503 { "help", no_argument, NULL, 'h' },
504 { "version", no_argument, NULL, ARG_VERSION },
505 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
506 { "host", required_argument, NULL, 'H' },
507 { "privileged", no_argument, NULL, 'P' },
508 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
509 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
518 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
527 puts(PACKAGE_STRING);
529 puts(SYSTEMD_FEATURES);
533 arg_transport = TRANSPORT_POLKIT;
537 arg_transport = TRANSPORT_SSH;
541 case ARG_ADJUST_SYSTEM_CLOCK:
542 arg_adjust_system_clock = true;
553 log_error("Unknown option code %c", c);
561 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
563 static const struct {
571 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
573 { "status", LESS, 1, show_status },
574 { "set-time", EQUAL, 2, set_time },
575 { "set-timezone", EQUAL, 2, set_timezone },
576 { "list-timezones", EQUAL, 1, list_timezones },
577 { "set-local-rtc", EQUAL, 2, set_local_rtc },
578 { "set-ntp", EQUAL, 2, set_ntp, },
588 left = argc - optind;
591 /* Special rule: no arguments means "status" */
594 if (streq(argv[optind], "help")) {
599 for (i = 0; i < ELEMENTSOF(verbs); i++)
600 if (streq(argv[optind], verbs[i].verb))
603 if (i >= ELEMENTSOF(verbs)) {
604 log_error("Unknown operation %s", argv[optind]);
609 switch (verbs[i].argc_cmp) {
612 if (left != verbs[i].argc) {
613 log_error("Invalid number of arguments.");
620 if (left < verbs[i].argc) {
621 log_error("Too few arguments.");
628 if (left > verbs[i].argc) {
629 log_error("Too many arguments.");
636 assert_not_reached("Unknown comparison operator.");
640 log_error("Failed to get D-Bus connection: %s", error->message);
644 return verbs[i].dispatch(bus, argv + optind, left);
647 int main(int argc, char *argv[]) {
648 int r, retval = EXIT_FAILURE;
649 DBusConnection *bus = NULL;
652 dbus_error_init(&error);
654 log_parse_environment();
657 r = parse_argv(argc, argv);
661 retval = EXIT_SUCCESS;
665 if (arg_transport == TRANSPORT_NORMAL)
666 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
667 else if (arg_transport == TRANSPORT_POLKIT)
668 bus_connect_system_polkit(&bus, &error);
669 else if (arg_transport == TRANSPORT_SSH)
670 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
672 assert_not_reached("Uh, invalid transport...");
674 r = timedatectl_main(bus, argc, argv, &error);
675 retval = r < 0 ? EXIT_FAILURE : r;
679 dbus_connection_flush(bus);
680 dbus_connection_close(bus);
681 dbus_connection_unref(bus);
684 dbus_error_free(&error);