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;
551 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
553 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
557 if (!relative && utc <= 0)
558 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
560 if (relative && utc == 0)
561 return sd_bus_reply_method_return(m, NULL);
566 n = now(CLOCK_REALTIME);
569 if ((utc > 0 && x < n) ||
571 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
573 timespec_store(&ts, x);
575 timespec_store(&ts, (usec_t) utc);
577 r = bus_verify_polkit_async(
580 "org.freedesktop.timedate1.set-time",
590 /* Set system clock */
591 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
592 log_error_errno(errno, "Failed to set local time: %m");
593 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
596 /* Sync down to RTC */
598 tm = localtime(&ts.tv_sec);
600 tm = gmtime(&ts.tv_sec);
601 clock_set_hwclock(tm);
604 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
605 "REALTIME="USEC_FMT, timespec_load(&ts),
606 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
609 return sd_bus_reply_method_return(m, NULL);
612 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
613 int ntp, interactive;
614 Context *c = userdata;
617 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
621 if ((bool)ntp == c->use_ntp)
622 return sd_bus_reply_method_return(m, NULL);
624 r = bus_verify_polkit_async(
627 "org.freedesktop.timedate1.set-ntp",
639 r = context_enable_ntp(c, bus, error);
643 r = context_start_ntp(c, bus, error);
647 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
649 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
651 return sd_bus_reply_method_return(m, NULL);
654 static const sd_bus_vtable timedate_vtable[] = {
655 SD_BUS_VTABLE_START(0),
656 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
657 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
658 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
659 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
660 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
661 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
662 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
663 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
664 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
665 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
666 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
670 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
671 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
678 r = sd_bus_default_system(&bus);
680 return log_error_errno(r, "Failed to get system bus connection: %m");
682 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
684 return log_error_errno(r, "Failed to register object: %m");
686 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
688 return log_error_errno(r, "Failed to register name: %m");
690 r = sd_bus_attach_event(bus, event, 0);
692 return log_error_errno(r, "Failed to attach bus to event loop: %m");
700 int main(int argc, char *argv[]) {
701 Context context = {};
702 _cleanup_event_unref_ sd_event *event = NULL;
703 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
706 log_set_target(LOG_TARGET_AUTO);
707 log_parse_environment();
713 log_error("This program takes no arguments.");
718 r = sd_event_default(&event);
720 log_error_errno(r, "Failed to allocate event loop: %m");
724 sd_event_set_watchdog(event, true);
726 r = connect_bus(&context, event, &bus);
730 r = context_read_data(&context);
732 log_error_errno(r, "Failed to read time zone data: %m");
736 r = context_read_ntp(&context, bus);
738 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
742 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
744 log_error_errno(r, "Failed to run event loop: %m");
749 context_free(&context);
751 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;