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", localtime_r(&sec, &tm)) > 0);
150 printf(" Timezone: %s\n"
153 "NTP synchronized: %s\n"
154 " RTC in local TZ: %s\n",
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: %s → %s, DST became %s\n"
180 strna(zn), strna(zc), is_dstc ? "active" : "inactive", 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: %s → %s, DST will become %s\n"
191 " the clock will jump %s\n"
194 strna(zc), strna(zn), is_dstn ? "active" : "inactive", jump_str(dn, s, sizeof(s)), a, b);
201 fputs("\n" ANSI_HIGHLIGHT_ON
202 "Warning: The RTC is configured to maintain time in the local time zone. This\n"
203 " mode is not fully supported and will create various problems with time\n"
204 " zone changes and daylight saving adjustments. If at all possible use\n"
205 " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
208 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
212 switch (dbus_message_iter_get_arg_type(iter)) {
214 case DBUS_TYPE_STRING: {
217 dbus_message_iter_get_basic(iter, &s);
219 if (streq(name, "Timezone"))
225 case DBUS_TYPE_BOOLEAN: {
228 dbus_message_iter_get_basic(iter, &b);
229 if (streq(name, "LocalRTC"))
231 else if (streq(name, "NTP"))
239 static int show_status(DBusConnection *bus, char **args, unsigned n) {
240 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
241 const char *interface = "";
243 DBusMessageIter iter, sub, sub2, sub3;
248 r = bus_method_call_with_reply(
250 "org.freedesktop.timedate1",
251 "/org/freedesktop/timedate1",
252 "org.freedesktop.DBus.Properties",
256 DBUS_TYPE_STRING, &interface,
261 if (!dbus_message_iter_init(reply, &iter) ||
262 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
263 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
264 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 = true;
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 = true;
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 = true, 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 = true, 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;
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();
492 STRV_FOREACH(i, zones)
498 static int help(void) {
500 printf("%s [OPTIONS...] COMMAND ...\n\n"
501 "Query or change system time and date settings.\n\n"
502 " -h --help Show this help\n"
503 " --version Show package version\n"
504 " --adjust-system-clock\n"
505 " Adjust system clock when changing local RTC mode\n"
506 " --no-pager Do not pipe output into a pager\n"
507 " --no-ask-password Do not prompt for password\n"
508 " -H --host=[USER@]HOST Operate on remote host\n\n"
510 " status Show current time settings\n"
511 " set-time TIME Set system time\n"
512 " set-timezone ZONE Set system timezone\n"
513 " list-timezones Show known timezones\n"
514 " set-local-rtc BOOL Control whether RTC is in local time\n"
515 " set-ntp BOOL Control whether NTP is enabled\n",
516 program_invocation_short_name);
521 static int parse_argv(int argc, char *argv[]) {
526 ARG_ADJUST_SYSTEM_CLOCK,
530 static const struct option options[] = {
531 { "help", no_argument, NULL, 'h' },
532 { "version", no_argument, NULL, ARG_VERSION },
533 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
534 { "host", required_argument, NULL, 'H' },
535 { "privileged", no_argument, NULL, 'P' },
536 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
537 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
546 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
555 puts(PACKAGE_STRING);
557 puts(SYSTEMD_FEATURES);
561 arg_transport = TRANSPORT_POLKIT;
565 arg_transport = TRANSPORT_SSH;
569 case ARG_ADJUST_SYSTEM_CLOCK:
570 arg_adjust_system_clock = true;
581 log_error("Unknown option code %c", c);
589 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
591 static const struct {
599 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
601 { "status", LESS, 1, show_status },
602 { "set-time", EQUAL, 2, set_time },
603 { "set-timezone", EQUAL, 2, set_timezone },
604 { "list-timezones", EQUAL, 1, list_timezones },
605 { "set-local-rtc", EQUAL, 2, set_local_rtc },
606 { "set-ntp", EQUAL, 2, set_ntp, },
616 left = argc - optind;
619 /* Special rule: no arguments means "status" */
622 if (streq(argv[optind], "help")) {
627 for (i = 0; i < ELEMENTSOF(verbs); i++)
628 if (streq(argv[optind], verbs[i].verb))
631 if (i >= ELEMENTSOF(verbs)) {
632 log_error("Unknown operation %s", argv[optind]);
637 switch (verbs[i].argc_cmp) {
640 if (left != verbs[i].argc) {
641 log_error("Invalid number of arguments.");
648 if (left < verbs[i].argc) {
649 log_error("Too few arguments.");
656 if (left > verbs[i].argc) {
657 log_error("Too many arguments.");
664 assert_not_reached("Unknown comparison operator.");
668 log_error("Failed to get D-Bus connection: %s", error->message);
672 return verbs[i].dispatch(bus, argv + optind, left);
675 int main(int argc, char *argv[]) {
676 int r, retval = EXIT_FAILURE;
677 DBusConnection *bus = NULL;
680 dbus_error_init(&error);
682 setlocale(LC_ALL, "");
683 log_parse_environment();
686 r = parse_argv(argc, argv);
690 retval = EXIT_SUCCESS;
694 if (arg_transport == TRANSPORT_NORMAL)
695 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
696 else if (arg_transport == TRANSPORT_POLKIT)
697 bus_connect_system_polkit(&bus, &error);
698 else if (arg_transport == TRANSPORT_SSH)
699 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
701 assert_not_reached("Uh, invalid transport...");
703 r = timedatectl_main(bus, argc, argv, &error);
704 retval = r < 0 ? EXIT_FAILURE : r;
708 dbus_connection_flush(bus);
709 dbus_connection_close(bus);
710 dbus_connection_unref(bus);
713 dbus_error_free(&error);