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

#include "libxfs.h"
#include "avl.h"
#include "globals.h"
#include "agheader.h"
#include "incore.h"
#include "protos.h"
#include "err_protos.h"
#include "dinode.h"
#include "versions.h"
#include "prefetch.h"
#include "progress.h"

/*
 * validates inode block or chunk, returns # of good inodes
 * the dinodes are verified using verify_uncertain_dinode() which
 * means only the basic inode info is checked, no fork checks.
 */
static int
check_aginode_block(xfs_mount_t	*mp,
			xfs_agnumber_t	agno,
			xfs_agblock_t	agbno)
{

	xfs_dinode_t	*dino_p;
	int		i;
	int		cnt = 0;
	xfs_buf_t	*bp;

	/*
	 * it's ok to read these possible inode blocks in one at
	 * a time because they don't belong to known inodes (if
	 * they did, we'd know about them courtesy of the incore inode
	 * tree and we wouldn't be here and we stale the buffers out
	 * so no one else will overlap them.
	 */
	bp = libxfs_readbuf(mp->m_dev, XFS_AGB_TO_DADDR(mp, agno, agbno),
			XFS_FSB_TO_BB(mp, 1), 0, NULL);
	if (!bp) {
		do_warn(_("cannot read agbno (%u/%u), disk block %" PRId64 "\n"),
			agno, agbno, XFS_AGB_TO_DADDR(mp, agno, agbno));
		return(0);
	}

	for (i = 0; i < mp->m_sb.sb_inopblock; i++)  {
		dino_p = xfs_make_iptr(mp, bp, i);
		if (!verify_uncertain_dinode(mp, dino_p, agno,
				XFS_OFFBNO_TO_AGINO(mp, agbno, i)))
			cnt++;
	}
	if (cnt)
		bp->b_ops = &xfs_inode_buf_ops;

	libxfs_putbuf(bp);
	return(cnt);
}

/*
 * tries to establish if the inode really exists in a valid
 * inode chunk.  returns number of new inodes if things are good
 * and 0 if bad.  start is the start of the discovered inode chunk.
 * routine assumes that ino is a legal inode number
 * (verified by verify_inum()).  If the inode chunk turns out
 * to be good, this routine will put the inode chunk into
 * the good inode chunk tree if required.
 *
 * the verify_(ag)inode* family of routines are utility
 * routines called by check_uncertain_aginodes() and
 * process_uncertain_aginodes().
 */
