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", EOPNOTSUPP
),
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
;
73 log_warning_errno(r
, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
75 log_warning_errno(r
, "Failed to get target of /etc/localtime: %m");
81 c
->local_rtc
= clock_is_localtime() > 0;
86 static int context_write_data_timezone(Context
*c
) {
87 _cleanup_free_
char *p
= NULL
;
92 if (isempty(c
->zone
)) {
93 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
99 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
103 r
= symlink_atomic(p
, "/etc/localtime");
110 static int context_write_data_local_rtc(Context
*c
) {
112 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
116 r
= read_full_file("/etc/adjtime", &s
, NULL
);
124 w
= strdup(NULL_ADJTIME_LOCAL
);
135 p
= strchr(p
+1, '\n');
147 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + b
+ 1);
151 *(char*) mempcpy(stpcpy(mempcpy(w
, s
, a
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
153 if (streq(w
, NULL_ADJTIME_UTC
)) {
154 if (unlink("/etc/adjtime") < 0)
162 mac_selinux_init("/etc");
163 return write_string_file_atomic_label("/etc/adjtime", w
);
166 static int context_read_ntp(Context
*c
, sd_bus
*bus
) {
167 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
168 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
175 r
= sd_bus_call_method(
177 "org.freedesktop.systemd1",
178 "/org/freedesktop/systemd1",
179 "org.freedesktop.systemd1.Manager",
184 "systemd-timesyncd.service");
187 if (sd_bus_error_has_name(&error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
188 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.LoadFailed") ||
189 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.NoSuchUnit"))
195 r
= sd_bus_message_read(reply
, "s", &s
);
200 c
->use_ntp
= STR_IN_SET(s
, "enabled", "enabled-runtime");
205 static int context_start_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
211 r
= sd_bus_call_method(
213 "org.freedesktop.systemd1",
214 "/org/freedesktop/systemd1",
215 "org.freedesktop.systemd1.Manager",
216 enabled
? "StartUnit" : "StopUnit",
220 "systemd-timesyncd.service",
223 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
224 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.LoadFailed") ||
225 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.NoSuchUnit"))
226 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
234 static int context_enable_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
241 r
= sd_bus_call_method(
243 "org.freedesktop.systemd1",
244 "/org/freedesktop/systemd1",
245 "org.freedesktop.systemd1.Manager",
250 "systemd-timesyncd.service",
253 r
= sd_bus_call_method(
255 "org.freedesktop.systemd1",
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
262 "systemd-timesyncd.service",
266 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
))
267 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
272 r
= sd_bus_call_method(
274 "org.freedesktop.systemd1",
275 "/org/freedesktop/systemd1",
276 "org.freedesktop.systemd1.Manager",
287 static int property_get_rtc_time(
290 const char *interface
,
291 const char *property
,
292 sd_bus_message
*reply
,
294 sd_bus_error
*error
) {
301 r
= clock_get_hwclock(&tm
);
303 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
305 } else if (r
== -ENOENT
) {
306 log_debug("/dev/rtc not found.");
307 t
= 0; /* no RTC found */
309 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %m");
311 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
313 return sd_bus_message_append(reply
, "t", t
);
316 static int property_get_time(
319 const char *interface
,
320 const char *property
,
321 sd_bus_message
*reply
,
323 sd_bus_error
*error
) {
325 return sd_bus_message_append(reply
, "t", now(CLOCK_REALTIME
));
328 static int property_get_ntp_sync(
331 const char *interface
,
332 const char *property
,
333 sd_bus_message
*reply
,
335 sd_bus_error
*error
) {
337 return sd_bus_message_append(reply
, "b", ntp_synced());
340 static int method_set_timezone(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
341 Context
*c
= userdata
;
350 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
354 if (!timezone_is_valid(z
))
355 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
357 if (streq_ptr(z
, c
->zone
))
358 return sd_bus_reply_method_return(m
, NULL
);
360 r
= bus_verify_polkit_async(
363 "org.freedesktop.timedate1.set-timezone",
371 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
380 /* 1. Write new configuration file */
381 r
= context_write_data_timezone(c
);
383 log_error_errno(r
, "Failed to set time zone: %m");
384 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %m");
387 /* 2. Tell the kernel our timezone */
388 clock_set_timezone(NULL
);
394 /* 3. Sync RTC from system clock, with the new delta */
395 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
396 assert_se(tm
= localtime(&ts
.tv_sec
));
397 clock_set_hwclock(tm
);
401 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE
),
402 "TIMEZONE=%s", c
->zone
,
403 LOG_MESSAGE("Changed time zone to '%s'.", c
->zone
),
406 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
408 return sd_bus_reply_method_return(m
, NULL
);
411 static int method_set_local_rtc(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
412 int lrtc
, fix_system
, interactive
;
413 Context
*c
= userdata
;
420 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
424 if (lrtc
== c
->local_rtc
)
425 return sd_bus_reply_method_return(m
, NULL
);
427 r
= bus_verify_polkit_async(
430 "org.freedesktop.timedate1.set-local-rtc",
442 /* 1. Write new configuration file */
443 r
= context_write_data_local_rtc(c
);
445 log_error_errno(r
, "Failed to set RTC to local/UTC: %m");
446 return sd_bus_error_set_errnof(error
, r
, "Failed to set RTC to local/UTC: %m");
449 /* 2. Tell the kernel our timezone */
450 clock_set_timezone(NULL
);
452 /* 3. Synchronize clocks */
453 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
458 /* Sync system clock from RTC; first,
459 * initialize the timezone fields of
462 tm
= *localtime(&ts
.tv_sec
);
464 tm
= *gmtime(&ts
.tv_sec
);
466 /* Override the main fields of
467 * struct tm, but not the timezone
469 if (clock_get_hwclock(&tm
) >= 0) {
471 /* And set the system clock
474 ts
.tv_sec
= mktime(&tm
);
476 ts
.tv_sec
= timegm(&tm
);
478 clock_settime(CLOCK_REALTIME
, &ts
);
484 /* Sync RTC from system clock */
486 tm
= localtime(&ts
.tv_sec
);
488 tm
= gmtime(&ts
.tv_sec
);
490 clock_set_hwclock(tm
);
493 log_info("RTC configured to %s time.", c
->local_rtc
? "local" : "UTC");
495 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL
);
497 return sd_bus_reply_method_return(m
, NULL
);
500 static int method_set_time(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
501 int relative
, interactive
;
502 Context
*c
= userdata
;
513 return sd_bus_error_setf(error
, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED
, "Automatic time synchronization is enabled");
515 /* this only gets used if dbus does not provide a timestamp */
516 start
= now(CLOCK_MONOTONIC
);
518 r
= sd_bus_message_read(m
, "xbb", &utc
, &relative
, &interactive
);
522 if (!relative
&& utc
<= 0)
523 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid absolute time");
525 if (relative
&& utc
== 0)
526 return sd_bus_reply_method_return(m
, NULL
);
531 n
= now(CLOCK_REALTIME
);
534 if ((utc
> 0 && x
< n
) ||
536 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Time value overflow");
538 timespec_store(&ts
, x
);
540 timespec_store(&ts
, (usec_t
) utc
);
542 r
= bus_verify_polkit_async(
545 "org.freedesktop.timedate1.set-time",
555 /* adjust ts for time spent in program */
556 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
557 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
558 if (r
< 0 && r
!= -ENODATA
)
561 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
563 /* Set system clock */
564 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
565 log_error_errno(errno
, "Failed to set local time: %m");
566 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
569 /* Sync down to RTC */
571 tm
= localtime(&ts
.tv_sec
);
573 tm
= gmtime(&ts
.tv_sec
);
574 clock_set_hwclock(tm
);
577 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE
),
578 "REALTIME="USEC_FMT
, timespec_load(&ts
),
579 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)),
582 return sd_bus_reply_method_return(m
, NULL
);
585 static int method_set_ntp(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
586 int enabled
, interactive
;
587 Context
*c
= userdata
;
593 r
= sd_bus_message_read(m
, "bb", &enabled
, &interactive
);
597 if ((bool)enabled
== c
->use_ntp
)
598 return sd_bus_reply_method_return(m
, NULL
);
600 r
= bus_verify_polkit_async(
603 "org.freedesktop.timedate1.set-ntp",
613 r
= context_enable_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
617 r
= context_start_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
621 c
->use_ntp
= enabled
;
622 log_info("Set NTP to %s", enabled
? "enabled" : "disabled");
624 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL
);
626 return sd_bus_reply_method_return(m
, NULL
);
629 static const sd_bus_vtable timedate_vtable
[] = {
630 SD_BUS_VTABLE_START(0),
631 SD_BUS_PROPERTY("Timezone", "s", NULL
, offsetof(Context
, zone
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
632 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool
, offsetof(Context
, local_rtc
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
633 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool
, offsetof(Context
, can_ntp
), 0),
634 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool
, offsetof(Context
, use_ntp
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
635 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync
, 0, 0),
636 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time
, 0, 0),
637 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time
, 0, 0),
638 SD_BUS_METHOD("SetTime", "xbb", NULL
, method_set_time
, SD_BUS_VTABLE_UNPRIVILEGED
),
639 SD_BUS_METHOD("SetTimezone", "sb", NULL
, method_set_timezone
, SD_BUS_VTABLE_UNPRIVILEGED
),
640 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL
, method_set_local_rtc
, SD_BUS_VTABLE_UNPRIVILEGED
),
641 SD_BUS_METHOD("SetNTP", "bb", NULL
, method_set_ntp
, SD_BUS_VTABLE_UNPRIVILEGED
),
645 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
646 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
653 r
= sd_bus_default_system(&bus
);
655 return log_error_errno(r
, "Failed to get system bus connection: %m");
657 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable
, c
);
659 return log_error_errno(r
, "Failed to register object: %m");
661 r
= sd_bus_request_name(bus
, "org.freedesktop.timedate1", 0);
663 return log_error_errno(r
, "Failed to register name: %m");
665 r
= sd_bus_attach_event(bus
, event
, 0);
667 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
675 int main(int argc
, char *argv
[]) {
676 Context context
= {};
677 _cleanup_event_unref_ sd_event
*event
= NULL
;
678 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
681 log_set_target(LOG_TARGET_AUTO
);
682 log_parse_environment();
688 log_error("This program takes no arguments.");
693 r
= sd_event_default(&event
);
695 log_error_errno(r
, "Failed to allocate event loop: %m");
699 sd_event_set_watchdog(event
, true);
701 r
= connect_bus(&context
, event
, &bus
);
705 (void) sd_bus_negotiate_timestamp(bus
, true);
707 r
= context_read_data(&context
);
709 log_error_errno(r
, "Failed to read time zone data: %m");
713 r
= context_read_ntp(&context
, bus
);
715 log_error_errno(r
, "Failed to determine whether NTP is enabled: %m");
719 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
721 log_error_errno(r
, "Failed to run event loop: %m");
726 context_free(&context
);
728 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;