]> 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 19aed4f6ab098c60b807b7d19cf4e0a7bae612ab..44359e18f8211e14f88a0023024b5f89b2d799fb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007 Silicon Graphics, Inc.
+ * Copyright (c) 2007, 2011 SGI
  * All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or
@@ -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
 
+/*
+ * It's possible that multiple files in a directory (or attributes
+ * in a file) produce the same obfuscated name.  If that happens, we
+ * try to create another one.  After several rounds of this though,
+ * we just give up and leave the original name as-is.
+ */
+#define        DUP_MAX         5       /* Max duplicates before we give up */
+
 /* copy all metadata structures to/from a file */
 
 static int     metadump_f(int argc, char **argv);
@@ -44,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 */
@@ -53,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;
@@ -61,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;
 
@@ -80,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"
@@ -132,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
@@ -144,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(
@@ -199,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))
@@ -222,7 +438,8 @@ valid_bno(
                return 1;
        if (agno == (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
                        agbno <= (mp->m_sb.sb_dblocks -
-                        (mp->m_sb.sb_agcount - 1) * mp->m_sb.sb_agblocks))
+                        (xfs_rfsblock_t)(mp->m_sb.sb_agcount - 1) *
+                        mp->m_sb.sb_agblocks))
                return 1;
 
        return 0;
@@ -326,183 +543,652 @@ copy_free_cnt_btree(
        return scan_btree(agno, root, levels, TYP_CNTBT, agf, scanfunc_freesp);
 }
 
-/* filename and extended attribute obfuscation routines */
+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;
 
-typedef struct name_ent {
-       struct name_ent         *next;
-       xfs_dahash_t            hash;
-       int                     namelen;
-       uchar_t                 name[1];
-} name_ent_t;
+       if (level == 0)
+               return 1;
 
-#define NAME_TABLE_SIZE                4096
+       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;
+       }
 
-static name_ent_t              **nametable;
+       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
-create_nametable(void)
+copy_rmap_btree(
+       xfs_agnumber_t  agno,
+       struct xfs_agf  *agf)
 {
-       nametable = calloc(NAME_TABLE_SIZE, sizeof(name_ent_t));
-       return nametable != NULL;
+       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;
+       unsigned char           name[1];
+};
+
+#define NAME_TABLE_SIZE                4096
+
+static struct name_ent         *nametable[NAME_TABLE_SIZE];
+
 static void
