[thin_repair] repair now checks the data reference counts.

This commit is contained in:
Joe Thornber 2011-08-24 10:45:39 +01:00
parent 511456f903
commit 987a8360c9
11 changed files with 445 additions and 137 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
test.data
thin_dump
thin_repair
*.metadata

View File

@ -1,5 +1,6 @@
SOURCE=\
endian_utils.cc \
error_set.cc \
metadata.cc \
metadata_disk_structures.cc

12
btree.h
View File

@ -320,9 +320,12 @@ namespace persistent_data {
virtual ~visitor() {}
typedef boost::shared_ptr<visitor> ptr;
virtual void visit_internal(unsigned level, btree_detail::node_ref<uint64_traits, BlockSize> const &n) = 0;
virtual void visit_internal_leaf(unsigned level, btree_detail::node_ref<uint64_traits, BlockSize> const &n) = 0;
virtual void visit_leaf(unsigned level, btree_detail::node_ref<ValueTraits, BlockSize> const &n) = 0;
virtual void visit_internal(unsigned level, bool is_root,
btree_detail::node_ref<uint64_traits, BlockSize> const &n) = 0;
virtual void visit_internal_leaf(unsigned level, bool is_root,
btree_detail::node_ref<uint64_traits, BlockSize> const &n) = 0;
virtual void visit_leaf(unsigned level, bool is_root,
btree_detail::node_ref<ValueTraits, BlockSize> const &n) = 0;
};
// Walks the tree in depth first order
@ -351,7 +354,8 @@ namespace persistent_data {
int *index);
void walk_tree(typename visitor::ptr visitor,
unsigned level, block_address b);
unsigned level, bool is_root,
block_address b);
typename persistent_data::transaction_manager<BlockSize>::ptr tm_;
bool destroy_;

View File

@ -614,32 +614,33 @@ template <unsigned Levels, typename ValueTraits, uint32_t BlockSize>
void
btree<Levels, ValueTraits, BlockSize>::visit(typename visitor::ptr visitor)
{
walk_tree(visitor, 0, root_);
walk_tree(visitor, 0, true, root_);
}
template <unsigned Levels, typename ValueTraits, uint32_t BlockSize>
void
btree<Levels, ValueTraits, BlockSize>::
walk_tree(typename visitor::ptr visitor,
unsigned level, block_address b)
unsigned level, bool is_root,
block_address b)
{
using namespace btree_detail;
auto blk = tm_->read_lock(b);
auto o = to_node<uint64_traits, BlockSize>(blk);
if (o.get_type() == INTERNAL) {
visitor->visit_internal(level, o);
visitor->visit_internal(level, is_root, o);
for (unsigned i = 0; i < o.get_nr_entries(); i++)
walk_tree(visitor, level, o.value_at(i));
walk_tree(visitor, level, false, o.value_at(i));
} else if (level < Levels - 1) {
visitor->visit_internal_leaf(level, o);
visitor->visit_internal_leaf(level, is_root, o);
for (unsigned i = 0; i < o.get_nr_entries(); i++)
walk_tree(visitor, level + 1, o.value_at(i));
walk_tree(visitor, level + 1, true, o.value_at(i));
} else {
auto ov = to_node<ValueTraits, BlockSize>(blk);
visitor->visit_leaf(level, ov);
visitor->visit_leaf(level, is_root, ov);
}
}

