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/>.
27 #include "sd-messages.h"
34 #include "clock-util.h"
35 #include "conf-files.h"
36 #include "path-util.h"
37 #include "fileio-label.h"
40 #include "bus-error.h"
41 #include "bus-common-errors.h"
42 #include "event-util.h"
44 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
45 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
47 static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
48 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", ENOTSUP),
52 typedef struct Context {
57 Hashmap *polkit_registry;
60 static void context_free(Context *c) {
64 bus_verify_polkit_async_registry_free(c->polkit_registry);
67 static int context_read_data(Context *c) {
68 _cleanup_free_ char *t = NULL;
73 r = readlink_malloc("/etc/localtime", &t);
76 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
78 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
82 e = path_startswith(t, "/usr/share/zoneinfo/");
84 e = path_startswith(t, "../usr/share/zoneinfo/");
87 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
98 if (isempty(c->zone)) {
103 c->local_rtc = clock_is_localtime() > 0;
108 static int context_write_data_timezone(Context *c) {
109 _cleanup_free_ char *p = NULL;
114 if (isempty(c->zone)) {
115 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
121 p = strappend("../usr/share/zoneinfo/", c->zone);
125 r = symlink_atomic(p, "/etc/localtime");
132 static int context_write_data_local_rtc(Context *c) {
134 _cleanup_free_ char *s = NULL, *w = NULL;
138 r = read_full_file("/etc/adjtime", &s, NULL);
146 w = strdup(NULL_ADJTIME_LOCAL);
157 p = strchr(p+1, '\n');
169 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
173 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
175 if (streq(w, NULL_ADJTIME_UTC)) {
176 if (unlink("/etc/adjtime") < 0)
184 mac_selinux_init("/etc");
185 return write_string_file_atomic_label("/etc/adjtime", w);
188 static int context_read_ntp(Context *c, sd_bus *bus) {
189 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
190 sd_bus_message *reply = NULL;
197 r = sd_bus_call_method(
199 "org.freedesktop.systemd1",
200 "/org/freedesktop/systemd1",
201 "org.freedesktop.systemd1.Manager",
206 "systemd-timesyncd.service");
209 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
210 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
211 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
217 r = sd_bus_message_read(reply, "s", &s);
222 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
227 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
235 r = sd_bus_call_method(
237 "org.freedesktop.systemd1",
238 "/org/freedesktop/systemd1",
239 "org.freedesktop.systemd1.Manager",
244 "systemd-timesyncd.service",
247 r = sd_bus_call_method(
249 "org.freedesktop.systemd1",
250 "/org/freedesktop/systemd1",
251 "org.freedesktop.systemd1.Manager",
256 "systemd-timesyncd.service",
260 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
261 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
262 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
263 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
271 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
279 r = sd_bus_call_method(
281 "org.freedesktop.systemd1",
282 "/org/freedesktop/systemd1",
283 "org.freedesktop.systemd1.Manager",
288 "systemd-timesyncd.service",
291 r = sd_bus_call_method(
293 "org.freedesktop.systemd1",
294 "/org/freedesktop/systemd1",
295 "org.freedesktop.systemd1.Manager",
300 "systemd-timesyncd.service",
304 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
305 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
310 r = sd_bus_call_method(
312 "org.freedesktop.systemd1",
313 "/org/freedesktop/systemd1",
314 "org.freedesktop.systemd1.Manager",
325 static int property_get_rtc_time(
328 const char *interface,
329 const char *property,
330 sd_bus_message *reply,
332 sd_bus_error *error) {
339 r = clock_get_hwclock(&tm);
341 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
343 } else if (r == -ENOENT) {
344 log_debug("/dev/rtc not found.");
345 t = 0; /* no RTC found */
347 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
349 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
351 return sd_bus_message_append(reply, "t", t);
354 static int property_get_time(
357 const char *interface,
358 const char *property,
359 sd_bus_message *reply,
361 sd_bus_error *error) {
363 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
366 static int property_get_ntp_sync(
369 const char *interface,
370 const char *property,
371 sd_bus_message *reply,
373 sd_bus_error *error) {
375 return sd_bus_message_append(reply, "b", ntp_synced());
378 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
379 Context *c = userdata;
389 r = sd_bus_message_read(m, "sb", &z, &interactive);
393 if (!timezone_is_valid(z))
394 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
396 if (streq_ptr(z, c->zone))
397 return sd_bus_reply_method_return(m, NULL);
399 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-timezone", interactive, &c->polkit_registry, error);
403 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
412 /* 1. Write new configuration file */
413 r = context_write_data_timezone(c);
415 log_error_errno(r, "Failed to set time zone: %m");
416 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
419 /* 2. Tell the kernel our timezone */
420 clock_set_timezone(NULL);
426 /* 3. Sync RTC from system clock, with the new delta */
427 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
428 assert_se(tm = localtime(&ts.tv_sec));
429 clock_set_hwclock(tm);
433 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
434 "TIMEZONE=%s", c->zone,
435 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
438 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
440 return sd_bus_reply_method_return(m, NULL);
443 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
444 int lrtc, fix_system, interactive;
445 Context *c = userdata;
453 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
457 if (lrtc == c->local_rtc)
458 return sd_bus_reply_method_return(m, NULL);
460 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-local-rtc", interactive, &c->polkit_registry, error);
468 /* 1. Write new configuration file */
469 r = context_write_data_local_rtc(c);
471 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
472 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
475 /* 2. Tell the kernel our timezone */
476 clock_set_timezone(NULL);
478 /* 3. Synchronize clocks */
479 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
484 /* Sync system clock from RTC; first,
485 * initialize the timezone fields of
488 tm = *localtime(&ts.tv_sec);
490 tm = *gmtime(&ts.tv_sec);
492 /* Override the main fields of
493 * struct tm, but not the timezone
495 if (clock_get_hwclock(&tm) >= 0) {
497 /* And set the system clock
500 ts.tv_sec = mktime(&tm);
502 ts.tv_sec = timegm(&tm);
504 clock_settime(CLOCK_REALTIME, &ts);
510 /* Sync RTC from system clock */
512 tm = localtime(&ts.tv_sec);
514 tm = gmtime(&ts.tv_sec);
516 clock_set_hwclock(tm);
519 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
521 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
523 return sd_bus_reply_method_return(m, NULL);
526 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
527 int relative, interactive;
528 Context *c = userdata;
539 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
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(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-time", interactive, &c->polkit_registry, error);
571 /* Set system clock */
572 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
573 log_error_errno(errno, "Failed to set local time: %m");
574 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
577 /* Sync down to RTC */
579 tm = localtime(&ts.tv_sec);
581 tm = gmtime(&ts.tv_sec);
582 clock_set_hwclock(tm);
585 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
586 "REALTIME="USEC_FMT, timespec_load(&ts),
587 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
590 return sd_bus_reply_method_return(m, NULL);
593 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
594 int ntp, interactive;
595 Context *c = userdata;
598 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
602 if ((bool)ntp == c->use_ntp)
603 return sd_bus_reply_method_return(m, NULL);
605 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-ntp", interactive, &c->polkit_registry, error);
613 r = context_enable_ntp(c, bus, error);
617 r = context_start_ntp(c, bus, error);
621 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
623 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
625 return sd_bus_reply_method_return(m, NULL);
628 static const sd_bus_vtable timedate_vtable[] = {
629 SD_BUS_VTABLE_START(0),
630 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
631 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
632 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
633 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
634 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
635 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
636 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
637 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
638 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
639 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
640 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
644 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
645 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
652 r = sd_bus_default_system(&bus);
654 return log_error_errno(r, "Failed to get system bus connection: %m");
656 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
658 return log_error_errno(r, "Failed to register object: %m");
660 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
662 return log_error_errno(r, "Failed to register name: %m");
664 r = sd_bus_attach_event(bus, event, 0);
666 return log_error_errno(r, "Failed to attach bus to event loop: %m");
674 int main(int argc, char *argv[]) {
675 Context context = {};
676 _cleanup_event_unref_ sd_event *event = NULL;
677 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
680 log_set_target(LOG_TARGET_AUTO);
681 log_parse_environment();
687 log_error("This program takes no arguments.");
692 r = sd_event_default(&event);
694 log_error_errno(r, "Failed to allocate event loop: %m");
698 sd_event_set_watchdog(event, true);
700 r = connect_bus(&context, event, &bus);
704 r = context_read_data(&context);
706 log_error_errno(r, "Failed to read time zone data: %m");
710 r = context_read_ntp(&context, bus);
712 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
716 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
718 log_error_errno(r, "Failed to run event loop: %m");
723 context_free(&context);
725 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;