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/.");
179 #ifdef HAVE_SYSV_COMPAT
180 r = read_one_line_file("/etc/timezone", &tz.zone);
183 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
186 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
190 if (r < 0 && r != -ENOENT)
191 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
197 if (isempty(tz.zone)) {
202 tz.local_rtc = hwclock_is_localtime() > 0;
207 static int write_data_timezone(void) {
209 _cleanup_free_ char *p = NULL;
213 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
216 #ifdef HAVE_SYSV_COMPAT
217 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
224 p = strappend("../usr/share/zoneinfo/", tz.zone);
228 r = symlink_atomic(p, "/etc/localtime");
232 #ifdef HAVE_SYSV_COMPAT
233 if (stat("/etc/timezone", &st) == 0 && S_ISREG(st.st_mode)) {
234 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
243 static int write_data_local_rtc(void) {
247 r = read_full_file("/etc/adjtime", &s, NULL);
255 w = strdup(NULL_ADJTIME_LOCAL);
268 p = strchr(p+1, '\n');
284 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
290 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
292 if (streq(w, NULL_ADJTIME_UTC)) {
295 if (unlink("/etc/adjtime") < 0) {
304 r = write_one_line_file_atomic("/etc/adjtime", w);
310 static char** get_ntp_services(void) {
311 char **r = NULL, **files, **i;
314 k = conf_files_list(&files, ".list",
315 "/etc/systemd/ntp-units.d",
316 "/run/systemd/ntp-units.d",
317 "/usr/local/lib/systemd/ntp-units.d",
318 "/usr/lib/systemd/ntp-units.d",
323 STRV_FOREACH(i, files) {
331 char line[PATH_MAX], *l, **q;
333 if (!fgets(line, sizeof(line), f)) {
336 log_error("Failed to read NTP units file: %m");
342 if (l[0] == 0 || l[0] == '#')
345 q = strv_append(r, l);
363 static int read_ntp(DBusConnection *bus) {
364 DBusMessage *m = NULL, *reply = NULL;
371 dbus_error_init(&error);
373 l = get_ntp_services();
378 dbus_message_unref(m);
379 m = dbus_message_new_method_call(
380 "org.freedesktop.systemd1",
381 "/org/freedesktop/systemd1",
382 "org.freedesktop.systemd1.Manager",
389 if (!dbus_message_append_args(m,
391 DBUS_TYPE_INVALID)) {
397 dbus_message_unref(reply);
398 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
400 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
401 /* This implementation does not exist, try next one */
402 dbus_error_free(&error);
406 log_error("Failed to issue method call: %s", bus_error_message(&error));
411 if (!dbus_message_get_args(reply, &error,
412 DBUS_TYPE_STRING, &s,
413 DBUS_TYPE_INVALID)) {
414 log_error("Failed to parse reply: %s", bus_error_message(&error));
420 streq(s, "enabled") ||
421 streq(s, "enabled-runtime");
426 /* NTP is not installed. */
432 dbus_message_unref(m);
435 dbus_message_unref(reply);
439 dbus_error_free(&error);
444 static int start_ntp(DBusConnection *bus, DBusError *error) {
445 DBusMessage *m = NULL, *reply = NULL;
446 const char *mode = "replace";
453 l = get_ntp_services();
456 dbus_message_unref(m);
457 m = dbus_message_new_method_call(
458 "org.freedesktop.systemd1",
459 "/org/freedesktop/systemd1",
460 "org.freedesktop.systemd1.Manager",
461 tz.use_ntp ? "StartUnit" : "StopUnit");
463 log_error("Could not allocate message.");
468 if (!dbus_message_append_args(m,
470 DBUS_TYPE_STRING, &mode,
471 DBUS_TYPE_INVALID)) {
472 log_error("Could not append arguments to message.");
478 dbus_message_unref(reply);
479 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
481 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
482 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
483 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
484 /* This implementation does not exist, try next one */
485 dbus_error_free(error);
489 log_error("Failed to issue method call: %s", bus_error_message(error));
498 /* No implementaiton available... */
503 dbus_message_unref(m);
506 dbus_message_unref(reply);
513 static int enable_ntp(DBusConnection *bus, DBusError *error) {
514 DBusMessage *m = NULL, *reply = NULL;
516 DBusMessageIter iter;
517 dbus_bool_t f = FALSE, t = TRUE;
523 l = get_ntp_services();
528 dbus_message_unref(m);
529 m = dbus_message_new_method_call(
530 "org.freedesktop.systemd1",
531 "/org/freedesktop/systemd1",
532 "org.freedesktop.systemd1.Manager",
533 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
535 log_error("Could not allocate message.");
540 dbus_message_iter_init_append(m, &iter);
545 r = bus_append_strv_iter(&iter, k);
547 log_error("Failed to append unit files.");
551 /* send runtime bool */
552 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
553 log_error("Failed to append runtime boolean.");
559 /* send force bool */
560 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
561 log_error("Failed to append force boolean.");
568 dbus_message_unref(reply);
569 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
571 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
572 /* This implementation does not exist, try next one */
573 dbus_error_free(error);
577 log_error("Failed to issue method call: %s", bus_error_message(error));
582 dbus_message_unref(m);
583 m = dbus_message_new_method_call(
584 "org.freedesktop.systemd1",
585 "/org/freedesktop/systemd1",
586 "org.freedesktop.systemd1.Manager",
589 log_error("Could not allocate message.");
594 dbus_message_unref(reply);
595 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
597 log_error("Failed to issue method call: %s", bus_error_message(error));
610 dbus_message_unref(m);
613 dbus_message_unref(reply);
620 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
628 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
634 static const BusProperty bus_timedate_properties[] = {
635 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
636 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
637 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
641 static const BusBoundProperties bps[] = {
642 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
646 static DBusHandlerResult timedate_message_handler(
647 DBusConnection *connection,
648 DBusMessage *message,
651 DBusMessage *reply = NULL, *changed = NULL;
658 dbus_error_init(&error);
660 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
662 dbus_bool_t interactive;
664 if (!dbus_message_get_args(
667 DBUS_TYPE_STRING, &z,
668 DBUS_TYPE_BOOLEAN, &interactive,
670 return bus_send_error_reply(connection, message, &error, -EINVAL);
672 if (!valid_timezone(z))
673 return bus_send_error_reply(connection, message, NULL, -EINVAL);
675 if (!streq_ptr(z, tz.zone)) {
678 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
680 return bus_send_error_reply(connection, message, &error, r);
689 /* 1. Write new configuration file */
690 r = write_data_timezone();
692 log_error("Failed to set timezone: %s", strerror(-r));
693 return bus_send_error_reply(connection, message, NULL, r);
700 /* 2. Teach kernel new timezone */
701 hwclock_apply_localtime_delta(NULL);
703 /* 3. Sync RTC from system clock, with the new delta */
704 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
705 assert_se(tm = localtime(&ts.tv_sec));
706 hwclock_set_time(tm);
710 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIMEZONE_CHANGE),
711 "TIMEZONE=%s", tz.zone,
712 "MESSAGE=Changed timezone to '%s'.", tz.zone,
715 changed = bus_properties_changed_new(
716 "/org/freedesktop/timedate1",
717 "org.freedesktop.timedate1",
723 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
725 dbus_bool_t fix_system;
726 dbus_bool_t interactive;
728 if (!dbus_message_get_args(
731 DBUS_TYPE_BOOLEAN, &lrtc,
732 DBUS_TYPE_BOOLEAN, &fix_system,
733 DBUS_TYPE_BOOLEAN, &interactive,
735 return bus_send_error_reply(connection, message, &error, -EINVAL);
737 if (lrtc != tz.local_rtc) {
740 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
742 return bus_send_error_reply(connection, message, &error, r);
746 /* 1. Write new configuration file */
747 r = write_data_local_rtc();
749 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
750 return bus_send_error_reply(connection, message, NULL, r);
753 /* 2. Teach kernel new timezone */
755 hwclock_apply_localtime_delta(NULL);
757 hwclock_reset_localtime_delta();
759 /* 3. Synchronize clocks */
760 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
765 /* Sync system clock from RTC; first,
766 * initialize the timezone fields of
769 tm = *localtime(&ts.tv_sec);
771 tm = *gmtime(&ts.tv_sec);
773 /* Override the main fields of
774 * struct tm, but not the timezone
776 if (hwclock_get_time(&tm) >= 0) {
778 /* And set the system clock
781 ts.tv_sec = mktime(&tm);
783 ts.tv_sec = timegm(&tm);
785 clock_settime(CLOCK_REALTIME, &ts);
791 /* Sync RTC from system clock */
793 tm = localtime(&ts.tv_sec);
795 tm = gmtime(&ts.tv_sec);
797 hwclock_set_time(tm);
800 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
802 changed = bus_properties_changed_new(
803 "/org/freedesktop/timedate1",
804 "org.freedesktop.timedate1",
810 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
812 dbus_bool_t relative;
813 dbus_bool_t interactive;
815 if (!dbus_message_get_args(
818 DBUS_TYPE_INT64, &utc,
819 DBUS_TYPE_BOOLEAN, &relative,
820 DBUS_TYPE_BOOLEAN, &interactive,
822 return bus_send_error_reply(connection, message, &error, -EINVAL);
824 if (!relative && utc <= 0)
825 return bus_send_error_reply(connection, message, NULL, -EINVAL);
827 if (!relative || utc != 0) {
831 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
833 return bus_send_error_reply(connection, message, &error, r);
836 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
838 timespec_store(&ts, utc);
840 /* Set system clock */
841 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
842 log_error("Failed to set local time: %m");
843 return bus_send_error_reply(connection, message, NULL, -errno);
846 /* Sync down to RTC */
848 tm = localtime(&ts.tv_sec);
850 tm = gmtime(&ts.tv_sec);
852 hwclock_set_time(tm);
855 "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE),
856 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
857 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
860 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
862 dbus_bool_t interactive;
864 if (!dbus_message_get_args(
867 DBUS_TYPE_BOOLEAN, &ntp,
868 DBUS_TYPE_BOOLEAN, &interactive,
870 return bus_send_error_reply(connection, message, &error, -EINVAL);
872 if (ntp != !!tz.use_ntp) {
874 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
876 return bus_send_error_reply(connection, message, &error, r);
880 r = enable_ntp(connection, &error);
882 return bus_send_error_reply(connection, message, &error, r);
884 r = start_ntp(connection, &error);
886 return bus_send_error_reply(connection, message, &error, r);
888 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
890 changed = bus_properties_changed_new(
891 "/org/freedesktop/timedate1",
892 "org.freedesktop.timedate1",
899 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
901 if (!(reply = dbus_message_new_method_return(message)))
904 if (!dbus_connection_send(connection, reply, NULL))
907 dbus_message_unref(reply);
912 if (!dbus_connection_send(connection, changed, NULL))
915 dbus_message_unref(changed);
918 return DBUS_HANDLER_RESULT_HANDLED;
922 dbus_message_unref(reply);
925 dbus_message_unref(changed);
927 dbus_error_free(&error);
929 return DBUS_HANDLER_RESULT_NEED_MEMORY;
932 static int connect_bus(DBusConnection **_bus) {
933 static const DBusObjectPathVTable timedate_vtable = {
934 .message_function = timedate_message_handler
937 DBusConnection *bus = NULL;
942 dbus_error_init(&error);
944 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
946 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
951 dbus_connection_set_exit_on_disconnect(bus, FALSE);
953 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
954 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
959 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
960 if (dbus_error_is_set(&error)) {
961 log_error("Failed to register name on bus: %s", bus_error_message(&error));
966 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
967 log_error("Failed to acquire name.");
978 dbus_connection_close(bus);
979 dbus_connection_unref(bus);
981 dbus_error_free(&error);
986 int main(int argc, char *argv[]) {
988 DBusConnection *bus = NULL;
989 bool exiting = false;
991 log_set_target(LOG_TARGET_AUTO);
992 log_parse_environment();
997 if (argc == 2 && streq(argv[1], "--introspect")) {
998 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1000 fputs(timedate_interface, stdout);
1001 fputs("</node>\n", stdout);
1006 log_error("This program takes no arguments.");
1013 log_error("Failed to read timezone data: %s", strerror(-r));
1017 r = connect_bus(&bus);
1023 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1027 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1030 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1033 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1035 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1045 dbus_connection_flush(bus);
1046 dbus_connection_close(bus);
1047 dbus_connection_unref(bus);
1050 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;