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_string_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)) {
370 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
372 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
373 /* This implementation does not exist, try next one */
374 dbus_error_free(&error);
378 log_error("Failed to issue method call: %s", bus_error_message(&error));
383 if (!dbus_message_get_args(reply, &error,
384 DBUS_TYPE_STRING, &s,
385 DBUS_TYPE_INVALID)) {
386 log_error("Failed to parse reply: %s", bus_error_message(&error));
393 streq(s, "enabled") ||
394 streq(s, "enabled-runtime");
399 /* NTP is not installed. */
406 dbus_message_unref(m);
409 dbus_message_unref(reply);
413 dbus_error_free(&error);
418 static int start_ntp(DBusConnection *bus, DBusError *error) {
419 DBusMessage *m = NULL, *reply = NULL;
420 const char *mode = "replace";
427 l = get_ntp_services();
430 dbus_message_unref(m);
431 m = dbus_message_new_method_call(
432 "org.freedesktop.systemd1",
433 "/org/freedesktop/systemd1",
434 "org.freedesktop.systemd1.Manager",
435 tz.use_ntp ? "StartUnit" : "StopUnit");
437 log_error("Could not allocate message.");
442 if (!dbus_message_append_args(m,
444 DBUS_TYPE_STRING, &mode,
445 DBUS_TYPE_INVALID)) {
446 log_error("Could not append arguments to message.");
451 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
453 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
454 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
455 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
456 /* This implementation does not exist, try next one */
457 dbus_error_free(error);
461 log_error("Failed to issue method call: %s", bus_error_message(error));
470 /* No implementaiton available... */
475 dbus_message_unref(m);
478 dbus_message_unref(reply);
485 static int enable_ntp(DBusConnection *bus, DBusError *error) {
486 DBusMessage *m = NULL, *reply = NULL;
488 DBusMessageIter iter;
489 dbus_bool_t f = FALSE, t = TRUE;
495 l = get_ntp_services();
500 dbus_message_unref(m);
501 m = dbus_message_new_method_call(
502 "org.freedesktop.systemd1",
503 "/org/freedesktop/systemd1",
504 "org.freedesktop.systemd1.Manager",
505 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
507 log_error("Could not allocate message.");
512 dbus_message_iter_init_append(m, &iter);
517 r = bus_append_strv_iter(&iter, k);
519 log_error("Failed to append unit files.");
523 /* send runtime bool */
524 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
525 log_error("Failed to append runtime boolean.");
531 /* send force bool */
532 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
533 log_error("Failed to append force boolean.");
539 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
541 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
542 /* This implementation does not exist, try next one */
543 dbus_error_free(error);
547 log_error("Failed to issue method call: %s", bus_error_message(error));
552 dbus_message_unref(m);
553 m = dbus_message_new_method_call(
554 "org.freedesktop.systemd1",
555 "/org/freedesktop/systemd1",
556 "org.freedesktop.systemd1.Manager",
559 log_error("Could not allocate message.");
564 dbus_message_unref(reply);
565 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
567 log_error("Failed to issue method call: %s", bus_error_message(error));
580 dbus_message_unref(m);
583 dbus_message_unref(reply);
590 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
598 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
604 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
612 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
618 static const BusProperty bus_timedate_properties[] = {
619 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
620 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
621 { "CanNTP", property_append_can_ntp, "b", offsetof(TZ, can_ntp) },
622 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
626 static const BusBoundProperties bps[] = {
627 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
631 static DBusHandlerResult timedate_message_handler(
632 DBusConnection *connection,
633 DBusMessage *message,
636 DBusMessage *reply = NULL, *changed = NULL;
643 dbus_error_init(&error);
645 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
647 dbus_bool_t interactive;
649 if (!dbus_message_get_args(
652 DBUS_TYPE_STRING, &z,
653 DBUS_TYPE_BOOLEAN, &interactive,
655 return bus_send_error_reply(connection, message, &error, -EINVAL);
657 if (!valid_timezone(z))
658 return bus_send_error_reply(connection, message, NULL, -EINVAL);
660 if (!streq_ptr(z, tz.zone)) {
663 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
665 return bus_send_error_reply(connection, message, &error, r);
674 /* 1. Write new configuration file */
675 r = write_data_timezone();
677 log_error("Failed to set timezone: %s", strerror(-r));
678 return bus_send_error_reply(connection, message, NULL, r);
681 /* 2. Tell the kernel our time zone */
682 hwclock_set_timezone(NULL);
688 /* 3. Sync RTC from system clock, with the new delta */
689 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
690 assert_se(tm = localtime(&ts.tv_sec));
691 hwclock_set_time(tm);
695 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
696 "TIMEZONE=%s", tz.zone,
697 "MESSAGE=Changed timezone to '%s'.", tz.zone,
700 changed = bus_properties_changed_new(
701 "/org/freedesktop/timedate1",
702 "org.freedesktop.timedate1",
708 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
710 dbus_bool_t fix_system;
711 dbus_bool_t interactive;
713 if (!dbus_message_get_args(
716 DBUS_TYPE_BOOLEAN, &lrtc,
717 DBUS_TYPE_BOOLEAN, &fix_system,
718 DBUS_TYPE_BOOLEAN, &interactive,
720 return bus_send_error_reply(connection, message, &error, -EINVAL);
722 if (lrtc != tz.local_rtc) {
725 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
727 return bus_send_error_reply(connection, message, &error, r);
731 /* 1. Write new configuration file */
732 r = write_data_local_rtc();
734 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
735 return bus_send_error_reply(connection, message, NULL, r);
738 /* 2. Tell the kernel our time zone */
739 hwclock_set_timezone(NULL);
741 /* 3. Synchronize clocks */
742 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
747 /* Sync system clock from RTC; first,
748 * initialize the timezone fields of
751 tm = *localtime(&ts.tv_sec);
753 tm = *gmtime(&ts.tv_sec);
755 /* Override the main fields of
756 * struct tm, but not the timezone
758 if (hwclock_get_time(&tm) >= 0) {
760 /* And set the system clock
763 ts.tv_sec = mktime(&tm);
765 ts.tv_sec = timegm(&tm);
767 clock_settime(CLOCK_REALTIME, &ts);
773 /* Sync RTC from system clock */
775 tm = localtime(&ts.tv_sec);
777 tm = gmtime(&ts.tv_sec);
779 hwclock_set_time(tm);
782 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
784 changed = bus_properties_changed_new(
785 "/org/freedesktop/timedate1",
786 "org.freedesktop.timedate1",
792 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
794 dbus_bool_t relative;
795 dbus_bool_t interactive;
797 if (!dbus_message_get_args(
800 DBUS_TYPE_INT64, &utc,
801 DBUS_TYPE_BOOLEAN, &relative,
802 DBUS_TYPE_BOOLEAN, &interactive,
804 return bus_send_error_reply(connection, message, &error, -EINVAL);
806 if (!relative && utc <= 0)
807 return bus_send_error_reply(connection, message, NULL, -EINVAL);
809 if (!relative || utc != 0) {
816 n = now(CLOCK_REALTIME);
819 if ((utc > 0 && x < n) ||
821 return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
823 timespec_store(&ts, x);
825 timespec_store(&ts, (usec_t) utc);
827 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
829 return bus_send_error_reply(connection, message, &error, r);
831 /* Set system clock */
832 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
833 log_error("Failed to set local time: %m");
834 return bus_send_error_reply(connection, message, NULL, -errno);
837 /* Sync down to RTC */
839 tm = localtime(&ts.tv_sec);
841 tm = gmtime(&ts.tv_sec);
843 hwclock_set_time(tm);
846 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
847 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
848 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
851 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
853 dbus_bool_t interactive;
855 if (!dbus_message_get_args(
858 DBUS_TYPE_BOOLEAN, &ntp,
859 DBUS_TYPE_BOOLEAN, &interactive,
861 return bus_send_error_reply(connection, message, &error, -EINVAL);
863 if (ntp != !!tz.use_ntp) {
865 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
867 return bus_send_error_reply(connection, message, &error, r);
871 r = enable_ntp(connection, &error);
873 return bus_send_error_reply(connection, message, &error, r);
875 r = start_ntp(connection, &error);
877 return bus_send_error_reply(connection, message, &error, r);
879 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
881 changed = bus_properties_changed_new(
882 "/org/freedesktop/timedate1",
883 "org.freedesktop.timedate1",
890 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
892 if (!(reply = dbus_message_new_method_return(message)))
895 if (!bus_maybe_send_reply(connection, message, reply))
898 dbus_message_unref(reply);
903 if (!dbus_connection_send(connection, changed, NULL))
906 dbus_message_unref(changed);
909 return DBUS_HANDLER_RESULT_HANDLED;
913 dbus_message_unref(reply);
916 dbus_message_unref(changed);
918 dbus_error_free(&error);
920 return DBUS_HANDLER_RESULT_NEED_MEMORY;
923 static int connect_bus(DBusConnection **_bus) {
924 static const DBusObjectPathVTable timedate_vtable = {
925 .message_function = timedate_message_handler
928 DBusConnection *bus = NULL;
933 dbus_error_init(&error);
935 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
937 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
942 dbus_connection_set_exit_on_disconnect(bus, FALSE);
944 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
945 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
950 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
951 if (dbus_error_is_set(&error)) {
952 log_error("Failed to register name on bus: %s", bus_error_message(&error));
957 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
958 log_error("Failed to acquire name.");
969 dbus_connection_close(bus);
970 dbus_connection_unref(bus);
972 dbus_error_free(&error);
977 int main(int argc, char *argv[]) {
979 DBusConnection *bus = NULL;
980 bool exiting = false;
982 log_set_target(LOG_TARGET_AUTO);
983 log_parse_environment();
988 if (argc == 2 && streq(argv[1], "--introspect")) {
989 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
991 fputs(timedate_interface, stdout);
992 fputs("</node>\n", stdout);
997 log_error("This program takes no arguments.");
1004 log_error("Failed to read timezone data: %s", strerror(-r));
1008 r = connect_bus(&bus);
1014 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1018 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1021 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1024 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1026 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1036 dbus_connection_flush(bus);
1037 dbus_connection_close(bus);
1038 dbus_connection_unref(bus);
1041 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;