// 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_log_format.h" #include"xfs_trans.h" #include"xfs_inode.h" #include"xfs_dir2.h" #include"xfs_dir2_priv.h" #include"xfs_attr_leaf.h" #include"scrub/scrub.h" #include"scrub/common.h" #include"scrub/trace.h" #include"scrub/dabtree.h"
/* Directory/Attribute Btree */
/* * Check for da btree operation errors. See the section about handling * operational errors in common.c.
*/ bool
xchk_da_process_error( struct xchk_da_btree *ds, int level, int *error)
{ struct xfs_scrub *sc = ds->sc;
if (*error == 0) returntrue;
switch (*error) { case -EDEADLOCK: case -ECHRNG: /* Used to restart an op with deadlock avoidance. */
trace_xchk_deadlock_retry(sc->ip, sc->sm, *error); break; case -EFSBADCRC: case -EFSCORRUPTED: /* Note the badness but don't abort. */
sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
*error = 0;
fallthrough; default:
trace_xchk_file_op_error(sc, ds->dargs.whichfork,
xfs_dir2_da_to_db(ds->dargs.geo,
ds->state->path.blk[level].blkno),
*error, __return_address); break;
} returnfalse;
}
/* * Check for da btree corruption. See the section about handling * operational errors in common.c.
*/ void
xchk_da_set_corrupt( struct xchk_da_btree *ds, int level)
{ struct xfs_scrub *sc = ds->sc;
/* Find an entry at a certain level in a da btree. */ staticstruct xfs_da_node_entry *
xchk_da_btree_node_entry( struct xchk_da_btree *ds, int level)
{ struct xfs_da_state_blk *blk = &ds->state->path.blk[level]; struct xfs_da3_icnode_hdr hdr;
/* Scrub a da btree hash (key). */ int
xchk_da_btree_hash( struct xchk_da_btree *ds, int level,
__be32 *hashp)
{ struct xfs_da_node_entry *entry;
xfs_dahash_t hash;
xfs_dahash_t parent_hash;
/* Is this hash in order? */
hash = be32_to_cpu(*hashp); if (hash < ds->hashes[level])
xchk_da_set_corrupt(ds, level);
ds->hashes[level] = hash;
if (level == 0) return 0;
/* Is this hash no larger than the parent hash? */
entry = xchk_da_btree_node_entry(ds, level - 1);
parent_hash = be32_to_cpu(entry->hashval); if (parent_hash < hash)
xchk_da_set_corrupt(ds, level);
return 0;
}
/* * Check a da btree pointer. Returns true if it's ok to use this * pointer.
*/ STATICbool
xchk_da_btree_ptr_ok( struct xchk_da_btree *ds, int level,
xfs_dablk_t blkno)
{ if (blkno < ds->lowest || (ds->highest != 0 && blkno >= ds->highest)) {
xchk_da_set_corrupt(ds, level); returnfalse;
}
returntrue;
}
/* * The da btree scrubber can handle leaf1 blocks as a degenerate * form of leafn blocks. Since the regular da code doesn't handle * leaf1, we must multiplex the verifiers.
*/ staticvoid
xchk_da_btree_read_verify( struct xfs_buf *bp)
{ struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) { case XFS_DIR2_LEAF1_MAGIC: case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops;
bp->b_ops->verify_read(bp); return; default: /* * xfs_da3_node_buf_ops already know how to handle * DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
*/
bp->b_ops = &xfs_da3_node_buf_ops;
bp->b_ops->verify_read(bp); return;
}
} staticvoid
xchk_da_btree_write_verify( struct xfs_buf *bp)
{ struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) { case XFS_DIR2_LEAF1_MAGIC: case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops;
bp->b_ops->verify_write(bp); return; default: /* * xfs_da3_node_buf_ops already know how to handle * DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
*/
bp->b_ops = &xfs_da3_node_buf_ops;
bp->b_ops->verify_write(bp); return;
}
} staticvoid *
xchk_da_btree_verify( struct xfs_buf *bp)
{ struct xfs_da_blkinfo *info = bp->b_addr;
switch (be16_to_cpu(info->magic)) { case XFS_DIR2_LEAF1_MAGIC: case XFS_DIR3_LEAF1_MAGIC:
bp->b_ops = &xfs_dir3_leaf1_buf_ops; return bp->b_ops->verify_struct(bp); default:
bp->b_ops = &xfs_da3_node_buf_ops; return bp->b_ops->verify_struct(bp);
}
}
/* * If the pointer is null, we shouldn't be able to move the upper * level pointer anywhere.
*/ if (sibling == 0) {
error = xfs_da3_path_shift(ds->state, altpath, direction, false, &retval); if (error == 0 && retval == 0)
xchk_da_set_corrupt(ds, level);
error = 0; goto out;
}
/* Move the alternate cursor one block in the direction given. */
error = xfs_da3_path_shift(ds->state, altpath, direction, false,
&retval); if (!xchk_da_process_error(ds, level, &error)) goto out; if (retval) {
xchk_da_set_corrupt(ds, level); goto out;
} if (altpath->blk[level].bp)
xchk_buffer_recheck(ds->sc, altpath->blk[level].bp);
/* Compare upper level pointer to sibling pointer. */ if (altpath->blk[level].blkno != sibling)
xchk_da_set_corrupt(ds, level);
out: /* Free all buffers in the altpath that aren't referenced from path. */ for (plevel = 0; plevel < altpath->active; plevel++) { if (altpath->blk[plevel].bp == NULL ||
(plevel < path->active &&
altpath->blk[plevel].bp == path->blk[plevel].bp)) continue;
/* Release old block. */ if (blk->bp) {
xfs_trans_brelse(dargs->trans, blk->bp);
blk->bp = NULL;
}
/* Check the pointer. */
blk->blkno = blkno; if (!xchk_da_btree_ptr_ok(ds, level, blkno)) goto out_nobuf;
/* Read the buffer. */
error = xfs_da_read_buf(dargs->trans, dargs->dp, blk->blkno,
XFS_DABUF_MAP_HOLE_OK, &blk->bp, dargs->whichfork,
&xchk_da_btree_buf_ops); if (!xchk_da_process_error(ds, level, &error)) goto out_nobuf; if (blk->bp)
xchk_buffer_recheck(ds->sc, blk->bp);
/* * We didn't find a dir btree root block, which means that * there's no LEAF1/LEAFN tree (at least not where it's supposed * to be), so jump out now.
*/ if (ds->dargs.whichfork == XFS_DATA_FORK && level == 0 &&
blk->bp == NULL) goto out_nobuf;
/* It's /not/ ok for attr trees not to have a da btree. */ if (blk->bp == NULL) {
xchk_da_set_corrupt(ds, level); goto out_nobuf;
}
/* We only started zeroing the header on v5 filesystems. */ if (xfs_has_crc(ds->sc->mp) && hdr3->hdr.pad)
xchk_da_set_corrupt(ds, level);
/* Check the owner. */ if (xfs_has_crc(ip->i_mount)) {
owner = be64_to_cpu(hdr3->owner); if (owner != ip->i_ino)
xchk_da_set_corrupt(ds, level);
}
/* Check the siblings. */
error = xchk_da_btree_block_check_siblings(ds, level, &hdr3->hdr); if (error) goto out;
/* Interpret the buffer. */ switch (blk->magic) { case XFS_ATTR_LEAF_MAGIC: case XFS_ATTR3_LEAF_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_ATTR_LEAF_BUF);
blk->magic = XFS_ATTR_LEAF_MAGIC;
blk->hashval = xfs_attr_leaf_lasthash(blk->bp, pmaxrecs); if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level); break; case XFS_DIR2_LEAFN_MAGIC: case XFS_DIR3_LEAFN_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DIR_LEAFN_BUF);
blk->magic = XFS_DIR2_LEAFN_MAGIC;
blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs); if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level); break; case XFS_DIR2_LEAF1_MAGIC: case XFS_DIR3_LEAF1_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DIR_LEAF1_BUF);
blk->magic = XFS_DIR2_LEAF1_MAGIC;
blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs); if (ds->tree_level != 0)
xchk_da_set_corrupt(ds, level); break; case XFS_DA_NODE_MAGIC: case XFS_DA3_NODE_MAGIC:
xfs_trans_buf_set_type(dargs->trans, blk->bp,
XFS_BLFT_DA_NODE_BUF);
blk->magic = XFS_DA_NODE_MAGIC;
node = blk->bp->b_addr;
xfs_da3_node_hdr_from_disk(ip->i_mount, &nodehdr, node);
btree = nodehdr.btree;
*pmaxrecs = nodehdr.count;
blk->hashval = be32_to_cpu(btree[*pmaxrecs - 1].hashval); if (level == 0) { if (nodehdr.level >= XFS_DA_NODE_MAXDEPTH) {
xchk_da_set_corrupt(ds, level); goto out_freebp;
}
ds->tree_level = nodehdr.level;
} else { if (ds->tree_level != nodehdr.level) {
xchk_da_set_corrupt(ds, level); goto out_freebp;
}
}
/* XXX: Check hdr3.pad32 once we know how to fix it. */ break; default:
xchk_da_set_corrupt(ds, level); goto out_freebp;
}
fa = xfs_da3_header_check(blk->bp, dargs->owner); if (fa) {
xchk_da_set_corrupt(ds, level); goto out_freebp;
}
/* * If we've been handed a block that is below the dabtree root, does * its hashval match what the parent block expected to see?
*/ if (level > 0) { struct xfs_da_node_entry *key;
/* Visit all nodes and leaves of a da btree. */ int
xchk_da_btree( struct xfs_scrub *sc, int whichfork,
xchk_da_btree_rec_fn scrub_fn, void *private)
{ struct xchk_da_btree *ds; struct xfs_mount *mp = sc->mp; struct xfs_da_state_blk *blks; struct xfs_da_node_entry *key;
xfs_dablk_t blkno; int level; int error;
/* Skip short format data structures; no btree to scan. */ if (!xfs_ifork_has_extents(xfs_ifork_ptr(sc->ip, whichfork))) return 0;
/* Find the root of the da tree, if present. */
blks = ds->state->path.blk;
error = xchk_da_btree_block(ds, level, blkno); if (error) goto out_state; /* * We didn't find a block at ds->lowest, which means that there's * no LEAF1/LEAFN tree (at least not where it's supposed to be), * so jump out now.
*/ if (blks[level].bp == NULL) goto out_state;
blks[level].index = 0; while (level >= 0 && level < XFS_DA_NODE_MAXDEPTH) { /* Handle leaf block. */ if (blks[level].magic != XFS_DA_NODE_MAGIC) { /* End of leaf, pop back towards the root. */ if (blks[level].index >= ds->maxrecs[level]) { if (level > 0)
blks[level - 1].index++;
ds->tree_level++;
level--; continue;
}
/* Dispatch record scrubbing. */
error = scrub_fn(ds, level); if (error) break; if (xchk_should_terminate(sc, &error) ||
(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) break;
blks[level].index++; continue;
}
/* End of node, pop back towards the root. */ if (blks[level].index >= ds->maxrecs[level]) { if (level > 0)
blks[level - 1].index++;
ds->tree_level++;
level--; continue;
}
/* Hashes in order for scrub? */
key = xchk_da_btree_node_entry(ds, level);
error = xchk_da_btree_hash(ds, level, &key->hashval); if (error) goto out;
/* Drill another level deeper. */
blkno = be32_to_cpu(key->before);
level++; if (level >= XFS_DA_NODE_MAXDEPTH) { /* Too deep! */
xchk_da_set_corrupt(ds, level - 1); break;
}
ds->tree_level--;
error = xchk_da_btree_block(ds, level, blkno); if (error) goto out; if (blks[level].bp == NULL) goto out;
blks[level].index = 0;
}
out: /* Release all the buffers we're tracking. */ for (level = 0; level < XFS_DA_NODE_MAXDEPTH; level++) { if (blks[level].bp == NULL) continue;
xfs_trans_brelse(sc->tp, blks[level].bp);
blks[level].bp = NULL;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.