/*
 * Copyright (c) 2000-2002,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 <math.h>
#include <sys/time.h>
#include "bmap.h"
#include "check.h"
#include "command.h"
#include "io.h"
#include "type.h"
#include "fprint.h"
#include "faddr.h"
#include "field.h"
#include "sb.h"
#include "output.h"
#include "init.h"
#include "malloc.h"
#include "dir2.h"

typedef enum {
	IS_USER_QUOTA, IS_PROJECT_QUOTA, IS_GROUP_QUOTA,
} qtype_t;

typedef enum {
	DBM_UNKNOWN,	DBM_AGF,	DBM_AGFL,	DBM_AGI,
	DBM_ATTR,	DBM_BTBMAPA,	DBM_BTBMAPD,	DBM_BTBNO,
	DBM_BTCNT,	DBM_BTINO,	DBM_DATA,	DBM_DIR,
	DBM_FREE1,	DBM_FREE2,	DBM_FREELIST,	DBM_INODE,
	DBM_LOG,	DBM_MISSING,	DBM_QUOTA,	DBM_RTBITMAP,
	DBM_RTDATA,	DBM_RTFREE,	DBM_RTSUM,	DBM_SB,
	DBM_SYMLINK,	DBM_BTFINO,
	DBM_NDBM
} dbm_t;

typedef struct inodata {
	struct inodata	*next;
	nlink_t		link_set;
	nlink_t		link_add;
	char		isdir;
	char		security;
	char		ilist;
	xfs_ino_t	ino;
	struct inodata	*parent;
	char		*name;
} inodata_t;
#define	MIN_INODATA_HASH_SIZE	256
#define	MAX_INODATA_HASH_SIZE	65536
#define	INODATA_AVG_HASH_LENGTH	8

typedef struct qinfo {
	xfs_qcnt_t	bc;
	xfs_qcnt_t	ic;
	xfs_qcnt_t	rc;
} qinfo_t;

#define	QDATA_HASH_SIZE	256
typedef	struct qdata {
	struct qdata	*next;
	xfs_dqid_t	id;
	qinfo_t		count;
	qinfo_t		dq;
} qdata_t;

typedef struct blkent {
	xfs_fileoff_t	startoff;
	int		nblks;
	xfs_fsblock_t	blks[1];
} blkent_t;
#define	BLKENT_SIZE(n)	\
	(offsetof(blkent_t, blks) + (sizeof(xfs_fsblock_t) * (n)))

typedef	struct blkmap {
	int		naents;
	int		nents;
	blkent_t	*ents[1];
} blkmap_t;
#define	BLKMAP_SIZE(n)	\
	(offsetof(blkmap_t, ents) + (sizeof(blkent_t *) * (n)))

typedef struct freetab {
	int			naents;
	int			nents;
	xfs_dir2_data_off_t	ents[1];
} freetab_t;
#define	FREETAB_SIZE(n)	\
	(offsetof(freetab_t, ents) + (sizeof(xfs_dir2_data_off_t) * (n)))

typedef struct dirhash {
	struct dirhash		*next;
	__u32			hashval;
	__u32			address;
	int			seen;
} dirhash_t;
#define	DIR_HASH_SIZE	1024
#define	DIR_HASH_FUNC(h,a)	(((h) ^ (a)) % DIR_HASH_SIZE)

static xfs_extlen_t	agffreeblks;
static xfs_extlen_t	agflongest;
static __uint64_t	agf_aggr_freeblks;	/* aggregate count over all */
static __uint32_t	agfbtreeblks;
static int		lazycount;
static xfs_agino_t	agicount;
static xfs_agino_t	agifreecount;
static xfs_fsblock_t	*blist;
static int		blist_size;
static char		**dbmap;	/* really dbm_t:8 */
static dirhash_t	**dirhash;
static int		error;
static __uint64_t	fdblocks;
static __uint64_t	frextents;
static __uint64_t	icount;
static __uint64_t	ifree;
static inodata_t	***inodata;
static int		inodata_hash_size;
static inodata_t	***inomap;
static int		nflag;
static int		pflag;
static int		tflag;
static qdata_t		**qpdata;
static int		qpdo;
static qdata_t		**qudata;
static int		qudo;
static qdata_t		**qgdata;
static int		qgdo;
static unsigned		sbversion;
static int		sbver_err;
static int		serious_error;
static int		sflag;
static xfs_suminfo_t	*sumcompute;
static xfs_suminfo_t	*sumfile;
static const char	*typename[] = {
	"unknown",
	"agf",
	"agfl",
	"agi",
	"attr",
	"btbmapa",
	"btbmapd",
	"btbno",
	"btcnt",
	"btino",
	"data",
	"dir",
	"free1",
	"free2",
	"freelist",
	"inode",
	"log",
	"missing",
	"quota",
	"rtbitmap",
	"rtdata",
	"rtfree",
	"rtsum",
	"sb",
	"symlink",
	"btfino",
	NULL
};
static int		verbose;

#define	CHECK_BLIST(b)	(blist_size && check_blist(b))
#define	CHECK_BLISTA(a,b)	\
	(blist_size && check_blist(XFS_AGB_TO_FSB(mp, a, b)))

typedef void	(*scan_lbtree_f_t)(struct xfs_btree_block *block,
				   int			level,
				   dbm_t		type,
				   xfs_fsblock_t	bno,
				   inodata_t		*id,
				   xfs_rfsblock_t	*totd,
				   xfs_rfsblock_t	*toti,
				   xfs_extnum_t		*nex,
				   blkmap_t		**blkmapp,
				   int			isroot,
				   typnm_t		btype);

typedef void	(*scan_sbtree_f_t)(struct xfs_btree_block *block,
				   int			level,
				   xfs_agf_t		*agf,
				   xfs_agblock_t	bno,
				   int			isroot);

static void		add_blist(xfs_fsblock_t	bno);
static void		add_ilist(xfs_ino_t ino);
static void		addlink_inode(inodata_t *id);
static void		addname_inode(inodata_t *id, char *name, int namelen);
static void		addparent_inode(inodata_t *id, xfs_ino_t parent);
static void		blkent_append(blkent_t **entp, xfs_fsblock_t b,
				      xfs_extlen_t c);
static blkent_t		*blkent_new(xfs_fileoff_t o, xfs_fsblock_t b,
				    xfs_extlen_t c);
static void		blkent_prepend(blkent_t **entp, xfs_fsblock_t b,
				       xfs_extlen_t c);
static blkmap_t		*blkmap_alloc(xfs_extnum_t);
static void		blkmap_free(blkmap_t *blkmap);
static xfs_fsblock_t	blkmap_get(blkmap_t *blkmap, xfs_fileoff_t o);
static int		blkmap_getn(blkmap_t *blkmap, xfs_fileoff_t o, int nb,
				    bmap_ext_t **bmpp);
static void		blkmap_grow(blkmap_t **blkmapp, blkent_t **entp,
				    blkent_t *newent);
static xfs_fileoff_t	blkmap_next_off(blkmap_t *blkmap, xfs_fileoff_t o,
					int *t);
static void		blkmap_set_blk(blkmap_t **blkmapp, xfs_fileoff_t o,
				       xfs_fsblock_t b);
static void		blkmap_set_ext(blkmap_t **blkmapp, xfs_fileoff_t o,
				       xfs_fsblock_t b, xfs_extlen_t c);
static void		blkmap_shrink(blkmap_t *blkmap, blkent_t **entp);
static int		blockfree_f(int argc, char **argv);
static int		blockget_f(int argc, char **argv);
static int		blocktrash_f(int argc, char **argv);
static int		blockuse_f(int argc, char **argv);
static int		check_blist(xfs_fsblock_t bno);
static void		check_dbmap(xfs_agnumber_t agno, xfs_agblock_t agbno,
				    xfs_extlen_t len, dbm_t type);
static int		check_inomap(xfs_agnumber_t agno, xfs_agblock_t agbno,
				     xfs_extlen_t len, xfs_ino_t c_ino);
static void		check_linkcounts(xfs_agnumber_t agno);
static int		check_range(xfs_agnumber_t agno, xfs_agblock_t agbno,
				    xfs_extlen_t len);
static void		check_rdbmap(xfs_rfsblock_t bno, xfs_extlen_t len,
				     dbm_t type);
static int		check_rinomap(xfs_rfsblock_t bno, xfs_extlen_t len,
				      xfs_ino_t c_ino);
static void		check_rootdir(void);
static int		check_rrange(xfs_rfsblock_t bno, xfs_extlen_t len);
static void		check_set_dbmap(xfs_agnumber_t agno,
					xfs_agblock_t agbno, xfs_extlen_t len,
					dbm_t type1, dbm_t type2,
					xfs_agnumber_t c_agno,
					xfs_agblock_t c_agbno);
static void		check_set_rdbmap(xfs_rfsblock_t bno, xfs_extlen_t len,
					 dbm_t type1, dbm_t type2);
static void		check_summary(void);
static void		checknot_dbmap(xfs_agnumber_t agno, xfs_agblock_t agbno,
				       xfs_extlen_t len, int typemask);
static void		checknot_rdbmap(xfs_rfsblock_t bno, xfs_extlen_t len,
					int typemask);
static void		dir_hash_add(xfs_dahash_t hash,
				     xfs_dir2_dataptr_t addr);
static void		dir_hash_check(inodata_t *id, int v);
static void		dir_hash_done(void);
static void		dir_hash_init(void);
static int		dir_hash_see(xfs_dahash_t hash,
				     xfs_dir2_dataptr_t addr);
static inodata_t	*find_inode(xfs_ino_t ino, int add);
static void		free_inodata(xfs_agnumber_t agno);
static int		init(int argc, char **argv);
static char		*inode_name(xfs_ino_t ino, inodata_t **ipp);
static int		ncheck_f(int argc, char **argv);
static char		*prepend_path(char *oldpath, char *parent);
static xfs_ino_t	process_block_dir_v2(blkmap_t *blkmap, int *dot,
					     int *dotdot, inodata_t *id);
static void		process_bmbt_reclist(xfs_bmbt_rec_t *rp, int numrecs,
					     dbm_t type, inodata_t *id,
					     xfs_rfsblock_t *tot,
					     blkmap_t **blkmapp);
static void		process_btinode(inodata_t *id, xfs_dinode_t *dip,
					dbm_t type, xfs_rfsblock_t *totd,
					xfs_rfsblock_t *toti, xfs_extnum_t *nex,
					blkmap_t **blkmapp, int whichfork);
static xfs_ino_t	process_data_dir_v2(int *dot, int *dotdot,
					    inodata_t *id, int v,
					    xfs_dablk_t dabno,
					    freetab_t **freetabp);
static xfs_dir2_data_free_t *process_data_dir_v2_freefind(
					struct xfs_dir2_data_hdr *data,
					struct xfs_dir2_data_unused *dup);
static void		process_dir(xfs_dinode_t *dip, blkmap_t *blkmap,
				    inodata_t *id);
static int		process_dir_v2(xfs_dinode_t *dip, blkmap_t *blkmap,
				       int *dot, int *dotdot, inodata_t *id,
				       xfs_ino_t *parent);
static void		process_exinode(inodata_t *id, xfs_dinode_t *dip,
					dbm_t type, xfs_rfsblock_t *totd,
					xfs_rfsblock_t *toti, xfs_extnum_t *nex,
					blkmap_t **blkmapp, int whichfork);
static void		process_inode(xfs_agf_t *agf, xfs_agino_t agino,
				      xfs_dinode_t *dip, int isfree);
static void		process_lclinode(inodata_t *id, xfs_dinode_t *dip,
					 dbm_t type, xfs_rfsblock_t *totd,
					 xfs_rfsblock_t *toti, xfs_extnum_t *nex,
					 blkmap_t **blkmapp, int whichfork);
static xfs_ino_t	process_leaf_node_dir_v2(blkmap_t *blkmap, int *dot,
						 int *dotdot, inodata_t *id,
						 xfs_fsize_t dirsize);
static void		process_leaf_node_dir_v2_free(inodata_t *id, int v,
						      xfs_dablk_t dbno,
						      freetab_t *freetab);
static void		process_leaf_node_dir_v2_int(inodata_t *id, int v,
						     xfs_dablk_t dbno,
						     freetab_t *freetab);
static void		process_quota(qtype_t qtype, inodata_t *id,
				      blkmap_t *blkmap);
static void		process_rtbitmap(blkmap_t *blkmap);
static void		process_rtsummary(blkmap_t *blkmap);
static xfs_ino_t	process_sf_dir_v2(xfs_dinode_t *dip, int *dot,
					  int *dotdot, inodata_t *id);
static void		quota_add(xfs_dqid_t *p, xfs_dqid_t *g, xfs_dqid_t *u,
				  int dq, xfs_qcnt_t bc, xfs_qcnt_t ic,
				  xfs_qcnt_t rc);
static void		quota_add1(qdata_t **qt, xfs_dqid_t id, int dq,
				   xfs_qcnt_t bc, xfs_qcnt_t ic,
				   xfs_qcnt_t rc);
static void		quota_check(char *s, qdata_t **qt);
static void		quota_init(void);
static void		scan_ag(xfs_agnumber_t agno);
static void		scan_freelist(xfs_agf_t *agf);
static void		scan_lbtree(xfs_fsblock_t root, int nlevels,
				    scan_lbtree_f_t func, dbm_t type,
				    inodata_t *id, xfs_rfsblock_t *totd,
				    xfs_rfsblock_t *toti, xfs_extnum_t *nex,
				    blkmap_t **blkmapp, int isroot,
				    typnm_t btype);
static void		scan_sbtree(xfs_agf_t *agf, xfs_agblock_t root,
				    int nlevels, int isroot,
				    scan_sbtree_f_t func, typnm_t btype);
static void		scanfunc_bmap(struct xfs_btree_block *block,
				      int level, dbm_t type, xfs_fsblock_t bno,
				      inodata_t *id, xfs_rfsblock_t *totd,
				      xfs_rfsblock_t *toti, xfs_extnum_t *nex,
				      blkmap_t **blkmapp, int isroot,
				      typnm_t btype);
static void		scanfunc_bno(struct xfs_btree_block *block, int level,
				     xfs_agf_t *agf, xfs_agblock_t bno,
				     int isroot);
static void		scanfunc_cnt(struct xfs_btree_block *block, int level,
				     xfs_agf_t *agf, xfs_agblock_t bno,
				     int isroot);
static void		scanfunc_ino(struct xfs_btree_block *block, int level,
				     xfs_agf_t *agf, xfs_agblock_t bno,
				     int isroot);
static void		scanfunc_fino(struct xfs_btree_block *block, int level,
				     struct xfs_agf *agf, xfs_agblock_t bno,
				     int isroot);
static void		set_dbmap(xfs_agnumber_t agno, xfs_agblock_t agbno,
				  xfs_extlen_t len, dbm_t type,
				  xfs_agnumber_t c_agno, xfs_agblock_t c_agbno);
static void		set_inomap(xfs_agnumber_t agno, xfs_agblock_t agbno,
				   xfs_extlen_t len, inodata_t *id);
static void		set_rdbmap(xfs_rfsblock_t bno, xfs_extlen_t len,
				   dbm_t type);
static void		set_rinomap(xfs_rfsblock_t bno, xfs_extlen_t len,
				    inodata_t *id);
static void		setlink_inode(inodata_t *id, nlink_t nlink, int isdir,
				       int security);

static const cmdinfo_t	blockfree_cmd =
	{ "blockfree", NULL, blockfree_f, 0, 0, 0,
	  NULL, N_("free block usage information"), NULL };
static const cmdinfo_t	blockget_cmd =
	{ "blockget", "check", blockget_f, 0, -1, 0,
	  N_("[-s|-v] [-n] [-t] [-b bno]... [-i ino] ..."),
	  N_("get block usage and check consistency"), NULL };
static const cmdinfo_t	blocktrash_cmd =
	{ "blocktrash", NULL, blocktrash_f, 0, -1, 0,
	  N_("[-n count] [-x minlen] [-y maxlen] [-s seed] [-0123] [-t type] ..."),
	  N_("trash randomly selected block(s)"), NULL };
static const cmdinfo_t	blockuse_cmd =
	{ "blockuse", NULL, blockuse_f, 0, 3, 0,
	  N_("[-n] [-c blockcount]"),
	  N_("print usage for current block(s)"), NULL };
static const cmdinfo_t	ncheck_cmd =
	{ "ncheck", NULL, ncheck_f, 0, -1, 0,
	  N_("[-s] [-i ino] ..."),
	  N_("print inode-name pairs"), NULL };


static void
add_blist(
	xfs_fsblock_t	bno)
{
	blist_size++;
	blist = xrealloc(blist, blist_size * sizeof(bno));
	blist[blist_size - 1] = bno;
}

static void
add_ilist(
	xfs_ino_t	ino)
{
	inodata_t	*id;

	id = find_inode(ino, 1);
	if (id == NULL) {
		dbprintf(_("-i %lld bad inode number\n"), ino);
		return;
	}
	id->ilist = 1;
}

static void
addlink_inode(
	inodata_t	*id)
{
	id->link_add++;
	if (verbose || id->ilist)
		dbprintf(_("inode %lld add link, now %u\n"), id->ino,
			id->link_add);
}

static void
addname_inode(
	inodata_t	*id,
	char		*name,
	int		namelen)
{
	if (!nflag || id->name)
		return;
	id->name = xmalloc(namelen + 1);
	memcpy(id->name, name, namelen);
	id->name[namelen] = '\0';
}

static void
addparent_inode(
	inodata_t	*id,
	xfs_ino_t	parent)
{
	inodata_t	*pid;

	pid = find_inode(parent, 1);
	id->parent = pid;
	if (verbose || id->ilist || (pid && pid->ilist))
		dbprintf(_("inode %lld parent %lld\n"), id->ino, parent);
}

static void
blkent_append(
	blkent_t	**entp,
	xfs_fsblock_t	b,
	xfs_extlen_t	c)
{
	blkent_t	*ent;
	int		i;

	ent = *entp;
	*entp = ent = xrealloc(ent, BLKENT_SIZE(c + ent->nblks));
	for (i = 0; i < c; i++)
		ent->blks[ent->nblks + i] = b + i;
	ent->nblks += c;
}

static blkent_t *
blkent_new(
	xfs_fileoff_t	o,
	xfs_fsblock_t	b,
	xfs_extlen_t	c)
{
	blkent_t	*ent;
	int		i;

	ent = xmalloc(BLKENT_SIZE(c));
	ent->nblks = c;
	ent->startoff = o;
	for (i = 0; i < c; i++)
		ent->blks[i] = b + i;
	return ent;
}

static void
blkent_prepend(
	blkent_t	**entp,
	xfs_fsblock_t	b,
	xfs_extlen_t	c)
{
	int		i;
	blkent_t	*newent;
	blkent_t	*oldent;

	oldent = *entp;
	newent = xmalloc(BLKENT_SIZE(oldent->nblks + c));
	newent->nblks = oldent->nblks + c;
	newent->startoff = oldent->startoff - c;
	for (i = 0; i < c; i++)
		newent->blks[i] = b + c;
	for (; i < oldent->nblks + c; i++)
		newent->blks[i] = oldent->blks[i - c];
	xfree(oldent);
	*entp = newent;
}

static blkmap_t *
blkmap_alloc(
	xfs_extnum_t	nex)
{
	blkmap_t	*blkmap;

	if (nex < 1)
		nex = 1;
	blkmap = xmalloc(BLKMAP_SIZE(nex));
	blkmap->naents = nex;
	blkmap->nents = 0;
	return blkmap;
}

static void
blkmap_free(
	blkmap_t	*blkmap)
{
	blkent_t	**entp;
	xfs_extnum_t	i;

	for (i = 0, entp = blkmap->ents; i < blkmap->nents; i++, entp++)
		xfree(*entp);
	xfree(blkmap);
}

