[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.
This commit is contained in:
		| @@ -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<void const*>(&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<block_address> &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<block_address> 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<void const*>(&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<space_map::ptr> 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<space_map::ptr>()); | ||||
| 				err_ << examine_data_mappings(tm, sb, options_.data_mapping_opts_, out_, | ||||
| 							      optional<space_map::ptr>()); | ||||
| 		} | ||||
|  | ||||
| 			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<void const*>(&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(); | ||||
| } | ||||
|  | ||||
| //---------------------------------------------------------------- | ||||
|   | ||||
| @@ -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<bcache::block_address> 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); | ||||
| } | ||||
|   | ||||
| @@ -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<uint64_t>(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); | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user