mirror of
https://github.com/torvalds/linux.git
synced 2026-10-23 23:51:02 +02:00
UMOUNT_CONNECTED as implemented allows for the creation of reference
count cycles. Here's a simple example
mkdir /x; mkfifo /ready /go
unshare -m sh -c 'mount -t tmpfs tmpfs /x
truncate -s 8M /x/img; mkfs.ext4 -q /x/img
dev=$(losetup -f --show /x/img)
mkdir /x/mp; mount $dev /x/mp
echo $dev > /ready; read r < /go' &
read dev < /ready
rmdir /x
echo > /go
wait
losetup -d $dev
losetup -a
Take a directory /x on the host, create a new mount namespace, mount a
tmpfs on /x, use a file on that tmpfs as the backing file for a loop
device, mount that loop device on that tmpfs. Now rmdir /x on the host.
This will lazily unmount the mount on top of /x in the container with
UMOUNT_CONNECTED. Once the namespace exits nothing references the mount
anymore. Now the tmpfs is pinned by the backing file of the loop device
and the loop mount is owned by the tmpfs superblock.
Fun fact, such cycles can be formed by at least the following
subsystems and I have added reproducers for all of them:
(1) a loop mount P from an image on a tmpfs next to it, so that P's
death shows as the loop device giving up its backing file
(2) autofs with a FIFO on P as its pipe, zram with a device node on P as
its writeback device, both on a minix image since vfat has neither
(3) ecryptfs with its lower directory on P, under a passphrase token
added to the session keyring
(4) binfmt_misc in a new user namespace with an 'F' interpreter on P
(5) a fuse server that answers FUSE_INIT with passthrough on and
registers a file on P as a backing file
(6) zloop with its zone files in a directory on P
(7) a mass storage gadget on the dummy UDC with its LUN file on P,
mounted from the SCSI disk the gadget shows up as
(8) md with a RAID1 of one loop device and its bitmap file on P, which
skips while SET_BITMAP_FILE has no way to succeed
(9) rmdir of P's mountpoint from the parent, then the child exits, then
the device must be free and LOOP_CLR_FD must release the file
The underlying mechanism is UMOUNT_CONNECTED (MNT_LOCKED falls into the
same class). With UMOUNT_CONNECTED an unmounted mount stays attached to
its parent. This is used to protect revealing the underlying mount and
is a non-negotiable security mechanism. So now the parent owns that
mount and is put on the parent's final mntput(). That moves it to
mnt_stuck_children and ultimately it's cleaned up by cleanup_mnt().
The fact that ownership of the child mount gets transferred to the
parent turns every reference from a child's superblock back to one of
its ancestors into a cycle.
Don't keep the child attached at all. What the parent needs is that a
lookup at the child's mountpoint keeps finding some mount, not the child
itself and it's not a guarantee we have given really.
When an unmounted mount would have stayed attached to its unmounted
parent disconnect it like every other unmounted mount and leave a
marker behind.
A lookup on the parent that misses the mount hash and hits a marker
finds knullfs. Either a file or a directory. Nothing leads from a marker
to any other mount.
The marker is owned by the parent and dropped by the parent's final
mntput() or by __detach_mounts() when the mountpoint is deleted from
under it. It is allocated together with the mount.
With that every unmounted mount is a root and holds only its own
reference which namespace_unlock() drops. No unmounted mount owns
another one. A superblock that pins an ancestor can't form a cycle.
It has a visible change. Mounts left connected (rmdir etc.) used to stay
traversable through the parent for as long as something held the parent.
Now it is detached with the umount. It lives as long as something
references it but it isn't reachable through the parent anymore and ".."
inside it leads nowhere which is the same as for every other lazily
unmounted mount.
Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f
Link: https://patch.msgid.link/20261002-work-mount-cover-v1-2-232a8f52b43c@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
760 lines
19 KiB
C
760 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/fs/pnode.c
|
|
*
|
|
* (C) Copyright IBM Corporation 2005.
|
|
* Author : Ram Pai (linuxram@us.ibm.com)
|
|
*/
|
|
#include <linux/mnt_namespace.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/nsproxy.h>
|
|
#include <uapi/linux/mount.h>
|
|
#include "internal.h"
|
|
#include "pnode.h"
|
|
|
|
/* return the next shared peer mount of @p */
|
|
static inline struct mount *next_peer(struct mount *p)
|
|
{
|
|
return list_entry(p->mnt_share.next, struct mount, mnt_share);
|
|
}
|
|
|
|
static inline struct mount *first_slave(struct mount *p)
|
|
{
|
|
return hlist_entry(p->mnt_slave_list.first, struct mount, mnt_slave);
|
|
}
|
|
|
|
static inline struct mount *next_slave(struct mount *p)
|
|
{
|
|
return hlist_entry(p->mnt_slave.next, struct mount, mnt_slave);
|
|
}
|
|
|
|
/* locks: namespace_shared && is_mounted(mnt) */
|
|
static struct mount *get_peer_under_root(struct mount *mnt,
|
|
struct mnt_namespace *ns,
|
|
const struct path *root)
|
|
{
|
|
struct mount *m = mnt;
|
|
|
|
do {
|
|
/* Check the namespace first for optimization */
|
|
if (m->mnt_ns == ns && is_path_reachable(m, m->mnt.mnt_root, root))
|
|
return m;
|
|
|
|
m = next_peer(m);
|
|
} while (m != mnt);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Get ID of closest dominating peer group having a representative
|
|
* under the given root.
|
|
*
|
|
* locks: namespace_shared
|
|
*/
|
|
int get_dominating_id(struct mount *mnt, const struct path *root)
|
|
{
|
|
struct mount *m;
|
|
|
|
for (m = mnt->mnt_master; m != NULL; m = m->mnt_master) {
|
|
struct mount *d = get_peer_under_root(m, mnt->mnt_ns, root);
|
|
if (d)
|
|
return d->mnt_group_id;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline bool will_be_unmounted(struct mount *m)
|
|
{
|
|
return m->mnt.mnt_flags & MNT_UMOUNT;
|
|
}
|
|
|
|
static void transfer_propagation(struct mount *mnt, struct mount *to)
|
|
{
|
|
struct hlist_node *p = NULL, *n;
|
|
struct mount *m;
|
|
|
|
hlist_for_each_entry_safe(m, n, &mnt->mnt_slave_list, mnt_slave) {
|
|
m->mnt_master = to;
|
|
if (!to)
|
|
hlist_del_init(&m->mnt_slave);
|
|
else
|
|
p = &m->mnt_slave;
|
|
}
|
|
if (p)
|
|
hlist_splice_init(&mnt->mnt_slave_list, p, &to->mnt_slave_list);
|
|
}
|
|
|
|
/*
|
|
* EXCL[namespace_sem]
|
|
*/
|
|
void change_mnt_propagation(struct mount *mnt, int type)
|
|
{
|
|
struct mount *m = mnt->mnt_master;
|
|
|
|
if (type == MS_SHARED) {
|
|
set_mnt_shared(mnt);
|
|
return;
|
|
}
|
|
if (IS_MNT_SHARED(mnt)) {
|
|
if (list_empty(&mnt->mnt_share)) {
|
|
mnt_release_group_id(mnt);
|
|
} else {
|
|
m = next_peer(mnt);
|
|
list_del_init(&mnt->mnt_share);
|
|
mnt->mnt_group_id = 0;
|
|
}
|
|
CLEAR_MNT_SHARED(mnt);
|
|
transfer_propagation(mnt, m);
|
|
}
|
|
hlist_del_init(&mnt->mnt_slave);
|
|
if (type == MS_SLAVE) {
|
|
mnt->mnt_master = m;
|
|
if (m)
|
|
hlist_add_head(&mnt->mnt_slave, &m->mnt_slave_list);
|
|
} else {
|
|
mnt->mnt_master = NULL;
|
|
if (type == MS_UNBINDABLE)
|
|
mnt->mnt_t_flags |= T_UNBINDABLE;
|
|
else
|
|
mnt->mnt_t_flags &= ~T_UNBINDABLE;
|
|
}
|
|
}
|
|
|
|
static struct mount *trace_transfers(struct mount *m)
|
|
{
|
|
while (1) {
|
|
struct mount *next = next_peer(m);
|
|
|
|
if (next != m) {
|
|
list_del_init(&m->mnt_share);
|
|
m->mnt_group_id = 0;
|
|
m->mnt_master = next;
|
|
} else {
|
|
if (IS_MNT_SHARED(m))
|
|
mnt_release_group_id(m);
|
|
next = m->mnt_master;
|
|
}
|
|
hlist_del_init(&m->mnt_slave);
|
|
CLEAR_MNT_SHARED(m);
|
|
SET_MNT_MARK(m);
|
|
|
|
if (!next || !will_be_unmounted(next))
|
|
return next;
|
|
if (IS_MNT_MARKED(next))
|
|
return next->mnt_master;
|
|
m = next;
|
|
}
|
|
}
|
|
|
|
static void set_destinations(struct mount *m, struct mount *master)
|
|
{
|
|
struct mount *next;
|
|
|
|
while ((next = m->mnt_master) != master) {
|
|
m->mnt_master = master;
|
|
m = next;
|
|
}
|
|
}
|
|
|
|
void bulk_make_private(struct list_head *set)
|
|
{
|
|
struct mount *m;
|
|
|
|
list_for_each_entry(m, set, mnt_list)
|
|
if (!IS_MNT_MARKED(m))
|
|
set_destinations(m, trace_transfers(m));
|
|
|
|
list_for_each_entry(m, set, mnt_list) {
|
|
transfer_propagation(m, m->mnt_master);
|
|
m->mnt_master = NULL;
|
|
CLEAR_MNT_MARK(m);
|
|
}
|
|
}
|
|
|
|
static struct mount *__propagation_next(struct mount *m,
|
|
struct mount *origin)
|
|
{
|
|
while (1) {
|
|
struct mount *master = m->mnt_master;
|
|
|
|
if (master == origin->mnt_master) {
|
|
struct mount *next = next_peer(m);
|
|
return (next == origin) ? NULL : next;
|
|
} else if (m->mnt_slave.next)
|
|
return next_slave(m);
|
|
|
|
/* back at master */
|
|
m = master;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get the next mount in the propagation tree.
|
|
* @m: the mount seen last
|
|
* @origin: the original mount from where the tree walk initiated
|
|
*
|
|
* Note that peer groups form contiguous segments of slave lists.
|
|
* We rely on that in get_source() to be able to find out if
|
|
* vfsmount found while iterating with propagation_next() is
|
|
* a peer of one we'd found earlier.
|
|
*/
|
|
static struct mount *propagation_next(struct mount *m,
|
|
struct mount *origin)
|
|
{
|
|
/* are there any slaves of this mount? */
|
|
if (!IS_MNT_NEW(m) && !hlist_empty(&m->mnt_slave_list))
|
|
return first_slave(m);
|
|
|
|
return __propagation_next(m, origin);
|
|
}
|
|
|
|
static struct mount *skip_propagation_subtree(struct mount *m,
|
|
struct mount *origin)
|
|
{
|
|
/*
|
|
* Advance m past everything that gets propagation from it.
|
|
*/
|
|
struct mount *p = __propagation_next(m, origin);
|
|
|
|
while (p && peers(m, p))
|
|
p = __propagation_next(p, origin);
|
|
|
|
return p;
|
|
}
|
|
|
|
static struct mount *next_group(struct mount *m, struct mount *origin)
|
|
{
|
|
while (1) {
|
|
while (1) {
|
|
struct mount *next;
|
|
if (!IS_MNT_NEW(m) && !hlist_empty(&m->mnt_slave_list))
|
|
return first_slave(m);
|
|
next = next_peer(m);
|
|
if (m->mnt_group_id == origin->mnt_group_id) {
|
|
if (next == origin)
|
|
return NULL;
|
|
} else if (m->mnt_slave.next != &next->mnt_slave)
|
|
break;
|
|
m = next;
|
|
}
|
|
/* m is the last peer */
|
|
while (1) {
|
|
struct mount *master = m->mnt_master;
|
|
if (m->mnt_slave.next)
|
|
return next_slave(m);
|
|
m = next_peer(master);
|
|
if (master->mnt_group_id == origin->mnt_group_id)
|
|
break;
|
|
if (master->mnt_slave.next == &m->mnt_slave)
|
|
break;
|
|
m = master;
|
|
}
|
|
if (m == origin)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static bool need_secondary(struct mount *m, struct mountpoint *dest_mp)
|
|
{
|
|
/* skip ones added by this propagate_mnt() */
|
|
if (IS_MNT_NEW(m))
|
|
return false;
|
|
/* skip if mountpoint isn't visible in m */
|
|
if (!is_subdir(dest_mp->m_dentry, m->mnt.mnt_root))
|
|
return false;
|
|
/* skip if m is in the anon_ns */
|
|
if (is_anon_ns(m->mnt_ns))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static struct mount *find_master(struct mount *m,
|
|
struct mount *last_copy,
|
|
struct mount *original)
|
|
{
|
|
struct mount *p;
|
|
|
|
// ascend until there's a copy for something with the same master
|
|
for (;;) {
|
|
p = m->mnt_master;
|
|
if (!p || IS_MNT_MARKED(p))
|
|
break;
|
|
m = p;
|
|
}
|
|
while (!peers(last_copy, original)) {
|
|
struct mount *parent = last_copy->mnt_parent;
|
|
if (parent->mnt_master == p) {
|
|
if (!peers(parent, m))
|
|
last_copy = last_copy->mnt_master;
|
|
break;
|
|
}
|
|
last_copy = last_copy->mnt_master;
|
|
}
|
|
return last_copy;
|
|
}
|
|
|
|
/**
|
|
* propagate_mnt() - create secondary copies for tree attachment
|
|
* @dest_mnt: destination mount.
|
|
* @dest_mp: destination mountpoint.
|
|
* @source_mnt: source mount.
|
|
* @tree_list: list of secondaries to be attached.
|
|
*
|
|
* Create secondary copies for attaching a tree with root @source_mnt
|
|
* at mount @dest_mnt with mountpoint @dest_mp. Link all new mounts
|
|
* into a propagation graph. Set mountpoints for all secondaries,
|
|
* link their roots into @tree_list via ->mnt_hash.
|
|
*/
|
|
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
|
|
struct mount *source_mnt, struct hlist_head *tree_list)
|
|
{
|
|
struct mount *m, *n, *copy, *this;
|
|
int err = 0, type;
|
|
|
|
if (dest_mnt->mnt_master)
|
|
SET_MNT_MARK(dest_mnt->mnt_master);
|
|
|
|
/* iterate over peer groups, depth first */
|
|
for (m = dest_mnt; m && !err; m = next_group(m, dest_mnt)) {
|
|
if (m == dest_mnt) { // have one for dest_mnt itself
|
|
copy = source_mnt;
|
|
type = CL_MAKE_SHARED;
|
|
n = next_peer(m);
|
|
if (n == m)
|
|
continue;
|
|
} else {
|
|
type = CL_SLAVE;
|
|
/* beginning of peer group among the slaves? */
|
|
if (IS_MNT_SHARED(m))
|
|
type |= CL_MAKE_SHARED;
|
|
n = m;
|
|
}
|
|
do {
|
|
if (!need_secondary(n, dest_mp))
|
|
continue;
|
|
if (type & CL_SLAVE) // first in this peer group
|
|
copy = find_master(n, copy, source_mnt);
|
|
this = copy_tree(copy, copy->mnt.mnt_root, type);
|
|
if (IS_ERR(this)) {
|
|
err = PTR_ERR(this);
|
|
break;
|
|
}
|
|
scoped_guard(mount_locked_reader)
|
|
mnt_set_mountpoint(n, dest_mp, this);
|
|
if (n->mnt_master)
|
|
SET_MNT_MARK(n->mnt_master);
|
|
copy = this;
|
|
hlist_add_head(&this->mnt_hash, tree_list);
|
|
err = count_mounts(n->mnt_ns, this);
|
|
if (err)
|
|
break;
|
|
type = CL_MAKE_SHARED;
|
|
} while ((n = next_peer(n)) != m);
|
|
}
|
|
|
|
hlist_for_each_entry(n, tree_list, mnt_hash) {
|
|
m = n->mnt_parent;
|
|
if (m->mnt_master)
|
|
CLEAR_MNT_MARK(m->mnt_master);
|
|
}
|
|
if (dest_mnt->mnt_master)
|
|
CLEAR_MNT_MARK(dest_mnt->mnt_master);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* return true if the refcount is greater than count
|
|
*/
|
|
static inline int do_refcount_check(struct mount *mnt, int count)
|
|
{
|
|
return mnt_get_count(mnt) > count;
|
|
}
|
|
|
|
/**
|
|
* propagation_would_overmount - check whether propagation from @from
|
|
* would overmount @to
|
|
* @from: shared mount
|
|
* @to: mount to check
|
|
* @mp: future mountpoint of @to on @from
|
|
*
|
|
* If @from propagates mounts to @to, @from and @to must either be peers
|
|
* or one of the masters in the hierarchy of masters of @to must be a
|
|
* peer of @from.
|
|
*
|
|
* If the root of the @to mount is equal to the future mountpoint @mp of
|
|
* the @to mount on @from then @to will be overmounted by whatever is
|
|
* propagated to it.
|
|
*
|
|
* Context: This function expects namespace_lock() to be held and that
|
|
* @mp is stable.
|
|
* Return: If @from overmounts @to, true is returned, false if not.
|
|
*/
|
|
bool propagation_would_overmount(const struct mount *from,
|
|
const struct mount *to,
|
|
const struct mountpoint *mp)
|
|
{
|
|
if (!IS_MNT_SHARED(from))
|
|
return false;
|
|
|
|
if (to->mnt.mnt_root != mp->m_dentry)
|
|
return false;
|
|
|
|
for (const struct mount *m = to; m; m = m->mnt_master) {
|
|
if (peers(from, m))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Does @m receive propagation from @parent? */
|
|
static bool receives_from(struct mount *m, struct mount *parent)
|
|
{
|
|
if (m == parent)
|
|
return false;
|
|
for (; m; m = m->mnt_master)
|
|
if (m == parent || peers(m, parent))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Does @m receive propagation from the victim's parent as well? If so, then
|
|
* the mount at the victim's mountpoint inside of @m is a umount candidate as
|
|
* well. So it's the next candidate in the chain. Otherwise the chain ends at
|
|
* @m.
|
|
*/
|
|
static struct mount *next_candidate(struct mount *m, struct mount *victim)
|
|
{
|
|
if (!receives_from(m, victim->mnt_parent))
|
|
return NULL;
|
|
return __lookup_mnt(&m->mnt, victim->mnt_mountpoint);
|
|
}
|
|
|
|
/*
|
|
* Would propagate_umount() pull out a mount of the chain of candidates that
|
|
* starts at @c, and does that mount have references beyond its own?
|
|
*
|
|
* This mirrors how trim_one(), trim_ancestors() and handle_locked() handle a
|
|
* synchronous umount:
|
|
*
|
|
* - single victim
|
|
* - without children
|
|
* - with MNT_LOCKED already cleared on every candidate by propagate_mount_unlock()
|
|
*
|
|
* A copy of the victim gets unmounted when each of its children is
|
|
* the next candidate in the chain or its overmount, unless the next
|
|
* unmount candidate is not its overmount and some unmount candidate further
|
|
* down has a child outside the chain. Keep this in sync with
|
|
* Documentation/filesystems/propagate_umount.txt.
|
|
*/
|
|
static bool chain_busy(struct mount *c, struct mount *victim)
|
|
{
|
|
struct mount *m, *n, *next, *deepest = NULL;
|
|
bool above;
|
|
|
|
/* the deepest candidate with a child outside the chain */
|
|
for (m = c; m; m = next) {
|
|
next = next_candidate(m, victim);
|
|
list_for_each_entry(n, &m->mnt_mounts, mnt_child) {
|
|
if (n != next && n != victim) {
|
|
deepest = m;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
above = deepest != NULL; /* @deepest is at or below @m */
|
|
for (m = c; m; m = next) {
|
|
bool goes = true;
|
|
|
|
next = next_candidate(m, victim);
|
|
list_for_each_entry(n, &m->mnt_mounts, mnt_child) {
|
|
if (n != next && n != m->overmount && n != victim) {
|
|
goes = false;
|
|
break;
|
|
}
|
|
}
|
|
if (goes && next && next != m->overmount && above && m != deepest)
|
|
goes = false;
|
|
if (m == deepest)
|
|
above = false;
|
|
if (goes && do_refcount_check(m, 1))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* check if the mount 'mnt' can be unmounted successfully.
|
|
* @mnt: the mount to be checked for unmount
|
|
* NOTE: unmounting 'mnt' would naturally propagate to all
|
|
* other mounts its parent propagates to.
|
|
* Check if any of the mounts that propagate_umount() would pull out
|
|
* along with it have more references than their own. If so return busy.
|
|
*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
int propagate_mount_busy(struct mount *mnt, int refcnt)
|
|
{
|
|
struct mount *parent = mnt->mnt_parent;
|
|
struct dentry *mp = mnt->mnt_mountpoint;
|
|
struct mount *m;
|
|
|
|
/*
|
|
* quickly check if the current mount can be unmounted.
|
|
* If not, we don't have to go checking for all other
|
|
* mounts
|
|
*/
|
|
if (!list_empty(&mnt->mnt_mounts) || do_refcount_check(mnt, refcnt))
|
|
return 1;
|
|
|
|
if (mnt == parent)
|
|
return 0;
|
|
|
|
/* the candidates are the mounts at @mp below the receivers */
|
|
for (m = propagation_next(parent, parent); m;
|
|
m = propagation_next(m, parent)) {
|
|
struct mount *c = __lookup_mnt(&m->mnt, mp);
|
|
|
|
/* each chain once, from its top: skip receivers that are candidates */
|
|
if (!c || (mnt_has_parent(m) && m->mnt_mountpoint == mp &&
|
|
receives_from(m->mnt_parent, parent)))
|
|
continue;
|
|
if (chain_busy(c, mnt))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clear MNT_LOCKED when it can be shown to be safe.
|
|
*
|
|
* mount_lock lock must be held for write
|
|
*/
|
|
void propagate_mount_unlock(struct mount *mnt)
|
|
{
|
|
struct mount *parent = mnt->mnt_parent;
|
|
struct mount *m, *child;
|
|
|
|
BUG_ON(parent == mnt);
|
|
|
|
for (m = propagation_next(parent, parent); m;
|
|
m = propagation_next(m, parent)) {
|
|
child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
|
|
if (child)
|
|
child->mnt.mnt_flags &= ~MNT_LOCKED;
|
|
}
|
|
}
|
|
|
|
static inline bool is_candidate(struct mount *m)
|
|
{
|
|
return m->mnt_t_flags & T_UMOUNT_CANDIDATE;
|
|
}
|
|
|
|
static void umount_one(struct mount *m, struct list_head *to_umount)
|
|
{
|
|
m->mnt.mnt_flags |= MNT_UMOUNT;
|
|
list_del_init(&m->mnt_child);
|
|
move_from_ns(m);
|
|
list_add_tail(&m->mnt_list, to_umount);
|
|
}
|
|
|
|
static void remove_from_candidate_list(struct mount *m)
|
|
{
|
|
m->mnt_t_flags &= ~(T_MARKED | T_UMOUNT_CANDIDATE);
|
|
list_del_init(&m->mnt_list);
|
|
}
|
|
|
|
static void gather_candidates(struct list_head *set,
|
|
struct list_head *candidates)
|
|
{
|
|
struct mount *m, *p, *q;
|
|
|
|
list_for_each_entry(m, set, mnt_list) {
|
|
if (is_candidate(m))
|
|
continue;
|
|
m->mnt_t_flags |= T_UMOUNT_CANDIDATE;
|
|
p = m->mnt_parent;
|
|
q = propagation_next(p, p);
|
|
while (q) {
|
|
struct mount *child = __lookup_mnt(&q->mnt,
|
|
m->mnt_mountpoint);
|
|
if (child) {
|
|
/*
|
|
* We might've already run into this one. That
|
|
* must've happened on earlier iteration of the
|
|
* outer loop; in that case we can skip those
|
|
* parents that get propagation from q - there
|
|
* will be nothing new on those as well.
|
|
*/
|
|
if (is_candidate(child)) {
|
|
q = skip_propagation_subtree(q, p);
|
|
continue;
|
|
}
|
|
child->mnt_t_flags |= T_UMOUNT_CANDIDATE;
|
|
if (!will_be_unmounted(child))
|
|
list_add(&child->mnt_list, candidates);
|
|
}
|
|
q = propagation_next(q, p);
|
|
}
|
|
}
|
|
list_for_each_entry(m, set, mnt_list)
|
|
m->mnt_t_flags &= ~T_UMOUNT_CANDIDATE;
|
|
}
|
|
|
|
/*
|
|
* We know that some child of @m can't be unmounted. In all places where the
|
|
* chain of descent of @m has child not overmounting the root of parent,
|
|
* the parent can't be unmounted either.
|
|
*/
|
|
static void trim_ancestors(struct mount *m)
|
|
{
|
|
struct mount *p;
|
|
|
|
for (p = m->mnt_parent; is_candidate(p); m = p, p = p->mnt_parent) {
|
|
if (IS_MNT_MARKED(m)) // all candidates beneath are overmounts
|
|
return;
|
|
SET_MNT_MARK(m);
|
|
if (m != p->overmount)
|
|
p->mnt_t_flags &= ~T_UMOUNT_CANDIDATE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find and exclude all umount candidates forbidden by @m
|
|
* (see Documentation/filesystems/propagate_umount.txt)
|
|
* If we can immediately tell that @m is OK to unmount (unlocked
|
|
* and all children are already committed to unmounting) commit
|
|
* to unmounting it.
|
|
* Only @m itself might be taken from the candidates list;
|
|
* anything found by trim_ancestors() is marked non-candidate
|
|
* and left on the list.
|
|
*/
|
|
static void trim_one(struct mount *m, struct list_head *to_umount)
|
|
{
|
|
bool remove_this = false, found = false, umount_this = false;
|
|
struct mount *n;
|
|
|
|
if (!is_candidate(m)) { // trim_ancestors() left it on list
|
|
remove_from_candidate_list(m);
|
|
return;
|
|
}
|
|
|
|
list_for_each_entry(n, &m->mnt_mounts, mnt_child) {
|
|
if (!is_candidate(n)) {
|
|
found = true;
|
|
if (n != m->overmount) {
|
|
remove_this = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
trim_ancestors(m);
|
|
} else if (!IS_MNT_LOCKED(m) && list_empty(&m->mnt_mounts)) {
|
|
remove_this = true;
|
|
umount_this = true;
|
|
}
|
|
if (remove_this) {
|
|
remove_from_candidate_list(m);
|
|
if (umount_this)
|
|
umount_one(m, to_umount);
|
|
}
|
|
}
|
|
|
|
static void handle_locked(struct mount *m, struct list_head *to_umount)
|
|
{
|
|
struct mount *cutoff = m, *p;
|
|
|
|
if (!is_candidate(m)) { // trim_ancestors() left it on list
|
|
remove_from_candidate_list(m);
|
|
return;
|
|
}
|
|
for (p = m; is_candidate(p); p = p->mnt_parent) {
|
|
remove_from_candidate_list(p);
|
|
if (!IS_MNT_LOCKED(p))
|
|
cutoff = p->mnt_parent;
|
|
}
|
|
if (will_be_unmounted(p))
|
|
cutoff = p;
|
|
while (m != cutoff) {
|
|
umount_one(m, to_umount);
|
|
m = m->mnt_parent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @m is not to going away, and it overmounts the top of a stack of mounts
|
|
* that are going away. We know that all of those are fully overmounted
|
|
* by the one above (@m being the topmost of the chain), so @m can be slid
|
|
* in place where the bottom of the stack is attached.
|
|
*
|
|
* NOTE: here we temporarily violate a constraint - two mounts end up with
|
|
* the same parent and mountpoint; that will be remedied as soon as we
|
|
* return from propagate_umount() - its caller (umount_tree()) will detach
|
|
* the stack from the parent it (and now @m) is attached to. umount_tree()
|
|
* detaches every unmounted mount from its parent, whether that parent
|
|
* remains in the tree or not.
|
|
*/
|
|
static void reparent(struct mount *m)
|
|
{
|
|
struct mount *p = m;
|
|
struct mountpoint *mp;
|
|
|
|
do {
|
|
mp = p->mnt_mp;
|
|
p = p->mnt_parent;
|
|
} while (will_be_unmounted(p));
|
|
|
|
mnt_change_mountpoint(p, mp, m);
|
|
mnt_notify_add(m);
|
|
}
|
|
|
|
/**
|
|
* propagate_umount - apply propagation rules to the set of mounts for umount()
|
|
* @set: the list of mounts to be unmounted.
|
|
*
|
|
* Collect all mounts that receive propagation from the mount in @set and have
|
|
* no obstacles to being unmounted. Add these additional mounts to the set.
|
|
*
|
|
* See Documentation/filesystems/propagate_umount.txt if you do anything in
|
|
* this area.
|
|
*
|
|
* Locks held:
|
|
* mount_lock (write_seqlock), namespace_sem (exclusive).
|
|
*/
|
|
void propagate_umount(struct list_head *set)
|
|
{
|
|
struct mount *m, *p;
|
|
LIST_HEAD(to_umount); // committed to unmounting
|
|
LIST_HEAD(candidates); // undecided umount candidates
|
|
|
|
// collect all candidates
|
|
gather_candidates(set, &candidates);
|
|
|
|
// reduce the set until it's non-shifting
|
|
list_for_each_entry_safe(m, p, &candidates, mnt_list)
|
|
trim_one(m, &to_umount);
|
|
|
|
// ... and non-revealing
|
|
while (!list_empty(&candidates)) {
|
|
m = list_first_entry(&candidates,struct mount, mnt_list);
|
|
handle_locked(m, &to_umount);
|
|
}
|
|
|
|
// now to_umount consists of all acceptable candidates
|
|
// deal with reparenting of surviving overmounts on those
|
|
list_for_each_entry(m, &to_umount, mnt_list) {
|
|
struct mount *over = m->overmount;
|
|
if (over && !will_be_unmounted(over))
|
|
reparent(over);
|
|
}
|
|
|
|
// and fold them into the set
|
|
list_splice_tail_init(&to_umount, set);
|
|
}
|