// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2007, 2011 SGI
 * All Rights Reserved.
 */

#include "libxfs.h"
#include "libxlog.h"
#include "bmap.h"
#include "command.h"
#include "metadump.h"
#include "io.h"
#include "output.h"
#include "type.h"
#include "init.h"
#include "sig.h"
#include "xfs_metadump.h"
#include "fprint.h"
#include "faddr.h"
#include "field.h"
#include "dir2.h"
#include "obfuscate.h"

#undef REMAP_DEBUG

#ifdef REMAP_DEBUG
# define remap_debug		printf
#else
# define remap_debug(...)	((void)0)
#endif

#define DEFAULT_MAX_EXT_SIZE	XFS_MAX_BMBT_EXTLEN

/* copy all metadata structures to/from a file */

static int	metadump_f(int argc, char **argv);
static void	metadump_help(void);

/*
 * metadump commands issue info/wornings/errors to standard error as
 * metadump supports stdout as a destination.
 *
 * All static functions return zero on failure, while the public functions
 * return zero on success.
 */

static const cmdinfo_t	metadump_cmd =
	{ "metadump", NULL, metadump_f, 0, -1, 0,
		N_("[-a] [-e] [-g] [-m max_extent] [-w] [-o] [-v 1|2] filename"),
		N_("dump metadata to a file"), metadump_help };

struct metadump_ops {
	/*
	 * Initialize Metadump. This may perform actions such as
	 * 1. Allocating memory for structures required for dumping the
	 *    metadata.
	 * 2. Writing a header to the beginning of the metadump file.
	 */
	int (*init)(void);
	/*
	 * Write metadata to the metadump file along with the required ancillary
	 * data. @off and @len are in units of 512 byte blocks.
	 */
	int (*write)(enum typnm type, const char *data, xfs_daddr_t off,
			int len);
	/*
	 * Flush any in-memory remanents of metadata to the metadump file.
	 */
	int (*finish_dump)(void);
	/*
	 * Free resources allocated during metadump process.
	 */
	void (*release)(void);
};

static struct metadump {
	int			version;
	bool			show_progress;
	bool			stop_on_read_error;
	int			max_extent_size;
	bool			show_warnings;
	bool			obfuscate;
	bool			zero_stale_data;
	bool			progress_since_warning;
	bool			dirty_log;
	bool			external_log;
	bool			stdout_metadump;
	xfs_ino_t		cur_ino;
	/* Metadump file */
	FILE			*outf;
	struct metadump_ops	*mdops;
	/* header + index + buffers */
	struct xfs_metablock	*metablock;
	__be64			*block_index;
	char			*block_buffer;
	int			num_indices;
	int			cur_index;
} metadump;

void
metadump_init(void)
{
	add_command(&metadump_cmd);
}

static void
metadump_help(void)
{
	dbprintf(_(
"\n"
" The 'metadump' command dumps the known metadata to a compact file suitable\n"
" for compressing and sending to an XFS maintainer for corruption analysis \n"
" or xfs_repair failures.\n\n"
" Options:\n"
"   -a -- Copy full metadata blocks without zeroing unused space\n"
"   -e -- Ignore read errors and keep going\n"
"   -g -- Display dump progress\n"
"   -m -- Specify max extent size in blocks to copy (default = %d blocks)\n"
"   -o -- Don't obfuscate names and extended attributes\n"
"   -v -- Metadump version to be used\n"
"   -w -- Show warnings of bad metadata information\n"
"\n"), DEFAULT_MAX_EXT_SIZE);
}

static void
print_warning(const char *fmt, ...)
{
	char		buf[200];
	va_list		ap;

	if (seenint())
		return;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	buf[sizeof(buf)-1] = '\0';

	fprintf(stderr, "%s%s: %s\n",
			metadump.progress_since_warning ? "\n" : "",
			progname, buf);
	metadump.progress_since_warning = false;
}

static void
print_progress(const char *fmt, ...)
{
	char		buf[60];
	va_list		ap;
	FILE		*f;

	if (seenint())
		return;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	buf[sizeof(buf)-1] = '\0';

	f = metadump.stdout_metadump ? stderr : stdout;
	fprintf(f, "\r%-59s", buf);
	fflush(f);
	metadump.progress_since_warning = true;
}

/*
 * we want to preserve the state of the metadata in the dump - whether it is
 * intact or corrupt, so even if the buffer has a verifier attached to it we
 * don't want to run it prior to writing the buffer to the metadump image.
 *
 * The only reason for running the verifier is to recalculate the CRCs on a
 * buffer that has been obfuscated. i.e. a buffer than metadump modified itself.
 * In this case, we only run the verifier if the buffer was not corrupt to begin
 * with so that we don't accidentally correct buffers with CRC or errors in them
 * when we are obfuscating them.
 */
static int
write_buf(
	iocur_t		*buf)
{
	struct xfs_buf	*bp = buf->bp;
	int		i;
	int		ret;

	/*
	 * Run the write verifier to recalculate the buffer CRCs and check
	 * metadump didn't introduce a new corruption. Warn if the verifier
	 * failed, but still continue to dump it into the output file.
	 */
	if (buf->need_crc && bp && bp->b_ops && !bp->b_error) {
		bp->b_ops->verify_write(bp);
		if (bp->b_error) {
			print_warning(
			    "obfuscation corrupted block at %s bno 0x%llx/0x%x",
				bp->b_ops->name,
				(long long)xfs_buf_daddr(bp), BBTOB(bp->b_length));
		}
	}

	/* handle discontiguous buffers */
	if (!buf->bbmap) {
		ret = metadump.mdops->write(buf->typ->typnm, buf->data, buf->bb,
				buf->blen);
		if (ret)
			return ret;
	} else {
		int	len = 0;
		for (i = 0; i < buf->bbmap->nmaps; i++) {
			ret = metadump.mdops->write(buf->typ->typnm,
					buf->data + BBTOB(len),
					buf->bbmap->b[i].bm_bn,
					buf->bbmap->b[i].bm_len);
			if (ret)
				return ret;
			len += buf->bbmap->b[i].bm_len;
		}
	}
	return seenint() ? -EINTR : 0;
}

/*
 * We could be processing a corrupt block, so we can't trust any of
 * the offsets or lengths to be within the buffer range. Hence check
 * carefully!
 */
static void
zero_btree_node(
	struct xfs_btree_block	*block,
	typnm_t			btype)
{
	int			nrecs;
	xfs_bmbt_ptr_t		*bpp;
	xfs_bmbt_key_t		*bkp;
	xfs_inobt_ptr_t		*ipp;
	xfs_inobt_key_t		*ikp;
	xfs_alloc_ptr_t		*app;
	xfs_alloc_key_t		*akp;
	char			*zp1, *zp2;
	char			*key_end;
	struct xfs_ino_geometry	*igeo = M_IGEO(mp);

	nrecs = be16_to_cpu(block->bb_numrecs);
	if (nrecs < 0)
		return;

	switch (btype) {
	case TYP_BMAPBTA:
	case TYP_BMAPBTD:
		if (nrecs > mp->m_bmap_dmxr[1])
			return;

		bkp = XFS_BMBT_KEY_ADDR(mp, block, 1);
		bpp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[1]);
		zp1 = (char *)&bkp[nrecs];
		zp2 = (char *)&bpp[nrecs];
		key_end = (char *)bpp;
		break;
	case TYP_INOBT:
	case TYP_FINOBT:
		if (nrecs > igeo->inobt_mxr[1])
			return;

		ikp = XFS_INOBT_KEY_ADDR(mp, block, 1);
		ipp = XFS_INOBT_PTR_ADDR(mp, block, 1, igeo->inobt_mxr[1]);
		zp1 = (char *)&ikp[nrecs];
		zp2 = (char *)&ipp[nrecs];
		key_end = (char *)ipp;
		break;
	case TYP_BNOBT:
	case TYP_CNTBT:
		if (nrecs > mp->m_alloc_mxr[1])
			return;

		akp = XFS_ALLOC_KEY_ADDR(mp, block, 1);
		app = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
		zp1 = (char *)&akp[nrecs];
		zp2 = (char *)&app[nrecs];
		key_end = (char *)app;
		break;
	default:
		return;
	}


	/* Zero from end of keys to beginning of pointers */
	memset(zp1, 0, key_end - zp1);

	/* Zero from end of pointers to end of block */
	memset(zp2, 0, (char *)block + mp->m_sb.sb_blocksize - zp2);
}

/*
 * We could be processing a corrupt block, so we can't trust any of
 * the offsets or lengths to be within the buffer range. Hence check
 * carefully!
 */
static void
zero_btree_leaf(
	struct xfs_btree_block	*block,
	typnm_t			btype)
{
	int			nrecs;
	struct xfs_bmbt_rec	*brp;
	struct xfs_inobt_rec	*irp;
	struct xfs_alloc_rec	*arp;
	char			*zp;

	nrecs = be16_to_cpu(block->bb_numrecs);
	if (nrecs < 0)
		return;

	switch (btype) {
	case TYP_BMAPBTA:
	case TYP_BMAPBTD:
		if (nrecs > mp->m_bmap_dmxr[0])
			return;

		brp = XFS_BMBT_REC_ADDR(mp, block, 1);
		zp = (char *)&brp[nrecs];
		break;
	case TYP_INOBT:
	case TYP_FINOBT:
		if (nrecs > M_IGEO(mp)->inobt_mxr[0])
			return;

		irp = XFS_INOBT_REC_ADDR(mp, block, 1);
		zp = (char *)&irp[nrecs];
		break;
	case TYP_BNOBT:
	case TYP_CNTBT:
		if (nrecs > mp->m_alloc_mxr[0])
			return;

		arp = XFS_ALLOC_REC_ADDR(mp, block, 1);
		zp = (char *)&arp[nrecs];
		break;
	default:
		return;
	}

	/* Zero from end of records to end of block */
	memset(zp, 0, (char *)block + mp->m_sb.sb_blocksize - zp);
}

static void
zero_btree_block(
	struct xfs_btree_block	*block,
	typnm_t			btype)
{
	int			level;

	level = be16_to_cpu(block->bb_level);

	if (level > 0)
		zero_btree_node(block, btype);
	else
		zero_btree_leaf(block, btype);
}

static int
scan_btree(
	xfs_agnumber_t	agno,
	xfs_agblock_t	agbno,
	int		level,
	typnm_t		btype,
	void		*arg,
	int		(*func)(struct xfs_btree_block	*block,
				xfs_agnumber_t		agno,
				xfs_agblock_t		agbno,
				int			level,
				typnm_t			btype,
				void			*arg))
{
	int		rval = 0;

	push_cur();
	set_cur(&typtab[btype], XFS_AGB_TO_DADDR(mp, agno, agbno), blkbb,
			DB_RING_IGN, NULL);
	if (iocur_top->data == NULL) {
		print_warning("cannot read %s block %u/%u", typtab[btype].name,
				agno, agbno);
		rval = !metadump.stop_on_read_error;
		goto pop_out;
	}

	if (metadump.zero_stale_data) {
		zero_btree_block(iocur_top->data, btype);
		iocur_top->need_crc = 1;
	}

	if (write_buf(iocur_top))
		goto pop_out;

	if (!(*func)(iocur_top->data, agno, agbno, level - 1, btype, arg))
		goto pop_out;
	rval = 1;
pop_out:
	pop_cur();
	return rval;
}

/* free space tree copy routines */

static int
valid_bno(
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno)
{
	if (agno < (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
			agbno <= mp->m_sb.sb_agblocks)
		return 1;
	if (agno == (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
			agbno <= (mp->m_sb.sb_dblocks -
			 (xfs_rfsblock_t)(mp->m_sb.sb_agcount - 1) *
			 mp->m_sb.sb_agblocks))
		return 1;

	return 0;
}


static int
scanfunc_freesp(
	struct xfs_btree_block	*block,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno,
	int			level,
	typnm_t			btype,
	void			*arg)
{
	xfs_alloc_ptr_t		*pp;
	int			i;
	int			numrecs;

	if (level == 0)
		return 1;

	numrecs = be16_to_cpu(block->bb_numrecs);
	if (numrecs > mp->m_alloc_mxr[1]) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs (%u) in %s block %u/%u",
				numrecs, typtab[btype].name, agno, agbno);
		return 1;
	}

	pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
	for (i = 0; i < numrecs; i++) {
		if (!valid_bno(agno, be32_to_cpu(pp[i]))) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in %s block %u/%u",
					agno, be32_to_cpu(pp[i]),
					typtab[btype].name, agno, agbno);
			continue;
		}
		if (!scan_btree(agno, be32_to_cpu(pp[i]), level, btype, arg,
				scanfunc_freesp))
			return 0;
	}
	return 1;
}

