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/>.
25 #include <sys/capability.h>
28 #include "sd-messages.h"
35 #include "clock-util.h"
36 #include "conf-files.h"
37 #include "path-util.h"
38 #include "fileio-label.h"
41 #include "bus-errors.h"
42 #include "event-util.h"
44 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
45 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
47 typedef struct Context {
52 Hashmap *polkit_registry;
55 static void context_free(Context *c, sd_bus *bus) {
59 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
62 static int context_read_data(Context *c) {
63 _cleanup_free_ char *t = NULL;
68 r = readlink_malloc("/etc/localtime", &t);
71 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
73 log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
77 e = path_startswith(t, "/usr/share/zoneinfo/");
79 e = path_startswith(t, "../usr/share/zoneinfo/");
82 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
93 if (isempty(c->zone)) {
98 c->local_rtc = clock_is_localtime() > 0;
103 static int context_write_data_timezone(Context *c) {
104 _cleanup_free_ char *p = NULL;
109 if (isempty(c->zone)) {
110 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
116 p = strappend("../usr/share/zoneinfo/", c->zone);
120 r = symlink_atomic(p, "/etc/localtime");
127 static int context_write_data_local_rtc(Context *c) {
129 _cleanup_free_ char *s = NULL, *w = NULL;
133 r = read_full_file("/etc/adjtime", &s, NULL);
141 w = strdup(NULL_ADJTIME_LOCAL);
152 p = strchr(p+1, '\n');
164 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
168 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
170 if (streq(w, NULL_ADJTIME_UTC)) {
171 if (unlink("/etc/adjtime") < 0)
180 return write_string_file_atomic_label("/etc/adjtime", w);
183 static char** get_ntp_services(void) {
184 _cleanup_strv_free_ char **r = NULL, **files = NULL;
188 k = conf_files_list(&files, ".list", NULL,
189 "/etc/systemd/ntp-units.d",
190 "/run/systemd/ntp-units.d",
191 "/usr/local/lib/systemd/ntp-units.d",
192 "/usr/lib/systemd/ntp-units.d",
197 STRV_FOREACH(i, files) {
198 _cleanup_fclose_ FILE *f;
205 char line[PATH_MAX], *l;
207 if (!fgets(line, sizeof(line), f)) {
209 log_error("Failed to read NTP unit file: %m");
215 if (l[0] == 0 || l[0] == '#')
218 if (strv_extend(&r, l) < 0) {
226 r = NULL; /* avoid cleanup */
231 static int context_read_ntp(Context *c, sd_bus *bus) {
232 _cleanup_strv_free_ char **l;
239 l = get_ntp_services();
241 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
242 sd_bus_message *reply = NULL;
245 r = sd_bus_call_method(
247 "org.freedesktop.systemd1",
248 "/org/freedesktop/systemd1",
249 "org.freedesktop.systemd1.Manager",
257 /* This implementation does not exist. Try the next one. */
258 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND))
264 r = sd_bus_message_read(reply, "s", &s);
269 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
277 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
278 _cleanup_strv_free_ char **l = NULL;
286 l = get_ntp_services();
290 r = sd_bus_call_method(
292 "org.freedesktop.systemd1",
293 "/org/freedesktop/systemd1",
294 "org.freedesktop.systemd1.Manager",
298 "ss", *i, "replace");
300 r = sd_bus_call_method(
302 "org.freedesktop.systemd1",
303 "/org/freedesktop/systemd1",
304 "org.freedesktop.systemd1.Manager",
308 "ss", *i, "replace");
311 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
312 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
313 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
314 /* This implementation does not exist. Try the next one. */
315 sd_bus_error_free(error);
325 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
329 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
330 _cleanup_strv_free_ char **l = NULL;
338 l = get_ntp_services();
341 r = sd_bus_call_method(
343 "org.freedesktop.systemd1",
344 "/org/freedesktop/systemd1",
345 "org.freedesktop.systemd1.Manager",
349 "asbb", 1, *i, false, true);
351 r = sd_bus_call_method(
353 "org.freedesktop.systemd1",
354 "/org/freedesktop/systemd1",
355 "org.freedesktop.systemd1.Manager",
359 "asb", 1, *i, false);
362 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
363 /* This implementation does not exist. Try the next one. */
364 sd_bus_error_free(error);
371 r = sd_bus_call_method(
373 "org.freedesktop.systemd1",
374 "/org/freedesktop/systemd1",
375 "org.freedesktop.systemd1.Manager",
386 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
390 static int property_get_rtc_time(
393 const char *interface,
394 const char *property,
395 sd_bus_message *reply,
397 sd_bus_error *error) {
404 r = clock_get_hwclock(&tm);
406 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
408 } else if (r == -ENOENT) {
409 log_debug("/dev/rtc not found.");
410 t = 0; /* no RTC found */
412 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
414 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
416 return sd_bus_message_append(reply, "t", t);
419 static int property_get_time(
422 const char *interface,
423 const char *property,
424 sd_bus_message *reply,
426 sd_bus_error *error) {
428 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
431 static int property_get_ntp_sync(
434 const char *interface,
435 const char *property,
436 sd_bus_message *reply,
438 sd_bus_error *error) {
440 return sd_bus_message_append(reply, "b", ntp_synced());
443 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
444 Context *c = userdata;
454 r = sd_bus_message_read(m, "sb", &z, &interactive);
458 if (!timezone_is_valid(z))
459 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
461 if (streq_ptr(z, c->zone))
462 return sd_bus_reply_method_return(m, NULL);
464 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-timezone", interactive, error, method_set_timezone, c);
468 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
477 /* 1. Write new configuration file */
478 r = context_write_data_timezone(c);
480 log_error("Failed to set time zone: %s", strerror(-r));
481 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
484 /* 2. Tell the kernel our timezone */
485 clock_set_timezone(NULL);
491 /* 3. Sync RTC from system clock, with the new delta */
492 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
493 assert_se(tm = localtime(&ts.tv_sec));
494 clock_set_hwclock(tm);
498 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
499 "TIMEZONE=%s", c->zone,
500 "MESSAGE=Changed time zone to '%s'.", c->zone,
503 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
505 return sd_bus_reply_method_return(m, NULL);
508 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
509 int lrtc, fix_system, interactive;
510 Context *c = userdata;
518 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
522 if (lrtc == c->local_rtc)
523 return sd_bus_reply_method_return(m, NULL);
525 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-local-rtc", interactive, error, method_set_local_rtc, c);
533 /* 1. Write new configuration file */
534 r = context_write_data_local_rtc(c);
536 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
537 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
540 /* 2. Tell the kernel our timezone */
541 clock_set_timezone(NULL);
543 /* 3. Synchronize clocks */
544 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
549 /* Sync system clock from RTC; first,
550 * initialize the timezone fields of
553 tm = *localtime(&ts.tv_sec);
555 tm = *gmtime(&ts.tv_sec);
557 /* Override the main fields of
558 * struct tm, but not the timezone
560 if (clock_get_hwclock(&tm) >= 0) {
562 /* And set the system clock
565 ts.tv_sec = mktime(&tm);
567 ts.tv_sec = timegm(&tm);
569 clock_settime(CLOCK_REALTIME, &ts);
575 /* Sync RTC from system clock */
577 tm = localtime(&ts.tv_sec);
579 tm = gmtime(&ts.tv_sec);
581 clock_set_hwclock(tm);
584 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
586 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
588 return sd_bus_reply_method_return(m, NULL);
591 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
592 int relative, interactive;
593 Context *c = userdata;
604 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
606 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
610 if (!relative && utc <= 0)
611 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
613 if (relative && utc == 0)
614 return sd_bus_reply_method_return(m, NULL);
619 n = now(CLOCK_REALTIME);
622 if ((utc > 0 && x < n) ||
624 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
626 timespec_store(&ts, x);
628 timespec_store(&ts, (usec_t) utc);
630 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-time", interactive, error, method_set_time, c);
636 /* Set system clock */
637 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
638 log_error("Failed to set local time: %m");
639 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
642 /* Sync down to RTC */
644 tm = localtime(&ts.tv_sec);
646 tm = gmtime(&ts.tv_sec);
647 clock_set_hwclock(tm);
650 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
651 "REALTIME="USEC_FMT, timespec_load(&ts),
652 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
655 return sd_bus_reply_method_return(m, NULL);
658 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
659 int ntp, interactive;
660 Context *c = userdata;
663 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
667 if ((bool)ntp == c->use_ntp)
668 return sd_bus_reply_method_return(m, NULL);
670 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.timedate1.set-ntp", interactive, error, method_set_ntp, c);
678 r = context_enable_ntp(c, bus, error);
682 r = context_start_ntp(c, bus, error);
686 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
688 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
690 return sd_bus_reply_method_return(m, NULL);
693 static const sd_bus_vtable timedate_vtable[] = {
694 SD_BUS_VTABLE_START(0),
695 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
696 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
697 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
698 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
699 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
700 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
701 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
702 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
703 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
704 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
705 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
709 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
710 _cleanup_bus_unref_ sd_bus *bus = NULL;
717 r = sd_bus_default_system(&bus);
719 log_error("Failed to get system bus connection: %s", strerror(-r));
723 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
725 log_error("Failed to register object: %s", strerror(-r));
729 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
731 log_error("Failed to register name: %s", strerror(-r));
735 r = sd_bus_attach_event(bus, event, 0);
737 log_error("Failed to attach bus to event loop: %s", strerror(-r));
747 int main(int argc, char *argv[]) {
748 Context context = {};
749 _cleanup_event_unref_ sd_event *event = NULL;
750 _cleanup_bus_unref_ sd_bus *bus = NULL;
753 log_set_target(LOG_TARGET_AUTO);
754 log_parse_environment();
760 log_error("This program takes no arguments.");
765 r = sd_event_default(&event);
767 log_error("Failed to allocate event loop: %s", strerror(-r));
771 sd_event_set_watchdog(event, true);
773 r = connect_bus(&context, event, &bus);
777 r = context_read_data(&context);
779 log_error("Failed to read time zone data: %s", strerror(-r));
783 r = context_read_ntp(&context, bus);
785 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
789 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
791 log_error("Failed to run event loop: %s", strerror(-r));
796 context_free(&context, bus);
798 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;