]> git.itanic.dy.fi Git - linux-stable/commitdiff
xfs: Fix deadlock between AGI and AGF when target_ip exists in xfs_rename()
authorkaixuxia <xiakaixu1987@gmail.com>
Sat, 24 Sep 2022 12:56:45 +0000 (18:26 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 28 Sep 2022 09:04:10 +0000 (11:04 +0200)
commit 93597ae8dac0149b5c00b787cba6bf7ba213e666 upstream.

When target_ip exists in xfs_rename(), the xfs_dir_replace() call may
need to hold the AGF lock to allocate more blocks, and then invoking
the xfs_droplink() call to hold AGI lock to drop target_ip onto the
unlinked list, so we get the lock order AGF->AGI. This would break the
ordering constraint on AGI and AGF locking - inode allocation locks
the AGI, then can allocate a new extent for new inodes, locking the
AGF after the AGI.

In this patch we check whether the replace operation need more
blocks firstly. If so, acquire the agi lock firstly to preserve
locking order(AGI/AGF). Actually, the locking order problem only
occurs when we are locking the AGI/AGF of the same AG. For multiple
AGs the AGI lock will be released after the transaction committed.

Signed-off-by: kaixuxia <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>
Acked-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Chandan Babu R <chandan.babu@oracle.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/xfs/libxfs/xfs_dir2.h
fs/xfs/libxfs/xfs_dir2_sf.c
fs/xfs/xfs_inode.c

index f542447794928e47c9eba683690866e105506aaa..01b1722333a97f784540f4c98b1104df2eb8ff58 100644 (file)
@@ -124,6 +124,8 @@ extern int xfs_dir_lookup(struct xfs_trans *tp, struct xfs_inode *dp,
 extern int xfs_dir_removename(struct xfs_trans *tp, struct xfs_inode *dp,
                                struct xfs_name *name, xfs_ino_t ino,
                                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_extlen_t tot);
index ae16ca7c422a9e705e7240e6f0ede97e36e9165d..90eff6c2de7e0dafba4e88a7f5b430853bb793bb 100644 (file)
@@ -944,6 +944,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.
  */
@@ -980,17 +1001,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);
                }
                /*
index 7a9048c4c2f951f315721e2c2dc84fc5817fdca9..8990be13a16c4758157d0c8f257922509959d8f5 100644 (file)
@@ -3215,6 +3215,7 @@ xfs_rename(
        struct xfs_trans        *tp;
        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);
@@ -3379,6 +3380,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, spaceres);
                if (error)