]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blobdiff - repair/scan.c
xfs_repair: fix 'bno' shadow vars in scan.c
[thirdparty/xfsprogs-dev.git] / repair / scan.c
index 158fb436354d972006e19df9388adffccbbf8784..12ca31441b7baac23d30b0ccc7c676e17d3f032f 100644 (file)
@@ -1,22 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
  * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it would be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write the Free Software Foundation,
- * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include <libxfs.h>
+#include "libxfs.h"
 #include "avl.h"
 #include "globals.h"
 #include "agheader.h"
 #include "versions.h"
 #include "bmap.h"
 #include "progress.h"
-
-extern int verify_set_agheader(xfs_mount_t *mp, xfs_buf_t *sbuf, xfs_sb_t *sb,
-               xfs_agf_t *agf, xfs_agi_t *agi, xfs_agnumber_t i);
+#include "threads.h"
+#include "slab.h"
+#include "rmap.h"
 
 static xfs_mount_t     *mp = NULL;
 
+/*
+ * Variables to validate AG header values against the manual count
+ * from the btree traversal.
+ */
+struct aghdr_cnts {
+       xfs_agnumber_t  agno;
+       xfs_extlen_t    agffreeblks;
+       xfs_extlen_t    agflongest;
+       uint64_t        agfbtreeblks;
+       uint32_t        agicount;
+       uint32_t        agifreecount;
+       uint64_t        fdblocks;
+       uint64_t        usedblocks;
+       uint64_t        ifreecount;
+       uint32_t        fibtfreecount;
+};
+
 void
 set_mp(xfs_mount_t *mpp)
 {
@@ -41,7 +46,7 @@ set_mp(xfs_mount_t *mpp)
        mp = mpp;
 }
 
-void
+static void
 scan_sbtree(
        xfs_agblock_t   root,
        int             nlevels,
@@ -52,18 +57,30 @@ scan_sbtree(
                                xfs_agblock_t           bno,
                                xfs_agnumber_t          agno,
                                int                     suspect,
-                               int                     isroot),
-       int             isroot)
+                               int                     isroot,
+                               uint32_t                magic,
+                               void                    *priv),
+       int             isroot,
+       uint32_t        magic,
+       void            *priv,
+       const struct xfs_buf_ops *ops)
 {
        xfs_buf_t       *bp;
 
        bp = libxfs_readbuf(mp->m_dev, XFS_AGB_TO_DADDR(mp, agno, root),
-                       XFS_FSB_TO_BB(mp, 1), 0);
+                       XFS_FSB_TO_BB(mp, 1), 0, ops);
        if (!bp) {
                do_error(_("can't read btree block %d/%d\n"), agno, root);
                return;
        }
-       (*func)(XFS_BUF_TO_BLOCK(bp), nlevels - 1, root, agno, suspect, isroot);
+       if (bp->b_error == -EFSBADCRC || bp->b_error == -EFSCORRUPTED) {
+               do_warn(_("btree block %d/%d is suspect, error %d\n"),
+                       agno, root, bp->b_error);
+               suspect = 1;
+       }
+
+       (*func)(XFS_BUF_TO_BLOCK(bp), nlevels - 1, root, agno, suspect,
+                                                       isroot, magic, priv);
        libxfs_putbuf(bp);
 }
 
@@ -72,50 +89,68 @@ scan_sbtree(
  */
 int
 scan_lbtree(
-       xfs_dfsbno_t    root,
+       xfs_fsblock_t   root,
        int             nlevels,
        int             (*func)(struct xfs_btree_block  *block,
                                int                     level,
                                int                     type,
                                int                     whichfork,
-                               xfs_dfsbno_t            bno,
+                               xfs_fsblock_t           bno,
                                xfs_ino_t               ino,
-                               xfs_drfsbno_t           *tot,
-                               __uint64_t              *nex,
+                               xfs_rfsblock_t          *tot,
+                               uint64_t                *nex,
                                blkmap_t                **blkmapp,
                                bmap_cursor_t           *bm_cursor,
                                int                     isroot,
                                int                     check_dups,
-                               int                     *dirty),
+                               int                     *dirty,
+                               uint64_t                magic),
        int             type,
        int             whichfork,
        xfs_ino_t       ino,
-       xfs_drfsbno_t   *tot,
-       __uint64_t      *nex,
+       xfs_rfsblock_t  *tot,
+       uint64_t        *nex,
        blkmap_t        **blkmapp,
        bmap_cursor_t   *bm_cursor,
        int             isroot,
-       int             check_dups)
+       int             check_dups,
+       uint64_t        magic,
+       const struct xfs_buf_ops *ops)
 {
        xfs_buf_t       *bp;
        int             err;
        int             dirty = 0;
+       bool            badcrc = false;
 
        bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, root),
-                     XFS_FSB_TO_BB(mp, 1), 0);
+                     XFS_FSB_TO_BB(mp, 1), 0, ops);
        if (!bp)  {
                do_error(_("can't read btree block %d/%d\n"),
                        XFS_FSB_TO_AGNO(mp, root),
                        XFS_FSB_TO_AGBNO(mp, root));
                return(1);
        }
+
+       /*
+        * only check for bad CRC here - caller will determine if there
+        * is a corruption or not and whether it got corrected and so needs
+        * writing back. CRC errors always imply we need to write the block.
+        */
+       if (bp->b_error == -EFSBADCRC) {
+               do_warn(_("btree block %d/%d is suspect, error %d\n"),
+                       XFS_FSB_TO_AGNO(mp, root),
+                       XFS_FSB_TO_AGBNO(mp, root), bp->b_error);
+               badcrc = true;
+       }
+
        err = (*func)(XFS_BUF_TO_BLOCK(bp), nlevels - 1,
                        type, whichfork, root, ino, tot, nex, blkmapp,
-                       bm_cursor, isroot, check_dups, &dirty);
+                       bm_cursor, isroot, check_dups, &dirty,
+                       magic);
 
        ASSERT(dirty == 0 || (dirty && !no_modify));
 
-       if (dirty && !no_modify)
+       if ((dirty || badcrc) && !no_modify)
                libxfs_writebuf(bp, 0);
        else
                libxfs_putbuf(bp);
@@ -124,38 +159,35 @@ scan_lbtree(
 }
 
 int
