1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 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/>.
22 #include <dbus/dbus.h>
28 #include "systemd/sd-id128.h"
29 #include "systemd/sd-messages.h"
32 #include "dbus-common.h"
36 #include "conf-files.h"
38 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
39 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
42 " <interface name=\"org.freedesktop.timedate1\">\n" \
43 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
45 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
46 " <method name=\"SetTime\">\n" \
47 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
48 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
49 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51 " <method name=\"SetTimezone\">\n" \
52 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
53 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
55 " <method name=\"SetLocalRTC\">\n" \
56 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
57 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
58 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
60 " <method name=\"SetNTP\">\n" \
61 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
62 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
66 #define INTROSPECTION \
67 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
70 BUS_PROPERTIES_INTERFACE \
71 BUS_INTROSPECTABLE_INTERFACE \
75 #define INTERFACES_LIST \
76 BUS_GENERIC_INTERFACES_LIST \
77 "org.freedesktop.timedate1\0"
79 /* Must start and end with '/' */
80 #define ZONEINFO_PATH "/usr/share/zoneinfo/"
82 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
94 static usec_t remain_until;
96 static void free_data(void) {
100 tz.local_rtc = false;
103 static bool valid_timezone(const char *name) {
112 if (*name == '/' || *name == 0)
115 for (p = name; *p; p++) {
116 if (!(*p >= '0' && *p <= '9') &&
117 !(*p >= 'a' && *p <= 'z') &&
118 !(*p >= 'A' && *p <= 'Z') &&
119 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
135 t = strappend(ZONEINFO_PATH, name);
145 if (!S_ISREG(st.st_mode))
151 static void verify_timezone(void) {
152 char *p, *a = NULL, *b = NULL;
159 p = strappend(ZONEINFO_PATH, tz.zone);
165 k = read_full_file(p, &b, &q);
168 j = read_full_file("/etc/localtime", &a, &l);
170 if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
171 log_warning("/etc/localtime and /etc/timezone out of sync.");
180 static int read_data(void) {
186 r = readlink_malloc("/etc/localtime", &t);
189 log_warning("/etc/localtime should be a symbolic link to a timezone data file in " ZONEINFO_PATH);
191 log_warning("Failed to get target of %s: %s", "/etc/localtime", strerror(-r));
193 /* we only support the trivial relative link of (/etc/)..$ABSOLUTE */
194 int rel_link_offset = startswith(t, "..") ? strlen("..") : 0;
196 if (!startswith(t + rel_link_offset, ZONEINFO_PATH))
197 log_warning("/etc/localtime should be a symbolic link to a timezone data file in " ZONEINFO_PATH);
199 tz.zone = strdup(t + rel_link_offset + strlen(ZONEINFO_PATH));
210 r = read_one_line_file("/etc/timezone", &tz.zone);
213 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
216 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
220 if (r < 0 && r != -ENOENT)
221 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
226 if (isempty(tz.zone)) {
233 tz.local_rtc = hwclock_is_localtime() > 0;
238 static int write_data_timezone(void) {
244 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
247 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
253 p = strappend(ZONEINFO_PATH, tz.zone);
257 r = symlink(p, "/etc/localtime");
263 if (stat("/etc/timezone", &st) == 0 && S_ISREG(st.st_mode)) {
264 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
272 static int write_data_local_rtc(void) {
276 r = read_full_file("/etc/adjtime", &s, NULL);
284 w = strdup(NULL_ADJTIME_LOCAL);
297 p = strchr(p+1, '\n');
313 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
319 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
321 if (streq(w, NULL_ADJTIME_UTC)) {
324 if (unlink("/etc/adjtime") < 0) {
333 r = write_one_line_file_atomic("/etc/adjtime", w);
339 static char** get_ntp_services(void) {
340 char **r = NULL, **files, **i;
343 k = conf_files_list(&files, ".list",
344 "/etc/systemd/ntp-units.d",
345 "/run/systemd/ntp-units.d",
346 "/usr/local/lib/systemd/ntp-units.d",
347 "/usr/lib/systemd/ntp-units.d",
352 STRV_FOREACH(i, files) {
360 char line[PATH_MAX], *l, **q;
362 if (!fgets(line, sizeof(line), f)) {
365 log_error("Failed to read NTP units file: %m");
371 if (l[0] == 0 || l[0] == '#')
374 q = strv_append(r, l);
392 static int read_ntp(DBusConnection *bus) {
393 DBusMessage *m = NULL, *reply = NULL;
400 dbus_error_init(&error);
402 l = get_ntp_services();
407 dbus_message_unref(m);
408 m = dbus_message_new_method_call(
409 "org.freedesktop.systemd1",
410 "/org/freedesktop/systemd1",
411 "org.freedesktop.systemd1.Manager",
418 if (!dbus_message_append_args(m,
420 DBUS_TYPE_INVALID)) {
426 dbus_message_unref(reply);
427 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
429 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
430 /* This implementation does not exist, try next one */
431 dbus_error_free(&error);
435 log_error("Failed to issue method call: %s", bus_error_message(&error));
440 if (!dbus_message_get_args(reply, &error,
441 DBUS_TYPE_STRING, &s,
442 DBUS_TYPE_INVALID)) {
443 log_error("Failed to parse reply: %s", bus_error_message(&error));
449 streq(s, "enabled") ||
450 streq(s, "enabled-runtime");
455 /* NTP is not installed. */
461 dbus_message_unref(m);
464 dbus_message_unref(reply);
468 dbus_error_free(&error);
473 static int start_ntp(DBusConnection *bus, DBusError *error) {
474 DBusMessage *m = NULL, *reply = NULL;
475 const char *mode = "replace";
482 l = get_ntp_services();
485 dbus_message_unref(m);
486 m = dbus_message_new_method_call(
487 "org.freedesktop.systemd1",
488 "/org/freedesktop/systemd1",
489 "org.freedesktop.systemd1.Manager",
490 tz.use_ntp ? "StartUnit" : "StopUnit");
492 log_error("Could not allocate message.");
497 if (!dbus_message_append_args(m,
499 DBUS_TYPE_STRING, &mode,
500 DBUS_TYPE_INVALID)) {
501 log_error("Could not append arguments to message.");
507 dbus_message_unref(reply);
508 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
510 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
511 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
512 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
513 /* This implementation does not exist, try next one */
514 dbus_error_free(error);
518 log_error("Failed to issue method call: %s", bus_error_message(error));
527 /* No implementaiton available... */
532 dbus_message_unref(m);
535 dbus_message_unref(reply);
542 static int enable_ntp(DBusConnection *bus, DBusError *error) {
543 DBusMessage *m = NULL, *reply = NULL;
545 DBusMessageIter iter;
546 dbus_bool_t f = FALSE, t = TRUE;
552 l = get_ntp_services();
557 dbus_message_unref(m);
558 m = dbus_message_new_method_call(
559 "org.freedesktop.systemd1",
560 "/org/freedesktop/systemd1",
561 "org.freedesktop.systemd1.Manager",
562 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
564 log_error("Could not allocate message.");
569 dbus_message_iter_init_append(m, &iter);
574 r = bus_append_strv_iter(&iter, k);
576 log_error("Failed to append unit files.");
580 /* send runtime bool */
581 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
582 log_error("Failed to append runtime boolean.");
588 /* send force bool */
589 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
590 log_error("Failed to append force boolean.");
597 dbus_message_unref(reply);
598 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
600 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
601 /* This implementation does not exist, try next one */
602 dbus_error_free(error);
606 log_error("Failed to issue method call: %s", bus_error_message(error));
611 dbus_message_unref(m);
612 m = dbus_message_new_method_call(
613 "org.freedesktop.systemd1",
614 "/org/freedesktop/systemd1",
615 "org.freedesktop.systemd1.Manager",
618 log_error("Could not allocate message.");
623 dbus_message_unref(reply);
624 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
626 log_error("Failed to issue method call: %s", bus_error_message(error));
639 dbus_message_unref(m);
642 dbus_message_unref(reply);
649 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
657 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
663 static const BusProperty bus_timedate_properties[] = {
664 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
665 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
666 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
670 static const BusBoundProperties bps[] = {
671 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
675 static DBusHandlerResult timedate_message_handler(
676 DBusConnection *connection,
677 DBusMessage *message,
680 DBusMessage *reply = NULL, *changed = NULL;
687 dbus_error_init(&error);
689 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
691 dbus_bool_t interactive;
693 if (!dbus_message_get_args(
696 DBUS_TYPE_STRING, &z,
697 DBUS_TYPE_BOOLEAN, &interactive,
699 return bus_send_error_reply(connection, message, &error, -EINVAL);
701 if (!valid_timezone(z))
702 return bus_send_error_reply(connection, message, NULL, -EINVAL);
704 if (!streq_ptr(z, tz.zone)) {
707 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
709 return bus_send_error_reply(connection, message, &error, r);
718 /* 1. Write new configuration file */
719 r = write_data_timezone();
721 log_error("Failed to set timezone: %s", strerror(-r));
722 return bus_send_error_reply(connection, message, NULL, r);
729 /* 2. Teach kernel new timezone */
730 hwclock_apply_localtime_delta(NULL);
732 /* 3. Sync RTC from system clock, with the new delta */
733 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
734 assert_se(tm = localtime(&ts.tv_sec));
735 hwclock_set_time(tm);
739 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIMEZONE_CHANGE),
740 "TIMEZONE=%s", tz.zone,
741 "MESSAGE=Changed timezone to '%s'.", tz.zone,
744 changed = bus_properties_changed_new(
745 "/org/freedesktop/timedate1",
746 "org.freedesktop.timedate1",
752 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
754 dbus_bool_t fix_system;
755 dbus_bool_t interactive;
757 if (!dbus_message_get_args(
760 DBUS_TYPE_BOOLEAN, &lrtc,
761 DBUS_TYPE_BOOLEAN, &fix_system,
762 DBUS_TYPE_BOOLEAN, &interactive,
764 return bus_send_error_reply(connection, message, &error, -EINVAL);
766 if (lrtc != tz.local_rtc) {
769 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
771 return bus_send_error_reply(connection, message, &error, r);
775 /* 1. Write new configuration file */
776 r = write_data_local_rtc();
778 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
779 return bus_send_error_reply(connection, message, NULL, r);
782 /* 2. Teach kernel new timezone */
784 hwclock_apply_localtime_delta(NULL);
786 hwclock_reset_localtime_delta();
788 /* 3. Synchronize clocks */
789 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
794 /* Sync system clock from RTC; first,
795 * initialize the timezone fields of
798 tm = *localtime(&ts.tv_sec);
800 tm = *gmtime(&ts.tv_sec);
802 /* Override the main fields of
803 * struct tm, but not the timezone
805 if (hwclock_get_time(&tm) >= 0) {
807 /* And set the system clock
810 ts.tv_sec = mktime(&tm);
812 ts.tv_sec = timegm(&tm);
814 clock_settime(CLOCK_REALTIME, &ts);
820 /* Sync RTC from system clock */
822 tm = localtime(&ts.tv_sec);
824 tm = gmtime(&ts.tv_sec);
826 hwclock_set_time(tm);
829 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
831 changed = bus_properties_changed_new(
832 "/org/freedesktop/timedate1",
833 "org.freedesktop.timedate1",
839 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
841 dbus_bool_t relative;
842 dbus_bool_t interactive;
844 if (!dbus_message_get_args(
847 DBUS_TYPE_INT64, &utc,
848 DBUS_TYPE_BOOLEAN, &relative,
849 DBUS_TYPE_BOOLEAN, &interactive,
851 return bus_send_error_reply(connection, message, &error, -EINVAL);
853 if (!relative && utc <= 0)
854 return bus_send_error_reply(connection, message, NULL, -EINVAL);
856 if (!relative || utc != 0) {
860 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
862 return bus_send_error_reply(connection, message, &error, r);
865 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
867 timespec_store(&ts, utc);
869 /* Set system clock */
870 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
871 log_error("Failed to set local time: %m");
872 return bus_send_error_reply(connection, message, NULL, -errno);
875 /* Sync down to RTC */
877 tm = localtime(&ts.tv_sec);
879 tm = gmtime(&ts.tv_sec);
881 hwclock_set_time(tm);
884 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE),
885 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
886 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
889 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
891 dbus_bool_t interactive;
893 if (!dbus_message_get_args(
896 DBUS_TYPE_BOOLEAN, &ntp,
897 DBUS_TYPE_BOOLEAN, &interactive,
899 return bus_send_error_reply(connection, message, &error, -EINVAL);
901 if (ntp != !!tz.use_ntp) {
903 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
905 return bus_send_error_reply(connection, message, &error, r);
909 r = enable_ntp(connection, &error);
911 return bus_send_error_reply(connection, message, &error, r);
913 r = start_ntp(connection, &error);
915 return bus_send_error_reply(connection, message, &error, r);
917 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
919 changed = bus_properties_changed_new(
920 "/org/freedesktop/timedate1",
921 "org.freedesktop.timedate1",
928 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
930 if (!(reply = dbus_message_new_method_return(message)))
933 if (!dbus_connection_send(connection, reply, NULL))
936 dbus_message_unref(reply);
941 if (!dbus_connection_send(connection, changed, NULL))
944 dbus_message_unref(changed);
947 return DBUS_HANDLER_RESULT_HANDLED;
951 dbus_message_unref(reply);
954 dbus_message_unref(changed);
956 dbus_error_free(&error);
958 return DBUS_HANDLER_RESULT_NEED_MEMORY;
961 static int connect_bus(DBusConnection **_bus) {
962 static const DBusObjectPathVTable timedate_vtable = {
963 .message_function = timedate_message_handler
966 DBusConnection *bus = NULL;
971 dbus_error_init(&error);
973 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
975 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
980 dbus_connection_set_exit_on_disconnect(bus, FALSE);
982 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
983 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
988 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
989 if (dbus_error_is_set(&error)) {
990 log_error("Failed to register name on bus: %s", bus_error_message(&error));
995 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
996 log_error("Failed to acquire name.");
1007 dbus_connection_close(bus);
1008 dbus_connection_unref(bus);
1010 dbus_error_free(&error);
1015 int main(int argc, char *argv[]) {
1017 DBusConnection *bus = NULL;
1018 bool exiting = false;
1020 log_set_target(LOG_TARGET_AUTO);
1021 log_parse_environment();
1026 if (argc == 2 && streq(argv[1], "--introspect")) {
1027 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1028 "<node>\n", stdout);
1029 fputs(timedate_interface, stdout);
1030 fputs("</node>\n", stdout);
1035 log_error("This program takes no arguments.");
1042 log_error("Failed to read timezone data: %s", strerror(-r));
1046 r = connect_bus(&bus);
1052 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1056 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1059 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1062 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1064 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1074 dbus_connection_flush(bus);
1075 dbus_connection_close(bus);
1076 dbus_connection_unref(bus);
1079 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;