static int
copy_free_bno_btree(
	xfs_agnumber_t	agno,
	xfs_agf_t	*agf)
{
	xfs_agblock_t	root;
	int		levels;

	root = be32_to_cpu(agf->agf_bno_root);
	levels = be32_to_cpu(agf->agf_bno_level);

	/* validate root and levels before processing the tree */
	if (root == 0 || root > mp->m_sb.sb_agblocks) {
		if (metadump.show_warnings)
			print_warning("invalid block number (%u) in bnobt "
					"root in agf %u", root, agno);
		return 1;
	}
	if (levels > mp->m_alloc_maxlevels) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in bnobt root "
					"in agf %u", levels, agno);
		return 1;
	}

	return scan_btree(agno, root, levels, TYP_BNOBT, agf, scanfunc_freesp);
}

static int
copy_free_cnt_btree(
	xfs_agnumber_t	agno,
	xfs_agf_t	*agf)
{
	xfs_agblock_t	root;
	int		levels;

	root = be32_to_cpu(agf->agf_cnt_root);
	levels = be32_to_cpu(agf->agf_cnt_level);

	/* validate root and levels before processing the tree */
	if (root == 0 || root > mp->m_sb.sb_agblocks) {
		if (metadump.show_warnings)
			print_warning("invalid block number (%u) in cntbt "
					"root in agf %u", root, agno);
		return 1;
	}
	if (levels > mp->m_alloc_maxlevels) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in cntbt root "
					"in agf %u", levels, agno);
		return 1;
	}

	return scan_btree(agno, root, levels, TYP_CNTBT, agf, scanfunc_freesp);
}

static int
scanfunc_rmapbt(
	struct xfs_btree_block	*block,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno,
	int			level,
	typnm_t			btype,
	void			*arg)
{
	xfs_rmap_ptr_t		*pp;
	int			i;
	int			numrecs;

	if (level == 0)
		return 1;

	numrecs = be16_to_cpu(block->bb_numrecs);
	if (numrecs > mp->m_rmap_mxr[1]) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs (%u) in %s block %u/%u",
				numrecs, typtab[btype].name, agno, agbno);
		return 1;
	}

	pp = XFS_RMAP_PTR_ADDR(block, 1, mp->m_rmap_mxr[1]);
	for (i = 0; i < numrecs; i++) {
		if (!valid_bno(agno, be32_to_cpu(pp[i]))) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in %s block %u/%u",
					agno, be32_to_cpu(pp[i]),
					typtab[btype].name, agno, agbno);
			continue;
		}
		if (!scan_btree(agno, be32_to_cpu(pp[i]), level, btype, arg,
				scanfunc_rmapbt))
			return 0;
	}
	return 1;
}

static int
copy_rmap_btree(
	xfs_agnumber_t	agno,
	struct xfs_agf	*agf)
{
	xfs_agblock_t	root;
	int		levels;

	if (!xfs_has_rmapbt(mp))
		return 1;

	root = be32_to_cpu(agf->agf_rmap_root);
	levels = be32_to_cpu(agf->agf_rmap_level);

	/* validate root and levels before processing the tree */
	if (root == 0 || root > mp->m_sb.sb_agblocks) {
		if (metadump.show_warnings)
			print_warning("invalid block number (%u) in rmapbt "
					"root in agf %u", root, agno);
		return 1;
	}
	if (levels > mp->m_rmap_maxlevels) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in rmapbt root "
					"in agf %u", levels, agno);
		return 1;
	}

	return scan_btree(agno, root, levels, TYP_RMAPBT, agf, scanfunc_rmapbt);
}

static int
scanfunc_refcntbt(
	struct xfs_btree_block	*block,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno,
	int			level,
	typnm_t			btype,
	void			*arg)
{
	xfs_refcount_ptr_t	*pp;
	int			i;
	int			numrecs;

	if (level == 0)
		return 1;

	numrecs = be16_to_cpu(block->bb_numrecs);
	if (numrecs > mp->m_refc_mxr[1]) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs (%u) in %s block %u/%u",
				numrecs, typtab[btype].name, agno, agbno);
		return 1;
	}

	pp = XFS_REFCOUNT_PTR_ADDR(block, 1, mp->m_refc_mxr[1]);
	for (i = 0; i < numrecs; i++) {
		if (!valid_bno(agno, be32_to_cpu(pp[i]))) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in %s block %u/%u",
					agno, be32_to_cpu(pp[i]),
					typtab[btype].name, agno, agbno);
			continue;
		}
		if (!scan_btree(agno, be32_to_cpu(pp[i]), level, btype, arg,
				scanfunc_refcntbt))
			return 0;
	}
	return 1;
}

static int
copy_refcount_btree(
	xfs_agnumber_t	agno,
	struct xfs_agf	*agf)
{
	xfs_agblock_t	root;
	int		levels;

	if (!xfs_has_reflink(mp))
		return 1;

	root = be32_to_cpu(agf->agf_refcount_root);
	levels = be32_to_cpu(agf->agf_refcount_level);

	/* validate root and levels before processing the tree */
	if (root == 0 || root > mp->m_sb.sb_agblocks) {
		if (metadump.show_warnings)
			print_warning("invalid block number (%u) in refcntbt "
					"root in agf %u", root, agno);
		return 1;
	}
	if (levels > mp->m_refc_maxlevels) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in refcntbt root "
					"in agf %u", levels, agno);
		return 1;
	}

	return scan_btree(agno, root, levels, TYP_REFCBT, agf, scanfunc_refcntbt);
}

/* filename and extended attribute obfuscation routines */

struct name_ent {
	struct name_ent		*next;
	xfs_dahash_t		hash;
	int			namelen;
	unsigned char		name[1];
};

#define NAME_TABLE_SIZE		4096

static struct name_ent		*nametable[NAME_TABLE_SIZE];

static void
nametable_clear(void)
{
	int		i;
	struct name_ent	*ent;

	for (i = 0; i < NAME_TABLE_SIZE; i++) {
		while ((ent = nametable[i])) {
			nametable[i] = ent->next;
			free(ent);
		}
	}
}

/*
 * See if the given name is already in the name table.  If so,
 * return a pointer to its entry, otherwise return a null pointer.
 */
static struct name_ent *
nametable_find(xfs_dahash_t hash, int namelen, unsigned char *name)
{
	struct name_ent	*ent;

	for (ent = nametable[hash % NAME_TABLE_SIZE]; ent; ent = ent->next) {
		if (ent->hash == hash && ent->namelen == namelen &&
				!memcmp(ent->name, name, namelen))
			return ent;
	}
	return NULL;
}

/*
 * Add the given name to the name table.  Returns a pointer to the
 * name's new entry, or a null pointer if an error occurs.
 */
static struct name_ent *
nametable_add(xfs_dahash_t hash, int namelen, unsigned char *name)
{
	struct name_ent	*ent;

	ent = malloc(sizeof *ent + namelen);
	if (!ent)
		return NULL;

	ent->namelen = namelen;
	memcpy(ent->name, name, namelen);
	ent->hash = hash;
	ent->next = nametable[hash % NAME_TABLE_SIZE];

	nametable[hash % NAME_TABLE_SIZE] = ent;

	return ent;
}

/*
 * Obfuscated name remapping table for parent pointer-enabled filesystems.
 * When this feature is enabled, we have to maintain consistency between the
 * names that appears in the dirent and the corresponding parent pointer.
 */

struct remap_ent {
	struct remap_ent	*next;
	xfs_ino_t		dir_ino;
	xfs_dahash_t		namehash;
	uint8_t			namelen;

	uint8_t			names[];
};

static inline uint8_t *remap_ent_before(struct remap_ent *ent)
{
	return &ent->names[0];
}

static inline uint8_t *remap_ent_after(struct remap_ent *ent)
{
	return &ent->names[ent->namelen];
}

#define REMAP_TABLE_SIZE		4096

static struct remap_ent		*remaptable[REMAP_TABLE_SIZE];

static void
remaptable_clear(void)
{
	int			i;
	struct remap_ent	*ent, *next;

	for (i = 0; i < REMAP_TABLE_SIZE; i++) {
		ent = remaptable[i];

		while (ent) {
			next = ent->next;
			free(ent);
			ent = next;
		}
	}
}

/* Try to find a remapping table entry. */
static struct remap_ent *
remaptable_find(
	xfs_ino_t		dir_ino,
	xfs_dahash_t		namehash,
	const unsigned char	*name,
	unsigned int		namelen)
{
	struct remap_ent	*ent = remaptable[namehash % REMAP_TABLE_SIZE];

	remap_debug("REMAP FIND: 0x%lx hash 0x%x '%.*s'\n",
			dir_ino, namehash, namelen, name);

	while (ent) {
		remap_debug("REMAP ENT: 0x%lx hash 0x%x '%.*s'\n",
				ent->dir_ino, ent->namehash, ent->namelen,
				remap_ent_before(ent));

		if (ent->dir_ino == dir_ino &&
		    ent->namehash == namehash &&
		    ent->namelen == namelen &&
		    !memcmp(remap_ent_before(ent), name, namelen))
			return ent;
		ent = ent->next;
	}

	return NULL;
}

/* Remember the remapping for a particular dirent that we obfuscated. */
static struct remap_ent *
remaptable_add(
	xfs_ino_t		dir_ino,
	xfs_dahash_t		namehash,
	const unsigned char	*old_name,
	unsigned int		namelen,
	const unsigned char	*new_name)
{
	struct remap_ent	*ent;

	ent = malloc(sizeof(struct remap_ent) + (namelen * 2));
	if (!ent)
		return NULL;

	ent->dir_ino = dir_ino;
	ent->namehash = namehash;
	ent->namelen = namelen;
	memcpy(remap_ent_before(ent), old_name, namelen);
	memcpy(remap_ent_after(ent), new_name, namelen);
	ent->next = remaptable[namehash % REMAP_TABLE_SIZE];

	remaptable[namehash % REMAP_TABLE_SIZE] = ent;

	remap_debug("REMAP ADD: 0x%lx hash 0x%x '%.*s' -> '%.*s'\n",
			dir_ino, namehash, namelen, old_name, namelen,
			new_name);
	return ent;
}

#define	ORPHANAGE	"lost+found"
#define	ORPHANAGE_LEN	(sizeof (ORPHANAGE) - 1)

static inline int
is_orphanage_dir(
	struct xfs_mount	*mp,
	xfs_ino_t		dir_ino,
	size_t			name_len,
	unsigned char		*name)
{
	return dir_ino == mp->m_sb.sb_rootino &&
			name_len == ORPHANAGE_LEN &&
			!memcmp(name, ORPHANAGE, ORPHANAGE_LEN);
}

/*
 * Determine whether a name is one we shouldn't obfuscate because
 * it's an orphan (or the "lost+found" directory itself).  Note
 * "cur_ino" is the inode for the directory currently being
 * processed.
 *
 * Returns 1 if the name should NOT be obfuscated or 0 otherwise.
 */
static int
in_lost_found(
	xfs_ino_t		ino,
	int			namelen,
	unsigned char		*name)
{
	static xfs_ino_t	orphanage_ino = 0;
	char			s[24];	/* 21 is enough (64 bits in decimal) */
	int			slen;

	/* Record the "lost+found" inode if we haven't done so already */

	ASSERT(ino != 0);
	if (!orphanage_ino && is_orphanage_dir(mp, metadump.cur_ino, namelen,
						name))
		orphanage_ino = ino;

	/* We don't obfuscate the "lost+found" directory itself */

	if (ino == orphanage_ino)
		return 1;

	/* Most files aren't in "lost+found" at all */

	if (metadump.cur_ino != orphanage_ino)
		return 0;

	/*
	 * Within "lost+found", we don't obfuscate any file whose
	 * name is the same as its inode number.  Any others are
	 * stray files and can be obfuscated.
	 */
	slen = snprintf(s, sizeof (s), "%llu", (unsigned long long) ino);

	return slen == namelen && !memcmp(name, s, namelen);
}

/*
 * Look up the given name in the name table.  If it is already
 * present, iterate through a well-defined sequence of alternate
 * names and attempt to use an alternate name instead.
 *
 * Returns 1 if the (possibly modified) name is not present in the
 * name table.  Returns 0 if the name and all possible alternates
 * are already in the table.
 */
