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=\"NTP\" type=\"b\" access=\"read\"/>\n" \
49 " <method name=\"SetTime\">\n" \
50 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
51 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
52 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
54 " <method name=\"SetTimezone\">\n" \
55 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
58 " <method name=\"SetLocalRTC\">\n" \
59 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
60 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
61 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
63 " <method name=\"SetNTP\">\n" \
64 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
65 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
69 #define INTROSPECTION \
70 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
73 BUS_PROPERTIES_INTERFACE \
74 BUS_INTROSPECTABLE_INTERFACE \
78 #define INTERFACES_LIST \
79 BUS_GENERIC_INTERFACES_LIST \
80 "org.freedesktop.timedate1\0"
82 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
94 static usec_t remain_until;
96 static void free_data(void) {
100 tz.local_rtc = false;
103 static bool valid_timezone(const char *name) {
112 if (*name == '/' || *name == 0)
115 for (p = name; *p; p++) {
116 if (!(*p >= '0' && *p <= '9') &&
117 !(*p >= 'a' && *p <= 'z') &&
118 !(*p >= 'A' && *p <= 'Z') &&
119 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
135 t = strappend("/usr/share/zoneinfo/", name);
145 if (!S_ISREG(st.st_mode))
151 static int read_data(void) {
153 _cleanup_free_ char *t = NULL;
157 r = readlink_malloc("/etc/localtime", &t);
160 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
162 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
166 e = path_startswith(t, "/usr/share/zoneinfo/");
168 e = path_startswith(t, "../usr/share/zoneinfo/");
171 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
182 if (isempty(tz.zone)) {
187 tz.local_rtc = hwclock_is_localtime() > 0;
192 static int write_data_timezone(void) {
194 _cleanup_free_ char *p = NULL;
197 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
203 p = strappend("../usr/share/zoneinfo/", tz.zone);
207 r = symlink_atomic(p, "/etc/localtime");
214 static int write_data_local_rtc(void) {
218 r = read_full_file("/etc/adjtime", &s, NULL);
226 w = strdup(NULL_ADJTIME_LOCAL);
239 p = strchr(p+1, '\n');
255 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
261 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
263 if (streq(w, NULL_ADJTIME_UTC)) {
266 if (unlink("/etc/adjtime") < 0) {
275 r = write_one_line_file_atomic_label("/etc/adjtime", w);
281 static char** get_ntp_services(void) {
282 char **r = NULL, **files, **i;
285 k = conf_files_list(&files, ".list", NULL,
286 "/etc/systemd/ntp-units.d",
287 "/run/systemd/ntp-units.d",
288 "/usr/local/lib/systemd/ntp-units.d",
289 "/usr/lib/systemd/ntp-units.d",
294 STRV_FOREACH(i, files) {
302 char line[PATH_MAX], *l, **q;
304 if (!fgets(line, sizeof(line), f)) {
307 log_error("Failed to read NTP units file: %m");
313 if (l[0] == 0 || l[0] == '#')
316 q = strv_append(r, l);
334 static int read_ntp(DBusConnection *bus) {
335 DBusMessage *m = NULL, *reply = NULL;
342 dbus_error_init(&error);
344 l = get_ntp_services();
349 dbus_message_unref(m);
350 m = dbus_message_new_method_call(
351 "org.freedesktop.systemd1",
352 "/org/freedesktop/systemd1",
353 "org.freedesktop.systemd1.Manager",
360 if (!dbus_message_append_args(m,
362 DBUS_TYPE_INVALID)) {
368 dbus_message_unref(reply);
369 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
371 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
372 /* This implementation does not exist, try next one */
373 dbus_error_free(&error);
377 log_error("Failed to issue method call: %s", bus_error_message(&error));
382 if (!dbus_message_get_args(reply, &error,
383 DBUS_TYPE_STRING, &s,
384 DBUS_TYPE_INVALID)) {
385 log_error("Failed to parse reply: %s", bus_error_message(&error));
391 streq(s, "enabled") ||
392 streq(s, "enabled-runtime");
397 /* NTP is not installed. */
403 dbus_message_unref(m);
406 dbus_message_unref(reply);
410 dbus_error_free(&error);
415 static int start_ntp(DBusConnection *bus, DBusError *error) {
416 DBusMessage *m = NULL, *reply = NULL;
417 const char *mode = "replace";
424 l = get_ntp_services();
427 dbus_message_unref(m);
428 m = dbus_message_new_method_call(
429 "org.freedesktop.systemd1",
430 "/org/freedesktop/systemd1",
431 "org.freedesktop.systemd1.Manager",
432 tz.use_ntp ? "StartUnit" : "StopUnit");
434 log_error("Could not allocate message.");
439 if (!dbus_message_append_args(m,
441 DBUS_TYPE_STRING, &mode,
442 DBUS_TYPE_INVALID)) {
443 log_error("Could not append arguments to message.");
449 dbus_message_unref(reply);
450 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
452 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
453 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
454 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
455 /* This implementation does not exist, try next one */
456 dbus_error_free(error);
460 log_error("Failed to issue method call: %s", bus_error_message(error));
469 /* No implementaiton available... */
474 dbus_message_unref(m);
477 dbus_message_unref(reply);
484 static int enable_ntp(DBusConnection *bus, DBusError *error) {
485 DBusMessage *m = NULL, *reply = NULL;
487 DBusMessageIter iter;
488 dbus_bool_t f = FALSE, t = TRUE;
494 l = get_ntp_services();
499 dbus_message_unref(m);
500 m = dbus_message_new_method_call(
501 "org.freedesktop.systemd1",
502 "/org/freedesktop/systemd1",
503 "org.freedesktop.systemd1.Manager",
504 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
506 log_error("Could not allocate message.");
511 dbus_message_iter_init_append(m, &iter);
516 r = bus_append_strv_iter(&iter, k);
518 log_error("Failed to append unit files.");
522 /* send runtime bool */
523 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
524 log_error("Failed to append runtime boolean.");
530 /* send force bool */
531 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
532 log_error("Failed to append force boolean.");
539 dbus_message_unref(reply);
540 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
542 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
543 /* This implementation does not exist, try next one */
544 dbus_error_free(error);
548 log_error("Failed to issue method call: %s", bus_error_message(error));
553 dbus_message_unref(m);
554 m = dbus_message_new_method_call(
555 "org.freedesktop.systemd1",
556 "/org/freedesktop/systemd1",
557 "org.freedesktop.systemd1.Manager",
560 log_error("Could not allocate message.");
565 dbus_message_unref(reply);
566 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
568 log_error("Failed to issue method call: %s", bus_error_message(error));
581 dbus_message_unref(m);
584 dbus_message_unref(reply);
591 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
599 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
605 static const BusProperty bus_timedate_properties[] = {
606 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
607 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
608 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
612 static const BusBoundProperties bps[] = {
613 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
617 static DBusHandlerResult timedate_message_handler(
618 DBusConnection *connection,
619 DBusMessage *message,
622 DBusMessage *reply = NULL, *changed = NULL;
629 dbus_error_init(&error);
631 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
633 dbus_bool_t interactive;
635 if (!dbus_message_get_args(
638 DBUS_TYPE_STRING, &z,
639 DBUS_TYPE_BOOLEAN, &interactive,
641 return bus_send_error_reply(connection, message, &error, -EINVAL);
643 if (!valid_timezone(z))
644 return bus_send_error_reply(connection, message, NULL, -EINVAL);
646 if (!streq_ptr(z, tz.zone)) {
649 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
651 return bus_send_error_reply(connection, message, &error, r);
660 /* 1. Write new configuration file */
661 r = write_data_timezone();
663 log_error("Failed to set timezone: %s", strerror(-r));
664 return bus_send_error_reply(connection, message, NULL, r);
667 /* 2. Tell the kernel our time zone */
668 hwclock_set_timezone(NULL);
674 /* 3. Sync RTC from system clock, with the new delta */
675 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
676 assert_se(tm = localtime(&ts.tv_sec));
677 hwclock_set_time(tm);
681 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
682 "TIMEZONE=%s", tz.zone,
683 "MESSAGE=Changed timezone to '%s'.", tz.zone,
686 changed = bus_properties_changed_new(
687 "/org/freedesktop/timedate1",
688 "org.freedesktop.timedate1",
694 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
696 dbus_bool_t fix_system;
697 dbus_bool_t interactive;
699 if (!dbus_message_get_args(
702 DBUS_TYPE_BOOLEAN, &lrtc,
703 DBUS_TYPE_BOOLEAN, &fix_system,
704 DBUS_TYPE_BOOLEAN, &interactive,
706 return bus_send_error_reply(connection, message, &error, -EINVAL);
708 if (lrtc != tz.local_rtc) {
711 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
713 return bus_send_error_reply(connection, message, &error, r);
717 /* 1. Write new configuration file */
718 r = write_data_local_rtc();
720 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
721 return bus_send_error_reply(connection, message, NULL, r);
724 /* 2. Tell the kernel our time zone */
725 hwclock_set_timezone(NULL);
727 /* 3. Synchronize clocks */
728 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
733 /* Sync system clock from RTC; first,
734 * initialize the timezone fields of
737 tm = *localtime(&ts.tv_sec);
739 tm = *gmtime(&ts.tv_sec);
741 /* Override the main fields of
742 * struct tm, but not the timezone
744 if (hwclock_get_time(&tm) >= 0) {
746 /* And set the system clock
749 ts.tv_sec = mktime(&tm);
751 ts.tv_sec = timegm(&tm);
753 clock_settime(CLOCK_REALTIME, &ts);
759 /* Sync RTC from system clock */
761 tm = localtime(&ts.tv_sec);
763 tm = gmtime(&ts.tv_sec);
765 hwclock_set_time(tm);
768 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
770 changed = bus_properties_changed_new(
771 "/org/freedesktop/timedate1",
772 "org.freedesktop.timedate1",
778 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
780 dbus_bool_t relative;
781 dbus_bool_t interactive;
783 if (!dbus_message_get_args(
786 DBUS_TYPE_INT64, &utc,
787 DBUS_TYPE_BOOLEAN, &relative,
788 DBUS_TYPE_BOOLEAN, &interactive,
790 return bus_send_error_reply(connection, message, &error, -EINVAL);
792 if (!relative && utc <= 0)
793 return bus_send_error_reply(connection, message, NULL, -EINVAL);
795 if (!relative || utc != 0) {
799 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
801 return bus_send_error_reply(connection, message, &error, r);
804 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
806 timespec_store(&ts, utc);
808 /* Set system clock */
809 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
810 log_error("Failed to set local time: %m");
811 return bus_send_error_reply(connection, message, NULL, -errno);
814 /* Sync down to RTC */
816 tm = localtime(&ts.tv_sec);
818 tm = gmtime(&ts.tv_sec);
820 hwclock_set_time(tm);
823 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
824 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
825 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
828 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
830 dbus_bool_t interactive;
832 if (!dbus_message_get_args(
835 DBUS_TYPE_BOOLEAN, &ntp,
836 DBUS_TYPE_BOOLEAN, &interactive,
838 return bus_send_error_reply(connection, message, &error, -EINVAL);
840 if (ntp != !!tz.use_ntp) {
842 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
844 return bus_send_error_reply(connection, message, &error, r);
848 r = enable_ntp(connection, &error);
850 return bus_send_error_reply(connection, message, &error, r);
852 r = start_ntp(connection, &error);
854 return bus_send_error_reply(connection, message, &error, r);
856 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
858 changed = bus_properties_changed_new(
859 "/org/freedesktop/timedate1",
860 "org.freedesktop.timedate1",
867 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
869 if (!(reply = dbus_message_new_method_return(message)))
872 if (!dbus_connection_send(connection, reply, NULL))
875 dbus_message_unref(reply);
880 if (!dbus_connection_send(connection, changed, NULL))
883 dbus_message_unref(changed);
886 return DBUS_HANDLER_RESULT_HANDLED;
890 dbus_message_unref(reply);
893 dbus_message_unref(changed);
895 dbus_error_free(&error);
897 return DBUS_HANDLER_RESULT_NEED_MEMORY;
900 static int connect_bus(DBusConnection **_bus) {
901 static const DBusObjectPathVTable timedate_vtable = {
902 .message_function = timedate_message_handler
905 DBusConnection *bus = NULL;
910 dbus_error_init(&error);
912 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
914 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
919 dbus_connection_set_exit_on_disconnect(bus, FALSE);
921 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
922 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
927 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
928 if (dbus_error_is_set(&error)) {
929 log_error("Failed to register name on bus: %s", bus_error_message(&error));
934 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
935 log_error("Failed to acquire name.");
946 dbus_connection_close(bus);
947 dbus_connection_unref(bus);
949 dbus_error_free(&error);
954 int main(int argc, char *argv[]) {
956 DBusConnection *bus = NULL;
957 bool exiting = false;
959 log_set_target(LOG_TARGET_AUTO);
960 log_parse_environment();
965 if (argc == 2 && streq(argv[1], "--introspect")) {
966 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
968 fputs(timedate_interface, stdout);
969 fputs("</node>\n", stdout);
974 log_error("This program takes no arguments.");
981 log_error("Failed to read timezone data: %s", strerror(-r));
985 r = connect_bus(&bus);
991 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
995 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
998 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1001 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1003 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1013 dbus_connection_flush(bus);
1014 dbus_connection_close(bus);
1015 dbus_connection_unref(bus);
1018 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;