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: n/a\n");
142 printf(" DST active: %s\n", yes_no(is_dstc));
146 assert_se(strftime(a, sizeof(a), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
150 assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
152 printf(" Last DST change: %s → %s, one hour %s\n"
155 strna(zn), strna(zc), is_dstc ? "forward" : "backwards", a, b);
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(&tn, &tm)) > 0);
165 printf(" Next DST change: %s → %s, one hour %s\n"
168 strna(zc), strna(zn), is_dstn ? "forward" : "backwards", a, b);
175 fputs("\n" ANSI_HIGHLIGHT_ON
176 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
177 " mode is not fully supported and will create various problems with time\n"
178 " zone changes and daylight saving adjustments. If at all possible use\n"
179 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
182 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
186 switch (dbus_message_iter_get_arg_type(iter)) {
188 case DBUS_TYPE_STRING: {
191 dbus_message_iter_get_basic(iter, &s);
193 if (streq(name, "Timezone"))
199 case DBUS_TYPE_BOOLEAN: {
202 dbus_message_iter_get_basic(iter, &b);
203 if (streq(name, "LocalRTC"))
205 else if (streq(name, "NTP"))
213 static int show_status(DBusConnection *bus, char **args, unsigned n) {
214 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
215 const char *interface = "";
217 DBusMessageIter iter, sub, sub2, sub3;
222 r = bus_method_call_with_reply(
224 "org.freedesktop.timedate1",
225 "/org/freedesktop/timedate1",
226 "org.freedesktop.DBus.Properties",
230 DBUS_TYPE_STRING, &interface,
235 if (!dbus_message_iter_init(reply, &iter) ||
236 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
237 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
238 log_error("Failed to parse reply.");
243 dbus_message_iter_recurse(&iter, &sub);
245 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
248 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
249 log_error("Failed to parse reply.");
253 dbus_message_iter_recurse(&sub, &sub2);
255 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
256 log_error("Failed to parse reply.");
260 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
261 log_error("Failed to parse reply.");
265 dbus_message_iter_recurse(&sub2, &sub3);
267 r = status_property(name, &sub3, &info);
269 log_error("Failed to parse reply.");
273 dbus_message_iter_next(&sub);
276 print_status_info(&info);
280 static int set_time(DBusConnection *bus, char **args, unsigned n) {
281 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
282 dbus_bool_t relative = false, interactive = true;
290 polkit_agent_open_if_enabled();
292 r = parse_timestamp(args[1], &t);
294 log_error("Failed to parse time specification: %s", args[1]);
298 u = (dbus_uint64_t) t;
300 return bus_method_call_with_reply(
302 "org.freedesktop.timedate1",
303 "/org/freedesktop/timedate1",
304 "org.freedesktop.timedate1",
309 DBUS_TYPE_BOOLEAN, &relative,
310 DBUS_TYPE_BOOLEAN, &interactive,
314 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
315 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
316 dbus_bool_t interactive = true;
321 polkit_agent_open_if_enabled();
323 return bus_method_call_with_reply(
325 "org.freedesktop.timedate1",
326 "/org/freedesktop/timedate1",
327 "org.freedesktop.timedate1",
331 DBUS_TYPE_STRING, &args[1],
332 DBUS_TYPE_BOOLEAN, &interactive,
336 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
337 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
338 dbus_bool_t interactive = true, b, q;
344 polkit_agent_open_if_enabled();
346 r = parse_boolean(args[1]);
348 log_error("Failed to parse local RTC setting: %s", args[1]);
353 q = arg_adjust_system_clock;
355 return bus_method_call_with_reply(
357 "org.freedesktop.timedate1",
358 "/org/freedesktop/timedate1",
359 "org.freedesktop.timedate1",
363 DBUS_TYPE_BOOLEAN, &b,
364 DBUS_TYPE_BOOLEAN, &q,
365 DBUS_TYPE_BOOLEAN, &interactive,
369 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
370 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
371 dbus_bool_t interactive = true, b;
377 polkit_agent_open_if_enabled();
379 r = parse_boolean(args[1]);
381 log_error("Failed to parse NTP setting: %s", args[1]);
387 return bus_method_call_with_reply(
389 "org.freedesktop.timedate1",
390 "/org/freedesktop/timedate1",
391 "org.freedesktop.timedate1",
395 DBUS_TYPE_BOOLEAN, &b,
396 DBUS_TYPE_BOOLEAN, &interactive,
400 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
401 _cleanup_fclose_ FILE *f = NULL;
402 _cleanup_strv_free_ char **zones = NULL;
409 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
411 log_error("Failed to open timezone database: %m");
416 char l[LINE_MAX], *p, **z, *w;
419 if (!fgets(l, sizeof(l), f)) {
423 log_error("Failed to read timezone database: %m");
429 if (isempty(p) || *p == '#')
433 /* Skip over country code */
434 p += strcspn(p, WHITESPACE);
435 p += strspn(p, WHITESPACE);
437 /* Skip over coordinates */
438 p += strcspn(p, WHITESPACE);
439 p += strspn(p, WHITESPACE);
441 /* Found timezone name */
442 k = strcspn(p, WHITESPACE);
450 z = realloc(zones, sizeof(char*) * (n_zones + 2));
457 zones[n_zones++] = w;
461 zones[n_zones] = NULL;
463 pager_open_if_enabled();
466 STRV_FOREACH(i, zones)
472 static int help(void) {
474 printf("%s [OPTIONS...] COMMAND ...\n\n"
475 "Query or change system time and date settings.\n\n"
476 " -h --help Show this help\n"
477 " --version Show package version\n"
478 " --adjust-system-clock\n"
479 " Adjust system clock when changing local RTC mode\n"
480 " --no-pager Do not pipe output into a pager\n"
481 " --no-ask-password Do not prompt for password\n"
482 " -H --host=[USER@]HOST Operate on remote host\n\n"
484 " status Show current time settings\n"
485 " set-time TIME Set system time\n"
486 " set-timezone ZONE Set system timezone\n"
487 " list-timezones Show known timezones\n"
488 " set-local-rtc BOOL Control whether RTC is in local time\n"
489 " set-ntp BOOL Control whether NTP is enabled\n",
490 program_invocation_short_name);
495 static int parse_argv(int argc, char *argv[]) {
500 ARG_ADJUST_SYSTEM_CLOCK,
504 static const struct option options[] = {
505 { "help", no_argument, NULL, 'h' },
506 { "version", no_argument, NULL, ARG_VERSION },
507 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
508 { "host", required_argument, NULL, 'H' },
509 { "privileged", no_argument, NULL, 'P' },
510 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
511 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
520 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
529 puts(PACKAGE_STRING);
531 puts(SYSTEMD_FEATURES);
535 arg_transport = TRANSPORT_POLKIT;
539 arg_transport = TRANSPORT_SSH;
543 case ARG_ADJUST_SYSTEM_CLOCK:
544 arg_adjust_system_clock = true;
555 log_error("Unknown option code %c", c);
563 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
565 static const struct {
573 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
575 { "status", LESS, 1, show_status },
576 { "set-time", EQUAL, 2, set_time },
577 { "set-timezone", EQUAL, 2, set_timezone },
578 { "list-timezones", EQUAL, 1, list_timezones },
579 { "set-local-rtc", EQUAL, 2, set_local_rtc },
580 { "set-ntp", EQUAL, 2, set_ntp, },
590 left = argc - optind;
593 /* Special rule: no arguments means "status" */
596 if (streq(argv[optind], "help")) {
601 for (i = 0; i < ELEMENTSOF(verbs); i++)
602 if (streq(argv[optind], verbs[i].verb))
605 if (i >= ELEMENTSOF(verbs)) {
606 log_error("Unknown operation %s", argv[optind]);
611 switch (verbs[i].argc_cmp) {
614 if (left != verbs[i].argc) {
615 log_error("Invalid number of arguments.");
622 if (left < verbs[i].argc) {
623 log_error("Too few arguments.");
630 if (left > verbs[i].argc) {
631 log_error("Too many arguments.");
638 assert_not_reached("Unknown comparison operator.");
642 log_error("Failed to get D-Bus connection: %s", error->message);
646 return verbs[i].dispatch(bus, argv + optind, left);
649 int main(int argc, char *argv[]) {
650 int r, retval = EXIT_FAILURE;
651 DBusConnection *bus = NULL;
654 dbus_error_init(&error);
656 log_parse_environment();
659 r = parse_argv(argc, argv);
663 retval = EXIT_SUCCESS;
667 if (arg_transport == TRANSPORT_NORMAL)
668 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
669 else if (arg_transport == TRANSPORT_POLKIT)
670 bus_connect_system_polkit(&bus, &error);
671 else if (arg_transport == TRANSPORT_SSH)
672 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
674 assert_not_reached("Uh, invalid transport...");
676 r = timedatectl_main(bus, argc, argv, &error);
677 retval = r < 0 ? EXIT_FAILURE : r;
681 dbus_connection_flush(bus);
682 dbus_connection_close(bus);
683 dbus_connection_unref(bus);
686 dbus_error_free(&error);