Blob Blame History Raw
diff --git a/db/metadump.c b/db/metadump.c
index a599571..238f864 100644
--- a/db/metadump.c
+++ b/db/metadump.c
@@ -17,6 +17,7 @@
  */
 
 #include <libxfs.h>
+#include <libxlog.h>
 #include "bmap.h"
 #include "command.h"
 #include "metadump.h"
@@ -56,7 +57,7 @@ static void	metadump_help(void);
 
 static const cmdinfo_t	metadump_cmd =
 	{ "metadump", NULL, metadump_f, 0, -1, 0,
-		N_("[-e] [-g] [-m max_extent] [-w] [-o] filename"),
+		N_("[-a] [-e] [-g] [-m max_extent] [-w] [-o] filename"),
 		N_("dump metadata to a file"), metadump_help };
 
 static FILE		*outf;		/* metadump file */
@@ -73,7 +74,8 @@ static xfs_ino_t	cur_ino;
 static int		show_progress = 0;
 static int		stop_on_read_error = 0;
 static int		max_extent_size = DEFAULT_MAX_EXT_SIZE;
-static int		dont_obfuscate = 0;
+static int		obfuscate = 1;
+static int		zero_stale_data = 1;
 static int		show_warnings = 0;
 static int		progress_since_warning = 0;
 
@@ -92,6 +94,7 @@ metadump_help(void)
 " 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"
@@ -242,6 +245,124 @@ write_buf(
 	return seenint() ? -EINTR : 0;
 }
 
