summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fs/libfs.c61
-rw-r--r--fs/nsfs.c50
-rw-r--r--fs/pidfs.c28
3 files changed, 73 insertions, 66 deletions
diff --git a/fs/libfs.c b/fs/libfs.c
index 2acba9d53756..7617e1bc6e5b 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1988,11 +1988,11 @@ static inline struct dentry *get_stashed_dentry(struct dentry *stashed)
return dentry;
}
-static struct dentry *stash_dentry(struct dentry **stashed, unsigned long ino,
- struct super_block *sb,
- const struct file_operations *fops,
- const struct inode_operations *iops,
- void *data)
+static struct dentry *prepare_anon_dentry(unsigned long ino,
+ struct super_block *sb,
+ const struct file_operations *fops,
+ const struct inode_operations *iops,
+ void *data)
{
struct dentry *dentry;
struct inode *inode;
@@ -2021,15 +2021,29 @@ static struct dentry *stash_dentry(struct dentry **stashed, unsigned long ino,
/* @data is now owned by the fs */
d_instantiate(dentry, inode);
+ return dentry;
+}
- if (cmpxchg(stashed, NULL, dentry)) {
- d_delete(dentry); /* make sure ->d_prune() does nothing */
- dput(dentry);
- cpu_relax();
- return ERR_PTR(-EAGAIN);
- }
+static struct dentry *stash_dentry(struct dentry **stashed,
+ struct dentry *dentry)
+{
+ guard(rcu)();
+ for (;;) {
+ struct dentry *old;
- return dentry;
+ /* Assume any old dentry was cleared out. */
+ old = cmpxchg(stashed, NULL, dentry);
+ if (likely(!old))
+ return dentry;
+
+ /* Check if somebody else installed a reusable dentry. */
+ if (lockref_get_not_dead(&old->d_lockref))
+ return old;
+
+ /* There's an old dead dentry there, try to take it over. */
+ if (likely(try_cmpxchg(stashed, &old, dentry)))
+ return dentry;
+ }
}
/**
@@ -2044,15 +2058,14 @@ static struct dentry *stash_dentry(struct dentry **stashed, unsigned long ino,
*
* The function tries to retrieve a stashed dentry from @stashed. If the dentry
* is still valid then it will be reused. If the dentry isn't able the function
- * will allocate a new dentry and inode. It will then try to update @stashed
- * with the newly added dentry. If it fails -EAGAIN is returned and the caller
- * my retry.
+ * will allocate a new dentry and inode. It will then check again whether it
+ * can reuse an existing dentry in case one has been added in the meantime or
+ * update @stashed with the newly added dentry.
*
* Special-purpose helper for nsfs and pidfs.
*
* Return: If 0 or an error is returned the caller can be sure that @data must
- * be cleaned up. If 1 or -EAGAIN is returned @data is owned by the
- * filesystem.
+ * be cleaned up. If 1 is returned @data is owned by the filesystem.
*/
int path_from_stashed(struct dentry **stashed, unsigned long ino,
struct vfsmount *mnt, const struct file_operations *fops,
@@ -2062,17 +2075,23 @@ int path_from_stashed(struct dentry **stashed, unsigned long ino,
struct dentry *dentry;
int ret = 0;
- dentry = get_stashed_dentry(*stashed);
- if (dentry)
+ /* See if dentry can be reused. */
+ path->dentry = get_stashed_dentry(*stashed);
+ if (path->dentry)
goto out_path;
- dentry = stash_dentry(stashed, ino, mnt->mnt_sb, fops, iops, data);
+ /* Allocate a new dentry. */
+ dentry = prepare_anon_dentry(ino, mnt->mnt_sb, fops, iops, data);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
+
+ /* Added a new dentry. @data is now owned by the filesystem. */
+ path->dentry = stash_dentry(stashed, dentry);
+ if (path->dentry != dentry)
+ dput(dentry);
ret = 1;
out_path:
- path->dentry = dentry;
path->mnt = mntget(mnt);
return ret;
}
diff --git a/fs/nsfs.c b/fs/nsfs.c
index e2da645c3d02..3a36bb62353c 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -36,10 +36,12 @@ static char *ns_dname(struct dentry *dentry, char *buffer, int buflen)
static void ns_prune_dentry(struct dentry *dentry)
{
- struct inode *inode = d_inode(dentry);
+ struct inode *inode;
+
+ inode = d_inode(dentry);
if (inode) {
struct ns_common *ns = inode->i_private;
- WRITE_ONCE(ns->stashed, NULL);
+ cmpxchg(&ns->stashed, dentry, NULL);
}
}
@@ -61,20 +63,17 @@ int ns_get_path_cb(struct path *path, ns_get_path_helper_t *ns_get_cb,
void *private_data)
{
int ret;
+ struct ns_common *ns;
- do {
- struct ns_common *ns = ns_get_cb(private_data);
- if (!ns)
- return -ENOENT;
- ret = path_from_stashed(&ns->stashed, ns->inum, nsfs_mnt,
- &ns_file_operations, NULL, ns, path);
- if (ret <= 0 && ret != -EAGAIN)
- ns->ops->put(ns);
- } while (ret == -EAGAIN);
-
+ ns = ns_get_cb(private_data);
+ if (!ns)
+ return -ENOENT;
+ ret = path_from_stashed(&ns->stashed, ns->inum, nsfs_mnt,
+ &ns_file_operations, NULL, ns, path);
+ if (ret <= 0)
+ ns->ops->put(ns);
if (ret < 0)
return ret;
-
return 0;
}
@@ -105,6 +104,7 @@ int open_related_ns(struct ns_common *ns,
struct ns_common *(*get_ns)(struct ns_common *ns))
{
struct path path = {};
+ struct ns_common *relative;
struct file *f;
int err;
int fd;
@@ -113,22 +113,16 @@ int open_related_ns(struct ns_common *ns,
if (fd < 0)
return fd;
- do {
- struct ns_common *relative;
-
- relative = get_ns(ns);
- if (IS_ERR(relative)) {
- put_unused_fd(fd);
- return PTR_ERR(relative);
- }
-
- err = path_from_stashed(&relative->stashed, relative->inum,
- nsfs_mnt, &ns_file_operations, NULL,
- relative, &path);
- if (err <= 0 && err != -EAGAIN)
- relative->ops->put(relative);
- } while (err == -EAGAIN);
+ relative = get_ns(ns);
+ if (IS_ERR(relative)) {
+ put_unused_fd(fd);
+ return PTR_ERR(relative);
+ }
+ err = path_from_stashed(&relative->stashed, relative->inum, nsfs_mnt,
+ &ns_file_operations, NULL, relative, &path);
+ if (err <= 0)
+ relative->ops->put(relative);
if (err < 0) {
put_unused_fd(fd);
return err;
diff --git a/fs/pidfs.c b/fs/pidfs.c
index cf606f15def5..5f33c820b7f8 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -140,7 +140,6 @@ struct pid *pidfd_pid(const struct file *file)
#ifdef CONFIG_FS_PID
static struct vfsmount *pidfs_mnt __ro_after_init;
-static struct super_block *pidfs_sb __ro_after_init;
/*
* The vfs falls back to simple_setattr() if i_op->setattr() isn't
@@ -195,7 +194,7 @@ static void pidfs_prune_dentry(struct dentry *dentry)
inode = d_inode(dentry);
if (inode) {
struct pid *pid = inode->i_private;
- WRITE_ONCE(pid->stashed, NULL);
+ cmpxchg(&pid->stashed, dentry, NULL);
}
}
@@ -231,19 +230,16 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
struct path path;
int ret;
- do {
- /*
- * Inode numbering for pidfs start at RESERVED_PIDS + 1.
- * This avoids collisions with the root inode which is 1
- * for pseudo filesystems.
- */
- ret = path_from_stashed(&pid->stashed, pid->ino, pidfs_mnt,
- &pidfs_file_operations,
- &pidfs_inode_operations, get_pid(pid),
- &path);
- if (ret <= 0 && ret != -EAGAIN)
- put_pid(pid);
- } while (ret == -EAGAIN);
+ /*
+ * Inode numbering for pidfs start at RESERVED_PIDS + 1.
+ * This avoids collisions with the root inode which is 1
+ * for pseudo filesystems.
+ */
+ ret = path_from_stashed(&pid->stashed, pid->ino, pidfs_mnt,
+ &pidfs_file_operations, &pidfs_inode_operations,
+ get_pid(pid), &path);
+ if (ret <= 0)
+ put_pid(pid);
if (ret < 0)
return ERR_PTR(ret);
@@ -257,8 +253,6 @@ void __init pidfs_init(void)
pidfs_mnt = kern_mount(&pidfs_type);
if (IS_ERR(pidfs_mnt))
panic("Failed to mount pidfs pseudo filesystem");
-
- pidfs_sb = pidfs_mnt->mnt_sb;
}
bool is_pidfs_sb(const struct super_block *sb)