// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include "libxfs.h"
#include "avl.h"
#include "btree.h"
#include "globals.h"
#include "incore.h"
#include "agheader.h"
#include "protos.h"
#include "err_protos.h"
#include "avl64.h"
#include "threads.h"

/*
 * note:  there are 4 sets of incore things handled here:
 * block bitmaps, extent trees, uncertain inode list,
 * and inode tree.  The tree-based code uses the AVL
 * tree package used by the IRIX kernel VM code
 * (sys/avl.h).  The inode list code uses the same records
 * as the inode tree code for convenience.  The bitmaps
 * and bitmap operators are mostly macros defined in incore.h.
 * There are one of everything per AG except for extent
 * trees.  There's one duplicate extent tree, one bno and
 * one bcnt extent tree per AG.  Not all of the above exist
 * through all phases.  The duplicate extent tree gets trashed
 * at the end of phase 4.  The bno/bcnt trees don't appear until
 * phase 5.  The uncertain inode list goes away at the end of
 * phase 3.  The inode tree and bno/bnct trees go away after phase 5.
 */

static avl64tree_desc_t	*rt_ext_tree_ptr;	/* dup extent tree for rt */
static pthread_mutex_t	rt_ext_tree_lock;

static struct btree_root **dup_extent_trees;	/* per ag dup extent trees */
static pthread_mutex_t *dup_extent_tree_locks;

static avltree_desc_t	**extent_bno_ptrs;	/*
						 * array of extent tree ptrs
						 * one per ag for free extents
						 * sorted by starting block
						 * number
						 */
static avltree_desc_t	**extent_bcnt_ptrs;	/*
						 * array of extent tree ptrs
						 * one per ag for free extents
						 * sorted by size
						 */

/*
 * duplicate extent tree functions
 */

void
release_dup_extent_tree(
	xfs_agnumber_t		agno)
{
	pthread_mutex_lock(&dup_extent_tree_locks[agno]);
	btree_clear(dup_extent_trees[agno]);
	pthread_mutex_unlock(&dup_extent_tree_locks[agno]);
}

int
add_dup_extent(
	xfs_agnumber_t		agno,
	xfs_agblock_t		startblock,
	xfs_extlen_t		blockcount)
{
	int	ret;
#ifdef XR_DUP_TRACE
	fprintf(stderr, "Adding dup extent - %d/%d %d\n", agno, startblock,
		blockcount);
#endif
	pthread_mutex_lock(&dup_extent_tree_locks[agno]);
	ret = btree_insert(dup_extent_trees[agno], startblock,
				(void *)(uintptr_t)(startblock + blockcount));
	pthread_mutex_unlock(&dup_extent_tree_locks[agno]);
	return ret;
}

int
search_dup_extent(
	xfs_agnumber_t		agno,
	xfs_agblock_t		start_agbno,
	xfs_agblock_t		end_agbno)
{
	unsigned long	bno;
	int		ret;

	pthread_mutex_lock(&dup_extent_tree_locks[agno]);
	if (!btree_find(dup_extent_trees[agno], start_agbno, &bno)) {
		ret = 0;
		goto out;	/* this really shouldn't happen */
	}
	if (bno < end_agbno) {
		ret = 1;
		goto out;
	}
	ret = (uintptr_t)btree_peek_prev(dup_extent_trees[agno], NULL) >
								start_agbno;
out:
	pthread_mutex_unlock(&dup_extent_tree_locks[agno]);
	return ret;
}


/*
 * extent tree stuff is avl trees of duplicate extents,
 * sorted in order by block number.  there is one tree per ag.
 */

static extent_tree_node_t *
mk_extent_tree_nodes(xfs_agblock_t new_startblock,
	xfs_extlen_t new_blockcount, extent_state_t new_state)
{
	extent_tree_node_t *new;

	new = malloc(sizeof(*new));
	if (!new)
		do_error(_("couldn't allocate new extent descriptor.\n"));

	new->avl_node.avl_nextino = NULL;
	new->ex_startblock = new_startblock;
	new->ex_blockcount = new_blockcount;
	new->ex_state = new_state;
	new->next = NULL;
	new->last = NULL;

	return new;
}

void
release_extent_tree_node(extent_tree_node_t *node)
{
	free(node);
}

