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) {
822 n = now(CLOCK_REALTIME);
825 if ((utc > 0 && x < n) ||
827 return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
829 timespec_store(&ts, x);
831 timespec_store(&ts, (usec_t) utc);
833 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
835 return bus_send_error_reply(connection, message, &error, r);
837 /* Set system clock */
838 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
839 log_error("Failed to set local time: %m");
840 return bus_send_error_reply(connection, message, NULL, -errno);
843 /* Sync down to RTC */
845 tm = localtime(&ts.tv_sec);
847 tm = gmtime(&ts.tv_sec);
849 hwclock_set_time(tm);
852 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
853 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
854 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
857 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
859 dbus_bool_t interactive;
861 if (!dbus_message_get_args(
864 DBUS_TYPE_BOOLEAN, &ntp,
865 DBUS_TYPE_BOOLEAN, &interactive,
867 return bus_send_error_reply(connection, message, &error, -EINVAL);
869 if (ntp != !!tz.use_ntp) {
871 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
873 return bus_send_error_reply(connection, message, &error, r);
877 r = enable_ntp(connection, &error);
879 return bus_send_error_reply(connection, message, &error, r);
881 r = start_ntp(connection, &error);
883 return bus_send_error_reply(connection, message, &error, r);
885 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
887 changed = bus_properties_changed_new(
888 "/org/freedesktop/timedate1",
889 "org.freedesktop.timedate1",
896 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
898 if (!(reply = dbus_message_new_method_return(message)))
901 if (!bus_maybe_send_reply(connection, message, reply))
904 dbus_message_unref(reply);
909 if (!dbus_connection_send(connection, changed, NULL))
912 dbus_message_unref(changed);
915 return DBUS_HANDLER_RESULT_HANDLED;
919 dbus_message_unref(reply);
922 dbus_message_unref(changed);
924 dbus_error_free(&error);
926 return DBUS_HANDLER_RESULT_NEED_MEMORY;
929 static int connect_bus(DBusConnection **_bus) {
930 static const DBusObjectPathVTable timedate_vtable = {
931 .message_function = timedate_message_handler
934 DBusConnection *bus = NULL;
939 dbus_error_init(&error);
941 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
943 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
948 dbus_connection_set_exit_on_disconnect(bus, FALSE);
950 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
951 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
956 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
957 if (dbus_error_is_set(&error)) {
958 log_error("Failed to register name on bus: %s", bus_error_message(&error));
963 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
964 log_error("Failed to acquire name.");
975 dbus_connection_close(bus);
976 dbus_connection_unref(bus);
978 dbus_error_free(&error);
983 int main(int argc, char *argv[]) {
985 DBusConnection *bus = NULL;
986 bool exiting = false;
988 log_set_target(LOG_TARGET_AUTO);
989 log_parse_environment();
994 if (argc == 2 && streq(argv[1], "--introspect")) {
995 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
997 fputs(timedate_interface, stdout);
998 fputs("</node>\n", stdout);
1003 log_error("This program takes no arguments.");
1010 log_error("Failed to read timezone data: %s", strerror(-r));
1014 r = connect_bus(&bus);
1020 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1024 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1027 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1030 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1032 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1042 dbus_connection_flush(bus);
1043 dbus_connection_close(bus);
1044 dbus_connection_unref(bus);
1047 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;