static xfs_fsblock_t
blkmap_get(
	blkmap_t	*blkmap,
	xfs_fileoff_t	o)
{
	blkent_t	*ent;
	blkent_t	**entp;
	int		i;

	for (i = 0, entp = blkmap->ents; i < blkmap->nents; i++, entp++) {
		ent = *entp;
		if (o >= ent->startoff && o < ent->startoff + ent->nblks)
			return ent->blks[o - ent->startoff];
	}
	return NULLFSBLOCK;
}

static int
blkmap_getn(
	blkmap_t	*blkmap,
	xfs_fileoff_t	o,
	int		nb,
	bmap_ext_t	**bmpp)
{
	bmap_ext_t	*bmp;
	blkent_t	*ent;
	xfs_fileoff_t	ento;
	blkent_t	**entp;
	int		i;
	int		nex;

	for (i = nex = 0, bmp = NULL, entp = blkmap->ents;
	     i < blkmap->nents;
	     i++, entp++) {
		ent = *entp;
		if (ent->startoff >= o + nb)
			break;
		if (ent->startoff + ent->nblks <= o)
			continue;
		for (ento = ent->startoff;
		     ento < ent->startoff + ent->nblks && ento < o + nb;
		     ento++) {
			if (ento < o)
				continue;
			if (bmp &&
			    bmp[nex - 1].startoff + bmp[nex - 1].blockcount ==
				    ento &&
			    bmp[nex - 1].startblock + bmp[nex - 1].blockcount ==
				    ent->blks[ento - ent->startoff])
				bmp[nex - 1].blockcount++;
			else {
				bmp = realloc(bmp, ++nex * sizeof(*bmp));
				bmp[nex - 1].startoff = ento;
				bmp[nex - 1].startblock =
					ent->blks[ento - ent->startoff];
				bmp[nex - 1].blockcount = 1;
				bmp[nex - 1].flag = 0;
			}
		}
	}
	*bmpp = bmp;
	return nex;
}

static void
blkmap_grow(
	blkmap_t	**blkmapp,
	blkent_t	**entp,
	blkent_t	*newent)
{
	blkmap_t	*blkmap;
	int		i;
	int		idx;

	blkmap = *blkmapp;
	idx = (int)(entp - blkmap->ents);
	if (blkmap->naents == blkmap->nents) {
		blkmap = xrealloc(blkmap, BLKMAP_SIZE(blkmap->nents + 1));
		*blkmapp = blkmap;
		blkmap->naents++;
	}
	for (i = blkmap->nents; i > idx; i--)
		blkmap->ents[i] = blkmap->ents[i - 1];
	blkmap->ents[idx] = newent;
	blkmap->nents++;
}

static xfs_fileoff_t
blkmap_last_off(
	blkmap_t	*blkmap)
{
	blkent_t	*ent;

	if (!blkmap->nents)
		return NULLFILEOFF;
	ent = blkmap->ents[blkmap->nents - 1];
	return ent->startoff + ent->nblks;
}

static xfs_fileoff_t
blkmap_next_off(
	blkmap_t	*blkmap,
	xfs_fileoff_t	o,
	int		*t)
{
	blkent_t	*ent;
	blkent_t	**entp;

	if (!blkmap->nents)
		return NULLFILEOFF;
	if (o == NULLFILEOFF) {
		*t = 0;
		ent = blkmap->ents[0];
		return ent->startoff;
	}
	entp = &blkmap->ents[*t];
	ent = *entp;
	if (o < ent->startoff + ent->nblks - 1)
		return o + 1;
	entp++;
	if (entp >= &blkmap->ents[blkmap->nents])
		return NULLFILEOFF;
	(*t)++;
	ent = *entp;
	return ent->startoff;
}

static void
blkmap_set_blk(
	blkmap_t	**blkmapp,
	xfs_fileoff_t	o,
	xfs_fsblock_t	b)
{
	blkmap_t	*blkmap;
	blkent_t	*ent;
	blkent_t	**entp;
	blkent_t	*nextent;

	blkmap = *blkmapp;
	for (entp = blkmap->ents; entp < &blkmap->ents[blkmap->nents]; entp++) {
		ent = *entp;
		if (o < ent->startoff - 1) {
			ent = blkent_new(o, b, 1);
			blkmap_grow(blkmapp, entp, ent);
			return;
		}
		if (o == ent->startoff - 1) {
			blkent_prepend(entp, b, 1);
			return;
		}
		if (o >= ent->startoff && o < ent->startoff + ent->nblks) {
			ent->blks[o - ent->startoff] = b;
			return;
		}
		if (o > ent->startoff + ent->nblks)
			continue;
		blkent_append(entp, b, 1);
		if (entp == &blkmap->ents[blkmap->nents - 1])
			return;
		ent = *entp;
		nextent = entp[1];
		if (ent->startoff + ent->nblks < nextent->startoff)
			return;
		blkent_append(entp, nextent->blks[0], nextent->nblks);
		blkmap_shrink(blkmap, &entp[1]);
		return;
	}
	ent = blkent_new(o, b, 1);
	blkmap_grow(blkmapp, entp, ent);
}

static void
blkmap_set_ext(
	blkmap_t	**blkmapp,
	xfs_fileoff_t	o,
	xfs_fsblock_t	b,
	xfs_extlen_t	c)
{
	blkmap_t	*blkmap;
	blkent_t	*ent;
	blkent_t	**entp;
	xfs_extnum_t	i;

	blkmap = *blkmapp;
	if (!blkmap->nents) {
		blkmap->ents[0] = blkent_new(o, b, c);
		blkmap->nents = 1;
		return;
	}
	entp = &blkmap->ents[blkmap->nents - 1];
	ent = *entp;
	if (ent->startoff + ent->nblks == o) {
		blkent_append(entp, b, c);
		return;
	}
	if (ent->startoff + ent->nblks < o) {
		ent = blkent_new(o, b, c);
		blkmap_grow(blkmapp, &blkmap->ents[blkmap->nents], ent);
		return;
	}
	for (i = 0; i < c; i++)
		blkmap_set_blk(blkmapp, o + i, b + i);
}

static void
blkmap_shrink(
	blkmap_t	*blkmap,
	blkent_t	**entp)
{
	int		i;
	int		idx;

	xfree(*entp);
	idx = (int)(entp - blkmap->ents);
	for (i = idx + 1; i < blkmap->nents; i++)
		blkmap->ents[i] = blkmap->ents[i - 1];
	blkmap->nents--;
}

/* ARGSUSED */
static int
blockfree_f(
	int		argc,
	char		**argv)
{
	xfs_agnumber_t	c;
	int		rt;

	if (!dbmap) {
		dbprintf(_("block usage information not allocated\n"));
		return 0;
	}
	rt = mp->m_sb.sb_rextents != 0;
	for (c = 0; c < mp->m_sb.sb_agcount; c++) {
		xfree(dbmap[c]);
		xfree(inomap[c]);
		free_inodata(c);
	}
	if (rt) {
		xfree(dbmap[c]);
		xfree(inomap[c]);
		xfree(sumcompute);
		xfree(sumfile);
		sumcompute = sumfile = NULL;
	}
	xfree(dbmap);
	xfree(inomap);
	xfree(inodata);
	dbmap = NULL;
	inomap = NULL;
	inodata = NULL;
	return 0;
}

/*
 * Check consistency of xfs filesystem contents.
 */
static int
blockget_f(
	int		argc,
	char		**argv)
{
	xfs_agnumber_t	agno;
	int		oldprefix;
	int		sbyell;

	if (dbmap) {
		dbprintf(_("already have block usage information\n"));
		return 0;
	}

	if (!init(argc, argv)) {
		if (serious_error)
			exitcode = 3;
		else
			exitcode = 1;
		return 0;
	}
	oldprefix = dbprefix;
	dbprefix |= pflag;
	for (agno = 0, sbyell = 0; agno < mp->m_sb.sb_agcount; agno++) {
		scan_ag(agno);
		if (sbver_err > 4 && !sbyell && sbver_err >= agno) {
			sbyell = 1;
			dbprintf(_("WARNING: this may be a newer XFS "
				 "filesystem.\n"));
		}
	}
	if (blist_size) {
		xfree(blist);
		blist = NULL;
		blist_size = 0;
	}
	if (serious_error) {
		exitcode = 2;
		dbprefix = oldprefix;
		return 0;
	}
	check_rootdir();
	/*
	 * Check that there are no blocks either
	 * a) unaccounted for or
	 * b) bno-free but not cnt-free
	 */
	if (!tflag) {	/* are we in test mode, faking out freespace? */
		for (agno = 0; agno < mp->m_sb.sb_agcount; agno++)
			checknot_dbmap(agno, 0, mp->m_sb.sb_agblocks,
				(1 << DBM_UNKNOWN) | (1 << DBM_FREE1));
	}
	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++)
		check_linkcounts(agno);
	if (mp->m_sb.sb_rblocks) {
		checknot_rdbmap(0,
			(xfs_extlen_t)(mp->m_sb.sb_rextents *
				       mp->m_sb.sb_rextsize),
			1 << DBM_UNKNOWN);
		check_summary();
	}
	if (mp->m_sb.sb_icount != icount) {
		if (!sflag)
			dbprintf(_("sb_icount %lld, counted %lld\n"),
				mp->m_sb.sb_icount, icount);
		error++;
	}
	if (mp->m_sb.sb_ifree != ifree) {
		if (!sflag)
			dbprintf(_("sb_ifree %lld, counted %lld\n"),
				mp->m_sb.sb_ifree, ifree);
		error++;
	}
	if (mp->m_sb.sb_fdblocks != fdblocks) {
		if (!sflag)
			dbprintf(_("sb_fdblocks %lld, counted %lld\n"),
				mp->m_sb.sb_fdblocks, fdblocks);
		error++;
	}
	if (lazycount && mp->m_sb.sb_fdblocks != agf_aggr_freeblks) {
		if (!sflag)
			dbprintf(_("sb_fdblocks %lld, aggregate AGF count %lld\n"),
				mp->m_sb.sb_fdblocks, agf_aggr_freeblks);
		error++;
	}
	if (mp->m_sb.sb_frextents != frextents) {
		if (!sflag)
			dbprintf(_("sb_frextents %lld, counted %lld\n"),
				mp->m_sb.sb_frextents, frextents);
		error++;
	}
	if (mp->m_sb.sb_bad_features2 != 0 &&
			mp->m_sb.sb_bad_features2 != mp->m_sb.sb_features2) {
		if (!sflag)
			dbprintf(_("sb_features2 (0x%x) not same as "
				"sb_bad_features2 (0x%x)\n"),
				mp->m_sb.sb_features2,
				mp->m_sb.sb_bad_features2);
		error++;
	}
	if ((sbversion & XFS_SB_VERSION_ATTRBIT) &&
					!xfs_sb_version_hasattr(&mp->m_sb)) {
		if (!sflag)
			dbprintf(_("sb versionnum missing attr bit %x\n"),
				XFS_SB_VERSION_ATTRBIT);
		error++;
	}
	if ((sbversion & XFS_SB_VERSION_QUOTABIT) &&
					!xfs_sb_version_hasquota(&mp->m_sb)) {
		if (!sflag)
			dbprintf(_("sb versionnum missing quota bit %x\n"),
				XFS_SB_VERSION_QUOTABIT);
		error++;
	}
	if (!(sbversion & XFS_SB_VERSION_ALIGNBIT) &&
					xfs_sb_version_hasalign(&mp->m_sb)) {
		if (!sflag)
			dbprintf(_("sb versionnum extra align bit %x\n"),
				XFS_SB_VERSION_ALIGNBIT);
		error++;
	}
	if (qudo)
		quota_check("user", qudata);
	if (qpdo)
		quota_check("project", qpdata);
	if (qgdo)
		quota_check("group", qgdata);
	if (sbver_err > mp->m_sb.sb_agcount / 2)
		dbprintf(_("WARNING: this may be a newer XFS filesystem.\n"));
	if (error)
		exitcode = 3;
	dbprefix = oldprefix;
	return 0;
}

typedef struct ltab {
	int	min;
	int	max;
} ltab_t;

static void
blocktrash_b(
	int		bit_offset,
	dbm_t		type,
	ltab_t		*ltabp,
	int		mode)
{
	int		bit;
	int		bitno;
	char		*buf;
	int		byte;
	int		len;
	int		mask;
	int		newbit;
	const struct xfs_buf_ops *stashed_ops;
	static char	*modestr[] = {
		N_("zeroed"), N_("set"), N_("flipped"), N_("randomized")
	};
	xfs_agnumber_t	agno;
	xfs_agblock_t	agbno;

	agno = XFS_FSB_TO_AGNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb));
	agbno = XFS_FSB_TO_AGBNO(mp, XFS_DADDR_TO_FSB(mp, iocur_top->bb));
	if (iocur_top->len == 0) {
		dbprintf(_("zero-length block %u/%u buffer to trash??\n"),
				agno, agbno);
		return;
	}
	len = (int)((random() % (ltabp->max - ltabp->min + 1)) + ltabp->min);
	/*
	 * bit_offset >= 0: start fuzzing at this exact bit_offset.
	 * bit_offset < 0: pick an offset at least as high at -(bit_offset + 1).
	 */
	if (bit_offset < 0) {
		bit_offset = -(bit_offset + 1);
		bit_offset += (int)(random() % (int)((iocur_top->len - bit_offset) * NBBY));
	}
	if (bit_offset + len >= iocur_top->len * NBBY)
		len = (iocur_top->len * NBBY) - bit_offset;
	newbit = 0;
	stashed_ops = iocur_top->bp->b_ops;
	iocur_top->bp->b_ops = NULL;
	if ((buf = iocur_top->data) == NULL) {
		dbprintf(_("can't read block %u/%u for trashing\n"), agno, agbno);
		return;
	}
	for (bitno = 0; bitno < len; bitno++) {
		bit = (bit_offset + bitno) % (mp->m_sb.sb_blocksize * NBBY);
		byte = bit / NBBY;
		bit %= NBBY;
		mask = 1 << bit;
		switch (mode) {
		case 0:
			newbit = 0;
			break;
		case 1:
			newbit = 1;
			break;
		case 2:
			newbit = (buf[byte] & mask) == 0;
			break;
		case 3:
			newbit = (int)random() & 1;
			break;
		}
		if (newbit)
			buf[byte] |= mask;
		else
			buf[byte] &= ~mask;
	}
	write_cur();
	iocur_top->bp->b_ops = stashed_ops;
	printf(_("blocktrash: %u/%u %s block %d bit%s starting %d:%d %s\n"),
		agno, agbno, typename[type], len, len == 1 ? "" : "s",
		bit_offset / NBBY, bit_offset % NBBY, modestr[mode]);
}

int
blocktrash_f(
	int		argc,
	char		**argv)
{
	xfs_agblock_t	agbno;
	xfs_agnumber_t	agno;
	xfs_rfsblock_t	bi;
	xfs_rfsblock_t	blocks;
	int		c;
	int		count;
	int		done;
	int		goodmask;
	int		i;
	ltab_t		*lentab;
	int		lentablen;
	int		max;
	int		min;
	int		mode;
	struct timeval	now;
	char		*p;
	xfs_rfsblock_t	randb;
	uint		seed;
	int		sopt;
	int		tmask;
	bool		this_block = false;
	int		bit_offset = -1;

	optind = 0;
	count = 1;
	min = 1;
	max = 128 * NBBY;
	mode = 2;
	gettimeofday(&now, NULL);
	seed = (unsigned int)(now.tv_sec ^ now.tv_usec);
	sopt = 0;
	tmask = 0;
	goodmask = (1 << DBM_AGF) |
		   (1 << DBM_AGFL) |
		   (1 << DBM_AGI) |
		   (1 << DBM_ATTR) |
		   (1 << DBM_BTBMAPA) |
		   (1 << DBM_BTBMAPD) |
		   (1 << DBM_BTBNO) |
		   (1 << DBM_BTCNT) |
		   (1 << DBM_BTINO) |
		   (1 << DBM_DIR) |
		   (1 << DBM_INODE) |
		   (1 << DBM_LOG) |
		   (1 << DBM_QUOTA) |
		   (1 << DBM_RTBITMAP) |
		   (1 << DBM_RTSUM) |
		   (1 << DBM_SYMLINK) |
		   (1 << DBM_BTFINO) |
		   (1 << DBM_SB);
	while ((c = getopt(argc, argv, "0123n:o:s:t:x:y:z")) != EOF) {
		switch (c) {
		case '0':
			mode = 0;
			break;
		case '1':
			mode = 1;
			break;
		case '2':
			mode = 2;
			break;
		case '3':
			mode = 3;
			break;
		case 'n':
			count = (int)strtol(optarg, &p, 0);
			if (*p != '\0' || count <= 0) {
				dbprintf(_("bad blocktrash count %s\n"), optarg);
				return 0;
			}
			break;
		case 'o': {
			int relative = 0;
			if (optarg[0] == '+') {
				optarg++;
				relative = 1;
			}
			bit_offset = (int)strtol(optarg, &p, 0);
			if (*p != '\0' || bit_offset < 0) {
				dbprintf(_("bad blocktrash offset %s\n"), optarg);
				return 0;
			}
			if (relative)
				bit_offset = -bit_offset - 1;
			break;
		}
		case 's':
			seed = (uint)strtoul(optarg, &p, 0);
			sopt = 1;
			break;
		case 't':
			for (i = 0; typename[i]; i++) {
				if (strcmp(typename[i], optarg) == 0)
					break;
			}
			if (!typename[i] || (((1 << i) & goodmask) == 0)) {
				dbprintf(_("bad blocktrash type %s\n"), optarg);
				return 0;
			}
			tmask |= 1 << i;
			break;
		case 'x':
			min = (int)strtol(optarg, &p, 0);
			if (*p != '\0' || min <= 0 ||
			    min > mp->m_sb.sb_blocksize * NBBY) {
				dbprintf(_("bad blocktrash min %s\n"), optarg);
				return 0;
			}
			break;
		case 'y':
			max = (int)strtol(optarg, &p, 0);
			if (*p != '\0' || max <= 0 ||
			    max > mp->m_sb.sb_blocksize * NBBY) {
				dbprintf(_("bad blocktrash max %s\n"), optarg);
				return 0;
			}
			break;
		case 'z':
			this_block = true;
			break;
		default:
			dbprintf(_("bad option for blocktrash command\n"));
			return 0;
		}
	}
	if (!this_block && !dbmap) {
		dbprintf(_("must run blockget first\n"));
		return 0;
	}
	if (this_block && iocur_sp == 0) {
		dbprintf(_("nothing on stack\n"));
		return 0;
	}
	if (min > max) {
		dbprintf(_("bad min/max for blocktrash command\n"));
		return 0;
	}
	if (tmask == 0)
		tmask = goodmask & ~((1 << DBM_LOG) | (1 << DBM_SB));
	lentab = xmalloc(sizeof(ltab_t));
	lentab->min = lentab->max = min;
	lentablen = 1;
	for (i = min + 1; i <= max; i++) {
		if ((i & (i - 1)) == 0) {
			lentab = xrealloc(lentab,
				sizeof(ltab_t) * (lentablen + 1));
			lentab[lentablen].min = lentab[lentablen].max = i;
			lentablen++;
		} else
			lentab[lentablen - 1].max = i;
	}
	if (!sopt)
		dbprintf(_("blocktrash: seed %u\n"), seed);
	srandom(seed);
	if (this_block) {
		blocktrash_b(bit_offset, DBM_UNKNOWN,
				&lentab[random() % lentablen], mode);
		goto out;
	}
	for (blocks = 0, agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
		for (agbno = 0, p = dbmap[agno];
		     agbno < mp->m_sb.sb_agblocks;
		     agbno++, p++) {
			if ((1 << *p) & tmask)
				blocks++;
		}
	}
	if (blocks == 0) {
		dbprintf(_("blocktrash: no matching blocks\n"));
		goto out;
	}
	for (i = 0; i < count; i++) {
		randb = (xfs_rfsblock_t)((((__int64_t)random() << 32) |
					 random()) % blocks);
		for (bi = 0, agno = 0, done = 0;
		     !done && agno < mp->m_sb.sb_agcount;
		     agno++) {
			for (agbno = 0, p = dbmap[agno];
			     agbno < mp->m_sb.sb_agblocks;
			     agbno++, p++) {
				if (!((1 << *p) & tmask))
					continue;
				if (bi++ < randb)
					continue;
				push_cur();
				set_cur(NULL,
					XFS_AGB_TO_DADDR(mp, agno, agbno),
					blkbb, DB_RING_IGN, NULL);
				blocktrash_b(bit_offset, (dbm_t)*p,
					&lentab[random() % lentablen], mode);
				pop_cur();
				done = 1;
				break;
			}
		}
	}
out:
	xfree(lentab);
	return 0;
}

