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"
43 #include "selinux-util.h"
45 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
46 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
48 static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
49 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", ENOTSUP),
53 typedef struct Context {
58 Hashmap *polkit_registry;
61 static void context_free(Context *c) {
65 bus_verify_polkit_async_registry_free(c->polkit_registry);
68 static int context_read_data(Context *c) {
69 _cleanup_free_ char *t = NULL;
74 r = readlink_malloc("/etc/localtime", &t);
77 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
79 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
83 e = path_startswith(t, "/usr/share/zoneinfo/");
85 e = path_startswith(t, "../usr/share/zoneinfo/");
88 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
99 if (isempty(c->zone)) {
104 c->local_rtc = clock_is_localtime() > 0;
109 static int context_write_data_timezone(Context *c) {
110 _cleanup_free_ char *p = NULL;
115 if (isempty(c->zone)) {
116 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
122 p = strappend("../usr/share/zoneinfo/", c->zone);
126 r = symlink_atomic(p, "/etc/localtime");
133 static int context_write_data_local_rtc(Context *c) {
135 _cleanup_free_ char *s = NULL, *w = NULL;
139 r = read_full_file("/etc/adjtime", &s, NULL);
147 w = strdup(NULL_ADJTIME_LOCAL);
158 p = strchr(p+1, '\n');
170 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
174 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
176 if (streq(w, NULL_ADJTIME_UTC)) {
177 if (unlink("/etc/adjtime") < 0)
185 mac_selinux_init("/etc");
186 return write_string_file_atomic_label("/etc/adjtime", w);
189 static int context_read_ntp(Context *c, sd_bus *bus) {
190 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
191 sd_bus_message *reply = NULL;
198 r = sd_bus_call_method(
200 "org.freedesktop.systemd1",
201 "/org/freedesktop/systemd1",
202 "org.freedesktop.systemd1.Manager",
207 "systemd-timesyncd.service");
210 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
211 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
212 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
218 r = sd_bus_message_read(reply, "s", &s);
223 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
228 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
236 r = sd_bus_call_method(
238 "org.freedesktop.systemd1",
239 "/org/freedesktop/systemd1",
240 "org.freedesktop.systemd1.Manager",
245 "systemd-timesyncd.service",
248 r = sd_bus_call_method(
250 "org.freedesktop.systemd1",
251 "/org/freedesktop/systemd1",
252 "org.freedesktop.systemd1.Manager",
257 "systemd-timesyncd.service",
261 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
262 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
263 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
264 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
272 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
280 r = sd_bus_call_method(
282 "org.freedesktop.systemd1",
283 "/org/freedesktop/systemd1",
284 "org.freedesktop.systemd1.Manager",
289 "systemd-timesyncd.service",
292 r = sd_bus_call_method(
294 "org.freedesktop.systemd1",
295 "/org/freedesktop/systemd1",
296 "org.freedesktop.systemd1.Manager",
301 "systemd-timesyncd.service",
305 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
306 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
311 r = sd_bus_call_method(
313 "org.freedesktop.systemd1",
314 "/org/freedesktop/systemd1",
315 "org.freedesktop.systemd1.Manager",
326 static int property_get_rtc_time(
329 const char *interface,
330 const char *property,
331 sd_bus_message *reply,
333 sd_bus_error *error) {
340 r = clock_get_hwclock(&tm);
342 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
344 } else if (r == -ENOENT) {
345 log_debug("/dev/rtc not found.");
346 t = 0; /* no RTC found */
348 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
350 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
352 return sd_bus_message_append(reply, "t", t);
355 static int property_get_time(
358 const char *interface,
359 const char *property,
360 sd_bus_message *reply,
362 sd_bus_error *error) {
364 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
367 static int property_get_ntp_sync(
370 const char *interface,
371 const char *property,
372 sd_bus_message *reply,
374 sd_bus_error *error) {
376 return sd_bus_message_append(reply, "b", ntp_synced());
379 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
380 Context *c = userdata;
390 r = sd_bus_message_read(m, "sb", &z, &interactive);
394 if (!timezone_is_valid(z))
395 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
397 if (streq_ptr(z, c->zone))
398 return sd_bus_reply_method_return(m, NULL);
400 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-timezone", interactive, &c->polkit_registry, error);
404 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
413 /* 1. Write new configuration file */
414 r = context_write_data_timezone(c);
416 log_error_errno(r, "Failed to set time zone: %m");
417 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
420 /* 2. Tell the kernel our timezone */
421 clock_set_timezone(NULL);
427 /* 3. Sync RTC from system clock, with the new delta */
428 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
429 assert_se(tm = localtime(&ts.tv_sec));
430 clock_set_hwclock(tm);
434 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
435 "TIMEZONE=%s", c->zone,
436 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
439 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
441 return sd_bus_reply_method_return(m, NULL);
444 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
445 int lrtc, fix_system, interactive;
446 Context *c = userdata;
454 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
458 if (lrtc == c->local_rtc)
459 return sd_bus_reply_method_return(m, NULL);
461 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-local-rtc", interactive, &c->polkit_registry, error);
469 /* 1. Write new configuration file */
470 r = context_write_data_local_rtc(c);
472 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
473 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
476 /* 2. Tell the kernel our timezone */
477 clock_set_timezone(NULL);
479 /* 3. Synchronize clocks */
480 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
485 /* Sync system clock from RTC; first,
486 * initialize the timezone fields of
489 tm = *localtime(&ts.tv_sec);
491 tm = *gmtime(&ts.tv_sec);
493 /* Override the main fields of
494 * struct tm, but not the timezone
496 if (clock_get_hwclock(&tm) >= 0) {
498 /* And set the system clock
501 ts.tv_sec = mktime(&tm);
503 ts.tv_sec = timegm(&tm);
505 clock_settime(CLOCK_REALTIME, &ts);
511 /* Sync RTC from system clock */
513 tm = localtime(&ts.tv_sec);
515 tm = gmtime(&ts.tv_sec);
517 clock_set_hwclock(tm);
520 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
522 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
524 return sd_bus_reply_method_return(m, NULL);
527 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
528 int relative, interactive;
529 Context *c = userdata;
540 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
542 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
546 if (!relative && utc <= 0)
547 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
549 if (relative && utc == 0)
550 return sd_bus_reply_method_return(m, NULL);
555 n = now(CLOCK_REALTIME);
558 if ((utc > 0 && x < n) ||
560 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
562 timespec_store(&ts, x);
564 timespec_store(&ts, (usec_t) utc);
566 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-time", interactive, &c->polkit_registry, error);
572 /* Set system clock */
573 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
574 log_error_errno(errno, "Failed to set local time: %m");
575 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
578 /* Sync down to RTC */
580 tm = localtime(&ts.tv_sec);
582 tm = gmtime(&ts.tv_sec);
583 clock_set_hwclock(tm);
586 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
587 "REALTIME="USEC_FMT, timespec_load(&ts),
588 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
591 return sd_bus_reply_method_return(m, NULL);
594 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
595 int ntp, interactive;
596 Context *c = userdata;
599 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
603 if ((bool)ntp == c->use_ntp)
604 return sd_bus_reply_method_return(m, NULL);
606 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-ntp", interactive, &c->polkit_registry, error);
614 r = context_enable_ntp(c, bus, error);
618 r = context_start_ntp(c, bus, error);
622 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
624 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
626 return sd_bus_reply_method_return(m, NULL);
629 static const sd_bus_vtable timedate_vtable[] = {
630 SD_BUS_VTABLE_START(0),
631 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
632 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
633 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
634 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
635 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
636 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
637 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
638 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
639 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
640 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
641 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
645 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
646 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
653 r = sd_bus_default_system(&bus);
655 return log_error_errno(r, "Failed to get system bus connection: %m");
657 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
659 return log_error_errno(r, "Failed to register object: %m");
661 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
663 return log_error_errno(r, "Failed to register name: %m");
665 r = sd_bus_attach_event(bus, event, 0);
667 return log_error_errno(r, "Failed to attach bus to event loop: %m");
675 int main(int argc, char *argv[]) {
676 Context context = {};
677 _cleanup_event_unref_ sd_event *event = NULL;
678 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
681 log_set_target(LOG_TARGET_AUTO);
682 log_parse_environment();
688 log_error("This program takes no arguments.");
693 r = sd_event_default(&event);
695 log_error_errno(r, "Failed to allocate event loop: %m");
699 sd_event_set_watchdog(event, true);
701 r = connect_bus(&context, event, &bus);
705 r = context_read_data(&context);
707 log_error_errno(r, "Failed to read time zone data: %m");
711 r = context_read_ntp(&context, bus);
713 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
717 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
719 log_error_errno(r, "Failed to run event loop: %m");
724 context_free(&context);
726 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;