/*
 * Copyright (c) 2000-2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <libxfs.h>
#include "avl.h"
#include "globals.h"
#include "agheader.h"
#include "incore.h"
#include "protos.h"
#include "err_protos.h"
#include "dir.h"
#include "dir2.h"
#include "dinode.h"
#include "scan.h"
#include "versions.h"
#include "attr_repair.h"
#include "bmap.h"
#include "threads.h"

/*
 * inode clearing routines
 */

/*
 * return the offset into the inode where the attribute fork starts
 */
/* ARGSUSED */
int
calc_attr_offset(xfs_mount_t *mp, xfs_dinode_t *dino)
{
	xfs_dinode_core_t	*dinoc = &dino->di_core;
	int			offset = ((__psint_t) &dino->di_u)
						- (__psint_t)dino;

	/*
	 * don't worry about alignment when calculating offset
	 * because the data fork is already 8-byte aligned
	 */
	switch (dinoc->di_format)  {
	case XFS_DINODE_FMT_DEV:
		offset += sizeof(xfs_dev_t);
		break;
	case XFS_DINODE_FMT_LOCAL:
		offset += INT_GET(dinoc->di_size, ARCH_CONVERT);
		break;
	case XFS_DINODE_FMT_UUID:
		offset += sizeof(uuid_t);
		break;
	case XFS_DINODE_FMT_EXTENTS:
		offset += INT_GET(dinoc->di_nextents, ARCH_CONVERT) * sizeof(xfs_bmbt_rec_32_t);
		break;
	case XFS_DINODE_FMT_BTREE:
		offset += INT_GET(dino->di_u.di_bmbt.bb_numrecs, ARCH_CONVERT) * sizeof(xfs_bmbt_rec_32_t);
		break;
	default:
		do_error(_("Unknown inode format.\n"));
		abort();
		break;
	}

	return(offset);
}

/* ARGSUSED */
int
clear_dinode_attr(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_ino_t ino_num)
{
	xfs_dinode_core_t *dinoc = &dino->di_core;

	ASSERT(dinoc->di_forkoff != 0);

	if (!no_modify)
		fprintf(stderr, _("clearing inode %llu attributes\n"),
			(unsigned long long)ino_num);
	else
		fprintf(stderr, _("would have cleared inode %llu attributes\n"),
			(unsigned long long)ino_num);

	if (INT_GET(dinoc->di_anextents, ARCH_CONVERT) != 0)  {
		if (no_modify)
			return(1);
		dinoc->di_anextents = 0;
	}

	if (dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS)  {
		if (no_modify)
			return(1);
		dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS;
	}

	/* get rid of the fork by clearing forkoff */

	/* Originally, when the attr repair code was added, the fork was cleared
	 * by turning it into shortform status.  This meant clearing the
	 * hdr.totsize/count fields and also changing aformat to LOCAL
	 * (vs EXTENTS).  Over various fixes, the aformat and forkoff have
	 * been updated to not show an attribute fork at all, however.
	 * It could be possible that resetting totsize/count are not needed,
	 * but just to be safe, leave it in for now.
	 */

	if (!no_modify) {
		xfs_attr_shortform_t *asf = (xfs_attr_shortform_t *)
				XFS_DFORK_APTR(dino);
		INT_SET(asf->hdr.totsize, ARCH_CONVERT,
			sizeof(xfs_attr_sf_hdr_t));
		INT_SET(asf->hdr.count, ARCH_CONVERT, 0);
		dinoc->di_forkoff = 0;  /* got to do this after asf is set */
	}

	/*
	 * always returns 1 since the fork gets zapped
	 */
	return(1);
}

