thin_dump now takes a --repair option

This commit is contained in:
Joe Thornber 2012-03-02 10:00:31 +00:00
parent 347003e6f3
commit 606c25d828
12 changed files with 196 additions and 91 deletions

View File

@ -19,7 +19,7 @@
.PHONY: all .PHONY: all
PROGRAMS=\ PROGRAMS=\
thin_repair \ thin_check \
thin_dump \ thin_dump \
thin_restore thin_restore
@ -45,8 +45,8 @@ SOURCE=\
xml_format.cc xml_format.cc
PROGRAM_SOURCE=\ PROGRAM_SOURCE=\
thin_check.cc \
thin_dump.cc \ thin_dump.cc \
thin_repair.cc \
thin_restore.cc thin_restore.cc
CXX:=@CXX@ CXX:=@CXX@
@ -87,7 +87,7 @@ test-programs: $(TEST_PROGRAMS)
THIN_DUMP_SOURCE=$(SOURCE) THIN_DUMP_SOURCE=$(SOURCE)
THIN_RESTORE_SOURCE=$(SOURCE) THIN_RESTORE_SOURCE=$(SOURCE)
THIN_REPAIR_SOURCE=\ THIN_CHECK_SOURCE=\
checksum.cc \ checksum.cc \
endian_utils.cc \ endian_utils.cc \
error_set.cc \ error_set.cc \
@ -103,7 +103,7 @@ THIN_REPAIR_SOURCE=\
THIN_DUMP_OBJECTS=$(subst .cc,.o,$(THIN_DUMP_SOURCE)) THIN_DUMP_OBJECTS=$(subst .cc,.o,$(THIN_DUMP_SOURCE))
THIN_RESTORE_OBJECTS=$(subst .cc,.o,$(THIN_RESTORE_SOURCE)) THIN_RESTORE_OBJECTS=$(subst .cc,.o,$(THIN_RESTORE_SOURCE))
THIN_REPAIR_OBJECTS=$(subst .cc,.o,$(THIN_REPAIR_SOURCE)) THIN_CHECK_OBJECTS=$(subst .cc,.o,$(THIN_CHECK_SOURCE))
thin_dump: $(THIN_DUMP_OBJECTS) thin_dump.o thin_dump: $(THIN_DUMP_OBJECTS) thin_dump.o
$(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS) $(LIBEXPAT) $(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS) $(LIBEXPAT)
@ -111,7 +111,7 @@ thin_dump: $(THIN_DUMP_OBJECTS) thin_dump.o
thin_restore: $(THIN_RESTORE_OBJECTS) thin_restore.o thin_restore: $(THIN_RESTORE_OBJECTS) thin_restore.o
$(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS) $(LIBEXPAT) $(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS) $(LIBEXPAT)
thin_repair: $(THIN_REPAIR_OBJECTS) thin_repair.o thin_check: $(THIN_CHECK_OBJECTS) thin_check.o
$(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS) $(CXX) $(CXXFLAGS) -o $@ $+ $(LIBS)
clean: clean:
@ -122,11 +122,11 @@ distclean: clean
install: $(PROGRAMS) install: $(PROGRAMS)
$(INSTALL_DIR) $(BINDIR) $(INSTALL_DIR) $(BINDIR)
$(INSTALL_PROGRAM) thin_repair $(BINDIR)/thin_repair $(INSTALL_PROGRAM) thin_check $(BINDIR)/thin_check
$(INSTALL_PROGRAM) thin_dump $(BINDIR)/thin_dump $(INSTALL_PROGRAM) thin_dump $(BINDIR)/thin_dump
$(INSTALL_PROGRAM) thin_restore $(BINDIR)/thin_restore $(INSTALL_PROGRAM) thin_restore $(BINDIR)/thin_restore
$(INSTALL_DIR) $(MANPATH)/man8 $(INSTALL_DIR) $(MANPATH)/man8
$(INSTALL_DATA) man8/thin_repair.8 $(MANPATH)/man8/thin_repair.8 $(INSTALL_DATA) man8/thin_check.8 $(MANPATH)/man8/thin_check.8
$(INSTALL_DATA) man8/thin_dump.8 $(MANPATH)/man8/thin_dump.8 $(INSTALL_DATA) man8/thin_dump.8 $(MANPATH)/man8/thin_dump.8
$(INSTALL_DATA) man8/thin_restore.8 $(MANPATH)/man8/thin_restore.8 $(INSTALL_DATA) man8/thin_restore.8 $(MANPATH)/man8/thin_restore.8
.PHONY: install .PHONY: install