static int
verify_inode_chunk(xfs_mount_t		*mp,
			xfs_ino_t	ino,
			xfs_ino_t	*start_ino)
{
	xfs_agnumber_t	agno;
	xfs_agino_t	agino;
	xfs_agino_t	start_agino;
	xfs_agblock_t	agbno;
	xfs_agblock_t	start_agbno = 0;
	xfs_agblock_t	end_agbno;
	xfs_agblock_t	max_agbno;
	xfs_agblock_t	cur_agbno;
	xfs_agblock_t	chunk_start_agbno;
	xfs_agblock_t	chunk_stop_agbno;
	ino_tree_node_t *irec_before_p = NULL;
	ino_tree_node_t *irec_after_p = NULL;
	ino_tree_node_t *irec_p;
	ino_tree_node_t *irec_next_p;
	int		irec_cnt;
	int		ino_cnt = 0;
	int		num_blks;
	int		i;
	int		j;
	int		state;
	xfs_extlen_t	blen;

	agno = XFS_INO_TO_AGNO(mp, ino);
	agino = XFS_INO_TO_AGINO(mp, ino);
	agbno = XFS_INO_TO_AGBNO(mp, ino);
	*start_ino = NULLFSINO;

	ASSERT(mp->m_ialloc_blks > 0);

	if (agno == mp->m_sb.sb_agcount - 1)
		max_agbno = mp->m_sb.sb_dblocks -
			(xfs_rfsblock_t) mp->m_sb.sb_agblocks * agno;
	else
		max_agbno = mp->m_sb.sb_agblocks;

	/*
	 * is the inode beyond the end of the AG?
	 */
	if (agbno >= max_agbno)
		return(0);

	/*
	 * check for the easy case, inodes per block >= XFS_INODES_PER_CHUNK
	 * (multiple chunks per block)
	 */
	if (mp->m_ialloc_blks == 1)  {
		if (agbno > max_agbno)
			return 0;
		if (check_aginode_block(mp, agno, agino) == 0)
			return 0;

		pthread_mutex_lock(&ag_locks[agno].lock);

		state = get_bmap(agno, agbno);
		switch (state) {
		case XR_E_INO:
			do_warn(
		_("uncertain inode block %d/%d already known\n"),
				agno, agbno);
			break;
		case XR_E_UNKNOWN:
		case XR_E_FREE1:
		case XR_E_FREE:
			set_bmap(agno, agbno, XR_E_INO);
			break;
		case XR_E_MULT:
		case XR_E_INUSE:
		case XR_E_INUSE_FS:
		case XR_E_FS_MAP:
			/*
			 * if block is already claimed, forget it.
			 */
			do_warn(
		_("inode block %d/%d multiply claimed, (state %d)\n"),
				agno, agbno, state);
			set_bmap(agno, agbno, XR_E_MULT);
			pthread_mutex_unlock(&ag_locks[agno].lock);
			return(0);
		default:
			do_warn(
		_("inode block %d/%d bad state, (state %d)\n"),
				agno, agbno, state);
			set_bmap(agno, agbno, XR_E_INO);
			break;
		}

		pthread_mutex_unlock(&ag_locks[agno].lock);

		start_agino = XFS_AGB_TO_AGINO(mp, agbno);
		*start_ino = XFS_AGINO_TO_INO(mp, agno, start_agino);

		/*
		 * put new inode record(s) into inode tree
		 */
		for (j = 0; j < chunks_pblock; j++)  {
			if ((irec_p = find_inode_rec(mp, agno, start_agino))
					== NULL)  {
				irec_p = set_inode_free_alloc(mp, agno,
							start_agino);
				for (i = 1; i < XFS_INODES_PER_CHUNK; i++)
					set_inode_free(irec_p, i);
			}
			if (start_agino <= agino && agino <
					start_agino + XFS_INODES_PER_CHUNK)
				set_inode_used(irec_p, agino - start_agino);

			start_agino += XFS_INODES_PER_CHUNK;
			ino_cnt += XFS_INODES_PER_CHUNK;
		}

		return(ino_cnt);
	} else if (fs_aligned_inodes)  {
		/*
		 * next easy case -- aligned inode filesystem.
		 * just check out the chunk
		 */
		start_agbno = rounddown(XFS_INO_TO_AGBNO(mp, ino),
					fs_ino_alignment);
		end_agbno = start_agbno + mp->m_ialloc_blks;

		/*
		 * if this fs has aligned inodes but the end of the
		 * chunk is beyond the end of the ag, this is a bad
		 * chunk
		 */
		if (end_agbno > max_agbno)
			return(0);

		/*
		 * check out all blocks in chunk
		 */
		ino_cnt = 0;
		for (cur_agbno = start_agbno; cur_agbno < end_agbno;
						cur_agbno++)  {
			ino_cnt += check_aginode_block(mp, agno, cur_agbno);
		}

		/*
		 * if we lose either 2 blocks worth of inodes or >25% of
		 * the chunk, just forget it.
		 */
		if (ino_cnt < XFS_INODES_PER_CHUNK - 2 * mp->m_sb.sb_inopblock
				|| ino_cnt < XFS_INODES_PER_CHUNK - 16)
			return(0);

		/*
		 * ok, put the record into the tree, if no conflict.
		 */
		if (find_uncertain_inode_rec(agno,
				XFS_AGB_TO_AGINO(mp, start_agbno)))
			return(0);

		start_agino = XFS_AGB_TO_AGINO(mp, start_agbno);
		*start_ino = XFS_AGINO_TO_INO(mp, agno, start_agino);

		irec_p = set_inode_free_alloc(mp, agno,
				XFS_AGB_TO_AGINO(mp, start_agbno));

		for (i = 1; i < XFS_INODES_PER_CHUNK; i++)
			set_inode_free(irec_p, i);

		ASSERT(start_agino <= agino &&
				start_agino + XFS_INODES_PER_CHUNK > agino);

		set_inode_used(irec_p, agino - start_agino);

		return(XFS_INODES_PER_CHUNK);
	}

	/*
	 * hard case -- pre-6.3 filesystem.
	 * set default start/end agbnos and ensure agbnos are legal.
	 * we're setting a range [start_agbno, end_agbno) such that
	 * a discovered inode chunk completely within that range
	 * would include the inode passed into us.
	 */
	if (mp->m_ialloc_blks > 1)  {
		if (agino > mp->m_ialloc_inos)
			start_agbno = agbno - mp->m_ialloc_blks + 1;
		else
			start_agbno = 1;
	}

	end_agbno = agbno + mp->m_ialloc_blks;

	if (end_agbno > max_agbno)
		end_agbno = max_agbno;

	/*
	 * search tree for known inodes within +/- 1 inode chunk range
	 */
	irec_before_p = irec_after_p = NULL;

	find_inode_rec_range(mp, agno, XFS_AGB_TO_AGINO(mp, start_agbno),
		XFS_OFFBNO_TO_AGINO(mp, end_agbno, mp->m_sb.sb_inopblock - 1),
		&irec_before_p, &irec_after_p);

	/*
	 * if we have known inode chunks in our search range, establish
	 * their start and end-points to tighten our search range.  range
	 * is [start, end) -- e.g. max/end agbno is one beyond the
	 * last block to be examined.  the avl routines work this way.
	 */
	if (irec_before_p)  {
		/*
		 * only one inode record in the range, move one boundary in
		 */
		if (irec_before_p == irec_after_p)  {
			if (irec_before_p->ino_startnum < agino)
				start_agbno = XFS_AGINO_TO_AGBNO(mp,
						irec_before_p->ino_startnum +
						XFS_INODES_PER_CHUNK);
			else
				end_agbno = XFS_AGINO_TO_AGBNO(mp,
						irec_before_p->ino_startnum);
		}

		/*
		 * find the start of the gap in the search range (which
		 * should contain our unknown inode).  if the only irec
		 * within +/- 1 chunks starts after the inode we're
		 * looking for, skip this stuff since the end_agbno
		 * of the range has already been trimmed in to not
		 * include that irec.
		 */
		if (irec_before_p->ino_startnum < agino)  {
			irec_p = irec_before_p;
			irec_next_p = next_ino_rec(irec_p);

			while(irec_next_p != NULL &&
				irec_p->ino_startnum + XFS_INODES_PER_CHUNK ==
					irec_next_p->ino_startnum)  {
				irec_p = irec_next_p;
				irec_next_p = next_ino_rec(irec_next_p);
			}

			start_agbno = XFS_AGINO_TO_AGBNO(mp,
						irec_p->ino_startnum) +
						mp->m_ialloc_blks;

			/*
			 * we know that the inode we're trying to verify isn't
			 * in an inode chunk so the next ino_rec marks the end
			 * of the gap -- is it within the search range?
			 */
			if (irec_next_p != NULL &&
					agino + mp->m_ialloc_inos >=
						irec_next_p->ino_startnum)
				end_agbno = XFS_AGINO_TO_AGBNO(mp,
						irec_next_p->ino_startnum);
		}

		ASSERT(start_agbno < end_agbno);
	}

	/*
	 * if the gap is too small to contain a chunk, we lose.
	 * this means that inode chunks known to be good surround
	 * the inode in question and that the space between them
	 * is too small for a legal inode chunk
	 */
	if (end_agbno - start_agbno < mp->m_ialloc_blks)
		return(0);

	/*
	 * now grunge around the disk, start at the inode block and
	 * go in each direction until you hit a non-inode block or
	 * run into a range boundary.  A non-inode block is block
	 * with *no* good inodes in it.  Unfortunately, we can't
	 * co-opt bad blocks into inode chunks (which might take
	 * care of disk blocks that turn into zeroes) because the
	 * filesystem could very well allocate two inode chunks
	 * with a one block file in between and we'd zap the file.
	 * We're better off just losing the rest of the
	 * inode chunk instead.
	 */
	for (cur_agbno = agbno; cur_agbno >= start_agbno; cur_agbno--)  {
		/*
		 * if the block has no inodes, it's a bad block so
		 * break out now without decrementing cur_agbno so
		 * chunk start blockno will be set to the last good block
		 */
		if (!(irec_cnt = check_aginode_block(mp, agno, cur_agbno)))
			break;
		ino_cnt += irec_cnt;
	}

	chunk_start_agbno = cur_agbno + 1;

	for (cur_agbno = agbno + 1; cur_agbno < end_agbno; cur_agbno++)   {
		/*
		 * if the block has no inodes, it's a bad block so
		 * break out now without incrementing cur_agbno so
		 * chunk start blockno will be set to the block
		 * immediately after the last good block.
		 */
		if (!(irec_cnt = check_aginode_block(mp, agno, cur_agbno)))
			break;
		ino_cnt += irec_cnt;
	}

	chunk_stop_agbno = cur_agbno;

	num_blks = chunk_stop_agbno - chunk_start_agbno;

	if (num_blks < mp->m_ialloc_blks || ino_cnt == 0)
		return 0;

	/*
	 * XXX - later - if the entire range is selected and they're all
	 * good inodes, keep searching in either direction.
	 * until you the range of inodes end, then split into chunks
	 * for now, just take one chunk's worth starting at the lowest
	 * possible point and hopefully we'll pick the rest up later.
	 *
	 * XXX - if we were going to fix up an inode chunk for
	 * any good inodes in the chunk, this is where we would
	 * do it.  For now, keep it simple and lose the rest of
	 * the chunk
	 */

	if (num_blks % mp->m_ialloc_blks != 0)  {
		num_blks = rounddown(num_blks, mp->m_ialloc_blks);
		chunk_stop_agbno = chunk_start_agbno + num_blks;
	}

	/*
	 * ok, we've got a candidate inode chunk.  now we have to
	 * verify that we aren't trying to use blocks that are already
	 * in use.  If so, mark them as multiply claimed since odds
	 * are very low that we found this chunk by stumbling across
	 * user data -- we're probably here as a result of a directory
	 * entry or an iunlinked pointer
	 */
	pthread_mutex_lock(&ag_locks[agno].lock);
	for (cur_agbno = chunk_start_agbno;
	     cur_agbno < chunk_stop_agbno;
	     cur_agbno += blen)  {
		state = get_bmap_ext(agno, cur_agbno, chunk_stop_agbno, &blen);
		switch (state) {
		case XR_E_MULT:
		case XR_E_INUSE:
		case XR_E_INUSE_FS:
		case XR_E_FS_MAP:
			do_warn(
	_("inode block %d/%d multiply claimed, (state %d)\n"),
				agno, cur_agbno, state);
			set_bmap_ext(agno, cur_agbno, blen, XR_E_MULT);
			pthread_mutex_unlock(&ag_locks[agno].lock);
			return 0;
		case XR_E_INO:
			do_error(
	_("uncertain inode block overlap, agbno = %d, ino = %" PRIu64 "\n"),
				agbno, ino);
			break;
		default:
			break;
		}
	}
	pthread_mutex_unlock(&ag_locks[agno].lock);

	/*
	 * ok, chunk is good.  put the record into the tree if required,
	 * and fill in the bitmap.  All inodes will be marked as "free"
	 * except for the one that led us to discover the chunk.  That's
	 * ok because we'll override the free setting later if the
	 * contents of the inode indicate it's in use.
	 */
	start_agino = XFS_AGB_TO_AGINO(mp, chunk_start_agbno);
	*start_ino = XFS_AGINO_TO_INO(mp, agno, start_agino);

	ASSERT(find_inode_rec(mp, agno, start_agino) == NULL);

	irec_p = set_inode_free_alloc(mp, agno, start_agino);
	for (i = 1; i < XFS_INODES_PER_CHUNK; i++)
		set_inode_free(irec_p, i);

	ASSERT(start_agino <= agino &&
			start_agino + XFS_INODES_PER_CHUNK > agino);

	set_inode_used(irec_p, agino - start_agino);

	pthread_mutex_lock(&ag_locks[agno].lock);

	for (cur_agbno = chunk_start_agbno;
	     cur_agbno < chunk_stop_agbno;
	     cur_agbno += blen)  {
		state = get_bmap_ext(agno, cur_agbno, chunk_stop_agbno, &blen);
		switch (state) {
		case XR_E_INO:
			do_error(
		_("uncertain inode block %" PRIu64 " already known\n"),
				XFS_AGB_TO_FSB(mp, agno, cur_agbno));
			break;
		case XR_E_UNKNOWN:
		case XR_E_FREE1:
		case XR_E_FREE:
			set_bmap_ext(agno, cur_agbno, blen, XR_E_INO);
			break;
		case XR_E_MULT:
		case XR_E_INUSE:
		case XR_E_INUSE_FS:
		case XR_E_FS_MAP:
			do_error(
		_("inode block %d/%d multiply claimed, (state %d)\n"),
				agno, cur_agbno, state);
			break;
		default:
			do_warn(
		_("inode block %d/%d bad state, (state %d)\n"),
				agno, cur_agbno, state);
			set_bmap_ext(agno, cur_agbno, blen, XR_E_INO);
			break;
		}
	}
	pthread_mutex_unlock(&ag_locks[agno].lock);

	return(ino_cnt);
}

