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, DST became %s\n"
180 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, DST will become %s, the clock will jump %s\n"
193 is_dstn ? "active" : "inactive", 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"))
238 static int show_status(DBusConnection *bus, char **args, unsigned n) {
239 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
240 const char *interface = "";
242 DBusMessageIter iter, sub, sub2, sub3;
247 r = bus_method_call_with_reply(
249 "org.freedesktop.timedate1",
250 "/org/freedesktop/timedate1",
251 "org.freedesktop.DBus.Properties",
255 DBUS_TYPE_STRING, &interface,
260 if (!dbus_message_iter_init(reply, &iter) ||
261 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
262 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
263 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;
434 f = fopen("/usr/share/zoneinfo/zone.tab", "re");
436 log_error("Failed to open timezone database: %m");
441 char l[LINE_MAX], *p, **z, *w;
444 if (!fgets(l, sizeof(l), f)) {
448 log_error("Failed to read timezone database: %m");
454 if (isempty(p) || *p == '#')
458 /* Skip over country code */
459 p += strcspn(p, WHITESPACE);
460 p += strspn(p, WHITESPACE);
462 /* Skip over coordinates */
463 p += strcspn(p, WHITESPACE);
464 p += strspn(p, WHITESPACE);
466 /* Found timezone name */
467 k = strcspn(p, WHITESPACE);
475 z = realloc(zones, sizeof(char*) * (n_zones + 2));
482 zones[n_zones++] = w;
486 zones[n_zones] = NULL;
488 pager_open_if_enabled();
491 STRV_FOREACH(i, zones)
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);
556 puts(SYSTEMD_FEATURES);
560 arg_transport = TRANSPORT_POLKIT;
564 arg_transport = TRANSPORT_SSH;
568 case ARG_ADJUST_SYSTEM_CLOCK:
569 arg_adjust_system_clock = true;
580 log_error("Unknown option code %c", c);
588 static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
590 static const struct {
598 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
600 { "status", LESS, 1, show_status },
601 { "set-time", EQUAL, 2, set_time },
602 { "set-timezone", EQUAL, 2, set_timezone },
603 { "list-timezones", EQUAL, 1, list_timezones },
604 { "set-local-rtc", EQUAL, 2, set_local_rtc },
605 { "set-ntp", EQUAL, 2, set_ntp, },
615 left = argc - optind;
618 /* Special rule: no arguments means "status" */
621 if (streq(argv[optind], "help")) {
626 for (i = 0; i < ELEMENTSOF(verbs); i++)
627 if (streq(argv[optind], verbs[i].verb))
630 if (i >= ELEMENTSOF(verbs)) {
631 log_error("Unknown operation %s", argv[optind]);
636 switch (verbs[i].argc_cmp) {
639 if (left != verbs[i].argc) {
640 log_error("Invalid number of arguments.");
647 if (left < verbs[i].argc) {
648 log_error("Too few arguments.");
655 if (left > verbs[i].argc) {
656 log_error("Too many arguments.");
663 assert_not_reached("Unknown comparison operator.");
667 log_error("Failed to get D-Bus connection: %s", error->message);
671 return verbs[i].dispatch(bus, argv + optind, left);
674 int main(int argc, char *argv[]) {
675 int r, retval = EXIT_FAILURE;
676 DBusConnection *bus = NULL;
679 dbus_error_init(&error);
681 setlocale(LC_ALL, "");
682 log_parse_environment();
685 r = parse_argv(argc, argv);
689 retval = EXIT_SUCCESS;
693 if (arg_transport == TRANSPORT_NORMAL)
694 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
695 else if (arg_transport == TRANSPORT_POLKIT)
696 bus_connect_system_polkit(&bus, &error);
697 else if (arg_transport == TRANSPORT_SSH)
698 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
700 assert_not_reached("Uh, invalid transport...");
702 r = timedatectl_main(bus, argc, argv, &error);
703 retval = r < 0 ? EXIT_FAILURE : r;
707 dbus_connection_flush(bus);
708 dbus_connection_close(bus);
709 dbus_connection_unref(bus);
712 dbus_error_free(&error);