| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include "avl.h" |
| #include "globals.h" |
| #include "agheader.h" |
| #include "incore.h" |
| #include "protos.h" |
| #include "err_protos.h" |
| #include "dinode.h" |
| #include "scan.h" |
| #include "versions.h" |
| #include "bmap.h" |
| #include "progress.h" |
| #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) |
| { |
| libxfs_bcache_purge(); |
| mp = mpp; |
| } |
| |
| /* |
| * Read a buffer into memory, even if it fails verifier checks. |
| * If an IO error happens, return a zeroed buffer. |
| */ |
| static inline int |
| salvage_buffer( |
| struct xfs_buftarg *target, |
| xfs_daddr_t blkno, |
| size_t numblks, |
| struct xfs_buf **bpp, |
| const struct xfs_buf_ops *ops) |
| { |
| int error; |
| |
| error = -libxfs_buf_read(target, blkno, numblks, |
| LIBXFS_READBUF_SALVAGE, bpp, ops); |
| if (error != EIO) |
| return error; |
| |
| /* |
| * If the read produced an IO error, grab the buffer (which will now |
| * be full of zeroes) and make it look like we read the data from the |
| * disk but it failed verification. |
| */ |
| error = -libxfs_buf_get(target, blkno, numblks, bpp); |
| if (error) |
| return error; |
| |
| (*bpp)->b_error = -EFSCORRUPTED; |
| (*bpp)->b_ops = ops; |
| return 0; |
| } |
| |
| static void |
| scan_sbtree( |
| xfs_agblock_t root, |
| int nlevels, |
| xfs_agnumber_t agno, |
| int suspect, |
| void (*func)(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 struct xfs_buf_ops *ops), |
| int isroot, |
| uint32_t magic, |
| void *priv, |
| const struct xfs_buf_ops *ops) |
| { |
| struct xfs_buf *bp; |
| int error; |
| |
| error = salvage_buffer(mp->m_dev, XFS_AGB_TO_DADDR(mp, agno, root), |
| XFS_FSB_TO_BB(mp, 1), &bp, ops); |
| if (error) { |
| do_error(_("can't read btree block %d/%d\n"), agno, root); |
| return; |
| } |
| 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, ops); |
| libxfs_buf_relse(bp); |
| } |
| |
| /* |
| * returns 1 on bad news (inode needs to be cleared), 0 on good |
| */ |
| int |
| scan_lbtree( |
| xfs_fsblock_t root, |
| int nlevels, |
| int (*func)(struct xfs_btree_block *block, |
| int level, |
| int type, |
| int whichfork, |
| xfs_fsblock_t bno, |
| xfs_ino_t ino, |
| xfs_rfsblock_t *tot, |
| xfs_extnum_t *nex, |
| blkmap_t **blkmapp, |
| bmap_cursor_t *bm_cursor, |
| int isroot, |
| int check_dups, |
| int *dirty, |
| uint64_t magic), |
| int type, |
| int whichfork, |
| xfs_ino_t ino, |
| xfs_rfsblock_t *tot, |
| xfs_extnum_t *nex, |
| blkmap_t **blkmapp, |
| bmap_cursor_t *bm_cursor, |
| int isroot, |
| int check_dups, |
| uint64_t magic, |
| const struct xfs_buf_ops *ops) |
| { |
| struct xfs_buf *bp; |
| int err; |
| int dirty = 0; |
| bool badcrc = false; |
| |
| err = salvage_buffer(mp->m_dev, XFS_FSB_TO_DADDR(mp, root), |
| XFS_FSB_TO_BB(mp, 1), &bp, ops); |
| if (err) { |
| 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, |
| magic); |
| |
| ASSERT(dirty == 0 || (dirty && !no_modify)); |
| |
| if ((dirty || badcrc) && !no_modify) { |
| libxfs_buf_mark_dirty(bp); |
| libxfs_buf_relse(bp); |
| } |
| else |
| libxfs_buf_relse(bp); |
| |
| return(err); |
| } |
| |
| int |
| scan_bmapbt( |
| struct xfs_btree_block *block, |
| int level, |
| int type, |
| int whichfork, |
| xfs_fsblock_t bno, |
| xfs_ino_t ino, |
| xfs_rfsblock_t *tot, |
| xfs_extnum_t *nex, |
| blkmap_t **blkmapp, |
| bmap_cursor_t *bm_cursor, |
| int isroot, |
| int check_dups, |
| 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_fileoff_t first_key; |
| xfs_fileoff_t last_key; |
| char *forkname = get_forkname(whichfork); |
| xfs_extnum_t numrecs; |
| xfs_agnumber_t agno; |
| xfs_agblock_t agbno; |
| int state; |
| int error; |
| |
| /* |
| * unlike the ag freeblock btrees, if anything looks wrong |
| * in an inode bmap tree, just bail. it's possible that |
| * we'll miss a case where the to-be-toasted inode and |
| * another inode are claiming the same block but that's |
| * highly unlikely. |
| */ |
| 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 %" 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 != NULLFSBLOCK) { |
| /* |
| * this is not the first block on this level |
| * so the cursor for this level has recorded the |
| * values for this's block left-sibling. |
| */ |
| if (bno != bm_cursor->level[level].right_fsbno) { |
| do_warn( |
| _("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); |
| return(1); |
| } |
| 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 %" 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); |
| } |
| } else { |
| /* |
| * 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) != NULLFSBLOCK) { |
| do_warn( |
| _("bad back (left) sibling pointer (saw %llu should be NULL (0))\n" |
| "\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); |
| } |
| } |
| |
| /* |
| * update cursor block pointers to reflect this block |
| */ |
| bm_cursor->level[level].fsbno = bno; |
| bm_cursor->level[level].left_fsbno = |
| be64_to_cpu(block->bb_u.l.bb_leftsib); |
| bm_cursor->level[level].right_fsbno = |
| be64_to_cpu(block->bb_u.l.bb_rightsib); |
| |
| 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: |
| set_bmap(agno, agbno, XR_E_INUSE); |
| break; |
| case XR_E_FS_MAP: |
| case XR_E_INUSE: |
| /* |
| * we'll try and continue searching here since |
| * the block looks like it's been claimed by file |
| * to store user data, a directory to store directory |
| * data, or the space allocation btrees but since |
| * we made it here, the block probably |
| * contains btree data. |
| */ |
| set_bmap(agno, agbno, XR_E_MULT); |
| do_warn( |
| _("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%" 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 |
| * so the block will be valid. Both files should be |
| * trashed along with any other file that impinges on |
| * any blocks referenced by either file. So we |
| * continue searching down this btree to mark all |
| * blocks duplicate |
| */ |
| break; |
| case XR_E_BAD_STATE: |
| default: |
| do_warn( |
| _("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 |
| * filesystem |
| */ |
| if (type != XR_INO_RTDATA || whichfork != XFS_DATA_FORK) { |
| if (search_dup_extent(XFS_FSB_TO_AGNO(mp, bno), |
| XFS_FSB_TO_AGBNO(mp, bno), |
| XFS_FSB_TO_AGBNO(mp, bno) + 1)) |
| return(1); |
| } else { |
| if (search_rt_dup_extent(mp, bno)) |
| return(1); |
| } |
| } |
| (*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 %" PRIu64 " bad # of bmap records (%" PRIu64 ", min - %u, max - %u)\n"), |
| ino, numrecs, mp->m_bmap_dmnr[0], |
| mp->m_bmap_dmxr[0]); |
| return(1); |
| } |
| rp = XFS_BMBT_REC_ADDR(mp, block, 1); |
| *nex += numrecs; |
| /* |
| * XXX - if we were going to fix up the btree record, |
| * we'd do it right here. For now, if there's a problem, |
| * 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); |
| if (err) |
| return 1; |
| |
| /* |
| * check that key ordering is monotonically increasing. |
| * if the last_key value in the cursor is set to |
| * 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 != |
| NULLFILEOFF) { |
| do_warn( |
| _("out-of-order bmap key (file offset) in inode %" PRIu64 ", %s fork, fsbno %" PRIu64 "\n"), |
| ino, forkname, bno); |
| return(1); |
| } |
| /* |
| * update cursor keys to reflect this block. |
| * don't have to check if last_key is > first_key |
| * since that gets checked by process_bmbt_reclist. |
| */ |
| 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); |
| } |
| } |
| if (numrecs > mp->m_bmap_dmxr[1] || (isroot == 0 && numrecs < |
| mp->m_bmap_dmnr[1])) { |
| do_warn( |
| _("inode %" PRIu64 " bad # of bmap records (%" PRIu64 ", 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 = NULLFILEOFF; |
| |
| for (i = 0, err = 0; i < numrecs; i++) { |
| /* |
| * XXX - if we were going to fix up the interior btree nodes, |
| * we'd do it right here. For now, if there's a problem, |
| * we'll bail out and presumably clear the inode. |
| */ |
| if (!libxfs_verify_fsbno(mp, be64_to_cpu(pp[i]))) { |
| 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, scan_bmapbt, |
| type, whichfork, ino, tot, nex, blkmapp, |
| bm_cursor, 0, check_dups, magic, |
| &xfs_bmbt_buf_ops); |
| if (err) |
| return(1); |
| |
| /* |
| * fix key (offset) mismatches between the first key |
| * in the child block (as recorded in the cursor) and the |
| * key in the interior node referencing the child block. |
| * |
| * fixes cases where entries have been shifted between |
| * child blocks but the parent hasn't been updated. We |
| * don't have to worry about the key values in the cursor |
| * not being set since we only look at the key values of |
| * our child and those are guaranteed to be set by the |
| * call to scan_lbtree() above. |
| */ |
| if (check_dups == 0 && be64_to_cpu(pkey[i].br_startoff) != |
| bm_cursor->level[level-1].first_key) { |
| if (!no_modify) { |
| do_warn( |
| _("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); |
| *dirty = 1; |
| pkey[i].br_startoff = cpu_to_be64( |
| bm_cursor->level[level-1].first_key); |
| } else { |
| do_warn( |
| _("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); |
| } |
| } |
| } |
| |
| /* |
| * If we're the last node at our level, check that the last child |
| * block's forward sibling pointer is NULL. |
| */ |
| if (check_dups == 0 && |
| bm_cursor->level[level].right_fsbno == NULLFSBLOCK && |
| bm_cursor->level[level - 1].right_fsbno != NULLFSBLOCK) { |
| do_warn( |
| _("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); |
| } |
| |
| /* |
| * update cursor keys to reflect this block |
| */ |
| if (check_dups == 0) { |
| bm_cursor->level[level].first_key = |
| be64_to_cpu(pkey[0].br_startoff); |
| bm_cursor->level[level].last_key = |
| be64_to_cpu(pkey[numrecs - 1].br_startoff); |
| } |
| |
| return(0); |
| } |
| |
| 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, |
| void *priv, |
| const struct xfs_buf_ops *ops) |
| { |
| struct aghdr_cnts *agcnts = priv; |
| struct xfs_perag *pag; |
| const char *name; |
| int i; |
| xfs_alloc_ptr_t *pp; |
| xfs_alloc_rec_t *rp; |
| int hdr_errors = 0; |
| int numrecs; |
| int state; |
| xfs_extlen_t lastcount = 0; |
| xfs_agblock_t lastblock = 0; |
| |
| 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"), |
| be32_to_cpu(block->bb_magic), name, agno, bno); |
| hdr_errors++; |
| 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); |
| hdr_errors++; |
| if (suspect) |
| return; |
| } |
| |
| /* |
| * check for btree blocks multiply claimed |
| */ |
| state = get_bmap(agno, bno); |
| 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"), |
| name, state, agno, bno, suspect); |
| return; |
| } |
| set_bmap(agno, bno, XR_E_FS_MAP); |
| |
| 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++; |
| } |
| if (isroot == 0 && numrecs < mp->m_alloc_mnr[0]) { |
| numrecs = mp->m_alloc_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_alloc_mnr[0], mp->m_alloc_mxr[0], |
| name, agno, bno); |
| suspect++; |
| } |
| |
| rp = XFS_ALLOC_REC_ADDR(mp, block, 1); |
| pag = libxfs_perag_get(mp, agno); |
| |
| for (i = 0; i < numrecs; i++) { |
| xfs_agblock_t b, end; |
| xfs_extlen_t len, blen; |
| |
| b = be32_to_cpu(rp[i].ar_startblock); |
| len = be32_to_cpu(rp[i].ar_blockcount); |
| end = b + len; |
| |
| if (!libxfs_verify_agbno(pag, 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 || end <= b || |
| !libxfs_verify_agbno(pag, 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 = end - 1; |
| } |
| } 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); |
| switch (state) { |
| case XR_E_UNKNOWN: |
| set_bmap_ext(agno, b, blen, XR_E_FREE1); |
| break; |
| case XR_E_FREE1: |
| /* |
| * no warning messages -- we'll catch |
| * FREE1 blocks later |
| */ |
| if (magic == XFS_ABTC_MAGIC || |
| magic == XFS_ABTC_CRC_MAGIC) { |
| set_bmap_ext(agno, b, blen, |
| XR_E_FREE); |
| break; |
| } |
| fallthrough; |
| default: |
| do_warn( |
| _("block (%d,%d-%d) multiply claimed by %s space tree, state - %d\n"), |
| agno, b, b + blen - 1, |
| name, state); |
| break; |
| } |
| } |
| } |
| libxfs_perag_put(pag); |
| return; |
| } |
| |
| /* |
| * interior record |
| */ |
| pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]); |
| |
| if (numrecs > mp->m_alloc_mxr[1]) { |
| numrecs = mp->m_alloc_mxr[1]; |
| hdr_errors++; |
| } |
| if (isroot == 0 && numrecs < mp->m_alloc_mnr[1]) { |
| numrecs = mp->m_alloc_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_alloc_mnr[1], mp->m_alloc_mxr[1], |
| name, agno, bno); |
| if (suspect) |
| return; |
| suspect++; |
| } else if (suspect) { |
| suspect = 0; |
| } |
| |
| pag = libxfs_perag_get(mp, agno); |
| for (i = 0; i < numrecs; i++) { |
| xfs_agblock_t agbno = be32_to_cpu(pp[i]); |
| |
| if (!libxfs_verify_agbno(pag, agbno)) { |
| do_warn( |
| _("bad btree pointer (%u) in %sbt block %u/%u\n"), |
| agbno, name, agno, bno); |
| suspect++; |
| libxfs_perag_put(pag); |
| return; |
| } |
| |
| /* |
| * 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. |
| */ |
| scan_sbtree(agbno, level, agno, suspect, scan_allocbt, 0, |
| magic, priv, ops); |
| } |
| libxfs_perag_put(pag); |
| } |
| |
| static bool |
| ino_issparse( |
| struct xfs_inobt_rec *rp, |
| int offset) |
| { |
| if (!xfs_has_sparseinodes(mp)) |
| return false; |
| |
| return xfs_inobt_is_sparse_disk(rp, offset); |
| } |
| |
| /* See if the rmapbt owners agree with our observations. */ |
| static void |
| process_rmap_rec( |
| struct xfs_mount *mp, |
| xfs_agnumber_t agno, |
| xfs_agblock_t b, |
| xfs_agblock_t end, |
| xfs_extlen_t blen, |
| int64_t owner, |
| int state, |
| const char *name) |
| { |
| 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( |
| _("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( |
| _("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( |
| _("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: |
| /* |
| * May be on the AGFL. If not, they'll |
| * be caught later. |
| */ |
| break; |
| case XR_E_INUSE1: |
| /* |
| * multiple inode owners are ok with |
| * reflink enabled |
| */ |
| if (xfs_has_reflink(mp) && |
| !XFS_RMAP_NON_INODE_OWNER(owner)) |
| break; |
| fallthrough; |
| 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; |
| } |
| } |
| |
| 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 laststartblock, |
| uint64_t owner, |
| uint64_t lastowner, |
| uint64_t offset, |
| uint64_t lastoffset) |
| { |
| if (b > laststartblock) |
| return true; |
| else if (b < laststartblock) |
| return false; |
| |
| if (owner > lastowner) |
| return true; |
| else if (owner < lastowner) |
| return false; |
| |
| return offset > lastoffset; |
| } |
| |
| static inline bool |
| verify_rmap_agbno( |
| struct xfs_mount *mp, |
| xfs_agnumber_t agno, |
| xfs_agblock_t agbno) |
| { |
| return agbno < libxfs_ag_block_count(mp, agno); |
| } |
| |
| |
| static void |
| scan_rmapbt( |
| 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 struct xfs_buf_ops *ops) |
| { |
| 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_agblock_t laststartblock = 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}; |
| struct xfs_perag *pag; |
| |
| if (magic != XFS_RMAP_CRC_MAGIC) { |
| name = "(unknown)"; |
| hdr_errors++; |
| suspect++; |
| goto out; |
| } |
| |
| 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++; |
| if (suspect) |
| goto out; |
| } |
| |
| /* |
| * 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_rmap_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 || end <= b || |
| !verify_rmap_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: |
| laststartblock = b; |
| lastblock = end - 1; |
| lastowner = owner; |
| lastoffset = offset; |
| } else { |
| bool bad; |
| |
| if (xfs_has_reflink(mp)) |
| bad = !rmap_in_order(b, laststartblock, |
| 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); |
| } |
| |
| pag = libxfs_perag_get(mp, agno); |
| 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 (!libxfs_verify_agbno(pag, agbno)) { |
| do_warn( |
| _("bad btree pointer (%u) in %sbt block %u/%u\n"), |
| agbno, name, agno, bno); |
| suspect++; |
| libxfs_perag_put(pag); |
| return; |
| } |
| |
| scan_sbtree(agbno, level, agno, suspect, scan_rmapbt, 0, magic, |
| priv, ops); |
| } |
| libxfs_perag_put(pag); |
| |
| 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 struct xfs_buf_ops *ops) |
| { |
| 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; |
| struct xfs_perag *pag; |
| |
| 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); |
| pag = libxfs_perag_get(mp, agno); |
| |
| for (i = 0; i < numrecs; i++) { |
| enum xfs_refc_domain domain; |
| 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_COWFLAG) { |
| domain = XFS_REFC_DOMAIN_COW; |
| agb &= ~XFS_REFC_COWFLAG; |
| } else { |
| domain = XFS_REFC_DOMAIN_SHARED; |
| } |
| |
| if (domain == XFS_REFC_DOMAIN_COW && 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 (domain != XFS_REFC_DOMAIN_COW) |
| do_warn( |
| _("leftover CoW extent has invalid startblock in record %u of %s btree block %u/%u\n"), |
| i, name, agno, bno); |
| } |
| end = agb + len; |
| |
| if (!libxfs_verify_agbno(pag, 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 || end <= agb || |
| !libxfs_verify_agbno(pag, 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 = end - 1; |
| } |
| |
| /* Is this record mergeable with the last one? */ |
| if (refc_priv->last_rec.rc_domain == domain && |
| refc_priv->last_rec.rc_startblock + |
| refc_priv->last_rec.rc_blockcount == agb && |
| 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_domain = domain; |
| refc_priv->last_rec.rc_startblock = agb; |
| refc_priv->last_rec.rc_blockcount = len; |
| refc_priv->last_rec.rc_refcount = nr; |
| } |
| |
| /* XXX: probably want to mark the reflinked areas? */ |
| } |
| libxfs_perag_put(pag); |
| 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; |
| } |
| |
| pag = libxfs_perag_get(mp, agno); |
| for (i = 0; i < numrecs; i++) { |
| xfs_agblock_t agbno = be32_to_cpu(pp[i]); |
| |
| if (!libxfs_verify_agbno(pag, agbno)) { |
| do_warn( |
| _("bad btree pointer (%u) in %sbt block %u/%u\n"), |
| agbno, name, agno, bno); |
| suspect++; |
| libxfs_perag_put(pag); |
| return; |
| } |
| |
| scan_sbtree(agbno, level, agno, suspect, scan_refcbt, 0, magic, |
| priv, ops); |
| } |
| libxfs_perag_put(pag); |
| 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; |
| struct xfs_perag *pag; |
| |
| *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. |
| */ |
| pag = libxfs_perag_get(mp, agno); |
| if (!libxfs_verify_agino(pag, ino)) { |
| do_warn( |
| _("bad starting inode # (%" PRIu64 " (0x%x 0x%x)) in %s rec, skipping rec\n"), |
| lino, agno, ino, inobt_name); |
| *skip = true; |
| libxfs_perag_put(pag); |
| return ++suspect; |
| } |
| |
| if (!libxfs_verify_agino(pag, 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; |
| libxfs_perag_put(pag); |
| return ++suspect; |
| } |
| |
| libxfs_perag_put(pag); |
| 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; |
| 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_has_sparseinodes(mp) && |
| 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; |
| 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_has_sparseinodes(mp) && |
| 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; |
| } |
| |
| struct ino_priv { |
| struct aghdr_cnts *agcnts; |
| uint32_t ino_blocks; |
| uint32_t fino_blocks; |
| }; |
| |
| /* |
| * 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, |
| const struct xfs_buf_ops *ops) |
| { |
| struct ino_priv *ipriv = priv; |
| struct aghdr_cnts *agcnts = ipriv->agcnts; |
| char *name; |
| xfs_agino_t lastino = 0; |
| int i; |
| int numrecs; |
| int state; |
| xfs_inobt_ptr_t *pp; |
| xfs_inobt_rec_t *rp; |
| int hdr_errors; |
| int freecount; |
| struct xfs_ino_geometry *igeo = M_IGEO(mp); |
| struct xfs_perag *pag; |
| |
| hdr_errors = 0; |
| |
| switch (magic) { |
| case XFS_FIBT_MAGIC: |
| case XFS_FIBT_CRC_MAGIC: |
| name = "fino"; |
| ipriv->fino_blocks++; |
| break; |
| case XFS_IBT_MAGIC: |
| case XFS_IBT_CRC_MAGIC: |
| name = "ino"; |
| ipriv->ino_blocks++; |
| break; |
| default: |
| name = "(unknown)"; |
| assert(0); |
| break; |
| } |
| |
| if (be32_to_cpu(block->bb_magic) != magic) { |
| do_warn(_("bad magic # %#x in %sbt block %d/%d\n"), |
| be32_to_cpu(block->bb_magic), name, 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 %sbt block %d/%d\n"), |
| level, be16_to_cpu(block->bb_level), name, agno, bno); |
| hdr_errors++; |
| bad_ino_btree = 1; |
| if (suspect) |
| return; |
| } |
| |
| /* |
| * check for btree blocks multiply claimed, any unknown/free state |
| * is ok in the bitmap block. |
| */ |
| 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: |
| set_bmap(agno, bno, XR_E_FS_MAP); |
| break; |
| default: |
| set_bmap(agno, bno, XR_E_MULT); |
| do_warn( |
| _("%sbt btree block claimed (state %d), agno %d, bno %d, suspect %d\n"), |
| name, state, agno, bno, suspect); |
| } |
| |
| numrecs = be16_to_cpu(block->bb_numrecs); |
| |
| /* |
| * leaf record in btree |
| */ |
| if (level == 0) { |
| /* check for trashed btree block */ |
| |
| if (numrecs > igeo->inobt_mxr[0]) { |
| numrecs = igeo->inobt_mxr[0]; |
| hdr_errors++; |
| } |
| if (isroot == 0 && numrecs < igeo->inobt_mnr[0]) { |
| numrecs = igeo->inobt_mnr[0]; |
| hdr_errors++; |
| } |
| |
| if (hdr_errors) { |
| bad_ino_btree = 1; |
| do_warn(_("dubious %sbt btree block header %d/%d\n"), |
| name, agno, bno); |
| suspect++; |
| } |
| |
| rp = XFS_INOBT_REC_ADDR(mp, block, 1); |
| |
| /* |
| * step through the records, each record points to |
| * a chunk of inodes. The start of inode chunks should |
| * be block-aligned. Each inode btree rec should point |
| * to the start of a block of inodes or the start of a group |
| * of INODES_PER_CHUNK (64) inodes. off is the offset into |
| * the block. skip processing of bogus records. |
| */ |
| for (i = 0; i < numrecs; i++) { |
| xfs_agino_t startino; |
| |
| freecount = inorec_get_freecount(mp, &rp[i]); |
| startino = be32_to_cpu(rp[i].ir_startino); |
| if (i > 0 && startino <= lastino) |
| do_warn(_( |
| "out-of-order %s btree record %d (%u) block %u/%u\n"), |
| name, i, startino, agno, bno); |
| else |
| lastino = startino + XFS_INODES_PER_CHUNK - 1; |
| |
| 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_has_sparseinodes(mp)) |
| 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; |
| |
| return; |
| } |
| |
| /* |
| * interior record, continue on |
| */ |
| if (numrecs > igeo->inobt_mxr[1]) { |
| numrecs = igeo->inobt_mxr[1]; |
| hdr_errors++; |
| } |
| if (isroot == 0 && numrecs < igeo->inobt_mnr[1]) { |
| numrecs = igeo->inobt_mnr[1]; |
| hdr_errors++; |
| } |
| |
| pp = XFS_INOBT_PTR_ADDR(mp, block, 1, igeo->inobt_mxr[1]); |
| |
| /* |
| * 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) { |
| bad_ino_btree = 1; |
| if (suspect) |
| return; |
| else suspect++; |
| } |
| |
| pag = libxfs_perag_get(mp, agno); |
| for (i = 0; i < numrecs; i++) { |
| xfs_agblock_t agbno = be32_to_cpu(pp[i]); |
| |
| if (!libxfs_verify_agbno(pag, agbno)) { |
| do_warn( |
| _("bad btree pointer (%u) in %sbt block %u/%u\n"), |
| agbno, name, agno, bno); |
| suspect++; |
| libxfs_perag_put(pag); |
| return; |
| } |
| |
| scan_sbtree(be32_to_cpu(pp[i]), level, agno, suspect, |
| scan_inobt, 0, magic, priv, ops); |
| } |
| libxfs_perag_put(pag); |
| } |
| |
| 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; |
| struct xfs_perag *pag; |
| |
| pag = libxfs_perag_get(mp, as->agno); |
| if (libxfs_verify_agbno(pag, bno)) |
| set_bmap(as->agno, bno, XR_E_FREE); |
| else |
| do_warn(_("bad agbno %u in agfl, agno %d\n"), |
| bno, as->agno); |
| |
| libxfs_perag_put(pag); |
| as->count++; |
| return 0; |
| } |
| |
| static void |
| scan_freelist( |
| xfs_agf_t *agf, |
| struct aghdr_cnts *agcnts) |
| { |
| struct xfs_buf *agflbuf; |
| xfs_agnumber_t agno; |
| struct agfl_state state; |
| int error; |
| |
| 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_INUSE_FS); |
| |
| if (be32_to_cpu(agf->agf_flcount) == 0) |
| return; |
| |
| error = salvage_buffer(mp->m_dev, |
| XFS_AG_DADDR(mp, agno, XFS_AGFL_DADDR(mp)), |
| XFS_FSS_TO_BB(mp, 1), &agflbuf, &xfs_agfl_buf_ops); |
| if (error) { |
| do_abort(_("can't read agfl block for ag %d\n"), agno); |
| return; |
| } |
| 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; |
| } |
| } |
| |
| 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_buf_relse(agflbuf); |
| } |
| |
| static void |
| validate_agf( |
| struct xfs_agf *agf, |
| xfs_agnumber_t agno, |
| struct aghdr_cnts *agcnts) |
| { |
| xfs_agblock_t bno; |
| uint32_t magic; |
| unsigned int levels; |
| struct xfs_perag *pag = libxfs_perag_get(mp, agno); |
| |
| levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]); |
| if (levels == 0 || levels > mp->m_alloc_maxlevels) { |
| do_warn(_("bad levels %u for btbno root, agno %d\n"), |
| levels, agno); |
| } |
| |
| bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]); |
| if (libxfs_verify_agbno(pag, bno)) { |
| magic = xfs_has_crc(mp) ? 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_bnobt_buf_ops); |
| } else { |
| do_warn(_("bad agbno %u for btbno root, agno %d\n"), |
| bno, agno); |
| } |
| |
| levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]); |
| if (levels == 0 || levels > mp->m_alloc_maxlevels) { |
| do_warn(_("bad levels %u for btbcnt root, agno %d\n"), |
| levels, agno); |
| } |
| |
| bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]); |
| if (libxfs_verify_agbno(pag, bno)) { |
| magic = xfs_has_crc(mp) ? 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_cntbt_buf_ops); |
| } else { |
| do_warn(_("bad agbno %u for btbcnt root, agno %d\n"), |
| bno, agno); |
| } |
| |
| if (xfs_has_rmapbt(mp)) { |
| struct rmap_priv priv; |
| |
| 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; |
| |
| levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_RMAP]); |
| if (levels == 0 || levels > mp->m_rmap_maxlevels) { |
| do_warn(_("bad levels %u for rmapbt root, agno %d\n"), |
| levels, agno); |
| rmap_avoid_check(); |
| } |
| |
| bno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_RMAP]); |
| if (libxfs_verify_agbno(pag, bno)) { |
| scan_sbtree(bno, levels, 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_has_reflink(mp)) { |
| levels = be32_to_cpu(agf->agf_refcount_level); |
| if (levels == 0 || levels > mp->m_refc_maxlevels) { |
| do_warn(_("bad levels %u for refcountbt root, agno %d\n"), |
| levels, agno); |
| refcount_avoid_check(); |
| } |
| |
| bno = be32_to_cpu(agf->agf_refcount_root); |
| if (libxfs_verify_agbno(pag, bno)) { |
| struct refc_priv priv; |
| |
| memset(&priv, 0, sizeof(priv)); |
| scan_sbtree(bno, levels, 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_has_lazysbcount(mp) && |
| 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); |
| } |
| libxfs_perag_put(pag); |
| |
| } |
| |
| static void |
| validate_agi( |
| struct xfs_agi *agi, |
| xfs_agnumber_t agno, |
| struct aghdr_cnts *agcnts) |
| { |
| struct ino_priv priv = { |
| .agcnts = agcnts, |
| }; |
| xfs_agblock_t bno; |
| int i; |
| uint32_t magic; |
| unsigned int levels; |
| struct xfs_perag *pag = libxfs_perag_get(mp, agno); |
| |
| levels = be32_to_cpu(agi->agi_level); |
| if (levels == 0 || levels > M_IGEO(mp)->inobt_maxlevels) { |
| do_warn(_("bad levels %u for inobt root, agno %d\n"), |
| levels, agno); |
| } |
| |
| bno = be32_to_cpu(agi->agi_root); |
| if (libxfs_verify_agbno(pag, bno)) { |
| magic = xfs_has_crc(mp) ? XFS_IBT_CRC_MAGIC |
| : XFS_IBT_MAGIC; |
| scan_sbtree(bno, be32_to_cpu(agi->agi_level), |
| agno, 0, scan_inobt, 1, magic, &priv, |
| &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_has_finobt(mp)) { |
| levels = be32_to_cpu(agi->agi_free_level); |
| if (levels == 0 || levels > M_IGEO(mp)->inobt_maxlevels) { |
| do_warn(_("bad levels %u for finobt root, agno %d\n"), |
| levels, agno); |
| } |
| |
| bno = be32_to_cpu(agi->agi_free_root); |
| if (libxfs_verify_agbno(pag, bno)) { |
| magic = xfs_has_crc(mp) ? |
| XFS_FIBT_CRC_MAGIC : XFS_FIBT_MAGIC; |
| scan_sbtree(bno, be32_to_cpu(agi->agi_free_level), |
| agno, 0, scan_inobt, 1, magic, &priv, |
| &xfs_finobt_buf_ops); |
| } else { |
| do_warn(_("bad agbno %u for finobt root, agno %d\n"), |
| be32_to_cpu(agi->agi_free_root), agno); |
| } |
| } |
| |
| if (xfs_has_inobtcounts(mp)) { |
| if (be32_to_cpu(agi->agi_iblocks) != priv.ino_blocks) |
| do_warn(_("bad inobt block count %u, saw %u\n"), |
| be32_to_cpu(agi->agi_iblocks), |
| priv.ino_blocks); |
| if (be32_to_cpu(agi->agi_fblocks) != priv.fino_blocks) |
| do_warn(_("bad finobt block count %u, saw %u\n"), |
| be32_to_cpu(agi->agi_fblocks), |
| priv.fino_blocks); |
| } |
| |
| 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_has_finobt(mp) && |
| 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)); |
| } |
| } |
| libxfs_perag_put(pag); |
| } |
| |
| /* |
| * 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; |
| int error; |
| |
| sb = (struct xfs_sb *)calloc(BBTOB(XFS_FSS_TO_BB(mp, 1)), 1); |
| if (!sb) { |
| do_error(_("can't allocate memory for superblock\n")); |
| return; |
| } |
| |
| error = salvage_buffer(mp->m_dev, XFS_AG_DADDR(mp, agno, XFS_SB_DADDR), |
| XFS_FSS_TO_BB(mp, 1), &sbbuf, &xfs_sb_buf_ops); |
| if (error) { |
| objname = _("root superblock"); |
| goto out_free_sb; |
| } |
| if (sbbuf->b_error == -EFSBADCRC) |
| do_warn(_("superblock has bad CRC for ag %d\n"), agno); |
| libxfs_sb_from_disk(sb, sbbuf->b_addr); |
| |
| error = salvage_buffer(mp->m_dev, |
| XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)), |
| XFS_FSS_TO_BB(mp, 1), &agfbuf, &xfs_agf_buf_ops); |
| if (error) { |
| objname = _("agf block"); |
| goto out_free_sbbuf; |
| } |
| if (agfbuf->b_error == -EFSBADCRC) |
| do_warn(_("agf has bad CRC for ag %d\n"), agno); |
| agf = agfbuf->b_addr; |
| |
| error = salvage_buffer(mp->m_dev, |
| XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)), |
| XFS_FSS_TO_BB(mp, 1), &agibuf, &xfs_agi_buf_ops); |
| if (error) { |
| objname = _("agi block"); |
| goto out_free_agfbuf; |
| } |
| if (agibuf->b_error == -EFSBADCRC) |
| do_warn(_("agi has bad CRC for ag %d\n"), agno); |
| agi = agibuf->b_addr; |
| |
| /* fix up bad ag headers */ |
| |
| status = verify_set_agheader(mp, sbbuf, sb, agf, agi, agno); |
| |
| if (status & XR_AG_SB_SEC) { |
| if (!no_modify) |
| sb_dirty = 1; |
| /* |
| * clear bad sector bit because we don't want |
| * to skip further processing. we just want to |
| * ensure that we write out the modified sb buffer. |
| */ |
| status &= ~XR_AG_SB_SEC; |
| } |
| if (status & XR_AG_SB) { |
| if (!no_modify) { |
| do_warn(_("reset bad sb for ag %d\n"), agno); |
| sb_dirty = 1; |
| } else { |
| do_warn(_("would reset bad sb for ag %d\n"), agno); |
| } |
| } |
| if (status & XR_AG_AGF) { |
| if (!no_modify) { |
| do_warn(_("reset bad agf for ag %d\n"), agno); |
| agf_dirty = 1; |
| } else { |
| do_warn(_("would reset bad agf for ag %d\n"), agno); |
| } |
| } |
| if (status & XR_AG_AGI) { |
| if (!no_modify) { |
| do_warn(_("reset bad agi for ag %d\n"), agno); |
| agi_dirty = 1; |
| } else { |
| do_warn(_("would reset bad agi for ag %d\n"), agno); |
| } |
| } |
| |
| if (status && no_modify) { |
| do_warn(_("bad uncorrected agheader %d, skipping ag...\n"), |
| agno); |
| goto out_free_agibuf; |
| } |
| |
| scan_freelist(agf, agcnts); |
| |
| 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_buf_mark_dirty(agibuf); |
| libxfs_buf_relse(agibuf); |
| } |
| else |
| libxfs_buf_relse(agibuf); |
| |
| if (agf_dirty && !no_modify) { |
| libxfs_buf_mark_dirty(agfbuf); |
| libxfs_buf_relse(agfbuf); |
| } |
| else |
| libxfs_buf_relse(agfbuf); |
| |
| if (sb_dirty && !no_modify) { |
| if (agno == 0) |
| memcpy(&mp->m_sb, sb, sizeof(xfs_sb_t)); |
| libxfs_sb_to_disk(sbbuf->b_addr, sb); |
| libxfs_buf_mark_dirty(sbbuf); |
| libxfs_buf_relse(sbbuf); |
| } else |
| libxfs_buf_relse(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_buf_relse(agibuf); |
| out_free_agfbuf: |
| libxfs_buf_relse(agfbuf); |
| out_free_sbbuf: |
| libxfs_buf_relse(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); |
| } |
| } |