/*
 * same as above only for ag inode chunks
 */
static int
verify_aginode_chunk(xfs_mount_t	*mp,
			xfs_agnumber_t	agno,
			xfs_agino_t	agino,
			xfs_agino_t	*agino_start)
{
	xfs_ino_t	ino;
	int		res;

	res = verify_inode_chunk(mp, XFS_AGINO_TO_INO(mp, agno, agino), &ino);

	if (res)
		*agino_start = XFS_INO_TO_AGINO(mp, ino);
	else
		*agino_start = NULLAGINO;

	return(res);
}

/*
 * this does the same as the two above only it returns a pointer
 * to the inode record in the good inode tree
 */
static ino_tree_node_t *
verify_aginode_chunk_irec(xfs_mount_t	*mp,
			xfs_agnumber_t	agno,
			xfs_agino_t	agino)
{
	xfs_agino_t start_agino;
	ino_tree_node_t *irec = NULL;

	if (verify_aginode_chunk(mp, agno, agino, &start_agino))
		irec = find_inode_rec(mp, agno, start_agino);

	return(irec);
}

/*
 * Set the state of an inode block during inode chunk processing. The block is
 * expected to be in the free or inode state. If free, it transitions to the
 * inode state. Warn if the block is in neither expected state as this indicates
 * multiply claimed blocks.
 */