int
blockuse_f(
	int		argc,
	char		**argv)
{
	xfs_agblock_t	agbno;
	xfs_agnumber_t	agno;
	int		c;
	int		count;
	xfs_agblock_t	end;
	xfs_fsblock_t	fsb;
	inodata_t	*i;
	char		*p;
	int		shownames;

	if (!dbmap) {
		dbprintf(_("must run blockget first\n"));
		return 0;
	}
	optind = 0;
	shownames = 0;
	fsb = XFS_DADDR_TO_FSB(mp, iocur_top->off >> BBSHIFT);
	agno = XFS_FSB_TO_AGNO(mp, fsb);
	end = agbno = XFS_FSB_TO_AGBNO(mp, fsb);
	while ((c = getopt(argc, argv, "c:n")) != EOF) {
		switch (c) {
		case 'c':
			count = (int)strtol(optarg, &p, 0);
			end = agbno + count - 1;
			if (*p != '\0' || count <= 0 ||
			    end >= mp->m_sb.sb_agblocks) {
				dbprintf(_("bad blockuse count %s\n"), optarg);
				return 0;
			}
			break;
		case 'n':
			if (!nflag) {
				dbprintf(_("must run blockget -n first\n"));
				return 0;
			}
			shownames = 1;
			break;
		default:
			dbprintf(_("bad option for blockuse command\n"));
			return 0;
		}
	}
	while (agbno <= end) {
		p = &dbmap[agno][agbno];
		i = inomap[agno][agbno];
		dbprintf(_("block %llu (%u/%u) type %s"),
			(xfs_fsblock_t)XFS_AGB_TO_FSB(mp, agno, agbno),
			agno, agbno, typename[(dbm_t)*p]);
		if (i) {
			dbprintf(_(" inode %lld"), i->ino);
			if (shownames && (p = inode_name(i->ino, NULL))) {
				dbprintf(" %s", p);
				xfree(p);
			}
		}
		dbprintf("\n");
		agbno++;
	}
	return 0;
}

static int
check_blist(
	xfs_fsblock_t	bno)
{
	int		i;

	for (i = 0; i < blist_size; i++) {
		if (blist[i] == bno)
			return 1;
	}
	return 0;
}

static void
check_dbmap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	dbm_t		type)
{
	xfs_extlen_t	i;
	char		*p;

	for (i = 0, p = &dbmap[agno][agbno]; i < len; i++, p++) {
		if ((dbm_t)*p != type) {
			if (!sflag || CHECK_BLISTA(agno, agbno + i))
				dbprintf(_("block %u/%u expected type %s got "
					 "%s\n"),
					agno, agbno + i, typename[type],
					typename[(dbm_t)*p]);
			error++;
		}
	}
}

void
check_init(void)
{
	add_command(&blockfree_cmd);
	add_command(&blockget_cmd);
	if (expert_mode)
		add_command(&blocktrash_cmd);
	add_command(&blockuse_cmd);
	add_command(&ncheck_cmd);
}

static int
check_inomap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	xfs_ino_t	c_ino)
{
	xfs_extlen_t	i;
	inodata_t	**idp;
	int		rval;

	if (!check_range(agno, agbno, len))  {
		dbprintf(_("blocks %u/%u..%u claimed by inode %lld\n"),
			agno, agbno, agbno + len - 1, c_ino);
		return 0;
	}
	for (i = 0, rval = 1, idp = &inomap[agno][agbno]; i < len; i++, idp++) {
		if (*idp) {
			if (!sflag || (*idp)->ilist ||
			    CHECK_BLISTA(agno, agbno + i))
				dbprintf(_("block %u/%u claimed by inode %lld, "
					 "previous inum %lld\n"),
					agno, agbno + i, c_ino, (*idp)->ino);
			error++;
			rval = 0;
		}
	}
	return rval;
}

static void
check_linkcounts(
	xfs_agnumber_t	agno)
{
	inodata_t	*ep;
	inodata_t	**ht;
	int		idx;
	char		*path;

	ht = inodata[agno];
	for (idx = 0; idx < inodata_hash_size; ht++, idx++) {
		ep = *ht;
		while (ep) {
			if (ep->link_set != ep->link_add || ep->link_set == 0) {
				path = inode_name(ep->ino, NULL);
				if (!path && ep->link_add)
					path = xstrdup("?");
				if (!sflag || ep->ilist) {
					if (ep->link_add)
						dbprintf(_("link count mismatch "
							 "for inode %lld (name "
							 "%s), nlink %d, "
							 "counted %d\n"),
							ep->ino, path,
							ep->link_set,
							ep->link_add);
					else if (ep->link_set)
						dbprintf(_("disconnected inode "
							 "%lld, nlink %d\n"),
							ep->ino, ep->link_set);
					else
						dbprintf(_("allocated inode %lld "
							 "has 0 link count\n"),
							ep->ino);
				}
				if (path)
					xfree(path);
				error++;
			} else if (verbose || ep->ilist) {
				path = inode_name(ep->ino, NULL);
				if (path) {
					dbprintf(_("inode %lld name %s\n"),
						ep->ino, path);
					xfree(path);
				}
			}
			ep = ep->next;
		}
	}

}

static int
check_range(
	xfs_agnumber_t  agno,
	xfs_agblock_t   agbno,
	xfs_extlen_t    len)
{
	xfs_extlen_t    i;
	xfs_agblock_t   low = 0;
	xfs_agblock_t   high = 0;
	int             valid_range = 0;
	int             cur, prev = 0;

	if (agno >= mp->m_sb.sb_agcount ||
	    agbno + len - 1 >= mp->m_sb.sb_agblocks) {
		for (i = 0; i < len; i++) {
			cur = !sflag || CHECK_BLISTA(agno, agbno + i) ? 1 : 0;
			if (cur == 1 && prev == 0) {
				low = high = agbno + i;
				valid_range = 1;
			} else if (cur == 0 && prev == 0) {
				/* Do nothing */
			} else if (cur == 0 && prev == 1) {
				if (low == high) {
					dbprintf(_("block %u/%u out of range\n"),
						agno, low);
				} else {
					dbprintf(_("blocks %u/%u..%u "
						"out of range\n"),
						agno, low, high);
				}
				valid_range = 0;
			} else if (cur == 1 && prev == 1) {
				high = agbno + i;
			}
			prev = cur;
		}
		if (valid_range) {
        		if (low == high) {
               			dbprintf(_("block %u/%u out of range\n"),
                       		agno, low);
			} else {
               			dbprintf(_("blocks %u/%u..%u "
               				"out of range\n"),
               				agno, low, high);
      		 	}
		}
		error++;
		return 0;
	}
	return 1;
}

static void
check_rdbmap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	dbm_t		type)
{
	xfs_extlen_t	i;
	char		*p;

	for (i = 0, p = &dbmap[mp->m_sb.sb_agcount][bno]; i < len; i++, p++) {
		if ((dbm_t)*p != type) {
			if (!sflag || CHECK_BLIST(bno + i))
				dbprintf(_("rtblock %llu expected type %s got "
					 "%s\n"),
					bno + i, typename[type],
					typename[(dbm_t)*p]);
			error++;
		}
	}
}

static int
check_rinomap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	xfs_ino_t	c_ino)
{
	xfs_extlen_t	i;
	inodata_t	**idp;
	int		rval;

	if (!check_rrange(bno, len)) {
		dbprintf(_("rtblocks %llu..%llu claimed by inode %lld\n"),
			bno, bno + len - 1, c_ino);
		return 0;
	}
	for (i = 0, rval = 1, idp = &inomap[mp->m_sb.sb_agcount][bno];
	     i < len;
	     i++, idp++) {
		if (*idp) {
			if (!sflag || (*idp)->ilist || CHECK_BLIST(bno + i))
				dbprintf(_("rtblock %llu claimed by inode %lld, "
					 "previous inum %lld\n"),
					bno + i, c_ino, (*idp)->ino);
			error++;
			rval = 0;
		}
	}
	return rval;
}

static void
check_rootdir(void)
{
	inodata_t	*id;

	id = find_inode(mp->m_sb.sb_rootino, 0);
	if (id == NULL) {
		if (!sflag)
			dbprintf(_("root inode %lld is missing\n"),
				mp->m_sb.sb_rootino);
		error++;
	} else if (!id->isdir) {
		if (!sflag || id->ilist)
			dbprintf(_("root inode %lld is not a directory\n"),
				mp->m_sb.sb_rootino);
		error++;
	}
}

static int
check_rrange(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len)
{
	xfs_extlen_t	i;

	if (bno + len - 1 >= mp->m_sb.sb_rblocks) {
		for (i = 0; i < len; i++) {
			if (!sflag || CHECK_BLIST(bno + i))
				dbprintf(_("rtblock %llu out of range\n"),
					bno + i);
		}
		error++;
		return 0;
	}
	return 1;
}

static void
check_set_dbmap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	dbm_t		type1,
	dbm_t		type2,
	xfs_agnumber_t	c_agno,
	xfs_agblock_t	c_agbno)
{
	xfs_extlen_t	i;
	int		mayprint;
	char		*p;

	if (!check_range(agno, agbno, len))  {
		dbprintf(_("blocks %u/%u..%u claimed by block %u/%u\n"), agno,
			agbno, agbno + len - 1, c_agno, c_agbno);
		return;
	}
	check_dbmap(agno, agbno, len, type1);
	mayprint = verbose | blist_size;
	for (i = 0, p = &dbmap[agno][agbno]; i < len; i++, p++) {
		*p = (char)type2;
		if (mayprint && (verbose || CHECK_BLISTA(agno, agbno + i)))
			dbprintf(_("setting block %u/%u to %s\n"), agno, agbno + i,
				typename[type2]);
	}
}

static void
check_set_rdbmap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	dbm_t		type1,
	dbm_t		type2)
{
	xfs_extlen_t	i;
	int		mayprint;
	char		*p;

	if (!check_rrange(bno, len))
		return;
	check_rdbmap(bno, len, type1);
	mayprint = verbose | blist_size;
	for (i = 0, p = &dbmap[mp->m_sb.sb_agcount][bno]; i < len; i++, p++) {
		*p = (char)type2;
		if (mayprint && (verbose || CHECK_BLIST(bno + i)))
			dbprintf(_("setting rtblock %llu to %s\n"),
				bno + i, typename[type2]);
	}
}

static void
check_summary(void)
{
	xfs_rfsblock_t	bno;
	xfs_suminfo_t	*csp;
	xfs_suminfo_t	*fsp;
	int		log;

	csp = sumcompute;
	fsp = sumfile;
	for (log = 0; log < mp->m_rsumlevels; log++) {
		for (bno = 0;
		     bno < mp->m_sb.sb_rbmblocks;
		     bno++, csp++, fsp++) {
			if (*csp != *fsp) {
				if (!sflag)
					dbprintf(_("rt summary mismatch, size %d "
						 "block %llu, file: %d, "
						 "computed: %d\n"),
						log, bno, *fsp, *csp);
				error++;
			}
		}
	}
}

static void
checknot_dbmap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	int		typemask)
{
	xfs_extlen_t	i;
	char		*p;

	if (!check_range(agno, agbno, len))
		return;
	for (i = 0, p = &dbmap[agno][agbno]; i < len; i++, p++) {
		if ((1 << *p) & typemask) {
			if (!sflag || CHECK_BLISTA(agno, agbno + i))
				dbprintf(_("block %u/%u type %s not expected\n"),
					agno, agbno + i, typename[(dbm_t)*p]);
			error++;
		}
	}
}

static void
checknot_rdbmap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	int		typemask)
{
	xfs_extlen_t	i;
	char		*p;

	if (!check_rrange(bno, len))
		return;
	for (i = 0, p = &dbmap[mp->m_sb.sb_agcount][bno]; i < len; i++, p++) {
		if ((1 << *p) & typemask) {
			if (!sflag || CHECK_BLIST(bno + i))
				dbprintf(_("rtblock %llu type %s not expected\n"),
					bno + i, typename[(dbm_t)*p]);
			error++;
		}
	}
}

static void
dir_hash_add(
	xfs_dahash_t		hash,
	xfs_dir2_dataptr_t	addr)
{
	int			i;
	dirhash_t		*p;

	i = DIR_HASH_FUNC(hash, addr);
	p = malloc(sizeof(*p));
	p->next = dirhash[i];
	dirhash[i] = p;
	p->hashval = hash;
	p->address = addr;
	p->seen = 0;
}

static void
dir_hash_check(
	inodata_t	*id,
	int		v)
{
	int		i;
	dirhash_t	*p;

	for (i = 0; i < DIR_HASH_SIZE; i++) {
		for (p = dirhash[i]; p; p = p->next) {
			if (p->seen)
				continue;
			if (!sflag || id->ilist || v)
				dbprintf(_("dir ino %lld missing leaf entry for "
					 "%x/%x\n"),
					id->ino, p->hashval, p->address);
			error++;
		}
	}
}

static void
dir_hash_done(void)
{
	int		i;
	dirhash_t	*n;
	dirhash_t	*p;

	for (i = 0; i < DIR_HASH_SIZE; i++) {
		for (p = dirhash[i]; p; p = n) {
			n = p->next;
			free(p);
		}
		dirhash[i] = NULL;
	}
}

static void
dir_hash_init(void)
{
	if (!dirhash)
		dirhash = calloc(DIR_HASH_SIZE, sizeof(*dirhash));
}

static int
dir_hash_see(
	xfs_dahash_t		hash,
	xfs_dir2_dataptr_t	addr)
{
	int			i;
	dirhash_t		*p;

	i = DIR_HASH_FUNC(hash, addr);
	for (p = dirhash[i]; p; p = p->next) {
		if (p->hashval == hash && p->address == addr) {
			if (p->seen)
				return 1;
			p->seen = 1;
			return 0;
		}
	}
	return -1;
}

static inodata_t *
find_inode(
	xfs_ino_t	ino,
	int		add)
{
	xfs_agino_t	agino;
	xfs_agnumber_t	agno;
	inodata_t	*ent;
	inodata_t	**htab;
	xfs_agino_t	ih;

	agno = XFS_INO_TO_AGNO(mp, ino);
	agino = XFS_INO_TO_AGINO(mp, ino);
	if (agno >= mp->m_sb.sb_agcount ||
	    XFS_AGINO_TO_INO(mp, agno, agino) != ino)
		return NULL;
	htab = inodata[agno];
	ih = agino % inodata_hash_size;
	ent = htab[ih];
	while (ent) {
		if (ent->ino == ino)
			return ent;
		ent = ent->next;
	}
	if (!add)
		return NULL;
	ent = xcalloc(1, sizeof(*ent));
	ent->ino = ino;
	ent->next = htab[ih];
	htab[ih] = ent;
	return ent;
}

static void
free_inodata(
	xfs_agnumber_t	agno)
{
	inodata_t	*hp;
	inodata_t	**ht;
	int		i;
	inodata_t	*next;

	ht = inodata[agno];
	for (i = 0; i < inodata_hash_size; i++) {
		hp = ht[i];
		while (hp) {
			next = hp->next;
			if (hp->name)
				xfree(hp->name);
			xfree(hp);
			hp = next;
		}
	}
	xfree(ht);
}

static int
init(
	int		argc,
	char		**argv)
{
	xfs_fsblock_t	bno;
	int		c;
	xfs_ino_t	ino;
	int		rt;

	serious_error = 0;
	if (mp->m_sb.sb_magicnum != XFS_SB_MAGIC) {
		dbprintf(_("bad superblock magic number %x, giving up\n"),
			mp->m_sb.sb_magicnum);
		serious_error = 1;
		return 0;
	}
	if (!sb_logcheck())
		return 0;
	rt = mp->m_sb.sb_rextents != 0;
	dbmap = xmalloc((mp->m_sb.sb_agcount + rt) * sizeof(*dbmap));
	inomap = xmalloc((mp->m_sb.sb_agcount + rt) * sizeof(*inomap));
	inodata = xmalloc(mp->m_sb.sb_agcount * sizeof(*inodata));
	inodata_hash_size =
		(int)MAX(MIN(mp->m_sb.sb_icount /
				(INODATA_AVG_HASH_LENGTH * mp->m_sb.sb_agcount),
			     MAX_INODATA_HASH_SIZE),
			 MIN_INODATA_HASH_SIZE);
	for (c = 0; c < mp->m_sb.sb_agcount; c++) {
		dbmap[c] = xcalloc(mp->m_sb.sb_agblocks, sizeof(**dbmap));
		inomap[c] = xcalloc(mp->m_sb.sb_agblocks, sizeof(**inomap));
		inodata[c] = xcalloc(inodata_hash_size, sizeof(**inodata));
	}
	if (rt) {
		dbmap[c] = xcalloc(mp->m_sb.sb_rblocks, sizeof(**dbmap));
		inomap[c] = xcalloc(mp->m_sb.sb_rblocks, sizeof(**inomap));
		sumfile = xcalloc(mp->m_rsumsize, 1);
		sumcompute = xcalloc(mp->m_rsumsize, 1);
	}
	nflag = sflag = tflag = verbose = optind = 0;
	while ((c = getopt(argc, argv, "b:i:npstv")) != EOF) {
		switch (c) {
		case 'b':
			bno = strtoll(optarg, NULL, 10);
			add_blist(bno);
			break;
		case 'i':
			ino = strtoll(optarg, NULL, 10);
			add_ilist(ino);
			break;
		case 'n':
			nflag = 1;
			break;
		case 'p':
			pflag = 1;
			break;
		case 's':
			sflag = 1;
			break;
		case 't':
			tflag = 1;
			break;
		case 'v':
			verbose = 1;
			break;
		default:
			dbprintf(_("bad option for blockget command\n"));
			return 0;
		}
	}
	error = sbver_err = serious_error = 0;
	fdblocks = frextents = icount = ifree = 0;
	sbversion = XFS_SB_VERSION_4;
	/*
	 * Note that inoalignmt == 0 is valid when fsb size is large enough for
	 * at least one full inode record per block. Check this case explicitly.
	 */
	if (mp->m_sb.sb_inoalignmt ||
	    (xfs_sb_version_hasalign(&mp->m_sb) &&
	     mp->m_sb.sb_inopblock >= XFS_INODES_PER_CHUNK))
		sbversion |= XFS_SB_VERSION_ALIGNBIT;
	if ((mp->m_sb.sb_uquotino && mp->m_sb.sb_uquotino != NULLFSINO) ||
	    (mp->m_sb.sb_gquotino && mp->m_sb.sb_gquotino != NULLFSINO) ||
	    (mp->m_sb.sb_pquotino && mp->m_sb.sb_pquotino != NULLFSINO))
		sbversion |= XFS_SB_VERSION_QUOTABIT;
	quota_init();
	return 1;
}

static char *
inode_name(
	xfs_ino_t	ino,
	inodata_t	**ipp)
{
	inodata_t	*id;
	char		*npath;
	char		*path;

	id = find_inode(ino, 0);
	if (ipp)
		*ipp = id;
	if (id == NULL)
		return NULL;
	if (id->name == NULL)
		return NULL;
	path = xstrdup(id->name);
	while (id->parent) {
		id = id->parent;
		if (id->name == NULL)
			break;
		npath = prepend_path(path, id->name);
		xfree(path);
		path = npath;
	}
	return path;
}

