From: Al Viro viro@zeniv.linux.org.uk
[ Upstream commit 9bc37e04823b5280dd0f22b6680fc23fe81ca325 ]
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 | 68 ++++++++++++++++++++++++++++++++++++------- include/linux/namei.h | 1 + 2 files changed, 58 insertions(+), 11 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c index 5e1c2ab2ae70..6daaf8456719 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2980,20 +2980,10 @@ static inline int may_create(struct user_namespace *mnt_userns, return inode_permission(mnt_userns, 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) I_MUTEX_PARENT, 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); diff --git a/include/linux/namei.h b/include/linux/namei.h index 00fee52df842..2b66021c740d 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -81,6 +81,7 @@ extern int follow_down(struct path *); 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);