static int
handle_duplicate_name(xfs_dahash_t hash, size_t name_len, unsigned char *name)
{
	unsigned char	new_name[name_len + 1];
	uint32_t	seq = 1;

	if (!nametable_find(hash, name_len, name))
		return 1;	/* No duplicate */

	/* Name is already in use.  Need to find an alternate. */

	do {
		int	found;

		/* Only change incoming name if we find an alternate */
		do {
			memcpy(new_name, name, name_len);
			found = find_alternate(name_len, new_name, seq++);
			if (found < 0)
				return 0;	/* No more to check */
		} while (!found);
	} while (nametable_find(hash, name_len, new_name));

	/*
	 * The alternate wasn't in the table already.  Pass it back
	 * to the caller.
	 */
	memcpy(name, new_name, name_len);

	return 1;
}

static inline xfs_dahash_t
dirattr_hashname(
	bool		is_dirent,
	const uint8_t	*name,
	int		namelen)
{
	if (is_dirent) {
		struct xfs_name	xname = {
			.name	= name,
			.len	= namelen,
		};

		return libxfs_dir2_hashname(mp, &xname);
	}

	return libxfs_attr_hashname(name, namelen);
}

static void
generate_obfuscated_name(
	xfs_ino_t		ino,
	int			namelen,
	unsigned char		*name)
{
	unsigned char		*orig_name = NULL;
	xfs_dahash_t		hash;

	/*
	 * We don't obfuscate "lost+found" or any orphan files
	 * therein.  When the name table is used for extended
	 * attributes, the inode number provided is 0, in which
	 * case we don't need to make this check.
	 */
	if (ino && in_lost_found(ino, namelen, name))
		return;

	/*
	 * If the name starts with a slash, just skip over it.  It
	 * isn't included in the hash and we don't record it in the
	 * name table.  Note that the namelen value passed in does
	 * not count the leading slash (if one is present).
	 */
	if (*name == '/')
		name++;

	/* Obfuscate the name (if possible) */
	hash = dirattr_hashname(ino != 0, name, namelen);

	/*
	 * If we're obfuscating a dirent name on a pptrs filesystem, see if we
	 * already processed the parent pointer and use the same name.
	 */
	if (xfs_has_parent(mp) && ino) {
		struct remap_ent	*remap;

		remap = remaptable_find(metadump.cur_ino, hash, name, namelen);
		if (remap) {
			remap_debug("found obfuscated dir 0x%lx '%.*s' -> 0x%lx -> '%.*s' \n",
					cur_ino, namelen,
					remap_ent_before(remap), ino, namelen,
					remap_ent_after(remap));
			memcpy(name, remap_ent_after(remap), namelen);
			return;
		}

		/*
		 * If we haven't procesed this dirent name before, save the
		 * old name for a remap table entry.  Obfuscate the name.
		 */
		orig_name = malloc(namelen);
		if (!orig_name) {
			orig_name = name;
			goto add_remap;
		}
		memcpy(orig_name, name, namelen);
	}

	obfuscate_name(hash, namelen, name, ino != 0);
	ASSERT(hash == dirattr_hashname(ino != 0, name, namelen));

	/*
	 * Make sure the name is not something already seen.  If we
	 * fail to find a suitable alternate, we're dealing with a
	 * very pathological situation, and we may end up creating
	 * a duplicate name in the metadump, so issue a warning.
	 */
	if (!handle_duplicate_name(hash, namelen, name)) {
		print_warning("duplicate name for inode %llu "
				"in dir inode %llu\n",
			(unsigned long long) ino,
			(unsigned long long) metadump.cur_ino);
		return;
	}

	/* Create an entry for the new name in the name table. */

	if (!nametable_add(hash, namelen, name))
		print_warning("unable to record name for inode %llu "
				"in dir inode %llu\n",
			(unsigned long long) ino,
			(unsigned long long) metadump.cur_ino);

	/*
	 * We've obfuscated a name in the directory entry.  Remember this
	 * remapping for when we come across the parent pointer later.
	 */
	if (!orig_name)
		return;

add_remap:
	remap_debug("obfuscating dir 0x%lx '%.*s' -> 0x%lx -> '%.*s' \n",
			metadump.cur_ino, namelen, orig_name, ino, namelen,
			name);

	if (!remaptable_add(metadump.cur_ino, hash, orig_name, namelen, name))
		print_warning("unable to record remapped dirent name for inode %llu "
				"in dir inode %llu\n",
			(unsigned long long) ino,
			(unsigned long long) metadump.cur_ino);
	if (orig_name && orig_name != name)
		free(orig_name);
}

static void
process_sf_dir(
	struct xfs_dinode	*dip)
{
	struct xfs_dir2_sf_hdr	*sfp;
	xfs_dir2_sf_entry_t	*sfep;
	uint64_t		ino_dir_size;
	int			i;

	sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip);
	ino_dir_size = be64_to_cpu(dip->di_size);
	if (ino_dir_size > XFS_DFORK_DSIZE(dip, mp)) {
		ino_dir_size = XFS_DFORK_DSIZE(dip, mp);
		if (metadump.show_warnings)
			print_warning("invalid size in dir inode %llu",
					(long long)metadump.cur_ino);
	}

	sfep = xfs_dir2_sf_firstentry(sfp);
	for (i = 0; (i < sfp->count) &&
			((char *)sfep - (char *)sfp < ino_dir_size); i++) {

		/*
		 * first check for bad name lengths. If they are bad, we
		 * have limitations to how much can be obfuscated.
		 */
		int	namelen = sfep->namelen;

		if (namelen == 0) {
			if (metadump.show_warnings)
				print_warning("zero length entry in dir inode "
					"%llu", (long long)metadump.cur_ino);
			if (i != sfp->count - 1)
				break;
			namelen = ino_dir_size - ((char *)&sfep->name[0] -
					 (char *)sfp);
		} else if ((char *)sfep - (char *)sfp +
				libxfs_dir2_sf_entsize(mp, sfp, sfep->namelen) >
				ino_dir_size) {
			if (metadump.show_warnings)
				print_warning("entry length in dir inode %llu "
					"overflows space",
					(long long)metadump.cur_ino);
			if (i != sfp->count - 1)
				break;
			namelen = ino_dir_size - ((char *)&sfep->name[0] -
					 (char *)sfp);
		}

		if (metadump.obfuscate)
			generate_obfuscated_name(
					 libxfs_dir2_sf_get_ino(mp, sfp, sfep),
					 namelen, &sfep->name[0]);

		sfep = (xfs_dir2_sf_entry_t *)((char *)sfep +
				libxfs_dir2_sf_entsize(mp, sfp, namelen));
	}

	/* zero stale data in rest of space in data fork, if any */
	if (metadump.zero_stale_data &&
	    (ino_dir_size < XFS_DFORK_DSIZE(dip, mp)))
		memset(sfep, 0, XFS_DFORK_DSIZE(dip, mp) - ino_dir_size);
}

/*
 * The pathname may not be null terminated. It may be terminated by the end of
 * a buffer or inode literal area, and the start of the next region contains
 * unknown data. Therefore, when we get to the last component of the symlink, we
 * cannot assume that strlen() will give us the right result. Hence we need to
 * track the remaining pathname length and use that instead.
 */
static void
obfuscate_path_components(
	char			*buf,
	uint64_t		len)
{
	unsigned char		*comp = (unsigned char *)buf;
	unsigned char		*end = comp + len;
	xfs_dahash_t		hash;

	while (comp < end) {
		char	*slash;
		int	namelen;

		/* find slash at end of this component */
		slash = strchr((char *)comp, '/');
		if (!slash) {
			/* last (or single) component */
			namelen = strnlen((char *)comp, len);
			hash = dirattr_hashname(true, comp, namelen);
			obfuscate_name(hash, namelen, comp, false);
			ASSERT(hash == dirattr_hashname(true, comp, namelen));
			break;
		}
		namelen = slash - (char *)comp;
		/* handle leading or consecutive slashes */
		if (!namelen) {
			comp++;
			len--;
			continue;
		}
		hash = dirattr_hashname(true, comp, namelen);
		obfuscate_name(hash, namelen, comp, false);
		ASSERT(hash == dirattr_hashname(true, comp, namelen));
		comp += namelen + 1;
		len -= namelen + 1;
	}
}

static void
process_sf_symlink(
	struct xfs_dinode	*dip)
{
	uint64_t		len;
	char			*buf;

	len = be64_to_cpu(dip->di_size);
	if (len > XFS_DFORK_DSIZE(dip, mp)) {
		if (metadump.show_warnings)
			print_warning("invalid size (%d) in symlink inode %llu",
					len, (long long)metadump.cur_ino);
		len = XFS_DFORK_DSIZE(dip, mp);
	}

	buf = (char *)XFS_DFORK_DPTR(dip);
	if (metadump.obfuscate)
		obfuscate_path_components(buf, len);

	/* zero stale data in rest of space in data fork, if any */
	if (metadump.zero_stale_data && len < XFS_DFORK_DSIZE(dip, mp))
		memset(&buf[len], 0, XFS_DFORK_DSIZE(dip, mp) - len);
}

/*
 * Decide if we want to obfuscate this parent pointer.  If we do, either find
 * the obfuscated name that we created when we scanned the corresponding dirent
 * and replace the name with that; or create a new obfuscated name for later
 * use.
 */
static void
maybe_obfuscate_pptr(
	unsigned int			attr_flags,
	uint8_t				*name,
	int				namelen,
	const void			*value,
	int				valuelen)
{
	unsigned char			old_name[MAXNAMELEN];
	struct remap_ent		*remap;
	xfs_dahash_t			hash;
	xfs_ino_t			child_ino = metadump.cur_ino;
	xfs_ino_t			parent_ino;
	int				error;

	if (!metadump.obfuscate)
		return;

	if (!(attr_flags & XFS_ATTR_PARENT))
		return;

	/* Do not obfuscate this parent pointer if it fails basic checks. */
	error = -libxfs_parent_from_attr(mp, attr_flags, name, namelen, value,
			valuelen, &parent_ino, NULL);
	if (error)
		return;
	memcpy(old_name, name, namelen);

	/*
	 * We don't obfuscate "lost+found" or any orphan files therein.  When
	 * the name table is used for extended attributes, the inode number
	 * provided is 0, in which case we don't need to make this check.
	 */
	metadump.cur_ino = parent_ino;
	if (in_lost_found(child_ino, namelen, name)) {
		metadump.cur_ino = child_ino;
		return;
	}
	metadump.cur_ino = child_ino;

	hash = dirattr_hashname(true, name, namelen);

	/*
	 * If we already processed the dirent, use the same name for the parent
	 * pointer.
	 */
	remap = remaptable_find(parent_ino, hash, name, namelen);
	if (remap) {
		remap_debug(
 "found obfuscated pptr 0x%lx '%.*s' -> 0x%lx -> '%.*s' \n",
				parent_ino, namelen, remap_ent_before(remap),
				metadump.cur_ino, namelen,
				remap_ent_after(remap));
		memcpy(name, remap_ent_after(remap), namelen);
		return;
	}

	/*
	 * Obfuscate the parent pointer name and remember this for later
	 * in case we encounter the dirent and need to reuse the name there.
	 */
	obfuscate_name(hash, namelen, name, true);

	remap_debug("obfuscated pptr 0x%lx '%.*s' -> 0x%lx -> '%.*s'\n",
			parent_ino, namelen, old_name, metadump.cur_ino,
			namelen, name);
	if (!remaptable_add(parent_ino, hash, old_name, namelen, name))
		print_warning(
 "unable to record remapped pptr name for inode %llu in dir inode %llu\n",
			(unsigned long long) metadump.cur_ino,
			(unsigned long long) parent_ino);
}

static inline bool
want_obfuscate_attr(
	unsigned int	nsp_flags,
	const void	*name,
	unsigned int	namelen,
	const void	*value,
	unsigned int	valuelen)
{
	if (!metadump.obfuscate)
		return false;

	/*
	 * If we didn't already obfuscate the parent pointer, it's probably
	 * corrupt.  Leave it intact for analysis.
	 */
	if (nsp_flags & XFS_ATTR_PARENT)
		return false;

	return true;
}

static void
process_sf_attr(
	struct xfs_dinode		*dip)
{
	/*
	 * with extended attributes, obfuscate the names and fill the actual
	 * values with 'v' (to see a valid string length, as opposed to NULLs)
	 */

	struct xfs_attr_sf_hdr		*hdr = XFS_DFORK_APTR(dip);
	struct xfs_attr_sf_entry	*asfep = libxfs_attr_sf_firstentry(hdr);
	int				ino_attr_size;
	int				i;

	if (hdr->count == 0)
		return;

	ino_attr_size = be16_to_cpu(hdr->totsize);
	if (ino_attr_size > XFS_DFORK_ASIZE(dip, mp)) {
		ino_attr_size = XFS_DFORK_ASIZE(dip, mp);
		if (metadump.show_warnings)
			print_warning("invalid attr size in inode %llu",
					(long long)metadump.cur_ino);
	}

