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
;
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/.");
93 if (isempty(c
->zone
)) {
98 c
->local_rtc
= clock_is_localtime() > 0;
103 static int context_write_data_timezone(Context
*c
) {
104 _cleanup_free_
char *p
= NULL
;
109 if (isempty(c
->zone
)) {
110 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
116 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
120 r
= symlink_atomic(p
, "/etc/localtime");
127 static int context_write_data_local_rtc(Context
*c
) {
129 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
133 r
= read_full_file("/etc/adjtime", &s
, NULL
);
141 w
= strdup(NULL_ADJTIME_LOCAL
);
152 p
= strchr(p
+1, '\n');
164 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + b
+ 1);
168 *(char*) mempcpy(stpcpy(mempcpy(w
, s
, a
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
170 if (streq(w
, NULL_ADJTIME_UTC
)) {
171 if (unlink("/etc/adjtime") < 0)
179 mac_selinux_init("/etc");
180 return write_string_file_atomic_label("/etc/adjtime", w
);
183 static int context_read_ntp(Context
*c
, sd_bus
*bus
) {
184 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
185 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
192 r
= sd_bus_call_method(
194 "org.freedesktop.systemd1",
195 "/org/freedesktop/systemd1",
196 "org.freedesktop.systemd1.Manager",
201 "systemd-timesyncd.service");
204 if (sd_bus_error_has_name(&error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
205 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.LoadFailed") ||
206 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.NoSuchUnit"))
212 r
= sd_bus_message_read(reply
, "s", &s
);
217 c
->use_ntp
= STR_IN_SET(s
, "enabled", "enabled-runtime");
222 static int context_start_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
228 r
= sd_bus_call_method(
230 "org.freedesktop.systemd1",
231 "/org/freedesktop/systemd1",
232 "org.freedesktop.systemd1.Manager",
233 enabled
? "StartUnit" : "StopUnit",
237 "systemd-timesyncd.service",
240 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
241 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.LoadFailed") ||
242 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.NoSuchUnit"))
243 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
251 static int context_enable_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
258 r
= sd_bus_call_method(
260 "org.freedesktop.systemd1",
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
267 "systemd-timesyncd.service",
270 r
= sd_bus_call_method(
272 "org.freedesktop.systemd1",
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
279 "systemd-timesyncd.service",
283 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
))
284 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
289 r
= sd_bus_call_method(
291 "org.freedesktop.systemd1",
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
304 static int property_get_rtc_time(
307 const char *interface
,
308 const char *property
,
309 sd_bus_message
*reply
,
311 sd_bus_error
*error
) {
318 r
= clock_get_hwclock(&tm
);
320 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
322 } else if (r
== -ENOENT
) {
323 log_debug("/dev/rtc not found.");
324 t
= 0; /* no RTC found */
326 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %m");
328 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
330 return sd_bus_message_append(reply
, "t", t
);
333 static int property_get_time(
336 const char *interface
,
337 const char *property
,
338 sd_bus_message
*reply
,
340 sd_bus_error
*error
) {
342 return sd_bus_message_append(reply
, "t", now(CLOCK_REALTIME
));
345 static int property_get_ntp_sync(
348 const char *interface
,
349 const char *property
,
350 sd_bus_message
*reply
,
352 sd_bus_error
*error
) {
354 return sd_bus_message_append(reply
, "b", ntp_synced());
357 static int method_set_timezone(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
358 Context
*c
= userdata
;
367 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
371 if (!timezone_is_valid(z
))
372 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
374 if (streq_ptr(z
, c
->zone
))
375 return sd_bus_reply_method_return(m
, NULL
);
377 r
= bus_verify_polkit_async(
380 "org.freedesktop.timedate1.set-timezone",
388 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
397 /* 1. Write new configuration file */
398 r
= context_write_data_timezone(c
);
400 log_error_errno(r
, "Failed to set time zone: %m");
401 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %m");
404 /* 2. Tell the kernel our timezone */
405 clock_set_timezone(NULL
);
411 /* 3. Sync RTC from system clock, with the new delta */
412 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
413 assert_se(tm
= localtime(&ts
.tv_sec
));
414 clock_set_hwclock(tm
);
418 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE
),
419 "TIMEZONE=%s", c
->zone
,
420 LOG_MESSAGE("Changed time zone to '%s'.", c
->zone
),
423 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
425 return sd_bus_reply_method_return(m
, NULL
);
428 static int method_set_local_rtc(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
429 int lrtc
, fix_system
, interactive
;
430 Context
*c
= userdata
;
437 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
441 if (lrtc
== c
->local_rtc
)
442 return sd_bus_reply_method_return(m
, NULL
);
444 r
= bus_verify_polkit_async(
447 "org.freedesktop.timedate1.set-local-rtc",
459 /* 1. Write new configuration file */
460 r
= context_write_data_local_rtc(c
);
462 log_error_errno(r
, "Failed to set RTC to local/UTC: %m");
463 return sd_bus_error_set_errnof(error
, r
, "Failed to set RTC to local/UTC: %m");
466 /* 2. Tell the kernel our timezone */
467 clock_set_timezone(NULL
);
469 /* 3. Synchronize clocks */
470 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
475 /* Sync system clock from RTC; first,
476 * initialize the timezone fields of
479 tm
= *localtime(&ts
.tv_sec
);
481 tm
= *gmtime(&ts
.tv_sec
);
483 /* Override the main fields of
484 * struct tm, but not the timezone
486 if (clock_get_hwclock(&tm
) >= 0) {
488 /* And set the system clock
491 ts
.tv_sec
= mktime(&tm
);
493 ts
.tv_sec
= timegm(&tm
);
495 clock_settime(CLOCK_REALTIME
, &ts
);
501 /* Sync RTC from system clock */
503 tm
= localtime(&ts
.tv_sec
);
505 tm
= gmtime(&ts
.tv_sec
);
507 clock_set_hwclock(tm
);
510 log_info("RTC configured to %s time.", c
->local_rtc
? "local" : "UTC");
512 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL
);
514 return sd_bus_reply_method_return(m
, NULL
);
517 static int method_set_time(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
518 int relative
, interactive
;
519 Context
*c
= userdata
;
530 return sd_bus_error_setf(error
, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED
, "Automatic time synchronization is enabled");
532 /* this only gets used if dbus does not provide a timestamp */
533 start
= now(CLOCK_MONOTONIC
);
535 r
= sd_bus_message_read(m
, "xbb", &utc
, &relative
, &interactive
);
539 if (!relative
&& utc
<= 0)
540 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid absolute time");
542 if (relative
&& utc
== 0)
543 return sd_bus_reply_method_return(m
, NULL
);
548 n
= now(CLOCK_REALTIME
);
551 if ((utc
> 0 && x
< n
) ||
553 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Time value overflow");
555 timespec_store(&ts
, x
);
557 timespec_store(&ts
, (usec_t
) utc
);
559 r
= bus_verify_polkit_async(
562 "org.freedesktop.timedate1.set-time",
572 /* adjust ts for time spent in program */
573 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
574 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
575 if (r
< 0 && r
!= -ENODATA
)
578 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
580 /* Set system clock */
581 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
582 log_error_errno(errno
, "Failed to set local time: %m");
583 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
586 /* Sync down to RTC */
588 tm
= localtime(&ts
.tv_sec
);
590 tm
= gmtime(&ts
.tv_sec
);
591 clock_set_hwclock(tm
);
594 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE
),
595 "REALTIME="USEC_FMT
, timespec_load(&ts
),
596 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)),
599 return sd_bus_reply_method_return(m
, NULL
);
602 static int method_set_ntp(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
603 int enabled
, interactive
;
604 Context
*c
= userdata
;
610 r
= sd_bus_message_read(m
, "bb", &enabled
, &interactive
);
614 if ((bool)enabled
== c
->use_ntp
)
615 return sd_bus_reply_method_return(m
, NULL
);
617 r
= bus_verify_polkit_async(
620 "org.freedesktop.timedate1.set-ntp",
630 r
= context_enable_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
634 r
= context_start_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
638 c
->use_ntp
= enabled
;
639 log_info("Set NTP to %s", enabled
? "enabled" : "disabled");
641 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL
);
643 return sd_bus_reply_method_return(m
, NULL
);
646 static const sd_bus_vtable timedate_vtable
[] = {
647 SD_BUS_VTABLE_START(0),
648 SD_BUS_PROPERTY("Timezone", "s", NULL
, offsetof(Context
, zone
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool
, offsetof(Context
, local_rtc
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool
, offsetof(Context
, can_ntp
), 0),
651 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool
, offsetof(Context
, use_ntp
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
652 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync
, 0, 0),
653 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time
, 0, 0),
654 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time
, 0, 0),
655 SD_BUS_METHOD("SetTime", "xbb", NULL
, method_set_time
, SD_BUS_VTABLE_UNPRIVILEGED
),
656 SD_BUS_METHOD("SetTimezone", "sb", NULL
, method_set_timezone
, SD_BUS_VTABLE_UNPRIVILEGED
),
657 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL
, method_set_local_rtc
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetNTP", "bb", NULL
, method_set_ntp
, SD_BUS_VTABLE_UNPRIVILEGED
),
662 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
663 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
670 r
= sd_bus_default_system(&bus
);
672 return log_error_errno(r
, "Failed to get system bus connection: %m");
674 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable
, c
);
676 return log_error_errno(r
, "Failed to register object: %m");
678 r
= sd_bus_request_name(bus
, "org.freedesktop.timedate1", 0);
680 return log_error_errno(r
, "Failed to register name: %m");
682 r
= sd_bus_attach_event(bus
, event
, 0);
684 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
692 int main(int argc
, char *argv
[]) {
693 Context context
= {};
694 _cleanup_event_unref_ sd_event
*event
= NULL
;
695 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
698 log_set_target(LOG_TARGET_AUTO
);
699 log_parse_environment();
705 log_error("This program takes no arguments.");
710 r
= sd_event_default(&event
);
712 log_error_errno(r
, "Failed to allocate event loop: %m");
716 sd_event_set_watchdog(event
, true);
718 r
= connect_bus(&context
, event
, &bus
);
722 (void) sd_bus_negotiate_timestamp(bus
, true);
724 r
= context_read_data(&context
);
726 log_error_errno(r
, "Failed to read time zone data: %m");
730 r
= context_read_ntp(&context
, bus
);
732 log_error_errno(r
, "Failed to determine whether NTP is enabled: %m");
736 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
738 log_error_errno(r
, "Failed to run event loop: %m");
743 context_free(&context
);
745 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;