static void
process_inode_agbno_state(
	struct xfs_mount	*mp,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno)
{
	int state;

	pthread_mutex_lock(&ag_locks[agno].lock);
	state = get_bmap(agno, agbno);
	switch (state) {
	case XR_E_INO:	/* already marked */
		break;
	case XR_E_UNKNOWN:
	case XR_E_FREE:
	case XR_E_FREE1:
		set_bmap(agno, agbno, XR_E_INO);
		break;
	case XR_E_BAD_STATE:
		do_error(_("bad state in block map %d\n"), state);
		break;
	default:
		set_bmap(agno, agbno, XR_E_MULT);
		do_warn(
	_("inode block %" PRIu64 " multiply claimed, state was %d\n"),
			XFS_AGB_TO_FSB(mp, agno, agbno), state);
		break;
	}
	pthread_mutex_unlock(&ag_locks[agno].lock);
}

/*
 * processes an inode allocation chunk/block, returns 1 on I/O errors,
 * 0 otherwise
 *
 * *bogus is set to 1 if the entire set of inodes is bad.
 */
static int
process_inode_chunk(
	xfs_mount_t 		*mp,
	xfs_agnumber_t		agno,
	int 			num_inos,
	ino_tree_node_t 	*first_irec,
	int 			ino_discovery,
	int 			check_dups,
	int 			extra_attr_check,
	int 			*bogus)
{
	xfs_ino_t		parent;
	ino_tree_node_t		*ino_rec;
	xfs_buf_t		**bplist;
	xfs_dinode_t		*dino;
	int			icnt;
	int			status;
	int			is_used;
	int			ino_dirty;
	int			irec_offset;
	int			ibuf_offset;
	xfs_agino_t		agino;
	xfs_agblock_t		agbno;
	xfs_ino_t		ino;
	int			dirty = 0;
	int			isa_dir = 0;
	int			blks_per_cluster;
	int			cluster_count;
	int			bp_index;
	int			cluster_offset;

	ASSERT(first_irec != NULL);
	ASSERT(XFS_AGINO_TO_OFFSET(mp, first_irec->ino_startnum) == 0);

	*bogus = 0;
	ASSERT(mp->m_ialloc_blks > 0);

	blks_per_cluster = mp->m_inode_cluster_size >> mp->m_sb.sb_blocklog;
	if (blks_per_cluster == 0)
		blks_per_cluster = 1;
	cluster_count = XFS_INODES_PER_CHUNK / inodes_per_cluster;
	if (cluster_count == 0)
		cluster_count = 1;

	/*
	 * get all blocks required to read in this chunk (may wind up
	 * having to process more chunks in a multi-chunk per block fs)
	 */
	agbno = XFS_AGINO_TO_AGBNO(mp, first_irec->ino_startnum);

	/*
	 * set up first irec
	 */
	ino_rec = first_irec;
	irec_offset = 0;

	bplist = malloc(cluster_count * sizeof(xfs_buf_t *));
	if (bplist == NULL)
		do_error(_("failed to allocate %zd bytes of memory\n"),
			cluster_count * sizeof(xfs_buf_t *));

	for (bp_index = 0; bp_index < cluster_count; bp_index++) {
		/*
		 * Skip the cluster buffer if the first inode is sparse. The
		 * remaining inodes in the cluster share the same state as
		 * sparse inodes occur at cluster granularity.
		 */
		if (is_inode_sparse(ino_rec, irec_offset)) {
			pftrace("skip sparse inode, startnum 0x%x idx %d",
				ino_rec->ino_startnum, irec_offset);
			bplist[bp_index] = NULL;
			goto next_readbuf;
		}

		pftrace("about to read off %llu in AG %d",
			XFS_AGB_TO_DADDR(mp, agno, agbno), agno);

		bplist[bp_index] = libxfs_readbuf(mp->m_dev,
					XFS_AGB_TO_DADDR(mp, agno, agbno),
					XFS_FSB_TO_BB(mp, blks_per_cluster), 0,
					&xfs_inode_buf_ops);
		if (!bplist[bp_index]) {
			do_warn(_("cannot read inode %" PRIu64 ", disk block %" PRId64 ", cnt %d\n"),
				XFS_AGINO_TO_INO(mp, agno, first_irec->ino_startnum),
				XFS_AGB_TO_DADDR(mp, agno, agbno),
				XFS_FSB_TO_BB(mp, blks_per_cluster));
			while (bp_index > 0) {
				bp_index--;
				libxfs_putbuf(bplist[bp_index]);
			}
			free(bplist);
			return(1);
		}

		pftrace("readbuf %p (%llu, %d) in AG %d", bplist[bp_index],
			(long long)XFS_BUF_ADDR(bplist[bp_index]),
			bplist[bp_index]->b_bcount, agno);

		bplist[bp_index]->b_ops = &xfs_inode_buf_ops;

next_readbuf:
		irec_offset += mp->m_sb.sb_inopblock * blks_per_cluster;
		agbno += blks_per_cluster;
	}
	agbno = XFS_AGINO_TO_AGBNO(mp, first_irec->ino_startnum);

	/*
	 * initialize counters
	 */
	irec_offset = 0;
	ibuf_offset = 0;
	cluster_offset = 0;
	icnt = 0;
	status = 0;
	bp_index = 0;

	/*
	 * verify inode chunk if necessary
	 */
	if (ino_discovery)  {
		for (;;)  {
			agino = irec_offset + ino_rec->ino_startnum;

			/* no buffers for sparse clusters */
			if (bplist[bp_index]) {
				/* make inode pointer */
				dino = xfs_make_iptr(mp, bplist[bp_index],
						     cluster_offset);

				/*
				 * we always think that the root and realtime
				 * inodes are verified even though we may have
				 * to reset them later to keep from losing the
				 * chunk that they're in
				 */
				if (verify_dinode(mp, dino, agno, agino) == 0 ||
						(agno == 0 &&
						(mp->m_sb.sb_rootino == agino ||
						 mp->m_sb.sb_rsumino == agino ||
						 mp->m_sb.sb_rbmino == agino)))
					status++;
			}

			irec_offset++;
			icnt++;
			cluster_offset++;

			if (icnt == mp->m_ialloc_inos &&
					irec_offset == XFS_INODES_PER_CHUNK)  {
				/*
				 * done! - finished up irec and block
				 * simultaneously
				 */
				break;
			} else if (irec_offset == XFS_INODES_PER_CHUNK)  {
				/*
				 * get new irec (multiple chunks per block fs)
				 */
				ino_rec = next_ino_rec(ino_rec);
				ASSERT(ino_rec->ino_startnum == agino + 1);
				irec_offset = 0;
			}
			if (cluster_offset == inodes_per_cluster) {
				bp_index++;
				cluster_offset = 0;
			}
		}

		/*
		 * if chunk/block is bad, blow it off.  the inode records
		 * will be deleted by the caller if appropriate.
		 */
		if (!status)  {
			*bogus = 1;
			for (bp_index = 0; bp_index < cluster_count; bp_index++)
				if (bplist[bp_index])
					libxfs_putbuf(bplist[bp_index]);
			free(bplist);
			return(0);
		}

		/*
		 * reset irec and counters
		 */
		ino_rec = first_irec;

		irec_offset = 0;
		cluster_offset = 0;
		bp_index = 0;
		icnt = 0;
		status = 0;
	}

	/*
	 * mark block as an inode block in the incore bitmap
	 */
	if (!is_inode_sparse(ino_rec, irec_offset))
		process_inode_agbno_state(mp, agno, agbno);

	for (;;) {
		agino = irec_offset + ino_rec->ino_startnum;
		ino = XFS_AGINO_TO_INO(mp, agno, agino);

		if (is_inode_sparse(ino_rec, irec_offset))
			goto process_next;

		/* make inode pointer */
		dino = xfs_make_iptr(mp, bplist[bp_index], cluster_offset);


		is_used = 3;
		ino_dirty = 0;
		parent = 0;

		status = process_dinode(mp, dino, agno, agino,
				is_inode_free(ino_rec, irec_offset),
				&ino_dirty, &is_used,ino_discovery, check_dups,
				extra_attr_check, &isa_dir, &parent);

		ASSERT(is_used != 3);
		if (ino_dirty) {
			dirty = 1;
			libxfs_dinode_calc_crc(mp, dino);
		}

		/*
		 * XXX - if we want to try and keep
		 * track of whether we need to bang on
		 * the inode maps (instead of just
		 * blindly reconstructing them like
		 * we do now, this is where to start.
		 */
		if (is_used)  {
			if (is_inode_free(ino_rec, irec_offset))  {
				if (verbose || no_modify)  {
					do_warn(
	_("imap claims in-use inode %" PRIu64 " is free, "),
						ino);
				}

				if (verbose || !no_modify)
					do_warn(_("correcting imap\n"));
				else
					do_warn(_("would correct imap\n"));
			}
			set_inode_used(ino_rec, irec_offset);

			/*
			 * store the on-disk file type for comparing in
			 * phase 6.
			 */
			set_inode_ftype(ino_rec, irec_offset,
				libxfs_mode_to_ftype(be16_to_cpu(dino->di_mode)));

			/*
			 * store on-disk nlink count for comparing in phase 7
			 */
			set_inode_disk_nlinks(ino_rec, irec_offset,
				dino->di_version > 1
					? be32_to_cpu(dino->di_nlink)
					: be16_to_cpu(dino->di_onlink));

		} else  {
			set_inode_free(ino_rec, irec_offset);
		}

		/*
		 * if we lose the root inode, or it turns into
		 * a non-directory, that allows us to double-check
		 * later whether or not we need to reinitialize it.
		 */
		if (isa_dir)  {
			set_inode_isadir(ino_rec, irec_offset);
			/*
			 * we always set the parent but
			 * we may as well wait until
			 * phase 4 (no inode discovery)
			 * because the parent info will
			 * be solid then.
			 */
			if (!ino_discovery)  {
				ASSERT(parent != 0);
				set_inode_parent(ino_rec, irec_offset, parent);
				ASSERT(parent ==
					get_inode_parent(ino_rec, irec_offset));
			}
		} else  {
			clear_inode_isadir(ino_rec, irec_offset);
		}

		if (status)  {
			if (mp->m_sb.sb_rootino == ino) {
				need_root_inode = 1;

				if (!no_modify)  {
					do_warn(
	_("cleared root inode %" PRIu64 "\n"),
						ino);
				} else  {
					do_warn(
	_("would clear root inode %" PRIu64 "\n"),
						ino);
				}
			} else if (mp->m_sb.sb_rbmino == ino) {
				need_rbmino = 1;

				if (!no_modify)  {
					do_warn(
	_("cleared realtime bitmap inode %" PRIu64 "\n"),
						ino);
				} else  {
					do_warn(
	_("would clear realtime bitmap inode %" PRIu64 "\n"),
						ino);
				}
			} else if (mp->m_sb.sb_rsumino == ino) {
				need_rsumino = 1;

				if (!no_modify)  {
					do_warn(
	_("cleared realtime summary inode %" PRIu64 "\n"),
						ino);
				} else  {
					do_warn(
	_("would clear realtime summary inode %" PRIu64 "\n"),
						ino);
				}
			} else if (!no_modify)  {
				do_warn(_("cleared inode %" PRIu64 "\n"),
					ino);
			} else  {
				do_warn(_("would have cleared inode %" PRIu64 "\n"),
					ino);
			}
			clear_inode_was_rl(ino_rec, irec_offset);
		}

process_next:
		irec_offset++;
		ibuf_offset++;
		icnt++;
		cluster_offset++;

		if (icnt == mp->m_ialloc_inos &&
				irec_offset == XFS_INODES_PER_CHUNK)  {
			/*
			 * done! - finished up irec and block simultaneously
			 */
			for (bp_index = 0; bp_index < cluster_count; bp_index++) {
				if (!bplist[bp_index])
					continue;

				pftrace("put/writebuf %p (%llu) in AG %d",
					bplist[bp_index], (long long)
					XFS_BUF_ADDR(bplist[bp_index]), agno);

				if (dirty && !no_modify)
					libxfs_writebuf(bplist[bp_index], 0);
				else
					libxfs_putbuf(bplist[bp_index]);
			}
			free(bplist);
			break;
		} else if (ibuf_offset == mp->m_sb.sb_inopblock)  {
			/*
			 * mark block as an inode block in the incore bitmap
			 * and reset inode buffer offset counter
			 */
			ibuf_offset = 0;
			agbno++;

			if (!is_inode_sparse(ino_rec, irec_offset))
				process_inode_agbno_state(mp, agno, agbno);
		} else if (irec_offset == XFS_INODES_PER_CHUNK)  {
			/*
			 * get new irec (multiple chunks per block fs)
			 */
			ino_rec = next_ino_rec(ino_rec);
			ASSERT(ino_rec->ino_startnum == agino + 1);
			irec_offset = 0;
		}
		if (cluster_offset == inodes_per_cluster) {
			bp_index++;
			cluster_offset = 0;
		}
	}
	return(0);
}