	for (i = 0; (i < hdr->count) &&
			((char *)asfep - (char *)hdr < ino_attr_size); i++) {
		void	*name, *value;
		int	namelen = asfep->namelen;

		if (namelen == 0) {
			if (metadump.show_warnings)
				print_warning("zero length attr entry in inode "
					"%llu", (long long)metadump.cur_ino);
			break;
		} else if ((char *)asfep - (char *)hdr +
				xfs_attr_sf_entsize(asfep) > ino_attr_size) {
			if (metadump.show_warnings)
				print_warning("attr entry length in inode %llu "
					"overflows space",
					(long long)metadump.cur_ino);
			break;
		}

		name = &asfep->nameval[0];
		value = &asfep->nameval[asfep->namelen];

		if (asfep->flags & XFS_ATTR_PARENT) {
			maybe_obfuscate_pptr(asfep->flags, name, namelen,
					value, asfep->valuelen);
		} else if (want_obfuscate_attr(asfep->flags, name, namelen,
					value, asfep->valuelen)) {
			generate_obfuscated_name(0, asfep->namelen, name);
			memset(value, 'v', asfep->valuelen);
		}

		asfep = (struct xfs_attr_sf_entry *)((char *)asfep +
				xfs_attr_sf_entsize(asfep));
	}

	/* zero stale data in rest of space in attr fork, if any */
	if (metadump.zero_stale_data &&
	    (ino_attr_size < XFS_DFORK_ASIZE(dip, mp)))
		memset(asfep, 0, XFS_DFORK_ASIZE(dip, mp) - ino_attr_size);
}

static void
process_dir_free_block(
	char				*block)
{
	struct xfs_dir2_free		*free;
	struct xfs_dir3_icfree_hdr	freehdr;

	if (!metadump.zero_stale_data)
		return;

	free = (struct xfs_dir2_free *)block;
	libxfs_dir2_free_hdr_from_disk(mp, &freehdr, free);

	switch (freehdr.magic) {
	case XFS_DIR2_FREE_MAGIC:
	case XFS_DIR3_FREE_MAGIC: {
		__be16			*bests;
		char			*high;
		int			used;

		/* Zero out space from end of bests[] to end of block */
		bests = freehdr.bests;
		high = (char *)&bests[freehdr.nvalid];
		used = high - (char*)free;
		memset(high, 0, mp->m_dir_geo->blksize - used);
		iocur_top->need_crc = 1;
		break;
	}
	default:
		if (metadump.show_warnings)
			print_warning("invalid magic in dir inode %llu "
				      "free block",
				      (unsigned long long)metadump.cur_ino);
		break;
	}
}

static void
process_dir_leaf_block(
	char				*block)
{
	struct xfs_dir2_leaf		*leaf;
	struct xfs_dir3_icleaf_hdr	leafhdr;

	if (!metadump.zero_stale_data)
		return;

	/* Yes, this works for dir2 & dir3.  Difference is padding. */
	leaf = (struct xfs_dir2_leaf *)block;
	libxfs_dir2_leaf_hdr_from_disk(mp, &leafhdr, leaf);

	switch (leafhdr.magic) {
	case XFS_DIR2_LEAF1_MAGIC:
	case XFS_DIR3_LEAF1_MAGIC: {
		struct xfs_dir2_leaf_tail	*ltp;
		__be16				*lbp;
		char				*free; /* end of ents */

		/* Zero out space from end of ents[] to bests */
		free = (char *)&leafhdr.ents[leafhdr.count];
		ltp = xfs_dir2_leaf_tail_p(mp->m_dir_geo, leaf);
		lbp = xfs_dir2_leaf_bests_p(ltp);
		memset(free, 0, (char *)lbp - free);
		iocur_top->need_crc = 1;
		break;
	}
	case XFS_DIR2_LEAFN_MAGIC:
	case XFS_DIR3_LEAFN_MAGIC: {
		char				*free;
		int				used;

		/* Zero out space from end of ents[] to end of block */
		free = (char *)&leafhdr.ents[leafhdr.count];
		used = free - (char*)leaf;
		memset(free, 0, mp->m_dir_geo->blksize - used);
		iocur_top->need_crc = 1;
		break;
	}
	default:
		break;
	}
}

static void
process_dir_data_block(
	char		*block,
	xfs_fileoff_t	offset,
	int		is_block_format)
{
	/*
	 * we have to rely on the fileoffset and signature of the block to
	 * handle it's contents. If it's invalid, leave it alone.
	 * for multi-fsblock dir blocks, if a name crosses an extent boundary,
	 * ignore it and continue.
	 */
	int		dir_offset;
	char		*ptr;
	char		*endptr;
	int		end_of_data;
	int		wantmagic;
	struct xfs_dir2_data_hdr *datahdr;

	datahdr = (struct xfs_dir2_data_hdr *)block;

	if (is_block_format) {
		xfs_dir2_leaf_entry_t	*blp;
		xfs_dir2_block_tail_t	*btp;

		btp = xfs_dir2_block_tail_p(mp->m_dir_geo, datahdr);
		blp = xfs_dir2_block_leaf_p(btp);
		if ((char *)blp > (char *)btp)
			blp = (xfs_dir2_leaf_entry_t *)btp;

		end_of_data = (char *)blp - block;
		if (xfs_has_crc(mp))
			wantmagic = XFS_DIR3_BLOCK_MAGIC;
		else
			wantmagic = XFS_DIR2_BLOCK_MAGIC;
	} else { /* leaf/node format */
		end_of_data = mp->m_dir_geo->fsbcount << mp->m_sb.sb_blocklog;
		if (xfs_has_crc(mp))
			wantmagic = XFS_DIR3_DATA_MAGIC;
		else
			wantmagic = XFS_DIR2_DATA_MAGIC;
	}

	if (be32_to_cpu(datahdr->magic) != wantmagic) {
		if (metadump.show_warnings)
			print_warning(
		"invalid magic in dir inode %llu block %ld",
		(unsigned long long)metadump.cur_ino, (long)offset);
		return;
	}

	dir_offset = mp->m_dir_geo->data_entry_offset;
	ptr = block + dir_offset;
	endptr = block + mp->m_dir_geo->blksize;

	while (ptr < endptr && dir_offset < end_of_data) {
		xfs_dir2_data_entry_t	*dep;
		xfs_dir2_data_unused_t	*dup;
		int			length;

		dup = (xfs_dir2_data_unused_t *)ptr;

		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
			int	free_length = be16_to_cpu(dup->length);
			if (dir_offset + free_length > end_of_data ||
			    !free_length ||
			    (free_length & (XFS_DIR2_DATA_ALIGN - 1))) {
				if (metadump.show_warnings)
					print_warning(
			"invalid length for dir free space in inode %llu",
						(long long)metadump.cur_ino);
				return;
			}
			if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) !=
					dir_offset)
				return;
			dir_offset += free_length;
			ptr += free_length;
			/*
			 * Zero the unused space up to the tag - the tag is
			 * actually at a variable offset, so zeroing &dup->tag
			 * is zeroing the free space in between
			 */
			if (metadump.zero_stale_data) {
				int zlen = free_length -
						sizeof(xfs_dir2_data_unused_t);

				if (zlen > 0) {
					memset(&dup->tag, 0, zlen);
					iocur_top->need_crc = 1;
				}
			}
			if (dir_offset >= end_of_data || ptr >= endptr)
				return;
		}

		dep = (xfs_dir2_data_entry_t *)ptr;
		length = libxfs_dir2_data_entsize(mp, dep->namelen);

		if (dir_offset + length > end_of_data ||
		    ptr + length > endptr) {
			if (metadump.show_warnings)
				print_warning(
			"invalid length for dir entry name in inode %llu",
					(long long)metadump.cur_ino);
			return;
		}
		if (be16_to_cpu(*libxfs_dir2_data_entry_tag_p(mp, dep)) !=
				dir_offset)
			return;

		if (metadump.obfuscate)
			generate_obfuscated_name(be64_to_cpu(dep->inumber),
					 dep->namelen, &dep->name[0]);
		dir_offset += length;
		ptr += length;
		/* Zero the unused space after name, up to the tag */
		if (metadump.zero_stale_data) {
			/* 1 byte for ftype; don't bother with conditional */
			int zlen =
				(char *)libxfs_dir2_data_entry_tag_p(mp, dep) -
				(char *)&dep->name[dep->namelen] - 1;
			if (zlen > 0) {
				memset(&dep->name[dep->namelen] + 1, 0, zlen);
				iocur_top->need_crc = 1;
			}
		}
	}
}

static int
process_symlink_block(
	xfs_fileoff_t	o,
	xfs_fsblock_t	s,
	xfs_filblks_t	c,
	typnm_t		btype,
	xfs_fileoff_t	last)
{
	struct bbmap	map;
	char		*link;
	int		rval = 1;

	push_cur();
	map.nmaps = 1;
	map.b[0].bm_bn = XFS_FSB_TO_DADDR(mp, s);
	map.b[0].bm_len = XFS_FSB_TO_BB(mp, c);
	set_cur(&typtab[btype], 0, 0, DB_RING_IGN, &map);
	if (!iocur_top->data) {
		xfs_agnumber_t	agno = XFS_FSB_TO_AGNO(mp, s);
		xfs_agblock_t	agbno = XFS_FSB_TO_AGBNO(mp, s);

		print_warning("cannot read %s block %u/%u (%llu)",
				typtab[btype].name, agno, agbno, s);
		rval = !metadump.stop_on_read_error;
		goto out_pop;
	}
	link = iocur_top->data;

	if (xfs_has_crc((mp)))
		link += sizeof(struct xfs_dsymlink_hdr);

	if (metadump.obfuscate)
		obfuscate_path_components(link, XFS_SYMLINK_BUF_SPACE(mp,
							mp->m_sb.sb_blocksize));
	if (metadump.zero_stale_data) {
		size_t	linklen, zlen;

		linklen = strlen(link);
		zlen = mp->m_sb.sb_blocksize - linklen;
		if (xfs_has_crc(mp))
			zlen -= sizeof(struct xfs_dsymlink_hdr);
		if (zlen < mp->m_sb.sb_blocksize)
			memset(link + linklen, 0, zlen);
	}

	iocur_top->need_crc = 1;
	if (write_buf(iocur_top))
		rval = 0;
out_pop:
	pop_cur();
	return rval;
}

#define MAX_REMOTE_VALS		4095

static struct attr_data_s {
	int			remote_val_count;
	xfs_dablk_t		remote_vals[MAX_REMOTE_VALS];
} attr_data;

static inline void
add_remote_vals(
	xfs_dablk_t 		blockidx,
	int			length)
{
	while (length > 0 && attr_data.remote_val_count < MAX_REMOTE_VALS) {
		attr_data.remote_vals[attr_data.remote_val_count] = blockidx;
		attr_data.remote_val_count++;
		blockidx++;
		length -= xfs_attr3_rmt_buf_space(mp);
	}

	if (attr_data.remote_val_count >= MAX_REMOTE_VALS) {
		print_warning(
"Overflowed attr obfuscation array. No longer obfuscating remote attrs.");
	}
}