162
btree_validator.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef BTREE_VALIDATOR_H
#define BTREE_VALIDATOR_H
#include "btree.h"
#include <sstream>
#include <map>
#include <set>
//----------------------------------------------------------------
namespace persistent_data {
//----------------------------------------------------------------
// Little helper class that keeps track of how many times blocks
// are referenced.
//----------------------------------------------------------------
class block_counter {
public:
void inc(block_address b) {
auto it = counts_.find(b);
if (it == counts_.end())
counts_.insert(make_pair(b, 1));
#if 0
else
it->second++;
#endif
}
unsigned get_count(block_address b) const {
auto it = counts_.find(b);
return (it == counts_.end()) ? 0 : it->second;
}
std::map<block_address, unsigned> const &get_counts() const {
return counts_;
}
private:
std::map<block_address, unsigned> counts_;
};
//----------------------------------------------------------------
// This class implements consistency checking for the btrees in
// general. Derive from this if you want some additional checks.
// It's worth summarising what is checked:
//
// Implemented
// -----------
//
// - block_nr
// - nr_entries < max_entries
// - max_entries fits in block
// - max_entries is divisible by 3
// - nr_entries > minimum (except for root nodes)
//
// Not implemented
// ---------------
//
// - checksum
// - leaf | internal flags (this can be inferred from siblings)
//----------------------------------------------------------------
template <uint32_t Levels, typename ValueTraits, uint32_t BlockSize>
class btree_validator : public btree<Levels, ValueTraits, BlockSize>::visitor {
public:
btree_validator(block_counter &counter)
: counter_(counter) {
}
void visit_internal(unsigned level, bool is_root,
btree_detail::node_ref<uint64_traits, BlockSize> const &n) {
counter_.inc(n.get_location());
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n, is_root);
}
void visit_internal_leaf(unsigned level, bool is_root,
btree_detail::node_ref<uint64_traits, BlockSize> const &n) {
counter_.inc(n.get_location());
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n, is_root);
}
void visit_leaf(unsigned level, bool is_root,
btree_detail::node_ref<ValueTraits, BlockSize> const &n) {
counter_.inc(n.get_location());
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n, is_root);
}
private:
void check_duplicate_block(block_address b) {
if (seen_.count(b)) {
std::ostringstream out;
out << "duplicate block in btree: " << b;
throw std::runtime_error(out.str());
}
seen_.insert(b);
}
template <typename node>
void check_block_nr(node const &n) const {
if (n.get_location() != n.get_block_nr()) {
std::ostringstream out;
out << "block number mismatch: actually "
<< n.get_location()
<< ", claims " << n.get_block_nr();
throw std::runtime_error(out.str());
}
}
template <typename node>
void check_max_entries(node const &n) const {
size_t elt_size = sizeof(uint64_t) + n.get_value_size();
if (elt_size * n.get_max_entries() + sizeof(node_header) > BlockSize) {
std::ostringstream out;
out << "max entries too large: " << n.get_max_entries();
throw std::runtime_error(out.str());
}
if (n.get_max_entries() % 3) {
std::ostringstream out;
out << "max entries is not divisible by 3: " << n.get_max_entries();
throw std::runtime_error(out.str());
}
}
template <typename node>
void check_nr_entries(node const &n, bool is_root) const {
if (n.get_nr_entries() > n.get_max_entries()) {
std::ostringstream out;
out << "bad nr_entries: "
<< n.get_nr_entries() << " < "
<< n.get_max_entries();
throw std::runtime_error(out.str());
}
block_address min = n.get_max_entries() / 3;
if (!is_root && (n.get_nr_entries() < min)) {
ostringstream out;
out << "too few entries in btree: "
<< n.get_nr_entries()
<< ", expected at least "
<< min;
throw runtime_error(out.str());
}
}
block_counter &counter_;
std::set<block_address> seen_;
};
}
//----------------------------------------------------------------
#endif

80
error_set.cc Normal file
View File

@ -0,0 +1,80 @@
#include "error_set.h"
#include <iostream>
using namespace persistent_data;
//----------------------------------------------------------------
error_set::error_set(std::string const &err)
: err_(err) {
}
std::string const &
error_set::get_description() const
{
return err_;
}
std::list<error_set::ptr> const &
error_set::get_children() const
{
return children_;
}
void
error_set::add_child(error_set::ptr err)
{
children_.push_back(err);
}
void
error_set::add_child(std::string const &err)
{
error_set::ptr e(new error_set(err));
add_child(e);
}
//--------------------------------
namespace {
void indent_by(std::ostream &out, unsigned indent) {
for (unsigned i = 0; i < indent; i++)
out << ' ';
}
void print_errs(std::ostream &out, error_set::ptr e, unsigned depth, unsigned indent) {
if (depth == 0)
return;
indent_by(out, indent);
out << e->get_description() << std::endl;
if (depth > 1) {
auto children = e->get_children();
for (auto it = children.begin(); it != children.end(); ++it)
print_errs(out, *it, depth - 1, indent + 2);
}
}
}
error_selector::error_selector(error_set::ptr errs, unsigned depth)
: errs_(errs),
depth_(depth)
{
}
void
error_selector::print(std::ostream &out) const
{
print_errs(out, errs_, depth_, 0);
}
std::ostream &
persistent_data::operator << (std::ostream &out, error_selector const &errs)
{
errs.print(out);
return out;
}
//----------------------------------------------------------------

