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 if (isempty(tz.zone)) {
185 tz.local_rtc = hwclock_is_localtime() > 0;
190 static int write_data_timezone(void) {
192 _cleanup_free_ char *p = NULL;
195 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
201 p = strappend("../usr/share/zoneinfo/", tz.zone);
205 r = symlink_atomic(p, "/etc/localtime");
212 static int write_data_local_rtc(void) {
216 r = read_full_file("/etc/adjtime", &s, NULL);
224 w = strdup(NULL_ADJTIME_LOCAL);
237 p = strchr(p+1, '\n');
253 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
259 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
261 if (streq(w, NULL_ADJTIME_UTC)) {
264 if (unlink("/etc/adjtime") < 0) {
273 r = write_one_line_file_atomic("/etc/adjtime", w);
279 static char** get_ntp_services(void) {
280 char **r = NULL, **files, **i;
283 k = conf_files_list(&files, ".list", NULL,
284 "/etc/systemd/ntp-units.d",
285 "/run/systemd/ntp-units.d",
286 "/usr/local/lib/systemd/ntp-units.d",
287 "/usr/lib/systemd/ntp-units.d",
292 STRV_FOREACH(i, files) {
300 char line[PATH_MAX], *l, **q;
302 if (!fgets(line, sizeof(line), f)) {
305 log_error("Failed to read NTP units file: %m");
311 if (l[0] == 0 || l[0] == '#')
314 q = strv_append(r, l);
332 static int read_ntp(DBusConnection *bus) {
333 DBusMessage *m = NULL, *reply = NULL;
340 dbus_error_init(&error);
342 l = get_ntp_services();
347 dbus_message_unref(m);
348 m = dbus_message_new_method_call(
349 "org.freedesktop.systemd1",
350 "/org/freedesktop/systemd1",
351 "org.freedesktop.systemd1.Manager",
358 if (!dbus_message_append_args(m,
360 DBUS_TYPE_INVALID)) {
366 dbus_message_unref(reply);
367 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
369 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
370 /* This implementation does not exist, try next one */
371 dbus_error_free(&error);
375 log_error("Failed to issue method call: %s", bus_error_message(&error));
380 if (!dbus_message_get_args(reply, &error,
381 DBUS_TYPE_STRING, &s,
382 DBUS_TYPE_INVALID)) {
383 log_error("Failed to parse reply: %s", bus_error_message(&error));
389 streq(s, "enabled") ||
390 streq(s, "enabled-runtime");
395 /* NTP is not installed. */
401 dbus_message_unref(m);
404 dbus_message_unref(reply);
408 dbus_error_free(&error);
413 static int start_ntp(DBusConnection *bus, DBusError *error) {
414 DBusMessage *m = NULL, *reply = NULL;
415 const char *mode = "replace";
422 l = get_ntp_services();
425 dbus_message_unref(m);
426 m = dbus_message_new_method_call(
427 "org.freedesktop.systemd1",
428 "/org/freedesktop/systemd1",
429 "org.freedesktop.systemd1.Manager",
430 tz.use_ntp ? "StartUnit" : "StopUnit");
432 log_error("Could not allocate message.");
437 if (!dbus_message_append_args(m,
439 DBUS_TYPE_STRING, &mode,
440 DBUS_TYPE_INVALID)) {
441 log_error("Could not append arguments to message.");
447 dbus_message_unref(reply);
448 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
450 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
451 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
452 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
453 /* This implementation does not exist, try next one */
454 dbus_error_free(error);
458 log_error("Failed to issue method call: %s", bus_error_message(error));
467 /* No implementaiton available... */
472 dbus_message_unref(m);
475 dbus_message_unref(reply);
482 static int enable_ntp(DBusConnection *bus, DBusError *error) {
483 DBusMessage *m = NULL, *reply = NULL;
485 DBusMessageIter iter;
486 dbus_bool_t f = FALSE, t = TRUE;
492 l = get_ntp_services();
497 dbus_message_unref(m);
498 m = dbus_message_new_method_call(
499 "org.freedesktop.systemd1",
500 "/org/freedesktop/systemd1",
501 "org.freedesktop.systemd1.Manager",
502 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
504 log_error("Could not allocate message.");
509 dbus_message_iter_init_append(m, &iter);
514 r = bus_append_strv_iter(&iter, k);
516 log_error("Failed to append unit files.");
520 /* send runtime bool */
521 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
522 log_error("Failed to append runtime boolean.");
528 /* send force bool */
529 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
530 log_error("Failed to append force boolean.");
537 dbus_message_unref(reply);
538 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
540 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
541 /* This implementation does not exist, try next one */
542 dbus_error_free(error);
546 log_error("Failed to issue method call: %s", bus_error_message(error));
551 dbus_message_unref(m);
552 m = dbus_message_new_method_call(
553 "org.freedesktop.systemd1",
554 "/org/freedesktop/systemd1",
555 "org.freedesktop.systemd1.Manager",
558 log_error("Could not allocate message.");
563 dbus_message_unref(reply);
564 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
566 log_error("Failed to issue method call: %s", bus_error_message(error));
579 dbus_message_unref(m);
582 dbus_message_unref(reply);
589 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
597 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
603 static const BusProperty bus_timedate_properties[] = {
604 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
605 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
606 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
610 static const BusBoundProperties bps[] = {
611 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
615 static DBusHandlerResult timedate_message_handler(
616 DBusConnection *connection,
617 DBusMessage *message,
620 DBusMessage *reply = NULL, *changed = NULL;
627 dbus_error_init(&error);
629 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
631 dbus_bool_t interactive;
633 if (!dbus_message_get_args(
636 DBUS_TYPE_STRING, &z,
637 DBUS_TYPE_BOOLEAN, &interactive,
639 return bus_send_error_reply(connection, message, &error, -EINVAL);
641 if (!valid_timezone(z))
642 return bus_send_error_reply(connection, message, NULL, -EINVAL);
644 if (!streq_ptr(z, tz.zone)) {
647 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
649 return bus_send_error_reply(connection, message, &error, r);
658 /* 1. Write new configuration file */
659 r = write_data_timezone();
661 log_error("Failed to set timezone: %s", strerror(-r));
662 return bus_send_error_reply(connection, message, NULL, r);
665 /* 2. Tell the kernel our time zone */
666 hwclock_set_timezone(NULL);
672 /* 3. Sync RTC from system clock, with the new delta */
673 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
674 assert_se(tm = localtime(&ts.tv_sec));
675 hwclock_set_time(tm);
679 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
680 "TIMEZONE=%s", tz.zone,
681 "MESSAGE=Changed timezone to '%s'.", tz.zone,
684 changed = bus_properties_changed_new(
685 "/org/freedesktop/timedate1",
686 "org.freedesktop.timedate1",
692 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
694 dbus_bool_t fix_system;
695 dbus_bool_t interactive;
697 if (!dbus_message_get_args(
700 DBUS_TYPE_BOOLEAN, &lrtc,
701 DBUS_TYPE_BOOLEAN, &fix_system,
702 DBUS_TYPE_BOOLEAN, &interactive,
704 return bus_send_error_reply(connection, message, &error, -EINVAL);
706 if (lrtc != tz.local_rtc) {
709 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
711 return bus_send_error_reply(connection, message, &error, r);
715 /* 1. Write new configuration file */
716 r = write_data_local_rtc();
718 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
719 return bus_send_error_reply(connection, message, NULL, r);
722 /* 2. Tell the kernel our time zone */
723 hwclock_set_timezone(NULL);
725 /* 3. Synchronize clocks */
726 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
731 /* Sync system clock from RTC; first,
732 * initialize the timezone fields of
735 tm = *localtime(&ts.tv_sec);
737 tm = *gmtime(&ts.tv_sec);
739 /* Override the main fields of
740 * struct tm, but not the timezone
742 if (hwclock_get_time(&tm) >= 0) {
744 /* And set the system clock
747 ts.tv_sec = mktime(&tm);
749 ts.tv_sec = timegm(&tm);
751 clock_settime(CLOCK_REALTIME, &ts);
757 /* Sync RTC from system clock */
759 tm = localtime(&ts.tv_sec);
761 tm = gmtime(&ts.tv_sec);
763 hwclock_set_time(tm);
766 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
768 changed = bus_properties_changed_new(
769 "/org/freedesktop/timedate1",
770 "org.freedesktop.timedate1",
776 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
778 dbus_bool_t relative;
779 dbus_bool_t interactive;
781 if (!dbus_message_get_args(
784 DBUS_TYPE_INT64, &utc,
785 DBUS_TYPE_BOOLEAN, &relative,
786 DBUS_TYPE_BOOLEAN, &interactive,
788 return bus_send_error_reply(connection, message, &error, -EINVAL);
790 if (!relative && utc <= 0)
791 return bus_send_error_reply(connection, message, NULL, -EINVAL);
793 if (!relative || utc != 0) {
797 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
799 return bus_send_error_reply(connection, message, &error, r);
802 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
804 timespec_store(&ts, utc);
806 /* Set system clock */
807 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
808 log_error("Failed to set local time: %m");
809 return bus_send_error_reply(connection, message, NULL, -errno);
812 /* Sync down to RTC */
814 tm = localtime(&ts.tv_sec);
816 tm = gmtime(&ts.tv_sec);
818 hwclock_set_time(tm);
821 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
822 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
823 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
826 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
828 dbus_bool_t interactive;
830 if (!dbus_message_get_args(
833 DBUS_TYPE_BOOLEAN, &ntp,
834 DBUS_TYPE_BOOLEAN, &interactive,
836 return bus_send_error_reply(connection, message, &error, -EINVAL);
838 if (ntp != !!tz.use_ntp) {
840 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
842 return bus_send_error_reply(connection, message, &error, r);
846 r = enable_ntp(connection, &error);
848 return bus_send_error_reply(connection, message, &error, r);
850 r = start_ntp(connection, &error);
852 return bus_send_error_reply(connection, message, &error, r);
854 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
856 changed = bus_properties_changed_new(
857 "/org/freedesktop/timedate1",
858 "org.freedesktop.timedate1",
865 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
867 if (!(reply = dbus_message_new_method_return(message)))
870 if (!dbus_connection_send(connection, reply, NULL))
873 dbus_message_unref(reply);
878 if (!dbus_connection_send(connection, changed, NULL))
881 dbus_message_unref(changed);
884 return DBUS_HANDLER_RESULT_HANDLED;
888 dbus_message_unref(reply);
891 dbus_message_unref(changed);
893 dbus_error_free(&error);
895 return DBUS_HANDLER_RESULT_NEED_MEMORY;
898 static int connect_bus(DBusConnection **_bus) {
899 static const DBusObjectPathVTable timedate_vtable = {
900 .message_function = timedate_message_handler
903 DBusConnection *bus = NULL;
908 dbus_error_init(&error);
910 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
912 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
917 dbus_connection_set_exit_on_disconnect(bus, FALSE);
919 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
920 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
925 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
926 if (dbus_error_is_set(&error)) {
927 log_error("Failed to register name on bus: %s", bus_error_message(&error));
932 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
933 log_error("Failed to acquire name.");
944 dbus_connection_close(bus);
945 dbus_connection_unref(bus);
947 dbus_error_free(&error);
952 int main(int argc, char *argv[]) {
954 DBusConnection *bus = NULL;
955 bool exiting = false;
957 log_set_target(LOG_TARGET_AUTO);
958 log_parse_environment();
963 if (argc == 2 && streq(argv[1], "--introspect")) {
964 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
966 fputs(timedate_interface, stdout);
967 fputs("</node>\n", stdout);
972 log_error("This program takes no arguments.");
979 log_error("Failed to read timezone data: %s", strerror(-r));
983 r = connect_bus(&bus);
989 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
993 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
996 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
999 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1001 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1011 dbus_connection_flush(bus);
1012 dbus_connection_close(bus);
1013 dbus_connection_unref(bus);
1016 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;