blob: ef6e571aab9b11a549a29f49d0a1d38ebc3280e8 [file] [log] [blame]
/*
* Copyright (c) 2007 Silicon Graphics, Inc.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <libxfs.h>
#include "bmap.h"
#include "command.h"
#include "metadump.h"
#include "io.h"
#include "output.h"
#include "type.h"
#include "init.h"
#include "sig.h"
#include "xfs_metadump.h"
#define DEFAULT_MAX_EXT_SIZE 1000
/* copy all metadata structures to/from a file */
static int metadump_f(int argc, char **argv);
static void metadump_help(void);
/*
* metadump commands issue info/wornings/errors to standard error as
* metadump supports stdout as a destination.
*
* All static functions return zero on failure, while the public functions
* return zero on success.
*/
static const cmdinfo_t metadump_cmd =
{ "metadump", NULL, metadump_f, 0, -1, 0,
N_("[-e] [-g] [-m max_extent] [-w] [-o] filename"),
N_("dump metadata to a file"), metadump_help };
static FILE *outf; /* metadump file */
static xfs_metablock_t *metablock; /* header + index + buffers */
static __be64 *block_index;
static char *block_buffer;
static int num_indicies;
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 show_warnings = 0;
static int progress_since_warning = 0;
void
metadump_init(void)
{
add_command(&metadump_cmd);
}
static void
metadump_help(void)
{
dbprintf(_(
"\n"
" The 'metadump' command dumps the known metadata to a compact file suitable\n"
" for compressing and sending to an XFS maintainer for corruption analysis \n"
" or xfs_repair failures.\n\n"
" Options:\n"
" -e -- Ignore read errors and keep going\n"
" -g -- Display dump progress\n"
" -m -- Specify max extent size in blocks to copy (default = %d blocks)\n"
" -o -- Don't obfuscate names and extended attributes\n"
" -w -- Show warnings of bad metadata information\n"
"\n"), DEFAULT_MAX_EXT_SIZE);
}
static void
print_warning(const char *fmt, ...)
{
char buf[200];
va_list ap;
if (seenint())
return;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
buf[sizeof(buf)-1] = '\0';
fprintf(stderr, "%s%s: %s\n", progress_since_warning ? "\n" : "",
progname, buf);
progress_since_warning = 0;
}
static void
print_progress(const char *fmt, ...)
{
char buf[60];
va_list ap;
FILE *f;
if (seenint())
return;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
buf[sizeof(buf)-1] = '\0';
f = (outf == stdout) ? stderr : stdout;
fprintf(f, "\r%-59s", buf);
fflush(f);
progress_since_warning = 1;
}
/*
* 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.
*/
static int
write_index(void)
{
/*
* write index block and following data blocks (streaming)
*/
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;
}
memset(block_index, 0, num_indicies * sizeof(__be64));
cur_index = 0;
return 1;
}
static int
write_buf(
iocur_t *buf)
{
char *data;
__int64_t off;
int i;
for (i = 0, off = buf->bb, data = buf->data;
i < buf->blen;
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;
}
}
return !seenint();
}
static int
scan_btree(
xfs_agnumber_t agno,
xfs_agblock_t agbno,
int level,
typnm_t btype,
void *arg,
int (*func)(struct xfs_btree_block *block,
xfs_agnumber_t agno,
xfs_agblock_t agbno,
int level,
typnm_t btype,
void *arg))
{
int rval = 0;
push_cur();
set_cur(&typtab[btype], XFS_AGB_TO_DADDR(mp, agno, agbno), blkbb,
DB_RING_IGN, NULL);
if (iocur_top->data == NULL) {
print_warning("cannot read %s block %u/%u", typtab[btype].name,
agno, agbno);
rval = !stop_on_read_error;
goto pop_out;
}
if (!write_buf(iocur_top))
goto pop_out;
if (!(*func)(iocur_top->data, agno, agbno, level - 1, btype, arg))
goto pop_out;
rval = 1;
pop_out:
pop_cur();
return rval;
}
/* free space tree copy routines */
static int
valid_bno(
xfs_agnumber_t agno,
xfs_agblock_t agbno)
{
if (agno < (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
agbno <= mp->m_sb.sb_agblocks)
return 1;
if (agno == (mp->m_sb.sb_agcount - 1) && agbno > 0 &&
agbno <= (mp->m_sb.sb_dblocks -
(xfs_drfsbno_t)(mp->m_sb.sb_agcount - 1) *
mp->m_sb.sb_agblocks))
return 1;
return 0;
}
static int
scanfunc_freesp(
struct xfs_btree_block *block,
xfs_agnumber_t agno,
xfs_agblock_t agbno,
int level,
typnm_t btype,
void *arg)
{
xfs_alloc_ptr_t *pp;
int i;
int numrecs;
if (level == 0)
return 1;
numrecs = be16_to_cpu(block->bb_numrecs);
if (numrecs > mp->m_alloc_mxr[1]) {
if (show_warnings)
print_warning("invalid numrecs (%u) in %s block %u/%u",
numrecs, typtab[btype].name, agno, agbno);
return 1;
}
pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]);
for (i = 0; i < numrecs; i++) {
if (!valid_bno(agno, be32_to_cpu(pp[i]))) {
if (show_warnings)
print_warning("invalid block number (%u/%u) "
"in %s block %u/%u",
agno, be32_to_cpu(pp[i]),
typtab[btype].name, agno, agbno);
continue;
}
if (!scan_btree(agno, be32_to_cpu(pp[i]), level, btype, arg,
scanfunc_freesp))
return 0;
}
return 1;
}
static int
copy_free_bno_btree(
xfs_agnumber_t agno,
xfs_agf_t *agf)
{
xfs_agblock_t root;
int levels;
root = be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]);
levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]);
/* 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 bnobt "
"root in agf %u", root, agno);
return 1;
}
if (levels >= XFS_BTREE_MAXLEVELS) {
if (show_warnings)
print_warning("invalid level (%u) in bnobt root "
"in agf %u", levels, agno);
return 1;
}
return scan_btree(agno, root, levels, TYP_BNOBT, agf, scanfunc_freesp);
}
static int
copy_free_cnt_btree(
xfs_agnumber_t agno,
xfs_agf_t *agf)
{
xfs_agblock_t root;
int levels;
root = be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]);
levels = be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]);
/* 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 cntbt "
"root in agf %u", root, agno);
return 1;
}
if (levels >= XFS_BTREE_MAXLEVELS) {
if (show_warnings)
print_warning("invalid level (%u) in cntbt root "
"in agf %u", levels, agno);
return 1;
}
return scan_btree(agno, root, levels, TYP_CNTBT, agf, scanfunc_freesp);
}
/* filename and extended attribute obfuscation routines */
typedef struct name_ent {
struct name_ent *next;
xfs_dahash_t hash;
int namelen;
uchar_t name[1];
} name_ent_t;
#define NAME_TABLE_SIZE 4096
static name_ent_t **nametable;
static int
create_nametable(void)
{
nametable = calloc(NAME_TABLE_SIZE, sizeof(name_ent_t));
return nametable != NULL;
}
static void
clear_nametable(void)
{
int i;
name_ent_t *p;
for (i = 0; i < NAME_TABLE_SIZE; i++) {
while (nametable[i]) {
p = nametable[i];
nametable[i] = p->next;
free(p);
}
}
}
#define is_invalid_char(c) ((c) == '/' || (c) == '\0')
#define rol32(x,y) (((x) << (y)) | ((x) >> (32 - (y))))
static inline uchar_t
random_filename_char(void)
{
uchar_t c;
do {
c = random() % 127 + 1;
} while (c == '/');
return c;
}
static int
is_special_dirent(
xfs_ino_t ino,
int namelen,
uchar_t *name)
{
static xfs_ino_t orphanage_ino = 0;
char s[32];
int slen;
/*
* due to the XFS name hashing algorithm, we cannot obfuscate
* names with 4 chars or less.
*/
if (namelen <= 4)
return 1;
if (ino == 0)
return 0;
/*
* don't obfuscate lost+found nor any inodes within lost+found with
* the inode number
*/
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 = sprintf(s, "%lld", (long long)ino);
return (slen == namelen && memcmp(name, s, namelen) == 0);
}
static void
generate_obfuscated_name(
xfs_ino_t ino,
int namelen,
uchar_t *name)
{
xfs_dahash_t hash;
name_ent_t *p;
int i;
int dup;
xfs_dahash_t newhash;
uchar_t newname[NAME_MAX];
if (is_special_dirent(ino, namelen, name))
return;
hash = libxfs_da_hashname(name, namelen);
/* create a random name with the same hash value */
do {
dup = 0;
newname[0] = '/';
for (;;) {
/* if the first char is a "/", preserve it */
i = (name[0] == '/');
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;
}
ASSERT(libxfs_da_hashname(newname, namelen) == hash);
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);
memcpy(name, newname, namelen);
p = malloc(sizeof(name_ent_t) + namelen);
if (p == NULL)
return;
p->next = nametable[hash % NAME_TABLE_SIZE];
p->hash = hash;
p->namelen = namelen;
memcpy(p->name, name, namelen);
nametable[hash % NAME_TABLE_SIZE] = p;
}
static void
obfuscate_sf_dir(
xfs_dinode_t *dip)
{
xfs_dir2_sf_t *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);
if (ino_dir_size > XFS_DFORK_DSIZE(dip, mp)) {
ino_dir_size = XFS_DFORK_DSIZE(dip, mp);
if (show_warnings)
print_warning("invalid size in dir inode %llu",
(long long)cur_ino);
}
sfep = xfs_dir2_sf_firstentry(sfp);
for (i = 0; (i < sfp->hdr.count) &&
((char *)sfep - (char *)sfp < ino_dir_size); i++) {
/*
* first check for bad name lengths. If they are bad, we
* have limitations to how much can be obfuscated.
*/
int namelen = sfep->namelen;
if (namelen == 0) {
if (show_warnings)
print_warning("zero length entry in dir inode "
"%llu", (long long)cur_ino);
if (i != sfp->hdr.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) >
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)
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]);
sfep = (xfs_dir2_sf_entry_t *)((char *)sfep +
xfs_dir2_sf_entsize_byname(sfp, namelen));
}
}
static void
obfuscate_sf_symlink(
xfs_dinode_t *dip)
{
__uint64_t len;
len = be64_to_cpu(dip->di_core.di_size);
if (len > XFS_DFORK_DSIZE(dip, mp)) {
if (show_warnings)
print_warning("invalid size (%d) in symlink inode %llu",
len, (long long)cur_ino);
len = XFS_DFORK_DSIZE(dip, mp);
}
while (len > 0)
dip->di_u.di_symlink[--len] = random() % 127 + 1;
}
static void
obfuscate_sf_attr(
xfs_dinode_t *dip)
{
/*
* with extended attributes, obfuscate the names and zero the actual
* values.
*/
xfs_attr_shortform_t *asfp;
xfs_attr_sf_entry_t *asfep;
int ino_attr_size;
int i;
asfp = (xfs_attr_shortform_t *)XFS_DFORK_APTR(dip);
if (asfp->hdr.count == 0)
return;
ino_attr_size = be16_to_cpu(asfp->hdr.totsize);
if (ino_attr_size > XFS_DFORK_ASIZE(dip, mp)) {
ino_attr_size = XFS_DFORK_ASIZE(dip, mp);
if (show_warnings)
print_warning("invalid attr size in inode %llu",
(long long)cur_ino);
}
asfep = &asfp->list[0];
for (i = 0; (i < asfp->hdr.count) &&
((char *)asfep - (char *)asfp < ino_attr_size); i++) {
int namelen = asfep->namelen;
if (namelen == 0) {
if (show_warnings)
print_warning("zero length attr entry in inode "
"%llu", (long long)cur_ino);
break;
} else if ((char *)asfep - (char *)asfp +
XFS_ATTR_SF_ENTSIZE(asfep) > ino_attr_size) {
if (show_warnings)
print_warning("attr entry length in inode %llu "
"overflows space", (long long)cur_ino);
break;
}
generate_obfuscated_name(0, asfep->namelen, &asfep->nameval[0]);
memset(&asfep->nameval[asfep->namelen], 0, 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;
static void
obfuscate_dir_data_blocks(
char *block,
xfs_dfiloff_t offset,
xfs_dfilblks_t count,
int is_block_format)
{
/*
* we have to rely on the fileoffset and signature of the block to
* handle it's contents. If it's invalid, leave it alone.
* for multi-fsblock dir blocks, if a name crosses an extent boundary,
* ignore it and continue.
*/
int 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 */
dir_data.bad_block = 0;
if (is_block_format) {
xfs_dir2_leaf_entry_t *blp;
xfs_dir2_block_tail_t *btp;
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;
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);
if (be32_to_cpu(((xfs_dir2_data_hdr_t*)block)->magic) !=
wantmagic) {
if (show_warnings)
print_warning("invalid magic in dir "
"inode %llu block %ld",
(long long)cur_ino,
(long)offset);
dir_data.bad_block = 1;
}
}
dir_data.block_index++;
if (dir_data.block_index == mp->m_dirblkfsbs)
dir_data.block_index = 0;
if (dir_data.bad_block)
continue;
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;
}
dir_offset += length;
ptr += length;
if (dir_offset >= dir_data.end_of_data ||
ptr >= endptr)
break;
}
dep = (xfs_dir2_data_entry_t *)ptr;
length = xfs_dir2_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;
}
generate_obfuscated_name(be64_to_cpu(dep->inumber),
dep->namelen, &dep->name[0]);
dir_offset += length;
ptr += length;
}
dir_data.offset_to_entry = dir_offset &
(mp->m_sb.sb_blocksize - 1);
}
}
static void
obfuscate_symlink_blocks(
char *block,
xfs_dfilblks_t count)
{
int i;
count <<= mp->m_sb.sb_blocklog;
for (i = 0; i < count; i++)
block[i] = random() % 127 + 1;
}
#define MAX_REMOTE_VALS 4095
static struct attr_data_s {
int remote_val_count;
xfs_dablk_t remote_vals[MAX_REMOTE_VALS];
} attr_data;
static inline void
add_remote_vals(
xfs_dablk_t blockidx,
int length)
{
while (length > 0 && attr_data.remote_val_count < MAX_REMOTE_VALS) {
attr_data.remote_vals[attr_data.remote_val_count] = blockidx;
attr_data.remote_val_count++;
blockidx++;
length -= XFS_LBSIZE(mp);
}
}
static void
obfuscate_attr_blocks(
char *block,
xfs_dfiloff_t offset,
xfs_dfilblks_t count)
{
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;
for (c = 0; c < count; c++, offset++, block += XFS_LBSIZE(mp)) {
leaf = (xfs_attr_leafblock_t *)block;
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;
}
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 (show_warnings)
print_warning("invalid attr count in inode %llu",
(long long)cur_ino);
continue;
}
for (i = 0, entry = &leaf->entries[0]; i < nentries;
i++, entry++) {
if (be16_to_cpu(entry->nameidx) > XFS_LBSIZE(mp)) {
if (show_warnings)
print_warning("invalid attr nameidx "
"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;
}
generate_obfuscated_name(0, local->namelen,
&local->nameval[0]);
memset(&local->nameval[local->namelen], 0,
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;
}
generate_obfuscated_name(0, remote->namelen,
&remote->name[0]);
add_remote_vals(be32_to_cpu(remote->valueblk),
be32_to_cpu(remote->valuelen));
}
}
}
}
/* inode copy routines */
static int
process_bmbt_reclist(
xfs_bmbt_rec_t *rp,
int numrecs,
typnm_t btype)
{
int i;
xfs_dfiloff_t o, op = NULLDFILOFF;
xfs_dfsbno_t s;
xfs_dfilblks_t c, cp = NULLDFILOFF;
int f;
xfs_dfiloff_t last;
xfs_agnumber_t agno;
xfs_agblock_t agbno;
if (btype == TYP_DATA)
return 1;
convert_extent(&rp[numrecs - 1], &o, &s, &c, &f);
last = o + c;
for (i = 0; i < numrecs; i++, rp++) {
convert_extent(rp, &o, &s, &c, &f);
/*
* ignore extents that are clearly bogus, and if a bogus
* one is found, stop processing remaining extents
*/
if (i > 0 && op + cp > o) {
if (show_warnings)
print_warning("bmap extent %d in %s ino %llu "
"starts at %llu, previous extent "
"ended at %llu", i,
typtab[btype].name, (long long)cur_ino,
o, op + cp - 1);
break;
}
if (c > max_extent_size) {
/*
* since we are only processing non-data extents,
* large numbers of blocks in a metadata extent is
* extremely rare and more than likely to be corrupt.
*/
if (show_warnings)
print_warning("suspicious count %u in bmap "
"extent %d in %s ino %llu", c, i,
typtab[btype].name, (long long)cur_ino);
break;
}
op = o;
cp = c;
agno = XFS_FSB_TO_AGNO(mp, s);
agbno = XFS_FSB_TO_AGBNO(mp, s);
if (!valid_bno(agno, agbno)) {
if (show_warnings)
print_warning("invalid block number %u/%u "
"(%llu) in bmap extent %d in %s ino "
"%llu", agno, agbno, s, i,
typtab[btype].name, (long long)cur_ino);
break;
}
if (!valid_bno(agno, agbno + c - 1)) {
if (show_warnings)
print_warning("bmap extent %i in %s inode %llu "
"overflows AG (end is %u/%u)", i,
typtab[btype].name, (long long)cur_ino,
agno, agbno + c - 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;
}
} 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;
}
}
pop_cur();
}
return 1;
}
static int
scanfunc_bmap(
struct xfs_btree_block *block,
xfs_agnumber_t agno,
xfs_agblock_t agbno,
int level,
typnm_t btype,
void *arg) /* ptr to itype */
{
int i;
xfs_bmbt_ptr_t *pp;
int nrecs;
nrecs = be16_to_cpu(block->bb_numrecs);
if (level == 0) {
if (nrecs > mp->m_bmap_dmxr[0]) {
if (show_warnings)
print_warning("invalid numrecs (%u) in %s "
"block %u/%u", nrecs,
typtab[btype].name, agno, agbno);
return 1;
}
return process_bmbt_reclist(XFS_BMBT_REC_ADDR(mp, block, 1),
nrecs, *(typnm_t*)arg);
}
if (nrecs > mp->m_bmap_dmxr[1]) {
if (show_warnings)
print_warning("invalid numrecs (%u) in %s block %u/%u",
nrecs, typtab[btype].name, agno, agbno);
return 1;
}
pp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[1]);
for (i = 0; i < nrecs; i++) {
xfs_agnumber_t ag;
xfs_agblock_t bno;
ag = XFS_FSB_TO_AGNO(mp, be64_to_cpu(pp[i]));
bno = XFS_FSB_TO_AGBNO(mp, be64_to_cpu(pp[i]));
if (bno == 0 || bno > mp->m_sb.sb_agblocks ||
ag > mp->m_sb.sb_agcount) {
if (show_warnings)
print_warning("invalid block number (%u/%u) "
"in %s block %u/%u", ag, bno,
typtab[btype].name, agno, agbno);
continue;
}
if (!scan_btree(ag, bno, level, btype, arg, scanfunc_bmap))
return 0;
}
return 1;
}
static int
process_btinode(
xfs_dinode_t *dip,
typnm_t itype)
{
xfs_bmdr_block_t *dib;
int i;
xfs_bmbt_ptr_t *pp;
int level;
int nrecs;
int maxrecs;
int whichfork;
typnm_t btype;
whichfork = (itype == TYP_ATTR) ? XFS_ATTR_FORK : XFS_DATA_FORK;
btype = (itype == TYP_ATTR) ? TYP_BMAPBTA : TYP_BMAPBTD;
dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR(dip, whichfork);
level = be16_to_cpu(dib->bb_level);
nrecs = be16_to_cpu(dib->bb_numrecs);
if (level > XFS_BM_MAXLEVELS(mp, whichfork)) {
if (show_warnings)
print_warning("invalid level (%u) in inode %lld %s "
"root", level, (long long)cur_ino,
typtab[btype].name);
return 1;
}
if (level == 0) {
return process_bmbt_reclist(XFS_BMDR_REC_ADDR(dib, 1),
nrecs, itype);
}
maxrecs = xfs_bmdr_maxrecs(mp, XFS_DFORK_SIZE(dip, mp, whichfork), 0);
if (nrecs > maxrecs) {
if (show_warnings)
print_warning("invalid numrecs (%u) in inode %lld %s "
"root", nrecs, (long long)cur_ino,
typtab[btype].name);
return 1;
}
pp = XFS_BMDR_PTR_ADDR(dib, 1, maxrecs);
for (i = 0; i < nrecs; i++) {
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]));
if (bno == 0 || bno > mp->m_sb.sb_agblocks ||
ag > mp->m_sb.sb_agcount) {
if (show_warnings)
print_warning("invalid block number (%u/%u) "
"in inode %llu %s root", ag,
bno, (long long)cur_ino,
typtab[btype].name);
continue;
}
if (!scan_btree(ag, bno, level, btype, &itype, scanfunc_bmap))
return 0;
}
return 1;
}
static int
process_exinode(
xfs_dinode_t *dip,
typnm_t itype)
{
int whichfork;
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)) {
if (show_warnings)
print_warning("bad number of extents %d in inode %lld",
nex, (long long)cur_ino);
return 1;
}
return process_bmbt_reclist((xfs_bmbt_rec_t *)XFS_DFORK_PTR(dip,
whichfork), nex, itype);
}
static int
process_inode_data(
xfs_dinode_t *dip,
typnm_t itype)
{
switch (dip->di_core.di_format) {
case XFS_DINODE_FMT_LOCAL:
if (!dont_obfuscate)
switch (itype) {
case TYP_DIR2:
obfuscate_sf_dir(dip);
break;
case TYP_SYMLINK:
obfuscate_sf_symlink(dip);
break;
default: ;
}
break;
case XFS_DINODE_FMT_EXTENTS:
return process_exinode(dip, itype);
case XFS_DINODE_FMT_BTREE:
return process_btinode(dip, itype);
}
return 1;
}
static int
process_inode(
xfs_agnumber_t agno,
xfs_agino_t agino,
xfs_dinode_t *dip)
{
int success;
success = 1;
cur_ino = XFS_AGINO_TO_INO(mp, agno, agino);
/* copy appropriate data fork metadata */
switch (be16_to_cpu(dip->di_core.di_mode) & S_IFMT) {
case S_IFDIR:
memset(&dir_data, 0, sizeof(dir_data));
success = process_inode_data(dip, TYP_DIR2);
break;
case S_IFLNK:
success = process_inode_data(dip, TYP_SYMLINK);
break;
case S_IFREG:
success = process_inode_data(dip, TYP_DATA);
break;
default: ;
}
clear_nametable();
/* copy extended attributes if they exist and forkoff is valid */
if (success && XFS_DFORK_DSIZE(dip, mp) < XFS_LITINO(mp)) {
attr_data.remote_val_count = 0;
switch (dip->di_core.di_aformat) {
case XFS_DINODE_FMT_LOCAL:
if (!dont_obfuscate)
obfuscate_sf_attr(dip);
break;
case XFS_DINODE_FMT_EXTENTS:
success = process_exinode(dip, TYP_ATTR);
break;
case XFS_DINODE_FMT_BTREE:
success = process_btinode(dip, TYP_ATTR);
break;
}
clear_nametable();
}
return success;
}
static __uint32_t inodes_copied = 0;
static int
copy_inode_chunk(
xfs_agnumber_t agno,
xfs_inobt_rec_t *rp)
{
xfs_agino_t agino;
int off;
xfs_agblock_t agbno;
int i;
int rval = 0;
agino = be32_to_cpu(rp->ir_startino);
agbno = XFS_AGINO_TO_AGBNO(mp, agino);
off = XFS_INO_TO_OFFSET(mp, agino);
if (agino == 0 || agino == NULLAGINO || !valid_bno(agno, agbno) ||
!valid_bno(agno, XFS_AGINO_TO_AGBNO(mp,
agino + XFS_INODES_PER_CHUNK - 1))) {
if (show_warnings)
print_warning("bad inode number %llu (%u/%u)",
XFS_AGINO_TO_INO(mp, agno, agino), agno, agino);
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) &&
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;
}
/*
* 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;
if (XFS_INOBT_IS_FREE_DISK(rp, i))
continue;
dip = (xfs_dinode_t *)((char *)iocur_top->data +
((off + i) << mp->m_sb.sb_inodelog));
if (!process_inode(agno, agino + i, dip))
goto pop_out;
}
skip_processing:
if (!write_buf(iocur_top))
goto pop_out;
inodes_copied += XFS_INODES_PER_CHUNK;
if (show_progress)
print_progress("Copied %u of %u inodes (%u of %u AGs)",
inodes_copied, mp->m_sb.sb_icount, agno,
mp->m_sb.sb_agcount);
rval = 1;
pop_out:
pop_cur();
return rval;
}
static int
scanfunc_ino(
struct xfs_btree_block *block,
xfs_agnumber_t agno,
xfs_agblock_t agbno,
int level,
typnm_t btype,
void *arg)
{
xfs_inobt_rec_t *rp;
xfs_inobt_ptr_t *pp;
int i;
int numrecs;
numrecs = be16_to_cpu(block->bb_numrecs);
if (level == 0) {
if (numrecs > mp->m_inobt_mxr[0]) {
if (show_warnings)
print_warning("invalid numrecs %d in %s "
"block %u/%u", numrecs,
typtab[btype].name, agno, agbno);
numrecs = mp->m_inobt_mxr[0];
}
rp = XFS_INOBT_REC_ADDR(mp, block, 1);
for (i = 0; i < numrecs; i++, rp++) {
if (!copy_inode_chunk(agno, rp))
return 0;
}
return 1;
}
if (numrecs > mp->m_inobt_mxr[1]) {
if (show_warnings)
print_warning("invalid numrecs %d in %s block %u/%u",
numrecs, typtab[btype].name, agno, agbno);
numrecs = mp->m_inobt_mxr[1];
}
pp = XFS_INOBT_PTR_ADDR(mp, block, 1, mp->m_inobt_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_ino))
return 0;
}
return 1;
}
static int
copy_inodes(
xfs_agnumber_t agno,
xfs_agi_t *agi)
{
xfs_agblock_t root;
int levels;
root = be32_to_cpu(agi->agi_root);
levels = be32_to_cpu(agi->agi_level);
/* validate root and levels before processing the tree */
if (root == 0 || root > mp->m_sb.sb_agblocks) {
if (show_warnings)
print_warning("invalid block number (%u) in inobt "
"root in agi %u", root, agno);
return 1;
}
if (levels >= XFS_BTREE_MAXLEVELS) {
if (show_warnings)
print_warning("invalid level (%u) in inobt root "
"in agi %u", levels, agno);
return 1;
}
return scan_btree(agno, root, levels, TYP_INOBT, agi, scanfunc_ino);
}
static int
scan_ag(
xfs_agnumber_t agno)
{
xfs_agf_t *agf;
xfs_agi_t *agi;
int stack_count = 0;
int rval = 0;
/* copy the superblock of the AG */
push_cur();
stack_count++;
set_cur(&typtab[TYP_SB], XFS_AG_DADDR(mp, agno, XFS_SB_DADDR),
XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
if (!iocur_top->data) {
print_warning("cannot read superblock for ag %u", agno);
if (stop_on_read_error)
goto pop_out;
} else {
if (!write_buf(iocur_top))
goto pop_out;
}
/* copy the AG free space btree root */
push_cur();
stack_count++;
set_cur(&typtab[TYP_AGF], XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)),
XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
agf = iocur_top->data;
if (iocur_top->data == NULL) {
print_warning("cannot read agf block for ag %u", agno);
if (stop_on_read_error)
goto pop_out;
} else {
if (!write_buf(iocur_top))
goto pop_out;
}
/* copy the AG inode btree root */
push_cur();
stack_count++;
set_cur(&typtab[TYP_AGI], XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)),
XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
agi = iocur_top->data;
if (iocur_top->data == NULL) {
print_warning("cannot read agi block for ag %u", agno);
if (stop_on_read_error)
goto pop_out;
} else {
if (!write_buf(iocur_top))
goto pop_out;
}
/* copy the AG free list header */
push_cur();
stack_count++;
set_cur(&typtab[TYP_AGFL], XFS_AG_DADDR(mp, agno, XFS_AGFL_DADDR(mp)),
XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
if (iocur_top->data == NULL) {
print_warning("cannot read agfl block for ag %u", agno);
if (stop_on_read_error)
goto pop_out;
} else {
if (!write_buf(iocur_top))
goto pop_out;
}
/* copy AG free space btrees */
if (agf) {
if (show_progress)
print_progress("Copying free space trees of AG %u",
agno);
if (!copy_free_bno_btree(agno, agf))
goto pop_out;
if (!copy_free_cnt_btree(agno, agf))
goto pop_out;
}
/* copy inode btrees and the inodes and their associated metadata */
if (agi) {
if (!copy_inodes(agno, agi))
goto pop_out;
}
rval = 1;
pop_out:
while (stack_count--)
pop_cur();
return rval;
}
static int
copy_ino(
xfs_ino_t ino,
typnm_t itype)
{
xfs_agnumber_t agno;
xfs_agblock_t agbno;
xfs_agino_t agino;
int offset;
int rval = 0;
if (ino == 0)
return 1;
agno = XFS_INO_TO_AGNO(mp, ino);
agino = XFS_INO_TO_AGINO(mp, ino);
agbno = XFS_AGINO_TO_AGBNO(mp, agino);
offset = XFS_AGINO_TO_OFFSET(mp, agino);
if (agno >= mp->m_sb.sb_agcount || agbno >= mp->m_sb.sb_agblocks ||
offset >= mp->m_sb.sb_inopblock) {
if (show_warnings)
print_warning("invalid %s inode number (%lld)",
typtab[itype].name, (long long)ino);
return 1;
}
push_cur();
set_cur(&typtab[TYP_INODE], XFS_AGB_TO_DADDR(mp, agno, agbno),
blkbb, DB_RING_IGN, NULL);
if (iocur_top->data == NULL) {
print_warning("cannot read %s inode %lld",
typtab[itype].name, (long long)ino);
rval = !stop_on_read_error;
goto pop_out;
}
off_cur(offset << mp->m_sb.sb_inodelog, mp->m_sb.sb_inodesize);
cur_ino = ino;
rval = process_inode_data(iocur_top->data, itype);
pop_out:
pop_cur();
return rval;
}
static int
copy_sb_inodes(void)
{
if (!copy_ino(mp->m_sb.sb_rbmino, TYP_RTBITMAP))
return 0;
if (!copy_ino(mp->m_sb.sb_rsumino, TYP_RTSUMMARY))
return 0;
if (!copy_ino(mp->m_sb.sb_uquotino, TYP_DQBLK))
return 0;
return copy_ino(mp->m_sb.sb_gquotino, TYP_DQBLK);
}
static int
copy_log(void)
{
if (show_progress)
print_progress("Copying log");
push_cur();
set_cur(&typtab[TYP_LOG], XFS_FSB_TO_DADDR(mp, mp->m_sb.sb_logstart),
mp->m_sb.sb_logblocks * blkbb, DB_RING_IGN, NULL);
if (iocur_top->data == NULL) {
pop_cur();
print_warning("cannot read log");
return !stop_on_read_error;
}
return write_buf(iocur_top);
}
static int
metadump_f(
int argc,
char **argv)
{
xfs_agnumber_t agno;
int c;
int start_iocur_sp;
char *p;
exitcode = 1;
show_progress = 0;
show_warnings = 0;
stop_on_read_error = 0;
if (mp->m_sb.sb_magicnum != XFS_SB_MAGIC) {
print_warning("bad superblock magic number %x, giving up",
mp->m_sb.sb_magicnum);
return 0;
}
while ((c = getopt(argc, argv, "egm:ow")) != EOF) {
switch (c) {
case 'e':
stop_on_read_error = 1;
break;
case 'g':
show_progress = 1;
break;
case 'm':
max_extent_size = (int)strtol(optarg, &p, 0);
if (*p != '\0' || max_extent_size <= 0) {
print_warning("bad max extent size %s",
optarg);
return 0;
}
break;
case 'o':
dont_obfuscate = 1;
break;
case 'w':
show_warnings = 1;
break;
default:
print_warning("bad option for metadump command");
return 0;
}
}
if (optind != argc - 1) {
print_warning("too few options for metadump (no filename given)");
return 0;
}
metablock = (xfs_metablock_t *)calloc(BBSIZE + 1, BBSIZE);
if (metablock == NULL) {
print_warning("memory allocation failure");
return 0;
}
metablock->mb_blocklog = BBSHIFT;
metablock->mb_magic = cpu_to_be32(XFS_MD_MAGIC);
if (!create_nametable()) {
print_warning("memory allocation failure");
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 = stdout;
} else {
outf = fopen(argv[optind], "wb");
if (outf == NULL) {
print_warning("cannot create dump file");
free(nametable);
free(metablock);
return 0;
}
}
exitcode = 0;
for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
if (!scan_ag(agno)) {
exitcode = 1;
break;
}
}
/* copy realtime and quota inode contents */
if (!exitcode)
exitcode = !copy_sb_inodes();
/* copy log if it's internal */
if ((mp->m_sb.sb_logstart != 0) && !exitcode)
exitcode = !copy_log();
/* write the remaining index */
if (!exitcode)
exitcode = !write_index();
if (progress_since_warning)
fputc('\n', (outf == stdout) ? stderr : stdout);
if (outf != stdout)
fclose(outf);
/* cleanup iocur stack */
while (iocur_sp > start_iocur_sp)
pop_cur();
free(nametable);
free(metablock);
return 0;
}