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(
403 "org.freedesktop.timedate1.set-timezone",
411 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
420 /* 1. Write new configuration file */
421 r = context_write_data_timezone(c);
423 log_error_errno(r, "Failed to set time zone: %m");
424 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
427 /* 2. Tell the kernel our timezone */
428 clock_set_timezone(NULL);
434 /* 3. Sync RTC from system clock, with the new delta */
435 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
436 assert_se(tm = localtime(&ts.tv_sec));
437 clock_set_hwclock(tm);
441 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
442 "TIMEZONE=%s", c->zone,
443 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
446 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
448 return sd_bus_reply_method_return(m, NULL);
451 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
452 int lrtc, fix_system, interactive;
453 Context *c = userdata;
461 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
465 if (lrtc == c->local_rtc)
466 return sd_bus_reply_method_return(m, NULL);
468 r = bus_verify_polkit_async(
471 "org.freedesktop.timedate1.set-local-rtc",
483 /* 1. Write new configuration file */
484 r = context_write_data_local_rtc(c);
486 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
487 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
490 /* 2. Tell the kernel our timezone */
491 clock_set_timezone(NULL);
493 /* 3. Synchronize clocks */
494 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
499 /* Sync system clock from RTC; first,
500 * initialize the timezone fields of
503 tm = *localtime(&ts.tv_sec);
505 tm = *gmtime(&ts.tv_sec);
507 /* Override the main fields of
508 * struct tm, but not the timezone
510 if (clock_get_hwclock(&tm) >= 0) {
512 /* And set the system clock
515 ts.tv_sec = mktime(&tm);
517 ts.tv_sec = timegm(&tm);
519 clock_settime(CLOCK_REALTIME, &ts);
525 /* Sync RTC from system clock */
527 tm = localtime(&ts.tv_sec);
529 tm = gmtime(&ts.tv_sec);
531 clock_set_hwclock(tm);
534 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
536 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
538 return sd_bus_reply_method_return(m, NULL);
541 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
542 int relative, interactive;
543 Context *c = userdata;
554 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
556 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
560 if (!relative && utc <= 0)
561 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
563 if (relative && utc == 0)
564 return sd_bus_reply_method_return(m, NULL);
569 n = now(CLOCK_REALTIME);
572 if ((utc > 0 && x < n) ||
574 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
576 timespec_store(&ts, x);
578 timespec_store(&ts, (usec_t) utc);
580 r = bus_verify_polkit_async(
583 "org.freedesktop.timedate1.set-time",
593 /* Set system clock */
594 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
595 log_error_errno(errno, "Failed to set local time: %m");
596 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
599 /* Sync down to RTC */
601 tm = localtime(&ts.tv_sec);
603 tm = gmtime(&ts.tv_sec);
604 clock_set_hwclock(tm);
607 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
608 "REALTIME="USEC_FMT, timespec_load(&ts),
609 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
612 return sd_bus_reply_method_return(m, NULL);
615 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
616 int ntp, interactive;
617 Context *c = userdata;
620 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
624 if ((bool)ntp == c->use_ntp)
625 return sd_bus_reply_method_return(m, NULL);
627 r = bus_verify_polkit_async(
630 "org.freedesktop.timedate1.set-ntp",
642 r = context_enable_ntp(c, bus, error);
646 r = context_start_ntp(c, bus, error);
650 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
652 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
654 return sd_bus_reply_method_return(m, NULL);
657 static const sd_bus_vtable timedate_vtable[] = {
658 SD_BUS_VTABLE_START(0),
659 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
660 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
661 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
662 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
663 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
664 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
665 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
666 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
667 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
668 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
669 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
673 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
674 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
681 r = sd_bus_default_system(&bus);
683 return log_error_errno(r, "Failed to get system bus connection: %m");
685 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
687 return log_error_errno(r, "Failed to register object: %m");
689 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
691 return log_error_errno(r, "Failed to register name: %m");
693 r = sd_bus_attach_event(bus, event, 0);
695 return log_error_errno(r, "Failed to attach bus to event loop: %m");
703 int main(int argc, char *argv[]) {
704 Context context = {};
705 _cleanup_event_unref_ sd_event *event = NULL;
706 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
709 log_set_target(LOG_TARGET_AUTO);
710 log_parse_environment();
716 log_error("This program takes no arguments.");
721 r = sd_event_default(&event);
723 log_error_errno(r, "Failed to allocate event loop: %m");
727 sd_event_set_watchdog(event, true);
729 r = connect_bus(&context, event, &bus);
733 r = context_read_data(&context);
735 log_error_errno(r, "Failed to read time zone data: %m");
739 r = context_read_ntp(&context, bus);
741 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
745 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
747 log_error_errno(r, "Failed to run event loop: %m");
752 context_free(&context);
754 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;