View File

@ -56,9 +56,10 @@ namespace persistent_data {
template <uint32_t Levels, typename ValueTraits> template <uint32_t Levels, typename ValueTraits>
class btree_checker : public btree<Levels, ValueTraits>::visitor { class btree_checker : public btree<Levels, ValueTraits>::visitor {
public: public:
btree_checker(block_counter &counter) btree_checker(block_counter &counter, bool avoid_repeated_visits = true)
: counter_(counter), : counter_(counter),
errs_(new error_set("btree errors")) { errs_(new error_set("btree errors")),
avoid_repeated_visits_(avoid_repeated_visits) {
} }
bool visit_internal(unsigned level, bool visit_internal(unsigned level,
@ -82,7 +83,7 @@ namespace persistent_data {
return check_leaf(level, sub_root, key, n); return check_leaf(level, sub_root, key, n);
} }
boost::optional<error_set::ptr> get_errors() const { error_set::ptr get_errors() const {
return errs_; return errs_;
} }
@ -140,10 +141,13 @@ namespace persistent_data {
counter_.inc(b); counter_.inc(b);
if (seen_.count(b) > 0) if (avoid_repeated_visits_) {
return true; if (seen_.count(b) > 0)
return true;
seen_.insert(b);
}
seen_.insert(b);
return false; return false;
} }
@ -291,6 +295,7 @@ namespace persistent_data {
std::set<block_address> seen_; std::set<block_address> seen_;
error_set::ptr errs_; error_set::ptr errs_;
boost::optional<uint64_t> last_leaf_key_[Levels]; boost::optional<uint64_t> last_leaf_key_[Levels];
bool avoid_repeated_visits_;
}; };
} }

View File

