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,
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.");
474 sd_bus_error_set_errnof(error, -r, "Failed to read RTC: %s", strerror(-r));
477 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
479 r = sd_bus_message_append(reply, "t", t);
486 static int property_get_time(
489 const char *interface,
490 const char *property,
491 sd_bus_message *reply,
497 r = sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
504 static int property_get_ntp_sync(
507 const char *interface,
508 const char *property,
509 sd_bus_message *reply,
515 r = sd_bus_message_append(reply, "b", ntp_synced());
522 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata) {
523 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
524 Context *c = userdata;
526 unsigned interactive;
534 r = sd_bus_message_read(m, "sb", &z, &interactive);
536 return sd_bus_reply_method_errno(bus, m, r, NULL);
538 if (!valid_timezone(z))
539 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
541 if (streq_ptr(z, c->zone))
542 return sd_bus_reply_method_return(bus, m, NULL);
544 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, &error, method_set_timezone, c);
546 return sd_bus_reply_method_errno(bus, m, r, &error);
548 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
557 /* 1. Write new configuration file */
558 r = context_write_data_timezone(c);
560 log_error("Failed to set timezone: %s", strerror(-r));
561 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set timezone: %s", strerror(-r));
564 /* 2. Tell the kernel our timezone */
565 hwclock_set_timezone(NULL);
571 /* 3. Sync RTC from system clock, with the new delta */
572 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
573 assert_se(tm = localtime(&ts.tv_sec));
574 hwclock_set_time(tm);
578 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
579 "TIMEZONE=%s", c->zone,
580 "MESSAGE=Changed timezone to '%s'.", c->zone,
583 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
585 return sd_bus_reply_method_return(bus, m, NULL);
588 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata) {
589 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
590 unsigned lrtc, fix_system, interactive;
591 Context *c = userdata;
599 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
601 return sd_bus_reply_method_errno(bus, m, r, NULL);
603 if (lrtc == c->local_rtc)
604 return sd_bus_reply_method_return(bus, m, NULL);
606 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, &error, method_set_local_rtc, c);
608 return sd_bus_reply_method_errno(bus, m, r, &error);
614 /* 1. Write new configuration file */
615 r = context_write_data_local_rtc(c);
617 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
618 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
621 /* 2. Tell the kernel our timezone */
622 hwclock_set_timezone(NULL);
624 /* 3. Synchronize clocks */
625 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
630 /* Sync system clock from RTC; first,
631 * initialize the timezone fields of
634 tm = *localtime(&ts.tv_sec);
636 tm = *gmtime(&ts.tv_sec);
638 /* Override the main fields of
639 * struct tm, but not the timezone
641 if (hwclock_get_time(&tm) >= 0) {
643 /* And set the system clock
646 ts.tv_sec = mktime(&tm);
648 ts.tv_sec = timegm(&tm);
650 clock_settime(CLOCK_REALTIME, &ts);
656 /* Sync RTC from system clock */
658 tm = localtime(&ts.tv_sec);
660 tm = gmtime(&ts.tv_sec);
662 hwclock_set_time(tm);
665 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
667 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
669 return sd_bus_reply_method_return(bus, m, NULL);
672 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata) {
673 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
674 unsigned relative, interactive;
675 Context *c = userdata;
685 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
687 return sd_bus_reply_method_errno(bus, m, r, NULL);
689 if (!relative && utc <= 0)
690 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
692 if (relative && utc == 0)
693 return sd_bus_reply_method_return(bus, m, NULL);
698 n = now(CLOCK_REALTIME);
701 if ((utc > 0 && x < n) ||
703 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
705 timespec_store(&ts, x);
707 timespec_store(&ts, (usec_t) utc);
709 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, &error, method_set_time, c);
711 return sd_bus_reply_method_errno(bus, m, r, &error);
715 /* Set system clock */
716 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
717 log_error("Failed to set local time: %m");
718 return sd_bus_reply_method_errnof(bus, m, errno, "Failed to set local time: %m");
721 /* Sync down to RTC */
723 tm = localtime(&ts.tv_sec);
725 tm = gmtime(&ts.tv_sec);
727 hwclock_set_time(tm);
730 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
731 "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
732 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
735 return sd_bus_reply_method_return(bus, m, NULL);
738 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata) {
739 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
740 unsigned ntp, interactive;
741 Context *c = userdata;
744 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
746 return sd_bus_reply_method_errno(bus, m, r, NULL);
748 if (ntp == c->use_ntp)
749 return sd_bus_reply_method_return(bus, m, NULL);
751 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, &error, method_set_ntp, c);
753 return sd_bus_reply_method_errno(bus, m, r, &error);
759 r = context_enable_ntp(c, bus, &error);
761 return sd_bus_reply_method_errno(bus, m, r, &error);
763 r = context_start_ntp(c, bus, &error);
765 return sd_bus_reply_method_errno(bus, m, r, &error);
767 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
769 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
771 return sd_bus_reply_method_return(bus, m, NULL);
774 static const sd_bus_vtable timedate_vtable[] = {
775 SD_BUS_VTABLE_START(0),
776 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
777 SD_BUS_PROPERTY("LocalRTC", "b", NULL, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
778 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_tristate, offsetof(Context, can_ntp), 0),
779 SD_BUS_PROPERTY("NTP", "b", bus_property_get_tristate, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
780 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
781 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
782 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
783 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, 0),
784 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, 0),
785 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, 0),
786 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, 0),
790 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
791 _cleanup_bus_unref_ sd_bus *bus = NULL;
798 r = sd_bus_open_system(&bus);
800 log_error("Failed to get system bus connection: %s", strerror(-r));
804 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
806 log_error("Failed to register object: %s", strerror(-r));
810 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", SD_BUS_NAME_DO_NOT_QUEUE);
812 log_error("Failed to register name: %s", strerror(-r));
816 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
817 log_error("Failed to acquire name.");
821 r = sd_bus_attach_event(bus, event, 0);
823 log_error("Failed to attach bus to event loop: %s", strerror(-r));
833 int main(int argc, char *argv[]) {
841 _cleanup_event_unref_ sd_event *event = NULL;
842 _cleanup_bus_unref_ sd_bus *bus = NULL;
845 log_set_target(LOG_TARGET_AUTO);
846 log_parse_environment();
852 log_error("This program takes no arguments.");
857 r = sd_event_new(&event);
859 log_error("Failed to allocate event loop: %s", strerror(-r));
863 r = connect_bus(&context, event, &bus);
867 r = context_read_data(&context);
869 log_error("Failed to read timezone data: %s", strerror(-r));
873 r = context_read_ntp(&context, bus);
875 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
879 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC);
881 log_error("Failed to run event loop: %s", strerror(-r));
888 context_free(&context, bus);
890 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;