btrfs-progs/btrfs-corrupt-block.c

1629 lines
41 KiB
C
Raw Normal View History

/*
* 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.
*/
#include "kerncompat.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include "kernel-shared/ctree.h"
#include "kernel-shared/volumes.h"
#include "kernel-shared/disk-io.h"
#include "kernel-shared/transaction.h"
#include "kernel-shared/extent_io.h"
#include "common/utils.h"
#include "common/help.h"
#include "common/extent-cache.h"
#include "common/messages.h"
#include "common/string-utils.h"
#define FIELD_BUF_LEN 80
static int debug_corrupt_block(struct extent_buffer *eb,
struct btrfs_root *root, u64 bytenr, u32 blocksize, u64 copy)
{
int ret;
int num_copies;
int mirror_num = 1;
while (1) {
if (!copy || mirror_num == copy) {
u64 read_len = eb->len;
ret = read_data_from_disk(eb->fs_info, eb->data,
eb->start, &read_len,
mirror_num);
if (read_len < eb->len)
ret = -EIO;
if (ret < 0) {
errno = -ret;
error("cannot read eb bytenr %llu: %m", eb->start);
return ret;
}
printf("corrupting %llu copy %d\n", eb->start,
mirror_num);
memset(eb->data, 0, eb->len);
ret = write_and_map_eb(eb->fs_info, eb);
if (ret < 0) {
errno = -ret;
error("cannot write eb bytenr %llu: %m", eb->start);
return ret;
}
}
num_copies = btrfs_num_copies(root->fs_info, eb->start,
eb->len);
if (num_copies == 1)
break;
mirror_num++;
if (mirror_num > num_copies)
break;
}
return 0;
}
static void print_usage(int ret)
{
printf("usage: btrfs-corrupt-block [options] device\n");
printf("\n");
printf("Corrupt data structures on a btrfs filesystem. For testing only!\n");
printf("\n");
printf(" -l EXTENT\n");
printf(" --logical EXTENT logical extent to be corrupted\n");
printf(" -c COPY\n");
printf(" --copy COPY copy of the extent to be corrupted (usually 1 or 2, default: 0)\n");
printf(" -b COUNT\n");
printf(" --bytes COUNT number of bytes to be corrupted\n");
printf(" -e\n");
printf(" --extent-record corrupt the extent\n");
printf(" -E\n");
printf(" --extent-tree corrupt the whole extent tree\n");
printf(" -u\n");
printf(" --chunk-record corrupt the given chunk\n");
printf(" -U\n");
printf(" --chunk-tree corrupt the whole whole chunk tree\n");
printf(" -i INODE\n");
printf(" --inode INODE inode number to corrupt (must also specify the field to corrupt)\n");
printf(" -x EXTENT\n");
printf(" --file-extent EXTENT file extent item to corrupt (must also specify -i for the inode and -f for the field to corrupt)\n");
printf(" -m BLOCK\n");
printf(" --metadata-block BLOCK\n");
printf(" metadata block to corrupt (must also specify -f for the field to corrupt)\n");
printf(" -k\n");
printf(" --keys corrupt block keys (set by --logical)\n");
printf(" -K <u64,u8,u64>\n");
printf(" --key <u64,u8,u64> corrupt the given key (must also specify -f for the field and optionally -r for the root)\n");
printf(" -f FIELD\n");
printf(" --field FIELD field name in the item to corrupt\n");
printf(" -I\n");
printf(" --item corrupt an item corresponding to the passed key triplet (must also specify the field, or a (bytes, offset, value) tuple to corrupt and root for the item)\n");
printf(" -D\n");
printf(" --dir-item corrupt a dir item corresponding to the passed key triplet, must also specify a field\n");
printf(" -d\n");
printf(" --delete delete item corresponding to passed key triplet\n");
printf(" -r\n");
printf(" --root operate on this root\n");
printf(" -C BYTENR\n");
printf(" --csum BYTENR delete a csum for the specified bytenr. When used with -b it'll delete that many bytes, otherwise it's just sectorsize\n");
printf(" --block-group OFFSET corrupt the given block group\n");
printf(" --value VALUE value to use for corrupting item data\n");
printf(" --offset OFFSET offset to use for corrupting item data\n");
exit(ret);
}
static void corrupt_keys(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info,
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_range(nr);
bad_slot = rand_range(nr);
if (bad_slot == slot)
return;
fprintf(stderr,
"corrupting keys in block %llu slot %d swapping with %d\n",
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 = fs_info->csum_size;
u16 csum_type = fs_info->csum_type;
csum_tree_block_size(eb, csum_size, 0, csum_type);
write_and_map_eb(eb->fs_info, eb);
}
}
static int corrupt_keys_in_block(struct btrfs_fs_info *fs_info, u64 bytenr)
{
struct extent_buffer *eb;
eb = read_tree_block(fs_info, bytenr, 0);
if (!extent_buffer_uptodate(eb))
return -EIO;;
corrupt_keys(NULL, fs_info, eb);
free_extent_buffer(eb);
return 0;
}
static int corrupt_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 bytenr)
{
struct btrfs_root *extent_root;
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_range(3);
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
key.objectid = bytenr;
key.type = (u8)-1;
key.offset = (u64)-1;
extent_root = btrfs_extent_root(trans->fs_info, bytenr);
while(1) {
ret = btrfs_search_slot(trans, 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_METADATA_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(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_range(nr);
u64 objectid;
struct btrfs_key key;
btrfs_item_key_to_cpu(eb, &key, victim);
objectid = key.objectid;
corrupt_extent(trans, root, objectid);
}
static void btrfs_corrupt_extent_tree(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct extent_buffer *eb)
{
struct btrfs_fs_info *fs_info = root->fs_info;
int i;
if (!eb)
return;
if (btrfs_is_leaf(eb)) {
btrfs_corrupt_extent_leaf(trans, root, eb);
return;
}
if (btrfs_header_level(eb) == 1 && eb != root->node) {
if (rand_range(5))
return;
}
for (i = 0; i < btrfs_header_nritems(eb); i++) {
struct extent_buffer *next;
next = read_tree_block(fs_info, btrfs_node_blockptr(eb, i),
btrfs_node_ptr_generation(eb, i));
if (!extent_buffer_uptodate(next))
continue;
btrfs_corrupt_extent_tree(trans, root, next);
free_extent_buffer(next);
}
}
enum btrfs_inode_field {
BTRFS_INODE_FIELD_ISIZE,
BTRFS_INODE_FIELD_NBYTES,
BTRFS_INODE_FIELD_NLINK,
BTRFS_INODE_FIELD_GENERATION,
BTRFS_INODE_FIELD_TRANSID,
BTRFS_INODE_FIELD_BLOCK_GROUP,
BTRFS_INODE_FIELD_MODE,
BTRFS_INODE_FIELD_UID,
BTRFS_INODE_FIELD_GID,
BTRFS_INODE_FIELD_BAD,
};
enum btrfs_file_extent_field {
BTRFS_FILE_EXTENT_DISK_BYTENR,
BTRFS_FILE_EXTENT_TYPE,
BTRFS_FILE_EXTENT_BAD,
};
enum btrfs_dir_item_field {
BTRFS_DIR_ITEM_NAME,
BTRFS_DIR_ITEM_LOCATION_OBJECTID,
BTRFS_DIR_ITEM_BAD,
};
enum btrfs_metadata_block_field {
BTRFS_METADATA_BLOCK_GENERATION,
BTRFS_METADATA_BLOCK_SHIFT_ITEMS,
BTRFS_METADATA_BLOCK_BAD,
};
enum btrfs_item_field {
BTRFS_ITEM_OFFSET,
BTRFS_ITEM_BAD,
};
enum btrfs_key_field {
BTRFS_KEY_OBJECTID,
BTRFS_KEY_TYPE,
BTRFS_KEY_OFFSET,
BTRFS_KEY_BAD,
};
enum btrfs_block_group_field {
BTRFS_BLOCK_GROUP_ITEM_USED,
BTRFS_BLOCK_GROUP_ITEM_FLAGS,
BTRFS_BLOCK_GROUP_ITEM_CHUNK_OBJECTID,
BTRFS_BLOCK_GROUP_ITEM_BAD,
};
static enum btrfs_block_group_field convert_block_group_field(char *field)
{
if (!strncmp(field, "used", FIELD_BUF_LEN))
return BTRFS_BLOCK_GROUP_ITEM_USED;
if (!strncmp(field, "flags", FIELD_BUF_LEN))
return BTRFS_BLOCK_GROUP_ITEM_FLAGS;
if (!strncmp(field, "chunk_objectid", FIELD_BUF_LEN))
return BTRFS_BLOCK_GROUP_ITEM_CHUNK_OBJECTID;
return BTRFS_BLOCK_GROUP_ITEM_BAD;
}
static enum btrfs_inode_field convert_inode_field(char *field)
{
if (!strncmp(field, "isize", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_ISIZE;
if (!strncmp(field, "nbytes", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_NBYTES;
if (!strncmp(field, "nlink", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_NLINK;
if (!strncmp(field, "generation", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_GENERATION;
if (!strncmp(field, "transid", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_TRANSID;
if (!strncmp(field, "block_group", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_BLOCK_GROUP;
if (!strncmp(field, "mode", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_MODE;
if (!strncmp(field, "uid", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_UID;
if (!strncmp(field, "gid", FIELD_BUF_LEN))
return BTRFS_INODE_FIELD_GID;
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;
if (!strncmp(field, "type", FIELD_BUF_LEN))
return BTRFS_FILE_EXTENT_TYPE;
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;
if (!strncmp(field, "shift_items", FIELD_BUF_LEN))
return BTRFS_METADATA_BLOCK_SHIFT_ITEMS;
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 enum btrfs_item_field convert_item_field(char *field)
{
if (!strncmp(field, "offset", FIELD_BUF_LEN))
return BTRFS_ITEM_OFFSET;
return BTRFS_ITEM_BAD;
}
static enum btrfs_dir_item_field convert_dir_item_field(char *field)
{
if (!strncmp(field, "name", FIELD_BUF_LEN))
return BTRFS_DIR_ITEM_NAME;
if (!strncmp(field, "location_objectid", FIELD_BUF_LEN))
return BTRFS_DIR_ITEM_LOCATION_OBJECTID;
return BTRFS_DIR_ITEM_BAD;
}
static u64 generate_u64(u64 orig)
{
u64 ret;
do {
ret = rand_u64();
} while (ret == orig);
return ret;
}
static u32 generate_u32(u32 orig)
{
u32 ret;
do {
ret = rand_u32();
} while (ret == orig);
return ret;
}
static u8 generate_u8(u8 orig)
{
u8 ret;
do {
ret = rand_u8();
} while (ret == orig);
return ret;
}
static int corrupt_block_group(struct btrfs_root *root, u64 bg, char *field)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
struct btrfs_block_group_item *bgi;
struct btrfs_key key;
enum btrfs_block_group_field corrupt_field;
u64 orig, bogus;
int ret = 0;
root = btrfs_extent_root(root->fs_info, 0);
corrupt_field = convert_block_group_field(field);
if (corrupt_field == BTRFS_BLOCK_GROUP_ITEM_BAD) {
error("invalid field %s", 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);
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
key.objectid = bg;
key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
key.offset = 0;
ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
if (ret < 0) {
error("error searching for bg %llu %d", bg, ret);
goto out;
}
ret = 0;
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
if (key.type != BTRFS_BLOCK_GROUP_ITEM_KEY) {
error("couldn't find the bg %llu", bg);
goto out;
}
bgi = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_block_group_item);
switch (corrupt_field) {
case BTRFS_BLOCK_GROUP_ITEM_USED:
orig = btrfs_block_group_used(path->nodes[0], bgi);
bogus = generate_u64(orig);
btrfs_set_block_group_used(path->nodes[0], bgi, bogus);
break;
case BTRFS_BLOCK_GROUP_ITEM_CHUNK_OBJECTID:
orig = btrfs_block_group_chunk_objectid(path->nodes[0], bgi);
bogus = generate_u64(orig);
btrfs_set_block_group_chunk_objectid(path->nodes[0], bgi,
bogus);
break;
case BTRFS_BLOCK_GROUP_ITEM_FLAGS:
orig = btrfs_block_group_flags(path->nodes[0], bgi);
bogus = generate_u64(orig);
btrfs_set_block_group_flags(path->nodes[0], bgi, bogus);
break;
default:
ret = -EINVAL;
goto out;
}
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
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;
if (corrupt_field == BTRFS_KEY_BAD) {
error("invalid field %s", 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) {
error("couldn't find the key to corrupt");
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:
error("invalid field %s, %d", 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_dir_item(struct btrfs_root *root, struct btrfs_key *key,
char *field)
{
struct btrfs_trans_handle *trans;
struct btrfs_dir_item *di;
struct btrfs_path *path;
char name[PATH_MAX];
struct btrfs_key location;
struct btrfs_disk_key disk_key;
unsigned long name_ptr;
enum btrfs_dir_item_field corrupt_field =
convert_dir_item_field(field);
u64 bogus;
u16 name_len;
int ret;
if (corrupt_field == BTRFS_DIR_ITEM_BAD) {
error("invalid field %s", 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) {
if (ret > 0)
ret = -ENOENT;
error("error searching for dir item %d", ret);
goto out;
}
di = btrfs_item_ptr(path->nodes[0], path->slots[0],
struct btrfs_dir_item);
switch (corrupt_field) {
case BTRFS_DIR_ITEM_NAME:
name_len = btrfs_dir_name_len(path->nodes[0], di);
name_ptr = (unsigned long)(di + 1);
read_extent_buffer(path->nodes[0], name, name_ptr, name_len);
name[0]++;
write_extent_buffer(path->nodes[0], name, name_ptr, name_len);
btrfs_mark_buffer_dirty(path->nodes[0]);
goto out;
case BTRFS_DIR_ITEM_LOCATION_OBJECTID:
btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location);
bogus = generate_u64(location.objectid);
location.objectid = bogus;
btrfs_cpu_key_to_disk(&disk_key, &location);
btrfs_set_dir_item_key(path->nodes[0], di, &disk_key);
btrfs_mark_buffer_dirty(path->nodes[0]);
goto out;
default:
ret = -EINVAL;
goto out;
}
out:
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
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) {
error("invalid field %s", 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]) {
error("couldn't find inode %llu", 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) {
error("couldn't find inode %llu", 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;
case BTRFS_INODE_FIELD_NBYTES:
orig = btrfs_inode_nbytes(path->nodes[0], ei);
bogus = generate_u64(orig);
btrfs_set_inode_nbytes(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_NLINK:
orig = btrfs_inode_nlink(path->nodes[0], ei);
bogus = generate_u32(orig);
btrfs_set_inode_nlink(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_GENERATION:
orig = btrfs_inode_generation(path->nodes[0], ei);
bogus = generate_u64(orig);
btrfs_set_inode_generation(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_TRANSID:
orig = btrfs_inode_transid(path->nodes[0], ei);
bogus = generate_u64(orig);
btrfs_set_inode_transid(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_BLOCK_GROUP:
orig = btrfs_inode_block_group(path->nodes[0], ei);
bogus = generate_u64(orig);
btrfs_set_inode_block_group(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_MODE:
orig = btrfs_inode_mode(path->nodes[0], ei);
bogus = generate_u32(orig);
btrfs_set_inode_mode(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_UID:
orig = btrfs_inode_uid(path->nodes[0], ei);
bogus = generate_u32(orig);
btrfs_set_inode_uid(path->nodes[0], ei, bogus);
break;
case BTRFS_INODE_FIELD_GID:
orig = btrfs_inode_gid(path->nodes[0], ei);
bogus = generate_u32(orig);
btrfs_set_inode_gid(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, u64 bogus)
{
struct btrfs_file_extent_item *fi;
struct btrfs_path *path;
struct btrfs_key key;
enum btrfs_file_extent_field corrupt_field;
u64 orig;
int ret = 0;
corrupt_field = convert_file_extent_field(field);
if (corrupt_field == BTRFS_FILE_EXTENT_BAD) {
error("invalid field %s", 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) {
error("couldn't find extent %llu for inode %llu",
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 = (bogus == (u64)-1) ? generate_u64(orig) : bogus;
btrfs_set_file_extent_disk_bytenr(path->nodes[0], fi, bogus);
break;
case BTRFS_FILE_EXTENT_TYPE:
if (bogus == (u64)-1) {
error("specify a new extent type value (-v)");
ret = -EINVAL;
goto out;
}
btrfs_set_file_extent_type(path->nodes[0], fi, (u8)bogus);
break;
default:
ret = -EINVAL;
break;
}
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_free_path(path);
return ret;
}
static void shift_items(struct btrfs_root *root, struct extent_buffer *eb)
{
int nritems = btrfs_header_nritems(eb);
int shift_space = btrfs_leaf_free_space(eb) / 2;
int slot = nritems / 2;
int i = 0;
unsigned int data_end = btrfs_item_offset(eb, nritems - 1);
/* Shift the item data up to and including slot back by shift space */
memmove_extent_buffer(eb, btrfs_leaf_data(eb) + data_end - shift_space,
btrfs_leaf_data(eb) + data_end,
btrfs_item_offset(eb, slot - 1) - data_end);
/* Now update the item pointers. */
for (i = nritems - 1; i >= slot; i--) {
u32 offset = btrfs_item_offset(eb, i);
offset -= shift_space;
btrfs_set_item_offset(eb, i, offset);
}
}
static int corrupt_metadata_block(struct btrfs_fs_info *fs_info, u64 block,
char *field)
{
struct extent_buffer *eb;
enum btrfs_metadata_block_field corrupt_field;
int ret;
corrupt_field = convert_metadata_block_field(field);
if (corrupt_field == BTRFS_METADATA_BLOCK_BAD) {
error("invalid field %s", field);
return -EINVAL;
}
eb = read_tree_block(fs_info, block, 0);
if (!extent_buffer_uptodate(eb)) {
error("couldn't read in tree block %s", field);
return -EINVAL;
}
ret = 0;
switch (corrupt_field) {
case BTRFS_METADATA_BLOCK_GENERATION:
{
u64 orig = btrfs_header_generation(eb);
u64 bogus = generate_u64(orig);
btrfs_set_header_generation(eb, bogus);
btrfs-progs: corrupt-block: re-generate the checksum for generation corruption [BUG] If using btrfs-corrupt-block to corrupt the generation of a tree block (in my example, it's csum root), it will cause csum mismatch other than the expected transid mismatch: # ./btrfs-corrupt-block --metadata-block 30474240 -f generation \ /dev/test/scratch1 # btrfs check /dev/test/scratch1 Opening filesystem to check... checksum verify failed on 30474240 wanted 0xb3e8059a found 0xb4a4b45c checksum verify failed on 30474240 wanted 0xb3e8059a found 0xb4a4b45c checksum verify failed on 30474240 wanted 0xb3e8059a found 0xb4a4b45c Csum didn't match ERROR: could not setup csum tree ERROR: cannot open file system [CAUSE] Inside the switch branch BTRFS_METADATA_BLOCK_GENERATION in corrupt_metadata_block(), we just set the generation and trigger write_and_map_eb(). However write_and_map_eb() doesn't re-generate the checksum by itself, thus we make the victim tree block to have a stale checksum. [FIX] Just call csum_tree_block_size() before write_and_map_eb(). Now the corrupted fs have the expected corruption pattern now: # btrfs check /dev/test/scratch1 Opening filesystem to check... parent transid verify failed on 30474240 wanted 7 found 11814770867473404344 parent transid verify failed on 30474240 wanted 7 found 11814770867473404344 parent transid verify failed on 30474240 wanted 7 found 11814770867473404344 Ignoring transid failure ... Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2022-09-13 07:19:26 +00:00
csum_tree_block_size(eb, fs_info->csum_size, 0,
fs_info->csum_type);
ret = write_and_map_eb(fs_info, eb);
free_extent_buffer(eb);
if (ret < 0) {
errno = -ret;
error("failed to write extent buffer at %llu: %m",
eb->start);
return ret;
}
break;
}
case BTRFS_METADATA_BLOCK_SHIFT_ITEMS:
{
struct btrfs_trans_handle *trans;
struct btrfs_root *root;
struct btrfs_path *path;
struct btrfs_key key, root_key;
u64 root_objectid;
u8 level;
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(fs_info, &root_key);
if (IS_ERR(root)) {
error("couldn't find owner root %llu", 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);
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
path->lowest_level = level;
ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
if (ret < 0) {
error("error searching to node %d", ret);
btrfs_free_path(path);
btrfs_abort_transaction(trans, ret);
return ret;
}
eb = path->nodes[level];
shift_items(root, path->nodes[level]);
btrfs_mark_buffer_dirty(path->nodes[level]);
btrfs_commit_transaction(trans, root);
break;
}
default:
ret = -EINVAL;
break;
}
return ret;
}
static int corrupt_btrfs_item(struct btrfs_root *root, struct btrfs_key *key,
char *field)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
enum btrfs_item_field corrupt_field;
u32 orig, bogus;
int ret;
corrupt_field = convert_item_field(field);
if (corrupt_field == BTRFS_ITEM_BAD) {
error("invalid field %s", 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);
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
ret = btrfs_search_slot(trans, root, key, path, 0, 1);
if (ret != 0) {
error("error searching to node %d", ret);
goto out;
}
ret = 0;
switch (corrupt_field) {
case BTRFS_ITEM_OFFSET:
orig = btrfs_item_offset(path->nodes[0], path->slots[0]);
bogus = generate_u32(orig);
btrfs_set_item_offset(path->nodes[0], path->slots[0], bogus);
break;
default:
ret = -EINVAL;
break;
}
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
}
static int corrupt_btrfs_item_data(struct btrfs_root *root,
struct btrfs_key *key,
u64 bogus_offset, u64 bogus_size,
char bogus_value)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
int ret;
void *data;
struct extent_buffer *leaf;
int slot;
u32 item_size;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
goto free_path;
}
ret = btrfs_search_slot(trans, root, key, path, 0, 1);
if (ret != 0) {
error("error searching to node %d", ret);
goto commit_txn;
}
leaf = path->nodes[0];
slot = path->slots[0];
data = btrfs_item_ptr(leaf, slot, void);
item_size = btrfs_item_size(leaf, slot);
if (bogus_offset + bogus_size > item_size) {
error("item corruption past end of item: %llu > %u\n",
bogus_offset + bogus_size, item_size);
ret = -EINVAL;
goto commit_txn;
}
data += bogus_offset;
memset_extent_buffer(leaf, bogus_value, (unsigned long)data, bogus_size);
btrfs_mark_buffer_dirty(leaf);
commit_txn:
btrfs_commit_transaction(trans, root);
free_path:
btrfs_free_path(path);
return ret;
}
static int delete_item(struct btrfs_root *root, struct btrfs_key *key)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
int ret;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
btrfs_free_path(path);
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
ret = btrfs_search_slot(trans, root, key, path, -1, 1);
if (ret) {
if (ret > 0)
ret = -ENOENT;
error("error searching to node %d", ret);
goto out;
}
ret = btrfs_del_item(trans, root, path);
btrfs_mark_buffer_dirty(path->nodes[0]);
out:
btrfs_commit_transaction(trans, root);
btrfs_free_path(path);
return ret;
}
static int delete_csum(struct btrfs_root *root, u64 bytenr, u64 bytes)
{
struct btrfs_trans_handle *trans;
int ret;
root = btrfs_csum_root(root->fs_info, bytenr);
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
ret = btrfs_del_csums(trans, bytenr, bytes);
if (ret)
error("error deleting csums %d", ret);
btrfs_commit_transaction(trans, root);
return ret;
}
/* corrupt item using NO cow.
* Because chunk recover will recover based on whole partition scanning,
* 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 */
static 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 */
if (slot >= btrfs_header_nritems(leaf)) {
error("invalid eb: no data or slot out of range: %d >= %d",
slot, btrfs_header_nritems(leaf));
return -EINVAL;
}
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(leaf, slot);
memset_extent_buffer(leaf, 0, ptr, item_size);
btrfs_mark_buffer_dirty(leaf);
}
return ret;
}
static 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) {
error("error searching tree");
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_range(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) {
error("error searching tree");
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_range(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;
}
static 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) {
error("can't find chunk with given offset %llu", offset);
goto out;
}
if (ret < 0) {
error("error searching chunk");
goto out;
}
out:
return ret;
}
static void parse_key(u64 *objectid, u8 *type, u64 *offset)
{
int ret = sscanf(optarg, "%llu,%hhu,%llu", objectid, type, offset);
if (ret != 3) {
error("error parsing key '%s': %d", optarg, errno);
print_usage(1);
}
}
static struct btrfs_root *open_root(struct btrfs_fs_info *fs_info,
u64 root_objectid)
{
struct btrfs_key root_key;
struct btrfs_root *root;
root_key.objectid = root_objectid;
root_key.type = BTRFS_ROOT_ITEM_KEY;
root_key.offset = (u64)-1;
root = btrfs_read_fs_root(fs_info, &root_key);
if (IS_ERR(root)) {
error("couldn't find root %llu", root_objectid);
print_usage(1);
}
return root;
}
int main(int argc, char **argv)
{
struct cache_tree root_cache;
struct btrfs_key key;
struct btrfs_root *root, *target_root;
char *dev;
/* chunk offset can be 0,so change to (u64)-1 */
u64 logical = (u64)-1;
int ret = 0;
u64 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;
int corrupt_item = 0;
int corrupt_di = 0;
int delete = 0;
int should_corrupt_key = 0;
u64 metadata_block = 0;
u64 inode = 0;
u64 file_extent = (u64)-1;
u64 root_objectid = 0;
u64 csum_bytenr = 0;
u64 block_group = 0;
char field[FIELD_BUF_LEN];
u64 bogus_value = (u64)-1;
u64 bogus_offset = (u64)-1;
field[0] = '\0';
memset(&key, 0, sizeof(key));
while(1) {
int c;
enum { GETOPT_VAL_BLOCK_GROUP = GETOPT_VAL_FIRST,
GETOPT_VAL_VALUE, GETOPT_VAL_OFFSET,
};
static const struct option long_options[] = {
/* { "byte-count", 1, NULL, 'b' }, */
{ "logical", required_argument, NULL, 'l' },
{ "copy", required_argument, NULL, 'c' },
{ "bytes", required_argument, NULL, 'b' },
{ "extent-record", no_argument, NULL, 'e' },
{ "extent-tree", no_argument, NULL, 'E' },
{ "keys", no_argument, NULL, 'k' },
{ "chunk-record", no_argument, NULL, 'u' },
{ "chunk-tree", no_argument, NULL, 'U' },
{ "inode", required_argument, NULL, 'i'},
{ "file-extent", required_argument, NULL, 'x'},
{ "metadata-block", required_argument, NULL, 'm'},
{ "field", required_argument, NULL, 'f'},
{ "key", required_argument, NULL, 'K'},
{ "item", no_argument, NULL, 'I'},
{ "dir-item", no_argument, NULL, 'D'},
{ "delete", no_argument, NULL, 'd'},
{ "root", no_argument, NULL, 'r'},
{ "csum", required_argument, NULL, 'C'},
{ "block-group", required_argument, NULL, GETOPT_VAL_BLOCK_GROUP},
{ "value", required_argument, NULL, GETOPT_VAL_VALUE},
{ "offset", required_argument, NULL, GETOPT_VAL_OFFSET},
{ "help", no_argument, NULL, GETOPT_VAL_HELP},
{ NULL, 0, NULL, 0 }
};
c = getopt_long(argc, argv, "l:c:b:eEkuUi:f:x:m:K:I:D:d:r:C:",
long_options, NULL);
if (c < 0)
break;
switch(c) {
case 'l':
logical = arg_strtou64(optarg);
break;
case 'c':
copy = arg_strtou64(optarg);
break;
case 'b':
bytes = arg_strtou64(optarg);
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;
break;
case 'i':
inode = arg_strtou64(optarg);
break;
case 'f':
strncpy(field, optarg, FIELD_BUF_LEN);
break;
case 'x':
file_extent = arg_strtou64(optarg);
break;
case 'm':
metadata_block = arg_strtou64(optarg);
break;
case 'K':
should_corrupt_key = 1;
parse_key(&key.objectid, &key.type, &key.offset);
break;
case 'D':
corrupt_di = 1;
parse_key(&key.objectid, &key.type, &key.offset);
break;
case 'I':
corrupt_item = 1;
parse_key(&key.objectid, &key.type, &key.offset);
break;
case 'd':
delete = 1;
parse_key(&key.objectid, &key.type, &key.offset);
break;
case 'r':
root_objectid = arg_strtou64(optarg);
break;
case 'C':
csum_bytenr = arg_strtou64(optarg);
break;
case GETOPT_VAL_BLOCK_GROUP:
block_group = arg_strtou64(optarg);
break;
case GETOPT_VAL_VALUE:
bogus_value = arg_strtou64(optarg);
break;
case GETOPT_VAL_OFFSET:
bogus_offset = arg_strtou64(optarg);
break;
case GETOPT_VAL_HELP:
default:
print_usage(c != GETOPT_VAL_HELP);
}
}
set_argv0(argv);
if (check_argc_min(argc - optind, 1))
return 1;
dev = argv[optind];
cache_tree_init(&root_cache);
root = open_ctree(dev, 0, OPEN_CTREE_WRITES);
if (!root) {
error("open ctree failed");
exit(1);
}
target_root = root;
if (root_objectid)
target_root = open_root(root->fs_info, root_objectid);
if (extent_rec) {
struct btrfs_trans_handle *trans;
if (logical == (u64)-1)
print_usage(1);
trans = btrfs_start_transaction(root, 1);
BUG_ON(IS_ERR(trans));
ret = corrupt_extent(trans, root, logical);
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (extent_tree) {
struct btrfs_trans_handle *trans;
struct btrfs_root *extent_root;
extent_root = btrfs_extent_root(root->fs_info, 0);
trans = btrfs_start_transaction(root, 1);
BUG_ON(IS_ERR(trans));
btrfs_corrupt_extent_tree(trans, extent_root,
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(1);
del = rand_range(3);
path = btrfs_alloc_path();
if (!path) {
error_msg(ERROR_MSG_MEMORY, NULL);
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);
BUG_ON(IS_ERR(trans));
ret = corrupt_item_nocow(trans, root->fs_info->chunk_root,
path, del);
if (ret < 0)
error("failed to corrupt chunk record");
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (chunk_tree) {
struct btrfs_trans_handle *trans;
trans = btrfs_start_transaction(root, 1);
BUG_ON(IS_ERR(trans));
ret = corrupt_chunk_tree(trans, root->fs_info->chunk_root);
if (ret < 0)
error("failed to corrupt chunk tree");
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (inode) {
struct btrfs_trans_handle *trans;
if (*field == 0)
print_usage(1);
trans = btrfs_start_transaction(root, 1);
BUG_ON(IS_ERR(trans));
if (file_extent == (u64)-1) {
printf("corrupting inode\n");
ret = corrupt_inode(trans, root, inode, field);
} else {
ret = corrupt_file_extent(trans, root, inode,
file_extent, field, bogus_value);
}
btrfs_commit_transaction(trans, root);
goto out_close;
}
if (metadata_block) {
if (*field == 0)
print_usage(1);
ret = corrupt_metadata_block(root->fs_info, metadata_block,
field);
goto out_close;
}
if (corrupt_di) {
if (!key.objectid || *field == 0)
print_usage(1);
ret = corrupt_dir_item(target_root, &key, field);
goto out_close;
}
if (csum_bytenr) {
ret = delete_csum(root, csum_bytenr, bytes);
goto out_close;
}
if (corrupt_item) {
if (!key.objectid)
print_usage(1);
if (!root_objectid)
print_usage(1);
if (*field != 0)
ret = corrupt_btrfs_item(target_root, &key, field);
else if (bogus_offset != (u64)-1 &&
bytes != (u64)-1 &&
bogus_value != (u64)-1)
ret = corrupt_btrfs_item_data(target_root, &key,
bogus_offset, bytes,
bogus_value);
else
print_usage(1);
goto out_close;
}
if (delete) {
if (!key.objectid)
print_usage(1);
ret = delete_item(target_root, &key);
goto out_close;
}
if (should_corrupt_key) {
if (*field == 0)
print_usage(1);
ret = corrupt_key(target_root, &key, field);
goto out_close;
}
if (block_group) {
if (*field == 0)
print_usage(1);
ret = corrupt_block_group(root, block_group, 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(1);
if (logical == (u64)-1)
print_usage(1);
if (bytes == 0)
bytes = root->fs_info->sectorsize;
bytes = round_up(bytes, root->fs_info->sectorsize);
while (bytes > 0) {
if (corrupt_block_keys) {
corrupt_keys_in_block(root->fs_info, logical);
} else {
struct extent_buffer *eb;
eb = btrfs_find_create_tree_block(root->fs_info,
logical);
if (!eb) {
error_msg(ERROR_MSG_MEMORY,
"allocating extent buffer for bytenr %llu",
logical);
ret = 1;
goto out_close;
}
debug_corrupt_block(eb, root, logical,
root->fs_info->sectorsize, copy);
free_extent_buffer(eb);
}
logical += root->fs_info->sectorsize;
bytes -= root->fs_info->sectorsize;
}
return ret;
out_close:
close_ctree(root);
return ret;
}