From 289e87d9f83d8d85905586bce3210837ef93cfa5 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 11 Aug 2020 19:44:48 +0800 Subject: [PATCH] btrfs-progs: check/lowmem: add ability to repair extent item generation There is an internal report about bad extent item generation triggering tree-checker. This patch will add the repair ability to btrfs check --mode=lowmem mode, by resetting the generation field of extent item. Currently the correct generation for tree block is fetched from its header, while for data extent it uses transid as fallback. Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- check/mode-common.c | 58 ++++++++++++++++++++++++++++++ check/mode-common.h | 3 ++ check/mode-lowmem.c | 87 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 141 insertions(+), 7 deletions(-) diff --git a/check/mode-common.c b/check/mode-common.c index b0357493..a6489191 100644 --- a/check/mode-common.c +++ b/check/mode-common.c @@ -1181,3 +1181,61 @@ int recow_extent_buffer(struct btrfs_root *root, struct extent_buffer *eb) btrfs_release_path(&path); return ret; } + +/* + * Try to get correct extent item generation. + * + * Return 0 if we get a correct generation. + * Return <0 if we failed to get one. + */ +int get_extent_item_generation(u64 bytenr, u64 *gen_ret) +{ + struct btrfs_root *root = gfs_info->extent_root; + struct btrfs_extent_item *ei; + struct btrfs_path path; + struct btrfs_key key; + int ret; + + key.objectid = bytenr; + key.type = BTRFS_METADATA_ITEM_KEY; + key.offset = (u64)-1; + + btrfs_init_path(&path); + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + /* Not possible */ + if (ret == 0) + ret = -EUCLEAN; + if (ret < 0) + goto out; + ret = btrfs_previous_extent_item(root, &path, bytenr); + if (ret > 0) + ret = -ENOENT; + if (ret < 0) + goto out; + + ei = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_extent_item); + + if (btrfs_extent_flags(path.nodes[0], ei) & + BTRFS_EXTENT_FLAG_TREE_BLOCK) { + struct extent_buffer *eb; + + eb = read_tree_block(gfs_info, bytenr, 0); + if (extent_buffer_uptodate(eb)) { + *gen_ret = btrfs_header_generation(eb); + ret = 0; + } else { + ret = -EIO; + } + free_extent_buffer(eb); + } else { + /* + * TODO: Grab proper data generation for data extents. + * But this is not an urgent objective, as we can still + * use transaction id as fall back + */ + ret = -ENOTSUP; + } +out: + btrfs_release_path(&path); + return ret; +} diff --git a/check/mode-common.h b/check/mode-common.h index 4efc07a4..604dc3e8 100644 --- a/check/mode-common.h +++ b/check/mode-common.h @@ -171,4 +171,7 @@ static inline u32 btrfs_type_to_imode(u8 type) return imode_by_btrfs_type[(type)]; } + +int get_extent_item_generation(u64 bytenr, u64 *gen_ret); + #endif diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 3282a2c5..8c80e5f0 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -4134,6 +4134,64 @@ out: return ret; } +/* + * Reset generation for extent item specified by @path. + * Will try to grab the proper generation number from other sources, but if + * it fails, then use current transid as fallback. + * + * Returns < 0 for error. + * Return 0 if the generation is reset. + */ +static int repair_extent_item_generation(struct btrfs_path *path) +{ + struct btrfs_trans_handle *trans; + struct btrfs_key key; + struct btrfs_extent_item *ei; + struct btrfs_root *extent_root = gfs_info->extent_root; + u64 new_gen = 0;; + int ret; + + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + ASSERT(key.type == BTRFS_METADATA_ITEM_KEY || + key.type == BTRFS_EXTENT_ITEM_KEY); + + get_extent_item_generation(key.objectid, &new_gen); + ret = avoid_extents_overwrite(); + if (ret) + return ret; + btrfs_release_path(path); + trans = btrfs_start_transaction(extent_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + errno = -ret; + error("failed to start transaction: %m"); + return ret; + } + ret = btrfs_search_slot(trans, extent_root, &key, path, 0, 1); + if (ret > 0) + ret = -ENOENT; + if (ret < 0) { + errno = -ret; + error("failed to locate extent item for %llu: %m", key.objectid); + btrfs_abort_transaction(trans, ret); + return ret; + } + if (!new_gen) + new_gen = trans->transid; + ei = btrfs_item_ptr(path->nodes[0], path->slots[0], struct btrfs_extent_item); + btrfs_set_extent_generation(path->nodes[0], ei, new_gen); + ret = btrfs_commit_transaction(trans, extent_root); + if (ret < 0) { + errno = -ret; + error("failed to commit transaction: %m"); + btrfs_abort_transaction(trans, ret); + return ret; + } + printf("Reset extent item (%llu) generation to %llu\n", + key.objectid, new_gen); + return ret; +} + /* * This function will check a given extent item, including its backref and * itself (like crossing stripe boundary and type) @@ -4165,7 +4223,7 @@ static int check_extent_item(struct btrfs_path *path) struct btrfs_key key; int ret; int err = 0; - int tmp_err; + int tmp_err = 0; u32 ptr_offset; btrfs_item_key_to_cpu(eb, &key, slot); @@ -4195,7 +4253,7 @@ static int check_extent_item(struct btrfs_path *path) error( "invalid generation for extent %llu, have %llu expect (0, %llu]", key.objectid, gen, super_gen + 1); - err |= INVALID_GENERATION; + tmp_err |= INVALID_GENERATION; } if (flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) @@ -4248,24 +4306,24 @@ next: case BTRFS_TREE_BLOCK_REF_KEY: root_objectid = offset; owner = level; - tmp_err = check_tree_block_backref(offset, key.objectid, level); + tmp_err |= check_tree_block_backref(offset, key.objectid, level); break; case BTRFS_SHARED_BLOCK_REF_KEY: parent = offset; - tmp_err = check_shared_block_backref(offset, key.objectid, level); + tmp_err |= check_shared_block_backref(offset, key.objectid, level); break; case BTRFS_EXTENT_DATA_REF_KEY: dref = (struct btrfs_extent_data_ref *)(&iref->offset); root_objectid = btrfs_extent_data_ref_root(eb, dref); owner = btrfs_extent_data_ref_objectid(eb, dref); owner_offset = btrfs_extent_data_ref_offset(eb, dref); - tmp_err = check_extent_data_backref(root_objectid, + tmp_err |= check_extent_data_backref(root_objectid, owner, owner_offset, key.objectid, key.offset, btrfs_extent_data_ref_count(eb, dref)); break; case BTRFS_SHARED_DATA_REF_KEY: parent = offset; - tmp_err = check_shared_data_backref(offset, key.objectid); + tmp_err |= check_shared_data_backref(offset, key.objectid); break; default: error("extent[%llu %d %llu] has unknown ref type: %d", @@ -4302,8 +4360,23 @@ next: item_size = btrfs_item_size_nr(eb, slot); goto next; } - } else { + } + if ((tmp_err & INVALID_GENERATION) && repair){ + ret = repair_extent_item_generation(path); + if (ret < 0) { + err |= tmp_err; + err |= FATAL_ERROR; + goto out; + } + /* Error has been repaired */ + tmp_err &= ~INVALID_GENERATION; err |= tmp_err; + eb = path->nodes[0]; + slot = path->slots[0]; + ei = btrfs_item_ptr(eb, slot, struct btrfs_extent_item); + item_size = btrfs_item_size_nr(eb, slot); + ptr_offset += btrfs_extent_inline_ref_size(type); + goto next; } ptr_offset += btrfs_extent_inline_ref_size(type);