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"
39 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
40 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
43 " <interface name=\"org.freedesktop.timedate1\">\n" \
44 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
45 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
46 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
47 " <method name=\"SetTime\">\n" \
48 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
49 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
50 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
52 " <method name=\"SetTimezone\">\n" \
53 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
54 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
56 " <method name=\"SetLocalRTC\">\n" \
57 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
58 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
59 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
61 " <method name=\"SetNTP\">\n" \
62 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
63 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
67 #define INTROSPECTION \
68 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
71 BUS_PROPERTIES_INTERFACE \
72 BUS_INTROSPECTABLE_INTERFACE \
76 #define INTERFACES_LIST \
77 BUS_GENERIC_INTERFACES_LIST \
78 "org.freedesktop.timedate1\0"
80 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
92 static usec_t remain_until;
94 static void free_data(void) {
101 static bool valid_timezone(const char *name) {
110 if (*name == '/' || *name == 0)
113 for (p = name; *p; p++) {
114 if (!(*p >= '0' && *p <= '9') &&
115 !(*p >= 'a' && *p <= 'z') &&
116 !(*p >= 'A' && *p <= 'Z') &&
117 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
133 t = strappend("/usr/share/zoneinfo/", name);
143 if (!S_ISREG(st.st_mode))
149 static int read_data(void) {
151 _cleanup_free_ char *t = NULL;
155 r = readlink_malloc("/etc/localtime", &t);
158 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
160 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
164 e = path_startswith(t, "/usr/share/zoneinfo/");
166 e = path_startswith(t, "../usr/share/zoneinfo/");
169 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
180 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
184 if (r < 0 && r != -ENOENT)
185 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
189 r = read_one_line_file("/etc/timezone", &tz.zone);
192 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
197 if (isempty(tz.zone)) {
202 tz.local_rtc = hwclock_is_localtime() > 0;
207 static int write_data_timezone(void) {
209 _cleanup_free_ char *p = NULL;
216 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
220 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
227 p = strappend("../usr/share/zoneinfo/", tz.zone);
231 r = symlink_atomic(p, "/etc/localtime");
236 if (stat("/etc/timezone", &st) == 0 && S_ISREG(st.st_mode)) {
237 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
246 static int write_data_local_rtc(void) {
250 r = read_full_file("/etc/adjtime", &s, NULL);
258 w = strdup(NULL_ADJTIME_LOCAL);
271 p = strchr(p+1, '\n');
287 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
293 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
295 if (streq(w, NULL_ADJTIME_UTC)) {
298 if (unlink("/etc/adjtime") < 0) {
307 r = write_one_line_file_atomic("/etc/adjtime", w);
313 static char** get_ntp_services(void) {
314 char **r = NULL, **files, **i;
317 k = conf_files_list(&files, ".list",
318 "/etc/systemd/ntp-units.d",
319 "/run/systemd/ntp-units.d",
320 "/usr/local/lib/systemd/ntp-units.d",
321 "/usr/lib/systemd/ntp-units.d",
326 STRV_FOREACH(i, files) {
334 char line[PATH_MAX], *l, **q;
336 if (!fgets(line, sizeof(line), f)) {
339 log_error("Failed to read NTP units file: %m");
345 if (l[0] == 0 || l[0] == '#')
348 q = strv_append(r, l);
366 static int read_ntp(DBusConnection *bus) {
367 DBusMessage *m = NULL, *reply = NULL;
374 dbus_error_init(&error);
376 l = get_ntp_services();
381 dbus_message_unref(m);
382 m = dbus_message_new_method_call(
383 "org.freedesktop.systemd1",
384 "/org/freedesktop/systemd1",
385 "org.freedesktop.systemd1.Manager",
392 if (!dbus_message_append_args(m,
394 DBUS_TYPE_INVALID)) {
400 dbus_message_unref(reply);
401 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
403 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
404 /* This implementation does not exist, try next one */
405 dbus_error_free(&error);
409 log_error("Failed to issue method call: %s", bus_error_message(&error));
414 if (!dbus_message_get_args(reply, &error,
415 DBUS_TYPE_STRING, &s,
416 DBUS_TYPE_INVALID)) {
417 log_error("Failed to parse reply: %s", bus_error_message(&error));
423 streq(s, "enabled") ||
424 streq(s, "enabled-runtime");
429 /* NTP is not installed. */
435 dbus_message_unref(m);
438 dbus_message_unref(reply);
442 dbus_error_free(&error);
447 static int start_ntp(DBusConnection *bus, DBusError *error) {
448 DBusMessage *m = NULL, *reply = NULL;
449 const char *mode = "replace";
456 l = get_ntp_services();
459 dbus_message_unref(m);
460 m = dbus_message_new_method_call(
461 "org.freedesktop.systemd1",
462 "/org/freedesktop/systemd1",
463 "org.freedesktop.systemd1.Manager",
464 tz.use_ntp ? "StartUnit" : "StopUnit");
466 log_error("Could not allocate message.");
471 if (!dbus_message_append_args(m,
473 DBUS_TYPE_STRING, &mode,
474 DBUS_TYPE_INVALID)) {
475 log_error("Could not append arguments to message.");
481 dbus_message_unref(reply);
482 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
484 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
485 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
486 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
487 /* This implementation does not exist, try next one */
488 dbus_error_free(error);
492 log_error("Failed to issue method call: %s", bus_error_message(error));
501 /* No implementaiton available... */
506 dbus_message_unref(m);
509 dbus_message_unref(reply);
516 static int enable_ntp(DBusConnection *bus, DBusError *error) {
517 DBusMessage *m = NULL, *reply = NULL;
519 DBusMessageIter iter;
520 dbus_bool_t f = FALSE, t = TRUE;
526 l = get_ntp_services();
531 dbus_message_unref(m);
532 m = dbus_message_new_method_call(
533 "org.freedesktop.systemd1",
534 "/org/freedesktop/systemd1",
535 "org.freedesktop.systemd1.Manager",
536 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
538 log_error("Could not allocate message.");
543 dbus_message_iter_init_append(m, &iter);
548 r = bus_append_strv_iter(&iter, k);
550 log_error("Failed to append unit files.");
554 /* send runtime bool */
555 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
556 log_error("Failed to append runtime boolean.");
562 /* send force bool */
563 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
564 log_error("Failed to append force boolean.");
571 dbus_message_unref(reply);
572 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
574 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
575 /* This implementation does not exist, try next one */
576 dbus_error_free(error);
580 log_error("Failed to issue method call: %s", bus_error_message(error));
585 dbus_message_unref(m);
586 m = dbus_message_new_method_call(
587 "org.freedesktop.systemd1",
588 "/org/freedesktop/systemd1",
589 "org.freedesktop.systemd1.Manager",
592 log_error("Could not allocate message.");
597 dbus_message_unref(reply);
598 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
600 log_error("Failed to issue method call: %s", bus_error_message(error));
613 dbus_message_unref(m);
616 dbus_message_unref(reply);
623 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
631 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
637 static const BusProperty bus_timedate_properties[] = {
638 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
639 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
640 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
644 static const BusBoundProperties bps[] = {
645 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
649 static DBusHandlerResult timedate_message_handler(
650 DBusConnection *connection,
651 DBusMessage *message,
654 DBusMessage *reply = NULL, *changed = NULL;
661 dbus_error_init(&error);
663 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
665 dbus_bool_t interactive;
667 if (!dbus_message_get_args(
670 DBUS_TYPE_STRING, &z,
671 DBUS_TYPE_BOOLEAN, &interactive,
673 return bus_send_error_reply(connection, message, &error, -EINVAL);
675 if (!valid_timezone(z))
676 return bus_send_error_reply(connection, message, NULL, -EINVAL);
678 if (!streq_ptr(z, tz.zone)) {
681 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
683 return bus_send_error_reply(connection, message, &error, r);
692 /* 1. Write new configuration file */
693 r = write_data_timezone();
695 log_error("Failed to set timezone: %s", strerror(-r));
696 return bus_send_error_reply(connection, message, NULL, r);
703 /* 2. Teach kernel new timezone */
704 hwclock_apply_localtime_delta(NULL);
706 /* 3. Sync RTC from system clock, with the new delta */
707 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
708 assert_se(tm = localtime(&ts.tv_sec));
709 hwclock_set_time(tm);
713 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIMEZONE_CHANGE),
714 "TIMEZONE=%s", tz.zone,
715 "MESSAGE=Changed timezone to '%s'.", tz.zone,
718 changed = bus_properties_changed_new(
719 "/org/freedesktop/timedate1",
720 "org.freedesktop.timedate1",
726 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
728 dbus_bool_t fix_system;
729 dbus_bool_t interactive;
731 if (!dbus_message_get_args(
734 DBUS_TYPE_BOOLEAN, &lrtc,
735 DBUS_TYPE_BOOLEAN, &fix_system,
736 DBUS_TYPE_BOOLEAN, &interactive,
738 return bus_send_error_reply(connection, message, &error, -EINVAL);
740 if (lrtc != tz.local_rtc) {
743 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
745 return bus_send_error_reply(connection, message, &error, r);
749 /* 1. Write new configuration file */
750 r = write_data_local_rtc();
752 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
753 return bus_send_error_reply(connection, message, NULL, r);
756 /* 2. Teach kernel new timezone */
758 hwclock_apply_localtime_delta(NULL);
760 hwclock_reset_localtime_delta();
762 /* 3. Synchronize clocks */
763 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
768 /* Sync system clock from RTC; first,
769 * initialize the timezone fields of
772 tm = *localtime(&ts.tv_sec);
774 tm = *gmtime(&ts.tv_sec);
776 /* Override the main fields of
777 * struct tm, but not the timezone
779 if (hwclock_get_time(&tm) >= 0) {
781 /* And set the system clock
784 ts.tv_sec = mktime(&tm);
786 ts.tv_sec = timegm(&tm);
788 clock_settime(CLOCK_REALTIME, &ts);
794 /* Sync RTC from system clock */
796 tm = localtime(&ts.tv_sec);
798 tm = gmtime(&ts.tv_sec);
800 hwclock_set_time(tm);
803 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
805 changed = bus_properties_changed_new(
806 "/org/freedesktop/timedate1",
807 "org.freedesktop.timedate1",
813 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
815 dbus_bool_t relative;
816 dbus_bool_t interactive;
818 if (!dbus_message_get_args(
821 DBUS_TYPE_INT64, &utc,
822 DBUS_TYPE_BOOLEAN, &relative,
823 DBUS_TYPE_BOOLEAN, &interactive,
825 return bus_send_error_reply(connection, message, &error, -EINVAL);
827 if (!relative && utc <= 0)
828 return bus_send_error_reply(connection, message, NULL, -EINVAL);
830 if (!relative || utc != 0) {
834 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
836 return bus_send_error_reply(connection, message, &error, r);
839 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
841 timespec_store(&ts, utc);
843 /* Set system clock */
844 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
845 log_error("Failed to set local time: %m");
846 return bus_send_error_reply(connection, message, NULL, -errno);
849 /* Sync down to RTC */
851 tm = localtime(&ts.tv_sec);
853 tm = gmtime(&ts.tv_sec);
855 hwclock_set_time(tm);
858 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE),
859 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
860 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
863 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
865 dbus_bool_t interactive;
867 if (!dbus_message_get_args(
870 DBUS_TYPE_BOOLEAN, &ntp,
871 DBUS_TYPE_BOOLEAN, &interactive,
873 return bus_send_error_reply(connection, message, &error, -EINVAL);
875 if (ntp != !!tz.use_ntp) {
877 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
879 return bus_send_error_reply(connection, message, &error, r);
883 r = enable_ntp(connection, &error);
885 return bus_send_error_reply(connection, message, &error, r);
887 r = start_ntp(connection, &error);
889 return bus_send_error_reply(connection, message, &error, r);
891 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
893 changed = bus_properties_changed_new(
894 "/org/freedesktop/timedate1",
895 "org.freedesktop.timedate1",
902 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
904 if (!(reply = dbus_message_new_method_return(message)))
907 if (!dbus_connection_send(connection, reply, NULL))
910 dbus_message_unref(reply);
915 if (!dbus_connection_send(connection, changed, NULL))
918 dbus_message_unref(changed);
921 return DBUS_HANDLER_RESULT_HANDLED;
925 dbus_message_unref(reply);
928 dbus_message_unref(changed);
930 dbus_error_free(&error);
932 return DBUS_HANDLER_RESULT_NEED_MEMORY;
935 static int connect_bus(DBusConnection **_bus) {
936 static const DBusObjectPathVTable timedate_vtable = {
937 .message_function = timedate_message_handler
940 DBusConnection *bus = NULL;
945 dbus_error_init(&error);
947 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
949 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
954 dbus_connection_set_exit_on_disconnect(bus, FALSE);
956 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
957 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
962 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
963 if (dbus_error_is_set(&error)) {
964 log_error("Failed to register name on bus: %s", bus_error_message(&error));
969 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
970 log_error("Failed to acquire name.");
981 dbus_connection_close(bus);
982 dbus_connection_unref(bus);
984 dbus_error_free(&error);
989 int main(int argc, char *argv[]) {
991 DBusConnection *bus = NULL;
992 bool exiting = false;
994 log_set_target(LOG_TARGET_AUTO);
995 log_parse_environment();
1000 if (argc == 2 && streq(argv[1], "--introspect")) {
1001 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1002 "<node>\n", stdout);
1003 fputs(timedate_interface, stdout);
1004 fputs("</node>\n", stdout);
1009 log_error("This program takes no arguments.");
1016 log_error("Failed to read timezone data: %s", strerror(-r));
1020 r = connect_bus(&bus);
1026 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1030 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1033 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1036 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1038 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1048 dbus_connection_flush(bus);
1049 dbus_connection_close(bus);
1050 dbus_connection_unref(bus);
1053 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;