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 time zone 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 time zone 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)) {
267 log_error("Failed to read NTP unit file: %m");
273 if (l[0] == 0 || l[0] == '#')
276 if (strv_extend(&r, l) < 0) {
284 r = NULL; /* avoid cleanup */
289 static int context_read_ntp(Context *c, sd_bus *bus) {
290 _cleanup_strv_free_ char **l;
297 l = get_ntp_services();
299 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
300 sd_bus_message *reply = NULL;
303 r = sd_bus_call_method(
305 "org.freedesktop.systemd1",
306 "/org/freedesktop/systemd1",
307 "org.freedesktop.systemd1.Manager",
315 /* This implementation does not exist. Try the next one. */
316 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
322 r = sd_bus_message_read(reply, "s", &s);
328 streq(s, "enabled") ||
329 streq(s, "enabled-runtime");
334 /* NTP is not installed. */
341 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
342 _cleanup_strv_free_ char **l = NULL;
350 l = get_ntp_services();
354 r = sd_bus_call_method(
356 "org.freedesktop.systemd1",
357 "/org/freedesktop/systemd1",
358 "org.freedesktop.systemd1.Manager",
362 "ss", *i, "replace");
364 r = sd_bus_call_method(
366 "org.freedesktop.systemd1",
367 "/org/freedesktop/systemd1",
368 "org.freedesktop.systemd1.Manager",
372 "ss", *i, "replace");
375 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
376 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
377 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
378 /* This implementation does not exist. Try the next one. */
379 sd_bus_error_free(error);
389 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
393 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
394 _cleanup_strv_free_ char **l = NULL;
402 l = get_ntp_services();
405 r = sd_bus_call_method(
407 "org.freedesktop.systemd1",
408 "/org/freedesktop/systemd1",
409 "org.freedesktop.systemd1.Manager",
413 "asbb", 1, *i, false, true);
415 r = sd_bus_call_method(
417 "org.freedesktop.systemd1",
418 "/org/freedesktop/systemd1",
419 "org.freedesktop.systemd1.Manager",
423 "asb", 1, *i, false);
426 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
427 /* This implementation does not exist. Try the next one. */
428 sd_bus_error_free(error);
435 r = sd_bus_call_method(
437 "org.freedesktop.systemd1",
438 "/org/freedesktop/systemd1",
439 "org.freedesktop.systemd1.Manager",
450 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
454 static int property_get_rtc_time(
457 const char *interface,
458 const char *property,
459 sd_bus_message *reply,
461 sd_bus_error *error) {
468 r = hwclock_get_time(&tm);
470 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
472 } else if (r == -ENOENT) {
473 log_debug("/dev/rtc not found.");
474 t = 0; /* no RTC found */
476 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
478 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
480 return sd_bus_message_append(reply, "t", t);
483 static int property_get_time(
486 const char *interface,
487 const char *property,
488 sd_bus_message *reply,
490 sd_bus_error *error) {
492 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
495 static int property_get_ntp_sync(
498 const char *interface,
499 const char *property,
500 sd_bus_message *reply,
502 sd_bus_error *error) {
504 return sd_bus_message_append(reply, "b", ntp_synced());
507 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
508 Context *c = userdata;
518 r = sd_bus_message_read(m, "sb", &z, &interactive);
522 if (!valid_timezone(z))
523 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
525 if (streq_ptr(z, c->zone))
526 return sd_bus_reply_method_return(m, NULL);
528 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
532 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
541 /* 1. Write new configuration file */
542 r = context_write_data_timezone(c);
544 log_error("Failed to set time zone: %s", strerror(-r));
545 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
548 /* 2. Tell the kernel our timezone */
549 hwclock_set_timezone(NULL);
555 /* 3. Sync RTC from system clock, with the new delta */
556 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
557 assert_se(tm = localtime(&ts.tv_sec));
558 hwclock_set_time(tm);
562 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
563 "TIMEZONE=%s", c->zone,
564 "MESSAGE=Changed time zone to '%s'.", c->zone,
567 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
569 return sd_bus_reply_method_return(m, NULL);
572 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
573 int lrtc, fix_system, interactive;
574 Context *c = userdata;
582 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
586 if (lrtc == c->local_rtc)
587 return sd_bus_reply_method_return(m, NULL);
589 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
597 /* 1. Write new configuration file */
598 r = context_write_data_local_rtc(c);
600 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
601 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
604 /* 2. Tell the kernel our timezone */
605 hwclock_set_timezone(NULL);
607 /* 3. Synchronize clocks */
608 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
613 /* Sync system clock from RTC; first,
614 * initialize the timezone fields of
617 tm = *localtime(&ts.tv_sec);
619 tm = *gmtime(&ts.tv_sec);
621 /* Override the main fields of
622 * struct tm, but not the timezone
624 if (hwclock_get_time(&tm) >= 0) {
626 /* And set the system clock
629 ts.tv_sec = mktime(&tm);
631 ts.tv_sec = timegm(&tm);
633 clock_settime(CLOCK_REALTIME, &ts);
639 /* Sync RTC from system clock */
641 tm = localtime(&ts.tv_sec);
643 tm = gmtime(&ts.tv_sec);
645 hwclock_set_time(tm);
648 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
650 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
652 return sd_bus_reply_method_return(m, NULL);
655 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
656 int relative, interactive;
657 Context *c = userdata;
667 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
671 if (!relative && utc <= 0)
672 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
674 if (relative && utc == 0)
675 return sd_bus_reply_method_return(m, NULL);
680 n = now(CLOCK_REALTIME);
683 if ((utc > 0 && x < n) ||
685 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
687 timespec_store(&ts, x);
689 timespec_store(&ts, (usec_t) utc);
691 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
697 /* Set system clock */
698 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
699 log_error("Failed to set local time: %m");
700 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
703 /* Sync down to RTC */
705 tm = localtime(&ts.tv_sec);
707 tm = gmtime(&ts.tv_sec);
709 hwclock_set_time(tm);
712 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
713 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
714 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
717 return sd_bus_reply_method_return(m, NULL);
720 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
721 int ntp, interactive;
722 Context *c = userdata;
725 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
729 if ((bool)ntp == c->use_ntp)
730 return sd_bus_reply_method_return(m, NULL);
732 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
740 r = context_enable_ntp(c, bus, error);
744 r = context_start_ntp(c, bus, error);
748 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
750 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
752 return sd_bus_reply_method_return(m, NULL);
755 #include <sys/capability.h>
757 static const sd_bus_vtable timedate_vtable[] = {
758 SD_BUS_VTABLE_START(0),
759 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
760 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
761 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
762 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
763 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
764 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
765 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
766 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
767 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
768 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
769 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
773 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
774 _cleanup_bus_unref_ sd_bus *bus = NULL;
781 r = sd_bus_default_system(&bus);
783 log_error("Failed to get system bus connection: %s", strerror(-r));
787 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
789 log_error("Failed to register object: %s", strerror(-r));
793 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
795 log_error("Failed to register name: %s", strerror(-r));
799 r = sd_bus_attach_event(bus, event, 0);
801 log_error("Failed to attach bus to event loop: %s", strerror(-r));
811 int main(int argc, char *argv[]) {
819 _cleanup_event_unref_ sd_event *event = NULL;
820 _cleanup_bus_unref_ sd_bus *bus = NULL;
823 log_set_target(LOG_TARGET_AUTO);
824 log_parse_environment();
830 log_error("This program takes no arguments.");
835 r = sd_event_default(&event);
837 log_error("Failed to allocate event loop: %s", strerror(-r));
841 sd_event_set_watchdog(event, true);
843 r = connect_bus(&context, event, &bus);
847 r = context_read_data(&context);
849 log_error("Failed to read time zone data: %s", strerror(-r));
853 r = context_read_ntp(&context, bus);
855 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
859 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
861 log_error("Failed to run event loop: %s", strerror(-r));
866 context_free(&context, bus);
868 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;