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>
28 #include "systemd/sd-id128.h"
29 #include "systemd/sd-messages.h"
32 #include "dbus-common.h"
36 #include "conf-files.h"
38 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
39 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
42 " <interface name=\"org.freedesktop.timedate1\">\n" \
43 " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
45 " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
46 " <method name=\"SetTime\">\n" \
47 " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
48 " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
49 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51 " <method name=\"SetTimezone\">\n" \
52 " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
53 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
55 " <method name=\"SetLocalRTC\">\n" \
56 " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
57 " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
58 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
60 " <method name=\"SetNTP\">\n" \
61 " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
62 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
66 #define INTROSPECTION \
67 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
70 BUS_PROPERTIES_INTERFACE \
71 BUS_INTROSPECTABLE_INTERFACE \
75 #define INTERFACES_LIST \
76 BUS_GENERIC_INTERFACES_LIST \
77 "org.freedesktop.timedate1\0"
79 const char timedate_interface
[] _introspect_("timedate1") = INTERFACE
;
91 static usec_t remain_until
;
93 static void free_data(void) {
100 static bool valid_timezone(const char *name
) {
109 if (*name
== '/' || *name
== 0)
112 for (p
= name
; *p
; p
++) {
113 if (!(*p
>= '0' && *p
<= '9') &&
114 !(*p
>= 'a' && *p
<= 'z') &&
115 !(*p
>= 'A' && *p
<= 'Z') &&
116 !(*p
== '-' || *p
== '_' || *p
== '+' || *p
== '/'))
132 t
= strappend("/usr/share/zoneinfo/", name
);
142 if (!S_ISREG(st
.st_mode
))
148 static void verify_timezone(void) {
149 char *p
, *a
= NULL
, *b
= NULL
;
156 p
= strappend("/usr/share/zoneinfo/", tz
.zone
);
162 j
= read_full_file("/etc/localtime", &a
, &l
);
163 k
= read_full_file(p
, &b
, &q
);
167 if (j
< 0 || k
< 0 || l
!= q
|| memcmp(a
, b
, l
)) {
168 log_warning("/etc/localtime and /etc/timezone out of sync.");
177 static int read_data(void) {
182 r
= read_one_line_file("/etc/timezone", &tz
.zone
);
185 log_warning("Failed to read /etc/timezone: %s", strerror(-r
));
188 r
= parse_env_file("/etc/sysconfig/clock", NEWLINE
,
192 if (r
< 0 && r
!= -ENOENT
)
193 log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r
));
197 if (isempty(tz
.zone
)) {
204 tz
.local_rtc
= hwclock_is_localtime() > 0;
209 static int write_data_timezone(void) {
214 if (unlink("/etc/timezone") < 0 && errno
!= ENOENT
)
217 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
223 p
= strappend("/usr/share/zoneinfo/", tz
.zone
);
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
);
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",
386 if (!dbus_message_append_args(m
,
388 DBUS_TYPE_INVALID
)) {
394 dbus_message_unref(reply
);
395 reply
= dbus_connection_send_with_reply_and_block(bus
, m
, -1, &error
);
397 if (streq(error
.name
, "org.freedesktop.DBus.Error.FileNotFound")) {
398 /* This implementation does not exist, try next one */
399 dbus_error_free(&error
);
403 log_error("Failed to issue method call: %s", bus_error_message(&error
));
408 if (!dbus_message_get_args(reply
, &error
,
409 DBUS_TYPE_STRING
, &s
,
410 DBUS_TYPE_INVALID
)) {
411 log_error("Failed to parse reply: %s", bus_error_message(&error
));
417 streq(s
, "enabled") ||
418 streq(s
, "enabled-runtime");
423 /* NTP is not installed. */
429 dbus_message_unref(m
);
432 dbus_message_unref(reply
);
436 dbus_error_free(&error
);
441 static int start_ntp(DBusConnection
*bus
, DBusError
*error
) {
442 DBusMessage
*m
= NULL
, *reply
= NULL
;
443 const char *mode
= "replace";
450 l
= get_ntp_services();
453 dbus_message_unref(m
);
454 m
= dbus_message_new_method_call(
455 "org.freedesktop.systemd1",
456 "/org/freedesktop/systemd1",
457 "org.freedesktop.systemd1.Manager",
458 tz
.use_ntp
? "StartUnit" : "StopUnit");
460 log_error("Could not allocate message.");
465 if (!dbus_message_append_args(m
,
467 DBUS_TYPE_STRING
, &mode
,
468 DBUS_TYPE_INVALID
)) {
469 log_error("Could not append arguments to message.");
475 dbus_message_unref(reply
);
476 reply
= dbus_connection_send_with_reply_and_block(bus
, m
, -1, error
);
478 if (streq(error
->name
, "org.freedesktop.DBus.Error.FileNotFound") ||
479 streq(error
->name
, "org.freedesktop.systemd1.LoadFailed") ||
480 streq(error
->name
, "org.freedesktop.systemd1.NoSuchUnit")) {
481 /* This implementation does not exist, try next one */
482 dbus_error_free(error
);
486 log_error("Failed to issue method call: %s", bus_error_message(error
));
495 /* No implementaiton available... */
500 dbus_message_unref(m
);
503 dbus_message_unref(reply
);
510 static int enable_ntp(DBusConnection
*bus
, DBusError
*error
) {
511 DBusMessage
*m
= NULL
, *reply
= NULL
;
513 DBusMessageIter iter
;
514 dbus_bool_t f
= FALSE
, t
= TRUE
;
520 l
= get_ntp_services();
525 dbus_message_unref(m
);
526 m
= dbus_message_new_method_call(
527 "org.freedesktop.systemd1",
528 "/org/freedesktop/systemd1",
529 "org.freedesktop.systemd1.Manager",
530 tz
.use_ntp
? "EnableUnitFiles" : "DisableUnitFiles");
532 log_error("Could not allocate message.");
537 dbus_message_iter_init_append(m
, &iter
);
542 r
= bus_append_strv_iter(&iter
, k
);
544 log_error("Failed to append unit files.");
548 /* send runtime bool */
549 if (!dbus_message_iter_append_basic(&iter
, DBUS_TYPE_BOOLEAN
, &f
)) {
550 log_error("Failed to append runtime boolean.");
556 /* send force bool */
557 if (!dbus_message_iter_append_basic(&iter
, DBUS_TYPE_BOOLEAN
, &t
)) {
558 log_error("Failed to append force boolean.");
565 dbus_message_unref(reply
);
566 reply
= dbus_connection_send_with_reply_and_block(bus
, m
, -1, error
);
568 if (streq(error
->name
, "org.freedesktop.DBus.Error.FileNotFound")) {
569 /* This implementation does not exist, try next one */
570 dbus_error_free(error
);
574 log_error("Failed to issue method call: %s", bus_error_message(error
));
579 dbus_message_unref(m
);
580 m
= dbus_message_new_method_call(
581 "org.freedesktop.systemd1",
582 "/org/freedesktop/systemd1",
583 "org.freedesktop.systemd1.Manager",
586 log_error("Could not allocate message.");
591 dbus_message_unref(reply
);
592 reply
= dbus_connection_send_with_reply_and_block(bus
, m
, -1, error
);
594 log_error("Failed to issue method call: %s", bus_error_message(error
));
607 dbus_message_unref(m
);
610 dbus_message_unref(reply
);
617 static int property_append_ntp(DBusMessageIter
*i
, const char *property
, void *data
) {
625 if (!dbus_message_iter_append_basic(i
, DBUS_TYPE_BOOLEAN
, &db
))
631 static const BusProperty bus_timedate_properties
[] = {
632 { "Timezone", bus_property_append_string
, "s", offsetof(TZ
, zone
), true },
633 { "LocalRTC", bus_property_append_bool
, "b", offsetof(TZ
, local_rtc
) },
634 { "NTP", property_append_ntp
, "b", offsetof(TZ
, use_ntp
) },
638 static const BusBoundProperties bps
[] = {
639 { "org.freedesktop.timedate1", bus_timedate_properties
, &tz
},
643 static DBusHandlerResult
timedate_message_handler(
644 DBusConnection
*connection
,
645 DBusMessage
*message
,
648 DBusMessage
*reply
= NULL
, *changed
= NULL
;
655 dbus_error_init(&error
);
657 if (dbus_message_is_method_call(message
, "org.freedesktop.timedate1", "SetTimezone")) {
659 dbus_bool_t interactive
;
661 if (!dbus_message_get_args(
664 DBUS_TYPE_STRING
, &z
,
665 DBUS_TYPE_BOOLEAN
, &interactive
,
667 return bus_send_error_reply(connection
, message
, &error
, -EINVAL
);
669 if (!valid_timezone(z
))
670 return bus_send_error_reply(connection
, message
, NULL
, -EINVAL
);
672 if (!streq_ptr(z
, tz
.zone
)) {
675 r
= verify_polkit(connection
, message
, "org.freedesktop.timedate1.set-timezone", interactive
, NULL
, &error
);
677 return bus_send_error_reply(connection
, message
, &error
, r
);
686 /* 1. Write new configuration file */
687 r
= write_data_timezone();
689 log_error("Failed to set timezone: %s", strerror(-r
));
690 return bus_send_error_reply(connection
, message
, NULL
, r
);
697 /* 2. Teach kernel new timezone */
698 hwclock_apply_localtime_delta(NULL
);
700 /* 3. Sync RTC from system clock, with the new delta */
701 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
702 assert_se(tm
= localtime(&ts
.tv_sec
));
703 hwclock_set_time(tm
);
707 "MESSAGE_ID=" SD_ID128_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIMEZONE_CHANGE
),
708 "TIMEZONE=%s", tz
.zone
,
709 "MESSAGE=Changed timezone to '%s'.", tz
.zone
,
712 changed
= bus_properties_changed_new(
713 "/org/freedesktop/timedate1",
714 "org.freedesktop.timedate1",
720 } else if (dbus_message_is_method_call(message
, "org.freedesktop.timedate1", "SetLocalRTC")) {
722 dbus_bool_t fix_system
;
723 dbus_bool_t interactive
;
725 if (!dbus_message_get_args(
728 DBUS_TYPE_BOOLEAN
, &lrtc
,
729 DBUS_TYPE_BOOLEAN
, &fix_system
,
730 DBUS_TYPE_BOOLEAN
, &interactive
,
732 return bus_send_error_reply(connection
, message
, &error
, -EINVAL
);
734 if (lrtc
!= tz
.local_rtc
) {
737 r
= verify_polkit(connection
, message
, "org.freedesktop.timedate1.set-local-rtc", interactive
, NULL
, &error
);
739 return bus_send_error_reply(connection
, message
, &error
, r
);
743 /* 1. Write new configuration file */
744 r
= write_data_local_rtc();
746 log_error("Failed to set RTC to local/UTC: %s", strerror(-r
));
747 return bus_send_error_reply(connection
, message
, NULL
, r
);
750 /* 2. Teach kernel new timezone */
752 hwclock_apply_localtime_delta(NULL
);
754 hwclock_reset_localtime_delta();
756 /* 3. Synchronize clocks */
757 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
762 /* Sync system clock from RTC; first,
763 * initialize the timezone fields of
766 tm
= *localtime(&ts
.tv_sec
);
768 tm
= *gmtime(&ts
.tv_sec
);
770 /* Override the main fields of
771 * struct tm, but not the timezone
773 if (hwclock_get_time(&tm
) >= 0) {
775 /* And set the system clock
778 ts
.tv_sec
= mktime(&tm
);
780 ts
.tv_sec
= timegm(&tm
);
782 clock_settime(CLOCK_REALTIME
, &ts
);
788 /* Sync RTC from system clock */
790 tm
= localtime(&ts
.tv_sec
);
792 tm
= gmtime(&ts
.tv_sec
);
794 hwclock_set_time(tm
);
797 log_info("RTC configured to %s time.", tz
.local_rtc
? "local" : "UTC");
799 changed
= bus_properties_changed_new(
800 "/org/freedesktop/timedate1",
801 "org.freedesktop.timedate1",
807 } else if (dbus_message_is_method_call(message
, "org.freedesktop.timedate1", "SetTime")) {
809 dbus_bool_t relative
;
810 dbus_bool_t interactive
;
812 if (!dbus_message_get_args(
815 DBUS_TYPE_INT64
, &utc
,
816 DBUS_TYPE_BOOLEAN
, &relative
,
817 DBUS_TYPE_BOOLEAN
, &interactive
,
819 return bus_send_error_reply(connection
, message
, &error
, -EINVAL
);
821 if (!relative
&& utc
<= 0)
822 return bus_send_error_reply(connection
, message
, NULL
, -EINVAL
);
824 if (!relative
|| utc
!= 0) {
828 r
= verify_polkit(connection
, message
, "org.freedesktop.timedate1.set-time", interactive
, NULL
, &error
);
830 return bus_send_error_reply(connection
, message
, &error
, r
);
833 timespec_store(&ts
, now(CLOCK_REALTIME
) + utc
);
835 timespec_store(&ts
, utc
);
837 /* Set system clock */
838 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
839 log_error("Failed to set local time: %m");
840 return bus_send_error_reply(connection
, message
, NULL
, -errno
);
843 /* Sync down to RTC */
845 tm
= localtime(&ts
.tv_sec
);
847 tm
= gmtime(&ts
.tv_sec
);
849 hwclock_set_time(tm
);
852 "MESSAGE_ID=" SD_ID128_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_MESSAGE_TIME_CHANGE
),
853 "REALTIME=%llu", (unsigned long long) timespec_load(&ts
),
854 "MESSAGE=Changed local time to %s", ctime(&ts
.tv_sec
),
857 } else if (dbus_message_is_method_call(message
, "org.freedesktop.timedate1", "SetNTP")) {
859 dbus_bool_t interactive
;
861 if (!dbus_message_get_args(
864 DBUS_TYPE_BOOLEAN
, &ntp
,
865 DBUS_TYPE_BOOLEAN
, &interactive
,
867 return bus_send_error_reply(connection
, message
, &error
, -EINVAL
);
869 if (ntp
!= !!tz
.use_ntp
) {
871 r
= verify_polkit(connection
, message
, "org.freedesktop.timedate1.set-ntp", interactive
, NULL
, &error
);
873 return bus_send_error_reply(connection
, message
, &error
, r
);
877 r
= enable_ntp(connection
, &error
);
879 return bus_send_error_reply(connection
, message
, &error
, r
);
881 r
= start_ntp(connection
, &error
);
883 return bus_send_error_reply(connection
, message
, &error
, r
);
885 log_info("Set NTP to %s", tz
.use_ntp
? "enabled" : "disabled");
887 changed
= bus_properties_changed_new(
888 "/org/freedesktop/timedate1",
889 "org.freedesktop.timedate1",
896 return bus_default_message_handler(connection
, message
, INTROSPECTION
, INTERFACES_LIST
, bps
);
898 if (!(reply
= dbus_message_new_method_return(message
)))
901 if (!dbus_connection_send(connection
, reply
, NULL
))
904 dbus_message_unref(reply
);
909 if (!dbus_connection_send(connection
, changed
, NULL
))
912 dbus_message_unref(changed
);
915 return DBUS_HANDLER_RESULT_HANDLED
;
919 dbus_message_unref(reply
);
922 dbus_message_unref(changed
);
924 dbus_error_free(&error
);
926 return DBUS_HANDLER_RESULT_NEED_MEMORY
;
929 static int connect_bus(DBusConnection
**_bus
) {
930 static const DBusObjectPathVTable timedate_vtable
= {
931 .message_function
= timedate_message_handler
934 DBusConnection
*bus
= NULL
;
939 dbus_error_init(&error
);
941 bus
= dbus_bus_get_private(DBUS_BUS_SYSTEM
, &error
);
943 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error
));
948 dbus_connection_set_exit_on_disconnect(bus
, FALSE
);
950 if (!dbus_connection_register_object_path(bus
, "/org/freedesktop/timedate1", &timedate_vtable
, NULL
) ||
951 !dbus_connection_add_filter(bus
, bus_exit_idle_filter
, &remain_until
, NULL
)) {
956 r
= dbus_bus_request_name(bus
, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE
, &error
);
957 if (dbus_error_is_set(&error
)) {
958 log_error("Failed to register name on bus: %s", bus_error_message(&error
));
963 if (r
!= DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
) {
964 log_error("Failed to acquire name.");
975 dbus_connection_close(bus
);
976 dbus_connection_unref(bus
);
978 dbus_error_free(&error
);
983 int main(int argc
, char *argv
[]) {
985 DBusConnection
*bus
= NULL
;
986 bool exiting
= false;
988 log_set_target(LOG_TARGET_AUTO
);
989 log_parse_environment();
994 if (argc
== 2 && streq(argv
[1], "--introspect")) {
995 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
997 fputs(timedate_interface
, stdout
);
998 fputs("</node>\n", stdout
);
1003 log_error("This program takes no arguments.");
1010 log_error("Failed to read timezone data: %s", strerror(-r
));
1014 r
= connect_bus(&bus
);
1020 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r
));
1024 remain_until
= now(CLOCK_MONOTONIC
) + DEFAULT_EXIT_USEC
;
1027 if (!dbus_connection_read_write_dispatch(bus
, exiting
? -1 : (int) (DEFAULT_EXIT_USEC
/USEC_PER_MSEC
)))
1030 if (!exiting
&& remain_until
< now(CLOCK_MONOTONIC
)) {
1032 bus_async_unregister_and_exit(bus
, "org.freedesktop.hostname1");
1042 dbus_connection_flush(bus
);
1043 dbus_connection_close(bus
);
1044 dbus_connection_unref(bus
);
1047 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;