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) {
219 char _cleanup_free_ *s = NULL, *w = NULL;
221 r = read_full_file("/etc/adjtime", &s, NULL);
229 w = strdup(NULL_ADJTIME_LOCAL);
240 p = strchr(p+1, '\n');
252 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
256 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
258 if (streq(w, NULL_ADJTIME_UTC)) {
259 if (unlink("/etc/adjtime") < 0)
267 return write_string_file_atomic_label("/etc/adjtime", w);
270 static char** get_ntp_services(void) {
271 char _cleanup_strv_free_ **r = NULL, **files;
275 k = conf_files_list(&files, ".list", NULL,
276 "/etc/systemd/ntp-units.d",
277 "/run/systemd/ntp-units.d",
278 "/usr/local/lib/systemd/ntp-units.d",
279 "/usr/lib/systemd/ntp-units.d",
284 STRV_FOREACH(i, files) {
285 FILE _cleanup_fclose_ *f;
292 char line[PATH_MAX], *l;
294 if (!fgets(line, sizeof(line), f)) {
297 log_error("Failed to read NTP units file: %m");
303 if (l[0] == 0 || l[0] == '#')
306 if (strv_extend(&r, l) < 0)
313 r = NULL; /* avoid cleanup */
318 static int read_ntp(DBusConnection *bus) {
319 DBusMessage *m = NULL, *reply = NULL;
326 dbus_error_init(&error);
328 l = get_ntp_services();
333 dbus_message_unref(m);
334 m = dbus_message_new_method_call(
335 "org.freedesktop.systemd1",
336 "/org/freedesktop/systemd1",
337 "org.freedesktop.systemd1.Manager",
344 if (!dbus_message_append_args(m,
346 DBUS_TYPE_INVALID)) {
351 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
353 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
354 /* This implementation does not exist, try next one */
355 dbus_error_free(&error);
359 log_error("Failed to issue method call: %s", bus_error_message(&error));
364 if (!dbus_message_get_args(reply, &error,
365 DBUS_TYPE_STRING, &s,
366 DBUS_TYPE_INVALID)) {
367 log_error("Failed to parse reply: %s", bus_error_message(&error));
374 streq(s, "enabled") ||
375 streq(s, "enabled-runtime");
380 /* NTP is not installed. */
387 dbus_message_unref(m);
390 dbus_message_unref(reply);
394 dbus_error_free(&error);
399 static int start_ntp(DBusConnection *bus, DBusError *error) {
400 DBusMessage *m = NULL, *reply = NULL;
401 const char *mode = "replace";
408 l = get_ntp_services();
411 dbus_message_unref(m);
412 m = dbus_message_new_method_call(
413 "org.freedesktop.systemd1",
414 "/org/freedesktop/systemd1",
415 "org.freedesktop.systemd1.Manager",
416 tz.use_ntp ? "StartUnit" : "StopUnit");
418 log_error("Could not allocate message.");
423 if (!dbus_message_append_args(m,
425 DBUS_TYPE_STRING, &mode,
426 DBUS_TYPE_INVALID)) {
427 log_error("Could not append arguments to message.");
432 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
434 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
435 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
436 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
437 /* This implementation does not exist, try next one */
438 dbus_error_free(error);
442 log_error("Failed to issue method call: %s", bus_error_message(error));
451 /* No implementaiton available... */
456 dbus_message_unref(m);
459 dbus_message_unref(reply);
466 static int enable_ntp(DBusConnection *bus, DBusError *error) {
467 DBusMessage *m = NULL, *reply = NULL;
469 DBusMessageIter iter;
470 dbus_bool_t f = FALSE, t = TRUE;
476 l = get_ntp_services();
481 dbus_message_unref(m);
482 m = dbus_message_new_method_call(
483 "org.freedesktop.systemd1",
484 "/org/freedesktop/systemd1",
485 "org.freedesktop.systemd1.Manager",
486 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
488 log_error("Could not allocate message.");
493 dbus_message_iter_init_append(m, &iter);
498 r = bus_append_strv_iter(&iter, k);
500 log_error("Failed to append unit files.");
504 /* send runtime bool */
505 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
506 log_error("Failed to append runtime boolean.");
512 /* send force bool */
513 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
514 log_error("Failed to append force boolean.");
520 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
522 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
523 /* This implementation does not exist, try next one */
524 dbus_error_free(error);
528 log_error("Failed to issue method call: %s", bus_error_message(error));
533 dbus_message_unref(m);
534 m = dbus_message_new_method_call(
535 "org.freedesktop.systemd1",
536 "/org/freedesktop/systemd1",
537 "org.freedesktop.systemd1.Manager",
540 log_error("Could not allocate message.");
545 dbus_message_unref(reply);
546 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
548 log_error("Failed to issue method call: %s", bus_error_message(error));
561 dbus_message_unref(m);
564 dbus_message_unref(reply);
571 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
579 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
585 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
593 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
599 static const BusProperty bus_timedate_properties[] = {
600 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
601 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
602 { "CanNTP", property_append_can_ntp, "b", offsetof(TZ, can_ntp) },
603 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
607 static const BusBoundProperties bps[] = {
608 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
612 static DBusHandlerResult timedate_message_handler(
613 DBusConnection *connection,
614 DBusMessage *message,
617 DBusMessage *reply = NULL, *changed = NULL;
624 dbus_error_init(&error);
626 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
628 dbus_bool_t interactive;
630 if (!dbus_message_get_args(
633 DBUS_TYPE_STRING, &z,
634 DBUS_TYPE_BOOLEAN, &interactive,
636 return bus_send_error_reply(connection, message, &error, -EINVAL);
638 if (!valid_timezone(z))
639 return bus_send_error_reply(connection, message, NULL, -EINVAL);
641 if (!streq_ptr(z, tz.zone)) {
644 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
646 return bus_send_error_reply(connection, message, &error, r);
655 /* 1. Write new configuration file */
656 r = write_data_timezone();
658 log_error("Failed to set timezone: %s", strerror(-r));
659 return bus_send_error_reply(connection, message, NULL, r);
662 /* 2. Tell the kernel our time zone */
663 hwclock_set_timezone(NULL);
669 /* 3. Sync RTC from system clock, with the new delta */
670 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
671 assert_se(tm = localtime(&ts.tv_sec));
672 hwclock_set_time(tm);
676 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
677 "TIMEZONE=%s", tz.zone,
678 "MESSAGE=Changed timezone to '%s'.", tz.zone,
681 changed = bus_properties_changed_new(
682 "/org/freedesktop/timedate1",
683 "org.freedesktop.timedate1",
689 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
691 dbus_bool_t fix_system;
692 dbus_bool_t interactive;
694 if (!dbus_message_get_args(
697 DBUS_TYPE_BOOLEAN, &lrtc,
698 DBUS_TYPE_BOOLEAN, &fix_system,
699 DBUS_TYPE_BOOLEAN, &interactive,
701 return bus_send_error_reply(connection, message, &error, -EINVAL);
703 if (lrtc != tz.local_rtc) {
706 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
708 return bus_send_error_reply(connection, message, &error, r);
712 /* 1. Write new configuration file */
713 r = write_data_local_rtc();
715 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
716 return bus_send_error_reply(connection, message, NULL, r);
719 /* 2. Tell the kernel our time zone */
720 hwclock_set_timezone(NULL);
722 /* 3. Synchronize clocks */
723 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
728 /* Sync system clock from RTC; first,
729 * initialize the timezone fields of
732 tm = *localtime(&ts.tv_sec);
734 tm = *gmtime(&ts.tv_sec);
736 /* Override the main fields of
737 * struct tm, but not the timezone
739 if (hwclock_get_time(&tm) >= 0) {
741 /* And set the system clock
744 ts.tv_sec = mktime(&tm);
746 ts.tv_sec = timegm(&tm);
748 clock_settime(CLOCK_REALTIME, &ts);
754 /* Sync RTC from system clock */
756 tm = localtime(&ts.tv_sec);
758 tm = gmtime(&ts.tv_sec);
760 hwclock_set_time(tm);
763 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
765 changed = bus_properties_changed_new(
766 "/org/freedesktop/timedate1",
767 "org.freedesktop.timedate1",
773 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
775 dbus_bool_t relative;
776 dbus_bool_t interactive;
778 if (!dbus_message_get_args(
781 DBUS_TYPE_INT64, &utc,
782 DBUS_TYPE_BOOLEAN, &relative,
783 DBUS_TYPE_BOOLEAN, &interactive,
785 return bus_send_error_reply(connection, message, &error, -EINVAL);
787 if (!relative && utc <= 0)
788 return bus_send_error_reply(connection, message, NULL, -EINVAL);
790 if (!relative || utc != 0) {
797 n = now(CLOCK_REALTIME);
800 if ((utc > 0 && x < n) ||
802 return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
804 timespec_store(&ts, x);
806 timespec_store(&ts, (usec_t) utc);
808 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
810 return bus_send_error_reply(connection, message, &error, r);
812 /* Set system clock */
813 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
814 log_error("Failed to set local time: %m");
815 return bus_send_error_reply(connection, message, NULL, -errno);
818 /* Sync down to RTC */
820 tm = localtime(&ts.tv_sec);
822 tm = gmtime(&ts.tv_sec);
824 hwclock_set_time(tm);
827 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
828 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
829 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
832 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
834 dbus_bool_t interactive;
836 if (!dbus_message_get_args(
839 DBUS_TYPE_BOOLEAN, &ntp,
840 DBUS_TYPE_BOOLEAN, &interactive,
842 return bus_send_error_reply(connection, message, &error, -EINVAL);
844 if (ntp != !!tz.use_ntp) {
846 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
848 return bus_send_error_reply(connection, message, &error, r);
852 r = enable_ntp(connection, &error);
854 return bus_send_error_reply(connection, message, &error, r);
856 r = start_ntp(connection, &error);
858 return bus_send_error_reply(connection, message, &error, r);
860 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
862 changed = bus_properties_changed_new(
863 "/org/freedesktop/timedate1",
864 "org.freedesktop.timedate1",
871 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
873 if (!(reply = dbus_message_new_method_return(message)))
876 if (!bus_maybe_send_reply(connection, message, reply))
879 dbus_message_unref(reply);
884 if (!dbus_connection_send(connection, changed, NULL))
887 dbus_message_unref(changed);
890 return DBUS_HANDLER_RESULT_HANDLED;
894 dbus_message_unref(reply);
897 dbus_message_unref(changed);
899 dbus_error_free(&error);
901 return DBUS_HANDLER_RESULT_NEED_MEMORY;
904 static int connect_bus(DBusConnection **_bus) {
905 static const DBusObjectPathVTable timedate_vtable = {
906 .message_function = timedate_message_handler
909 DBusConnection *bus = NULL;
914 dbus_error_init(&error);
916 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
918 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
923 dbus_connection_set_exit_on_disconnect(bus, FALSE);
925 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
926 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
931 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
932 if (dbus_error_is_set(&error)) {
933 log_error("Failed to register name on bus: %s", bus_error_message(&error));
938 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
939 log_error("Failed to acquire name.");
950 dbus_connection_close(bus);
951 dbus_connection_unref(bus);
953 dbus_error_free(&error);
958 int main(int argc, char *argv[]) {
960 DBusConnection *bus = NULL;
961 bool exiting = false;
963 log_set_target(LOG_TARGET_AUTO);
964 log_parse_environment();
969 if (argc == 2 && streq(argv[1], "--introspect")) {
970 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
972 fputs(timedate_interface, stdout);
973 fputs("</node>\n", stdout);
978 log_error("This program takes no arguments.");
985 log_error("Failed to read timezone data: %s", strerror(-r));
989 r = connect_bus(&bus);
995 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
999 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1002 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1005 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1007 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1017 dbus_connection_flush(bus);
1018 dbus_connection_close(bus);
1019 dbus_connection_unref(bus);
1022 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;