// 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_) { } uint64_t size() const { return (end_ > begin_) ? (end_ - begin_) : 0; } // returns true if r is left or right-adjacent bool is_adjacent_to(block_range const &r) const { block_range const &lhs = begin_ < r.begin_ ? *this : r; block_range const &rhs = begin_ < r.begin_ ? r : *this; uint64_t common_end = std::min(end_, r.end_); if (size() && r.size() && rhs.begin_ == common_end && ((!blocknr_begin_ && !r.blocknr_begin_) || (blocknr_begin_ && r.blocknr_begin_ && *rhs.blocknr_begin_ > *lhs.blocknr_begin_ && (*rhs.blocknr_begin_ - *lhs.blocknr_begin_ == rhs.begin_ - lhs.begin_))) && type_ == r.type_ && ref_count_ == r.ref_count_ && value_size_ == r.value_size_ && is_valid_ == r.is_valid_) return true; return false; } bool concat(block_range const &r) { if (!is_adjacent_to(r)) return false; begin_ = std::min(begin_, r.begin_); end_ = std::max(end_, r.end_); return true; } }; void output_block_range(block_range const &r, std::ostream &out) { if (!r.size()) 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; } // store the current block if (!run_range.concat(curr_range)) { 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); } //---------------------------------------------------------------------------