/* ARGSUSED */
int
clear_dinode_core(xfs_dinode_core_t *dinoc, xfs_ino_t ino_num)
{
	int dirty = 0;

	if (INT_GET(dinoc->di_magic, ARCH_CONVERT) != XFS_DINODE_MAGIC)  {
		dirty = 1;

		if (no_modify)
			return(1);

		INT_SET(dinoc->di_magic, ARCH_CONVERT, XFS_DINODE_MAGIC);
	}

	if (!XFS_DINODE_GOOD_VERSION(dinoc->di_version) ||
	    (!fs_inode_nlink && dinoc->di_version > XFS_DINODE_VERSION_1))  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_version = (fs_inode_nlink) ? XFS_DINODE_VERSION_2
						: XFS_DINODE_VERSION_1;
	}

	if (INT_GET(dinoc->di_mode, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_mode = 0;
	}

	if (INT_GET(dinoc->di_flags, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_flags = 0;
	}

	if (INT_GET(dinoc->di_dmevmask, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_dmevmask = 0;
	}

	if (dinoc->di_forkoff != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_forkoff = 0;
	}

	if (dinoc->di_format != XFS_DINODE_FMT_EXTENTS)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_format = XFS_DINODE_FMT_EXTENTS;
	}

	if (dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS;
	}

	if (INT_GET(dinoc->di_size, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_size = 0;
	}

	if (INT_GET(dinoc->di_nblocks, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_nblocks = 0;
	}

	if (INT_GET(dinoc->di_onlink, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_onlink = 0;
	}

	if (INT_GET(dinoc->di_nextents, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_nextents = 0;
	}

	if (INT_GET(dinoc->di_anextents, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_anextents = 0;
	}

	if (dinoc->di_version > XFS_DINODE_VERSION_1 &&
			INT_GET(dinoc->di_nlink, ARCH_CONVERT) != 0)  {
		dirty = 1;

		if (no_modify)
			return(1);

		dinoc->di_nlink = 0;
	}

	return(dirty);
}

/* ARGSUSED */
int
clear_dinode_unlinked(xfs_mount_t *mp, xfs_dinode_t *dino)
{

	if (dino->di_next_unlinked != NULLAGINO)  {
		if (!no_modify)
			dino->di_next_unlinked = NULLAGINO;
		return(1);
	}

	return(0);
}

/*
 * this clears the unlinked list too so it should not be called
 * until after the agi unlinked lists are walked in phase 3.
 * returns > zero if the inode has been altered while being cleared
 */
int
clear_dinode(xfs_mount_t *mp, xfs_dinode_t *dino, xfs_ino_t ino_num)
{
	int dirty;

	dirty = clear_dinode_core(&dino->di_core, ino_num);
	dirty += clear_dinode_unlinked(mp, dino);

	/* and clear the forks */

	if (dirty && !no_modify)
		bzero(&dino->di_u, XFS_LITINO(mp));

	return(dirty);
}


/*
 * misc. inode-related utility routines
 */

/* 
 * verify_ag_bno is heavily used. In the common case, it 
 * performs just two number of compares
 */
static __inline int
verify_ag_bno(xfs_sb_t *sbp,
		xfs_agnumber_t agno,
		xfs_agblock_t agbno)
{
	if (agno < (sbp->sb_agcount - 1)) {
		if (agbno >= sbp->sb_agblocks) {
			return 1;	/* bad */
		}
		return 0;		/* good */
	}
	if (agno == (sbp->sb_agcount - 1)) {
		if (agbno >=
			(sbp->sb_dblocks -
			(sbp->sb_agcount-1) *
			sbp->sb_agblocks)) {
			return 1;	/* bad */
		}
		return 0;		/* good */
	}
	return 1;			/* bad */
}

/*
 * returns 0 if inode number is valid, 1 if bogus
 */
int
verify_inum(xfs_mount_t		*mp,
		xfs_ino_t	ino)
{
	xfs_agnumber_t	agno;
	xfs_agino_t	agino;
	xfs_agblock_t	agbno;
	xfs_sb_t	*sbp = &mp->m_sb;;

	/* range check ag #, ag block.  range-checking offset is pointless */

	agno = XFS_INO_TO_AGNO(mp, ino);
	agino = XFS_INO_TO_AGINO(mp, ino);
	agbno = XFS_AGINO_TO_AGBNO(mp, agino);
	if (agbno == 0)
		return 1;

	if (ino == 0 || ino == NULLFSINO)
		return(1);

	if (ino != XFS_AGINO_TO_INO(mp, agno, agino))
		return(1);

	return verify_ag_bno(sbp, agno, agbno);
}

/*
 * have a separate routine to ensure that we don't accidentally
 * lose illegally set bits in the agino by turning it into an FSINO
 * to feed to the above routine
 */
int
verify_aginum(xfs_mount_t	*mp,
		xfs_agnumber_t	agno,
		xfs_agino_t	agino)
{
	xfs_agblock_t	agbno;
	xfs_sb_t	*sbp = &mp->m_sb;;

	/* range check ag #, ag block.  range-checking offset is pointless */

	if (agino == 0 || agino == NULLAGINO)
		return(1);

	/*
	 * agino's can't be too close to NULLAGINO because the min blocksize
	 * is 9 bits and at most 1 bit of that gets used for the inode offset
	 * so if the agino gets shifted by the # of offset bits and compared
	 * to the legal agbno values, a bogus agino will be too large.  there
	 * will be extra bits set at the top that shouldn't be set.
	 */
	agbno = XFS_AGINO_TO_AGBNO(mp, agino);
	if (agbno == 0)
		return 1;

	return verify_ag_bno(sbp, agno, agbno);
}

/*
 * return 1 if block number is good, 0 if out of range
 */
int
verify_dfsbno(xfs_mount_t	*mp,
		xfs_dfsbno_t	fsbno)
{
	xfs_agnumber_t	agno;
	xfs_agblock_t	agbno;
	xfs_sb_t	*sbp = &mp->m_sb;;

	/* range check ag #, ag block.  range-checking offset is pointless */

	agno = XFS_FSB_TO_AGNO(mp, fsbno);
	agbno = XFS_FSB_TO_AGBNO(mp, fsbno);

	return verify_ag_bno(sbp, agno, agbno) == 0;
}

#define XR_DFSBNORANGE_VALID	0
#define XR_DFSBNORANGE_BADSTART	1
#define XR_DFSBNORANGE_BADEND	2
#define XR_DFSBNORANGE_OVERFLOW	3

static __inline int
verify_dfsbno_range(xfs_mount_t	*mp,
		xfs_dfsbno_t	fsbno,
		xfs_dfilblks_t	count)
{
	xfs_agnumber_t	agno;
	xfs_agblock_t	agbno;
	xfs_sb_t	*sbp = &mp->m_sb;;

	/* the start and end blocks better be in the same allocation group */
	agno = XFS_FSB_TO_AGNO(mp, fsbno);
	if (agno != XFS_FSB_TO_AGNO(mp, fsbno + count - 1)) {
		return XR_DFSBNORANGE_OVERFLOW;
	}

	agbno = XFS_FSB_TO_AGBNO(mp, fsbno);
	if (verify_ag_bno(sbp, agno, agbno)) {
		return XR_DFSBNORANGE_BADSTART;
	}

	agbno = XFS_FSB_TO_AGBNO(mp, fsbno + count - 1);
	if (verify_ag_bno(sbp, agno, agbno)) {
		return XR_DFSBNORANGE_BADEND;
	}

	return (XR_DFSBNORANGE_VALID);
}

int
verify_agbno(xfs_mount_t	*mp,
		xfs_agnumber_t	agno,
		xfs_agblock_t	agbno)
{
	xfs_sb_t	*sbp = &mp->m_sb;;

	/* range check ag #, ag block.  range-checking offset is pointless */
	return verify_ag_bno(sbp, agno, agbno) == 0;
}

void
convert_extent(
	xfs_bmbt_rec_32_t	*rp,
	xfs_dfiloff_t		*op,	/* starting offset (blockno in file) */
	xfs_dfsbno_t		*sp,	/* starting block (fs blockno) */
	xfs_dfilblks_t		*cp,	/* blockcount */
	int			*fp)	/* extent flag */
{
	xfs_bmbt_irec_t irec, *s = &irec;
	xfs_bmbt_rec_t rpcopy, *p = &rpcopy;

	memcpy(&rpcopy, rp, sizeof(rpcopy));
	/* Just use the extent parsing routine from the kernel */
	libxfs_bmbt_disk_get_all(p, s);

	if (fs_has_extflgbit)  {
		if (s->br_state == XFS_EXT_UNWRITTEN) {
			*fp = 1;
		} else {
			*fp = 0;
		}
	} else  {
		*fp = 0;
	}
	*op = s->br_startoff;
	*sp = s->br_startblock;
	*cp = s->br_blockcount;
}

/*
 * return address of block fblock if it's within the range described
 * by the extent list.  Otherwise, returns a null address.
 */
/* ARGSUSED */
xfs_dfsbno_t
get_bmbt_reclist(
	xfs_mount_t		*mp,
	xfs_bmbt_rec_32_t	*rp,
	int			numrecs,
	xfs_dfiloff_t		fblock)
{
	int			i;
	xfs_dfilblks_t		cnt;
	xfs_dfiloff_t		off_bno;
	xfs_dfsbno_t		start;
	int			flag;

	for (i = 0; i < numrecs; i++, rp++) {
		convert_extent(rp, &off_bno, &start, &cnt, &flag);
		if (off_bno >= fblock && off_bno + cnt < fblock)
			return(start + fblock - off_bno);
	}

	return(NULLDFSBNO);
}

/*
 * process_bmbt_reclist_int is the most compute intensive
 * function in repair. The following macros reduce the
 * the large number of lock/unlock steps it would otherwise
 * call.
 */
#define	PROCESS_BMBT_DECL(type, var)	type var

#define	PROCESS_BMBT_LOCK(agno)							\
	if (do_parallel && (agno != locked_agno)) {				\
		if (locked_agno != -1)	/* release old ag lock */		\
			PREPAIR_RW_UNLOCK_NOTEST(&per_ag_lock[locked_agno]);	\
		PREPAIR_RW_WRITE_LOCK_NOTEST(&per_ag_lock[agno]);		\
		locked_agno = agno;						\
	}

#define	PROCESS_BMBT_UNLOCK_RETURN(val)						\
	do {									\
		if (locked_agno != -1) 						\
			PREPAIR_RW_UNLOCK_NOTEST(&per_ag_lock[locked_agno]);	\
		return (val);							\
	} while (0)

/*
 * return 1 if inode should be cleared, 0 otherwise
 * if check_dups should be set to 1, that implies that
 * the primary purpose of this call is to see if the
 * file overlaps with any duplicate extents (in the
 * duplicate extent list).
 */
/* ARGSUSED */
int
process_bmbt_reclist_int(
	xfs_mount_t		*mp,
	xfs_bmbt_rec_32_t	*rp,
	int			numrecs,
	int			type,
	xfs_ino_t		ino,
	xfs_drfsbno_t		*tot,
	blkmap_t		**blkmapp,
	xfs_dfiloff_t		*first_key,
	xfs_dfiloff_t		*last_key,
	int			check_dups,
	int			whichfork)
{
	xfs_dfsbno_t		b;
	xfs_drtbno_t		ext;
	xfs_dfilblks_t		c;		/* count */
	xfs_dfilblks_t		cp = 0;		/* prev count */
	xfs_dfsbno_t		s;		/* start */
	xfs_dfsbno_t		sp = 0;		/* prev start */
	xfs_dfiloff_t		o = 0;		/* offset */
	xfs_dfiloff_t		op = 0;		/* prev offset */
	char			*ftype;
	char			*forkname;
	int			i;
	int			state;
	int			flag;		/* extent flag */
	int			pwe;		/* partially-written extent */
	xfs_dfsbno_t		e;
	xfs_agnumber_t		agno;
	xfs_agblock_t		agbno;
	PROCESS_BMBT_DECL
				(xfs_agnumber_t, locked_agno=-1);

	if (whichfork == XFS_DATA_FORK)
		forkname = _("data");
	else
		forkname = _("attr");

	if (type == XR_INO_RTDATA)
		ftype = _("real-time");
	else
		ftype = _("regular");

	for (i = 0; i < numrecs; i++, rp++) {
		convert_extent(rp, &o, &s, &c, &flag);
		if (i == 0)
			*last_key = *first_key = o;
		else
			*last_key = o;
		if (i > 0 && op + cp > o)  {
			do_warn(
	_("bmap rec out of order, inode %llu entry %d "
	  "[o s c] [%llu %llu %llu], %d [%llu %llu %llu]\n"),
				ino, i, o, s, c, i-1, op, sp, cp);
			PROCESS_BMBT_UNLOCK_RETURN(1);
		}
		op = o;
		cp = c;
		sp = s;

		/*
		 * check numeric validity of the extent
		 */
		if (c == 0)  {
			do_warn(
	_("zero length extent (off = %llu, fsbno = %llu) in ino %llu\n"),
				o, s, ino);
			PROCESS_BMBT_UNLOCK_RETURN(1);
		}
		if (type == XR_INO_RTDATA) {
			if (s >= mp->m_sb.sb_rblocks)  {
				do_warn(
	_("inode %llu - bad rt extent start block number %llu, offset %llu\n"),
					ino, s, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}
			if (s + c - 1 >= mp->m_sb.sb_rblocks)  {
				do_warn(
	_("inode %llu - bad rt extent last block number %llu, offset %llu\n"),
					ino, s + c - 1, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}
			if (s + c - 1 < s)  {
				do_warn(
	_("inode %llu - bad rt extent overflows - start %llu, end %llu, "
	  "offset %llu\n"),
					ino, s, s + c - 1, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}
		} else  {
			switch (verify_dfsbno_range(mp, s, c)) {
			case XR_DFSBNORANGE_VALID:
				break;
			case XR_DFSBNORANGE_BADSTART:
				do_warn(
	_("inode %llu - bad extent starting block number %llu, offset %llu\n"),
					ino, s, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			case XR_DFSBNORANGE_BADEND:
				do_warn(
	_("inode %llu - bad extent last block number %llu, offset %llu\n"),
					ino, s + c - 1, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			case XR_DFSBNORANGE_OVERFLOW:
				do_warn(

	_("inode %llu - bad extent overflows - start %llu, end %llu, "
	  "offset %llu\n"),
					ino, s, s + c - 1, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}
			if (o >= fs_max_file_offset)  {
				do_warn(
	_("inode %llu - extent offset too large - start %llu, count %llu, "
	  "offset %llu\n"),
					ino, s, c, o);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}
		}

		/*
		 * realtime file data fork
		 */
		if (type == XR_INO_RTDATA && whichfork == XFS_DATA_FORK)  {
			/*
			 * XXX - verify that the blocks listed in the record
			 * are multiples of an extent
			 */
			if (XFS_SB_VERSION_HASEXTFLGBIT(&mp->m_sb) == 0
			   && (s % mp->m_sb.sb_rextsize != 0 ||
					c % mp->m_sb.sb_rextsize != 0))  {
				do_warn(
	_("malformed rt inode extent [%llu %llu] (fs rtext size = %u)\n"),
					s, c, mp->m_sb.sb_rextsize);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}

			/*
			 * XXX - set the appropriate number of extents
			 */
			for (b = s; b < s + c; b += mp->m_sb.sb_rextsize)  {
				ext = (xfs_drtbno_t) b / mp->m_sb.sb_rextsize;
				if (XFS_SB_VERSION_HASEXTFLGBIT(&mp->m_sb) &&
				    flag && (b % mp->m_sb.sb_rextsize != 0)) {
					pwe = 1;
				} else {
					pwe = 0;
				}

				if (check_dups == 1)  {
					if (search_rt_dup_extent(mp, ext) &&
					    !pwe)  {
						do_warn(
	_("data fork in rt ino %llu claims dup rt extent, off - %llu, "
	  "start - %llu, count %llu\n"),
							ino, o, s, c);
						PROCESS_BMBT_UNLOCK_RETURN(1);
					}
					continue;
				}

				state = get_rtbno_state(mp, ext);

				switch (state)  {
				case XR_E_FREE:
/* XXX - turn this back on after we
	run process_rtbitmap() in phase2
					do_warn(
			_("%s fork in rt ino %llu claims free rt block %llu\n"),
						forkname, ino, ext);
*/
					/* fall through ... */
				case XR_E_UNKNOWN:
					set_rtbno_state(mp, ext, XR_E_INUSE);
					break;
				case XR_E_BAD_STATE:
					do_error(
				_("bad state in rt block map %llu\n"), ext);
					abort();
					break;
				case XR_E_FS_MAP:
				case XR_E_INO:
				case XR_E_INUSE_FS:
					do_error(
	_("%s fork in rt inode %llu found metadata block %llu in %s bmap\n"),
						forkname, ino, ext, ftype);
				case XR_E_INUSE:
					if (pwe)
						break;
				case XR_E_MULT:
					set_rtbno_state(mp, ext, XR_E_MULT);
					do_warn(
	_("%s fork in rt inode %llu claims used rt block %llu\n"),
						forkname, ino, ext);
					PROCESS_BMBT_UNLOCK_RETURN(1);
				case XR_E_FREE1:
				default:
					do_error(
				_("illegal state %d in %s block map %llu\n"),
						state, ftype, b);
				}
			}

			/*
			 * bump up the block counter
			 */
			*tot += c;

			/*
			 * skip rest of loop processing since that's
			 * all for regular file forks and attr forks
			 */
			continue;
		}


		/*
		 * regular file data fork or attribute fork
		 */
		if (blkmapp && *blkmapp)
			blkmap_set_ext(blkmapp, o, s, c);
		/*
		 * Profiling shows that the following loop takes the
		 * most time in all of xfs_repair.
		 */
		agno = XFS_FSB_TO_AGNO(mp, s);
		agbno = XFS_FSB_TO_AGBNO(mp, s);
		e = s + c;
		PROCESS_BMBT_LOCK(agno);
		for (b = s; b < e; b++, agbno++)  {
			if (check_dups == 1)  {
				/*
				 * if we're just checking the bmap for dups,
				 * return if we find one, otherwise, continue
				 * checking each entry without setting the
				 * block bitmap
				 */
				if (search_dup_extent(mp, agno, agbno)) {
					do_warn(
	_("%s fork in ino %llu claims dup extent, off - %llu, "
	  "start - %llu, cnt %llu\n"),
						forkname, ino, o, s, c);
					PROCESS_BMBT_UNLOCK_RETURN(1);
				}
				continue;
			}

			/* FIX FOR BUG 653709 -- EKN
			 * realtime attribute fork, should be valid block number
			 * in regular data space, not realtime partion.
			 */
			if (type == XR_INO_RTDATA && whichfork == XFS_ATTR_FORK) {
			  if (mp->m_sb.sb_agcount < agno)
				PROCESS_BMBT_UNLOCK_RETURN(1);
			}

			/* Process in chunks of 16 (XR_BB_UNIT/XR_BB) 
			 * for common XR_E_UNKNOWN to XR_E_INUSE transition
			 */
			if (((agbno & XR_BB_MASK) == 0) && ((s + c - b) >= (XR_BB_UNIT/XR_BB))) {
				if (ba_bmap[agno][agbno>>XR_BB] == XR_E_UNKNOWN_LL) {
					ba_bmap[agno][agbno>>XR_BB] = XR_E_INUSE_LL;
					agbno += (XR_BB_UNIT/XR_BB) - 1;
					b += (XR_BB_UNIT/XR_BB) - 1;
					continue;
				}

			}

			state = get_agbno_state(mp, agno, agbno);
			switch (state)  {
			case XR_E_FREE:
			case XR_E_FREE1:
				do_warn(
			_("%s fork in ino %llu claims free block %llu\n"),
					forkname, ino, (__uint64_t) b);
				/* fall through ... */
			case XR_E_UNKNOWN:
				set_agbno_state(mp, agno, agbno, XR_E_INUSE);
				break;
			case XR_E_BAD_STATE:
				do_error(_("bad state in block map %llu\n"), b);
				abort();
				break;
			case XR_E_FS_MAP:
			case XR_E_INO:
			case XR_E_INUSE_FS:
				do_warn(
			_("%s fork in inode %llu claims metadata block %llu\n"),
					forkname, ino, (__uint64_t) b);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			case XR_E_INUSE:
			case XR_E_MULT:
				set_agbno_state(mp, agno, agbno, XR_E_MULT);
				do_warn(
			_("%s fork in %s inode %llu claims used block %llu\n"),
					forkname, ftype, ino, (__uint64_t) b);
				PROCESS_BMBT_UNLOCK_RETURN(1);
			default:
				do_error(
			_("illegal state %d in block map %llu\n"),
					state, b);
				abort();
			}
		}
		*tot += c;
	}

	PROCESS_BMBT_UNLOCK_RETURN(0);
}

/*
 * return 1 if inode should be cleared, 0 otherwise, sets block bitmap
 * as a side-effect
 */
int
process_bmbt_reclist(
	xfs_mount_t		*mp,
	xfs_bmbt_rec_32_t	*rp,
	int			numrecs,
	int			type,
	xfs_ino_t		ino,
	xfs_drfsbno_t		*tot,
	blkmap_t		**blkmapp,
	xfs_dfiloff_t		*first_key,
	xfs_dfiloff_t		*last_key,
	int			whichfork)
{
	return(process_bmbt_reclist_int(mp, rp, numrecs, type, ino, tot,
					blkmapp, first_key, last_key, 0,
					whichfork));
}

/*
 * return 1 if inode should be cleared, 0 otherwise, does not set
 * block bitmap
 */
int
scan_bmbt_reclist(
	xfs_mount_t		*mp,
	xfs_bmbt_rec_32_t	*rp,
	int			numrecs,
	int			type,
	xfs_ino_t		ino,
	xfs_drfsbno_t		*tot,
	int			whichfork)
{
	xfs_dfiloff_t		first_key = 0;
	xfs_dfiloff_t		last_key = 0;

	return(process_bmbt_reclist_int(mp, rp, numrecs, type, ino, tot,
					NULL, &first_key, &last_key, 1,
					whichfork));
}

/*
 * these two are meant for routines that read and work with inodes
 * one at a time where the inodes may be in any order (like walking
 * the unlinked lists to look for inodes).  the caller is responsible
 * for writing/releasing the buffer.
 */
xfs_buf_t *
get_agino_buf(xfs_mount_t	 *mp,
		xfs_agnumber_t	agno,
		xfs_agino_t	agino,
		xfs_dinode_t	**dipp)
{
	ino_tree_node_t *irec;
	xfs_buf_t *bp;
	int size;

	if ((irec = find_inode_rec(agno, agino)) == NULL)
		return(NULL);

	size = XFS_FSB_TO_BB(mp, MAX(1, XFS_INODES_PER_CHUNK/inodes_per_block));
	bp = libxfs_readbuf(mp->m_dev, XFS_AGB_TO_DADDR(mp, agno,
		XFS_AGINO_TO_AGBNO(mp, irec->ino_startnum)), size, 0);
	if (!bp) {
		do_warn(_("cannot read inode (%u/%u), disk block %lld\n"),
			agno, irec->ino_startnum,
			XFS_AGB_TO_DADDR(mp, agno,
				XFS_AGINO_TO_AGBNO(mp, irec->ino_startnum)));
		return(NULL);
	}

	*dipp = XFS_MAKE_IPTR(mp, bp, agino -
		XFS_OFFBNO_TO_AGINO(mp, XFS_AGINO_TO_AGBNO(mp,
						irec->ino_startnum),
		0));

	return(bp);
}

/*
 * these next routines return the filesystem blockno of the
 * block containing the block "bno" in the file whose bmap
 * tree (or extent list) is rooted by "rootblock".
 *
 * the next routines are utility routines for the third
 * routine, get_bmapi().
 */
/* ARGSUSED */
xfs_dfsbno_t
getfunc_extlist(xfs_mount_t		*mp,
		xfs_ino_t		ino,
		xfs_dinode_t		*dip,
		xfs_dfiloff_t		bno,
		int			whichfork)
{
	xfs_dfiloff_t		fbno;
	xfs_dfilblks_t		bcnt;
	xfs_dfsbno_t		fsbno;
	xfs_dfsbno_t		final_fsbno = NULLDFSBNO;
	xfs_bmbt_rec_32_t	*rootblock = (xfs_bmbt_rec_32_t *)
						XFS_DFORK_PTR(dip, whichfork);
	xfs_extnum_t		nextents = XFS_DFORK_NEXTENTS(dip, whichfork);
	int			i;
	int			flag;

	for (i = 0; i < nextents; i++)  {
		convert_extent(rootblock + i, &fbno, &fsbno, &bcnt, &flag);

		if (fbno <= bno && bno < fbno + bcnt)  {
			final_fsbno = bno - fbno + fsbno;
			break;
		}
	}

	return(final_fsbno);
}

xfs_dfsbno_t
getfunc_btree(xfs_mount_t		*mp,
		xfs_ino_t		ino,
		xfs_dinode_t		*dip,
		xfs_dfiloff_t		bno,
		int			whichfork)
{
	int			i;
#ifdef DEBUG
	int			prev_level;
#endif
	int			flag;
	int			found;
	xfs_bmbt_rec_32_t	*rec;
	xfs_bmbt_ptr_t		*pp;
	xfs_bmbt_key_t		*key;
	xfs_bmdr_key_t		*rkey;
	xfs_bmdr_ptr_t		*rp;
	xfs_dfiloff_t		fbno;
	xfs_dfsbno_t		fsbno;
	xfs_dfilblks_t		bcnt;
	xfs_buf_t		*bp;
	xfs_dfsbno_t		final_fsbno = NULLDFSBNO;
	xfs_bmbt_block_t	*block;
	xfs_bmdr_block_t	*rootblock = (xfs_bmdr_block_t *)
			XFS_DFORK_PTR(dip, whichfork);

	ASSERT(rootblock->bb_level != 0);
	/*
	 * deal with root block, it's got a slightly different
	 * header structure than interior nodes.  We know that
	 * a btree should have at least 2 levels otherwise it
	 * would be an extent list.
	 */
	rkey = XFS_BTREE_KEY_ADDR(
			XFS_DFORK_SIZE(dip, mp, whichfork),
			xfs_bmdr, rootblock, 1,
			XFS_BTREE_BLOCK_MAXRECS(XFS_DFORK_SIZE(dip,
						mp, whichfork),
			xfs_bmdr, 1));
	rp = XFS_BTREE_PTR_ADDR(
			XFS_DFORK_SIZE(dip, mp, whichfork),
			xfs_bmdr, rootblock, 1,
			XFS_BTREE_BLOCK_MAXRECS(XFS_DFORK_SIZE(dip,
						mp, whichfork),
			xfs_bmdr, 1));
	for (found = -1, i = 0; i < rootblock->bb_numrecs - 1; i++)  {
		if (rkey[i].br_startoff <= bno
				&& bno < rkey[i+1].br_startoff)  {
			found = i;
			break;
		}
	}
	if (i == rootblock->bb_numrecs - 1 && bno >= rkey[i].br_startoff)
		found = i;

	ASSERT(found != -1);

	fsbno = INT_GET(rp[found], ARCH_CONVERT);

	ASSERT(verify_dfsbno(mp, fsbno));

	bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno),
				XFS_FSB_TO_BB(mp, 1), 0);
	if (!bp) {
		do_error(_("cannot read bmap block %llu\n"), fsbno);
		return(NULLDFSBNO);
	}
	block = XFS_BUF_TO_BMBT_BLOCK(bp);

	/*
	 * ok, now traverse any interior btree nodes
	 */
#ifdef DEBUG
	prev_level = INT_GET(block->bb_level, ARCH_CONVERT);
#endif

	while (INT_GET(block->bb_level, ARCH_CONVERT) > 0)  {
#ifdef DEBUG
		ASSERT(INT_GET(block->bb_level, ARCH_CONVERT) < prev_level);

		prev_level = INT_GET(block->bb_level, ARCH_CONVERT);
#endif

		if (INT_GET(block->bb_numrecs, ARCH_CONVERT) >
						mp->m_bmap_dmxr[1]) {
			do_warn(_("# of bmap records in inode %llu exceeds max "
				  "(%u, max - %u)\n"),
				ino, INT_GET(block->bb_numrecs, ARCH_CONVERT),
				mp->m_bmap_dmxr[1]);
			libxfs_putbuf(bp);
			return(NULLDFSBNO);
		}
		if (verbose && INT_GET(block->bb_numrecs, ARCH_CONVERT) <
						mp->m_bmap_dmnr[1]) {
			do_warn(_("- # of bmap records in inode %llu less than "
				  "minimum (%u, min - %u), proceeding ...\n"),
				ino, INT_GET(block->bb_numrecs, ARCH_CONVERT),
				mp->m_bmap_dmnr[1]);
		}
		key = XFS_BTREE_KEY_ADDR(mp->m_sb.sb_blocksize,
			xfs_bmbt, block, 1, mp->m_bmap_dmxr[1]);
		pp = XFS_BTREE_PTR_ADDR(mp->m_sb.sb_blocksize,
			xfs_bmbt, block, 1, mp->m_bmap_dmxr[1]);
		for (	found = -1, i = 0;
			i < INT_GET(block->bb_numrecs, ARCH_CONVERT) - 1;
			i++) {
			if (INT_GET(key[i].br_startoff, ARCH_CONVERT) <= bno &&
			    bno < INT_GET(key[i+1].br_startoff, ARCH_CONVERT)) {
				found = i;
				break;
			}
		}
		if (i == INT_GET(block->bb_numrecs, ARCH_CONVERT) - 1 &&
			bno >= INT_GET(key[i].br_startoff, ARCH_CONVERT))
			found = i;

		ASSERT(found != -1);
		fsbno = INT_GET(pp[found], ARCH_CONVERT);

		ASSERT(verify_dfsbno(mp, fsbno));

		/*
		 * release current btree block and read in the
		 * next btree block to be traversed
		 */
		libxfs_putbuf(bp);
		bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno),
					XFS_FSB_TO_BB(mp, 1), 0);
		if (!bp) {
			do_error(_("cannot read bmap block %llu\n"), fsbno);
			return(NULLDFSBNO);
		}
		block = XFS_BUF_TO_BMBT_BLOCK(bp);
	}

	/*
	 * current block must be a leaf block
	 */
	ASSERT(INT_GET(block->bb_level, ARCH_CONVERT) == 0);
	if (INT_GET(block->bb_numrecs, ARCH_CONVERT) > mp->m_bmap_dmxr[0]) {
		do_warn(_("# of bmap records in inode %llu greater than "
			  "maximum (%u, max - %u)\n"),
			ino, INT_GET(block->bb_numrecs, ARCH_CONVERT),
			mp->m_bmap_dmxr[0]);
		libxfs_putbuf(bp);
		return(NULLDFSBNO);
	}
	if (verbose && INT_GET(block->bb_numrecs, ARCH_CONVERT) <
					mp->m_bmap_dmnr[0])
		do_warn(_("- # of bmap records in inode %llu less than minimum "
			  "(%u, min - %u), continuing...\n"),
			ino, INT_GET(block->bb_numrecs, ARCH_CONVERT),
			mp->m_bmap_dmnr[0]);

	rec = (xfs_bmbt_rec_32_t *)XFS_BTREE_REC_ADDR(mp->m_sb.sb_blocksize,
		xfs_bmbt, block, 1, mp->m_bmap_dmxr[0]);
	for (i = 0; i < INT_GET(block->bb_numrecs, ARCH_CONVERT); i++)  {
		convert_extent(rec + i, &fbno, &fsbno, &bcnt, &flag);

		if (fbno <= bno && bno < fbno + bcnt)  {
			final_fsbno = bno - fbno + fsbno;
			break;
		}
	}
	libxfs_putbuf(bp);

	if (final_fsbno == NULLDFSBNO)
		do_warn(_("could not map block %llu\n"), bno);

	return(final_fsbno);
}

/*
 * this could be smarter.  maybe we should have an open inode
 * routine that would get the inode buffer and return back
 * an inode handle.  I'm betting for the moment that this
 * is used only by the directory and attribute checking code
 * and that the avl tree find and buffer cache search are
 * relatively cheap.  If they're too expensive, we'll just
 * have to fix this and add an inode handle to the da btree
 * cursor.
 *
 * caller is responsible for checking doubly referenced blocks
 * and references to holes
 */
xfs_dfsbno_t
get_bmapi(xfs_mount_t *mp, xfs_dinode_t *dino_p,
		xfs_ino_t ino_num, xfs_dfiloff_t bno, int whichfork)
{
	xfs_dfsbno_t		fsbno;

	switch (XFS_DFORK_FORMAT(dino_p, whichfork)) {
	case XFS_DINODE_FMT_EXTENTS:
		fsbno = getfunc_extlist(mp, ino_num, dino_p, bno, whichfork);
		break;
	case XFS_DINODE_FMT_BTREE:
		fsbno = getfunc_btree(mp, ino_num, dino_p, bno, whichfork);
		break;
	case XFS_DINODE_FMT_LOCAL:
		do_error(_("get_bmapi() called for local inode %llu\n"),
			ino_num);
		fsbno = NULLDFSBNO;
		break;
	default:
		/*
		 * shouldn't happen
		 */
		do_error(_("bad inode format for inode %llu\n"), ino_num);
		fsbno = NULLDFSBNO;
	}

	return(fsbno);
}

/*
 * higher level inode processing stuff starts here:
 * first, one utility routine for each type of inode
 */

/*
 * return 1 if inode should be cleared, 0 otherwise
 */
/* ARGSUSED */
int
process_btinode(
	xfs_mount_t		*mp,
	xfs_agnumber_t		agno,
	xfs_agino_t		ino,
	xfs_dinode_t		*dip,
	int			type,
	int			*dirty,
	xfs_drfsbno_t		*tot,
	__uint64_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork,
	int			check_dups)
{
	xfs_bmdr_block_t	*dib;
	xfs_dfiloff_t		last_key;
	xfs_dfiloff_t		first_key = 0;
	xfs_ino_t		lino;
	xfs_bmbt_ptr_t		*pp;
	xfs_bmbt_key_t		*pkey;
	char			*forkname;
	int			i;
	bmap_cursor_t		cursor;

	dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR(dip, whichfork);
	lino = XFS_AGINO_TO_INO(mp, agno, ino);
	*tot = 0;
	*nex = 0;

	if (whichfork == XFS_DATA_FORK)
		forkname = _("data");
	else
		forkname = _("attr");

	if (INT_GET(dib->bb_level, ARCH_CONVERT) == 0) {
		/*
		 * This should never happen since a btree inode
		 * has to have at least one other block in the
		 * bmap in addition to the root block in the
		 * inode's data fork.
		 *
		 * XXX - if we were going to fix up the inode,
		 * we'd try to treat the fork as an interior
		 * node and see if we could get an accurate
		 * level value from one of the blocks pointed
		 * to by the pointers in the fork.  For now
		 * though, we just bail (and blow out the inode).
		 */
		do_warn(_("bad level 0 in inode %llu bmap btree root block\n"),
			XFS_AGINO_TO_INO(mp, agno, ino));
		return(1);
	}
	/*
	 * use bmdr/dfork_dsize since the root block is in the data fork
	 */
	init_bm_cursor(&cursor, INT_GET(dib->bb_level, ARCH_CONVERT) + 1);

	if (XFS_BMDR_SPACE_CALC(INT_GET(dib->bb_numrecs, ARCH_CONVERT)) >
			((whichfork == XFS_DATA_FORK) ?
			XFS_DFORK_DSIZE(dip, mp) :
			XFS_DFORK_ASIZE(dip, mp)))  {
		do_warn(
	_("indicated size of %s btree root (%d bytes) greater than space in "
	  "inode %llu %s fork\n"),
			forkname, XFS_BMDR_SPACE_CALC(INT_GET(dib->bb_numrecs,
				ARCH_CONVERT)),
			lino, forkname);
		return(1);
	}

	pp = XFS_BTREE_PTR_ADDR(
			XFS_DFORK_SIZE(dip, mp, whichfork),
		xfs_bmdr, dib, 1,
		XFS_BTREE_BLOCK_MAXRECS(
			XFS_DFORK_SIZE(dip, mp, whichfork),
		xfs_bmdr, 0));
	pkey = XFS_BTREE_KEY_ADDR(
			XFS_DFORK_SIZE(dip, mp, whichfork),
		xfs_bmdr, dib, 1,
		XFS_BTREE_BLOCK_MAXRECS(
			XFS_DFORK_SIZE(dip, mp, whichfork),
		xfs_bmdr, 0));

	last_key = NULLDFILOFF;

	for (i = 0; i < INT_GET(dib->bb_numrecs, ARCH_CONVERT); i++)  {
		/*
		 * XXX - if we were going to do more to fix up the inode
		 * btree, we'd do it right here.  For now, if there's a
		 * problem, we'll bail out and presumably clear the inode.
		 */
		if (!verify_dfsbno(mp, INT_GET(pp[i], ARCH_CONVERT)))  {
			do_warn(_("bad bmap btree ptr 0x%llx in ino %llu\n"),
				INT_GET(pp[i], ARCH_CONVERT), lino);
			return(1);
		}

		if (scan_lbtree((xfs_dfsbno_t)INT_GET(pp[i], ARCH_CONVERT), INT_GET(dib->bb_level, ARCH_CONVERT),
				    scanfunc_bmap, type, whichfork,
				    lino, tot, nex, blkmapp, &cursor,
				    1, check_dups))
			return(1);
		/*
		 * fix key (offset) mismatches between the keys in root
		 * block records and the first key of each child block.
		 * fixes cases where entries have been shifted between
		 * blocks but the parent hasn't been updated
		 */
		if (check_dups == 0 &&
		    cursor.level[INT_GET(dib->bb_level,
				ARCH_CONVERT)-1].first_key !=
		    INT_GET(pkey[i].br_startoff, ARCH_CONVERT))  {
			if (!no_modify)  {
				do_warn(
	_("correcting key in bmbt root (was %llu, now %llu) in inode "
	  "%llu %s fork\n"),
					INT_GET(pkey[i].br_startoff,
						ARCH_CONVERT),
					cursor.level[INT_GET(dib->bb_level,
						ARCH_CONVERT)-1].first_key,
					XFS_AGINO_TO_INO(mp, agno, ino),
					forkname);
				*dirty = 1;
				INT_SET(pkey[i].br_startoff, ARCH_CONVERT,
					cursor.level[INT_GET(dib->bb_level,
						ARCH_CONVERT)-1].first_key);
			} else  {
				do_warn(
	_("bad key in bmbt root (is %llu, would reset to %llu) in inode "
	  "%llu %s fork\n"),
					INT_GET(pkey[i].br_startoff,
						ARCH_CONVERT),
					cursor.level[INT_GET(dib->bb_level,
						ARCH_CONVERT)-1].first_key,
					XFS_AGINO_TO_INO(mp, agno, ino),
					forkname);
			}
		}
		/*
		 * make sure that keys are in ascending order.  blow out
		 * inode if the ordering doesn't hold
		 */
		if (check_dups == 0)  {
			if (last_key != NULLDFILOFF && last_key >=
			    cursor.level[INT_GET(dib->bb_level,
					ARCH_CONVERT)-1].first_key)  {
				do_warn(
		_("out of order bmbt root key %llu in inode %llu %s fork\n"),
					first_key,
					XFS_AGINO_TO_INO(mp, agno, ino),
					forkname);
				return(1);
			}
			last_key = cursor.level[INT_GET(dib->bb_level,
						ARCH_CONVERT)-1].first_key;
		}
	}
	/*
	 * Check that the last child block's forward sibling pointer
	 * is NULL.
	 */
	if (check_dups == 0 &&
		cursor.level[0].right_fsbno != NULLDFSBNO)  {
		do_warn(
	_("bad fwd (right) sibling pointer (saw %llu should be NULLDFSBNO)\n"),
			cursor.level[0].right_fsbno);
		do_warn(
	_("\tin inode %u (%s fork) bmap btree block %llu\n"),
			XFS_AGINO_TO_INO(mp, agno, ino), forkname,
			cursor.level[0].fsbno);
		return(1);
	}

	return(0);
}

/*
 * return 1 if inode should be cleared, 0 otherwise
 */
/* ARGSUSED */
int
process_exinode(
	xfs_mount_t		*mp,
	xfs_agnumber_t		agno,
	xfs_agino_t		ino,
	xfs_dinode_t		*dip,
	int			type,
	int			*dirty,
	xfs_drfsbno_t		*tot,
	__uint64_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork,
	int			check_dups)
{
	xfs_ino_t		lino;
	xfs_bmbt_rec_32_t	*rp;
	xfs_dfiloff_t		first_key;
	xfs_dfiloff_t		last_key;

	lino = XFS_AGINO_TO_INO(mp, agno, ino);
	rp = (xfs_bmbt_rec_32_t *)XFS_DFORK_PTR(dip, whichfork);
	*tot = 0;
	*nex = XFS_DFORK_NEXTENTS(dip, whichfork);
	/*
	 * XXX - if we were going to fix up the btree record,
	 * we'd do it right here.  For now, if there's a problem,
	 * we'll bail out and presumably clear the inode.
	 */
	if (check_dups == 0)
		return(process_bmbt_reclist(mp, rp, *nex, type, lino,
					tot, blkmapp, &first_key, &last_key,
					whichfork));
	else
		return(scan_bmbt_reclist(mp, rp, *nex, type, lino, tot,
					whichfork));
}

/*
 * return 1 if inode should be cleared, 0 otherwise
 */
/* ARGSUSED */
int
process_lclinode(
	xfs_mount_t		*mp,
	xfs_agnumber_t		agno,
	xfs_agino_t		ino,
	xfs_dinode_t		*dip,
	int			type,
	int			*dirty,
	xfs_drfsbno_t		*tot,
	__uint64_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork,
	int			check_dups)
{
	xfs_attr_shortform_t	*asf;
	xfs_dinode_core_t	*dic;
	xfs_ino_t		lino;

	*tot = 0;
	*nex = 0;	/* local inodes have 0 extents */

	dic = &dip->di_core;
	lino = XFS_AGINO_TO_INO(mp, agno, ino);
	if (whichfork == XFS_DATA_FORK &&
	    INT_GET(dic->di_size, ARCH_CONVERT) >
	    XFS_DFORK_DSIZE(dip, mp)) {
		do_warn(
	_("local inode %llu data fork is too large (size = %lld, max = %d)\n"),
			lino, INT_GET(dic->di_size, ARCH_CONVERT),
			XFS_DFORK_DSIZE(dip, mp));
		return(1);
	} else if (whichfork == XFS_ATTR_FORK) {
		asf = (xfs_attr_shortform_t *)
			XFS_DFORK_APTR(dip);
		if (INT_GET(asf->hdr.totsize, ARCH_CONVERT) >
		    XFS_DFORK_ASIZE(dip, mp)) {
			do_warn(
	_("local inode %llu attr fork too large (size %d, max = %d)\n"),
				lino, INT_GET(asf->hdr.totsize, ARCH_CONVERT),
				XFS_DFORK_ASIZE(dip, mp));
			return(1);
		}
		if (INT_GET(asf->hdr.totsize, ARCH_CONVERT) <
		    sizeof(xfs_attr_sf_hdr_t)) {
			do_warn(
	_("local inode %llu attr too small (size = %d, min size = %d)\n"),
				lino, INT_GET(asf->hdr.totsize, ARCH_CONVERT),
				sizeof(xfs_attr_sf_hdr_t));
			return(1);
		}
	}

	return(0);
}

int
process_symlink_extlist(xfs_mount_t *mp, xfs_ino_t lino, xfs_dinode_t *dino)
{
	xfs_dfsbno_t		start;		/* start */
	xfs_dfilblks_t		cnt;		/* count */
	xfs_dfiloff_t		offset;		/* offset */
	xfs_dfiloff_t		expected_offset;
	xfs_bmbt_rec_32_t	*rp;
	int			numrecs;
	int			i;
	int			max_blocks;
	int			whichfork = XFS_DATA_FORK;
	int			flag;

	if (INT_GET(dino->di_core.di_size, ARCH_CONVERT) <=
	    XFS_DFORK_SIZE(dino, mp, whichfork))  {
		if (dino->di_core.di_format == XFS_DINODE_FMT_LOCAL)  {
			return(0);
		} else  {
			do_warn(
	_("mismatch between format (%d) and size (%lld) in symlink ino %llu\n"),
				dino->di_core.di_format,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT),
				lino);
			return(1);
		}
	} else if (dino->di_core.di_format == XFS_DINODE_FMT_LOCAL)  {
		do_warn(
	_("mismatch between format (%d) and size (%lld) in symlink inode %llu\n"),
				dino->di_core.di_format,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT),
				lino);
		return(1);
	}

	rp = (xfs_bmbt_rec_32_t *)XFS_DFORK_PTR(dino, whichfork);
	numrecs = XFS_DFORK_NEXTENTS(dino, whichfork);

	/*
	 * the max # of extents in a symlink inode is equal to the
	 * number of max # of blocks required to store the symlink
	 */
	if (numrecs > max_symlink_blocks)  {
		do_warn(
		_("bad number of extents (%d) in symlink %llu data fork\n"),
			numrecs, lino);
		return(1);
	}

	max_blocks = max_symlink_blocks;
	expected_offset = 0;

	for (i = 0; numrecs > 0; i++, numrecs--)  {
		convert_extent(rp, &offset, &start, &cnt, &flag);

		if (offset != expected_offset)  {
			do_warn(
		_("bad extent #%d offset (%llu) in symlink %llu data fork\n"),
				i, offset, lino);
			return(1);
		}
		if (cnt == 0 || cnt > max_blocks)  {
			do_warn(
		_("bad extent #%d count (%llu) in symlink %llu data fork\n"),
				i, cnt, lino);
			return(1);
		}

		max_blocks -= cnt;
		expected_offset += cnt;
	}

	return(0);
}

/*
 * takes a name and length and returns 1 if the name contains
 * a \0, returns 0 otherwise
 */
int
null_check(char *name, int length)
{
	int i;

	ASSERT(length < MAXPATHLEN);

	for (i = 0; i < length; i++, name++)  {
		if (*name == '\0')
			return(1);
	}

	return(0);
}

/*
 * like usual, returns 0 if everything's ok and 1 if something's
 * bogus
 */
int
process_symlink(xfs_mount_t *mp, xfs_ino_t lino, xfs_dinode_t *dino,
		blkmap_t *blkmap)
{
	xfs_dfsbno_t		fsbno;
	xfs_dinode_core_t	*dinoc = &dino->di_core;
	xfs_buf_t		*bp = NULL;
	char			*symlink, *cptr, *buf_data;
	int			i, size, amountdone;
	char			data[MAXPATHLEN];

	/*
	 * check size against kernel symlink limits.  we know
	 * size is consistent with inode storage format -- e.g.
	 * the inode is structurally ok so we don't have to check
	 * for that
	 */
	if (INT_GET(dinoc->di_size, ARCH_CONVERT) >= MAXPATHLEN)  {
		do_warn(_("symlink in inode %llu too long (%lld chars)\n"),
			lino, INT_GET(dinoc->di_size, ARCH_CONVERT));
		return(1);
	}

	/*
	 * have to check symlink component by component.
	 * get symlink contents into data area
	 */
	symlink = &data[0];
	if (INT_GET(dinoc->di_size, ARCH_CONVERT)
			<= XFS_DFORK_DSIZE(dino, mp))  {
		/*
		 * local symlink, just copy the symlink out of the
		 * inode into the data area
		 */
		bcopy((char *)XFS_DFORK_DPTR(dino),
			symlink, INT_GET(dinoc->di_size, ARCH_CONVERT));
	} else {
		/*
		 * stored in a meta-data file, have to bmap one block
		 * at a time and copy the symlink into the data area
		 */
		i = size = amountdone = 0;
		cptr = symlink;

		while (amountdone < INT_GET(dinoc->di_size, ARCH_CONVERT)) {
			fsbno = blkmap_get(blkmap, i);
			if (fsbno != NULLDFSBNO)
				bp = libxfs_readbuf(mp->m_dev,
						XFS_FSB_TO_DADDR(mp, fsbno),
						XFS_FSB_TO_BB(mp, 1), 0);
			if (!bp || fsbno == NULLDFSBNO) {
				do_warn(
		_("cannot read inode %llu, file block %d, disk block %llu\n"),
					lino, i, fsbno);
				return(1);
			}

			buf_data = (char *)XFS_BUF_PTR(bp);
			size = MIN(INT_GET(dinoc->di_size, ARCH_CONVERT)
				- amountdone, (int)XFS_FSB_TO_BB(mp, 1)*BBSIZE);
			bcopy(buf_data, cptr, size);
			cptr += size;
			amountdone += size;
			i++;
			libxfs_putbuf(bp);
		}
	}
	data[INT_GET(dinoc->di_size, ARCH_CONVERT)] = '\0';

	/*
	 * check for nulls
	 */
	if (null_check(symlink, (int) INT_GET(dinoc->di_size, ARCH_CONVERT)))  {
		do_warn(
		_("found illegal null character in symlink inode %llu\n"),
			lino);
		return(1);
	}

	/*
	 * check for any component being too long
	 */
	if (INT_GET(dinoc->di_size, ARCH_CONVERT) >= MAXNAMELEN)  {
		cptr = strchr(symlink, '/');

		while (cptr != NULL)  {
			if (cptr - symlink >= MAXNAMELEN)  {
				do_warn(
			_("component of symlink in inode %llu too long\n"),
					lino);
				return(1);
			}
			symlink = cptr + 1;
			cptr = strchr(symlink, '/');
		}

		if (strlen(symlink) >= MAXNAMELEN)  {
			do_warn(
			_("component of symlink in inode %llu too long\n"),
				lino);
			return(1);
		}
	}

	return(0);
}

/*
 * called to process the set of misc inode special inode types
 * that have no associated data storage (fifos, pipes, devices, etc.).
 */
/* ARGSUSED */
int
process_misc_ino_types(xfs_mount_t	*mp,
			xfs_dinode_t	*dino,
			xfs_ino_t	lino,
			int		type)
{
	/*
	 * disallow mountpoint inodes until such time as the
	 * kernel actually allows them to be created (will
	 * probably require a superblock version rev, sigh).
	 */
	if (type == XR_INO_MOUNTPOINT)  {
		do_warn(_("inode %llu has bad inode type (IFMNT)\n"), lino);
		return(1);
	}

	/*
	 * must also have a zero size
	 */
	if (INT_GET(dino->di_core.di_size, ARCH_CONVERT) != 0)  {
		switch (type)  {
		case XR_INO_CHRDEV:
			do_warn(_("size of character device inode %llu != 0 "
				  "(%lld bytes)\n"), lino,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT));
			break;
		case XR_INO_BLKDEV:
			do_warn(_("size of block device inode %llu != 0 "
				  "(%lld bytes)\n"), lino,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT));
			break;
		case XR_INO_SOCK:
			do_warn(_("size of socket inode %llu != 0 "
				  "(%lld bytes)\n"), lino,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT));
			break;
		case XR_INO_FIFO:
			do_warn(_("size of fifo inode %llu != 0 "
				  "(%lld bytes)\n"), lino,
				INT_GET(dino->di_core.di_size, ARCH_CONVERT));
			break;
		default:
			do_warn(_("Internal error - process_misc_ino_types, "
				  "illegal type %d\n"), type);
			abort();
		}

		return(1);
	}

	return(0);
}

static __inline int
process_misc_ino_types_blocks(xfs_drfsbno_t totblocks, xfs_ino_t lino, int type)
{
	/*
	 * you can not enforce all misc types have zero data fork blocks
	 * by checking dino->di_core.di_nblocks because atotblocks (attribute
	 * blocks) are part of nblocks. We must check this later when atotblocks
	 * has been calculated or by doing a simple check that anExtents == 0.
	 * We must also guarantee that totblocks is 0. Thus nblocks checking
	 * will be done later in process_dinode_int for misc types.
	 */

	if (totblocks != 0)  {
		switch (type)  {
		case XR_INO_CHRDEV:
			do_warn(
		_("size of character device inode %llu != 0 (%llu blocks)\n"),
				lino, totblocks);
			break;
		case XR_INO_BLKDEV:
			do_warn(
		_("size of block device inode %llu != 0 (%llu blocks)\n"),
				lino, totblocks);
			break;
		case XR_INO_SOCK:
			do_warn(
		_("size of socket inode %llu != 0 (%llu blocks)\n"),
				lino, totblocks);
			break;
		case XR_INO_FIFO:
			do_warn(
		_("size of fifo inode %llu != 0 (%llu blocks)\n"),
				lino, totblocks);
			break;
		default:
			return(0);
		}
		return(1);
	}
	return (0);
}

/*
 * returns 0 if the inode is ok, 1 if the inode is corrupt
 * check_dups can be set to 1 *only* when called by the
 * first pass of the duplicate block checking of phase 4.
 * *dirty is set > 0 if the dinode has been altered and
 * needs to be written out.
 *
 * for detailed, info, look at process_dinode() comments.
 */
/* ARGSUSED */
int
process_dinode_int(xfs_mount_t *mp,
		xfs_dinode_t *dino,
		xfs_agnumber_t agno,
		xfs_agino_t ino,
		int was_free,		/* 1 if inode is currently free */
		int *dirty,		/* out == > 0 if inode is now dirty */
		int *cleared,		/* out == 1 if inode was cleared */
		int *used,		/* out == 1 if inode is in use */
		int verify_mode,	/* 1 == verify but don't modify inode */
		int uncertain,		/* 1 == inode is uncertain */
		int ino_discovery,	/* 1 == check dirs for unknown inodes */
		int check_dups,		/* 1 == check if inode claims
					 * duplicate blocks		*/
		int extra_attr_check, /* 1 == do attribute format and value checks */
		int *isa_dir,		/* out == 1 if inode is a directory */
		xfs_ino_t *parent)	/* out -- parent if ino is a dir */
{
	xfs_drfsbno_t		totblocks = 0;
	xfs_drfsbno_t		atotblocks = 0;
	xfs_dinode_core_t	*dinoc;
	char			*rstring;
	int			type;
	int			rtype;
	int			do_rt;
	int			err;
	int			retval = 0;
	__uint64_t		nextents;
	__uint64_t		anextents;
	xfs_ino_t		lino;
	const int		is_free = 0;
	const int		is_used = 1;
	int			repair = 0;
	blkmap_t		*ablkmap = NULL;
	blkmap_t		*dblkmap = NULL;
	static char		okfmts[] = {
		0,				/* free inode */
		1 << XFS_DINODE_FMT_DEV,	/* FIFO */
		1 << XFS_DINODE_FMT_DEV,	/* CHR */
		0,				/* type 3 unused */
		(1 << XFS_DINODE_FMT_LOCAL) |
		(1 << XFS_DINODE_FMT_EXTENTS) |
		(1 << XFS_DINODE_FMT_BTREE),	/* DIR */
		0,				/* type 5 unused */
		1 << XFS_DINODE_FMT_DEV,	/* BLK */
		0,				/* type 7 unused */
		(1 << XFS_DINODE_FMT_EXTENTS) |
		(1 << XFS_DINODE_FMT_BTREE),	/* REG */
		0,				/* type 9 unused */
		(1 << XFS_DINODE_FMT_LOCAL) |
		(1 << XFS_DINODE_FMT_EXTENTS),	/* LNK */
		0,				/* type 11 unused */
		1 << XFS_DINODE_FMT_DEV,	/* SOCK */
		0,				/* type 13 unused */
		1 << XFS_DINODE_FMT_UUID,	/* MNT */
		0				/* type 15 unused */
	};

	retval = 0;
	totblocks = atotblocks = 0;
	*dirty = *isa_dir = *cleared = 0;
	*used = is_used;
	type = rtype = XR_INO_UNKNOWN;
	rstring = NULL;
	do_rt = 0;

	dinoc = &dino->di_core;
	lino = XFS_AGINO_TO_INO(mp, agno, ino);

	/*
	 * if in verify mode, don't modify the inode.
	 *
	 * if correcting, reset stuff that has known values
	 *
	 * if in uncertain mode, be silent on errors since we're
	 * trying to find out if these are inodes as opposed
	 * to assuming that they are.  Just return the appropriate
	 * return code in that case.
	 */

	if (INT_GET(dinoc->di_magic, ARCH_CONVERT) != XFS_DINODE_MAGIC)  {
		retval++;
		if (!verify_mode)  {
			do_warn(_("bad magic number 0x%x on inode %llu, "),
				INT_GET(dinoc->di_magic, ARCH_CONVERT), lino);
			if (!no_modify)  {
				do_warn(_("resetting magic number\n"));
				*dirty = 1;
				INT_SET(dinoc->di_magic, ARCH_CONVERT,
					XFS_DINODE_MAGIC);
			} else  {
				do_warn(_("would reset magic number\n"));
			}
		} else if (!uncertain) {
			do_warn(_("bad magic number 0x%x on inode %llu\n"),
				INT_GET(dinoc->di_magic, ARCH_CONVERT), lino);
		}
	}

	if (!XFS_DINODE_GOOD_VERSION(dinoc->di_version) ||
	    (!fs_inode_nlink && dinoc->di_version > XFS_DINODE_VERSION_1))  {
		retval++;
		if (!verify_mode)  {
			do_warn(_("bad version number 0x%x on inode %llu, "),
				dinoc->di_version, lino);
			if (!no_modify)  {
				do_warn(_("resetting version number\n"));
				*dirty = 1;
				dinoc->di_version = (fs_inode_nlink) ?
					XFS_DINODE_VERSION_2 :
					XFS_DINODE_VERSION_1;
			} else  {
				do_warn(_("would reset version number\n"));
			}
		} else  if (!uncertain) {
			do_warn(_("bad version number 0x%x on inode %llu\n"),
				dinoc->di_version, lino);
		}
	}

	/*
	 * blow out of here if the inode size is < 0
	 */
	if (INT_GET(dinoc->di_size, ARCH_CONVERT) < 0)  {
		retval++;
		if (!verify_mode)  {
			do_warn(_("bad (negative) size %lld on inode %llu\n"),
				INT_GET(dinoc->di_size, ARCH_CONVERT), lino);
			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				*cleared = 1;
			} else  {
				*dirty = 1;
				*cleared = 1;
			}
			*used = is_free;
		} else if (!uncertain)  {
			do_warn(_("bad (negative) size %lld on inode %llu\n"),
				INT_GET(dinoc->di_size, ARCH_CONVERT), lino);
		}

		return(1);
	}

	/*
	 * was_free value is not meaningful if we're in verify mode
	 */
	if (!verify_mode && INT_GET(dinoc->di_mode, ARCH_CONVERT) == 0 && was_free == 1)  {
		/*
		 * easy case, inode free -- inode and map agree, clear
		 * it just in case to ensure that format, etc. are
		 * set correctly
		 */
		if (!no_modify)  {
			err =  clear_dinode(mp, dino, lino);
			if (err)  {
				*dirty = 1;
				*cleared = 1;
			}
		}
		*used = is_free;
		return(0);
	} else if (!verify_mode && INT_GET(dinoc->di_mode, ARCH_CONVERT) == 0 && was_free == 0)  {
		/*
		 * the inode looks free but the map says it's in use.
		 * clear the inode just to be safe and mark the inode
		 * free.
		 */
		do_warn(_("imap claims a free inode %llu is in use, "), lino);

		if (!no_modify)  {
			do_warn(_("correcting imap and clearing inode\n"));

			err =  clear_dinode(mp, dino, lino);
			if (err)  {
				retval++;
				*dirty = 1;
				*cleared = 1;
			}
		} else  {
			do_warn(_("would correct imap and clear inode\n"));

			*dirty = 1;
			*cleared = 1;
		}

		*used = is_free;

		return(retval > 0 ? 1 : 0);
	}

	/*
	 * because of the lack of any write ordering guarantee, it's
	 * possible that the core got updated but the forks didn't.
	 * so rather than be ambitious (and probably incorrect),
	 * if there's an inconsistency, we get conservative and
	 * just pitch the file.  blow off checking formats of
	 * free inodes since technically any format is legal
	 * as we reset the inode when we re-use it.
	 */
	if (INT_GET(dinoc->di_mode, ARCH_CONVERT) != 0 &&
		((((INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) >> 12) > 15) ||
		(uchar_t) dinoc->di_format > XFS_DINODE_FMT_UUID ||
			(!(okfmts[(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) >> 12] &
			  (1 << dinoc->di_format))))) {
		/* bad inode format */
		retval++;
		if (!uncertain)
			do_warn(_("bad inode format in inode %llu\n"), lino);
		if (!verify_mode)  {
			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}
		}
		*cleared = 1;
		*used = is_free;

		return(retval > 0 ? 1 : 0);
	}

	if (verify_mode)
		return(retval > 0 ? 1 : 0);

	/*
	 * clear the next unlinked field if necessary on a good
	 * inode only during phase 4 -- when checking for inodes
	 * referencing duplicate blocks.  then it's safe because
	 * we've done the inode discovery and have found all the inodes
	 * we're going to find.  check_dups is set to 1 only during
	 * phase 4.  Ugly.
	 */
	if (check_dups && !no_modify)
		*dirty += clear_dinode_unlinked(mp, dino);

	/* set type and map type info */

	switch (INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT) {
	case S_IFDIR:
		type = XR_INO_DIR;
		*isa_dir = 1;
		break;
	case S_IFREG:
		if (INT_GET(dinoc->di_flags, ARCH_CONVERT) & XFS_DIFLAG_REALTIME)
			type = XR_INO_RTDATA;
		else if (lino == mp->m_sb.sb_rbmino)
			type = XR_INO_RTBITMAP;
		else if (lino == mp->m_sb.sb_rsumino)
			type = XR_INO_RTSUM;
		else
			type = XR_INO_DATA;
		break;
	case S_IFLNK:
		type = XR_INO_SYMLINK;
		break;
	case S_IFCHR:
		type = XR_INO_CHRDEV;
		break;
	case S_IFBLK:
		type = XR_INO_BLKDEV;
		break;
	case S_IFSOCK:
		type = XR_INO_SOCK;
		break;
	case S_IFIFO:
		type = XR_INO_FIFO;
		break;
	default:
		retval++;
		if (!verify_mode)  {
			do_warn(_("bad inode type %#o inode %llu\n"),
				(int) (INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT), lino);
			if (!no_modify)  
				*dirty += clear_dinode(mp, dino, lino);
			else 
				*dirty = 1;
			*cleared = 1;
			*used = is_free;
		} else if (!uncertain)  {
			do_warn(_("bad inode type %#o inode %llu\n"),
				(int) (INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT), lino);
		}
		return 1;
	}

	/*
	 * type checks for root, realtime inodes, and quota inodes
	 */
	if (lino == mp->m_sb.sb_rootino && type != XR_INO_DIR)  {
		do_warn(_("bad inode type for root inode %llu, "), lino);
		type = XR_INO_DIR;

		if (!no_modify)  {
			do_warn(_("resetting to directory\n"));
			INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT,
			  &= ~(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT));
			INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT,
			  |= INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFDIR);
		} else  {
			do_warn(_("would reset to directory\n"));
		}
	} else if (lino == mp->m_sb.sb_rsumino)  {
		do_rt = 1;
		rstring = _("summary");
		rtype = XR_INO_RTSUM;
	} else if (lino == mp->m_sb.sb_rbmino)  {
		do_rt = 1;
		rstring = _("bitmap");
		rtype = XR_INO_RTBITMAP;
	} else if (lino == mp->m_sb.sb_uquotino)  {
		if (type != XR_INO_DATA)  {
			do_warn(_("user quota inode has bad type 0x%x\n"),
				INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT);

			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			mp->m_sb.sb_uquotino = NULLFSINO;

			return(1);
		}
	} else if (lino == mp->m_sb.sb_gquotino)  {
		if (type != XR_INO_DATA)  {
			do_warn(_("group quota inode has bad type 0x%x\n"),
				INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT);

			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			mp->m_sb.sb_gquotino = NULLFSINO;

			return(1);
		}
	}

	if (do_rt && type != rtype)  {
		type = XR_INO_DATA;

		do_warn(_("bad inode type for realtime %s inode %llu, "),
			rstring, lino);

		if (!no_modify)  {
			do_warn(_("resetting to regular file\n"));
			INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT,
			  &= ~(INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFMT));
			INT_MOD_EXPR(dinoc->di_mode, ARCH_CONVERT,
			  |= INT_GET(dinoc->di_mode, ARCH_CONVERT) & S_IFREG);
		} else  {
			do_warn(_("would reset to regular file\n"));
		}
	}

	/*
	 * only regular files with REALTIME or EXTSIZE flags set can have
	 * extsize set, or directories with EXTSZINHERIT.
	 */
	if (INT_GET(dinoc->di_extsize, ARCH_CONVERT) != 0)  {
		if ((type == XR_INO_RTDATA) ||
		    (type == XR_INO_DIR &&
				(INT_GET(dinoc->di_flags, ARCH_CONVERT) &
				 XFS_DIFLAG_EXTSZINHERIT)) ||
		    (type == XR_INO_DATA &&
				(INT_GET(dinoc->di_flags, ARCH_CONVERT) &
				 XFS_DIFLAG_EXTSIZE)))  {
			/* s'okay */ ;
		} else {
			do_warn(
	_("bad non-zero extent size %u for non-realtime/extsize inode %llu, "),
				INT_GET(dinoc->di_extsize, ARCH_CONVERT), lino);

			if (!no_modify)  {
				do_warn(_("resetting to zero\n"));
				dinoc->di_extsize = 0;
				*dirty = 1;
			} else  {
				do_warn(_("would reset to zero\n"));
			}
		}
	}

	/*
	 * for realtime inodes, check sizes to see that
	 * they are consistent with the # of realtime blocks.
	 * also, verify that they contain only one extent and
	 * are extent format files.  If anything's wrong, clear
	 * the inode -- we'll recreate it in phase 6.
	 */
	if (do_rt &&
	    ((lino == mp->m_sb.sb_rbmino &&
	      INT_GET(dinoc->di_size, ARCH_CONVERT)
			  != mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize) ||
	     (lino == mp->m_sb.sb_rsumino &&
	      INT_GET(dinoc->di_size, ARCH_CONVERT) != mp->m_rsumsize))) {

		do_warn(_("bad size %llu for realtime %s inode %llu\n"),
			INT_GET(dinoc->di_size, ARCH_CONVERT), rstring, lino);

		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}

		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;

		return(1);
	}

	if (do_rt && mp->m_sb.sb_rblocks == 0 && INT_GET(dinoc->di_nextents, ARCH_CONVERT) != 0)  {
		do_warn(_("bad # of extents (%u) for realtime %s inode %llu\n"),
			INT_GET(dinoc->di_nextents, ARCH_CONVERT), rstring, lino);

		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}

		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;

		return(1);
	}

	/*
	 * Setup nextents and anextents for blkmap_alloc calls.
	 */
	nextents = INT_GET(dinoc->di_nextents, ARCH_CONVERT);
	if (nextents > INT_GET(dinoc->di_nblocks, ARCH_CONVERT) || nextents > XFS_MAX_INCORE_EXTENTS)
		nextents = 1;
	anextents = INT_GET(dinoc->di_anextents, ARCH_CONVERT);
	if (anextents > INT_GET(dinoc->di_nblocks, ARCH_CONVERT) || anextents > XFS_MAX_INCORE_EXTENTS)
		anextents = 1;

	/*
	 * general size/consistency checks:
	 *
	 * if the size <= size of the data fork, directories  must be
	 * local inodes unlike regular files which would be extent inodes.
	 * all the other mentioned types have to have a zero size value.
	 *
	 * if the size and format don't match, get out now rather than
	 * risk trying to process a non-existent extents or btree
	 * type data fork.
	 */
	switch (type)  {
	case XR_INO_DIR:
		if (INT_GET(dinoc->di_size, ARCH_CONVERT) <=
			XFS_DFORK_DSIZE(dino, mp) &&
		    (dinoc->di_format != XFS_DINODE_FMT_LOCAL))  {
			do_warn(
_("mismatch between format (%d) and size (%lld) in directory ino %llu\n"),
				dinoc->di_format,
				INT_GET(dinoc->di_size, ARCH_CONVERT),
				lino);

			if (!no_modify)  {
				*dirty += clear_dinode(mp,
						dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		if (dinoc->di_format != XFS_DINODE_FMT_LOCAL)
			dblkmap = blkmap_alloc(nextents);
		break;
	case XR_INO_SYMLINK:
		if (process_symlink_extlist(mp, lino, dino))  {
			do_warn(_("bad data fork in symlink %llu\n"), lino);

			if (!no_modify)  {
				*dirty += clear_dinode(mp,
						dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		if (dinoc->di_format != XFS_DINODE_FMT_LOCAL)
			dblkmap = blkmap_alloc(nextents);
		break;
	case XR_INO_CHRDEV:	/* fall through to FIFO case ... */
	case XR_INO_BLKDEV:	/* fall through to FIFO case ... */
	case XR_INO_SOCK:	/* fall through to FIFO case ... */
	case XR_INO_MOUNTPOINT:	/* fall through to FIFO case ... */
	case XR_INO_FIFO:
		if (process_misc_ino_types(mp, dino, lino, type))  {
			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		break;
	case XR_INO_RTDATA:
		/*
		 * if we have no realtime blocks, any inode claiming
		 * to be a real-time file is bogus
		 */
		if (mp->m_sb.sb_rblocks == 0)  {
			do_warn(
			_("found inode %llu claiming to be a real-time file\n"),
				lino);

			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		break;
	case XR_INO_RTBITMAP:
		if (INT_GET(dinoc->di_size, ARCH_CONVERT) !=
		    (__int64_t)mp->m_sb.sb_rbmblocks * mp->m_sb.sb_blocksize)  {
			do_warn(
	_("realtime bitmap inode %llu has bad size %lld (should be %lld)\n"),
				lino, INT_GET(dinoc->di_size, ARCH_CONVERT),
				(__int64_t) mp->m_sb.sb_rbmblocks *
				mp->m_sb.sb_blocksize);

			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		dblkmap = blkmap_alloc(nextents);
		break;
	case XR_INO_RTSUM:
		if (INT_GET(dinoc->di_size, ARCH_CONVERT) != mp->m_rsumsize)  {
			do_warn(
	_("realtime summary inode %llu has bad size %lld (should be %d)\n"),
				lino, INT_GET(dinoc->di_size, ARCH_CONVERT),
				mp->m_rsumsize);

			if (!no_modify)  {
				*dirty += clear_dinode(mp, dino, lino);
				ASSERT(*dirty > 0);
			}

			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;

			return(1);
		}
		dblkmap = blkmap_alloc(nextents);
		break;
	default:
		break;
	}

	/*
	 * check for illegal values of forkoff
	 */
	err = 0;
	if (dinoc->di_forkoff != 0)  {
		switch (dinoc->di_format)  {
		case XFS_DINODE_FMT_DEV:
			if (dinoc->di_forkoff !=
					(roundup(sizeof(xfs_dev_t), 8) >> 3))  {
				do_warn(
		_("bad attr fork offset %d in dev inode %llu, should be %d\n"),
					(int) dinoc->di_forkoff,
					lino,
					(int) (roundup(sizeof(xfs_dev_t), 8) >> 3));
				err = 1;
			}
			break;
		case XFS_DINODE_FMT_UUID:
			if (dinoc->di_forkoff !=
					(roundup(sizeof(uuid_t), 8) >> 3))  {
				do_warn(
		_("bad attr fork offset %d in uuid inode %llu, should be %d\n"),
					(int) dinoc->di_forkoff,
					lino,
					(int)(roundup(sizeof(uuid_t), 8) >> 3));
				err = 1;
			}
			break;
		case XFS_DINODE_FMT_LOCAL:	/* fall through ... */
		case XFS_DINODE_FMT_EXTENTS:	/* fall through ... */
		case XFS_DINODE_FMT_BTREE: {
			if (dinoc->di_forkoff >= (XFS_LITINO(mp) >> 3))  {
				do_warn(
		_("bad attr fork offset %d in inode %llu, max=%d\n"),
					(int) dinoc->di_forkoff,
					lino, XFS_LITINO(mp) >> 3);
				err = 1;
			}
			break;
		    }
		default:
			do_error(_("unexpected inode format %d\n"),
				(int) dinoc->di_format);
			break;
		}
	}

	if (err)  {
		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}

		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;
		blkmap_free(dblkmap);
		return(1);
	}

	/*
	 * check data fork -- if it's bad, clear the inode
	 */
	nextents = 0;
	switch (dinoc->di_format) {
	case XFS_DINODE_FMT_LOCAL:
		err = process_lclinode(mp, agno, ino, dino, type,
			dirty, &totblocks, &nextents, &dblkmap,
			XFS_DATA_FORK, check_dups);
		break;
	case XFS_DINODE_FMT_EXTENTS:
		err = process_exinode(mp, agno, ino, dino, type,
			dirty, &totblocks, &nextents, &dblkmap,
			XFS_DATA_FORK, check_dups);
		break;
	case XFS_DINODE_FMT_BTREE:
		err = process_btinode(mp, agno, ino, dino, type,
			dirty, &totblocks, &nextents, &dblkmap,
			XFS_DATA_FORK, check_dups);
		break;
	case XFS_DINODE_FMT_DEV:	/* fall through */
	case XFS_DINODE_FMT_UUID:
		err = 0;
		break;
	default:
		do_error(_("unknown format %d, ino %llu (mode = %d)\n"),
			dinoc->di_format, lino,
			INT_GET(dinoc->di_mode, ARCH_CONVERT));
	}

	if (err)  {
		/*
		 * problem in the data fork, clear out the inode
		 * and get out
		 */
		do_warn(_("bad data fork in inode %llu\n"), lino);

		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}

		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;
		blkmap_free(dblkmap);
		return(1);
	}

	if (check_dups)  {
		/*
		 * if check_dups was non-zero, we have to
		 * re-process data fork to set bitmap since the
		 * bitmap wasn't set the first time through
		 */
		switch (dinoc->di_format) {
		case XFS_DINODE_FMT_LOCAL:
			err = process_lclinode(mp, agno, ino, dino, type,
				dirty, &totblocks, &nextents, &dblkmap,
				XFS_DATA_FORK, 0);
			break;
		case XFS_DINODE_FMT_EXTENTS:
			err = process_exinode(mp, agno, ino, dino, type,
				dirty, &totblocks, &nextents, &dblkmap,
				XFS_DATA_FORK, 0);
			break;
		case XFS_DINODE_FMT_BTREE:
			err = process_btinode(mp, agno, ino, dino, type,
				dirty, &totblocks, &nextents, &dblkmap,
				XFS_DATA_FORK, 0);
			break;
		case XFS_DINODE_FMT_DEV:	/* fall through */
		case XFS_DINODE_FMT_UUID:
			err = 0;
			break;
		default:
			do_error(_("unknown format %d, ino %llu (mode = %d)\n"),
				dinoc->di_format, lino,
				INT_GET(dinoc->di_mode, ARCH_CONVERT));
		}

		if (no_modify && err != 0)  {
			*cleared = 1;
			*used = is_free;
			*isa_dir = 0;
			blkmap_free(dblkmap);
			return(1);
		}

		ASSERT(err == 0);
	}

	/*
	 * check attribute fork if necessary.  attributes are
	 * always stored in the regular filesystem.
	 */

	if (!XFS_DFORK_Q(dino) &&
	    dinoc->di_aformat != XFS_DINODE_FMT_EXTENTS) {
		do_warn(_("bad attribute format %d in inode %llu, "),
			dinoc->di_aformat, lino);
		if (!no_modify) {
			do_warn(_("resetting value\n"));
			dinoc->di_aformat = XFS_DINODE_FMT_EXTENTS;
			*dirty = 1;
		} else
			do_warn(_("would reset value\n"));
		anextents = 0;
	} else if (XFS_DFORK_Q(dino)) {
		switch (dinoc->di_aformat) {
		case XFS_DINODE_FMT_LOCAL:
			anextents = 0;
			err = process_lclinode(mp, agno, ino, dino,
				type, dirty, &atotblocks, &anextents, &ablkmap,
				XFS_ATTR_FORK, check_dups);
			break;
		case XFS_DINODE_FMT_EXTENTS:
			ablkmap = blkmap_alloc(anextents);
			anextents = 0;
			err = process_exinode(mp, agno, ino, dino,
				type, dirty, &atotblocks, &anextents, &ablkmap,
				XFS_ATTR_FORK, check_dups);
			break;
		case XFS_DINODE_FMT_BTREE:
			ablkmap = blkmap_alloc(anextents);
			anextents = 0;
			err = process_btinode(mp, agno, ino, dino,
				type, dirty, &atotblocks, &anextents, &ablkmap,
				XFS_ATTR_FORK, check_dups);
			break;
		default:
			anextents = 0;
			do_warn(_("illegal attribute format %d, ino %llu\n"),
					dinoc->di_aformat, lino);
			err = 1;
			break;
		}

		if (err)  {
			/*
			 * clear the attribute fork if necessary.  we can't
			 * clear the inode because we've already put the
			 * inode space info into the blockmap.
			 *
			 * XXX - put the inode onto the "move it" list and
			 *	log the the attribute scrubbing
			 */
			do_warn(_("bad attribute fork in inode %llu"), lino);

			if (!no_modify)  {
				if (delete_attr_ok)  {
					do_warn(_(", clearing attr fork\n"));
					*dirty += clear_dinode_attr(mp,
							dino, lino);
				} else  {
					do_warn("\n");
					*dirty += clear_dinode(mp,
							dino, lino);
				}
				ASSERT(*dirty > 0);
			} else  {
				do_warn(_(", would clear attr fork\n"));
			}

			atotblocks = 0;
			anextents = 0;

			if (delete_attr_ok)  {
				if (!no_modify)
					dinoc->di_aformat = XFS_DINODE_FMT_LOCAL;
			} else  {
				*cleared = 1;
				*used = is_free;
				*isa_dir = 0;
				blkmap_free(dblkmap);
				blkmap_free(ablkmap);
			}
			return(1);

		} else if (check_dups)  {
			switch (dinoc->di_aformat) {
			case XFS_DINODE_FMT_LOCAL:
				err = process_lclinode(mp, agno, ino, dino,
					type, dirty, &atotblocks, &anextents,
					&ablkmap, XFS_ATTR_FORK, 0);
				break;
			case XFS_DINODE_FMT_EXTENTS:
				err = process_exinode(mp, agno, ino, dino,
					type, dirty, &atotblocks, &anextents,
					&ablkmap, XFS_ATTR_FORK, 0);
				break;
			case XFS_DINODE_FMT_BTREE:
				err = process_btinode(mp, agno, ino, dino,
					type, dirty, &atotblocks, &anextents,
					&ablkmap, XFS_ATTR_FORK, 0);
				break;
			default:
				do_error(
				_("illegal attribute fmt %d, ino %llu\n"),
					dinoc->di_aformat, lino);
			}

			if (no_modify && err != 0)  {
				*cleared = 1;
				*used = is_free;
				*isa_dir = 0;
				blkmap_free(dblkmap);
				blkmap_free(ablkmap);
				return(1);
			}

			ASSERT(err == 0);
		}

		/*
		 * do attribute semantic-based consistency checks now
		 */

		/* get this only in phase 3, not in both phase 3 and 4 */
		if (extra_attr_check) {
		    if ((err = process_attributes(mp, lino, dino, ablkmap,
				    &repair))) {
			    do_warn(
		_("problem with attribute contents in inode %llu\n"), lino);
			    if(!repair) {
				    /* clear attributes if not done already */
				    if (!no_modify)  {
					    *dirty += clear_dinode_attr(
							mp, dino, lino);
					    dinoc->di_aformat =
						XFS_DINODE_FMT_LOCAL;
				    } else  {
					    do_warn(
					_("would clear attr fork\n"));
				    }
				    atotblocks = 0;
				    anextents = 0;
			    }
			    else {
				    *dirty = 1; /* it's been repaired */
			     }
		    }
		}
		blkmap_free(ablkmap);

	} else
		anextents = 0;

	/*
	* enforce totblocks is 0 for misc types
	*/
	if (process_misc_ino_types_blocks(totblocks, lino, type)) {
		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}
		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;
		blkmap_free(dblkmap);
		return(1);
	}

	/*
	 * correct space counters if required
	 */
	if (totblocks + atotblocks != INT_GET(dinoc->di_nblocks, ARCH_CONVERT))  {
		if (!no_modify)  {
			do_warn(
	_("correcting nblocks for inode %llu, was %llu - counted %llu\n"),
				lino, INT_GET(dinoc->di_nblocks, ARCH_CONVERT),
				totblocks + atotblocks);
			*dirty = 1;
			INT_SET(dinoc->di_nblocks, ARCH_CONVERT, totblocks + atotblocks);
		} else  {
			do_warn(
	_("bad nblocks %llu for inode %llu, would reset to %llu\n"),
				INT_GET(dinoc->di_nblocks, ARCH_CONVERT), lino,
				totblocks + atotblocks);
		}
	}

	if (nextents > MAXEXTNUM)  {
		do_warn(_("too many data fork extents (%llu) in inode %llu\n"),
			nextents, lino);

		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}
		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;
		blkmap_free(dblkmap);

		return(1);
	}
	if (nextents != INT_GET(dinoc->di_nextents, ARCH_CONVERT))  {
		if (!no_modify)  {
			do_warn(
	_("correcting nextents for inode %llu, was %d - counted %llu\n"),
				lino, INT_GET(dinoc->di_nextents, ARCH_CONVERT),
				nextents);
			*dirty = 1;
			INT_SET(dinoc->di_nextents, ARCH_CONVERT,
				(xfs_extnum_t) nextents);
		} else  {
			do_warn(
		_("bad nextents %d for inode %llu, would reset to %llu\n"),
				INT_GET(dinoc->di_nextents, ARCH_CONVERT),
				lino, nextents);
		}
	}

	if (anextents > MAXAEXTNUM)  {
		do_warn(_("too many attr fork extents (%llu) in inode %llu\n"),
			anextents, lino);

		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}
		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;
		blkmap_free(dblkmap);
		return(1);
	}
	if (anextents != INT_GET(dinoc->di_anextents, ARCH_CONVERT))  {
		if (!no_modify)  {
			do_warn(
	_("correcting anextents for inode %llu, was %d - counted %llu\n"),
				lino,
				INT_GET(dinoc->di_anextents, ARCH_CONVERT),
				anextents);
			*dirty = 1;
			INT_SET(dinoc->di_anextents, ARCH_CONVERT,
				(xfs_aextnum_t) anextents);
		} else  {
			do_warn(
		_("bad anextents %d for inode %llu, would reset to %llu\n"),
				INT_GET(dinoc->di_anextents, ARCH_CONVERT),
				lino, anextents);
		}
	}

	/*
	 * do any semantic type-based checking here
	 */
	switch (type)  {
	case XR_INO_DIR:
		if (XFS_SB_VERSION_HASDIRV2(&mp->m_sb))
			err = process_dir2(mp, lino, dino, ino_discovery,
					dirty, "", parent, dblkmap);
		else
			err = process_dir(mp, lino, dino, ino_discovery,
					dirty, "", parent, dblkmap);
		if (err)
			do_warn(
			_("problem with directory contents in inode %llu\n"),
				lino);
		break;
	case XR_INO_RTBITMAP:
		/* process_rtbitmap XXX */
		err = 0;
		break;
	case XR_INO_RTSUM:
		/* process_rtsummary XXX */
		err = 0;
		break;
	case XR_INO_SYMLINK:
		if ((err = process_symlink(mp, lino, dino, dblkmap)))
			do_warn(_("problem with symbolic link in inode %llu\n"),
				lino);
		break;
	case XR_INO_DATA:	/* fall through to FIFO case ... */
	case XR_INO_RTDATA:	/* fall through to FIFO case ... */
	case XR_INO_CHRDEV:	/* fall through to FIFO case ... */
	case XR_INO_BLKDEV:	/* fall through to FIFO case ... */
	case XR_INO_SOCK:	/* fall through to FIFO case ... */
	case XR_INO_FIFO:
		err = 0;
		break;
	default:
		printf(_("Unexpected inode type\n"));
		abort();
	}

	if (dblkmap)
		blkmap_free(dblkmap);

	if (err)  {
		/*
		 * problem in the inode type-specific semantic
		 * checking, clear out the inode and get out
		 */
		if (!no_modify)  {
			*dirty += clear_dinode(mp, dino, lino);
			ASSERT(*dirty > 0);
		}
		*cleared = 1;
		*used = is_free;
		*isa_dir = 0;

		return(1);
	}

	/*
	 * check nlinks feature, if it's a version 1 inode,
	 * just leave nlinks alone.  even if it's set wrong,
	 * it'll be reset when read in.
	 */
	if (dinoc->di_version > XFS_DINODE_VERSION_1 && !fs_inode_nlink)  {
		/*
		 * do we have a fs/inode version mismatch with a valid
		 * version 2 inode here that has to stay version 2 or
		 * lose links?
		 */
		if (INT_GET(dinoc->di_nlink, ARCH_CONVERT) > XFS_MAXLINK_1)  {
			/*
			 * yes.  are nlink inodes allowed?
			 */
			if (fs_inode_nlink_allowed)  {
				/*
				 * yes, update status variable which will
				 * cause sb to be updated later.
				 */
				fs_inode_nlink = 1;
				do_warn(
				_("version 2 inode %llu claims > %u links, "),
					lino, XFS_MAXLINK_1);
				if (!no_modify)  {
					do_warn(
			_("updating superblock version number\n"));
				} else  {
					do_warn(
			_("would update superblock version number\n"));
				}
			} else  {
				/*
				 * no, have to convert back to onlinks
				 * even if we lose some links
				 */
				do_warn(
			_("WARNING:  version 2 inode %llu claims > %u links, "),
					lino, XFS_MAXLINK_1);
				if (!no_modify)  {
					do_warn(
	_("converting back to version 1,\n\tthis may destroy %d links\n"),
						INT_GET(dinoc->di_nlink,
							ARCH_CONVERT)
						- XFS_MAXLINK_1);

					dinoc->di_version =
						XFS_DINODE_VERSION_1;
					INT_SET(dinoc->di_nlink, ARCH_CONVERT,
						XFS_MAXLINK_1);
					INT_SET(dinoc->di_onlink, ARCH_CONVERT,
						XFS_MAXLINK_1);

					*dirty = 1;
				} else  {
					do_warn(
	_("would convert back to version 1,\n\tthis might destroy %d links\n"),
						INT_GET(dinoc->di_nlink,
							ARCH_CONVERT)
						- XFS_MAXLINK_1);
				}
			}
		} else  {
			/*
			 * do we have a v2 inode that we could convert back
			 * to v1 without losing any links?  if we do and
			 * we have a mismatch between superblock bits and the
			 * version bit, alter the version bit in this case.
			 *
			 * the case where we lost links was handled above.
			 */
			do_warn(_("found version 2 inode %llu, "), lino);
			if (!no_modify)  {
				do_warn(_("converting back to version 1\n"));

				dinoc->di_version =
					XFS_DINODE_VERSION_1;
				INT_SET(dinoc->di_onlink, ARCH_CONVERT,
					INT_GET(dinoc->di_nlink, ARCH_CONVERT));

				*dirty = 1;
			} else  {
				do_warn(_("would convert back to version 1\n"));
			}
		}
	}

	/*
	 * ok, if it's still a version 2 inode, it's going
	 * to stay a version 2 inode.  it should have a zero
	 * onlink field, so clear it.
	 */
	if (dinoc->di_version > XFS_DINODE_VERSION_1 &&
	    INT_GET(dinoc->di_onlink, ARCH_CONVERT) > 0 &&
	    fs_inode_nlink > 0)  {
		if (!no_modify)  {
			do_warn(
_("clearing obsolete nlink field in version 2 inode %llu, was %d, now 0\n"),
				lino, INT_GET(dinoc->di_onlink, ARCH_CONVERT));
			dinoc->di_onlink = 0;
			*dirty = 1;
		} else  {
			do_warn(
_("would clear obsolete nlink field in version 2 inode %llu, currently %d\n"),
				lino, INT_GET(dinoc->di_onlink, ARCH_CONVERT));
			*dirty = 1;
		}
	}

	return(retval > 0 ? 1 : 0);
}

/*
 * returns 1 if inode is used, 0 if free.
 * performs any necessary salvaging actions.
 * note that we leave the generation count alone
 * because nothing we could set it to would be
 * guaranteed to be correct so the best guess for
 * the correct value is just to leave it alone.
 *
 * The trick is detecting empty files.  For those,
 * the core and the forks should all be in the "empty"
 * or zero-length state -- a zero or possibly minimum length
 * (in the case of dirs) extent list -- although inline directories
 * and symlinks might be handled differently.  So it should be
 * possible to sanity check them against each other.
 *
 * If the forks are an empty extent list though, then forget it.
 * The file is toast anyway since we can't recover its storage.
 *
 * Parameters:
 *	Ins:
 *		mp -- mount structure
 *		dino -- pointer to on-disk inode structure
 *		agno/ino -- inode numbers
 *		free -- whether the map thinks the inode is free (1 == free)
 *		ino_discovery -- whether we should examine directory
 *				contents to discover new inodes
 *		check_dups -- whether we should check to see if the
 *				inode references duplicate blocks
 *				if so, we compare the inode's claimed
 *				blocks against the contents of the
 *				duplicate extent list but we don't
 *				set the bitmap.  If not, we set the
 *				bitmap and try and detect multiply
 *				claimed blocks using the bitmap.
 *	Outs:
 *		dirty -- whether we changed the inode (1 == yes)
 *		cleared -- whether we cleared the inode (1 == yes).  In
 *				no modify mode, if we would have cleared it
 *		used -- 1 if the inode is used, 0 if free.  In no modify
 *			mode, whether the inode should be used or free
 *		isa_dir -- 1 if the inode is a directory, 0 if not.  In
 *			no modify mode, if the inode would be a dir or not.
 *
 *	Return value -- 0 if the inode is good, 1 if it is/was corrupt
 */

int
process_dinode(xfs_mount_t *mp,
		xfs_dinode_t *dino,
		xfs_agnumber_t agno,
		xfs_agino_t ino,
		int was_free,
		int *dirty,
		int *cleared,
		int *used,
		int ino_discovery,
		int check_dups,
		int extra_attr_check,
		int *isa_dir,
		xfs_ino_t *parent)
{
	const int verify_mode = 0;
	const int uncertain = 0;

#ifdef XR_INODE_TRACE
	fprintf(stderr, "processing inode %d/%d\n", agno, ino);
#endif
	return(process_dinode_int(mp, dino, agno, ino, was_free, dirty,
				cleared, used, verify_mode, uncertain,
				ino_discovery, check_dups, extra_attr_check,
				isa_dir, parent));
}

/*
 * a more cursory check, check inode core, *DON'T* check forks
 * this basically just verifies whether the inode is an inode
 * and whether or not it has been totally trashed.  returns 0
 * if the inode passes the cursory sanity check, 1 otherwise.
 */
int
verify_dinode(xfs_mount_t *mp,
		xfs_dinode_t *dino,
		xfs_agnumber_t agno,
		xfs_agino_t ino)
{
	xfs_ino_t parent;
	int cleared = 0;
	int used = 0;
	int dirty = 0;
	int isa_dir = 0;
	const int verify_mode = 1;
	const int check_dups = 0;
	const int ino_discovery = 0;
	const int uncertain = 0;

	return(process_dinode_int(mp, dino, agno, ino, 0, &dirty,
				&cleared, &used, verify_mode,
				uncertain, ino_discovery, check_dups,
				0, &isa_dir, &parent));
}

/*
 * like above only for inode on the uncertain list.  it sets
 * the uncertain flag which makes process_dinode_int quieter.
 * returns 0 if the inode passes the cursory sanity check, 1 otherwise.
 */
int
verify_uncertain_dinode(xfs_mount_t *mp,
		xfs_dinode_t *dino,
		xfs_agnumber_t agno,
		xfs_agino_t ino)
{
	xfs_ino_t parent;
	int cleared = 0;
	int used = 0;
	int dirty = 0;
	int isa_dir = 0;
	const int verify_mode = 1;
	const int check_dups = 0;
	const int ino_discovery = 0;
	const int uncertain = 1;

	return(process_dinode_int(mp, dino, agno, ino, 0, &dirty,
				&cleared, &used, verify_mode,
				uncertain, ino_discovery, check_dups,
				0, &isa_dir, &parent));
}