-scanfunc_bmap(
+scan_bmapbt(
        struct xfs_btree_block  *block,
        int                     level,
        int                     type,
        int                     whichfork,
-       xfs_dfsbno_t            bno,
+       xfs_fsblock_t           bno,
        xfs_ino_t               ino,
-       xfs_drfsbno_t           *tot,
-       __uint64_t              *nex,
+       xfs_rfsblock_t          *tot,
+       uint64_t                *nex,
        blkmap_t                **blkmapp,
        bmap_cursor_t           *bm_cursor,
        int                     isroot,
        int                     check_dups,
-       int                     *dirty)
+       int                     *dirty,
+       uint64_t                magic)
 {
        int                     i;
        int                     err;
        xfs_bmbt_ptr_t          *pp;
        xfs_bmbt_key_t          *pkey;
        xfs_bmbt_rec_t          *rp;
-       xfs_dfiloff_t           first_key;
-       xfs_dfiloff_t           last_key;
-       char                    *forkname;
+       xfs_fileoff_t           first_key;
+       xfs_fileoff_t           last_key;
+       char                    *forkname = get_forkname(whichfork);
        int                     numrecs;
        xfs_agnumber_t          agno;
        xfs_agblock_t           agbno;
        int                     state;
-
-       if (whichfork == XFS_DATA_FORK)
-               forkname = _("data");
-       else
-               forkname = _("attr");
+       int                     error;
 
        /*
         * unlike the ag freeblock btrees, if anything looks wrong
@@ -164,26 +196,57 @@ scanfunc_bmap(
         * another inode are claiming the same block but that's
         * highly unlikely.
         */
-       if (be32_to_cpu(block->bb_magic) != XFS_BMAP_MAGIC) {
-               do_warn(_("bad magic # %#x in inode %llu (%s fork) bmbt "
-                       "block %llu\n"), be32_to_cpu(block->bb_magic),
-                       ino, forkname, bno);
+       if (be32_to_cpu(block->bb_magic) != magic) {
+               do_warn(
+_("bad magic # %#x in inode %" PRIu64 " (%s fork) bmbt block %" PRIu64 "\n"),
+                       be32_to_cpu(block->bb_magic), ino, forkname, bno);
                return(1);
        }
        if (be16_to_cpu(block->bb_level) != level) {
-               do_warn(_("expected level %d got %d in inode %llu, (%s fork) "
-                       "bmbt block %llu\n"), level,
-                       be16_to_cpu(block->bb_level), ino, forkname, bno);
+               do_warn(
+_("expected level %d got %d in inode %" PRIu64 ", (%s fork) bmbt block %" PRIu64 "\n"),
+                       level, be16_to_cpu(block->bb_level),
+                       ino, forkname, bno);
                return(1);
        }
 
+       if (magic == XFS_BMAP_CRC_MAGIC) {
+               /* verify owner */
+               if (be64_to_cpu(block->bb_u.l.bb_owner) != ino) {
+                       do_warn(
+_("expected owner inode %" PRIu64 ", got %llu, bmbt block %" PRIu64 "\n"),
+                               ino,
+                               (unsigned long long)be64_to_cpu(block->bb_u.l.bb_owner),
+                               bno);
+                       return 1;
+               }
+               /* verify block number */
+               if (be64_to_cpu(block->bb_u.l.bb_blkno) !=
+                   XFS_FSB_TO_DADDR(mp, bno)) {
+                       do_warn(
+_("expected block %" PRIu64 ", got %llu, bmbt block %" PRIu64 "\n"),
+                               XFS_FSB_TO_DADDR(mp, bno),
+                               (unsigned long long)be64_to_cpu(block->bb_u.l.bb_blkno),
+                               bno);
+                       return 1;
+               }
+               /* verify uuid */
+               if (platform_uuid_compare(&block->bb_u.l.bb_uuid,
+                                         &mp->m_sb.sb_meta_uuid) != 0) {
+                       do_warn(
+_("wrong FS UUID, bmbt block %" PRIu64 "\n"),
+                               bno);
+                       return 1;
+               }
+       }
+
        if (check_dups == 0)  {
                /*
                 * check sibling pointers. if bad we have a conflict
                 * between the sibling pointers and the child pointers
                 * in the parent block.  blow out the inode if that happens
                 */
-               if (bm_cursor->level[level].fsbno != NULLDFSBNO)  {
+               if (bm_cursor->level[level].fsbno != NULLFSBLOCK)  {
                        /*
                         * this is not the first block on this level
                         * so the cursor for this level has recorded the
@@ -191,8 +254,8 @@ scanfunc_bmap(
                         */
                        if (bno != bm_cursor->level[level].right_fsbno)  {
                                do_warn(
-_("bad fwd (right) sibling pointer (saw %llu parent block says %llu)\n"
-  "\tin inode %llu (%s fork) bmap btree block %llu\n"),
+_("bad fwd (right) sibling pointer (saw %" PRIu64 " parent block says %" PRIu64 ")\n"
+  "\tin inode %" PRIu64 " (%s fork) bmap btree block %" PRIu64 "\n"),
                                        bm_cursor->level[level].right_fsbno,
                                        bno, ino, forkname,
                                        bm_cursor->level[level].fsbno);
@@ -201,9 +264,10 @@ _("bad fwd (right) sibling pointer (saw %llu parent block says %llu)\n"
                        if (be64_to_cpu(block->bb_u.l.bb_leftsib) !=
                                        bm_cursor->level[level].fsbno)  {
                                do_warn(
-_("bad back (left) sibling pointer (saw %llu parent block says %llu)\n"
-  "\tin inode %llu (%s fork) bmap btree block %llu\n"),
-                                       be64_to_cpu(block->bb_u.l.bb_leftsib),
+_("bad back (left) sibling pointer (saw %llu parent block says %" PRIu64 ")\n"
+  "\tin inode %" PRIu64 " (%s fork) bmap btree block %" PRIu64 "\n"),
+                                      (unsigned long long)
+                                              be64_to_cpu(block->bb_u.l.bb_leftsib),
                                        bm_cursor->level[level].fsbno,
                                        ino, forkname, bno);
                                return(1);
@@ -213,11 +277,12 @@ _("bad back (left) sibling pointer (saw %llu parent block says %llu)\n"
                         * This is the first or only block on this level.
                         * Check that the left sibling pointer is NULL
                         */
-                       if (be64_to_cpu(block->bb_u.l.bb_leftsib) != NULLDFSBNO) {
+                       if (be64_to_cpu(block->bb_u.l.bb_leftsib) != NULLFSBLOCK) {
                                do_warn(
 _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
-  "\tin inode %llu (%s fork) bmap btree block %llu\n"),
-                                       be64_to_cpu(block->bb_u.l.bb_leftsib),
+  "\tin inode %" PRIu64 " (%s fork) bmap btree block %" PRIu64 "\n"),
+                                      (unsigned long long)
+                                              be64_to_cpu(block->bb_u.l.bb_leftsib),
                                        ino, forkname, bno);
                                return(1);
                        }
@@ -235,8 +300,16 @@ _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
                agno = XFS_FSB_TO_AGNO(mp, bno);
                agbno = XFS_FSB_TO_AGBNO(mp, bno);
 
+               pthread_mutex_lock(&ag_locks[agno].lock);
                state = get_bmap(agno, agbno);
                switch (state) {
+               case XR_E_INUSE1:
+                       /*
+                        * block was claimed as in use data by the rmap
+                        * btree, but has not been found in the data extent
+                        * map for the inode. That means this bmbt block hasn't
+                        * yet been claimed as in use, which means -it's ours-
+                        */
                case XR_E_UNKNOWN:
                case XR_E_FREE1:
                case XR_E_FREE:
@@ -254,15 +327,15 @@ _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
                         */
                        set_bmap(agno, agbno, XR_E_MULT);
                        do_warn(
-               _("inode 0x%llx bmap block 0x%llx claimed, state is %d\n"),
-                               ino, (__uint64_t) bno, state);
+_("inode 0x%" PRIx64 "bmap block 0x%" PRIx64 " claimed, state is %d\n"),
+                               ino, bno, state);
                        break;
                case XR_E_MULT:
                case XR_E_INUSE_FS:
                        set_bmap(agno, agbno, XR_E_MULT);
                        do_warn(
-               _("inode 0x%llx bmap block 0x%llx claimed, state is %d\n"),
-                               ino, (__uint64_t) bno, state);
+_("inode 0x%" PRIx64 " bmap block 0x%" PRIx64 " claimed, state is %d\n"),
+                               ino, bno, state);
                        /*
                         * if we made it to here, this is probably a bmap block
                         * that is being used by *another* file as a bmap block
@@ -276,10 +349,11 @@ _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
                case XR_E_BAD_STATE:
                default:
                        do_warn(
-               _("bad state %d, inode 0x%llx bmap block 0x%llx\n"),
-                               state, ino, (__uint64_t) bno);
+_("bad state %d, inode %" PRIu64 " bmap block 0x%" PRIx64 "\n"),
+                               state, ino, bno);
                        break;
                }
+               pthread_mutex_unlock(&ag_locks[agno].lock);
        } else  {
                /*
                 * attribute fork for realtime files is in the regular
@@ -298,11 +372,23 @@ _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
        (*tot)++;
        numrecs = be16_to_cpu(block->bb_numrecs);
 
+       /* Record BMBT blocks in the reverse-mapping data. */
+       if (check_dups && collect_rmaps) {
+               agno = XFS_FSB_TO_AGNO(mp, bno);
+               pthread_mutex_lock(&ag_locks[agno].lock);
+               error = rmap_add_bmbt_rec(mp, ino, whichfork, bno);
+               pthread_mutex_unlock(&ag_locks[agno].lock);
+               if (error)
+                       do_error(
+_("couldn't add inode %"PRIu64" bmbt block %"PRIu64" reverse-mapping data."),
+                               ino, bno);
+       }
+
        if (level == 0) {
                if (numrecs > mp->m_bmap_dmxr[0] || (isroot == 0 && numrecs <
                                                        mp->m_bmap_dmnr[0])) {
                                do_warn(
-       _("inode 0x%llx bad # of bmap records (%u, min - %u, max - %u)\n"),
+_("inode %" PRIu64 " bad # of bmap records (%u, min - %u, max - %u)\n"),
                                        ino, numrecs, mp->m_bmap_dmnr[0],
                                        mp->m_bmap_dmxr[0]);
                        return(1);
@@ -315,24 +401,24 @@ _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n"
                 * we'll bail out and presumably clear the inode.
                 */
                if (check_dups == 0)  {
-                       err = process_bmbt_reclist(mp, rp, numrecs,
-                                       type, ino, tot, blkmapp,
-                                       &first_key, &last_key,
-                                       whichfork);
+                       err = process_bmbt_reclist(mp, rp, &numrecs, type, ino,
+                                                  tot, blkmapp, &first_key,
+                                                  &last_key, whichfork);
                        if (err)
-                               return(1);
+                               return 1;
+
                        /*
                         * check that key ordering is monotonically increasing.
                         * if the last_key value in the cursor is set to
-                        * NULLDFILOFF, then we know this is the first block
+                        * NULLFILEOFF, then we know this is the first block
                         * on the leaf level and we shouldn't check the
                         * last_key value.
                         */
                        if (first_key <= bm_cursor->level[level].last_key &&
                                        bm_cursor->level[level].last_key !=
-                                       NULLDFILOFF)  {
+                                       NULLFILEOFF)  {
                                do_warn(
-_("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
+_("out-of-order bmap key (file offset) in inode %" PRIu64 ", %s fork, fsbno %" PRIu64 "\n"),
                                        ino, forkname, bno);
                                return(1);
                        }
@@ -344,22 +430,23 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
                        bm_cursor->level[level].first_key = first_key;
                        bm_cursor->level[level].last_key = last_key;
 
-                       return(0);
-               } else
-                       return(scan_bmbt_reclist(mp, rp, numrecs,
-                                               type, ino, tot, whichfork));
+                       return 0;
+               } else {
+                       return scan_bmbt_reclist(mp, rp, &numrecs, type, ino,
+                                                tot, whichfork);
+               }
        }
        if (numrecs > mp->m_bmap_dmxr[1] || (isroot == 0 && numrecs <
                                                        mp->m_bmap_dmnr[1])) {
                do_warn(
-       _("inode 0x%llx bad # of bmap records (%u, min - %u, max - %u)\n"),
+_("inode %" PRIu64 " bad # of bmap records (%u, min - %u, max - %u)\n"),
                        ino, numrecs, mp->m_bmap_dmnr[1], mp->m_bmap_dmxr[1]);
                return(1);
        }
        pp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[1]);
        pkey = XFS_BMBT_KEY_ADDR(mp, block, 1);
 
-       last_key = NULLDFILOFF;
+       last_key = NULLFILEOFF;
 
        for (i = 0, err = 0; i < numrecs; i++)  {
                /*
@@ -368,14 +455,16 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
                 * we'll bail out and presumably clear the inode.
                 */
                if (!verify_dfsbno(mp, be64_to_cpu(pp[i])))  {
-                       do_warn(_("bad bmap btree ptr 0x%llx in ino %llu\n"),
-                               be64_to_cpu(pp[i]), ino);
+                       do_warn(
+_("bad bmap btree ptr 0x%llx in ino %" PRIu64 "\n"),
+                              (unsigned long long) be64_to_cpu(pp[i]), ino);
                        return(1);
                }
 
-               err = scan_lbtree(be64_to_cpu(pp[i]), level, scanfunc_bmap,
+               err = scan_lbtree(be64_to_cpu(pp[i]), level, scan_bmapbt,
                                type, whichfork, ino, tot, nex, blkmapp,
-                               bm_cursor, 0, check_dups);
+                               bm_cursor, 0, check_dups, magic,
+                               &xfs_bmbt_buf_ops);
                if (err)
                        return(1);
 
@@ -395,9 +484,10 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
                                        bm_cursor->level[level-1].first_key)  {
                        if (!no_modify)  {
                                do_warn(
-               _("correcting bt key (was %llu, now %llu) in inode %llu\n"
-                 "\t\t%s fork, btree block %llu\n"),
-                                       be64_to_cpu(pkey[i].br_startoff),
+_("correcting bt key (was %llu, now %" PRIu64 ") in inode %" PRIu64 "\n"
+  "\t\t%s fork, btree block %" PRIu64 "\n"),
+                                      (unsigned long long)
+                                              be64_to_cpu(pkey[i].br_startoff),
                                        bm_cursor->level[level-1].first_key,
                                        ino,
                                        forkname, bno);
@@ -406,9 +496,10 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
                                        bm_cursor->level[level-1].first_key);
                        } else  {
                                do_warn(
-       _("bad btree key (is %llu, should be %llu) in inode %llu\n"
-         "\t\t%s fork, btree block %llu\n"),
-                                       be64_to_cpu(pkey[i].br_startoff),
+_("bad btree key (is %llu, should be %" PRIu64 ") in inode %" PRIu64 "\n"
+  "\t\t%s fork, btree block %" PRIu64 "\n"),
+                                      (unsigned long long)
+                                              be64_to_cpu(pkey[i].br_startoff),
                                        bm_cursor->level[level-1].first_key,
                                        ino, forkname, bno);
                        }
@@ -420,11 +511,11 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
         * block's forward sibling pointer is NULL.
         */
        if (check_dups == 0 &&
-                       bm_cursor->level[level].right_fsbno == NULLDFSBNO &&
-                       bm_cursor->level[level - 1].right_fsbno != NULLDFSBNO) {
+                       bm_cursor->level[level].right_fsbno == NULLFSBLOCK &&
+                       bm_cursor->level[level - 1].right_fsbno != NULLFSBLOCK) {
                do_warn(
-       _("bad fwd (right) sibling pointer (saw %llu should be NULLDFSBNO)\n"
-         "\tin inode %llu (%s fork) bmap btree block %llu\n"),
+_("bad fwd (right) sibling pointer (saw %" PRIu64 " should be NULLFSBLOCK)\n"
+  "\tin inode %" PRIu64 " (%s fork) bmap btree block %" PRIu64 "\n"),
                        bm_cursor->level[level - 1].right_fsbno,
                        ino, forkname, bm_cursor->level[level - 1].fsbno);
                return(1);
@@ -443,16 +534,18 @@ _("out-of-order bmap key (file offset) in inode %llu, %s fork, fsbno %llu\n"),
        return(0);
 }
 
-void
-scanfunc_allocbt(
+static void
+scan_allocbt(
        struct xfs_btree_block  *block,
        int                     level,
        xfs_agblock_t           bno,
        xfs_agnumber_t          agno,
        int                     suspect,
        int                     isroot,
-       __uint32_t              magic)
+       uint32_t                magic,
+       void                    *priv)
 {
+       struct aghdr_cnts       *agcnts = priv;
        const char              *name;
        int                     i;
        xfs_alloc_ptr_t         *pp;
@@ -460,10 +553,23 @@ scanfunc_allocbt(
        int                     hdr_errors = 0;
        int                     numrecs;
        int                     state;
+       xfs_extlen_t            lastcount = 0;
+       xfs_agblock_t           lastblock = 0;
 
-       assert(magic == XFS_ABTB_MAGIC || magic == XFS_ABTC_MAGIC);
-
-       name = (magic == XFS_ABTB_MAGIC) ? "bno" : "cnt";
+       switch (magic) {
+       case XFS_ABTB_CRC_MAGIC:
+       case XFS_ABTB_MAGIC:
+               name = "bno";
+               break;
+       case XFS_ABTC_CRC_MAGIC:
+       case XFS_ABTC_MAGIC:
+               name = "cnt";
+               break;
+       default:
+               name = "(unknown)";
+               assert(0);
+               break;
+       }
 
        if (be32_to_cpu(block->bb_magic) != magic) {
                do_warn(_("bad magic # %#x in bt%s block %d/%d\n"),
@@ -472,6 +578,17 @@ scanfunc_allocbt(
                if (suspect)
                        return;
        }
+
+       /*
+        * All freespace btree blocks except the roots are freed for a
+        * fully used filesystem, thus they are counted towards the
+        * free data block counter.
+        */
+       if (!isroot) {
+               agcnts->agfbtreeblks++;
+               agcnts->fdblocks++;
+       }
+
        if (be16_to_cpu(block->bb_level) != level) {
                do_warn(_("expected level %d got %d in bt%s block %d/%d\n"),
                        level, be16_to_cpu(block->bb_level), name, agno, bno);
@@ -484,7 +601,7 @@ scanfunc_allocbt(
         * check for btree blocks multiply claimed
         */
        state = get_bmap(agno, bno);
-       switch (state != XR_E_UNKNOWN)  {
+       if (state != XR_E_UNKNOWN)  {
                set_bmap(agno, bno, XR_E_MULT);
                do_warn(
 _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
@@ -496,7 +613,6 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
        numrecs = be16_to_cpu(block->bb_numrecs);
 
        if (level == 0) {
-
                if (numrecs > mp->m_alloc_mxr[0])  {
                        numrecs = mp->m_alloc_mxr[0];
                        hdr_errors++;
@@ -506,8 +622,14 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                        hdr_errors++;
                }
 
-               if (hdr_errors)
+               if (hdr_errors) {
+                       do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in bt%s block %u/%u\n"),
+                               be16_to_cpu(block->bb_numrecs),
+                               mp->m_alloc_mnr[0], mp->m_alloc_mxr[0],
+                               name, agno, bno);
                        suspect++;
+               }
 
                rp = XFS_ALLOC_REC_ADDR(mp, block, 1);
                for (i = 0; i < numrecs; i++) {
@@ -518,12 +640,41 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                        len = be32_to_cpu(rp[i].ar_blockcount);
                        end = b + len;
 
-                       if (b == 0 || !verify_agbno(mp, agno, b))
-                               continue;
-                       if (len == 0 || len > MAXEXTLEN)
+                       if (b == 0 || !verify_agbno(mp, agno, b)) {
+                               do_warn(
+       _("invalid start block %u in record %u of %s btree block %u/%u\n"),
+                                       b, i, name, agno, bno);
                                continue;
-                       if (!verify_agbno(mp, agno, end - 1))
+                       }
+                       if (len == 0 || !verify_agbno(mp, agno, end - 1)) {
+                               do_warn(
+       _("invalid length %u in record %u of %s btree block %u/%u\n"),
+                                       len, i, name, agno, bno);
                                continue;
+                       }
+
+                       if (magic == XFS_ABTB_MAGIC ||
+                           magic == XFS_ABTB_CRC_MAGIC) {
+                               if (b <= lastblock) {
+                                       do_warn(_(
+       "out-of-order bno btree record %d (%u %u) block %u/%u\n"),
+                                               i, b, len, agno, bno);
+                               } else {
+                                       lastblock = b;
+                               }
+                       } else {
+                               agcnts->fdblocks += len;
+                               agcnts->agffreeblks += len;
+                               if (len > agcnts->agflongest)
+                                       agcnts->agflongest = len;
+                               if (len < lastcount) {
+                                       do_warn(_(
+       "out-of-order cnt btree record %d (%u %u) block %u/%u\n"),
+                                               i, b, len, agno, bno);
+                               } else {
+                                       lastcount = len;
+                               }
+                       }
 
                        for ( ; b < end; b += blen)  {
                                state = get_bmap_ext(agno, b, end, &blen);
@@ -536,11 +687,13 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                                         * no warning messages -- we'll catch
                                         * FREE1 blocks later
                                         */
-                                       if (magic == XFS_ABTC_MAGIC) {
+                                       if (magic == XFS_ABTC_MAGIC ||
+                                           magic == XFS_ABTC_CRC_MAGIC) {
                                                set_bmap_ext(agno, b, blen,
                                                             XR_E_FREE);
                                                break;
                                        }
+                                       /* fall through */
                                default:
                                        do_warn(
        _("block (%d,%d-%d) multiply claimed by %s space tree, state - %d\n"),
@@ -571,18 +724,21 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
         * don't pass bogus tree flag down further if this block
         * looked ok.  bail out if two levels in a row look bad.
         */
-
-       if (suspect && !hdr_errors)
-               suspect = 0;
-
        if (hdr_errors)  {
+               do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in bt%s block %u/%u\n"),
+                       be16_to_cpu(block->bb_numrecs),
+                       mp->m_alloc_mnr[1], mp->m_alloc_mxr[1],
+                       name, agno, bno);
                if (suspect)
                        return;
-               else suspect++;
+               suspect++;
+       } else if (suspect) {
+               suspect = 0;
        }
 
        for (i = 0; i < numrecs; i++)  {
-               xfs_agblock_t           bno = be32_to_cpu(pp[i]);
+               xfs_agblock_t           agbno = be32_to_cpu(pp[i]);
 
                /*
                 * XXX - put sibling detection right here.
@@ -593,246 +749,1214 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                 * pointer mismatch, try and extract as much data
                 * as possible.
                 */
-               if (bno != 0 && verify_agbno(mp, agno, bno)) {
-                       scan_sbtree(bno, level, agno, suspect,
-                                   (magic == XFS_ABTB_MAGIC) ?
-                                    scanfunc_bno : scanfunc_cnt, 0);
+               if (agbno != 0 && verify_agbno(mp, agno, agbno)) {
+                       switch (magic) {
+                       case XFS_ABTB_CRC_MAGIC:
+                       case XFS_ABTB_MAGIC:
+                               scan_sbtree(agbno, level, agno, suspect,
+                                           scan_allocbt, 0, magic, priv,
+                                           &xfs_allocbt_buf_ops);
+                               break;
+                       case XFS_ABTC_CRC_MAGIC:
+                       case XFS_ABTC_MAGIC:
+                               scan_sbtree(agbno, level, agno, suspect,
+                                           scan_allocbt, 0, magic, priv,
+                                           &xfs_allocbt_buf_ops);
+                               break;
+                       }
                }
        }
 }
 
-void
-scanfunc_bno(
-       struct xfs_btree_block  *block,
-       int                     level,
-       xfs_agblock_t           bno,
-       xfs_agnumber_t          agno,
-       int                     suspect,
-       int                     isroot)
+static bool
+ino_issparse(
+       struct xfs_inobt_rec    *rp,
+       int                     offset)
 {
-       return scanfunc_allocbt(block, level, bno, agno,
-                               suspect, isroot, XFS_ABTB_MAGIC);
-}
+       if (!xfs_sb_version_hassparseinodes(&mp->m_sb))
+               return false;
 
-void
-scanfunc_cnt(
-       struct xfs_btree_block  *block,
-       int                     level,
-       xfs_agblock_t           bno,
-       xfs_agnumber_t          agno,
-       int                     suspect,
-       int                     isroot
-       )
-{
-       return scanfunc_allocbt(block, level, bno, agno,
-                               suspect, isroot, XFS_ABTC_MAGIC);
+       return xfs_inobt_is_sparse_disk(rp, offset);
 }
 
-static int
-scan_single_ino_chunk(
+/* See if the rmapbt owners agree with our observations. */
+static void
+process_rmap_rec(
+       struct xfs_mount        *mp,
        xfs_agnumber_t          agno,
-       xfs_inobt_rec_t         *rp,
-       int                     suspect)
+       xfs_agblock_t           b,
+       xfs_agblock_t           end,
+       xfs_extlen_t            blen,
+       int64_t                 owner,
+       int                     state,
+       const char              *name)
 {
-       xfs_ino_t               lino;
-       xfs_agino_t             ino;
-       xfs_agblock_t           agbno;
-       int                     j;
-       int                     nfree;
-       int                     off;
-       int                     state;
-       ino_tree_node_t         *ino_rec, *first_rec, *last_rec;
-
-       ino = be32_to_cpu(rp->ir_startino);
-       off = XFS_AGINO_TO_OFFSET(mp, ino);
-       agbno = XFS_AGINO_TO_AGBNO(mp, ino);
-       lino = XFS_AGINO_TO_INO(mp, agno, ino);
-
-       /*
-        * on multi-block block chunks, all chunks start
-        * at the beginning of the block.  with multi-chunk
-        * blocks, all chunks must start on 64-inode boundaries
-        * since each block can hold N complete chunks. if
-        * fs has aligned inodes, all chunks must start
-        * at a fs_ino_alignment*N'th agbno.  skip recs
-        * with badly aligned starting inodes.
-        */
-       if (ino == 0 ||
-           (inodes_per_block <= XFS_INODES_PER_CHUNK && off !=  0) ||
-           (inodes_per_block > XFS_INODES_PER_CHUNK &&
-            off % XFS_INODES_PER_CHUNK != 0) ||
-           (fs_aligned_inodes && agbno % fs_ino_alignment != 0))  {
+       switch (state) {
+       case XR_E_UNKNOWN:
+               switch (owner) {
+               case XFS_RMAP_OWN_FS:
+               case XFS_RMAP_OWN_LOG:
+                       set_bmap_ext(agno, b, blen, XR_E_INUSE_FS1);
+                       break;
+               case XFS_RMAP_OWN_AG:
+               case XFS_RMAP_OWN_INOBT:
+                       set_bmap_ext(agno, b, blen, XR_E_FS_MAP1);
+                       break;
+               case XFS_RMAP_OWN_INODES:
+                       set_bmap_ext(agno, b, blen, XR_E_INO1);
+                       break;
+               case XFS_RMAP_OWN_REFC:
+                       set_bmap_ext(agno, b, blen, XR_E_REFC);
+                       break;
+               case XFS_RMAP_OWN_COW:
+                       set_bmap_ext(agno, b, blen, XR_E_COW);
+                       break;
+               case XFS_RMAP_OWN_NULL:
+                       /* still unknown */
+                       break;
+               default:
+                       /* file data */
+                       set_bmap_ext(agno, b, blen, XR_E_INUSE1);
+                       break;
+               }
+               break;
+       case XR_E_INUSE_FS:
+               if (owner == XFS_RMAP_OWN_FS ||
+                   owner == XFS_RMAP_OWN_LOG)
+                       break;
                do_warn(
-       _("badly aligned inode rec (starting inode = %llu)\n"),
-                       lino);
-               suspect++;
-       }
-
-       /*
-        * verify numeric validity of inode chunk first
-        * before inserting into a tree.  don't have to
-        * worry about the overflow case because the
-        * starting ino number of a chunk can only get
-        * within 255 inodes of max (NULLAGINO).  if it
-        * gets closer, the agino number will be illegal
-        * as the agbno will be too large.
-        */
-       if (verify_aginum(mp, agno, ino))  {
+_("Static meta block (%d,%d-%d) mismatch in %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
+       case XR_E_FS_MAP:
+               if (owner == XFS_RMAP_OWN_AG ||
+                   owner == XFS_RMAP_OWN_INOBT)
+                       break;
                do_warn(
-_("bad starting inode # (%llu (0x%x 0x%x)) in ino rec, skipping rec\n"),
-                       lino, agno, ino);
-               return ++suspect;
-       }
-
-       if (verify_aginum(mp, agno,
-                       ino + XFS_INODES_PER_CHUNK - 1))  {
+_("AG meta block (%d,%d-%d) mismatch in %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
+       case XR_E_INO:
+               if (owner == XFS_RMAP_OWN_INODES)
+                       break;
                do_warn(
-_("bad ending inode # (%llu (0x%x 0x%x)) in ino rec, skipping rec\n"),
-                       lino + XFS_INODES_PER_CHUNK - 1,
-                       agno, ino + XFS_INODES_PER_CHUNK - 1);
-               return ++suspect;
-       }
-
-       /*
-        * set state of each block containing inodes
-        */
-       if (off == 0 && !suspect)  {
-               for (j = 0;
-                    j < XFS_INODES_PER_CHUNK;
-                    j += mp->m_sb.sb_inopblock)  {
-                       agbno = XFS_AGINO_TO_AGBNO(mp, ino + j);
-
-                       state = get_bmap(agno, agbno);
-                       if (state == XR_E_UNKNOWN)  {
-                               set_bmap(agno, agbno, XR_E_INO);
-                       } else if (state == XR_E_INUSE_FS && agno == 0 &&
-                                  ino + j >= first_prealloc_ino &&
-                                  ino + j < last_prealloc_ino)  {
-                               set_bmap(agno, agbno, XR_E_INO);
-                       } else  {
-                               do_warn(
-_("inode chunk claims used block, inobt block - agno %d, bno %d, inopb %d\n"),
-                                       agno, agbno, mp->m_sb.sb_inopblock);
-                               /*
-                                * XXX - maybe should mark
-                                * block a duplicate
-                                */
-                               return ++suspect;
-                       }
-               }
-       }
-
-       /*
-        * ensure only one avl entry per chunk
-        */
-       find_inode_rec_range(agno, ino, ino + XFS_INODES_PER_CHUNK,
-                            &first_rec, &last_rec);
-       if (first_rec != NULL)  {
+_("inode block (%d,%d-%d) mismatch in %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
+       case XR_E_REFC:
+               if (owner == XFS_RMAP_OWN_REFC)
+                       break;
+               do_warn(
+_("AG refcount block (%d,%d-%d) mismatch in %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
+       case XR_E_INUSE:
+               if (owner >= 0 &&
+                   owner < mp->m_sb.sb_dblocks)
+                       break;
+               do_warn(
+_("in use block (%d,%d-%d) mismatch in %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
+       case XR_E_FREE1:
+       case XR_E_FREE:
                /*
-                * this chunk overlaps with one (or more)
-                * already in the tree
+                * May be on the AGFL. If not, they'll
+                * be caught later.
                 */
-               do_warn(
-_("inode rec for ino %llu (%d/%d) overlaps existing rec (start %d/%d)\n"),
-                       lino, agno, ino, agno, first_rec->ino_startnum);
-               suspect++;
-
+               break;
+       case XR_E_INUSE1:
                /*
-                * if the 2 chunks start at the same place,
-                * then we don't have to put this one
-                * in the uncertain list.  go to the next one.
+                * multiple inode owners are ok with
+                * reflink enabled
                 */
-               if (first_rec->ino_startnum == ino)
-                       return suspect;
+               if (xfs_sb_version_hasreflink(&mp->m_sb) &&
+                   !XFS_RMAP_NON_INODE_OWNER(owner))
+                       break;
+               /* fall through */
+       default:
+               do_warn(
+_("unknown block (%d,%d-%d) mismatch on %s tree, state - %d,%" PRIx64 "\n"),
+                       agno, b, b + blen - 1,
+                       name, state, owner);
+               break;
        }
+}
 
-       nfree = 0;
-
-       /*
-        * now mark all the inodes as existing and free or used.
-        * if the tree is suspect, put them into the uncertain
-        * inode tree.
-        */
-       if (!suspect)  {
-               if (XFS_INOBT_IS_FREE_DISK(rp, 0)) {
-                       nfree++;
-                       ino_rec = set_inode_free_alloc(agno, ino);
-               } else  {
-                       ino_rec = set_inode_used_alloc(agno, ino);
-               }
-               for (j = 1; j < XFS_INODES_PER_CHUNK; j++) {
-                       if (XFS_INOBT_IS_FREE_DISK(rp, j)) {
-                               nfree++;
-                               set_inode_free(ino_rec, j);
-                       } else  {
-                               set_inode_used(ino_rec, j);
-                       }
-               }
-       } else  {
-               for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
-                       if (XFS_INOBT_IS_FREE_DISK(rp, j)) {
-                               nfree++;
-                               add_aginode_uncertain(agno, ino + j, 1);
-                       } else  {
-                               add_aginode_uncertain(agno, ino + j, 0);
-                       }
-               }
-       }
+struct rmap_priv {
+       struct aghdr_cnts       *agcnts;
+       struct xfs_rmap_irec    high_key;
+       struct xfs_rmap_irec    last_rec;
+       xfs_agblock_t           nr_blocks;
+};
+
+static bool
+rmap_in_order(
+       xfs_agblock_t   b,
+       xfs_agblock_t   lastblock,
+       uint64_t        owner,
+       uint64_t        lastowner,
+       uint64_t        offset,
+       uint64_t        lastoffset)
+{
+       if (b > lastblock)
+               return true;
+       else if (b < lastblock)
+               return false;
 
-       if (nfree != be32_to_cpu(rp->ir_freecount)) {
-               do_warn(_("ir_freecount/free mismatch, inode "
-                       "chunk %d/%d, freecount %d nfree %d\n"),
-                       agno, ino, be32_to_cpu(rp->ir_freecount), nfree);
-       }
+       if (owner > lastowner)
+               return true;
+       else if (owner < lastowner)
+               return false;
 
-       return suspect;
+       return offset > lastoffset;
 }
 
-
-/*
- * this one walks the inode btrees sucking the info there into
- * the incore avl tree.  We try and rescue corrupted btree records
- * to minimize our chances of losing inodes.  Inode info from potentially
- * corrupt sources could be bogus so rather than put the info straight
- * into the tree, instead we put it on a list and try and verify the
- * info in the next phase by examining what's on disk.  At that point,
- * we'll be able to figure out what's what and stick the corrected info
- * into the tree.  We do bail out at some point and give up on a subtree
- * so as to avoid walking randomly all over the ag.
- *
- * Note that it's also ok if the free/inuse info wrong, we can correct
- * that when we examine the on-disk inode.  The important thing is to
- * get the start and alignment of the inode chunks right.  Those chunks
- * that we aren't sure about go into the uncertain list.
- */
-void
-scanfunc_ino(
+static void
+scan_rmapbt(
        struct xfs_btree_block  *block,
        int                     level,
        xfs_agblock_t           bno,
        xfs_agnumber_t          agno,
        int                     suspect,
-       int                     isroot
-       )
+       int                     isroot,
+       uint32_t                magic,
+       void                    *priv)
 {
+       const char              *name = "rmap";
        int                     i;
+       xfs_rmap_ptr_t          *pp;
+       struct xfs_rmap_rec     *rp;
+       struct rmap_priv        *rmap_priv = priv;
+       int                     hdr_errors = 0;
        int                     numrecs;
        int                     state;
-       xfs_inobt_ptr_t         *pp;
-       xfs_inobt_rec_t         *rp;
-       int                     hdr_errors;
-
-       hdr_errors = 0;
+       xfs_agblock_t           lastblock = 0;
+       uint64_t                lastowner = 0;
+       uint64_t                lastoffset = 0;
+       struct xfs_rmap_key     *kp;
+       struct xfs_rmap_irec    key = {0};
+
+       if (magic != XFS_RMAP_CRC_MAGIC) {
+               name = "(unknown)";
+               hdr_errors++;
+               suspect++;
+               goto out;
+       }
 
-       if (be32_to_cpu(block->bb_magic) != XFS_IBT_MAGIC) {
-               do_warn(_("bad magic # %#x in inobt block %d/%d\n"),
-                       be32_to_cpu(block->bb_magic), agno, bno);
+       if (be32_to_cpu(block->bb_magic) != magic) {
+               do_warn(_("bad magic # %#x in bt%s block %d/%d\n"),
+                       be32_to_cpu(block->bb_magic), name, agno, bno);
                hdr_errors++;
-               bad_ino_btree = 1;
                if (suspect)
-                       return;
+                       goto out;
        }
-       if (be16_to_cpu(block->bb_level) != level) {
+
+       /*
+        * All RMAP btree blocks except the roots are freed for a
+        * fully empty filesystem, thus they are counted towards the
+        * free data block counter.
+        */
+       if (!isroot) {
+               rmap_priv->agcnts->agfbtreeblks++;
+               rmap_priv->agcnts->fdblocks++;
+       }
+       rmap_priv->nr_blocks++;
+
+       if (be16_to_cpu(block->bb_level) != level) {
+               do_warn(_("expected level %d got %d in bt%s block %d/%d\n"),
+                       level, be16_to_cpu(block->bb_level), name, agno, bno);
+               hdr_errors++;
+               if (suspect)
+                       goto out;
+       }
+
+       /* check for btree blocks multiply claimed */
+       state = get_bmap(agno, bno);
+       if (!(state == XR_E_UNKNOWN || state == XR_E_FS_MAP1))  {
+               set_bmap(agno, bno, XR_E_MULT);
+               do_warn(
+_("%s rmap btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
+                               name, state, agno, bno, suspect);
+               goto out;
+       }
+       set_bmap(agno, bno, XR_E_FS_MAP);
+
+       numrecs = be16_to_cpu(block->bb_numrecs);
+       if (level == 0) {
+               if (numrecs > mp->m_rmap_mxr[0])  {
+                       numrecs = mp->m_rmap_mxr[0];
+                       hdr_errors++;
+               }
+               if (isroot == 0 && numrecs < mp->m_rmap_mnr[0])  {
+                       numrecs = mp->m_rmap_mnr[0];
+                       hdr_errors++;
+               }
+
+               if (hdr_errors) {
+                       do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in bt%s block %u/%u\n"),
+                               be16_to_cpu(block->bb_numrecs),
+                               mp->m_rmap_mnr[0], mp->m_rmap_mxr[0],
+                               name, agno, bno);
+                       suspect++;
+               }
+
+               rp = XFS_RMAP_REC_ADDR(block, 1);
+               for (i = 0; i < numrecs; i++) {
+                       xfs_agblock_t           b, end;
+                       xfs_extlen_t            len, blen;
+                       int64_t                 owner, offset;
+
+                       b = be32_to_cpu(rp[i].rm_startblock);
+                       len = be32_to_cpu(rp[i].rm_blockcount);
+                       owner = be64_to_cpu(rp[i].rm_owner);
+                       offset = be64_to_cpu(rp[i].rm_offset);
+
+                       key.rm_flags = 0;
+                       key.rm_startblock = b;
+                       key.rm_blockcount = len;
+                       key.rm_owner = owner;
+                       if (libxfs_rmap_irec_offset_unpack(offset, &key)) {
+                               /* Look for impossible flags. */
+                               do_warn(
+       _("invalid flags in record %u of %s btree block %u/%u\n"),
+                                       i, name, agno, bno);
+                               continue;
+                       }
+
+                       end = key.rm_startblock + key.rm_blockcount;
+
+                       /* Make sure agbno & len make sense. */
+                       if (!verify_agbno(mp, agno, b)) {
+                               do_warn(
+       _("invalid start block %u in record %u of %s btree block %u/%u\n"),
+                                       b, i, name, agno, bno);
+                               continue;
+                       }
+                       if (len == 0 || !verify_agbno(mp, agno, end - 1)) {
+                               do_warn(
+       _("invalid length %u in record %u of %s btree block %u/%u\n"),
+                                       len, i, name, agno, bno);
+                               continue;
+                       }
+
+                       /* Look for impossible owners. */
+                       if (!((owner > XFS_RMAP_OWN_MIN &&
+                              owner <= XFS_RMAP_OWN_FS) ||
+                             (XFS_INO_TO_AGNO(mp, owner) < mp->m_sb.sb_agcount &&
+                              XFS_AGINO_TO_AGBNO(mp,
+                                       XFS_INO_TO_AGINO(mp, owner)) <
+                                       mp->m_sb.sb_agblocks)))
+                               do_warn(
+       _("invalid owner in rmap btree record %d (%"PRId64" %u) block %u/%u\n"),
+                                               i, owner, len, agno, bno);
+
+                       /* Look for impossible record field combinations. */
+                       if (XFS_RMAP_NON_INODE_OWNER(key.rm_owner)) {
+                               if (key.rm_flags)
+                                       do_warn(
+       _("record %d of block (%u/%u) in %s btree cannot have non-inode owner with flags\n"),
+                                               i, agno, bno, name);
+                               if (key.rm_offset)
+                                       do_warn(
+       _("record %d of block (%u/%u) in %s btree cannot have non-inode owner with offset\n"),
+                                               i, agno, bno, name);
+                       }
+
+                       /* Check for out of order records. */
+                       if (i == 0) {
+advance:
+                               lastblock = b;
+                               lastowner = owner;
+                               lastoffset = offset;
+                       } else {
+                               bool bad;
+
+                               if (xfs_sb_version_hasreflink(&mp->m_sb))
+                                       bad = !rmap_in_order(b, lastblock,
+                                                       owner, lastowner,
+                                                       offset, lastoffset);
+                               else
+                                       bad = b <= lastblock;
+                               if (bad)
+                                       do_warn(
+       _("out-of-order rmap btree record %d (%u %"PRId64" %"PRIx64" %u) block %u/%u\n"),
+                                       i, b, owner, offset, len, agno, bno);
+                               else
+                                       goto advance;
+                       }
+
+                       /* Is this mergeable with the previous record? */
+                       if (rmaps_are_mergeable(&rmap_priv->last_rec, &key)) {
+                               do_warn(
+       _("record %d in block (%u/%u) of %s tree should be merged with previous record\n"),
+                                       i, agno, bno, name);
+                               rmap_priv->last_rec.rm_blockcount +=
+                                               key.rm_blockcount;
+                       } else
+                               rmap_priv->last_rec = key;
+
+                       /* Check that we don't go past the high key. */
+                       key.rm_startblock += key.rm_blockcount - 1;
+                       if (!XFS_RMAP_NON_INODE_OWNER(key.rm_owner) &&
+                           !(key.rm_flags & XFS_RMAP_BMBT_BLOCK))
+                               key.rm_offset += key.rm_blockcount - 1;
+                       key.rm_blockcount = 0;
+                       if (rmap_diffkeys(&key, &rmap_priv->high_key) > 0) {
+                               do_warn(
+       _("record %d greater than high key of block (%u/%u) in %s tree\n"),
+                                       i, agno, bno, name);
+                       }
+
+                       /* Check for block owner collisions. */
+                       for ( ; b < end; b += blen)  {
+                               state = get_bmap_ext(agno, b, end, &blen);
+                               process_rmap_rec(mp, agno, b, end, blen, owner,
+                                               state, name);
+                       }
+               }
+               goto out;
+       }
+
+       /*
+        * interior record
+        */
+       pp = XFS_RMAP_PTR_ADDR(block, 1, mp->m_rmap_mxr[1]);
+
+       if (numrecs > mp->m_rmap_mxr[1])  {
+               numrecs = mp->m_rmap_mxr[1];
+               hdr_errors++;
+       }
+       if (isroot == 0 && numrecs < mp->m_rmap_mnr[1])  {
+               numrecs = mp->m_rmap_mnr[1];
+               hdr_errors++;
+       }
+
+       /*
+        * don't pass bogus tree flag down further if this block
+        * looked ok.  bail out if two levels in a row look bad.
+        */
+       if (hdr_errors)  {
+               do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in bt%s block %u/%u\n"),
+                       be16_to_cpu(block->bb_numrecs),
+                       mp->m_rmap_mnr[1], mp->m_rmap_mxr[1],
+                       name, agno, bno);
+               if (suspect)
+                       goto out;
+               suspect++;
+       } else if (suspect) {
+               suspect = 0;
+       }
+
+       /* check the node's high keys */
+       for (i = 0; !isroot && i < numrecs; i++) {
+               kp = XFS_RMAP_HIGH_KEY_ADDR(block, i + 1);
+
+               key.rm_flags = 0;
+               key.rm_startblock = be32_to_cpu(kp->rm_startblock);
+               key.rm_owner = be64_to_cpu(kp->rm_owner);
+               if (libxfs_rmap_irec_offset_unpack(be64_to_cpu(kp->rm_offset),
+                               &key)) {
+                       /* Look for impossible flags. */
+                       do_warn(
+       _("invalid flags in key %u of %s btree block %u/%u\n"),
+                               i, name, agno, bno);
+                       continue;
+               }
+               if (rmap_diffkeys(&key, &rmap_priv->high_key) > 0)
+                       do_warn(
+       _("key %d greater than high key of block (%u/%u) in %s tree\n"),
+                               i, agno, bno, name);
+       }
+
+       for (i = 0; i < numrecs; i++)  {
+               xfs_agblock_t           agbno = be32_to_cpu(pp[i]);
+
+               /*
+                * XXX - put sibling detection right here.
+                * we know our sibling chain is good.  So as we go,
+                * we check the entry before and after each entry.
+                * If either of the entries references a different block,
+                * check the sibling pointer.  If there's a sibling
+                * pointer mismatch, try and extract as much data
+                * as possible.
+                */
+               kp = XFS_RMAP_HIGH_KEY_ADDR(block, i + 1);
+               rmap_priv->high_key.rm_flags = 0;
+               rmap_priv->high_key.rm_startblock =
+                               be32_to_cpu(kp->rm_startblock);
+               rmap_priv->high_key.rm_owner =
+                               be64_to_cpu(kp->rm_owner);
+               if (libxfs_rmap_irec_offset_unpack(be64_to_cpu(kp->rm_offset),
+                               &rmap_priv->high_key)) {
+                       /* Look for impossible flags. */
+                       do_warn(
+       _("invalid flags in high key %u of %s btree block %u/%u\n"),
+                               i, name, agno, agbno);
+                       continue;
+               }
+
+               if (agbno != 0 && verify_agbno(mp, agno, agbno)) {
+                       scan_sbtree(agbno, level, agno, suspect, scan_rmapbt, 0,
+                                   magic, priv, &xfs_rmapbt_buf_ops);
+               }
+       }
+
+out:
+       if (suspect)
+               rmap_avoid_check();
+}
+
+struct refc_priv {
+       struct xfs_refcount_irec        last_rec;
+       xfs_agblock_t                   nr_blocks;
+};
+
+
+static void
+scan_refcbt(
+       struct xfs_btree_block  *block,
+       int                     level,
+       xfs_agblock_t           bno,
+       xfs_agnumber_t          agno,
+       int                     suspect,
+       int                     isroot,
+       uint32_t                magic,
+       void                    *priv)
+{
+       const char              *name = "refcount";
+       int                     i;
+       xfs_refcount_ptr_t      *pp;
+       struct xfs_refcount_rec *rp;
+       int                     hdr_errors = 0;
+       int                     numrecs;
+       int                     state;
+       xfs_agblock_t           lastblock = 0;
+       struct refc_priv        *refc_priv = priv;
+
+       if (magic != XFS_REFC_CRC_MAGIC) {
+               name = "(unknown)";
+               hdr_errors++;
+               suspect++;
+               goto out;
+       }
+
+       if (be32_to_cpu(block->bb_magic) != magic) {
+               do_warn(_("bad magic # %#x in %s btree block %d/%d\n"),
+                       be32_to_cpu(block->bb_magic), name, agno, bno);
+               hdr_errors++;
+               if (suspect)
+                       goto out;
+       }
+
+       if (be16_to_cpu(block->bb_level) != level) {
+               do_warn(_("expected level %d got %d in %s btree block %d/%d\n"),
+                       level, be16_to_cpu(block->bb_level), name, agno, bno);
+               hdr_errors++;
+               if (suspect)
+                       goto out;
+       }
+
+       refc_priv->nr_blocks++;
+
+       /* check for btree blocks multiply claimed */
+       state = get_bmap(agno, bno);
+       if (!(state == XR_E_UNKNOWN || state == XR_E_REFC))  {
+               set_bmap(agno, bno, XR_E_MULT);
+               do_warn(
+_("%s btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
+                               name, state, agno, bno, suspect);
+               goto out;
+       }
+       set_bmap(agno, bno, XR_E_FS_MAP);
+
+       numrecs = be16_to_cpu(block->bb_numrecs);
+       if (level == 0) {
+               if (numrecs > mp->m_refc_mxr[0])  {
+                       numrecs = mp->m_refc_mxr[0];
+                       hdr_errors++;
+               }
+               if (isroot == 0 && numrecs < mp->m_refc_mnr[0])  {
+                       numrecs = mp->m_refc_mnr[0];
+                       hdr_errors++;
+               }
+
+               if (hdr_errors) {
+                       do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in %s btree block %u/%u\n"),
+                               be16_to_cpu(block->bb_numrecs),
+                               mp->m_refc_mnr[0], mp->m_refc_mxr[0],
+                               name, agno, bno);
+                       suspect++;
+               }
+
+               rp = XFS_REFCOUNT_REC_ADDR(block, 1);
+               for (i = 0; i < numrecs; i++) {
+                       xfs_agblock_t           b, agb, end;
+                       xfs_extlen_t            len;
+                       xfs_nlink_t             nr;
+
+                       b = agb = be32_to_cpu(rp[i].rc_startblock);
+                       len = be32_to_cpu(rp[i].rc_blockcount);
+                       nr = be32_to_cpu(rp[i].rc_refcount);
+                       if (b >= XFS_REFC_COW_START && nr != 1)
+                               do_warn(
+_("leftover CoW extent has incorrect refcount in record %u of %s btree block %u/%u\n"),
+                                       i, name, agno, bno);
+                       if (nr == 1) {
+                               if (agb < XFS_REFC_COW_START)
+                                       do_warn(
+_("leftover CoW extent has invalid startblock in record %u of %s btree block %u/%u\n"),
+                                               i, name, agno, bno);
+                               agb -= XFS_REFC_COW_START;
+                       }
+                       end = agb + len;
+
+                       if (!verify_agbno(mp, agno, agb)) {
+                               do_warn(
+       _("invalid start block %u in record %u of %s btree block %u/%u\n"),
+                                       b, i, name, agno, bno);
+                               continue;
+                       }
+                       if (len == 0 || !verify_agbno(mp, agno, end - 1)) {
+                               do_warn(
+       _("invalid length %u in record %u of %s btree block %u/%u\n"),
+                                       len, i, name, agno, bno);
+                               continue;
+                       }
+
+                       if (nr == 1) {
+                               xfs_agblock_t   c;
+                               xfs_extlen_t    cnr;
+
+                               for (c = agb; c < end; c += cnr) {
+                                       state = get_bmap_ext(agno, c, end, &cnr);
+                                       switch (state) {
+                                       case XR_E_UNKNOWN:
+                                       case XR_E_COW:
+                                               do_warn(
+_("leftover CoW extent (%u/%u) len %u\n"),
+                                               agno, c, cnr);
+                                               set_bmap_ext(agno, c, cnr, XR_E_FREE);
+                                               break;
+                                       default:
+                                               do_warn(
+_("extent (%u/%u) len %u claimed, state is %d\n"),
+                                               agno, c, cnr, state);
+                                               break;
+                                       }
+                               }
+                       } else if (nr < 2 || nr > MAXREFCOUNT) {
+                               do_warn(
+       _("invalid reference count %u in record %u of %s btree block %u/%u\n"),
+                                       nr, i, name, agno, bno);
+                               continue;
+                       }
+
+                       if (b && b <= lastblock) {
+                               do_warn(_(
+       "out-of-order %s btree record %d (%u %u) block %u/%u\n"),
+                                       name, i, b, len, agno, bno);
+                       } else {
+                               lastblock = b;
+                       }
+
+                       /* Is this record mergeable with the last one? */
+                       if (refc_priv->last_rec.rc_startblock +
+                           refc_priv->last_rec.rc_blockcount == b &&
+                           refc_priv->last_rec.rc_refcount == nr) {
+                               do_warn(
+       _("record %d in block (%u/%u) of %s tree should be merged with previous record\n"),
+                                       i, agno, bno, name);
+                               refc_priv->last_rec.rc_blockcount += len;
+                       } else {
+                               refc_priv->last_rec.rc_startblock = b;
+                               refc_priv->last_rec.rc_blockcount = len;
+                               refc_priv->last_rec.rc_refcount = nr;
+                       }
+
+                       /* XXX: probably want to mark the reflinked areas? */
+               }
+               goto out;
+       }
+
+       /*
+        * interior record
+        */
+       pp = XFS_REFCOUNT_PTR_ADDR(block, 1, mp->m_refc_mxr[1]);
+
+       if (numrecs > mp->m_refc_mxr[1])  {
+               numrecs = mp->m_refc_mxr[1];
+               hdr_errors++;
+       }
+       if (isroot == 0 && numrecs < mp->m_refc_mnr[1])  {
+               numrecs = mp->m_refc_mnr[1];
+               hdr_errors++;
+       }
+
+       /*
+        * don't pass bogus tree flag down further if this block
+        * looked ok.  bail out if two levels in a row look bad.
+        */
+       if (hdr_errors)  {
+               do_warn(
+       _("bad btree nrecs (%u, min=%u, max=%u) in %s btree block %u/%u\n"),
+                       be16_to_cpu(block->bb_numrecs),
+                       mp->m_refc_mnr[1], mp->m_refc_mxr[1],
+                       name, agno, bno);
+               if (suspect)
+                       goto out;
+               suspect++;
+       } else if (suspect) {
+               suspect = 0;
+       }
+
+       for (i = 0; i < numrecs; i++)  {
+               xfs_agblock_t           agbno = be32_to_cpu(pp[i]);
+
+               if (agbno != 0 && verify_agbno(mp, agno, agbno)) {
+                       scan_sbtree(agbno, level, agno, suspect, scan_refcbt, 0,
+                                   magic, priv, &xfs_refcountbt_buf_ops);
+               }
+       }
+out:
+       if (suspect)
+               refcount_avoid_check();
+       return;
+}
+
+/*
+ * The following helpers are to help process and validate individual on-disk
+ * inode btree records. We have two possible inode btrees with slightly
+ * different semantics. Many of the validations and actions are equivalent, such
+ * as record alignment constraints, etc. Other validations differ, such as the
+ * fact that the inode chunk block allocation state is set by the content of the
+ * core inobt and verified by the content of the finobt.
+ *
+ * The following structures are used to facilitate common validation routines
+ * where the only difference between validation of the inobt or finobt might be
+ * the error messages that results in the event of failure.
+ */
+
+enum inobt_type {
+       INOBT,
+       FINOBT
+};
+static const char *inobt_names[] = {
+       "inobt",
+       "finobt"
+};
+
+static int
+verify_single_ino_chunk_align(
+       xfs_agnumber_t          agno,
+       enum inobt_type         type,
+       struct xfs_inobt_rec    *rp,
+       int                     suspect,
+       bool                    *skip)
+{
+       const char              *inobt_name = inobt_names[type];
+       xfs_ino_t               lino;
+       xfs_agino_t             ino;
+       xfs_agblock_t           agbno;
+       int                     off;
+
+       *skip = false;
+       ino = be32_to_cpu(rp->ir_startino);
+       off = XFS_AGINO_TO_OFFSET(mp, ino);
+       agbno = XFS_AGINO_TO_AGBNO(mp, ino);
+       lino = XFS_AGINO_TO_INO(mp, agno, ino);
+
+       /*
+        * on multi-block block chunks, all chunks start at the beginning of the
+        * block. with multi-chunk blocks, all chunks must start on 64-inode
+        * boundaries since each block can hold N complete chunks. if fs has
+        * aligned inodes, all chunks must start at a fs_ino_alignment*N'th
+        * agbno. skip recs with badly aligned starting inodes.
+        */
+       if (ino == 0 ||
+           (inodes_per_block <= XFS_INODES_PER_CHUNK && off !=  0) ||
+           (inodes_per_block > XFS_INODES_PER_CHUNK &&
+            off % XFS_INODES_PER_CHUNK != 0) ||
+           (fs_aligned_inodes && fs_ino_alignment &&
+            agbno % fs_ino_alignment != 0)) {
+               do_warn(
+       _("badly aligned %s rec (starting inode = %" PRIu64 ")\n"),
+                       inobt_name, lino);
+               suspect++;
+       }
+
+       /*
+        * verify numeric validity of inode chunk first before inserting into a
+        * tree. don't have to worry about the overflow case because the
+        * starting ino number of a chunk can only get within 255 inodes of max
+        * (NULLAGINO). if it gets closer, the agino number will be illegal as
+        * the agbno will be too large.
+        */
+       if (verify_aginum(mp, agno, ino)) {
+               do_warn(
+_("bad starting inode # (%" PRIu64 " (0x%x 0x%x)) in %s rec, skipping rec\n"),
+                       lino, agno, ino, inobt_name);
+               *skip = true;
+               return ++suspect;
+       }
+
+       if (verify_aginum(mp, agno,
+                       ino + XFS_INODES_PER_CHUNK - 1)) {
+               do_warn(
+_("bad ending inode # (%" PRIu64 " (0x%x 0x%zx)) in %s rec, skipping rec\n"),
+                       lino + XFS_INODES_PER_CHUNK - 1,
+                       agno,
+                       ino + XFS_INODES_PER_CHUNK - 1,
+                       inobt_name);
+               *skip = true;
+               return ++suspect;
+       }
+
+       return suspect;
+}
+
+/*
+ * Process the state of individual inodes in an on-disk inobt record and import
+ * into the appropriate in-core tree based on whether the on-disk tree is
+ * suspect. Return the total and free inode counts based on the record free and
+ * hole masks.
+ */
+static int
+import_single_ino_chunk(
+       xfs_agnumber_t          agno,
+       enum inobt_type         type,
+       struct xfs_inobt_rec    *rp,
+       int                     suspect,
+       int                     *p_nfree,
+       int                     *p_ninodes)
+{
+       struct ino_tree_node    *ino_rec = NULL;
+       const char              *inobt_name = inobt_names[type];
+       xfs_agino_t             ino;
+       int                     j;
+       int                     nfree;
+       int                     ninodes;
+
+       ino = be32_to_cpu(rp->ir_startino);
+
+       if (!suspect) {
+               if (XFS_INOBT_IS_FREE_DISK(rp, 0))
+                       ino_rec = set_inode_free_alloc(mp, agno, ino);
+               else
+                       ino_rec = set_inode_used_alloc(mp, agno, ino);
+               for (j = 1; j < XFS_INODES_PER_CHUNK; j++) {
+                       if (XFS_INOBT_IS_FREE_DISK(rp, j))
+                               set_inode_free(ino_rec, j);
+                       else
+                               set_inode_used(ino_rec, j);
+               }
+       } else {
+               for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
+                       if (XFS_INOBT_IS_FREE_DISK(rp, j))
+                               add_aginode_uncertain(mp, agno, ino + j, 1);
+                       else
+                               add_aginode_uncertain(mp, agno, ino + j, 0);
+               }
+       }
+
+       /*
+        * Mark sparse inodes as such in the in-core tree. Verify that sparse
+        * inodes are free and that freecount is consistent with the free mask.
+        */
+       nfree = ninodes = 0;
+       for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
+               if (ino_issparse(rp, j)) {
+                       if (!suspect && !XFS_INOBT_IS_FREE_DISK(rp, j)) {
+                               do_warn(
+_("ir_holemask/ir_free mismatch, %s chunk %d/%u, holemask 0x%x free 0x%llx\n"),
+                                       inobt_name, agno, ino,
+                                       be16_to_cpu(rp->ir_u.sp.ir_holemask),
+                                       (unsigned long long)be64_to_cpu(rp->ir_free));
+                               suspect++;
+                       }
+                       if (!suspect && ino_rec)
+                               set_inode_sparse(ino_rec, j);
+               } else {
+                       /* count fields track non-sparse inos */
+                       if (XFS_INOBT_IS_FREE_DISK(rp, j))
+                               nfree++;
+                       ninodes++;
+               }
+       }
+
+       *p_nfree = nfree;
+       *p_ninodes = ninodes;
+
+       return suspect;
+}
+
+static int
+scan_single_ino_chunk(
+       xfs_agnumber_t          agno,
+       xfs_inobt_rec_t         *rp,
+       int                     suspect)
+{
+       xfs_ino_t               lino;
+       xfs_agino_t             ino;
+       xfs_agblock_t           agbno;
+       int                     j;
+       int                     nfree;
+       int                     ninodes;
+       int                     off;
+       int                     state;
+       ino_tree_node_t         *first_rec, *last_rec;
+       int                     freecount;
+       bool                    skip = false;
+
+       ino = be32_to_cpu(rp->ir_startino);
+       off = XFS_AGINO_TO_OFFSET(mp, ino);
+       agbno = XFS_AGINO_TO_AGBNO(mp, ino);
+       lino = XFS_AGINO_TO_INO(mp, agno, ino);
+       freecount = inorec_get_freecount(mp, rp);
+
+       /*
+        * Verify record alignment, start/end inode numbers, etc.
+        */
+       suspect = verify_single_ino_chunk_align(agno, INOBT, rp, suspect,
+                                               &skip);
+       if (skip)
+               return suspect;
+
+       /*
+        * set state of each block containing inodes
+        */
+       if (off == 0 && !suspect)  {
+               for (j = 0;
+                    j < XFS_INODES_PER_CHUNK;
+                    j += mp->m_sb.sb_inopblock)  {
+
+                       /* inodes in sparse chunks don't use blocks */
+                       if (ino_issparse(rp, j))
+                               continue;
+
+                       agbno = XFS_AGINO_TO_AGBNO(mp, ino + j);
+                       state = get_bmap(agno, agbno);
+                       switch (state) {
+                       case XR_E_INO:
+                               break;
+                       case XR_E_UNKNOWN:
+                       case XR_E_INO1: /* seen by rmap */
+                               set_bmap(agno, agbno, XR_E_INO);
+                               break;
+                       case XR_E_INUSE_FS:
+                       case XR_E_INUSE_FS1:
+                               if (agno == 0 &&
+                                   ino + j >= first_prealloc_ino &&
+                                   ino + j < last_prealloc_ino) {
+                                       set_bmap(agno, agbno, XR_E_INO);
+                                       break;
+                               }
+                               /* fall through */
+                       default:
+                               /* XXX - maybe should mark block a duplicate */
+                               do_warn(
+_("inode chunk claims used block, inobt block - agno %d, bno %d, inopb %d\n"),
+                                       agno, agbno, mp->m_sb.sb_inopblock);
+                               return ++suspect;
+                       }
+               }
+       }
+
+       /*
+        * ensure only one avl entry per chunk
+        */
+       find_inode_rec_range(mp, agno, ino, ino + XFS_INODES_PER_CHUNK,
+                            &first_rec, &last_rec);
+       if (first_rec != NULL)  {
+               /*
+                * this chunk overlaps with one (or more)
+                * already in the tree
+                */
+               do_warn(
+_("inode rec for ino %" PRIu64 " (%d/%d) overlaps existing rec (start %d/%d)\n"),
+                       lino, agno, ino, agno, first_rec->ino_startnum);
+               suspect++;
+
+               /*
+                * if the 2 chunks start at the same place,
+                * then we don't have to put this one
+                * in the uncertain list.  go to the next one.
+                */
+               if (first_rec->ino_startnum == ino)
+                       return suspect;
+       }
+
+       /*
+        * Import the state of individual inodes into the appropriate in-core
+        * trees, mark them free or used, and get the resulting total and free
+        * inode counts.
+        */
+       nfree = ninodes = 0;
+       suspect = import_single_ino_chunk(agno, INOBT, rp, suspect, &nfree,
+                                        &ninodes);
+
+       if (nfree != freecount) {
+               do_warn(
+_("ir_freecount/free mismatch, inode chunk %d/%u, freecount %d nfree %d\n"),
+                       agno, ino, freecount, nfree);
+       }
+
+       /* verify sparse record formats have a valid inode count */
+       if (xfs_sb_version_hassparseinodes(&mp->m_sb) &&
+           ninodes != rp->ir_u.sp.ir_count) {
+               do_warn(
+_("invalid inode count, inode chunk %d/%u, count %d ninodes %d\n"),
+                       agno, ino, rp->ir_u.sp.ir_count, ninodes);
+       }
+
+       return suspect;
+}
+
+static int
+scan_single_finobt_chunk(
+       xfs_agnumber_t          agno,
+       xfs_inobt_rec_t         *rp,
+       int                     suspect)
+{
+       xfs_ino_t               lino;
+       xfs_agino_t             ino;
+       xfs_agblock_t           agbno;
+       int                     j;
+       int                     nfree;
+       int                     ninodes;
+       int                     off;
+       int                     state;
+       ino_tree_node_t         *first_rec, *last_rec;
+       int                     freecount;
+       bool                    skip = false;
+
+       ino = be32_to_cpu(rp->ir_startino);
+       off = XFS_AGINO_TO_OFFSET(mp, ino);
+       agbno = XFS_AGINO_TO_AGBNO(mp, ino);
+       lino = XFS_AGINO_TO_INO(mp, agno, ino);
+       freecount = inorec_get_freecount(mp, rp);
+
+       /*
+        * Verify record alignment, start/end inode numbers, etc.
+        */
+       suspect = verify_single_ino_chunk_align(agno, FINOBT, rp, suspect,
+                                               &skip);
+       if (skip)
+               return suspect;
+
+       /*
+        * cross check state of each block containing inodes referenced by the
+        * finobt against what we have already scanned from the alloc inobt.
+        */
+       if (off == 0 && !suspect) {
+               for (j = 0;
+                    j < XFS_INODES_PER_CHUNK;
+                    j += mp->m_sb.sb_inopblock) {
+                       agbno = XFS_AGINO_TO_AGBNO(mp, ino + j);
+                       state = get_bmap(agno, agbno);
+
+                       /* sparse inodes should not refer to inode blocks */
+                       if (ino_issparse(rp, j)) {
+                               if (state == XR_E_INO) {
+                                       do_warn(
+_("sparse inode chunk claims inode block, finobt block - agno %d, bno %d, inopb %d\n"),
+                                               agno, agbno, mp->m_sb.sb_inopblock);
+                                       suspect++;
+                               }
+                               continue;
+                       }
+
+                       switch (state) {
+                       case XR_E_INO:
+                               break;
+                       case XR_E_INO1: /* seen by rmap */
+                               set_bmap(agno, agbno, XR_E_INO);
+                               break;
+                       case XR_E_UNKNOWN:
+                               do_warn(
+_("inode chunk claims untracked block, finobt block - agno %d, bno %d, inopb %d\n"),
+                                       agno, agbno, mp->m_sb.sb_inopblock);
+
+                               set_bmap(agno, agbno, XR_E_INO);
+                               suspect++;
+                               break;
+                       case XR_E_INUSE_FS:
+                       case XR_E_INUSE_FS1:
+                               if (agno == 0 &&
+                                   ino + j >= first_prealloc_ino &&
+                                   ino + j < last_prealloc_ino) {
+                                       do_warn(
+_("inode chunk claims untracked block, finobt block - agno %d, bno %d, inopb %d\n"),
+                                               agno, agbno, mp->m_sb.sb_inopblock);
+
+                                       set_bmap(agno, agbno, XR_E_INO);
+                                       suspect++;
+                                       break;
+                               }
+                               /* fall through */
+                       default:
+                               do_warn(
+_("inode chunk claims used block, finobt block - agno %d, bno %d, inopb %d\n"),
+                                       agno, agbno, mp->m_sb.sb_inopblock);
+                               return ++suspect;
+                       }
+               }
+       }
+
+       /*
+        * ensure we have an incore entry for each chunk
+        */
+       find_inode_rec_range(mp, agno, ino, ino + XFS_INODES_PER_CHUNK,
+                            &first_rec, &last_rec);
+
+       if (first_rec) {
+               if (suspect)
+                       return suspect;
+
+               /*
+                * verify consistency between finobt record and incore state
+                */
+               if (first_rec->ino_startnum != ino) {
+                       do_warn(
+_("finobt rec for ino %" PRIu64 " (%d/%u) does not match existing rec (%d/%d)\n"),
+                               lino, agno, ino, agno, first_rec->ino_startnum);
+                       return ++suspect;
+               }
+
+               nfree = ninodes = 0;
+               for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
+                       int isfree = XFS_INOBT_IS_FREE_DISK(rp, j);
+                       int issparse = ino_issparse(rp, j);
+
+                       if (!issparse)
+                               ninodes++;
+                       if (isfree && !issparse)
+                               nfree++;
+
+                       /*
+                        * inode allocation state should be consistent between
+                        * the inobt and finobt
+                        */
+                       if (!suspect &&
+                           isfree != is_inode_free(first_rec, j))
+                               suspect++;
+
+                       if (!suspect &&
+                           issparse != is_inode_sparse(first_rec, j))
+                               suspect++;
+               }
+
+               goto check_freecount;
+       }
+
+       /*
+        * The finobt contains a record that the previous inobt scan never
+        * found. Warn about it and import the inodes into the appropriate
+        * trees.
+        *
+        * Note that this should do the right thing if the previous inobt scan
+        * had added these inodes to the uncertain tree. If the finobt is not
+        * suspect, these inodes should supercede the uncertain ones. Otherwise,
+        * the uncertain tree helpers handle the case where uncertain inodes
+        * already exist.
+        */
+       do_warn(_("undiscovered finobt record, ino %" PRIu64 " (%d/%u)\n"),
+               lino, agno, ino);
+
+       nfree = ninodes = 0;
+       suspect = import_single_ino_chunk(agno, FINOBT, rp, suspect, &nfree,
+                                        &ninodes);
+
+check_freecount:
+
+       /*
+        * Verify that the record freecount matches the actual number of free
+        * inodes counted in the record. Don't increment 'suspect' here, since
+        * we have already verified the allocation state of the individual
+        * inodes against the in-core state. This will have already incremented
+        * 'suspect' if something is wrong. If suspect hasn't been set at this
+        * point, these warnings mean that we have a simple freecount
+        * inconsistency or a stray finobt record (as opposed to a broader tree
+        * corruption). Issue a warning and continue the scan. The final btree
+        * reconstruction will correct this naturally.
+        */
+       if (nfree != freecount) {
+               do_warn(
+_("finobt ir_freecount/free mismatch, inode chunk %d/%u, freecount %d nfree %d\n"),
+                       agno, ino, freecount, nfree);
+       }
+
+       if (!nfree) {
+               do_warn(
+_("finobt record with no free inodes, inode chunk %d/%u\n"), agno, ino);
+       }
+
+       /* verify sparse record formats have a valid inode count */
+       if (xfs_sb_version_hassparseinodes(&mp->m_sb) &&
+           ninodes != rp->ir_u.sp.ir_count) {
+               do_warn(
+_("invalid inode count, inode chunk %d/%u, count %d ninodes %d\n"),
+                       agno, ino, rp->ir_u.sp.ir_count, ninodes);
+       }
+
+       return suspect;
+}
+
+/*
+ * this one walks the inode btrees sucking the info there into
+ * the incore avl tree.  We try and rescue corrupted btree records
+ * to minimize our chances of losing inodes.  Inode info from potentially
+ * corrupt sources could be bogus so rather than put the info straight
+ * into the tree, instead we put it on a list and try and verify the
+ * info in the next phase by examining what's on disk.  At that point,
+ * we'll be able to figure out what's what and stick the corrected info
+ * into the tree.  We do bail out at some point and give up on a subtree
+ * so as to avoid walking randomly all over the ag.
+ *
+ * Note that it's also ok if the free/inuse info wrong, we can correct
+ * that when we examine the on-disk inode.  The important thing is to
+ * get the start and alignment of the inode chunks right.  Those chunks
+ * that we aren't sure about go into the uncertain list.
+ */
+static void
+scan_inobt(
+       struct xfs_btree_block  *block,
+       int                     level,
+       xfs_agblock_t           bno,
+       xfs_agnumber_t          agno,
+       int                     suspect,
+       int                     isroot,
+       uint32_t                magic,
+       void                    *priv)
+{
+       struct aghdr_cnts       *agcnts = priv;
+       int                     i;
+       int                     numrecs;
+       int                     state;
+       xfs_inobt_ptr_t         *pp;
+       xfs_inobt_rec_t         *rp;
+       int                     hdr_errors;
+       int                     freecount;
+
+       hdr_errors = 0;
+
+       if (be32_to_cpu(block->bb_magic) != magic) {
+               do_warn(_("bad magic # %#x in inobt block %d/%d\n"),
+                       be32_to_cpu(block->bb_magic), agno, bno);
+               hdr_errors++;
+               bad_ino_btree = 1;
+               if (suspect)
+                       return;
+       }
+       if (be16_to_cpu(block->bb_level) != level) {
                do_warn(_("expected level %d got %d in inobt block %d/%d\n"),
                        level, be16_to_cpu(block->bb_level), agno, bno);
                hdr_errors++;
@@ -847,6 +1971,7 @@ scanfunc_ino(
         */
        state = get_bmap(agno, bno);
        switch (state)  {
+       case XR_E_FS_MAP1: /* already been seen by an rmap scan */
        case XR_E_UNKNOWN:
        case XR_E_FREE1:
        case XR_E_FREE:
@@ -893,8 +2018,38 @@ _("inode btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                 * of INODES_PER_CHUNK (64) inodes.  off is the offset into
                 * the block.  skip processing of bogus records.
                 */
-               for (i = 0; i < numrecs; i++)
-                       suspect = scan_single_ino_chunk(agno, &rp[i], suspect);
+               for (i = 0; i < numrecs; i++) {
+                       freecount = inorec_get_freecount(mp, &rp[i]);
+
+                       if (magic == XFS_IBT_MAGIC ||
+                           magic == XFS_IBT_CRC_MAGIC) {
+                               int icount = XFS_INODES_PER_CHUNK;
+
+                               /*
+                                * ir_count holds the inode count for all
+                                * records on fs' with sparse inode support
+                                */
+                               if (xfs_sb_version_hassparseinodes(&mp->m_sb))
+                                       icount = rp[i].ir_u.sp.ir_count;
+
+                               agcnts->agicount += icount;
+                               agcnts->agifreecount += freecount;
+                               agcnts->ifreecount += freecount;
+
+                               suspect = scan_single_ino_chunk(agno, &rp[i],
+                                               suspect);
+                       } else {
+                               /*
+                                * the finobt tracks records with free inodes,
+                                * so only the free inode count is expected to be
+                                * consistent with the agi
+                                */
+                               agcnts->fibtfreecount += freecount;
+
+                               suspect = scan_single_finobt_chunk(agno, &rp[i],
+                                               suspect);
+                       }
+               }
 
                if (suspect)
                        bad_ino_btree = 1;
@@ -935,113 +2090,299 @@ _("inode btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
                if (be32_to_cpu(pp[i]) != 0 && verify_agbno(mp, agno,
                                                        be32_to_cpu(pp[i])))
                        scan_sbtree(be32_to_cpu(pp[i]), level, agno,
-                                       suspect, scanfunc_ino, 0);
+                                       suspect, scan_inobt, 0, magic, priv,
+                                       &xfs_inobt_buf_ops);
        }
 }
 
-void
+struct agfl_state {
+       unsigned int    count;
+       xfs_agnumber_t  agno;
+};
+
+static int
+scan_agfl(
+       struct xfs_mount        *mp,
+       xfs_agblock_t           bno,
+       void                    *priv)
+{
+       struct agfl_state       *as = priv;
+
+       if (verify_agbno(mp, as->agno, bno))
+               set_bmap(as->agno, bno, XR_E_FREE);
+       else
+               do_warn(_("bad agbno %u in agfl, agno %d\n"),
+                       bno, as->agno);
+       as->count++;
+       return 0;
+}
+
+static void
 scan_freelist(
-       xfs_agf_t       *agf)
+       xfs_agf_t               *agf,
+       struct aghdr_cnts       *agcnts)
 {
-       xfs_agfl_t      *agfl;
-       xfs_buf_t       *agflbuf;
-       xfs_agnumber_t  agno;
-       xfs_agblock_t   bno;
-       int             count;
-       int             i;
+       xfs_buf_t               *agflbuf;
+       xfs_agnumber_t          agno;
+       struct agfl_state       state;
 
        agno = be32_to_cpu(agf->agf_seqno);
 
        if (XFS_SB_BLOCK(mp) != XFS_AGFL_BLOCK(mp) &&
            XFS_AGF_BLOCK(mp) != XFS_AGFL_BLOCK(mp) &&
            XFS_AGI_BLOCK(mp) != XFS_AGFL_BLOCK(mp))
-               set_bmap(agno, XFS_AGFL_BLOCK(mp), XR_E_FS_MAP);
+               set_bmap(agno, XFS_AGFL_BLOCK(mp), XR_E_INUSE_FS);
 
        if (be32_to_cpu(agf->agf_flcount) == 0)
                return;
 
        agflbuf = libxfs_readbuf(mp->m_dev,
                                 XFS_AG_DADDR(mp, agno, XFS_AGFL_DADDR(mp)),
-                                XFS_FSS_TO_BB(mp, 1), 0);
+                                XFS_FSS_TO_BB(mp, 1), 0, &xfs_agfl_buf_ops);
        if (!agflbuf)  {
                do_abort(_("can't read agfl block for ag %d\n"), agno);
                return;
        }
-       agfl = XFS_BUF_TO_AGFL(agflbuf);
-       i = be32_to_cpu(agf->agf_flfirst);
-       count = 0;
-       for (;;) {
-               bno = be32_to_cpu(agfl->agfl_bno[i]);
-               if (verify_agbno(mp, agno, bno))
-                       set_bmap(agno, bno, XR_E_FREE);
-               else
-                       do_warn(_("bad agbno %u in agfl, agno %d\n"),
-                               bno, agno);
-               count++;
-               if (i == be32_to_cpu(agf->agf_fllast))
-                       break;
-               if (++i == XFS_AGFL_SIZE(mp))
-                       i = 0;
+       if (agflbuf->b_error == -EFSBADCRC)
+               do_warn(_("agfl has bad CRC for ag %d\n"), agno);
+
+       if (no_modify) {
+               /* agf values not fixed in verify_set_agf, so recheck */
+               if (be32_to_cpu(agf->agf_flfirst) >= libxfs_agfl_size(mp) ||
+                   be32_to_cpu(agf->agf_fllast) >= libxfs_agfl_size(mp)) {
+                       do_warn(_("agf %d freelist blocks bad, skipping "
+                                 "freelist scan\n"), agno);
+                       return;
+               }
        }
-       if (count != be32_to_cpu(agf->agf_flcount)) {
-               do_warn(_("freeblk count %d != flcount %d in ag %d\n"), count,
-                       be32_to_cpu(agf->agf_flcount), agno);
+
+       state.count = 0;
+       state.agno = agno;
+       libxfs_agfl_walk(mp, agf, agflbuf, scan_agfl, &state);
+       if (state.count != be32_to_cpu(agf->agf_flcount)) {
+               do_warn(_("freeblk count %d != flcount %d in ag %d\n"),
+                               state.count, be32_to_cpu(agf->agf_flcount),
+                               agno);
        }
+
+       agcnts->fdblocks += state.count;
+
        libxfs_putbuf(agflbuf);
 }
 
-void
-scan_ag(
-       xfs_agnumber_t  agno)
+static void
+validate_agf(
+       struct xfs_agf          *agf,
+       xfs_agnumber_t          agno,
+       struct aghdr_cnts       *agcnts)
 {
-       xfs_agf_t       *agf;
-       xfs_buf_t       *agfbuf;
-       int             agf_dirty;
-       xfs_agi_t       *agi;
-       xfs_buf_t       *agibuf;
-       int             agi_dirty;
-       xfs_sb_t        *sb;
-       xfs_buf_t       *sbbuf;
-       int             sb_dirty;
-       int             status;
+       xfs_agblock_t           bno;
+       uint32_t                magic;
+
+       bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]);
+       if (bno != 0 && verify_agbno(mp, agno, bno)) {
+               magic = xfs_sb_version_hascrc(&mp->m_sb) ? XFS_ABTB_CRC_MAGIC
+                                                        : XFS_ABTB_MAGIC;
+               scan_sbtree(bno, be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]),
+                           agno, 0, scan_allocbt, 1, magic, agcnts,
+                           &xfs_allocbt_buf_ops);
+       } else {
+               do_warn(_("bad agbno %u for btbno root, agno %d\n"),
+                       bno, agno);
+       }
+
+       bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]);
+       if (bno != 0 && verify_agbno(mp, agno, bno)) {
+               magic = xfs_sb_version_hascrc(&mp->m_sb) ? XFS_ABTC_CRC_MAGIC
+                                                        : XFS_ABTC_MAGIC;
+               scan_sbtree(bno, be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]),
+                           agno, 0, scan_allocbt, 1, magic, agcnts,
+                           &xfs_allocbt_buf_ops);
+       } else  {
+               do_warn(_("bad agbno %u for btbcnt root, agno %d\n"),
+                       bno, agno);
+       }
 
-       agi_dirty = agf_dirty = sb_dirty = 0;
+       if (xfs_sb_version_hasrmapbt(&mp->m_sb)) {
+               struct rmap_priv        priv;
 
-       sbbuf = libxfs_readbuf(mp->m_dev, XFS_AG_DADDR(mp, agno, XFS_SB_DADDR),
-                               XFS_FSS_TO_BB(mp, 1), 0);
-       if (!sbbuf)  {
-               do_error(_("can't get root superblock for ag %d\n"), agno);
-               return;
+               memset(&priv.high_key, 0xFF, sizeof(priv.high_key));
+               priv.high_key.rm_blockcount = 0;
+               priv.agcnts = agcnts;
+               priv.last_rec.rm_owner = XFS_RMAP_OWN_UNKNOWN;
+               priv.nr_blocks = 0;
+               bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_RMAP]);
+               if (bno != 0 && verify_agbno(mp, agno, bno)) {
+                       scan_sbtree(bno,
+                                   be32_to_cpu(agf->agf_levels[XFS_BTNUM_RMAP]),
+                                   agno, 0, scan_rmapbt, 1, XFS_RMAP_CRC_MAGIC,
+                                   &priv, &xfs_rmapbt_buf_ops);
+                       if (be32_to_cpu(agf->agf_rmap_blocks) != priv.nr_blocks)
+                               do_warn(_("bad rmapbt block count %u, saw %u\n"),
+                                       priv.nr_blocks,
+                                       be32_to_cpu(agf->agf_rmap_blocks));
+               } else  {
+                       do_warn(_("bad agbno %u for rmapbt root, agno %d\n"),
+                               bno, agno);
+                       rmap_avoid_check();
+               }
+       }
+
+       if (xfs_sb_version_hasreflink(&mp->m_sb)) {
+               bno = be32_to_cpu(agf->agf_refcount_root);
+               if (bno != 0 && verify_agbno(mp, agno, bno)) {
+                       struct refc_priv        priv;
+
+                       memset(&priv, 0, sizeof(priv));
+                       scan_sbtree(bno,
+                                   be32_to_cpu(agf->agf_refcount_level),
+                                   agno, 0, scan_refcbt, 1, XFS_REFC_CRC_MAGIC,
+                                   &priv, &xfs_refcountbt_buf_ops);
+                       if (be32_to_cpu(agf->agf_refcount_blocks) != priv.nr_blocks)
+                               do_warn(_("bad refcountbt block count %u, saw %u\n"),
+                                       priv.nr_blocks,
+                                       be32_to_cpu(agf->agf_refcount_blocks));
+               } else  {
+                       do_warn(_("bad agbno %u for refcntbt root, agno %d\n"),
+                               bno, agno);
+                       refcount_avoid_check();
+               }
+       }
+
+       if (be32_to_cpu(agf->agf_freeblks) != agcnts->agffreeblks) {
+               do_warn(_("agf_freeblks %u, counted %u in ag %u\n"),
+                       be32_to_cpu(agf->agf_freeblks), agcnts->agffreeblks, agno);
+       }
+
+       if (be32_to_cpu(agf->agf_longest) != agcnts->agflongest) {
+               do_warn(_("agf_longest %u, counted %u in ag %u\n"),
+                       be32_to_cpu(agf->agf_longest), agcnts->agflongest, agno);
+       }
+
+       if (xfs_sb_version_haslazysbcount(&mp->m_sb) &&
+           be32_to_cpu(agf->agf_btreeblks) != agcnts->agfbtreeblks) {
+               do_warn(_("agf_btreeblks %u, counted %" PRIu64 " in ag %u\n"),
+                       be32_to_cpu(agf->agf_btreeblks), agcnts->agfbtreeblks, agno);
        }
 
-       sb = (xfs_sb_t *)calloc(BBSIZE, 1);
+}
+
+static void
+validate_agi(
+       struct xfs_agi          *agi,
+       xfs_agnumber_t          agno,
+       struct aghdr_cnts       *agcnts)
+{
+       xfs_agblock_t           bno;
+       int                     i;
+       uint32_t                magic;
+
+       bno = be32_to_cpu(agi->agi_root);
+       if (bno != 0 && verify_agbno(mp, agno, bno)) {
+               magic = xfs_sb_version_hascrc(&mp->m_sb) ? XFS_IBT_CRC_MAGIC
+                                                        : XFS_IBT_MAGIC;
+               scan_sbtree(bno, be32_to_cpu(agi->agi_level),
+                           agno, 0, scan_inobt, 1, magic, agcnts,
+                           &xfs_inobt_buf_ops);
+       } else {
+               do_warn(_("bad agbno %u for inobt root, agno %d\n"),
+                       be32_to_cpu(agi->agi_root), agno);
+       }
+
+       if (xfs_sb_version_hasfinobt(&mp->m_sb)) {
+               bno = be32_to_cpu(agi->agi_free_root);
+               if (bno != 0 && verify_agbno(mp, agno, bno)) {
+                       magic = xfs_sb_version_hascrc(&mp->m_sb) ?
+                                       XFS_FIBT_CRC_MAGIC : XFS_FIBT_MAGIC;
+                       scan_sbtree(bno, be32_to_cpu(agi->agi_free_level),
+                                   agno, 0, scan_inobt, 1, magic, agcnts,
+                                   &xfs_inobt_buf_ops);
+               } else {
+                       do_warn(_("bad agbno %u for finobt root, agno %d\n"),
+                               be32_to_cpu(agi->agi_free_root), agno);
+               }
+       }
+
+       if (be32_to_cpu(agi->agi_count) != agcnts->agicount) {
+               do_warn(_("agi_count %u, counted %u in ag %u\n"),
+                        be32_to_cpu(agi->agi_count), agcnts->agicount, agno);
+       }
+
+       if (be32_to_cpu(agi->agi_freecount) != agcnts->agifreecount) {
+               do_warn(_("agi_freecount %u, counted %u in ag %u\n"),
+                       be32_to_cpu(agi->agi_freecount), agcnts->agifreecount, agno);
+       }
+
+       if (xfs_sb_version_hasfinobt(&mp->m_sb) &&
+           be32_to_cpu(agi->agi_freecount) != agcnts->fibtfreecount) {
+               do_warn(_("agi_freecount %u, counted %u in ag %u finobt\n"),
+                       be32_to_cpu(agi->agi_freecount), agcnts->fibtfreecount,
+                       agno);
+       }
+
+       for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
+               xfs_agino_t     agino = be32_to_cpu(agi->agi_unlinked[i]);
+
+               if (agino != NULLAGINO) {
+                       do_warn(
+       _("agi unlinked bucket %d is %u in ag %u (inode=%" PRIu64 ")\n"),
+                               i, agino, agno,
+                               XFS_AGINO_TO_INO(mp, agno, agino));
+               }
+       }
+}
+
+/*
+ * Scan an AG for obvious corruption.
+ */
+static void
+scan_ag(
+       struct workqueue*wq,
+       xfs_agnumber_t  agno,
+       void            *arg)
+{
+       struct aghdr_cnts *agcnts = arg;
+       struct xfs_agf  *agf;
+       struct xfs_buf  *agfbuf = NULL;
+       int             agf_dirty = 0;
+       struct xfs_agi  *agi;
+       struct xfs_buf  *agibuf = NULL;
+       int             agi_dirty = 0;
+       struct xfs_sb   *sb = NULL;
+       struct xfs_buf  *sbbuf = NULL;
+       int             sb_dirty = 0;
+       int             status;
+       char            *objname = NULL;
+
+       sb = (struct xfs_sb *)calloc(BBTOB(XFS_FSS_TO_BB(mp, 1)), 1);
        if (!sb) {
                do_error(_("can't allocate memory for superblock\n"));
-               libxfs_putbuf(sbbuf);
                return;
        }
+
+       sbbuf = libxfs_readbuf(mp->m_dev, XFS_AG_DADDR(mp, agno, XFS_SB_DADDR),
+                               XFS_FSS_TO_BB(mp, 1), 0, &xfs_sb_buf_ops);
+       if (!sbbuf)  {
+               objname = _("root superblock");
+               goto out_free_sb;
+       }
        libxfs_sb_from_disk(sb, XFS_BUF_TO_SBP(sbbuf));
 
        agfbuf = libxfs_readbuf(mp->m_dev,
                        XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)),
-                       XFS_FSS_TO_BB(mp, 1), 0);
+                       XFS_FSS_TO_BB(mp, 1), 0, &xfs_agf_buf_ops);
        if (!agfbuf)  {
-               do_error(_("can't read agf block for ag %d\n"), agno);
-               libxfs_putbuf(sbbuf);
-               free(sb);
-               return;
+               objname = _("agf block");
+               goto out_free_sbbuf;
        }
        agf = XFS_BUF_TO_AGF(agfbuf);
 
        agibuf = libxfs_readbuf(mp->m_dev,
                        XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)),
-                       XFS_FSS_TO_BB(mp, 1), 0);
+                       XFS_FSS_TO_BB(mp, 1), 0, &xfs_agi_buf_ops);
        if (!agibuf)  {
-               do_error(_("can't read agi block for ag %d\n"), agno);
-               libxfs_putbuf(agfbuf);
-               libxfs_putbuf(sbbuf);
-               free(sb);
-               return;
+               objname = _("agi block");
+               goto out_free_agfbuf;
        }
        agi = XFS_BUF_TO_AGI(agibuf);
 
@@ -1085,70 +2426,130 @@ scan_ag(
        }
 
        if (status && no_modify)  {
-               libxfs_putbuf(agibuf);
-               libxfs_putbuf(agfbuf);
-               libxfs_putbuf(sbbuf);
-               free(sb);
-
                do_warn(_("bad uncorrected agheader %d, skipping ag...\n"),
                        agno);
-
-               return;
+               goto out_free_agibuf;
        }
 
-       scan_freelist(agf);
-
-       if (be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]) != 0 && verify_agbno(mp,
-                       agno, be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO])))
-               scan_sbtree(be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]),
-                               be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]),
-                               agno, 0, scanfunc_bno, 1);
-       else
-               do_warn(_("bad agbno %u for btbno root, agno %d\n"),
-                       be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]),
-                       agno);
-
-       if (be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]) != 0 && verify_agbno(mp,
-                       agno, be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT])))
-               scan_sbtree(be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]),
-                               be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]),
-                               agno, 0, scanfunc_cnt, 1);
-       else
-               do_warn(_("bad agbno %u for btbcnt root, agno %d\n"),
-                       be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]),
-                       agno);
+       scan_freelist(agf, agcnts);
 