/*
 * routines to recycle all nodes in a tree.  it walks the tree
 * and puts all nodes back on the free list so the nodes can be
 * reused.  the duplicate and bno/bcnt extent trees for each AG
 * are recycled after they're no longer needed to save memory
 */
static void
release_extent_tree(avltree_desc_t *tree)
{
	extent_tree_node_t	*ext;
	extent_tree_node_t	*tmp;
	extent_tree_node_t	*lext;
	extent_tree_node_t	*ltmp;

	if (tree->avl_firstino == NULL)
		return;

	ext = (extent_tree_node_t *) tree->avl_firstino;

	while (ext != NULL)  {
		tmp = (extent_tree_node_t *) ext->avl_node.avl_nextino;

		/*
		 * ext->next is guaranteed to be set only in bcnt trees
		 */
		if (ext->next != NULL)  {
			lext = ext->next;
			while (lext != NULL)  {
				ltmp = lext->next;
				release_extent_tree_node(lext);
				lext = ltmp;
			}
		}

		release_extent_tree_node(ext);
		ext = tmp;
	}

	tree->avl_root = tree->avl_firstino = NULL;

	return;
}

/*
 * top-level (visible) routines
 */
void
release_agbno_extent_tree(xfs_agnumber_t agno)
{
	release_extent_tree(extent_bno_ptrs[agno]);

	return;
}

void
release_agbcnt_extent_tree(xfs_agnumber_t agno)
{
	release_extent_tree(extent_bcnt_ptrs[agno]);

	return;
}

/*
 * the next 4 routines manage the trees of free extents -- 2 trees
 * per AG.  The first tree is sorted by block number.  The second
 * tree is sorted by extent size.  This is the bno tree.
 */
void
add_bno_extent(xfs_agnumber_t agno, xfs_agblock_t startblock,
		xfs_extlen_t blockcount)
{
	extent_tree_node_t *ext;

	ASSERT(extent_bno_ptrs != NULL);
	ASSERT(extent_bno_ptrs[agno] != NULL);

	ext = mk_extent_tree_nodes(startblock, blockcount, XR_E_FREE);

	if (avl_insert(extent_bno_ptrs[agno], (avlnode_t *) ext) == NULL)  {
		do_error(_("duplicate bno extent range\n"));
	}
}

extent_tree_node_t *
findfirst_bno_extent(xfs_agnumber_t agno)
{
	ASSERT(extent_bno_ptrs != NULL);
	ASSERT(extent_bno_ptrs[agno] != NULL);

	return((extent_tree_node_t *) extent_bno_ptrs[agno]->avl_firstino);
}

extent_tree_node_t *
find_bno_extent(xfs_agnumber_t agno, xfs_agblock_t startblock)
{
	ASSERT(extent_bno_ptrs != NULL);
	ASSERT(extent_bno_ptrs[agno] != NULL);

	return((extent_tree_node_t *) avl_find(extent_bno_ptrs[agno],
						startblock));
}

/*
 * delete a node that's in the tree (pointer obtained by a find routine)
 */
void
get_bno_extent(xfs_agnumber_t agno, extent_tree_node_t *ext)
{
	ASSERT(extent_bno_ptrs != NULL);
	ASSERT(extent_bno_ptrs[agno] != NULL);

	avl_delete(extent_bno_ptrs[agno], &ext->avl_node);

	return;
}

/*
 * the next 4 routines manage the trees of free extents -- 2 trees
 * per AG.  The first tree is sorted by block number.  The second
 * tree is sorted by extent size.  This is the bcnt tree.
 */
