summaryrefslogtreecommitdiff
path: root/fs/namespace.c
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2025-02-21 14:13:03 +0100
committerChristian Brauner <brauner@kernel.org>2025-03-04 09:29:52 +0100
commitb73ec10a458796bed6d51768d5f26c4660f62ae4 (patch)
tree7e7166e2533e1448340bed79836a442f9f5b9be0 /fs/namespace.c
parent043bc81efbfc5580ecd9da76118722649205f30a (diff)
fs: add fastpath for dissolve_on_fput()
Instead of acquiring the namespace semaphore and the mount lock everytime we close a file with FMODE_NEED_UNMOUNT set add a fastpath that checks whether we need to at all. Most of the time the caller will have attached the mount to the filesystem hierarchy and there's nothing to do. Link: https://lore.kernel.org/r/20250221-brauner-open_tree-v1-4-dbcfcb98c676@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
Diffstat (limited to 'fs/namespace.c')
-rw-r--r--fs/namespace.c59
1 files changed, 47 insertions, 12 deletions
diff --git a/fs/namespace.c b/fs/namespace.c
index c9670a198b50..8cb15539aaaf 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2246,22 +2246,57 @@ struct vfsmount *collect_mounts(const struct path *path)
static void free_mnt_ns(struct mnt_namespace *);
static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool);
+static inline bool must_dissolve(struct mnt_namespace *mnt_ns)
+{
+ /*
+ * This mount belonged to an anonymous mount namespace
+ * but was moved to a non-anonymous mount namespace and
+ * then unmounted.
+ */
+ if (unlikely(!mnt_ns))
+ return false;
+
+ /*
+ * This mount belongs to a non-anonymous mount namespace
+ * and we know that such a mount can never transition to
+ * an anonymous mount namespace again.
+ */
+ if (!is_anon_ns(mnt_ns)) {
+ /*
+ * A detached mount either belongs to an anonymous mount
+ * namespace or a non-anonymous mount namespace. It
+ * should never belong to something purely internal.
+ */
+ VFS_WARN_ON_ONCE(mnt_ns == MNT_NS_INTERNAL);
+ return false;
+ }
+
+ return true;
+}
+
void dissolve_on_fput(struct vfsmount *mnt)
{
struct mnt_namespace *ns;
- namespace_lock();
- lock_mount_hash();
- ns = real_mount(mnt)->mnt_ns;
- if (ns) {
- if (is_anon_ns(ns))
- umount_tree(real_mount(mnt), UMOUNT_CONNECTED);
- else
- ns = NULL;
+ struct mount *m = real_mount(mnt);
+
+ scoped_guard(rcu) {
+ if (!must_dissolve(READ_ONCE(m->mnt_ns)))
+ return;
}
- unlock_mount_hash();
- namespace_unlock();
- if (ns)
- free_mnt_ns(ns);
+
+ scoped_guard(rwsem_write, &namespace_sem) {
+ ns = m->mnt_ns;
+ if (!must_dissolve(ns))
+ return;
+
+ lock_mount_hash();
+ umount_tree(m, UMOUNT_CONNECTED);
+ unlock_mount_hash();
+ }
+
+ /* Make sure we notice when we leak mounts. */
+ VFS_WARN_ON_ONCE(!mnt_ns_empty(ns));
+ free_mnt_ns(ns);
}
void drop_collected_mounts(struct vfsmount *mnt)