From b05b9aa2275a894c5e716a22413f705f6943b0bf Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Sat, 27 Feb 2016 15:24:14 +0800 Subject: [PATCH] [thin_scan] first draft --- Makefile.in | 1 + thin-provisioning/commands.cc | 1 + thin-provisioning/commands.h | 7 + thin-provisioning/thin_scan.cc | 417 +++++++++++++++++++++++++++++++++ 4 files changed, 426 insertions(+) create mode 100644 thin-provisioning/thin_scan.cc diff --git a/Makefile.in b/Makefile.in index b515463..931cb38 100644 --- a/Makefile.in +++ b/Makefile.in @@ -95,6 +95,7 @@ SOURCE=\ thin-provisioning/thin_repair.cc \ thin-provisioning/thin_restore.cc \ thin-provisioning/thin_rmap.cc \ + thin-provisioning/thin_scan.cc \ thin-provisioning/thin_trim.cc \ thin-provisioning/xml_format.cc diff --git a/thin-provisioning/commands.cc b/thin-provisioning/commands.cc index c25abce..6d2f7e4 100644 --- a/thin-provisioning/commands.cc +++ b/thin-provisioning/commands.cc @@ -16,6 +16,7 @@ thin_provisioning::register_thin_commands(base::application &app) app.add_cmd(command::ptr(new thin_restore_cmd())); app.add_cmd(command::ptr(new thin_repair_cmd())); app.add_cmd(command::ptr(new thin_rmap_cmd())); + app.add_cmd(command::ptr(new thin_scan_cmd())); app.add_cmd(command::ptr(new thin_trim_cmd())); } diff --git a/thin-provisioning/commands.h b/thin-provisioning/commands.h index ec1f1ec..a8fa4a4 100644 --- a/thin-provisioning/commands.h +++ b/thin-provisioning/commands.h @@ -63,6 +63,13 @@ namespace thin_provisioning { virtual int run(int argc, char **argv); }; + class thin_scan_cmd : public base::command { + public: + thin_scan_cmd(); + virtual void usage(std::ostream &out) const; + virtual int run(int argc, char **argv); + }; + class thin_trim_cmd : public base::command { public: thin_trim_cmd(); diff --git a/thin-provisioning/thin_scan.cc b/thin-provisioning/thin_scan.cc new file mode 100644 index 0000000..c5afbd9 --- /dev/null +++ b/thin-provisioning/thin_scan.cc @@ -0,0 +1,417 @@ +// This file is part of the thin-provisioning-tools source. +// +// thin-provisioning-tools is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// thin-provisioning-tools is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with thin-provisioning-tools. If not, see +// . + +#include +#include +#include +#include +#include + +#include "persistent-data/data-structures/btree.h" +#include "persistent-data/data-structures/simple_traits.h" +#include "persistent-data/file_utils.h" +#include "persistent-data/space-maps/core.h" +#include "persistent-data/space-maps/disk_structures.h" +#include "thin-provisioning/metadata.h" +#include "thin-provisioning/superblock.h" +#include "thin-provisioning/commands.h" +#include "version.h" + +using namespace thin_provisioning; + +//---------------------------------------------------------------- + +namespace { + // extracted from btree_damage_visitor.h + template + bool check_block_nr(node const &n) { + if (n.get_location() != n.get_block_nr()) { + return false; + } + return true; + } + + // extracted from btree_damage_visitor.h + template + bool check_max_entries(node const &n) { + size_t elt_size = sizeof(uint64_t) + n.get_value_size(); + if (elt_size * n.get_max_entries() + sizeof(node_header) > MD_BLOCK_SIZE) { + return false; + } + + if (n.get_max_entries() % 3) { + return false; + } + + return true; + } + + // extracted from btree_damage_visitor.h + template + bool check_nr_entries(node const &n, bool is_root) { + if (n.get_nr_entries() > n.get_max_entries()) { + return false; + } + + block_address min = n.get_max_entries() / 3; + if (!is_root && (n.get_nr_entries() < min)) { + return false; + } + + return true; + } + + // extracted from btree_damage_visitor.h + template + bool check_ordered_keys(node const &n) { + unsigned nr_entries = n.get_nr_entries(); + + if (nr_entries == 0) + return true; // can only happen if a root node + + uint64_t last_key = n.key_at(0); + + for (unsigned i = 1; i < nr_entries; i++) { + uint64_t k = n.key_at(i); + if (k <= last_key) { + return false; + } + last_key = k; + } + + return true; + } + + transaction_manager::ptr + open_tm(block_manager<>::ptr bm) { + space_map::ptr sm(new core_map(bm->get_nr_blocks())); + sm->inc(superblock_detail::SUPERBLOCK_LOCATION); + transaction_manager::ptr tm(new transaction_manager(bm, sm)); + return tm; + } +} + +namespace { + // FIXME: deprecated conversion from string constant to ‘char*’ + char const* metadata_block_type_name[] = { + "unknown", + "zero", + "superblock", + "btree_internal", + "btree_leaf", + "btree_unknown", + "index_block", + "bitmap_block" + }; + + enum metadata_block_type { + UNKNOWN = 0, + ZERO, + SUPERBLOCK, + BTREE_INTERNAL, + BTREE_LEAF, + BTREE_UNKNOWN, + INDEX_BLOCK, + BITMAP_BLOCK + }; + + struct block_range { + uint64_t begin_; + uint64_t end_; // one-pass-the-end + boost::optional blocknr_begin_; + metadata_block_type type_; + int64_t ref_count_; // ref_count in metadata space map + size_t value_size_; // btree node only + bool is_valid_; // btree node only + + block_range() + : begin_(0), end_(0), + type_(UNKNOWN), ref_count_(-1), + value_size_(0), is_valid_(false) + { + } + + block_range(block_range const &rhs) + : begin_(rhs.begin_), end_(rhs.end_), + blocknr_begin_(rhs.blocknr_begin_), + type_(rhs.type_), ref_count_(rhs.ref_count_), + value_size_(rhs.value_size_), is_valid_(rhs.is_valid_) + { + } + }; + + void output_block_range(block_range const &r, std::ostream &out) { + if (r.end_ <= r.begin_) + return; + + if (r.end_ - r.begin_ > 1) { + out << "" << endl; + } else + out << "\"/>" << endl; + } + + //------------------------------------------------------------------- + + struct flags { + flags() { + } + + boost::optional scan_begin_; + boost::optional scan_end_; + }; + + int scan_metadata_(string const &input, + std::ostream &out, + flags const &f) { + using namespace persistent_data; + using namespace thin_provisioning; + using namespace sm_disk_detail; + + block_manager<>::ptr bm; + bm = open_bm(input, block_manager<>::READ_ONLY); + + block_address scan_begin = f.scan_begin_ ? *f.scan_begin_ : 0; + block_address scan_end = f.scan_end_ ? *f.scan_end_ : bm->get_nr_blocks(); + + const std::vector zeros(MD_BLOCK_SIZE, 0); + + // try to open metadata space-map (it's okay to fail) + // note: transaction_manager and space_map must be in the same scope + transaction_manager::ptr tm; + checked_space_map::ptr metadata_sm; + try { + superblock_detail::superblock sb = read_superblock(bm); + tm = open_tm(bm); + metadata_sm = open_metadata_sm(*tm, &sb.metadata_space_map_root_); + tm->set_sm(metadata_sm); + } catch (std::exception &e) { + cerr << e.what() << endl; + } + + block_range curr_range; + block_range run_range; + + bcache::validator::ptr sv = superblock_validator(); + bcache::validator::ptr nv = create_btree_node_validator(); + bcache::validator::ptr iv = index_validator(); + bcache::validator::ptr bv = bitmap_validator(); + + for (block_address b = scan_begin; b < scan_end; ++b) { + block_manager<>::read_ref rr = bm->read_lock(b); + + curr_range.begin_ = b; + curr_range.end_ = b + 1; + curr_range.blocknr_begin_ = boost::none; + curr_range.type_ = UNKNOWN; + curr_range.is_valid_ = false; + + if (!memcmp(rr.data(), zeros.data(), MD_BLOCK_SIZE)) + curr_range.type_ = ZERO; + + if (curr_range.type_ == UNKNOWN && sv->check_raw(rr.data())) { + curr_range.type_ = SUPERBLOCK; + curr_range.is_valid_ = true; + } + + if (curr_range.type_ == UNKNOWN && nv->check_raw(rr.data())) { + // note: check_raw() doesn't check node_header::blocknr_ + node_ref n = btree_detail::to_node(rr); + uint32_t flags = to_cpu(n.raw()->header.flags); + if ((flags & INTERNAL_NODE) && !(flags & LEAF_NODE)) + curr_range.type_ = BTREE_INTERNAL; + else if (flags & LEAF_NODE) + curr_range.type_ = BTREE_LEAF; + else + curr_range.type_ = BTREE_UNKNOWN; + + if (curr_range.type_ != BTREE_UNKNOWN && + check_block_nr(n) && + check_max_entries(n) && + check_nr_entries(n, true) && + check_ordered_keys(n)) + curr_range.is_valid_ = true; + else + curr_range.is_valid_ = false; + + curr_range.blocknr_begin_ = n.get_block_nr(); + curr_range.value_size_ = n.get_value_size(); + } + + if (curr_range.type_ == UNKNOWN && bv->check_raw(rr.data())) { + curr_range.type_ = BITMAP_BLOCK; + bitmap_header const *data = reinterpret_cast(rr.data()); + curr_range.blocknr_begin_ = to_cpu(data->blocknr); + curr_range.is_valid_ = (to_cpu(data->blocknr) == b) ? true : false; + } + + if (curr_range.type_ == UNKNOWN && iv->check_raw(rr.data())) { + curr_range.type_ = INDEX_BLOCK; + metadata_index const *mi = reinterpret_cast(rr.data()); + curr_range.blocknr_begin_ = to_cpu(mi->blocknr_); + curr_range.is_valid_ = (to_cpu(mi->blocknr_) == b) ? true : false; + } + + try { + curr_range.ref_count_ = metadata_sm ? + static_cast(metadata_sm->get_count(b)) : -1; + } catch (std::exception &e) { + curr_range.ref_count_ = -1; + } + + // output the current block + if (run_range.end_ == 0) + run_range = curr_range; + else if (((!curr_range.blocknr_begin_ && !run_range.blocknr_begin_) || + (curr_range.blocknr_begin_ && run_range.blocknr_begin_ && + *curr_range.blocknr_begin_ == *run_range.blocknr_begin_ + (run_range.end_ - run_range.begin_))) && + curr_range.type_ == run_range.type_ && + curr_range.ref_count_ == run_range.ref_count_ && + curr_range.value_size_ == run_range.value_size_ && + curr_range.is_valid_ == run_range.is_valid_) { + ++run_range.end_; + } else { + output_block_range(run_range, out); + run_range = curr_range; + } + } + + // output the last run + output_block_range(run_range, out); + + return 0; + } + + int scan_metadata(string const &input, + boost::optional output, + flags const &f) { + try { + if (output) { + std::ofstream out(output->c_str()); + scan_metadata_(input, out, f); + } else + scan_metadata_(input, cout, f); + } catch (std::exception &e) { + cerr << e.what() << endl; + return 1; + } + return 0; + } +} + +//--------------------------------------------------------------------------- + +thin_scan_cmd::thin_scan_cmd() + : command("thin_scan") +{ +} + +void +thin_scan_cmd::usage(std::ostream &out) const { + out << "Usage: " << get_name() << " [options] {device|file}" << endl + << "Options:" << endl + << " {-h|--help}" << endl + << " {-o|--output} " << endl + << " {--begin} " << endl + << " {--end} " << endl + << " {-V|--version}" << endl; +} + +int +thin_scan_cmd::run(int argc, char **argv) +{ + const char shortopts[] = "ho:V"; + const struct option longopts[] = { + { "help", no_argument, NULL, 'h'}, + { "output", required_argument, NULL, 'o'}, + { "version", no_argument, NULL, 'V'}, + { "begin", required_argument, NULL, 1}, + { "end", required_argument, NULL, 2}, + { NULL, no_argument, NULL, 0 } + }; + boost::optional output; + flags f; + + char c; + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 'h': + usage(cout); + return 0; + + case 'o': + output = optarg; + break; + + case 'V': + cout << THIN_PROVISIONING_TOOLS_VERSION << endl; + return 0; + + case 1: + try { + f.scan_begin_ = boost::lexical_cast(optarg); + } catch (std::exception &e) { + cerr << e.what() << endl; + return 1; + } + break; + + case 2: + try { + f.scan_end_ = boost::lexical_cast(optarg); + } catch (std::exception &e) { + cerr << e.what() << endl; + return 1; + } + break; + + default: + usage(cerr); + return 1; + } + } + + if (argc == optind) { + cerr << "No input file provided." << endl; + usage(cerr); + return 1; + } + + if (f.scan_begin_ && f.scan_end_ && (*f.scan_end_ <= *f.scan_begin_)) { + cerr << "badly formed region (end <= begin)" << endl; + return 1; + } + + return scan_metadata(argv[optind], output, f); +} + +//---------------------------------------------------------------------------