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", ENOTSUP),
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/.");
96 if (isempty(c->zone)) {
101 c->local_rtc = clock_is_localtime() > 0;
106 static int context_write_data_timezone(Context *c) {
107 _cleanup_free_ char *p = NULL;
112 if (isempty(c->zone)) {
113 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
119 p = strappend("../usr/share/zoneinfo/", c->zone);
123 r = symlink_atomic(p, "/etc/localtime");
130 static int context_write_data_local_rtc(Context *c) {
132 _cleanup_free_ char *s = NULL, *w = NULL;
136 r = read_full_file("/etc/adjtime", &s, NULL);
144 w = strdup(NULL_ADJTIME_LOCAL);
155 p = strchr(p+1, '\n');
167 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
171 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
173 if (streq(w, NULL_ADJTIME_UTC)) {
174 if (unlink("/etc/adjtime") < 0)
182 mac_selinux_init("/etc");
183 return write_string_file_atomic_label("/etc/adjtime", w);
186 static int context_read_ntp(Context *c, sd_bus *bus) {
187 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
188 sd_bus_message *reply = NULL;
195 r = sd_bus_call_method(
197 "org.freedesktop.systemd1",
198 "/org/freedesktop/systemd1",
199 "org.freedesktop.systemd1.Manager",
204 "systemd-timesyncd.service");
207 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
208 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
209 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
215 r = sd_bus_message_read(reply, "s", &s);
220 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
225 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
233 r = sd_bus_call_method(
235 "org.freedesktop.systemd1",
236 "/org/freedesktop/systemd1",
237 "org.freedesktop.systemd1.Manager",
242 "systemd-timesyncd.service",
245 r = sd_bus_call_method(
247 "org.freedesktop.systemd1",
248 "/org/freedesktop/systemd1",
249 "org.freedesktop.systemd1.Manager",
254 "systemd-timesyncd.service",
258 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
259 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
260 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
261 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
269 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
277 r = sd_bus_call_method(
279 "org.freedesktop.systemd1",
280 "/org/freedesktop/systemd1",
281 "org.freedesktop.systemd1.Manager",
286 "systemd-timesyncd.service",
289 r = sd_bus_call_method(
291 "org.freedesktop.systemd1",
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
298 "systemd-timesyncd.service",
302 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
303 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
308 r = sd_bus_call_method(
310 "org.freedesktop.systemd1",
311 "/org/freedesktop/systemd1",
312 "org.freedesktop.systemd1.Manager",
323 static int property_get_rtc_time(
326 const char *interface,
327 const char *property,
328 sd_bus_message *reply,
330 sd_bus_error *error) {
337 r = clock_get_hwclock(&tm);
339 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
341 } else if (r == -ENOENT) {
342 log_debug("/dev/rtc not found.");
343 t = 0; /* no RTC found */
345 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
347 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
349 return sd_bus_message_append(reply, "t", t);
352 static int property_get_time(
355 const char *interface,
356 const char *property,
357 sd_bus_message *reply,
359 sd_bus_error *error) {
361 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
364 static int property_get_ntp_sync(
367 const char *interface,
368 const char *property,
369 sd_bus_message *reply,
371 sd_bus_error *error) {
373 return sd_bus_message_append(reply, "b", ntp_synced());
376 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
377 Context *c = userdata;
387 r = sd_bus_message_read(m, "sb", &z, &interactive);
391 if (!timezone_is_valid(z))
392 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
394 if (streq_ptr(z, c->zone))
395 return sd_bus_reply_method_return(m, NULL);
397 r = bus_verify_polkit_async(
400 "org.freedesktop.timedate1.set-timezone",
408 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
417 /* 1. Write new configuration file */
418 r = context_write_data_timezone(c);
420 log_error_errno(r, "Failed to set time zone: %m");
421 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
424 /* 2. Tell the kernel our timezone */
425 clock_set_timezone(NULL);
431 /* 3. Sync RTC from system clock, with the new delta */
432 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
433 assert_se(tm = localtime(&ts.tv_sec));
434 clock_set_hwclock(tm);
438 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
439 "TIMEZONE=%s", c->zone,
440 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
443 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
445 return sd_bus_reply_method_return(m, NULL);
448 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
449 int lrtc, fix_system, interactive;
450 Context *c = userdata;
458 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
462 if (lrtc == c->local_rtc)
463 return sd_bus_reply_method_return(m, NULL);
465 r = bus_verify_polkit_async(
468 "org.freedesktop.timedate1.set-local-rtc",
480 /* 1. Write new configuration file */
481 r = context_write_data_local_rtc(c);
483 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
484 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
487 /* 2. Tell the kernel our timezone */
488 clock_set_timezone(NULL);
490 /* 3. Synchronize clocks */
491 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
496 /* Sync system clock from RTC; first,
497 * initialize the timezone fields of
500 tm = *localtime(&ts.tv_sec);
502 tm = *gmtime(&ts.tv_sec);
504 /* Override the main fields of
505 * struct tm, but not the timezone
507 if (clock_get_hwclock(&tm) >= 0) {
509 /* And set the system clock
512 ts.tv_sec = mktime(&tm);
514 ts.tv_sec = timegm(&tm);
516 clock_settime(CLOCK_REALTIME, &ts);
522 /* Sync RTC from system clock */
524 tm = localtime(&ts.tv_sec);
526 tm = gmtime(&ts.tv_sec);
528 clock_set_hwclock(tm);
531 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
533 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
535 return sd_bus_reply_method_return(m, NULL);
538 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
539 int relative, interactive;
540 Context *c = userdata;
552 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
554 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
558 if (!relative && utc <= 0)
559 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
561 if (relative && utc == 0)
562 return sd_bus_reply_method_return(m, NULL);
567 n = now(CLOCK_REALTIME);
570 if ((utc > 0 && x < n) ||
572 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
574 timespec_store(&ts, x);
576 timespec_store(&ts, (usec_t) utc);
578 r = bus_verify_polkit_async(
581 "org.freedesktop.timedate1.set-time",
591 /* adjust ts for time spent in program */
592 r = sd_bus_message_get_monotonic_usec(m, &start);
593 if (r < 0 && r != -ENODATA)
596 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
598 /* Set system clock */
599 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
600 log_error_errno(errno, "Failed to set local time: %m");
601 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
604 /* Sync down to RTC */
606 tm = localtime(&ts.tv_sec);
608 tm = gmtime(&ts.tv_sec);
609 clock_set_hwclock(tm);
612 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
613 "REALTIME="USEC_FMT, timespec_load(&ts),
614 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
617 return sd_bus_reply_method_return(m, NULL);
620 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
621 int ntp, interactive;
622 Context *c = userdata;
625 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
629 if ((bool)ntp == c->use_ntp)
630 return sd_bus_reply_method_return(m, NULL);
632 r = bus_verify_polkit_async(
635 "org.freedesktop.timedate1.set-ntp",
647 r = context_enable_ntp(c, bus, error);
651 r = context_start_ntp(c, bus, error);
655 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
657 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
659 return sd_bus_reply_method_return(m, NULL);
662 static const sd_bus_vtable timedate_vtable[] = {
663 SD_BUS_VTABLE_START(0),
664 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
665 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
666 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
667 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
668 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
669 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
670 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
671 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
672 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
673 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
674 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
678 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
679 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
686 r = sd_bus_default_system(&bus);
688 return log_error_errno(r, "Failed to get system bus connection: %m");
690 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
692 return log_error_errno(r, "Failed to register object: %m");
694 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
696 return log_error_errno(r, "Failed to register name: %m");
698 r = sd_bus_attach_event(bus, event, 0);
700 return log_error_errno(r, "Failed to attach bus to event loop: %m");
708 int main(int argc, char *argv[]) {
709 Context context = {};
710 _cleanup_event_unref_ sd_event *event = NULL;
711 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
714 log_set_target(LOG_TARGET_AUTO);
715 log_parse_environment();
721 log_error("This program takes no arguments.");
726 r = sd_event_default(&event);
728 log_error_errno(r, "Failed to allocate event loop: %m");
732 sd_event_set_watchdog(event, true);
734 r = connect_bus(&context, event, &bus);
738 (void)sd_bus_negotiate_timestamp(bus, true);
740 r = context_read_data(&context);
742 log_error_errno(r, "Failed to read time zone data: %m");
746 r = context_read_ntp(&context, bus);
748 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
752 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
754 log_error_errno(r, "Failed to run event loop: %m");
759 context_free(&context);
761 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;