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);
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);
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 char** get_ntp_services(void) {
306 char **r = NULL, **files, **i;
309 k = conf_files_list(&files, ".list",
310 "/etc/systemd/ntp-units.d",
311 "/run/systemd/ntp-units.d",
312 "/usr/local/lib/systemd/ntp-units.d",
313 "/usr/lib/systemd/ntp-units.d",
318 STRV_FOREACH(i, files) {
326 char line[PATH_MAX], *l, **q;
328 if (!fgets(line, sizeof(line), f)) {
331 log_error("Failed to read NTP units file: %m");
337 if (l[0] == 0 || l[0] == '#')
340 q = strv_append(r, l);
358 static int read_ntp(DBusConnection *bus) {
359 DBusMessage *m = NULL, *reply = NULL;
366 dbus_error_init(&error);
368 l = get_ntp_services();
373 dbus_message_unref(m);
374 m = dbus_message_new_method_call(
375 "org.freedesktop.systemd1",
376 "/org/freedesktop/systemd1",
377 "org.freedesktop.systemd1.Manager",
384 if (!dbus_message_append_args(m,
386 DBUS_TYPE_INVALID)) {
392 dbus_message_unref(reply);
393 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
395 if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
396 /* This implementation does not exist, try next one */
397 dbus_error_free(&error);
401 log_error("Failed to issue method call: %s", bus_error_message(&error));
406 if (!dbus_message_get_args(reply, &error,
407 DBUS_TYPE_STRING, &s,
408 DBUS_TYPE_INVALID)) {
409 log_error("Failed to parse reply: %s", bus_error_message(&error));
415 streq(s, "enabled") ||
416 streq(s, "enabled-runtime");
421 /* NTP is not installed. */
427 dbus_message_unref(m);
430 dbus_message_unref(reply);
434 dbus_error_free(&error);
439 static int start_ntp(DBusConnection *bus, DBusError *error) {
440 DBusMessage *m = NULL, *reply = NULL;
441 const char *mode = "replace";
448 l = get_ntp_services();
451 dbus_message_unref(m);
452 m = dbus_message_new_method_call(
453 "org.freedesktop.systemd1",
454 "/org/freedesktop/systemd1",
455 "org.freedesktop.systemd1.Manager",
456 tz.use_ntp ? "StartUnit" : "StopUnit");
458 log_error("Could not allocate message.");
463 if (!dbus_message_append_args(m,
465 DBUS_TYPE_STRING, &mode,
466 DBUS_TYPE_INVALID)) {
467 log_error("Could not append arguments to message.");
473 dbus_message_unref(reply);
474 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
476 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
477 streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
478 streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
479 /* This implementation does not exist, try next one */
480 dbus_error_free(error);
484 log_error("Failed to issue method call: %s", bus_error_message(error));
493 /* No implementaiton available... */
498 dbus_message_unref(m);
501 dbus_message_unref(reply);
508 static int enable_ntp(DBusConnection *bus, DBusError *error) {
509 DBusMessage *m = NULL, *reply = NULL;
511 DBusMessageIter iter;
512 dbus_bool_t f = FALSE, t = TRUE;
518 l = get_ntp_services();
523 dbus_message_unref(m);
524 m = dbus_message_new_method_call(
525 "org.freedesktop.systemd1",
526 "/org/freedesktop/systemd1",
527 "org.freedesktop.systemd1.Manager",
528 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
530 log_error("Could not allocate message.");
535 dbus_message_iter_init_append(m, &iter);
540 r = bus_append_strv_iter(&iter, k);
542 log_error("Failed to append unit files.");
546 /* send runtime bool */
547 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
548 log_error("Failed to append runtime boolean.");
554 /* send force bool */
555 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
556 log_error("Failed to append force boolean.");
563 dbus_message_unref(reply);
564 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
566 if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
567 /* This implementation does not exist, try next one */
568 dbus_error_free(error);
572 log_error("Failed to issue method call: %s", bus_error_message(error));
577 dbus_message_unref(m);
578 m = dbus_message_new_method_call(
579 "org.freedesktop.systemd1",
580 "/org/freedesktop/systemd1",
581 "org.freedesktop.systemd1.Manager",
584 log_error("Could not allocate message.");
589 dbus_message_unref(reply);
590 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
592 log_error("Failed to issue method call: %s", bus_error_message(error));
605 dbus_message_unref(m);
608 dbus_message_unref(reply);
615 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
623 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
629 static const BusProperty bus_timedate_properties[] = {
630 { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true },
631 { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) },
632 { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) },
636 static const BusBoundProperties bps[] = {
637 { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
641 static DBusHandlerResult timedate_message_handler(
642 DBusConnection *connection,
643 DBusMessage *message,
646 DBusMessage *reply = NULL, *changed = NULL;
653 dbus_error_init(&error);
655 if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
657 dbus_bool_t interactive;
659 if (!dbus_message_get_args(
662 DBUS_TYPE_STRING, &z,
663 DBUS_TYPE_BOOLEAN, &interactive,
665 return bus_send_error_reply(connection, message, &error, -EINVAL);
667 if (!valid_timezone(z))
668 return bus_send_error_reply(connection, message, NULL, -EINVAL);
670 if (!streq_ptr(z, tz.zone)) {
673 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
675 return bus_send_error_reply(connection, message, &error, r);
684 /* 1. Write new configuration file */
685 r = write_data_timezone();
687 log_error("Failed to set timezone: %s", strerror(-r));
688 return bus_send_error_reply(connection, message, NULL, r);
695 /* 2. Teach kernel new timezone */
696 hwclock_apply_localtime_delta(NULL);
698 /* 3. Sync RTC from system clock, with the new delta */
699 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
700 assert_se(tm = localtime(&ts.tv_sec));
701 hwclock_set_time(tm);
704 log_info("Changed timezone to '%s'.", tz.zone);
706 changed = bus_properties_changed_new(
707 "/org/freedesktop/timedate1",
708 "org.freedesktop.timedate1",
714 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
716 dbus_bool_t fix_system;
717 dbus_bool_t interactive;
719 if (!dbus_message_get_args(
722 DBUS_TYPE_BOOLEAN, &lrtc,
723 DBUS_TYPE_BOOLEAN, &fix_system,
724 DBUS_TYPE_BOOLEAN, &interactive,
726 return bus_send_error_reply(connection, message, &error, -EINVAL);
728 if (lrtc != tz.local_rtc) {
731 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
733 return bus_send_error_reply(connection, message, &error, r);
737 /* 1. Write new configuration file */
738 r = write_data_local_rtc();
740 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
741 return bus_send_error_reply(connection, message, NULL, r);
744 /* 2. Teach kernel new timezone */
746 hwclock_apply_localtime_delta(NULL);
748 hwclock_reset_localtime_delta();
750 /* 3. Synchronize clocks */
751 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
756 /* Sync system clock from RTC; first,
757 * initialize the timezone fields of
760 tm = *localtime(&ts.tv_sec);
762 tm = *gmtime(&ts.tv_sec);
764 /* Override the main fields of
765 * struct tm, but not the timezone
767 if (hwclock_get_time(&tm) >= 0) {
769 /* And set the system clock
772 ts.tv_sec = mktime(&tm);
774 ts.tv_sec = timegm(&tm);
776 clock_settime(CLOCK_REALTIME, &ts);
782 /* Sync RTC from system clock */
784 tm = localtime(&ts.tv_sec);
786 tm = gmtime(&ts.tv_sec);
788 hwclock_set_time(tm);
791 log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
793 changed = bus_properties_changed_new(
794 "/org/freedesktop/timedate1",
795 "org.freedesktop.timedate1",
801 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
803 dbus_bool_t relative;
804 dbus_bool_t interactive;
806 if (!dbus_message_get_args(
809 DBUS_TYPE_INT64, &utc,
810 DBUS_TYPE_BOOLEAN, &relative,
811 DBUS_TYPE_BOOLEAN, &interactive,
813 return bus_send_error_reply(connection, message, &error, -EINVAL);
815 if (!relative && utc <= 0)
816 return bus_send_error_reply(connection, message, NULL, -EINVAL);
818 if (!relative || utc != 0) {
822 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
824 return bus_send_error_reply(connection, message, &error, r);
827 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
829 timespec_store(&ts, utc);
831 /* Set system clock */
832 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
833 log_error("Failed to set local time: %m");
834 return bus_send_error_reply(connection, message, NULL, -errno);
837 /* Sync down to RTC */
839 tm = localtime(&ts.tv_sec);
841 tm = gmtime(&ts.tv_sec);
843 hwclock_set_time(tm);
845 log_info("Changed local time to %s", ctime(&ts.tv_sec));
847 } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
849 dbus_bool_t interactive;
851 if (!dbus_message_get_args(
854 DBUS_TYPE_BOOLEAN, &ntp,
855 DBUS_TYPE_BOOLEAN, &interactive,
857 return bus_send_error_reply(connection, message, &error, -EINVAL);
859 if (ntp != !!tz.use_ntp) {
861 r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
863 return bus_send_error_reply(connection, message, &error, r);
867 r = enable_ntp(connection, &error);
869 return bus_send_error_reply(connection, message, &error, r);
871 r = start_ntp(connection, &error);
873 return bus_send_error_reply(connection, message, &error, r);
875 log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
877 changed = bus_properties_changed_new(
878 "/org/freedesktop/timedate1",
879 "org.freedesktop.timedate1",
886 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
888 if (!(reply = dbus_message_new_method_return(message)))
891 if (!dbus_connection_send(connection, reply, NULL))
894 dbus_message_unref(reply);
899 if (!dbus_connection_send(connection, changed, NULL))
902 dbus_message_unref(changed);
905 return DBUS_HANDLER_RESULT_HANDLED;
909 dbus_message_unref(reply);
912 dbus_message_unref(changed);
914 dbus_error_free(&error);
916 return DBUS_HANDLER_RESULT_NEED_MEMORY;
919 static int connect_bus(DBusConnection **_bus) {
920 static const DBusObjectPathVTable timedate_vtable = {
921 .message_function = timedate_message_handler
924 DBusConnection *bus = NULL;
929 dbus_error_init(&error);
931 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
933 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
938 dbus_connection_set_exit_on_disconnect(bus, FALSE);
940 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
941 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
946 r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
947 if (dbus_error_is_set(&error)) {
948 log_error("Failed to register name on bus: %s", bus_error_message(&error));
953 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
954 log_error("Failed to acquire name.");
965 dbus_connection_close(bus);
966 dbus_connection_unref(bus);
968 dbus_error_free(&error);
973 int main(int argc, char *argv[]) {
975 DBusConnection *bus = NULL;
976 bool exiting = false;
978 log_set_target(LOG_TARGET_AUTO);
979 log_parse_environment();
984 if (argc == 2 && streq(argv[1], "--introspect")) {
985 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
987 fputs(timedate_interface, stdout);
988 fputs("</node>\n", stdout);
993 log_error("This program takes no arguments.");
1000 log_error("Failed to read timezone data: %s", strerror(-r));
1004 r = connect_bus(&bus);
1010 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1014 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1017 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1020 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1022 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
1032 dbus_connection_flush(bus);
1033 dbus_connection_close(bus);
1034 dbus_connection_unref(bus);
1037 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;