]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blobdiff - db/metadump.c
misc: fix libxfs api violations
[thirdparty/xfsprogs-dev.git] / db / metadump.c
index c5ffddbcd7a5e2a0358e777e1a87479645f700a7..44359e18f8211e14f88a0023024b5f89b2d799fb 100644 (file)
@@ -16,7 +16,8 @@
  * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include <libxfs.h>
+#include "libxfs.h"
+#include "libxlog.h"
 #include "bmap.h"
 #include "command.h"
 #include "metadump.h"
 #include "init.h"
 #include "sig.h"
 #include "xfs_metadump.h"
+#include "fprint.h"
+#include "faddr.h"
+#include "field.h"
+#include "dir2.h"
 
 #define DEFAULT_MAX_EXT_SIZE   1000
 
@@ -52,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 */
@@ -61,7 +66,7 @@ static xfs_metablock_t        *metablock;     /* header + index + buffers */
 static __be64          *block_index;
 static char            *block_buffer;
 
-static int             num_indicies;
+static int             num_indices;
 static int             cur_index;
 
 static xfs_ino_t       cur_ino;
@@ -69,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;
 
@@ -88,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"
@@ -140,7 +147,9 @@ print_progress(const char *fmt, ...)
  * A complete dump file will have a "zero" entry in the last index block,
  * even if the dump is exactly aligned, the last index will be full of
  * zeros. If the last index entry is non-zero, the dump is incomplete.
- * Correspondingly, the last chunk will have a count < num_indicies.
+ * Correspondingly, the last chunk will have a count < num_indices.
+ *
+ * Return 0 for success, -1 for failure.
  */
 
 static int
@@ -152,35 +161,228 @@ write_index(void)
        metablock->mb_count = cpu_to_be16(cur_index);
        if (fwrite(metablock, (cur_index + 1) << BBSHIFT, 1, outf) != 1) {
                print_warning("error writing to file: %s", strerror(errno));
-               return 0;
+               return -errno;
        }
 
-       memset(block_index, 0, num_indicies * sizeof(__be64));
+       memset(block_index, 0, num_indices * sizeof(__be64));
        cur_index = 0;
-       return 1;
+       return 0;
 }
 
+/*
+ * Return 0 for success, -errno for failure.
+ */
 static int
