xfs: standardize ondisk to incore conversion for inode btrees
authorDarrick J. Wong <djwong@kernel.org>
Wed, 12 Apr 2023 02:00:01 +0000 (19:00 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Wed, 12 Apr 2023 02:00:01 +0000 (19:00 -0700)
Create a xfs_inobt_check_irec function to detect corruption in btree
records.  Fix all xfs_inobt_btrec_to_irec callsites to call the new
helper and bubble up corruption reports.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
fs/xfs/libxfs/xfs_ialloc.c
fs/xfs/libxfs/xfs_ialloc.h
fs/xfs/libxfs/xfs_ialloc_btree.c
fs/xfs/libxfs/xfs_ialloc_btree.h
fs/xfs/scrub/ialloc.c

index 7ee292a..32af832 100644 (file)
@@ -95,6 +95,33 @@ xfs_inobt_btrec_to_irec(
        irec->ir_free = be64_to_cpu(rec->inobt.ir_free);
 }
 
+/* Simple checks for inode records. */
+xfs_failaddr_t
+xfs_inobt_check_irec(
+       struct xfs_btree_cur                    *cur,
+       const struct xfs_inobt_rec_incore       *irec)
+{
+       uint64_t                        realfree;
+
+       if (!xfs_verify_agino(cur->bc_ag.pag, irec->ir_startino))
+               return __this_address;
+       if (irec->ir_count < XFS_INODES_PER_HOLEMASK_BIT ||
+           irec->ir_count > XFS_INODES_PER_CHUNK)
+               return __this_address;
+       if (irec->ir_freecount > XFS_INODES_PER_CHUNK)
+               return __this_address;
+
+       /* if there are no holes, return the first available offset */
+       if (!xfs_inobt_issparse(irec->ir_holemask))
+               realfree = irec->ir_free;
+       else
+               realfree = irec->ir_free & xfs_inobt_irec_to_allocmask(irec);
+       if (hweight64(realfree) != irec->ir_freecount)
+               return __this_address;
+
+       return NULL;
+}
+
 /*
  * Get the data from the pointed-to record.
  */
@@ -106,38 +133,25 @@ xfs_inobt_get_rec(
 {
        struct xfs_mount                *mp = cur->bc_mp;
        union xfs_btree_rec             *rec;
+       xfs_failaddr_t                  fa;
        int                             error;
-       uint64_t                        realfree;
 
        error = xfs_btree_get_rec(cur, &rec, stat);
        if (error || *stat == 0)
                return error;
 
        xfs_inobt_btrec_to_irec(mp, rec, irec);
-
-       if (!xfs_verify_agino(cur->bc_ag.pag, irec->ir_startino))
-               goto out_bad_rec;
-       if (irec->ir_count < XFS_INODES_PER_HOLEMASK_BIT ||
-           irec->ir_count > XFS_INODES_PER_CHUNK)
-               goto out_bad_rec;
-       if (irec->ir_freecount > XFS_INODES_PER_CHUNK)
-               goto out_bad_rec;
-
-       /* if there are no holes, return the first available offset */
-       if (!xfs_inobt_issparse(irec->ir_holemask))
-               realfree = irec->ir_free;
-       else
-               realfree = irec->ir_free & xfs_inobt_irec_to_allocmask(irec);
-       if (hweight64(realfree) != irec->ir_freecount)
+       fa = xfs_inobt_check_irec(cur, irec);
+       if (fa)
                goto out_bad_rec;
 
        return 0;
 
 out_bad_rec:
        xfs_warn(mp,
-               "%s Inode BTree record corruption in AG %d detected!",
+               "%s Inode BTree record corruption in AG %d detected at %pS!",
                cur->bc_btnum == XFS_BTNUM_INO ? "Used" : "Free",
-               cur->bc_ag.pag->pag_agno);
+               cur->bc_ag.pag->pag_agno, fa);
        xfs_warn(mp,
 "start inode 0x%x, count 0x%x, free 0x%x freemask 0x%llx, holemask 0x%x",
                irec->ir_startino, irec->ir_count, irec->ir_freecount,
@@ -2690,6 +2704,9 @@ xfs_ialloc_count_inodes_rec(
        struct xfs_ialloc_count_inodes  *ci = priv;
 
        xfs_inobt_btrec_to_irec(cur->bc_mp, rec, &irec);
+       if (xfs_inobt_check_irec(cur, &irec) != NULL)
+               return -EFSCORRUPTED;
+
        ci->count += irec.ir_count;
        ci->freecount += irec.ir_freecount;
 
index ab8c30b..90b0e50 100644 (file)
@@ -93,6 +93,8 @@ union xfs_btree_rec;
 void xfs_inobt_btrec_to_irec(struct xfs_mount *mp,
                const union xfs_btree_rec *rec,
                struct xfs_inobt_rec_incore *irec);
+xfs_failaddr_t xfs_inobt_check_irec(struct xfs_btree_cur *cur,
+               const struct xfs_inobt_rec_incore *irec);
 int xfs_ialloc_has_inodes_at_extent(struct xfs_btree_cur *cur,
                xfs_agblock_t bno, xfs_extlen_t len, bool *exists);
 int xfs_ialloc_has_inode_record(struct xfs_btree_cur *cur, xfs_agino_t low,
index ad6c521..f900c05 100644 (file)
@@ -608,7 +608,7 @@ xfs_iallocbt_maxlevels_ondisk(void)
  */
 uint64_t
 xfs_inobt_irec_to_allocmask(
-       struct xfs_inobt_rec_incore     *rec)
+       const struct xfs_inobt_rec_incore       *rec)
 {
        uint64_t                        bitmap = 0;
        uint64_t                        inodespbit;
index e859a6e..3262c3f 100644 (file)
@@ -53,7 +53,7 @@ struct xfs_btree_cur *xfs_inobt_stage_cursor(struct xfs_perag *pag,
 extern int xfs_inobt_maxrecs(struct xfs_mount *, int, int);
 
 /* ir_holemask to inode allocation bitmap conversion */
-uint64_t xfs_inobt_irec_to_allocmask(struct xfs_inobt_rec_incore *);
+uint64_t xfs_inobt_irec_to_allocmask(const struct xfs_inobt_rec_incore *irec);
 
 #if defined(DEBUG) || defined(XFS_WARN)
 int xfs_inobt_rec_check_count(struct xfs_mount *,
index 9563769..11afb4c 100644 (file)
@@ -119,15 +119,6 @@ xchk_iallocbt_chunk(
        return true;
 }
 
-/* Count the number of free inodes. */
-static unsigned int
-xchk_iallocbt_freecount(
-       xfs_inofree_t                   freemask)
-{
-       BUILD_BUG_ON(sizeof(freemask) != sizeof(__u64));
-       return hweight64(freemask);
-}
-
 /*
  * Check that an inode's allocation status matches ir_free in the inobt
  * record.  First we try querying the in-core inode state, and if the inode
@@ -431,24 +422,17 @@ xchk_iallocbt_rec(
        int                             holecount;
        int                             i;
        int                             error = 0;
-       unsigned int                    real_freecount;
        uint16_t                        holemask;
 
        xfs_inobt_btrec_to_irec(mp, rec, &irec);
-
-       if (irec.ir_count > XFS_INODES_PER_CHUNK ||
-           irec.ir_freecount > XFS_INODES_PER_CHUNK)
-               xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
-
-       real_freecount = irec.ir_freecount +
-                       (XFS_INODES_PER_CHUNK - irec.ir_count);
-       if (real_freecount != xchk_iallocbt_freecount(irec.ir_free))
+       if (xfs_inobt_check_irec(bs->cur, &irec) != NULL) {
                xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
+               return 0;
+       }
 
        agino = irec.ir_startino;
        /* Record has to be properly aligned within the AG. */
-       if (!xfs_verify_agino(pag, agino) ||
-           !xfs_verify_agino(pag, agino + XFS_INODES_PER_CHUNK - 1)) {
+       if (!xfs_verify_agino(pag, agino + XFS_INODES_PER_CHUNK - 1)) {
                xchk_btree_set_corrupt(bs->sc, bs->cur, 0);
                goto out;
        }