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");
78 free_and_replace(c
->zone
, t
);
80 c
->local_rtc
= clock_is_localtime(NULL
) > 0;
85 static int context_write_data_timezone(Context
*c
) {
86 _cleanup_free_
char *p
= NULL
;
91 if (isempty(c
->zone
)) {
92 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
98 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
102 r
= symlink_atomic(p
, "/etc/localtime");
109 static int context_write_data_local_rtc(Context
*c
) {
111 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
115 r
= read_full_file("/etc/adjtime", &s
, NULL
);
123 w
= strdup(NULL_ADJTIME_LOCAL
);
128 const char *e
= "\n"; /* default if there is less than 3 lines */
129 const char *prepend
= "";
132 p
= strchrnul(s
, '\n');
134 /* only one line, no \n terminator */
136 else if (p
[1] == '\0') {
137 /* only one line, with \n terminator */
141 p
= strchr(p
+1, '\n');
143 /* only two lines, no \n terminator */
148 /* third line might have a \n terminator or not */
150 end
= strchr(p
, '\n');
151 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
160 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + strlen(prepend
) + b
+ 1);
164 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w
, s
, a
), prepend
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
166 if (streq(w
, NULL_ADJTIME_UTC
)) {
167 if (unlink("/etc/adjtime") < 0)
176 return write_string_file_atomic_label("/etc/adjtime", w
);
179 static int context_read_ntp(Context
*c
, sd_bus
*bus
) {
180 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
181 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
188 r
= sd_bus_call_method(
190 "org.freedesktop.systemd1",
191 "/org/freedesktop/systemd1",
192 "org.freedesktop.systemd1.Manager",
197 "systemd-timesyncd.service");
200 if (sd_bus_error_has_name(&error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
201 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.LoadFailed") ||
202 sd_bus_error_has_name(&error
, "org.freedesktop.systemd1.NoSuchUnit"))
208 r
= sd_bus_message_read(reply
, "s", &s
);
213 c
->use_ntp
= STR_IN_SET(s
, "enabled", "enabled-runtime");
218 static int context_start_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
224 r
= sd_bus_call_method(
226 "org.freedesktop.systemd1",
227 "/org/freedesktop/systemd1",
228 "org.freedesktop.systemd1.Manager",
229 enabled
? "StartUnit" : "StopUnit",
233 "systemd-timesyncd.service",
236 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
) ||
237 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.LoadFailed") ||
238 sd_bus_error_has_name(error
, "org.freedesktop.systemd1.NoSuchUnit"))
239 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
247 static int context_enable_ntp(sd_bus
*bus
, sd_bus_error
*error
, bool enabled
) {
254 r
= sd_bus_call_method(
256 "org.freedesktop.systemd1",
257 "/org/freedesktop/systemd1",
258 "org.freedesktop.systemd1.Manager",
263 "systemd-timesyncd.service",
266 r
= sd_bus_call_method(
268 "org.freedesktop.systemd1",
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
275 "systemd-timesyncd.service",
279 if (sd_bus_error_has_name(error
, SD_BUS_ERROR_FILE_NOT_FOUND
))
280 return sd_bus_error_set_const(error
, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
285 r
= sd_bus_call_method(
287 "org.freedesktop.systemd1",
288 "/org/freedesktop/systemd1",
289 "org.freedesktop.systemd1.Manager",
300 static int property_get_rtc_time(
303 const char *interface
,
304 const char *property
,
305 sd_bus_message
*reply
,
307 sd_bus_error
*error
) {
314 r
= clock_get_hwclock(&tm
);
316 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
318 } else if (r
== -ENOENT
) {
319 log_debug("/dev/rtc not found.");
320 t
= 0; /* no RTC found */
322 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %m");
324 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
326 return sd_bus_message_append(reply
, "t", t
);
329 static int property_get_time(
332 const char *interface
,
333 const char *property
,
334 sd_bus_message
*reply
,
336 sd_bus_error
*error
) {
338 return sd_bus_message_append(reply
, "t", now(CLOCK_REALTIME
));
341 static int property_get_ntp_sync(
344 const char *interface
,
345 const char *property
,
346 sd_bus_message
*reply
,
348 sd_bus_error
*error
) {
350 return sd_bus_message_append(reply
, "b", ntp_synced());
353 static int method_set_timezone(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
354 Context
*c
= userdata
;
363 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
367 if (!timezone_is_valid(z
))
368 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
370 if (streq_ptr(z
, c
->zone
))
371 return sd_bus_reply_method_return(m
, NULL
);
373 r
= bus_verify_polkit_async(
376 "org.freedesktop.timedate1.set-timezone",
385 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
394 /* 1. Write new configuration file */
395 r
= context_write_data_timezone(c
);
397 log_error_errno(r
, "Failed to set time zone: %m");
398 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %m");
401 /* 2. Tell the kernel our timezone */
402 clock_set_timezone(NULL
);
408 /* 3. Sync RTC from system clock, with the new delta */
409 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
410 assert_se(tm
= localtime(&ts
.tv_sec
));
411 clock_set_hwclock(tm
);
415 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR
,
416 "TIMEZONE=%s", c
->zone
,
417 LOG_MESSAGE("Changed time zone to '%s'.", c
->zone
),
420 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
422 return sd_bus_reply_method_return(m
, NULL
);
425 static int method_set_local_rtc(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
426 int lrtc
, fix_system
, interactive
;
427 Context
*c
= userdata
;
434 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
438 if (lrtc
== c
->local_rtc
)
439 return sd_bus_reply_method_return(m
, NULL
);
441 r
= bus_verify_polkit_async(
444 "org.freedesktop.timedate1.set-local-rtc",
457 /* 1. Write new configuration file */
458 r
= context_write_data_local_rtc(c
);
460 log_error_errno(r
, "Failed to set RTC to local/UTC: %m");
461 return sd_bus_error_set_errnof(error
, r
, "Failed to set RTC to local/UTC: %m");
464 /* 2. Tell the kernel our timezone */
465 clock_set_timezone(NULL
);
467 /* 3. Synchronize clocks */
468 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
473 /* Sync system clock from RTC; first,
474 * initialize the timezone fields of
477 tm
= *localtime(&ts
.tv_sec
);
479 tm
= *gmtime(&ts
.tv_sec
);
481 /* Override the main fields of
482 * struct tm, but not the timezone
484 if (clock_get_hwclock(&tm
) >= 0) {
486 /* And set the system clock
489 ts
.tv_sec
= mktime(&tm
);
491 ts
.tv_sec
= timegm(&tm
);
493 clock_settime(CLOCK_REALTIME
, &ts
);
499 /* Sync RTC from system clock */
501 tm
= localtime(&ts
.tv_sec
);
503 tm
= gmtime(&ts
.tv_sec
);
505 clock_set_hwclock(tm
);
508 log_info("RTC configured to %s time.", c
->local_rtc
? "local" : "UTC");
510 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL
);
512 return sd_bus_reply_method_return(m
, NULL
);
515 static int method_set_time(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
516 int relative
, interactive
;
517 Context
*c
= userdata
;
528 return sd_bus_error_setf(error
, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED
, "Automatic time synchronization is enabled");
530 /* this only gets used if dbus does not provide a timestamp */
531 start
= now(CLOCK_MONOTONIC
);
533 r
= sd_bus_message_read(m
, "xbb", &utc
, &relative
, &interactive
);
537 if (!relative
&& utc
<= 0)
538 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid absolute time");
540 if (relative
&& utc
== 0)
541 return sd_bus_reply_method_return(m
, NULL
);
546 n
= now(CLOCK_REALTIME
);
549 if ((utc
> 0 && x
< n
) ||
551 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Time value overflow");
553 timespec_store(&ts
, x
);
555 timespec_store(&ts
, (usec_t
) utc
);
557 r
= bus_verify_polkit_async(
560 "org.freedesktop.timedate1.set-time",
571 /* adjust ts for time spent in program */
572 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
573 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
574 if (r
< 0 && r
!= -ENODATA
)
577 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
579 /* Set system clock */
580 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
581 log_error_errno(errno
, "Failed to set local time: %m");
582 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
585 /* Sync down to RTC */
587 tm
= localtime(&ts
.tv_sec
);
589 tm
= gmtime(&ts
.tv_sec
);
590 clock_set_hwclock(tm
);
593 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR
,
594 "REALTIME="USEC_FMT
, timespec_load(&ts
),
595 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)),
598 return sd_bus_reply_method_return(m
, NULL
);
601 static int method_set_ntp(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
602 int enabled
, interactive
;
603 Context
*c
= userdata
;
609 r
= sd_bus_message_read(m
, "bb", &enabled
, &interactive
);
613 if ((bool)enabled
== c
->use_ntp
)
614 return sd_bus_reply_method_return(m
, NULL
);
616 r
= bus_verify_polkit_async(
619 "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 %sd", enable_disable(enabled
));
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_(sd_bus_flush_close_unrefp
) 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_async(bus
, NULL
, "org.freedesktop.timedate1", 0, NULL
, NULL
);
680 return log_error_errno(r
, "Failed to request 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");
686 *_bus
= TAKE_PTR(bus
);
691 int main(int argc
, char *argv
[]) {
692 Context context
= {};
693 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
694 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
697 log_set_target(LOG_TARGET_AUTO
);
698 log_parse_environment();
704 log_error("This program takes no arguments.");
709 r
= sd_event_default(&event
);
711 log_error_errno(r
, "Failed to allocate event loop: %m");
715 sd_event_set_watchdog(event
, true);
717 r
= connect_bus(&context
, event
, &bus
);
721 (void) sd_bus_negotiate_timestamp(bus
, true);
723 r
= context_read_data(&context
);
725 log_error_errno(r
, "Failed to read time zone data: %m");
729 r
= context_read_ntp(&context
, bus
);
731 log_error_errno(r
, "Failed to determine whether NTP is enabled: %m");
735 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
737 log_error_errno(r
, "Failed to run event loop: %m");
742 context_free(&context
);
744 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;