void
add_bcnt_extent(xfs_agnumber_t agno, xfs_agblock_t startblock,
		xfs_extlen_t blockcount)
{
	extent_tree_node_t *ext, *prev, *current, *top;
	xfs_agblock_t		tmp_startblock;
	xfs_extlen_t		tmp_blockcount;
	extent_state_t		tmp_state;

	ASSERT(extent_bcnt_ptrs != NULL);
	ASSERT(extent_bcnt_ptrs[agno] != NULL);

	ext = mk_extent_tree_nodes(startblock, blockcount, XR_E_FREE);

	ASSERT(ext->next == NULL);

#ifdef XR_BCNT_TRACE
	fprintf(stderr, "adding bcnt: agno = %d, start = %u, count = %u\n",
			agno, startblock, blockcount);
#endif
	if ((current = (extent_tree_node_t *) avl_find(extent_bcnt_ptrs[agno],
							blockcount)) != NULL)  {
		/*
		 * avl tree code doesn't handle dups so insert
		 * onto linked list in increasing startblock order
		 *
		 * when called from mk_incore_fstree,
		 * startblock is in increasing order.
		 * current is an "anchor" node.
		 * quick check if the new ext goes to the end.
		 * if so, append at the end, using the last field
		 * of the "anchor".
		 */
		ASSERT(current->last != NULL);
		if (startblock > current->last->ex_startblock) {
			current->last->next = ext;
			current->last = ext;
			return;
		}

		/*
		 * scan, to find the proper location for new entry.
		 * this scan is *very* expensive and gets worse with
		 * with increasing entries.
		 */
		top = prev = current;
		while (current != NULL &&
				startblock > current->ex_startblock)  {
			prev = current;
			current = current->next;
		}

		if (top == current)  {
			ASSERT(top == prev);
			/*
			 * new entry should be ahead of current.
			 * to keep the avl tree intact,
			 * swap the values of to-be-inserted element
			 * and the values of the head of the list.
			 * then insert as the 2nd element on the list.
			 *
			 * see the comment in get_bcnt_extent()
			 * as to why we have to do this.
			 */
			tmp_startblock = top->ex_startblock;
			tmp_blockcount = top->ex_blockcount;
			tmp_state = top->ex_state;

			top->ex_startblock = ext->ex_startblock;
			top->ex_blockcount = ext->ex_blockcount;
			top->ex_state = ext->ex_state;

			ext->ex_startblock = tmp_startblock;
			ext->ex_blockcount = tmp_blockcount;
			ext->ex_state = tmp_state;

			current = top->next;
			prev = top;
		}

		prev->next = ext;
		ext->next = current;

		return;
	}

	if (avl_insert(extent_bcnt_ptrs[agno], (avlnode_t *) ext) == NULL)  {
		do_error(_(":  duplicate bno extent range\n"));
	}

	ext->last = ext;	/* ext is an "anchor" node */

	return;
}

extent_tree_node_t *
findfirst_bcnt_extent(xfs_agnumber_t agno)
{
	ASSERT(extent_bcnt_ptrs != NULL);
	ASSERT(extent_bcnt_ptrs[agno] != NULL);

	return((extent_tree_node_t *) extent_bcnt_ptrs[agno]->avl_firstino);
}

extent_tree_node_t *
findbiggest_bcnt_extent(xfs_agnumber_t agno)
{
	ASSERT(extent_bcnt_ptrs != NULL);
	ASSERT(extent_bcnt_ptrs[agno] != NULL);

	return((extent_tree_node_t *) avl_lastino(extent_bcnt_ptrs[agno]->avl_root));
}

extent_tree_node_t *
findnext_bcnt_extent(xfs_agnumber_t agno, extent_tree_node_t *ext)
{
	avlnode_t *nextino;

	if (ext->next != NULL)  {
		ASSERT(ext->ex_blockcount == ext->next->ex_blockcount);
		ASSERT(ext->ex_startblock < ext->next->ex_startblock);
		return(ext->next);
	} else  {
		/*
		 * have to look at the top of the list to get the
		 * correct avl_nextino pointer since that pointer
		 * is maintained and altered by the AVL code.
		 */
		nextino = avl_find(extent_bcnt_ptrs[agno], ext->ex_blockcount);
		ASSERT(nextino != NULL);
		if (nextino->avl_nextino != NULL)  {
			ASSERT(ext->ex_blockcount < ((extent_tree_node_t *)
					nextino->avl_nextino)->ex_blockcount);
		}
		return((extent_tree_node_t *) nextino->avl_nextino);
	}
}

/*
 * this is meant to be called after you walk the bno tree to
 * determine exactly which extent you want (so you'll know the
 * desired value for startblock when you call this routine).
 */