-write_buf(
-       iocur_t         *buf)
+write_buf_segment(
+       char            *data,
+       __int64_t       off,
+       int             len)
 {
-       char            *data;
-       __int64_t       off;
        int             i;
+       int             ret;
 
-       for (i = 0, off = buf->bb, data = buf->data;
-                       i < buf->blen;
-                       i++, off++, data += BBSIZE) {
+       for (i = 0; i < len; i++, off++, data += BBSIZE) {
                block_index[cur_index] = cpu_to_be64(off);
                memcpy(&block_buffer[cur_index << BBSHIFT], data, BBSIZE);
-               if (++cur_index == num_indicies) {
-                       if (!write_index())
-                               return 0;
+               if (++cur_index == num_indices) {
+                       ret = write_index();
+                       if (ret)
+                               return -EIO;
+               }
+       }
+       return 0;
+}
+
+/*
+ * 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)bp->b_bn, bp->b_bcount);
+               }
+       }
+
+       /* handle discontiguous buffers */
+       if (!buf->bbmap) {
+               ret = write_buf_segment(buf->data, buf->bb, buf->blen);
+               if (ret)
+                       return ret;
+       } else {
+               int     len = 0;
+               for (i = 0; i < buf->bbmap->nmaps; i++) {
+                       ret = write_buf_segment(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();
+       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;
+
+       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 > mp->m_inobt_mxr[1])
+                       return;
+
+               ikp = XFS_INOBT_KEY_ADDR(mp, block, 1);
+               ipp = XFS_INOBT_PTR_ADDR(mp, block, 1, mp->m_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 > mp->m_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(
@@ -207,7 +409,13 @@ scan_btree(
                rval = !stop_on_read_error;
                goto pop_out;
        }
-       if (!write_buf(iocur_top))
+
+       if (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))
@@ -230,7 +438,7 @@ valid_bno(
                return 1;
        if (agno == (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
                        agbno <= (mp->m_sb.sb_dblocks -
-                        (xfs_drfsbno_t)(mp->m_sb.sb_agcount - 1) *
+                        (xfs_rfsblock_t)(mp->m_sb.sb_agcount - 1) *
                         mp->m_sb.sb_agblocks))
                return 1;
 
@@ -335,13 +543,85 @@ copy_free_cnt_btree(
        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 (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 (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_sb_version_hasrmapbt(&mp->m_sb))
+               return 1;
+
+       root = be32_to_cpu(agf->agf_roots[XFS_BTNUM_RMAP]);
+       levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_RMAP]);
+
+       /* validate root and levels before processing the tree */
+       if (root == 0 || root > mp->m_sb.sb_agblocks) {
+               if (show_warnings)
+                       print_warning("invalid block number (%u) in rmapbt "
+                                       "root in agf %u", root, agno);
+               return 1;
+       }
+       if (levels >= XFS_BTREE_MAXLEVELS) {
+               if (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);
+}
+
 /* filename and extended attribute obfuscation routines */
 
 struct name_ent {
        struct name_ent         *next;
        xfs_dahash_t            hash;
        int                     namelen;
-       uchar_t                 name[1];
+       unsigned char           name[1];
 };
 
 #define NAME_TABLE_SIZE                4096
@@ -367,7 +647,7 @@ nametable_clear(void)
  * return a pointer to its entry, otherwise return a null pointer.
  */
 static struct name_ent *
-nametable_find(xfs_dahash_t hash, int namelen, uchar_t *name)
+nametable_find(xfs_dahash_t hash, int namelen, unsigned char *name)
 {
        struct name_ent *ent;
 
@@ -384,7 +664,7 @@ nametable_find(xfs_dahash_t hash, int namelen, uchar_t *name)
  * name's new entry, or a null pointer if an error occurs.
  */
 static struct name_ent *
-nametable_add(xfs_dahash_t hash, int namelen, uchar_t *name)
+nametable_add(xfs_dahash_t hash, int namelen, unsigned char *name)
 {
        struct name_ent *ent;
 
@@ -405,10 +685,10 @@ nametable_add(xfs_dahash_t hash, int namelen, uchar_t *name)
 #define is_invalid_char(c)     ((c) == '/' || (c) == '\0')
 #define rol32(x,y)             (((x) << (y)) | ((x) >> (32 - (y))))
 
-static inline uchar_t
+static inline unsigned char
 random_filename_char(void)
 {
-       static uchar_t filename_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+       static unsigned char filename_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                                "abcdefghijklmnopqrstuvwxyz"
                                                "0123456789-_";
 
@@ -423,7 +703,7 @@ is_orphanage_dir(
        struct xfs_mount        *mp,
        xfs_ino_t               dir_ino,
        size_t                  name_len,
-       uchar_t                 *name)
+       unsigned char           *name)
 {
        return dir_ino == mp->m_sb.sb_rootino &&
                        name_len == ORPHANAGE_LEN &&
@@ -442,7 +722,7 @@ static int
 in_lost_found(
        xfs_ino_t               ino,
        int                     namelen,
-       uchar_t                 *name)
+       unsigned char           *name)
 {
        static xfs_ino_t        orphanage_ino = 0;
        char                    s[24];  /* 21 is enough (64 bits in decimal) */
@@ -483,13 +763,13 @@ static void
 obfuscate_name(
        xfs_dahash_t    hash,
        size_t          name_len,
-       uchar_t         *name)
+       unsigned char   *name)
 {
-       uchar_t         *newp = name;
+       unsigned char   *newp = name;
        int             i;
        xfs_dahash_t    new_hash = 0;
-       uchar_t         *first;
-       uchar_t         high_bit;
+       unsigned char   *first;
+       unsigned char   high_bit;
        int             shift;
 
        /*
@@ -638,16 +918,16 @@ obfuscate_name(
 static int
 flip_bit(
        size_t          name_len,
-       uchar_t         *name,
+       unsigned char   *name,
        uint32_t        bitseq)
 {
        int     index;
        size_t  offset;
-       uchar_t *p0, *p1;
-       uchar_t m0, m1;
+       unsigned char *p0, *p1;
+       unsigned char m0, m1;
        struct {
            int         byte;   /* Offset from start within name */
-           uchar_t     bit;    /* Bit within that byte */
+           unsigned char bit;  /* Bit within that byte */
        } bit_to_flip[][2] = {  /* Sorted by second entry's byte */
            { { 0, 0 }, { 1, 7 } },     /* Each row defines a pair */
            { { 1, 0 }, { 2, 7 } },     /* of bytes and a bit within */
@@ -776,7 +1056,7 @@ flip_bit(
 static int
 find_alternate(
        size_t          name_len,
-       uchar_t         *name,
+       unsigned char   *name,
        uint32_t        seq)
 {
        uint32_t        bitseq = 0;
@@ -813,9 +1093,9 @@ find_alternate(
  * are already in the table.
  */
 static int
-handle_duplicate_name(xfs_dahash_t hash, size_t name_len, uchar_t *name)
+handle_duplicate_name(xfs_dahash_t hash, size_t name_len, unsigned char *name)
 {
-       uchar_t         new_name[name_len + 1];
+       unsigned char   new_name[name_len + 1];
        uint32_t        seq = 1;
 
        if (!nametable_find(hash, name_len, name))
@@ -848,7 +1128,7 @@ static void
 generate_obfuscated_name(
        xfs_ino_t               ino,
        int                     namelen,
-       uchar_t                 *name)
+       unsigned char           *name)
 {
        xfs_dahash_t            hash;
 
@@ -899,15 +1179,15 @@ generate_obfuscated_name(
 }
 
 static void
-obfuscate_sf_dir(
+process_sf_dir(
        xfs_dinode_t            *dip)
 {
-       xfs_dir2_sf_t           *sfp;
+       struct xfs_dir2_sf_hdr  *sfp;
        xfs_dir2_sf_entry_t     *sfep;
        __uint64_t              ino_dir_size;
        int                     i;
 
-       sfp = (xfs_dir2_sf_t *)XFS_DFORK_DPTR(dip);
+       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);
@@ -917,7 +1197,7 @@ obfuscate_sf_dir(
        }
 
        sfep = xfs_dir2_sf_firstentry(sfp);
-       for (i = 0; (i < sfp->hdr.count) &&
+       for (i = 0; (i < sfp->count) &&
                        ((char *)sfep - (char *)sfp < ino_dir_size); i++) {
 
                /*
@@ -930,33 +1210,81 @@ obfuscate_sf_dir(
                        if (show_warnings)
                                print_warning("zero length entry in dir inode "
                                                "%llu", (long long)cur_ino);
-                       if (i != sfp->hdr.count - 1)
+                       if (i != sfp->count - 1)
                                break;
                        namelen = ino_dir_size - ((char *)&sfep->name[0] -
                                         (char *)sfp);
                } else if ((char *)sfep - (char *)sfp +
-                               xfs_dir2_sf_entsize_byentry(sfp, sfep) >
+                               M_DIROPS(mp)->sf_entsize(sfp, sfep->namelen) >
                                ino_dir_size) {
                        if (show_warnings)
                                print_warning("entry length in dir inode %llu "
                                        "overflows space", (long long)cur_ino);
-                       if (i != sfp->hdr.count - 1)
+                       if (i != sfp->count - 1)
                                break;
                        namelen = ino_dir_size - ((char *)&sfep->name[0] -
                                         (char *)sfp);
                }
 
-               generate_obfuscated_name(xfs_dir2_sf_get_inumber(sfp,
-                               xfs_dir2_sf_inumberp(sfep)), namelen,
-                               &sfep->name[0]);
+               if (obfuscate)
+                       generate_obfuscated_name(
+                                        M_DIROPS(mp)->sf_get_ino(sfp, sfep),
+                                        namelen, &sfep->name[0]);
 
                sfep = (xfs_dir2_sf_entry_t *)((char *)sfep +
-                               xfs_dir2_sf_entsize_byname(sfp, namelen));
+                               M_DIROPS(mp)->sf_entsize(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);
 }
 
+/*
+ * 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_sf_symlink(
+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 = libxfs_da_hashname(comp, namelen);
+                       obfuscate_name(hash, namelen, comp);
+                       break;
+               }
+               namelen = slash - (char *)comp;
+               /* handle leading or consecutive slashes */
+               if (!namelen) {
+                       comp++;
+                       len--;
+                       continue;
+               }
+               hash = libxfs_da_hashname(comp, namelen);
+               obfuscate_name(hash, namelen, comp);
+               comp += namelen + 1;
+               len -= namelen + 1;
+       }
+}
+
+static void
+process_sf_symlink(
        xfs_dinode_t            *dip)
 {
        __uint64_t              len;
@@ -971,17 +1299,21 @@ obfuscate_sf_symlink(
        }
 
        buf = (char *)XFS_DFORK_DPTR(dip);
-       while (len > 0)
-               buf[--len] = random() % 127 + 1;
+       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;
@@ -1020,32 +1352,27 @@ 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));
        }
-}
-
-/*
- * dir_data structure is used to track multi-fsblock dir2 blocks between extent
- * processing calls.
- */
 
-static struct dir_data_s {
-       int                     end_of_data;
-       int                     block_index;
-       int                     offset_to_entry;
-       int                     bad_block;
-} dir_data;
+       /* 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_blocks(
-       char                    *block,
-       xfs_dfiloff_t           offset,
-       xfs_dfilblks_t          count,
-       int                     is_block_format)
+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
@@ -1053,134 +1380,145 @@ obfuscate_dir_data_blocks(
         * for multi-fsblock dir blocks, if a name crosses an extent boundary,
         * ignore it and continue.
         */
-       int                     c;
-       int                     dir_offset;
-       char                    *ptr;
-       char                    *endptr;
-
-       if (is_block_format && count != mp->m_dirblkfsbs)
-               return; /* too complex to handle this rare case */
-
-       for (c = 0, endptr = block; c < count; c++) {
-
-               if (dir_data.block_index == 0) {
-                       int             wantmagic;
-
-                       if (offset % mp->m_dirblkfsbs != 0)
-                               return; /* corrupted, leave it alone */
+       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_sb_version_hascrc(&mp->m_sb))
+                       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_sb_version_hascrc(&mp->m_sb))
+                       wantmagic = XFS_DIR3_DATA_MAGIC;
+               else
+                       wantmagic = XFS_DIR2_DATA_MAGIC;
+       }
 
-                       dir_data.bad_block = 0;
+       if (be32_to_cpu(datahdr->magic) != wantmagic) {
+               if (show_warnings)
+                       print_warning(
+               "invalid magic in dir inode %llu block %ld",
+                                       (long long)cur_ino, (long)offset);
+               return;
+       }
 
-                       if (is_block_format) {
-                               xfs_dir2_leaf_entry_t   *blp;
-                               xfs_dir2_block_tail_t   *btp;
+       dir_offset = M_DIROPS(mp)->data_entry_offset;
+       ptr = block + dir_offset;
+       endptr = block + mp->m_dir_geo->blksize;
 
-                               btp = xfs_dir2_block_tail_p(mp,
-                                               (xfs_dir2_block_t *)block);
-                               blp = xfs_dir2_block_leaf_p(btp);
-                               if ((char *)blp > (char *)btp)
-                                       blp = (xfs_dir2_leaf_entry_t *)btp;
+       while (ptr < endptr && dir_offset < end_of_data) {
+               xfs_dir2_data_entry_t   *dep;
+               xfs_dir2_data_unused_t  *dup;
+               int                     length;
 
-                               dir_data.end_of_data = (char *)blp - block;
-                               wantmagic = XFS_DIR2_BLOCK_MAGIC;
-                       } else { /* leaf/node format */
-                               dir_data.end_of_data = mp->m_dirblkfsbs <<
-                                               mp->m_sb.sb_blocklog;
-                               wantmagic = XFS_DIR2_DATA_MAGIC;
-                       }
-                       dir_data.offset_to_entry = offsetof(xfs_dir2_data_t, u);
+               dup = (xfs_dir2_data_unused_t *)ptr;
 
-                       if (be32_to_cpu(((xfs_dir2_data_hdr_t*)block)->magic) !=
-                                       wantmagic) {
+               if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+                       int     length = be16_to_cpu(dup->length);
+                       if (dir_offset + length > end_of_data ||
+                           !length || (length & (XFS_DIR2_DATA_ALIGN - 1))) {
                                if (show_warnings)
-                                       print_warning("invalid magic in dir "
-                                               "inode %llu block %ld",
-                                               (long long)cur_ino,
-                                               (long)offset);
-                               dir_data.bad_block = 1;
+                                       print_warning(
+                       "invalid length for dir free space in inode %llu",
+                                               (long long)cur_ino);
+                               return;
                        }
-               }
-               dir_data.block_index++;
-               if (dir_data.block_index == mp->m_dirblkfsbs)
-                       dir_data.block_index = 0;
-
-               if (dir_data.bad_block)
-                       continue;
+                       if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) !=
+                                       dir_offset)
+                               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);
 
-               dir_offset = (dir_data.block_index << mp->m_sb.sb_blocklog) +
-                               dir_data.offset_to_entry;
-
-               ptr = endptr + dir_data.offset_to_entry;
-               endptr += mp->m_sb.sb_blocksize;
-
-               while (ptr < endptr && dir_offset < dir_data.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     length = be16_to_cpu(dup->length);
-                               if (dir_offset + length > dir_data.end_of_data ||
-                                               length == 0 || (length &
-                                                (XFS_DIR2_DATA_ALIGN - 1))) {
-                                       if (show_warnings)
-                                               print_warning("invalid length "
-                                                       "for dir free space in "
-                                                       "inode %llu",
-                                                       (long long)cur_ino);
-                                       dir_data.bad_block = 1;
-                                       break;
-                               }
-                               if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) !=
-                                               dir_offset) {
-                                       dir_data.bad_block = 1;
-                                       break;
+                               if (zlen > 0) {
+                                       memset(&dup->tag, 0, zlen);
+                                       iocur_top->need_crc = 1;
                                }
-                               dir_offset += length;
-                               ptr += length;
-                               if (dir_offset >= dir_data.end_of_data ||
-                                               ptr >= endptr)
-                                       break;
                        }
+                       if (dir_offset >= end_of_data || ptr >= endptr)
+                               return;
+               }
 
-                       dep = (xfs_dir2_data_entry_t *)ptr;
-                       length = xfs_dir2_data_entsize(dep->namelen);
+               dep = (xfs_dir2_data_entry_t *)ptr;
+               length = M_DIROPS(mp)->data_entsize(dep->namelen);
 
-                       if (dir_offset + length > dir_data.end_of_data ||
-                                       ptr + length > endptr) {
-                               if (show_warnings)
-                                       print_warning("invalid length for "
-                                               "dir entry name in inode %llu",
-                                               (long long)cur_ino);
-                               break;
-                       }
-                       if (be16_to_cpu(*xfs_dir2_data_entry_tag_p(dep)) !=
-                                       dir_offset) {
-                               dir_data.bad_block = 1;
-                               break;
-                       }
+               if (dir_offset + length > end_of_data ||
+                   ptr + length > endptr) {
+                       if (show_warnings)
+                               print_warning(
+                       "invalid length for dir entry name in inode %llu",
+                                       (long long)cur_ino);
+                       return;
+               }
+               if (be16_to_cpu(*M_DIROPS(mp)->data_entry_tag_p(dep)) !=
+                               dir_offset)
+                       return;
+
+               if (obfuscate)
                        generate_obfuscated_name(be64_to_cpu(dep->inumber),
-                                       dep->namelen, &dep->name[0]);
-                       dir_offset += length;
-                       ptr += length;
+                                        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 *)M_DIROPS(mp)->data_entry_tag_p(dep) -
+                               (char *)&dep->name[dep->namelen] - 1;
+                       if (zlen > 0) {
+                               memset(&dep->name[dep->namelen] + 1, 0, zlen);
+                               iocur_top->need_crc = 1;
+                       }
                }
-               dir_data.offset_to_entry = dir_offset &
-                                               (mp->m_sb.sb_blocksize - 1);
        }
 }
 
 static void
-obfuscate_symlink_blocks(
-       char                    *block,
-       xfs_dfilblks_t          count)
+process_symlink_block(
+       char                    *block)
 {
-       int                     i;
-
-       count <<= mp->m_sb.sb_blocklog;
-       for (i = 0; i < count; i++)
-               block[i] = random() % 127 + 1;
+       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
@@ -1199,88 +1537,306 @@ add_remote_vals(
                attr_data.remote_vals[attr_data.remote_val_count] = blockidx;
                attr_data.remote_val_count++;
                blockidx++;
-               length -= XFS_LBSIZE(mp);
+               length -= mp->m_sb.sb_blocksize;
+       }
+
+       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
-obfuscate_attr_blocks(
-       char                    *block,
-       xfs_dfiloff_t           offset,
-       xfs_dfilblks_t          count)
+process_attr_block(
+       char                            *block,
+       xfs_fileoff_t                   offset)
 {
-       xfs_attr_leafblock_t    *leaf;
-       int                     c;
-       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;
+
+       /* 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 (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;
+       }
 
-       for (c = 0; c < count; c++, offset++, block += XFS_LBSIZE(mp)) {
+       /* Ok, it's a leaf - get header; accounts for crc & non-crc */
+       xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &hdr, leaf);
 
-               leaf = (xfs_attr_leafblock_t *)block;
+       nentries = hdr.count;
+       if (nentries * sizeof(xfs_attr_leaf_entry_t) +
+                       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;
+       }
 
-               if (be16_to_cpu(leaf->hdr.info.magic) != XFS_ATTR_LEAF_MAGIC) {
-                       for (i = 0; i < attr_data.remote_val_count; i++) {
-                               if (attr_data.remote_vals[i] == offset)
-                                       memset(block, 0, XFS_LBSIZE(mp));
-                       }
-                       continue;
-               }
+       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);
 
-               nentries = be16_to_cpu(leaf->hdr.count);
-               if (nentries * sizeof(xfs_attr_leaf_entry_t) +
-                               sizeof(xfs_attr_leaf_hdr_t) > XFS_LBSIZE(mp)) {
+               if (be16_to_cpu(entry->nameidx) > mp->m_sb.sb_blocksize) {
                        if (show_warnings)
-                               print_warning("invalid attr count in inode %llu",
+                               print_warning(
+                               "invalid attr nameidx in inode %llu",
                                                (long long)cur_ino);
-                       continue;
+                       break;
                }
-
-               for (i = 0, entry = &leaf->entries[0]; i < nentries;
-                               i++, entry++) {
-                       if (be16_to_cpu(entry->nameidx) > XFS_LBSIZE(mp)) {
+               if (entry->flags & XFS_ATTR_LOCAL) {
+                       local = xfs_attr3_leaf_name_local(leaf, i);
+                       if (local->namelen == 0) {
                                if (show_warnings)
-                                       print_warning("invalid attr nameidx "
-                                                       "in inode %llu",
-                                                       (long long)cur_ino);
+                                       print_warning(
+                               "zero length for attr name in inode %llu",
+                                               (long long)cur_ino);
                                break;
                        }
-                       if (entry->flags & XFS_ATTR_LOCAL) {
-                               local = xfs_attr_leaf_name_local(leaf, i);
-                               if (local->namelen == 0) {
-                                       if (show_warnings)
-                                               print_warning("zero length for "
-                                                       "attr name in inode %llu",
-                                                       (long long)cur_ino);
-                                       break;
-                               }
+                       if (obfuscate) {
                                generate_obfuscated_name(0, local->namelen,
                                        &local->nameval[0]);
-                               memset(&local->nameval[local->namelen], 0,
+                               memset(&local->nameval[local->namelen], 'v',
                                        be16_to_cpu(local->valuelen));
-                       } else {
-                               remote = xfs_attr_leaf_name_remote(leaf, i);
-                               if (remote->namelen == 0 ||
-                                               remote->valueblk == 0) {
-                                       if (show_warnings)
-                                               print_warning("invalid attr "
-                                                       "entry in inode %llu",
-                                                       (long long)cur_ino);
-                                       break;
-                               }
+                       }
+                       /* 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) {
+                               if (show_warnings)
+                                       print_warning(
+                               "invalid attr entry in inode %llu",
+                                               (long long)cur_ino);
+                               break;
+                       }
+                       if (obfuscate) {
                                generate_obfuscated_name(0, remote->namelen,
-                                       &remote->name[0]);
+                                                        &remote->name[0]);
                                add_remote_vals(be32_to_cpu(remote->valueblk),
-                                       be32_to_cpu(remote->valuelen));
+                                               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]);
+       }
 }
 
-/* inode copy routines */
+/* 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)
+{
+       char            *dp;
+       int             ret = 0;
+       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);
+                       if (stop_on_read_error)
+                               ret = -EIO;
+                       goto out_pop;
+
+               }
+
+               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;
+
+                               M_DIROPS(mp)->node_hdr_from_disk(&hdr, node);
+                               used = M_DIROPS(mp)->node_hdr_size;
+
+                               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->leafblk)
+                               break;
+
+                       process_dir_data_block(dp, o,
+                                        last == mp->m_dir_geo->fsbcount);
+                       iocur_top->need_crc = 1;
+                       break;
+               case TYP_SYMLINK:
+                       process_symlink_block(dp);
+                       iocur_top->need_crc = 1;
+                       break;
+               case TYP_ATTR:
+                       process_attr_block(dp, o);
+                       iocur_top->need_crc = 1;
+                       break;
+               default:
+                       break;
+               }
+
+write:
+               ret = write_buf(iocur_top);
+out_pop:
+               pop_cur();
+               if (ret)
+                       break;
+               o++;
+               s++;
+       }
+
+       return ret;
+}
+
+/*
+ * 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_objects(
+       xfs_fileoff_t   o,
+       xfs_fsblock_t   s,
+       xfs_filblks_t   c,
+       typnm_t         btype,
+       xfs_fileoff_t   last)
+{
+       int             ret = 0;
+
+       switch (btype) {
+       case TYP_DIR2:
+               break;
+       default:
+               print_warning("bad type for multi-fsb object %d", btype);
+               return -EINVAL;
+       }
+
+       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);
+                               if (stop_on_read_error)
+                                       ret = -1;
+                               goto out_pop;
+
+                       }
+
+                       if ((!obfuscate && !zero_stale_data) ||
+                            o >= mp->m_dir_geo->leafblk) {
+                               ret = write_buf(iocur_top);
+                               goto out_pop;
+                       }
+
+                       process_dir_data_block(iocur_top->data, o,
+                                              last == mp->m_dir_geo->fsbcount);
+                       iocur_top->need_crc = 1;
+                       ret = write_buf(iocur_top);
+out_pop:
+                       pop_cur();
+                       mfsb_map.nmaps = 0;
+                       if (ret)
+                               break;
+               }
+               c -= bm_len;
+               s += bm_len;
+       }
+
+       return ret;
+}
+
+/* inode copy routines */
 static int
 process_bmbt_reclist(
        xfs_bmbt_rec_t          *rp,
@@ -1288,13 +1844,14 @@ process_bmbt_reclist(
        typnm_t                 btype)
 {
        int                     i;
-       xfs_dfiloff_t           o, op = NULLDFILOFF;
-       xfs_dfsbno_t            s;
-       xfs_dfilblks_t          c, cp = NULLDFILOFF;
+       xfs_fileoff_t           o, op = NULLFILEOFF;
+       xfs_fsblock_t           s;
+       xfs_filblks_t           c, cp = NULLFILEOFF;
        int                     f;
-       xfs_dfiloff_t           last;
+       xfs_fileoff_t           last;
        xfs_agnumber_t          agno;
        xfs_agblock_t           agbno;
+       int                     error;
 
        if (btype == TYP_DATA)
                return 1;
@@ -1356,44 +1913,14 @@ process_bmbt_reclist(
                        break;
                }
 
-               push_cur();
-               set_cur(&typtab[btype], XFS_FSB_TO_DADDR(mp, s), c * blkbb,
-                               DB_RING_IGN, NULL);
-               if (iocur_top->data == NULL) {
-                       print_warning("cannot read %s block %u/%u (%llu)",
-                                       typtab[btype].name, agno, agbno, s);
-                       if (stop_on_read_error) {
-                               pop_cur();
-                               return 0;
-                       }
+               /* multi-extent blocks require special handling */
+               if (btype != TYP_DIR2 || mp->m_dir_geo->fsbcount == 1) {
+                       error = process_single_fsb_objects(o, s, c, btype, last);
                } else {
-                       if (!dont_obfuscate)
-                           switch (btype) {
-                               case TYP_DIR2:
-                                       if (o < mp->m_dirleafblk)
-                                               obfuscate_dir_data_blocks(
-                                                       iocur_top->data, o, c,
-                                                       last == mp->m_dirblkfsbs);
-                                       break;
-
-                               case TYP_SYMLINK:
-                                       obfuscate_symlink_blocks(
-                                               iocur_top->data, c);
-                                       break;
-
-                               case TYP_ATTR:
-                                       obfuscate_attr_blocks(iocur_top->data,
-                                               o, c);
-                                       break;
-
-                               default: ;
-                           }
-                       if (!write_buf(iocur_top)) {
-                               pop_cur();
-                               return 0;
-                       }
+                       error = process_multi_fsb_objects(o, s, c, btype, last);
                }
-               pop_cur();
+               if (error)
+                       return 0;
        }
 
        return 1;
@@ -1437,8 +1964,8 @@ scanfunc_bmap(
                xfs_agnumber_t  ag;
                xfs_agblock_t   bno;
 
-               ag = XFS_FSB_TO_AGNO(mp, be64_to_cpu(pp[i]));
-               bno = XFS_FSB_TO_AGBNO(mp, be64_to_cpu(pp[i]));
+               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) {
@@ -1489,7 +2016,7 @@ process_btinode(
                                            nrecs, itype);
        }
 
-       maxrecs = xfs_bmdr_maxrecs(mp, XFS_DFORK_SIZE(dip, mp, whichfork), 0);
+       maxrecs = libxfs_bmdr_maxrecs(XFS_DFORK_SIZE(dip, mp, whichfork), 0);
        if (nrecs > maxrecs) {
                if (show_warnings)
                        print_warning("invalid numrecs (%u) in inode %lld %s "
@@ -1503,8 +2030,8 @@ process_btinode(
                xfs_agnumber_t  ag;
                xfs_agblock_t   bno;
 
-               ag = XFS_FSB_TO_AGNO(mp, be64_to_cpu(pp[i]));
-               bno = XFS_FSB_TO_AGBNO(mp, be64_to_cpu(pp[i]));
+               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) {
@@ -1528,19 +2055,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);
 }
@@ -1552,14 +2086,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: ;
@@ -1575,25 +2109,54 @@ process_inode_data(
        return 1;
 }
 
+/*
+ * 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,
-       xfs_dinode_t            *dip)
+       xfs_dinode_t            *dip,
+       bool                    free_inode)
 {
        int                     success;
+       bool                    crc_was_ok = false; /* no recalc by default */
+       bool                    need_new_crc = false;
 
        success = 1;
        cur_ino = XFS_AGINO_TO_INO(mp, agno, agino);
 
+       /* we only care about crc recalculation if we will modify the inode. */
+       if (obfuscate || 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 (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:
-                       memset(&dir_data, 0, sizeof(dir_data));
                        success = process_inode_data(dip, TYP_DIR2);
+                       if (dip->di_format == XFS_DINODE_FMT_LOCAL)
+                               need_new_crc = 1;
                        break;
                case S_IFLNK:
                        success = process_inode_data(dip, TYP_SYMLINK);
+                       if (dip->di_format == XFS_DINODE_FMT_LOCAL)
+                               need_new_crc = 1;
                        break;
                case S_IFREG:
                        success = process_inode_data(dip, TYP_DATA);
@@ -1603,12 +2166,14 @@ process_inode(
        nametable_clear();
 
        /* copy extended attributes if they exist and forkoff is valid */
-       if (success && XFS_DFORK_DSIZE(dip, mp) < XFS_LITINO(mp)) {
+       if (success &&
+           XFS_DFORK_DSIZE(dip, mp) < XFS_LITINO(mp, dip->di_version)) {
                attr_data.remote_val_count = 0;
                switch (dip->di_aformat) {
                        case XFS_DINODE_FMT_LOCAL:
-                               if (!dont_obfuscate)
-                                       obfuscate_sf_attr(dip);
+                               need_new_crc = 1;
+                               if (obfuscate || zero_stale_data)
+                                       process_sf_attr(dip);
                                break;
 
                        case XFS_DINODE_FMT_EXTENTS:
@@ -1621,6 +2186,14 @@ 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)
+               libxfs_dinode_calc_crc(mp, dip);
        return success;
 }
 
@@ -1634,13 +2207,43 @@ copy_inode_chunk(
        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;
 
        agino = be32_to_cpu(rp->ir_startino);
        agbno = XFS_AGINO_TO_AGBNO(mp, agino);
+       end_agbno = agbno + mp->m_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_sb_version_hassparseinodes(&mp->m_sb))
+               blks_per_buf = xfs_icluster_size_fsb(mp);
+       else
+               blks_per_buf = mp->m_ialloc_blks;
+       inodes_per_buf = min(blks_per_buf << mp->m_sb.sb_inopblog,
+                            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))) {
@@ -1650,53 +2253,58 @@ copy_inode_chunk(
                return 1;
        }
 
-       push_cur();
-       set_cur(&typtab[TYP_INODE], XFS_AGB_TO_DADDR(mp, agno, agbno),
-                       XFS_FSB_TO_BB(mp, XFS_IALLOC_BLOCKS(mp)),
-                       DB_RING_IGN, NULL);
-       if (iocur_top->data == NULL) {
-               print_warning("cannot read inode block %u/%u", agno, agbno);
-               rval = !stop_on_read_error;
-               goto pop_out;
-       }
-
        /*
         * 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_sb_version_hasalign(&mp->m_sb) &&
+                                       mp->m_sb.sb_inoalignmt != 0 &&
                                        agbno % mp->m_sb.sb_inoalignmt != 0)) {
                if (show_warnings)
                        print_warning("badly aligned inode (start = %llu)",
                                        XFS_AGINO_TO_INO(mp, agno, agino));
-               goto skip_processing;
+               return 1;
        }
 
-       /*
-        * scan through inodes and copy any btree extent lists, directory
-        * contents and extended attributes.
-        */
-       for (i = 0; i < XFS_INODES_PER_CHUNK; i++) {
-               xfs_dinode_t            *dip;
+       push_cur();
+       ioff = 0;
+       while (agbno < end_agbno && ioff < XFS_INODES_PER_CHUNK) {
+               if (xfs_inobt_is_sparse_disk(rp, ioff))
+                       goto next_bp;
 
-               if (XFS_INOBT_IS_FREE_DISK(rp, i))
-                       continue;
+               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 = !stop_on_read_error;
+                       goto pop_out;
+               }
+
+               for (i = 0; i < inodes_per_buf; i++) {
+                       xfs_dinode_t    *dip;
+
+                       dip = (xfs_dinode_t *)((char *)iocur_top->data +
+                                       ((off + i) << mp->m_sb.sb_inodelog));
 
-               dip = (xfs_dinode_t *)((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, i)))
+                               goto pop_out;
 
-               if (!process_inode(agno, agino + i, dip))
+                       inodes_copied++;
+               }
+
+               if (write_buf(iocur_top))
                        goto pop_out;
-       }
-skip_processing:
-       if (!write_buf(iocur_top))
-               goto pop_out;
 
-       inodes_copied += XFS_INODES_PER_CHUNK;
+next_bp:
+               agbno += blks_per_buf;
+               ioff += inodes_per_buf;
+       }
 
        if (show_progress)
                print_progress("Copied %u of %u inodes (%u of %u AGs)",
@@ -1721,6 +2329,7 @@ scanfunc_ino(
        xfs_inobt_ptr_t         *pp;
        int                     i;
        int                     numrecs;
+       int                     finobt = *(int *) arg;
 
        numrecs = be16_to_cpu(block->bb_numrecs);
 
@@ -1732,6 +2341,14 @@ scanfunc_ino(
                                        typtab[btype].name, agno, agbno);
                        numrecs = mp->m_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))
@@ -1771,6 +2388,7 @@ copy_inodes(
 {
        xfs_agblock_t           root;
        int                     levels;
+       int                     finobt = 0;
 
        root = be32_to_cpu(agi->agi_root);
        levels = be32_to_cpu(agi->agi_level);
@@ -1789,7 +2407,20 @@ copy_inodes(
                return 1;
        }
 
-       return scan_btree(agno, root, levels, TYP_INOBT, agi, scanfunc_ino);
+       if (!scan_btree(agno, root, levels, TYP_INOBT, &finobt, scanfunc_ino))
+               return 0;
+
+       if (xfs_sb_version_hasfinobt(&mp->m_sb)) {
+               root = be32_to_cpu(agi->agi_free_root);
+               levels = be32_to_cpu(agi->agi_free_level);
+
+               finobt = 1;
+               if (!scan_btree(agno, root, levels, TYP_INOBT, &finobt,
+                               scanfunc_ino))
+                       return 0;
+       }
+
+       return 1;
 }
 
 static int
@@ -1811,7 +2442,14 @@ scan_ag(
                if (stop_on_read_error)
                        goto pop_out;
        } else {
-               if (!write_buf(iocur_top))
+               /* 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;
        }
 
@@ -1826,7 +2464,7 @@ scan_ag(
                if (stop_on_read_error)
                        goto pop_out;
        } else {
-               if (!write_buf(iocur_top))
+               if (write_buf(iocur_top))
                        goto pop_out;
        }
 
@@ -1841,7 +2479,7 @@ scan_ag(
                if (stop_on_read_error)
                        goto pop_out;
        } else {
-               if (!write_buf(iocur_top))
+               if (write_buf(iocur_top))
                        goto pop_out;
        }
 
@@ -1855,7 +2493,24 @@ scan_ag(
                if (stop_on_read_error)
                        goto pop_out;
        } else {
-               if (!write_buf(iocur_top))
+               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;
        }
 
@@ -1868,6 +2523,8 @@ scan_ag(
                        goto pop_out;
                if (!copy_free_cnt_btree(agno, agf))
                        goto pop_out;
+               if (!copy_rmap_btree(agno, agf))
+                       goto pop_out;
        }
 
        /* copy inode btrees and the inodes and their associated metadata */
@@ -1893,7 +2550,7 @@ copy_ino(
        int                     offset;
        int                     rval = 0;
 
-       if (ino == 0)
+       if (ino == 0 || ino == NULLFSINO)
                return 1;
 
        agno = XFS_INO_TO_AGNO(mp, ino);
@@ -1940,12 +2597,22 @@ copy_sb_inodes(void)
        if (!copy_ino(mp->m_sb.sb_uquotino, TYP_DQBLK))
                return 0;
 
-       return copy_ino(mp->m_sb.sb_gquotino, TYP_DQBLK);
+       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 (show_progress)
                print_progress("Copying log");
 
@@ -1957,7 +2624,43 @@ copy_log(void)
                print_warning("cannot read log");
                return !stop_on_read_error;
        }
-       return write_buf(iocur_top);
+
+       /* If not obfuscating or zeroing, just copy the log as it is */
+       if (!obfuscate && !zero_stale_data)
+               goto done;
+
+       dirty = xlog_is_dirty(mp, &log, &x, 0);
+
+       switch (dirty) {
+       case 0:
+               /* clear out a clean log */
+               if (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_sb_version_haslogv2(&mp->m_sb) ? 2 : 1;
+               if (xfs_sb_version_hascrc(&mp->m_sb))
+                       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 */
+               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);
 }
 
 static int
@@ -1981,8 +2684,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;
@@ -1998,7 +2704,7 @@ metadump_f(
                                }
                                break;
                        case 'o':
-                               dont_obfuscate = 1;
+                               obfuscate = 0;
                                break;
                        case 'w':
                                show_warnings = 1;
@@ -2024,7 +2730,20 @@ metadump_f(
 
        block_index = (__be64 *)((char *)metablock + sizeof(xfs_metablock_t));
        block_buffer = (char *)metablock + BBSIZE;
-       num_indicies = (BBSIZE - sizeof(xfs_metablock_t)) / sizeof(__be64);
+       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 > num_indices * BBSIZE) {
+               print_warning("Cannot dump filesystem with sector size %u",
+                             mp->m_sb.sb_sectsize);
+               free(metablock);
+               return 0;
+       }
+
        cur_index = 0;
        start_iocur_sp = iocur_sp;
 
@@ -2063,7 +2782,7 @@ metadump_f(
 
        /* write the remaining index */
        if (!exitcode)
-               exitcode = !write_index();
+               exitcode = write_index() < 0;
 
        if (progress_since_warning)
                fputc('\n', (outf == stdout) ? stderr : stdout);