On Fri, Apr 01, 2022 at 09:52:09PM +0900, Namjae Jeon wrote: > > take source and new parent and do the following: > > > > if (READ_ONCE(source->d_parent) == new_parent) { > > inode_lock_nested(new_parent->d_inode, I_MUTEX_PARENT); > > if (likely(source->d_parent == new_parent)) > > return NULL; and inode_unlock(new_parent->d_inode); to do locking in proper order... > > } > > // fuck that, looks like a cross-rename one. > > mutex_lock(&source->d_sb->s_vfs_rename_mutex); > > // now all ->d_parent are stable > > if (unlikely(source->d_parent == new_parent)) { > > inode_lock_nested(new_parent->d_inode, I_MUTEX_PARENT); > > // we want the same rules as for lock_rename() > > mutex_unlock(&source->d_sb->s_vfs_rename_mutex); > > return NULL; > > } > > // cross-directory it is... > > same as lock_rename() after having grabbed ->s_vfs_rename_mutex // p1 != p2, p1->d_sb == p2->d_sb, p1->d_sb->s_vfs_rename_mutex held static struct dentry *lock_two_directories(struct dentry *p1, struct dentry *p2) { struct dentry *p; p = d_ancestor(p2, p1); if (p) { inode_lock_nested(p2->d_inode, I_MUTEX_PARENT); inode_lock_nested(p1->d_inode, I_MUTEX_CHILD); return p; } p = d_ancestor(p1, p2); if (p) { inode_lock_nested(p1->d_inode, I_MUTEX_PARENT); inode_lock_nested(p2->d_inode, I_MUTEX_CHILD); return p; } inode_lock_nested(p1->d_inode, I_MUTEX_PARENT); inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2); return NULL; } 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); } struct dentry *lock_rename_child(struct dentry *c1, struct dentry *p2) { if (READ_ONCE(c1->d_parent) == p2) { inode_lock_nested(p2->d_inode, I_MUTEX_PARENT); if (likely(c1->d_parent == p2)) return NULL; // too bad, we'd raced with another rename inode_unlock(p2->d_inode); } // looks like it's cross-directory mutex_lock(&c1->d_sb->s_vfs_rename_mutex); // recheck, now that ->d_parent is stable if (likely(c1->d_parent != p2)) return lock_two_directories(c1->d_parent, p2); // it's not cross-directory, after all - raced with another rename inode_lock_nested(p2->d_inode, I_MUTEX_PARENT); // drop ->s_vfs_rename_mutex, so we won't confuse unlock_rename() // note that locked p2 alone is enough to prevent moves to or from // p2, so c1->d_parent will remain p2 until we unlock p2 mutex_unlock(&c1->d_sb->s_vfs_rename_mutex); return NULL; }