+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;
+	void			*zp1, *zp2;
+	int			zlen1, zlen2;
+
+	nrecs = be16_to_cpu(block->bb_numrecs);
+
+	switch (btype) {
+	case TYP_BMAPBTA:
+	case TYP_BMAPBTD:
+		bkp = XFS_BMBT_KEY_ADDR(mp, block, 1);
+		bpp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[1]);
+		zp1 = &bkp[nrecs];
+		zlen1 = (char *)&bpp[0] - (char *)&bkp[nrecs];
+		zp2 = &bpp[nrecs];
+		zlen2 = (char *)block + mp->m_sb.sb_blocksize -
+							(char *)&bpp[nrecs];
+		break;
+	case TYP_INOBT:
+	case TYP_FINOBT:
+		ikp = XFS_INOBT_KEY_ADDR(mp, block, 1);
+		ipp = XFS_INOBT_PTR_ADDR(mp, block, 1, mp->m_inobt_mxr[1]);
+		zp1 = &ikp[nrecs];
+		zlen1 = (char *)&ipp[0] - (char *)&ikp[nrecs];
+		zp2 = &ipp[nrecs];
+		zlen2 = (char *)block + mp->m_sb.sb_blocksize -
+							(char *)&ipp[nrecs];
+		break;
+	case TYP_BNOBT:
+	case TYP_CNTBT:
+		akp = XFS_ALLOC_KEY_ADDR(mp, block, 1);
+		app = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
+		zp1 = &akp[nrecs];
+		zlen1 = (char *)&app[0] - (char *)&akp[nrecs];
+		zp2 = &app[nrecs];
+		zlen2 = (char *)block + mp->m_sb.sb_blocksize -
+							(char *)&app[nrecs];
+		break;
+	default:
+		zp1 = NULL;
+		break;
+	}
+
+	if (zp1 && zp2) {
+		/* Zero from end of keys to beginning of pointers */
+		memset(zp1, 0, zlen1);
+		/* Zero from end of pointers to end of block */
+		memset(zp2, 0, zlen2);
+	}
+}
+
+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;
+	void			*zp;
+	int			zlen;
+
+	nrecs = be16_to_cpu(block->bb_numrecs);
+
+	switch (btype) {
+	case TYP_BMAPBTA:
+	case TYP_BMAPBTD:
+		brp = XFS_BMBT_REC_ADDR(mp, block, 1);
+		zp = &brp[nrecs];
+		zlen = (char *)block + mp->m_sb.sb_blocksize - (char *)&brp[nrecs];
+		break;
+	case TYP_INOBT:
+	case TYP_FINOBT:
+		irp = XFS_INOBT_REC_ADDR(mp, block, 1);
+		zp = &irp[nrecs];
+		zlen = (char *)block + mp->m_sb.sb_blocksize - (char *)&irp[nrecs];
+		break;
+	case TYP_BNOBT:
+	case TYP_CNTBT:
+		arp = XFS_ALLOC_REC_ADDR(mp, block, 1);
+		zp = &arp[nrecs];
+		zlen = (char *)block + mp->m_sb.sb_blocksize - (char *)&arp[nrecs];
+		break;
+	default:
+		zp = NULL;
+		break;
+	}
+
+	/* Zero from end of records to end of block */
+	if (zp && zlen < mp->m_sb.sb_blocksize)
+		memset(zp, 0, zlen);
+}
+
+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(
@@ -268,6 +389,12 @@ scan_btree(
 		rval = !stop_on_read_error;
 		goto pop_out;
 	}
+
+	if (zero_stale_data) {
+		zero_btree_block(iocur_top->data, btype);
+		iocur_top->need_crc = 1;
+	}
+
 	if (write_buf(iocur_top))
 		goto pop_out;
 
@@ -960,7 +1087,7 @@ generate_obfuscated_name(
 }
 
 static void
-obfuscate_sf_dir(
+process_sf_dir(
 	xfs_dinode_t		*dip)
 {
 	struct xfs_dir2_sf_hdr	*sfp;
@@ -1007,12 +1134,18 @@ obfuscate_sf_dir(
 					 (char *)sfp);
 		}
 
-		generate_obfuscated_name(xfs_dir3_sfe_get_ino(mp, sfp, sfep),
+		if (obfuscate)
+			generate_obfuscated_name(
+					 xfs_dir3_sfe_get_ino(mp, sfp, sfep),
 					 namelen, &sfep->name[0]);
 
 		sfep = (xfs_dir2_sf_entry_t *)((char *)sfep +
 				xfs_dir3_sf_entsize(mp, sfp, namelen));
 	}
+
+	/* zero stale data in rest of space in data fork, if any */
+	if (zero_stale_data && (ino_dir_size < XFS_DFORK_DSIZE(dip, mp)))
+		memset(sfep, 0, XFS_DFORK_DSIZE(dip, mp) - ino_dir_size);
 }
 
 /*
@@ -1059,7 +1192,7 @@ obfuscate_path_components(
 }
 
 static void
-obfuscate_sf_symlink(
+process_sf_symlink(
 	xfs_dinode_t		*dip)
 {
 	__uint64_t		len;
@@ -1074,16 +1207,21 @@ obfuscate_sf_symlink(
 	}
 
 	buf = (char *)XFS_DFORK_DPTR(dip);
-	obfuscate_path_components(buf, len);
+	if (obfuscate)
+		obfuscate_path_components(buf, len);
+
+	/* zero stale data in rest of space in data fork, if any */
+	if (zero_stale_data && len < XFS_DFORK_DSIZE(dip, mp))
+		memset(&buf[len], 0, XFS_DFORK_DSIZE(dip, mp) - len);
 }
 
 static void
-obfuscate_sf_attr(
+process_sf_attr(
 	xfs_dinode_t		*dip)
 {
 	/*
-	 * with extended attributes, obfuscate the names and zero the actual
-	 * values.
+	 * with extended attributes, obfuscate the names and fill the actual
+	 * values with 'v' (to see a valid string length, as opposed to NULLs)
 	 */
 
 	xfs_attr_shortform_t	*asfp;
@@ -1122,16 +1260,24 @@ obfuscate_sf_attr(
 			break;
 		}
 
-		generate_obfuscated_name(0, asfep->namelen, &asfep->nameval[0]);
-		memset(&asfep->nameval[asfep->namelen], 0, asfep->valuelen);
+		if (obfuscate) {
+			generate_obfuscated_name(0, asfep->namelen,
+						 &asfep->nameval[0]);
+			memset(&asfep->nameval[asfep->namelen], 'v',
+			       asfep->valuelen);
+		}
 
 		asfep = (xfs_attr_sf_entry_t *)((char *)asfep +
 				XFS_ATTR_SF_ENTSIZE(asfep));
 	}
+
+	/* zero stale data in rest of space in attr fork, if any */
+	if (zero_stale_data && (ino_attr_size < XFS_DFORK_ASIZE(dip, mp)))
+		memset(asfep, 0, XFS_DFORK_ASIZE(dip, mp) - ino_attr_size);
 }
 
 static void