49
error_set.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef ERROR_SET_H
#define ERROR_SET_H
#include <boost/shared_ptr.hpp>
#include <list>
#include <iosfwd>
//----------------------------------------------------------------
namespace persistent_data {
// When checking the metadata for a thin device we don't want to
// stop at the first error. Instead should collect as much
// information as possible. The errors are hierarchical, so the
// user can control how much detail is displayed.
class error_set {
public:
typedef boost::shared_ptr<error_set> ptr;
error_set(std::string const &err);
std::string const &get_description() const;
std::list<error_set::ptr> const &get_children() const;
void add_child(error_set::ptr err);
void add_child(std::string const &err);
private:
std::string err_;
std::list<error_set::ptr> children_;
};
// The error_selector is a little proxy class used when printing
// errors to a stream.
class error_selector {
public:
error_selector(error_set::ptr errs, unsigned depth);
void print(std::ostream &out) const;
private:
error_set::ptr errs_;
unsigned depth_;
};
std::ostream &operator << (std::ostream &out, error_selector const &errs);
}
//----------------------------------------------------------------
#endif

View File

@ -1,10 +1,13 @@
#include "metadata.h"
#include "btree_validator.h"
#include "core_map.h"
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <set>
#include <map>
using namespace std;
using namespace persistent_data;
@ -38,122 +41,41 @@ namespace {
return sb;
}
//----------------------------------------------------------------
// This class implements consistency checking for the
// btrees in general. It's worth summarising what is checked:
//
// Implemented
// -----------
//
// - No block appears in the tree more than once.
// - block_nr
// - nr_entries < max_entries
// - max_entries fits in block
// - max_entries is divisible by 3
//
// Not implemented
// ---------------
//
// - checksum
// - leaf | internal flags (this can be inferred from siblings)
// - nr_entries > minimum
//----------------------------------------------------------------
template <uint32_t Levels, typename ValueTraits, uint32_t BlockSize>
class btree_validator : public btree<Levels, ValueTraits, BlockSize>::visitor {
public:
void visit_internal(unsigned level, btree_detail::node_ref<uint64_traits, BlockSize> const &n) {
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n);
}
void visit_internal_leaf(unsigned level, btree_detail::node_ref<uint64_traits, BlockSize> const &n) {
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n);
}
void visit_leaf(unsigned level, btree_detail::node_ref<ValueTraits, BlockSize> const &n) {
check_duplicate_block(n.get_location());
check_block_nr(n);
check_max_entries(n);
check_nr_entries(n);
}
private:
void check_duplicate_block(block_address b) {
if (seen_.count(b)) {
ostringstream out;
out << "duplicate block in btree: " << b;
throw runtime_error(out.str());
}
seen_.insert(b);
}
template <typename node>
void check_block_nr(node const &n) const {
if (n.get_location() != n.get_block_nr()) {
ostringstream out;
out << "block number mismatch: actually "
<< n.get_location()
<< ", claims " << n.get_block_nr();
throw runtime_error(out.str());
}
}
template <typename node>
void check_max_entries(node const &n) const {
size_t elt_size = sizeof(uint64_t) + n.get_value_size();
if (elt_size * n.get_max_entries() + sizeof(node_header) > BlockSize) {
ostringstream out;
out << "max entries too large: " << n.get_max_entries();
throw runtime_error(out.str());
}
if (n.get_max_entries() % 3) {
ostringstream out;
out << "max entries is not divisible by 3: " << n.get_max_entries();
throw runtime_error(out.str());
}
}
template <typename node>
void check_nr_entries(node const &n) const {
if (n.get_nr_entries() > n.get_max_entries()) {
ostringstream out;
out << "bad nr_entries: "
<< n.get_nr_entries() << " < "
<< n.get_max_entries();
throw runtime_error(out.str());
}
}
set<block_address> seen_;
};
// As well as the standard btree checks, we build up a set of what
// devices having mappings defined, which can later be cross
// referenced with the details tree.
// referenced with the details tree. A separate block_counter is
// used to later verify the data space map.
class mapping_validator : public btree_validator<2, block_traits, MD_BLOCK_SIZE> {
public:
typedef boost::shared_ptr<mapping_validator> ptr;
void visit_internal_leaf(unsigned level,
mapping_validator(block_counter &metadata_counter, block_counter &data_counter)
: btree_validator<2, block_traits, MD_BLOCK_SIZE>(metadata_counter),
data_counter_(data_counter) {
}
void visit_internal_leaf(unsigned level, bool is_root,
btree_detail::node_ref<uint64_traits, MD_BLOCK_SIZE> const &n) {
btree_validator<2, block_traits, MD_BLOCK_SIZE>::visit_internal_leaf(level, n);
btree_validator<2, block_traits, MD_BLOCK_SIZE>::visit_internal_leaf(level, is_root, n);
for (unsigned i = 0; i < n.get_nr_entries(); i++)
devices_.insert(n.key_at(i));
}
void visit_leaf(unsigned level, bool is_root,
btree_detail::node_ref<block_traits, MD_BLOCK_SIZE> const &n) {
btree_validator<2, block_traits, MD_BLOCK_SIZE>::visit_leaf(level, is_root, n);
for (unsigned i = 0; i < n.get_nr_entries(); i++)
data_counter_.inc(n.value_at(i).block_);
}
set<uint64_t> get_devices() const {
return devices_;
}
private:
block_counter &data_counter_;
set<uint64_t> devices_;
};
@ -161,9 +83,13 @@ namespace {
public:
typedef boost::shared_ptr<details_validator> ptr;
void visit_leaf(unsigned level,
details_validator(block_counter &counter)
: btree_validator<1, device_details_traits, MD_BLOCK_SIZE>(counter) {
}
void visit_leaf(unsigned level, bool is_root,
btree_detail::node_ref<device_details_traits, MD_BLOCK_SIZE> const &n) {
btree_validator<1, device_details_traits, MD_BLOCK_SIZE>::visit_leaf(level, n);
btree_validator<1, device_details_traits, MD_BLOCK_SIZE>::visit_leaf(level, is_root, n);
for (unsigned i = 0; i < n.get_nr_entries(); i++)
devices_.insert(n.key_at(i));
@ -203,7 +129,10 @@ void
thin::insert(block_address thin_block, block_address data_block)
{
uint64_t key[2] = {dev_, thin_block};
return metadata_->mappings_.insert(key, data_block);
block_time bt;
bt.block_ = data_block;
bt.time_ = 0; // FIXME: use current time.
return metadata_->mappings_.insert(key, bt);
}
void
@ -253,9 +182,10 @@ thin::set_mapped_blocks(block_address count)
metadata::metadata(std::string const &dev_path)
: tm_(open_tm(dev_path)),
sb_(read_superblock(tm_->get_bm())),
data_sm_(open_disk_sm<MD_BLOCK_SIZE>(tm_, static_cast<void *>(&sb_.data_space_map_root_))),
details_(tm_, sb_.device_details_root_, typename device_details_traits::ref_counter()),
mappings_top_level_(tm_, sb_.data_mapping_root_, mtree_ref_counter<MD_BLOCK_SIZE>(tm_)),
mappings_(tm_, sb_.data_mapping_root_, space_map_ref_counter(data_sm_))
mappings_(tm_, sb_.data_mapping_root_, block_time_ref_counter(data_sm_))
{
#if 0
::memset(&sb_, 0, sizeof(sb_));
@ -290,7 +220,7 @@ metadata::create_thin(thin_dev_t dev)
if (device_exists(dev))
throw std::runtime_error("Device already exists");
single_mapping_tree::ptr new_tree(new single_mapping_tree(tm_, space_map_ref_counter(data_sm_)));
single_mapping_tree::ptr new_tree(new single_mapping_tree(tm_, block_time_ref_counter(data_sm_)));
mappings_top_level_.insert(key, new_tree->get_root());
mappings_.set_root(mappings_top_level_.get_root()); // FIXME: ugly
}
@ -306,7 +236,7 @@ metadata::create_snap(thin_dev_t dev, thin_dev_t origin)
throw std::runtime_error("unknown origin");
single_mapping_tree otree(tm_, *mtree_root,
space_map_ref_counter(data_sm_));
block_time_ref_counter(data_sm_));
single_mapping_tree::ptr clone(otree.clone());
mappings_top_level_.insert(snap_key, clone->get_root());
@ -396,14 +326,19 @@ metadata::device_exists(thin_dev_t dev) const
return details_.lookup(key);
}
void
boost::optional<error_set::ptr>
metadata::check()
{
mapping_validator::ptr mv(new mapping_validator);
error_set::ptr errors(new error_set("Errors in metadata"));
block_counter metadata_counter, data_counter;
mapping_validator::ptr mv(new mapping_validator(metadata_counter,
data_counter));
mappings_.visit(mv);
auto mapped_devs = mv->get_devices();
details_validator::ptr dv(new details_validator);
details_validator::ptr dv(new details_validator(metadata_counter));
details_.visit(dv);
auto details_devs = dv->get_devices();
@ -414,6 +349,32 @@ metadata::check()
<< ", yet there is no entry in the details tree.";
throw runtime_error(out.str());
}
data_sm_->check(metadata_counter);
{
error_set::ptr data_errors(new error_set("Errors in data reference counts"));
bool bad = false;
auto data_counts = data_counter.get_counts();
for (auto it = data_counts.begin(); it != data_counts.end(); ++it) {
uint32_t ref_count = data_sm_->get_count(it->first);
if (ref_count != it->second) {
ostringstream out;
out << it->first << ": was " << ref_count
<< ", expected " << it->second;
data_errors->add_child(out.str());
bad = true;
}
}
if (bad)
errors->add_child(data_errors);
}
return (errors->get_children().size() > 0) ?
optional<error_set::ptr>(errors) :
optional<error_set::ptr>();
}
//----------------------------------------------------------------

View File

@ -2,10 +2,12 @@
#define MULTISNAP_METADATA_H
#include "block.h"
#include "transaction_manager.h"
#include "btree.h"
#include "endian_utils.h"
#include "error_set.h"
#include "metadata_disk_structures.h"
#include "space_map_disk.h"
#include "transaction_manager.h"
#include <string>
@ -43,17 +45,43 @@ namespace thin_provisioning {
space_map::ptr sm_;
};
struct block_time {
uint64_t block_;
uint32_t time_;
};
class block_time_ref_counter {
public:
block_time_ref_counter(space_map::ptr sm)
: sm_(sm) {
}
void inc(block_time bt) {
sm_->inc(bt.block_);
}
void dec(block_time bt) {
sm_->dec(bt.block_);
}
private:
space_map::ptr sm_;
};
struct block_traits {
typedef base::__le64 disk_type;
typedef uint64_t value_type;
typedef space_map_ref_counter ref_counter;
typedef block_time value_type;
typedef block_time_ref_counter ref_counter;
static void unpack(disk_type const &disk, value_type &value) {
value = base::to_cpu<uint64_t>(disk);
uint64_t v = to_cpu<uint64_t>(disk);
value.block_ = v >> 24;
value.time_ = v & ((1 << 24) - 1);
}
static void pack(value_type const &value, disk_type &disk) {
disk = base::to_disk<base::__le64>(value);
uint64_t v = (value.block_ << 24) | value.time_;
disk = base::to_disk<base::__le64>(v);
}
};
@ -95,7 +123,7 @@ namespace thin_provisioning {
class thin {
public:
typedef boost::shared_ptr<thin> ptr;
typedef boost::optional<block_address> maybe_address;
typedef boost::optional<block_time> maybe_address;
thin_dev_t get_dev_t() const;
maybe_address lookup(block_address thin_block);
@ -144,7 +172,7 @@ namespace thin_provisioning {
thin::ptr open_thin(thin_dev_t);
// Validation and repair
void check();
boost::optional<persistent_data::error_set::ptr> check();
private:
friend class thin;
@ -164,7 +192,7 @@ namespace thin_provisioning {
// Ignoring the metadata sm for now, since we don't need it for the basic 'dump' tool
// space_map::ptr metadata_sm_;
space_map::ptr data_sm_;
sm_disk_detail::sm_disk<MD_BLOCK_SIZE>::ptr data_sm_;
detail_tree details_;
dev_tree mappings_top_level_;
mapping_tree mappings_;

View File

@ -1,6 +1,7 @@
#ifndef SPACE_MAP_DISK_H
#define SPACE_MAP_DISK_H
#include "btree_validator.h"
#include "space_map.h"
#include "transaction_manager.h"
#include "endian_utils.h"
@ -124,6 +125,7 @@ namespace persistent_data {
sm_disk_base(typename transaction_manager<BlockSize>::ptr tm,
sm_root const &root)
: tm_(tm),
entries_per_block_((BlockSize - sizeof(bitmap_header)) * 4),
nr_blocks_(root.nr_blocks_),
nr_allocated_(root.nr_allocated_),
ref_counts_(tm_, root.ref_count_root_, ref_count_traits::ref_counter()) {
@ -288,6 +290,16 @@ namespace persistent_data {
btree<1, ref_count_traits, BlockSize> ref_counts_;
};
template <uint32_t BlockSize>
class bitmap_tree_validator : public btree_validator<1, index_entry_traits, BlockSize> {
public:
typedef boost::shared_ptr<bitmap_tree_validator> ptr;
bitmap_tree_validator(block_counter &counter)
: btree_validator<1, index_entry_traits, BlockSize>(counter) {
}
};
template <uint32_t BlockSize>
class sm_disk : public sm_disk_base<BlockSize> {
public:
@ -295,13 +307,13 @@ namespace persistent_data {
sm_disk(typename transaction_manager<BlockSize>::ptr tm)
: sm_disk_base<BlockSize>(tm),
bitmaps_(sm_disk_base<BlockSize>::get_tm(), typename sm_disk_detail::index_entry_traits::ref_counter()) {
bitmaps_(sm_disk_base<BlockSize>::get_tm(), typename index_entry_traits::ref_counter()) {
}
sm_disk(typename transaction_manager<BlockSize>::ptr tm,
sm_root const &root)
: sm_disk_base<BlockSize>(tm, root),
bitmaps_(sm_disk_base<BlockSize>::get_tm(), root.bitmap_root_, typename sm_disk_detail::index_entry_traits::ref_counter()) {
bitmaps_(sm_disk_base<BlockSize>::get_tm(), root.bitmap_root_, typename index_entry_traits::ref_counter()) {
}
size_t root_size() {
@ -323,6 +335,11 @@ namespace persistent_data {
::memcpy(dest, &d, sizeof(d));
}
void check(block_counter &counter) {
typename bitmap_tree_validator<BlockSize>::ptr v(new bitmap_tree_validator<BlockSize>(counter));
bitmaps_.visit(v);
}
private:
index_entry find_ie(block_address b) const {
uint64_t key[1] = {b / sm_disk_base<BlockSize>::get_entries_per_block()};
@ -343,7 +360,7 @@ namespace persistent_data {
}
template <uint32_t MetadataBlockSize>
persistent_space_map::ptr
typename sm_disk_detail::sm_disk<MetadataBlockSize>::ptr
create_disk_sm(typename transaction_manager<MetadataBlockSize>::ptr tm,
block_address nr_blocks)
{
@ -355,7 +372,7 @@ namespace persistent_data {
}
template <uint32_t MetadataBlockSize>
persistent_space_map::ptr
typename sm_disk_detail::sm_disk<MetadataBlockSize>::ptr
open_disk_sm(typename transaction_manager<MetadataBlockSize>::ptr tm,
void *root)
{
@ -366,7 +383,7 @@ namespace persistent_data {
::memcpy(&d, root, sizeof(d));
sm_root_traits::unpack(d, v);
return typename persistent_space_map::ptr(
return typename sm_disk<MetadataBlockSize>::ptr(
new sm_disk<MetadataBlockSize>(tm, v));
}
}

View File

@ -7,10 +7,16 @@ using namespace std;
using namespace thin_provisioning;
namespace {
void check(string const &path) {
int check(string const &path) {
metadata md(path);
md.check();
auto maybe_errors = md.check();
if (maybe_errors) {
cerr << error_selector(*maybe_errors, 3);
return 1;
}
return 0;
}
void usage(string const &cmd) {
@ -25,7 +31,5 @@ int main(int argc, char **argv)
exit(1);
}
check(argv[1]);
return 0;
return check(argv[1]);
}