/* Handle remote and leaf attributes */
static void
process_attr_block(
	char				*block,
	xfs_fileoff_t			offset)
{
	struct xfs_attr_leafblock	*leaf;
	struct xfs_attr3_icleaf_hdr	hdr;
	int				i;
	int				nentries;
	xfs_attr_leaf_entry_t 		*entry;
	xfs_attr_leaf_name_local_t 	*local;
	xfs_attr_leaf_name_remote_t 	*remote;
	uint32_t			bs = mp->m_sb.sb_blocksize;
	char				*first_name;


	leaf = (xfs_attr_leafblock_t *)block;

	/* Remote attributes - attr3 has XFS_ATTR3_RMT_MAGIC, attr has none */
	if ((be16_to_cpu(leaf->hdr.info.magic) != XFS_ATTR_LEAF_MAGIC) &&
	    (be16_to_cpu(leaf->hdr.info.magic) != XFS_ATTR3_LEAF_MAGIC)) {
		for (i = 0; i < attr_data.remote_val_count; i++) {
			if (metadump.obfuscate &&
			    attr_data.remote_vals[i] == offset)
				/* Macros to handle both attr and attr3 */
				memset(block +
					(bs - xfs_attr3_rmt_buf_space(mp)),
				      'v', xfs_attr3_rmt_buf_space(mp));
		}
		return;
	}

	/* Ok, it's a leaf - get header; accounts for crc & non-crc */
	libxfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &hdr, leaf);

	nentries = hdr.count;
	if (nentries == 0 ||
	    nentries * sizeof(xfs_attr_leaf_entry_t) +
			xfs_attr3_leaf_hdr_size(leaf) >
				xfs_attr3_rmt_buf_space(mp)) {
		if (metadump.show_warnings)
			print_warning("invalid attr count in inode %llu",
					(long long)metadump.cur_ino);
		return;
	}

	entry = xfs_attr3_leaf_entryp(leaf);
	/* We will move this as we parse */
	first_name = NULL;
	for (i = 0; i < nentries; i++, entry++) {
		int nlen, vlen, zlen;

		/* Grows up; if this name is topmost, move first_name */
		if (!first_name || xfs_attr3_leaf_name(leaf, i) < first_name)
			first_name = xfs_attr3_leaf_name(leaf, i);

		if (be16_to_cpu(entry->nameidx) > mp->m_sb.sb_blocksize) {
			if (metadump.show_warnings)
				print_warning(
				"invalid attr nameidx in inode %llu",
						(long long)metadump.cur_ino);
			break;
		}
		if (entry->flags & XFS_ATTR_LOCAL) {
			void *name, *value;
			unsigned int valuelen;

			local = xfs_attr3_leaf_name_local(leaf, i);
			if (local->namelen == 0) {
				if (metadump.show_warnings)
					print_warning(
				"zero length for attr name in inode %llu",
						(long long)metadump.cur_ino);
				break;
			}

			name = &local->nameval[0];
			value = &local->nameval[local->namelen];
			valuelen = be16_to_cpu(local->valuelen);

			if (entry->flags & XFS_ATTR_PARENT) {
				maybe_obfuscate_pptr(entry->flags, name,
						local->namelen, value,
						valuelen);
			} else if (want_obfuscate_attr(entry->flags, name,
						local->namelen, value,
						valuelen)) {
				generate_obfuscated_name(0, local->namelen,
						name);
				memset(value, 'v', valuelen);
			}
			/* zero from end of nameval[] to next name start */
			nlen = local->namelen;
			vlen = be16_to_cpu(local->valuelen);
			zlen = xfs_attr_leaf_entsize_local(nlen, vlen) -
				(offsetof(struct xfs_attr_leaf_name_local, nameval) +
				 nlen + vlen);
			if (metadump.zero_stale_data)
				memset(&local->nameval[nlen + vlen], 0, zlen);
		} else {
			remote = xfs_attr3_leaf_name_remote(leaf, i);
			if (remote->namelen == 0 || remote->valueblk == 0) {
				if (metadump.show_warnings)
					print_warning(
				"invalid attr entry in inode %llu",
						(long long)metadump.cur_ino);
				break;
			}
			if (entry->flags & XFS_ATTR_PARENT) {
				/* do not obfuscate obviously busted pptr */
				add_remote_vals(be32_to_cpu(remote->valueblk),
						be32_to_cpu(remote->valuelen));
			} else if (metadump.obfuscate) {
				generate_obfuscated_name(0, remote->namelen,
							 &remote->name[0]);
				add_remote_vals(be32_to_cpu(remote->valueblk),
						be32_to_cpu(remote->valuelen));
			}
			/* zero from end of name[] to next name start */
			nlen = remote->namelen;
			zlen = xfs_attr_leaf_entsize_remote(nlen) -
				(offsetof(struct xfs_attr_leaf_name_remote, name) +
				 nlen);
			if (metadump.zero_stale_data)
				memset(&remote->name[nlen], 0, zlen);
		}
	}

	/* Zero from end of entries array to the first name/val */
	if (metadump.zero_stale_data) {
		struct xfs_attr_leaf_entry *entries;

		entries = xfs_attr3_leaf_entryp(leaf);
		memset(&entries[nentries], 0,
		       first_name - (char *)&entries[nentries]);
	}
}

/* Processes symlinks, attrs, directories ... */
static int
process_single_fsb_objects(
	xfs_fileoff_t	o,
	xfs_fsblock_t	s,
	xfs_filblks_t	c,
	typnm_t		btype,
	xfs_fileoff_t	last)
{
	int		rval = 1;
	char		*dp;
	int		i;

	for (i = 0; i < c; i++) {
		push_cur();
		set_cur(&typtab[btype], XFS_FSB_TO_DADDR(mp, s), blkbb,
				DB_RING_IGN, NULL);

		if (!iocur_top->data) {
			xfs_agnumber_t	agno = XFS_FSB_TO_AGNO(mp, s);
			xfs_agblock_t	agbno = XFS_FSB_TO_AGBNO(mp, s);

			print_warning("cannot read %s block %u/%u (%llu)",
					typtab[btype].name, agno, agbno, s);
			rval = !metadump.stop_on_read_error;
			goto out_pop;

		}

		if (!metadump.obfuscate && !metadump.zero_stale_data)
			goto write;

		/* Zero unused part of interior nodes */
		if (metadump.zero_stale_data) {
			xfs_da_intnode_t *node = iocur_top->data;
			int magic = be16_to_cpu(node->hdr.info.magic);

			if (magic == XFS_DA_NODE_MAGIC ||
			    magic == XFS_DA3_NODE_MAGIC) {
				struct xfs_da3_icnode_hdr hdr;
				int used;

				libxfs_da3_node_hdr_from_disk(mp, &hdr, node);
				switch (btype) {
				case TYP_DIR2:
					used = mp->m_dir_geo->node_hdr_size;
					break;
				case TYP_ATTR:
					used = mp->m_attr_geo->node_hdr_size;
					break;
				default:
					/* unknown type, don't zero anything */
					used = mp->m_sb.sb_blocksize;
					break;
				}

				used += hdr.count
					* sizeof(struct xfs_da_node_entry);

				if (used < mp->m_sb.sb_blocksize) {
					memset((char *)node + used, 0,
						mp->m_sb.sb_blocksize - used);
					iocur_top->need_crc = 1;
				}
			}
		}

		/* Handle leaf nodes */
		dp = iocur_top->data;
		switch (btype) {
		case TYP_DIR2:
			if (o >= mp->m_dir_geo->freeblk) {
				process_dir_free_block(dp);
			} else if (o >= mp->m_dir_geo->leafblk) {
				process_dir_leaf_block(dp);
			} else {
				process_dir_data_block(dp, o,
					 last == mp->m_dir_geo->fsbcount);
			}
			iocur_top->need_crc = 1;
			break;
		case TYP_ATTR:
			process_attr_block(dp, o);
			iocur_top->need_crc = 1;
			break;
		default:
			break;
		}

write:
		if (write_buf(iocur_top))
			rval = 0;
out_pop:
		pop_cur();
		if (!rval)
			break;
		o++;
		s++;
	}

	return rval;
}

/*
 * Static map to aggregate multiple extents into a single directory block.
 */
static struct bbmap mfsb_map;
static int mfsb_length;

static int
process_multi_fsb_dir(
	xfs_fileoff_t	o,
	xfs_fsblock_t	s,
	xfs_filblks_t	c,
	typnm_t		btype,
	xfs_fileoff_t	last)
{
	char		*dp;
	int		rval = 1;

	while (c > 0) {
		unsigned int	bm_len;

		if (mfsb_length + c >= mp->m_dir_geo->fsbcount) {
			bm_len = mp->m_dir_geo->fsbcount - mfsb_length;
			mfsb_length = 0;
		} else {
			mfsb_length += c;
			bm_len = c;
		}

		mfsb_map.b[mfsb_map.nmaps].bm_bn = XFS_FSB_TO_DADDR(mp, s);
		mfsb_map.b[mfsb_map.nmaps].bm_len = XFS_FSB_TO_BB(mp, bm_len);
		mfsb_map.nmaps++;

		if (mfsb_length == 0) {
			push_cur();
			set_cur(&typtab[btype], 0, 0, DB_RING_IGN, &mfsb_map);
			if (!iocur_top->data) {
				xfs_agnumber_t	agno = XFS_FSB_TO_AGNO(mp, s);
				xfs_agblock_t	agbno = XFS_FSB_TO_AGBNO(mp, s);

				print_warning("cannot read %s block %u/%u (%llu)",
						typtab[btype].name, agno, agbno, s);
				rval = !metadump.stop_on_read_error;
				goto out_pop;

			}

			if (!metadump.obfuscate && !metadump.zero_stale_data)
				goto write;

			dp = iocur_top->data;
			if (o >= mp->m_dir_geo->freeblk) {
				process_dir_free_block(dp);
			} else if (o >= mp->m_dir_geo->leafblk) {
				process_dir_leaf_block(dp);
			} else {
				process_dir_data_block(dp, o,
					 last == mp->m_dir_geo->fsbcount);
			}
			iocur_top->need_crc = 1;
write:
			if (write_buf(iocur_top))
				rval = 0;
out_pop:
			pop_cur();
			mfsb_map.nmaps = 0;
			if (!rval)
				break;
		}
		c -= bm_len;
		s += bm_len;
	}

	return rval;
}

static bool
is_multi_fsb_object(
	struct xfs_mount	*mp,
	typnm_t			btype)
{
	if (btype == TYP_DIR2 && mp->m_dir_geo->fsbcount > 1)
		return true;
	if (btype == TYP_SYMLINK)
		return true;
	return false;
}

static int
process_multi_fsb_objects(
	xfs_fileoff_t	o,
	xfs_fsblock_t	s,
	xfs_filblks_t	c,
	typnm_t		btype,
	xfs_fileoff_t	last)
{
	switch (btype) {
	case TYP_DIR2:
		return process_multi_fsb_dir(o, s, c, btype, last);
	case TYP_SYMLINK:
		return process_symlink_block(o, s, c, btype, last);
	default:
		print_warning("bad type for multi-fsb object %d", btype);
		return 1;
	}
}

/* inode copy routines */
static int
process_bmbt_reclist(
	xfs_bmbt_rec_t		*rp,
	int			numrecs,
	typnm_t			btype)
{
	int			i;
	xfs_fileoff_t		o, op = NULLFILEOFF;
	xfs_fsblock_t		s;
	xfs_filblks_t		c, cp = NULLFILEOFF;
	int			f;
	xfs_fileoff_t		last;
	xfs_agnumber_t		agno;
	xfs_agblock_t		agbno;
	bool			is_multi_fsb = is_multi_fsb_object(mp, btype);
	int			rval = 1;

	if (btype == TYP_DATA)
		return 1;

	convert_extent(&rp[numrecs - 1], &o, &s, &c, &f);
	last = o + c;

	for (i = 0; i < numrecs; i++, rp++) {
		convert_extent(rp, &o, &s, &c, &f);

		/*
		 * ignore extents that are clearly bogus, and if a bogus
		 * one is found, stop processing remaining extents
		 */
		if (i > 0 && op + cp > o) {
			if (metadump.show_warnings)
				print_warning("bmap extent %d in %s ino %llu "
					"starts at %llu, previous extent "
					"ended at %llu", i,
					typtab[btype].name,
					(long long)metadump.cur_ino,
					o, op + cp - 1);
			break;
		}

		if (c > metadump.max_extent_size) {
			/*
			 * since we are only processing non-data extents,
			 * large numbers of blocks in a metadata extent is
			 * extremely rare and more than likely to be corrupt.
			 */
			if (metadump.show_warnings)
				print_warning("suspicious count %u in bmap "
					"extent %d in %s ino %llu", c, i,
					typtab[btype].name,
					(long long)metadump.cur_ino);
			break;
		}

		op = o;
		cp = c;

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

		if (!valid_bno(agno, agbno)) {
			if (metadump.show_warnings)
				print_warning("invalid block number %u/%u "
					"(%llu) in bmap extent %d in %s ino "
					"%llu", agno, agbno, s, i,
					typtab[btype].name,
					(long long)metadump.cur_ino);
			break;
		}

		if (!valid_bno(agno, agbno + c - 1)) {
			if (metadump.show_warnings)
				print_warning("bmap extent %i in %s inode %llu "
					"overflows AG (end is %u/%u)", i,
					typtab[btype].name,
					(long long)metadump.cur_ino,
					agno, agbno + c - 1);
			break;
		}

		/* multi-extent blocks require special handling */
		if (is_multi_fsb)
			rval = process_multi_fsb_objects(o, s, c, btype,
					last);
		else
			rval = process_single_fsb_objects(o, s, c, btype,
					last);
		if (!rval)
			break;
	}

	return rval;
}

