| // 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) |
| return true; |
| |
| 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; |
| } |
| return false; |
| } |
| |
| /* |
| * 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; |
| |
| sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; |
| |
| trace_xchk_fblock_error(sc, ds->dargs.whichfork, |
| xfs_dir2_da_to_db(ds->dargs.geo, |
| ds->state->path.blk[level].blkno), |
| __return_address); |
| } |
| |
| /* Flag a da btree node in need of optimization. */ |
| void |
| xchk_da_set_preen( |
| struct xchk_da_btree *ds, |
| int level) |
| { |
| struct xfs_scrub *sc = ds->sc; |
| |
| sc->sm->sm_flags |= XFS_SCRUB_OFLAG_PREEN; |
| trace_xchk_fblock_preen(sc, ds->dargs.whichfork, |
| xfs_dir2_da_to_db(ds->dargs.geo, |
| ds->state->path.blk[level].blkno), |
| __return_address); |
| } |
| |
| /* Find an entry at a certain level in a da btree. */ |
| static struct 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; |
| |
| ASSERT(blk->magic == XFS_DA_NODE_MAGIC); |
| |
| xfs_da3_node_hdr_from_disk(ds->sc->mp, &hdr, blk->bp->b_addr); |
| return hdr.btree + blk->index; |
| } |
| |
| /* 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. |
| */ |
| STATIC bool |
| 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); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * 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. |
| */ |
| static void |
| 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; |
| } |
| } |
| static void |
| 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; |
| } |
| } |
| static void * |
| 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); |
| } |
| } |
| |
| static const struct xfs_buf_ops xchk_da_btree_buf_ops = { |
| .name = "xchk_da_btree", |
| .verify_read = xchk_da_btree_read_verify, |
| .verify_write = xchk_da_btree_write_verify, |
| .verify_struct = xchk_da_btree_verify, |
| }; |
| |
| /* Check a block's sibling. */ |
| STATIC int |
| xchk_da_btree_block_check_sibling( |
| struct xchk_da_btree *ds, |
| int level, |
| int direction, |
| xfs_dablk_t sibling) |
| { |
| struct xfs_da_state_path *path = &ds->state->path; |
| struct xfs_da_state_path *altpath = &ds->state->altpath; |
| int retval; |
| int plevel; |
| int error; |
| |
| memcpy(altpath, path, sizeof(ds->state->altpath)); |
| |
| /* |
| * 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; |
| |
| xfs_trans_brelse(ds->dargs.trans, altpath->blk[plevel].bp); |
| altpath->blk[plevel].bp = NULL; |
| } |
| |
| return error; |
| } |
| |
| /* Check a block's sibling pointers. */ |
| STATIC int |
| xchk_da_btree_block_check_siblings( |
| struct xchk_da_btree *ds, |
| int level, |
| struct xfs_da_blkinfo *hdr) |
| { |
| xfs_dablk_t forw; |
| xfs_dablk_t back; |
| int error = 0; |
| |
| forw = be32_to_cpu(hdr->forw); |
| back = be32_to_cpu(hdr->back); |
| |
| /* Top level blocks should not have sibling pointers. */ |
| if (level == 0) { |
| if (forw != 0 || back != 0) |
| xchk_da_set_corrupt(ds, level); |
| return 0; |
| } |
| |
| /* |
| * Check back (left) and forw (right) pointers. These functions |
| * absorb error codes for us. |
| */ |
| error = xchk_da_btree_block_check_sibling(ds, level, 0, back); |
| if (error) |
| goto out; |
| error = xchk_da_btree_block_check_sibling(ds, level, 1, forw); |
| |
| out: |
| memset(&ds->state->altpath, 0, sizeof(ds->state->altpath)); |
| return error; |
| } |
| |
| /* Load a dir/attribute block from a btree. */ |
| STATIC int |
| xchk_da_btree_block( |
| struct xchk_da_btree *ds, |
| int level, |
| xfs_dablk_t blkno) |
| { |
| struct xfs_da_state_blk *blk; |
| struct xfs_da_intnode *node; |
| struct xfs_da_node_entry *btree; |
| struct xfs_da3_blkinfo *hdr3; |
| struct xfs_da_args *dargs = &ds->dargs; |
| struct xfs_inode *ip = ds->dargs.dp; |
| xfs_failaddr_t fa; |
| xfs_ino_t owner; |
| int *pmaxrecs; |
| struct xfs_da3_icnode_hdr nodehdr; |
| int error = 0; |
| |
| blk = &ds->state->path.blk[level]; |
| ds->state->path.active = level + 1; |
| |
| /* 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; |
| } |
| |
| hdr3 = blk->bp->b_addr; |
| blk->magic = be16_to_cpu(hdr3->hdr.magic); |
| pmaxrecs = &ds->maxrecs[level]; |
| |
| /* 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; |
| |
| key = xchk_da_btree_node_entry(ds, level - 1); |
| if (be32_to_cpu(key->hashval) != blk->hashval) { |
| xchk_da_set_corrupt(ds, level); |
| goto out_freebp; |
| } |
| } |
| |
| out: |
| return error; |
| out_freebp: |
| xfs_trans_brelse(dargs->trans, blk->bp); |
| blk->bp = NULL; |
| out_nobuf: |
| blk->blkno = 0; |
| return error; |
| } |
| |
| /* 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; |
| |
| /* Set up initial da state. */ |
| ds = kzalloc(sizeof(struct xchk_da_btree), XCHK_GFP_FLAGS); |
| if (!ds) |
| return -ENOMEM; |
| ds->dargs.dp = sc->ip; |
| ds->dargs.whichfork = whichfork; |
| ds->dargs.trans = sc->tp; |
| ds->dargs.op_flags = XFS_DA_OP_OKNOENT; |
| ds->dargs.owner = sc->ip->i_ino; |
| ds->state = xfs_da_state_alloc(&ds->dargs); |
| ds->sc = sc; |
| ds->private = private; |
| if (whichfork == XFS_ATTR_FORK) { |
| ds->dargs.geo = mp->m_attr_geo; |
| ds->lowest = 0; |
| ds->highest = 0; |
| } else { |
| ds->dargs.geo = mp->m_dir_geo; |
| ds->lowest = ds->dargs.geo->leafblk; |
| ds->highest = ds->dargs.geo->freeblk; |
| } |
| blkno = ds->lowest; |
| level = 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; |
| } |
| |
| out_state: |
| xfs_da_state_free(ds->state); |
| kfree(ds); |
| return error; |
| } |