From 8742514c1a1a5bc0f423da9101deb66115e187ff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 25 Nov 2012 00:32:40 +0100 Subject: timer: recalculate next elapse for calendar timer units when the system clock is changed --- src/core/manager.c | 108 +++++++++++++++++++++++++++++++++++++++++++++------ src/core/manager.h | 4 +- src/core/timer.c | 13 +++++++ src/core/unit.h | 3 ++ src/shared/missing.h | 4 ++ 5 files changed, 120 insertions(+), 12 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index 72ce2f2595..54600c6bfc 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -36,6 +36,7 @@ #include #include #include +#include #ifdef HAVE_AUDIT #include @@ -120,19 +121,61 @@ static int manager_setup_notify(Manager *m) { ev.events = EPOLLIN; ev.data.ptr = &m->notify_watch; - if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) { + log_error("Failed to add timer change fd to epoll: %m"); return -errno; + } sa.un.sun_path[0] = '@'; m->notify_socket = strdup(sa.un.sun_path); if (!m->notify_socket) - return -ENOMEM; + return log_oom(); log_debug("Using notification socket %s", m->notify_socket); return 0; } +static int manager_setup_time_change(Manager *m) { + struct epoll_event ev; + struct itimerspec its; + + assert(m); + assert(m->time_change_watch.type == WATCH_INVALID); + + /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever + * CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */ + + m->time_change_watch.type = WATCH_TIME_CHANGE; + m->time_change_watch.fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->time_change_watch.fd < 0) { + log_error("Failed to create timerfd: %m"); + return -errno; + } + + zero(its); + its.it_value.tv_sec = 10000000000; /* Year 2287 or so... */ + if (timerfd_settime(m->time_change_watch.fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { + log_debug("Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m"); + close_nointr_nofail(m->time_change_watch.fd); + watch_init(&m->time_change_watch); + return 0; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->time_change_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->time_change_watch.fd, &ev) < 0) { + log_error("Failed to add timer change fd to epoll: %m"); + return -errno; + } + + log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd."); + + return 0; +} + static int enable_special_signals(Manager *m) { int fd; @@ -208,7 +251,8 @@ static int manager_setup_signals(Manager *m) { assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); m->signal_watch.type = WATCH_SIGNAL; - if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) + m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (m->signal_watch.fd < 0) return -errno; zero(ev); @@ -259,7 +303,13 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { m->pin_cgroupfs_fd = -1; m->idle_pipe[0] = m->idle_pipe[1] = -1; - m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1; + watch_init(&m->signal_watch); + watch_init(&m->mount_watch); + watch_init(&m->swap_watch); + watch_init(&m->udev_watch); + watch_init(&m->time_change_watch); + + m->epoll_fd = m->dev_autofs_fd = -1; m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ m->environment = strv_copy(environ); @@ -289,20 +339,29 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) goto fail; - if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + m->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (m->epoll_fd < 0) + goto fail; + + r = manager_setup_signals(m); + if (r < 0) goto fail; - if ((r = manager_setup_signals(m)) < 0) + r = manager_setup_cgroup(m); + if (r < 0) goto fail; - if ((r = manager_setup_cgroup(m)) < 0) + r = manager_setup_notify(m); + if (r < 0) goto fail; - if ((r = manager_setup_notify(m)) < 0) + r = manager_setup_time_change(m); + if (r < 0) goto fail; /* Try to connect to the busses, if possible. */ - if ((r = bus_init(m, running_as != SYSTEMD_SYSTEM)) < 0) + r = bus_init(m, running_as != SYSTEMD_SYSTEM); + if (r < 0) goto fail; m->taint_usr = dir_is_empty("/usr") > 0; @@ -487,6 +546,8 @@ void manager_free(Manager *m) { close_nointr_nofail(m->signal_watch.fd); if (m->notify_watch.fd >= 0) close_nointr_nofail(m->notify_watch.fd); + if (m->time_change_watch.fd >= 0) + close_nointr_nofail(m->time_change_watch.fd); free(m->notify_socket); @@ -1369,6 +1430,7 @@ static int process_event(Manager *m, struct epoll_event *ev) { if (k < 0 && (errno == EINTR || errno == EAGAIN)) break; + log_error("Failed to read timer event counter: %s", k < 0 ? strerror(-k) : "Short read"); return k < 0 ? -errno : -EIO; } @@ -1402,6 +1464,28 @@ static int process_event(Manager *m, struct epoll_event *ev) { bus_timeout_event(m, w, ev->events); break; + case WATCH_TIME_CHANGE: { + Unit *u; + Iterator i; + + log_struct(LOG_INFO, + MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), + "MESSAGE=Time has been changed", + NULL); + + /* Restart the watch */ + close_nointr_nofail(m->time_change_watch.fd); + watch_init(&m->time_change_watch); + manager_setup_time_change(m); + + HASHMAP_FOREACH(u, m->units, i) { + if (UNIT_VTABLE(u)->time_change) + UNIT_VTABLE(u)->time_change(u); + } + + break; + } + default: log_error("event type=%i", w->type); assert_not_reached("Unknown epoll event type."); @@ -1533,10 +1617,12 @@ int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { if (!startswith(s, "/org/freedesktop/systemd1/job/")) return -EINVAL; - if ((r = safe_atou(s + 30, &id)) < 0) + r = safe_atou(s + 30, &id); + if (r < 0) return r; - if (!(j = manager_get_job(m, id))) + j = manager_get_job(m, id); + if (!j) return -ENOENT; *_j = j; diff --git a/src/core/manager.h b/src/core/manager.h index 1644bd6f77..e9de4969b4 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -60,7 +60,8 @@ enum WatchType { WATCH_SWAP, WATCH_UDEV, WATCH_DBUS_WATCH, - WATCH_DBUS_TIMEOUT + WATCH_DBUS_TIMEOUT, + WATCH_TIME_CHANGE }; struct Watch { @@ -125,6 +126,7 @@ struct Manager { Watch notify_watch; Watch signal_watch; + Watch time_change_watch; int epoll_fd; diff --git a/src/core/timer.c b/src/core/timer.c index 54c0d0b330..bd27970449 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -527,6 +527,18 @@ static void timer_reset_failed(Unit *u) { t->result = TIMER_SUCCESS; } +static void timer_time_change(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + + if (t->state != TIMER_WAITING) + return; + + log_info("%s: time change, recalculating next elapse.", u->id); + timer_enter_waiting(t, false); +} + static const char* const timer_state_table[_TIMER_STATE_MAX] = { [TIMER_DEAD] = "dead", [TIMER_WAITING] = "waiting", @@ -582,6 +594,7 @@ const UnitVTable timer_vtable = { .timer_event = timer_timer_event, .reset_failed = timer_reset_failed, + .time_change = timer_time_change, .bus_interface = "org.freedesktop.systemd1.Timer", .bus_message_handler = bus_timer_message_handler, diff --git a/src/core/unit.h b/src/core/unit.h index 11804d8bcf..b40f034de1 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -358,6 +358,9 @@ struct UnitVTable { /* Return the set of units that are following each other */ int (*following_set)(Unit *u, Set **s); + /* Called whenever CLOCK_REALTIME made a jump */ + void (*time_change)(Unit *u); + /* This is called for each unit type and should be used to * enumerate existing devices and load them. However, * everything that is loaded here should still stay in diff --git a/src/shared/missing.h b/src/shared/missing.h index 3777cf69b2..c53579ff7f 100644 --- a/src/shared/missing.h +++ b/src/shared/missing.h @@ -253,3 +253,7 @@ static inline int name_to_handle_at(int fd, const char *name, struct file_handle #ifndef CIFS_MAGIC_NUMBER #define CIFS_MAGIC_NUMBER 0xFF534D42 #endif + +#ifndef TFD_TIMER_CANCEL_ON_SET +#define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif -- cgit v1.2.3-54-g00ecf