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 const char *arg_host = NULL;
49 static void pager_open_if_enabled(void) {
57 static void polkit_agent_open_if_enabled(void) {
59 /* Open the polkit agent as a child process if necessary */
61 if (!arg_ask_password)
67 typedef struct StatusInfo {
73 static bool ntp_synced(void) {
77 if (adjtimex(&txc) < 0)
80 if (txc.status & STA_UNSYNC)
86 static const char *jump_str(int delta_minutes, char *s, size_t size) {
87 if (delta_minutes == 60)
88 return "one hour forward";
89 if (delta_minutes == -60)
90 return "one hour backwards";
91 if (delta_minutes < 0) {
92 snprintf(s, size, "%i minutes backwards", -delta_minutes);
95 if (delta_minutes > 0) {
96 snprintf(s, size, "%i minutes forward", delta_minutes);
102 static void print_status_info(StatusInfo *i) {
104 char a[FORMAT_TIMESTAMP_MAX];
105 char b[FORMAT_TIMESTAMP_MAX];
112 bool is_dstc, is_dstn;
117 /* enforce the values of /etc/localtime */
119 fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
123 n = now(CLOCK_REALTIME);
124 sec = (time_t) (n / USEC_PER_SEC);
127 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
129 printf(" Local time: %s\n", a);
132 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
134 printf(" Universal time: %s\n", a);
137 r = hwclock_get_time(&tm);
139 /* Calculcate the week-day */
142 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", &tm) > 0);
144 printf(" RTC time: %s\n", a);
148 assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
150 printf(" Timezone: %s (%s)\n"
152 "NTP synchronized: %s\n"
153 " RTC in local TZ: %s\n",
157 yes_no(ntp_synced()),
158 yes_no(i->local_rtc));
160 r = time_get_dst(sec, "/etc/localtime",
162 &tn, &dn, &zn, &is_dstn);
164 printf(" DST active: n/a\n");
166 printf(" DST active: %s\n", yes_no(is_dstc));
170 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
174 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
176 printf(" Last DST change: DST %s at\n"
179 is_dstc ? "began" : "ended", a, b);
183 assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
187 assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
189 printf(" Next DST change: DST %s (the clock jumps %s) at\n"
192 is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
199 fputs("\n" ANSI_HIGHLIGHT_ON
200 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
201 " mode is not fully supported and will create various problems with time\n"
202 " zone changes and daylight saving adjustments. If at all possible use\n"
203 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
206 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
210 switch (dbus_message_iter_get_arg_type(iter)) {
212 case DBUS_TYPE_STRING: {
215 dbus_message_iter_get_basic(iter, &s);
217 if (streq(name, "Timezone"))
223 case DBUS_TYPE_BOOLEAN: {
226 dbus_message_iter_get_basic(iter, &b);
227 if (streq(name, "LocalRTC"))
229 else if (streq(name, "NTP"))
237 static int show_status(DBusConnection *bus, char **args, unsigned n) {
238 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
239 const char *interface = "";
241 DBusMessageIter iter, sub, sub2, sub3;
246 r = bus_method_call_with_reply(
248 "org.freedesktop.timedate1",
249 "/org/freedesktop/timedate1",
250 "org.freedesktop.DBus.Properties",
254 DBUS_TYPE_STRING, &interface,
259 if (!dbus_message_iter_init(reply, &iter) ||
260 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
261 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
262 log_error("Failed to parse reply.");
267 dbus_message_iter_recurse(&iter, &sub);
269 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
272 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
273 log_error("Failed to parse reply.");
277 dbus_message_iter_recurse(&sub, &sub2);
279 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
280 log_error("Failed to parse reply.");
284 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
285 log_error("Failed to parse reply.");
289 dbus_message_iter_recurse(&sub2, &sub3);
291 r = status_property(name, &sub3, &info);
293 log_error("Failed to parse reply.");
297 dbus_message_iter_next(&sub);
300 print_status_info(&info);
304 static int set_time(DBusConnection *bus, char **args, unsigned n) {
305 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
306 dbus_bool_t relative = false, interactive = true;
314 polkit_agent_open_if_enabled();
316 r = parse_timestamp(args[1], &t);
318 log_error("Failed to parse time specification: %s", args[1]);
322 u = (dbus_uint64_t) t;
324 return bus_method_call_with_reply(
326 "org.freedesktop.timedate1",
327 "/org/freedesktop/timedate1",
328 "org.freedesktop.timedate1",
333 DBUS_TYPE_BOOLEAN, &relative,
334 DBUS_TYPE_BOOLEAN, &interactive,
338 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
339 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
340 dbus_bool_t interactive = true;
345 polkit_agent_open_if_enabled();
347 return bus_method_call_with_reply(
349 "org.freedesktop.timedate1",
350 "/org/freedesktop/timedate1",
351 "org.freedesktop.timedate1",
355 DBUS_TYPE_STRING, &args[1],
356 DBUS_TYPE_BOOLEAN, &interactive,
360 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
361 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
362 dbus_bool_t interactive = true, b, q;
368 polkit_agent_open_if_enabled();
370 r = parse_boolean(args[1]);
372 log_error("Failed to parse local RTC setting: %s", args[1]);
377 q = arg_adjust_system_clock;
379 return bus_method_call_with_reply(
381 "org.freedesktop.timedate1",
382 "/org/freedesktop/timedate1",
383 "org.freedesktop.timedate1",
387 DBUS_TYPE_BOOLEAN, &b,
388 DBUS_TYPE_BOOLEAN, &q,
389 DBUS_TYPE_BOOLEAN, &interactive,
393 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
394 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
395 dbus_bool_t interactive = true, b;
401 polkit_agent_open_if_enabled();
403 r = parse_boolean(args[1]);
405 log_error("Failed to parse NTP setting: %s", args[1]);
411 return bus_method_call_with_reply(
413 "org.freedesktop.timedate1",
414 "/org/freedesktop/timedate1",
415 "org.freedesktop.timedate1",
419 DBUS_TYPE_BOOLEAN, &b,
420 DBUS_TYPE_BOOLEAN, &interactive,
424 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
425 _cleanup_fclose_ FILE *f = NULL;
426 _cleanup_strv_free_ char **zones = NULL;
433 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
435 log_error("Failed to open timezone database: %m");
440 char l[LINE_MAX], *p, **z, *w;
443 if (!fgets(l, sizeof(l), f)) {
447 log_error("Failed to read timezone database: %m");
453 if (isempty(p) || *p == '#')
457 /* Skip over country code */
458 p += strcspn(p, WHITESPACE);
459 p += strspn(p, WHITESPACE);
461 /* Skip over coordinates */
462 p += strcspn(p, WHITESPACE);
463 p += strspn(p, WHITESPACE);
465 /* Found timezone name */
466 k = strcspn(p, WHITESPACE);
474 z = realloc(zones, sizeof(char*) * (n_zones + 2));
481 zones[n_zones++] = w;
485 zones[n_zones] = NULL;
487 pager_open_if_enabled();
490 STRV_FOREACH(i, zones)
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 " --no-ask-password Do not prompt for password\n"
506 " -H --host=[USER@]HOST Operate on remote host\n\n"
508 " status Show current time settings\n"
509 " set-time TIME Set system time\n"
510 " set-timezone ZONE Set system timezone\n"
511 " list-timezones Show known timezones\n"
512 " set-local-rtc BOOL Control whether RTC is in local time\n"
513 " set-ntp BOOL Control whether NTP is enabled\n",
514 program_invocation_short_name);
519 static int parse_argv(int argc, char *argv[]) {
524 ARG_ADJUST_SYSTEM_CLOCK,
528 static const struct option options[] = {
529 { "help", no_argument, NULL, 'h' },
530 { "version", no_argument, NULL, ARG_VERSION },
531 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
532 { "host", required_argument, NULL, 'H' },
533 { "privileged", no_argument, NULL, 'P' },
534 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
535 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
544 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
553 puts(PACKAGE_STRING);
554 puts(SYSTEMD_FEATURES);
558 arg_transport = TRANSPORT_POLKIT;
562 arg_transport = TRANSPORT_SSH;
566 case ARG_ADJUST_SYSTEM_CLOCK:
567 arg_adjust_system_clock = true;
578 log_error("Unknown option code %c", c);
586 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
588 static const struct {
596 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
598 { "status", LESS, 1, show_status },
599 { "set-time", EQUAL, 2, set_time },
600 { "set-timezone", EQUAL, 2, set_timezone },
601 { "list-timezones", EQUAL, 1, list_timezones },
602 { "set-local-rtc", EQUAL, 2, set_local_rtc },
603 { "set-ntp", EQUAL, 2, set_ntp, },
613 left = argc - optind;
616 /* Special rule: no arguments means "status" */
619 if (streq(argv[optind], "help")) {
624 for (i = 0; i < ELEMENTSOF(verbs); i++)
625 if (streq(argv[optind], verbs[i].verb))
628 if (i >= ELEMENTSOF(verbs)) {
629 log_error("Unknown operation %s", argv[optind]);
634 switch (verbs[i].argc_cmp) {
637 if (left != verbs[i].argc) {
638 log_error("Invalid number of arguments.");
645 if (left < verbs[i].argc) {
646 log_error("Too few arguments.");
653 if (left > verbs[i].argc) {
654 log_error("Too many arguments.");
661 assert_not_reached("Unknown comparison operator.");
665 log_error("Failed to get D-Bus connection: %s", error->message);
669 return verbs[i].dispatch(bus, argv + optind, left);
672 int main(int argc, char *argv[]) {
673 int r, retval = EXIT_FAILURE;
674 DBusConnection *bus = NULL;
677 dbus_error_init(&error);
679 setlocale(LC_ALL, "");
680 log_parse_environment();
683 r = parse_argv(argc, argv);
687 retval = EXIT_SUCCESS;
691 if (arg_transport == TRANSPORT_NORMAL)
692 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
693 else if (arg_transport == TRANSPORT_POLKIT)
694 bus_connect_system_polkit(&bus, &error);
695 else if (arg_transport == TRANSPORT_SSH)
696 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
698 assert_not_reached("Uh, invalid transport...");
700 r = timedatectl_main(bus, argc, argv, &error);
701 retval = r < 0 ? EXIT_FAILURE : r;
705 dbus_connection_flush(bus);
706 dbus_connection_close(bus);
707 dbus_connection_unref(bus);
710 dbus_error_free(&error);