summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2023-02-24 19:20:07 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2023-02-24 19:20:07 -0800
commit3df88c6a175d883b58fc3c31e36c94eb5e2ad180 (patch)
treeedf5dbd6d199ec94d3617945be0b8dbfbc578798
parent10cc5d483ebc00e82d9a38d3419b2edc8b79b64d (diff)
parentda27f796a832122ee533c7685438dad1c4e338dd (diff)
Merge branch 'work.namespace' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull ipc namespace update from Al Viro: "Rik's patches reducing the amount of synchronize_rcu() triggered by ipc namespace destruction. I've some pending stuff reducing that on the normal umount side, but it's nowhere near ready and Rik's stuff shouldn't be held back due to conflicts - I'll just redo the parts of my series that stray into ipc/*" * 'work.namespace' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: ipc,namespace: batch free ipc_namespace structures ipc,namespace: make ipc namespace allocation wait for pending free
-rw-r--r--fs/namespace.c18
-rw-r--r--include/linux/mount.h1
-rw-r--r--ipc/mqueue.c5
-rw-r--r--ipc/namespace.c35
-rw-r--r--ipc/util.h2
5 files changed, 41 insertions, 20 deletions
diff --git a/fs/namespace.c b/fs/namespace.c
index 5927d90e24a0..bc0f15257b49 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1283,6 +1283,17 @@ struct vfsmount *mntget(struct vfsmount *mnt)
}
EXPORT_SYMBOL(mntget);
+/*
+ * Make a mount point inaccessible to new lookups.
+ * Because there may still be current users, the caller MUST WAIT
+ * for an RCU grace period before destroying the mount point.
+ */
+void mnt_make_shortterm(struct vfsmount *mnt)
+{
+ if (mnt)
+ real_mount(mnt)->mnt_ns = NULL;
+}
+
/**
* path_is_mountpoint() - Check if path is a mount in the current namespace.
* @path: path to check
@@ -4459,8 +4470,8 @@ EXPORT_SYMBOL_GPL(kern_mount);
void kern_unmount(struct vfsmount *mnt)
{
/* release long term mount so mount point can be released */
- if (!IS_ERR_OR_NULL(mnt)) {
- real_mount(mnt)->mnt_ns = NULL;
+ if (!IS_ERR(mnt)) {
+ mnt_make_shortterm(mnt);
synchronize_rcu(); /* yecchhh... */
mntput(mnt);
}
@@ -4472,8 +4483,7 @@ void kern_unmount_array(struct vfsmount *mnt[], unsigned int num)
unsigned int i;
for (i = 0; i < num; i++)
- if (mnt[i])
- real_mount(mnt[i])->mnt_ns = NULL;
+ mnt_make_shortterm(mnt[i]);
synchronize_rcu_expedited();
for (i = 0; i < num; i++)
mntput(mnt[i]);
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 52f452b2259a..1ea326c368f7 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -86,6 +86,7 @@ extern void mnt_drop_write(struct vfsmount *mnt);
extern void mnt_drop_write_file(struct file *file);
extern void mntput(struct vfsmount *mnt);
extern struct vfsmount *mntget(struct vfsmount *mnt);
+extern void mnt_make_shortterm(struct vfsmount *mnt);
extern struct vfsmount *mnt_clone_internal(const struct path *path);
extern bool __mnt_is_readonly(struct vfsmount *mnt);
extern bool mnt_may_suid(struct vfsmount *mnt);
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index 0160e9f2b07c..71881bddad25 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -1709,11 +1709,6 @@ void mq_clear_sbinfo(struct ipc_namespace *ns)
ns->mq_mnt->mnt_sb->s_fs_info = NULL;
}
-void mq_put_mnt(struct ipc_namespace *ns)
-{
- kern_unmount(ns->mq_mnt);
-}
-
static int __init init_mqueue_fs(void)
{
int error;
diff --git a/ipc/namespace.c b/ipc/namespace.c
index 8316ea585733..6ecc30effd3e 100644
--- a/ipc/namespace.c
+++ b/ipc/namespace.c
@@ -19,6 +19,12 @@
#include "util.h"
+/*
+ * The work queue is used to avoid the cost of synchronize_rcu in kern_unmount.
+ */
+static void free_ipc(struct work_struct *unused);
+static DECLARE_WORK(free_ipc_work, free_ipc);
+
static struct ucounts *inc_ipc_namespaces(struct user_namespace *ns)
{
return inc_ucount(ns, current_euid(), UCOUNT_IPC_NAMESPACES);
@@ -37,9 +43,18 @@ static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns,
int err;
err = -ENOSPC;
+ again:
ucounts = inc_ipc_namespaces(user_ns);
- if (!ucounts)
+ if (!ucounts) {
+ /*
+ * IPC namespaces are freed asynchronously, by free_ipc_work.
+ * If frees were pending, flush_work will wait, and
+ * return true. Fail the allocation if no frees are pending.
+ */
+ if (flush_work(&free_ipc_work))
+ goto again;
goto fail;
+ }
err = -ENOMEM;
ns = kzalloc(sizeof(struct ipc_namespace), GFP_KERNEL_ACCOUNT);
@@ -130,10 +145,11 @@ void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids,
static void free_ipc_ns(struct ipc_namespace *ns)
{
- /* mq_put_mnt() waits for a grace period as kern_unmount()
- * uses synchronize_rcu().
+ /*
+ * Caller needs to wait for an RCU grace period to have passed
+ * after making the mount point inaccessible to new accesses.
*/
- mq_put_mnt(ns);
+ mntput(ns->mq_mnt);
sem_exit_ns(ns);
msg_exit_ns(ns);
shm_exit_ns(ns);
@@ -154,15 +170,16 @@ static void free_ipc(struct work_struct *unused)
struct ipc_namespace *n, *t;
llist_for_each_entry_safe(n, t, node, mnt_llist)
+ mnt_make_shortterm(n->mq_mnt);
+
+ /* Wait for any last users to have gone away. */
+ synchronize_rcu();
+
+ llist_for_each_entry_safe(n, t, node, mnt_llist)
free_ipc_ns(n);
}
/*
- * The work queue is used to avoid the cost of synchronize_rcu in kern_unmount.
- */
-static DECLARE_WORK(free_ipc_work, free_ipc);
-
-/*
* put_ipc_ns - drop a reference to an ipc namespace.
* @ns: the namespace to put
*
diff --git a/ipc/util.h b/ipc/util.h
index b2906e366539..67bdd2aa2c28 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -56,10 +56,8 @@ struct pid_namespace;
#ifdef CONFIG_POSIX_MQUEUE
extern void mq_clear_sbinfo(struct ipc_namespace *ns);
-extern void mq_put_mnt(struct ipc_namespace *ns);
#else
static inline void mq_clear_sbinfo(struct ipc_namespace *ns) { }
-static inline void mq_put_mnt(struct ipc_namespace *ns) { }
#endif
#ifdef CONFIG_SYSVIPC