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 int read_ntp(DBusConnection *bus) {
307 DBusMessage *m = NULL, *reply = NULL;
308 const char *name = "systemd-timedated-ntp.target", *s;
314 dbus_error_init(&error);
316 m = dbus_message_new_method_call(
317 "org.freedesktop.systemd1",
318 "/org/freedesktop/systemd1",
319 "org.freedesktop.systemd1.Manager",
323 log_error("Out of memory");
328 if (!dbus_message_append_args(m,
329 DBUS_TYPE_STRING, &name,
330 DBUS_TYPE_INVALID)) {
331 log_error("Could not append arguments to message.");
336 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
339 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
340 /* NTP is not installed. */
346 log_error("Failed to issue method call: %s", bus_error_message(&error));
351 if (!dbus_message_get_args(reply, &error,
352 DBUS_TYPE_STRING, &s,
353 DBUS_TYPE_INVALID)) {
354 log_error("Failed to parse reply: %s", bus_error_message(&error));
360 streq(s, "enabled") ||
361 streq(s, "enabled-runtime");
366 dbus_message_unref(m);
369 dbus_message_unref(reply);
371 dbus_error_free(&error);
376 static int start_ntp(DBusConnection *bus, DBusError *error) {
377 DBusMessage *m = NULL, *reply = NULL;
378 const char *name = "systemd-timedated-ntp.target", *mode = "replace";
384 m = dbus_message_new_method_call(
385 "org.freedesktop.systemd1",
386 "/org/freedesktop/systemd1",
387 "org.freedesktop.systemd1.Manager",
388 tz.use_ntp ? "StartUnit" : "StopUnit");
390 log_error("Could not allocate message.");
395 if (!dbus_message_append_args(m,
396 DBUS_TYPE_STRING, &name,
397 DBUS_TYPE_STRING, &mode,
398 DBUS_TYPE_INVALID)) {
399 log_error("Could not append arguments to message.");
404 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
406 log_error("Failed to issue method call: %s", bus_error_message(error));
415 dbus_message_unref(m);
418 dbus_message_unref(reply);
423 static int enable_ntp(DBusConnection *bus, DBusError *error) {
424 DBusMessage *m = NULL, *reply = NULL;
425 const char * const names[] = { "systemd-timedated-ntp.target", NULL };
427 DBusMessageIter iter;
428 dbus_bool_t f = FALSE, t = TRUE;
433 m = dbus_message_new_method_call(
434 "org.freedesktop.systemd1",
435 "/org/freedesktop/systemd1",
436 "org.freedesktop.systemd1.Manager",
437 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
440 log_error("Could not allocate message.");
445 dbus_message_iter_init_append(m, &iter);
447 r = bus_append_strv_iter(&iter, (char**) names);
449 log_error("Failed to append unit files.");
452 /* send runtime bool */
453 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
454 log_error("Failed to append runtime boolean.");
460 /* send force bool */
461 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
462 log_error("Failed to append force boolean.");
468 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
470 log_error("Failed to issue method call: %s", bus_error_message(error));
475 dbus_message_unref(m);
476 m = dbus_message_new_method_call(
477 "org.freedesktop.systemd1",
478 "/org/freedesktop/systemd1",
479 "org.freedesktop.systemd1.Manager",
482 log_error("Could not allocate message.");
487 dbus_message_unref(reply);
488 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
490 log_error("Failed to issue method call: %s", bus_error_message(error));
499 dbus_message_unref(m);
502 dbus_message_unref(reply);
507 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
515 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
521 static const BusProperty bus_timedate_properties[] = {
522 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
523 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
524 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
528 static const BusBoundProperties bps[] = {
529 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
533 static DBusHandlerResult timedate_message_handler(
534 DBusConnection *connection,
535 DBusMessage *message,
538 DBusMessage *reply = NULL, *changed = NULL;
545 dbus_error_init(&error);
547 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
549 dbus_bool_t interactive;
551 if (!dbus_message_get_args(
554 DBUS_TYPE_STRING, &z,
555 DBUS_TYPE_BOOLEAN, &interactive,
557 return bus_send_error_reply(connection, message, &error, -EINVAL);
559 if (!valid_timezone(z))
560 return bus_send_error_reply(connection, message, NULL, -EINVAL);
562 if (!streq_ptr(z, tz.zone)) {
565 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
567 return bus_send_error_reply(connection, message, &error, r);
576 /* 1. Write new configuration file */
577 r = write_data_timezone();
579 log_error("Failed to set timezone: %s", strerror(-r));
580 return bus_send_error_reply(connection, message, NULL, r);
587 /* 2. Teach kernel new timezone */
588 hwclock_apply_localtime_delta(NULL);
590 /* 3. Sync RTC from system clock, with the new delta */
591 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
592 assert_se(tm = localtime(&ts.tv_sec));
593 hwclock_set_time(tm);
596 log_info("Changed timezone to '%s'.", tz.zone);
598 changed = bus_properties_changed_new(
599 "/org/freedesktop/timedate1",
600 "org.freedesktop.timedate1",
606 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
608 dbus_bool_t fix_system;
609 dbus_bool_t interactive;
611 if (!dbus_message_get_args(
614 DBUS_TYPE_BOOLEAN, &lrtc,
615 DBUS_TYPE_BOOLEAN, &fix_system,
616 DBUS_TYPE_BOOLEAN, &interactive,
618 return bus_send_error_reply(connection, message, &error, -EINVAL);
620 if (lrtc != tz.local_rtc) {
623 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
625 return bus_send_error_reply(connection, message, &error, r);
629 /* 1. Write new configuration file */
630 r = write_data_local_rtc();
632 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
633 return bus_send_error_reply(connection, message, NULL, r);
636 /* 2. Teach kernel new timezone */
638 hwclock_apply_localtime_delta(NULL);
640 hwclock_reset_localtime_delta();
642 /* 3. Synchronize clocks */
643 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
648 /* Sync system clock from RTC; first,
649 * initialize the timezone fields of
652 tm = *localtime(&ts.tv_sec);
654 tm = *gmtime(&ts.tv_sec);
656 /* Override the main fields of
657 * struct tm, but not the timezone
659 if (hwclock_get_time(&tm) >= 0) {
661 /* And set the system clock
664 ts.tv_sec = mktime(&tm);
666 ts.tv_sec = timegm(&tm);
668 clock_settime(CLOCK_REALTIME, &ts);
674 /* Sync RTC from system clock */
676 tm = localtime(&ts.tv_sec);
678 tm = gmtime(&ts.tv_sec);
680 hwclock_set_time(tm);
683 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
685 changed = bus_properties_changed_new(
686 "/org/freedesktop/timedate1",
687 "org.freedesktop.timedate1",
693 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
695 dbus_bool_t relative;
696 dbus_bool_t interactive;
698 if (!dbus_message_get_args(
701 DBUS_TYPE_INT64, &utc,
702 DBUS_TYPE_BOOLEAN, &relative,
703 DBUS_TYPE_BOOLEAN, &interactive,
705 return bus_send_error_reply(connection, message, &error, -EINVAL);
707 if (!relative && utc <= 0)
708 return bus_send_error_reply(connection, message, NULL, -EINVAL);
710 if (!relative || utc != 0) {
714 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
716 return bus_send_error_reply(connection, message, &error, r);
719 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
721 timespec_store(&ts, utc);
723 /* Set system clock */
724 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
725 log_error("Failed to set local time: %m");
726 return bus_send_error_reply(connection, message, NULL, -errno);
729 /* Sync down to RTC */
731 tm = localtime(&ts.tv_sec);
733 tm = gmtime(&ts.tv_sec);
735 hwclock_set_time(tm);
737 log_info("Changed local time to %s", ctime(&ts.tv_sec));
739 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
741 dbus_bool_t interactive;
743 if (!dbus_message_get_args(
746 DBUS_TYPE_BOOLEAN, &ntp,
747 DBUS_TYPE_BOOLEAN, &interactive,
749 return bus_send_error_reply(connection, message, &error, -EINVAL);
751 if (ntp != !!tz.use_ntp) {
753 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
755 return bus_send_error_reply(connection, message, &error, r);
759 r = enable_ntp(connection, &error);
761 return bus_send_error_reply(connection, message, &error, r);
763 r = start_ntp(connection, &error);
765 return bus_send_error_reply(connection, message, &error, r);
767 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
769 changed = bus_properties_changed_new(
770 "/org/freedesktop/timedate1",
771 "org.freedesktop.timedate1",
778 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
780 if (!(reply = dbus_message_new_method_return(message)))
783 if (!dbus_connection_send(connection, reply, NULL))
786 dbus_message_unref(reply);
791 if (!dbus_connection_send(connection, changed, NULL))
794 dbus_message_unref(changed);
797 return DBUS_HANDLER_RESULT_HANDLED;
801 dbus_message_unref(reply);
804 dbus_message_unref(changed);
806 dbus_error_free(&error);
808 return DBUS_HANDLER_RESULT_NEED_MEMORY;
811 static int connect_bus(DBusConnection **_bus) {
812 static const DBusObjectPathVTable timedate_vtable = {
813 .message_function = timedate_message_handler
816 DBusConnection *bus = NULL;
821 dbus_error_init(&error);
823 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
825 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
830 dbus_connection_set_exit_on_disconnect(bus, FALSE);
832 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
833 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
834 log_error("Not enough memory");
839 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
840 if (dbus_error_is_set(&error)) {
841 log_error("Failed to register name on bus: %s", bus_error_message(&error));
846 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
847 log_error("Failed to acquire name.");
858 dbus_connection_close(bus);
859 dbus_connection_unref(bus);
861 dbus_error_free(&error);
866 int main(int argc, char *argv[]) {
868 DBusConnection *bus = NULL;
869 bool exiting = false;
871 log_set_target(LOG_TARGET_AUTO);
872 log_parse_environment();
877 if (argc == 2 && streq(argv[1], "--introspect")) {
878 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
880 fputs(timedate_interface, stdout);
881 fputs("</node>\n", stdout);
886 log_error("This program takes no arguments.");
893 log_error("Failed to read timezone data: %s", strerror(-r));
897 r = connect_bus(&bus);
903 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
907 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
910 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
913 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
915 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
925 dbus_connection_flush(bus);
926 dbus_connection_close(bus);
927 dbus_connection_unref(bus);
930 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;