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 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
35 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
38 " <interface name=\"org.freedesktop.timedate1\">\n" \
39 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
40 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
41 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
42 " <method name=\"SetTime\">\n" \
43 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
44 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
45 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
47 " <method name=\"SetTimezone\">\n" \
48 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
49 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51 " <method name=\"SetLocalRTC\">\n" \
52 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
53 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
54 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
56 " <method name=\"SetNTP\">\n" \
57 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
58 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
62 #define INTROSPECTION \
63 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
66 BUS_PROPERTIES_INTERFACE \
67 BUS_INTROSPECTABLE_INTERFACE \
71 #define INTERFACES_LIST \
72 BUS_GENERIC_INTERFACES_LIST \
73 "org.freedesktop.timedate1\0"
75 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
87 static usec_t remain_until;
89 static void free_data(void) {
96 static bool valid_timezone(const char *name) {
105 if (*name == '/' || *name == 0)
108 for (p = name; *p; p++) {
109 if (!(*p >= '0' && *p <= '9') &&
110 !(*p >= 'a' && *p <= 'z') &&
111 !(*p >= 'A' && *p <= 'Z') &&
112 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
128 t = strappend("/usr/share/zoneinfo/", name);
138 if (!S_ISREG(st.st_mode))
144 static void verify_timezone(void) {
145 char *p, *a = NULL, *b = NULL;
152 p = strappend("/usr/share/zoneinfo/", tz.zone);
154 log_error("Out of memory");
158 j = read_full_file("/etc/localtime", &a, &l);
159 k = read_full_file(p, &b, &q);
163 if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
164 log_warning("/etc/localtime and /etc/timezone out of sync.");
173 static int read_data(void) {
178 r = read_one_line_file("/etc/timezone", &tz.zone);
181 log_warning("Failed to read /etc/timezone: %s", strerror(-r));
184 r = parse_env_file("/etc/sysconfig/clock", NEWLINE,
188 if (r < 0 && r != -ENOENT)
189 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r));
193 if (isempty(tz.zone)) {
200 tz.local_rtc = hwclock_is_localtime() > 0;
205 static int write_data_timezone(void) {
210 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
213 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
219 p = strappend("/usr/share/zoneinfo/", tz.zone);
221 log_error("Out of memory");
225 r = symlink_or_copy_atomic(p, "/etc/localtime");
231 r = write_one_line_file_atomic("/etc/timezone", tz.zone);
238 static int write_data_local_rtc(void) {
242 r = read_full_file("/etc/adjtime", &s, NULL);
250 w = strdup(NULL_ADJTIME_LOCAL);
263 p = strchr(p+1, '\n');
279 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
285 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
287 if (streq(w, NULL_ADJTIME_UTC)) {
290 if (unlink("/etc/adjtime") < 0) {
299 r = write_one_line_file_atomic("/etc/adjtime", w);
305 static int read_ntp(DBusConnection *bus) {
306 DBusMessage *m = NULL, *reply = NULL;
307 const char *name = "systemd-timedated-ntp.target", *s;
313 dbus_error_init(&error);
315 m = dbus_message_new_method_call(
316 "org.freedesktop.systemd1",
317 "/org/freedesktop/systemd1",
318 "org.freedesktop.systemd1.Manager",
322 log_error("Out of memory");
327 if (!dbus_message_append_args(m,
328 DBUS_TYPE_STRING, &name,
329 DBUS_TYPE_INVALID)) {
330 log_error("Could not append arguments to message.");
335 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
338 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
339 /* NTP is not installed. */
345 log_error("Failed to issue method call: %s", bus_error_message(&error));
350 if (!dbus_message_get_args(reply, &error,
351 DBUS_TYPE_STRING, &s,
352 DBUS_TYPE_INVALID)) {
353 log_error("Failed to parse reply: %s", bus_error_message(&error));
359 streq(s, "enabled") ||
360 streq(s, "enabled-runtime");
365 dbus_message_unref(m);
368 dbus_message_unref(reply);
370 dbus_error_free(&error);
375 static int start_ntp(DBusConnection *bus, DBusError *error) {
376 DBusMessage *m = NULL, *reply = NULL;
377 const char *name = "systemd-timedated-ntp.target", *mode = "replace";
383 m = dbus_message_new_method_call(
384 "org.freedesktop.systemd1",
385 "/org/freedesktop/systemd1",
386 "org.freedesktop.systemd1.Manager",
387 tz.use_ntp ? "StartUnit" : "StopUnit");
389 log_error("Could not allocate message.");
394 if (!dbus_message_append_args(m,
395 DBUS_TYPE_STRING, &name,
396 DBUS_TYPE_STRING, &mode,
397 DBUS_TYPE_INVALID)) {
398 log_error("Could not append arguments to message.");
403 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
405 log_error("Failed to issue method call: %s", bus_error_message(error));
414 dbus_message_unref(m);
417 dbus_message_unref(reply);
422 static int enable_ntp(DBusConnection *bus, DBusError *error) {
423 DBusMessage *m = NULL, *reply = NULL;
424 const char * const names[] = { "systemd-timedated-ntp.target", NULL };
426 DBusMessageIter iter;
427 dbus_bool_t f = FALSE, t = TRUE;
432 m = dbus_message_new_method_call(
433 "org.freedesktop.systemd1",
434 "/org/freedesktop/systemd1",
435 "org.freedesktop.systemd1.Manager",
436 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
439 log_error("Could not allocate message.");
444 dbus_message_iter_init_append(m, &iter);
446 r = bus_append_strv_iter(&iter, (char**) names);
448 log_error("Failed to append unit files.");
451 /* send runtime bool */
452 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
453 log_error("Failed to append runtime boolean.");
459 /* send force bool */
460 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
461 log_error("Failed to append force boolean.");
467 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
469 log_error("Failed to issue method call: %s", bus_error_message(error));
474 dbus_message_unref(m);
475 m = dbus_message_new_method_call(
476 "org.freedesktop.systemd1",
477 "/org/freedesktop/systemd1",
478 "org.freedesktop.systemd1.Manager",
481 log_error("Could not allocate message.");
486 dbus_message_unref(reply);
487 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
489 log_error("Failed to issue method call: %s", bus_error_message(error));
498 dbus_message_unref(m);
501 dbus_message_unref(reply);
506 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
514 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
520 static const BusProperty bus_timedate_properties[] = {
521 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
522 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
523 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
527 static const BusBoundProperties bps[] = {
528 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
532 static DBusHandlerResult timedate_message_handler(
533 DBusConnection *connection,
534 DBusMessage *message,
537 DBusMessage *reply = NULL, *changed = NULL;
544 dbus_error_init(&error);
546 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
548 dbus_bool_t interactive;
550 if (!dbus_message_get_args(
553 DBUS_TYPE_STRING, &z,
554 DBUS_TYPE_BOOLEAN, &interactive,
556 return bus_send_error_reply(connection, message, &error, -EINVAL);
558 if (!valid_timezone(z))
559 return bus_send_error_reply(connection, message, NULL, -EINVAL);
561 if (!streq_ptr(z, tz.zone)) {
564 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
566 return bus_send_error_reply(connection, message, &error, r);
575 /* 1. Write new configuration file */
576 r = write_data_timezone();
578 log_error("Failed to set timezone: %s", strerror(-r));
579 return bus_send_error_reply(connection, message, NULL, r);
586 /* 2. Teach kernel new timezone */
587 hwclock_apply_localtime_delta(NULL);
589 /* 3. Sync RTC from system clock, with the new delta */
590 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
591 assert_se(tm = localtime(&ts.tv_sec));
592 hwclock_set_time(tm);
595 log_info("Changed timezone to '%s'.", tz.zone);
597 changed = bus_properties_changed_new(
598 "/org/freedesktop/timedate1",
599 "org.freedesktop.timedate1",
605 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
607 dbus_bool_t fix_system;
608 dbus_bool_t interactive;
610 if (!dbus_message_get_args(
613 DBUS_TYPE_BOOLEAN, &lrtc,
614 DBUS_TYPE_BOOLEAN, &fix_system,
615 DBUS_TYPE_BOOLEAN, &interactive,
617 return bus_send_error_reply(connection, message, &error, -EINVAL);
619 if (lrtc != tz.local_rtc) {
622 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
624 return bus_send_error_reply(connection, message, &error, r);
628 /* 1. Write new configuration file */
629 r = write_data_local_rtc();
631 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
632 return bus_send_error_reply(connection, message, NULL, r);
635 /* 2. Teach kernel new timezone */
637 hwclock_apply_localtime_delta(NULL);
639 hwclock_reset_localtime_delta();
641 /* 3. Synchronize clocks */
642 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
647 /* Sync system clock from RTC; first,
648 * initialize the timezone fields of
651 tm = *localtime(&ts.tv_sec);
653 tm = *gmtime(&ts.tv_sec);
655 /* Override the main fields of
656 * struct tm, but not the timezone
658 if (hwclock_get_time(&tm) >= 0) {
660 /* And set the system clock
663 ts.tv_sec = mktime(&tm);
665 ts.tv_sec = timegm(&tm);
667 clock_settime(CLOCK_REALTIME, &ts);
673 /* Sync RTC from system clock */
675 tm = localtime(&ts.tv_sec);
677 tm = gmtime(&ts.tv_sec);
679 hwclock_set_time(tm);
682 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
684 changed = bus_properties_changed_new(
685 "/org/freedesktop/timedate1",
686 "org.freedesktop.timedate1",
692 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
694 dbus_bool_t relative;
695 dbus_bool_t interactive;
697 if (!dbus_message_get_args(
700 DBUS_TYPE_INT64, &utc,
701 DBUS_TYPE_BOOLEAN, &relative,
702 DBUS_TYPE_BOOLEAN, &interactive,
704 return bus_send_error_reply(connection, message, &error, -EINVAL);
706 if (!relative && utc <= 0)
707 return bus_send_error_reply(connection, message, NULL, -EINVAL);
709 if (!relative || utc != 0) {
713 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
715 return bus_send_error_reply(connection, message, &error, r);
718 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
720 timespec_store(&ts, utc);
722 /* Set system clock */
723 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
724 log_error("Failed to set local time: %m");
725 return bus_send_error_reply(connection, message, NULL, -errno);
728 /* Sync down to RTC */
730 tm = localtime(&ts.tv_sec);
732 tm = gmtime(&ts.tv_sec);
734 hwclock_set_time(tm);
736 log_info("Changed local time to %s", ctime(&ts.tv_sec));
738 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
740 dbus_bool_t interactive;
742 if (!dbus_message_get_args(
745 DBUS_TYPE_BOOLEAN, &ntp,
746 DBUS_TYPE_BOOLEAN, &interactive,
748 return bus_send_error_reply(connection, message, &error, -EINVAL);
750 if (ntp != !!tz.use_ntp) {
752 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
754 return bus_send_error_reply(connection, message, &error, r);
758 r = enable_ntp(connection, &error);
760 return bus_send_error_reply(connection, message, &error, r);
762 r = start_ntp(connection, &error);
764 return bus_send_error_reply(connection, message, &error, r);
766 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
768 changed = bus_properties_changed_new(
769 "/org/freedesktop/timedate1",
770 "org.freedesktop.timedate1",
777 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
779 if (!(reply = dbus_message_new_method_return(message)))
782 if (!dbus_connection_send(connection, reply, NULL))
785 dbus_message_unref(reply);
790 if (!dbus_connection_send(connection, changed, NULL))
793 dbus_message_unref(changed);
796 return DBUS_HANDLER_RESULT_HANDLED;
800 dbus_message_unref(reply);
803 dbus_message_unref(changed);
805 dbus_error_free(&error);
807 return DBUS_HANDLER_RESULT_NEED_MEMORY;
810 static int connect_bus(DBusConnection **_bus) {
811 static const DBusObjectPathVTable timedate_vtable = {
812 .message_function = timedate_message_handler
815 DBusConnection *bus = NULL;
820 dbus_error_init(&error);
822 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
824 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
829 dbus_connection_set_exit_on_disconnect(bus, FALSE);
831 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
832 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
833 log_error("Not enough memory");
838 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
839 if (dbus_error_is_set(&error)) {
840 log_error("Failed to register name on bus: %s", bus_error_message(&error));
845 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
846 log_error("Failed to acquire name.");
857 dbus_connection_close(bus);
858 dbus_connection_unref(bus);
860 dbus_error_free(&error);
865 int main(int argc, char *argv[]) {
867 DBusConnection *bus = NULL;
868 bool exiting = false;
870 log_set_target(LOG_TARGET_AUTO);
871 log_parse_environment();
876 if (argc == 2 && streq(argv[1], "--introspect")) {
877 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
879 fputs(timedate_interface, stdout);
880 fputs("</node>\n", stdout);
885 log_error("This program takes no arguments.");
892 log_error("Failed to read timezone data: %s", strerror(-r));
896 r = connect_bus(&bus);
902 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
906 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
909 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
912 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
914 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
924 dbus_connection_flush(bus);
925 dbus_connection_close(bus);
926 dbus_connection_unref(bus);
929 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;