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) {
75 struct timex txc = {};
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",
156 i->can_ntp ? yes_no(i->ntp) : "n/a",
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"))
231 else if (streq(name, "CanNTP"))
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;
244 StatusInfo info = {};
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.");
268 dbus_message_iter_recurse(&iter, &sub);
270 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
273 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
274 log_error("Failed to parse reply.");
278 dbus_message_iter_recurse(&sub, &sub2);
280 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
281 log_error("Failed to parse reply.");
285 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
286 log_error("Failed to parse reply.");
290 dbus_message_iter_recurse(&sub2, &sub3);
292 r = status_property(name, &sub3, &info);
294 log_error("Failed to parse reply.");
298 dbus_message_iter_next(&sub);
301 print_status_info(&info);
305 static int set_time(DBusConnection *bus, char **args, unsigned n) {
306 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
307 dbus_bool_t relative = false, interactive = true;
315 polkit_agent_open_if_enabled();
317 r = parse_timestamp(args[1], &t);
319 log_error("Failed to parse time specification: %s", args[1]);
323 u = (dbus_uint64_t) t;
325 return bus_method_call_with_reply(
327 "org.freedesktop.timedate1",
328 "/org/freedesktop/timedate1",
329 "org.freedesktop.timedate1",
334 DBUS_TYPE_BOOLEAN, &relative,
335 DBUS_TYPE_BOOLEAN, &interactive,
339 static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
340 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
341 dbus_bool_t interactive = true;
346 polkit_agent_open_if_enabled();
348 return bus_method_call_with_reply(
350 "org.freedesktop.timedate1",
351 "/org/freedesktop/timedate1",
352 "org.freedesktop.timedate1",
356 DBUS_TYPE_STRING, &args[1],
357 DBUS_TYPE_BOOLEAN, &interactive,
361 static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
362 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
363 dbus_bool_t interactive = true, b, q;
369 polkit_agent_open_if_enabled();
371 r = parse_boolean(args[1]);
373 log_error("Failed to parse local RTC setting: %s", args[1]);
378 q = arg_adjust_system_clock;
380 return bus_method_call_with_reply(
382 "org.freedesktop.timedate1",
383 "/org/freedesktop/timedate1",
384 "org.freedesktop.timedate1",
388 DBUS_TYPE_BOOLEAN, &b,
389 DBUS_TYPE_BOOLEAN, &q,
390 DBUS_TYPE_BOOLEAN, &interactive,
394 static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
395 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
396 dbus_bool_t interactive = true, b;
402 polkit_agent_open_if_enabled();
404 r = parse_boolean(args[1]);
406 log_error("Failed to parse NTP setting: %s", args[1]);
412 return bus_method_call_with_reply(
414 "org.freedesktop.timedate1",
415 "/org/freedesktop/timedate1",
416 "org.freedesktop.timedate1",
420 DBUS_TYPE_BOOLEAN, &b,
421 DBUS_TYPE_BOOLEAN, &interactive,
425 static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
426 _cleanup_fclose_ FILE *f = NULL;
427 _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();
495 static int help(void) {
497 printf("%s [OPTIONS...] COMMAND ...\n\n"
498 "Query or change system time and date settings.\n\n"
499 " -h --help Show this help\n"
500 " --version Show package version\n"
501 " --adjust-system-clock\n"
502 " Adjust system clock when changing local RTC mode\n"
503 " --no-pager Do not pipe output into a pager\n"
504 " --no-ask-password Do not prompt for password\n"
505 " -H --host=[USER@]HOST Operate on remote host\n\n"
507 " status Show current time settings\n"
508 " set-time TIME Set system time\n"
509 " set-timezone ZONE Set system timezone\n"
510 " list-timezones Show known timezones\n"
511 " set-local-rtc BOOL Control whether RTC is in local time\n"
512 " set-ntp BOOL Control whether NTP is enabled\n",
513 program_invocation_short_name);
518 static int parse_argv(int argc, char *argv[]) {
523 ARG_ADJUST_SYSTEM_CLOCK,
527 static const struct option options[] = {
528 { "help", no_argument, NULL, 'h' },
529 { "version", no_argument, NULL, ARG_VERSION },
530 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
531 { "host", required_argument, NULL, 'H' },
532 { "privileged", no_argument, NULL, 'P' },
533 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
534 { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
543 while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) {
552 puts(PACKAGE_STRING);
553 puts(SYSTEMD_FEATURES);
557 arg_transport = TRANSPORT_POLKIT;
561 arg_transport = TRANSPORT_SSH;
565 case ARG_ADJUST_SYSTEM_CLOCK:
566 arg_adjust_system_clock = true;
577 log_error("Unknown option code %c", c);
585 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
587 static const struct {
595 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
597 { "status", LESS, 1, show_status },
598 { "set-time", EQUAL, 2, set_time },
599 { "set-timezone", EQUAL, 2, set_timezone },
600 { "list-timezones", EQUAL, 1, list_timezones },
601 { "set-local-rtc", EQUAL, 2, set_local_rtc },
602 { "set-ntp", EQUAL, 2, set_ntp, },
612 left = argc - optind;
615 /* Special rule: no arguments means "status" */
618 if (streq(argv[optind], "help")) {
623 for (i = 0; i < ELEMENTSOF(verbs); i++)
624 if (streq(argv[optind], verbs[i].verb))
627 if (i >= ELEMENTSOF(verbs)) {
628 log_error("Unknown operation %s", argv[optind]);
633 switch (verbs[i].argc_cmp) {
636 if (left != verbs[i].argc) {
637 log_error("Invalid number of arguments.");
644 if (left < verbs[i].argc) {
645 log_error("Too few arguments.");
652 if (left > verbs[i].argc) {
653 log_error("Too many arguments.");
660 assert_not_reached("Unknown comparison operator.");
664 log_error("Failed to get D-Bus connection: %s", error->message);
668 return verbs[i].dispatch(bus, argv + optind, left);
671 int main(int argc, char *argv[]) {
672 int r, retval = EXIT_FAILURE;
673 DBusConnection *bus = NULL;
676 dbus_error_init(&error);
678 setlocale(LC_ALL, "");
679 log_parse_environment();
682 r = parse_argv(argc, argv);
686 retval = EXIT_SUCCESS;
690 if (arg_transport == TRANSPORT_NORMAL)
691 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
692 else if (arg_transport == TRANSPORT_POLKIT)
693 bus_connect_system_polkit(&bus, &error);
694 else if (arg_transport == TRANSPORT_SSH)
695 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
697 assert_not_reached("Uh, invalid transport...");
699 r = timedatectl_main(bus, argc, argv, &error);
700 retval = r < 0 ? EXIT_FAILURE : r;
704 dbus_connection_flush(bus);
705 dbus_connection_close(bus);
706 dbus_connection_unref(bus);
709 dbus_error_free(&error);