@ -61,6 +61,12 @@ error_set::add_child(string const &err)
add_child(e); add_child(e);
} }
bool
error_set::empty() const
{
return !children_.size();
}
//-------------------------------- //--------------------------------
namespace { namespace {

View File

@ -43,6 +43,7 @@ namespace persistent_data {
void add_child(error_set::ptr err); void add_child(error_set::ptr err);
void add_child(boost::optional<error_set::ptr> maybe_errs); void add_child(boost::optional<error_set::ptr> maybe_errs);
void add_child(std::string const &err); void add_child(std::string const &err);
bool empty() const;
private: private:
std::string err_; std::string err_;

View File

@ -18,6 +18,9 @@ the device-mapper target) or file.
{-i|--input} {device|file} {-i|--input} {device|file}
[{-f|--format} {xml|human_readable}] [{-f|--format} {xml|human_readable}]
.B thin_dump
{-r|--repair}
.B thin_dump .B thin_dump
{-h|--help} {-h|--help}

View File

@ -27,27 +27,30 @@ namespace {
// devices having mappings defined, which can later be cross // devices having mappings defined, which can later be cross
// referenced with the details tree. A separate block_counter is // referenced with the details tree. A separate block_counter is
// used to later verify the data space map. // used to later verify the data space map.
class mapping_validator : public btree_checker<2, block_traits> { class mapping_validator : public btree<2, block_traits>::visitor {
public: public:
typedef boost::shared_ptr<mapping_validator> ptr; typedef boost::shared_ptr<mapping_validator> ptr;
typedef btree_checker<2, block_traits> super; typedef btree_checker<2, block_traits> checker;
mapping_validator(block_counter &metadata_counter, block_counter &data_counter) mapping_validator(block_counter &metadata_counter, block_counter &data_counter)
: super(metadata_counter), : checker_(metadata_counter),
data_counter_(data_counter) { data_counter_(data_counter)
{
}
bool visit_internal(unsigned level,
bool sub_root,
optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) {
return checker_.visit_internal(level, sub_root, key, n);
} }
// Sharing can only occur in level 1 nodes.
// FIXME: not true once we start having held roots.
bool visit_internal_leaf(unsigned level, bool visit_internal_leaf(unsigned level,
bool sub_root, bool sub_root,
optional<uint64_t> key, optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) { btree_detail::node_ref<uint64_traits> const &n) {
bool r = super::visit_internal_leaf(level, sub_root, key, n); bool r = checker_.visit_internal_leaf(level, sub_root, key, n);
if (!r && level == 0) {
throw runtime_error("unexpected sharing in level 0 of mapping tree.");
}
for (unsigned i = 0; i < n.get_nr_entries(); i++) for (unsigned i = 0; i < n.get_nr_entries(); i++)
devices_.insert(n.key_at(i)); devices_.insert(n.key_at(i));
@ -59,7 +62,7 @@ namespace {
bool sub_root, bool sub_root,
optional<uint64_t> key, optional<uint64_t> key,
btree_detail::node_ref<block_traits> const &n) { btree_detail::node_ref<block_traits> const &n) {
bool r = super::visit_leaf(level, sub_root, key, n); bool r = checker_.visit_leaf(level, sub_root, key, n);
if (r) if (r)
for (unsigned i = 0; i < n.get_nr_entries(); i++) for (unsigned i = 0; i < n.get_nr_entries(); i++)
@ -73,30 +76,46 @@ namespace {
} }
private: private:
checker checker_;
block_counter &data_counter_; block_counter &data_counter_;
set<uint64_t> devices_; set<uint64_t> devices_;
}; };
class details_validator : public btree_checker<1, device_details_traits> { class details_validator : public btree<1, device_details_traits>::visitor {
public: public:
typedef boost::shared_ptr<details_validator> ptr; typedef boost::shared_ptr<details_validator> ptr;
typedef btree_checker<1, device_details_traits> super; typedef btree_checker<1, device_details_traits> checker;
details_validator(block_counter &counter) details_validator(block_counter &counter)
: super(counter) { : checker_(counter) {
}
bool visit_internal(unsigned level,
bool sub_root,
optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) {
return checker_.visit_internal(level, sub_root, key, n);
}
bool visit_internal_leaf(unsigned level,
bool sub_root,
optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) {
return checker_.visit_internal_leaf(level, sub_root, key, n);
} }
bool visit_leaf(unsigned level, bool visit_leaf(unsigned level,
bool sub_root, bool sub_root,
optional<uint64_t> key, optional<uint64_t> key,
btree_detail::node_ref<device_details_traits> const &n) { btree_detail::node_ref<device_details_traits> const &n) {
bool r = super::visit_leaf(level, sub_root, key, n);
if (r) if (!checker_.visit_leaf(level, sub_root, key, n))
for (unsigned i = 0; i < n.get_nr_entries(); i++) return false;
devices_.insert(n.key_at(i));
return r; for (unsigned i = 0; i < n.get_nr_entries(); i++)
devices_.insert(n.key_at(i));
return true;
} }
set<uint64_t> const &get_devices() const { set<uint64_t> const &get_devices() const {
@ -104,6 +123,7 @@ namespace {
} }
private: private:
checker checker_;
set<uint64_t> devices_; set<uint64_t> devices_;
}; };

View File

@ -27,28 +27,48 @@ namespace {
class mappings_extractor : public btree<2, block_traits>::visitor { class mappings_extractor : public btree<2, block_traits>::visitor {
public: public:
typedef boost::shared_ptr<mappings_extractor> ptr; typedef boost::shared_ptr<mappings_extractor> ptr;
typedef btree_checker<2, block_traits> checker;
mappings_extractor(uint64_t dev_id, emitter::ptr e, mappings_extractor(uint64_t dev_id, emitter::ptr e,
space_map::ptr md_sm, space_map::ptr data_sm) space_map::ptr md_sm, space_map::ptr data_sm)
: dev_id_(dev_id), : counter_(),
checker_(counter_),
dev_id_(dev_id),
e_(e), e_(e),
md_sm_(md_sm), md_sm_(md_sm),
data_sm_(data_sm), data_sm_(data_sm),
in_range_(false) { in_range_(false),
found_errors_(false) {
} }
bool visit_internal(unsigned level, bool sub_root, boost::optional<uint64_t> key, bool visit_internal(unsigned level, bool sub_root, boost::optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) { btree_detail::node_ref<uint64_traits> const &n) {
if (!checker_.visit_internal(level, sub_root, key, n)) {
found_errors_ = true;
return false;
}
return (sub_root && key) ? (*key == dev_id_) : true; return (sub_root && key) ? (*key == dev_id_) : true;
} }
bool visit_internal_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> key, bool visit_internal_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) { btree_detail::node_ref<uint64_traits> const &n) {
if (!checker_.visit_internal_leaf(level, sub_root, key, n)) {
found_errors_ = true;
return false;
}
return true; return true;
} }
bool visit_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> maybe_key, bool visit_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> maybe_key,
btree_detail::node_ref<block_traits> const &n) { btree_detail::node_ref<block_traits> const &n) {
if (!checker_.visit_leaf(level, sub_root, maybe_key, n)) {
found_errors_ = true;
return false;
}
for (unsigned i = 0; i < n.get_nr_entries(); i++) { for (unsigned i = 0; i < n.get_nr_entries(); i++) {
block_time bt = n.value_at(i); block_time bt = n.value_at(i);
add_mapping(n.key_at(i), bt.block_, bt.time_); add_mapping(n.key_at(i), bt.block_, bt.time_);
@ -57,11 +77,14 @@ namespace {
return true; return true;
} }
void visit_complete() { void visit_complete() {
end_mapping(); end_mapping();
} }
bool corruption() const {
return !checker_.get_errors()->empty();
}
private: private:
void start_mapping(uint64_t origin_block, uint64_t dest_block, uint32_t time) { void start_mapping(uint64_t origin_block, uint64_t dest_block, uint32_t time) {
origin_start_ = origin_block; origin_start_ = origin_block;
@ -97,6 +120,9 @@ namespace {
} }
} }
// Declaration order of counter_ and checker_ is important.
block_counter counter_;
checker checker_;
uint64_t dev_id_; uint64_t dev_id_;
emitter::ptr e_; emitter::ptr e_;
space_map::ptr md_sm_; space_map::ptr md_sm_;
@ -105,28 +131,34 @@ namespace {
bool in_range_; bool in_range_;
uint64_t origin_start_, dest_start_, len_; uint64_t origin_start_, dest_start_, len_;
uint32_t time_; uint32_t time_;
bool found_errors_;
}; };
class details_extractor : public btree<1, device_details_traits>::visitor { class details_extractor : public btree<1, device_details_traits>::visitor {
public: public:
typedef boost::shared_ptr<details_extractor> ptr; typedef boost::shared_ptr<details_extractor> ptr;
typedef btree_checker<1, device_details_traits> checker;
details_extractor() { details_extractor()
: counter_(),
checker_(counter_, false) {
} }
bool visit_internal(unsigned level, bool sub_root, boost::optional<uint64_t> key, bool visit_internal(unsigned level, bool sub_root, boost::optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) { btree_detail::node_ref<uint64_traits> const &n) {
return true; return checker_.visit_internal(level, sub_root, key, n);
} }
bool visit_internal_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> key, bool visit_internal_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> key,
btree_detail::node_ref<uint64_traits> const &n) { btree_detail::node_ref<uint64_traits> const &n) {
return true; return checker_.visit_internal_leaf(level, sub_root, key, n);
} }
bool visit_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> maybe_key, bool visit_leaf(unsigned level, bool sub_root, boost::optional<uint64_t> maybe_key,
btree_detail::node_ref<device_details_traits> const &n) { btree_detail::node_ref<device_details_traits> const &n) {
if (!checker_.visit_leaf(level, sub_root, maybe_key, n))
return false;
for (unsigned i = 0; i < n.get_nr_entries(); i++) for (unsigned i = 0; i < n.get_nr_entries(); i++)
devices_.insert(make_pair(n.key_at(i), n.value_at(i))); devices_.insert(make_pair(n.key_at(i), n.value_at(i)));
@ -137,7 +169,14 @@ namespace {
return devices_; return devices_;
} }
bool corruption() const {
return !checker_.get_errors()->empty();
}
private: private:
// Declaration order of counter_ and checker_ is important.
block_counter counter_;
checker checker_;
map<uint64_t, device_details> devices_; map<uint64_t, device_details> devices_;
}; };
} }
@ -145,13 +184,16 @@ namespace {
//---------------------------------------------------------------- //----------------------------------------------------------------
void void
thin_provisioning::metadata_dump(metadata::ptr md, emitter::ptr e) thin_provisioning::metadata_dump(metadata::ptr md, emitter::ptr e, bool repair)
{ {
e->begin_superblock("", md->sb_.time_, md->sb_.trans_id_, md->sb_.data_block_size_); e->begin_superblock("", md->sb_.time_, md->sb_.trans_id_, md->sb_.data_block_size_);
details_extractor::ptr de(new details_extractor); details_extractor::ptr de(new details_extractor);
md->details_->visit(de); md->details_->visit(de);
if (de->corruption() && !repair)
throw runtime_error("corruption in device details tree");
map<uint64_t, device_details> const &devs = de->get_devices(); map<uint64_t, device_details> const &devs = de->get_devices();
map<uint64_t, device_details>::const_iterator it, end = devs.end(); map<uint64_t, device_details>::const_iterator it, end = devs.end();
@ -168,6 +210,12 @@ thin_provisioning::metadata_dump(metadata::ptr md, emitter::ptr e)
mappings_extractor::ptr me(new mappings_extractor(dev_id, e, md->metadata_sm_, md->data_sm_)); mappings_extractor::ptr me(new mappings_extractor(dev_id, e, md->metadata_sm_, md->data_sm_));
md->mappings_->visit(me); md->mappings_->visit(me);
if (me->corruption() && !repair) {
ostringstream out;
out << "corruption in mappings for device " << dev_id;
throw runtime_error(out.str());
}
e->end_device(); e->end_device();
} }

View File

@ -25,7 +25,10 @@
//---------------------------------------------------------------- //----------------------------------------------------------------
namespace thin_provisioning { namespace thin_provisioning {
void metadata_dump(metadata::ptr md, emitter::ptr e); // Set the @repair flag if your metadata is corrupt, and you'd like
// the dumper to do it's best to recover info. If not set, any
// corruption encountered will cause an exception to be thrown.
void metadata_dump(metadata::ptr md, emitter::ptr e, bool repair);
} }
//---------------------------------------------------------------- //----------------------------------------------------------------

View File

@ -30,11 +30,16 @@ using namespace thin_provisioning;
namespace { namespace {
int check(string const &path) { int check(string const &path) {
metadata::ptr md(new metadata(path, metadata::OPEN)); try {
metadata::ptr md(new metadata(path, metadata::OPEN));
optional<error_set::ptr> maybe_errors = metadata_check(md); optional<error_set::ptr> maybe_errors = metadata_check(md);
if (maybe_errors) { if (maybe_errors) {
cerr << error_selector(*maybe_errors, 3); cerr << error_selector(*maybe_errors, 3);
return 1;
}
} catch (std::exception &e) {
cerr << e.what();
return 1; return 1;
} }

View File

@ -33,59 +33,76 @@ using namespace thin_provisioning;
//---------------------------------------------------------------- //----------------------------------------------------------------
namespace { namespace {
void dump(string const &path, string const &format) { int dump(string const &path, string const &format, bool repair) {
metadata::ptr md(new metadata(path, metadata::OPEN)); try {
emitter::ptr e; metadata::ptr md(new metadata(path, metadata::OPEN));
emitter::ptr e;
if (format == "xml") if (format == "xml")
e = create_xml_emitter(cout); e = create_xml_emitter(cout);
else if (format == "human_readable") else if (format == "human_readable")
e = create_human_readable_emitter(cout); e = create_human_readable_emitter(cout);
else { else {
cerr << "unknown format '" << format << "'" << endl; cerr << "unknown format '" << format << "'" << endl;
exit(1); exit(1);
}
metadata_dump(md, e, repair);
} catch (std::exception &e) {
cerr << e.what();
return 1;
} }
metadata_dump(md, e); return 0;
} }
void usage(string const &cmd) { void usage(string const &cmd) {
cerr << "Usage: " << cmd << " [options] {device|file}" << endl << endl; cerr << "Usage: " << cmd << " [options] {device|file}" << endl << endl
cerr << "Options:" << endl; << "Options:" << endl
cerr << " {-h|--help}" << endl; << " {-h|--help}" << endl
cerr << " {-f|--format} {xml|human_readable}" << endl; << " {-f|--format} {xml|human_readable}" << endl
cerr << " {-i|--input} {xml|human_readable} input_file" << endl; << " {-i|--input} {xml|human_readable} input_file" << endl
cerr << " {-V|--version}" << endl; << " {-r|--repair}" << endl
<< " {-V|--version}" << endl;
} }
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int c; int c;
bool repair = false;
const char shortopts[] = "hf:i:V"; const char shortopts[] = "hf:i:V";
string filename, format = "xml"; string filename, format = "xml";
const struct option longopts[] = { const struct option longopts[] = {
{ "help", no_argument, NULL, 'h'}, { "help", no_argument, NULL, 'h'},
{ "format", required_argument, NULL, 'f' }, { "format", required_argument, NULL, 'f' },
{ "input", required_argument, NULL, 'i'}, { "input", required_argument, NULL, 'i'},
{ "repair", no_argument, NULL, 'r'},
{ "version", no_argument, NULL, 'V'}, { "version", no_argument, NULL, 'V'},
{ NULL, no_argument, NULL, 0 } { NULL, no_argument, NULL, 0 }
}; };
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch(c) { switch(c) {
case 'h': case 'h':
usage(basename(argv[0])); usage(basename(argv[0]));
return 0; return 0;
case 'f':
format = optarg; case 'f':
break; format = optarg;
case 'i': break;
filename = optarg;
break; case 'i':
case 'V': filename = optarg;
cerr << THIN_PROVISIONING_TOOLS_VERSION << endl; break;
return 0;
case 'r':
repair = true;
break;
case 'V':
cerr << THIN_PROVISIONING_TOOLS_VERSION << endl;
return 0;
} }
} }
@ -99,8 +116,7 @@ int main(int argc, char **argv)
return 1; return 1;
} }
dump(filename, format); return dump(filename, format, repair);
return 0;
} }
//---------------------------------------------------------------- //----------------------------------------------------------------

View File

@ -35,22 +35,21 @@ using namespace thin_provisioning;
//---------------------------------------------------------------- //----------------------------------------------------------------
namespace { namespace {
void restore(string const &backup_file, string const &dev) { int restore(string const &backup_file, string const &dev) {
// FIXME: hard coded try {
block_address const NR_BLOCKS = 100000; // FIXME: hard coded
block_address const NR_BLOCKS = 100000;
metadata::ptr md(new metadata(dev, metadata::CREATE, 128, NR_BLOCKS)); metadata::ptr md(new metadata(dev, metadata::CREATE, 128, NR_BLOCKS));
emitter::ptr restorer = create_restore_emitter(md); emitter::ptr restorer = create_restore_emitter(md);
ifstream in(backup_file.c_str(), ifstream::in); ifstream in(backup_file.c_str(), ifstream::in);
// FIXME:
//try {
parse_xml(in, restorer); parse_xml(in, restorer);
#if 0 } catch (std::exception &e) {
} catch (...) { cerr << e.what();
in.close(); return 1;
throw;
} }
#endif
return 0;
} }
void usage(string const &cmd) { void usage(string const &cmd) {
@ -108,8 +107,7 @@ int main(int argc, char **argv)
return 1; return 1;
} }
restore(input, output); return restore(input, output);
return 0;
} }