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);
699 /* 2. Tell the kernel our time zone */
700 hwclock_set_timezone(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. Tell the kernel our time zone */
757 hwclock_set_timezone(NULL);
759 /* 3. Synchronize clocks */
760 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
765 /* Sync system clock from RTC; first,
766 * initialize the timezone fields of
769 tm = *localtime(&ts.tv_sec);
771 tm = *gmtime(&ts.tv_sec);
773 /* Override the main fields of
774 * struct tm, but not the timezone
776 if (hwclock_get_time(&tm) >= 0) {
778 /* And set the system clock
781 ts.tv_sec = mktime(&tm);
783 ts.tv_sec = timegm(&tm);
785 clock_settime(CLOCK_REALTIME, &ts);
791 /* Sync RTC from system clock */
793 tm = localtime(&ts.tv_sec);
795 tm = gmtime(&ts.tv_sec);
797 hwclock_set_time(tm);
800 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
802 changed = bus_properties_changed_new(
803 "/org/freedesktop/timedate1",
804 "org.freedesktop.timedate1",
810 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
812 dbus_bool_t relative;
813 dbus_bool_t interactive;
815 if (!dbus_message_get_args(
818 DBUS_TYPE_INT64, &utc,
819 DBUS_TYPE_BOOLEAN, &relative,
820 DBUS_TYPE_BOOLEAN, &interactive,
822 return bus_send_error_reply(connection, message, &error, -EINVAL);
824 if (!relative && utc <= 0)
825 return bus_send_error_reply(connection, message, NULL, -EINVAL);
827 if (!relative || utc != 0) {
831 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
833 return bus_send_error_reply(connection, message, &error, r);
836 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
838 timespec_store(&ts, utc);
840 /* Set system clock */
841 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
842 log_error("Failed to set local time: %m");
843 return bus_send_error_reply(connection, message, NULL, -errno);
846 /* Sync down to RTC */
848 tm = localtime(&ts.tv_sec);
850 tm = gmtime(&ts.tv_sec);
852 hwclock_set_time(tm);
855 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE),
856 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
857 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
860 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
862 dbus_bool_t interactive;
864 if (!dbus_message_get_args(
867 DBUS_TYPE_BOOLEAN, &ntp,
868 DBUS_TYPE_BOOLEAN, &interactive,
870 return bus_send_error_reply(connection, message, &error, -EINVAL);
872 if (ntp != !!tz.use_ntp) {
874 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
876 return bus_send_error_reply(connection, message, &error, r);
880 r = enable_ntp(connection, &error);
882 return bus_send_error_reply(connection, message, &error, r);
884 r = start_ntp(connection, &error);
886 return bus_send_error_reply(connection, message, &error, r);
888 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
890 changed = bus_properties_changed_new(
891 "/org/freedesktop/timedate1",
892 "org.freedesktop.timedate1",
899 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
901 if (!(reply = dbus_message_new_method_return(message)))
904 if (!dbus_connection_send(connection, reply, NULL))
907 dbus_message_unref(reply);
912 if (!dbus_connection_send(connection, changed, NULL))
915 dbus_message_unref(changed);
918 return DBUS_HANDLER_RESULT_HANDLED;
922 dbus_message_unref(reply);
925 dbus_message_unref(changed);
927 dbus_error_free(&error);
929 return DBUS_HANDLER_RESULT_NEED_MEMORY;
932 static int connect_bus(DBusConnection **_bus) {
933 static const DBusObjectPathVTable timedate_vtable = {
934 .message_function = timedate_message_handler
937 DBusConnection *bus = NULL;
942 dbus_error_init(&error);
944 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
946 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
951 dbus_connection_set_exit_on_disconnect(bus, FALSE);
953 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
954 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
959 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
960 if (dbus_error_is_set(&error)) {
961 log_error("Failed to register name on bus: %s", bus_error_message(&error));
966 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
967 log_error("Failed to acquire name.");
978 dbus_connection_close(bus);
979 dbus_connection_unref(bus);
981 dbus_error_free(&error);
986 int main(int argc, char *argv[]) {
988 DBusConnection *bus = NULL;
989 bool exiting = false;
991 log_set_target(LOG_TARGET_AUTO);
992 log_parse_environment();
997 if (argc == 2 && streq(argv[1], "--introspect")) {
998 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1000 fputs(timedate_interface, stdout);
1001 fputs("</node>\n", stdout);
1006 log_error("This program takes no arguments.");
1013 log_error("Failed to read timezone data: %s", strerror(-r));
1017 r = connect_bus(&bus);
1023 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1027 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1030 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1033 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1035 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1045 dbus_connection_flush(bus);
1046 dbus_connection_close(bus);
1047 dbus_connection_unref(bus);
1050 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;