diff options
Diffstat (limited to 'cmds-check.c')
-rw-r--r-- | cmds-check.c | 360 |
1 files changed, 359 insertions, 1 deletions
diff --git a/cmds-check.c b/cmds-check.c index c5bca84b..7bb68ceb 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -4643,6 +4643,353 @@ out: return ret; } +static int pin_down_tree_blocks(struct btrfs_fs_info *fs_info, + struct extent_buffer *eb, int tree_root) +{ + struct extent_buffer *tmp; + struct btrfs_root_item *ri; + struct btrfs_key key; + u64 bytenr; + u32 leafsize; + int level = btrfs_header_level(eb); + int nritems; + int ret; + int i; + + btrfs_pin_extent(fs_info, eb->start, eb->len); + + leafsize = btrfs_super_leafsize(fs_info->super_copy); + nritems = btrfs_header_nritems(eb); + for (i = 0; i < nritems; i++) { + if (level == 0) { + btrfs_item_key_to_cpu(eb, &key, i); + if (key.type != BTRFS_ROOT_ITEM_KEY) + continue; + /* Skip the extent root and reloc roots */ + if (key.objectid == BTRFS_EXTENT_TREE_OBJECTID || + key.objectid == BTRFS_TREE_RELOC_OBJECTID || + key.objectid == BTRFS_DATA_RELOC_TREE_OBJECTID) + continue; + ri = btrfs_item_ptr(eb, i, struct btrfs_root_item); + bytenr = btrfs_disk_root_bytenr(eb, ri); + + /* + * If at any point we start needing the real root we + * will have to build a stump root for the root we are + * in, but for now this doesn't actually use the root so + * just pass in extent_root. + */ + tmp = read_tree_block(fs_info->extent_root, bytenr, + leafsize, 0); + if (!tmp) { + fprintf(stderr, "Error reading root block\n"); + return -EIO; + } + ret = pin_down_tree_blocks(fs_info, tmp, 0); + free_extent_buffer(tmp); + if (ret) + return ret; + } else { + bytenr = btrfs_node_blockptr(eb, i); + + /* If we aren't the tree root don't read the block */ + if (level == 1 && !tree_root) { + btrfs_pin_extent(fs_info, bytenr, leafsize); + continue; + } + + tmp = read_tree_block(fs_info->extent_root, bytenr, + leafsize, 0); + if (!tmp) { + fprintf(stderr, "Error reading tree block\n"); + return -EIO; + } + ret = pin_down_tree_blocks(fs_info, tmp, tree_root); + free_extent_buffer(tmp); + if (ret) + return ret; + } + } + + return 0; +} + +static int pin_metadata_blocks(struct btrfs_fs_info *fs_info) +{ + int ret; + + ret = pin_down_tree_blocks(fs_info, fs_info->chunk_root->node, 0); + if (ret) + return ret; + + return pin_down_tree_blocks(fs_info, fs_info->tree_root->node, 1); +} + +static int reset_block_groups(struct btrfs_fs_info *fs_info) +{ + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_chunk *chunk; + struct btrfs_key key; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + key.objectid = 0; + key.type = BTRFS_CHUNK_ITEM_KEY; + key.offset = 0; + + ret = btrfs_search_slot(NULL, fs_info->chunk_root, &key, path, 0, 0); + if (ret < 0) { + btrfs_free_path(path); + return ret; + } + + /* + * We do this in case the block groups were screwed up and had alloc + * bits that aren't actually set on the chunks. This happens with + * restored images every time and could happen in real life I guess. + */ + fs_info->avail_data_alloc_bits = 0; + fs_info->avail_metadata_alloc_bits = 0; + fs_info->avail_system_alloc_bits = 0; + + /* First we need to create the in-memory block groups */ + while (1) { + if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(fs_info->chunk_root, path); + if (ret < 0) { + btrfs_free_path(path); + return ret; + } + if (ret) { + ret = 0; + break; + } + } + leaf = path->nodes[0]; + btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + if (key.type != BTRFS_CHUNK_ITEM_KEY) { + path->slots[0]++; + continue; + } + + chunk = btrfs_item_ptr(leaf, path->slots[0], + struct btrfs_chunk); + btrfs_add_block_group(fs_info, 0, + btrfs_chunk_type(leaf, chunk), + key.objectid, key.offset, + btrfs_chunk_length(leaf, chunk)); + path->slots[0]++; + } + + btrfs_free_path(path); + return 0; +} + +static int reset_balance(struct btrfs_trans_handle *trans, + struct btrfs_fs_info *fs_info) +{ + struct btrfs_root *root = fs_info->tree_root; + struct btrfs_path *path; + struct extent_buffer *leaf; + struct btrfs_key key; + int del_slot, del_nr = 0; + int ret; + int found = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + key.objectid = BTRFS_BALANCE_OBJECTID; + key.type = BTRFS_BALANCE_ITEM_KEY; + key.offset = 0; + + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret) { + if (ret > 0) + ret = 0; + goto out; + } + + ret = btrfs_del_item(trans, root, path); + if (ret) + goto out; + btrfs_release_path(root, path); + + key.objectid = BTRFS_TREE_RELOC_OBJECTID; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = 0; + + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret < 0) + goto out; + while (1) { + if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + if (!found) + break; + + if (del_nr) { + ret = btrfs_del_items(trans, root, path, + del_slot, del_nr); + del_nr = 0; + if (ret) + goto out; + } + key.offset++; + btrfs_release_path(root, path); + + found = 0; + ret = btrfs_search_slot(trans, root, &key, path, + -1, 1); + if (ret < 0) + goto out; + continue; + } + found = 1; + leaf = path->nodes[0]; + btrfs_item_key_to_cpu(leaf, &key, path->slots[0]); + if (key.objectid > BTRFS_TREE_RELOC_OBJECTID) + break; + if (key.objectid != BTRFS_TREE_RELOC_OBJECTID) { + path->slots[0]++; + continue; + } + if (!del_nr) { + del_slot = path->slots[0]; + del_nr = 1; + } else { + del_nr++; + } + path->slots[0]++; + } + + if (del_nr) { + ret = btrfs_del_items(trans, root, path, del_slot, del_nr); + if (ret) + goto out; + } + btrfs_release_path(root, path); + + key.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(root)) { + fprintf(stderr, "Error reading data reloc tree\n"); + return PTR_ERR(root); + } + root->track_dirty = 1; + if (root->last_trans != trans->transid) { + root->last_trans = trans->transid; + root->commit_root = root->node; + extent_buffer_get(root->node); + } + ret = btrfs_fsck_reinit_root(trans, root, 0); +out: + btrfs_free_path(path); + return ret; +} + +static int reinit_extent_tree(struct btrfs_fs_info *fs_info) +{ + struct btrfs_trans_handle *trans; + u64 start = 0; + int ret; + + /* + * The only reason we don't do this is because right now we're just + * walking the trees we find and pinning down their bytes, we don't look + * at any of the leaves. In order to do mixed groups we'd have to check + * the leaves of any fs roots and pin down the bytes for any file + * extents we find. Not hard but why do it if we don't have to? + */ + if (btrfs_fs_incompat(fs_info, BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS)) { + fprintf(stderr, "We don't support re-initing the extent tree " + "for mixed block groups yet, please notify a btrfs " + "developer you want to do this so they can add this " + "functionality.\n"); + return -EINVAL; + } + + trans = btrfs_start_transaction(fs_info->extent_root, 1); + if (IS_ERR(trans)) { + fprintf(stderr, "Error starting transaction\n"); + return PTR_ERR(trans); + } + + /* + * first we need to walk all of the trees except the extent tree and pin + * down the bytes that are in use so we don't overwrite any existing + * metadata. + */ + ret = pin_metadata_blocks(fs_info); + if (ret) { + fprintf(stderr, "error pinning down used bytes\n"); + return ret; + } + + /* + * Need to drop all the block groups since we're going to recreate all + * of them again. + */ + btrfs_free_block_groups(fs_info); + ret = reset_block_groups(fs_info); + if (ret) { + fprintf(stderr, "error resetting the block groups\n"); + return ret; + } + + /* Ok we can allocate now, reinit the extent root */ + ret = btrfs_fsck_reinit_root(trans, fs_info->extent_root, 1); + if (ret) { + fprintf(stderr, "extent root initialization failed\n"); + /* + * When the transaction code is updated we should end the + * transaction, but for now progs only knows about commit so + * just return an error. + */ + return ret; + } + + ret = reset_balance(trans, fs_info); + if (ret) { + fprintf(stderr, "error reseting the pending balance\n"); + return ret; + } + + /* + * Now we have all the in-memory block groups setup so we can make + * allocations properly, and the metadata we care about is safe since we + * pinned all of it above. + */ + while (1) { + struct btrfs_block_group_cache *cache; + + cache = btrfs_lookup_first_block_group(fs_info, start); + if (!cache) + break; + start = cache->key.objectid + cache->key.offset; + ret = btrfs_insert_item(trans, fs_info->extent_root, + &cache->key, &cache->item, + sizeof(cache->item)); + if (ret) { + fprintf(stderr, "Error adding block group\n"); + return ret; + } + btrfs_extent_post_op(trans, fs_info->extent_root); + } + + /* + * Ok now we commit and run the normal fsck, which will add extent + * entries for all of the items it finds. + */ + return btrfs_commit_transaction(trans, fs_info->extent_root); +} + static struct option long_options[] = { { "super", 1, NULL, 's' }, { "repair", 0, NULL, 0 }, @@ -4674,6 +5021,7 @@ int cmd_check(int argc, char **argv) int repair = 0; int option_index = 0; int init_csum_tree = 0; + int init_extent_tree = 0; int rw = 0; while(1) { @@ -4702,6 +5050,10 @@ int cmd_check(int argc, char **argv) printf("Creating a new CRC tree\n"); init_csum_tree = 1; rw = 1; + } else if (option_index == 3) { + init_extent_tree = 1; + rw = 1; + repair = 1; } } @@ -4740,6 +5092,12 @@ int cmd_check(int argc, char **argv) root = info->fs_root; + if (init_extent_tree) { + printf("Creating a new extent tree\n"); + ret = reinit_extent_tree(info); + if (ret) + return ret; + } fprintf(stderr, "checking extents\n"); if (init_csum_tree) { struct btrfs_trans_handle *trans; @@ -4751,7 +5109,7 @@ int cmd_check(int argc, char **argv) return PTR_ERR(trans); } - ret = btrfs_fsck_reinit_root(trans, info->csum_root); + ret = btrfs_fsck_reinit_root(trans, info->csum_root, 0); if (ret) { fprintf(stderr, "crc root initialization failed\n"); return -EIO; |