extent_tree_node_t *
get_bcnt_extent(xfs_agnumber_t agno, xfs_agblock_t startblock,
		xfs_extlen_t blockcount)
{
	extent_tree_node_t	*ext, *prev, *top;
	xfs_agblock_t		tmp_startblock;
	xfs_extlen_t		tmp_blockcount;
	extent_state_t		tmp_state;

	prev = NULL;
	ASSERT(extent_bcnt_ptrs != NULL);
	ASSERT(extent_bcnt_ptrs[agno] != NULL);

	if ((ext = (extent_tree_node_t *) avl_find(extent_bcnt_ptrs[agno],
							blockcount)) == NULL)
		return(NULL);

	top = ext;

	if (ext->next != NULL)  {
		/*
		 * pull it off the list
		 */
		while (ext != NULL && startblock != ext->ex_startblock)  {
			prev = ext;
			ext = ext->next;
		}
		ASSERT(ext != NULL);
		if (ext == top)  {
			/*
			 * this node is linked into the tree so we
			 * swap the core values so we can delete
			 * the next item on the list instead of
			 * the head of the list.  This is because
			 * the rest of the tree undoubtedly has
			 * pointers to the piece of memory that
			 * is the head of the list so pulling
			 * the item out of the list and hence
			 * the avl tree would be a bad idea.
			 *
			 * (cheaper than the alternative, a tree
			 * delete of this node followed by a tree
			 * insert of the next node on the list).
			 */
			tmp_startblock = ext->next->ex_startblock;
			tmp_blockcount = ext->next->ex_blockcount;
			tmp_state = ext->next->ex_state;

			ext->next->ex_startblock = ext->ex_startblock;
			ext->next->ex_blockcount = ext->ex_blockcount;
			ext->next->ex_state = ext->ex_state;

			ext->ex_startblock = tmp_startblock;
			ext->ex_blockcount = tmp_blockcount;
			ext->ex_state = tmp_state;

			ext = ext->next;
			prev = top;
		}
		/*
		 * now, a simple list deletion
		 */
		prev->next = ext->next;
		ext->next = NULL;
	} else  {
		/*
		 * no list, just one node.  simply delete
		 */
		avl_delete(extent_bcnt_ptrs[agno], &ext->avl_node);
	}

	ASSERT(ext->ex_startblock == startblock);
	ASSERT(ext->ex_blockcount == blockcount);
	return(ext);
}

static uintptr_t
avl_ext_start(avlnode_t *node)
{
	return((uintptr_t)
		((extent_tree_node_t *) node)->ex_startblock);
}

static uintptr_t
avl_ext_end(avlnode_t *node)
{
	return((uintptr_t) (
		((extent_tree_node_t *) node)->ex_startblock +
		((extent_tree_node_t *) node)->ex_blockcount));
}

/*
 * convert size to an address for the AVL tree code -- the bigger the size,
 * the lower the address so the biggest extent will be first in the tree
 */
static uintptr_t
avl_ext_bcnt_start(avlnode_t *node)
{
/*
	return((uintptr_t) (BCNT_ADDR(((extent_tree_node_t *)
						node)->ex_blockcount)));
*/
	return((uintptr_t) ((extent_tree_node_t *)node)->ex_blockcount);
}

static uintptr_t
avl_ext_bcnt_end(avlnode_t *node)
{
/*
	return((uintptr_t) (BCNT_ADDR(((extent_tree_node_t *)
						node)->ex_blockcount)));
*/
	return((uintptr_t) ((extent_tree_node_t *)node)->ex_blockcount);
}

static avlops_t avl_extent_bcnt_tree_ops = {
	avl_ext_bcnt_start,
	avl_ext_bcnt_end
};

static avlops_t avl_extent_tree_ops = {
	avl_ext_start,
	avl_ext_end
};

/*
 * for real-time extents -- have to dup code since realtime extent
 * startblocks can be 64-bit values.
 */
static rt_extent_tree_node_t *
mk_rt_extent_tree_nodes(xfs_rtblock_t new_startblock,
	xfs_extlen_t new_blockcount, extent_state_t new_state)
{
	rt_extent_tree_node_t *new;

	new = malloc(sizeof(*new));
	if (!new)
		do_error(_("couldn't allocate new extent descriptor.\n"));

	new->avl_node.avl_nextino = NULL;
	new->rt_startblock = new_startblock;
	new->rt_blockcount = new_blockcount;
	new->rt_state = new_state;
	return new;
}

#if 0
void
release_rt_extent_tree_node(rt_extent_tree_node_t *node)
{
	free(node);
}