static int
ncheck_f(
	int		argc,
	char		**argv)
{
	xfs_agnumber_t	agno;
	int		c;
	inodata_t	*hp;
	inodata_t	**ht;
	int		i;
	inodata_t	*id;
	xfs_ino_t	*ilist;
	int		ilist_size;
	xfs_ino_t	*ilp;
	xfs_ino_t	ino;
	char		*p;
	int		security;

	if (!inodata || !nflag) {
		dbprintf(_("must run blockget -n first\n"));
		return 0;
	}
	security = optind = ilist_size = 0;
	ilist = NULL;
	while ((c = getopt(argc, argv, "i:s")) != EOF) {
		switch (c) {
		case 'i':
			ino = strtoll(optarg, NULL, 10);
			ilist = xrealloc(ilist, (ilist_size + 1) *
				sizeof(*ilist));
			ilist[ilist_size++] = ino;
			break;
		case 's':
			security = 1;
			break;
		default:
			dbprintf(_("bad option -%c for ncheck command\n"), c);
			xfree(ilist);
			return 0;
		}
	}
	if (ilist) {
		for (ilp = ilist; ilp < &ilist[ilist_size]; ilp++) {
			ino = *ilp;
			if ((p = inode_name(ino, &hp))) {
				dbprintf("%11llu %s", ino, p);
				if (hp->isdir)
					dbprintf("/.");
				dbprintf("\n");
				xfree(p);
			}
		}
		xfree(ilist);
		return 0;
	}
	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
		ht = inodata[agno];
		for (i = 0; i < inodata_hash_size; i++) {
			hp = ht[i];
			for (hp = ht[i]; hp; hp = hp->next) {
				ino = XFS_AGINO_TO_INO(mp, agno, hp->ino);
				p = inode_name(ino, &id);
				if (!p || !id)
					continue;
				if (!security || id->security) {
					dbprintf("%11llu %s", ino, p);
					if (hp->isdir)
						dbprintf("/.");
					dbprintf("\n");
				}
				xfree(p);
			}
		}
	}
	return 0;
}

static char *
prepend_path(
	char	*oldpath,
	char	*parent)
{
	int	len;
	char	*path;

	len = (int)(strlen(oldpath) + strlen(parent) + 2);
	path = xmalloc(len);
	snprintf(path, len, "%s/%s", parent, oldpath);
	return path;
}

static xfs_ino_t
process_block_dir_v2(
	blkmap_t	*blkmap,
	int		*dot,
	int		*dotdot,
	inodata_t	*id)
{
	xfs_fsblock_t	b;
	bbmap_t		bbmap;
	bmap_ext_t	*bmp;
	int		nex;
	xfs_ino_t	parent;
	int		v;
	int		x;

	nex = blkmap_getn(blkmap, 0, mp->m_dir_geo->fsbcount, &bmp);
	v = id->ilist || verbose;
	if (nex == 0) {
		if (!sflag || v)
			dbprintf(_("block 0 for directory inode %lld is "
				 "missing\n"),
				id->ino);
		error++;
		return 0;
	}
	push_cur();
	if (nex > 1)
		make_bbmap(&bbmap, nex, bmp);
	set_cur(&typtab[TYP_DIR2], XFS_FSB_TO_DADDR(mp, bmp->startblock),
		mp->m_dir_geo->fsbcount * blkbb, DB_RING_IGN, nex > 1 ? &bbmap : NULL);
	for (x = 0; !v && x < nex; x++) {
		for (b = bmp[x].startblock;
		     !v && b < bmp[x].startblock + bmp[x].blockcount;
		     b++)
			v = CHECK_BLIST(b);
	}
	free(bmp);
	if (iocur_top->data == NULL) {
		if (!sflag || id->ilist || v)
			dbprintf(_("can't read block 0 for directory inode "
				 "%lld\n"),
				id->ino);
		error++;
		pop_cur();
		return 0;
	}
	dir_hash_init();
	parent = process_data_dir_v2(dot, dotdot, id, v, mp->m_dir_geo->datablk,
		NULL);
	dir_hash_check(id, v);
	dir_hash_done();
	pop_cur();
	return parent;
}

static void
process_bmbt_reclist(
	xfs_bmbt_rec_t		*rp,
	int			numrecs,
	dbm_t			type,
	inodata_t		*id,
	xfs_rfsblock_t		*tot,
	blkmap_t		**blkmapp)
{
	xfs_agblock_t		agbno;
	xfs_agnumber_t		agno;
	xfs_fsblock_t		b;
	xfs_filblks_t		c;
	xfs_filblks_t		cp;
	int			f;
	int			i;
	xfs_agblock_t		iagbno;
	xfs_agnumber_t		iagno;
	xfs_fileoff_t		o;
	xfs_fileoff_t		op;
	xfs_fsblock_t		s;
	int			v;

	cp = op = 0;
	v = verbose || id->ilist;
	iagno = XFS_INO_TO_AGNO(mp, id->ino);
	iagbno = XFS_INO_TO_AGBNO(mp, id->ino);
	for (i = 0; i < numrecs; i++, rp++) {
		convert_extent(rp, &o, &s, &c, &f);
		if (v)
			dbprintf(_("inode %lld extent [%lld,%lld,%lld,%d]\n"),
				id->ino, o, s, c, f);
		if (!sflag && i > 0 && op + cp > o)
			dbprintf(_("bmap rec out of order, inode %lld entry %d\n"),
				id->ino, i);
		op = o;
		cp = c;
		if (type == DBM_RTDATA) {
			if (!sflag && s >= mp->m_sb.sb_rblocks) {
				dbprintf(_("inode %lld bad rt block number %lld, "
					 "offset %lld\n"),
					id->ino, s, o);
				continue;
			}
		} else if (!sflag) {
			agno = XFS_FSB_TO_AGNO(mp, s);
			agbno = XFS_FSB_TO_AGBNO(mp, s);
			if (agno >= mp->m_sb.sb_agcount ||
			    agbno >= mp->m_sb.sb_agblocks) {
				dbprintf(_("inode %lld bad block number %lld "
					 "[%d,%d], offset %lld\n"),
					id->ino, s, agno, agbno, o);
				continue;
			}
			if (agbno + c - 1 >= mp->m_sb.sb_agblocks) {
				dbprintf(_("inode %lld bad block number %lld "
					 "[%d,%d], offset %lld\n"),
					id->ino, s + c - 1, agno,
					agbno + (xfs_agblock_t)c - 1, o);
				continue;
			}
		}
		if (blkmapp && *blkmapp)
			blkmap_set_ext(blkmapp, (xfs_fileoff_t)o,
				(xfs_fsblock_t)s, (xfs_extlen_t)c);
		if (type == DBM_RTDATA) {
			set_rdbmap((xfs_fsblock_t)s, (xfs_extlen_t)c,
				DBM_RTDATA);
			set_rinomap((xfs_fsblock_t)s, (xfs_extlen_t)c, id);
			for (b = (xfs_fsblock_t)s;
			     blist_size && b < s + c;
			     b++, o++) {
				if (CHECK_BLIST(b))
					dbprintf(_("inode %lld block %lld at "
						 "offset %lld\n"),
						id->ino, (xfs_fsblock_t)b, o);
			}
		} else {
			agno = XFS_FSB_TO_AGNO(mp, (xfs_fsblock_t)s);
			agbno = XFS_FSB_TO_AGBNO(mp, (xfs_fsblock_t)s);
			set_dbmap(agno, agbno, (xfs_extlen_t)c, type, iagno,
				iagbno);
			set_inomap(agno, agbno, (xfs_extlen_t)c, id);
			for (b = (xfs_fsblock_t)s;
			     blist_size && b < s + c;
			     b++, o++, agbno++) {
				if (CHECK_BLIST(b))
					dbprintf(_("inode %lld block %lld at "
						 "offset %lld\n"),
						id->ino, (xfs_fsblock_t)b, o);
			}
		}
		*tot += c;
	}
}

static void
process_btinode(
	inodata_t		*id,
	xfs_dinode_t		*dip,
	dbm_t			type,
	xfs_rfsblock_t		*totd,
	xfs_rfsblock_t		*toti,
	xfs_extnum_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork)
{
	xfs_bmdr_block_t	*dib;
	int			i;
	xfs_bmbt_ptr_t		*pp;

	dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR(dip, whichfork);
	if (be16_to_cpu(dib->bb_level) >= XFS_BM_MAXLEVELS(mp, whichfork)) {
		if (!sflag || id->ilist)
			dbprintf(_("level for ino %lld %s fork bmap root too "
				 "large (%u)\n"),
				id->ino,
				whichfork == XFS_DATA_FORK ? _("data") : _("attr"),
				be16_to_cpu(dib->bb_level));
		error++;
		return;
	}
	if (be16_to_cpu(dib->bb_numrecs) >
			xfs_bmdr_maxrecs(XFS_DFORK_SIZE(dip, mp, whichfork),
			be16_to_cpu(dib->bb_level) == 0)) {
		if (!sflag || id->ilist)
			dbprintf(_("numrecs for ino %lld %s fork bmap root too "
				 "large (%u)\n"),
				id->ino,
				whichfork == XFS_DATA_FORK ? _("data") : _("attr"),
				be16_to_cpu(dib->bb_numrecs));
		error++;
		return;
	}
	if (be16_to_cpu(dib->bb_level) == 0) {
		xfs_bmbt_rec_t	*rp = XFS_BMDR_REC_ADDR(dib, 1);
		process_bmbt_reclist(rp, be16_to_cpu(dib->bb_numrecs), type, 
							id, totd, blkmapp);
		*nex += be16_to_cpu(dib->bb_numrecs);
		return;
	} else {
		pp = XFS_BMDR_PTR_ADDR(dib, 1, xfs_bmdr_maxrecs(
				XFS_DFORK_SIZE(dip, mp, whichfork), 0));
		for (i = 0; i < be16_to_cpu(dib->bb_numrecs); i++)
			scan_lbtree(be64_to_cpu(pp[i]), 
					be16_to_cpu(dib->bb_level), 
					scanfunc_bmap, type, id, totd, toti, 
					nex, blkmapp, 1, 
					whichfork == XFS_DATA_FORK ?
						TYP_BMAPBTD : TYP_BMAPBTA);
	}
	if (*nex <= XFS_DFORK_SIZE(dip, mp, whichfork) / sizeof(xfs_bmbt_rec_t)) {
		if (!sflag || id->ilist)
			dbprintf(_("extent count for ino %lld %s fork too low "
				 "(%d) for file format\n"),
				id->ino,
				whichfork == XFS_DATA_FORK ? _("data") : _("attr"),
				*nex);
		error++;
	}
}

static xfs_ino_t
process_data_dir_v2(
	int			*dot,
	int			*dotdot,
	inodata_t		*id,
	int			v,
	xfs_dablk_t		dabno,
	freetab_t		**freetabp)
{
	xfs_dir2_dataptr_t	addr;
	xfs_dir2_data_free_t	*bf;
	int			bf_err;
	struct xfs_dir2_data_hdr *block;
	xfs_dir2_block_tail_t	*btp = NULL;
	inodata_t		*cid;
	int			count;
	struct xfs_dir2_data_hdr *data;
	xfs_dir2_db_t		db;
	xfs_dir2_data_entry_t	*dep;
	xfs_dir2_data_free_t	*dfp;
	xfs_dir2_data_unused_t	*dup;
	char			*endptr;
	int			freeseen;
	freetab_t		*freetab;
	int			i;
	int			lastfree;
	int			lastfree_err;
	xfs_dir2_leaf_entry_t	*lep = NULL;
	xfs_ino_t		lino;
	xfs_ino_t		parent = 0;
	char			*ptr;
	int			stale = 0;
	int			tag_err;
	__be16			*tagp;
	struct xfs_name		xname;

	data = iocur_top->data;
	block = iocur_top->data;
	if (be32_to_cpu(block->magic) != XFS_DIR2_BLOCK_MAGIC &&
			be32_to_cpu(data->magic) != XFS_DIR2_DATA_MAGIC &&
			be32_to_cpu(block->magic) != XFS_DIR3_BLOCK_MAGIC &&
			be32_to_cpu(data->magic) != XFS_DIR3_DATA_MAGIC) {
		if (!sflag || v)
			dbprintf(_("bad directory data magic # %#x for dir ino "
				 "%lld block %d\n"),
				be32_to_cpu(data->magic), id->ino, dabno);
		error++;
		return NULLFSINO;
	}
	db = xfs_dir2_da_to_db(mp->m_dir_geo, dabno);
	bf = M_DIROPS(mp)->data_bestfree_p(data);
	ptr = (char *)M_DIROPS(mp)->data_unused_p(data);
	if (be32_to_cpu(block->magic) == XFS_DIR2_BLOCK_MAGIC ||
	    be32_to_cpu(block->magic) == XFS_DIR3_BLOCK_MAGIC) {
		btp = xfs_dir2_block_tail_p(mp->m_dir_geo, block);
		lep = xfs_dir2_block_leaf_p(btp);
		endptr = (char *)lep;
		if (endptr <= ptr || endptr > (char *)btp) {
			endptr = (char *)data + mp->m_dir_geo->blksize;
			lep = NULL;
			if (!sflag || v)
				dbprintf(_("bad block directory tail for dir ino "
					 "%lld\n"),
					id->ino);
			error++;
		}
	} else
		endptr = (char *)data + mp->m_dir_geo->blksize;
	bf_err = lastfree_err = tag_err = 0;
	count = lastfree = freeseen = 0;
	if (be16_to_cpu(bf[0].length) == 0) {
		bf_err += be16_to_cpu(bf[0].offset) != 0;
		freeseen |= 1 << 0;
	}
	if (be16_to_cpu(bf[1].length) == 0) {
		bf_err += be16_to_cpu(bf[1].offset) != 0;
		freeseen |= 1 << 1;
	}
	if (be16_to_cpu(bf[2].length) == 0) {
		bf_err += be16_to_cpu(bf[2].offset) != 0;
		freeseen |= 1 << 2;
	}
	bf_err += be16_to_cpu(bf[0].length) < be16_to_cpu(bf[1].length);
	bf_err += be16_to_cpu(bf[1].length) < be16_to_cpu(bf[2].length);
	if (freetabp) {
		freetab = *freetabp;
		if (freetab->naents <= db) {
			*freetabp = freetab =
				realloc(freetab, FREETAB_SIZE(db + 1));
			for (i = freetab->naents; i < db; i++)
				freetab->ents[i] = NULLDATAOFF;
			freetab->naents = db + 1;
		}
		if (freetab->nents < db + 1)
			freetab->nents = db + 1;
		freetab->ents[db] = be16_to_cpu(bf[0].length);
	}
	while (ptr < endptr) {
		dup = (xfs_dir2_data_unused_t *)ptr;
		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
			lastfree_err += lastfree != 0;
			tagp = xfs_dir2_data_unused_tag_p(dup);
			if ((be16_to_cpu(dup->length) & (XFS_DIR2_DATA_ALIGN - 1)) ||
					be16_to_cpu(dup->length) == 0 ||
					(char *)tagp >= endptr) {
				if (!sflag || v)
					dbprintf(_("dir %lld block %d bad free "
						 "entry at %d\n"),
						id->ino, dabno,
						(int)((char *)dup -
						      (char *)data));
				error++;
				break;
			}
			tag_err += be16_to_cpu(*tagp) != (char *)dup - (char *)data;
			dfp = process_data_dir_v2_freefind(data, dup);
			if (dfp) {
				i = (int)(dfp - bf);
				bf_err += (freeseen & (1 << i)) != 0;
				freeseen |= 1 << i;
			} else
				bf_err += be16_to_cpu(dup->length) > 
						be16_to_cpu(bf[2].length);
			ptr += be16_to_cpu(dup->length);
			lastfree = 1;
			continue;
		}
		dep = (xfs_dir2_data_entry_t *)dup;
		if (dep->namelen == 0) {
			if (!sflag || v)
				dbprintf(_("dir %lld block %d zero length entry "
					 "at %d\n"),
					id->ino, dabno,
					(int)((char *)dep - (char *)data));
			error++;
		}
		tagp = M_DIROPS(mp)->data_entry_tag_p(dep);
		if ((char *)tagp >= endptr) {
			if (!sflag || v)
				dbprintf(_("dir %lld block %d bad entry at %d\n"),
					id->ino, dabno,
					(int)((char *)dep - (char *)data));
			error++;
			break;
		}
		tag_err += be16_to_cpu(*tagp) != (char *)dep - (char *)data;
		addr = xfs_dir2_db_off_to_dataptr(mp->m_dir_geo, db,
			(char *)dep - (char *)data);
		xname.name = dep->name;
		xname.len = dep->namelen;
		dir_hash_add(mp->m_dirnameops->hashname(&xname), addr);
		ptr += M_DIROPS(mp)->data_entsize(dep->namelen);
		count++;
		lastfree = 0;
		lino = be64_to_cpu(dep->inumber);
		cid = find_inode(lino, 1);
		if (v)
			dbprintf(_("dir %lld block %d entry %*.*s %lld\n"),
				id->ino, dabno, dep->namelen, dep->namelen,
				dep->name, lino);
		if (cid)
			addlink_inode(cid);
		else {
			if (!sflag || v)
				dbprintf(_("dir %lld block %d entry %*.*s bad "
					 "inode number %lld\n"),
					id->ino, dabno, dep->namelen,
					dep->namelen, dep->name, lino);
			error++;
		}
		if (dep->namelen == 2 && dep->name[0] == '.' &&
		    dep->name[1] == '.') {
			if (parent) {
				if (!sflag || v)
					dbprintf(_("multiple .. entries in dir "
						 "%lld (%lld, %lld)\n"),
						id->ino, parent, lino);
				error++;
			} else
				parent = cid ? lino : NULLFSINO;
			(*dotdot)++;
		} else if (dep->namelen != 1 || dep->name[0] != '.') {
			if (cid != NULL) {
				if (!cid->parent)
					cid->parent = id;
				addname_inode(cid, (char *)dep->name,
					dep->namelen);
			}
		} else {
			if (lino != id->ino) {
				if (!sflag || v)
					dbprintf(_("dir %lld entry . inode "
						 "number mismatch (%lld)\n"),
						id->ino, lino);
				error++;
			}
			(*dot)++;
		}
	}
	if (be32_to_cpu(data->magic) == XFS_DIR2_BLOCK_MAGIC ||
	    be32_to_cpu(data->magic) == XFS_DIR3_BLOCK_MAGIC) {
		endptr = (char *)data + mp->m_dir_geo->blksize;
		for (i = stale = 0; lep && i < be32_to_cpu(btp->count); i++) {
			if ((char *)&lep[i] >= endptr) {
				if (!sflag || v)
					dbprintf(_("dir %lld block %d bad count "
						 "%u\n"), id->ino, dabno, 
						be32_to_cpu(btp->count));
				error++;
				break;
			}
			if (be32_to_cpu(lep[i].address) == XFS_DIR2_NULL_DATAPTR)
				stale++;
			else if (dir_hash_see(be32_to_cpu(lep[i].hashval), 
						be32_to_cpu(lep[i].address))) {
				if (!sflag || v)
					dbprintf(_("dir %lld block %d extra leaf "
						 "entry %x %x\n"), 
						id->ino, dabno, 
						be32_to_cpu(lep[i].hashval),
						be32_to_cpu(lep[i].address));
				error++;
			}
		}
	}
	bf_err += freeseen != 7;
	if (bf_err) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d bad bestfree data\n"),
				id->ino, dabno);
		error++;
	}
	if ((be32_to_cpu(data->magic) == XFS_DIR2_BLOCK_MAGIC ||
	     be32_to_cpu(data->magic) == XFS_DIR3_BLOCK_MAGIC) &&
	    count != be32_to_cpu(btp->count) - be32_to_cpu(btp->stale)) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d bad block tail count %d "
				 "(stale %d)\n"), 
				id->ino, dabno, be32_to_cpu(btp->count), 
				be32_to_cpu(btp->stale));
		error++;
	}
	if ((be32_to_cpu(data->magic) == XFS_DIR2_BLOCK_MAGIC ||
	     be32_to_cpu(data->magic) == XFS_DIR2_BLOCK_MAGIC) &&
					stale != be32_to_cpu(btp->stale)) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d bad stale tail count %d\n"),
				id->ino, dabno, be32_to_cpu(btp->stale));
		error++;
	}
	if (lastfree_err) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d consecutive free entries\n"),
				id->ino, dabno);
		error++;
	}
	if (tag_err) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d entry/unused tag "
				 "mismatch\n"),
				id->ino, dabno);
		error++;
	}
	return parent;
}

