]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
add locked_recursive_removal()
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 9 May 2024 20:32:51 +0000 (16:32 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 3 Jul 2025 02:36:27 +0000 (22:36 -0400)
simple_recursive_removal() assumes that parent is not locked and
locks it when it finally gets to removing the victim itself.
Usually that's what we want, but there are places where the
parent is *already* locked and we need it to stay that way.
In those cases simple_recursive_removal() would, of course,
deadlock, so we have to play racy games with unlocking/relocking
the parent around the call or open-code the entire thing.

A better solution is to provide a variant that expects to
be called with the parent already locked by the caller.
Parent should be locked with I_MUTEX_PARENT, to avoid false
positives from lockdep.

Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/libfs.c
include/linux/fs.h

index 20b05a6db7701b70048a45377d8f1924a7c4da6a..429caacc5229259e14017df28cb67297698e8102 100644 (file)
@@ -605,8 +605,9 @@ struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
 }
 EXPORT_SYMBOL(find_next_child);
 
-void simple_recursive_removal(struct dentry *dentry,
-                              void (*callback)(struct dentry *))
+static void __simple_recursive_removal(struct dentry *dentry,
+                              void (*callback)(struct dentry *),
+                             bool locked)
 {
        struct dentry *this = dget(dentry);
        while (true) {
@@ -625,7 +626,8 @@ void simple_recursive_removal(struct dentry *dentry,
                        victim = this;
                        this = this->d_parent;
                        inode = this->d_inode;
-                       inode_lock_nested(inode, I_MUTEX_CHILD);
+                       if (!locked || victim != dentry)
+                               inode_lock_nested(inode, I_MUTEX_CHILD);
                        if (simple_positive(victim)) {
                                d_invalidate(victim);   // avoid lost mounts
                                if (callback)
@@ -638,7 +640,8 @@ void simple_recursive_removal(struct dentry *dentry,
                                                      inode_set_ctime_current(inode));
                                if (d_is_dir(dentry))
                                        drop_nlink(inode);
-                               inode_unlock(inode);
+                               if (!locked)
+                                       inode_unlock(inode);
                                dput(dentry);
                                return;
                        }
@@ -647,8 +650,22 @@ void simple_recursive_removal(struct dentry *dentry,
                this = child;
        }
 }
+
+void simple_recursive_removal(struct dentry *dentry,
+                              void (*callback)(struct dentry *))
+{
+       return __simple_recursive_removal(dentry, callback, false);
+}
 EXPORT_SYMBOL(simple_recursive_removal);
 
+/* caller holds parent directory with I_MUTEX_PARENT */
+void locked_recursive_removal(struct dentry *dentry,
+                              void (*callback)(struct dentry *))
+{
+       return __simple_recursive_removal(dentry, callback, true);
+}
+EXPORT_SYMBOL(locked_recursive_removal);
+
 static const struct super_operations simple_super_operations = {
        .statfs         = simple_statfs,
 };
index 96c7925a655199ebdfefafa587ea5ae3e3665867..4f0c6bf8d652e21cf84c257c5c51e610814f1bd0 100644 (file)
@@ -3595,6 +3595,8 @@ extern int simple_rename(struct mnt_idmap *, struct inode *,
                         unsigned int);
 extern void simple_recursive_removal(struct dentry *,
                               void (*callback)(struct dentry *));
+extern void locked_recursive_removal(struct dentry *,
+                              void (*callback)(struct dentry *));
 extern int noop_fsync(struct file *, loff_t, loff_t, int);
 extern ssize_t noop_direct_IO(struct kiocb *iocb, struct iov_iter *iter);
 extern int simple_empty(struct dentry *);