diff options
-rw-r--r-- | cmds-check.c | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/cmds-check.c b/cmds-check.c index abc78362..719ec698 100644 --- a/cmds-check.c +++ b/cmds-check.c @@ -2646,6 +2646,97 @@ static void free_corrupt_block(struct cache_extent *cache) FREE_EXTENT_CACHE_BASED_TREE(corrupt_blocks, free_corrupt_block); +/* + * Repair the btree of the given root. + * + * The fix is to remove the node key in corrupt_blocks cache_tree. + * and rebalance the tree. + * After the fix, the btree should be writeable. + */ +static int repair_btree(struct btrfs_root *root, + struct cache_tree *corrupt_blocks) +{ + struct btrfs_trans_handle *trans; + struct btrfs_path *path; + struct btrfs_corrupt_block *corrupt; + struct cache_extent *cache; + struct btrfs_key key; + u64 offset; + int level; + int ret = 0; + + if (cache_tree_empty(corrupt_blocks)) + return 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + fprintf(stderr, "Error starting transaction: %s\n", + strerror(-ret)); + return ret; + } + cache = first_cache_extent(corrupt_blocks); + while (cache) { + corrupt = container_of(cache, struct btrfs_corrupt_block, + cache); + level = corrupt->level; + path->lowest_level = level; + key.objectid = corrupt->key.objectid; + key.type = corrupt->key.type; + key.offset = corrupt->key.offset; + + /* + * Here we don't want to do any tree balance, since it may + * cause a balance with corrupted brother leaf/node, + * so ins_len set to 0 here. + * Balance will be done after all corrupt node/leaf is deleted. + */ + ret = btrfs_search_slot(trans, root, &key, path, 0, 1); + if (ret < 0) + goto out; + offset = btrfs_node_blockptr(path->nodes[level], + path->slots[level]); + + /* Remove the ptr */ + ret = btrfs_del_ptr(trans, root, path, level, + path->slots[level]); + if (ret < 0) + goto out; + /* + * Remove the corresponding extent + * return value is not concerned. + */ + btrfs_release_path(path); + ret = btrfs_free_extent(trans, root, offset, root->nodesize, + 0, root->root_key.objectid, + level - 1, 0); + cache = next_cache_extent(cache); + } + + /* Balance the btree using btrfs_search_slot() */ + cache = first_cache_extent(corrupt_blocks); + while (cache) { + corrupt = container_of(cache, struct btrfs_corrupt_block, + cache); + memcpy(&key, &corrupt->key, sizeof(key)); + ret = btrfs_search_slot(trans, root, &key, path, -1, 1); + if (ret < 0) + goto out; + /* return will always >0 since it won't find the item */ + ret = 0; + btrfs_release_path(path); + cache = next_cache_extent(cache); + } +out: + btrfs_free_path(path); + btrfs_commit_transaction(trans, root); + return ret; +} + static int check_fs_root(struct btrfs_root *root, struct cache_tree *root_cache, struct walk_control *wc) @@ -2748,6 +2839,17 @@ skip_walking: corrupt->key.offset); cache = next_cache_extent(cache); } + if (repair) { + printf("Try to repair the btree for root %llu\n", + root->root_key.objectid); + ret = repair_btree(root, &corrupt_blocks); + if (ret < 0) + fprintf(stderr, "Failed to repair btree: %s\n", + strerror(-ret)); + if (!ret) + printf("Btree for root %llu is fixed\n", + root->root_key.objectid); + } } err = merge_root_recs(root, &root_node.root_cache, root_cache); |