static xfs_dir2_data_free_t *
process_data_dir_v2_freefind(
	struct xfs_dir2_data_hdr *data,
	xfs_dir2_data_unused_t	*dup)
{
	struct xfs_dir2_data_free *bf;
	struct xfs_dir2_data_free *dfp;
	xfs_dir2_data_aoff_t	off;

	off = (xfs_dir2_data_aoff_t)((char *)dup - (char *)data);
	bf = M_DIROPS(mp)->data_bestfree_p(data);
	if (be16_to_cpu(dup->length) <
			be16_to_cpu(bf[XFS_DIR2_DATA_FD_COUNT - 1].length))
		return NULL;
	for (dfp = bf; dfp < &bf[XFS_DIR2_DATA_FD_COUNT]; dfp++) {
		if (be16_to_cpu(dfp->offset) == 0)
			return NULL;
		if (be16_to_cpu(dfp->offset) == off)
			return dfp;
	}
	return NULL;
}

static void
process_dir(
	xfs_dinode_t	*dip,
	blkmap_t	*blkmap,
	inodata_t	*id)
{
	xfs_fsblock_t	bno;
	int		dot;
	int		dotdot;
	xfs_ino_t	parent;

	dot = dotdot = 0;
	if (process_dir_v2(dip, blkmap, &dot, &dotdot, id, &parent))
		return;

	bno = XFS_INO_TO_FSB(mp, id->ino);
	if (dot == 0) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("no . entry for directory %lld\n"), id->ino);
		error++;
	}
	if (dotdot == 0) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("no .. entry for directory %lld\n"), id->ino);
		error++;
	} else if (parent == id->ino && id->ino != mp->m_sb.sb_rootino) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_(". and .. same for non-root directory %lld\n"),
				id->ino);
		error++;
	} else if (id->ino == mp->m_sb.sb_rootino && id->ino != parent) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("root directory %lld has .. %lld\n"), id->ino,
				parent);
		error++;
	} else if (parent != NULLFSINO && id->ino != parent)
		addparent_inode(id, parent);
}

static int
process_dir_v2(
	xfs_dinode_t	*dip,
	blkmap_t	*blkmap,
	int		*dot,
	int		*dotdot,
	inodata_t	*id,
	xfs_ino_t	*parent)
{
	xfs_fileoff_t	last = 0;
	xfs_fsize_t	size = be64_to_cpu(dip->di_size);

	if (blkmap)
		last = blkmap_last_off(blkmap);
	if (size <= XFS_DFORK_DSIZE(dip, mp) &&
				dip->di_format == XFS_DINODE_FMT_LOCAL)
		*parent = process_sf_dir_v2(dip, dot, dotdot, id);
	else if (last == mp->m_dir_geo->fsbcount &&
			(dip->di_format == XFS_DINODE_FMT_EXTENTS ||
			dip->di_format == XFS_DINODE_FMT_BTREE))
		*parent = process_block_dir_v2(blkmap, dot, dotdot, id);
	else if (last >= mp->m_dir_geo->leafblk + mp->m_dir_geo->fsbcount &&
			(dip->di_format == XFS_DINODE_FMT_EXTENTS ||
			dip->di_format == XFS_DINODE_FMT_BTREE))
		*parent = process_leaf_node_dir_v2(blkmap, dot, dotdot, id, size);
	else  {
		dbprintf(_("bad size (%lld) or format (%d) for directory inode "
			 "%lld\n"),
			size, dip->di_format, id->ino);
		error++;
		return 1;
	}
	return 0;
}

/* ARGSUSED */
static void
process_exinode(
	inodata_t		*id,
	xfs_dinode_t		*dip,
	dbm_t			type,
	xfs_rfsblock_t		*totd,
	xfs_rfsblock_t		*toti,
	xfs_extnum_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork)
{
	xfs_bmbt_rec_t		*rp;

	rp = (xfs_bmbt_rec_t *)XFS_DFORK_PTR(dip, whichfork);
	*nex = XFS_DFORK_NEXTENTS(dip, whichfork);
	if (*nex < 0 || *nex > XFS_DFORK_SIZE(dip, mp, whichfork) / 
						sizeof(xfs_bmbt_rec_t)) {
		if (!sflag || id->ilist)
			dbprintf(_("bad number of extents %d for inode %lld\n"),
				*nex, id->ino);
		error++;
		return;
	}
	process_bmbt_reclist(rp, *nex, type, id, totd, blkmapp);
}

static void
process_inode(
	xfs_agf_t		*agf,
	xfs_agino_t		agino,
	xfs_dinode_t		*dip,
	int			isfree)
{
	blkmap_t		*blkmap;
	xfs_fsblock_t		bno = 0;
	xfs_icdinode_t		idic;
	inodata_t		*id = NULL;
	xfs_ino_t		ino;
	xfs_extnum_t		nextents = 0;
	int			nlink;
	int			security;
	xfs_rfsblock_t		totblocks;
	xfs_rfsblock_t		totdblocks = 0;
	xfs_rfsblock_t		totiblocks = 0;
	dbm_t			type;
	xfs_extnum_t		anextents = 0;
	xfs_rfsblock_t		atotdblocks = 0;
	xfs_rfsblock_t		atotiblocks = 0;
	xfs_qcnt_t		bc = 0;
	xfs_qcnt_t		ic = 0;
	xfs_qcnt_t		rc = 0;
	xfs_dqid_t		dqprid;
	int			v = 0;
	static char		okfmts[] = {
		0,				/* type 0 unused */
		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 */
	};
	static char		*fmtnames[] = {
		"dev", "local", "extents", "btree", "uuid"
	};

	libxfs_dinode_from_disk(&idic, dip);

	ino = XFS_AGINO_TO_INO(mp, be32_to_cpu(agf->agf_seqno), agino);
	if (!isfree) {
		id = find_inode(ino, 1);
		bno = XFS_INO_TO_FSB(mp, ino);
		blkmap = NULL;
	}
	v = (!sflag || (id && id->ilist) || CHECK_BLIST(bno));
	if (idic.di_magic != XFS_DINODE_MAGIC) {
		if (isfree || v)
			dbprintf(_("bad magic number %#x for inode %lld\n"),
				idic.di_magic, ino);
		error++;
		return;
	}
	if (!xfs_dinode_good_version(mp, idic.di_version)) {
		if (isfree || v)
			dbprintf(_("bad version number %#x for inode %lld\n"),
				idic.di_version, ino);
		error++;
		return;
	}
	if (isfree) {
		if (idic.di_nblocks != 0) {
			if (v)
				dbprintf(_("bad nblocks %lld for free inode "
					 "%lld\n"),
					idic.di_nblocks, ino);
			error++;
		}
		if (idic.di_version == 1)
			nlink = idic.di_onlink;
		else
			nlink = idic.di_nlink;
		if (nlink != 0) {
			if (v)
				dbprintf(_("bad nlink %d for free inode %lld\n"),
					nlink, ino);
			error++;
		}
		if (idic.di_mode != 0) {
			if (v)
				dbprintf(_("bad mode %#o for free inode %lld\n"),
					idic.di_mode, ino);
			error++;
		}
		return;
	}

	if (be32_to_cpu(dip->di_next_unlinked) != NULLAGINO) {
		if (v)
			dbprintf(_("bad next unlinked %#x for inode %lld\n"),
				be32_to_cpu(dip->di_next_unlinked), ino);
		error++;
	}
	/*
	 * di_mode is a 16-bit uint so no need to check the < 0 case
	 */
	if ((((idic.di_mode & S_IFMT) >> 12) > 15) ||
	    (!(okfmts[(idic.di_mode & S_IFMT) >> 12] & (1 << idic.di_format)))) {
		if (v)
			dbprintf(_("bad format %d for inode %lld type %#o\n"),
				idic.di_format, id->ino, idic.di_mode & S_IFMT);
		error++;
		return;
	}
	if ((unsigned int)XFS_DFORK_ASIZE(dip, mp) >=
					XFS_LITINO(mp, idic.di_version))  {
		if (v)
			dbprintf(_("bad fork offset %d for inode %lld\n"),
				idic.di_forkoff, id->ino);
		error++;
		return;
	}
	if ((unsigned int)idic.di_aformat > XFS_DINODE_FMT_BTREE)  {
		if (v)
			dbprintf(_("bad attribute format %d for inode %lld\n"),
				idic.di_aformat, id->ino);
		error++;
		return;
	}
	if (verbose || (id && id->ilist) || CHECK_BLIST(bno))
		dbprintf(_("inode %lld mode %#o fmt %s "
			 "afmt %s "
			 "nex %d anex %d nblk %lld sz %lld%s%s%s%s%s%s%s\n"),
			id->ino, idic.di_mode, fmtnames[(int)idic.di_format],
			fmtnames[(int)idic.di_aformat],
			idic.di_nextents,
			idic.di_anextents,
			idic.di_nblocks, idic.di_size,
			idic.di_flags & XFS_DIFLAG_REALTIME ? " rt" : "",
			idic.di_flags & XFS_DIFLAG_PREALLOC ? " pre" : "",
			idic.di_flags & XFS_DIFLAG_IMMUTABLE? " imm" : "",
			idic.di_flags & XFS_DIFLAG_APPEND   ? " app" : "",
			idic.di_flags & XFS_DIFLAG_SYNC     ? " syn" : "",
			idic.di_flags & XFS_DIFLAG_NOATIME  ? " noa" : "",
			idic.di_flags & XFS_DIFLAG_NODUMP   ? " nod" : "");
	security = 0;
	switch (idic.di_mode & S_IFMT) {
	case S_IFDIR:
		type = DBM_DIR;
		if (idic.di_format == XFS_DINODE_FMT_LOCAL)
			break;
		blkmap = blkmap_alloc(idic.di_nextents);
		break;
	case S_IFREG:
		if (idic.di_flags & XFS_DIFLAG_REALTIME)
			type = DBM_RTDATA;
		else if (id->ino == mp->m_sb.sb_rbmino) {
			type = DBM_RTBITMAP;
			blkmap = blkmap_alloc(idic.di_nextents);
			addlink_inode(id);
		} else if (id->ino == mp->m_sb.sb_rsumino) {
			type = DBM_RTSUM;
			blkmap = blkmap_alloc(idic.di_nextents);
			addlink_inode(id);
		}
		else if (id->ino == mp->m_sb.sb_uquotino ||
			 id->ino == mp->m_sb.sb_gquotino ||
			 id->ino == mp->m_sb.sb_pquotino) {
			type = DBM_QUOTA;
			blkmap = blkmap_alloc(idic.di_nextents);
			addlink_inode(id);
		}
		else
			type = DBM_DATA;
		if (idic.di_mode & (S_ISUID | S_ISGID))
			security = 1;
		break;
	case S_IFLNK:
		type = DBM_SYMLINK;
		break;
	default:
		security = 1;
		type = DBM_UNKNOWN;
		break;
	}
	if (idic.di_version == 1)
		setlink_inode(id, idic.di_onlink, type == DBM_DIR, security);
	else {
		sbversion |= XFS_SB_VERSION_NLINKBIT;
		setlink_inode(id, idic.di_nlink, type == DBM_DIR, security);
	}
	switch (idic.di_format) {
	case XFS_DINODE_FMT_LOCAL:
		process_lclinode(id, dip, type, &totdblocks, &totiblocks,
			&nextents, &blkmap, XFS_DATA_FORK);
		break;
	case XFS_DINODE_FMT_EXTENTS:
		process_exinode(id, dip, type, &totdblocks, &totiblocks,
			&nextents, &blkmap, XFS_DATA_FORK);
		break;
	case XFS_DINODE_FMT_BTREE:
		process_btinode(id, dip, type, &totdblocks, &totiblocks,
			&nextents, &blkmap, XFS_DATA_FORK);
		break;
	}
	if (XFS_DFORK_Q(dip)) {
		sbversion |= XFS_SB_VERSION_ATTRBIT;
		switch (idic.di_aformat) {
		case XFS_DINODE_FMT_LOCAL:
			process_lclinode(id, dip, DBM_ATTR, &atotdblocks,
				&atotiblocks, &anextents, NULL, XFS_ATTR_FORK);
			break;
		case XFS_DINODE_FMT_EXTENTS:
			process_exinode(id, dip, DBM_ATTR, &atotdblocks,
				&atotiblocks, &anextents, NULL, XFS_ATTR_FORK);
			break;
		case XFS_DINODE_FMT_BTREE:
			process_btinode(id, dip, DBM_ATTR, &atotdblocks,
				&atotiblocks, &anextents, NULL, XFS_ATTR_FORK);
			break;
		}
	}
	if (qgdo || qpdo || qudo) {
		switch (type) {
		case DBM_DATA:
		case DBM_DIR:
		case DBM_RTBITMAP:
		case DBM_RTSUM:
		case DBM_SYMLINK:
		case DBM_UNKNOWN:
			bc = totdblocks + totiblocks +
			     atotdblocks + atotiblocks;
			ic = 1;
			break;
		case DBM_RTDATA:
			bc = totiblocks + atotdblocks + atotiblocks;
			rc = totdblocks;
			ic = 1;
			break;
		default:
			break;
		}
		if (ic) {
			dqprid = xfs_get_projid(&idic);	/* dquot ID is u32 */
			quota_add(&dqprid, &idic.di_gid, &idic.di_uid,
				  0, bc, ic, rc);
		}
	}
	totblocks = totdblocks + totiblocks + atotdblocks + atotiblocks;
	if (totblocks != idic.di_nblocks) {
		if (v)
			dbprintf(_("bad nblocks %lld for inode %lld, counted "
				 "%lld\n"),
				idic.di_nblocks, id->ino, totblocks);
		error++;
	}
	if (nextents != idic.di_nextents) {
		if (v)
			dbprintf(_("bad nextents %d for inode %lld, counted %d\n"),
				idic.di_nextents, id->ino, nextents);
		error++;
	}
	if (anextents != idic.di_anextents) {
		if (v)
			dbprintf(_("bad anextents %d for inode %lld, counted "
				 "%d\n"),
				idic.di_anextents, id->ino, anextents);
		error++;
	}
	if (type == DBM_DIR)
		process_dir(dip, blkmap, id);
	else if (type == DBM_RTBITMAP)
		process_rtbitmap(blkmap);
	else if (type == DBM_RTSUM)
		process_rtsummary(blkmap);
	/*
	 * If the CHKD flag is not set, this can legitimately contain garbage;
	 * xfs_repair may have cleared that bit.
	 */
	else if (type == DBM_QUOTA) {
		if (id->ino == mp->m_sb.sb_uquotino &&
		    (mp->m_sb.sb_qflags & XFS_UQUOTA_ACCT) &&
		    (mp->m_sb.sb_qflags & XFS_UQUOTA_CHKD))
			process_quota(IS_USER_QUOTA, id, blkmap);
		else if (id->ino == mp->m_sb.sb_gquotino &&
			 (mp->m_sb.sb_qflags & XFS_GQUOTA_ACCT) &&
			 (mp->m_sb.sb_qflags & XFS_GQUOTA_CHKD))
			process_quota(IS_GROUP_QUOTA, id, blkmap);
		else if (id->ino == mp->m_sb.sb_pquotino &&
			 (mp->m_sb.sb_qflags & XFS_PQUOTA_ACCT) &&
			 (mp->m_sb.sb_qflags & XFS_PQUOTA_CHKD))
			process_quota(IS_PROJECT_QUOTA, id, blkmap);
	}
	if (blkmap)
		blkmap_free(blkmap);
}

/* ARGSUSED */
static void
process_lclinode(
	inodata_t		*id,
	xfs_dinode_t		*dip,
	dbm_t			type,
	xfs_rfsblock_t		*totd,
	xfs_rfsblock_t		*toti,
	xfs_extnum_t		*nex,
	blkmap_t		**blkmapp,
	int			whichfork)
{
	xfs_attr_shortform_t	*asf;
	xfs_fsblock_t		bno;

	bno = XFS_INO_TO_FSB(mp, id->ino);
	if (whichfork == XFS_DATA_FORK && be64_to_cpu(dip->di_size) >
						XFS_DFORK_DSIZE(dip, mp)) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("local inode %lld data is too large (size "
				 "%lld)\n"),
				id->ino, be64_to_cpu(dip->di_size));
		error++;
	}
	else if (whichfork == XFS_ATTR_FORK) {
		asf = (xfs_attr_shortform_t *)XFS_DFORK_APTR(dip);
		if (be16_to_cpu(asf->hdr.totsize) > XFS_DFORK_ASIZE(dip, mp)) {
			if (!sflag || id->ilist || CHECK_BLIST(bno))
				dbprintf(_("local inode %lld attr is too large "
					 "(size %d)\n"),
					id->ino, be16_to_cpu(asf->hdr.totsize));
			error++;
		}
	}
}

static xfs_ino_t
process_leaf_node_dir_v2(
	blkmap_t		*blkmap,
	int			*dot,
	int			*dotdot,
	inodata_t		*id,
	xfs_fsize_t		dirsize)
{
	xfs_fsblock_t		b;
	bbmap_t			bbmap;
	bmap_ext_t		*bmp;
	xfs_fileoff_t		dbno;
	freetab_t		*freetab;
	int			i;
	xfs_ino_t		lino;
	int			nex;
	xfs_ino_t		parent;
	int			t = 0;
	int			v;
	int			v2;
	int			x;

	v2 = verbose || id->ilist;
	v = parent = 0;
	dbno = NULLFILEOFF;
	freetab = malloc(FREETAB_SIZE(dirsize / mp->m_dir_geo->blksize));
	freetab->naents = (int)(dirsize / mp->m_dir_geo->blksize);
	freetab->nents = 0;
	for (i = 0; i < freetab->naents; i++)
		freetab->ents[i] = NULLDATAOFF;
	dir_hash_init();
	while ((dbno = blkmap_next_off(blkmap, dbno, &t)) != NULLFILEOFF) {
		nex = blkmap_getn(blkmap, dbno, mp->m_dir_geo->fsbcount, &bmp);
		ASSERT(nex > 0);
		for (v = v2, x = 0; !v && x < nex; x++) {
			for (b = bmp[x].startblock;
			     !v && b < bmp[x].startblock + bmp[x].blockcount;
			     b++)
				v = CHECK_BLIST(b);
		}
		if (v)
			dbprintf(_("dir inode %lld block %u=%llu\n"), id->ino,
				(__uint32_t)dbno,
				(xfs_fsblock_t)bmp->startblock);
		push_cur();
		if (nex > 1)
			make_bbmap(&bbmap, nex, bmp);
		set_cur(&typtab[TYP_DIR2], XFS_FSB_TO_DADDR(mp, bmp->startblock),
			mp->m_dir_geo->fsbcount * blkbb, DB_RING_IGN,
			nex > 1 ? &bbmap : NULL);
		free(bmp);
		if (iocur_top->data == NULL) {
			if (!sflag || v)
				dbprintf(_("can't read block %u for directory "
					 "inode %lld\n"),
					(__uint32_t)dbno, id->ino);
			error++;
			pop_cur();
			dbno += mp->m_dir_geo->fsbcount - 1;
			continue;
		}
		if (dbno < mp->m_dir_geo->leafblk) {
			lino = process_data_dir_v2(dot, dotdot, id, v,
				(xfs_dablk_t)dbno, &freetab);
			if (lino) {
				if (parent) {
					if (!sflag || v)
						dbprintf(_("multiple .. entries "
							 "in dir %lld\n"),
							id->ino);
					error++;
				} else
					parent = lino;
			}
		} else if (dbno < mp->m_dir_geo->freeblk) {
			process_leaf_node_dir_v2_int(id, v, (xfs_dablk_t)dbno,
				freetab);
		} else {
			process_leaf_node_dir_v2_free(id, v, (xfs_dablk_t)dbno,
				freetab);
		}
		pop_cur();
		dbno += mp->m_dir_geo->fsbcount - 1;
	}
	dir_hash_check(id, v);
	dir_hash_done();
	for (i = 0; i < freetab->nents; i++) {
		if (freetab->ents[i] != NULLDATAOFF) {
			if (!sflag || v)
				dbprintf(_("missing free index for data block %d "
					 "in dir ino %lld\n"),
					xfs_dir2_db_to_da(mp->m_dir_geo, i), id->ino);
			error++;
		}
	}
	free(freetab);
	return parent;
}

