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 {
74 static bool ntp_synced(void) {
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 time zone. 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;
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.");
270 dbus_message_iter_recurse(&iter, &sub);
272 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
275 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
276 log_error("Failed to parse reply.");
280 dbus_message_iter_recurse(&sub, &sub2);
282 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
283 log_error("Failed to parse reply.");
287 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
288 log_error("Failed to parse reply.");
292 dbus_message_iter_recurse(&sub2, &sub3);
294 r = status_property(name, &sub3, &info);
296 log_error("Failed to parse reply.");
300 dbus_message_iter_next(&sub);
303 print_status_info(&info);
307 static int set_time(DBusConnection *bus, char **args, unsigned n) {
308 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
309 dbus_bool_t relative = false, interactive = true;
317 polkit_agent_open_if_enabled();
319 r = parse_timestamp(args[1], &t);
321 log_error("Failed to parse time specification: %s", args[1]);
325 u = (dbus_uint64_t) t;
327 return bus_method_call_with_reply(
329 "org.freedesktop.timedate1",
330 "/org/freedesktop/timedate1",
331 "org.freedesktop.timedate1",
336 DBUS_TYPE_BOOLEAN, &relative,
337 DBUS_TYPE_BOOLEAN, &interactive,
341 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
342 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
343 dbus_bool_t interactive = true;
348 polkit_agent_open_if_enabled();
350 return bus_method_call_with_reply(
352 "org.freedesktop.timedate1",
353 "/org/freedesktop/timedate1",
354 "org.freedesktop.timedate1",
358 DBUS_TYPE_STRING, &args[1],
359 DBUS_TYPE_BOOLEAN, &interactive,
363 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
364 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
365 dbus_bool_t interactive = true, b, q;
371 polkit_agent_open_if_enabled();
373 r = parse_boolean(args[1]);
375 log_error("Failed to parse local RTC setting: %s", args[1]);
380 q = arg_adjust_system_clock;
382 return bus_method_call_with_reply(
384 "org.freedesktop.timedate1",
385 "/org/freedesktop/timedate1",
386 "org.freedesktop.timedate1",
390 DBUS_TYPE_BOOLEAN, &b,
391 DBUS_TYPE_BOOLEAN, &q,
392 DBUS_TYPE_BOOLEAN, &interactive,
396 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
397 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
398 dbus_bool_t interactive = true, b;
404 polkit_agent_open_if_enabled();
406 r = parse_boolean(args[1]);
408 log_error("Failed to parse NTP setting: %s", args[1]);
414 return bus_method_call_with_reply(
416 "org.freedesktop.timedate1",
417 "/org/freedesktop/timedate1",
418 "org.freedesktop.timedate1",
422 DBUS_TYPE_BOOLEAN, &b,
423 DBUS_TYPE_BOOLEAN, &interactive,
427 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
428 _cleanup_fclose_ FILE *f = NULL;
429 _cleanup_strv_free_ char **zones = NULL;
435 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
437 log_error("Failed to open timezone database: %m");
442 char l[LINE_MAX], *p, **z, *w;
445 if (!fgets(l, sizeof(l), f)) {
449 log_error("Failed to read timezone database: %m");
455 if (isempty(p) || *p == '#')
459 /* Skip over country code */
460 p += strcspn(p, WHITESPACE);
461 p += strspn(p, WHITESPACE);
463 /* Skip over coordinates */
464 p += strcspn(p, WHITESPACE);
465 p += strspn(p, WHITESPACE);
467 /* Found timezone name */
468 k = strcspn(p, WHITESPACE);
476 z = realloc(zones, sizeof(char*) * (n_zones + 2));
483 zones[n_zones++] = w;
487 zones[n_zones] = NULL;
489 pager_open_if_enabled();
497 static int help(void) {
499 printf("%s [OPTIONS...] COMMAND ...\n\n"
500 "Query or change system time and date settings.\n\n"
501 " -h --help Show this help\n"
502 " --version Show package version\n"
503 " --adjust-system-clock\n"
504 " Adjust system clock when changing local RTC mode\n"
505 " --no-pager Do not pipe output into a pager\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;
567 case ARG_ADJUST_SYSTEM_CLOCK:
568 arg_adjust_system_clock = true;
579 log_error("Unknown option code %c", c);
587 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
589 static const struct {
597 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
599 { "status", LESS, 1, show_status },
600 { "set-time", EQUAL, 2, set_time },
601 { "set-timezone", EQUAL, 2, set_timezone },
602 { "list-timezones", EQUAL, 1, list_timezones },
603 { "set-local-rtc", EQUAL, 2, set_local_rtc },
604 { "set-ntp", EQUAL, 2, set_ntp, },
614 left = argc - optind;
617 /* Special rule: no arguments means "status" */
620 if (streq(argv[optind], "help")) {
625 for (i = 0; i < ELEMENTSOF(verbs); i++)
626 if (streq(argv[optind], verbs[i].verb))
629 if (i >= ELEMENTSOF(verbs)) {
630 log_error("Unknown operation %s", argv[optind]);
635 switch (verbs[i].argc_cmp) {
638 if (left != verbs[i].argc) {
639 log_error("Invalid number of arguments.");
646 if (left < verbs[i].argc) {
647 log_error("Too few arguments.");
654 if (left > verbs[i].argc) {
655 log_error("Too many arguments.");
662 assert_not_reached("Unknown comparison operator.");
666 log_error("Failed to get D-Bus connection: %s", error->message);
670 return verbs[i].dispatch(bus, argv + optind, left);
673 int main(int argc, char *argv[]) {
674 int r, retval = EXIT_FAILURE;
675 DBusConnection *bus = NULL;
678 dbus_error_init(&error);
680 setlocale(LC_ALL, "");
681 log_parse_environment();
684 r = parse_argv(argc, argv);
688 retval = EXIT_SUCCESS;
692 if (arg_transport == TRANSPORT_NORMAL)
693 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
694 else if (arg_transport == TRANSPORT_POLKIT)
695 bus_connect_system_polkit(&bus, &error);
696 else if (arg_transport == TRANSPORT_SSH)
697 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
699 assert_not_reached("Uh, invalid transport...");
701 r = timedatectl_main(bus, argc, argv, &error);
702 retval = r < 0 ? EXIT_FAILURE : r;
706 dbus_connection_flush(bus);
707 dbus_connection_close(bus);
708 dbus_connection_unref(bus);
711 dbus_error_free(&error);