2011-12-16 00:04:31 +05:30
|
|
|
// Copyright (C) 2011 Red Hat, Inc. All rights reserved.
|
2011-12-06 19:23:05 +05:30
|
|
|
//
|
2011-12-06 19:13:56 +05:30
|
|
|
// 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
|
|
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
#include "thin-provisioning/emitter.h"
|
|
|
|
#include "thin-provisioning/metadata_dumper.h"
|
2013-05-20 22:07:46 +05:30
|
|
|
#include "thin-provisioning/mapping_tree.h"
|
2019-04-17 16:47:13 +05:30
|
|
|
#include "persistent-data/data-structures/simple_traits.h"
|
|
|
|
#include "persistent-data/file_utils.h"
|
2011-10-10 18:40:30 +05:30
|
|
|
|
2019-03-29 18:26:31 +05:30
|
|
|
#include <map>
|
|
|
|
#include <vector>
|
|
|
|
|
2019-05-27 20:36:59 +05:30
|
|
|
using namespace boost;
|
2011-10-10 18:40:30 +05:30
|
|
|
using namespace persistent_data;
|
|
|
|
using namespace thin_provisioning;
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
#define SHOW_WORKING 0
|
|
|
|
|
2011-10-10 18:40:30 +05:30
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
namespace {
|
|
|
|
void raise_metadata_damage() {
|
|
|
|
throw std::runtime_error("metadata contains errors (run thin_check for details).\n"
|
|
|
|
"perhaps you wanted to run with --repair");
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
struct ignore_details_damage : public device_tree_detail::damage_visitor {
|
|
|
|
void visit(device_tree_detail::missing_devices const &d) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct fatal_details_damage : public device_tree_detail::damage_visitor {
|
|
|
|
void visit(device_tree_detail::missing_devices const &d) {
|
|
|
|
raise_metadata_damage();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
device_tree_detail::damage_visitor::ptr details_damage_policy(bool repair) {
|
|
|
|
typedef device_tree_detail::damage_visitor::ptr dvp;
|
|
|
|
|
|
|
|
if (repair)
|
|
|
|
return dvp(new ignore_details_damage());
|
|
|
|
else
|
|
|
|
return dvp(new fatal_details_damage());
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
struct ignore_mapping_damage : public mapping_tree_detail::damage_visitor {
|
|
|
|
void visit(mapping_tree_detail::missing_devices const &d) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void visit(mapping_tree_detail::missing_mappings const &d) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct fatal_mapping_damage : public mapping_tree_detail::damage_visitor {
|
|
|
|
void visit(mapping_tree_detail::missing_devices const &d) {
|
|
|
|
raise_metadata_damage();
|
|
|
|
}
|
|
|
|
|
|
|
|
void visit(mapping_tree_detail::missing_mappings const &d) {
|
|
|
|
raise_metadata_damage();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
mapping_tree_detail::damage_visitor::ptr mapping_damage_policy(bool repair) {
|
|
|
|
typedef mapping_tree_detail::damage_visitor::ptr mvp;
|
|
|
|
|
|
|
|
if (repair)
|
|
|
|
return mvp(new ignore_mapping_damage());
|
|
|
|
else
|
|
|
|
return mvp(new fatal_mapping_damage());
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
typedef map<block_address, device_tree_detail::device_details> dd_map;
|
|
|
|
|
|
|
|
class details_extractor : public device_tree_detail::device_visitor {
|
|
|
|
public:
|
|
|
|
details_extractor(dump_options const &opts)
|
|
|
|
: opts_(opts) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void visit(block_address dev_id, device_tree_detail::device_details const &dd) {
|
|
|
|
if (opts_.selected_dev(dev_id))
|
|
|
|
dd_.insert(make_pair(dev_id, dd));
|
|
|
|
}
|
|
|
|
|
|
|
|
dd_map const &get_details() const {
|
|
|
|
return dd_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
dump_options const &opts_;
|
|
|
|
dd_map dd_;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct d_thin_id_extractor : public device_tree_detail::device_visitor {
|
|
|
|
void visit(block_address dev_id, device_tree_detail::device_details const &dd) {
|
|
|
|
dd_.insert(dev_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
set<uint32_t> dd_;
|
|
|
|
};
|
|
|
|
|
2019-05-27 20:36:59 +05:30
|
|
|
// See comment on get_map_ids
|
|
|
|
optional<set<uint32_t> >
|
2019-04-17 16:47:13 +05:30
|
|
|
get_dev_ids(transaction_manager &tm, block_address root) {
|
|
|
|
d_thin_id_extractor de;
|
|
|
|
fatal_details_damage dv;
|
|
|
|
auto tree = device_tree(tm, root, device_tree_detail::device_details_traits::ref_counter());
|
2019-05-27 20:36:59 +05:30
|
|
|
|
|
|
|
try {
|
|
|
|
walk_device_tree(tree, de, dv);
|
|
|
|
} catch (...) {
|
|
|
|
return optional<set<uint32_t>>();
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return de.dd_;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct m_thin_id_extractor : public mapping_tree_detail::device_visitor {
|
|
|
|
void visit(btree_path const &path, block_address dtree_root) {
|
|
|
|
dd_.insert(path[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
set<uint32_t> dd_;
|
|
|
|
};
|
2019-05-27 20:36:59 +05:30
|
|
|
|
|
|
|
// The walk will do more sanity checks than we did when scanning the metadata, so
|
|
|
|
// it's possible that it will fail and throw a metadata damage exception.
|
|
|
|
optional<set<uint32_t> >
|
2019-04-17 16:47:13 +05:30
|
|
|
get_map_ids(transaction_manager &tm, block_address root) {
|
|
|
|
m_thin_id_extractor me;
|
|
|
|
fatal_mapping_damage mv;
|
|
|
|
auto tree = dev_tree(tm, root, mapping_tree_detail::mtree_traits::ref_counter(tm));
|
2019-05-27 20:36:59 +05:30
|
|
|
|
|
|
|
try {
|
|
|
|
walk_mapping_tree(tree, me, mv);
|
|
|
|
} catch (...) {
|
|
|
|
return optional<set<uint32_t>>();
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return me.dd_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 18:26:31 +05:30
|
|
|
// We only need to examine the mapping tree, and device details tree.
|
|
|
|
// The space maps can be inferred.
|
|
|
|
|
|
|
|
// Repair process:
|
|
|
|
// - We only trigger the repair process if there's damage when walking from
|
|
|
|
// the roots given in the superblock.
|
|
|
|
// - If there is damage, then we try and find the most recent roots with the
|
|
|
|
// least corruption. We're seeing cases where just the superblock has been
|
|
|
|
// trashed so finding the best roots is essential, and sadly non trivial.
|
|
|
|
|
|
|
|
// Finding roots:
|
|
|
|
// This is about classifying and summarising btree nodes. The use of a btree
|
|
|
|
// node may not be obvious when inspecting it in isolation. But more information
|
|
|
|
// may be gleaned by examining child and sibling nodes.
|
|
|
|
//
|
|
|
|
// So the process is:
|
|
|
|
// - scan every metadata block, summarising it's potential uses.
|
|
|
|
// - repeatedly iterate those summaries until we can glean no more useful information.
|
|
|
|
// - sort candidate roots, choose best
|
|
|
|
|
|
|
|
// Summary information:
|
|
|
|
// - btree; mapping top level, mapping bottom level, device tree (more than one possible)
|
|
|
|
// - node type; internal or leaf
|
|
|
|
// - age; for mapping trees we can infer a minimum age from the block/time
|
|
|
|
// values. In addition two similar leaf nodes can be compared by looking
|
|
|
|
// at the block/time for _specific_ blocks. This means we can define an ordering
|
|
|
|
// on the ages, but not equality.
|
|
|
|
// - Device details can be aged based on the last_snapshot_time field.
|
|
|
|
|
|
|
|
// Iteration of summary info:
|
|
|
|
// - constraints propagate both up and down the trees. eg, node 'a' may
|
|
|
|
// be ambiguous (all internal nodes are ambigous). If we find that all it's
|
|
|
|
// children are device details trees, then we infer that this is too and lose
|
|
|
|
// the ambiguity. Now if it has a sibling we can infer on this too.
|
|
|
|
// - Some characteristics only propagate upwards. eg, age. So we need two monoids
|
|
|
|
// for summary info (up and down).
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
using namespace std;
|
|
|
|
using namespace boost;
|
|
|
|
using namespace persistent_data::btree_detail;
|
|
|
|
using namespace thin_provisioning::device_tree_detail;
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
enum btree_type {
|
2019-03-29 18:26:31 +05:30
|
|
|
TOP_LEVEL,
|
|
|
|
BOTTOM_LEVEL,
|
|
|
|
DEVICE_DETAILS
|
|
|
|
};
|
|
|
|
|
|
|
|
struct node_info {
|
|
|
|
node_info()
|
2019-04-17 16:47:13 +05:30
|
|
|
: valid(true),
|
|
|
|
type(TOP_LEVEL),
|
2019-03-29 18:26:31 +05:30
|
|
|
b(0),
|
|
|
|
values(0),
|
|
|
|
key_low(0),
|
|
|
|
key_high(0),
|
2019-04-17 16:47:13 +05:30
|
|
|
age(0),
|
|
|
|
nr_mappings(0) {
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
bool valid;
|
|
|
|
btree_type type;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
|
|
|
block_address b;
|
|
|
|
unsigned values;
|
|
|
|
uint64_t key_low;
|
|
|
|
uint64_t key_high;
|
2019-04-17 16:47:13 +05:30
|
|
|
//set<uint32_t> devices;
|
2019-03-29 18:26:31 +05:30
|
|
|
uint32_t age;
|
2019-04-17 16:47:13 +05:30
|
|
|
map<uint32_t, uint32_t> time_counts;
|
|
|
|
unsigned nr_mappings;
|
2019-03-29 18:26:31 +05:30
|
|
|
};
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
#if SHOW_WORKING
|
|
|
|
ostream &operator <<(ostream &out, node_info const &n) {
|
|
|
|
out << "b=" << n.b << ", valid=" << n.valid << ", type=" << n.type << ", values=" << n.values;
|
|
|
|
out << ", nr_mapped=" << n.nr_mappings;
|
|
|
|
for (auto const &p : n.time_counts)
|
|
|
|
out << ", t" << p.first << "=" << p.second;
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
#endif
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
bool cmp_time_counts(pair<node_info, node_info> const &lhs_pair,
|
|
|
|
pair<node_info, node_info> const &rhs_pair) {
|
|
|
|
auto const &lhs = lhs_pair.first.time_counts;
|
|
|
|
auto const &rhs = rhs_pair.first.time_counts;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (auto lhs_it = lhs.crbegin(); lhs_it != lhs.crend(); lhs_it++) {
|
|
|
|
for (auto rhs_it = rhs.crbegin(); rhs_it != rhs.crend(); rhs_it++) {
|
|
|
|
if (lhs_it->first > rhs_it->first)
|
|
|
|
return true;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
else if (rhs_it->first > lhs_it->first)
|
|
|
|
return false;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
else if (lhs_it->second > rhs_it->second)
|
|
|
|
return true;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
else if (rhs_it->second > lhs_it->second)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return true;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
class gatherer {
|
|
|
|
public:
|
|
|
|
gatherer(block_manager<> &bm)
|
|
|
|
: bm_(bm),
|
|
|
|
referenced_(bm.get_nr_blocks(), false),
|
|
|
|
examined_(bm.get_nr_blocks(), false) {
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
optional<pair<block_address, block_address>>
|
|
|
|
find_best_roots(transaction_manager &tm) {
|
|
|
|
vector<node_info> mapping_roots;
|
|
|
|
vector<node_info> device_roots;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
auto nr_blocks = bm_.get_nr_blocks();
|
|
|
|
for (block_address b = 0; b < nr_blocks; b++)
|
|
|
|
get_info(b);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (block_address b = 0; b < nr_blocks; b++) {
|
|
|
|
if (referenced(b))
|
|
|
|
continue;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
auto info = get_info(b);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (info.valid) {
|
|
|
|
if (info.type == TOP_LEVEL)
|
|
|
|
mapping_roots.push_back(info);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
else if (info.type == DEVICE_DETAILS)
|
|
|
|
device_roots.push_back(info);
|
|
|
|
}
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
#if SHOW_WORKING
|
|
|
|
cerr << "mapping candidates (" << mapping_roots.size() << "):\n";
|
|
|
|
for (auto const &i : mapping_roots)
|
|
|
|
cerr << i << "\n";
|
|
|
|
|
|
|
|
cerr << "\ndevice candidates (" << device_roots.size() << "):\n";
|
|
|
|
for (auto const &i : device_roots)
|
|
|
|
cerr << i << "\n";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
auto pairs = find_compatible_roots(tm, device_roots, mapping_roots);
|
|
|
|
|
|
|
|
#if SHOW_WORKING
|
|
|
|
for (auto const &p : pairs)
|
|
|
|
cerr << "(" << p.first << ", " << p.second << ")\n";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (pairs.size())
|
|
|
|
return pairs[0];
|
|
|
|
else
|
|
|
|
return optional<pair<block_address, block_address>>();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool set_eq(set<uint32_t> const &lhs, set<uint32_t> const &rhs) {
|
|
|
|
for (auto v : lhs)
|
|
|
|
if (!rhs.count(v))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<pair<block_address, block_address> >
|
|
|
|
find_compatible_roots(transaction_manager &tm,
|
|
|
|
vector<node_info> const &device_roots,
|
|
|
|
vector<node_info> const &mapping_roots) {
|
|
|
|
vector<pair<node_info, node_info>> pairs;
|
|
|
|
set<block_address> d_roots;
|
|
|
|
set<block_address> m_roots;
|
|
|
|
|
|
|
|
// construct pairs that have the same number of entries
|
|
|
|
for (auto const &di : device_roots)
|
|
|
|
for (auto const &mi : mapping_roots)
|
|
|
|
if (di.values == mi.values && di.nr_mappings == mi.nr_mappings) {
|
|
|
|
pairs.push_back(make_pair(di, mi));
|
|
|
|
d_roots.insert(di.b);
|
|
|
|
m_roots.insert(mi.b);
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
sort(pairs.begin(), pairs.end(), cmp_time_counts);
|
|
|
|
|
|
|
|
map<block_address, set<uint32_t>> ds;
|
2019-05-27 20:36:59 +05:30
|
|
|
for (auto b : d_roots) {
|
|
|
|
auto maybe_ids = get_dev_ids(tm, b);
|
|
|
|
if (maybe_ids)
|
|
|
|
ds.insert(make_pair(b, *maybe_ids));
|
|
|
|
}
|
2019-04-17 16:47:13 +05:30
|
|
|
|
|
|
|
map<block_address, set<uint32_t>> ms;
|
2019-05-27 20:36:59 +05:30
|
|
|
for (auto b : m_roots) {
|
|
|
|
auto maybe_ids = get_map_ids(tm, b);
|
|
|
|
if (maybe_ids)
|
|
|
|
ms.insert(make_pair(b, *maybe_ids));
|
|
|
|
}
|
2019-04-17 16:47:13 +05:30
|
|
|
|
|
|
|
// now we check that the thin_ids are identical
|
|
|
|
vector<pair<block_address, block_address>> filtered;
|
|
|
|
for (auto const &p : pairs) {
|
|
|
|
auto lhs = ds.find(p.first.b);
|
|
|
|
if (lhs == ds.end())
|
2019-03-29 18:26:31 +05:30
|
|
|
continue;
|
2019-04-17 16:47:13 +05:30
|
|
|
|
|
|
|
auto rhs = ms.find(p.second.b);
|
|
|
|
if (rhs == ms.end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
filtered.push_back(make_pair(p.first.b, p.second.b));
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
|
|
|
|
return filtered;
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
void mark_referenced(block_address b) {
|
|
|
|
referenced_[b] = true;
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
bool referenced(block_address b) const {
|
|
|
|
return referenced_[b];
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
bool is_btree_node(block_address b) {
|
|
|
|
auto v = create_btree_node_validator();
|
|
|
|
auto rr = bm_.read_lock(b);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return v->check_raw(rr.data());
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
// The bottom layer has the block time encoded in it, with the time
|
|
|
|
// in the bottom 24 bits. This means every block/time apart from block 0
|
|
|
|
// will result in a value that's outside the range of the metadata device.
|
|
|
|
bool is_top_level(node_ref<uint64_traits> &n) {
|
|
|
|
auto nr_metadata_blocks = bm_.get_nr_blocks();
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++)
|
|
|
|
if (n.value_at(i) >= nr_metadata_blocks)
|
|
|
|
return false;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return true;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
uint32_t get_dd_age(device_details const &dd) {
|
|
|
|
return max(dd.creation_time_, dd.snapshotted_time_);
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
void fail(node_info &n, const char *reason) {
|
|
|
|
// cerr << n.b << " failed: " << reason << "\n";
|
|
|
|
n.valid = false;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
bool failed(node_info const &n) {
|
|
|
|
return !n.valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void inc_time_count(map<uint32_t, uint32_t> &counts, uint32_t time) {
|
|
|
|
auto it = counts.find(time);
|
|
|
|
if (it == counts.end()) {
|
|
|
|
counts.insert(make_pair(time, 1));
|
|
|
|
} else
|
|
|
|
it->second++;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
void merge_time_counts(map<uint32_t, uint32_t> &lhs, map<uint32_t, uint32_t> const &rhs) {
|
|
|
|
for (auto const &p : rhs) {
|
|
|
|
auto it = lhs.find(p.first);
|
|
|
|
if (it == lhs.end())
|
|
|
|
lhs.insert(p);
|
|
|
|
else
|
|
|
|
it->second += p.second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
node_info get_internal_info(block_manager<>::read_ref &rr) {
|
|
|
|
node_info info;
|
|
|
|
info.b = rr.get_location();
|
|
|
|
|
2019-03-29 18:26:31 +05:30
|
|
|
// values refer to blocks, so we should have infos for them.
|
|
|
|
auto n = to_node<block_traits>(rr);
|
|
|
|
uint64_t key_low = 0;
|
|
|
|
unsigned values = 0;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++) {
|
2019-04-17 16:47:13 +05:30
|
|
|
auto child = get_info(n.value_at(i));
|
|
|
|
if (failed(child)) {
|
|
|
|
fail(info, "child failed");
|
2019-03-29 18:26:31 +05:30
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (!i)
|
|
|
|
info.type = child.type;
|
|
|
|
|
|
|
|
else if (info.type != child.type) {
|
|
|
|
fail(info, "mismatch types");
|
|
|
|
break;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
|
|
|
// we use the keys to help decide if this is a valid child
|
2019-04-17 16:47:13 +05:30
|
|
|
if (key_low && child.key_low <= key_low) {
|
|
|
|
fail(info, "bad keys");
|
2019-03-29 18:26:31 +05:30
|
|
|
break;
|
|
|
|
|
|
|
|
} else
|
|
|
|
key_low = child.key_high;
|
|
|
|
|
|
|
|
values += child.values;
|
2019-04-17 16:47:13 +05:30
|
|
|
merge_time_counts(info.time_counts, child.time_counts);
|
|
|
|
info.age = max(info.age, child.age);
|
|
|
|
info.nr_mappings += child.nr_mappings;
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
// We don't clear the orphan flags until we know the parent is good
|
2019-04-17 16:47:13 +05:30
|
|
|
if (!failed(info)) {
|
|
|
|
info.values = values;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++)
|
|
|
|
mark_referenced(n.value_at(i));
|
2019-03-29 18:26:31 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return info;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
node_info get_leaf_info(block_manager<>::read_ref &rr, node_header const &hdr) {
|
|
|
|
node_info info;
|
|
|
|
info.b = rr.get_location();
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
auto vsize = to_cpu<uint32_t>(hdr.value_size);
|
|
|
|
info.values = to_cpu<uint32_t>(hdr.nr_entries);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (vsize == sizeof(device_details_traits::disk_type)) {
|
|
|
|
auto n = to_node<device_details_traits>(rr);
|
|
|
|
info.type = DEVICE_DETAILS;
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (n.get_nr_entries()) {
|
|
|
|
info.key_low = n.key_at(0);
|
|
|
|
info.key_high = n.key_at(n.get_nr_entries() - 1);
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++) {
|
|
|
|
info.age = max(info.age, get_dd_age(n.value_at(i)));
|
|
|
|
info.nr_mappings += n.value_at(i).mapped_blocks_;
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
} else if (vsize == sizeof(uint64_t)) {
|
|
|
|
auto n = to_node<uint64_traits>(rr);
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (n.get_nr_entries()) {
|
|
|
|
info.key_low = n.key_at(0);
|
|
|
|
info.key_high = n.key_at(n.get_nr_entries() - 1);
|
|
|
|
}
|
2019-03-29 18:26:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
if (is_top_level(n)) {
|
|
|
|
info.type = TOP_LEVEL;
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++) {
|
|
|
|
node_info child = get_info(n.value_at(i));
|
|
|
|
if (!child.valid || (child.type != BOTTOM_LEVEL)) {
|
|
|
|
fail(info, "child not bottom level");
|
|
|
|
return info;
|
|
|
|
}
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
info.age = max(info.age, child.age);
|
|
|
|
merge_time_counts(info.time_counts, child.time_counts);
|
|
|
|
info.nr_mappings += child.nr_mappings;
|
|
|
|
}
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++)
|
|
|
|
mark_referenced(n.value_at(i));
|
2011-10-10 18:40:30 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
} else {
|
|
|
|
auto n = to_node<mapping_tree_detail::block_traits>(rr);
|
|
|
|
info.type = BOTTOM_LEVEL;
|
2012-03-02 15:30:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
for (unsigned i = 0; i < n.get_nr_entries(); i++) {
|
|
|
|
auto bt = n.value_at(i);
|
|
|
|
inc_time_count(info.time_counts, bt.time_);
|
|
|
|
info.age = max(info.age, bt.time_);
|
|
|
|
}
|
2012-03-02 15:30:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
info.nr_mappings = n.get_nr_entries();
|
|
|
|
}
|
|
|
|
}
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return info;
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
node_info get_info_(block_address b) {
|
|
|
|
if (!is_btree_node(b)) {
|
|
|
|
node_info info;
|
|
|
|
info.b = b;
|
|
|
|
fail(info, "not btree node");
|
|
|
|
return info;
|
|
|
|
}
|
2012-03-02 15:30:31 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
auto rr = bm_.read_lock(b);
|
|
|
|
auto hdr = reinterpret_cast<node_header const *>(rr.data());
|
2011-10-10 18:40:30 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
auto flags = to_cpu<uint32_t>(hdr->flags);
|
|
|
|
if (flags & INTERNAL_NODE)
|
|
|
|
return get_internal_info(rr);
|
|
|
|
else
|
|
|
|
return get_leaf_info(rr, *hdr);
|
2013-10-16 14:50:43 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
node_info get_info(block_address b) {
|
|
|
|
if (examined_[b]) {
|
|
|
|
auto it = infos_.find(b);
|
|
|
|
if (it == infos_.end()) {
|
|
|
|
node_info info;
|
|
|
|
info.b = b;
|
|
|
|
fail(info, "unknown");
|
|
|
|
return info;
|
|
|
|
}
|
2016-03-22 20:11:02 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return it->second;
|
|
|
|
} else {
|
|
|
|
node_info info = get_info_(b);
|
|
|
|
examined_[b] = true;
|
|
|
|
if (!failed(info))
|
|
|
|
infos_.insert(make_pair(b, info));
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
return info;
|
|
|
|
}
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
block_manager<> &bm_;
|
|
|
|
vector<bool> referenced_;
|
|
|
|
vector<bool> examined_;
|
|
|
|
map<block_address, node_info> infos_;
|
2013-10-16 14:50:43 +05:30
|
|
|
};
|
2019-04-17 16:47:13 +05:30
|
|
|
}
|
2013-10-16 14:50:43 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace {
|
2013-10-16 14:50:43 +05:30
|
|
|
class mapping_emitter : public mapping_tree_detail::mapping_visitor {
|
|
|
|
public:
|
|
|
|
mapping_emitter(emitter::ptr e)
|
|
|
|
: e_(e),
|
|
|
|
in_range_(false) {
|
|
|
|
}
|
|
|
|
|
|
|
|
~mapping_emitter() {
|
2011-10-10 18:40:30 +05:30
|
|
|
end_mapping();
|
|
|
|
}
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
typedef mapping_tree_detail::block_time block_time;
|
|
|
|
void visit(btree_path const &path, block_time const &bt) {
|
|
|
|
add_mapping(path[0], bt);
|
2012-03-02 15:30:31 +05:30
|
|
|
}
|
|
|
|
|
2011-10-10 18:40:30 +05:30
|
|
|
private:
|
2013-10-16 14:50:43 +05:30
|
|
|
void start_mapping(uint64_t origin_block, block_time const &bt) {
|
2011-10-10 18:40:30 +05:30
|
|
|
origin_start_ = origin_block;
|
2013-10-16 14:50:43 +05:30
|
|
|
dest_start_ = bt.block_;
|
|
|
|
time_ = bt.time_;
|
2011-10-10 18:40:30 +05:30
|
|
|
len_ = 1;
|
|
|
|
in_range_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void end_mapping() {
|
|
|
|
if (in_range_) {
|
|
|
|
if (len_ == 1)
|
2011-10-28 18:23:15 +05:30
|
|
|
e_->single_map(origin_start_, dest_start_, time_);
|
2011-10-10 18:40:30 +05:30
|
|
|
else
|
2011-10-28 18:23:15 +05:30
|
|
|
e_->range_map(origin_start_, dest_start_, time_, len_);
|
2011-10-10 18:40:30 +05:30
|
|
|
|
|
|
|
in_range_ = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
void add_mapping(uint64_t origin_block, block_time const &bt) {
|
2011-10-10 18:40:30 +05:30
|
|
|
if (!in_range_)
|
2013-10-16 14:50:43 +05:30
|
|
|
start_mapping(origin_block, bt);
|
2011-10-10 18:40:30 +05:30
|
|
|
|
|
|
|
else if (origin_block == origin_start_ + len_ &&
|
2013-10-16 14:50:43 +05:30
|
|
|
bt.block_ == dest_start_ + len_ &&
|
|
|
|
time_ == bt.time_)
|
2011-10-10 18:40:30 +05:30
|
|
|
len_++;
|
|
|
|
|
|
|
|
else {
|
|
|
|
end_mapping();
|
2013-10-16 14:50:43 +05:30
|
|
|
start_mapping(origin_block, bt);
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
emitter::ptr e_;
|
2013-10-16 14:50:43 +05:30
|
|
|
block_address origin_start_;
|
|
|
|
block_address dest_start_;
|
2011-10-28 18:23:15 +05:30
|
|
|
uint32_t time_;
|
2013-10-16 14:50:43 +05:30
|
|
|
block_address len_;
|
|
|
|
bool in_range_;
|
2011-10-10 18:40:30 +05:30
|
|
|
};
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
class mapping_tree_emitter : public mapping_tree_detail::device_visitor {
|
2011-10-10 18:40:30 +05:30
|
|
|
public:
|
2016-03-22 20:11:02 +05:30
|
|
|
mapping_tree_emitter(dump_options const &opts,
|
|
|
|
metadata::ptr md,
|
2013-10-16 14:50:43 +05:30
|
|
|
emitter::ptr e,
|
|
|
|
dd_map const &dd,
|
|
|
|
mapping_tree_detail::damage_visitor::ptr damage_policy)
|
2016-03-22 20:11:02 +05:30
|
|
|
: opts_(opts),
|
|
|
|
md_(md),
|
2013-10-16 14:50:43 +05:30
|
|
|
e_(e),
|
|
|
|
dd_(dd),
|
|
|
|
damage_policy_(damage_policy) {
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
void visit(btree_path const &path, block_address tree_root) {
|
|
|
|
block_address dev_id = path[0];
|
2011-10-10 18:40:30 +05:30
|
|
|
|
2016-03-22 20:11:02 +05:30
|
|
|
if (!opts_.selected_dev(dev_id))
|
|
|
|
return;
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
dd_map::const_iterator it = dd_.find(path[0]);
|
|
|
|
if (it != dd_.end()) {
|
|
|
|
device_tree_detail::device_details const &d = it->second;
|
|
|
|
e_->begin_device(dev_id,
|
|
|
|
d.mapped_blocks_,
|
|
|
|
d.transaction_id_,
|
|
|
|
d.creation_time_,
|
|
|
|
d.snapshotted_time_);
|
2012-03-02 15:30:31 +05:30
|
|
|
|
2016-03-21 22:47:57 +05:30
|
|
|
try {
|
2016-03-22 20:31:37 +05:30
|
|
|
if (!opts_.skip_mappings_)
|
2016-06-19 19:05:11 +05:30
|
|
|
emit_mappings(dev_id, tree_root);
|
2019-03-29 18:26:31 +05:30
|
|
|
} catch (std::exception &e) {
|
2016-03-21 22:47:57 +05:30
|
|
|
cerr << e.what();
|
|
|
|
e_->end_device();
|
|
|
|
throw;
|
|
|
|
}
|
2013-10-16 14:50:43 +05:30
|
|
|
e_->end_device();
|
2011-10-10 18:40:30 +05:30
|
|
|
|
2019-04-17 16:47:13 +05:30
|
|
|
} else {
|
2013-10-16 14:50:43 +05:30
|
|
|
ostringstream msg;
|
|
|
|
msg << "mappings present for device " << dev_id
|
|
|
|
<< ", but it isn't present in device tree";
|
|
|
|
throw runtime_error(msg.str());
|
|
|
|
}
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
2013-10-16 14:50:43 +05:30
|
|
|
private:
|
2016-06-19 19:05:11 +05:30
|
|
|
void emit_mappings(uint64_t dev_id, block_address subtree_root) {
|
2013-10-16 14:50:43 +05:30
|
|
|
mapping_emitter me(e_);
|
2014-08-26 15:44:49 +05:30
|
|
|
single_mapping_tree tree(*md_->tm_, subtree_root,
|
2013-10-16 14:50:43 +05:30
|
|
|
mapping_tree_detail::block_time_ref_counter(md_->data_sm_));
|
2016-06-19 19:05:11 +05:30
|
|
|
walk_mapping_tree(tree, dev_id, static_cast<mapping_tree_detail::mapping_visitor &>(me), *damage_policy_);
|
2012-03-02 15:30:31 +05:30
|
|
|
}
|
|
|
|
|
2016-03-22 20:11:02 +05:30
|
|
|
dump_options const &opts_;
|
2013-10-16 14:50:43 +05:30
|
|
|
metadata::ptr md_;
|
|
|
|
emitter::ptr e_;
|
|
|
|
dd_map const &dd_;
|
|
|
|
mapping_tree_detail::damage_visitor::ptr damage_policy_;
|
2011-10-10 18:40:30 +05:30
|
|
|
};
|
2016-03-21 22:47:57 +05:30
|
|
|
|
|
|
|
block_address get_nr_blocks(metadata::ptr md) {
|
|
|
|
if (md->data_sm_)
|
|
|
|
return md->data_sm_->get_nr_blocks();
|
|
|
|
|
|
|
|
else if (md->sb_.blocknr_ == superblock_detail::SUPERBLOCK_LOCATION)
|
|
|
|
// grab from the root structure of the space map
|
|
|
|
return get_nr_blocks_in_data_sm(*md->tm_, &md->sb_.data_space_map_root_);
|
|
|
|
|
|
|
|
else
|
|
|
|
// metadata snap, we really don't know
|
|
|
|
return 0ull;
|
|
|
|
}
|
2019-05-27 21:13:32 +05:30
|
|
|
|
|
|
|
void
|
|
|
|
metadata_repair_(block_manager<>::ptr bm, emitter::ptr e)
|
|
|
|
{
|
|
|
|
// We assume the superblock is wrong, and find the best roots
|
|
|
|
// for ourselves. We've had a few cases where people have
|
|
|
|
// activated a pool on multiple hosts at once, which results in
|
|
|
|
// the superblock being over written.
|
|
|
|
gatherer g(*bm);
|
|
|
|
auto tm = open_tm(bm, superblock_detail::SUPERBLOCK_LOCATION);
|
|
|
|
auto p = g.find_best_roots(*tm);
|
|
|
|
|
|
|
|
metadata::ptr md;
|
|
|
|
|
|
|
|
if (p) {
|
|
|
|
// We found good roots, so we fill out our own superblock,
|
|
|
|
// with some help from the old sb.
|
|
|
|
|
|
|
|
// FIXME: what happens if the superblock can't be read?
|
|
|
|
// catch and fill out defaults? what should the data_block_size be?
|
|
|
|
auto sb = read_superblock(*bm);
|
|
|
|
|
|
|
|
sb.metadata_snap_ = 0;
|
|
|
|
|
|
|
|
sb.device_details_root_ = p->first;
|
|
|
|
sb.data_mapping_root_ = p->second;
|
|
|
|
sb.metadata_nr_blocks_ = bm->get_nr_blocks();
|
|
|
|
|
|
|
|
md.reset(new metadata(bm, sb));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// We couldn't find any good roots, so we'll fall back to using the
|
|
|
|
// on disk superblock.
|
|
|
|
md.reset(new metadata(bm, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
dump_options opts;
|
|
|
|
details_extractor de(opts);
|
|
|
|
device_tree_detail::damage_visitor::ptr dd_policy(details_damage_policy(true));
|
|
|
|
walk_device_tree(*md->details_, de, *dd_policy);
|
|
|
|
|
|
|
|
e->begin_superblock("", md->sb_.time_,
|
|
|
|
md->sb_.trans_id_,
|
|
|
|
md->sb_.flags_,
|
|
|
|
md->sb_.version_,
|
|
|
|
md->sb_.data_block_size_,
|
|
|
|
get_nr_blocks(md),
|
|
|
|
boost::optional<block_address>());
|
|
|
|
|
|
|
|
{
|
|
|
|
mapping_tree_detail::damage_visitor::ptr md_policy(mapping_damage_policy(true));
|
|
|
|
mapping_tree_emitter mte(opts, md, e, de.get_details(), mapping_damage_policy(true));
|
|
|
|
walk_mapping_tree(*md->mappings_top_level_, mte, *md_policy);
|
|
|
|
}
|
|
|
|
|
|
|
|
e->end_superblock();
|
|
|
|
}
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
2011-10-28 16:43:03 +05:30
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
2011-10-10 18:40:30 +05:30
|
|
|
void
|
2016-03-22 20:11:02 +05:30
|
|
|
thin_provisioning::metadata_dump(metadata::ptr md, emitter::ptr e, dump_options const &opts)
|
2011-10-10 18:40:30 +05:30
|
|
|
{
|
2019-04-17 16:47:13 +05:30
|
|
|
details_extractor de(opts);
|
|
|
|
device_tree_detail::damage_visitor::ptr dd_policy(details_damage_policy(false));
|
|
|
|
walk_device_tree(*md->details_, de, *dd_policy);
|
|
|
|
|
|
|
|
e->begin_superblock("", md->sb_.time_,
|
|
|
|
md->sb_.trans_id_,
|
|
|
|
md->sb_.flags_,
|
|
|
|
md->sb_.version_,
|
|
|
|
md->sb_.data_block_size_,
|
|
|
|
get_nr_blocks(md),
|
|
|
|
boost::optional<block_address>());
|
|
|
|
|
|
|
|
{
|
|
|
|
mapping_tree_detail::damage_visitor::ptr md_policy(mapping_damage_policy(false));
|
|
|
|
mapping_tree_emitter mte(opts, md, e, de.get_details(), mapping_damage_policy(false));
|
|
|
|
walk_mapping_tree(*md->mappings_top_level_, mte, *md_policy);
|
|
|
|
}
|
|
|
|
|
|
|
|
e->end_superblock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
thin_provisioning::metadata_repair(block_manager<>::ptr bm, emitter::ptr e)
|
|
|
|
{
|
2019-05-27 21:13:32 +05:30
|
|
|
auto sb = read_superblock(*bm);
|
2019-04-17 16:47:13 +05:30
|
|
|
auto tm = open_tm(bm, superblock_detail::SUPERBLOCK_LOCATION);
|
|
|
|
|
2019-05-27 21:13:32 +05:30
|
|
|
if (get_dev_ids(*tm, sb.device_details_root_) && get_map_ids(*tm, sb.data_mapping_root_)) {
|
|
|
|
// The roots in the superblock look ok (perhaps the corruption was in the
|
|
|
|
// space maps). Just dump the metadata skipping repair.
|
|
|
|
dump_options opts;
|
|
|
|
auto md = metadata::ptr(new metadata(bm, false));
|
|
|
|
metadata_dump(md, e, opts);
|
|
|
|
} else
|
|
|
|
metadata_repair_(bm, e);
|
2011-10-10 18:40:30 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
2016-02-27 12:50:31 +05:30
|
|
|
|
|
|
|
void
|
|
|
|
thin_provisioning::metadata_dump_subtree(metadata::ptr md, emitter::ptr e, bool repair, uint64_t subtree_root) {
|
|
|
|
mapping_emitter me(e);
|
|
|
|
single_mapping_tree tree(*md->tm_, subtree_root,
|
|
|
|
mapping_tree_detail::block_time_ref_counter(md->data_sm_));
|
2016-06-19 19:05:11 +05:30
|
|
|
// FIXME: pass the current device id instead of zero
|
|
|
|
walk_mapping_tree(tree, 0, static_cast<mapping_tree_detail::mapping_visitor &>(me),
|
2016-02-27 12:50:31 +05:30
|
|
|
*mapping_damage_policy(repair));
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|