-clear_nametable(void)
+nametable_clear(void)
 {
-       int                     i;
-       name_ent_t              *p;
+       int             i;
+       struct name_ent *ent;
 
        for (i = 0; i < NAME_TABLE_SIZE; i++) {
-               while (nametable[i]) {
-                       p = nametable[i];
-                       nametable[i] = p->next;
-                       free(p);
+               while ((ent = nametable[i])) {
+                       nametable[i] = ent->next;
+                       free(ent);
                }
        }
 }
 
+/*
+ * See if the given name is already in the name table.  If so,
+ * return a pointer to its entry, otherwise return a null pointer.
+ */
+static struct name_ent *
+nametable_find(xfs_dahash_t hash, int namelen, unsigned char *name)
+{
+       struct name_ent *ent;
+
+       for (ent = nametable[hash % NAME_TABLE_SIZE]; ent; ent = ent->next) {
+               if (ent->hash == hash && ent->namelen == namelen &&
+                               !memcmp(ent->name, name, namelen))
+                       return ent;
+       }
+       return NULL;
+}
+
+/*
+ * Add the given name to the name table.  Returns a pointer to the
+ * name's new entry, or a null pointer if an error occurs.
+ */
+static struct name_ent *
+nametable_add(xfs_dahash_t hash, int namelen, unsigned char *name)
+{
+       struct name_ent *ent;
+
+       ent = malloc(sizeof *ent + namelen);
+       if (!ent)
+               return NULL;
+
+       ent->namelen = namelen;
+       memcpy(ent->name, name, namelen);
+       ent->hash = hash;
+       ent->next = nametable[hash % NAME_TABLE_SIZE];
+
+       nametable[hash % NAME_TABLE_SIZE] = ent;
+
+       return ent;
+}
 
 #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)
 {
-       uchar_t                 c;
+       static unsigned char filename_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                               "abcdefghijklmnopqrstuvwxyz"
+                                               "0123456789-_";
 
-       do {
-               c = random() % 127 + 1;
-       } while (c == '/');
-       return c;
+       return filename_alphabet[random() % (sizeof filename_alphabet - 1)];
+}
+
+#define        ORPHANAGE       "lost+found"
+#define        ORPHANAGE_LEN   (sizeof (ORPHANAGE) - 1)
+
+static inline int
+is_orphanage_dir(
+       struct xfs_mount        *mp,
+       xfs_ino_t               dir_ino,
+       size_t                  name_len,
+       unsigned char           *name)
+{
+       return dir_ino == mp->m_sb.sb_rootino &&
+                       name_len == ORPHANAGE_LEN &&
+                       !memcmp(name, ORPHANAGE, ORPHANAGE_LEN);
 }
 
+/*
+ * Determine whether a name is one we shouldn't obfuscate because
+ * it's an orphan (or the "lost+found" directory itself).  Note
+ * "cur_ino" is the inode for the directory currently being
+ * processed.
+ *
+ * Returns 1 if the name should NOT be obfuscated or 0 otherwise.
+ */
 static int
-is_special_dirent(
+in_lost_found(
        xfs_ino_t               ino,
        int                     namelen,
-       uchar_t                 *name)
+       unsigned char           *name)
 {
        static xfs_ino_t        orphanage_ino = 0;
-       char                    s[32];
+       char                    s[24];  /* 21 is enough (64 bits in decimal) */
        int                     slen;
 
-       /*
-        * due to the XFS name hashing algorithm, we cannot obfuscate
-        * names with 4 chars or less.
-        */
-       if (namelen <= 4)
+       /* Record the "lost+found" inode if we haven't done so already */
+
+       ASSERT(ino != 0);
+       if (!orphanage_ino && is_orphanage_dir(mp, cur_ino, namelen, name))
+               orphanage_ino = ino;
+
+       /* We don't obfuscate the "lost+found" directory itself */
+
+       if (ino == orphanage_ino)
                return 1;
 
-       if (ino == 0)
+       /* Most files aren't in "lost+found" at all */
+
+       if (cur_ino != orphanage_ino)
                return 0;
 
        /*
-        * don't obfuscate lost+found nor any inodes within lost+found with
-        * the inode number
+        * Within "lost+found", we don't obfuscate any file whose
+        * name is the same as its inode number.  Any others are
+        * stray files and can be obfuscated.
         */
-       if (cur_ino == mp->m_sb.sb_rootino && namelen == 10 &&
-                       memcmp(name, "lost+found", 10) == 0) {
-               orphanage_ino = ino;
-               return 1;
-       }
-       if (cur_ino != orphanage_ino)
-               return 0;
+       slen = snprintf(s, sizeof (s), "%llu", (unsigned long long) ino);
 
-       slen = sprintf(s, "%lld", (long long)ino);
-       return (slen == namelen && memcmp(name, s, namelen) == 0);
+       return slen == namelen && !memcmp(name, s, namelen);
 }
 
+/*
+ * Given a name and its hash value, massage the name in such a way
+ * that the result is another name of equal length which shares the
+ * same hash value.
+ */
 static void
-generate_obfuscated_name(
-       xfs_ino_t               ino,
-       int                     namelen,
-       uchar_t                 *name)
+obfuscate_name(
+       xfs_dahash_t    hash,
+       size_t          name_len,
+       unsigned char   *name)
 {
-       xfs_dahash_t            hash;
-       name_ent_t              *p;
-       int                     i;
-       int                     dup;
-       xfs_dahash_t            newhash;
-       uchar_t                 newname[NAME_MAX];
+       unsigned char   *newp = name;
+       int             i;
+       xfs_dahash_t    new_hash = 0;
+       unsigned char   *first;
+       unsigned char   high_bit;
+       int             shift;
 
-       if (is_special_dirent(ino, namelen, name))
+       /*
+        * Our obfuscation algorithm requires at least 5-character
+        * names, so don't bother if the name is too short.  We
+        * work backward from a hash value to determine the last
+        * five bytes in a name required to produce a new name
+        * with the same hash.
+        */
+       if (name_len < 5)
                return;
 
-       hash = libxfs_da_hashname(name, namelen);
+       /*
+        * The beginning of the obfuscated name can be pretty much
+        * anything, so fill it in with random characters.
+        * Accumulate its new hash value as we go.
+        */
+       for (i = 0; i < name_len - 5; i++) {
+               *newp = random_filename_char();
+               new_hash = *newp ^ rol32(new_hash, 7);
+               newp++;
+       }
+
+       /*
+        * Compute which five bytes need to be used at the end of
+        * the name so the hash of the obfuscated name is the same
+        * as the hash of the original.  If any result in an invalid
+        * character, flip a bit and arrange for a corresponding bit
+        * in a neighboring byte to be flipped as well.  For the
+        * last byte, the "neighbor" to change is the first byte
+        * we're computing here.
+        */
+       new_hash = rol32(new_hash, 3) ^ hash;
+
+       first = newp;
+       high_bit = 0;
+       for (shift = 28; shift >= 0; shift -= 7) {
+               *newp = (new_hash >> shift & 0x7f) ^ high_bit;
+               if (is_invalid_char(*newp)) {
+                       *newp ^= 1;
+                       high_bit = 0x80;
+               } else
+                       high_bit = 0;
+               ASSERT(!is_invalid_char(*newp));
+               newp++;
+       }
+
+       /*
+        * If we flipped a bit on the last byte, we need to fix up
+        * the matching bit in the first byte.  The result will
+        * be a valid character, because we know that first byte
+        * has 0's in its upper four bits (it was produced by a
+        * 28-bit right-shift of a 32-bit unsigned value).
+        */
+       if (high_bit) {
+               *first ^= 0x10;
+               ASSERT(!is_invalid_char(*first));
+       }
+       ASSERT(libxfs_da_hashname(name, name_len) == hash);
+}
+
+/*
+ * Flip a bit in each of two bytes at the end of the given name.
+ * This is used in generating a series of alternate names to be used
+ * in the event a duplicate is found.
+ *
+ * The bits flipped are selected such that they both affect the same
+ * bit in the name's computed hash value, so flipping them both will
+ * preserve the hash.
+ *
+ * The following diagram aims to show the portion of a computed
+ * hash that a given byte of a name affects.
+ *
+ *        31    28      24    21            14           8 7       3     0
+ *        +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ * hash:   | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+ *        +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ *       last-4 ->|           |<-- last-2 --->|           |<--- last ---->|
+ *              |<-- last-3 --->|           |<-- last-1 --->|     |<- last-4
+ *                      |<-- last-7 --->|           |<-- last-5 --->|
+ *        |<-- last-8 --->|           |<-- last-6 --->|
+ *                     . . . and so on
+ *
+ * The last byte of the name directly affects the low-order byte of
+ * the hash.  The next-to-last affects bits 7-14, the next one back
+ * affects bits 14-21, and so on.  The effect wraps around when it
+ * goes beyond the top of the hash (as happens for byte last-4).
+ *
+ * Bits that are flipped together "overlap" on the hash value.  As
+ * an example of overlap, the last two bytes both affect bit 7 in
+ * the hash.  That pair of bytes (and their overlapping bits) can be
+ * used for this "flip bit" operation (it's the first pair tried,
+ * actually).
+ *
+ * A table defines overlapping pairs--the bytes involved and bits
+ * within them--that can be used this way.  The byte offset is
+ * relative to a starting point within the name, which will be set
+ * to affect the bytes at the end of the name.  The function is
+ * called with a "bitseq" value which indicates which bit flip is
+ * desired, and this translates directly into selecting which entry
+ * in the bit_to_flip[] table to apply.
+ *
+ * The function returns 1 if the operation was successful.  It
+ * returns 0 if the result produced a character that's not valid in
+ * a name (either '/' or a '\0').  Finally, it returns -1 if the bit
+ * sequence number is beyond what is supported for a name of this
+ * length.
+ *
+ * Discussion
+ * ----------
+ * (Also see the discussion above find_alternate(), below.)
+ *
+ * In order to make this function work for any length name, the
+ * table is ordered by increasing byte offset, so that the earliest
+ * entries can apply to the shortest strings.  This way all names
+ * are done consistently.
+ *
+ * When bit flips occur, they can convert printable characters
+ * into non-printable ones.  In an effort to reduce the impact of
+ * this, the first bit flips are chosen to affect bytes the end of
+ * the name (and furthermore, toward the low bits of a byte).  Those
+ * bytes are often non-printable anyway because of the way they are
+ * initially selected by obfuscate_name()).  This is accomplished,
+ * using later table entries first.
+ *
+ * Each row in the table doubles the number of alternates that
+ * can be generated.  A two-byte name is limited to using only
+ * the first row, so it's possible to generate two alternates
+ * (the original name, plus the alternate produced by flipping
+ * the one pair of bits).  In a 5-byte name, the effect of the
+ * first byte overlaps the last by 4 its, and there are 8 bits
+ * to flip, allowing for 256 possible alternates.
+ *
+ * Short names (less than 5 bytes) are never even obfuscated, so for
+ * such names the relatively small number of alternates should never
+ * really be a problem.
+ *
+ * Long names (more than 6 bytes, say) are not likely to exhaust
+ * the number of available alternates.  In fact, the table could
+ * probably have stopped at 8 entries, on the assumption that 256
+ * alternates should be enough for most any situation.  The entries
+ * beyond those are present mostly for demonstration of how it could
+ * be populated with more entries, should it ever be necessary to do
+ * so.
+ */
+static int
+flip_bit(
+       size_t          name_len,
+       unsigned char   *name,
+       uint32_t        bitseq)
+{
+       int     index;
+       size_t  offset;
+       unsigned char *p0, *p1;
+       unsigned char m0, m1;
+       struct {
+           int         byte;   /* Offset from start within name */
+           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 */
+           { { 2, 0 }, { 3, 7 } },     /* each byte.  Each bit in */
+           { { 0, 4 }, { 4, 0 } },     /* a pair affects the same */
+           { { 0, 5 }, { 4, 1 } },     /* bit in the hash, so flipping */
+           { { 0, 6 }, { 4, 2 } },     /* both will change the name */
+           { { 0, 7 }, { 4, 3 } },     /* while preserving the hash. */
+           { { 3, 0 }, { 4, 7 } },
+           { { 0, 0 }, { 5, 3 } },     /* The first entry's byte offset */
+           { { 0, 1 }, { 5, 4 } },     /* must be less than the second. */
+           { { 0, 2 }, { 5, 5 } },
+           { { 0, 3 }, { 5, 6 } },     /* The table can be extended to */
+           { { 0, 4 }, { 5, 7 } },     /* an arbitrary number of entries */
+           { { 4, 0 }, { 5, 7 } },     /* but there's not much point. */
+               /* . . . */
+       };
+
+       /* Find the first entry *not* usable for name of this length */
+
+       for (index = 0; index < ARRAY_SIZE(bit_to_flip); index++)
+               if (bit_to_flip[index][1].byte >= name_len)
+                       break;
+
+       /*
+        * Back up to the last usable entry.  If that number is
+        * smaller than the bit sequence number, inform the caller
+        * that nothing this large (or larger) will work.
+        */
+       if (bitseq > --index)
+               return -1;
+
+       /*
+        * We will be switching bits at the end of name, with a
+        * preference for affecting the last bytes first.  Compute
+        * where in the name we'll start applying the changes.
+        */
+       offset = name_len - (bit_to_flip[index][1].byte + 1);
+       index -= bitseq;        /* Use later table entries first */
+
+       p0 = name + offset + bit_to_flip[index][0].byte;
+       p1 = name + offset + bit_to_flip[index][1].byte;
+       m0 = 1 << bit_to_flip[index][0].bit;
+       m1 = 1 << bit_to_flip[index][1].bit;
+
+       /* Only change the bytes if it produces valid characters */
+
+       if (is_invalid_char(*p0 ^ m0) || is_invalid_char(*p1 ^ m1))
+               return 0;
+
+       *p0 ^= m0;
+       *p1 ^= m1;
 
-       /* create a random name with the same hash value */
+       return 1;
+}
+
+/*
+ * This function generates a well-defined sequence of "alternate"
+ * names for a given name.  An alternate is a name having the same
+ * length and same hash value as the original name.  This is needed
+ * because the algorithm produces only one obfuscated name to use
+ * for a given original name, and it's possible that result matches
+ * a name already seen.  This function checks for this, and if it
+ * occurs, finds another suitable obfuscated name to use.
+ *
+ * Each bit in the binary representation of the sequence number is
+ * used to select one possible "bit flip" operation to perform on
+ * the name.  So for example:
+ *    seq = 0: selects no bits to flip
+ *    seq = 1: selects the 0th bit to flip
+ *    seq = 2: selects the 1st bit to flip
+ *    seq = 3: selects the 0th and 1st bit to flip
+ *    ... and so on.
+ *
+ * The flip_bit() function takes care of the details of the bit
+ * flipping within the name.  Note that the "1st bit" in this
+ * context is a bit sequence number; i.e. it doesn't necessarily
+ * mean bit 0x02 will be changed.
+ *
+ * If a valid name (one that contains no '/' or '\0' characters) is
+ * produced by this process for the given sequence number, this
+ * function returns 1.  If the result is not valid, it returns 0.
+ * Returns -1 if the sequence number is beyond the the maximum for
+ * names of the given length.
+ *
+ *
+ * Discussion
+ * ----------
+ * The number of alternates available for a given name is dependent
+ * on its length.  A "bit flip" involves inverting two bits in
+ * a name--the two bits being selected such that their values
+ * affect the name's hash value in the same way.  Alternates are
+ * thus generated by inverting the value of pairs of such
+ * "overlapping" bits in the original name.  Each byte after the
+ * first in a name adds at least one bit of overlap to work with.
+ * (See comments above flip_bit() for more discussion on this.)
+ *
+ * So the number of alternates is dependent on the number of such
+ * overlapping bits in a name.  If there are N bit overlaps, there
+ * 2^N alternates for that hash value.
+ *
+ * Here are the number of overlapping bits available for generating
+ * alternates for names of specific lengths:
+ *     1       0       (must have 2 bytes to have any overlap)
+ *     2       1       One bit overlaps--so 2 possible alternates
+ *     3       2       Two bits overlap--so 4 possible alternates
+ *     4       4       Three bits overlap, so 2^3 alternates
+ *     5       8       8 bits overlap (due to wrapping), 256 alternates
+ *     6       18      2^18 alternates
+ *     7       28      2^28 alternates
+ *        ...
+ * It's clear that the number of alternates grows very quickly with
+ * the length of the name.  But note that the set of alternates
+ * includes invalid names.  And for certain (contrived) names, the
+ * number of valid names is a fairly small fraction of the total
+ * number of alternates.
+ *
+ * The main driver for this infrastructure for coming up with
+ * alternate names is really related to names 5 (or possibly 6)
+ * bytes in length.  5-byte obfuscated names contain no randomly-
+ * generated bytes in them, and the chance of an obfuscated name
+ * matching an already-seen name is too high to just ignore.  This
+ * methodical selection of alternates ensures we don't produce
+ * duplicate names unless we have exhausted our options.
+ */
+static int
+find_alternate(
+       size_t          name_len,
+       unsigned char   *name,
+       uint32_t        seq)
+{
+       uint32_t        bitseq = 0;
+       uint32_t        bits = seq;
+
+       if (!seq)
+               return 1;       /* alternate 0 is the original name */
+       if (name_len < 2)       /* Must have 2 bytes to flip */
+               return -1;
+
+       for (bitseq = 0; bits; bitseq++) {
+               uint32_t        mask = 1 << bitseq;
+               int             fb;
+
+               if (!(bits & mask))
+                       continue;
+
+               fb = flip_bit(name_len, name, bitseq);
+               if (fb < 1)
+                       return fb ? -1 : 0;
+               bits ^= mask;
+       }
+
+       return 1;
+}
+
+/*
+ * Look up the given name in the name table.  If it is already
+ * present, iterate through a well-defined sequence of alternate
+ * names and attempt to use an alternate name instead.
+ *
+ * Returns 1 if the (possibly modified) name is not present in the
+ * name table.  Returns 0 if the name and all possible alternates
+ * are already in the table.
+ */
+static int
+handle_duplicate_name(xfs_dahash_t hash, size_t name_len, unsigned char *name)
+{
+       unsigned char   new_name[name_len + 1];
+       uint32_t        seq = 1;
+
+       if (!nametable_find(hash, name_len, name))
+               return 1;       /* No duplicate */
+
+       /* Name is already in use.  Need to find an alternate. */
 
        do {
-               dup = 0;
-               newname[0] = '/';
+               int     found;
 
-               for (;;) {
-                       /* if the first char is a "/", preserve it */
-                       i = (name[0] == '/');
+               /* Only change incoming name if we find an alternate */
+               do {
+                       memcpy(new_name, name, name_len);
+                       found = find_alternate(name_len, new_name, seq++);
+                       if (found < 0)
+                               return 0;       /* No more to check */
+               } while (!found);
+       } while (nametable_find(hash, name_len, new_name));
 
-                       for (newhash = 0; i < namelen - 5; i++) {
-                               newname[i] = random_filename_char();
-                               newhash = newname[i] ^ rol32(newhash, 7);
-                       }
-                       newhash = rol32(newhash, 3) ^ hash;
-                       if (name[0] != '/' || namelen > 5) {
-                               newname[namelen - 5] = (newhash >> 28) |
-                                               (random_filename_char() & 0xf0);
-                               if (is_invalid_char(newname[namelen - 5]))
-                                       continue;
-                       }
-                       newname[namelen - 4] = (newhash >> 21) & 0x7f;
-                       if (is_invalid_char(newname[namelen - 4]))
-                               continue;
-                       newname[namelen - 3] = (newhash >> 14) & 0x7f;
-                       if (is_invalid_char(newname[namelen - 3]))
-                               continue;
-                       newname[namelen - 2] = (newhash >> 7) & 0x7f;
-                       if (is_invalid_char(newname[namelen - 2]))
-                               continue;
-                       newname[namelen - 1] = ((newhash >> 0) ^
-                                       (newname[namelen - 5] >> 4)) & 0x7f;
-                       if (is_invalid_char(newname[namelen - 1]))
-                               continue;
-                       break;
-               }
+       /*
+        * The alternate wasn't in the table already.  Pass it back
+        * to the caller.
+        */
+       memcpy(name, new_name, name_len);
+
+       return 1;
+}
 
-               ASSERT(libxfs_da_hashname(newname, namelen) == hash);
+static void
+generate_obfuscated_name(
+       xfs_ino_t               ino,
+       int                     namelen,
+       unsigned char           *name)
+{
+       xfs_dahash_t            hash;
+
+       /*
+        * We don't obfuscate "lost+found" or any orphan files
+        * therein.  When the name table is used for extended
+        * attributes, the inode number provided is 0, in which
+        * case we don't need to make this check.
+        */
+       if (ino && in_lost_found(ino, namelen, name))
+               return;
+
+       /*
+        * If the name starts with a slash, just skip over it.  It
+        * isn't included in the hash and we don't record it in the
+        * name table.  Note that the namelen value passed in does
+        * not count the leading slash (if one is present).
+        */
+       if (*name == '/')
+               name++;
 
-               for (p = nametable[hash % NAME_TABLE_SIZE]; p; p = p->next) {
-                       if (p->hash == hash && p->namelen == namelen &&
-                                       memcmp(p->name, newname, namelen) == 0){
-                               dup = 1;
-                               break;
-                       }
-               }
-       } while (dup);
+       /* Obfuscate the name (if possible) */
 
-       memcpy(name, newname, namelen);
+       hash = libxfs_da_hashname(name, namelen);
+       obfuscate_name(hash, namelen, name);
 
-       p = malloc(sizeof(name_ent_t) + namelen);
-       if (p == NULL)
+       /*
+        * Make sure the name is not something already seen.  If we
+        * fail to find a suitable alternate, we're dealing with a
+        * very pathological situation, and we may end up creating
+        * a duplicate name in the metadump, so issue a warning.
+        */
+       if (!handle_duplicate_name(hash, namelen, name)) {
+               print_warning("duplicate name for inode %llu "
+                               "in dir inode %llu\n",
+                       (unsigned long long) ino,
+                       (unsigned long long) cur_ino);
                return;
+       }
 
-       p->next = nametable[hash % NAME_TABLE_SIZE];
-       p->hash = hash;
-       p->namelen = namelen;
-       memcpy(p->name, name, namelen);
+       /* Create an entry for the new name in the name table. */
 
-       nametable[hash % NAME_TABLE_SIZE] = p;
+       if (!nametable_add(hash, namelen, name))
+               print_warning("unable to record name for inode %llu "
+                               "in dir inode %llu\n",
+                       (unsigned long long) ino,
+                       (unsigned long long) cur_ino);
 }
 
 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 = &dip->di_u.di_dir2sf;
