| // 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; | 
 | } |