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;
207 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
211 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
218 p = strappend("../usr/share/zoneinfo/", tz.zone);
222 r = symlink_atomic(p, "/etc/localtime");
227 if (stat("/etc/timezone", &st) == 0 && S_ISREG(st.st_mode)) {
228 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
237 static int write_data_local_rtc(void) {
241 r = read_full_file("/etc/adjtime", &s, NULL);
249 w = strdup(NULL_ADJTIME_LOCAL);
262 p = strchr(p+1, '\n');
278 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
284 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
286 if (streq(w, NULL_ADJTIME_UTC)) {
289 if (unlink("/etc/adjtime") < 0) {
298 r = write_one_line_file_atomic("/etc/adjtime", w);
304 static char** get_ntp_services(void) {
305 char **r = NULL, **files, **i;
308 k = conf_files_list(&files, ".list",
309 "/etc/systemd/ntp-units.d",
310 "/run/systemd/ntp-units.d",
311 "/usr/local/lib/systemd/ntp-units.d",
312 "/usr/lib/systemd/ntp-units.d",
317 STRV_FOREACH(i, files) {
325 char line[PATH_MAX], *l, **q;
327 if (!fgets(line, sizeof(line), f)) {
330 log_error("Failed to read NTP units file: %m");
336 if (l[0] == 0 || l[0] == '#')
339 q = strv_append(r, l);
357 static int read_ntp(DBusConnection *bus) {
358 DBusMessage *m = NULL, *reply = NULL;
365 dbus_error_init(&error);
367 l = get_ntp_services();
372 dbus_message_unref(m);
373 m = dbus_message_new_method_call(
374 "org.freedesktop.systemd1",
375 "/org/freedesktop/systemd1",
376 "org.freedesktop.systemd1.Manager",
383 if (!dbus_message_append_args(m,
385 DBUS_TYPE_INVALID)) {
391 dbus_message_unref(reply);
392 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
394 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
395 /* This implementation does not exist, try next one */
396 dbus_error_free(&error);
400 log_error("Failed to issue method call: %s", bus_error_message(&error));
405 if (!dbus_message_get_args(reply, &error,
406 DBUS_TYPE_STRING, &s,
407 DBUS_TYPE_INVALID)) {
408 log_error("Failed to parse reply: %s", bus_error_message(&error));
414 streq(s, "enabled") ||
415 streq(s, "enabled-runtime");
420 /* NTP is not installed. */
426 dbus_message_unref(m);
429 dbus_message_unref(reply);
433 dbus_error_free(&error);
438 static int start_ntp(DBusConnection *bus, DBusError *error) {
439 DBusMessage *m = NULL, *reply = NULL;
440 const char *mode = "replace";
447 l = get_ntp_services();
450 dbus_message_unref(m);
451 m = dbus_message_new_method_call(
452 "org.freedesktop.systemd1",
453 "/org/freedesktop/systemd1",
454 "org.freedesktop.systemd1.Manager",
455 tz.use_ntp ? "StartUnit" : "StopUnit");
457 log_error("Could not allocate message.");
462 if (!dbus_message_append_args(m,
464 DBUS_TYPE_STRING, &mode,
465 DBUS_TYPE_INVALID)) {
466 log_error("Could not append arguments to message.");
472 dbus_message_unref(reply);
473 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
475 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
476 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
477 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
478 /* This implementation does not exist, try next one */
479 dbus_error_free(error);
483 log_error("Failed to issue method call: %s", bus_error_message(error));
492 /* No implementaiton available... */
497 dbus_message_unref(m);
500 dbus_message_unref(reply);
507 static int enable_ntp(DBusConnection *bus, DBusError *error) {
508 DBusMessage *m = NULL, *reply = NULL;
510 DBusMessageIter iter;
511 dbus_bool_t f = FALSE, t = TRUE;
517 l = get_ntp_services();
522 dbus_message_unref(m);
523 m = dbus_message_new_method_call(
524 "org.freedesktop.systemd1",
525 "/org/freedesktop/systemd1",
526 "org.freedesktop.systemd1.Manager",
527 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
529 log_error("Could not allocate message.");
534 dbus_message_iter_init_append(m, &iter);
539 r = bus_append_strv_iter(&iter, k);
541 log_error("Failed to append unit files.");
545 /* send runtime bool */
546 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
547 log_error("Failed to append runtime boolean.");
553 /* send force bool */
554 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
555 log_error("Failed to append force boolean.");
562 dbus_message_unref(reply);
563 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
565 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
566 /* This implementation does not exist, try next one */
567 dbus_error_free(error);
571 log_error("Failed to issue method call: %s", bus_error_message(error));
576 dbus_message_unref(m);
577 m = dbus_message_new_method_call(
578 "org.freedesktop.systemd1",
579 "/org/freedesktop/systemd1",
580 "org.freedesktop.systemd1.Manager",
583 log_error("Could not allocate message.");
588 dbus_message_unref(reply);
589 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
591 log_error("Failed to issue method call: %s", bus_error_message(error));
604 dbus_message_unref(m);
607 dbus_message_unref(reply);
614 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
622 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
628 static const BusProperty bus_timedate_properties[] = {
629 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
630 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
631 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
635 static const BusBoundProperties bps[] = {
636 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
640 static DBusHandlerResult timedate_message_handler(
641 DBusConnection *connection,
642 DBusMessage *message,
645 DBusMessage *reply = NULL, *changed = NULL;
652 dbus_error_init(&error);
654 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
656 dbus_bool_t interactive;
658 if (!dbus_message_get_args(
661 DBUS_TYPE_STRING, &z,
662 DBUS_TYPE_BOOLEAN, &interactive,
664 return bus_send_error_reply(connection, message, &error, -EINVAL);
666 if (!valid_timezone(z))
667 return bus_send_error_reply(connection, message, NULL, -EINVAL);
669 if (!streq_ptr(z, tz.zone)) {
672 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
674 return bus_send_error_reply(connection, message, &error, r);
683 /* 1. Write new configuration file */
684 r = write_data_timezone();
686 log_error("Failed to set timezone: %s", strerror(-r));
687 return bus_send_error_reply(connection, message, NULL, r);
690 /* 2. Tell the kernel our time zone */
691 hwclock_set_timezone(NULL);
697 /* 3. Sync RTC from system clock, with the new delta */
698 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
699 assert_se(tm = localtime(&ts.tv_sec));
700 hwclock_set_time(tm);
704 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
705 "TIMEZONE=%s", tz.zone,
706 "MESSAGE=Changed timezone to '%s'.", tz.zone,
709 changed = bus_properties_changed_new(
710 "/org/freedesktop/timedate1",
711 "org.freedesktop.timedate1",
717 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
719 dbus_bool_t fix_system;
720 dbus_bool_t interactive;
722 if (!dbus_message_get_args(
725 DBUS_TYPE_BOOLEAN, &lrtc,
726 DBUS_TYPE_BOOLEAN, &fix_system,
727 DBUS_TYPE_BOOLEAN, &interactive,
729 return bus_send_error_reply(connection, message, &error, -EINVAL);
731 if (lrtc != tz.local_rtc) {
734 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
736 return bus_send_error_reply(connection, message, &error, r);
740 /* 1. Write new configuration file */
741 r = write_data_local_rtc();
743 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
744 return bus_send_error_reply(connection, message, NULL, r);
747 /* 2. Tell the kernel our time zone */
748 hwclock_set_timezone(NULL);
750 /* 3. Synchronize clocks */
751 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
756 /* Sync system clock from RTC; first,
757 * initialize the timezone fields of
760 tm = *localtime(&ts.tv_sec);
762 tm = *gmtime(&ts.tv_sec);
764 /* Override the main fields of
765 * struct tm, but not the timezone
767 if (hwclock_get_time(&tm) >= 0) {
769 /* And set the system clock
772 ts.tv_sec = mktime(&tm);
774 ts.tv_sec = timegm(&tm);
776 clock_settime(CLOCK_REALTIME, &ts);
782 /* Sync RTC from system clock */
784 tm = localtime(&ts.tv_sec);
786 tm = gmtime(&ts.tv_sec);
788 hwclock_set_time(tm);
791 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
793 changed = bus_properties_changed_new(
794 "/org/freedesktop/timedate1",
795 "org.freedesktop.timedate1",
801 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
803 dbus_bool_t relative;
804 dbus_bool_t interactive;
806 if (!dbus_message_get_args(
809 DBUS_TYPE_INT64, &utc,
810 DBUS_TYPE_BOOLEAN, &relative,
811 DBUS_TYPE_BOOLEAN, &interactive,
813 return bus_send_error_reply(connection, message, &error, -EINVAL);
815 if (!relative && utc <= 0)
816 return bus_send_error_reply(connection, message, NULL, -EINVAL);
818 if (!relative || utc != 0) {
822 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
824 return bus_send_error_reply(connection, message, &error, r);
827 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
829 timespec_store(&ts, utc);
831 /* Set system clock */
832 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
833 log_error("Failed to set local time: %m");
834 return bus_send_error_reply(connection, message, NULL, -errno);
837 /* Sync down to RTC */
839 tm = localtime(&ts.tv_sec);
841 tm = gmtime(&ts.tv_sec);
843 hwclock_set_time(tm);
846 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
847 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
848 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
851 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
853 dbus_bool_t interactive;
855 if (!dbus_message_get_args(
858 DBUS_TYPE_BOOLEAN, &ntp,
859 DBUS_TYPE_BOOLEAN, &interactive,
861 return bus_send_error_reply(connection, message, &error, -EINVAL);
863 if (ntp != !!tz.use_ntp) {
865 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
867 return bus_send_error_reply(connection, message, &error, r);
871 r = enable_ntp(connection, &error);
873 return bus_send_error_reply(connection, message, &error, r);
875 r = start_ntp(connection, &error);
877 return bus_send_error_reply(connection, message, &error, r);
879 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
881 changed = bus_properties_changed_new(
882 "/org/freedesktop/timedate1",
883 "org.freedesktop.timedate1",
890 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
892 if (!(reply = dbus_message_new_method_return(message)))
895 if (!dbus_connection_send(connection, reply, NULL))
898 dbus_message_unref(reply);
903 if (!dbus_connection_send(connection, changed, NULL))
906 dbus_message_unref(changed);
909 return DBUS_HANDLER_RESULT_HANDLED;
913 dbus_message_unref(reply);
916 dbus_message_unref(changed);
918 dbus_error_free(&error);
920 return DBUS_HANDLER_RESULT_NEED_MEMORY;
923 static int connect_bus(DBusConnection **_bus) {
924 static const DBusObjectPathVTable timedate_vtable = {
925 .message_function = timedate_message_handler
928 DBusConnection *bus = NULL;
933 dbus_error_init(&error);
935 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
937 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
942 dbus_connection_set_exit_on_disconnect(bus, FALSE);
944 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
945 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
950 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
951 if (dbus_error_is_set(&error)) {
952 log_error("Failed to register name on bus: %s", bus_error_message(&error));
957 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
958 log_error("Failed to acquire name.");
969 dbus_connection_close(bus);
970 dbus_connection_unref(bus);
972 dbus_error_free(&error);
977 int main(int argc, char *argv[]) {
979 DBusConnection *bus = NULL;
980 bool exiting = false;
982 log_set_target(LOG_TARGET_AUTO);
983 log_parse_environment();
988 if (argc == 2 && streq(argv[1], "--introspect")) {
989 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
991 fputs(timedate_interface, stdout);
992 fputs("</node>\n", stdout);
997 log_error("This program takes no arguments.");
1004 log_error("Failed to read timezone data: %s", strerror(-r));
1008 r = connect_bus(&bus);
1014 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1018 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1021 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1024 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1026 bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1036 dbus_connection_flush(bus);
1037 dbus_connection_close(bus);
1038 dbus_connection_unref(bus);
1041 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;