Btrfs-progs: add ability to corrupt metadata block fields
authorJosef Bacik <jbacik@fusionio.com>
Tue, 1 Oct 2013 12:54:58 +0000 (08:54 -0400)
committerChris Mason <chris.mason@fusionio.com>
Wed, 16 Oct 2013 12:23:11 +0000 (08:23 -0400)
This patch allows us to garble the generation field of a metadata block.  We
will search down to the block, set the bogus generation and write the block back
out.  I used this for verifying a transid fix patch for btrfsck.  Thanks,

Signed-off-by: Josef Bacik <jbacik@fusionio.com>
Signed-off-by: David Sterba <dsterba@suse.cz>
Signed-off-by: Chris Mason <chris.mason@fusionio.com>
btrfs-corrupt-block.c

index 9e72ca8..c4f80fc 100644 (file)
@@ -103,6 +103,8 @@ static void print_usage(void)
                "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-f The field in the item to corrupt\n");
        exit(1);
 }
@@ -139,7 +141,9 @@ static void corrupt_keys(struct btrfs_trans_handle *trans,
        }
        btrfs_mark_buffer_dirty(eb);
        if (!trans) {
-               csum_tree_block(root, eb, 0);
+               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);
        }
 }
@@ -297,6 +301,11 @@ enum btrfs_file_extent_field {
        BTRFS_FILE_EXTENT_BAD,
 };
 
+enum btrfs_metadata_block_field {
+       BTRFS_METADATA_BLOCK_GENERATION,
+       BTRFS_METADATA_BLOCK_BAD,
+};
+
 static enum btrfs_inode_field convert_inode_field(char *field)
 {
        if (!strncmp(field, "isize", FIELD_BUF_LEN))
@@ -311,6 +320,14 @@ static enum btrfs_file_extent_field convert_file_extent_field(char *field)
        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 u64 generate_u64(u64 orig)
 {
        u64 ret;
@@ -436,6 +453,87 @@ out:
        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' },
@@ -448,6 +546,7 @@ static struct option long_options[] = {
        { "chunk-tree", 0, NULL, 'U' },
        { "inode", 1, NULL, 'i'},
        { "file-extent", 1, NULL, 'x'},
+       { "metadata-block", 1, NULL, 'm'},
        { "field", 1, NULL, 'f'},
        { 0, 0, 0, 0}
 };
@@ -609,6 +708,7 @@ int main(int ac, char **av)
        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];
@@ -618,7 +718,7 @@ int main(int ac, char **av)
 
        while(1) {
                int c;
-               c = getopt_long(ac, av, "l:c:b:eEkuUi:f:x:", long_options,
+               c = getopt_long(ac, av, "l:c:b:eEkuUi:f:x:m:", long_options,
                                &option_index);
                if (c < 0)
                        break;
@@ -676,6 +776,15 @@ int main(int ac, char **av)
                                        print_usage();
                                }
                                break;
+                       case 'm':
+                               errno = 0;
+                               metadata_block = atoll(optarg);
+                               if (errno) {
+                                       fprintf(stderr, "error converting "
+                                               "%d\n", errno);
+                                       print_usage();
+                               }
+                               break;
                        default:
                                print_usage();
                }
@@ -761,7 +870,12 @@ int main(int ac, char **av)
                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 we made it here and we have extent set then we didn't specify
         * inode and we're screwed.