-       ino_dir_size = be64_to_cpu(dip->di_core.di_size);
+       sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip);
+       ino_dir_size = be64_to_cpu(dip->di_size);
        if (ino_dir_size > XFS_DFORK_DSIZE(dip, mp)) {
                ino_dir_size = XFS_DFORK_DSIZE(dip, mp);
                if (show_warnings)
@@ -511,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++) {
 
                /*
@@ -524,38 +1210,87 @@ 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_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
-obfuscate_sf_symlink(
+process_sf_symlink(
        xfs_dinode_t            *dip)
 {
        __uint64_t              len;
+       char                    *buf;
 
-       len = be64_to_cpu(dip->di_core.di_size);
+       len = be64_to_cpu(dip->di_size);
        if (len > XFS_DFORK_DSIZE(dip, mp)) {
                if (show_warnings)
                        print_warning("invalid size (%d) in symlink inode %llu",
@@ -563,17 +1298,22 @@ obfuscate_sf_symlink(
                len = XFS_DFORK_DSIZE(dip, mp);
        }
 
-       while (len > 0)
-               dip->di_u.di_symlink[--len] = random() % 127 + 1;
+       buf = (char *)XFS_DFORK_DPTR(dip);
+       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;
@@ -612,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
@@ -645,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
@@ -791,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,
@@ -880,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;
@@ -948,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;
@@ -1029,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) {
@@ -1081,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 "
@@ -1095,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) {
@@ -1120,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);
 }
@@ -1142,16 +2084,16 @@ process_inode_data(
        xfs_dinode_t            *dip,
        typnm_t                 itype)
 {
-       switch (dip->di_core.di_format) {
+       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: ;
@@ -1167,40 +2109,71 @@ 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_core.di_mode) & S_IFMT) {
+       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);
                        break;
                default: ;
        }
-       clear_nametable();
+       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_core.di_aformat) {
+               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:
@@ -1211,8 +2184,16 @@ process_inode(
                                success = process_btinode(dip, TYP_ATTR);
                                break;
                }
-               clear_nametable();
+               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;
 }
 
@@ -1226,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))) {
@@ -1242,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)",
@@ -1313,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);
 
@@ -1324,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))
@@ -1363,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);
@@ -1381,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
@@ -1403,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;
        }
 
@@ -1418,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;
        }
 
@@ -1433,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;
        }
 
@@ -1447,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;
        }
 
@@ -1460,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 */
@@ -1485,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);
@@ -1532,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");
 