-       if (be32_to_cpu(agi->agi_root) != 0 && verify_agbno(mp, agno,
-                                               be32_to_cpu(agi->agi_root)))
-               scan_sbtree(be32_to_cpu(agi->agi_root),
-                       be32_to_cpu(agi->agi_level), agno, 0, scanfunc_ino, 1);
-       else
-               do_warn(_("bad agbno %u for inobt root, agno %d\n"),
-                       be32_to_cpu(agi->agi_root), agno);
+       validate_agf(agf, agno, agcnts);
+       validate_agi(agi, agno, agcnts);
 
        ASSERT(agi_dirty == 0 || (agi_dirty && !no_modify));
+       ASSERT(agf_dirty == 0 || (agf_dirty && !no_modify));
+       ASSERT(sb_dirty == 0 || (sb_dirty && !no_modify));
+
+       /*
+        * Only pay attention to CRC/verifier errors if we can correct them.
+        * Note that we can get uncorrected EFSCORRUPTED errors here because
+        * the verifier will flag on out of range values that we can't correct
+        * until phase 5 when we have all the information necessary to rebuild
+        * the freespace/inode btrees. We can correct bad CRC errors
+        * immediately, though.
+        */
+       if (!no_modify) {
+               agi_dirty += (agibuf->b_error == -EFSBADCRC);
+               agf_dirty += (agfbuf->b_error == -EFSBADCRC);
+               sb_dirty += (sbbuf->b_error == -EFSBADCRC);
+       }
 
        if (agi_dirty && !no_modify)
                libxfs_writebuf(agibuf, 0);
        else
                libxfs_putbuf(agibuf);
 