void
release_rt_extent_tree()
{
	extent_tree_node_t	*ext;
	extent_tree_node_t	*tmp;
	extent_tree_node_t	*lext;
	extent_tree_node_t	*ltmp;
	avl64tree_desc_t	*tree;

	tree = rt_extent_tree_ptr;

	if (tree->avl_firstino == NULL)
		return;

	ext = (extent_tree_node_t *) tree->avl_firstino;

	while (ext != NULL)  {
		tmp = (extent_tree_node_t *) ext->avl_node.avl_nextino;
		release_rt_extent_tree_node(ext);
		ext = tmp;
	}

	tree->avl_root = tree->avl_firstino = NULL;

	return;
}
#endif

/*
 * don't need release functions for realtime tree teardown
 * since we only have one tree, not one per AG
 */
/* ARGSUSED */
void
free_rt_dup_extent_tree(xfs_mount_t *mp)
{
	ASSERT(mp->m_sb.sb_rblocks != 0);
	free(rt_ext_tree_ptr);
	rt_ext_tree_ptr = NULL;
}

/*
 * add a duplicate real-time extent
 */
void
add_rt_dup_extent(xfs_rtblock_t startblock, xfs_extlen_t blockcount)
{
	rt_extent_tree_node_t *first, *last, *ext, *next_ext;
	xfs_rtblock_t new_startblock;
	xfs_extlen_t new_blockcount;

	pthread_mutex_lock(&rt_ext_tree_lock);
	avl64_findranges(rt_ext_tree_ptr, startblock - 1,
		startblock + blockcount + 1,
		(avl64node_t **) &first, (avl64node_t **) &last);
	/*
	 * find adjacent and overlapping extent blocks
	 */
	if (first == NULL && last == NULL)  {
		/* nothing, just make and insert new extent */

		ext = mk_rt_extent_tree_nodes(startblock,
				blockcount, XR_E_MULT);

		if (avl64_insert(rt_ext_tree_ptr,
				(avl64node_t *) ext) == NULL)  {
			do_error(_("duplicate extent range\n"));
		}

		pthread_mutex_unlock(&rt_ext_tree_lock);
		return;
	}

	ASSERT(first != NULL && last != NULL);

	/*
	 * find the new composite range, delete old extent nodes
	 * as we go
	 */
	new_startblock = startblock;
	new_blockcount = blockcount;

	for (ext = first;
		ext != (rt_extent_tree_node_t *) last->avl_node.avl_nextino;
		ext = next_ext)  {
		/*
		 * preserve the next inorder node
		 */
		next_ext = (rt_extent_tree_node_t *) ext->avl_node.avl_nextino;
		/*
		 * just bail if the new extent is contained within an old one
		 */
		if (ext->rt_startblock <= startblock &&
				ext->rt_blockcount >= blockcount) {
			pthread_mutex_unlock(&rt_ext_tree_lock);
			return;
		}
		/*
		 * now check for overlaps and adjacent extents
		 */
		if (ext->rt_startblock + ext->rt_blockcount >= startblock
			|| ext->rt_startblock <= startblock + blockcount)  {

			if (ext->rt_startblock < new_startblock)
				new_startblock = ext->rt_startblock;

			if (ext->rt_startblock + ext->rt_blockcount >
					new_startblock + new_blockcount)
				new_blockcount = ext->rt_startblock +
							ext->rt_blockcount -
							new_startblock;

			avl64_delete(rt_ext_tree_ptr, (avl64node_t *) ext);
			continue;
		}
	}

	ext = mk_rt_extent_tree_nodes(new_startblock,
				new_blockcount, XR_E_MULT);

	if (avl64_insert(rt_ext_tree_ptr, (avl64node_t *) ext) == NULL)  {
		do_error(_("duplicate extent range\n"));
	}

	pthread_mutex_unlock(&rt_ext_tree_lock);
	return;
}

/*
 * returns 1 if block is a dup, 0 if not
 */
/* ARGSUSED */
int
search_rt_dup_extent(xfs_mount_t *mp, xfs_rtblock_t bno)
{
	int ret;

	pthread_mutex_lock(&rt_ext_tree_lock);
	if (avl64_findrange(rt_ext_tree_ptr, bno) != NULL)
		ret = 1;
	else
		ret = 0;
	pthread_mutex_unlock(&rt_ext_tree_lock);
	return(ret);
}

static uint64_t
avl64_rt_ext_start(avl64node_t *node)
{
	return(((rt_extent_tree_node_t *) node)->rt_startblock);
}

static uint64_t
avl64_ext_end(avl64node_t *node)
{
	return(((rt_extent_tree_node_t *) node)->rt_startblock +
		((rt_extent_tree_node_t *) node)->rt_blockcount);
}

