371 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * Copyright (c) 2022-2024 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_defer.h"
 | |
| #include "xfs_btree.h"
 | |
| #include "xfs_buf_mem.h"
 | |
| #include "xfs_btree_mem.h"
 | |
| #include "xfs_error.h"
 | |
| #include "scrub/rcbag_btree.h"
 | |
| #include "scrub/trace.h"
 | |
| 
 | |
| static struct kmem_cache	*rcbagbt_cur_cache;
 | |
| 
 | |
| STATIC void
 | |
| rcbagbt_init_key_from_rec(
 | |
| 	union xfs_btree_key		*key,
 | |
| 	const union xfs_btree_rec	*rec)
 | |
| {
 | |
| 	struct rcbag_key	*bag_key = (struct rcbag_key *)key;
 | |
| 	const struct rcbag_rec	*bag_rec = (const struct rcbag_rec *)rec;
 | |
| 
 | |
| 	BUILD_BUG_ON(sizeof(struct rcbag_key) > sizeof(union xfs_btree_key));
 | |
| 	BUILD_BUG_ON(sizeof(struct rcbag_rec) > sizeof(union xfs_btree_rec));
 | |
| 
 | |
| 	bag_key->rbg_startblock = bag_rec->rbg_startblock;
 | |
| 	bag_key->rbg_blockcount = bag_rec->rbg_blockcount;
 | |
| }
 | |
| 
 | |
| STATIC void
 | |
| rcbagbt_init_rec_from_cur(
 | |
| 	struct xfs_btree_cur	*cur,
 | |
| 	union xfs_btree_rec	*rec)
 | |
| {
 | |
| 	struct rcbag_rec	*bag_rec = (struct rcbag_rec *)rec;
 | |
| 	struct rcbag_rec	*bag_irec = (struct rcbag_rec *)&cur->bc_rec;
 | |
| 
 | |
| 	bag_rec->rbg_startblock = bag_irec->rbg_startblock;
 | |
| 	bag_rec->rbg_blockcount = bag_irec->rbg_blockcount;
 | |
| 	bag_rec->rbg_refcount = bag_irec->rbg_refcount;
 | |
| }
 | |
| 
 | |
| STATIC int64_t
 | |
| rcbagbt_key_diff(
 | |
| 	struct xfs_btree_cur		*cur,
 | |
| 	const union xfs_btree_key	*key)
 | |
