xfs: fix the minrecs logic when dealing with inode root child blocks
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 9 Nov 2020 00:32:41 +0000 (16:32 -0800)
committerDarrick J. Wong <darrick.wong@oracle.com>
Wed, 18 Nov 2020 17:23:50 +0000 (09:23 -0800)
The comment and logic in xchk_btree_check_minrecs for dealing with
inode-rooted btrees isn't quite correct.  While the direct children of
the inode root are allowed to have fewer records than what would
normally be allowed for a regular ondisk btree block, this is only true
if there is only one child block and the number of records don't fit in
the inode root.

Fixes: 08a3a692ef58 ("xfs: btree scrub should check minrecs")
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Chandan Babu R <chandanrlinux@gmail.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/scrub/btree.c

index f52a7b8..debf392 100644 (file)
@@ -452,32 +452,41 @@ xchk_btree_check_minrecs(
        int                     level,
        struct xfs_btree_block  *block)
 {
-       unsigned int            numrecs;
-       int                     ok_level;
-
-       numrecs = be16_to_cpu(block->bb_numrecs);
+       struct xfs_btree_cur    *cur = bs->cur;
+       unsigned int            root_level = cur->bc_nlevels - 1;
+       unsigned int            numrecs = be16_to_cpu(block->bb_numrecs);
 
        /* More records than minrecs means the block is ok. */
-       if (numrecs >= bs->cur->bc_ops->get_minrecs(bs->cur, level))
+       if (numrecs >= cur->bc_ops->get_minrecs(cur, level))
                return;
 
        /*
-        * Certain btree blocks /can/ have fewer than minrecs records.  Any
-        * level greater than or equal to the level of the highest dedicated
-        * btree block are allowed to violate this constraint.
-        *
-        * For a btree rooted in a block, the btree root can have fewer than
-        * minrecs records.  If the btree is rooted in an inode and does not
-        * store records in the root, the direct children of the root and the
-        * root itself can have fewer than minrecs records.
+        * For btrees rooted in the inode, it's possible that the root block
+        * contents spilled into a regular ondisk block because there wasn't
+        * enough space in the inode root.  The number of records in that
+        * child block might be less than the standard minrecs, but that's ok
+        * provided that there's only one direct child of the root.
         */
-       ok_level = bs->cur->bc_nlevels - 1;
-       if (bs->cur->bc_flags & XFS_BTREE_ROOT_IN_INODE)
-               ok_level--;
-       if (level >= ok_level)
+       if ((cur->bc_flags & XFS_BTREE_ROOT_IN_INODE) &&
+           level == cur->bc_nlevels - 2) {
+               struct xfs_btree_block  *root_block;
+               struct xfs_buf          *root_bp;
+               int                     root_maxrecs;
+
+               root_block = xfs_btree_get_block(cur, root_level, &root_bp);
+               root_maxrecs = cur->bc_ops->get_dmaxrecs(cur, root_level);
+               if (be16_to_cpu(root_block->bb_numrecs) != 1 ||
+                   numrecs <= root_maxrecs)
+                       xchk_btree_set_corrupt(bs->sc, cur, level);
                return;
+       }
 
-       xchk_btree_set_corrupt(bs->sc, bs->cur, level);
+       /*
+        * Otherwise, only the root level is allowed to have fewer than minrecs
+        * records or keyptrs.
+        */
+       if (level < root_level)
+               xchk_btree_set_corrupt(bs->sc, cur, level);
 }
 
 /*