From 49efa06ffa5b5482995b56d0f4d2f125a9e41e6e Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 14 Jul 2020 17:57:29 +0800 Subject: [PATCH] [thin_check] Support fixing leaked metadata blocks - The leaked blocks are defined as allocated but not used ones, i.e., expected ref-count == 0 and actual ref-count <= 2. - Blocks with expected ref-count > 0, or actual ref-count > 2, are treated as unrepairable leaks. - The fixing processing won't be executed if there's any unrepairable leak or errors. - The fixing process implicitly clearing the superblock needs_check flag if there's no error. --- thin-provisioning/metadata_checker.cc | 270 ++++++++++++++++++++------ thin-provisioning/metadata_checker.h | 5 +- thin-provisioning/thin_check.cc | 18 +- 3 files changed, 232 insertions(+), 61 deletions(-) diff --git a/thin-provisioning/metadata_checker.cc b/thin-provisioning/metadata_checker.cc index b32d5cc..95f8179 100644 --- a/thin-provisioning/metadata_checker.cc +++ b/thin-provisioning/metadata_checker.cc @@ -220,23 +220,15 @@ namespace { return err; } - error_state check_space_map_counts(transaction_manager::ptr tm, - superblock_detail::superblock const &sb, - nested_output &out) { - out << "checking space map counts" << end_message(); - nested_output::nest _ = out.push(); - - block_counter bc; - count_metadata(tm, sb, bc); - - // Finally we need to check the metadata space map agrees - // with the counts we've just calculated. + error_state compare_metadata_space_maps(space_map::ptr actual, + block_counter const &expected, + nested_output &out) { error_state err = NO_ERROR; - persistent_space_map::ptr metadata_sm = - open_metadata_sm(*tm, static_cast(&sb.metadata_space_map_root_)); - for (unsigned b = 0; b < metadata_sm->get_nr_blocks(); b++) { - ref_t c_actual = metadata_sm->get_count(b); - ref_t c_expected = bc.get_count(b); + block_address nr_blocks = actual->get_nr_blocks(); + + for (block_address b = 0; b < nr_blocks; b++) { + ref_t c_actual = actual->get_count(b); + ref_t c_expected = expected.get_count(b); if (c_actual != c_expected) { out << "metadata reference counts differ for block " << b @@ -251,6 +243,67 @@ namespace { return err; } + error_state collect_leaked_blocks(space_map::ptr actual, + block_counter const &expected, + std::set &leaked) { + error_state err = NO_ERROR; + block_address nr_blocks = actual->get_nr_blocks(); + + for (block_address b = 0; b < nr_blocks; b++) { + ref_t c_actual = actual->get_count(b); + ref_t c_expected = expected.get_count(b); + + if (c_actual == c_expected) + continue; + + if (c_actual < c_expected) { + err << FATAL; + break; + } + + // Theoretically, the ref-count of a leaked block + // should be only one. Here a leaked ref-count of two + // is allowed. + if (c_expected || c_actual >= 3) + err << NON_FATAL; + else if (c_actual > 0) + leaked.insert(b); + } + + return err; + } + + error_state clear_leaked_blocks(space_map::ptr actual, + block_counter const &expected) { + error_state err = NO_ERROR; + std::set leaked; + + err << collect_leaked_blocks(actual, expected, leaked); + if (err != NO_ERROR) + return err; + + for (auto const &b : leaked) + actual->set_count(b, 0); + + return err; + } + + error_state check_metadata_space_map_counts(transaction_manager::ptr tm, + superblock_detail::superblock const &sb, + block_counter &bc, + nested_output &out) { + out << "checking space map counts" << end_message(); + nested_output::nest _ = out.push(); + + count_metadata(tm, sb, bc); + + // Finally we need to check the metadata space map agrees + // with the counts we've just calculated. + space_map::ptr metadata_sm = + open_metadata_sm(*tm, static_cast(&sb.metadata_space_map_root_)); + return compare_metadata_space_maps(metadata_sm, bc, out); + } + error_state compare_space_maps(space_map::ptr actual, space_map::ptr expected, nested_output &out) { @@ -297,40 +350,36 @@ namespace { class metadata_checker { public: - metadata_checker(block_manager::ptr bm, + metadata_checker(std::string const &path, check_options check_opts, output_options output_opts) - : bm_(bm), + : path_(path), options_(check_opts), out_(cerr, 2), - info_out_(cout, 0) { + info_out_(cout, 0), + err_(NO_ERROR) { if (output_opts == OUTPUT_QUIET) { out_.disable(); info_out_.disable(); } + + sb_location_ = get_superblock_location(); } - error_state check() { - error_state err = NO_ERROR; - auto sb_location = superblock_detail::SUPERBLOCK_LOCATION; + void check() { + block_manager::ptr bm = open_bm(path_, block_manager::READ_ONLY, + !options_.use_metadata_snap_); - if (options_.use_metadata_snap_) { - superblock_detail::superblock sb = read_superblock(bm_, sb_location); - sb_location = sb.metadata_snap_; - if (sb_location == superblock_detail::SUPERBLOCK_LOCATION) - throw runtime_error("No metadata snapshot found."); - } - - err << examine_superblock(bm_, sb_location, out_); - if (err == FATAL) { - if (check_for_xml(bm_)) + err_ = examine_superblock(bm, sb_location_, out_); + if (err_ == FATAL) { + if (check_for_xml(bm)) out_ << "This looks like XML. thin_check only checks the binary metadata format." << end_message(); - return err; + return; } - superblock_detail::superblock sb = read_superblock(bm_, sb_location); - transaction_manager::ptr tm = open_tm(bm_, sb_location); + transaction_manager::ptr tm = open_tm(bm, sb_location_); + superblock_detail::superblock sb = read_superblock(bm, sb_location_); sb.data_mapping_root_ = mapping_root(sb, options_); print_info(tm, sb, info_out_); @@ -338,24 +387,77 @@ namespace { if (options_.sm_opts_ == check_options::SPACE_MAP_FULL) { space_map::ptr data_sm{open_disk_sm(*tm, &sb.data_space_map_root_)}; optional core_sm{create_core_map(data_sm->get_nr_blocks())}; - err << examine_data_mappings(tm, sb, options_.data_mapping_opts_, out_, core_sm); + err_ << examine_data_mappings(tm, sb, options_.data_mapping_opts_, out_, core_sm); + + if (err_ == FATAL) + return; // if we're checking everything, and there were no errors, // then we should check the space maps too. - if (err != FATAL) { - err << examine_metadata_space_map(tm, sb, options_.sm_opts_, out_); + err_ << examine_metadata_space_map(tm, sb, options_.sm_opts_, out_, expected_rc_); - if (core_sm) - err << compare_space_maps(data_sm, *core_sm, out_); - } + // check the data space map + if (core_sm) + err_ << compare_space_maps(data_sm, *core_sm, out_); } else - err << examine_data_mappings(tm, sb, options_.data_mapping_opts_, out_, - optional()); + err_ << examine_data_mappings(tm, sb, options_.data_mapping_opts_, out_, + optional()); + } - return err; + bool fix_metadata_leaks() { + if (!verify_preconditions_before_fixing()) { + out_ << "metadata has not been fully examined" << end_message(); + return false; + } + + // skip if the metadata cannot be fixed, or there's no leaked blocks + if (err_ == FATAL) + return false; + else if (err_ == NO_ERROR) + return true; + + block_manager::ptr bm = open_bm(path_, block_manager::READ_WRITE); + superblock_detail::superblock sb = read_superblock(bm, sb_location_); + transaction_manager::ptr tm = open_tm(bm, sb_location_); + persistent_space_map::ptr metadata_sm = + open_metadata_sm(*tm, static_cast(&sb.metadata_space_map_root_)); + tm->set_sm(metadata_sm); + + err_ = clear_leaked_blocks(metadata_sm, expected_rc_); + + if (err_ != NO_ERROR) + return false; + + metadata_sm->commit(); + metadata_sm->copy_root(&sb.metadata_space_map_root_, sizeof(sb.metadata_space_map_root_)); + write_superblock(bm, sb); + + out_ << "fixed metadata leaks" << end_message(); + + return true; + } + + error_state get_error() const { + return err_; } private: + block_address + get_superblock_location() { + block_address sb_location = superblock_detail::SUPERBLOCK_LOCATION; + + if (options_.use_metadata_snap_) { + block_manager::ptr bm = open_bm(path_, block_manager::READ_ONLY, + !options_.use_metadata_snap_); + superblock_detail::superblock sb = read_superblock(bm, sb_location); + sb_location = sb.metadata_snap_; + if (sb_location == superblock_detail::SUPERBLOCK_LOCATION) + throw runtime_error("No metadata snapshot found."); + } + + return sb_location; + } + error_state examine_data_mappings(transaction_manager::ptr tm, superblock_detail::superblock const &sb, @@ -382,12 +484,13 @@ namespace { examine_metadata_space_map(transaction_manager::ptr tm, superblock_detail::superblock const &sb, check_options::space_map_options option, - nested_output &out) { + nested_output &out, + block_counter &bc) { error_state err = NO_ERROR; switch (option) { case check_options::SPACE_MAP_FULL: - err << check_space_map_counts(tm, sb, out); + err << check_metadata_space_map_counts(tm, sb, bc, out); break; default: break; // do nothing @@ -396,10 +499,26 @@ namespace { return err; } - block_manager::ptr bm_; + bool verify_preconditions_before_fixing() const { + if (options_.use_metadata_snap_ || + !!options_.override_mapping_root_ || + options_.sm_opts_ != check_options::SPACE_MAP_FULL || + options_.data_mapping_opts_ != check_options::DATA_MAPPING_LEVEL2) + return false; + + if (!expected_rc_.get_counts().size()) + return false; + + return true; + } + + std::string const &path_; check_options options_; nested_output out_; nested_output info_out_; + block_address sb_location_; + block_counter expected_rc_; + base::error_state err_; // metadata state }; } @@ -409,7 +528,8 @@ check_options::check_options() : use_metadata_snap_(false), data_mapping_opts_(DATA_MAPPING_LEVEL2), sm_opts_(SPACE_MAP_FULL), - ignore_non_fatal_(false) { + ignore_non_fatal_(false), + fix_metadata_leaks_(false) { } void check_options::set_superblock_only() { @@ -434,14 +554,52 @@ void check_options::set_metadata_snap() { void check_options::set_ignore_non_fatal() { ignore_non_fatal_ = true; } - -base::error_state -thin_provisioning::check_metadata(block_manager::ptr bm, - check_options const &check_opts, - output_options output_opts) -{ - metadata_checker checker(bm, check_opts, output_opts); - return checker.check(); + +void check_options::set_fix_metadata_leaks() { + fix_metadata_leaks_ = true; +} + +bool check_options::check_conformance() { + if (fix_metadata_leaks_) { + if (ignore_non_fatal_) { + cerr << "cannot perform fix by ignoring non-fatal errors" << endl; + return false; + } + + if (use_metadata_snap_) { + cerr << "cannot perform fix within metadata snap" << endl; + return false; + } + + if (!!override_mapping_root_) { + cerr << "cannot perform fix with an overridden mapping root" << endl; + return false; + } + + if (data_mapping_opts_ != DATA_MAPPING_LEVEL2 || + sm_opts_ != SPACE_MAP_FULL) { + cerr << "cannot perform fix without a full examination" << endl; + return false; + } + } + + return true; +} + +//---------------------------------------------------------------- + +base::error_state +thin_provisioning::check_metadata(std::string const &path, + check_options const &check_opts, + output_options output_opts) +{ + metadata_checker checker(path, check_opts, output_opts); + + checker.check(); + if (check_opts.fix_metadata_leaks_) + checker.fix_metadata_leaks(); + + return checker.get_error(); } //---------------------------------------------------------------- diff --git a/thin-provisioning/metadata_checker.h b/thin-provisioning/metadata_checker.h index 843f5b6..001b4f1 100644 --- a/thin-provisioning/metadata_checker.h +++ b/thin-provisioning/metadata_checker.h @@ -40,17 +40,20 @@ namespace thin_provisioning { check_options(); + bool check_conformance(); void set_superblock_only(); void set_skip_mappings(); void set_override_mapping_root(bcache::block_address b); void set_metadata_snap(); void set_ignore_non_fatal(); + void set_fix_metadata_leaks(); bool use_metadata_snap_; data_mapping_options data_mapping_opts_; space_map_options sm_opts_; boost::optional override_mapping_root_; bool ignore_non_fatal_; + bool fix_metadata_leaks_; }; enum output_options { @@ -59,7 +62,7 @@ namespace thin_provisioning { }; base::error_state - check_metadata(persistent_data::block_manager::ptr bm, + check_metadata(std::string const &path, check_options const &check_opts, output_options output_opts); } diff --git a/thin-provisioning/thin_check.cc b/thin-provisioning/thin_check.cc index 26599c6..bd69b11 100644 --- a/thin-provisioning/thin_check.cc +++ b/thin-provisioning/thin_check.cc @@ -76,10 +76,8 @@ namespace { return 1; } - block_manager::ptr bm = open_bm(path, block_manager::READ_ONLY, - !fs.check_opts.use_metadata_snap_); output_options output_opts = !fs.quiet ? OUTPUT_NORMAL : OUTPUT_QUIET; - error_state err = check_metadata(bm, fs.check_opts, output_opts); + error_state err = check_metadata(path, fs.check_opts, output_opts); if (fs.ignore_non_fatal_errors) success = (err == FATAL) ? false : true; @@ -116,6 +114,7 @@ thin_check_cmd::usage(std::ostream &out) const << " {-h|--help}\n" << " {-V|--version}\n" << " {-m|--metadata-snap}\n" + << " {--fix-metadata-leaks}\n" << " {--override-mapping-root}\n" << " {--clear-needs-check-flag}\n" << " {--ignore-non-fatal-errors}\n" @@ -140,6 +139,7 @@ thin_check_cmd::run(int argc, char **argv) { "ignore-non-fatal-errors", no_argument, NULL, 3}, { "clear-needs-check-flag", no_argument, NULL, 4 }, { "override-mapping-root", required_argument, NULL, 5}, + { "fix-metadata-leaks", no_argument, NULL, 6}, { NULL, no_argument, NULL, 0 } }; @@ -187,6 +187,11 @@ thin_check_cmd::run(int argc, char **argv) fs.check_opts.set_override_mapping_root(boost::lexical_cast(optarg)); break; + case 6: + // fix-metadata-leaks + fs.check_opts.set_fix_metadata_leaks(); + break; + default: usage(cerr); return 1; @@ -194,7 +199,12 @@ thin_check_cmd::run(int argc, char **argv) } if (fs.clear_needs_check_flag_on_success && fs.check_opts.use_metadata_snap_) { - cerr << "--metadata-snap cannot be combined with --clear-needs-check-flag."; + cerr << "--metadata-snap cannot be combined with --clear-needs-check-flag." << endl; + usage(cerr); + exit(1); + } + + if (!fs.check_opts.check_conformance()) { usage(cerr); exit(1); }