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"
35 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
36 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
39 " <interface name=\"org.freedesktop.timedate1\">\n" \
40 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
41 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
42 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
43 " <method name=\"SetTime\">\n" \
44 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
45 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
46 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
48 " <method name=\"SetTimezone\">\n" \
49 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
50 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
52 " <method name=\"SetLocalRTC\">\n" \
53 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
54 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
55 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57 " <method name=\"SetNTP\">\n" \
58 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
59 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
63 #define INTROSPECTION \
64 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
67 BUS_PROPERTIES_INTERFACE \
68 BUS_INTROSPECTABLE_INTERFACE \
72 #define INTERFACES_LIST \
73 BUS_GENERIC_INTERFACES_LIST \
74 "org.freedesktop.timedate1\0"
76 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
88 static usec_t remain_until;
90 static void free_data(void) {
97 static bool valid_timezone(const char *name) {
106 if (*name == '/' || *name == 0)
109 for (p = name; *p; p++) {
110 if (!(*p >= '0' && *p <= '9') &&
111 !(*p >= 'a' && *p <= 'z') &&
112 !(*p >= 'A' && *p <= 'Z') &&
113 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
129 t = strappend("/usr/share/zoneinfo/", name);
139 if (!S_ISREG(st.st_mode))
145 static void verify_timezone(void) {
146 char *p, *a = NULL, *b = NULL;
153 p = strappend("/usr/share/zoneinfo/", tz.zone);
155 log_error("Out of memory");
159 j = read_full_file("/etc/localtime", &a, &l);
160 k = read_full_file(p, &b, &q);
164 if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
165 log_warning("/etc/localtime and /etc/timezone out of sync.");
174 static int read_data(void) {
179 r = read_one_line_file("/etc/timezone", &tz.zone);
182 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
185 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
189 if (r < 0 && r != -ENOENT)
190 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
194 if (isempty(tz.zone)) {
201 tz.local_rtc = hwclock_is_localtime() > 0;
206 static int write_data_timezone(void) {
211 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
214 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
220 p = strappend("/usr/share/zoneinfo/", tz.zone);
222 log_error("Out of memory");
226 r = symlink_or_copy_atomic(p, "/etc/localtime");
232 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
239 static int write_data_local_rtc(void) {
243 r = read_full_file("/etc/adjtime", &s, NULL);
251 w = strdup(NULL_ADJTIME_LOCAL);
264 p = strchr(p+1, '\n');
280 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
286 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
288 if (streq(w, NULL_ADJTIME_UTC)) {
291 if (unlink("/etc/adjtime") < 0) {
300 r = write_one_line_file_atomic("/etc/adjtime", w);
306 static char** get_ntp_services(void) {
310 f = fopen(SYSTEMD_NTP_UNITS, "re");
315 char line[PATH_MAX], *l, **q;
317 if (!fgets(line, sizeof(line), f)) {
320 log_error("Failed to read NTP units file: %m");
326 if (l[0] == 0 || l[0] == '#')
330 q = strv_append(r, l);
332 log_error("Out of memory");
345 static int read_ntp(DBusConnection *bus) {
346 DBusMessage *m = NULL, *reply = NULL;
353 dbus_error_init(&error);
355 l = get_ntp_services();
360 dbus_message_unref(m);
361 m = dbus_message_new_method_call(
362 "org.freedesktop.systemd1",
363 "/org/freedesktop/systemd1",
364 "org.freedesktop.systemd1.Manager",
367 log_error("Out of memory");
372 if (!dbus_message_append_args(m,
374 DBUS_TYPE_INVALID)) {
375 log_error("Could not append arguments to message.");
381 dbus_message_unref(reply);
382 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
384 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
385 /* This implementation does not exist, try next one */
386 dbus_error_free(&error);
390 log_error("Failed to issue method call: %s", bus_error_message(&error));
395 if (!dbus_message_get_args(reply, &error,
396 DBUS_TYPE_STRING, &s,
397 DBUS_TYPE_INVALID)) {
398 log_error("Failed to parse reply: %s", bus_error_message(&error));
404 streq(s, "enabled") ||
405 streq(s, "enabled-runtime");
410 /* NTP is not installed. */
416 dbus_message_unref(m);
419 dbus_message_unref(reply);
423 dbus_error_free(&error);
428 static int start_ntp(DBusConnection *bus, DBusError *error) {
429 DBusMessage *m = NULL, *reply = NULL;
430 const char *mode = "replace";
437 l = get_ntp_services();
440 dbus_message_unref(m);
441 m = dbus_message_new_method_call(
442 "org.freedesktop.systemd1",
443 "/org/freedesktop/systemd1",
444 "org.freedesktop.systemd1.Manager",
445 tz.use_ntp ? "StartUnit" : "StopUnit");
447 log_error("Could not allocate message.");
452 if (!dbus_message_append_args(m,
454 DBUS_TYPE_STRING, &mode,
455 DBUS_TYPE_INVALID)) {
456 log_error("Could not append arguments to message.");
462 dbus_message_unref(reply);
463 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
465 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
466 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
467 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
468 /* This implementation does not exist, try next one */
469 dbus_error_free(error);
473 log_error("Failed to issue method call: %s", bus_error_message(error));
482 /* No implementaiton available... */
487 dbus_message_unref(m);
490 dbus_message_unref(reply);
497 static int enable_ntp(DBusConnection *bus, DBusError *error) {
498 DBusMessage *m = NULL, *reply = NULL;
500 DBusMessageIter iter;
501 dbus_bool_t f = FALSE, t = TRUE;
507 l = get_ntp_services();
512 dbus_message_unref(m);
513 m = dbus_message_new_method_call(
514 "org.freedesktop.systemd1",
515 "/org/freedesktop/systemd1",
516 "org.freedesktop.systemd1.Manager",
517 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
519 log_error("Could not allocate message.");
524 dbus_message_iter_init_append(m, &iter);
529 r = bus_append_strv_iter(&iter, k);
531 log_error("Failed to append unit files.");
535 /* send runtime bool */
536 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
537 log_error("Failed to append runtime boolean.");
543 /* send force bool */
544 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
545 log_error("Failed to append force boolean.");
552 dbus_message_unref(reply);
553 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
555 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
556 /* This implementation does not exist, try next one */
557 dbus_error_free(error);
561 log_error("Failed to issue method call: %s", bus_error_message(error));
566 dbus_message_unref(m);
567 m = dbus_message_new_method_call(
568 "org.freedesktop.systemd1",
569 "/org/freedesktop/systemd1",
570 "org.freedesktop.systemd1.Manager",
573 log_error("Could not allocate message.");
578 dbus_message_unref(reply);
579 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
581 log_error("Failed to issue method call: %s", bus_error_message(error));
594 dbus_message_unref(m);
597 dbus_message_unref(reply);
604 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
612 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
618 static const BusProperty bus_timedate_properties[] = {
619 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
620 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
621 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
625 static const BusBoundProperties bps[] = {
626 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
630 static DBusHandlerResult timedate_message_handler(
631 DBusConnection *connection,
632 DBusMessage *message,
635 DBusMessage *reply = NULL, *changed = NULL;
642 dbus_error_init(&error);
644 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
646 dbus_bool_t interactive;
648 if (!dbus_message_get_args(
651 DBUS_TYPE_STRING, &z,
652 DBUS_TYPE_BOOLEAN, &interactive,
654 return bus_send_error_reply(connection, message, &error, -EINVAL);
656 if (!valid_timezone(z))
657 return bus_send_error_reply(connection, message, NULL, -EINVAL);
659 if (!streq_ptr(z, tz.zone)) {
662 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
664 return bus_send_error_reply(connection, message, &error, r);
673 /* 1. Write new configuration file */
674 r = write_data_timezone();
676 log_error("Failed to set timezone: %s", strerror(-r));
677 return bus_send_error_reply(connection, message, NULL, r);
684 /* 2. Teach kernel new timezone */
685 hwclock_apply_localtime_delta(NULL);
687 /* 3. Sync RTC from system clock, with the new delta */
688 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
689 assert_se(tm = localtime(&ts.tv_sec));
690 hwclock_set_time(tm);
693 log_info("Changed timezone to '%s'.", tz.zone);
695 changed = bus_properties_changed_new(
696 "/org/freedesktop/timedate1",
697 "org.freedesktop.timedate1",
703 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
705 dbus_bool_t fix_system;
706 dbus_bool_t interactive;
708 if (!dbus_message_get_args(
711 DBUS_TYPE_BOOLEAN, &lrtc,
712 DBUS_TYPE_BOOLEAN, &fix_system,
713 DBUS_TYPE_BOOLEAN, &interactive,
715 return bus_send_error_reply(connection, message, &error, -EINVAL);
717 if (lrtc != tz.local_rtc) {
720 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
722 return bus_send_error_reply(connection, message, &error, r);
726 /* 1. Write new configuration file */
727 r = write_data_local_rtc();
729 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
730 return bus_send_error_reply(connection, message, NULL, r);
733 /* 2. Teach kernel new timezone */
735 hwclock_apply_localtime_delta(NULL);
737 hwclock_reset_localtime_delta();
739 /* 3. Synchronize clocks */
740 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
745 /* Sync system clock from RTC; first,
746 * initialize the timezone fields of
749 tm = *localtime(&ts.tv_sec);
751 tm = *gmtime(&ts.tv_sec);
753 /* Override the main fields of
754 * struct tm, but not the timezone
756 if (hwclock_get_time(&tm) >= 0) {
758 /* And set the system clock
761 ts.tv_sec = mktime(&tm);
763 ts.tv_sec = timegm(&tm);
765 clock_settime(CLOCK_REALTIME, &ts);
771 /* Sync RTC from system clock */
773 tm = localtime(&ts.tv_sec);
775 tm = gmtime(&ts.tv_sec);
777 hwclock_set_time(tm);
780 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
782 changed = bus_properties_changed_new(
783 "/org/freedesktop/timedate1",
784 "org.freedesktop.timedate1",
790 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
792 dbus_bool_t relative;
793 dbus_bool_t interactive;
795 if (!dbus_message_get_args(
798 DBUS_TYPE_INT64, &utc,
799 DBUS_TYPE_BOOLEAN, &relative,
800 DBUS_TYPE_BOOLEAN, &interactive,
802 return bus_send_error_reply(connection, message, &error, -EINVAL);
804 if (!relative && utc <= 0)
805 return bus_send_error_reply(connection, message, NULL, -EINVAL);
807 if (!relative || utc != 0) {
811 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
813 return bus_send_error_reply(connection, message, &error, r);
816 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
818 timespec_store(&ts, utc);
820 /* Set system clock */
821 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
822 log_error("Failed to set local time: %m");
823 return bus_send_error_reply(connection, message, NULL, -errno);
826 /* Sync down to RTC */
828 tm = localtime(&ts.tv_sec);
830 tm = gmtime(&ts.tv_sec);
832 hwclock_set_time(tm);
834 log_info("Changed local time to %s", ctime(&ts.tv_sec));
836 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
838 dbus_bool_t interactive;
840 if (!dbus_message_get_args(
843 DBUS_TYPE_BOOLEAN, &ntp,
844 DBUS_TYPE_BOOLEAN, &interactive,
846 return bus_send_error_reply(connection, message, &error, -EINVAL);
848 if (ntp != !!tz.use_ntp) {
850 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
852 return bus_send_error_reply(connection, message, &error, r);
856 r = enable_ntp(connection, &error);
858 return bus_send_error_reply(connection, message, &error, r);
860 r = start_ntp(connection, &error);
862 return bus_send_error_reply(connection, message, &error, r);
864 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
866 changed = bus_properties_changed_new(
867 "/org/freedesktop/timedate1",
868 "org.freedesktop.timedate1",
875 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
877 if (!(reply = dbus_message_new_method_return(message)))
880 if (!dbus_connection_send(connection, reply, NULL))
883 dbus_message_unref(reply);
888 if (!dbus_connection_send(connection, changed, NULL))
891 dbus_message_unref(changed);
894 return DBUS_HANDLER_RESULT_HANDLED;
898 dbus_message_unref(reply);
901 dbus_message_unref(changed);
903 dbus_error_free(&error);
905 return DBUS_HANDLER_RESULT_NEED_MEMORY;
908 static int connect_bus(DBusConnection **_bus) {
909 static const DBusObjectPathVTable timedate_vtable = {
910 .message_function = timedate_message_handler
913 DBusConnection *bus = NULL;
918 dbus_error_init(&error);
920 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
922 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
927 dbus_connection_set_exit_on_disconnect(bus, FALSE);
929 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
930 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
931 log_error("Not enough memory");
936 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
937 if (dbus_error_is_set(&error)) {
938 log_error("Failed to register name on bus: %s", bus_error_message(&error));
943 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
944 log_error("Failed to acquire name.");
955 dbus_connection_close(bus);
956 dbus_connection_unref(bus);
958 dbus_error_free(&error);
963 int main(int argc, char *argv[]) {
965 DBusConnection *bus = NULL;
966 bool exiting = false;
968 log_set_target(LOG_TARGET_AUTO);
969 log_parse_environment();
974 if (argc == 2 && streq(argv[1], "--introspect")) {
975 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
977 fputs(timedate_interface, stdout);
978 fputs("</node>\n", stdout);
983 log_error("This program takes no arguments.");
990 log_error("Failed to read timezone data: %s", strerror(-r));
994 r = connect_bus(&bus);
1000 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1004 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1007 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1010 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1012 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1022 dbus_connection_flush(bus);
1023 dbus_connection_close(bus);
1024 dbus_connection_unref(bus);
1027 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;