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-errors.h"
41 #include "event-util.h"
43 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
44 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
46 typedef struct Context {
51 Hashmap *polkit_registry;
54 static void context_free(Context *c, sd_bus *bus) {
58 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
61 static bool valid_timezone(const char *name) {
70 if (*name == '/' || *name == 0)
73 for (p = name; *p; p++) {
74 if (!(*p >= '0' && *p <= '9') &&
75 !(*p >= 'a' && *p <= 'z') &&
76 !(*p >= 'A' && *p <= 'Z') &&
77 !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
93 t = strappend("/usr/share/zoneinfo/", name);
103 if (!S_ISREG(st.st_mode))
109 static int context_read_data(Context *c) {
110 _cleanup_free_ char *t = NULL;
115 r = readlink_malloc("/etc/localtime", &t);
118 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
120 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
124 e = path_startswith(t, "/usr/share/zoneinfo/");
126 e = path_startswith(t, "../usr/share/zoneinfo/");
129 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
140 if (isempty(c->zone)) {
145 c->local_rtc = clock_is_localtime() > 0;
150 static int context_write_data_timezone(Context *c) {
151 _cleanup_free_ char *p = NULL;
156 if (isempty(c->zone)) {
157 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
163 p = strappend("../usr/share/zoneinfo/", c->zone);
167 r = symlink_atomic(p, "/etc/localtime");
174 static int context_write_data_local_rtc(Context *c) {
176 _cleanup_free_ char *s = NULL, *w = NULL;
180 r = read_full_file("/etc/adjtime", &s, NULL);
188 w = strdup(NULL_ADJTIME_LOCAL);
199 p = strchr(p+1, '\n');
211 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
215 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
217 if (streq(w, NULL_ADJTIME_UTC)) {
218 if (unlink("/etc/adjtime") < 0)
227 return write_string_file_atomic_label("/etc/adjtime", w);
230 static char** get_ntp_services(void) {
231 _cleanup_strv_free_ char **r = NULL, **files = NULL;
235 k = conf_files_list(&files, ".list", NULL,
236 "/etc/systemd/ntp-units.d",
237 "/run/systemd/ntp-units.d",
238 "/usr/local/lib/systemd/ntp-units.d",
239 "/usr/lib/systemd/ntp-units.d",
244 STRV_FOREACH(i, files) {
245 _cleanup_fclose_ FILE *f;
252 char line[PATH_MAX], *l;
254 if (!fgets(line, sizeof(line), f)) {
256 log_error("Failed to read NTP unit file: %m");
262 if (l[0] == 0 || l[0] == '#')
265 if (strv_extend(&r, l) < 0) {
273 r = NULL; /* avoid cleanup */
278 static int context_read_ntp(Context *c, sd_bus *bus) {
279 _cleanup_strv_free_ char **l;
286 l = get_ntp_services();
288 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
289 sd_bus_message *reply = NULL;
292 r = sd_bus_call_method(
294 "org.freedesktop.systemd1",
295 "/org/freedesktop/systemd1",
296 "org.freedesktop.systemd1.Manager",
304 /* This implementation does not exist. Try the next one. */
305 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
311 r = sd_bus_message_read(reply, "s", &s);
316 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
324 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
325 _cleanup_strv_free_ char **l = NULL;
333 l = get_ntp_services();
337 r = sd_bus_call_method(
339 "org.freedesktop.systemd1",
340 "/org/freedesktop/systemd1",
341 "org.freedesktop.systemd1.Manager",
345 "ss", *i, "replace");
347 r = sd_bus_call_method(
349 "org.freedesktop.systemd1",
350 "/org/freedesktop/systemd1",
351 "org.freedesktop.systemd1.Manager",
355 "ss", *i, "replace");
358 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
359 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
360 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
361 /* This implementation does not exist. Try the next one. */
362 sd_bus_error_free(error);
372 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
376 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
377 _cleanup_strv_free_ char **l = NULL;
385 l = get_ntp_services();
388 r = sd_bus_call_method(
390 "org.freedesktop.systemd1",
391 "/org/freedesktop/systemd1",
392 "org.freedesktop.systemd1.Manager",
396 "asbb", 1, *i, false, true);
398 r = sd_bus_call_method(
400 "org.freedesktop.systemd1",
401 "/org/freedesktop/systemd1",
402 "org.freedesktop.systemd1.Manager",
406 "asb", 1, *i, false);
409 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
410 /* This implementation does not exist. Try the next one. */
411 sd_bus_error_free(error);
418 r = sd_bus_call_method(
420 "org.freedesktop.systemd1",
421 "/org/freedesktop/systemd1",
422 "org.freedesktop.systemd1.Manager",
433 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
437 static int property_get_rtc_time(
440 const char *interface,
441 const char *property,
442 sd_bus_message *reply,
444 sd_bus_error *error) {
451 r = clock_get_hwclock(&tm);
453 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
455 } else if (r == -ENOENT) {
456 log_debug("/dev/rtc not found.");
457 t = 0; /* no RTC found */
459 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
461 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
463 return sd_bus_message_append(reply, "t", t);
466 static int property_get_time(
469 const char *interface,
470 const char *property,
471 sd_bus_message *reply,
473 sd_bus_error *error) {
475 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
478 static int property_get_ntp_sync(
481 const char *interface,
482 const char *property,
483 sd_bus_message *reply,
485 sd_bus_error *error) {
487 return sd_bus_message_append(reply, "b", ntp_synced());
490 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
491 Context *c = userdata;
501 r = sd_bus_message_read(m, "sb", &z, &interactive);
505 if (!valid_timezone(z))
506 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
508 if (streq_ptr(z, c->zone))
509 return sd_bus_reply_method_return(m, NULL);
511 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
515 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
524 /* 1. Write new configuration file */
525 r = context_write_data_timezone(c);
527 log_error("Failed to set time zone: %s", strerror(-r));
528 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
531 /* 2. Tell the kernel our timezone */
532 clock_set_timezone(NULL);
538 /* 3. Sync RTC from system clock, with the new delta */
539 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
540 assert_se(tm = localtime(&ts.tv_sec));
541 clock_set_hwclock(tm);
545 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
546 "TIMEZONE=%s", c->zone,
547 "MESSAGE=Changed time zone to '%s'.", c->zone,
550 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
552 return sd_bus_reply_method_return(m, NULL);
555 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
556 int lrtc, fix_system, interactive;
557 Context *c = userdata;
565 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
569 if (lrtc == c->local_rtc)
570 return sd_bus_reply_method_return(m, NULL);
572 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
580 /* 1. Write new configuration file */
581 r = context_write_data_local_rtc(c);
583 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
584 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
587 /* 2. Tell the kernel our timezone */
588 clock_set_timezone(NULL);
590 /* 3. Synchronize clocks */
591 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
596 /* Sync system clock from RTC; first,
597 * initialize the timezone fields of
600 tm = *localtime(&ts.tv_sec);
602 tm = *gmtime(&ts.tv_sec);
604 /* Override the main fields of
605 * struct tm, but not the timezone
607 if (clock_get_hwclock(&tm) >= 0) {
609 /* And set the system clock
612 ts.tv_sec = mktime(&tm);
614 ts.tv_sec = timegm(&tm);
616 clock_settime(CLOCK_REALTIME, &ts);
622 /* Sync RTC from system clock */
624 tm = localtime(&ts.tv_sec);
626 tm = gmtime(&ts.tv_sec);
628 clock_set_hwclock(tm);
631 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
633 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
635 return sd_bus_reply_method_return(m, NULL);
638 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
639 int relative, interactive;
640 Context *c = userdata;
651 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
653 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
657 if (!relative && utc <= 0)
658 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
660 if (relative && utc == 0)
661 return sd_bus_reply_method_return(m, NULL);
666 n = now(CLOCK_REALTIME);
669 if ((utc > 0 && x < n) ||
671 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
673 timespec_store(&ts, x);
675 timespec_store(&ts, (usec_t) utc);
677 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
683 /* Set system clock */
684 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
685 log_error("Failed to set local time: %m");
686 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
689 /* Sync down to RTC */
691 tm = localtime(&ts.tv_sec);
693 tm = gmtime(&ts.tv_sec);
694 clock_set_hwclock(tm);
697 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
698 "REALTIME="USEC_FMT, timespec_load(&ts),
699 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
702 return sd_bus_reply_method_return(m, NULL);
705 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
706 int ntp, interactive;
707 Context *c = userdata;
710 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
714 if ((bool)ntp == c->use_ntp)
715 return sd_bus_reply_method_return(m, NULL);
717 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
725 r = context_enable_ntp(c, bus, error);
729 r = context_start_ntp(c, bus, error);
733 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
735 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
737 return sd_bus_reply_method_return(m, NULL);
740 #include <sys/capability.h>
742 static const sd_bus_vtable timedate_vtable[] = {
743 SD_BUS_VTABLE_START(0),
744 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
745 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
746 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
747 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
748 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
749 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
750 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
751 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
752 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
753 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
754 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
758 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
759 _cleanup_bus_unref_ sd_bus *bus = NULL;
766 r = sd_bus_default_system(&bus);
768 log_error("Failed to get system bus connection: %s", strerror(-r));
772 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
774 log_error("Failed to register object: %s", strerror(-r));
778 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
780 log_error("Failed to register name: %s", strerror(-r));
784 r = sd_bus_attach_event(bus, event, 0);
786 log_error("Failed to attach bus to event loop: %s", strerror(-r));
796 int main(int argc, char *argv[]) {
797 Context context = {};
798 _cleanup_event_unref_ sd_event *event = NULL;
799 _cleanup_bus_unref_ sd_bus *bus = NULL;
802 log_set_target(LOG_TARGET_AUTO);
803 log_parse_environment();
809 log_error("This program takes no arguments.");
814 r = sd_event_default(&event);
816 log_error("Failed to allocate event loop: %s", strerror(-r));
820 sd_event_set_watchdog(event, true);
822 r = connect_bus(&context, event, &bus);
826 r = context_read_data(&context);
828 log_error("Failed to read time zone data: %s", strerror(-r));
832 r = context_read_ntp(&context, bus);
834 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
838 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
840 log_error("Failed to run event loop: %s", strerror(-r));
845 context_free(&context, bus);
847 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;