static int
scanfunc_bmap(
	struct xfs_btree_block	*block,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno,
	int			level,
	typnm_t			btype,
	void			*arg)	/* ptr to itype */
{
	int			i;
	xfs_bmbt_ptr_t		*pp;
	int			nrecs;

	nrecs = be16_to_cpu(block->bb_numrecs);

	if (level == 0) {
		if (nrecs > mp->m_bmap_dmxr[0]) {
			if (metadump.show_warnings)
				print_warning("invalid numrecs (%u) in %s "
					"block %u/%u", nrecs,
					typtab[btype].name, agno, agbno);
			return 1;
		}
		return process_bmbt_reclist(XFS_BMBT_REC_ADDR(mp, block, 1),
					    nrecs, *(typnm_t*)arg);
	}

	if (nrecs > mp->m_bmap_dmxr[1]) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs (%u) in %s block %u/%u",
					nrecs, typtab[btype].name, agno, agbno);
		return 1;
	}
	pp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[1]);
	for (i = 0; i < nrecs; i++) {
		xfs_agnumber_t	ag;
		xfs_agblock_t	bno;

		ag = XFS_FSB_TO_AGNO(mp, get_unaligned_be64(&pp[i]));
		bno = XFS_FSB_TO_AGBNO(mp, get_unaligned_be64(&pp[i]));

		if (bno == 0 || bno > mp->m_sb.sb_agblocks ||
				ag > mp->m_sb.sb_agcount) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in %s block %u/%u", ag, bno,
					typtab[btype].name, agno, agbno);
			continue;
		}

		if (!scan_btree(ag, bno, level, btype, arg, scanfunc_bmap))
			return 0;
	}
	return 1;
}

static int
process_btinode(
	struct xfs_dinode 	*dip,
	typnm_t			itype)
{
	xfs_bmdr_block_t	*dib;
	int			i;
	xfs_bmbt_ptr_t		*pp;
	int			level;
	int			nrecs;
	int			maxrecs;
	int			whichfork;
	typnm_t			btype;

	whichfork = (itype == TYP_ATTR) ? XFS_ATTR_FORK : XFS_DATA_FORK;
	btype = (itype == TYP_ATTR) ? TYP_BMAPBTA : TYP_BMAPBTD;

	dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR(dip, whichfork);
	level = be16_to_cpu(dib->bb_level);
	nrecs = be16_to_cpu(dib->bb_numrecs);

	if (level > XFS_BM_MAXLEVELS(mp, whichfork)) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in inode %lld %s "
				"root", level, (long long)metadump.cur_ino,
				typtab[btype].name);
		return 1;
	}

	if (level == 0) {
		return process_bmbt_reclist(XFS_BMDR_REC_ADDR(dib, 1),
					    nrecs, itype);
	}

	maxrecs = libxfs_bmdr_maxrecs(XFS_DFORK_SIZE(dip, mp, whichfork), 0);
	if (nrecs > maxrecs) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs (%u) in inode %lld %s "
				"root", nrecs, (long long)metadump.cur_ino,
				typtab[btype].name);
		return 1;
	}

	pp = XFS_BMDR_PTR_ADDR(dib, 1, maxrecs);

	if (metadump.zero_stale_data) {
		char	*top;

		/* Unused btree key space */
		top = (char*)XFS_BMDR_KEY_ADDR(dib, nrecs + 1);
		memset(top, 0, (char*)pp - top);

		/* Unused btree ptr space */
		top = (char*)&pp[nrecs];
		memset(top, 0, (char*)dib + XFS_DFORK_SIZE(dip, mp, whichfork) - top);
	}

	for (i = 0; i < nrecs; i++) {
		xfs_agnumber_t	ag;
		xfs_agblock_t	bno;

		ag = XFS_FSB_TO_AGNO(mp, get_unaligned_be64(&pp[i]));
		bno = XFS_FSB_TO_AGBNO(mp, get_unaligned_be64(&pp[i]));

		if (bno == 0 || bno > mp->m_sb.sb_agblocks ||
				ag > mp->m_sb.sb_agcount) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in inode %llu %s root", ag, bno,
					(long long)metadump.cur_ino,
					typtab[btype].name);
			continue;
		}

		if (!scan_btree(ag, bno, level, btype, &itype, scanfunc_bmap))
			return 0;
	}
	return 1;
}

static int
process_exinode(
	struct xfs_dinode 	*dip,
	typnm_t			itype)
{
	int			whichfork;
	int			used;
	xfs_extnum_t		nex, max_nex;

	whichfork = (itype == TYP_ATTR) ? XFS_ATTR_FORK : XFS_DATA_FORK;

	nex = xfs_dfork_nextents(dip, whichfork);
	max_nex = xfs_iext_max_nextents(
			xfs_dinode_has_large_extent_counts(dip),
			whichfork);
	used = nex * sizeof(xfs_bmbt_rec_t);
	if (nex > max_nex || used > XFS_DFORK_SIZE(dip, mp, whichfork)) {
		if (metadump.show_warnings)
			print_warning("bad number of extents %llu in inode %lld",
				(unsigned long long)nex,
				(long long)metadump.cur_ino);
		return 1;
	}

	/* Zero unused data fork past used extents */
	if (metadump.zero_stale_data &&
	    (used < XFS_DFORK_SIZE(dip, mp, whichfork)))
		memset(XFS_DFORK_PTR(dip, whichfork) + used, 0,
		       XFS_DFORK_SIZE(dip, mp, whichfork) - used);


	return process_bmbt_reclist((xfs_bmbt_rec_t *)XFS_DFORK_PTR(dip,
					whichfork), nex, itype);
}

static int
process_inode_data(
	struct xfs_dinode	*dip,
	typnm_t			itype)
{
	switch (dip->di_format) {
		case XFS_DINODE_FMT_LOCAL:
			if (!(metadump.obfuscate || metadump.zero_stale_data))
				break;

			/*
			 * If the fork size is invalid, we can't safely do
			 * anything with this fork. Leave it alone to preserve
			 * the information for diagnostic purposes.
			 */
			if (XFS_DFORK_DSIZE(dip, mp) > XFS_LITINO(mp)) {
				print_warning(
"Invalid data fork size (%d) in inode %llu, preserving contents!",
						XFS_DFORK_DSIZE(dip, mp),
						(long long)metadump.cur_ino);
				break;
			}

			switch (itype) {
				case TYP_DIR2:
					process_sf_dir(dip);
					break;

				case TYP_SYMLINK:
					process_sf_symlink(dip);
					break;

				default:
					break;
			}
			break;

		case XFS_DINODE_FMT_EXTENTS:
			return process_exinode(dip, itype);

		case XFS_DINODE_FMT_BTREE:
			return process_btinode(dip, itype);
	}
	return 1;
}

static void
process_dev_inode(
	struct xfs_dinode		*dip)
{
	if (xfs_dfork_data_extents(dip)) {
		if (metadump.show_warnings)
			print_warning("inode %llu has unexpected extents",
				      (unsigned long long)metadump.cur_ino);
		return;
	}

	/*
	 * If the fork size is invalid, we can't safely do anything with
	 * this fork. Leave it alone to preserve the information for diagnostic
	 * purposes.
	 */
	if (XFS_DFORK_DSIZE(dip, mp) > XFS_LITINO(mp)) {
		print_warning(
"Invalid data fork size (%d) in inode %llu, preserving contents!",
			XFS_DFORK_DSIZE(dip, mp), (long long)metadump.cur_ino);
		return;
	}

	if (metadump.zero_stale_data) {
		unsigned int	size = sizeof(xfs_dev_t);

		memset(XFS_DFORK_DPTR(dip) + size, 0,
				XFS_DFORK_DSIZE(dip, mp) - size);
	}
}

/*
 * when we process the inode, we may change the data in the data and/or
 * attribute fork if they are in short form and we are obfuscating names.
 * In this case we need to recalculate the CRC of the inode, but we should
 * only do that if the CRC in the inode is good to begin with. If the crc
 * is not ok, we just leave it alone.
 */
static int
process_inode(
	xfs_agnumber_t		agno,
	xfs_agino_t 		agino,
	struct xfs_dinode 	*dip,
	bool			free_inode)
{
	int			rval = 1;
	bool			crc_was_ok = false; /* no recalc by default */
	bool			need_new_crc = false;

	metadump.cur_ino = XFS_AGINO_TO_INO(mp, agno, agino);

	/* we only care about crc recalculation if we will modify the inode. */
	if (metadump.obfuscate || metadump.zero_stale_data) {
		crc_was_ok = libxfs_verify_cksum((char *)dip,
					mp->m_sb.sb_inodesize,
					offsetof(struct xfs_dinode, di_crc));
	}

	if (free_inode) {
		if (metadump.zero_stale_data) {
			/* Zero all of the inode literal area */
			memset(XFS_DFORK_DPTR(dip), 0, XFS_LITINO(mp));
		}
		goto done;
	}

	/* copy appropriate data fork metadata */
	switch (be16_to_cpu(dip->di_mode) & S_IFMT) {
		case S_IFDIR:
			rval = process_inode_data(dip, TYP_DIR2);
			if (dip->di_format == XFS_DINODE_FMT_LOCAL)
				need_new_crc = true;
			break;
		case S_IFLNK:
			rval = process_inode_data(dip, TYP_SYMLINK);
			if (dip->di_format == XFS_DINODE_FMT_LOCAL)
				need_new_crc = true;
			break;
		case S_IFREG:
			rval = process_inode_data(dip, TYP_DATA);
			break;
		case S_IFIFO:
		case S_IFCHR:
		case S_IFBLK:
		case S_IFSOCK:
			process_dev_inode(dip);
			need_new_crc = true;
			break;
		default:
			break;
	}
	nametable_clear();
	if (!rval)
		goto done;

	/* copy extended attributes if they exist and forkoff is valid */
	if (XFS_DFORK_DSIZE(dip, mp) < XFS_LITINO(mp)) {
		attr_data.remote_val_count = 0;
		switch (dip->di_aformat) {
			case XFS_DINODE_FMT_LOCAL:
				need_new_crc = true;
				if (metadump.obfuscate ||
				    metadump.zero_stale_data)
					process_sf_attr(dip);
				break;

			case XFS_DINODE_FMT_EXTENTS:
				rval = process_exinode(dip, TYP_ATTR);
				break;

			case XFS_DINODE_FMT_BTREE:
				rval = process_btinode(dip, TYP_ATTR);
				break;
		}
		nametable_clear();
	}

done:
	/* Heavy handed but low cost; just do it as a catch-all. */
	if (metadump.zero_stale_data)
		need_new_crc = true;

	if (crc_was_ok && need_new_crc)
		libxfs_dinode_calc_crc(mp, dip);

	return rval;
}

static uint32_t	inodes_copied;

