author: Chandan Babu R <chandanrlinux@gmail.com> 2021-04-06 06:59:18 -0700
committer: Darrick J. Wong <djwong@kernel.org> 2021-04-07 14:37:06 -0700
commit: ae7bae68ea4943318e3014d4a6d4a2a289e16aab
parent: b6785e279d53ca5c4fa6be1146e85000870d73ef
Commit Summary:
Diffstat:
1 file changed, 27 insertions, 2 deletions
diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c
index debf392e0515..a94bd8122c60 100644
--- a/fs/xfs/scrub/btree.c
+++ b/fs/xfs/scrub/btree.c
@@ -9,6 +9,7 @@
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
+#include "xfs_inode.h"
#include "xfs_btree.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
@@ -442,6 +443,30 @@ xchk_btree_check_owner(
return xchk_btree_check_block_owner(bs, level, XFS_BUF_ADDR(bp));
}
+/* Decide if we want to check minrecs of a btree block in the inode root. */
+static inline bool
+xchk_btree_check_iroot_minrecs(
+ struct xchk_btree *bs)
+{
+ /*
+ * xfs_bmap_add_attrfork_btree had an implementation bug wherein it
+ * would miscalculate the space required for the data fork bmbt root
+ * when adding an attr fork, and promote the iroot contents to an
+ * external block unnecessarily. This went unnoticed for many years
+ * until scrub found filesystems in this state. Inode rooted btrees are
+ * not supposed to have immediate child blocks that are small enough
+ * that the contents could fit in the inode root, but we can't fail
+ * existing filesystems, so instead we disable the check for data fork
+ * bmap btrees when there's an attr fork.
+ */
+ if (bs->cur->bc_btnum == XFS_BTNUM_BMAP &&
+ bs->cur->bc_ino.whichfork == XFS_DATA_FORK &&
+ XFS_IFORK_Q(bs->sc->ip))
+ return false;
+
+ return true;
+}
+
/*
* Check that this btree block has at least minrecs records or is one of the
* special blocks that don't require that.
@@ -475,8 +500,9 @@ xchk_btree_check_minrecs(
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)
+ if (xchk_btree_check_iroot_minrecs(bs) &&
+ (be16_to_cpu(root_block->bb_numrecs) != 1 ||
+ numrecs <= root_maxrecs))
xchk_btree_set_corrupt(bs->sc, cur, level);
return;
}