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"
37 #include "path-util.h"
38 #include "fileio-label.h"
41 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
42 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
45 " <interface name=\"org.freedesktop.timedate1\">\n" \
46 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
47 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
48 " <property name=\"CanNTP\" type=\"b\" access=\"read\"/>\n" \
49 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
50 " <method name=\"SetTime\">\n" \
51 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
52 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
53 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
55 " <method name=\"SetTimezone\">\n" \
56 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
57 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
59 " <method name=\"SetLocalRTC\">\n" \
60 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
61 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
62 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
64 " <method name=\"SetNTP\">\n" \
65 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
66 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
70 #define INTROSPECTION \
71 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
74 BUS_PROPERTIES_INTERFACE \
75 BUS_INTROSPECTABLE_INTERFACE \
79 #define INTERFACES_LIST \
80 BUS_GENERIC_INTERFACES_LIST \
81 "org.freedesktop.timedate1\0"
83 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
97 static usec_t remain_until;
99 static void free_data(void) {
103 tz.local_rtc = false;
106 static bool valid_timezone(const char *name) {
115 if (*name == '/' || *name == 0)
118 for (p = name; *p; p++) {
119 if (!(*p >= '0' && *p <= '9') &&
120 !(*p >= 'a' && *p <= 'z') &&
121 !(*p >= 'A' && *p <= 'Z') &&
122 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
138 t = strappend("/usr/share/zoneinfo/", name);
148 if (!S_ISREG(st.st_mode))
154 static int read_data(void) {
156 _cleanup_free_ char *t = NULL;
160 r = readlink_malloc("/etc/localtime", &t);
163 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
165 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
169 e = path_startswith(t, "/usr/share/zoneinfo/");
171 e = path_startswith(t, "../usr/share/zoneinfo/");
174 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
185 if (isempty(tz.zone)) {
190 tz.local_rtc = hwclock_is_localtime() > 0;
195 static int write_data_timezone(void) {
197 _cleanup_free_ char *p = NULL;
200 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
206 p = strappend("../usr/share/zoneinfo/", tz.zone);
210 r = symlink_atomic(p, "/etc/localtime");
217 static int write_data_local_rtc(void) {
221 r = read_full_file("/etc/adjtime", &s, NULL);
229 w = strdup(NULL_ADJTIME_LOCAL);
242 p = strchr(p+1, '\n');
258 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
264 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
266 if (streq(w, NULL_ADJTIME_UTC)) {
269 if (unlink("/etc/adjtime") < 0) {
278 r = write_one_line_file_atomic_label("/etc/adjtime", w);
284 static char** get_ntp_services(void) {
285 char **r = NULL, **files, **i;
288 k = conf_files_list(&files, ".list", NULL,
289 "/etc/systemd/ntp-units.d",
290 "/run/systemd/ntp-units.d",
291 "/usr/local/lib/systemd/ntp-units.d",
292 "/usr/lib/systemd/ntp-units.d",
297 STRV_FOREACH(i, files) {
305 char line[PATH_MAX], *l, **q;
307 if (!fgets(line, sizeof(line), f)) {
310 log_error("Failed to read NTP units file: %m");
316 if (l[0] == 0 || l[0] == '#')
319 q = strv_append(r, l);
337 static int read_ntp(DBusConnection *bus) {
338 DBusMessage *m = NULL, *reply = NULL;
345 dbus_error_init(&error);
347 l = get_ntp_services();
352 dbus_message_unref(m);
353 m = dbus_message_new_method_call(
354 "org.freedesktop.systemd1",
355 "/org/freedesktop/systemd1",
356 "org.freedesktop.systemd1.Manager",
363 if (!dbus_message_append_args(m,
365 DBUS_TYPE_INVALID)) {
371 dbus_message_unref(reply);
372 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
374 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
375 /* This implementation does not exist, try next one */
376 dbus_error_free(&error);
380 log_error("Failed to issue method call: %s", bus_error_message(&error));
385 if (!dbus_message_get_args(reply, &error,
386 DBUS_TYPE_STRING, &s,
387 DBUS_TYPE_INVALID)) {
388 log_error("Failed to parse reply: %s", bus_error_message(&error));
395 streq(s, "enabled") ||
396 streq(s, "enabled-runtime");
401 /* NTP is not installed. */
408 dbus_message_unref(m);
411 dbus_message_unref(reply);
415 dbus_error_free(&error);
420 static int start_ntp(DBusConnection *bus, DBusError *error) {
421 DBusMessage *m = NULL, *reply = NULL;
422 const char *mode = "replace";
429 l = get_ntp_services();
432 dbus_message_unref(m);
433 m = dbus_message_new_method_call(
434 "org.freedesktop.systemd1",
435 "/org/freedesktop/systemd1",
436 "org.freedesktop.systemd1.Manager",
437 tz.use_ntp ? "StartUnit" : "StopUnit");
439 log_error("Could not allocate message.");
444 if (!dbus_message_append_args(m,
446 DBUS_TYPE_STRING, &mode,
447 DBUS_TYPE_INVALID)) {
448 log_error("Could not append arguments to message.");
454 dbus_message_unref(reply);
455 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
457 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
458 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
459 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
460 /* This implementation does not exist, try next one */
461 dbus_error_free(error);
465 log_error("Failed to issue method call: %s", bus_error_message(error));
474 /* No implementaiton available... */
479 dbus_message_unref(m);
482 dbus_message_unref(reply);
489 static int enable_ntp(DBusConnection *bus, DBusError *error) {
490 DBusMessage *m = NULL, *reply = NULL;
492 DBusMessageIter iter;
493 dbus_bool_t f = FALSE, t = TRUE;
499 l = get_ntp_services();
504 dbus_message_unref(m);
505 m = dbus_message_new_method_call(
506 "org.freedesktop.systemd1",
507 "/org/freedesktop/systemd1",
508 "org.freedesktop.systemd1.Manager",
509 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
511 log_error("Could not allocate message.");
516 dbus_message_iter_init_append(m, &iter);
521 r = bus_append_strv_iter(&iter, k);
523 log_error("Failed to append unit files.");
527 /* send runtime bool */
528 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
529 log_error("Failed to append runtime boolean.");
535 /* send force bool */
536 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
537 log_error("Failed to append force boolean.");
544 dbus_message_unref(reply);
545 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
547 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
548 /* This implementation does not exist, try next one */
549 dbus_error_free(error);
553 log_error("Failed to issue method call: %s", bus_error_message(error));
558 dbus_message_unref(m);
559 m = dbus_message_new_method_call(
560 "org.freedesktop.systemd1",
561 "/org/freedesktop/systemd1",
562 "org.freedesktop.systemd1.Manager",
565 log_error("Could not allocate message.");
570 dbus_message_unref(reply);
571 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
573 log_error("Failed to issue method call: %s", bus_error_message(error));
586 dbus_message_unref(m);
589 dbus_message_unref(reply);
596 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
604 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
610 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
618 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
624 static const BusProperty bus_timedate_properties[] = {
625 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
626 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
627 { "CanNTP", property_append_can_ntp, "b", offsetof(TZ, can_ntp) },
628 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
632 static const BusBoundProperties bps[] = {
633 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
637 static DBusHandlerResult timedate_message_handler(
638 DBusConnection *connection,
639 DBusMessage *message,
642 DBusMessage *reply = NULL, *changed = NULL;
649 dbus_error_init(&error);
651 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
653 dbus_bool_t interactive;
655 if (!dbus_message_get_args(
658 DBUS_TYPE_STRING, &z,
659 DBUS_TYPE_BOOLEAN, &interactive,
661 return bus_send_error_reply(connection, message, &error, -EINVAL);
663 if (!valid_timezone(z))
664 return bus_send_error_reply(connection, message, NULL, -EINVAL);
666 if (!streq_ptr(z, tz.zone)) {
669 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
671 return bus_send_error_reply(connection, message, &error, r);
680 /* 1. Write new configuration file */
681 r = write_data_timezone();
683 log_error("Failed to set timezone: %s", strerror(-r));
684 return bus_send_error_reply(connection, message, NULL, r);
687 /* 2. Tell the kernel our time zone */
688 hwclock_set_timezone(NULL);
694 /* 3. Sync RTC from system clock, with the new delta */
695 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
696 assert_se(tm = localtime(&ts.tv_sec));
697 hwclock_set_time(tm);
701 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
702 "TIMEZONE=%s", tz.zone,
703 "MESSAGE=Changed timezone to '%s'.", tz.zone,
706 changed = bus_properties_changed_new(
707 "/org/freedesktop/timedate1",
708 "org.freedesktop.timedate1",
714 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
716 dbus_bool_t fix_system;
717 dbus_bool_t interactive;
719 if (!dbus_message_get_args(
722 DBUS_TYPE_BOOLEAN, &lrtc,
723 DBUS_TYPE_BOOLEAN, &fix_system,
724 DBUS_TYPE_BOOLEAN, &interactive,
726 return bus_send_error_reply(connection, message, &error, -EINVAL);
728 if (lrtc != tz.local_rtc) {
731 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
733 return bus_send_error_reply(connection, message, &error, r);
737 /* 1. Write new configuration file */
738 r = write_data_local_rtc();
740 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
741 return bus_send_error_reply(connection, message, NULL, r);
744 /* 2. Tell the kernel our time zone */
745 hwclock_set_timezone(NULL);
747 /* 3. Synchronize clocks */
748 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
753 /* Sync system clock from RTC; first,
754 * initialize the timezone fields of
757 tm = *localtime(&ts.tv_sec);
759 tm = *gmtime(&ts.tv_sec);
761 /* Override the main fields of
762 * struct tm, but not the timezone
764 if (hwclock_get_time(&tm) >= 0) {
766 /* And set the system clock
769 ts.tv_sec = mktime(&tm);
771 ts.tv_sec = timegm(&tm);
773 clock_settime(CLOCK_REALTIME, &ts);
779 /* Sync RTC from system clock */
781 tm = localtime(&ts.tv_sec);
783 tm = gmtime(&ts.tv_sec);
785 hwclock_set_time(tm);
788 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
790 changed = bus_properties_changed_new(
791 "/org/freedesktop/timedate1",
792 "org.freedesktop.timedate1",
798 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
800 dbus_bool_t relative;
801 dbus_bool_t interactive;
803 if (!dbus_message_get_args(
806 DBUS_TYPE_INT64, &utc,
807 DBUS_TYPE_BOOLEAN, &relative,
808 DBUS_TYPE_BOOLEAN, &interactive,
810 return bus_send_error_reply(connection, message, &error, -EINVAL);
812 if (!relative && utc <= 0)
813 return bus_send_error_reply(connection, message, NULL, -EINVAL);
815 if (!relative || utc != 0) {
819 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
821 return bus_send_error_reply(connection, message, &error, r);
824 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
826 timespec_store(&ts, utc);
828 /* Set system clock */
829 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
830 log_error("Failed to set local time: %m");
831 return bus_send_error_reply(connection, message, NULL, -errno);
834 /* Sync down to RTC */
836 tm = localtime(&ts.tv_sec);
838 tm = gmtime(&ts.tv_sec);
840 hwclock_set_time(tm);
843 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
844 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
845 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
848 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
850 dbus_bool_t interactive;
852 if (!dbus_message_get_args(
855 DBUS_TYPE_BOOLEAN, &ntp,
856 DBUS_TYPE_BOOLEAN, &interactive,
858 return bus_send_error_reply(connection, message, &error, -EINVAL);
860 if (ntp != !!tz.use_ntp) {
862 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
864 return bus_send_error_reply(connection, message, &error, r);
868 r = enable_ntp(connection, &error);
870 return bus_send_error_reply(connection, message, &error, r);
872 r = start_ntp(connection, &error);
874 return bus_send_error_reply(connection, message, &error, r);
876 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
878 changed = bus_properties_changed_new(
879 "/org/freedesktop/timedate1",
880 "org.freedesktop.timedate1",
887 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
889 if (!(reply = dbus_message_new_method_return(message)))
892 if (!bus_maybe_send_reply(connection, message, reply))
895 dbus_message_unref(reply);
900 if (!dbus_connection_send(connection, changed, NULL))
903 dbus_message_unref(changed);
906 return DBUS_HANDLER_RESULT_HANDLED;
910 dbus_message_unref(reply);
913 dbus_message_unref(changed);
915 dbus_error_free(&error);
917 return DBUS_HANDLER_RESULT_NEED_MEMORY;
920 static int connect_bus(DBusConnection **_bus) {
921 static const DBusObjectPathVTable timedate_vtable = {
922 .message_function = timedate_message_handler
925 DBusConnection *bus = NULL;
930 dbus_error_init(&error);
932 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
934 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
939 dbus_connection_set_exit_on_disconnect(bus, FALSE);
941 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
942 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
947 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
948 if (dbus_error_is_set(&error)) {
949 log_error("Failed to register name on bus: %s", bus_error_message(&error));
954 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
955 log_error("Failed to acquire name.");
966 dbus_connection_close(bus);
967 dbus_connection_unref(bus);
969 dbus_error_free(&error);
974 int main(int argc, char *argv[]) {
976 DBusConnection *bus = NULL;
977 bool exiting = false;
979 log_set_target(LOG_TARGET_AUTO);
980 log_parse_environment();
985 if (argc == 2 && streq(argv[1], "--introspect")) {
986 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
988 fputs(timedate_interface, stdout);
989 fputs("</node>\n", stdout);
994 log_error("This program takes no arguments.");
1001 log_error("Failed to read timezone data: %s", strerror(-r));
1005 r = connect_bus(&bus);
1011 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1015 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1018 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1021 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1023 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1033 dbus_connection_flush(bus);
1034 dbus_connection_close(bus);
1035 dbus_connection_unref(bus);
1038 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;