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;
99 static usec_t remain_until;
101 static void free_data(void) {
105 tz.local_rtc = false;
108 static bool valid_timezone(const char *name) {
117 if (*name == '/' || *name == 0)
120 for (p = name; *p; p++) {
121 if (!(*p >= '0' && *p <= '9') &&
122 !(*p >= 'a' && *p <= 'z') &&
123 !(*p >= 'A' && *p <= 'Z') &&
124 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
140 t = strappend("/usr/share/zoneinfo/", name);
150 if (!S_ISREG(st.st_mode))
156 static int read_data(void) {
158 _cleanup_free_ char *t = NULL;
162 r = readlink_malloc("/etc/localtime", &t);
165 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
167 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
171 e = path_startswith(t, "/usr/share/zoneinfo/");
173 e = path_startswith(t, "../usr/share/zoneinfo/");
176 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
187 if (isempty(tz.zone)) {
192 tz.local_rtc = hwclock_is_localtime() > 0;
197 static int write_data_timezone(void) {
199 _cleanup_free_ char *p = NULL;
202 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
208 p = strappend("../usr/share/zoneinfo/", tz.zone);
212 r = symlink_atomic(p, "/etc/localtime");
219 static int write_data_local_rtc(void) {
221 _cleanup_free_ char *s = NULL, *w = NULL;
223 r = read_full_file("/etc/adjtime", &s, NULL);
231 w = strdup(NULL_ADJTIME_LOCAL);
242 p = strchr(p+1, '\n');
254 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
258 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
260 if (streq(w, NULL_ADJTIME_UTC)) {
261 if (unlink("/etc/adjtime") < 0)
269 return write_string_file_atomic_label("/etc/adjtime", w);
272 static char** get_ntp_services(void) {
273 _cleanup_strv_free_ char **r = NULL, **files;
277 k = conf_files_list(&files, ".list", NULL,
278 "/etc/systemd/ntp-units.d",
279 "/run/systemd/ntp-units.d",
280 "/usr/local/lib/systemd/ntp-units.d",
281 "/usr/lib/systemd/ntp-units.d",
286 STRV_FOREACH(i, files) {
287 _cleanup_fclose_ FILE *f;
294 char line[PATH_MAX], *l;
296 if (!fgets(line, sizeof(line), f)) {
299 log_error("Failed to read NTP units file: %m");
305 if (l[0] == 0 || l[0] == '#')
308 if (strv_extend(&r, l) < 0) {
316 r = NULL; /* avoid cleanup */
321 static int read_ntp(DBusConnection *bus) {
322 DBusMessage *m = NULL, *reply = NULL;
329 dbus_error_init(&error);
331 l = get_ntp_services();
336 dbus_message_unref(m);
337 m = dbus_message_new_method_call(
338 "org.freedesktop.systemd1",
339 "/org/freedesktop/systemd1",
340 "org.freedesktop.systemd1.Manager",
347 if (!dbus_message_append_args(m,
349 DBUS_TYPE_INVALID)) {
354 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
356 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
357 /* This implementation does not exist, try next one */
358 dbus_error_free(&error);
362 log_error("Failed to issue method call: %s", bus_error_message(&error));
367 if (!dbus_message_get_args(reply, &error,
368 DBUS_TYPE_STRING, &s,
369 DBUS_TYPE_INVALID)) {
370 log_error("Failed to parse reply: %s", bus_error_message(&error));
377 streq(s, "enabled") ||
378 streq(s, "enabled-runtime");
383 /* NTP is not installed. */
390 dbus_message_unref(m);
393 dbus_message_unref(reply);
397 dbus_error_free(&error);
402 static int start_ntp(DBusConnection *bus, DBusError *error) {
403 DBusMessage *m = NULL, *reply = NULL;
404 const char *mode = "replace";
411 l = get_ntp_services();
414 dbus_message_unref(m);
415 m = dbus_message_new_method_call(
416 "org.freedesktop.systemd1",
417 "/org/freedesktop/systemd1",
418 "org.freedesktop.systemd1.Manager",
419 tz.use_ntp ? "StartUnit" : "StopUnit");
421 log_error("Could not allocate message.");
426 if (!dbus_message_append_args(m,
428 DBUS_TYPE_STRING, &mode,
429 DBUS_TYPE_INVALID)) {
430 log_error("Could not append arguments to message.");
435 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
437 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
438 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
439 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
440 /* This implementation does not exist, try next one */
441 dbus_error_free(error);
445 log_error("Failed to issue method call: %s", bus_error_message(error));
454 /* No implementaiton available... */
459 dbus_message_unref(m);
462 dbus_message_unref(reply);
469 static int enable_ntp(DBusConnection *bus, DBusError *error) {
470 DBusMessage *m = NULL, *reply = NULL;
472 DBusMessageIter iter;
473 dbus_bool_t f = FALSE, t = TRUE;
479 l = get_ntp_services();
484 dbus_message_unref(m);
485 m = dbus_message_new_method_call(
486 "org.freedesktop.systemd1",
487 "/org/freedesktop/systemd1",
488 "org.freedesktop.systemd1.Manager",
489 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
491 log_error("Could not allocate message.");
496 dbus_message_iter_init_append(m, &iter);
501 r = bus_append_strv_iter(&iter, k);
503 log_error("Failed to append unit files.");
507 /* send runtime bool */
508 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
509 log_error("Failed to append runtime boolean.");
515 /* send force bool */
516 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
517 log_error("Failed to append force boolean.");
523 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
525 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
526 /* This implementation does not exist, try next one */
527 dbus_error_free(error);
531 log_error("Failed to issue method call: %s", bus_error_message(error));
536 dbus_message_unref(m);
537 m = dbus_message_new_method_call(
538 "org.freedesktop.systemd1",
539 "/org/freedesktop/systemd1",
540 "org.freedesktop.systemd1.Manager",
543 log_error("Could not allocate message.");
548 dbus_message_unref(reply);
549 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
551 log_error("Failed to issue method call: %s", bus_error_message(error));
564 dbus_message_unref(m);
567 dbus_message_unref(reply);
574 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
582 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
588 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
596 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
602 static const BusProperty bus_timedate_properties[] = {
603 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
604 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
605 { "CanNTP", property_append_can_ntp, "b", offsetof(TZ, can_ntp) },
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 timezone */
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 timezone */
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) {
800 n = now(CLOCK_REALTIME);
803 if ((utc > 0 && x < n) ||
805 return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
807 timespec_store(&ts, x);
809 timespec_store(&ts, (usec_t) utc);
811 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
813 return bus_send_error_reply(connection, message, &error, r);
815 /* Set system clock */
816 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
817 log_error("Failed to set local time: %m");
818 return bus_send_error_reply(connection, message, NULL, -errno);
821 /* Sync down to RTC */
823 tm = localtime(&ts.tv_sec);
825 tm = gmtime(&ts.tv_sec);
827 hwclock_set_time(tm);
830 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
831 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
832 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
835 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
837 dbus_bool_t interactive;
839 if (!dbus_message_get_args(
842 DBUS_TYPE_BOOLEAN, &ntp,
843 DBUS_TYPE_BOOLEAN, &interactive,
845 return bus_send_error_reply(connection, message, &error, -EINVAL);
847 if (ntp != !!tz.use_ntp) {
849 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
851 return bus_send_error_reply(connection, message, &error, r);
855 r = enable_ntp(connection, &error);
857 return bus_send_error_reply(connection, message, &error, r);
859 r = start_ntp(connection, &error);
861 return bus_send_error_reply(connection, message, &error, r);
863 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
865 changed = bus_properties_changed_new(
866 "/org/freedesktop/timedate1",
867 "org.freedesktop.timedate1",
874 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
876 if (!(reply = dbus_message_new_method_return(message)))
879 if (!bus_maybe_send_reply(connection, message, reply))
882 dbus_message_unref(reply);
887 if (!dbus_connection_send(connection, changed, NULL))
890 dbus_message_unref(changed);
893 return DBUS_HANDLER_RESULT_HANDLED;
897 dbus_message_unref(reply);
900 dbus_message_unref(changed);
902 dbus_error_free(&error);
904 return DBUS_HANDLER_RESULT_NEED_MEMORY;
907 static int connect_bus(DBusConnection **_bus) {
908 static const DBusObjectPathVTable timedate_vtable = {
909 .message_function = timedate_message_handler
912 DBusConnection *bus = NULL;
917 dbus_error_init(&error);
919 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
921 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
926 dbus_connection_set_exit_on_disconnect(bus, FALSE);
928 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
929 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
934 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
935 if (dbus_error_is_set(&error)) {
936 log_error("Failed to register name on bus: %s", bus_error_message(&error));
941 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
942 log_error("Failed to acquire name.");
953 dbus_connection_close(bus);
954 dbus_connection_unref(bus);
956 dbus_error_free(&error);
961 int main(int argc, char *argv[]) {
963 DBusConnection *bus = NULL;
964 bool exiting = false;
966 log_set_target(LOG_TARGET_AUTO);
967 log_parse_environment();
972 if (argc == 2 && streq(argv[1], "--introspect")) {
973 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
975 fputs(timedate_interface, stdout);
976 fputs("</node>\n", stdout);
981 log_error("This program takes no arguments.");
988 log_error("Failed to read timezone data: %s", strerror(-r));
992 r = connect_bus(&bus);
998 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1002 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1005 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1008 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1010 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1020 dbus_connection_flush(bus);
1021 dbus_connection_close(bus);
1022 dbus_connection_unref(bus);
1025 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;