/*
- * 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
* 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);
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 */
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;
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;
" 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"
* 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
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(
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))
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;
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)
}
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++) {
/*
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",
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;
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
* 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
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,
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;
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;
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) {
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 "
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) {
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);
}
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: ;
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:
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;
}
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))) {
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)",
xfs_inobt_ptr_t *pp;
int i;
int numrecs;
+ int finobt = *(int *) arg;
numrecs = be16_to_cpu(block->bb_numrecs);
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))
{
xfs_agblock_t root;
int levels;
+ int finobt = 0;
root = be32_to_cpu(agi->agi_root);
levels = be32_to_cpu(agi->agi_level);
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
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;
}
if (stop_on_read_error)
goto pop_out;
} else {
- if (!write_buf(iocur_top))
+ if (write_buf(iocur_top))
goto pop_out;
}
if (stop_on_read_error)
goto pop_out;
} else {
- if (!write_buf(iocur_top))
+ if (write_buf(iocur_top))
goto pop_out;
}
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;
}
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 */
int offset;
int rval = 0;
- if (ino == 0)
+ if (ino == 0 || ino == NULLFSINO)
return 1;
agno = XFS_INO_TO_AGNO(mp, ino);
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");
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
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;
}
break;
case 'o':
- dont_obfuscate = 1;
+ obfuscate = 0;
break;
case 'w':
show_warnings = 1;
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;
}
outf = fopen(argv[optind], "wb");
if (outf == NULL) {
print_warning("cannot create dump file");
- free(nametable);
free(metablock);
return 0;
}
/* write the remaining index */
if (!exitcode)
- exitcode = !write_index();
+ exitcode = write_index() < 0;
if (progress_since_warning)
fputc('\n', (outf == stdout) ? stderr : stdout);
while (iocur_sp > start_iocur_sp)
pop_cur();
- free(nametable);
free(metablock);
return 0;