-       ASSERT(agf_dirty == 0 || (agf_dirty && !no_modify));
-
        if (agf_dirty && !no_modify)
                libxfs_writebuf(agfbuf, 0);
        else
                libxfs_putbuf(agfbuf);
 
-       ASSERT(sb_dirty == 0 || (sb_dirty && !no_modify));
-
        if (sb_dirty && !no_modify) {
                if (agno == 0)
                        memcpy(&mp->m_sb, sb, sizeof(xfs_sb_t));
-               libxfs_sb_to_disk(XFS_BUF_TO_SBP(sbbuf), sb, XFS_SB_ALL_BITS);
+               libxfs_sb_to_disk(XFS_BUF_TO_SBP(sbbuf), sb);
                libxfs_writebuf(sbbuf, 0);
        } else
                libxfs_putbuf(sbbuf);
        free(sb);
        PROG_RPT_INC(prog_rpt_done[agno], 1);
+
+#ifdef XR_INODE_TRACE
+       print_inode_list(i);
+#endif
+       return;
+
+out_free_agibuf:
+       libxfs_putbuf(agibuf);
+out_free_agfbuf:
+       libxfs_putbuf(agfbuf);
+out_free_sbbuf:
+       libxfs_putbuf(sbbuf);
+out_free_sb:
+       free(sb);
+
+       if (objname)
+               do_error(_("can't get %s for ag %d\n"), objname, agno);
+}
+
+void
+scan_ags(
+       struct xfs_mount        *mp,
+       int                     scan_threads)
+{
+       struct aghdr_cnts       *agcnts;
+       uint64_t                fdblocks = 0;
+       uint64_t                icount = 0;
+       uint64_t                ifreecount = 0;
+       uint64_t                usedblocks = 0;
+       xfs_agnumber_t          i;
+       struct workqueue        wq;
+
+       agcnts = malloc(mp->m_sb.sb_agcount * sizeof(*agcnts));
+       if (!agcnts) {
+               do_abort(_("no memory for ag header counts\n"));
+               return;
+       }
+       memset(agcnts, 0, mp->m_sb.sb_agcount * sizeof(*agcnts));
+
+       create_work_queue(&wq, mp, scan_threads);
+
+       for (i = 0; i < mp->m_sb.sb_agcount; i++)
+               queue_work(&wq, scan_ag, i, &agcnts[i]);
+
+       destroy_work_queue(&wq);
+
+       /* tally up the counts */
+       for (i = 0; i < mp->m_sb.sb_agcount; i++) {
+               fdblocks += agcnts[i].fdblocks;
+               icount += agcnts[i].agicount;
+               ifreecount += agcnts[i].ifreecount;
+               usedblocks += agcnts[i].usedblocks;
+       }
+
+       free(agcnts);
+
+       /*
+        * Validate that our manual counts match the superblock.
+        */
+       if (mp->m_sb.sb_icount != icount) {
+               do_warn(_("sb_icount %" PRIu64 ", counted %" PRIu64 "\n"),
+                       mp->m_sb.sb_icount, icount);
+       }
+
+       if (mp->m_sb.sb_ifree != ifreecount) {
+               do_warn(_("sb_ifree %" PRIu64 ", counted %" PRIu64 "\n"),
+                       mp->m_sb.sb_ifree, ifreecount);
+       }
+
+       if (mp->m_sb.sb_fdblocks != fdblocks) {
+               do_warn(_("sb_fdblocks %" PRIu64 ", counted %" PRIu64 "\n"),
+                       mp->m_sb.sb_fdblocks, fdblocks);
+       }
+
+       if (usedblocks &&
+           usedblocks != mp->m_sb.sb_dblocks - fdblocks) {
+               do_warn(_("used blocks %" PRIu64 ", counted %" PRIu64 "\n"),
+                       mp->m_sb.sb_dblocks - fdblocks, usedblocks);
+       }
 }