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) {
59 bus_verify_polkit_async_registry_free(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 int context_read_ntp(Context *c, sd_bus *bus) {
184 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
185 sd_bus_message *reply = NULL;
192 r = sd_bus_call_method(
194 "org.freedesktop.systemd1",
195 "/org/freedesktop/systemd1",
196 "org.freedesktop.systemd1.Manager",
201 "systemd-timesyncd.service");
204 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
205 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
206 sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
212 r = sd_bus_message_read(reply, "s", &s);
217 c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
222 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
230 r = sd_bus_call_method(
232 "org.freedesktop.systemd1",
233 "/org/freedesktop/systemd1",
234 "org.freedesktop.systemd1.Manager",
239 "systemd-timesyncd.service",
242 r = sd_bus_call_method(
244 "org.freedesktop.systemd1",
245 "/org/freedesktop/systemd1",
246 "org.freedesktop.systemd1.Manager",
251 "systemd-timesyncd.service",
255 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
256 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
257 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
258 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
268 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
276 r = sd_bus_call_method(
278 "org.freedesktop.systemd1",
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
285 "systemd-timesyncd.service",
288 r = sd_bus_call_method(
290 "org.freedesktop.systemd1",
291 "/org/freedesktop/systemd1",
292 "org.freedesktop.systemd1.Manager",
297 "systemd-timesyncd.service",
301 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
302 sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
309 r = sd_bus_call_method(
311 "org.freedesktop.systemd1",
312 "/org/freedesktop/systemd1",
313 "org.freedesktop.systemd1.Manager",
324 static int property_get_rtc_time(
327 const char *interface,
328 const char *property,
329 sd_bus_message *reply,
331 sd_bus_error *error) {
338 r = clock_get_hwclock(&tm);
340 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
342 } else if (r == -ENOENT) {
343 log_debug("/dev/rtc not found.");
344 t = 0; /* no RTC found */
346 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
348 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
350 return sd_bus_message_append(reply, "t", t);
353 static int property_get_time(
356 const char *interface,
357 const char *property,
358 sd_bus_message *reply,
360 sd_bus_error *error) {
362 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
365 static int property_get_ntp_sync(
368 const char *interface,
369 const char *property,
370 sd_bus_message *reply,
372 sd_bus_error *error) {
374 return sd_bus_message_append(reply, "b", ntp_synced());
377 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
378 Context *c = userdata;
388 r = sd_bus_message_read(m, "sb", &z, &interactive);
392 if (!timezone_is_valid(z))
393 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
395 if (streq_ptr(z, c->zone))
396 return sd_bus_reply_method_return(m, NULL);
398 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-timezone", interactive, &c->polkit_registry, error);
402 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
411 /* 1. Write new configuration file */
412 r = context_write_data_timezone(c);
414 log_error("Failed to set time zone: %s", strerror(-r));
415 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
418 /* 2. Tell the kernel our timezone */
419 clock_set_timezone(NULL);
425 /* 3. Sync RTC from system clock, with the new delta */
426 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
427 assert_se(tm = localtime(&ts.tv_sec));
428 clock_set_hwclock(tm);
432 MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
433 "TIMEZONE=%s", c->zone,
434 "MESSAGE=Changed time zone to '%s'.", c->zone,
437 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
439 return sd_bus_reply_method_return(m, NULL);
442 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
443 int lrtc, fix_system, interactive;
444 Context *c = userdata;
452 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
456 if (lrtc == c->local_rtc)
457 return sd_bus_reply_method_return(m, NULL);
459 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-local-rtc", interactive, &c->polkit_registry, error);
467 /* 1. Write new configuration file */
468 r = context_write_data_local_rtc(c);
470 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
471 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
474 /* 2. Tell the kernel our timezone */
475 clock_set_timezone(NULL);
477 /* 3. Synchronize clocks */
478 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
483 /* Sync system clock from RTC; first,
484 * initialize the timezone fields of
487 tm = *localtime(&ts.tv_sec);
489 tm = *gmtime(&ts.tv_sec);
491 /* Override the main fields of
492 * struct tm, but not the timezone
494 if (clock_get_hwclock(&tm) >= 0) {
496 /* And set the system clock
499 ts.tv_sec = mktime(&tm);
501 ts.tv_sec = timegm(&tm);
503 clock_settime(CLOCK_REALTIME, &ts);
509 /* Sync RTC from system clock */
511 tm = localtime(&ts.tv_sec);
513 tm = gmtime(&ts.tv_sec);
515 clock_set_hwclock(tm);
518 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
520 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
522 return sd_bus_reply_method_return(m, NULL);
525 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
526 int relative, interactive;
527 Context *c = userdata;
538 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
540 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
544 if (!relative && utc <= 0)
545 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
547 if (relative && utc == 0)
548 return sd_bus_reply_method_return(m, NULL);
553 n = now(CLOCK_REALTIME);
556 if ((utc > 0 && x < n) ||
558 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
560 timespec_store(&ts, x);
562 timespec_store(&ts, (usec_t) utc);
564 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-time", interactive, &c->polkit_registry, error);
570 /* Set system clock */
571 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
572 log_error("Failed to set local time: %m");
573 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
576 /* Sync down to RTC */
578 tm = localtime(&ts.tv_sec);
580 tm = gmtime(&ts.tv_sec);
581 clock_set_hwclock(tm);
584 MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
585 "REALTIME="USEC_FMT, timespec_load(&ts),
586 "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
589 return sd_bus_reply_method_return(m, NULL);
592 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
593 int ntp, interactive;
594 Context *c = userdata;
597 r = sd_bus_message_read(m, "bb", &ntp, &interactive);
601 if ((bool)ntp == c->use_ntp)
602 return sd_bus_reply_method_return(m, NULL);
604 r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-ntp", interactive, &c->polkit_registry, error);
612 r = context_enable_ntp(c, bus, error);
616 r = context_start_ntp(c, bus, error);
620 log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
622 sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
624 return sd_bus_reply_method_return(m, NULL);
627 static const sd_bus_vtable timedate_vtable[] = {
628 SD_BUS_VTABLE_START(0),
629 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
630 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
631 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
632 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
633 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
634 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
635 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
636 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
637 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
638 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
639 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
643 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
644 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
651 r = sd_bus_default_system(&bus);
653 log_error("Failed to get system bus connection: %s", strerror(-r));
657 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
659 log_error("Failed to register object: %s", strerror(-r));
663 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
665 log_error("Failed to register name: %s", strerror(-r));
669 r = sd_bus_attach_event(bus, event, 0);
671 log_error("Failed to attach bus to event loop: %s", strerror(-r));
681 int main(int argc, char *argv[]) {
682 Context context = {};
683 _cleanup_event_unref_ sd_event *event = NULL;
684 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
687 log_set_target(LOG_TARGET_AUTO);
688 log_parse_environment();
694 log_error("This program takes no arguments.");
699 r = sd_event_default(&event);
701 log_error("Failed to allocate event loop: %s", strerror(-r));
705 sd_event_set_watchdog(event, true);
707 r = connect_bus(&context, event, &bus);
711 r = context_read_data(&context);
713 log_error("Failed to read time zone data: %s", strerror(-r));
717 r = context_read_ntp(&context, bus);
719 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
723 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
725 log_error("Failed to run event loop: %s", strerror(-r));
730 context_free(&context);
732 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;