/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <limits.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 "utils.h"

#define FIELD_BUF_LEN 80

static struct extent_buffer *debug_corrupt_block(struct btrfs_root *root,
		u64 bytenr, u32 blocksize, u64 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->fs_info, 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(int ret)
{
	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");
	fprintf(stderr, "\t-I An item to corrupt (must also specify the field "
		"to corrupt and a root+key for the item)\n");
	fprintf(stderr, "\t-D Corrupt a dir item, must specify key and field\n");
	fprintf(stderr, "\t-d Delete this item (must specify -K)\n");
	fprintf(stderr, "\t-r Operate on this root (only works with -d)\n");
	fprintf(stderr, "\t-C Delete a csum for the specified bytenr.  When "
		"used with -b it'll delete that many bytes, otherwise it's "
		"just sectorsize\n");
	exit(ret);
}

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 (!extent_buffer_uptodate(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, u64 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;

	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() % 5)
			return;
	}

	for (i = 0; i < btrfs_header_nritems(eb); i++) {
		struct extent_buffer *next;

		next = read_tree_block(root, btrfs_node_blockptr(eb, i),
				       root->leafsize,
				       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_BAD,
};

enum btrfs_file_extent_field {
	BTRFS_FILE_EXTENT_DISK_BYTENR,
	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,
};

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;
	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;
	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();
	} while (ret == orig);
	return ret;
}

static u32 generate_u32(u32 orig)
{
	u32 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_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) {
		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) {
		if (ret > 0)
			ret = -ENOENT;
		fprintf(stderr, "Error searching for dir item %d\n", 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) {
		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;
	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;
	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 void shift_items(struct btrfs_root *root, struct extent_buffer *eb)
{
	int nritems = btrfs_header_nritems(eb);
	int shift_space = btrfs_leaf_free_space(root, eb) / 2;
	int slot = nritems / 2;
	int i = 0;
	unsigned int data_end = btrfs_item_offset_nr(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_nr(eb, slot - 1) - data_end);

	/* Now update the item pointers. */
	for (i = nritems - 1; i >= slot; i--) {
		u32 offset = btrfs_item_offset_nr(eb, i);
		offset -= shift_space;
		btrfs_set_item_offset(eb, btrfs_item_nr(i), offset);
	}
}

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 (!extent_buffer_uptodate(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;
	case BTRFS_METADATA_BLOCK_SHIFT_ITEMS:
		shift_items(root, path->nodes[level]);
		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 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) {
		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);
		fprintf(stderr, "Couldn't start transaction %ld\n",
			PTR_ERR(trans));
		return PTR_ERR(trans);
	}

	ret = btrfs_search_slot(trans, root, key, path, 0, 1);
	if (ret != 0) {
		fprintf(stderr, "Error searching to node %d\n", ret);
		goto out;
	}

	ret = 0;
	switch (corrupt_field) {
	case BTRFS_ITEM_OFFSET:
		orig = btrfs_item_offset_nr(path->nodes[0], path->slots[0]);
		bogus = generate_u32(orig);
		btrfs_set_item_offset(path->nodes[0],
				      btrfs_item_nr(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 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);
		fprintf(stderr, "Couldn't start transaction %ld\n",
			PTR_ERR(trans));
		return PTR_ERR(trans);
	}

	ret = btrfs_search_slot(trans, root, key, path, -1, 1);
	if (ret) {
		if (ret > 0)
			ret = -ENOENT;
		fprintf(stderr, "Error searching to node %d\n", 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 = root->fs_info->csum_root;
	trans = btrfs_start_transaction(root, 1);
	if (IS_ERR(trans)) {
		fprintf(stderr, "Couldn't start transaction %ld\n",
			PTR_ERR(trans));
		return PTR_ERR(trans);
	}

	ret = btrfs_del_csums(trans, root, bytenr, bytes);
	if (ret)
		fprintf(stderr, "Error deleting csums %d\n", ret);
	btrfs_commit_transaction(trans, root);
	return ret;
}

/* 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 */
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 */
	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;
}
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) {
		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;
}
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) {
		fprintf(stderr, "Can't find chunk with given offset %llu\n",
			offset);
		goto out;
	}
	if (ret < 0) {
		fprintf(stderr, "Error searching chunk\n");
		goto out;
	}
out:
	return ret;

}
int main(int argc, char **argv)
{
	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;
	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;
	u64 metadata_block = 0;
	u64 inode = 0;
	u64 file_extent = (u64)-1;
	u64 root_objectid = 0;
	u64 csum_bytenr = 0;
	char field[FIELD_BUF_LEN];

	field[0] = '\0';
	srand(128);
	memset(&key, 0, sizeof(key));

	while(1) {
		int c;
		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'},
			{ "help", no_argument, NULL, GETOPT_VAL_HELP},
			{ NULL, 0, NULL, 0 }
		};

		c = getopt_long(argc, argv, "l:c:b:eEkuUi:f:x:m:K:IDdr: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':
				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(1);
				}
				break;
			case 'D':
				corrupt_di = 1;
				break;
			case 'I':
				corrupt_item = 1;
				break;
			case 'd':
				delete = 1;
				break;
			case 'r':
				root_objectid = arg_strtou64(optarg);
				break;
			case 'C':
				csum_bytenr = 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))
		print_usage(1);
	dev = argv[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(1);
		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(1);
		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 (*field == 0)
			print_usage(1);

		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 (*field == 0)
			print_usage(1);
		ret = corrupt_metadata_block(root, metadata_block, field);
		goto out_close;
	}
	if (corrupt_di) {
		if (!key.objectid || *field == 0)
			print_usage(1);
		ret = corrupt_dir_item(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);
		ret = corrupt_btrfs_item(root, &key, field);
	}
	if (delete) {
		struct btrfs_root *target = root;

		if (!key.objectid)
			print_usage(1);
		if (root_objectid) {
			struct btrfs_key root_key;

			root_key.objectid = root_objectid;
			root_key.type = BTRFS_ROOT_ITEM_KEY;
			root_key.offset = (u64)-1;

			target = btrfs_read_fs_root(root->fs_info, &root_key);
			if (IS_ERR(target)) {
				fprintf(stderr, "Couldn't find root %llu\n",
					(unsigned long long)root_objectid);
				print_usage(1);
			}
		}
		ret = delete_item(target, &key);
		goto out_close;
	}
	if (key.objectid || key.offset || key.type) {
		if (*field == 0)
			print_usage(1);
		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(1);

	if (logical == (u64)-1)
		print_usage(1);

	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;
}