-obfuscate_dir_data_block(
+process_dir_data_block(
 	char		*block,
 	xfs_dfiloff_t	offset,
 	int		is_block_format)
@@ -1151,9 +1297,6 @@ obfuscate_dir_data_block(
 
 	datahdr = (struct xfs_dir2_data_hdr *)block;
 
-	if (offset % mp->m_dirblkfsbs != 0)
-		return;	/* corrupted, leave it alone */
-
 	if (is_block_format) {
 		xfs_dir2_leaf_entry_t	*blp;
 		xfs_dir2_block_tail_t	*btp;
@@ -1186,7 +1329,7 @@ obfuscate_dir_data_block(
 
 	dir_offset = xfs_dir3_data_entry_offset(datahdr);
 	ptr = block + dir_offset;
-	endptr = block + mp->m_sb.sb_blocksize;
+	endptr = block + mp->m_dirblksize;
 
 	while (ptr < endptr && dir_offset < end_of_data) {
 		xfs_dir2_data_entry_t	*dep;
@@ -1210,6 +1353,20 @@ obfuscate_dir_data_block(
 				return;
 			dir_offset += length;
 			ptr += 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 (zero_stale_data) {
+				int zlen = 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;
 		}
@@ -1228,19 +1385,48 @@ obfuscate_dir_data_block(
 		if (be16_to_cpu(*xfs_dir3_data_entry_tag_p(mp, dep)) !=
 				dir_offset)
 			return;
-		generate_obfuscated_name(be64_to_cpu(dep->inumber),
+
+		if (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 (zero_stale_data) {
+			/* 1 byte for ftype; don't bother with conditional */
+			int zlen =
+				(char *)xfs_dir3_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 void
-obfuscate_symlink_block(
+process_symlink_block(
 	char			*block)
 {
-	/* XXX: need to handle CRC headers */
-	obfuscate_path_components(block, mp->m_sb.sb_blocksize);
+	char *link = block;
+
+	if (xfs_sb_version_hascrc(&(mp)->m_sb))
+		link += sizeof(struct xfs_dsymlink_hdr);
+
+	if (obfuscate)
+		obfuscate_path_components(link, XFS_SYMLINK_BUF_SPACE(mp,
+							mp->m_sb.sb_blocksize));
+	if (zero_stale_data) {
+		size_t	linklen, zlen;
+
+		linklen = strlen(link);
+		zlen = mp->m_sb.sb_blocksize - linklen;
+		if (xfs_sb_version_hascrc(&mp->m_sb))
+			zlen -= sizeof(struct xfs_dsymlink_hdr);
+		if (zlen < mp->m_sb.sb_blocksize)
+			memset(link + linklen, 0, zlen);
+	}
 }
 
 #define MAX_REMOTE_VALS		4095
@@ -1268,39 +1454,61 @@ add_remote_vals(
 	}
 }
 
+/* Handle remote and leaf attributes */
 static void
-obfuscate_attr_block(
-	char			*block,
-	xfs_dfiloff_t		offset)
+process_attr_block(
+	char				*block,
+	xfs_fileoff_t			offset)
 {
-	xfs_attr_leafblock_t	*leaf;
-	int			i;
-	int			nentries;
-	xfs_attr_leaf_entry_t 	*entry;
-	xfs_attr_leaf_name_local_t *local;
-	xfs_attr_leaf_name_remote_t *remote;
+	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;
 
-	if (be16_to_cpu(leaf->hdr.info.magic) != XFS_ATTR_LEAF_MAGIC) {
+	/* 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++) {
-			/* XXX: need to handle CRC headers */
-			if (attr_data.remote_vals[i] == offset)
-				memset(block, 0, XFS_LBSIZE(mp));
+			if (obfuscate && attr_data.remote_vals[i] == offset)
+				/* Macros to handle both attr and attr3 */
+				memset(block +
+					(bs - XFS_ATTR3_RMT_BUF_SPACE(mp, bs)),
+				      'v', XFS_ATTR3_RMT_BUF_SPACE(mp, bs));
 		}
 		return;
 	}
 
-	nentries = be16_to_cpu(leaf->hdr.count);
+	/* Ok, it's a leaf - get header; accounts for crc & non-crc */
+	xfs_attr3_leaf_hdr_from_disk(&hdr, leaf);
+
+	nentries = hdr.count;
 	if (nentries * sizeof(xfs_attr_leaf_entry_t) +
-			sizeof(xfs_attr_leaf_hdr_t) > XFS_LBSIZE(mp)) {
+			xfs_attr3_leaf_hdr_size(leaf) >
+				XFS_ATTR3_RMT_BUF_SPACE(mp, bs)) {
 		if (show_warnings)
 			print_warning("invalid attr count in inode %llu",
 					(long long)cur_ino);
 		return;
 	}
 
-	for (i = 0, entry = &leaf->entries[0]; i < nentries; i++, entry++) {
+	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) > XFS_LBSIZE(mp)) {
 			if (show_warnings)
 				print_warning(
@@ -1317,10 +1525,20 @@ obfuscate_attr_block(
 						(long long)cur_ino);
 				break;
 			}
-			generate_obfuscated_name(0, local->namelen,
-				&local->nameval[0]);
-			memset(&local->nameval[local->namelen], 0,
-				be16_to_cpu(local->valuelen));
+			if (obfuscate) {
+				generate_obfuscated_name(0, local->namelen,
+					&local->nameval[0]);
+				memset(&local->nameval[local->namelen], 'v',
+					be16_to_cpu(local->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) -
+				(sizeof(xfs_attr_leaf_name_local_t) - 1 +
+				 nlen + vlen);
+			if (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) {
@@ -1330,14 +1548,33 @@ obfuscate_attr_block(
 						(long long)cur_ino);
 				break;
 			}
-			generate_obfuscated_name(0, remote->namelen,
-						 &remote->name[0]);
-			add_remote_vals(be32_to_cpu(remote->valueblk),
-					be32_to_cpu(remote->valuelen));
+			if (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) -
+				(sizeof(xfs_attr_leaf_name_remote_t) - 1 +
+				 nlen);
+			if (zero_stale_data)
+				memset(&remote->name[nlen], 0, zlen);
 		}
 	}
+
+	/* Zero from end of entries array to the first name/val */
+	if (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_dfiloff_t	o,
@@ -1367,25 +1604,50 @@ process_single_fsb_objects(
 
 		}
 
-		if (dont_obfuscate)
+		if (!obfuscate && !zero_stale_data)
 			goto write;
 
+		/* Zero unused part of interior nodes */
+		if (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;
+
+				xfs_da3_node_hdr_from_disk(&hdr, node);
+				used = xfs_da3_node_hdr_size(node);
+
+				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_dirleafblk)
 				break;
 
-			obfuscate_dir_data_block(dp, o,
+			process_dir_data_block(dp, o,
 						 last == mp->m_dirblkfsbs);
 			iocur_top->need_crc = 1;
 			break;
 		case TYP_SYMLINK:
-			obfuscate_symlink_block(dp);
+			process_symlink_block(dp);
 			iocur_top->need_crc = 1;
 			break;
 		case TYP_ATTR:
-			obfuscate_attr_block(dp, o);
+			process_attr_block(dp, o);
 			iocur_top->need_crc = 1;
 			break;
 		default:
@@ -1459,13 +1721,14 @@ process_multi_fsb_objects(
 
 			}
 
-			if (dont_obfuscate || o >= mp->m_dirleafblk) {
+			if ((!obfuscate && !zero_stale_data) ||
+			     o >= mp->m_dirleafblk) {
 				ret = write_buf(iocur_top);
 				goto out_pop;
 			}
 
-			obfuscate_dir_data_block(iocur_top->data, o,
-						  last == mp->m_dirblkfsbs);
+			process_dir_data_block(iocur_top->data, o,
+					       last == mp->m_dirblkfsbs);
 			iocur_top->need_crc = 1;
 			ret = write_buf(iocur_top);
 out_pop:
@@ -1700,19 +1963,26 @@ process_exinode(
 	typnm_t			itype)
 {
 	int			whichfork;
+	int			used;
 	xfs_extnum_t		nex;
 
 	whichfork = (itype == TYP_ATTR) ? XFS_ATTR_FORK : XFS_DATA_FORK;
 
 	nex = XFS_DFORK_NEXTENTS(dip, whichfork);
-	if (nex < 0 || nex > XFS_DFORK_SIZE(dip, mp, whichfork) /
-						sizeof(xfs_bmbt_rec_t)) {
+	used = nex * sizeof(xfs_bmbt_rec_t);
+	if (nex < 0 || used > XFS_DFORK_SIZE(dip, mp, whichfork)) {
 		if (show_warnings)
 			print_warning("bad number of extents %d in inode %lld",
 				nex, (long long)cur_ino);
 		return 1;
 	}
 
+	/* Zero unused data fork past used extents */
+	if (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);
 }
@@ -1724,14 +1994,14 @@ process_inode_data(
 {
 	switch (dip->di_format) {
 		case XFS_DINODE_FMT_LOCAL:
-			if (!dont_obfuscate)
+			if (obfuscate || zero_stale_data)
 				switch (itype) {
 					case TYP_DIR2:
-						obfuscate_sf_dir(dip);
+						process_sf_dir(dip);
 						break;
 
 					case TYP_SYMLINK:
-						obfuscate_sf_symlink(dip);
+						process_sf_symlink(dip);
 						break;
 
 					default: ;
@@ -1758,7 +2028,8 @@ static int
 process_inode(
 	xfs_agnumber_t		agno,
 	xfs_agino_t 		agino,
-	xfs_dinode_t 		*dip)
+	xfs_dinode_t 		*dip,
+	bool			free_inode)
 {
 	int			success;
 	bool			crc_was_ok = false; /* no recalc by default */
@@ -1767,13 +2038,22 @@ process_inode(
 	success = 1;
 	cur_ino = XFS_AGINO_TO_INO(mp, agno, agino);
 
-	/* we only care about crc recalculation if we are obfuscating names. */
-	if (!dont_obfuscate) {
+	/* we only care about crc recalculation if we will modify the inode. */
+	if (obfuscate || zero_stale_data) {
 		crc_was_ok = xfs_verify_cksum((char *)dip,
 					mp->m_sb.sb_inodesize,
 					offsetof(struct xfs_dinode, di_crc));
 	}
 
+	if (free_inode) {
+		if (zero_stale_data) {
+			/* Zero all of the inode literal area */
+			memset(XFS_DFORK_DPTR(dip), 0,
+			       XFS_LITINO(mp, dip->di_version));
+		}
+		goto done;
+	}
+
 	/* copy appropriate data fork metadata */
 	switch (be16_to_cpu(dip->di_mode) & S_IFMT) {
 		case S_IFDIR:
@@ -1800,8 +2080,8 @@ process_inode(
 		switch (dip->di_aformat) {
 			case XFS_DINODE_FMT_LOCAL:
 				need_new_crc = 1;
-				if (!dont_obfuscate)
-					obfuscate_sf_attr(dip);
+				if (obfuscate || zero_stale_data)
+					process_sf_attr(dip);
 				break;
 
 			case XFS_DINODE_FMT_EXTENTS:
@@ -1815,6 +2095,11 @@ process_inode(
 		nametable_clear();
 	}
 
+done:
+	/* Heavy handed but low cost; just do it as a catch-all. */
+	if (zero_stale_data)
+		need_new_crc = 1;
+
 	if (crc_was_ok && need_new_crc)
 		xfs_dinode_calc_crc(mp, dip);
 	return success;
@@ -1880,13 +2165,12 @@ copy_inode_chunk(
 	for (i = 0; i < XFS_INODES_PER_CHUNK; i++) {
 		xfs_dinode_t            *dip;
 
-		if (XFS_INOBT_IS_FREE_DISK(rp, i))
-			continue;
-
 		dip = (xfs_dinode_t *)((char *)iocur_top->data +
 				((off + i) << mp->m_sb.sb_inodelog));
 
-		if (!process_inode(agno, agino + i, dip))
+		/* process_inode handles free inodes, too */
+		if (!process_inode(agno, agino + i, dip,
+				   XFS_INOBT_IS_FREE_DISK(rp, i)))
 			goto pop_out;
 	}
 skip_processing:
@@ -2031,6 +2315,13 @@ scan_ag(
 		if (stop_on_read_error)
 			goto pop_out;
 	} else {
+		/* Replace any filesystem label with "L's" */
+		if (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;
 	}
@@ -2075,6 +2366,23 @@ scan_ag(
 		if (stop_on_read_error)
 			goto pop_out;
 	} else {
+		if (agf && zero_stale_data) {
+			/* Zero out unused bits of agfl */
+			int i;
+			 __be32  *agfl_bno;
+
+			agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, iocur_top->bp);
+			i = be32_to_cpu(agf->agf_fllast);
+
+			for (;;) {
+				if (++i == XFS_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;
 	}
@@ -2169,6 +2477,8 @@ copy_sb_inodes(void)
 static int
 copy_log(void)
 {
+	int dirty;
+
 	if (show_progress)
 		print_progress("Copying log");
 
@@ -2180,6 +2490,34 @@ copy_log(void)
 		print_warning("cannot read log");
 		return !stop_on_read_error;
 	}
+
+	/* If not obfuscating or zeroing, just copy the log as it is */
+	if (!obfuscate && !zero_stale_data)
+		goto done;
+
+	dirty = xlog_is_dirty(mp, &x, 0);
+
+	switch (dirty) {
+	case 0:
+		/* clear out a clean log */
+		if (show_progress)
+			print_progress("Zeroing clean log");
+		memset(iocur_top->data, 0,
+			mp->m_sb.sb_logblocks * mp->m_sb.sb_blocksize);
+		break;
+	case 1:
+		/* keep the dirty log */
+		print_warning(
+_("Filesystem log is dirty; image will contain unobfuscated metadata in log."));
+		break;
+	case -1:
+		/* log detection error */
+		print_warning(
+_("Could not discern log; image will contain unobfuscated metadata in log."));
+		break;
+	}
+
+done:
 	return !write_buf(iocur_top);
 }
 
@@ -2204,8 +2542,11 @@ metadump_f(
 		return 0;
 	}
 
-	while ((c = getopt(argc, argv, "egm:ow")) != EOF) {
+	while ((c = getopt(argc, argv, "aegm:ow")) != EOF) {
 		switch (c) {
+			case 'a':
+				zero_stale_data = 0;
+				break;
 			case 'e':
 				stop_on_read_error = 1;
 				break;
@@ -2221,7 +2562,7 @@ metadump_f(
 				}
 				break;
 			case 'o':
-				dont_obfuscate = 1;
+				obfuscate = 0;
 				break;
 			case 'w':
 				show_warnings = 1;
diff --git a/db/sb.c b/db/sb.c
index 974abd2..f0c2145 100644
--- a/db/sb.c
+++ b/db/sb.c
@@ -225,8 +225,7 @@ int xlog_recover_do_trans(struct xlog *log, xlog_recover_t *t, int p)
 int
 sb_logcheck(void)
 {
-	struct xlog	log;
-	xfs_daddr_t	head_blk, tail_blk;
+	int		dirty;
 
 	if (mp->m_sb.sb_logstart) {
 		if (x.logdev && x.logdev != x.ddev) {
@@ -242,26 +241,13 @@ sb_logcheck(void)
 		}
 	}
 
-	memset(&log, 0, sizeof(log));
 	libxfs_buftarg_init(mp, x.ddev, x.logdev, x.rtdev);
-	x.logBBsize = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks);
-	x.logBBstart = XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart);
-	x.lbsize = BBSIZE;
-	if (xfs_sb_version_hassector(&mp->m_sb))
-		x.lbsize <<= (mp->m_sb.sb_logsectlog - BBSHIFT);
-
-	log.l_dev = mp->m_logdev_targp;
-	log.l_logsize = BBTOB(log.l_logBBsize);
-	log.l_logBBsize = x.logBBsize;
-	log.l_logBBstart = x.logBBstart;
-	log.l_sectBBsize  = BTOBB(x.lbsize);
-	log.l_mp = mp;
-
-	if (xlog_find_tail(&log, &head_blk, &tail_blk)) {
+
+	dirty = xlog_is_dirty(mp, &x, 0);
+	if (dirty == -1) {
 		dbprintf(_("ERROR: cannot find log head/tail, run xfs_repair\n"));
 		return 0;
-	}
-	if (head_blk != tail_blk) {
+	} else if (dirty == 1) {
 		dbprintf(_(
 "ERROR: The filesystem has valuable metadata changes in a log which needs to\n"
 "be replayed.  Mount the filesystem to replay the log, and unmount it before\n"
@@ -271,6 +257,7 @@ sb_logcheck(void)
 "of the filesystem before doing this.\n"), progname);
 		return 0;
 	}
+	/* Log is clean */
 	return 1;
 }
 
diff --git a/db/type.c b/db/type.c
index b29f2a4..0aa3137 100644
--- a/db/type.c
+++ b/db/type.c
@@ -70,6 +70,7 @@ static const typ_t	__typtab[] = {
 	{ TYP_SB, "sb", handle_struct, sb_hfld, NULL },
 	{ TYP_SYMLINK, "symlink", handle_string, NULL, NULL },
 	{ TYP_TEXT, "text", handle_text, NULL, NULL },
+	{ TYP_FINOBT, "finobt", handle_struct, inobt_hfld, NULL },
 	{ TYP_NONE, NULL }
 };
 
@@ -104,6 +105,8 @@ static const typ_t	__typtab_crc[] = {
 	{ TYP_SYMLINK, "symlink", handle_struct, symlink_crc_hfld,
 		&xfs_symlink_buf_ops },
 	{ TYP_TEXT, "text", handle_text, NULL, NULL },
+	{ TYP_FINOBT, "finobt", handle_struct, inobt_crc_hfld,
+		&xfs_inobt_buf_ops },
 	{ TYP_NONE, NULL }
 };
 
diff --git a/db/type.h b/db/type.h
index 3bb26f1..e8d8df7 100644
--- a/db/type.h
+++ b/db/type.h
@@ -27,7 +27,7 @@ typedef enum typnm
 	TYP_BMAPBTD, TYP_BNOBT, TYP_CNTBT, TYP_DATA,
 	TYP_DIR2, TYP_DQBLK, TYP_INOBT, TYP_INODATA, TYP_INODE,
 	TYP_LOG, TYP_RTBITMAP, TYP_RTSUMMARY, TYP_SB, TYP_SYMLINK,
-	TYP_TEXT, TYP_NONE
+	TYP_TEXT, TYP_FINOBT, TYP_NONE
 } typnm_t;
 
 #define DB_WRITE 1
diff --git a/db/xfs_metadump.sh b/db/xfs_metadump.sh
index a95d5a5..836d3ae 100755
--- a/db/xfs_metadump.sh
+++ b/db/xfs_metadump.sh
@@ -5,11 +5,12 @@
 
 OPTS=" "
 DBOPTS=" "
-USAGE="Usage: xfs_metadump [-efFogwV] [-m max_extents] [-l logdev] source target"
+USAGE="Usage: xfs_metadump [-aefFogwV] [-m max_extents] [-l logdev] source target"
 
-while getopts "efgl:m:owFV" c
+while getopts "aefgl:m:owFV" c
 do
 	case $c in
+	a)	OPTS=$OPTS"-a ";;
 	e)	OPTS=$OPTS"-e ";;
 	g)	OPTS=$OPTS"-g ";;
 	m)	OPTS=$OPTS"-m "$OPTARG" ";;
diff --git a/include/libxlog.h b/include/libxlog.h
index a61e437..d2a33c1 100644
--- a/include/libxlog.h
+++ b/include/libxlog.h
@@ -78,6 +78,8 @@ extern int	print_record_header;
 /* libxfs parameters */
 extern libxfs_init_t	x;
 
+
+extern int xlog_is_dirty(xfs_mount_t *mp, libxfs_init_t *x, int verbose);
 extern struct xfs_buf *xlog_get_bp(struct xlog *, int);
 extern void	xlog_put_bp(struct xfs_buf *);
 extern int	xlog_bread(struct xlog *log, xfs_daddr_t blk_no, int nbblks,
diff --git a/libxlog/util.c b/libxlog/util.c
index 949b79d..edb23a8 100644
--- a/libxlog/util.c
+++ b/libxlog/util.c
@@ -23,6 +23,62 @@ int print_skip_uuid;
 int print_record_header;
 libxfs_init_t x;
 
+/*
+ * Return 1 for dirty, 0 for clean, -1 for errors
+ */
+int
+xlog_is_dirty(
+	xfs_mount_t *mp,
+	libxfs_init_t *x,
+	int verbose)
+{
+	int error;
+	struct xlog	log;
+	xfs_daddr_t head_blk, tail_blk;
+
+	memset(&log, 0, sizeof(log));
+
+	/* We (re-)init members of libxfs_init_t here?  really? */
+	x->logBBsize = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks);
+	x->logBBstart = XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart);
+	x->lbsize = BBSIZE;
+	if (xfs_sb_version_hassector(&mp->m_sb))
+		x->lbsize <<= (mp->m_sb.sb_logsectlog - BBSHIFT);
+
+	log.l_dev = mp->m_logdev_targp;
+	log.l_logBBsize = x->logBBsize;
+	log.l_logBBstart = x->logBBstart;
+	log.l_sectBBsize = BTOBB(x->lbsize);
+	log.l_mp = mp;
+	if (xfs_sb_version_hassector(&mp->m_sb)) {
+		log.l_sectbb_log = mp->m_sb.sb_logsectlog - BBSHIFT;
+		ASSERT(log.l_sectbb_log <= mp->m_sectbb_log);
+		/* for larger sector sizes, must have v2 or external log */
+		ASSERT(log.l_sectbb_log == 0 ||
+			log.l_logBBstart == 0 ||
+			xfs_sb_version_haslogv2(&mp->m_sb));
+		ASSERT(mp->m_sb.sb_logsectlog >= BBSHIFT);
+	}
+	log.l_sectbb_mask = (1 << log.l_sectbb_log) - 1;
+
+	if ((error = xlog_find_tail(&log, &head_blk, &tail_blk))) {
+		xlog_warn(_("%s: cannot find log head/tail "
+			  "(xlog_find_tail=%d)\n"),
+			__func__, error);
+		return -1;
+	}
+
+	if (verbose)
+		xlog_warn(
+	_("%s: head block %" PRId64 " tail block %" PRId64 "\n"),
+			__func__, head_blk, tail_blk);
+
+	if (head_blk != tail_blk)
+		return 1;
+
+	return 0;
+}
+
 static int
 header_check_uuid(xfs_mount_t *mp, xlog_rec_header_t *head)
 {
diff --git a/man/man8/xfs_metadump.8 b/man/man8/xfs_metadump.8
index 077fff5..81a17a9 100644
--- a/man/man8/xfs_metadump.8
+++ b/man/man8/xfs_metadump.8
@@ -4,7 +4,7 @@ xfs_metadump \- copy XFS filesystem metadata to a file
 .SH SYNOPSIS
 .B xfs_metadump
 [
-.B \-efFgow
+.B \-aefFgow
 ] [
 .B \-m
 .I max_extents
@@ -74,6 +74,14 @@ tool.
 .PP
 .SH OPTIONS
 .TP
+.B \-a
+Copies entire metadata blocks.  Normally,
+.B xfs_metadump
+will zero any stale
+bytes interspersed with in-use metadata.  Use this option to copy full metadata
+blocks, to provide more debugging information for a corrupted filesystem.  Note
+that the extra data will be unobfuscated.
+.TP
 .B \-e
 Stops the dump on a read error. Normally, it will ignore read errors and copy
 all the metadata that is accessible.