/*
 * check all inodes mentioned in the ag's incore inode maps.
 * the map may be incomplete.  If so, we'll catch the missing
 * inodes (hopefully) when we traverse the directory tree.
 * check_dirs is set to 1 if directory inodes should be
 * processed for internal consistency, parent setting and
 * discovery of unknown inodes.  this only happens
 * in phase 3.  check_dups is set to 1 if we're looking for
 * inodes that reference duplicate blocks so we can trash
 * the inode right then and there.  this is set only in
 * phase 4 after we've run through and set the bitmap once.
 */
void
process_aginodes(
	xfs_mount_t		*mp,
	prefetch_args_t		*pf_args,
	xfs_agnumber_t		agno,
	int 			ino_discovery,
	int 			check_dups,
	int 			extra_attr_check)
{
	int 			num_inos, bogus;
	ino_tree_node_t 	*ino_rec, *first_ino_rec, *prev_ino_rec;
#ifdef XR_PF_TRACE
	int			count;
#endif
	first_ino_rec = ino_rec = findfirst_inode_rec(agno);

	while (ino_rec != NULL)  {
		/*
		 * paranoia - step through inode records until we step
		 * through a full allocation of inodes.  this could
		 * be an issue in big-block filesystems where a block
		 * can hold more than one inode chunk.  make sure to
		 * grab the record corresponding to the beginning of
		 * the next block before we call the processing routines.
		 */
		num_inos = XFS_INODES_PER_CHUNK;
		while (num_inos < mp->m_ialloc_inos && ino_rec != NULL)  {
			/*
			 * inodes chunks will always be aligned and sized
			 * correctly
			 */
			if ((ino_rec = next_ino_rec(ino_rec)) != NULL)
				num_inos += XFS_INODES_PER_CHUNK;
		}

		ASSERT(num_inos == mp->m_ialloc_inos);

		if (pf_args) {
			sem_post(&pf_args->ra_count);
#ifdef XR_PF_TRACE
			sem_getvalue(&pf_args->ra_count, &count);
			pftrace("processing inode chunk %p in AG %d (sem count = %d)",
				first_ino_rec, agno, count);
#endif
		}

		if (process_inode_chunk(mp, agno, num_inos, first_ino_rec,
				ino_discovery, check_dups, extra_attr_check,
				&bogus))  {
			/* XXX - i/o error, we've got a problem */
			abort();
		}

		if (!bogus)
			first_ino_rec = ino_rec = next_ino_rec(ino_rec);
		else  {
			/*
			 * inodes pointed to by this record are
			 * completely bogus, blow the records for
			 * this chunk out.
			 * the inode block(s) will get reclaimed
			 * in phase 4 when the block map is
			 * reconstructed after inodes claiming
			 * duplicate blocks are deleted.
			 */
			num_inos = 0;
			ino_rec = first_ino_rec;
			while (num_inos < mp->m_ialloc_inos &&
					ino_rec != NULL)  {
				prev_ino_rec = ino_rec;

				if ((ino_rec = next_ino_rec(ino_rec)) != NULL)
					num_inos += XFS_INODES_PER_CHUNK;

				get_inode_rec(mp, agno, prev_ino_rec);
				free_inode_rec(agno, prev_ino_rec);
			}

			first_ino_rec = ino_rec;
		}
		PROG_RPT_INC(prog_rpt_done[agno], num_inos);
	}
}