static int
copy_inode_chunk(
	xfs_agnumber_t 		agno,
	xfs_inobt_rec_t 	*rp)
{
	xfs_agino_t 		agino;
	int			off;
	xfs_agblock_t		agbno;
	xfs_agblock_t		end_agbno;
	int			i;
	int			rval = 0;
	int			blks_per_buf;
	int			inodes_per_buf;
	int			ioff;
	struct xfs_ino_geometry *igeo = M_IGEO(mp);

	agino = be32_to_cpu(rp->ir_startino);
	agbno = XFS_AGINO_TO_AGBNO(mp, agino);
	end_agbno = agbno + igeo->ialloc_blks;
	off = XFS_INO_TO_OFFSET(mp, agino);

	/*
	 * If the fs supports sparse inode records, we must process inodes a
	 * cluster at a time because that is the sparse allocation granularity.
	 * Otherwise, we risk CRC corruption errors on reads of inode chunks.
	 *
	 * Also make sure that that we don't process more than the single record
	 * we've been passed (large block sizes can hold multiple inode chunks).
	 */
	if (xfs_has_sparseinodes(mp))
		blks_per_buf = igeo->blocks_per_cluster;
	else
		blks_per_buf = igeo->ialloc_blks;
	inodes_per_buf = min(XFS_FSB_TO_INO(mp, blks_per_buf),
			     XFS_INODES_PER_CHUNK);

	/*
	 * Sanity check that we only process a single buffer if ir_startino has
	 * a buffer offset. A non-zero offset implies that the entire chunk lies
	 * within a block.
	 */
	if (off && inodes_per_buf != XFS_INODES_PER_CHUNK) {
		print_warning("bad starting inode offset %d", off);
		return 0;
	}

	if (agino == 0 || agino == NULLAGINO || !valid_bno(agno, agbno) ||
			!valid_bno(agno, XFS_AGINO_TO_AGBNO(mp,
					agino + XFS_INODES_PER_CHUNK - 1))) {
		if (metadump.show_warnings)
			print_warning("bad inode number %llu (%u/%u)",
				XFS_AGINO_TO_INO(mp, agno, agino), agno, agino);
		return 1;
	}

	/*
	 * check for basic assumptions about inode chunks, and if any
	 * assumptions fail, don't process the inode chunk.
	 */
	if ((mp->m_sb.sb_inopblock <= XFS_INODES_PER_CHUNK && off != 0) ||
			(mp->m_sb.sb_inopblock > XFS_INODES_PER_CHUNK &&
					off % XFS_INODES_PER_CHUNK != 0) ||
			(xfs_has_align(mp) &&
					mp->m_sb.sb_inoalignmt != 0 &&
					agbno % mp->m_sb.sb_inoalignmt != 0)) {
		if (metadump.show_warnings)
			print_warning("badly aligned inode (start = %llu)",
					XFS_AGINO_TO_INO(mp, agno, agino));
		return 1;
	}

	push_cur();
	ioff = 0;
	while (agbno < end_agbno && ioff < XFS_INODES_PER_CHUNK) {
		if (xfs_inobt_is_sparse_disk(rp, ioff))
			goto next_bp;

		set_cur(&typtab[TYP_INODE], XFS_AGB_TO_DADDR(mp, agno, agbno),
			XFS_FSB_TO_BB(mp, blks_per_buf), DB_RING_IGN, NULL);
		if (iocur_top->data == NULL) {
			print_warning("cannot read inode block %u/%u",
				      agno, agbno);
			rval = !metadump.stop_on_read_error;
			goto pop_out;
		}

		for (i = 0; i < inodes_per_buf; i++) {
			struct xfs_dinode	*dip;

			dip = (struct xfs_dinode *)((char *)iocur_top->data +
					((off + i) << mp->m_sb.sb_inodelog));

			/* process_inode handles free inodes, too */
			if (!process_inode(agno, agino + ioff + i, dip,
					XFS_INOBT_IS_FREE_DISK(rp, ioff + i)))
				goto pop_out;

			inodes_copied++;
		}

		if (write_buf(iocur_top))
			goto pop_out;

next_bp:
		agbno += blks_per_buf;
		ioff += inodes_per_buf;
	}

	if (metadump.show_progress)
		print_progress("Copied %u of %u inodes (%u of %u AGs)",
				inodes_copied, mp->m_sb.sb_icount, agno,
				mp->m_sb.sb_agcount);
	rval = 1;
pop_out:
	pop_cur();
	return rval;
}

static int
scanfunc_ino(
	struct xfs_btree_block	*block,
	xfs_agnumber_t		agno,
	xfs_agblock_t		agbno,
	int			level,
	typnm_t			btype,
	void			*arg)
{
	xfs_inobt_rec_t		*rp;
	xfs_inobt_ptr_t		*pp;
	int			i;
	int			numrecs;
	int			finobt = *(int *) arg;
	struct xfs_ino_geometry	*igeo = M_IGEO(mp);

	numrecs = be16_to_cpu(block->bb_numrecs);

	if (level == 0) {
		if (numrecs > igeo->inobt_mxr[0]) {
			if (metadump.show_warnings)
				print_warning("invalid numrecs %d in %s "
					"block %u/%u", numrecs,
					typtab[btype].name, agno, agbno);
			numrecs = igeo->inobt_mxr[0];
		}

		/*
		 * Only copy the btree blocks for the finobt. The inobt scan
		 * copies the inode chunks.
		 */
		if (finobt)
			return 1;

		rp = XFS_INOBT_REC_ADDR(mp, block, 1);
		for (i = 0; i < numrecs; i++, rp++) {
			if (!copy_inode_chunk(agno, rp))
				return 0;
		}
		return 1;
	}

	if (numrecs > igeo->inobt_mxr[1]) {
		if (metadump.show_warnings)
			print_warning("invalid numrecs %d in %s block %u/%u",
				numrecs, typtab[btype].name, agno, agbno);
		numrecs = igeo->inobt_mxr[1];
	}

	pp = XFS_INOBT_PTR_ADDR(mp, block, 1, igeo->inobt_mxr[1]);
	for (i = 0; i < numrecs; i++) {
		if (!valid_bno(agno, be32_to_cpu(pp[i]))) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u/%u) "
					"in %s block %u/%u",
					agno, be32_to_cpu(pp[i]),
					typtab[btype].name, agno, agbno);
			continue;
		}
		if (!scan_btree(agno, be32_to_cpu(pp[i]), level,
				btype, arg, scanfunc_ino))
			return 0;
	}
	return 1;
}

static int
copy_inodes(
	xfs_agnumber_t		agno,
	xfs_agi_t		*agi)
{
	xfs_agblock_t		root;
	int			levels;
	int			finobt = 0;

	root = be32_to_cpu(agi->agi_root);
	levels = be32_to_cpu(agi->agi_level);

	/* validate root and levels before processing the tree */
	if (root == 0 || root > mp->m_sb.sb_agblocks) {
		if (metadump.show_warnings)
			print_warning("invalid block number (%u) in inobt "
					"root in agi %u", root, agno);
		return 1;
	}
	if (levels > M_IGEO(mp)->inobt_maxlevels) {
		if (metadump.show_warnings)
			print_warning("invalid level (%u) in inobt root "
					"in agi %u", levels, agno);
		return 1;
	}

	if (!scan_btree(agno, root, levels, TYP_INOBT, &finobt, scanfunc_ino))
		return 0;

	if (xfs_has_finobt(mp)) {
		root = be32_to_cpu(agi->agi_free_root);
		levels = be32_to_cpu(agi->agi_free_level);

		if (root == 0 || root > mp->m_sb.sb_agblocks) {
			if (metadump.show_warnings)
				print_warning("invalid block number (%u) in "
						"finobt root in agi %u", root,
						agno);
			return 1;
		}

		if (levels > M_IGEO(mp)->inobt_maxlevels) {
			if (metadump.show_warnings)
				print_warning("invalid level (%u) in finobt "
						"root in agi %u", levels, agno);
			return 1;
		}

		finobt = 1;
		if (!scan_btree(agno, root, levels, TYP_FINOBT, &finobt,
				scanfunc_ino))
			return 0;
	}

	return 1;
}

static int
scan_ag(
	xfs_agnumber_t	agno)
{
	xfs_agf_t	*agf;
	xfs_agi_t	*agi;
	int		stack_count = 0;
	int		rval = 0;

	/* copy the superblock of the AG */
	push_cur();
	stack_count++;
	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) {
		print_warning("cannot read superblock for ag %u", agno);
		if (metadump.stop_on_read_error)
			goto pop_out;
	} else {
		/* Replace any filesystem label with "L's" */
		if (metadump.obfuscate) {
			struct xfs_sb *sb = iocur_top->data;
			memset(sb->sb_fname, 'L',
			       min(strlen(sb->sb_fname), sizeof(sb->sb_fname)));
			iocur_top->need_crc = 1;
		}
		if (write_buf(iocur_top))
			goto pop_out;
	}

	/* copy the AG free space btree root */
	push_cur();
	stack_count++;
	set_cur(&typtab[TYP_AGF], XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)),
			XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	agf = iocur_top->data;
	if (iocur_top->data == NULL) {
		print_warning("cannot read agf block for ag %u", agno);
		if (metadump.stop_on_read_error)
			goto pop_out;
	} else {
		if (write_buf(iocur_top))
			goto pop_out;
	}

	/* copy the AG inode btree root */
	push_cur();
	stack_count++;
	set_cur(&typtab[TYP_AGI], XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)),
			XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	agi = iocur_top->data;
	if (iocur_top->data == NULL) {
		print_warning("cannot read agi block for ag %u", agno);
		if (metadump.stop_on_read_error)
			goto pop_out;
	} else {
		if (write_buf(iocur_top))
			goto pop_out;
	}

	/* copy the AG free list header */
	push_cur();
	stack_count++;
	set_cur(&typtab[TYP_AGFL], XFS_AG_DADDR(mp, agno, XFS_AGFL_DADDR(mp)),
			XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
	if (iocur_top->data == NULL) {
		print_warning("cannot read agfl block for ag %u", agno);
		if (metadump.stop_on_read_error)
			goto pop_out;
	} else {
		if (agf && metadump.zero_stale_data) {
			/* Zero out unused bits of agfl */
			int i;
			 __be32  *agfl_bno;

			agfl_bno = xfs_buf_to_agfl_bno(iocur_top->bp);
			i = be32_to_cpu(agf->agf_fllast);

			for (;;) {
				if (++i == libxfs_agfl_size(mp))
					i = 0;
				if (i == be32_to_cpu(agf->agf_flfirst))
					break;
				agfl_bno[i] = cpu_to_be32(NULLAGBLOCK);
			}
			iocur_top->need_crc = 1;
		}
		if (write_buf(iocur_top))
			goto pop_out;
	}

	/* copy AG free space btrees */
	if (agf) {
		if (metadump.show_progress)
			print_progress("Copying free space trees of AG %u",
					agno);
		if (!copy_free_bno_btree(agno, agf))
			goto pop_out;
		if (!copy_free_cnt_btree(agno, agf))
			goto pop_out;
		if (!copy_rmap_btree(agno, agf))
			goto pop_out;
		if (!copy_refcount_btree(agno, agf))
			goto pop_out;
	}

	/* copy inode btrees and the inodes and their associated metadata */
	if (agi) {
		if (!copy_inodes(agno, agi))
			goto pop_out;
	}
	rval = 1;
pop_out:
	while (stack_count--)
		pop_cur();
	return rval;
}

static int
copy_ino(
	xfs_ino_t		ino,
	typnm_t			itype)
{
	xfs_agnumber_t		agno;
	xfs_agblock_t		agbno;
	xfs_agino_t		agino;
	int			offset;
	int			rval = 1;

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

	agno = XFS_INO_TO_AGNO(mp, ino);
	agino = XFS_INO_TO_AGINO(mp, ino);
	agbno = XFS_AGINO_TO_AGBNO(mp, agino);
	offset = XFS_AGINO_TO_OFFSET(mp, agino);

	if (agno >= mp->m_sb.sb_agcount || agbno >= mp->m_sb.sb_agblocks ||
			offset >= mp->m_sb.sb_inopblock) {
		if (metadump.show_warnings)
			print_warning("invalid %s inode number (%lld)",
					typtab[itype].name, (long long)ino);
		return 1;
	}

	push_cur();
	set_cur(&typtab[TYP_INODE], XFS_AGB_TO_DADDR(mp, agno, agbno),
			blkbb, DB_RING_IGN, NULL);
	if (iocur_top->data == NULL) {
		print_warning("cannot read %s inode %lld",
				typtab[itype].name, (long long)ino);
		rval = !metadump.stop_on_read_error;
		goto pop_out;
	}
	off_cur(offset << mp->m_sb.sb_inodelog, mp->m_sb.sb_inodesize);

	metadump.cur_ino = ino;
	rval = process_inode_data(iocur_top->data, itype);
pop_out:
	pop_cur();
	return rval;
}


static int
copy_sb_inodes(void)
{
	if (!copy_ino(mp->m_sb.sb_rbmino, TYP_RTBITMAP))
		return 0;

	if (!copy_ino(mp->m_sb.sb_rsumino, TYP_RTSUMMARY))
		return 0;

	if (!copy_ino(mp->m_sb.sb_uquotino, TYP_DQBLK))
		return 0;

	if (!copy_ino(mp->m_sb.sb_gquotino, TYP_DQBLK))
		return 0;

	return copy_ino(mp->m_sb.sb_pquotino, TYP_DQBLK);
}

static int
copy_log(void)
{
	struct xlog	log;
	int		dirty;
	xfs_daddr_t	logstart;
	int		logblocks;
	int		logversion;
	int		cycle = XLOG_INIT_CYCLE;

	if (metadump.show_progress)
		print_progress("Copying log");

	push_cur();
	if (metadump.external_log) {
		ASSERT(mp->m_sb.sb_logstart == 0);
		set_log_cur(&typtab[TYP_LOG],
				XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart),
				mp->m_sb.sb_logblocks * blkbb, DB_RING_IGN,
				NULL);
	} else {
		ASSERT(mp->m_sb.sb_logstart != 0);
		set_cur(&typtab[TYP_LOG],
				XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart),
				mp->m_sb.sb_logblocks * blkbb, DB_RING_IGN,
				NULL);
	}

	if (iocur_top->data == NULL) {
		pop_cur();
		print_warning("cannot read log");
		return !metadump.stop_on_read_error;
	}

	/* If not obfuscating or zeroing, just copy the log as it is */
	if (!metadump.obfuscate && !metadump.zero_stale_data)
		goto done;

	dirty = xlog_is_dirty(mp, &log);

	switch (dirty) {
	case 0:
		/* clear out a clean log */
		if (metadump.show_progress)
			print_progress("Zeroing clean log");

		logstart = XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart);
		logblocks = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks);
		logversion = xfs_has_logv2(mp) ? 2 : 1;
		if (xfs_has_crc(mp))
			cycle = log.l_curr_cycle + 1;

		libxfs_log_clear(NULL, iocur_top->data, logstart, logblocks,
				 &mp->m_sb.sb_uuid, logversion,
				 mp->m_sb.sb_logsunit, XLOG_FMT, cycle, true);
		break;
	case 1:
		/* keep the dirty log */
		if (metadump.obfuscate)
			print_warning(
_("Warning: log recovery of an obfuscated metadata image can leak "
"unobfuscated metadata and/or cause image corruption.  If possible, "
"please mount the filesystem to clean the log, or disable obfuscation."));
		break;
	case -1:
		/* log detection error */
		if (metadump.obfuscate)
			print_warning(
_("Could not discern log; image will contain unobfuscated metadata in log."));
		break;
	}

