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/>.
26 #include "sd-messages.h"
33 #include "clock-util.h"
34 #include "path-util.h"
35 #include "fileio-label.h"
37 #include "bus-error.h"
38 #include "bus-common-errors.h"
39 #include "event-util.h"
40 #include "selinux-util.h"
42 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
43 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
45 static BUS_ERROR_MAP_ELF_REGISTER
const sd_bus_error_map timedated_errors
[] = {
46 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", ENOTSUP
),
50 typedef struct Context
{
55 Hashmap
*polkit_registry
;
58 static void context_free(Context
*c
) {
62 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
65 static int context_read_data(Context
*c
) {
66 _cleanup_free_
char *t
= NULL
;
71 r
= readlink_malloc("/etc/localtime", &t
);
74 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
76 log_warning_errno(r
, "Failed to get target of /etc/localtime: %m");
80 e
= path_startswith(t
, "/usr/share/zoneinfo/");
82 e
= path_startswith(t
, "../usr/share/zoneinfo/");
85 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
96 if (isempty(c
->zone
)) {
101 c
->local_rtc
= clock_is_localtime() > 0;
106 static int context_write_data_timezone(Context
*c
) {
107 _cleanup_free_
char *p
= NULL
;
112 if (isempty(c
->zone
)) {
113 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
119 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
123 r
= symlink_atomic(p
, "/etc/localtime");
130 static int context_write_data_local_rtc(Context
*c
) {
132 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
136 r
= read_full_file("/etc/adjtime", &s
, NULL
);
144 w
= strdup(NULL_ADJTIME_LOCAL
);
155 p
= strchr(p
+1, '\n');
167 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + b
+ 1);
171 *(char*) mempcpy(stpcpy(mempcpy(w
, s
, a
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
173 if (streq(w
, NULL_ADJTIME_UTC
)) {
174 if (unlink("/etc/adjtime") < 0)
182 mac_selinux_init("/etc");
183 return write_string_file_atomic_label("/etc/adjtime", w
);
186 static int context_read_ntp(Context
*c
, sd_bus
*bus
) {
187 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
188 sd_bus_message
*reply
= NULL
;
195 r
= sd_bus_call_method(
197 "org.freedesktop.systemd1",
198 "/org/freedesktop/systemd1",
199 "org.freedesktop.systemd1.Manager",
204 "systemd-timesyncd.service");
207 if (sd_bus_error_has_name(&error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
208 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.LoadFailed") ||
209 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.NoSuchUnit"))
215 r
= sd_bus_message_read(reply
, "s", &s
);
220 c
->use_ntp
= STR_IN_SET(s
, "enabled", "enabled-runtime");
225 static int context_start_ntp(Context
*c
, sd_bus
*bus
, sd_bus_error
*error
) {
233 r
= sd_bus_call_method(
235 "org.freedesktop.systemd1",
236 "/org/freedesktop/systemd1",
237 "org.freedesktop.systemd1.Manager",
242 "systemd-timesyncd.service",
245 r
= sd_bus_call_method(
247 "org.freedesktop.systemd1",
248 "/org/freedesktop/systemd1",
249 "org.freedesktop.systemd1.Manager",
254 "systemd-timesyncd.service",
258 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
259 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.LoadFailed") ||
260 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.NoSuchUnit"))
261 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
269 static int context_enable_ntp(Context
*c
, sd_bus
*bus
, sd_bus_error
*error
) {
277 r
= sd_bus_call_method(
279 "org.freedesktop.systemd1",
280 "/org/freedesktop/systemd1",
281 "org.freedesktop.systemd1.Manager",
286 "systemd-timesyncd.service",
289 r
= sd_bus_call_method(
291 "org.freedesktop.systemd1",
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
298 "systemd-timesyncd.service",
302 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
))
303 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
308 r
= sd_bus_call_method(
310 "org.freedesktop.systemd1",
311 "/org/freedesktop/systemd1",
312 "org.freedesktop.systemd1.Manager",
323 static int property_get_rtc_time(
326 const char *interface
,
327 const char *property
,
328 sd_bus_message
*reply
,
330 sd_bus_error
*error
) {
337 r
= clock_get_hwclock(&tm
);
339 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
341 } else if (r
== -ENOENT
) {
342 log_debug("/dev/rtc not found.");
343 t
= 0; /* no RTC found */
345 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %s", strerror(-r
));
347 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
349 return sd_bus_message_append(reply
, "t", t
);
352 static int property_get_time(
355 const char *interface
,
356 const char *property
,
357 sd_bus_message
*reply
,
359 sd_bus_error
*error
) {
361 return sd_bus_message_append(reply
, "t", now(CLOCK_REALTIME
));
364 static int property_get_ntp_sync(
367 const char *interface
,
368 const char *property
,
369 sd_bus_message
*reply
,
371 sd_bus_error
*error
) {
373 return sd_bus_message_append(reply
, "b", ntp_synced());
376 static int method_set_timezone(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
377 Context
*c
= userdata
;
387 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
391 if (!timezone_is_valid(z
))
392 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
394 if (streq_ptr(z
, c
->zone
))
395 return sd_bus_reply_method_return(m
, NULL
);
397 r
= bus_verify_polkit_async(
400 "org.freedesktop.timedate1.set-timezone",
408 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
417 /* 1. Write new configuration file */
418 r
= context_write_data_timezone(c
);
420 log_error_errno(r
, "Failed to set time zone: %m");
421 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %s", strerror(-r
));
424 /* 2. Tell the kernel our timezone */
425 clock_set_timezone(NULL
);
431 /* 3. Sync RTC from system clock, with the new delta */
432 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
433 assert_se(tm
= localtime(&ts
.tv_sec
));
434 clock_set_hwclock(tm
);
438 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE
),
439 "TIMEZONE=%s", c
->zone
,
440 LOG_MESSAGE("Changed time zone to '%s'.", c
->zone
),
443 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
445 return sd_bus_reply_method_return(m
, NULL
);
448 static int method_set_local_rtc(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
449 int lrtc
, fix_system
, interactive
;
450 Context
*c
= userdata
;
458 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
462 if (lrtc
== c
->local_rtc
)
463 return sd_bus_reply_method_return(m
, NULL
);
465 r
= bus_verify_polkit_async(
468 "org.freedesktop.timedate1.set-local-rtc",
480 /* 1. Write new configuration file */
481 r
= context_write_data_local_rtc(c
);
483 log_error_errno(r
, "Failed to set RTC to local/UTC: %m");
484 return sd_bus_error_set_errnof(error
, r
, "Failed to set RTC to local/UTC: %s", strerror(-r
));
487 /* 2. Tell the kernel our timezone */
488 clock_set_timezone(NULL
);
490 /* 3. Synchronize clocks */
491 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
496 /* Sync system clock from RTC; first,
497 * initialize the timezone fields of
500 tm
= *localtime(&ts
.tv_sec
);
502 tm
= *gmtime(&ts
.tv_sec
);
504 /* Override the main fields of
505 * struct tm, but not the timezone
507 if (clock_get_hwclock(&tm
) >= 0) {
509 /* And set the system clock
512 ts
.tv_sec
= mktime(&tm
);
514 ts
.tv_sec
= timegm(&tm
);
516 clock_settime(CLOCK_REALTIME
, &ts
);
522 /* Sync RTC from system clock */
524 tm
= localtime(&ts
.tv_sec
);
526 tm
= gmtime(&ts
.tv_sec
);
528 clock_set_hwclock(tm
);
531 log_info("RTC configured to %s time.", c
->local_rtc
? "local" : "UTC");
533 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL
);
535 return sd_bus_reply_method_return(m
, NULL
);
538 static int method_set_time(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
539 int relative
, interactive
;
540 Context
*c
= userdata
;
552 return sd_bus_error_setf(error
, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED
, "Automatic time synchronization is enabled");
554 /* this only gets used if dbus does not provide a timestamp */
555 start
= now(CLOCK_MONOTONIC
);
557 r
= sd_bus_message_read(m
, "xbb", &utc
, &relative
, &interactive
);
561 if (!relative
&& utc
<= 0)
562 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid absolute time");
564 if (relative
&& utc
== 0)
565 return sd_bus_reply_method_return(m
, NULL
);
570 n
= now(CLOCK_REALTIME
);
573 if ((utc
> 0 && x
< n
) ||
575 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Time value overflow");
577 timespec_store(&ts
, x
);
579 timespec_store(&ts
, (usec_t
) utc
);
581 r
= bus_verify_polkit_async(
584 "org.freedesktop.timedate1.set-time",
594 /* adjust ts for time spent in program */
595 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
596 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
597 if (r
< 0 && r
!= -ENODATA
)
600 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
602 /* Set system clock */
603 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
604 log_error_errno(errno
, "Failed to set local time: %m");
605 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
608 /* Sync down to RTC */
610 tm
= localtime(&ts
.tv_sec
);
612 tm
= gmtime(&ts
.tv_sec
);
613 clock_set_hwclock(tm
);
616 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE
),
617 "REALTIME="USEC_FMT
, timespec_load(&ts
),
618 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)),
621 return sd_bus_reply_method_return(m
, NULL
);
624 static int method_set_ntp(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
625 int ntp
, interactive
;
626 Context
*c
= userdata
;
629 r
= sd_bus_message_read(m
, "bb", &ntp
, &interactive
);
633 if ((bool)ntp
== c
->use_ntp
)
634 return sd_bus_reply_method_return(m
, NULL
);
636 r
= bus_verify_polkit_async(
639 "org.freedesktop.timedate1.set-ntp",
651 r
= context_enable_ntp(c
, bus
, error
);
655 r
= context_start_ntp(c
, bus
, error
);
659 log_info("Set NTP to %s", c
->use_ntp
? "enabled" : "disabled");
661 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL
);
663 return sd_bus_reply_method_return(m
, NULL
);
666 static const sd_bus_vtable timedate_vtable
[] = {
667 SD_BUS_VTABLE_START(0),
668 SD_BUS_PROPERTY("Timezone", "s", NULL
, offsetof(Context
, zone
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
669 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool
, offsetof(Context
, local_rtc
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
670 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool
, offsetof(Context
, can_ntp
), 0),
671 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool
, offsetof(Context
, use_ntp
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
672 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync
, 0, 0),
673 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time
, 0, 0),
674 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time
, 0, 0),
675 SD_BUS_METHOD("SetTime", "xbb", NULL
, method_set_time
, SD_BUS_VTABLE_UNPRIVILEGED
),
676 SD_BUS_METHOD("SetTimezone", "sb", NULL
, method_set_timezone
, SD_BUS_VTABLE_UNPRIVILEGED
),
677 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL
, method_set_local_rtc
, SD_BUS_VTABLE_UNPRIVILEGED
),
678 SD_BUS_METHOD("SetNTP", "bb", NULL
, method_set_ntp
, SD_BUS_VTABLE_UNPRIVILEGED
),
682 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
683 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
690 r
= sd_bus_default_system(&bus
);
692 return log_error_errno(r
, "Failed to get system bus connection: %m");
694 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable
, c
);
696 return log_error_errno(r
, "Failed to register object: %m");
698 r
= sd_bus_request_name(bus
, "org.freedesktop.timedate1", 0);
700 return log_error_errno(r
, "Failed to register name: %m");
702 r
= sd_bus_attach_event(bus
, event
, 0);
704 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
712 int main(int argc
, char *argv
[]) {
713 Context context
= {};
714 _cleanup_event_unref_ sd_event
*event
= NULL
;
715 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
718 log_set_target(LOG_TARGET_AUTO
);
719 log_parse_environment();
725 log_error("This program takes no arguments.");
730 r
= sd_event_default(&event
);
732 log_error_errno(r
, "Failed to allocate event loop: %m");
736 sd_event_set_watchdog(event
, true);
738 r
= connect_bus(&context
, event
, &bus
);
742 (void)sd_bus_negotiate_timestamp(bus
, true);
744 r
= context_read_data(&context
);
746 log_error_errno(r
, "Failed to read time zone data: %m");
750 r
= context_read_ntp(&context
, bus
);
752 log_error_errno(r
, "Failed to determine whether NTP is enabled: %m");
756 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
758 log_error_errno(r
, "Failed to run event loop: %m");
763 context_free(&context
);
765 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;