/*
 * verify the uncertain inode list for an ag.
 * Good inodes get moved into the good inode tree.
 * returns 0 if there are no uncertain inode records to
 * be processed, 1 otherwise.  This routine destroys the
 * the entire uncertain inode tree for the ag as a side-effect.
 */
void
check_uncertain_aginodes(xfs_mount_t *mp, xfs_agnumber_t agno)
{
	ino_tree_node_t		*irec;
	ino_tree_node_t		*nrec;
	xfs_agino_t		start;
	xfs_agino_t		i;
	xfs_agino_t		agino;
	int			got_some;

	nrec = NULL;
	got_some = 0;

	clear_uncertain_ino_cache(agno);

	if ((irec = findfirst_uncertain_inode_rec(agno)) == NULL)
		return;

	/*
	 * the trick here is to find a contiguous range
	 * of inodes, make sure that it doesn't overlap
	 * with a known to exist chunk, and then make
	 * sure it is a number of entire chunks.
	 * we check on-disk once we have an idea of what's
	 * going on just to double-check.
	 *
	 * process the uncertain inode record list and look
	 * on disk to see if the referenced inodes are good
	 */

	do_warn(_("found inodes not in the inode allocation tree\n"));

	do {
		/*
		 * check every confirmed (which in this case means
		 * inode that we really suspect to be an inode) inode
		 */
		for (i = 0; i < XFS_INODES_PER_CHUNK; i++)  {
			if (!is_inode_confirmed(irec, i))
				continue;

			agino = i + irec->ino_startnum;

			if (verify_aginum(mp, agno, agino))
				continue;

			if (nrec != NULL && nrec->ino_startnum <= agino &&
					agino < nrec->ino_startnum +
					XFS_INODES_PER_CHUNK)
				continue;

			if ((nrec = find_inode_rec(mp, agno, agino)) == NULL)
				if (!verify_aginum(mp, agno, agino))
					if (verify_aginode_chunk(mp, agno,
							agino, &start))
						got_some = 1;
		}

		get_uncertain_inode_rec(mp, agno, irec);
		free_inode_rec(agno, irec);

		irec = findfirst_uncertain_inode_rec(agno);
	} while (irec != NULL);

	if (got_some)
		do_warn(_("found inodes not in the inode allocation tree\n"));

	return;
}

