]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: subscribe to /etc/localtime timezone changes and update timer elapsation accord...
authorLennart Poettering <lennart@poettering.net>
Mon, 28 May 2018 19:33:10 +0000 (21:33 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 6 Jun 2018 08:53:56 +0000 (10:53 +0200)
Fixes: #8233
This is our first real-life usecase for the new sd_event_add_inotify()
calls we just added.

src/core/manager.c
src/core/manager.h
src/core/timer.c
src/core/unit.h

index 3a2674ce9384608643438c55135dc202bdcc668e..0f767d651cd36bd7c3b38a7dcd74fbeb467371d3 100644 (file)
@@ -108,6 +108,7 @@ static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint
 static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata);
 static int manager_dispatch_run_queue(sd_event_source *source, void *userdata);
 static int manager_dispatch_sigchld(sd_event_source *source, void *userdata);
+static int manager_dispatch_timezone_change(sd_event_source *source, const struct inotify_event *event, void *userdata);
 static int manager_run_environment_generators(Manager *m);
 static int manager_run_generators(Manager *m);
 
@@ -391,6 +392,65 @@ static int manager_setup_time_change(Manager *m) {
         return 0;
 }
 
+static int manager_read_timezone_stat(Manager *m) {
+        struct stat st;
+        bool changed;
+
+        assert(m);
+
+        /* Read the current stat() data of /etc/localtime so that we detect changes */
+        if (lstat("/etc/localtime", &st) < 0) {
+                log_debug_errno(errno, "Failed to stat /etc/localtime, ignoring: %m");
+                changed = m->etc_localtime_accessible;
+                m->etc_localtime_accessible = false;
+        } else {
+                usec_t k;
+
+                k = timespec_load(&st.st_mtim);
+                changed = !m->etc_localtime_accessible || k != m->etc_localtime_mtime;
+
+                m->etc_localtime_mtime = k;
+                m->etc_localtime_accessible = true;
+        }
+
+        return changed;
+}
+
+static int manager_setup_timezone_change(Manager *m) {
+        sd_event_source *new_event = NULL;
+        int r;
+
+        assert(m);
+
+        if (m->test_run_flags != 0)
+                return 0;
+
+        /* We watch /etc/localtime for three events: change of the link count (which might mean removal from /etc even
+         * though another link might be kept), renames, and file close operations after writing. Note we don't bother
+         * with IN_DELETE_SELF, as that would just report when the inode is removed entirely, i.e. after the link count
+         * went to zero and all fds to it are closed.
+         *
+         * Note that we never follow symlinks here. This is a simplification, but should cover almost all cases
+         * correctly.
+         *
+         * Note that we create the new event source first here, before releasing the old one. This should optimize
+         * behaviour as this way sd-event can reuse the old watch in case the inode didn't change. */
+
+        r = sd_event_add_inotify(m->event, &new_event, "/etc/localtime",
+                                 IN_ATTRIB|IN_MOVE_SELF|IN_CLOSE_WRITE|IN_DONT_FOLLOW, manager_dispatch_timezone_change, m);
+        if (r == -ENOENT) /* If the file doesn't exist yet, subscribe to /etc instead, and wait until it is created
+                           * either by O_CREATE or by rename() */
+                r = sd_event_add_inotify(m->event, &new_event, "/etc",
+                                         IN_CREATE|IN_MOVED_TO|IN_ONLYDIR, manager_dispatch_timezone_change, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create timezone change event source: %m");
+
+        sd_event_source_unref(m->timezone_change_event_source);
+        m->timezone_change_event_source = new_event;
+
+        return 0;
+}
+
 static int enable_special_signals(Manager *m) {
         _cleanup_close_ int fd = -1;
 
@@ -775,6 +835,14 @@ int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
                 if (r < 0)
                         return r;
 
+                r = manager_read_timezone_stat(m);
+                if (r < 0)
+                        return r;
+
+                r = manager_setup_timezone_change(m);
+                if (r < 0)
+                        return r;
+
                 r = manager_setup_sigchld_event_source(m);
                 if (r < 0)
                         return r;
@@ -1216,6 +1284,7 @@ Manager* manager_free(Manager *m) {
         sd_event_source_unref(m->notify_event_source);
         sd_event_source_unref(m->cgroups_agent_event_source);
         sd_event_source_unref(m->time_change_event_source);
+        sd_event_source_unref(m->timezone_change_event_source);
         sd_event_source_unref(m->jobs_in_progress_event_source);
         sd_event_source_unref(m->run_queue_event_source);
         sd_event_source_unref(m->user_lookup_event_source);
@@ -2570,6 +2639,41 @@ static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint
         return 0;
 }
 
+static int manager_dispatch_timezone_change(
+                sd_event_source *source,
+                const struct inotify_event *e,
+                void *userdata) {
+
+        Manager *m = userdata;
+        int changed;
+        Iterator i;
+        Unit *u;
+
+        assert(m);
+
+        log_debug("inotify event for /etc/localtime");
+
+        changed = manager_read_timezone_stat(m);
+        if (changed < 0)
+                return changed;
+        if (!changed)
+                return 0;
+
+        /* Something changed, restart the watch, to ensure we watch the new /etc/localtime if it changed */
+        (void) manager_setup_timezone_change(m);
+
+        /* Read the new timezone */
+        tzset();
+
+        log_debug("Timezone has been changed (now: %s).", tzname[daylight]);
+
+        HASHMAP_FOREACH(u, m->units, i)
+                if (UNIT_VTABLE(u)->timezone_change)
+                        UNIT_VTABLE(u)->timezone_change(u);
+
+        return 0;
+}
+
 static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
         Manager *m = userdata;
 
index 1f97c15365c83e23edfa9dd5063d6cfb2d93cb52..8868e9c158c268756e6817d2d137e5244a13c9c9 100644 (file)
@@ -170,6 +170,8 @@ struct Manager {
         int time_change_fd;
         sd_event_source *time_change_event_source;
 
+        sd_event_source *timezone_change_event_source;
+
         sd_event_source *jobs_in_progress_event_source;
 
         int user_lookup_fds[2];
@@ -250,6 +252,10 @@ struct Manager {
 
         unsigned gc_marker;
 
+        /* The stat() data the last time we saw /etc/localtime */
+        usec_t etc_localtime_mtime;
+        bool etc_localtime_accessible:1;
+
         /* Flags */
         ManagerExitCode exit_code:5;
 
index b2fe05d4cffabb3fbcc09469b6d6a6161dea9bb4..5babb6d4633bbb46b9551e29d7e3cb893309868a 100644 (file)
@@ -819,6 +819,18 @@ static void timer_time_change(Unit *u) {
         timer_enter_waiting(t, false);
 }
 
+static void timer_timezone_change(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+
+        if (t->state != TIMER_WAITING)
+                return;
+
+        log_unit_debug(u, "Timezone change, recalculating next elapse.");
+        timer_enter_waiting(t, false);
+}
+
 static const char* const timer_base_table[_TIMER_BASE_MAX] = {
         [TIMER_ACTIVE] = "OnActiveSec",
         [TIMER_BOOT] = "OnBootSec",
@@ -868,6 +880,7 @@ const UnitVTable timer_vtable = {
 
         .reset_failed = timer_reset_failed,
         .time_change = timer_time_change,
+        .timezone_change = timer_timezone_change,
 
         .bus_vtable = bus_timer_vtable,
         .bus_set_property = bus_timer_set_property,
index 76270a0a7c729023f496d3b9861a50bd9f9a1bf3..32bdd1064331aa172eff8e651cfed3a7e3925180 100644 (file)
@@ -516,6 +516,9 @@ typedef struct UnitVTable {
         /* Called whenever CLOCK_REALTIME made a jump */
         void (*time_change)(Unit *u);
 
+        /* Called whenever /etc/localtime was modified */
+        void (*timezone_change)(Unit *u);
+
         /* Returns the next timeout of a unit */
         int (*get_timeout)(Unit *u, usec_t *timeout);