mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-16 16:38:56 +00:00

Zone file systems reuse the basic RT group enabled XFS file system structure to support a mode where each RT group is always written from start to end and then reset for reuse (after moving out any remaining data). There are few minor but important changes, which are indicated by a new incompat flag: 1) there are no bitmap and summary inodes, thus the /rtgroups/{rgno}.{bitmap,summary} metadir files do not exist and the sb_rbmblocks superblock field must be cleared to zero. 2) there is a new superblock field that specifies the start of an internal RT section. This allows supporting SMR HDDs that have random writable space at the beginning which is used for the XFS data device (which really is the metadata device for this configuration), directly followed by a RT device on the same block device. While something similar could be achieved using dm-linear just having a single device directly consumed by XFS makes handling the file systems a lot easier. 3) Another superblock field that tracks the amount of reserved space (or overprovisioning) that is never used for user capacity, but allows GC to run more smoothly. 4) an overlay of the cowextsize field for the rtrmap inode so that we can persistently track the total amount of rtblocks currently used in a RT group. There is no data structure other than the rmap that tracks used space in an RT group, and this counter is used to decide when a RT group has been entirely emptied, and to select one that is relatively empty if garbage collection needs to be performed. While this counter could be tracked entirely in memory and rebuilt from the rmap at mount time, that would lead to very long mount times with the large number of RT groups implied by the number of hardware zones especially on SMR hard drives with 256MB zone sizes. Signed-off-by: Christoph Hellwig <hch@lst.de> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
1062 lines
28 KiB
C
1062 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2017-2023 Oracle. All Rights Reserved.
|
|
* Author: Darrick J. Wong <djwong@kernel.org>
|
|
*/
|
|
#include "xfs.h"
|
|
#include "xfs_fs.h"
|
|
#include "xfs_shared.h"
|
|
#include "xfs_format.h"
|
|
#include "xfs_trans_resv.h"
|
|
#include "xfs_mount.h"
|
|
#include "xfs_btree.h"
|
|
#include "xfs_sb.h"
|
|
#include "xfs_alloc.h"
|
|
#include "xfs_ialloc.h"
|
|
#include "xfs_rmap.h"
|
|
#include "xfs_ag.h"
|
|
#include "xfs_inode.h"
|
|
#include "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
|
|
int
|
|
xchk_setup_agheader(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
if (xchk_need_intent_drain(sc))
|
|
xchk_fsgates_enable(sc, XCHK_FSGATES_DRAIN);
|
|
return xchk_setup_fs(sc);
|
|
}
|
|
|
|
/* Superblock */
|
|
|
|
/* Cross-reference with the other btrees. */
|
|
STATIC void
|
|
xchk_superblock_xref(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_buf *bp)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_agnumber_t agno = sc->sm->sm_agno;
|
|
xfs_agblock_t agbno;
|
|
int error;
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return;
|
|
|
|
agbno = XFS_SB_BLOCK(mp);
|
|
|
|
error = xchk_ag_init_existing(sc, agno, &sc->sa);
|
|
if (!xchk_xref_process_error(sc, agno, agbno, &error))
|
|
return;
|
|
|
|
xchk_xref_is_used_space(sc, agbno, 1);
|
|
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
|
|
xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
|
|
xchk_xref_is_not_shared(sc, agbno, 1);
|
|
xchk_xref_is_not_cow_staging(sc, agbno, 1);
|
|
|
|
/* scrub teardown will take care of sc->sa for us */
|
|
}
|
|
|
|
/*
|
|
* Calculate the ondisk superblock size in bytes given the feature set of the
|
|
* mounted filesystem (aka the primary sb). This is subtlely different from
|
|
* the logic in xfs_repair, which computes the size of a secondary sb given the
|
|
* featureset listed in the secondary sb.
|
|
*/
|
|
STATIC size_t
|
|
xchk_superblock_ondisk_size(
|
|
struct xfs_mount *mp)
|
|
{
|
|
if (xfs_has_zoned(mp))
|
|
return offsetofend(struct xfs_dsb, sb_rtreserved);
|
|
if (xfs_has_metadir(mp))
|
|
return offsetofend(struct xfs_dsb, sb_pad);
|
|
if (xfs_has_metauuid(mp))
|
|
return offsetofend(struct xfs_dsb, sb_meta_uuid);
|
|
if (xfs_has_crc(mp))
|
|
return offsetofend(struct xfs_dsb, sb_lsn);
|
|
if (xfs_sb_version_hasmorebits(&mp->m_sb))
|
|
return offsetofend(struct xfs_dsb, sb_bad_features2);
|
|
if (xfs_has_logv2(mp))
|
|
return offsetofend(struct xfs_dsb, sb_logsunit);
|
|
if (xfs_has_sector(mp))
|
|
return offsetofend(struct xfs_dsb, sb_logsectsize);
|
|
/* only support dirv2 or more recent */
|
|
return offsetofend(struct xfs_dsb, sb_dirblklog);
|
|
}
|
|
|
|
/*
|
|
* Scrub the filesystem superblock.
|
|
*
|
|
* Note: We do /not/ attempt to check AG 0's superblock. Mount is
|
|
* responsible for validating all the geometry information in sb 0, so
|
|
* if the filesystem is capable of initiating online scrub, then clearly
|
|
* sb 0 is ok and we can use its information to check everything else.
|
|
*/
|
|
int
|
|
xchk_superblock(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
struct xfs_buf *bp;
|
|
struct xfs_dsb *sb;
|
|
struct xfs_perag *pag;
|
|
size_t sblen;
|
|
xfs_agnumber_t agno;
|
|
uint32_t v2_ok;
|
|
__be32 features_mask;
|
|
int error;
|
|
__be16 vernum_mask;
|
|
|
|
agno = sc->sm->sm_agno;
|
|
if (agno == 0)
|
|
return 0;
|
|
|
|
/*
|
|
* Grab an active reference to the perag structure. If we can't get
|
|
* it, we're racing with something that's tearing down the AG, so
|
|
* signal that the AG no longer exists.
|
|
*/
|
|
pag = xfs_perag_get(mp, agno);
|
|
if (!pag)
|
|
return -ENOENT;
|
|
|
|
error = xfs_sb_read_secondary(mp, sc->tp, agno, &bp);
|
|
/*
|
|
* The superblock verifier can return several different error codes
|
|
* if it thinks the superblock doesn't look right. For a mount these
|
|
* would all get bounced back to userspace, but if we're here then the
|
|
* fs mounted successfully, which means that this secondary superblock
|
|
* is simply incorrect. Treat all these codes the same way we treat
|
|
* any corruption.
|
|
*/
|
|
switch (error) {
|
|
case -EINVAL: /* also -EWRONGFS */
|
|
case -ENOSYS:
|
|
case -EFBIG:
|
|
error = -EFSCORRUPTED;
|
|
fallthrough;
|
|
default:
|
|
break;
|
|
}
|
|
if (!xchk_process_error(sc, agno, XFS_SB_BLOCK(mp), &error))
|
|
goto out_pag;
|
|
|
|
sb = bp->b_addr;
|
|
|
|
/*
|
|
* Verify the geometries match. Fields that are permanently
|
|
* set by mkfs are checked; fields that can be updated later
|
|
* (and are not propagated to backup superblocks) are preen
|
|
* checked.
|
|
*/
|
|
if (sb->sb_blocksize != cpu_to_be32(mp->m_sb.sb_blocksize))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_dblocks != cpu_to_be64(mp->m_sb.sb_dblocks))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rblocks != cpu_to_be64(mp->m_sb.sb_rblocks))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rextents != cpu_to_be64(mp->m_sb.sb_rextents))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (!uuid_equal(&sb->sb_uuid, &mp->m_sb.sb_uuid))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_logstart != cpu_to_be64(mp->m_sb.sb_logstart))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rootino != cpu_to_be64(mp->m_sb.sb_rootino))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (xfs_has_metadir(sc->mp)) {
|
|
if (sb->sb_rbmino != cpu_to_be64(0))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rsumino != cpu_to_be64(0))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
} else {
|
|
if (sb->sb_rbmino != cpu_to_be64(mp->m_sb.sb_rbmino))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_rsumino != cpu_to_be64(mp->m_sb.sb_rsumino))
|
|
xchk_block_set_preen(sc, bp);
|
|
}
|
|
|
|
if (sb->sb_rextsize != cpu_to_be32(mp->m_sb.sb_rextsize))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_agblocks != cpu_to_be32(mp->m_sb.sb_agblocks))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_agcount != cpu_to_be32(mp->m_sb.sb_agcount))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rbmblocks != cpu_to_be32(mp->m_sb.sb_rbmblocks))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_logblocks != cpu_to_be32(mp->m_sb.sb_logblocks))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/* Check sb_versionnum bits that are set at mkfs time. */
|
|
vernum_mask = cpu_to_be16(XFS_SB_VERSION_NUMBITS |
|
|
XFS_SB_VERSION_ALIGNBIT |
|
|
XFS_SB_VERSION_DALIGNBIT |
|
|
XFS_SB_VERSION_SHAREDBIT |
|
|
XFS_SB_VERSION_LOGV2BIT |
|
|
XFS_SB_VERSION_SECTORBIT |
|
|
XFS_SB_VERSION_EXTFLGBIT |
|
|
XFS_SB_VERSION_DIRV2BIT);
|
|
if ((sb->sb_versionnum & vernum_mask) !=
|
|
(cpu_to_be16(mp->m_sb.sb_versionnum) & vernum_mask))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/* Check sb_versionnum bits that can be set after mkfs time. */
|
|
vernum_mask = cpu_to_be16(XFS_SB_VERSION_ATTRBIT |
|
|
XFS_SB_VERSION_NLINKBIT |
|
|
XFS_SB_VERSION_QUOTABIT);
|
|
if ((sb->sb_versionnum & vernum_mask) !=
|
|
(cpu_to_be16(mp->m_sb.sb_versionnum) & vernum_mask))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_sectsize != cpu_to_be16(mp->m_sb.sb_sectsize))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_inodesize != cpu_to_be16(mp->m_sb.sb_inodesize))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_inopblock != cpu_to_be16(mp->m_sb.sb_inopblock))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (memcmp(sb->sb_fname, mp->m_sb.sb_fname, sizeof(sb->sb_fname)))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_blocklog != mp->m_sb.sb_blocklog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_sectlog != mp->m_sb.sb_sectlog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_inodelog != mp->m_sb.sb_inodelog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_inopblog != mp->m_sb.sb_inopblog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_agblklog != mp->m_sb.sb_agblklog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rextslog != mp->m_sb.sb_rextslog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_imax_pct != mp->m_sb.sb_imax_pct)
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
/*
|
|
* Skip the summary counters since we track them in memory anyway.
|
|
* sb_icount, sb_ifree, sb_fdblocks, sb_frexents
|
|
*/
|
|
|
|
if (xfs_has_metadir(mp)) {
|
|
if (sb->sb_uquotino != cpu_to_be64(0))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_gquotino != cpu_to_be64(0))
|
|
xchk_block_set_preen(sc, bp);
|
|
} else {
|
|
if (sb->sb_uquotino != cpu_to_be64(mp->m_sb.sb_uquotino))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_gquotino != cpu_to_be64(mp->m_sb.sb_gquotino))
|
|
xchk_block_set_preen(sc, bp);
|
|
}
|
|
|
|
/*
|
|
* Skip the quota flags since repair will force quotacheck.
|
|
* sb_qflags
|
|
*/
|
|
|
|
if (sb->sb_flags != mp->m_sb.sb_flags)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_shared_vn != mp->m_sb.sb_shared_vn)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_inoalignmt != cpu_to_be32(mp->m_sb.sb_inoalignmt))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_unit != cpu_to_be32(mp->m_sb.sb_unit))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_width != cpu_to_be32(mp->m_sb.sb_width))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_dirblklog != mp->m_sb.sb_dirblklog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_logsectlog != mp->m_sb.sb_logsectlog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_logsectsize != cpu_to_be16(mp->m_sb.sb_logsectsize))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_logsunit != cpu_to_be32(mp->m_sb.sb_logsunit))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/* Do we see any invalid bits in sb_features2? */
|
|
if (!xfs_sb_version_hasmorebits(&mp->m_sb)) {
|
|
if (sb->sb_features2 != 0)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
} else {
|
|
v2_ok = XFS_SB_VERSION2_OKBITS;
|
|
if (xfs_sb_is_v5(&mp->m_sb))
|
|
v2_ok |= XFS_SB_VERSION2_CRCBIT;
|
|
|
|
if (!!(sb->sb_features2 & cpu_to_be32(~v2_ok)))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_features2 != sb->sb_bad_features2)
|
|
xchk_block_set_preen(sc, bp);
|
|
}
|
|
|
|
/* Check sb_features2 flags that are set at mkfs time. */
|
|
features_mask = cpu_to_be32(XFS_SB_VERSION2_LAZYSBCOUNTBIT |
|
|
XFS_SB_VERSION2_PROJID32BIT |
|
|
XFS_SB_VERSION2_CRCBIT |
|
|
XFS_SB_VERSION2_FTYPE);
|
|
if ((sb->sb_features2 & features_mask) !=
|
|
(cpu_to_be32(mp->m_sb.sb_features2) & features_mask))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/* Check sb_features2 flags that can be set after mkfs time. */
|
|
features_mask = cpu_to_be32(XFS_SB_VERSION2_ATTR2BIT);
|
|
if ((sb->sb_features2 & features_mask) !=
|
|
(cpu_to_be32(mp->m_sb.sb_features2) & features_mask))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (!xfs_has_crc(mp)) {
|
|
/* all v5 fields must be zero */
|
|
if (memchr_inv(&sb->sb_features_compat, 0,
|
|
sizeof(struct xfs_dsb) -
|
|
offsetof(struct xfs_dsb, sb_features_compat)))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
} else {
|
|
/* compat features must match */
|
|
if (sb->sb_features_compat !=
|
|
cpu_to_be32(mp->m_sb.sb_features_compat))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/* ro compat features must match */
|
|
if (sb->sb_features_ro_compat !=
|
|
cpu_to_be32(mp->m_sb.sb_features_ro_compat))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/*
|
|
* NEEDSREPAIR is ignored on a secondary super, so we should
|
|
* clear it when we find it, though it's not a corruption.
|
|
*/
|
|
features_mask = cpu_to_be32(XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR);
|
|
if ((cpu_to_be32(mp->m_sb.sb_features_incompat) ^
|
|
sb->sb_features_incompat) & features_mask)
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
/* all other incompat features must match */
|
|
if ((cpu_to_be32(mp->m_sb.sb_features_incompat) ^
|
|
sb->sb_features_incompat) & ~features_mask)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
/*
|
|
* log incompat features protect newer log record types from
|
|
* older log recovery code. Log recovery doesn't check the
|
|
* secondary supers, so we can clear these if needed.
|
|
*/
|
|
if (sb->sb_features_log_incompat)
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
/* Don't care about sb_crc */
|
|
|
|
if (sb->sb_spino_align != cpu_to_be32(mp->m_sb.sb_spino_align))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (xfs_has_metadir(mp)) {
|
|
if (sb->sb_pquotino != cpu_to_be64(0))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
} else {
|
|
if (sb->sb_pquotino != cpu_to_be64(mp->m_sb.sb_pquotino))
|
|
xchk_block_set_preen(sc, bp);
|
|
}
|
|
|
|
/* Don't care about sb_lsn */
|
|
}
|
|
|
|
if (xfs_has_metauuid(mp)) {
|
|
/* The metadata UUID must be the same for all supers */
|
|
if (!uuid_equal(&sb->sb_meta_uuid, &mp->m_sb.sb_meta_uuid))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
}
|
|
|
|
if (xfs_has_metadir(mp)) {
|
|
if (sb->sb_metadirino != cpu_to_be64(mp->m_sb.sb_metadirino))
|
|
xchk_block_set_preen(sc, bp);
|
|
|
|
if (sb->sb_rgcount != cpu_to_be32(mp->m_sb.sb_rgcount))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rgextents != cpu_to_be32(mp->m_sb.sb_rgextents))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (sb->sb_rgblklog != mp->m_sb.sb_rgblklog)
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
if (memchr_inv(sb->sb_pad, 0, sizeof(sb->sb_pad)))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
}
|
|
|
|
/* Everything else must be zero. */
|
|
sblen = xchk_superblock_ondisk_size(mp);
|
|
if (memchr_inv((char *)sb + sblen, 0, BBTOB(bp->b_length) - sblen))
|
|
xchk_block_set_corrupt(sc, bp);
|
|
|
|
xchk_superblock_xref(sc, bp);
|
|
out_pag:
|
|
xfs_perag_put(pag);
|
|
return error;
|
|
}
|
|
|
|
/* AGF */
|
|
|
|
/* Tally freespace record lengths. */
|
|
STATIC int
|
|
xchk_agf_record_bno_lengths(
|
|
struct xfs_btree_cur *cur,
|
|
const struct xfs_alloc_rec_incore *rec,
|
|
void *priv)
|
|
{
|
|
xfs_extlen_t *blocks = priv;
|
|
|
|
(*blocks) += rec->ar_blockcount;
|
|
return 0;
|
|
}
|
|
|
|
/* Check agf_freeblks */
|
|
static inline void
|
|
xchk_agf_xref_freeblks(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
|
|
xfs_extlen_t blocks = 0;
|
|
int error;
|
|
|
|
if (!sc->sa.bno_cur)
|
|
return;
|
|
|
|
error = xfs_alloc_query_all(sc->sa.bno_cur,
|
|
xchk_agf_record_bno_lengths, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.bno_cur))
|
|
return;
|
|
if (blocks != be32_to_cpu(agf->agf_freeblks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
/* Cross reference the AGF with the cntbt (freespace by length btree) */
|
|
static inline void
|
|
xchk_agf_xref_cntbt(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
|
|
xfs_agblock_t agbno;
|
|
xfs_extlen_t blocks;
|
|
int have;
|
|
int error;
|
|
|
|
if (!sc->sa.cnt_cur)
|
|
return;
|
|
|
|
/* Any freespace at all? */
|
|
error = xfs_alloc_lookup_le(sc->sa.cnt_cur, 0, -1U, &have);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.cnt_cur))
|
|
return;
|
|
if (!have) {
|
|
if (agf->agf_freeblks != cpu_to_be32(0))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
return;
|
|
}
|
|
|
|
/* Check agf_longest */
|
|
error = xfs_alloc_get_rec(sc->sa.cnt_cur, &agbno, &blocks, &have);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.cnt_cur))
|
|
return;
|
|
if (!have || blocks != be32_to_cpu(agf->agf_longest))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
/* Check the btree block counts in the AGF against the btrees. */
|
|
STATIC void
|
|
xchk_agf_xref_btreeblks(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_filblks_t blocks;
|
|
xfs_agblock_t btreeblks;
|
|
int error;
|
|
|
|
/* agf_btreeblks didn't exist before lazysbcount */
|
|
if (!xfs_has_lazysbcount(sc->mp))
|
|
return;
|
|
|
|
/* Check agf_rmap_blocks; set up for agf_btreeblks check */
|
|
if (sc->sa.rmap_cur) {
|
|
error = xfs_btree_count_blocks(sc->sa.rmap_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.rmap_cur))
|
|
return;
|
|
btreeblks = blocks - 1;
|
|
if (blocks != be32_to_cpu(agf->agf_rmap_blocks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
} else {
|
|
btreeblks = 0;
|
|
}
|
|
|
|
/*
|
|
* No rmap cursor; we can't xref if we have the rmapbt feature.
|
|
* We also can't do it if we're missing the free space btree cursors.
|
|
*/
|
|
if ((xfs_has_rmapbt(mp) && !sc->sa.rmap_cur) ||
|
|
!sc->sa.bno_cur || !sc->sa.cnt_cur)
|
|
return;
|
|
|
|
/* Check agf_btreeblks */
|
|
error = xfs_btree_count_blocks(sc->sa.bno_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.bno_cur))
|
|
return;
|
|
btreeblks += blocks - 1;
|
|
|
|
error = xfs_btree_count_blocks(sc->sa.cnt_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.cnt_cur))
|
|
return;
|
|
btreeblks += blocks - 1;
|
|
|
|
if (btreeblks != be32_to_cpu(agf->agf_btreeblks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
/* Check agf_refcount_blocks against tree size */
|
|
static inline void
|
|
xchk_agf_xref_refcblks(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
|
|
xfs_filblks_t blocks;
|
|
int error;
|
|
|
|
if (!sc->sa.refc_cur)
|
|
return;
|
|
|
|
error = xfs_btree_count_blocks(sc->sa.refc_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.refc_cur))
|
|
return;
|
|
if (blocks != be32_to_cpu(agf->agf_refcount_blocks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
/* Cross-reference with the other btrees. */
|
|
STATIC void
|
|
xchk_agf_xref(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_agblock_t agbno;
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return;
|
|
|
|
agbno = XFS_AGF_BLOCK(mp);
|
|
|
|
xchk_ag_btcur_init(sc, &sc->sa);
|
|
|
|
xchk_xref_is_used_space(sc, agbno, 1);
|
|
xchk_agf_xref_freeblks(sc);
|
|
xchk_agf_xref_cntbt(sc);
|
|
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
|
|
xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
|
|
xchk_agf_xref_btreeblks(sc);
|
|
xchk_xref_is_not_shared(sc, agbno, 1);
|
|
xchk_xref_is_not_cow_staging(sc, agbno, 1);
|
|
xchk_agf_xref_refcblks(sc);
|
|
|
|
/* scrub teardown will take care of sc->sa for us */
|
|
}
|
|
|
|
/* Scrub the AGF. */
|
|
int
|
|
xchk_agf(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
struct xfs_agf *agf;
|
|
struct xfs_perag *pag;
|
|
xfs_agnumber_t agno = sc->sm->sm_agno;
|
|
xfs_agblock_t agbno;
|
|
xfs_agblock_t eoag;
|
|
xfs_agblock_t agfl_first;
|
|
xfs_agblock_t agfl_last;
|
|
xfs_agblock_t agfl_count;
|
|
xfs_agblock_t fl_count;
|
|
int level;
|
|
int error = 0;
|
|
|
|
error = xchk_ag_read_headers(sc, agno, &sc->sa);
|
|
if (!xchk_process_error(sc, agno, XFS_AGF_BLOCK(sc->mp), &error))
|
|
goto out;
|
|
xchk_buffer_recheck(sc, sc->sa.agf_bp);
|
|
|
|
agf = sc->sa.agf_bp->b_addr;
|
|
pag = sc->sa.pag;
|
|
|
|
/* Check the AG length */
|
|
eoag = be32_to_cpu(agf->agf_length);
|
|
if (eoag != pag_group(pag)->xg_block_count)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
/* Check the AGF btree roots and levels */
|
|
agbno = be32_to_cpu(agf->agf_bno_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
agbno = be32_to_cpu(agf->agf_cnt_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
level = be32_to_cpu(agf->agf_bno_level);
|
|
if (level <= 0 || level > mp->m_alloc_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
level = be32_to_cpu(agf->agf_cnt_level);
|
|
if (level <= 0 || level > mp->m_alloc_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
if (xfs_has_rmapbt(mp)) {
|
|
agbno = be32_to_cpu(agf->agf_rmap_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
level = be32_to_cpu(agf->agf_rmap_level);
|
|
if (level <= 0 || level > mp->m_rmap_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
if (xfs_has_reflink(mp)) {
|
|
agbno = be32_to_cpu(agf->agf_refcount_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
level = be32_to_cpu(agf->agf_refcount_level);
|
|
if (level <= 0 || level > mp->m_refc_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
}
|
|
|
|
/* Check the AGFL counters */
|
|
agfl_first = be32_to_cpu(agf->agf_flfirst);
|
|
agfl_last = be32_to_cpu(agf->agf_fllast);
|
|
agfl_count = be32_to_cpu(agf->agf_flcount);
|
|
if (agfl_last > agfl_first)
|
|
fl_count = agfl_last - agfl_first + 1;
|
|
else
|
|
fl_count = xfs_agfl_size(mp) - agfl_first + agfl_last + 1;
|
|
if (agfl_count != 0 && fl_count != agfl_count)
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
/* Do the incore counters match? */
|
|
if (pag->pagf_freeblks != be32_to_cpu(agf->agf_freeblks))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
if (pag->pagf_flcount != be32_to_cpu(agf->agf_flcount))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
if (xfs_has_lazysbcount(sc->mp) &&
|
|
pag->pagf_btreeblks != be32_to_cpu(agf->agf_btreeblks))
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
|
|
xchk_agf_xref(sc);
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/* AGFL */
|
|
|
|
struct xchk_agfl_info {
|
|
/* Number of AGFL entries that the AGF claims are in use. */
|
|
unsigned int agflcount;
|
|
|
|
/* Number of AGFL entries that we found. */
|
|
unsigned int nr_entries;
|
|
|
|
/* Buffer to hold AGFL entries for extent checking. */
|
|
xfs_agblock_t *entries;
|
|
|
|
struct xfs_buf *agfl_bp;
|
|
struct xfs_scrub *sc;
|
|
};
|
|
|
|
/* Cross-reference with the other btrees. */
|
|
STATIC void
|
|
xchk_agfl_block_xref(
|
|
struct xfs_scrub *sc,
|
|
xfs_agblock_t agbno)
|
|
{
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return;
|
|
|
|
xchk_xref_is_used_space(sc, agbno, 1);
|
|
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
|
|
xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_AG);
|
|
xchk_xref_is_not_shared(sc, agbno, 1);
|
|
xchk_xref_is_not_cow_staging(sc, agbno, 1);
|
|
}
|
|
|
|
/* Scrub an AGFL block. */
|
|
STATIC int
|
|
xchk_agfl_block(
|
|
struct xfs_mount *mp,
|
|
xfs_agblock_t agbno,
|
|
void *priv)
|
|
{
|
|
struct xchk_agfl_info *sai = priv;
|
|
struct xfs_scrub *sc = sai->sc;
|
|
|
|
if (xfs_verify_agbno(sc->sa.pag, agbno) &&
|
|
sai->nr_entries < sai->agflcount)
|
|
sai->entries[sai->nr_entries++] = agbno;
|
|
else
|
|
xchk_block_set_corrupt(sc, sai->agfl_bp);
|
|
|
|
xchk_agfl_block_xref(sc, agbno);
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return -ECANCELED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xchk_agblock_cmp(
|
|
const void *pa,
|
|
const void *pb)
|
|
{
|
|
const xfs_agblock_t *a = pa;
|
|
const xfs_agblock_t *b = pb;
|
|
|
|
return (int)*a - (int)*b;
|
|
}
|
|
|
|
/* Cross-reference with the other btrees. */
|
|
STATIC void
|
|
xchk_agfl_xref(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_agblock_t agbno;
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return;
|
|
|
|
agbno = XFS_AGFL_BLOCK(mp);
|
|
|
|
xchk_ag_btcur_init(sc, &sc->sa);
|
|
|
|
xchk_xref_is_used_space(sc, agbno, 1);
|
|
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
|
|
xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
|
|
xchk_xref_is_not_shared(sc, agbno, 1);
|
|
xchk_xref_is_not_cow_staging(sc, agbno, 1);
|
|
|
|
/*
|
|
* Scrub teardown will take care of sc->sa for us. Leave sc->sa
|
|
* active so that the agfl block xref can use it too.
|
|
*/
|
|
}
|
|
|
|
/* Scrub the AGFL. */
|
|
int
|
|
xchk_agfl(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xchk_agfl_info sai = {
|
|
.sc = sc,
|
|
};
|
|
struct xfs_agf *agf;
|
|
xfs_agnumber_t agno = sc->sm->sm_agno;
|
|
unsigned int i;
|
|
int error;
|
|
|
|
/* Lock the AGF and AGI so that nobody can touch this AG. */
|
|
error = xchk_ag_read_headers(sc, agno, &sc->sa);
|
|
if (!xchk_process_error(sc, agno, XFS_AGFL_BLOCK(sc->mp), &error))
|
|
return error;
|
|
if (!sc->sa.agf_bp)
|
|
return -EFSCORRUPTED;
|
|
|
|
/* Try to read the AGFL, and verify its structure if we get it. */
|
|
error = xfs_alloc_read_agfl(sc->sa.pag, sc->tp, &sai.agfl_bp);
|
|
if (!xchk_process_error(sc, agno, XFS_AGFL_BLOCK(sc->mp), &error))
|
|
return error;
|
|
xchk_buffer_recheck(sc, sai.agfl_bp);
|
|
|
|
xchk_agfl_xref(sc);
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
goto out;
|
|
|
|
/* Allocate buffer to ensure uniqueness of AGFL entries. */
|
|
agf = sc->sa.agf_bp->b_addr;
|
|
sai.agflcount = be32_to_cpu(agf->agf_flcount);
|
|
if (sai.agflcount > xfs_agfl_size(sc->mp)) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
goto out;
|
|
}
|
|
sai.entries = kvcalloc(sai.agflcount, sizeof(xfs_agblock_t),
|
|
XCHK_GFP_FLAGS);
|
|
if (!sai.entries) {
|
|
error = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Check the blocks in the AGFL. */
|
|
error = xfs_agfl_walk(sc->mp, sc->sa.agf_bp->b_addr, sai.agfl_bp,
|
|
xchk_agfl_block, &sai);
|
|
if (error == -ECANCELED) {
|
|
error = 0;
|
|
goto out_free;
|
|
}
|
|
if (error)
|
|
goto out_free;
|
|
|
|
if (sai.agflcount != sai.nr_entries) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
goto out_free;
|
|
}
|
|
|
|
/* Sort entries, check for duplicates. */
|
|
sort(sai.entries, sai.nr_entries, sizeof(sai.entries[0]),
|
|
xchk_agblock_cmp, NULL);
|
|
for (i = 1; i < sai.nr_entries; i++) {
|
|
if (sai.entries[i] == sai.entries[i - 1]) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agf_bp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
out_free:
|
|
kvfree(sai.entries);
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/* AGI */
|
|
|
|
/* Check agi_count/agi_freecount */
|
|
static inline void
|
|
xchk_agi_xref_icounts(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agi *agi = sc->sa.agi_bp->b_addr;
|
|
xfs_agino_t icount;
|
|
xfs_agino_t freecount;
|
|
int error;
|
|
|
|
if (!sc->sa.ino_cur)
|
|
return;
|
|
|
|
error = xfs_ialloc_count_inodes(sc->sa.ino_cur, &icount, &freecount);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.ino_cur))
|
|
return;
|
|
if (be32_to_cpu(agi->agi_count) != icount ||
|
|
be32_to_cpu(agi->agi_freecount) != freecount)
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agi_bp);
|
|
}
|
|
|
|
/* Check agi_[fi]blocks against tree size */
|
|
static inline void
|
|
xchk_agi_xref_fiblocks(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_agi *agi = sc->sa.agi_bp->b_addr;
|
|
xfs_filblks_t blocks;
|
|
int error = 0;
|
|
|
|
if (!xfs_has_inobtcounts(sc->mp))
|
|
return;
|
|
|
|
if (sc->sa.ino_cur) {
|
|
error = xfs_btree_count_blocks(sc->sa.ino_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.ino_cur))
|
|
return;
|
|
if (blocks != be32_to_cpu(agi->agi_iblocks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agi_bp);
|
|
}
|
|
|
|
if (sc->sa.fino_cur) {
|
|
error = xfs_btree_count_blocks(sc->sa.fino_cur, &blocks);
|
|
if (!xchk_should_check_xref(sc, &error, &sc->sa.fino_cur))
|
|
return;
|
|
if (blocks != be32_to_cpu(agi->agi_fblocks))
|
|
xchk_block_xref_set_corrupt(sc, sc->sa.agi_bp);
|
|
}
|
|
}
|
|
|
|
/* Cross-reference with the other btrees. */
|
|
STATIC void
|
|
xchk_agi_xref(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
xfs_agblock_t agbno;
|
|
|
|
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
|
|
return;
|
|
|
|
agbno = XFS_AGI_BLOCK(mp);
|
|
|
|
xchk_ag_btcur_init(sc, &sc->sa);
|
|
|
|
xchk_xref_is_used_space(sc, agbno, 1);
|
|
xchk_xref_is_not_inode_chunk(sc, agbno, 1);
|
|
xchk_agi_xref_icounts(sc);
|
|
xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS);
|
|
xchk_xref_is_not_shared(sc, agbno, 1);
|
|
xchk_xref_is_not_cow_staging(sc, agbno, 1);
|
|
xchk_agi_xref_fiblocks(sc);
|
|
|
|
/* scrub teardown will take care of sc->sa for us */
|
|
}
|
|
|
|
/*
|
|
* Check the unlinked buckets for links to bad inodes. We hold the AGI, so
|
|
* there cannot be any threads updating unlinked list pointers in this AG.
|
|
*/
|
|
STATIC void
|
|
xchk_iunlink(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_agi *agi)
|
|
{
|
|
unsigned int i;
|
|
struct xfs_inode *ip;
|
|
|
|
for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
|
|
xfs_agino_t agino = be32_to_cpu(agi->agi_unlinked[i]);
|
|
|
|
while (agino != NULLAGINO) {
|
|
if (agino % XFS_AGI_UNLINKED_BUCKETS != i) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
return;
|
|
}
|
|
|
|
ip = xfs_iunlink_lookup(sc->sa.pag, agino);
|
|
if (!ip) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
return;
|
|
}
|
|
|
|
if (!xfs_inode_on_unlinked_list(ip)) {
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
return;
|
|
}
|
|
|
|
agino = ip->i_next_unlinked;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Scrub the AGI. */
|
|
int
|
|
xchk_agi(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_mount *mp = sc->mp;
|
|
struct xfs_agi *agi;
|
|
struct xfs_perag *pag;
|
|
struct xfs_ino_geometry *igeo = M_IGEO(sc->mp);
|
|
xfs_agnumber_t agno = sc->sm->sm_agno;
|
|
xfs_agblock_t agbno;
|
|
xfs_agblock_t eoag;
|
|
xfs_agino_t agino;
|
|
xfs_agino_t first_agino;
|
|
xfs_agino_t last_agino;
|
|
xfs_agino_t icount;
|
|
int i;
|
|
int level;
|
|
int error = 0;
|
|
|
|
error = xchk_ag_read_headers(sc, agno, &sc->sa);
|
|
if (!xchk_process_error(sc, agno, XFS_AGI_BLOCK(sc->mp), &error))
|
|
goto out;
|
|
xchk_buffer_recheck(sc, sc->sa.agi_bp);
|
|
|
|
agi = sc->sa.agi_bp->b_addr;
|
|
pag = sc->sa.pag;
|
|
|
|
/* Check the AG length */
|
|
eoag = be32_to_cpu(agi->agi_length);
|
|
if (eoag != pag_group(pag)->xg_block_count)
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
/* Check btree roots and levels */
|
|
agbno = be32_to_cpu(agi->agi_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
level = be32_to_cpu(agi->agi_level);
|
|
if (level <= 0 || level > igeo->inobt_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
if (xfs_has_finobt(mp)) {
|
|
agbno = be32_to_cpu(agi->agi_free_root);
|
|
if (!xfs_verify_agbno(pag, agbno))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
level = be32_to_cpu(agi->agi_free_level);
|
|
if (level <= 0 || level > igeo->inobt_maxlevels)
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
}
|
|
|
|
/* Check inode counters */
|
|
xfs_agino_range(mp, agno, &first_agino, &last_agino);
|
|
icount = be32_to_cpu(agi->agi_count);
|
|
if (icount > last_agino - first_agino + 1 ||
|
|
icount < be32_to_cpu(agi->agi_freecount))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
/* Check inode pointers */
|
|
agino = be32_to_cpu(agi->agi_newino);
|
|
if (!xfs_verify_agino_or_null(pag, agino))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
agino = be32_to_cpu(agi->agi_dirino);
|
|
if (!xfs_verify_agino_or_null(pag, agino))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
/* Check unlinked inode buckets */
|
|
for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
|
|
agino = be32_to_cpu(agi->agi_unlinked[i]);
|
|
if (!xfs_verify_agino_or_null(pag, agino))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
}
|
|
|
|
if (agi->agi_pad32 != cpu_to_be32(0))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
/* Do the incore counters match? */
|
|
if (pag->pagi_count != be32_to_cpu(agi->agi_count))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
if (pag->pagi_freecount != be32_to_cpu(agi->agi_freecount))
|
|
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
|
|
|
|
xchk_iunlink(sc, agi);
|
|
|
|
xchk_agi_xref(sc);
|
|
out:
|
|
return error;
|
|
}
|