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>
30 #include "dbus-common.h"
34 #include "conf-files.h"
36 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
37 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
40 " <interface name=\"org.freedesktop.timedate1\">\n" \
41 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
42 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
43 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
44 " <method name=\"SetTime\">\n" \
45 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
46 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
47 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
49 " <method name=\"SetTimezone\">\n" \
50 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
51 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
53 " <method name=\"SetLocalRTC\">\n" \
54 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
55 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
58 " <method name=\"SetNTP\">\n" \
59 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
60 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
64 #define INTROSPECTION \
65 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
68 BUS_PROPERTIES_INTERFACE \
69 BUS_INTROSPECTABLE_INTERFACE \
73 #define INTERFACES_LIST \
74 BUS_GENERIC_INTERFACES_LIST \
75 "org.freedesktop.timedate1\0"
77 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
89 static usec_t remain_until;
91 static void free_data(void) {
98 static bool valid_timezone(const char *name) {
107 if (*name == '/' || *name == 0)
110 for (p = name; *p; p++) {
111 if (!(*p >= '0' && *p <= '9') &&
112 !(*p >= 'a' && *p <= 'z') &&
113 !(*p >= 'A' && *p <= 'Z') &&
114 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
130 t = strappend("/usr/share/zoneinfo/", name);
140 if (!S_ISREG(st.st_mode))
146 static void verify_timezone(void) {
147 char *p, *a = NULL, *b = NULL;
154 p = strappend("/usr/share/zoneinfo/", tz.zone);
156 log_error("Out of memory");
160 j = read_full_file("/etc/localtime", &a, &l);
161 k = read_full_file(p, &b, &q);
165 if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
166 log_warning("/etc/localtime and /etc/timezone out of sync.");
175 static int read_data(void) {
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));
195 if (isempty(tz.zone)) {
202 tz.local_rtc = hwclock_is_localtime() > 0;
207 static int write_data_timezone(void) {
212 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
215 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
221 p = strappend("/usr/share/zoneinfo/", tz.zone);
223 log_error("Out of memory");
227 r = symlink_or_copy_atomic(p, "/etc/localtime");
233 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
240 static int write_data_local_rtc(void) {
244 r = read_full_file("/etc/adjtime", &s, NULL);
252 w = strdup(NULL_ADJTIME_LOCAL);
265 p = strchr(p+1, '\n');
281 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
287 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
289 if (streq(w, NULL_ADJTIME_UTC)) {
292 if (unlink("/etc/adjtime") < 0) {
301 r = write_one_line_file_atomic("/etc/adjtime", w);
307 static char** get_ntp_services(void) {
308 char **r = NULL, **files, **i;
311 k = conf_files_list(&files, ".list",
312 "/etc/systemd/ntp-units.d",
313 "/run/systemd/ntp-units.d",
314 "/usr/local/lib/systemd/ntp-units.d",
315 "/usr/lib/systemd/ntp-units.d",
320 STRV_FOREACH(i, files) {
328 char line[PATH_MAX], *l, **q;
330 if (!fgets(line, sizeof(line), f)) {
333 log_error("Failed to read NTP units file: %m");
339 if (l[0] == 0 || l[0] == '#')
342 q = strv_append(r, l);
344 log_error("Out of memory");
360 static int read_ntp(DBusConnection *bus) {
361 DBusMessage *m = NULL, *reply = NULL;
368 dbus_error_init(&error);
370 l = get_ntp_services();
375 dbus_message_unref(m);
376 m = dbus_message_new_method_call(
377 "org.freedesktop.systemd1",
378 "/org/freedesktop/systemd1",
379 "org.freedesktop.systemd1.Manager",
382 log_error("Out of memory");
387 if (!dbus_message_append_args(m,
389 DBUS_TYPE_INVALID)) {
390 log_error("Could not append arguments to message.");
396 dbus_message_unref(reply);
397 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
399 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
400 /* This implementation does not exist, try next one */
401 dbus_error_free(&error);
405 log_error("Failed to issue method call: %s", bus_error_message(&error));
410 if (!dbus_message_get_args(reply, &error,
411 DBUS_TYPE_STRING, &s,
412 DBUS_TYPE_INVALID)) {
413 log_error("Failed to parse reply: %s", bus_error_message(&error));
419 streq(s, "enabled") ||
420 streq(s, "enabled-runtime");
425 /* NTP is not installed. */
431 dbus_message_unref(m);
434 dbus_message_unref(reply);
438 dbus_error_free(&error);
443 static int start_ntp(DBusConnection *bus, DBusError *error) {
444 DBusMessage *m = NULL, *reply = NULL;
445 const char *mode = "replace";
452 l = get_ntp_services();
455 dbus_message_unref(m);
456 m = dbus_message_new_method_call(
457 "org.freedesktop.systemd1",
458 "/org/freedesktop/systemd1",
459 "org.freedesktop.systemd1.Manager",
460 tz.use_ntp ? "StartUnit" : "StopUnit");
462 log_error("Could not allocate message.");
467 if (!dbus_message_append_args(m,
469 DBUS_TYPE_STRING, &mode,
470 DBUS_TYPE_INVALID)) {
471 log_error("Could not append arguments to message.");
477 dbus_message_unref(reply);
478 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
480 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
481 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
482 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
483 /* This implementation does not exist, try next one */
484 dbus_error_free(error);
488 log_error("Failed to issue method call: %s", bus_error_message(error));
497 /* No implementaiton available... */
502 dbus_message_unref(m);
505 dbus_message_unref(reply);
512 static int enable_ntp(DBusConnection *bus, DBusError *error) {
513 DBusMessage *m = NULL, *reply = NULL;
515 DBusMessageIter iter;
516 dbus_bool_t f = FALSE, t = TRUE;
522 l = get_ntp_services();
527 dbus_message_unref(m);
528 m = dbus_message_new_method_call(
529 "org.freedesktop.systemd1",
530 "/org/freedesktop/systemd1",
531 "org.freedesktop.systemd1.Manager",
532 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
534 log_error("Could not allocate message.");
539 dbus_message_iter_init_append(m, &iter);
544 r = bus_append_strv_iter(&iter, k);
546 log_error("Failed to append unit files.");
550 /* send runtime bool */
551 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
552 log_error("Failed to append runtime boolean.");
558 /* send force bool */
559 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
560 log_error("Failed to append force boolean.");
567 dbus_message_unref(reply);
568 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
570 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
571 /* This implementation does not exist, try next one */
572 dbus_error_free(error);
576 log_error("Failed to issue method call: %s", bus_error_message(error));
581 dbus_message_unref(m);
582 m = dbus_message_new_method_call(
583 "org.freedesktop.systemd1",
584 "/org/freedesktop/systemd1",
585 "org.freedesktop.systemd1.Manager",
588 log_error("Could not allocate message.");
593 dbus_message_unref(reply);
594 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
596 log_error("Failed to issue method call: %s", bus_error_message(error));
609 dbus_message_unref(m);
612 dbus_message_unref(reply);
619 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
627 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
633 static const BusProperty bus_timedate_properties[] = {
634 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
635 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
636 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
640 static const BusBoundProperties bps[] = {
641 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
645 static DBusHandlerResult timedate_message_handler(
646 DBusConnection *connection,
647 DBusMessage *message,
650 DBusMessage *reply = NULL, *changed = NULL;
657 dbus_error_init(&error);
659 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
661 dbus_bool_t interactive;
663 if (!dbus_message_get_args(
666 DBUS_TYPE_STRING, &z,
667 DBUS_TYPE_BOOLEAN, &interactive,
669 return bus_send_error_reply(connection, message, &error, -EINVAL);
671 if (!valid_timezone(z))
672 return bus_send_error_reply(connection, message, NULL, -EINVAL);
674 if (!streq_ptr(z, tz.zone)) {
677 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
679 return bus_send_error_reply(connection, message, &error, r);
688 /* 1. Write new configuration file */
689 r = write_data_timezone();
691 log_error("Failed to set timezone: %s", strerror(-r));
692 return bus_send_error_reply(connection, message, NULL, r);
699 /* 2. Teach kernel new timezone */
700 hwclock_apply_localtime_delta(NULL);
702 /* 3. Sync RTC from system clock, with the new delta */
703 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
704 assert_se(tm = localtime(&ts.tv_sec));
705 hwclock_set_time(tm);
708 log_info("Changed timezone to '%s'.", tz.zone);
710 changed = bus_properties_changed_new(
711 "/org/freedesktop/timedate1",
712 "org.freedesktop.timedate1",
718 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
720 dbus_bool_t fix_system;
721 dbus_bool_t interactive;
723 if (!dbus_message_get_args(
726 DBUS_TYPE_BOOLEAN, &lrtc,
727 DBUS_TYPE_BOOLEAN, &fix_system,
728 DBUS_TYPE_BOOLEAN, &interactive,
730 return bus_send_error_reply(connection, message, &error, -EINVAL);
732 if (lrtc != tz.local_rtc) {
735 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
737 return bus_send_error_reply(connection, message, &error, r);
741 /* 1. Write new configuration file */
742 r = write_data_local_rtc();
744 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
745 return bus_send_error_reply(connection, message, NULL, r);
748 /* 2. Teach kernel new timezone */
750 hwclock_apply_localtime_delta(NULL);
752 hwclock_reset_localtime_delta();
754 /* 3. Synchronize clocks */
755 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
760 /* Sync system clock from RTC; first,
761 * initialize the timezone fields of
764 tm = *localtime(&ts.tv_sec);
766 tm = *gmtime(&ts.tv_sec);
768 /* Override the main fields of
769 * struct tm, but not the timezone
771 if (hwclock_get_time(&tm) >= 0) {
773 /* And set the system clock
776 ts.tv_sec = mktime(&tm);
778 ts.tv_sec = timegm(&tm);
780 clock_settime(CLOCK_REALTIME, &ts);
786 /* Sync RTC from system clock */
788 tm = localtime(&ts.tv_sec);
790 tm = gmtime(&ts.tv_sec);
792 hwclock_set_time(tm);
795 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
797 changed = bus_properties_changed_new(
798 "/org/freedesktop/timedate1",
799 "org.freedesktop.timedate1",
805 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
807 dbus_bool_t relative;
808 dbus_bool_t interactive;
810 if (!dbus_message_get_args(
813 DBUS_TYPE_INT64, &utc,
814 DBUS_TYPE_BOOLEAN, &relative,
815 DBUS_TYPE_BOOLEAN, &interactive,
817 return bus_send_error_reply(connection, message, &error, -EINVAL);
819 if (!relative && utc <= 0)
820 return bus_send_error_reply(connection, message, NULL, -EINVAL);
822 if (!relative || utc != 0) {
826 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
828 return bus_send_error_reply(connection, message, &error, r);
831 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
833 timespec_store(&ts, utc);
835 /* Set system clock */
836 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
837 log_error("Failed to set local time: %m");
838 return bus_send_error_reply(connection, message, NULL, -errno);
841 /* Sync down to RTC */
843 tm = localtime(&ts.tv_sec);
845 tm = gmtime(&ts.tv_sec);
847 hwclock_set_time(tm);
849 log_info("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)) {
946 log_error("Not enough memory");
951 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
952 if (dbus_error_is_set(&error)) {
953 log_error("Failed to register name on bus: %s", bus_error_message(&error));
958 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
959 log_error("Failed to acquire name.");
970 dbus_connection_close(bus);
971 dbus_connection_unref(bus);
973 dbus_error_free(&error);
978 int main(int argc, char *argv[]) {
980 DBusConnection *bus = NULL;
981 bool exiting = false;
983 log_set_target(LOG_TARGET_AUTO);
984 log_parse_environment();
989 if (argc == 2 && streq(argv[1], "--introspect")) {
990 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
992 fputs(timedate_interface, stdout);
993 fputs("</node>\n", stdout);
998 log_error("This program takes no arguments.");
1005 log_error("Failed to read timezone data: %s", strerror(-r));
1009 r = connect_bus(&bus);
1015 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1019 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1022 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1025 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1027 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1037 dbus_connection_flush(bus);
1038 dbus_connection_close(bus);
1039 dbus_connection_unref(bus);
1042 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;