xfs: Fix deadlock between AGI and AGF when target_ip exists in xfs_rename()

Backport from the mainline kernel:
	commit 93597ae8dac0149b5c00b787cba6bf7ba213e666

Fix deadlock between AGI and AGF when target_ip exists in xfs_rename().

Signed-off-by: Kaixu Xia <kaixuxia@tencent.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
[darrick: reword the comment]
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
This commit is contained in:
Kaixu Xia 2019-12-12 02:29:13 +08:00 committed by Xiaoming Gao
parent f8c4b7aac0
commit 44a72efebd
3 changed files with 42 additions and 5 deletions

View File

@ -139,6 +139,8 @@ extern int xfs_dir_removename(struct xfs_trans *tp, struct xfs_inode *dp,
struct xfs_name *name, xfs_ino_t ino,
xfs_fsblock_t *first,
struct xfs_defer_ops *dfops, xfs_extlen_t tot);
extern bool xfs_dir2_sf_replace_needblock(struct xfs_inode *dp,
xfs_ino_t inum);
extern int xfs_dir_replace(struct xfs_trans *tp, struct xfs_inode *dp,
struct xfs_name *name, xfs_ino_t inum,
xfs_fsblock_t *first,

View File

@ -961,6 +961,27 @@ xfs_dir2_sf_removename(
return 0;
}
/*
* Check whether the sf dir replace operation need more blocks.
*/
bool
xfs_dir2_sf_replace_needblock(
struct xfs_inode *dp,
xfs_ino_t inum)
{
int newsize;
struct xfs_dir2_sf_hdr *sfp;
if (dp->i_d.di_format != XFS_DINODE_FMT_LOCAL)
return false;
sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
newsize = dp->i_df.if_bytes + (sfp->count + 1) * XFS_INO64_DIFF;
return inum > XFS_DIR2_MAX_SHORT_INUM &&
sfp->i8count == 0 && newsize > XFS_IFORK_DSIZE(dp);
}
/*
* Replace the inode number of an entry in a shortform directory.
*/
@ -997,17 +1018,14 @@ xfs_dir2_sf_replace(
*/
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && sfp->i8count == 0) {
int error; /* error return value */
int newsize; /* new inode size */
newsize = dp->i_df.if_bytes + (sfp->count + 1) * XFS_INO64_DIFF;
/*
* Won't fit as shortform, convert to block then do replace.
*/
if (newsize > XFS_IFORK_DSIZE(dp)) {
if (xfs_dir2_sf_replace_needblock(dp, args->inumber)) {
error = xfs_dir2_sf_to_block(args);
if (error) {
if (error)
return error;
}
return xfs_dir2_block_replace(args);
}
/*

View File

@ -2947,6 +2947,7 @@ xfs_rename(
xfs_fsblock_t first_block;
struct xfs_inode *wip = NULL; /* whiteout inode */
struct xfs_inode *inodes[__XFS_SORT_INODES];
struct xfs_buf *agibp;
int num_inodes = __XFS_SORT_INODES;
bool new_parent = (src_dp != target_dp);
bool src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode);
@ -3116,6 +3117,22 @@ xfs_rename(
* In case there is already an entry with the same
* name at the destination directory, remove it first.
*/
/*
* Check whether the replace operation will need to allocate
* blocks. This happens when the shortform directory lacks
* space and we have to convert it to a block format directory.
* When more blocks are necessary, we must lock the AGI first
* to preserve locking order (AGI -> AGF).
*/
if (xfs_dir2_sf_replace_needblock(target_dp, src_ip->i_ino)) {
error = xfs_read_agi(mp, tp,
XFS_INO_TO_AGNO(mp, target_ip->i_ino),
&agibp);
if (error)
goto out_trans_cancel;
}
error = xfs_dir_replace(tp, target_dp, target_name,
src_ip->i_ino,
&first_block, &dfops, spaceres);