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 = read_one_line_file("/etc/timezone", &tz.zone);
183 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
188 if (isempty(tz.zone)) {
193 tz.local_rtc = hwclock_is_localtime() > 0;
198 static int write_data_timezone(void) {
200 _cleanup_free_ char *p = NULL;
203 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
209 p = strappend("../usr/share/zoneinfo/", tz.zone);
213 r = symlink_atomic(p, "/etc/localtime");
220 static int write_data_local_rtc(void) {
224 r = read_full_file("/etc/adjtime", &s, NULL);
232 w = strdup(NULL_ADJTIME_LOCAL);
245 p = strchr(p+1, '\n');
261 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
267 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
269 if (streq(w, NULL_ADJTIME_UTC)) {
272 if (unlink("/etc/adjtime") < 0) {
281 r = write_one_line_file_atomic("/etc/adjtime", w);
287 static char** get_ntp_services(void) {
288 char **r = NULL, **files, **i;
291 k = conf_files_list(&files, ".list",
292 "/etc/systemd/ntp-units.d",
293 "/run/systemd/ntp-units.d",
294 "/usr/local/lib/systemd/ntp-units.d",
295 "/usr/lib/systemd/ntp-units.d",
300 STRV_FOREACH(i, files) {
308 char line[PATH_MAX], *l, **q;
310 if (!fgets(line, sizeof(line), f)) {
313 log_error("Failed to read NTP units file: %m");
319 if (l[0] == 0 || l[0] == '#')
322 q = strv_append(r, l);
340 static int read_ntp(DBusConnection *bus) {
341 DBusMessage *m = NULL, *reply = NULL;
348 dbus_error_init(&error);
350 l = get_ntp_services();
355 dbus_message_unref(m);
356 m = dbus_message_new_method_call(
357 "org.freedesktop.systemd1",
358 "/org/freedesktop/systemd1",
359 "org.freedesktop.systemd1.Manager",
366 if (!dbus_message_append_args(m,
368 DBUS_TYPE_INVALID)) {
374 dbus_message_unref(reply);
375 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
377 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
378 /* This implementation does not exist, try next one */
379 dbus_error_free(&error);
383 log_error("Failed to issue method call: %s", bus_error_message(&error));
388 if (!dbus_message_get_args(reply, &error,
389 DBUS_TYPE_STRING, &s,
390 DBUS_TYPE_INVALID)) {
391 log_error("Failed to parse reply: %s", bus_error_message(&error));
397 streq(s, "enabled") ||
398 streq(s, "enabled-runtime");
403 /* NTP is not installed. */
409 dbus_message_unref(m);
412 dbus_message_unref(reply);
416 dbus_error_free(&error);
421 static int start_ntp(DBusConnection *bus, DBusError *error) {
422 DBusMessage *m = NULL, *reply = NULL;
423 const char *mode = "replace";
430 l = get_ntp_services();
433 dbus_message_unref(m);
434 m = dbus_message_new_method_call(
435 "org.freedesktop.systemd1",
436 "/org/freedesktop/systemd1",
437 "org.freedesktop.systemd1.Manager",
438 tz.use_ntp ? "StartUnit" : "StopUnit");
440 log_error("Could not allocate message.");
445 if (!dbus_message_append_args(m,
447 DBUS_TYPE_STRING, &mode,
448 DBUS_TYPE_INVALID)) {
449 log_error("Could not append arguments to message.");
455 dbus_message_unref(reply);
456 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
458 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
459 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
460 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
461 /* This implementation does not exist, try next one */
462 dbus_error_free(error);
466 log_error("Failed to issue method call: %s", bus_error_message(error));
475 /* No implementaiton available... */
480 dbus_message_unref(m);
483 dbus_message_unref(reply);
490 static int enable_ntp(DBusConnection *bus, DBusError *error) {
491 DBusMessage *m = NULL, *reply = NULL;
493 DBusMessageIter iter;
494 dbus_bool_t f = FALSE, t = TRUE;
500 l = get_ntp_services();
505 dbus_message_unref(m);
506 m = dbus_message_new_method_call(
507 "org.freedesktop.systemd1",
508 "/org/freedesktop/systemd1",
509 "org.freedesktop.systemd1.Manager",
510 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
512 log_error("Could not allocate message.");
517 dbus_message_iter_init_append(m, &iter);
522 r = bus_append_strv_iter(&iter, k);
524 log_error("Failed to append unit files.");
528 /* send runtime bool */
529 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
530 log_error("Failed to append runtime boolean.");
536 /* send force bool */
537 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
538 log_error("Failed to append force boolean.");
545 dbus_message_unref(reply);
546 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
548 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
549 /* This implementation does not exist, try next one */
550 dbus_error_free(error);
554 log_error("Failed to issue method call: %s", bus_error_message(error));
559 dbus_message_unref(m);
560 m = dbus_message_new_method_call(
561 "org.freedesktop.systemd1",
562 "/org/freedesktop/systemd1",
563 "org.freedesktop.systemd1.Manager",
566 log_error("Could not allocate message.");
571 dbus_message_unref(reply);
572 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
574 log_error("Failed to issue method call: %s", bus_error_message(error));
587 dbus_message_unref(m);
590 dbus_message_unref(reply);
597 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
605 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
611 static const BusProperty bus_timedate_properties[] = {
612 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
613 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
614 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
618 static const BusBoundProperties bps[] = {
619 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
623 static DBusHandlerResult timedate_message_handler(
624 DBusConnection *connection,
625 DBusMessage *message,
628 DBusMessage *reply = NULL, *changed = NULL;
635 dbus_error_init(&error);
637 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
639 dbus_bool_t interactive;
641 if (!dbus_message_get_args(
644 DBUS_TYPE_STRING, &z,
645 DBUS_TYPE_BOOLEAN, &interactive,
647 return bus_send_error_reply(connection, message, &error, -EINVAL);
649 if (!valid_timezone(z))
650 return bus_send_error_reply(connection, message, NULL, -EINVAL);
652 if (!streq_ptr(z, tz.zone)) {
655 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
657 return bus_send_error_reply(connection, message, &error, r);
666 /* 1. Write new configuration file */
667 r = write_data_timezone();
669 log_error("Failed to set timezone: %s", strerror(-r));
670 return bus_send_error_reply(connection, message, NULL, r);
673 /* 2. Tell the kernel our time zone */
674 hwclock_set_timezone(NULL);
680 /* 3. Sync RTC from system clock, with the new delta */
681 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
682 assert_se(tm = localtime(&ts.tv_sec));
683 hwclock_set_time(tm);
687 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
688 "TIMEZONE=%s", tz.zone,
689 "MESSAGE=Changed timezone to '%s'.", tz.zone,
692 changed = bus_properties_changed_new(
693 "/org/freedesktop/timedate1",
694 "org.freedesktop.timedate1",
700 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
702 dbus_bool_t fix_system;
703 dbus_bool_t interactive;
705 if (!dbus_message_get_args(
708 DBUS_TYPE_BOOLEAN, &lrtc,
709 DBUS_TYPE_BOOLEAN, &fix_system,
710 DBUS_TYPE_BOOLEAN, &interactive,
712 return bus_send_error_reply(connection, message, &error, -EINVAL);
714 if (lrtc != tz.local_rtc) {
717 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
719 return bus_send_error_reply(connection, message, &error, r);
723 /* 1. Write new configuration file */
724 r = write_data_local_rtc();
726 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
727 return bus_send_error_reply(connection, message, NULL, r);
730 /* 2. Tell the kernel our time zone */
731 hwclock_set_timezone(NULL);
733 /* 3. Synchronize clocks */
734 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
739 /* Sync system clock from RTC; first,
740 * initialize the timezone fields of
743 tm = *localtime(&ts.tv_sec);
745 tm = *gmtime(&ts.tv_sec);
747 /* Override the main fields of
748 * struct tm, but not the timezone
750 if (hwclock_get_time(&tm) >= 0) {
752 /* And set the system clock
755 ts.tv_sec = mktime(&tm);
757 ts.tv_sec = timegm(&tm);
759 clock_settime(CLOCK_REALTIME, &ts);
765 /* Sync RTC from system clock */
767 tm = localtime(&ts.tv_sec);
769 tm = gmtime(&ts.tv_sec);
771 hwclock_set_time(tm);
774 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
776 changed = bus_properties_changed_new(
777 "/org/freedesktop/timedate1",
778 "org.freedesktop.timedate1",
784 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
786 dbus_bool_t relative;
787 dbus_bool_t interactive;
789 if (!dbus_message_get_args(
792 DBUS_TYPE_INT64, &utc,
793 DBUS_TYPE_BOOLEAN, &relative,
794 DBUS_TYPE_BOOLEAN, &interactive,
796 return bus_send_error_reply(connection, message, &error, -EINVAL);
798 if (!relative && utc <= 0)
799 return bus_send_error_reply(connection, message, NULL, -EINVAL);
801 if (!relative || utc != 0) {
805 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
807 return bus_send_error_reply(connection, message, &error, r);
810 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
812 timespec_store(&ts, utc);
814 /* Set system clock */
815 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
816 log_error("Failed to set local time: %m");
817 return bus_send_error_reply(connection, message, NULL, -errno);
820 /* Sync down to RTC */
822 tm = localtime(&ts.tv_sec);
824 tm = gmtime(&ts.tv_sec);
826 hwclock_set_time(tm);
829 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
830 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
831 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
834 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
836 dbus_bool_t interactive;
838 if (!dbus_message_get_args(
841 DBUS_TYPE_BOOLEAN, &ntp,
842 DBUS_TYPE_BOOLEAN, &interactive,
844 return bus_send_error_reply(connection, message, &error, -EINVAL);
846 if (ntp != !!tz.use_ntp) {
848 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
850 return bus_send_error_reply(connection, message, &error, r);
854 r = enable_ntp(connection, &error);
856 return bus_send_error_reply(connection, message, &error, r);
858 r = start_ntp(connection, &error);
860 return bus_send_error_reply(connection, message, &error, r);
862 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
864 changed = bus_properties_changed_new(
865 "/org/freedesktop/timedate1",
866 "org.freedesktop.timedate1",
873 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
875 if (!(reply = dbus_message_new_method_return(message)))
878 if (!dbus_connection_send(connection, reply, NULL))
881 dbus_message_unref(reply);
886 if (!dbus_connection_send(connection, changed, NULL))
889 dbus_message_unref(changed);
892 return DBUS_HANDLER_RESULT_HANDLED;
896 dbus_message_unref(reply);
899 dbus_message_unref(changed);
901 dbus_error_free(&error);
903 return DBUS_HANDLER_RESULT_NEED_MEMORY;
906 static int connect_bus(DBusConnection **_bus) {
907 static const DBusObjectPathVTable timedate_vtable = {
908 .message_function = timedate_message_handler
911 DBusConnection *bus = NULL;
916 dbus_error_init(&error);
918 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
920 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
925 dbus_connection_set_exit_on_disconnect(bus, FALSE);
927 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
928 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
933 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
934 if (dbus_error_is_set(&error)) {
935 log_error("Failed to register name on bus: %s", bus_error_message(&error));
940 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
941 log_error("Failed to acquire name.");
952 dbus_connection_close(bus);
953 dbus_connection_unref(bus);
955 dbus_error_free(&error);
960 int main(int argc, char *argv[]) {
962 DBusConnection *bus = NULL;
963 bool exiting = false;
965 log_set_target(LOG_TARGET_AUTO);
966 log_parse_environment();
971 if (argc == 2 && streq(argv[1], "--introspect")) {
972 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
974 fputs(timedate_interface, stdout);
975 fputs("</node>\n", stdout);
980 log_error("This program takes no arguments.");
987 log_error("Failed to read timezone data: %s", strerror(-r));
991 r = connect_bus(&bus);
997 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1001 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1004 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1007 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1009 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1019 dbus_connection_flush(bus);
1020 dbus_connection_close(bus);
1021 dbus_connection_unref(bus);
1024 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;