@@ -1549,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
@@ -1573,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;
@@ -1590,7 +2704,7 @@ metadump_f(
                                }
                                break;
                        case 'o':
-                               dont_obfuscate = 1;
+                               obfuscate = 0;
                                break;
                        case 'w':
                                show_warnings = 1;
@@ -1614,22 +2728,28 @@ metadump_f(
        metablock->mb_blocklog = BBSHIFT;
        metablock->mb_magic = cpu_to_be32(XFS_MD_MAGIC);
 
-       if (!create_nametable()) {
-               print_warning("memory allocation failure");
+       block_index = (__be64 *)((char *)metablock + sizeof(xfs_metablock_t));
+       block_buffer = (char *)metablock + BBSIZE;
+       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;
        }
 
-       block_index = (__be64 *)((char *)metablock + sizeof(xfs_metablock_t));
-       block_buffer = (char *)metablock + BBSIZE;
-       num_indicies = (BBSIZE - sizeof(xfs_metablock_t)) / sizeof(__be64);
        cur_index = 0;
        start_iocur_sp = iocur_sp;
 
        if (strcmp(argv[optind], "-") == 0) {
                if (isatty(fileno(stdout))) {
                        print_warning("cannot write to a terminal");
-                       free(nametable);
                        free(metablock);
                        return 0;
                }
@@ -1638,7 +2758,6 @@ metadump_f(
                outf = fopen(argv[optind], "wb");
                if (outf == NULL) {
                        print_warning("cannot create dump file");
-                       free(nametable);
                        free(metablock);
                        return 0;
                }
@@ -1663,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);
@@ -1675,7 +2794,6 @@ metadump_f(
        while (iocur_sp > start_iocur_sp)
                pop_cur();
 
-       free(nametable);
        free(metablock);
 
        return 0;