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/.");
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(sd_bus *bus, sd_bus_error *error, bool enabled) {
231 r = sd_bus_call_method(
233 "org.freedesktop.systemd1",
234 "/org/freedesktop/systemd1",
235 "org.freedesktop.systemd1.Manager",
236 enabled ? "StartUnit" : "StopUnit",
240 "systemd-timesyncd.service",
243 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
244 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
245 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
246 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
254 static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
261 r = sd_bus_call_method(
263 "org.freedesktop.systemd1",
264 "/org/freedesktop/systemd1",
265 "org.freedesktop.systemd1.Manager",
270 "systemd-timesyncd.service",
273 r = sd_bus_call_method(
275 "org.freedesktop.systemd1",
276 "/org/freedesktop/systemd1",
277 "org.freedesktop.systemd1.Manager",
282 "systemd-timesyncd.service",
286 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
287 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
292 r = sd_bus_call_method(
294 "org.freedesktop.systemd1",
295 "/org/freedesktop/systemd1",
296 "org.freedesktop.systemd1.Manager",
307 static int property_get_rtc_time(
310 const char *interface,
311 const char *property,
312 sd_bus_message *reply,
314 sd_bus_error *error) {
321 r = clock_get_hwclock(&tm);
323 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
325 } else if (r == -ENOENT) {
326 log_debug("/dev/rtc not found.");
327 t = 0; /* no RTC found */
329 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
331 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
333 return sd_bus_message_append(reply, "t", t);
336 static int property_get_time(
339 const char *interface,
340 const char *property,
341 sd_bus_message *reply,
343 sd_bus_error *error) {
345 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
348 static int property_get_ntp_sync(
351 const char *interface,
352 const char *property,
353 sd_bus_message *reply,
355 sd_bus_error *error) {
357 return sd_bus_message_append(reply, "b", ntp_synced());
360 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
361 Context *c = userdata;
371 r = sd_bus_message_read(m, "sb", &z, &interactive);
375 if (!timezone_is_valid(z))
376 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
378 if (streq_ptr(z, c->zone))
379 return sd_bus_reply_method_return(m, NULL);
381 r = bus_verify_polkit_async(
384 "org.freedesktop.timedate1.set-timezone",
392 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
401 /* 1. Write new configuration file */
402 r = context_write_data_timezone(c);
404 log_error_errno(r, "Failed to set time zone: %m");
405 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
408 /* 2. Tell the kernel our timezone */
409 clock_set_timezone(NULL);
415 /* 3. Sync RTC from system clock, with the new delta */
416 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
417 assert_se(tm = localtime(&ts.tv_sec));
418 clock_set_hwclock(tm);
422 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
423 "TIMEZONE=%s", c->zone,
424 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
427 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
429 return sd_bus_reply_method_return(m, NULL);
432 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
433 int lrtc, fix_system, interactive;
434 Context *c = userdata;
442 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
446 if (lrtc == c->local_rtc)
447 return sd_bus_reply_method_return(m, NULL);
449 r = bus_verify_polkit_async(
452 "org.freedesktop.timedate1.set-local-rtc",
464 /* 1. Write new configuration file */
465 r = context_write_data_local_rtc(c);
467 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
468 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
471 /* 2. Tell the kernel our timezone */
472 clock_set_timezone(NULL);
474 /* 3. Synchronize clocks */
475 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
480 /* Sync system clock from RTC; first,
481 * initialize the timezone fields of
484 tm = *localtime(&ts.tv_sec);
486 tm = *gmtime(&ts.tv_sec);
488 /* Override the main fields of
489 * struct tm, but not the timezone
491 if (clock_get_hwclock(&tm) >= 0) {
493 /* And set the system clock
496 ts.tv_sec = mktime(&tm);
498 ts.tv_sec = timegm(&tm);
500 clock_settime(CLOCK_REALTIME, &ts);
506 /* Sync RTC from system clock */
508 tm = localtime(&ts.tv_sec);
510 tm = gmtime(&ts.tv_sec);
512 clock_set_hwclock(tm);
515 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
517 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
519 return sd_bus_reply_method_return(m, NULL);
522 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
523 int relative, interactive;
524 Context *c = userdata;
536 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
538 /* this only gets used if dbus does not provide a timestamp */
539 start = now(CLOCK_MONOTONIC);
541 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
545 if (!relative && utc <= 0)
546 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
548 if (relative && utc == 0)
549 return sd_bus_reply_method_return(m, NULL);
554 n = now(CLOCK_REALTIME);
557 if ((utc > 0 && x < n) ||
559 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
561 timespec_store(&ts, x);
563 timespec_store(&ts, (usec_t) utc);
565 r = bus_verify_polkit_async(
568 "org.freedesktop.timedate1.set-time",
578 /* adjust ts for time spent in program */
579 r = sd_bus_message_get_monotonic_usec(m, &start);
580 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
581 if (r < 0 && r != -ENODATA)
584 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
586 /* Set system clock */
587 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
588 log_error_errno(errno, "Failed to set local time: %m");
589 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
592 /* Sync down to RTC */
594 tm = localtime(&ts.tv_sec);
596 tm = gmtime(&ts.tv_sec);
597 clock_set_hwclock(tm);
600 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
601 "REALTIME="USEC_FMT, timespec_load(&ts),
602 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
605 return sd_bus_reply_method_return(m, NULL);
608 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
609 int enabled, interactive;
610 Context *c = userdata;
613 r = sd_bus_message_read(m, "bb", &enabled, &interactive);
617 if ((bool)enabled == c->use_ntp)
618 return sd_bus_reply_method_return(m, NULL);
620 r = bus_verify_polkit_async(
623 "org.freedesktop.timedate1.set-ntp",
633 r = context_enable_ntp(bus, error, enabled);
637 r = context_start_ntp(bus, error, enabled);
641 c->use_ntp = enabled;
642 log_info("Set NTP to %s", enabled ? "enabled" : "disabled");
644 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
646 return sd_bus_reply_method_return(m, NULL);
649 static const sd_bus_vtable timedate_vtable[] = {
650 SD_BUS_VTABLE_START(0),
651 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
652 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
653 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
654 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
655 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
656 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
657 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
658 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
659 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
660 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
661 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
665 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
666 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
673 r = sd_bus_default_system(&bus);
675 return log_error_errno(r, "Failed to get system bus connection: %m");
677 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
679 return log_error_errno(r, "Failed to register object: %m");
681 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
683 return log_error_errno(r, "Failed to register name: %m");
685 r = sd_bus_attach_event(bus, event, 0);
687 return log_error_errno(r, "Failed to attach bus to event loop: %m");
695 int main(int argc, char *argv[]) {
696 Context context = {};
697 _cleanup_event_unref_ sd_event *event = NULL;
698 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
701 log_set_target(LOG_TARGET_AUTO);
702 log_parse_environment();
708 log_error("This program takes no arguments.");
713 r = sd_event_default(&event);
715 log_error_errno(r, "Failed to allocate event loop: %m");
719 sd_event_set_watchdog(event, true);
721 r = connect_bus(&context, event, &bus);
725 (void) sd_bus_negotiate_timestamp(bus, true);
727 r = context_read_data(&context);
729 log_error_errno(r, "Failed to read time zone data: %m");
733 r = context_read_ntp(&context, bus);
735 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
739 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
741 log_error_errno(r, "Failed to run event loop: %m");
746 context_free(&context);
748 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;