done:
	return !write_buf(iocur_top);
}

static int
init_metadump_v1(void)
{
	metadump.metablock = (xfs_metablock_t *)calloc(BBSIZE + 1, BBSIZE);
	if (metadump.metablock == NULL) {
		print_warning("memory allocation failure");
		return -1;
	}
	metadump.metablock->mb_blocklog = BBSHIFT;
	metadump.metablock->mb_magic = cpu_to_be32(XFS_MD_MAGIC_V1);

	/* Set flags about state of metadump */
	metadump.metablock->mb_info = XFS_METADUMP_INFO_FLAGS;
	if (metadump.obfuscate)
		metadump.metablock->mb_info |= XFS_METADUMP_OBFUSCATED;
	if (!metadump.zero_stale_data)
		metadump.metablock->mb_info |= XFS_METADUMP_FULLBLOCKS;
	if (metadump.dirty_log)
		metadump.metablock->mb_info |= XFS_METADUMP_DIRTYLOG;

	metadump.block_index = (__be64 *)((char *)metadump.metablock +
				sizeof(xfs_metablock_t));
	metadump.block_buffer = (char *)(metadump.metablock) + BBSIZE;
	metadump.num_indices = (BBSIZE - sizeof(xfs_metablock_t)) / sizeof(__be64);

	/*
	 * A metadump block can hold at most num_indices of BBSIZE sectors;
	 * do not try to dump a filesystem with a sector size which does not
	 * fit within num_indices (i.e. within a single metablock).
	 */
	if (mp->m_sb.sb_sectsize > metadump.num_indices * BBSIZE) {
		print_warning("Cannot dump filesystem with sector size %u",
			      mp->m_sb.sb_sectsize);
		free(metadump.metablock);
		return -1;
	}

	metadump.cur_index = 0;

	return 0;
}

static int
finish_dump_metadump_v1(void)
{
	/*
	 * write index block and following data blocks (streaming)
	 */
	metadump.metablock->mb_count = cpu_to_be16(metadump.cur_index);
	if (fwrite(metadump.metablock, (metadump.cur_index + 1) << BBSHIFT, 1,
			metadump.outf) != 1) {
		print_warning("error writing to target file");
		return -1;
	}

	memset(metadump.block_index, 0, metadump.num_indices * sizeof(__be64));
	metadump.cur_index = 0;
	return 0;
}

static int
write_metadump_v1(
	enum typnm	type,
	const char	*data,
	xfs_daddr_t	off,
	int		len)
{
	int		i;
	int		ret;

	for (i = 0; i < len; i++, off++, data += BBSIZE) {
		metadump.block_index[metadump.cur_index] = cpu_to_be64(off);
		memcpy(&metadump.block_buffer[metadump.cur_index << BBSHIFT],
				data, BBSIZE);
		if (++metadump.cur_index == metadump.num_indices) {
			ret = finish_dump_metadump_v1();
			if (ret)
				return -EIO;
		}
	}

	return 0;
}

static void
release_metadump_v1(void)
{
	free(metadump.metablock);
}

static struct metadump_ops metadump1_ops = {
	.init		= init_metadump_v1,
	.write		= write_metadump_v1,
	.finish_dump	= finish_dump_metadump_v1,
	.release	= release_metadump_v1,
};

static int
init_metadump_v2(void)
{
	struct xfs_metadump_header	xmh = {0};
	uint32_t			compat_flags = 0;

	xmh.xmh_magic = cpu_to_be32(XFS_MD_MAGIC_V2);
	xmh.xmh_version = cpu_to_be32(2);

	if (metadump.obfuscate)
		compat_flags |= XFS_MD2_COMPAT_OBFUSCATED;
	if (!metadump.zero_stale_data)
		compat_flags |= XFS_MD2_COMPAT_FULLBLOCKS;
	if (metadump.dirty_log)
		compat_flags |= XFS_MD2_COMPAT_DIRTYLOG;
	if (metadump.external_log)
		compat_flags |= XFS_MD2_COMPAT_EXTERNALLOG;

	xmh.xmh_compat_flags = cpu_to_be32(compat_flags);

	if (fwrite(&xmh, sizeof(xmh), 1, metadump.outf) != 1) {
		print_warning("error writing to target file");
		return -1;
	}

	return 0;
}

static int
write_metadump_v2(
	enum typnm		type,
	const char		*data,
	xfs_daddr_t		off,
	int			len)
{
	struct xfs_meta_extent	xme;
	uint64_t		addr;

	addr = off;
	if (type == TYP_LOG &&
	    mp->m_logdev_targp->bt_bdev != mp->m_ddev_targp->bt_bdev)
		addr |= XME_ADDR_LOG_DEVICE;
	else
		addr |= XME_ADDR_DATA_DEVICE;

	xme.xme_addr = cpu_to_be64(addr);
	xme.xme_len = cpu_to_be32(len);

	if (fwrite(&xme, sizeof(xme), 1, metadump.outf) != 1) {
		print_warning("error writing to target file");
		return -EIO;
	}

	if (fwrite(data, len << BBSHIFT, 1, metadump.outf) != 1) {
		print_warning("error writing to target file");
		return -EIO;
	}

	return 0;
}

static struct metadump_ops metadump2_ops = {
	.init	= init_metadump_v2,
	.write	= write_metadump_v2,
};

static int
metadump_f(
	int 		argc,
	char 		**argv)
{
	xfs_agnumber_t	agno;
	int		c;
	int		start_iocur_sp;
	int		outfd = -1;
	int		ret;
	char		*p;
	bool		version_opt_set = false;

	exitcode = 1;

	metadump.version = 1;
	metadump.show_progress = false;
	metadump.stop_on_read_error = false;
	metadump.max_extent_size = DEFAULT_MAX_EXT_SIZE;
	metadump.show_warnings = false;
	metadump.obfuscate = true;
	metadump.zero_stale_data = true;
	metadump.dirty_log = false;
	metadump.external_log = false;

	if (mp->m_sb.sb_magicnum != XFS_SB_MAGIC) {
		print_warning("bad superblock magic number %x, giving up",
				mp->m_sb.sb_magicnum);
		return 0;
	}

	/*
	 * on load, we sanity-checked agcount and possibly set to 1
	 * if it was corrupted and large.
	 */
	if (mp->m_sb.sb_agcount == 1 &&
	    XFS_MAX_DBLOCKS(&mp->m_sb) < mp->m_sb.sb_dblocks) {
		print_warning("truncated agcount, giving up");
		return 0;
	}

	while ((c = getopt(argc, argv, "aegm:ov:w")) != EOF) {
		switch (c) {
			case 'a':
				metadump.zero_stale_data = false;
				break;
			case 'e':
				metadump.stop_on_read_error = true;
				break;
			case 'g':
				metadump.show_progress = true;
				break;
			case 'm':
				metadump.max_extent_size =
					(int)strtol(optarg, &p, 0);
				if (*p != '\0' ||
				    metadump.max_extent_size <= 0) {
					print_warning("bad max extent size %s",
							optarg);
					return 0;
				}
				break;
			case 'o':
				metadump.obfuscate = false;
				break;
			case 'v':
				metadump.version = (int)strtol(optarg, &p, 0);
				if (*p != '\0' ||
				    (metadump.version != 1 &&
						metadump.version != 2)) {
					print_warning("bad metadump version: %s",
						optarg);
					return 0;
				}
				version_opt_set = true;
				break;
			case 'w':
				metadump.show_warnings = true;
				break;
			default:
				print_warning("bad option for metadump command");
				return 0;
		}
	}

	if (optind != argc - 1) {
		print_warning("too few options for metadump (no filename given)");
		return 0;
	}

	if (mp->m_logdev_targp->bt_bdev != mp->m_ddev_targp->bt_bdev)
		metadump.external_log = true;

	if (metadump.external_log && !version_opt_set)
		metadump.version = 2;

	if (metadump.version == 2 && mp->m_sb.sb_logstart == 0 &&
	    !metadump.external_log) {
		print_warning("external log device not loaded, use -l");
		return 1;
	}

	/*
	 * If we'll copy the log, see if the log is dirty.
	 *
	 * Metadump v1 does not support dumping the contents of an external
	 * log. Hence we skip the dirty log check.
	 */
	if (!(metadump.version == 1 && metadump.external_log)) {
		push_cur();
		if (metadump.external_log) {
			ASSERT(mp->m_sb.sb_logstart == 0);
			set_log_cur(&typtab[TYP_LOG],
					XFS_FSB_TO_DADDR(mp,
							mp->m_sb.sb_logstart),
					mp->m_sb.sb_logblocks * blkbb,
					DB_RING_IGN, NULL);
		} else {
			ASSERT(mp->m_sb.sb_logstart != 0);
			set_cur(&typtab[TYP_LOG],
					XFS_FSB_TO_DADDR(mp,
							mp->m_sb.sb_logstart),
					mp->m_sb.sb_logblocks * blkbb,
					DB_RING_IGN, NULL);
		}

		if (iocur_top->data) {	/* best effort */
			struct xlog	log;

			if (xlog_is_dirty(mp, &log))
				metadump.dirty_log = true;
		}
		pop_cur();
	}

	start_iocur_sp = iocur_sp;

	if (strcmp(argv[optind], "-") == 0) {
		if (isatty(fileno(stdout))) {
			print_warning("cannot write to a terminal");
			goto out;
		}
		/*
		 * Redirect stdout to stderr for the duration of the
		 * metadump operation so that dbprintf and other messages
		 * are sent to the console instead of polluting the
		 * metadump stream.
		 *
		 * We get to do this the hard way because musl doesn't
		 * allow reassignment of stdout.
		 */
		fflush(stdout);
		outfd = dup(STDOUT_FILENO);
		if (outfd < 0) {
			perror("opening dump stream");
			goto out;
		}
		ret = dup2(STDERR_FILENO, STDOUT_FILENO);
		if (ret < 0) {
			perror("redirecting stdout");
			close(outfd);
			goto out;
		}
		metadump.outf = fdopen(outfd, "a");
		if (metadump.outf == NULL) {
			fprintf(stderr, "cannot create dump stream\n");
			dup2(outfd, STDOUT_FILENO);
			close(outfd);
			goto out;
		}
		metadump.stdout_metadump = true;
	} else {
		metadump.outf = fopen(argv[optind], "wb");
		if (metadump.outf == NULL) {
			print_warning("cannot create dump file");
			goto out;
		}
	}

	if (metadump.version == 1)
		metadump.mdops = &metadump1_ops;
	else
		metadump.mdops = &metadump2_ops;

	ret = metadump.mdops->init();
	if (ret)
		goto out;

	exitcode = 0;

	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
		if (!scan_ag(agno)) {
			exitcode = 1;
			break;
		}
	}

	/* copy realtime and quota inode contents */
	if (!exitcode)
		exitcode = !copy_sb_inodes();

	/* copy log */
	if (!exitcode && !(metadump.version == 1 && metadump.external_log))
		exitcode = !copy_log();

	/* write the remaining index */
	if (!exitcode && metadump.mdops->finish_dump)
		exitcode = metadump.mdops->finish_dump() < 0;

	if (metadump.progress_since_warning)
		fputc('\n', metadump.stdout_metadump ? stderr : stdout);

	if (metadump.stdout_metadump) {
		fflush(metadump.outf);
		fflush(stdout);
		ret = dup2(outfd, STDOUT_FILENO);
		if (ret < 0)
			perror("un-redirecting stdout");
		metadump.stdout_metadump = false;
	}
	fclose(metadump.outf);

	/* cleanup iocur stack */
	while (iocur_sp > start_iocur_sp)
		pop_cur();

	if (metadump.mdops->release)
		metadump.mdops->release();

out:
	remaptable_clear();
	return 0;
}