static void
process_leaf_node_dir_v3_free(
	inodata_t		*id,
	int			v,
	xfs_dablk_t		dabno,
	freetab_t		*freetab)
{
	xfs_dir2_data_off_t	ent;
	struct xfs_dir3_free	*free;
	int			i;
	int			maxent;
	int			used;

	free = iocur_top->data;
	maxent = M_DIROPS(mp)->free_max_bests(mp->m_dir_geo);
	if (be32_to_cpu(free->hdr.firstdb) != xfs_dir2_da_to_db(mp->m_dir_geo, 
					dabno - mp->m_dir_geo->freeblk) * maxent) {
		if (!sflag || v)
			dbprintf(_("bad free block firstdb %d for dir ino %lld "
				 "block %d\n"),
				be32_to_cpu(free->hdr.firstdb), id->ino, dabno);
		error++;
		return;
	}
	if (be32_to_cpu(free->hdr.nvalid) > maxent || 
				be32_to_cpu(free->hdr.nvalid) < 0 ||
				be32_to_cpu(free->hdr.nused) > maxent || 
				be32_to_cpu(free->hdr.nused) < 0 ||
				be32_to_cpu(free->hdr.nused) > 
					be32_to_cpu(free->hdr.nvalid)) {
		if (!sflag || v)
			dbprintf(_("bad free block nvalid/nused %d/%d for dir "
				 "ino %lld block %d\n"),
				be32_to_cpu(free->hdr.nvalid), 
				be32_to_cpu(free->hdr.nused), id->ino, dabno);
		error++;
		return;
	}
	for (used = i = 0; i < be32_to_cpu(free->hdr.nvalid); i++) {
		if (freetab->nents <= be32_to_cpu(free->hdr.firstdb) + i)
			ent = NULLDATAOFF;
		else
			ent = freetab->ents[be32_to_cpu(free->hdr.firstdb) + i];
		if (ent != be16_to_cpu(free->bests[i])) {
			if (!sflag || v)
				dbprintf(_("bad free block ent %d is %d should "
					 "be %d for dir ino %lld block %d\n"),
					i, be16_to_cpu(free->bests[i]), ent, 
					id->ino, dabno);
			error++;
		}
		if (be16_to_cpu(free->bests[i]) != NULLDATAOFF)
			used++;
		if (ent != NULLDATAOFF)
			freetab->ents[be32_to_cpu(free->hdr.firstdb) + i] = 
								NULLDATAOFF;
	}
	if (used != be32_to_cpu(free->hdr.nused)) {
		if (!sflag || v)
			dbprintf(_("bad free block nused %d should be %d for dir "
				 "ino %lld block %d\n"),
				be32_to_cpu(free->hdr.nused), used, id->ino, 
				dabno);
		error++;
	}
}

static void
process_leaf_node_dir_v2_free(
	inodata_t		*id,
	int			v,
	xfs_dablk_t		dabno,
	freetab_t		*freetab)
{
	xfs_dir2_data_off_t	ent;
	xfs_dir2_free_t		*free;
	int			i;
	int			maxent;
	int			used;

	free = iocur_top->data;
	if (be32_to_cpu(free->hdr.magic) != XFS_DIR2_FREE_MAGIC &&
	    be32_to_cpu(free->hdr.magic) != XFS_DIR3_FREE_MAGIC) {
		if (!sflag || v)
			dbprintf(_("bad free block magic # %#x for dir ino %lld "
				 "block %d\n"),
				be32_to_cpu(free->hdr.magic), id->ino, dabno);
		error++;
		return;
	}
	if (be32_to_cpu(free->hdr.magic) == XFS_DIR3_FREE_MAGIC) {
		process_leaf_node_dir_v3_free(id, v, dabno, freetab);
		return;
	}
	maxent = M_DIROPS(mp)->free_max_bests(mp->m_dir_geo);
	if (be32_to_cpu(free->hdr.firstdb) != xfs_dir2_da_to_db(mp->m_dir_geo, 
					dabno - mp->m_dir_geo->freeblk) * maxent) {
		if (!sflag || v)
			dbprintf(_("bad free block firstdb %d for dir ino %lld "
				 "block %d\n"),
				be32_to_cpu(free->hdr.firstdb), id->ino, dabno);
		error++;
		return;
	}
	if (be32_to_cpu(free->hdr.nvalid) > maxent || 
				be32_to_cpu(free->hdr.nvalid) < 0 ||
				be32_to_cpu(free->hdr.nused) > maxent || 
				be32_to_cpu(free->hdr.nused) < 0 ||
				be32_to_cpu(free->hdr.nused) > 
					be32_to_cpu(free->hdr.nvalid)) {
		if (!sflag || v)
			dbprintf(_("bad free block nvalid/nused %d/%d for dir "
				 "ino %lld block %d\n"),
				be32_to_cpu(free->hdr.nvalid), 
				be32_to_cpu(free->hdr.nused), id->ino, dabno);
		error++;
		return;
	}
	for (used = i = 0; i < be32_to_cpu(free->hdr.nvalid); i++) {
		if (freetab->nents <= be32_to_cpu(free->hdr.firstdb) + i)
			ent = NULLDATAOFF;
		else
			ent = freetab->ents[be32_to_cpu(free->hdr.firstdb) + i];
		if (ent != be16_to_cpu(free->bests[i])) {
			if (!sflag || v)
				dbprintf(_("bad free block ent %d is %d should "
					 "be %d for dir ino %lld block %d\n"),
					i, be16_to_cpu(free->bests[i]), ent, 
					id->ino, dabno);
			error++;
		}
		if (be16_to_cpu(free->bests[i]) != NULLDATAOFF)
			used++;
		if (ent != NULLDATAOFF)
			freetab->ents[be32_to_cpu(free->hdr.firstdb) + i] = 
								NULLDATAOFF;
	}
	if (used != be32_to_cpu(free->hdr.nused)) {
		if (!sflag || v)
			dbprintf(_("bad free block nused %d should be %d for dir "
				 "ino %lld block %d\n"),
				be32_to_cpu(free->hdr.nused), used, id->ino, 
				dabno);
		error++;
	}
}

/*
 * Get address of the bestcount field in the single-leaf block.
 */
static inline int
xfs_dir3_leaf_ents_count(struct xfs_dir2_leaf *lp)
{
	if (lp->hdr.info.magic == cpu_to_be16(XFS_DIR3_LEAF1_MAGIC) ||
	    lp->hdr.info.magic == cpu_to_be16(XFS_DIR3_LEAFN_MAGIC)) {
		struct xfs_dir3_leaf *lp3 = (struct xfs_dir3_leaf *)lp;

		return be16_to_cpu(lp3->hdr.count);
	}
	return be16_to_cpu(lp->hdr.count);
}

static void
process_leaf_node_dir_v2_int(
	inodata_t		*id,
	int			v,
	xfs_dablk_t		dabno,
	freetab_t		*freetab)
{
	int			i;
	__be16			*lbp;
	xfs_dir2_leaf_t		*leaf;
	struct xfs_dir3_leaf	*leaf3 = NULL;
	xfs_dir2_leaf_entry_t	*lep;
	xfs_dir2_leaf_tail_t	*ltp;
	xfs_da_intnode_t	*node;
	int			stale;
	struct xfs_da3_icnode_hdr nodehdr;

	leaf = iocur_top->data;
	switch (be16_to_cpu(leaf->hdr.info.magic)) {
	case XFS_DIR3_LEAF1_MAGIC:
	case XFS_DIR3_LEAFN_MAGIC:
	case XFS_DA3_NODE_MAGIC:
		leaf3 = iocur_top->data;
		break;
	}
	switch (be16_to_cpu(leaf->hdr.info.magic)) {
	case XFS_DIR2_LEAF1_MAGIC:
	case XFS_DIR3_LEAF1_MAGIC:
		if (be32_to_cpu(leaf->hdr.info.forw) || 
					be32_to_cpu(leaf->hdr.info.back)) {
			if (!sflag || v)
				dbprintf(_("bad leaf block forw/back pointers "
					 "%d/%d for dir ino %lld block %d\n"),
					be32_to_cpu(leaf->hdr.info.forw),
					be32_to_cpu(leaf->hdr.info.back), 
					id->ino, dabno);
			error++;
		}
		if (dabno != mp->m_dir_geo->leafblk) {
			if (!sflag || v)
				dbprintf(_("single leaf block for dir ino %lld "
					 "block %d should be at block %d\n"),
					id->ino, dabno,
					(xfs_dablk_t)mp->m_dir_geo->leafblk);
			error++;
		}
		ltp = xfs_dir2_leaf_tail_p(mp->m_dir_geo, leaf);
		lbp = xfs_dir2_leaf_bests_p(ltp);
		for (i = 0; i < be32_to_cpu(ltp->bestcount); i++) {
			if (freetab->nents <= i || freetab->ents[i] != 
						be16_to_cpu(lbp[i])) {
				if (!sflag || v)
					dbprintf(_("bestfree %d for dir ino %lld "
						 "block %d doesn't match table "
						 "value %d\n"),
						freetab->nents <= i ?
							NULLDATAOFF :
							freetab->ents[i],
						id->ino,
						xfs_dir2_db_to_da(mp->m_dir_geo, i),
						be16_to_cpu(lbp[i]));
			}
			if (freetab->nents > i)
				freetab->ents[i] = NULLDATAOFF;
		}
		break;
	case XFS_DIR2_LEAFN_MAGIC:
	case XFS_DIR3_LEAFN_MAGIC:
		/* if it's at the root location then we can check the
		 * pointers are null XXX */
		break;
	case XFS_DA_NODE_MAGIC:
	case XFS_DA3_NODE_MAGIC:
		node = iocur_top->data;
		M_DIROPS(mp)->node_hdr_from_disk(&nodehdr, node);
		if (nodehdr.level < 1 || nodehdr.level > XFS_DA_NODE_MAXDEPTH) {
			if (!sflag || v)
				dbprintf(_("bad node block level %d for dir ino "
					 "%lld block %d\n"),
					nodehdr.level, id->ino, 
					dabno);
			error++;
		}
		return;
	default:
		if (!sflag || v)
			dbprintf(_("bad directory data magic # %#x for dir ino "
				 "%lld block %d\n"),
				be16_to_cpu(leaf->hdr.info.magic), id->ino, 
				dabno);
		error++;
		return;
	}
	lep = M_DIROPS(mp)->leaf_ents_p(leaf);
	for (i = stale = 0; i < xfs_dir3_leaf_ents_count(leaf); i++) {
		if (be32_to_cpu(lep[i].address) == XFS_DIR2_NULL_DATAPTR)
			stale++;
		else if (dir_hash_see(be32_to_cpu(lep[i].hashval), 
						be32_to_cpu(lep[i].address))) {
			if (!sflag || v)
				dbprintf(_("dir %lld block %d extra leaf entry "
					 "%x %x\n"), id->ino, dabno, 
					be32_to_cpu(lep[i].hashval),
					be32_to_cpu(lep[i].address));
			error++;
		}
	}
	if (leaf3 && stale != be16_to_cpu(leaf3->hdr.stale)) {
		if (!sflag || v)
			dbprintf(_("dir3 %lld block %d stale mismatch "
				 "%d/%d\n"),
				 id->ino, dabno, stale,
				 be16_to_cpu(leaf3->hdr.stale));
		error++;
	} else if (!leaf && stale != be16_to_cpu(leaf->hdr.stale)) {
		if (!sflag || v)
			dbprintf(_("dir %lld block %d stale mismatch "
				 "%d/%d\n"),
				 id->ino, dabno, stale,
				 be16_to_cpu(leaf->hdr.stale));
		error++;
	}
}

static void
process_quota(
	qtype_t		qtype,
	inodata_t	*id,
	blkmap_t	*blkmap)
{
	xfs_fsblock_t	bno;
	int		cb;
	xfs_dqblk_t	*dqb;
	xfs_dqid_t	dqid;
	u_int8_t	exp_flags = 0;
	uint		i;
	uint		perblock;
	xfs_fileoff_t	qbno;
	char		*s = NULL;
	int		scicb;
	int		t = 0;

	switch (qtype) {
	case IS_USER_QUOTA:
		s = "user";
		exp_flags = XFS_DQ_USER;
		break;
	case IS_PROJECT_QUOTA:
		s = "project";
		exp_flags = XFS_DQ_PROJ;
		break;
	case IS_GROUP_QUOTA:
		s = "group";
		exp_flags = XFS_DQ_GROUP;
		break;
	default:
		ASSERT(0);
	}

	perblock = (uint)(mp->m_sb.sb_blocksize / sizeof(*dqb));
	dqid = 0;
	qbno = NULLFILEOFF;
	while ((qbno = blkmap_next_off(blkmap, qbno, &t)) != NULLFILEOFF) {
		bno = blkmap_get(blkmap, qbno);
		dqid = (xfs_dqid_t)qbno * perblock;
		cb = CHECK_BLIST(bno);
		scicb = !sflag || id->ilist || cb;
		push_cur();
		set_cur(&typtab[TYP_DQBLK], XFS_FSB_TO_DADDR(mp, bno), blkbb,
			DB_RING_IGN, NULL);
		if ((dqb = iocur_top->data) == NULL) {
			if (scicb)
				dbprintf(_("can't read block %lld for %s quota "
					 "inode (fsblock %lld)\n"),
					(xfs_fileoff_t)qbno, s,
					(xfs_fsblock_t)bno);
			error++;
			pop_cur();
			continue;
		}
		for (i = 0; i < perblock; i++, dqid++, dqb++) {
			if (verbose || id->ilist || cb)
				dbprintf(_("%s dqblk %lld entry %d id %u bc "
					 "%lld ic %lld rc %lld\n"),
					s, (xfs_fileoff_t)qbno, i, dqid,
					be64_to_cpu(dqb->dd_diskdq.d_bcount),
					be64_to_cpu(dqb->dd_diskdq.d_icount),
					be64_to_cpu(dqb->dd_diskdq.d_rtbcount));
			if (be16_to_cpu(dqb->dd_diskdq.d_magic) != XFS_DQUOT_MAGIC) {
				if (scicb)
					dbprintf(_("bad magic number %#x for %s "
						 "dqblk %lld entry %d id %u\n"),
						be16_to_cpu(dqb->dd_diskdq.d_magic), s,
						(xfs_fileoff_t)qbno, i, dqid);
				error++;
				continue;
			}
			if (dqb->dd_diskdq.d_version != XFS_DQUOT_VERSION) {
				if (scicb)
					dbprintf(_("bad version number %#x for "
						 "%s dqblk %lld entry %d id "
						 "%u\n"),
						dqb->dd_diskdq.d_version, s,
						(xfs_fileoff_t)qbno, i, dqid);
				error++;
				continue;
			}
			if (dqb->dd_diskdq.d_flags != exp_flags) {
				if (scicb)
					dbprintf(_("bad flags %#x for %s dqblk "
						 "%lld entry %d id %u\n"),
						dqb->dd_diskdq.d_flags, s,
						(xfs_fileoff_t)qbno, i, dqid);
				error++;
				continue;
			}
			if (be32_to_cpu(dqb->dd_diskdq.d_id) != dqid) {
				if (scicb)
					dbprintf(_("bad id %u for %s dqblk %lld "
						 "entry %d id %u\n"),
						be32_to_cpu(dqb->dd_diskdq.d_id), s,
						(xfs_fileoff_t)qbno, i, dqid);
				error++;
				continue;
			}
			quota_add((qtype == IS_PROJECT_QUOTA) ? &dqid : NULL,
				  (qtype == IS_GROUP_QUOTA) ? &dqid : NULL,
				  (qtype == IS_USER_QUOTA) ? &dqid : NULL,
				  1,
				  be64_to_cpu(dqb->dd_diskdq.d_bcount),
				  be64_to_cpu(dqb->dd_diskdq.d_icount),
				  be64_to_cpu(dqb->dd_diskdq.d_rtbcount));
		}
		pop_cur();
	}
}

static void
process_rtbitmap(
	blkmap_t	*blkmap)
{
	int		bit;
	int		bitsperblock;
	xfs_fileoff_t	bmbno;
	xfs_fsblock_t	bno;
	xfs_rtblock_t	extno;
	int		len;
	int		log;
	int		offs;
	int		prevbit;
	xfs_rfsblock_t	rtbno;
	int		start_bmbno;
	int		start_bit;
	int		t;
	xfs_rtword_t	*words;

	bitsperblock = mp->m_sb.sb_blocksize * NBBY;
	bit = extno = prevbit = start_bmbno = start_bit = 0;
	bmbno = NULLFILEOFF;
	while ((bmbno = blkmap_next_off(blkmap, bmbno, &t)) !=
	       NULLFILEOFF) {
		bno = blkmap_get(blkmap, bmbno);
		if (bno == NULLFSBLOCK) {
			if (!sflag)
				dbprintf(_("block %lld for rtbitmap inode is "
					 "missing\n"),
					(xfs_fileoff_t)bmbno);
			error++;
			continue;
		}
		push_cur();
		set_cur(&typtab[TYP_RTBITMAP], XFS_FSB_TO_DADDR(mp, bno), blkbb,
			DB_RING_IGN, NULL);
		if ((words = iocur_top->data) == NULL) {
			if (!sflag)
				dbprintf(_("can't read block %lld for rtbitmap "
					 "inode\n"),
					(xfs_fileoff_t)bmbno);
			error++;
			pop_cur();
			continue;
		}
		for (bit = 0;
		     bit < bitsperblock && extno < mp->m_sb.sb_rextents;
		     bit++, extno++) {
			if (xfs_isset(words, bit)) {
				rtbno = extno * mp->m_sb.sb_rextsize;
				set_rdbmap(rtbno, mp->m_sb.sb_rextsize,
					DBM_RTFREE);
				frextents++;
				if (prevbit == 0) {
					start_bmbno = (int)bmbno;
					start_bit = bit;
					prevbit = 1;
				}
			} else if (prevbit == 1) {
				len = ((int)bmbno - start_bmbno) *
					bitsperblock + (bit - start_bit);
				log = XFS_RTBLOCKLOG(len);
				offs = XFS_SUMOFFS(mp, log, start_bmbno);
				sumcompute[offs]++;
				prevbit = 0;
			}
		}
		pop_cur();
		if (extno == mp->m_sb.sb_rextents)
			break;
	}
	if (prevbit == 1) {
		len = ((int)bmbno - start_bmbno) * bitsperblock +
			(bit - start_bit);
		log = XFS_RTBLOCKLOG(len);
		offs = XFS_SUMOFFS(mp, log, start_bmbno);
		sumcompute[offs]++;
	}
}

