diff --git a/Makefile.in b/Makefile.in
index 931cb38..542154d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -89,6 +89,8 @@ SOURCE=\
thin-provisioning/thin_check.cc \
thin-provisioning/thin_delta.cc \
thin-provisioning/thin_dump.cc \
+ thin-provisioning/thin_ll_dump.cc \
+ thin-provisioning/thin_ll_restore.cc \
thin-provisioning/thin_ls.cc \
thin-provisioning/thin_metadata_size.cc \
thin-provisioning/thin_pool.cc \
diff --git a/thin-provisioning/commands.cc b/thin-provisioning/commands.cc
index 6d2f7e4..ba240c9 100644
--- a/thin-provisioning/commands.cc
+++ b/thin-provisioning/commands.cc
@@ -11,6 +11,8 @@ thin_provisioning::register_thin_commands(base::application &app)
app.add_cmd(command::ptr(new thin_check_cmd()));
app.add_cmd(command::ptr(new thin_delta_cmd()));
app.add_cmd(command::ptr(new thin_dump_cmd()));
+ app.add_cmd(command::ptr(new thin_ll_dump_cmd()));
+ app.add_cmd(command::ptr(new thin_ll_restore_cmd()));
app.add_cmd(command::ptr(new thin_ls_cmd()));
app.add_cmd(command::ptr(new thin_metadata_size_cmd()));
app.add_cmd(command::ptr(new thin_restore_cmd()));
diff --git a/thin-provisioning/commands.h b/thin-provisioning/commands.h
index a8fa4a4..d6ed78f 100644
--- a/thin-provisioning/commands.h
+++ b/thin-provisioning/commands.h
@@ -28,6 +28,20 @@ namespace thin_provisioning {
virtual int run(int argc, char **argv);
};
+ class thin_ll_dump_cmd : public base::command {
+ public:
+ thin_ll_dump_cmd();
+ virtual void usage(std::ostream &out) const;
+ virtual int run(int argc, char **argv);
+ };
+
+ class thin_ll_restore_cmd : public base::command {
+ public:
+ thin_ll_restore_cmd();
+ virtual void usage(std::ostream &out) const;
+ virtual int run(int argc, char **argv);
+ };
+
class thin_ls_cmd : public base::command {
public:
thin_ls_cmd();
diff --git a/thin-provisioning/thin_ll_dump.cc b/thin-provisioning/thin_ll_dump.cc
new file mode 100644
index 0000000..7f7b14c
--- /dev/null
+++ b/thin-provisioning/thin_ll_dump.cc
@@ -0,0 +1,473 @@
+// 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 "base/indented_stream.h"
+#include "persistent-data/file_utils.h"
+#include "persistent-data/data-structures/btree.h"
+#include "persistent-data/data-structures/btree_counter.h"
+#include "persistent-data/data-structures/simple_traits.h"
+#include "persistent-data/space-maps/core.h"
+#include "persistent-data/space-maps/disk_structures.h"
+#include "thin-provisioning/metadata.h"
+#include "thin-provisioning/metadata_counter.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 {
+ struct node_info {
+ uint64_t blocknr_;
+ uint32_t flags_;
+ uint64_t key_begin_;
+ uint64_t key_end_;
+ uint64_t nr_entries_;
+ uint32_t value_size_;
+ };
+
+ //-------------------------------------------------------------------
+
+ struct btree_node_checker {
+ typedef boost::shared_ptr ptr;
+ virtual ~btree_node_checker() {}
+ virtual bool check(node_ref &n) = 0;
+ };
+
+ struct unvisited_btree_node_filter: public btree_node_checker {
+ unvisited_btree_node_filter(block_counter const &bc)
+ : nv_(create_btree_node_validator()), bc_(bc) {
+ }
+
+ virtual bool check(node_ref &n) {
+ uint32_t flags = to_cpu(n.raw()->header.flags);
+ if ((n.get_value_size() == sizeof(mapping_tree_detail::block_traits::disk_type) ||
+ n.get_value_size() == sizeof(device_tree_detail::device_details_traits::disk_type)) &&
+ !bc_.get_count(n.get_location()) &&
+ check_block_nr(n) &&
+ (((flags & INTERNAL_NODE) && !(flags & LEAF_NODE)) ||
+ (flags & LEAF_NODE)) &&
+ nv_->check_raw(n.raw()) &&
+ check_max_entries(n) &&
+ check_nr_entries(n, true) &&
+ check_ordered_keys(n))
+ return true;
+ return false;
+ }
+
+ bcache::validator::ptr nv_;
+ block_counter const &bc_;
+ };
+
+ //-------------------------------------------------------------------
+
+ void find_btree_nodes(block_manager<>::ptr bm,
+ block_address begin,
+ block_address end,
+ btree_node_checker::ptr checker,
+ base::run_set &found) {
+ using namespace persistent_data;
+
+ for (block_address b = begin; b < end; ++b) {
+ block_manager<>::read_ref rr = bm->read_lock(b);
+ node_ref n = btree_detail::to_node(rr);
+
+ if (checker->check(n))
+ found.add(b);
+ }
+ }
+
+ //-------------------------------------------------------------------
+
+ bool first_key_cmp(node_info const &lhs, node_info const &rhs) {
+ return lhs.key_begin_ < rhs.key_begin_;
+ }
+
+ template
+ void convert_to_node_info(node_ref const &n, node_info &ni) {
+ ni.blocknr_ = n.get_location();
+ ni.flags_ = to_cpu(n.raw()->header.flags);
+ if ((ni.nr_entries_ = n.get_nr_entries()) > 0) {
+ ni.key_begin_ = n.key_at(0);
+ ni.key_end_ = n.key_at(n.get_nr_entries() - 1);
+ }
+ ni.value_size_ = n.get_value_size();
+ }
+
+ void output_node_info(indented_stream &out, node_info const &ni) {
+ out.indent();
+ out << "" << endl;
+ }
+
+ //-------------------------------------------------------------------
+
+ class ll_mapping_tree_emitter : public mapping_tree_detail::device_visitor {
+ public:
+ ll_mapping_tree_emitter(block_manager<>::ptr bm,
+ indented_stream &out)
+ : bm_(bm), out_(out) {
+ }
+
+ void visit(btree_path const &path, block_address tree_root) {
+ out_.indent();
+ out_ << "" << endl;
+ out_.inc();
+
+ // Do not throw exception. Process the next entry inside the current node.
+ try {
+ block_manager<>::read_ref rr = bm_->read_lock(tree_root);
+ node_ref n = btree_detail::to_node(rr);
+ node_info ni;
+ convert_to_node_info(n, ni);
+ output_node_info(out_, ni);
+ } catch (std::exception &e) {
+ cerr << e.what() << endl;
+ }
+
+ out_.dec();
+ out_.indent();
+ out_ << "" << endl;
+ }
+ private:
+ block_manager<>::ptr bm_;
+ indented_stream& out_;
+ };
+
+ //-------------------------------------------------------------------
+
+ struct flags {
+ flags() : use_metadata_snap_(false) {
+ }
+
+ bool use_metadata_snap_;
+ boost::optional metadata_snap_;
+ boost::optional data_mapping_root_;
+ boost::optional device_details_root_;
+ boost::optional scan_begin_;
+ boost::optional scan_end_;
+ };
+
+ int low_level_dump_(string const &input,
+ std::ostream &output,
+ flags const &f) {
+ block_manager<>::ptr 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();
+
+ // Allow to read superblock at arbitrary location for low-level dump,
+ // without checking equality between the given metadata_snap and sb.metadata_snap_
+ superblock_detail::superblock sb = read_superblock(bm, superblock_detail::SUPERBLOCK_LOCATION);
+ if (f.use_metadata_snap_) {
+ sb = f.metadata_snap_ ?
+ read_superblock(bm, *f.metadata_snap_) :
+ read_superblock(bm, sb.metadata_snap_);
+ }
+ // override sb.data_mapping_root_
+ if (f.data_mapping_root_)
+ sb.data_mapping_root_ = *f.data_mapping_root_;
+ // override sb.device_details_root_
+ if (f.device_details_root_)
+ sb.device_details_root_ = *f.device_details_root_;
+
+ transaction_manager::ptr tm = open_tm(bm);
+
+ indented_stream out(output);
+
+ out.indent();
+ out << "" << endl;
+ out.inc();
+
+ // output the top-level data mapping tree
+ ll_mapping_tree_emitter ll_mte(tm->get_bm(), out);
+ dev_tree dtree(*tm, sb.data_mapping_root_,
+ mapping_tree_detail::mtree_traits::ref_counter(tm));
+ noop_damage_visitor noop_dv;
+ btree_visit_values(dtree, ll_mte, noop_dv);
+
+ out.dec();
+ out.indent();
+ out << "" << endl;
+
+ // find orphans
+ binary_block_counter bc;
+ bc.inc(superblock_detail::SUPERBLOCK_LOCATION);
+ count_metadata(tm, sb, bc, true);
+ btree_node_checker::ptr filter = btree_node_checker::ptr(
+ new unvisited_btree_node_filter(bc));
+ base::run_set orphans;
+ find_btree_nodes(bm, scan_begin, scan_end, filter, orphans);
+
+ // sort orphans
+ std::vector nodes;
+ for (base::run_set::const_iterator it = orphans.begin();
+ it != orphans.end();
+ ++it) {
+ if (it->begin_ && it->end_) {
+ for (block_address b = *it->begin_; b < *it->end_; ++b) {
+ block_manager<>::read_ref rr = bm->read_lock(b);
+ node_ref n = btree_detail::to_node(rr);
+ nodes.push_back(node_info());
+ convert_to_node_info(n, nodes.back());
+ }
+ }
+ }
+ std::sort(nodes.begin(), nodes.end(), first_key_cmp);
+
+ // output orphans
+ out.indent();
+ out << "" << std::endl;
+ out.inc();
+
+ for (size_t i = 0; i < nodes.size(); ++i)
+ output_node_info(out, nodes[i]);
+
+ out.dec();
+ out.indent();
+ out << "" << std::endl;
+
+ return 0;
+ }
+
+ int low_level_dump(string const &input,
+ boost::optional output,
+ flags const &f) {
+ try {
+ if (output) {
+ ofstream out(output->c_str());
+ low_level_dump_(input, out, f);
+ } else
+ low_level_dump_(input, cout, f);
+ } catch (std::exception &e) {
+ cerr << e.what() << endl;
+ return 1;
+ }
+ return 0;
+ }
+}
+
+//---------------------------------------------------------------------------
+
+thin_ll_dump_cmd::thin_ll_dump_cmd()
+ : command("thin_ll_dump")
+{
+}
+
+void
+thin_ll_dump_cmd::usage(ostream &out) const {
+ out << "Usage: " << get_name() << " [options] {device|file}" << endl
+ << "Options:" << endl
+ << " {-h|--help}" << endl
+ << " {-m|--metadata-snap}[block#]" << endl
+ << " {-o|--output} " << endl
+ << " {--begin} " << endl
+ << " {--end} " << endl
+ << " {--data-mapping-root} " << endl
+ << " {--device-details-root} " << endl
+ << " {-V|--version}" << endl;
+}
+
+int
+thin_ll_dump_cmd::run(int argc, char **argv)
+{
+ const char shortopts[] = "hm:o:V";
+ const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h'},
+ { "metadata-snap", optional_argument, NULL, 'm'},
+ { "output", required_argument, NULL, 'o'},
+ { "version", no_argument, NULL, 'V'},
+ { "begin", required_argument, NULL, 1},
+ { "end", required_argument, NULL, 2},
+ { "data-mapping-root", required_argument, NULL, 3},
+ { "device-details-root", required_argument, NULL, 4},
+ { 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 'm':
+ f.use_metadata_snap_ = true;
+ if (optarg) {
+ try {
+ f.metadata_snap_ = boost::lexical_cast(optarg);
+ } catch (std::exception &e) {
+ cerr << e.what() << endl;
+ return 1;
+ }
+ }
+ break;
+
+ 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;
+
+ case 3:
+ try {
+ f.data_mapping_root_ = boost::lexical_cast(optarg);
+ } catch (std::exception &e) {
+ cerr << e.what() << endl;
+ return 1;
+ }
+ break;
+
+ case 4:
+ try {
+ f.device_details_root_ = 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;
+ usage(cerr);
+ return 1;
+ }
+
+ return low_level_dump(argv[optind], output, f);
+}
+
+//---------------------------------------------------------------------------
diff --git a/thin-provisioning/thin_ll_restore.cc b/thin-provisioning/thin_ll_restore.cc
new file mode 100644
index 0000000..1168579
--- /dev/null
+++ b/thin-provisioning/thin_ll_restore.cc
@@ -0,0 +1,276 @@
+// 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 "base/xml_utils.h"
+#include "metadata_dumper.h"
+#include "metadata.h"
+#include "persistent-data/file_utils.h"
+#include "persistent-data/space-maps/disk_structures.h"
+#include "restore_emitter.h"
+#include "xml_format.h"
+#include "thin-provisioning/commands.h"
+#include "version.h"
+
+#include
+#include
+#include
+
+using namespace persistent_data;
+using namespace std;
+using namespace thin_provisioning;
+using namespace xml_utils;
+
+//----------------------------------------------------------------
+
+namespace {
+ struct user_data {
+ block_manager<>::ptr input_bm_;
+ block_manager<>::ptr output_bm_;
+
+ metadata::ptr md_;
+ XML_Parser parser_;
+ emitter::ptr emitter_;
+ };
+
+ void open_resources(user_data &ud, attributes const &attr) {
+ boost::optional val;
+
+ // open the input metadata
+ // Allow to read superblock at arbitrary location for low-level restore
+ block_address sb_location = (val = get_opt_attr(attr, "blocknr")) ?
+ *val : superblock_detail::SUPERBLOCK_LOCATION;
+ ud.md_ = metadata::ptr(new metadata(ud.input_bm_, sb_location));
+
+ // override superblock::device_details_root_
+ if ((val = get_opt_attr(attr, "device_details_root"))) {
+ ud.md_->sb_.device_details_root_ = *val;
+ ud.md_->details_ = device_tree::ptr(new device_tree(*ud.md_->tm_, *val,
+ device_tree_detail::device_details_traits::ref_counter()));
+ }
+
+ // open the output metadata
+ metadata::ptr new_md(new metadata(ud.output_bm_, metadata::CREATE, 128, 0));
+
+ ud.emitter_ = create_restore_emitter(new_md);
+ }
+
+ void parse_superblock(metadata::ptr md, emitter::ptr e, attributes const &attr) {
+ sm_disk_detail::sm_root_disk const *d =
+ reinterpret_cast(md->sb_.data_space_map_root_);
+ sm_disk_detail::sm_root v;
+ sm_disk_detail::sm_root_traits::unpack(*d, v);
+
+ e->begin_superblock("", md->sb_.time_,
+ md->sb_.trans_id_,
+ md->sb_.flags_,
+ md->sb_.version_,
+ md->sb_.data_block_size_,
+ v.nr_blocks_,
+ boost::optional());
+ }
+
+ void parse_device(metadata::ptr md, emitter::ptr e, attributes const &attr) {
+ uint32_t dev_id = get_attr(attr, "dev_id");
+ device_tree_detail::device_details details;
+ details.transaction_id_ = 0;
+ details.creation_time_ = 0;
+ details.snapshotted_time_ = 0;
+
+ device_tree::ptr details_tree;
+ boost::optional details_root = get_opt_attr(attr, "blocknr");
+ if (details_root)
+ details_tree = device_tree::ptr(new device_tree(*md->tm_, *details_root,
+ device_tree_detail::device_details_traits::ref_counter()));
+ else
+ details_tree = md->details_;
+
+ uint64_t key[1] = {dev_id};
+ device_tree::maybe_value v;
+ try {
+ v = details_tree->lookup(key);
+ } catch (std::exception &e) {
+ cerr << "missing device " << dev_id << ": " << e.what() << endl;
+ }
+ if (v)
+ details = *v;
+
+ e->begin_device(dev_id,
+ 0,
+ details.transaction_id_,
+ details.creation_time_,
+ details.snapshotted_time_);
+ }
+
+ void parse_node(metadata::ptr md, emitter::ptr e, attributes const &attr) {
+ metadata_dump_subtree(md, e, true, get_attr(attr, "blocknr"));
+ }
+
+ void start_tag(void *data, char const *el, char const **attr) {
+ user_data *ud = static_cast(data);
+ attributes a;
+
+ build_attributes(a, attr);
+
+ if (!strcmp(el, "superblock")) {
+ open_resources(*ud, a);
+ parse_superblock(ud->md_, ud->emitter_, a);
+
+ } else if (!strcmp(el, "device"))
+ parse_device(ud->md_, ud->emitter_, a);
+
+ else if (!strcmp(el, "node"))
+ parse_node(ud->md_, ud->emitter_, a);
+
+ else
+ throw runtime_error("unknown tag type");
+ }
+
+ void end_tag(void *data, const char *el) {
+ user_data *ud = static_cast(data);
+
+ if (!strcmp(el, "superblock")) {
+ ud->emitter_->end_superblock();
+ XML_StopParser(ud->parser_, XML_FALSE); // skip the rest elements
+ }
+
+ else if (!strcmp(el, "device"))
+ ud->emitter_->end_device();
+
+ else if (!strcmp(el, "node"))
+ ;
+
+ else
+ throw runtime_error("unknown tag type");
+ }
+}
+
+//---------------------------------------------------------------------------
+
+namespace {
+ struct flags {
+ flags() {
+ }
+ };
+
+ int low_level_restore_(string const &src_metadata, string const &input,
+ string const &output, flags const &f) {
+ user_data ud;
+ ud.input_bm_ = open_bm(src_metadata, block_manager<>::READ_ONLY);
+ ud.output_bm_ = open_bm(output, block_manager<>::READ_WRITE);
+
+ xml_parser p;
+ ud.parser_ = p.get_parser();
+
+ XML_SetUserData(p.get_parser(), &ud);
+ XML_SetElementHandler(p.get_parser(), start_tag, end_tag);
+
+ bool quiet = true;
+ p.parse(input, quiet);
+
+ return 0;
+ }
+
+ int low_level_restore(string const &src_metadata, string const &input,
+ string const &output, flags const &f) {
+ try {
+ low_level_restore_(src_metadata, input, output, f);
+ } catch (std::exception &e) {
+ cerr << e.what() << endl;
+ return 1;
+ }
+ return 0;
+ }
+}
+
+//---------------------------------------------------------------------------
+
+thin_ll_restore_cmd::thin_ll_restore_cmd()
+ : command("thin_ll_restore")
+{
+}
+
+void
+thin_ll_restore_cmd::usage(ostream &out) const {
+ out << "Usage: " << get_name() << " [options]" << endl
+ << "Options:" << endl
+ << " {-h|--help}" << endl
+ << " {-E|--source-metadata} " << endl
+ << " {-i|--input} " << endl
+ << " {-o|--output}