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 method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
456 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
457 Context *c = userdata;
463 r = sd_bus_message_read(m, "sb", &z, &interactive);
465 return sd_bus_reply_method_errno(bus, m, r, NULL);
467 if (!valid_timezone(z))
468 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
470 if (streq_ptr(z, c->zone))
471 return sd_bus_reply_method_return(bus, m, NULL);
473 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
475 return sd_bus_reply_method_errno(bus, m, r, &error);
486 /* 1. Write new configuration file */
487 r = context_write_data_timezone(c);
489 log_error("Failed to set timezone: %s", strerror(-r));
490 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
493 /* 2. Tell the kernel our timezone */
494 hwclock_set_timezone(NULL);
500 /* 3. Sync RTC from system clock, with the new delta */
501 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
502 assert_se(tm = localtime(&ts.tv_sec));
503 hwclock_set_time(tm);
507 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
508 "TIMEZONE=%s", c->zone,
509 "MESSAGE=Changed timezone to '%s'.", c->zone,
512 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
514 return sd_bus_reply_method_return(bus, m, NULL);
517 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
518 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
519 bool lrtc, fix_system, interactive;
520 Context *c = userdata;
528 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
530 return sd_bus_reply_method_errno(bus, m, r, NULL);
532 if (lrtc == c->local_rtc)
533 return sd_bus_reply_method_return(bus, m, NULL);
535 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
537 return sd_bus_reply_method_errno(bus, m, r, &error);
543 /* 1. Write new configuration file */
544 r = context_write_data_local_rtc(c);
546 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
547 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
550 /* 2. Tell the kernel our timezone */
551 hwclock_set_timezone(NULL);
553 /* 3. Synchronize clocks */
554 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
559 /* Sync system clock from RTC; first,
560 * initialize the timezone fields of
563 tm = *localtime(&ts.tv_sec);
565 tm = *gmtime(&ts.tv_sec);
567 /* Override the main fields of
568 * struct tm, but not the timezone
570 if (hwclock_get_time(&tm) >= 0) {
572 /* And set the system clock
575 ts.tv_sec = mktime(&tm);
577 ts.tv_sec = timegm(&tm);
579 clock_settime(CLOCK_REALTIME, &ts);
585 /* Sync RTC from system clock */
587 tm = localtime(&ts.tv_sec);
589 tm = gmtime(&ts.tv_sec);
591 hwclock_set_time(tm);
594 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
596 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
598 return sd_bus_reply_method_return(bus, m, NULL);
601 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
602 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
603 bool relative, interactive;
604 Context *c = userdata;
614 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
616 return sd_bus_reply_method_errno(bus, m, r, NULL);
618 if (!relative && utc <= 0)
619 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
621 if (relative && utc == 0)
622 return sd_bus_reply_method_return(bus, m, NULL);
627 n = now(CLOCK_REALTIME);
630 if ((utc > 0 && x < n) ||
632 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
634 timespec_store(&ts, x);
636 timespec_store(&ts, (usec_t) utc);
638 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
640 return sd_bus_reply_method_errno(bus, m, r, &error);
644 /* Set system clock */
645 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
646 log_error("Failed to set local time: %m");
647 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
650 /* Sync down to RTC */
652 tm = localtime(&ts.tv_sec);
654 tm = gmtime(&ts.tv_sec);
656 hwclock_set_time(tm);
659 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
660 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
661 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
664 return sd_bus_reply_method_return(bus, m, NULL);
667 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
668 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
669 bool ntp, interactive;
670 Context *c = userdata;
673 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
675 return sd_bus_reply_method_errno(bus, m, r, NULL);
677 if (ntp == c->use_ntp)
678 return sd_bus_reply_method_return(bus, m, NULL);
680 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
682 return sd_bus_reply_method_errno(bus, m, r, &error);
688 r = context_enable_ntp(c, bus, &error);
690 return sd_bus_reply_method_errno(bus, m, r, &error);
692 r = context_start_ntp(c, bus, &error);
694 return sd_bus_reply_method_errno(bus, m, r, &error);
696 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
698 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
700 return sd_bus_reply_method_return(bus, m, NULL);
703 static const sd_bus_vtable timedate_vtable[] = {
704 SD_BUS_VTABLE_START(0),
705 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
706 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
707 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
708 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
709 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
710 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
711 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
712 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
716 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
717 _cleanup_bus_unref_ sd_bus *bus = NULL;
724 r = sd_bus_open_system(&bus);
726 log_error("Failed to get system bus connection: %s", strerror(-r));
730 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
732 log_error("Failed to register object: %s", strerror(-r));
736 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
738 log_error("Failed to register name: %s", strerror(-r));
742 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
743 log_error("Failed to acquire name.");
747 r = sd_bus_attach_event(bus, event, 0);
749 log_error("Failed to attach bus to event loop: %s", strerror(-r));
759 int main(int argc, char *argv[]) {
767 _cleanup_event_unref_ sd_event *event = NULL;
768 _cleanup_bus_unref_ sd_bus *bus = NULL;
771 log_set_target(LOG_TARGET_AUTO);
772 log_set_max_level(LOG_DEBUG);
773 log_parse_environment();
779 log_error("This program takes no arguments.");
784 r = sd_event_new(&event);
786 log_error("Failed to allocate event loop: %s", strerror(-r));
790 r = connect_bus(&context, event, &bus);
794 r = context_read_data(&context);
796 log_error("Failed to read timezone data: %s", strerror(-r));
800 r = context_read_ntp(&context, bus);
802 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
806 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
808 log_error("Failed to run event loop: %s", strerror(-r));
816 context_free(&context, bus);
818 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;