static void
process_rtsummary(
	blkmap_t	*blkmap)
{
	xfs_fsblock_t	bno;
	char		*bytes;
	xfs_fileoff_t	sumbno;
	int		t;

	sumbno = NULLFILEOFF;
	while ((sumbno = blkmap_next_off(blkmap, sumbno, &t)) != NULLFILEOFF) {
		bno = blkmap_get(blkmap, sumbno);
		if (bno == NULLFSBLOCK) {
			if (!sflag)
				dbprintf(_("block %lld for rtsummary inode is "
					 "missing\n"),
					(xfs_fileoff_t)sumbno);
			error++;
			continue;
		}
		push_cur();
		set_cur(&typtab[TYP_RTSUMMARY], XFS_FSB_TO_DADDR(mp, bno),
			blkbb, DB_RING_IGN, NULL);
		if ((bytes = iocur_top->data) == NULL) {
			if (!sflag)
				dbprintf(_("can't read block %lld for rtsummary "
					 "inode\n"),
					(xfs_fileoff_t)sumbno);
			error++;
			pop_cur();
			continue;
		}
		memcpy((char *)sumfile + sumbno * mp->m_sb.sb_blocksize, bytes,
			mp->m_sb.sb_blocksize);
		pop_cur();
	}
}

static xfs_ino_t
process_sf_dir_v2(
	xfs_dinode_t		*dip,
	int			*dot,
	int			*dotdot,
	inodata_t		*id)
{
	inodata_t		*cid;
	int			i;
	int			i8;
	xfs_ino_t		lino;
	int			offset;
	struct xfs_dir2_sf_hdr	*sf;
	xfs_dir2_sf_entry_t	*sfe;
	int			v;

	sf = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip);
	addlink_inode(id);
	v = verbose || id->ilist;
	if (v)
		dbprintf(_("dir %lld entry . %lld\n"), id->ino, id->ino);
	(*dot)++;
	sfe = xfs_dir2_sf_firstentry(sf);
	offset = M_DIROPS(mp)->data_first_offset;
	for (i = sf->count - 1, i8 = 0; i >= 0; i--) {
		if ((intptr_t)sfe + M_DIROPS(mp)->sf_entsize(sf, sfe->namelen) -
		    (intptr_t)sf > be64_to_cpu(dip->di_size)) {
			if (!sflag)
				dbprintf(_("dir %llu bad size in entry at %d\n"),
					id->ino,
					(int)((char *)sfe - (char *)sf));
			error++;
			break;
		}
		lino = M_DIROPS(mp)->sf_get_ino(sf, sfe);
		if (lino > XFS_DIR2_MAX_SHORT_INUM)
			i8++;
		cid = find_inode(lino, 1);
		if (cid == NULL) {
			if (!sflag)
				dbprintf(_("dir %lld entry %*.*s bad inode "
					 "number %lld\n"),
					id->ino, sfe->namelen, sfe->namelen,
					sfe->name, lino);
			error++;
		} else {
			addlink_inode(cid);
			if (!cid->parent)
				cid->parent = id;
			addname_inode(cid, (char *)sfe->name, sfe->namelen);
		}
		if (v)
			dbprintf(_("dir %lld entry %*.*s offset %d %lld\n"),
				id->ino, sfe->namelen, sfe->namelen, sfe->name,
				xfs_dir2_sf_get_offset(sfe), lino);
		if (xfs_dir2_sf_get_offset(sfe) < offset) {
			if (!sflag)
				dbprintf(_("dir %lld entry %*.*s bad offset %d\n"),
					id->ino, sfe->namelen, sfe->namelen,
					sfe->name, xfs_dir2_sf_get_offset(sfe));
			error++;
		}
		offset =
			xfs_dir2_sf_get_offset(sfe) +
			M_DIROPS(mp)->sf_entsize(sf, sfe->namelen);
		sfe = M_DIROPS(mp)->sf_nextentry(sf, sfe);
	}
	if (i < 0 && (intptr_t)sfe - (intptr_t)sf != 
					be64_to_cpu(dip->di_size)) {
		if (!sflag)
			dbprintf(_("dir %llu size is %lld, should be %u\n"),
				id->ino, be64_to_cpu(dip->di_size),
				(uint)((char *)sfe - (char *)sf));
		error++;
	}
	if (offset + (sf->count + 2) * sizeof(xfs_dir2_leaf_entry_t) +
	    sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize) {
		if (!sflag)
			dbprintf(_("dir %llu offsets too high\n"), id->ino);
		error++;
	}
	lino = M_DIROPS(mp)->sf_get_parent_ino(sf);
	if (lino > XFS_DIR2_MAX_SHORT_INUM)
		i8++;
	cid = find_inode(lino, 1);
	if (cid)
		addlink_inode(cid);
	else {
		if (!sflag)
			dbprintf(_("dir %lld entry .. bad inode number %lld\n"),
				id->ino, lino);
		error++;
	}
	if (v)
		dbprintf(_("dir %lld entry .. %lld\n"), id->ino, lino);
	if (i8 != sf->i8count) {
		if (!sflag)
			dbprintf(_("dir %lld i8count mismatch is %d should be "
				 "%d\n"),
				id->ino, sf->i8count, i8);
		error++;
	}
	(*dotdot)++;
	return cid ? lino : NULLFSINO;
}


static void
quota_add(
	xfs_dqid_t	*prjid,
	xfs_dqid_t	*grpid,
	xfs_dqid_t	*usrid,
	int		dq,
	xfs_qcnt_t	bc,
	xfs_qcnt_t	ic,
	xfs_qcnt_t	rc)
{
	if (qudo && usrid != NULL)
		quota_add1(qudata, *usrid, dq, bc, ic, rc);
	if (qgdo && grpid != NULL)
		quota_add1(qgdata, *grpid, dq, bc, ic, rc);
	if (qpdo && prjid != NULL)
		quota_add1(qpdata, *prjid, dq, bc, ic, rc);
}

static void
quota_add1(
	qdata_t		**qt,
	xfs_dqid_t	id,
	int		dq,
	xfs_qcnt_t	bc,
	xfs_qcnt_t	ic,
	xfs_qcnt_t	rc)
{
	qdata_t		*qe;
	int		qh;
	qinfo_t		*qi;

	qh = (int)(id % QDATA_HASH_SIZE);
	qe = qt[qh];
	while (qe) {
		if (qe->id == id) {
			qi = dq ? &qe->dq : &qe->count;
			qi->bc += bc;
			qi->ic += ic;
			qi->rc += rc;
			return;
		}
		qe = qe->next;
	}
	qe = xmalloc(sizeof(*qe));
	qe->id = id;
	qi = dq ? &qe->dq : &qe->count;
	qi->bc = bc;
	qi->ic = ic;
	qi->rc = rc;
	qi = dq ? &qe->count : &qe->dq;
	qi->bc = qi->ic = qi->rc = 0;
	qe->next = qt[qh];
	qt[qh] = qe;
}

static void
quota_check(
	char	*s,
	qdata_t	**qt)
{
	int	i;
	qdata_t	*next;
	qdata_t	*qp;

	for (i = 0; i < QDATA_HASH_SIZE; i++) {
		qp = qt[i];
		while (qp) {
			next = qp->next;
			if (qp->count.bc != qp->dq.bc ||
			    qp->count.ic != qp->dq.ic ||
			    qp->count.rc != qp->dq.rc) {
				if (!sflag) {
					dbprintf(_("%s quota id %u, have/exp"),
						s, qp->id);
					if (qp->count.bc != qp->dq.bc)
						dbprintf(_(" bc %lld/%lld"),
							qp->dq.bc,
							qp->count.bc);
					if (qp->count.ic != qp->dq.ic)
						dbprintf(_(" ic %lld/%lld"),
							qp->dq.ic,
							qp->count.ic);
					if (qp->count.rc != qp->dq.rc)
						dbprintf(_(" rc %lld/%lld"),
							qp->dq.rc,
							qp->count.rc);
					dbprintf("\n");
				}
				error++;
			}
			xfree(qp);
			qp = next;
		}
	}
	xfree(qt);
}

static void
quota_init(void)
{
	qudo = mp->m_sb.sb_uquotino != 0 &&
	       mp->m_sb.sb_uquotino != NULLFSINO &&
	       (mp->m_sb.sb_qflags & XFS_UQUOTA_ACCT) &&
	       (mp->m_sb.sb_qflags & XFS_UQUOTA_CHKD);
	qgdo = mp->m_sb.sb_gquotino != 0 &&
	       mp->m_sb.sb_gquotino != NULLFSINO &&
	       (mp->m_sb.sb_qflags & XFS_GQUOTA_ACCT) &&
	       (mp->m_sb.sb_qflags & XFS_GQUOTA_CHKD);
	qpdo = mp->m_sb.sb_pquotino != 0 &&
	       mp->m_sb.sb_pquotino != NULLFSINO &&
	       (mp->m_sb.sb_qflags & XFS_PQUOTA_ACCT) &&
	       (mp->m_sb.sb_qflags & XFS_PQUOTA_CHKD);
	if (qudo)
		qudata = xcalloc(QDATA_HASH_SIZE, sizeof(qdata_t *));
	if (qgdo)
		qgdata = xcalloc(QDATA_HASH_SIZE, sizeof(qdata_t *));
	if (qpdo)
		qpdata = xcalloc(QDATA_HASH_SIZE, sizeof(qdata_t *));
}

static void
scan_ag(
	xfs_agnumber_t	agno)
{
	xfs_agf_t	*agf;
	xfs_agi_t	*agi;
	int		i;
	xfs_sb_t	tsb;
	xfs_sb_t	*sb = &tsb;

	agffreeblks = agflongest = 0;
	agfbtreeblks = -2;
	agicount = agifreecount = 0;
	push_cur();	/* 1 pushed */
	set_cur(&typtab[TYP_SB],
		XFS_AG_DADDR(mp, agno, XFS_SB_DADDR),
		XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);

	if (!iocur_top->data) {
		dbprintf(_("can't read superblock for ag %u\n"), agno);
		serious_error++;
		goto pop1_out;
	}

	libxfs_sb_from_disk(sb, iocur_top->data);

	if (sb->sb_magicnum != XFS_SB_MAGIC) {
		if (!sflag)
			dbprintf(_("bad sb magic # %#x in ag %u\n"),
				sb->sb_magicnum, agno);
		error++;
	}
	if (!xfs_sb_good_version(sb)) {
		if (!sflag)
			dbprintf(_("bad sb version # %#x in ag %u\n"),
				sb->sb_versionnum, agno);
		error++;
		sbver_err++;
	}
	if (!lazycount && xfs_sb_version_haslazysbcount(sb)) {
		lazycount = 1;
	}
	if (agno == 0 && sb->sb_inprogress != 0) {
		if (!sflag)
			dbprintf(_("mkfs not completed successfully\n"));
		error++;
	}
	set_dbmap(agno, XFS_SB_BLOCK(mp), 1, DBM_SB, agno, XFS_SB_BLOCK(mp));
	if (sb->sb_logstart && XFS_FSB_TO_AGNO(mp, sb->sb_logstart) == agno)
		set_dbmap(agno, XFS_FSB_TO_AGBNO(mp, sb->sb_logstart),
			sb->sb_logblocks, DBM_LOG, agno, XFS_SB_BLOCK(mp));
	push_cur();	/* 2 pushed */
	set_cur(&typtab[TYP_AGF],
		XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)),
		XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	if ((agf = iocur_top->data) == NULL) {
		dbprintf(_("can't read agf block for ag %u\n"), agno);
		serious_error++;
		goto pop2_out;
	}
	if (be32_to_cpu(agf->agf_magicnum) != XFS_AGF_MAGIC) {
		if (!sflag)
			dbprintf(_("bad agf magic # %#x in ag %u\n"),
				be32_to_cpu(agf->agf_magicnum), agno);
		error++;
	}
	if (!XFS_AGF_GOOD_VERSION(be32_to_cpu(agf->agf_versionnum))) {
		if (!sflag)
			dbprintf(_("bad agf version # %#x in ag %u\n"),
				be32_to_cpu(agf->agf_versionnum), agno);
		error++;
	}
	if (XFS_SB_BLOCK(mp) != XFS_AGF_BLOCK(mp))
		set_dbmap(agno, XFS_AGF_BLOCK(mp), 1, DBM_AGF, agno,
			XFS_SB_BLOCK(mp));
	if (sb->sb_agblocks > be32_to_cpu(agf->agf_length))
		set_dbmap(agno, be32_to_cpu(agf->agf_length),
			sb->sb_agblocks - be32_to_cpu(agf->agf_length),
			DBM_MISSING, agno, XFS_SB_BLOCK(mp));
	push_cur();	/* 3 pushed */
	set_cur(&typtab[TYP_AGI],
		XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)),
		XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	if ((agi = iocur_top->data) == NULL) {
		dbprintf(_("can't read agi block for ag %u\n"), agno);
		serious_error++;
		goto pop3_out;
	}
	if (be32_to_cpu(agi->agi_magicnum) != XFS_AGI_MAGIC) {
		if (!sflag)
			dbprintf(_("bad agi magic # %#x in ag %u\n"),
				be32_to_cpu(agi->agi_magicnum), agno);
		error++;
	}
	if (!XFS_AGI_GOOD_VERSION(be32_to_cpu(agi->agi_versionnum))) {
		if (!sflag)
			dbprintf(_("bad agi version # %#x in ag %u\n"),
				be32_to_cpu(agi->agi_versionnum), agno);
		error++;
	}
	if (XFS_SB_BLOCK(mp) != XFS_AGI_BLOCK(mp) &&
	    XFS_AGF_BLOCK(mp) != XFS_AGI_BLOCK(mp))
		set_dbmap(agno, XFS_AGI_BLOCK(mp), 1, DBM_AGI, agno,
			XFS_SB_BLOCK(mp));
	scan_freelist(agf);
	fdblocks--;
	scan_sbtree(agf,
		be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]),
		be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]),
		1, scanfunc_bno, TYP_BNOBT);
	fdblocks--;
	scan_sbtree(agf,
		be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]),
		be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]),
		1, scanfunc_cnt, TYP_CNTBT);
	scan_sbtree(agf,
		be32_to_cpu(agi->agi_root),
		be32_to_cpu(agi->agi_level),
		1, scanfunc_ino, TYP_INOBT);
	if (agi->agi_free_root) {
		scan_sbtree(agf,
			be32_to_cpu(agi->agi_free_root),
			be32_to_cpu(agi->agi_free_level),
			1, scanfunc_fino, TYP_FINOBT);
	}
	if (be32_to_cpu(agf->agf_freeblks) != agffreeblks) {
		if (!sflag)
			dbprintf(_("agf_freeblks %u, counted %u in ag %u\n"),
				be32_to_cpu(agf->agf_freeblks),
				agffreeblks, agno);
		error++;
	}
	if (be32_to_cpu(agf->agf_longest) != agflongest) {
		if (!sflag)
			dbprintf(_("agf_longest %u, counted %u in ag %u\n"),
				be32_to_cpu(agf->agf_longest),
				agflongest, agno);
		error++;
	}
	if (lazycount &&
	    be32_to_cpu(agf->agf_btreeblks) != agfbtreeblks) {
		if (!sflag)
			dbprintf(_("agf_btreeblks %u, counted %u in ag %u\n"),
				be32_to_cpu(agf->agf_btreeblks),
				agfbtreeblks, agno);
		error++;
	}
	agf_aggr_freeblks += agffreeblks + agfbtreeblks;
	if (be32_to_cpu(agi->agi_count) != agicount) {
		if (!sflag)
			dbprintf(_("agi_count %u, counted %u in ag %u\n"),
				be32_to_cpu(agi->agi_count),
				agicount, agno);
		error++;
	}
	if (be32_to_cpu(agi->agi_freecount) != agifreecount) {
		if (!sflag)
			dbprintf(_("agi_freecount %u, counted %u in ag %u\n"),
				be32_to_cpu(agi->agi_freecount),
				agifreecount, agno);
		error++;
	}
	for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
		if (be32_to_cpu(agi->agi_unlinked[i]) != NULLAGINO) {
			if (!sflag) {
				xfs_agino_t agino=be32_to_cpu(agi->agi_unlinked[i]);
				dbprintf(_("agi unlinked bucket %d is %u in ag "
					 "%u (inode=%lld)\n"), i, agino, agno,
					XFS_AGINO_TO_INO(mp, agno, agino));
			}
			error++;
		}
	}
pop3_out:
	pop_cur();
pop2_out:
	pop_cur();
pop1_out:
	pop_cur();
}