static avl64ops_t avl64_extent_tree_ops = {
	avl64_rt_ext_start,
	avl64_ext_end
};

void
incore_ext_init(xfs_mount_t *mp)
{
	int i;
	xfs_agnumber_t agcount = mp->m_sb.sb_agcount;

	pthread_mutex_init(&rt_ext_tree_lock, NULL);

	dup_extent_trees = calloc(agcount, sizeof(struct btree_root *));
	if (!dup_extent_trees)
		do_error(_("couldn't malloc dup extent tree descriptor table\n"));

	dup_extent_tree_locks = calloc(agcount, sizeof(pthread_mutex_t));
	if (!dup_extent_tree_locks)
		do_error(_("couldn't malloc dup extent tree descriptor table\n"));

	if ((extent_bno_ptrs = malloc(agcount *
					sizeof(avltree_desc_t *))) == NULL)
		do_error(
	_("couldn't malloc free by-bno extent tree descriptor table\n"));

	if ((extent_bcnt_ptrs = malloc(agcount *
					sizeof(avltree_desc_t *))) == NULL)
		do_error(
	_("couldn't malloc free by-bcnt extent tree descriptor table\n"));

	for (i = 0; i < agcount; i++)  {
		if ((extent_bno_ptrs[i] =
				malloc(sizeof(avltree_desc_t))) == NULL)
			do_error(
			_("couldn't malloc bno extent tree descriptor\n"));
		if ((extent_bcnt_ptrs[i] =
				malloc(sizeof(avltree_desc_t))) == NULL)
			do_error(
			_("couldn't malloc bcnt extent tree descriptor\n"));
	}

	for (i = 0; i < agcount; i++)  {
		btree_init(&dup_extent_trees[i]);
		pthread_mutex_init(&dup_extent_tree_locks[i], NULL);
		avl_init_tree(extent_bno_ptrs[i], &avl_extent_tree_ops);
		avl_init_tree(extent_bcnt_ptrs[i], &avl_extent_bcnt_tree_ops);
	}

	if ((rt_ext_tree_ptr = malloc(sizeof(avl64tree_desc_t))) == NULL)
		do_error(_("couldn't malloc dup rt extent tree descriptor\n"));

	avl64_init_tree(rt_ext_tree_ptr, &avl64_extent_tree_ops);
}

/*
 * this routine actually frees all the memory used to track per-AG trees
 */
void
incore_ext_teardown(xfs_mount_t *mp)
{
	xfs_agnumber_t i;

	for (i = 0; i < mp->m_sb.sb_agcount; i++)  {
		btree_destroy(dup_extent_trees[i]);
		free(extent_bno_ptrs[i]);
		free(extent_bcnt_ptrs[i]);
	}

	free(dup_extent_trees);
	free(extent_bcnt_ptrs);
	free(extent_bno_ptrs);

	dup_extent_trees = NULL;
	extent_bcnt_ptrs = NULL;
	extent_bno_ptrs = NULL;
}

static int
count_extents(xfs_agnumber_t agno, avltree_desc_t *tree, int whichtree)
{
	extent_tree_node_t *node;
	int i = 0;

	node = (extent_tree_node_t *) tree->avl_firstino;

	while (node != NULL)  {
		i++;
		if (whichtree)
			node = findnext_bcnt_extent(agno, node);
		else
			node = findnext_bno_extent(node);
	}

	return(i);
}

int
count_bno_extents_blocks(xfs_agnumber_t agno, uint *numblocks)
{
	uint64_t nblocks;
	extent_tree_node_t *node;
	int i = 0;

	ASSERT(agno < glob_agcount);

	nblocks = 0;

	node = (extent_tree_node_t *) extent_bno_ptrs[agno]->avl_firstino;

	while (node != NULL) {
		nblocks += node->ex_blockcount;
		i++;
		node = findnext_bno_extent(node);
	}

	*numblocks = nblocks;
	return(i);
}

int
count_bno_extents(xfs_agnumber_t agno)
{
	ASSERT(agno < glob_agcount);
	return(count_extents(agno, extent_bno_ptrs[agno], 0));
}

int
count_bcnt_extents(xfs_agnumber_t agno)
{
	ASSERT(agno < glob_agcount);
	return(count_extents(agno, extent_bcnt_ptrs[agno], 1));
}
