btrfs-progs/btrfs-corrupt-block.c
Josef Bacik 70749a77fe Btrfs-progs: deal with invalid key orderings and bad orphan items V2
A user had a fs where the objectid of an orphan item was not the actual orphan
item objectid.  This screwed up fsck because the block has keys in the wrong
order, also the fs scanning stuff will freak out because we have an inode with
nlink 0 and no orphan item.  So this patch is pretty big but is all related.

1) Deal with bad key ordering.  We can easily fix this up, so fix the checking
stuff to tell us exactly what it found when it said there was a problem.  Then
if it's bad key ordering we can reorder the keys and restart the scan.

2) Deal with bad keys.  If we find an orphan item with the wrong objectid it's
likely to screw with stuff, so keep track of these sort of things with a
bad_item list and just run through and delete any objects that don't make sense.
So far we just do this for orphan items but we could extend this as new stuff
pops up.

3) Deal with missing orphan items.  This is easy, if we have a file with i_nlink
set to 0 and no orphan item we can just add an orphan item.

4) Add the infrastructure to corrupt actual key values.  Needed this to create a
test image to verify I was fixing things properly.

This patch fixes the corrupt image I'm adding and passes the other make test
tests.  Thanks,

Signed-off-by: Josef Bacik <jbacik@fb.com>
Signed-off-by: David Sterba <dsterba@suse.cz>
Signed-off-by: Chris Mason <clm@fb.com>
2014-01-31 08:22:15 -08:00

1024 lines
24 KiB
C

