1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "sd-messages.h"
11 #include "alloc-util.h"
12 #include "bus-common-errors.h"
13 #include "bus-error.h"
15 #include "clock-util.h"
17 #include "fileio-label.h"
21 #include "path-util.h"
22 #include "selinux-util.h"
23 #include "string-util.h"
26 #include "unit-name.h"
27 #include "user-util.h"
30 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
31 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
33 typedef struct UnitStatusInfo
{
36 char *unit_file_state
;
39 LIST_FIELDS(struct UnitStatusInfo
, units
);
42 typedef struct Context
{
45 Hashmap
*polkit_registry
;
47 LIST_HEAD(UnitStatusInfo
, units
);
50 static void unit_status_info_clear(UnitStatusInfo
*p
) {
53 p
->load_state
= mfree(p
->load_state
);
54 p
->unit_file_state
= mfree(p
->unit_file_state
);
55 p
->active_state
= mfree(p
->active_state
);
58 static void unit_status_info_free(UnitStatusInfo
*p
) {
61 unit_status_info_clear(p
);
66 static void context_free(Context
*c
) {
72 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
74 while ((p
= c
->units
)) {
75 LIST_REMOVE(units
, c
->units
, p
);
76 unit_status_info_free(p
);
80 static int context_add_ntp_service(Context
*c
, const char *s
) {
83 if (!unit_name_is_valid(s
, UNIT_NAME_PLAIN
))
86 /* Do not add this if it is already listed */
87 LIST_FOREACH(units
, u
, c
->units
)
88 if (streq(u
->name
, s
))
91 u
= new0(UnitStatusInfo
, 1);
101 LIST_APPEND(units
, c
->units
, u
);
106 static int context_parse_ntp_services(Context
*c
) {
112 env
= getenv("SYSTEMD_TIMEDATED_NTP_SERVICES");
114 r
= context_add_ntp_service(c
, "systemd-timesyncd.service");
116 log_warning_errno(r
, "Failed to add NTP service \"systemd-timesyncd.service\", ignoring: %m");
122 _cleanup_free_
char *word
= NULL
;
124 r
= extract_first_word(&p
, &word
, ":", 0);
130 log_error("Invalid syntax, ignoring: %s", env
);
134 r
= context_add_ntp_service(c
, word
);
136 log_warning_errno(r
, "Failed to add NTP service \"%s\", ignoring: %m", word
);
142 static int context_ntp_service_is_active(Context
*c
) {
143 UnitStatusInfo
*info
;
148 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
150 LIST_FOREACH(units
, info
, c
->units
)
151 count
+= streq_ptr(info
->active_state
, "active");
156 static int context_ntp_service_is_enabled(Context
*c
) {
157 UnitStatusInfo
*info
;
162 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
164 LIST_FOREACH(units
, info
, c
->units
)
165 count
+= STRPTR_IN_SET(info
->unit_file_state
, "enabled", "enabled-runtime");
170 static int context_ntp_service_exists(Context
*c
) {
171 UnitStatusInfo
*info
;
176 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
178 LIST_FOREACH(units
, info
, c
->units
)
179 count
+= streq_ptr(info
->load_state
, "loaded");
184 static int context_read_data(Context
*c
) {
185 _cleanup_free_
char *t
= NULL
;
190 r
= get_timezone(&t
);
192 log_warning_errno(r
, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
194 log_warning_errno(r
, "Failed to get target of /etc/localtime: %m");
196 free_and_replace(c
->zone
, t
);
198 c
->local_rtc
= clock_is_localtime(NULL
) > 0;
203 static int context_write_data_timezone(Context
*c
) {
204 _cleanup_free_
char *p
= NULL
;
209 if (isempty(c
->zone
)) {
210 if (unlink("/etc/localtime") < 0 && errno
!= ENOENT
)
216 p
= strappend("../usr/share/zoneinfo/", c
->zone
);
220 r
= symlink_atomic(p
, "/etc/localtime");
227 static int context_write_data_local_rtc(Context
*c
) {
229 _cleanup_free_
char *s
= NULL
, *w
= NULL
;
233 r
= read_full_file("/etc/adjtime", &s
, NULL
);
241 w
= strdup(NULL_ADJTIME_LOCAL
);
246 const char *e
= "\n"; /* default if there is less than 3 lines */
247 const char *prepend
= "";
250 p
= strchrnul(s
, '\n');
252 /* only one line, no \n terminator */
254 else if (p
[1] == '\0') {
255 /* only one line, with \n terminator */
259 p
= strchr(p
+1, '\n');
261 /* only two lines, no \n terminator */
266 /* third line might have a \n terminator or not */
268 end
= strchr(p
, '\n');
269 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
278 w
= new(char, a
+ (c
->local_rtc
? 5 : 3) + strlen(prepend
) + b
+ 1);
282 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w
, s
, a
), prepend
), c
->local_rtc
? "LOCAL" : "UTC"), e
, b
) = 0;
284 if (streq(w
, NULL_ADJTIME_UTC
)) {
285 if (unlink("/etc/adjtime") < 0)
294 return write_string_file_atomic_label("/etc/adjtime", w
);
297 static int context_update_ntp_status(Context
*c
, sd_bus
*bus
, sd_bus_message
*m
) {
298 static const struct bus_properties_map map
[] = {
299 { "LoadState", "s", NULL
, offsetof(UnitStatusInfo
, load_state
) },
300 { "ActiveState", "s", NULL
, offsetof(UnitStatusInfo
, active_state
) },
301 { "UnitFileState", "s", NULL
, offsetof(UnitStatusInfo
, unit_file_state
) },
304 static sd_bus_message
*_m
= NULL
;
311 /* Suppress multiple call of context_update_ntp_status() within single DBus transaction. */
317 LIST_FOREACH(units
, u
, c
->units
) {
318 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
319 _cleanup_free_
char *path
= NULL
;
321 unit_status_info_clear(u
);
323 path
= unit_dbus_path_from_name(u
->name
);
327 r
= bus_map_all_properties(
329 "org.freedesktop.systemd1",
337 return log_error_errno(r
, "Failed to get properties: %s", bus_error_message(&error
, r
));
343 static int unit_start_or_stop(UnitStatusInfo
*u
, sd_bus
*bus
, sd_bus_error
*error
, bool start
) {
350 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
352 if (streq(u
->active_state
, "active") == start
)
355 r
= sd_bus_call_method(
357 "org.freedesktop.systemd1",
358 "/org/freedesktop/systemd1",
359 "org.freedesktop.systemd1.Manager",
360 start
? "StartUnit" : "StopUnit",
372 static int unit_enable_or_disable(UnitStatusInfo
*u
, sd_bus
*bus
, sd_bus_error
*error
, bool enable
) {
379 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
381 if (streq(u
->unit_file_state
, "enabled") == enable
)
385 r
= sd_bus_call_method(
387 "org.freedesktop.systemd1",
388 "/org/freedesktop/systemd1",
389 "org.freedesktop.systemd1.Manager",
397 r
= sd_bus_call_method(
399 "org.freedesktop.systemd1",
400 "/org/freedesktop/systemd1",
401 "org.freedesktop.systemd1.Manager",
411 r
= sd_bus_call_method(
413 "org.freedesktop.systemd1",
414 "/org/freedesktop/systemd1",
415 "org.freedesktop.systemd1.Manager",
425 static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_time
, "t", now(CLOCK_REALTIME
));
426 static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_ntp_sync
, "b", ntp_synced());
428 static int property_get_rtc_time(
431 const char *interface
,
432 const char *property
,
433 sd_bus_message
*reply
,
435 sd_bus_error
*error
) {
442 r
= clock_get_hwclock(&tm
);
444 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
446 } else if (r
== -ENOENT
) {
447 log_debug("/dev/rtc not found.");
448 t
= 0; /* no RTC found */
450 return sd_bus_error_set_errnof(error
, r
, "Failed to read RTC: %m");
452 t
= (usec_t
) timegm(&tm
) * USEC_PER_SEC
;
454 return sd_bus_message_append(reply
, "t", t
);
457 static int property_get_can_ntp(
460 const char *interface
,
461 const char *property
,
462 sd_bus_message
*reply
,
464 sd_bus_error
*error
) {
466 Context
*c
= userdata
;
475 r
= context_update_ntp_status(c
, bus
, reply
);
479 return sd_bus_message_append(reply
, "b", context_ntp_service_exists(c
) > 0);
482 static int property_get_ntp(
485 const char *interface
,
486 const char *property
,
487 sd_bus_message
*reply
,
489 sd_bus_error
*error
) {
491 Context
*c
= userdata
;
500 r
= context_update_ntp_status(c
, bus
, reply
);
504 return sd_bus_message_append(reply
, "b", context_ntp_service_is_active(c
) > 0);
507 static int method_set_timezone(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
508 Context
*c
= userdata
;
515 r
= sd_bus_message_read(m
, "sb", &z
, &interactive
);
519 if (!timezone_is_valid(z
, LOG_DEBUG
))
520 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid time zone '%s'", z
);
522 if (streq_ptr(z
, c
->zone
))
523 return sd_bus_reply_method_return(m
, NULL
);
525 r
= bus_verify_polkit_async(
528 "org.freedesktop.timedate1.set-timezone",
537 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
539 r
= free_and_strdup(&c
->zone
, z
);
543 /* 1. Write new configuration file */
544 r
= context_write_data_timezone(c
);
546 log_error_errno(r
, "Failed to set time zone: %m");
547 return sd_bus_error_set_errnof(error
, r
, "Failed to set time zone: %m");
550 /* 2. Make glibc notice the new timezone */
553 /* 3. Tell the kernel our timezone */
554 r
= clock_set_timezone(NULL
);
556 log_debug_errno(r
, "Failed to tell kernel about timezone, ignoring: %m");
562 /* 4. Sync RTC from system clock, with the new delta */
563 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
564 assert_se(tm
= localtime(&ts
.tv_sec
));
566 r
= clock_set_hwclock(tm
);
568 log_debug_errno(r
, "Failed to sync time to hardware clock, ignoring: %m");
572 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR
,
573 "TIMEZONE=%s", c
->zone
,
574 "TIMEZONE_SHORTNAME=%s", tzname
[daylight
],
575 "DAYLIGHT=%i", daylight
,
576 LOG_MESSAGE("Changed time zone to '%s' (%s).", c
->zone
, tzname
[daylight
]));
578 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL
);
580 return sd_bus_reply_method_return(m
, NULL
);
583 static int method_set_local_rtc(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
584 int lrtc
, fix_system
, interactive
;
585 Context
*c
= userdata
;
592 r
= sd_bus_message_read(m
, "bbb", &lrtc
, &fix_system
, &interactive
);
596 if (lrtc
== c
->local_rtc
)
597 return sd_bus_reply_method_return(m
, NULL
);
599 r
= bus_verify_polkit_async(
602 "org.freedesktop.timedate1.set-local-rtc",
615 /* 1. Write new configuration file */
616 r
= context_write_data_local_rtc(c
);
618 log_error_errno(r
, "Failed to set RTC to local/UTC: %m");
619 return sd_bus_error_set_errnof(error
, r
, "Failed to set RTC to local/UTC: %m");
622 /* 2. Tell the kernel our timezone */
623 r
= clock_set_timezone(NULL
);
625 log_debug_errno(r
, "Failed to tell kernel about timezone, ignoring: %m");
627 /* 3. Synchronize clocks */
628 assert_se(clock_gettime(CLOCK_REALTIME
, &ts
) == 0);
633 /* Sync system clock from RTC; first, initialize the timezone fields of struct tm. */
635 tm
= *localtime(&ts
.tv_sec
);
637 tm
= *gmtime(&ts
.tv_sec
);
639 /* Override the main fields of struct tm, but not the timezone fields */
640 r
= clock_get_hwclock(&tm
);
642 log_debug_errno(r
, "Failed to get hardware clock, ignoring: %m");
644 /* And set the system clock with this */
646 ts
.tv_sec
= mktime(&tm
);
648 ts
.tv_sec
= timegm(&tm
);
650 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0)
651 log_debug_errno(errno
, "Failed to update system clock, ignoring: %m");
657 /* Sync RTC from system clock */
659 tm
= localtime(&ts
.tv_sec
);
661 tm
= gmtime(&ts
.tv_sec
);
663 r
= clock_set_hwclock(tm
);
665 log_debug_errno(r
, "Failed to sync time to hardware clock, ignoring: %m");
668 log_info("RTC configured to %s time.", c
->local_rtc
? "local" : "UTC");
670 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL
);
672 return sd_bus_reply_method_return(m
, NULL
);
675 static int method_set_time(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
676 sd_bus
*bus
= sd_bus_message_get_bus(m
);
677 int relative
, interactive
, r
;
678 Context
*c
= userdata
;
687 r
= context_update_ntp_status(c
, bus
, m
);
689 return sd_bus_error_set_errnof(error
, r
, "Failed to update context: %m");
691 if (context_ntp_service_is_active(c
) > 0)
692 return sd_bus_error_set(error
, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED
, "Automatic time synchronization is enabled");
694 /* this only gets used if dbus does not provide a timestamp */
695 start
= now(CLOCK_MONOTONIC
);
697 r
= sd_bus_message_read(m
, "xbb", &utc
, &relative
, &interactive
);
701 if (!relative
&& utc
<= 0)
702 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid absolute time");
704 if (relative
&& utc
== 0)
705 return sd_bus_reply_method_return(m
, NULL
);
710 n
= now(CLOCK_REALTIME
);
713 if ((utc
> 0 && x
< n
) ||
715 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Time value overflow");
717 timespec_store(&ts
, x
);
719 timespec_store(&ts
, (usec_t
) utc
);
721 r
= bus_verify_polkit_async(
724 "org.freedesktop.timedate1.set-time",
735 /* adjust ts for time spent in program */
736 r
= sd_bus_message_get_monotonic_usec(m
, &start
);
737 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
738 if (r
< 0 && r
!= -ENODATA
)
741 timespec_store(&ts
, timespec_load(&ts
) + (now(CLOCK_MONOTONIC
) - start
));
743 /* Set system clock */
744 if (clock_settime(CLOCK_REALTIME
, &ts
) < 0) {
745 log_error_errno(errno
, "Failed to set local time: %m");
746 return sd_bus_error_set_errnof(error
, errno
, "Failed to set local time: %m");
749 /* Sync down to RTC */
751 tm
= localtime(&ts
.tv_sec
);
753 tm
= gmtime(&ts
.tv_sec
);
755 r
= clock_set_hwclock(tm
);
757 log_debug_errno(r
, "Failed to update hardware clock, ignoring: %m");
760 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR
,
761 "REALTIME="USEC_FMT
, timespec_load(&ts
),
762 LOG_MESSAGE("Changed local time to %s", ctime(&ts
.tv_sec
)));
764 return sd_bus_reply_method_return(m
, NULL
);
767 static int method_set_ntp(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
768 sd_bus
*bus
= sd_bus_message_get_bus(m
);
769 Context
*c
= userdata
;
771 int enable
, interactive
, q
, r
;
777 r
= sd_bus_message_read(m
, "bb", &enable
, &interactive
);
781 r
= context_update_ntp_status(c
, bus
, m
);
785 if (context_ntp_service_exists(c
) <= 0)
786 return sd_bus_error_set(error
, BUS_ERROR_NO_NTP_SUPPORT
, "NTP not supported");
788 r
= bus_verify_polkit_async(
791 "org.freedesktop.timedate1.set-ntp",
803 LIST_FOREACH(units
, u
, c
->units
) {
804 if (!streq(u
->load_state
, "loaded"))
807 q
= unit_enable_or_disable(u
, bus
, error
, enable
);
811 q
= unit_start_or_stop(u
, bus
, error
, enable
);
816 else if (context_ntp_service_is_enabled(c
) <= 0)
817 LIST_FOREACH(units
, u
, c
->units
) {
818 if (!streq(u
->load_state
, "loaded"))
821 r
= unit_enable_or_disable(u
, bus
, error
, enable
);
825 r
= unit_start_or_stop(u
, bus
, error
, enable
);
829 else if (context_ntp_service_is_active(c
) <= 0)
830 LIST_FOREACH(units
, u
, c
->units
) {
831 if (!streq(u
->load_state
, "loaded") ||
832 !streq(u
->unit_file_state
, "enabled"))
835 r
= unit_start_or_stop(u
, bus
, error
, enable
);
842 log_info("Set NTP to %sd", enable_disable(enable
));
844 (void) sd_bus_emit_properties_changed(bus
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL
);
846 return sd_bus_reply_method_return(m
, NULL
);
849 static const sd_bus_vtable timedate_vtable
[] = {
850 SD_BUS_VTABLE_START(0),
851 SD_BUS_PROPERTY("Timezone", "s", NULL
, offsetof(Context
, zone
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
852 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool
, offsetof(Context
, local_rtc
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
853 SD_BUS_PROPERTY("CanNTP", "b", property_get_can_ntp
, 0, 0),
854 SD_BUS_PROPERTY("NTP", "b", property_get_ntp
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
855 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync
, 0, 0),
856 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time
, 0, 0),
857 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time
, 0, 0),
858 SD_BUS_METHOD("SetTime", "xbb", NULL
, method_set_time
, SD_BUS_VTABLE_UNPRIVILEGED
),
859 SD_BUS_METHOD("SetTimezone", "sb", NULL
, method_set_timezone
, SD_BUS_VTABLE_UNPRIVILEGED
),
860 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL
, method_set_local_rtc
, SD_BUS_VTABLE_UNPRIVILEGED
),
861 SD_BUS_METHOD("SetNTP", "bb", NULL
, method_set_ntp
, SD_BUS_VTABLE_UNPRIVILEGED
),
865 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
866 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
873 r
= sd_bus_default_system(&bus
);
875 return log_error_errno(r
, "Failed to get system bus connection: %m");
877 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable
, c
);
879 return log_error_errno(r
, "Failed to register object: %m");
881 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.timedate1", 0, NULL
, NULL
);
883 return log_error_errno(r
, "Failed to request name: %m");
885 r
= sd_bus_attach_event(bus
, event
, 0);
887 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
889 *_bus
= TAKE_PTR(bus
);
894 int main(int argc
, char *argv
[]) {
895 Context context
= {};
896 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
897 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
900 log_set_target(LOG_TARGET_AUTO
);
901 log_parse_environment();
907 log_error("This program takes no arguments.");
912 r
= sd_event_default(&event
);
914 log_error_errno(r
, "Failed to allocate event loop: %m");
918 sd_event_set_watchdog(event
, true);
920 r
= connect_bus(&context
, event
, &bus
);
924 (void) sd_bus_negotiate_timestamp(bus
, true);
926 r
= context_read_data(&context
);
928 log_error_errno(r
, "Failed to read time zone data: %m");
932 r
= context_parse_ntp_services(&context
);
936 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
938 log_error_errno(r
, "Failed to run event loop: %m");
943 context_free(&context
);
945 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;