From: Lennart Poettering Date: Mon, 28 May 2018 19:33:10 +0000 (+0200) Subject: core: subscribe to /etc/localtime timezone changes and update timer elapsation accord... X-Git-Tag: v239~137^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=bbf5fd8e41b1abdf03c8ab463a2c9af7c7dc64d8;p=thirdparty%2Fsystemd.git core: subscribe to /etc/localtime timezone changes and update timer elapsation accordingly Fixes: #8233 This is our first real-life usecase for the new sd_event_add_inotify() calls we just added. --- diff --git a/src/core/manager.c b/src/core/manager.c index 3a2674ce938..0f767d651cd 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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; diff --git a/src/core/manager.h b/src/core/manager.h index 1f97c15365c..8868e9c158c 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -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; diff --git a/src/core/timer.c b/src/core/timer.c index b2fe05d4cff..5babb6d4633 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -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, diff --git a/src/core/unit.h b/src/core/unit.h index 76270a0a7c7..32bdd106433 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -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);