static void
scan_freelist(
	xfs_agf_t	*agf)
{
	xfs_agnumber_t	seqno = be32_to_cpu(agf->agf_seqno);
	xfs_agfl_t	*agfl;
	xfs_agblock_t	bno;
	uint		count;
	int		i;
	__be32		*freelist;

	if (XFS_SB_BLOCK(mp) != XFS_AGFL_BLOCK(mp) &&
	    XFS_AGF_BLOCK(mp) != XFS_AGFL_BLOCK(mp) &&
	    XFS_AGI_BLOCK(mp) != XFS_AGFL_BLOCK(mp))
		set_dbmap(seqno, XFS_AGFL_BLOCK(mp), 1, DBM_AGFL, seqno,
			XFS_SB_BLOCK(mp));
	if (be32_to_cpu(agf->agf_flcount) == 0)
		return;
	push_cur();
	set_cur(&typtab[TYP_AGFL],
		XFS_AG_DADDR(mp, seqno, XFS_AGFL_DADDR(mp)),
		XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	if ((agfl = iocur_top->data) == NULL) {
		dbprintf(_("can't read agfl block for ag %u\n"), seqno);
		serious_error++;
		pop_cur();
		return;
	}
	i = be32_to_cpu(agf->agf_flfirst);

	/* verify agf values before proceeding */
	if (be32_to_cpu(agf->agf_flfirst) >= XFS_AGFL_SIZE(mp) ||
	    be32_to_cpu(agf->agf_fllast) >= XFS_AGFL_SIZE(mp)) {
		dbprintf(_("agf %d freelist blocks bad, skipping "
			  "freelist scan\n"), i);
		pop_cur();
		return;
	}

	/* open coded XFS_BUF_TO_AGFL_BNO */
	freelist = xfs_sb_version_hascrc(&((mp)->m_sb)) ? &agfl->agfl_bno[0]
							: (__be32 *)agfl;
	count = 0;
	for (;;) {
		bno = be32_to_cpu(freelist[i]);
		set_dbmap(seqno, bno, 1, DBM_FREELIST, seqno,
			XFS_AGFL_BLOCK(mp));
		count++;
		if (i == be32_to_cpu(agf->agf_fllast))
			break;
		if (++i == XFS_AGFL_SIZE(mp))
			i = 0;
	}
	if (count != be32_to_cpu(agf->agf_flcount)) {
		if (!sflag)
			dbprintf(_("freeblk count %u != flcount %u in ag %u\n"),
				count, be32_to_cpu(agf->agf_flcount),
				seqno);
		error++;
	}
	fdblocks += count;
	agf_aggr_freeblks += count;
	pop_cur();
}

static void
scan_lbtree(
	xfs_fsblock_t	root,
	int		nlevels,
	scan_lbtree_f_t	func,
	dbm_t		type,
	inodata_t	*id,
	xfs_rfsblock_t	*totd,
	xfs_rfsblock_t	*toti,
	xfs_extnum_t	*nex,
	blkmap_t	**blkmapp,
	int		isroot,
	typnm_t		btype)
{
	push_cur();
	set_cur(&typtab[btype], XFS_FSB_TO_DADDR(mp, root), blkbb, DB_RING_IGN,
		NULL);
	if (iocur_top->data == NULL) {
		if (!sflag)
			dbprintf(_("can't read btree block %u/%u\n"),
				XFS_FSB_TO_AGNO(mp, root),
				XFS_FSB_TO_AGBNO(mp, root));
		error++;
		pop_cur();
		return;
	}
	(*func)(iocur_top->data, nlevels - 1, type, root, id, totd, toti, nex,
		blkmapp, isroot, btype);
	pop_cur();
}

static void
scan_sbtree(
	xfs_agf_t	*agf,
	xfs_agblock_t	root,
	int		nlevels,
	int		isroot,
	scan_sbtree_f_t	func,
	typnm_t		btype)
{
	xfs_agnumber_t	seqno = be32_to_cpu(agf->agf_seqno);

	push_cur();
	set_cur(&typtab[btype],
		XFS_AGB_TO_DADDR(mp, seqno, root), blkbb, DB_RING_IGN, NULL);
	if (iocur_top->data == NULL) {
		if (!sflag)
			dbprintf(_("can't read btree block %u/%u\n"), seqno, root);
		error++;
		pop_cur();
		return;
	}
	(*func)(iocur_top->data, nlevels - 1, agf, root, isroot);
	pop_cur();
}

static void
scanfunc_bmap(
	struct xfs_btree_block	*block,
	int			level,
	dbm_t			type,
	xfs_fsblock_t		bno,
	inodata_t		*id,
	xfs_rfsblock_t		*totd,
	xfs_rfsblock_t		*toti,
	xfs_extnum_t		*nex,
	blkmap_t		**blkmapp,
	int			isroot,
	typnm_t			btype)
{
	xfs_agblock_t		agbno;
	xfs_agnumber_t		agno;
	int			i;
	xfs_bmbt_ptr_t		*pp;
	xfs_bmbt_rec_t		*rp;

	agno = XFS_FSB_TO_AGNO(mp, bno);
	agbno = XFS_FSB_TO_AGBNO(mp, bno);
	if (be32_to_cpu(block->bb_magic) != XFS_BMAP_MAGIC &&
	    be32_to_cpu(block->bb_magic) != XFS_BMAP_CRC_MAGIC) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("bad magic # %#x in inode %lld bmbt block "
				 "%u/%u\n"),
				be32_to_cpu(block->bb_magic), id->ino, agno, agbno);
		error++;
	}
	if (be16_to_cpu(block->bb_level) != level) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("expected level %d got %d in inode %lld bmbt "
				 "block %u/%u\n"),
				level, be16_to_cpu(block->bb_level), id->ino, agno, agbno);
		error++;
	}
	set_dbmap(agno, agbno, 1, type, agno, agbno);
	set_inomap(agno, agbno, 1, id);
	(*toti)++;
	if (level == 0) {
		if (be16_to_cpu(block->bb_numrecs) > mp->m_bmap_dmxr[0] ||
		    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_bmap_dmnr[0])) {
			if (!sflag || id->ilist || CHECK_BLIST(bno))
				dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) "
					 "in inode %lld bmap block %lld\n"),
					be16_to_cpu(block->bb_numrecs), mp->m_bmap_dmnr[0],
					mp->m_bmap_dmxr[0], id->ino,
					(xfs_fsblock_t)bno);
			error++;
			return;
		}
		rp = XFS_BMBT_REC_ADDR(mp, block, 1);
		*nex += be16_to_cpu(block->bb_numrecs);
		process_bmbt_reclist(rp, be16_to_cpu(block->bb_numrecs), type, id, totd,
			blkmapp);
		return;
	}
	if (be16_to_cpu(block->bb_numrecs) > mp->m_bmap_dmxr[1] ||
	    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_bmap_dmnr[1])) {
		if (!sflag || id->ilist || CHECK_BLIST(bno))
			dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in "
				 "inode %lld bmap block %lld\n"),
				be16_to_cpu(block->bb_numrecs), mp->m_bmap_dmnr[1],
				mp->m_bmap_dmxr[1], id->ino, (xfs_fsblock_t)bno);
		error++;
		return;
	}
	pp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[0]);
	for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
		scan_lbtree(be64_to_cpu(pp[i]), level, scanfunc_bmap, type, id, 
					totd, toti, nex, blkmapp, 0, btype);
}

static void
scanfunc_bno(
	struct xfs_btree_block	*block,
	int			level,
	xfs_agf_t		*agf,
	xfs_agblock_t		bno,
	int			isroot)
{
	int			i;
	xfs_alloc_ptr_t		*pp;
	xfs_alloc_rec_t		*rp;
	xfs_agnumber_t		seqno = be32_to_cpu(agf->agf_seqno);
	xfs_agblock_t		lastblock;

	if (be32_to_cpu(block->bb_magic) != XFS_ABTB_MAGIC &&
	    be32_to_cpu(block->bb_magic) != XFS_ABTB_CRC_MAGIC) {
		dbprintf(_("bad magic # %#x in btbno block %u/%u\n"),
			be32_to_cpu(block->bb_magic), seqno, bno);
		serious_error++;
		return;
	}
	fdblocks++;
	agfbtreeblks++;
	if (be16_to_cpu(block->bb_level) != level) {
		if (!sflag)
			dbprintf(_("expected level %d got %d in btbno block "
				 "%u/%u\n"),
				level, be16_to_cpu(block->bb_level), seqno, bno);
		error++;
	}
	set_dbmap(seqno, bno, 1, DBM_BTBNO, seqno, bno);
	if (level == 0) {
		if (be16_to_cpu(block->bb_numrecs) > mp->m_alloc_mxr[0] ||
		    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_alloc_mnr[0])) {
			dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in "
				 "btbno block %u/%u\n"),
				be16_to_cpu(block->bb_numrecs), mp->m_alloc_mnr[0],
				mp->m_alloc_mxr[0], seqno, bno);
			serious_error++;
			return;
		}
		rp = XFS_ALLOC_REC_ADDR(mp, block, 1);
		lastblock = 0;
		for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) {
			set_dbmap(seqno, be32_to_cpu(rp[i].ar_startblock),
				be32_to_cpu(rp[i].ar_blockcount), DBM_FREE1,
				seqno, bno);
			if (be32_to_cpu(rp[i].ar_startblock) <= lastblock) {
				dbprintf(_(
		"out-of-order bno btree record %d (%u %u) block %u/%u\n"),
				i, be32_to_cpu(rp[i].ar_startblock),
				be32_to_cpu(rp[i].ar_blockcount),
				be32_to_cpu(agf->agf_seqno), bno);
				serious_error++;
			} else {
				lastblock = be32_to_cpu(rp[i].ar_startblock);
			}
		}
		return;
	}
	if (be16_to_cpu(block->bb_numrecs) > mp->m_alloc_mxr[1] ||
	    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_alloc_mnr[1])) {
		dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in btbno block "
			 "%u/%u\n"),
			be16_to_cpu(block->bb_numrecs), mp->m_alloc_mnr[1],
			mp->m_alloc_mxr[1], seqno, bno);
		serious_error++;
		return;
	}
	pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
	for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
		scan_sbtree(agf, be32_to_cpu(pp[i]), level, 0, scanfunc_bno, TYP_BNOBT);
}

static void
scanfunc_cnt(
	struct xfs_btree_block	*block,
	int			level,
	xfs_agf_t		*agf,
	xfs_agblock_t		bno,
	int			isroot)
{
	xfs_agnumber_t		seqno = be32_to_cpu(agf->agf_seqno);
	int			i;
	xfs_alloc_ptr_t		*pp;
	xfs_alloc_rec_t		*rp;
	xfs_extlen_t		lastcount;

	if (be32_to_cpu(block->bb_magic) != XFS_ABTC_MAGIC &&
	    be32_to_cpu(block->bb_magic) != XFS_ABTC_CRC_MAGIC) {
		dbprintf(_("bad magic # %#x in btcnt block %u/%u\n"),
			be32_to_cpu(block->bb_magic), seqno, bno);
		serious_error++;
		return;
	}
	fdblocks++;
	agfbtreeblks++;
	if (be16_to_cpu(block->bb_level) != level) {
		if (!sflag)
			dbprintf(_("expected level %d got %d in btcnt block "
				 "%u/%u\n"),
				level, be16_to_cpu(block->bb_level), seqno, bno);
		error++;
	}
	set_dbmap(seqno, bno, 1, DBM_BTCNT, seqno, bno);
	if (level == 0) {
		if (be16_to_cpu(block->bb_numrecs) > mp->m_alloc_mxr[0] ||
		    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_alloc_mnr[0])) {
			dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in "
				 "btbno block %u/%u\n"),
				be16_to_cpu(block->bb_numrecs), mp->m_alloc_mnr[0],
				mp->m_alloc_mxr[0], seqno, bno);
			serious_error++;
			return;
		}
		rp = XFS_ALLOC_REC_ADDR(mp, block, 1);
		lastcount = 0;
		for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) {
			check_set_dbmap(seqno, be32_to_cpu(rp[i].ar_startblock),
				be32_to_cpu(rp[i].ar_blockcount), DBM_FREE1, DBM_FREE2,
				seqno, bno);
			fdblocks += be32_to_cpu(rp[i].ar_blockcount);
			agffreeblks += be32_to_cpu(rp[i].ar_blockcount);
			if (be32_to_cpu(rp[i].ar_blockcount) > agflongest)
				agflongest = be32_to_cpu(rp[i].ar_blockcount);
			if (be32_to_cpu(rp[i].ar_blockcount) < lastcount) {
				dbprintf(_(
		"out-of-order cnt btree record %d (%u %u) block %u/%u\n"),
					 i, be32_to_cpu(rp[i].ar_startblock),
					 be32_to_cpu(rp[i].ar_blockcount),
					 be32_to_cpu(agf->agf_seqno), bno);
			} else {
				lastcount = be32_to_cpu(rp[i].ar_blockcount);
			}
		}
		return;
	}
	if (be16_to_cpu(block->bb_numrecs) > mp->m_alloc_mxr[1] ||
	    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_alloc_mnr[1])) {
		dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in btbno block "
			 "%u/%u\n"),
			be16_to_cpu(block->bb_numrecs), mp->m_alloc_mnr[1],
			mp->m_alloc_mxr[1], seqno, bno);
		serious_error++;
		return;
	}
	pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
	for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
		scan_sbtree(agf, be32_to_cpu(pp[i]), level, 0, scanfunc_cnt, TYP_CNTBT);
}

static void
scanfunc_ino(
	struct xfs_btree_block	*block,
	int			level,
	xfs_agf_t		*agf,
	xfs_agblock_t		bno,
	int			isroot)
{
	xfs_agino_t		agino;
	xfs_agnumber_t		seqno = be32_to_cpu(agf->agf_seqno);
	int			i;
	int			isfree;
	int			j;
	int			nfree;
	int			off;
	xfs_inobt_ptr_t		*pp;
	xfs_inobt_rec_t		*rp;

	if (be32_to_cpu(block->bb_magic) != XFS_IBT_MAGIC &&
	    be32_to_cpu(block->bb_magic) != XFS_IBT_CRC_MAGIC) {
		dbprintf(_("bad magic # %#x in inobt block %u/%u\n"),
			be32_to_cpu(block->bb_magic), seqno, bno);
		serious_error++;
		return;
	}
	if (be16_to_cpu(block->bb_level) != level) {
		if (!sflag)
			dbprintf(_("expected level %d got %d in inobt block "
				 "%u/%u\n"),
				level, be16_to_cpu(block->bb_level), seqno, bno);
		error++;
	}
	set_dbmap(seqno, bno, 1, DBM_BTINO, seqno, bno);
	if (level == 0) {
		if (be16_to_cpu(block->bb_numrecs) > mp->m_inobt_mxr[0] ||
		    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_inobt_mnr[0])) {
			dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in "
				 "inobt block %u/%u\n"),
				be16_to_cpu(block->bb_numrecs), mp->m_inobt_mnr[0],
				mp->m_inobt_mxr[0], seqno, bno);
			serious_error++;
			return;
		}
		rp = XFS_INOBT_REC_ADDR(mp, block, 1);
		for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) {
			agino = be32_to_cpu(rp[i].ir_startino);
			off = XFS_INO_TO_OFFSET(mp, agino);
			if (off == 0) {
				if ((sbversion & XFS_SB_VERSION_ALIGNBIT) &&
				    mp->m_sb.sb_inoalignmt &&
				    (XFS_INO_TO_AGBNO(mp, agino) %
				     mp->m_sb.sb_inoalignmt))
					sbversion &= ~XFS_SB_VERSION_ALIGNBIT;
				set_dbmap(seqno, XFS_AGINO_TO_AGBNO(mp, agino),
					(xfs_extlen_t)MAX(1,
						XFS_INODES_PER_CHUNK >>
						mp->m_sb.sb_inopblog),
					DBM_INODE, seqno, bno);
			}
			icount += XFS_INODES_PER_CHUNK;
			agicount += XFS_INODES_PER_CHUNK;
			ifree += be32_to_cpu(rp[i].ir_u.f.ir_freecount);
			agifreecount += be32_to_cpu(rp[i].ir_u.f.ir_freecount);
			push_cur();
			set_cur(&typtab[TYP_INODE],
				XFS_AGB_TO_DADDR(mp, seqno,
						 XFS_AGINO_TO_AGBNO(mp, agino)),
				(int)XFS_FSB_TO_BB(mp, mp->m_ialloc_blks),
				DB_RING_IGN, NULL);
			if (iocur_top->data == NULL) {
				if (!sflag)
					dbprintf(_("can't read inode block "
						 "%u/%u\n"),
						seqno,
						XFS_AGINO_TO_AGBNO(mp, agino));
				error++;
				pop_cur();
				continue;
			}
			for (j = 0, nfree = 0; j < XFS_INODES_PER_CHUNK; j++) {
				isfree = XFS_INOBT_IS_FREE_DISK(&rp[i], j);
				if (isfree)
					nfree++;
				process_inode(agf, agino + j,
					(xfs_dinode_t *)((char *)iocur_top->data + ((off + j) << mp->m_sb.sb_inodelog)),
						isfree);
			}
			if (nfree != be32_to_cpu(rp[i].ir_u.f.ir_freecount)) {
				if (!sflag)
					dbprintf(_("ir_freecount/free mismatch, "
						 "inode chunk %u/%u, freecount "
						 "%d nfree %d\n"),
						seqno, agino,
						be32_to_cpu(rp[i].ir_u.f.ir_freecount), nfree);
				error++;
			}
			pop_cur();
		}
		return;
	}
	if (be16_to_cpu(block->bb_numrecs) > mp->m_inobt_mxr[1] ||
	    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_inobt_mnr[1])) {
		dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in inobt block "
			 "%u/%u\n"),
			be16_to_cpu(block->bb_numrecs), mp->m_inobt_mnr[1],
			mp->m_inobt_mxr[1], seqno, bno);
		serious_error++;
		return;
	}
	pp = XFS_INOBT_PTR_ADDR(mp, block, 1, mp->m_inobt_mxr[1]);
	for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
		scan_sbtree(agf, be32_to_cpu(pp[i]), level, 0, scanfunc_ino, TYP_INOBT);
}

static void
scanfunc_fino(
	struct xfs_btree_block	*block,
	int			level,
	struct xfs_agf		*agf,
	xfs_agblock_t		bno,
	int			isroot)
{
	xfs_agino_t		agino;
	xfs_agnumber_t		seqno = be32_to_cpu(agf->agf_seqno);
	int			i;
	int			off;
	xfs_inobt_ptr_t		*pp;
	struct xfs_inobt_rec	*rp;

	if (be32_to_cpu(block->bb_magic) != XFS_FIBT_MAGIC &&
	    be32_to_cpu(block->bb_magic) != XFS_FIBT_CRC_MAGIC) {
		dbprintf(_("bad magic # %#x in finobt block %u/%u\n"),
			be32_to_cpu(block->bb_magic), seqno, bno);
		serious_error++;
		return;
	}
	if (be16_to_cpu(block->bb_level) != level) {
		if (!sflag)
			dbprintf(_("expected level %d got %d in finobt block "
				 "%u/%u\n"),
				level, be16_to_cpu(block->bb_level), seqno, bno);
		error++;
	}
	set_dbmap(seqno, bno, 1, DBM_BTFINO, seqno, bno);
	if (level == 0) {
		if (be16_to_cpu(block->bb_numrecs) > mp->m_inobt_mxr[0] ||
		    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_inobt_mnr[0])) {
			dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in "
				 "finobt block %u/%u\n"),
				be16_to_cpu(block->bb_numrecs), mp->m_inobt_mnr[0],
				mp->m_inobt_mxr[0], seqno, bno);
			serious_error++;
			return;
		}
		rp = XFS_INOBT_REC_ADDR(mp, block, 1);
		for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) {
			agino = be32_to_cpu(rp[i].ir_startino);
			off = XFS_INO_TO_OFFSET(mp, agino);
			if (off == 0) {
				if ((sbversion & XFS_SB_VERSION_ALIGNBIT) &&
				    mp->m_sb.sb_inoalignmt &&
				    (XFS_INO_TO_AGBNO(mp, agino) %
				     mp->m_sb.sb_inoalignmt))
					sbversion &= ~XFS_SB_VERSION_ALIGNBIT;
				check_set_dbmap(seqno, XFS_AGINO_TO_AGBNO(mp, agino),
					(xfs_extlen_t)MAX(1,
						XFS_INODES_PER_CHUNK >>
						mp->m_sb.sb_inopblog),
					DBM_INODE, DBM_INODE, seqno, bno);
			}
		}
		return;
	}
	if (be16_to_cpu(block->bb_numrecs) > mp->m_inobt_mxr[1] ||
	    (isroot == 0 && be16_to_cpu(block->bb_numrecs) < mp->m_inobt_mnr[1])) {
		dbprintf(_("bad btree nrecs (%u, min=%u, max=%u) in finobt block "
			 "%u/%u\n"),
			be16_to_cpu(block->bb_numrecs), mp->m_inobt_mnr[1],
			mp->m_inobt_mxr[1], seqno, bno);
		serious_error++;
		return;
	}
	pp = XFS_INOBT_PTR_ADDR(mp, block, 1, mp->m_inobt_mxr[1]);
	for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
		scan_sbtree(agf, be32_to_cpu(pp[i]), level, 0, scanfunc_fino, TYP_FINOBT);
}

static void
set_dbmap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	dbm_t		type,
	xfs_agnumber_t	c_agno,
	xfs_agblock_t	c_agbno)
{
	check_set_dbmap(agno, agbno, len, DBM_UNKNOWN, type, c_agno, c_agbno);
}

static void
set_inomap(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	xfs_extlen_t	len,
	inodata_t	*id)
{
	xfs_extlen_t	i;
	inodata_t	**idp;
	int		mayprint;

	if (!check_inomap(agno, agbno, len, id->ino))
		return;
	mayprint = verbose | id->ilist | blist_size;
	for (i = 0, idp = &inomap[agno][agbno]; i < len; i++, idp++) {
		*idp = id;
		if (mayprint &&
		    (verbose || id->ilist || CHECK_BLISTA(agno, agbno + i)))
			dbprintf(_("setting inode to %lld for block %u/%u\n"),
				id->ino, agno, agbno + i);
	}
}

static void
set_rdbmap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	dbm_t		type)
{
	check_set_rdbmap(bno, len, DBM_UNKNOWN, type);
}

static void
set_rinomap(
	xfs_rfsblock_t	bno,
	xfs_extlen_t	len,
	inodata_t	*id)
{
	xfs_extlen_t	i;
	inodata_t	**idp;
	int		mayprint;

	if (!check_rinomap(bno, len, id->ino))
		return;
	mayprint = verbose | id->ilist | blist_size;
	for (i = 0, idp = &inomap[mp->m_sb.sb_agcount][bno];
	     i < len;
	     i++, idp++) {
		*idp = id;
		if (mayprint && (verbose || id->ilist || CHECK_BLIST(bno + i)))
			dbprintf(_("setting inode to %lld for rtblock %llu\n"),
				id->ino, bno + i);
	}
}

static void
setlink_inode(
	inodata_t	*id,
	nlink_t		nlink,
	int		isdir,
	int		security)
{
	id->link_set = nlink;
	id->isdir = isdir;
	id->security = security;
	if (verbose || id->ilist)
		dbprintf(_("inode %lld nlink %u %s dir\n"), id->ino, nlink,
			isdir ? "is" : "not");
}