/*
 * verify and process the uncertain inodes for an ag.
 * this is different from check_ in that we can't just
 * move the good inodes into the good inode tree and let
 * process_aginodes() deal with them because this gets called
 * after process_aginodes() has been run on the ag inode tree.
 * So we have to process the inodes as well as verify since
 * we don't want to rerun process_aginodes() on a tree that has
 * mostly been processed.
 *
 * Note that if this routine does process some inodes, it can
 * add uncertain inodes to any ag which would require that
 * the routine be called again to process those newly-added
 * uncertain inodes.
 *
 * returns 0 if no inodes were processed and 1 if inodes
 * were processed (and it is possible that new uncertain
 * inodes were discovered).
 *
 * as a side-effect, this routine tears down the uncertain
 * inode tree for the ag.
 */
int
process_uncertain_aginodes(xfs_mount_t *mp, xfs_agnumber_t agno)
{
	ino_tree_node_t		*irec;
	ino_tree_node_t		*nrec;
	xfs_agino_t		agino;
	int			i;
	int			bogus;
	int			cnt;
	int			got_some;

#ifdef XR_INODE_TRACE
	fprintf(stderr, "in process_uncertain_aginodes, agno = %d\n", agno);
#endif

	got_some = 0;

	clear_uncertain_ino_cache(agno);

	if ((irec = findfirst_uncertain_inode_rec(agno)) == NULL)
		return(0);

	nrec = NULL;

	do  {
		/*
		 * check every confirmed inode
		 */
		for (cnt = i = 0; i < XFS_INODES_PER_CHUNK; i++)  {
			if (!is_inode_confirmed(irec, i))
				continue;
			cnt++;
			agino = i + irec->ino_startnum;
#ifdef XR_INODE_TRACE
	fprintf(stderr, "ag inode = %d (0x%x)\n", agino, agino);
#endif
			/*
			 * skip over inodes already processed (in the
			 * good tree), bad inode numbers, and inode numbers
			 * pointing to bogus inodes
			 */
			if (verify_aginum(mp, agno, agino))
				continue;

			if (nrec != NULL && nrec->ino_startnum <= agino &&
					agino < nrec->ino_startnum +
					XFS_INODES_PER_CHUNK)
				continue;

			if ((nrec = find_inode_rec(mp, agno, agino)) != NULL)
				continue;

			/*
			 * verify the chunk.  if good, it will be
			 * added to the good inode tree.
			 */
			if ((nrec = verify_aginode_chunk_irec(mp,
						agno, agino)) == NULL)
				continue;

			got_some = 1;

			/*
			 * process the inode record we just added
			 * to the good inode tree.  The inode
			 * processing may add more records to the
			 * uncertain inode lists.
			 */
			if (process_inode_chunk(mp, agno, mp->m_ialloc_inos,
						nrec, 1, 0, 0, &bogus))  {
				/* XXX - i/o error, we've got a problem */
				abort();
			}
		}

		ASSERT(cnt != 0);
		/*
		 * now return the uncertain inode record to the free pool
		 * and pull another one off the list for processing
		 */
		get_uncertain_inode_rec(mp, agno, irec);
		free_inode_rec(agno, irec);

		irec = findfirst_uncertain_inode_rec(agno);
	} while (irec != NULL);

	if (got_some)
		do_warn(_("found inodes not in the inode allocation tree\n"));

	return(1);
}
