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"
35 #include "conf-files.h"
36 #include "path-util.h"
37 #include "fileio-label.h"
40 #include "event-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 typedef struct Context {
50 Hashmap *polkit_registry;
53 static void context_reset(Context *c) {
60 c->can_ntp = c->use_ntp = -1;
63 static void context_free(Context *c, sd_bus *bus) {
67 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
70 static bool valid_timezone(const char *name) {
79 if (*name == '/' || *name == 0)
82 for (p = name; *p; p++) {
83 if (!(*p >= '0' && *p <= '9') &&
84 !(*p >= 'a' && *p <= 'z') &&
85 !(*p >= 'A' && *p <= 'Z') &&
86 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
102 t = strappend("/usr/share/zoneinfo/", name);
112 if (!S_ISREG(st.st_mode))
118 static int context_read_data(Context *c) {
119 _cleanup_free_ char *t = NULL;
126 r = readlink_malloc("/etc/localtime", &t);
129 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
131 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
135 e = path_startswith(t, "/usr/share/zoneinfo/");
137 e = path_startswith(t, "../usr/share/zoneinfo/");
140 log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
151 if (isempty(c->zone)) {
156 c->local_rtc = hwclock_is_localtime() > 0;
161 static int context_write_data_timezone(Context *c) {
162 _cleanup_free_ char *p = NULL;
167 if (isempty(c->zone)) {
168 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
174 p = strappend("../usr/share/zoneinfo/", c->zone);
178 r = symlink_atomic(p, "/etc/localtime");
185 static int context_write_data_local_rtc(Context *c) {
187 _cleanup_free_ char *s = NULL, *w = NULL;
191 r = read_full_file("/etc/adjtime", &s, NULL);
199 w = strdup(NULL_ADJTIME_LOCAL);
210 p = strchr(p+1, '\n');
222 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
226 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
228 if (streq(w, NULL_ADJTIME_UTC)) {
229 if (unlink("/etc/adjtime") < 0)
238 return write_string_file_atomic_label("/etc/adjtime", w);
241 static char** get_ntp_services(void) {
242 _cleanup_strv_free_ char **r = NULL, **files = NULL;
246 k = conf_files_list(&files, ".list", NULL,
247 "/etc/systemd/ntp-units.d",
248 "/run/systemd/ntp-units.d",
249 "/usr/local/lib/systemd/ntp-units.d",
250 "/usr/lib/systemd/ntp-units.d",
255 STRV_FOREACH(i, files) {
256 _cleanup_fclose_ FILE *f;
263 char line[PATH_MAX], *l;
265 if (!fgets(line, sizeof(line), f)) {
268 log_error("Failed to read NTP units file: %m");
274 if (l[0] == 0 || l[0] == '#')
277 if (strv_extend(&r, l) < 0) {
285 r = NULL; /* avoid cleanup */
290 static int context_read_ntp(Context *c, sd_bus *bus) {
291 _cleanup_strv_free_ char **l;
298 l = get_ntp_services();
300 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
301 sd_bus_message *reply = NULL;
304 r = sd_bus_call_method(
306 "org.freedesktop.systemd1",
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
316 /* This implementation does not exist, try next one */
317 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
323 r = sd_bus_message_read(reply, "s", &s);
329 streq(s, "enabled") ||
330 streq(s, "enabled-runtime");
335 /* NTP is not installed. */
342 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
343 _cleanup_strv_free_ char **l = NULL;
351 l = get_ntp_services();
355 r = sd_bus_call_method(
357 "org.freedesktop.systemd1",
358 "/org/freedesktop/systemd1",
359 "org.freedesktop.systemd1.Manager",
363 "ss", *i, "replace");
365 r = sd_bus_call_method(
367 "org.freedesktop.systemd1",
368 "/org/freedesktop/systemd1",
369 "org.freedesktop.systemd1.Manager",
373 "ss", *i, "replace");
376 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
377 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
378 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
379 /* This implementation does not exist, try next one */
380 sd_bus_error_free(error);
390 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
394 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
395 _cleanup_strv_free_ char **l = NULL;
403 l = get_ntp_services();
406 r = sd_bus_call_method(
408 "org.freedesktop.systemd1",
409 "/org/freedesktop/systemd1",
410 "org.freedesktop.systemd1.Manager",
414 "asbb", 1, *i, false, true);
416 r = sd_bus_call_method(
418 "org.freedesktop.systemd1",
419 "/org/freedesktop/systemd1",
420 "org.freedesktop.systemd1.Manager",
424 "asb", 1, *i, false);
427 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
428 /* This implementation does not exist, try next one */
429 sd_bus_error_free(error);
436 r = sd_bus_call_method(
438 "org.freedesktop.systemd1",
439 "/org/freedesktop/systemd1",
440 "org.freedesktop.systemd1.Manager",
451 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
455 static int property_get_rtc_time(
458 const char *interface,
459 const char *property,
460 sd_bus_message *reply,
462 sd_bus_error *error) {
469 r = hwclock_get_time(&tm);
471 log_warning("/dev/rtc is busy, is somebody keeping it open continously? That's not a good idea... Returning a bogus RTC timestamp.");
473 } else if (r == -ENOENT) {
474 log_debug("Not /dev/rtc found.");
475 t = 0; /* no RTC found */
477 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
479 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
481 return sd_bus_message_append(reply, "t", t);
484 static int property_get_time(
487 const char *interface,
488 const char *property,
489 sd_bus_message *reply,
491 sd_bus_error *error) {
493 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
496 static int property_get_ntp_sync(
499 const char *interface,
500 const char *property,
501 sd_bus_message *reply,
503 sd_bus_error *error) {
505 return sd_bus_message_append(reply, "b", ntp_synced());
508 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
509 Context *c = userdata;
519 r = sd_bus_message_read(m, "sb", &z, &interactive);
523 if (!valid_timezone(z))
524 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
526 if (streq_ptr(z, c->zone))
527 return sd_bus_reply_method_return(m, NULL);
529 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
533 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
542 /* 1. Write new configuration file */
543 r = context_write_data_timezone(c);
545 log_error("Failed to set timezone: %s", strerror(-r));
546 return sd_bus_error_set_errnof(error, r, "Failed to set timezone: %s", strerror(-r));
549 /* 2. Tell the kernel our timezone */
550 hwclock_set_timezone(NULL);
556 /* 3. Sync RTC from system clock, with the new delta */
557 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
558 assert_se(tm = localtime(&ts.tv_sec));
559 hwclock_set_time(tm);
563 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
564 "TIMEZONE=%s", c->zone,
565 "MESSAGE=Changed timezone to '%s'.", c->zone,
568 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
570 return sd_bus_reply_method_return(m, NULL);
573 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
574 int lrtc, fix_system, interactive;
575 Context *c = userdata;
583 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
587 if (lrtc == c->local_rtc)
588 return sd_bus_reply_method_return(m, NULL);
590 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
598 /* 1. Write new configuration file */
599 r = context_write_data_local_rtc(c);
601 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
602 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
605 /* 2. Tell the kernel our timezone */
606 hwclock_set_timezone(NULL);
608 /* 3. Synchronize clocks */
609 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
614 /* Sync system clock from RTC; first,
615 * initialize the timezone fields of
618 tm = *localtime(&ts.tv_sec);
620 tm = *gmtime(&ts.tv_sec);
622 /* Override the main fields of
623 * struct tm, but not the timezone
625 if (hwclock_get_time(&tm) >= 0) {
627 /* And set the system clock
630 ts.tv_sec = mktime(&tm);
632 ts.tv_sec = timegm(&tm);
634 clock_settime(CLOCK_REALTIME, &ts);
640 /* Sync RTC from system clock */
642 tm = localtime(&ts.tv_sec);
644 tm = gmtime(&ts.tv_sec);
646 hwclock_set_time(tm);
649 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
651 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
653 return sd_bus_reply_method_return(m, NULL);
656 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
657 int relative, interactive;
658 Context *c = userdata;
668 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
672 if (!relative && utc <= 0)
673 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
675 if (relative && utc == 0)
676 return sd_bus_reply_method_return(m, NULL);
681 n = now(CLOCK_REALTIME);
684 if ((utc > 0 && x < n) ||
686 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
688 timespec_store(&ts, x);
690 timespec_store(&ts, (usec_t) utc);
692 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
698 /* Set system clock */
699 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
700 log_error("Failed to set local time: %m");
701 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
704 /* Sync down to RTC */
706 tm = localtime(&ts.tv_sec);
708 tm = gmtime(&ts.tv_sec);
710 hwclock_set_time(tm);
713 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
714 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
715 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
718 return sd_bus_reply_method_return(m, NULL);
721 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
722 int ntp, interactive;
723 Context *c = userdata;
726 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
730 if ((bool)ntp == c->use_ntp)
731 return sd_bus_reply_method_return(m, NULL);
733 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
741 r = context_enable_ntp(c, bus, error);
745 r = context_start_ntp(c, bus, error);
749 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
751 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
753 return sd_bus_reply_method_return(m, NULL);
756 #include <sys/capability.h>
758 static const sd_bus_vtable timedate_vtable[] = {
759 SD_BUS_VTABLE_START(0),
760 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
761 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
762 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
763 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
764 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
765 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
766 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
767 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
768 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
769 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
770 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
774 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
775 _cleanup_bus_unref_ sd_bus *bus = NULL;
782 r = sd_bus_default_system(&bus);
784 log_error("Failed to get system bus connection: %s", strerror(-r));
788 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
790 log_error("Failed to register object: %s", strerror(-r));
794 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
796 log_error("Failed to register name: %s", strerror(-r));
800 r = sd_bus_attach_event(bus, event, 0);
802 log_error("Failed to attach bus to event loop: %s", strerror(-r));
812 int main(int argc, char *argv[]) {
820 _cleanup_event_unref_ sd_event *event = NULL;
821 _cleanup_bus_unref_ sd_bus *bus = NULL;
824 log_set_target(LOG_TARGET_AUTO);
825 log_parse_environment();
831 log_error("This program takes no arguments.");
836 r = sd_event_default(&event);
838 log_error("Failed to allocate event loop: %s", strerror(-r));
842 sd_event_set_watchdog(event, true);
844 r = connect_bus(&context, event, &bus);
848 r = context_read_data(&context);
850 log_error("Failed to read timezone data: %s", strerror(-r));
854 r = context_read_ntp(&context, bus);
856 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
860 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
862 log_error("Failed to run event loop: %s", strerror(-r));
867 context_free(&context, bus);
869 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;