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);
+}
+
+//---------------------------------------------------------------------------