| {
 | |
| 	struct rcbag_rec		*rec = (struct rcbag_rec *)&cur->bc_rec;
 | |
| 	const struct rcbag_key		*kp = (const struct rcbag_key *)key;
 | |
| 
 | |
| 	if (kp->rbg_startblock > rec->rbg_startblock)
 | |
| 		return 1;
 | |
| 	if (kp->rbg_startblock < rec->rbg_startblock)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (kp->rbg_blockcount > rec->rbg_blockcount)
 | |
| 		return 1;
 | |
| 	if (kp->rbg_blockcount < rec->rbg_blockcount)
 | |
| 		return -1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| STATIC int64_t
 | |
| rcbagbt_diff_two_keys(
 | |
| 	struct xfs_btree_cur		*cur,
 | |
| 	const union xfs_btree_key	*k1,
 | |
| 	const union xfs_btree_key	*k2,
 | |
| 	const union xfs_btree_key	*mask)
 | |
| {
 | |
| 	const struct rcbag_key		*kp1 = (const struct rcbag_key *)k1;
 | |
| 	const struct rcbag_key		*kp2 = (const struct rcbag_key *)k2;
 | |
| 
 | |
| 	ASSERT(mask == NULL);
 | |
| 
 | |
| 	if (kp1->rbg_startblock > kp2->rbg_startblock)
 | |
| 		return 1;
 | |
| 	if (kp1->rbg_startblock < kp2->rbg_startblock)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (kp1->rbg_blockcount > kp2->rbg_blockcount)
 | |
| 		return 1;
 | |
| 	if (kp1->rbg_blockcount < kp2->rbg_blockcount)
 | |
| 		return -1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| STATIC int
 | |
| rcbagbt_keys_inorder(
 | |
| 	struct xfs_btree_cur		*cur,
 | |
| 	const union xfs_btree_key	*k1,
 | |
| 	const union xfs_btree_key	*k2)
 | |
| {
 | |
| 	const struct rcbag_key		*kp1 = (const struct rcbag_key *)k1;
 | |
| 	const struct rcbag_key		*kp2 = (const struct rcbag_key *)k2;
 | |
| 
 | |
| 	if (kp1->rbg_startblock > kp2->rbg_startblock)
 | |
| 		return 0;
 | |
| 	if (kp1->rbg_startblock < kp2->rbg_startblock)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (kp1->rbg_blockcount > kp2->rbg_blockcount)
 | |
| 		return 0;
 | |
| 	if (kp1->rbg_blockcount < kp2->rbg_blockcount)
 | |
| 		return 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| STATIC int
 | |
| rcbagbt_recs_inorder(
 | |
| 	struct xfs_btree_cur		*cur,
 | |
| 	const union xfs_btree_rec	*r1,
 | |
| 	const union xfs_btree_rec	*r2)
 | |
| {
 | |
| 	const struct rcbag_rec		*rp1 = (const struct rcbag_rec *)r1;
 | |
| 	const struct rcbag_rec		*rp2 = (const struct rcbag_rec *)r2;
 | |
| 
 | |
| 	if (rp1->rbg_startblock > rp2->rbg_startblock)
 | |
| 		return 0;
 | |
| 	if (rp1->rbg_startblock < rp2->rbg_startblock)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (rp1->rbg_blockcount > rp2->rbg_blockcount)
 | |
| 		return 0;
 | |
| 	if (rp1->rbg_blockcount < rp2->rbg_blockcount)
 | |
| 		return 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static xfs_failaddr_t
 | |
| rcbagbt_verify(
 | |
| 	struct xfs_buf		*bp)
 | |
| {
 | |
| 	struct xfs_mount	*mp = bp->b_mount;
 | |
| 	struct xfs_btree_block	*block = XFS_BUF_TO_BLOCK(bp);
 | |
| 	xfs_failaddr_t		fa;
 | |
| 	unsigned int		level;
 | |
| 	unsigned int		maxrecs;
 | |
| 
 | |
| 	if (!xfs_verify_magic(bp, block->bb_magic))
 | |
| 		return __this_address;
 | |
| 
 | |
| 	fa = xfs_btree_fsblock_v5hdr_verify(bp, XFS_RMAP_OWN_UNKNOWN);
 | |
| 	if (fa)
 | |
| 		return fa;
 | |
| 
 | |
| 	level = be16_to_cpu(block->bb_level);
 | |
| 	if (level >= rcbagbt_maxlevels_possible())
 | |
| 		return __this_address;
 | |
| 
 | |
| 	maxrecs = rcbagbt_maxrecs(mp, XFBNO_BLOCKSIZE, level == 0);
 | |
| 	return xfs_btree_memblock_verify(bp, maxrecs);
 | |
| }
 | |
| 
 | |
| static void
 | |
| rcbagbt_rw_verify(
 | |
| 	struct xfs_buf	*bp)
 | |
| {
 | |
| 	xfs_failaddr_t	fa = rcbagbt_verify(bp);
 | |
| 
 | |
| 	if (fa)
 | |
| 		xfs_verifier_error(bp, -EFSCORRUPTED, fa);
 | |
| }
 | |
| 
 | |
| /* skip crc checks on in-memory btrees to save time */
 | |
| static const struct xfs_buf_ops rcbagbt_mem_buf_ops = {
 | |
| 	.name			= "rcbagbt_mem",
 | |
| 	.magic			= { 0, cpu_to_be32(RCBAG_MAGIC) },
 | |
| 	.verify_read		= rcbagbt_rw_verify,
 | |
| 	.verify_write		= rcbagbt_rw_verify,
 | |
| 	.verify_struct		= rcbagbt_verify,
 | |
| };
 | |
| 
 | |
| static const struct xfs_btree_ops rcbagbt_mem_ops = {
 | |
| 	.name			= "rcbag",
 | |
| 	.type			= XFS_BTREE_TYPE_MEM,
 | |
| 
 | |
| 	.rec_len		= sizeof(struct rcbag_rec),
 | |
| 	.key_len		= sizeof(struct rcbag_key),
 | |
| 	.ptr_len		= XFS_BTREE_LONG_PTR_LEN,
 | |
| 
 | |
| 	.lru_refs		= 1,
 | |
| 	.statoff		= XFS_STATS_CALC_INDEX(xs_rcbag_2),
 | |
| 
 | |
| 	.dup_cursor		= xfbtree_dup_cursor,
 | |
| 	.set_root		= xfbtree_set_root,
 | |
| 	.alloc_block		= xfbtree_alloc_block,
 | |
| 	.free_block		= xfbtree_free_block,
 | |
| 	.get_minrecs		= xfbtree_get_minrecs,
 | |
| 	.get_maxrecs		= xfbtree_get_maxrecs,
 | |
| 	.init_key_from_rec	= rcbagbt_init_key_from_rec,
 | |
| 	.init_rec_from_cur	= rcbagbt_init_rec_from_cur,
 | |
| 	.init_ptr_from_cur	= xfbtree_init_ptr_from_cur,
 | |
| 	.key_diff		= rcbagbt_key_diff,
 | |
| 	.buf_ops		= &rcbagbt_mem_buf_ops,
 | |
| 	.diff_two_keys		= rcbagbt_diff_two_keys,
 | |
| 	.keys_inorder		= rcbagbt_keys_inorder,
 | |
| 	.recs_inorder		= rcbagbt_recs_inorder,
 | |
| };
 | |
| 
 | |
| /* Create a cursor for an in-memory btree. */
 | |
| struct xfs_btree_cur *
 | |
| rcbagbt_mem_cursor(
 | |
| 	struct xfs_mount	*mp,
 | |
| 	struct xfs_trans	*tp,
 | |
| 	struct xfbtree		*xfbtree)
 | |
| {
 | |
| 	struct xfs_btree_cur	*cur;
 | |
| 
 | |
| 	cur = xfs_btree_alloc_cursor(mp, tp, &rcbagbt_mem_ops,
 | |
| 			rcbagbt_maxlevels_possible(), rcbagbt_cur_cache);
 | |
| 
 | |
| 	cur->bc_mem.xfbtree = xfbtree;
 | |
| 	cur->bc_nlevels = xfbtree->nlevels;
 | |
| 	return cur;
 | |
| }
 | |
| 
 | |
| /* Create an in-memory refcount bag btree. */
 | |
| int
 | |
| rcbagbt_mem_init(
 | |
| 	struct xfs_mount	*mp,
 | |
| 	struct xfbtree		*xfbt,
 | |
| 	struct xfs_buftarg	*btp)
 | |
| {
 | |
| 	xfbt->owner = 0;
 | |
| 	return xfbtree_init(mp, xfbt, btp, &rcbagbt_mem_ops);
 | |
| }
 | |
| 
 | |
| /* Calculate number of records in a refcount bag btree block. */
 | |
| static inline unsigned int
 | |
| rcbagbt_block_maxrecs(
 | |
| 	unsigned int		blocklen,
 | |
| 	bool			leaf)
 | |
| {
 | |
| 	if (leaf)
 | |
| 		return blocklen / sizeof(struct rcbag_rec);
 | |
| 	return blocklen /
 | |
| 		(sizeof(struct rcbag_key) + sizeof(rcbag_ptr_t));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Calculate number of records in an refcount bag btree block.
 | |
|  */
 | |
| unsigned int
 | |
| rcbagbt_maxrecs(
 | |
| 	struct xfs_mount	*mp,
 | |
| 	unsigned int		blocklen,
 | |
| 	bool			leaf)
 | |
| {
 | |
| 	blocklen -= RCBAG_BLOCK_LEN;
 | |
| 	return rcbagbt_block_maxrecs(blocklen, leaf);
 | |
| }
 | |
| 
 | |
| /* Compute the max possible height for refcount bag btrees. */
 | |
| unsigned int
 | |
| rcbagbt_maxlevels_possible(void)
 | |
| {
 | |
| 	unsigned int		minrecs[2];
 | |
| 	unsigned int		blocklen;
 | |
| 
 | |
| 	blocklen = XFBNO_BLOCKSIZE - XFS_BTREE_LBLOCK_CRC_LEN;
 | |
| 
 | |
| 	minrecs[0] = rcbagbt_block_maxrecs(blocklen, true) / 2;
 | |
| 	minrecs[1] = rcbagbt_block_maxrecs(blocklen, false) / 2;
 | |
| 
 | |
| 	return xfs_btree_space_to_height(minrecs, ULLONG_MAX);
 | |
| }
 | |
| 
 | |
| /* Calculate the refcount bag btree size for some records. */
 | |
| unsigned long long
 | |
| rcbagbt_calc_size(
 | |
| 	unsigned long long	nr_records)
 | |
| {
 | |
| 	unsigned int		minrecs[2];
 | |
| 	unsigned int		blocklen;
 | |
| 
 | |
| 	blocklen = XFBNO_BLOCKSIZE - XFS_BTREE_LBLOCK_CRC_LEN;
 | |
| 
 | |
| 	minrecs[0] = rcbagbt_block_maxrecs(blocklen, true) / 2;
 | |
| 	minrecs[1] = rcbagbt_block_maxrecs(blocklen, false) / 2;
 | |
| 
 | |
| 	return xfs_btree_calc_size(minrecs, nr_records);
 | |
| }
 | |
| 
 | |
| int __init
 | |
| rcbagbt_init_cur_cache(void)
 | |
| {
 | |
| 	rcbagbt_cur_cache = kmem_cache_create("xfs_rcbagbt_cur",
 | |
| 			xfs_btree_cur_sizeof(rcbagbt_maxlevels_possible()),
 | |
| 			0, 0, NULL);
 | |
| 
 | |
| 	if (!rcbagbt_cur_cache)
 | |
| 		return -ENOMEM;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void
 | |
| rcbagbt_destroy_cur_cache(void)
 | |
| {
 | |
| 	kmem_cache_destroy(rcbagbt_cur_cache);
 | |
| 	rcbagbt_cur_cache = NULL;
 | |
| }
 | |
| 
 | |
| /* Look up the refcount bag record corresponding to this reverse mapping. */
 | |
| int
 | |
| rcbagbt_lookup_eq(
 | |
| 	struct xfs_btree_cur		*cur,
 | |
| 	const struct xfs_rmap_irec	*rmap,
 | |
| 	int				*success)
 | |
| {
 | |
| 	struct rcbag_rec		*rec = (struct rcbag_rec *)&cur->bc_rec;
 | |
| 
 | |
| 	rec->rbg_startblock = rmap->rm_startblock;
 | |
| 	rec->rbg_blockcount = rmap->rm_blockcount;
 | |
| 
 | |
| 	return xfs_btree_lookup(cur, XFS_LOOKUP_EQ, success);
 | |
| }
 | |
| 
 | |
| /* Get the data from the pointed-to record. */
 | |
| int
 | |
| rcbagbt_get_rec(
 | |
| 	struct xfs_btree_cur	*cur,
 | |
| 	struct rcbag_rec	*rec,
 | |
| 	int			*has)
 | |
| {
 | |
| 	union xfs_btree_rec	*btrec;
 | |
| 	int			error;
 | |
| 
 | |
| 	error = xfs_btree_get_rec(cur, &btrec, has);
 | |
| 	if (error || !(*has))
 | |
| 		return error;
 | |
| 
 | |
| 	memcpy(rec, btrec, sizeof(struct rcbag_rec));
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* Update the record referred to by cur to the value given. */
 | |
| int
 | |
| rcbagbt_update(
 | |
| 	struct xfs_btree_cur	*cur,
 | |
| 	const struct rcbag_rec	*rec)
 | |
| {
 | |
| 	union xfs_btree_rec	btrec;
 | |
| 
 | |
| 	memcpy(&btrec, rec, sizeof(struct rcbag_rec));
 | |
| 	return xfs_btree_update(cur, &btrec);
 | |
| }
 | |
| 
 | |
| /* Update the record referred to by cur to the value given. */
 | |
| int
 | |
| rcbagbt_insert(
 | |
| 	struct xfs_btree_cur	*cur,
 | |
| 	const struct rcbag_rec	*rec,
 | |
| 	int			*success)
 | |
| {
 | |
| 	struct rcbag_rec	*btrec = (struct rcbag_rec *)&cur->bc_rec;
 | |
| 
 | |
| 	memcpy(btrec, rec, sizeof(struct rcbag_rec));
 | |
| 	return xfs_btree_insert(cur, success);
 | |
| }
 |