OSDN Git Service

fs: introduce lock_rename_child() helper
authorAl Viro <viro@zeniv.linux.org.uk>
Wed, 15 Mar 2023 22:34:34 +0000 (07:34 +0900)
committerAl Viro <viro@zeniv.linux.org.uk>
Fri, 21 Apr 2023 02:37:05 +0000 (22:37 -0400)
Pass the dentry of a source file and the dentry of a destination directory
to lock parent inodes for rename. As soon as this function returns,
->d_parent of the source file dentry is stable and inodes are properly
locked for calling vfs-rename. This helper is needed for ksmbd server.
rename request of SMB protocol has to rename an opened file, no matter
which directory it's in.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c
include/linux/namei.h

index edfedfb..6bc1964 100644 (file)
@@ -2980,20 +2980,10 @@ static inline int may_create(struct mnt_idmap *idmap,
        return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
 }
 
-/*
- * p1 and p2 should be directories on the same fs.
- */
-struct dentry *lock_rename(struct dentry *p1, struct dentry *p2)
+static struct dentry *lock_two_directories(struct dentry *p1, struct dentry *p2)
 {
        struct dentry *p;
 
-       if (p1 == p2) {
-               inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
-               return NULL;
-       }
-
-       mutex_lock(&p1->d_sb->s_vfs_rename_mutex);
-
        p = d_ancestor(p2, p1);
        if (p) {
                inode_lock_nested(p2->d_inode, I_MUTEX_PARENT);
@@ -3012,8 +3002,64 @@ struct dentry *lock_rename(struct dentry *p1, struct dentry *p2)
        inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2);
        return NULL;
 }
+
+/*
+ * p1 and p2 should be directories on the same fs.
+ */
+struct dentry *lock_rename(struct dentry *p1, struct dentry *p2)
+{
+       if (p1 == p2) {
+               inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
+               return NULL;
+       }
+
+       mutex_lock(&p1->d_sb->s_vfs_rename_mutex);
+       return lock_two_directories(p1, p2);
+}
 EXPORT_SYMBOL(lock_rename);
 
+/*
+ * c1 and p2 should be on the same fs.
+ */
+struct dentry *lock_rename_child(struct dentry *c1, struct dentry *p2)
+{
+       if (READ_ONCE(c1->d_parent) == p2) {
+               /*
+                * hopefully won't need to touch ->s_vfs_rename_mutex at all.
+                */
+               inode_lock_nested(p2->d_inode, I_MUTEX_PARENT);
+               /*
+                * now that p2 is locked, nobody can move in or out of it,
+                * so the test below is safe.
+                */
+               if (likely(c1->d_parent == p2))
+                       return NULL;
+
+               /*
+                * c1 got moved out of p2 while we'd been taking locks;
+                * unlock and fall back to slow case.
+                */
+               inode_unlock(p2->d_inode);
+       }
+
+       mutex_lock(&c1->d_sb->s_vfs_rename_mutex);
+       /*
+        * nobody can move out of any directories on this fs.
+        */
+       if (likely(c1->d_parent != p2))
+               return lock_two_directories(c1->d_parent, p2);
+
+       /*
+        * c1 got moved into p2 while we were taking locks;
+        * we need p2 locked and ->s_vfs_rename_mutex unlocked,
+        * for consistency with lock_rename().
+        */
+       inode_lock_nested(p2->d_inode, I_MUTEX_PARENT);
+       mutex_unlock(&c1->d_sb->s_vfs_rename_mutex);
+       return NULL;
+}
+EXPORT_SYMBOL(lock_rename_child);
+
 void unlock_rename(struct dentry *p1, struct dentry *p2)
 {
        inode_unlock(p1->d_inode);
index ba9b32b..5864e4d 100644 (file)
@@ -83,6 +83,7 @@ extern int follow_down(struct path *path, unsigned int flags);
 extern int follow_up(struct path *);
 
 extern struct dentry *lock_rename(struct dentry *, struct dentry *);
+extern struct dentry *lock_rename_child(struct dentry *, struct dentry *);
 extern void unlock_rename(struct dentry *, struct dentry *);
 
 extern int __must_check nd_jump_link(const struct path *path);