Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Darrick J. Wong | 2633 | 97.30% | 20 | 68.97% |
Christoph Hellwig | 45 | 1.66% | 5 | 17.24% |
Jia-Ju Bai | 22 | 0.81% | 1 | 3.45% |
Gustavo A. R. Silva | 2 | 0.07% | 1 | 3.45% |
Carlos Maiolino | 2 | 0.07% | 1 | 3.45% |
David Chinner | 2 | 0.07% | 1 | 3.45% |
Total | 2706 | 29 |
// 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; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1