core/automount: tear down autofs when the unit vanished across reload
When an automount unit ceases to exist across daemon-reload (observed: because a generator no longer emits it), its deserialized state still makes `automount_coldplug()` pick up the autofs pipe and resume the unit as waiting/running. But nothing can serve that mount point anymore. The kernel keeps blocking every process that touches the mount point until PID 1 answers (which it won't), forever. To fix this, fail pending requests and unmount the autofs when coldplugging an automount unit that is not loaded anymore. Fixes #43700 Signed-off-by: Martin Pitt <martin@amutable.com>
This commit is contained in:
+50
-6
@@ -48,6 +48,7 @@ static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, vo
|
||||
static int automount_start_expire(Automount *a);
|
||||
static void automount_stop_expire(Automount *a);
|
||||
static int automount_send_ready(Automount *a, Set *tokens, int status);
|
||||
static void automount_enter_dead(Automount *a, AutomountResult f);
|
||||
|
||||
static void automount_init(Unit *u) {
|
||||
Automount *a = ASSERT_PTR(AUTOMOUNT(u));
|
||||
@@ -76,7 +77,7 @@ static void automount_release_requests(Automount *a, int status) {
|
||||
log_unit_warning_errno(UNIT(a), r, "Failed to release pending automount expire requests, ignoring: %m");
|
||||
}
|
||||
|
||||
static void unmount_autofs(Automount *a) {
|
||||
static void unmount_autofs(Automount *a, bool keep_mount_point) {
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
@@ -87,9 +88,7 @@ static void unmount_autofs(Automount *a) {
|
||||
a->pipe_event_source = sd_event_source_disable_unref(a->pipe_event_source);
|
||||
a->pipe_fd = safe_close(a->pipe_fd);
|
||||
|
||||
/* If we reload/reexecute things we keep the mount point around */
|
||||
if (!IN_SET(UNIT(a)->manager->objective, MANAGER_RELOAD, MANAGER_REEXECUTE)) {
|
||||
|
||||
if (!keep_mount_point) {
|
||||
automount_release_requests(a, -EHOSTDOWN);
|
||||
|
||||
if (a->where) {
|
||||
@@ -100,10 +99,17 @@ static void unmount_autofs(Automount *a) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool automount_keep_mount_point(Automount *a) {
|
||||
assert(a);
|
||||
|
||||
/* If we reload/reexecute things we keep the mount point around, the reloaded unit picks it up again. */
|
||||
return IN_SET(UNIT(a)->manager->objective, MANAGER_RELOAD, MANAGER_REEXECUTE);
|
||||
}
|
||||
|
||||
static void automount_done(Unit *u) {
|
||||
Automount *a = ASSERT_PTR(AUTOMOUNT(u));
|
||||
|
||||
unmount_autofs(a);
|
||||
unmount_autofs(a, automount_keep_mount_point(a));
|
||||
|
||||
a->where = mfree(a->where);
|
||||
a->extra_options = mfree(a->extra_options);
|
||||
@@ -273,7 +279,7 @@ static void automount_set_state(Automount *a, AutomountState state) {
|
||||
automount_stop_expire(a);
|
||||
|
||||
if (!IN_SET(state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING))
|
||||
unmount_autofs(a);
|
||||
unmount_autofs(a, automount_keep_mount_point(a));
|
||||
|
||||
if (state != old_state)
|
||||
log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state));
|
||||
@@ -281,6 +287,41 @@ static void automount_set_state(Automount *a, AutomountState state) {
|
||||
unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], /* reload_success= */ true);
|
||||
}
|
||||
|
||||
/* This automount unit's file vanished while we were reloading. Nobody can serve the autofs mount point
|
||||
* anymore, but the kernel keeps blocking every process that is trying to access it while the mount exists.
|
||||
* If the mount is already active, nothing waits for it: then only let go of the pipe. Otherwise detach the
|
||||
* automount and fail the unit, which is what automount_enter_running() does with a request that arrives
|
||||
* while the unit is not loaded. */
|
||||
static int automount_coldplug_vanished(Automount *a) {
|
||||
Unit *u = UNIT(ASSERT_PTR(a));
|
||||
struct stat st;
|
||||
bool exposed;
|
||||
|
||||
if (lstat(a->where, &st) < 0) {
|
||||
/* Not knowing what is mounted there, giving the autofs up is the safe side */
|
||||
log_unit_warning_errno(u, errno, "Failed to stat automount point '%s', assuming it is exposed: %m", a->where);
|
||||
exposed = true;
|
||||
} else
|
||||
exposed = S_ISDIR(st.st_mode) && st.st_dev == a->dev_id;
|
||||
|
||||
log_unit_warning(u, "Unit is not loaded anymore (%s), %s automount point '%s'.",
|
||||
unit_load_state_to_string(u->load_state),
|
||||
exposed ? "tearing down" : "abandoning", a->where);
|
||||
|
||||
if (!exposed)
|
||||
/* Mop up pending requests, nothing else will answer any more. The file system on top is
|
||||
* what they were waiting for, so tell them it is there. */
|
||||
automount_release_requests(a, 0);
|
||||
|
||||
unmount_autofs(a, /* keep_mount_point= */ !exposed);
|
||||
|
||||
/* Nobody can act on a failed unit without a file, so do not keep it around as one, like the orphans
|
||||
* manager_deserialize() creates for vanished unit files. */
|
||||
u->collect_mode = COLLECT_INACTIVE_OR_FAILED;
|
||||
automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int automount_coldplug(Unit *u) {
|
||||
Automount *a = ASSERT_PTR(AUTOMOUNT(u));
|
||||
int r;
|
||||
@@ -302,6 +343,9 @@ static int automount_coldplug(Unit *u) {
|
||||
|
||||
assert(a->pipe_fd >= 0);
|
||||
|
||||
if (u->load_state != UNIT_LOADED)
|
||||
return automount_coldplug_vanished(a);
|
||||
|
||||
r = sd_event_add_io(u->manager->event, &a->pipe_event_source, a->pipe_fd, EPOLLIN, automount_dispatch_io, u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
Reference in New Issue
Block a user