core/automount: tear down autofs when the unit vanished across reload (#43705)
This commit is contained in:
+119
-14
@@ -48,6 +48,8 @@ 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 int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid);
|
||||
|
||||
static void automount_init(Unit *u) {
|
||||
Automount *a = ASSERT_PTR(AUTOMOUNT(u));
|
||||
@@ -59,7 +61,63 @@ static void automount_init(Unit *u) {
|
||||
UNIT(a)->ignore_on_isolate = true;
|
||||
}
|
||||
|
||||
static void unmount_autofs(Automount *a) {
|
||||
static int autofs_set_catatonic(int dev_autofs_fd, int ioctl_fd) {
|
||||
struct autofs_dev_ioctl param;
|
||||
|
||||
assert(dev_autofs_fd >= 0);
|
||||
assert(ioctl_fd >= 0);
|
||||
|
||||
init_autofs_dev_ioctl(¶m);
|
||||
param.ioctlfd = ioctl_fd;
|
||||
|
||||
return RET_NERRNO(ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_CATATONIC, ¶m));
|
||||
}
|
||||
|
||||
/* Requests the kernel queued since we last read the pipe are in no token set of ours. Turning the autofs
|
||||
* catatonic fails them with ENOENT; merely closing the pipe would get the kernel there only once yet another
|
||||
* request fails to reach us, which under a file system mounted on top never happens. Call this after answering
|
||||
* the known tokens, so that the kernel does not discard them first. */
|
||||
static void automount_set_catatonic(Automount *a) {
|
||||
_cleanup_close_ int ioctl_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
|
||||
/* No way to talk to the autofs, e.g. when open_dev_autofs() failed at coldplug */
|
||||
if (UNIT(a)->manager->dev_autofs_fd < 0 || !a->where)
|
||||
return;
|
||||
|
||||
ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id);
|
||||
if (ioctl_fd < 0) {
|
||||
log_unit_warning_errno(UNIT(a), ioctl_fd, "Failed to open automount ioctl fd for '%s', ignoring: %m", a->where);
|
||||
return;
|
||||
}
|
||||
|
||||
r = autofs_set_catatonic(UNIT(a)->manager->dev_autofs_fd, ioctl_fd);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(UNIT(a), r, "Failed to make automount point '%s' catatonic, ignoring: %m", a->where);
|
||||
}
|
||||
|
||||
/* Nothing else can answer the requests the kernel already queued for us, and it keeps their processes
|
||||
* blocked until we do. Make a failure to do so at least visible. The status is what the mount requests get:
|
||||
* an expire request can only ever fail. */
|
||||
static void automount_release_requests(Automount *a, int status) {
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
|
||||
r = automount_send_ready(a, a->tokens, status);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(UNIT(a), r, "Failed to release pending automount requests, ignoring: %m");
|
||||
|
||||
r = automount_send_ready(a, a->expire_tokens, -EHOSTDOWN);
|
||||
if (r < 0)
|
||||
log_unit_warning_errno(UNIT(a), r, "Failed to release pending automount expire requests, ignoring: %m");
|
||||
|
||||
automount_set_catatonic(a);
|
||||
}
|
||||
|
||||
static void unmount_autofs(Automount *a, bool keep_mount_point) {
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
@@ -67,27 +125,30 @@ static void unmount_autofs(Automount *a) {
|
||||
if (a->pipe_fd < 0)
|
||||
return;
|
||||
|
||||
if (!keep_mount_point)
|
||||
automount_release_requests(a, -EHOSTDOWN);
|
||||
|
||||
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)) {
|
||||
|
||||
automount_send_ready(a, a->tokens, -EHOSTDOWN);
|
||||
automount_send_ready(a, a->expire_tokens, -EHOSTDOWN);
|
||||
|
||||
if (a->where) {
|
||||
r = repeat_unmount(a->where, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
if (r < 0)
|
||||
log_unit_error_errno(UNIT(a), r, "Failed to unmount: %m");
|
||||
}
|
||||
if (!keep_mount_point && a->where) {
|
||||
r = repeat_unmount(a->where, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||
if (r < 0)
|
||||
log_unit_error_errno(UNIT(a), r, "Failed to unmount: %m");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -257,7 +318,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));
|
||||
@@ -265,6 +326,42 @@ 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.
|
||||
* Answer whatever is queued and detach the automount, 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. The autofs below it cannot be
|
||||
* unmounted and stays behind catatonic: recovering that path takes a manual umount, and
|
||||
* until then automount_start() refuses to use it again. */
|
||||
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;
|
||||
@@ -286,6 +383,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;
|
||||
@@ -458,6 +558,11 @@ static int automount_send_ready(Automount *a, Set *tokens, int status) {
|
||||
if (set_isempty(tokens))
|
||||
return 0;
|
||||
|
||||
/* We have nothing to talk to the autofs with, e.g. because open_dev_autofs() failed at coldplug and
|
||||
* left us with the deserialized pipe and tokens. Report that instead of asserting below. */
|
||||
if (UNIT(a)->manager->dev_autofs_fd < 0 || !a->where)
|
||||
return -EBADF;
|
||||
|
||||
ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id);
|
||||
if (ioctl_fd < 0)
|
||||
return ioctl_fd;
|
||||
|
||||
Reference in New Issue
Block a user