/*
* Copyright (C) 2009 Oracle. 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 v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will 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 to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#define _XOPEN_SOURCE 500
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include "kerncompat.h"
#include "ctree.h"
#include "volumes.h"
#include "disk-io.h"
#include "print-tree.h"
#include "transaction.h"
#include "list.h"
#include "version.h"
#include "utils.h"
#define FIELD_BUF_LEN 80
struct extent_buffer *debug_corrupt_block(struct btrfs_root *root, u64 bytenr,
u32 blocksize, int copy)
{
int ret;
struct extent_buffer *eb;
u64 length;
struct btrfs_multi_bio *multi = NULL;
struct btrfs_device *device;
int num_copies;
int mirror_num = 1;
eb = btrfs_find_create_tree_block(root, bytenr, blocksize);
if (!eb)
return NULL;
length = blocksize;
while (1) {
ret = btrfs_map_block(&root->fs_info->mapping_tree, READ,
eb->start, &length, &multi,
mirror_num, NULL);
BUG_ON(ret);
device = multi->stripes[0].dev;
eb->fd = device->fd;
device->total_ios++;
eb->dev_bytenr = multi->stripes[0].physical;
fprintf(stdout,
"mirror %d logical %llu physical %llu device %s\n",
mirror_num, (unsigned long long)bytenr,
(unsigned long long)eb->dev_bytenr, device->name);
kfree(multi);
if (!copy || mirror_num == copy) {
ret = read_extent_from_disk(eb, 0, eb->len);
printf("corrupting %llu copy %d\n", eb->start,
mirror_num);
memset(eb->data, 0, eb->len);
write_extent_to_disk(eb);
fsync(eb->fd);
}
num_copies = btrfs_num_copies(&root->fs_info->mapping_tree,
eb->start, eb->len);
if (num_copies == 1)
break;
mirror_num++;
if (mirror_num > num_copies)
break;
}
return eb;
}
static void print_usage(void)
{
fprintf(stderr, "usage: btrfs-corrupt-block [options] device\n");
fprintf(stderr, "\t-l Logical extent to be corrupted\n");
fprintf(stderr, "\t-c Copy of the extent to be corrupted"
" (usually 1 or 2, default: 0)\n");
fprintf(stderr, "\t-b Number of bytes to be corrupted\n");
fprintf(stderr, "\t-e Extent to be corrupted\n");
fprintf(stderr, "\t-E The whole extent tree to be corrupted\n");
fprintf(stderr, "\t-u Given chunk item to be corrupted\n");
fprintf(stderr, "\t-U The whole chunk tree to be corrupted\n");
fprintf(stderr, "\t-i The inode item to corrupt (must also specify "
"the field to corrupt)\n");
fprintf(stderr, "\t-x The file extent item to corrupt (must also "
"specify -i for the inode and -f for the field to corrupt)\n");
fprintf(stderr, "\t-m The metadata block to corrupt (must also "
"specify -f for the field to corrupt)\n");
fprintf(stderr, "\t-K The key to corrupt in the format "
"<num>,<num>,<num> (must also specify -f for the field)\n");
fprintf(stderr, "\t-f The field in the item to corrupt\n");
exit(1);
}
static void corrupt_keys(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct extent_buffer *eb)
{
int slot;
int bad_slot;
int nr;
struct btrfs_disk_key bad_key;;
nr = btrfs_header_nritems(eb);
if (nr == 0)
return;
slot = rand() % nr;
bad_slot = rand() % nr;
if (bad_slot == slot)
return;
fprintf(stderr,
"corrupting keys in block %llu slot %d swapping with %d\n",
(unsigned long long)eb->start, slot, bad_slot);
if (btrfs_header_level(eb) == 0) {
btrfs_item_key(eb, &bad_key, bad_slot);
btrfs_set_item_key(eb, &bad_key, slot);
} else {
btrfs_node_key(eb, &bad_key, bad_slot);
btrfs_set_node_key(eb, &bad_key, slot);
}
btrfs_mark_buffer_dirty(eb);
if (!trans) {
u16 csum_size =
btrfs_super_csum_size(root->fs_info->super_copy);
csum_tree_block_size(eb, csum_size, 0);
write_extent_to_disk(eb);
}
}
static int corrupt_keys_in_block(struct btrfs_root *root, u64 bytenr)
{
struct extent_buffer *eb;
eb = read_tree_block(root, bytenr, root->leafsize, 0);
if (!eb)
return -EIO;;
corrupt_keys(NULL, root, eb);
free_extent_buffer(eb);
return 0;
}
static int corrupt_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 bytenr, int copy)
{
struct btrfs_key key;
struct extent_buffer *leaf;
u32 item_size;
unsigned long ptr;
struct btrfs_path *path;
int ret;
int slot;
int should_del = rand() % 3;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
key.objectid = bytenr;
key.type = (u8)-1;
key.offset = (u64)-1;
while(1) {
ret = btrfs_search_slot(trans, root->fs_info->extent_root,
&key, path, -1, 1);
if (ret < 0)
break;
if (ret > 0) {
if (path->slots[0] == 0)
break;
path->slots[0]--;
ret = 0;
}
leaf = path->nodes[0];
slot = path->slots[0];
btrfs_item_key_to_cpu(leaf, &key, slot);
if (key.objectid != bytenr)
break;
if (key.type != BTRFS_EXTENT_ITEM_KEY &&
key.type != BTRFS_TREE_BLOCK_REF_KEY &&
key.type != BTRFS_EXTENT_DATA_REF_KEY &&
key.type != BTRFS_EXTENT_REF_V0_KEY &&
key.type != BTRFS_SHARED_BLOCK_REF_KEY &&
key.type != BTRFS_SHARED_DATA_REF_KEY)
goto next;
if (should_del) {
fprintf(stderr,
"deleting extent record: key %llu %u %llu\n",
key.objectid, key.type, key.offset);
if (key.type == BTRFS_EXTENT_ITEM_KEY) {
/* make sure this extent doesn't get
* reused for other purposes */
btrfs_pin_extent(root->fs_info,
key.objectid, key.offset);
}
btrfs_del_item(trans, root, path);
} else {
fprintf(stderr,
"corrupting extent record: key %llu %u %llu\n",
key.objectid, key.type, key.offset);
ptr = btrfs_item_ptr_offset(leaf, slot);
item_size = btrfs_item_size_nr(leaf, slot);
memset_extent_buffer(leaf, 0, ptr, item_size);
btrfs_mark_buffer_dirty(leaf);
}
next:
btrfs_release_path(path);
if (key.offset > 0)
key.offset--;
if (key.offset == 0)
break;
}
btrfs_free_path(path);
return 0;
}
static void btrfs_corrupt_extent_leaf(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct extent_buffer *eb)
{
u32 nr = btrfs_header_nritems(eb);
u32 victim = rand() % nr;
u64 objectid;
struct btrfs_key key;
btrfs_item_key_to_cpu(eb, &key, victim);
objectid = key.objectid;
corrupt_extent(trans, root, objectid, 1);
}
static void btrfs_corrupt_extent_tree(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct extent_buffer *eb)
{
int i;
u32 nr;
if (!eb)
return;
nr = btrfs_header_nritems(eb);
if (btrfs_is_leaf(eb)) {
btrfs_corrupt_extent_leaf(trans, root, eb);
return;
}
if (btrfs_header_level(eb) == 1 && eb != root->node) {
if (rand() % 5)
return;
}
for (i = 0; i < nr; i++) {
struct extent_buffer *next;
next = read_tree_block(root, btrfs_node_blockptr(eb, i),
root->leafsize,
btrfs_node_ptr_generation(eb, i));
if (!next)
continue;
btrfs_corrupt_extent_tree(trans, root, next);
free_extent_buffer(next);
}
}
enum btrfs_inode_field {
BTRFS_INODE_FIELD_ISIZE,
BTRFS_INODE_FIELD_BAD,
};
enum btrfs_file_extent_field {
BTRFS_FILE_EXTENT_DISK_BYTENR,
BTRFS_FILE_EXTENT_BAD,
};
enum btrfs_metadata_block_field {
BTRFS_METADATA_BLOCK_GENERATION,
BTRFS_METADATA_BLOCK_BAD,
};
enum btrfs_key_field {
BTRFS_KEY_OBJECTID,
BTRFS_KEY_TYPE,
BTRFS_KEY_OFFSET,
BTRFS_KEY_BAD,
};
static enum btrfs_inode_field convert_inode_field(char *field)
{
if (!strncmp(field, "isize", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_ISIZE;
return BTRFS_INODE_FIELD_BAD;
}
static enum btrfs_file_extent_field convert_file_extent_field(char *field)
{
if (!strncmp(field, "disk_bytenr", FIELD_BUF_LEN))
return BTRFS_FILE_EXTENT_DISK_BYTENR;
return BTRFS_FILE_EXTENT_BAD;
}
static enum btrfs_metadata_block_field
convert_metadata_block_field(char *field)
{
if (!strncmp(field, "generation", FIELD_BUF_LEN))
return BTRFS_METADATA_BLOCK_GENERATION;
return BTRFS_METADATA_BLOCK_BAD;
}
static enum btrfs_key_field convert_key_field(char *field)
{
if (!strncmp(field, "objectid", FIELD_BUF_LEN))
return BTRFS_KEY_OBJECTID;
if (!strncmp(field, "type", FIELD_BUF_LEN))
return BTRFS_KEY_TYPE;
if (!strncmp(field, "offset", FIELD_BUF_LEN))
return BTRFS_KEY_OFFSET;
return BTRFS_KEY_BAD;
}
static u64 generate_u64(u64 orig)
{
u64 ret;
do {
ret = rand();
} while (ret == orig);
return ret;
}
static u8 generate_u8(u8 orig)
{
u8 ret;
do {
ret = rand();
} while (ret == orig);
return ret;
}
static int corrupt_key(struct btrfs_root *root, struct btrfs_key *key,
char *field)
{
enum btrfs_key_field corrupt_field = convert_key_field(field);
struct btrfs_path *path;
struct btrfs_trans_handle *trans;
int ret;
root = root->fs_info->fs_root;
if (corrupt_field == BTRFS_KEY_BAD) {
fprintf(stderr, "Invalid field %s\n", field);
return -EINVAL;
}
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
btrfs_free_path(path);
return PTR_ERR(trans);
}
ret = btrfs_search_slot(trans, root, key, path, 0, 1);
if (ret < 0)
goto out;
if (ret > 0) {
fprintf(stderr, "Couldn't find the key to corrupt\n");
ret = -ENOENT;
goto out;
}
switch (corrupt_field) {
case BTRFS_KEY_OBJECTID:
key->objectid = generate_u64(key->objectid);
break;
case BTRFS_KEY_TYPE:
key->type = generate_u8(key->type);
break;
case BTRFS_KEY_OFFSET:
key->offset = generate_u64(key->objectid);
break;
default:
fprintf(stderr, "Invalid field %s, %d\n", field,
corrupt_field);
ret = -EINVAL;
goto out;
}
btrfs_set_item_key_unsafe(root, path, key);
out:
btrfs_free_path(path);
btrfs_commit_transaction(trans, root);
return ret;
}
static int corrupt_inode(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 inode, char *field)
{
struct btrfs_inode_item *ei;
struct btrfs_path *path;
struct btrfs_key key;
enum btrfs_inode_field corrupt_field = convert_inode_field(field);
u64 bogus;
u64 orig;
int ret;
if (corrupt_field == BTRFS_INODE_FIELD_BAD) {
fprintf(stderr, "Invalid field %s\n", field);
return -EINVAL;
}
key.objectid = inode;
key.type = BTRFS_INODE_ITEM_KEY;
key.offset = (u64)-1;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
if (ret < 0)
goto out;
if (ret) {
if (!path->slots[0]) {
fprintf(stderr, "Couldn't find inode %Lu\n", inode);
ret = -ENOENT;
goto out;
}
path->slots[0]--;
ret = 0;
}
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.objectid != inode) {
fprintf(stderr, "Couldn't find inode %Lu\n", inode);
ret = -ENOENT;
goto out;
}
ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_inode_item);
switch (corrupt_field) {
case BTRFS_INODE_FIELD_ISIZE:
orig = btrfs_inode_size(path->nodes[0], ei);
bogus = generate_u64(orig);
btrfs_set_inode_size(path->nodes[0], ei, bogus);
break;
default:
ret = -EINVAL;
break;
}
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_free_path(path);
return ret;
}
static int corrupt_file_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 inode, u64 extent,
char *field)
{
struct btrfs_file_extent_item *fi;
struct btrfs_path *path;
struct btrfs_key key;
enum btrfs_file_extent_field corrupt_field;
u64 bogus;
u64 orig;
int ret = 0;
corrupt_field = convert_file_extent_field(field);
if (corrupt_field == BTRFS_FILE_EXTENT_BAD) {
fprintf(stderr, "Invalid field %s\n", field);
return -EINVAL;
}
key.objectid = inode;
key.type = BTRFS_EXTENT_DATA_KEY;
key.offset = extent;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
if (ret < 0)
goto out;
if (ret) {
fprintf(stderr, "Couldn't find extent %llu for inode %llu\n",
extent, inode);
ret = -ENOENT;
goto out;
}
fi = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_file_extent_item);
switch (corrupt_field) {
case BTRFS_FILE_EXTENT_DISK_BYTENR:
orig = btrfs_file_extent_disk_bytenr(path->nodes[0], fi);
bogus = generate_u64(orig);
btrfs_set_file_extent_disk_bytenr(path->nodes[0], fi, bogus);
break;
default:
ret = -EINVAL;
break;
}
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_free_path(path);
return ret;
}
static int corrupt_metadata_block(struct btrfs_root *root, u64 block,
char *field)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
struct extent_buffer *eb;
struct btrfs_key key, root_key;
enum btrfs_metadata_block_field corrupt_field;
u64 root_objectid;
u64 orig, bogus;
u8 level;
int ret;
corrupt_field = convert_metadata_block_field(field);
if (corrupt_field == BTRFS_METADATA_BLOCK_BAD) {
fprintf(stderr, "Invalid field %s\n", field);
return -EINVAL;
}
eb = read_tree_block(root, block, root->leafsize, 0);
if (!eb) {
fprintf(stderr, "Couldn't read in tree block %s\n", field);
return -EINVAL;
}
root_objectid = btrfs_header_owner(eb);
level = btrfs_header_level(eb);
if (level)
btrfs_node_key_to_cpu(eb, &key, 0);
else
btrfs_item_key_to_cpu(eb, &key, 0);
free_extent_buffer(eb);
root_key.objectid = root_objectid;
root_key.type = BTRFS_ROOT_ITEM_KEY;
root_key.offset = (u64)-1;
root = btrfs_read_fs_root(root->fs_info, &root_key);
if (IS_ERR(root)) {
fprintf(stderr, "Couldn't finde owner root %llu\n",
key.objectid);
return PTR_ERR(root);
}
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
btrfs_free_path(path);
fprintf(stderr, "Couldn't start transaction %ld\n",
PTR_ERR(trans));
return PTR_ERR(trans);
}
path->lowest_level = level;
ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
if (ret < 0) {
fprintf(stderr, "Error searching to node %d\n", ret);
goto out;
}
eb = path->nodes[level];
ret = 0;
switch (corrupt_field) {
case BTRFS_METADATA_BLOCK_GENERATION:
orig = btrfs_header_generation(eb);
bogus = generate_u64(orig);
btrfs_set_header_generation(eb, bogus);
break;
default:
ret = -EINVAL;
break;
}
btrfs_mark_buffer_dirty(path->nodes[level]);
out:
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
}
static struct option long_options[] = {
/* { "byte-count", 1, NULL, 'b' }, */
{ "logical", 1, NULL, 'l' },
{ "copy", 1, NULL, 'c' },
{ "bytes", 1, NULL, 'b' },
{ "extent-record", 0, NULL, 'e' },
{ "extent-tree", 0, NULL, 'E' },
{ "keys", 0, NULL, 'k' },
{ "chunk-record", 0, NULL, 'u' },
{ "chunk-tree", 0, NULL, 'U' },
{ "inode", 1, NULL, 'i'},
{ "file-extent", 1, NULL, 'x'},
{ "metadata-block", 1, NULL, 'm'},
{ "field", 1, NULL, 'f'},
{ "key", 1, NULL, 'K'},
{ 0, 0, 0, 0}
};
/* corrupt item using NO cow.
* Because chunk recover will recover based on whole partition scaning,
* If using COW, chunk recover will use the old item to recover,
* which is still OK but we want to check the ability to rebuild chunk
* not only restore the old ones */
int corrupt_item_nocow(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_path *path,
int del)
{
int ret = 0;
struct btrfs_key key;
struct extent_buffer *leaf;
unsigned long ptr;
int slot;
u32 item_size;
leaf = path->nodes[0];
slot = path->slots[0];
/* Not deleting the first item of a leaf to keep leaf structure */
if (slot == 0)
del = 0;
/* Only accept valid eb */
BUG_ON(!leaf->data || slot >= btrfs_header_nritems(leaf));
btrfs_item_key_to_cpu(leaf, &key, slot);
if (del) {
fprintf(stdout, "Deleting key and data [%llu, %u, %llu].\n",
key.objectid, key.type, key.offset);
btrfs_del_item(trans, root, path);
} else {
fprintf(stdout, "Corrupting key and data [%llu, %u, %llu].\n",
key.objectid, key.type, key.offset);
ptr = btrfs_item_ptr_offset(leaf, slot);
item_size = btrfs_item_size_nr(leaf, slot);
memset_extent_buffer(leaf, 0, ptr, item_size);
btrfs_mark_buffer_dirty(leaf);
}
return ret;
}
int corrupt_chunk_tree(struct btrfs_trans_handle *trans,
struct btrfs_root *root)
{
int ret;
int del;
int slot;
struct btrfs_path *path;
struct btrfs_key key;
struct btrfs_key found_key;
struct extent_buffer *leaf;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
key.objectid = (u64)-1;
key.offset = (u64)-1;
key.type = (u8)-1;
/* Here, cow and ins_len must equals 0 for the following reasons:
* 1) chunk recover is based on disk scanning, so COW should be
* disabled in case the original chunk being scanned and
* recovered using the old chunk.
* 2) if cow = 0, ins_len must also be set to 0, or BUG_ON will be
* triggered.
*/
ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
BUG_ON(ret == 0);
if (ret < 0) {
fprintf(stderr, "Error searching tree\n");
goto free_out;
}
/* corrupt/del dev_item first */
while (!btrfs_previous_item(root, path, 0, BTRFS_DEV_ITEM_KEY)) {
slot = path->slots[0];
leaf = path->nodes[0];
del = rand() % 3;
/* Never delete the first item to keep the leaf structure */
if (path->slots[0] == 0)
del = 0;
ret = corrupt_item_nocow(trans, root, path, del);
if (ret)
goto free_out;
}
btrfs_release_path(path);
/* Here, cow and ins_len must equals 0 for the following reasons:
* 1) chunk recover is based on disk scanning, so COW should be
* disabled in case the original chunk being scanned and
* recovered using the old chunk.
* 2) if cow = 0, ins_len must also be set to 0, or BUG_ON will be
* triggered.
*/
ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
BUG_ON(ret == 0);
if (ret < 0) {
fprintf(stderr, "Error searching tree\n");
goto free_out;
}
/* corrupt/del chunk then*/
while (!btrfs_previous_item(root, path, 0, BTRFS_CHUNK_ITEM_KEY)) {
slot = path->slots[0];
leaf = path->nodes[0];
del = rand() % 3;
btrfs_item_key_to_cpu(leaf, &found_key, slot);
ret = corrupt_item_nocow(trans, root, path, del);
if (ret)
goto free_out;
}
free_out:
btrfs_free_path(path);
return ret;
}
int find_chunk_offset(struct btrfs_root *root,
struct btrfs_path *path, u64 offset)
{
struct btrfs_key key;
int ret;
key.objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
key.type = BTRFS_CHUNK_ITEM_KEY;
key.offset = offset;
/* Here, cow and ins_len must equals 0 for following reasons:
* 1) chunk recover is based on disk scanning, so COW should
* be disabled in case the original chunk being scanned
* and recovered using the old chunk.
* 2) if cow = 0, ins_len must also be set to 0, or BUG_ON
* will be triggered.
*/
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
if (ret > 0) {
fprintf(stderr, "Can't find chunk with given offset %llu\n",
offset);
goto out;
}
if (ret < 0) {
fprintf(stderr, "Error searching chunk");
goto out;
}
out:
return ret;
}
int main(int ac, char **av)
{
struct cache_tree root_cache;
struct btrfs_key key;
struct btrfs_root *root;
struct extent_buffer *eb;
char *dev;
/* chunk offset can be 0,so change to (u64)-1 */
u64 logical = (u64)-1;
int ret = 0;
int option_index = 0;
int copy = 0;
u64 bytes = 4096;
int extent_rec = 0;
int extent_tree = 0;
int corrupt_block_keys = 0;
int chunk_rec = 0;
int chunk_tree = 0;
u64 metadata_block = 0;
u64 inode = 0;
u64 file_extent = (u64)-1;
char field[FIELD_BUF_LEN];
field[0] = '\0';
srand(128);
memset(&key, 0, sizeof(key));
while(1) {
int c;
c = getopt_long(ac, av, "l:c:b:eEkuUi:f:x:m:K:", long_options,
&option_index);
if (c < 0)
break;
switch(c) {
case 'l':
logical = atoll(optarg);
break;
case 'c':
copy = atoi(optarg);
if (copy <= 0) {
fprintf(stderr,
"invalid copy number\n");
print_usage();
}
break;
case 'b':
bytes = atoll(optarg);
if (bytes == 0) {
fprintf(stderr,
"invalid byte count\n");
print_usage();
}
break;
case 'e':
extent_rec = 1;
break;
case 'E':
extent_tree = 1;
break;
case 'k':
corrupt_block_keys = 1;
break;
case 'u':
chunk_rec = 1;
break;
case 'U':
chunk_tree = 1;
case 'i':
inode = atoll(optarg);
if (inode == 0) {
fprintf(stderr,
"invalid inode number\n");
print_usage();
}
break;
case 'f':
strncpy(field, optarg, FIELD_BUF_LEN);
break;
case 'x':
errno = 0;
file_extent = atoll(optarg);
if (errno) {
fprintf(stderr, "error converting "
"%d\n", errno);
print_usage();
}
break;
case 'm':
errno = 0;
metadata_block = atoll(optarg);
if (errno) {
fprintf(stderr, "error converting "
"%d\n", errno);
print_usage();
}
break;
case 'K':
ret = sscanf(optarg, "%llu,%u,%llu",
&key.objectid,
(unsigned int *)&key.type,
&key.offset);
if (ret != 3) {
fprintf(stderr, "error reading key "
"%d\n", errno);
print_usage();
}
break;
default:
print_usage();
}
}
ac = ac - optind;
if (ac == 0)
print_usage();
dev = av[optind];
radix_tree_init();
cache_tree_init(&root_cache);
root = open_ctree(dev, 0, OPEN_CTREE_WRITES);
if (!root) {
fprintf(stderr, "Open ctree failed\n");
exit(1);
}
if (extent_rec) {
struct btrfs_trans_handle *trans;
if (logical == (u64)-1)
print_usage();
trans = btrfs_start_transaction(root, 1);
ret = corrupt_extent (trans, root, logical, 0);
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (extent_tree) {
struct btrfs_trans_handle *trans;
trans = btrfs_start_transaction(root, 1);
btrfs_corrupt_extent_tree(trans, root->fs_info->extent_root,
root->fs_info->extent_root->node);
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (chunk_rec) {
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
int del;
if (logical == (u64)-1)
print_usage();
del = rand() % 3;
path = btrfs_alloc_path();
if (!path) {
fprintf(stderr, "path allocation failed\n");
goto out_close;
}
if (find_chunk_offset(root->fs_info->chunk_root, path,
logical) != 0) {
btrfs_free_path(path);
goto out_close;
}
trans = btrfs_start_transaction(root, 1);
ret = corrupt_item_nocow(trans, root->fs_info->chunk_root,
path, del);
if (ret < 0)
fprintf(stderr, "Failed to corrupt chunk record\n");
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (chunk_tree) {
struct btrfs_trans_handle *trans;
trans = btrfs_start_transaction(root, 1);
ret = corrupt_chunk_tree(trans, root->fs_info->chunk_root);
if (ret < 0)
fprintf(stderr, "Failed to corrupt chunk tree\n");
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (inode) {
struct btrfs_trans_handle *trans;
if (!strlen(field))
print_usage();
trans = btrfs_start_transaction(root, 1);
if (file_extent == (u64)-1) {
printf("corrupting inode\n");
ret = corrupt_inode(trans, root, inode, field);
} else {
printf("corrupting file extent\n");
ret = corrupt_file_extent(trans, root, inode,
file_extent, field);
}
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (metadata_block) {
if (!strlen(field))
print_usage();
ret = corrupt_metadata_block(root, metadata_block, field);
goto out_close;
}
if (key.objectid || key.offset || key.type) {
if (!strlen(field))
print_usage();
ret = corrupt_key(root, &key, field);
goto out_close;
}
/*
* If we made it here and we have extent set then we didn't specify
* inode and we're screwed.
*/
if (file_extent != (u64)-1)
print_usage();
if (logical == (u64)-1)
print_usage();
if (bytes == 0)
bytes = root->sectorsize;
bytes = (bytes + root->sectorsize - 1) / root->sectorsize;
bytes *= root->sectorsize;
while (bytes > 0) {
if (corrupt_block_keys) {
corrupt_keys_in_block(root, logical);
} else {
eb = debug_corrupt_block(root, logical,
root->sectorsize, copy);
free_extent_buffer(eb);
}
logical += root->sectorsize;
bytes -= root->sectorsize;
}
return ret;
out_close:
close_ctree(root);
return ret;
}