1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "sd-messages.h"
29 #include "alloc-util.h"
30 #include "bus-common-errors.h"
31 #include "bus-error.h"
33 #include "clock-util.h"
35 #include "fileio-label.h"
37 #include "path-util.h"
38 #include "selinux-util.h"
40 #include "user-util.h"
43 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
44 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
46 static BUS_ERROR_MAP_ELF_REGISTER
const sd_bus_error_map timedated_errors
[] = {
47 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP
),
51 typedef struct Context
{
56 Hashmap
*polkit_registry
;
59 static void context_free(Context
*c
) {
63 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
66 static int context_read_data(Context
*c
) {
67 _cleanup_free_
char *t
= NULL
;
74 log_warning_errno(r
, "/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");
82 c
->local_rtc
= clock_is_localtime(NULL
) > 0;
87 static int context_write_data_timezone(Context
*c
) {
88 _cleanup_free_
char *p
= NULL
;
93 if (isempty(c
->zone
)) {
94 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
100 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
104 r
= symlink_atomic(p
, "/etc/localtime");
111 static int context_write_data_local_rtc(Context
*c
) {
113 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
117 r
= read_full_file("/etc/adjtime", &s
, NULL
);
125 w
= strdup(NULL_ADJTIME_LOCAL
);
130 const char *e
= "\n"; /* default if there is less than 3 lines */
131 const char *prepend
= "";
134 p
= strchrnul(s
, '\n');
136 /* only one line, no \n terminator */
138 else if (p
[1] == '\0') {
139 /* only one line, with \n terminator */
143 p
= strchr(p
+1, '\n');
145 /* only two lines, no \n terminator */
150 /* third line might have a \n terminator or not */
152 end
= strchr(p
, '\n');
153 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
162 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + strlen(prepend
) + b
+ 1);
166 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w
, s
, a
), prepend
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
168 if (streq(w
, NULL_ADJTIME_UTC
)) {
169 if (unlink("/etc/adjtime") < 0)
178 return write_string_file_atomic_label("/etc/adjtime", w
);
181 static int context_read_ntp(Context
*c
, sd_bus
*bus
) {
182 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
183 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
190 r
= sd_bus_call_method(
192 "org.freedesktop.systemd1",
193 "/org/freedesktop/systemd1",
194 "org.freedesktop.systemd1.Manager",
199 "systemd-timesyncd.service");
202 if (sd_bus_error_has_name(&error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
203 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.LoadFailed") ||
204 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.NoSuchUnit"))
210 r
= sd_bus_message_read(reply
, "s", &s
);
215 c
->use_ntp
= STR_IN_SET(s
, "enabled", "enabled-runtime");
220 static int context_start_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
226 r
= sd_bus_call_method(
228 "org.freedesktop.systemd1",
229 "/org/freedesktop/systemd1",
230 "org.freedesktop.systemd1.Manager",
231 enabled
? "StartUnit" : "StopUnit",
235 "systemd-timesyncd.service",
238 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
239 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.LoadFailed") ||
240 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.NoSuchUnit"))
241 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
249 static int context_enable_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
256 r
= sd_bus_call_method(
258 "org.freedesktop.systemd1",
259 "/org/freedesktop/systemd1",
260 "org.freedesktop.systemd1.Manager",
265 "systemd-timesyncd.service",
268 r
= sd_bus_call_method(
270 "org.freedesktop.systemd1",
271 "/org/freedesktop/systemd1",
272 "org.freedesktop.systemd1.Manager",
277 "systemd-timesyncd.service",
281 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
))
282 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
287 r
= sd_bus_call_method(
289 "org.freedesktop.systemd1",
290 "/org/freedesktop/systemd1",
291 "org.freedesktop.systemd1.Manager",
302 static int property_get_rtc_time(
305 const char *interface
,
306 const char *property
,
307 sd_bus_message
*reply
,
309 sd_bus_error
*error
) {
316 r
= clock_get_hwclock(&tm
);
318 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
320 } else if (r
== -ENOENT
) {
321 log_debug("/dev/rtc not found.");
322 t
= 0; /* no RTC found */
324 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %m");
326 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
328 return sd_bus_message_append(reply
, "t", t
);
331 static int property_get_time(
334 const char *interface
,
335 const char *property
,
336 sd_bus_message
*reply
,
338 sd_bus_error
*error
) {
340 return sd_bus_message_append(reply
, "t", now(CLOCK_REALTIME
));
343 static int property_get_ntp_sync(
346 const char *interface
,
347 const char *property
,
348 sd_bus_message
*reply
,
350 sd_bus_error
*error
) {
352 return sd_bus_message_append(reply
, "b", ntp_synced());
355 static int method_set_timezone(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
356 Context
*c
= userdata
;
365 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
369 if (!timezone_is_valid(z
))
370 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
372 if (streq_ptr(z
, c
->zone
))
373 return sd_bus_reply_method_return(m
, NULL
);
375 r
= bus_verify_polkit_async(
378 "org.freedesktop.timedate1.set-timezone",
387 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
396 /* 1. Write new configuration file */
397 r
= context_write_data_timezone(c
);
399 log_error_errno(r
, "Failed to set time zone: %m");
400 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %m");
403 /* 2. Tell the kernel our timezone */
404 clock_set_timezone(NULL
);
410 /* 3. Sync RTC from system clock, with the new delta */
411 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
412 assert_se(tm
= localtime(&ts
.tv_sec
));
413 clock_set_hwclock(tm
);
417 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR
,
418 "TIMEZONE=%s", c
->zone
,
419 LOG_MESSAGE("Changed time zone to '%s'.", c
->zone
),
422 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
424 return sd_bus_reply_method_return(m
, NULL
);
427 static int method_set_local_rtc(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
428 int lrtc
, fix_system
, interactive
;
429 Context
*c
= userdata
;
436 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
440 if (lrtc
== c
->local_rtc
)
441 return sd_bus_reply_method_return(m
, NULL
);
443 r
= bus_verify_polkit_async(
446 "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",
573 /* adjust ts for time spent in program */
574 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
575 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
576 if (r
< 0 && r
!= -ENODATA
)
579 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
581 /* Set system clock */
582 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
583 log_error_errno(errno
, "Failed to set local time: %m");
584 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
587 /* Sync down to RTC */
589 tm
= localtime(&ts
.tv_sec
);
591 tm
= gmtime(&ts
.tv_sec
);
592 clock_set_hwclock(tm
);
595 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR
,
596 "REALTIME="USEC_FMT
, timespec_load(&ts
),
597 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)),
600 return sd_bus_reply_method_return(m
, NULL
);
603 static int method_set_ntp(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
604 int enabled
, interactive
;
605 Context
*c
= userdata
;
611 r
= sd_bus_message_read(m
, "bb", &enabled
, &interactive
);
615 if ((bool)enabled
== c
->use_ntp
)
616 return sd_bus_reply_method_return(m
, NULL
);
618 r
= bus_verify_polkit_async(
621 "org.freedesktop.timedate1.set-ntp",
632 r
= context_enable_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
636 r
= context_start_ntp(sd_bus_message_get_bus(m
), error
, enabled
);
640 c
->use_ntp
= enabled
;
641 log_info("Set NTP to %sd", enable_disable(enabled
));
643 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL
);
645 return sd_bus_reply_method_return(m
, NULL
);
648 static const sd_bus_vtable timedate_vtable
[] = {
649 SD_BUS_VTABLE_START(0),
650 SD_BUS_PROPERTY("Timezone", "s", NULL
, offsetof(Context
, zone
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
651 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool
, offsetof(Context
, local_rtc
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
652 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool
, offsetof(Context
, can_ntp
), 0),
653 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool
, offsetof(Context
, use_ntp
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
654 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync
, 0, 0),
655 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time
, 0, 0),
656 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time
, 0, 0),
657 SD_BUS_METHOD("SetTime", "xbb", NULL
, method_set_time
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetTimezone", "sb", NULL
, method_set_timezone
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL
, method_set_local_rtc
, SD_BUS_VTABLE_UNPRIVILEGED
),
660 SD_BUS_METHOD("SetNTP", "bb", NULL
, method_set_ntp
, SD_BUS_VTABLE_UNPRIVILEGED
),
664 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
665 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
672 r
= sd_bus_default_system(&bus
);
674 return log_error_errno(r
, "Failed to get system bus connection: %m");
676 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable
, c
);
678 return log_error_errno(r
, "Failed to register object: %m");
680 r
= sd_bus_request_name(bus
, "org.freedesktop.timedate1", 0);
682 return log_error_errno(r
, "Failed to register name: %m");
684 r
= sd_bus_attach_event(bus
, event
, 0);
686 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
694 int main(int argc
, char *argv
[]) {
695 Context context
= {};
696 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
697 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
700 log_set_target(LOG_TARGET_AUTO
);
701 log_parse_environment();
707 log_error("This program takes no arguments.");
712 r
= sd_event_default(&event
);
714 log_error_errno(r
, "Failed to allocate event loop: %m");
718 sd_event_set_watchdog(event
, true);
720 r
= connect_bus(&context
, event
, &bus
);
724 (void) sd_bus_negotiate_timestamp(bus
, true);
726 r
= context_read_data(&context
);
728 log_error_errno(r
, "Failed to read time zone data: %m");
732 r
= context_read_ntp(&context
, bus
);
734 log_error_errno(r
, "Failed to determine whether NTP is enabled: %m");
738 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
740 log_error_errno(r
, "Failed to run event loop: %m");
745 context_free(&context
);
747 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;