// This file is part of the thin-provisioning-tools source.
//
// thin-provisioning-tools is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// thin-provisioning-tools is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with thin-provisioning-tools. If not, see
// .
#include
#include
#include
#include
#include
#include "persistent-data/data-structures/btree.h"
#include "persistent-data/data-structures/simple_traits.h"
#include "persistent-data/file_utils.h"
#include "persistent-data/space-maps/core.h"
#include "persistent-data/space-maps/disk_structures.h"
#include "thin-provisioning/metadata.h"
#include "thin-provisioning/superblock.h"
#include "thin-provisioning/commands.h"
#include "version.h"
using namespace boost;
using namespace std;
using namespace thin_provisioning;
//----------------------------------------------------------------
namespace {
bool check_flags(uint32_t flags) {
flags &= 0x3;
if (flags == INTERNAL_NODE || flags == LEAF_NODE)
return true;
return false;
}
// 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;
}
}
namespace {
uint32_t const SUPERBLOCK_CSUM_SEED = 160774;
uint32_t const BITMAP_CSUM_XOR = 240779;
uint32_t const INDEX_CSUM_XOR = 160478;
uint32_t const BTREE_CSUM_XOR = 121107;
enum metadata_block_type {
UNKNOWN = 0,
ZERO,
SUPERBLOCK,
INDEX_BLOCK,
BITMAP_BLOCK,
BTREE_NODE
};
// For UNKNOWN and ZERO
class block_range {
public:
block_range()
: begin_(0),
end_(0),
type_(UNKNOWN),
is_valid_(false),
ref_count_(-1) {
}
block_range(block_range const &rhs)
: begin_(rhs.begin_),
end_(rhs.end_),
type_(rhs.type_),
is_valid_(rhs.is_valid_),
ref_count_(rhs.ref_count_) {
}
virtual ~block_range() {}
virtual void reset(int type,
typename block_manager::read_ref &rr,
int64_t ref_count) {
begin_ = rr.get_location();
end_ = begin_ + 1;
type_ = type;
ref_count_ = ref_count;
is_valid_ = false;
}
virtual std::unique_ptr clone() const {
return std::unique_ptr(new block_range(*this));
}
inline uint64_t size() const {
return (end_ > begin_) ? (end_ - begin_) : 0;
}
// returns true if r is left or right-adjacent
bool is_adjacent_to(block_range const &r) const {
if (begin_ < r.begin_)
return is_adjacent_to_(r);
return r.is_adjacent_to_(*this);
}
bool concat(block_range const &r) {
if (!is_adjacent_to(r))
return false;
begin_ = std::min(begin_, r.begin_);
end_ = std::max(end_, r.end_);
return true;
}
virtual char const *type_name() const {
switch (type_) {
case ZERO:
return "zero";
default:
return "unknown";
}
}
virtual void print(std::ostream &out) const {
uint64_t s = size();
if (s > 1) {
out << "";
} else if (s == 1) {
out << "";
}
}
friend ostream &operator<<(std::ostream &out, block_range const &r);
protected:
// return true is rhs is right-adjacent
virtual bool is_adjacent_to_(block_range const &rhs) const {
if (type_ != rhs.type_)
return false;
if (rhs.begin_ != end_)
return false;
if (ref_count_ != rhs.ref_count_ ||
is_valid_ != rhs.is_valid_)
return false;
return true;
}
uint64_t begin_;
uint64_t end_; // one-pass-the-end. end_ == begin_ indicates an empty range.
int type_;
bool is_valid_;
int64_t ref_count_; // ref_count in metadata space map
};
// For SUPERBLOCK, INDEX_BLOCK and BITMAP_BLOCK
class meta_block_range: public block_range {
public:
meta_block_range()
: block_range(),
blocknr_begin_(0) {
}
meta_block_range(meta_block_range const &rhs)
: block_range(rhs),
blocknr_begin_(rhs.blocknr_begin_) {
}
virtual void reset(int type,
typename block_manager::read_ref &rr,
int64_t ref_count) {
using namespace persistent_data;
using namespace sm_disk_detail;
using namespace superblock_detail;
begin_ = rr.get_location();
end_ = begin_ + 1;
type_ = type;
ref_count_ = ref_count;
switch (type) {
case SUPERBLOCK:
blocknr_begin_ = to_cpu(reinterpret_cast(rr.data())->blocknr_);
break;
case BITMAP_BLOCK:
blocknr_begin_ = to_cpu(reinterpret_cast(rr.data())->blocknr);
break;
case INDEX_BLOCK:
blocknr_begin_ = to_cpu(reinterpret_cast(rr.data())->blocknr_);
break;
default:
blocknr_begin_ = 0;
}
is_valid_ = (blocknr_begin_ == begin_) ? true : false;
}
virtual std::unique_ptr clone() const {
return std::unique_ptr(new meta_block_range(*this));
}
virtual char const *type_name() const {
switch (type_) {
case SUPERBLOCK:
return "superblock";
case INDEX_BLOCK:
return "index_block";
case BITMAP_BLOCK:
return "bitmap_block";
default:
return "unknown";
}
}
virtual void print(std::ostream &out) const {
uint64_t s = size();
if (s > 1) {
out << "";
} else if (s == 1) {
out << "";
}
}
protected:
virtual bool is_adjacent_to_(block_range const &rhs) const {
if (!block_range::is_adjacent_to_(rhs))
return false;
meta_block_range const &r = dynamic_cast(rhs);
if (r.blocknr_begin_ < blocknr_begin_)
return false;
if (r.blocknr_begin_ - blocknr_begin_ != r.begin_ - begin_)
return false;
return true;
}
block_address blocknr_begin_; // block number in header
};
// For BTREE_NODE
class btree_block_range: public meta_block_range {
public:
btree_block_range()
: meta_block_range(),
flags_(0),
value_size_(0) {
}
btree_block_range(btree_block_range const &rhs)
: meta_block_range(rhs),
flags_(rhs.flags_),
value_size_(rhs.value_size_) {
}
virtual void reset(int type,
typename block_manager::read_ref &rr,
int64_t ref_count) {
node_ref n = btree_detail::to_node(rr);
begin_ = rr.get_location();
end_ = begin_ + 1;
type_ = type;
ref_count_ = ref_count;
blocknr_begin_ = n.get_block_nr();
flags_ = to_cpu(n.raw()->header.flags);
value_size_ = n.get_value_size();
if (check_flags(flags_) &&
check_block_nr(n) &&
check_max_entries(n) &&
check_nr_entries(n, true) &&
check_ordered_keys(n))
is_valid_ = true;
else
is_valid_ = false;
}
virtual std::unique_ptr clone() const {
return std::unique_ptr(new btree_block_range(*this));
}
virtual char const *type_name() const {
if ((flags_ & INTERNAL_NODE) && !(flags_ & LEAF_NODE))
return "btree_internal";
else if (flags_ & LEAF_NODE)
return "btree_leaf";
else
return "btree_unknown";
};
virtual void print(std::ostream &out) const {
uint64_t s = size();
if (s > 1) {
out << "";
} else if (s == 1) {
out << "";
}
}
protected:
virtual bool is_adjacent_to_(block_range const &rhs) const {
if (!meta_block_range::is_adjacent_to_(rhs))
return false;
btree_block_range const &r = dynamic_cast(rhs);
if ((flags_ & 0x3) != (r.flags_ & 0x3))
return false;
if (value_size_ != r.value_size_)
return false;
return true;
}
uint32_t flags_;
size_t value_size_;
};
ostream &operator<<(std::ostream &out, block_range const &r) {
r.print(out);
return out;
}
//-------------------------------------------------------------------
class range_factory {
public:
virtual ~range_factory() {}
block_range const &convert_to_range(block_manager::read_ref rr, int64_t ref_count) {
if (!memcmp(rr.data(), zeros_.data(), MD_BLOCK_SIZE)) {
br_.reset(ZERO, rr, ref_count);
return br_;
}
uint32_t const *cksum = reinterpret_cast(rr.data());
base::crc32c sum(*cksum);
sum.append(cksum + 1, MD_BLOCK_SIZE - sizeof(uint32_t));
switch (sum.get_sum()) {
case SUPERBLOCK_CSUM_SEED:
mbr_.reset(SUPERBLOCK, rr, ref_count);
return mbr_;
case INDEX_CSUM_XOR:
mbr_.reset(INDEX_BLOCK, rr, ref_count);
return mbr_;
case BITMAP_CSUM_XOR:
mbr_.reset(BITMAP_BLOCK, rr, ref_count);
return mbr_;
case BTREE_CSUM_XOR:
bbr_.reset(BTREE_NODE, rr, ref_count);
return bbr_;
default:
br_.reset(UNKNOWN, rr, ref_count);
return br_;
}
}
private:
static const std::vector zeros_;
// for internal caching only
block_range br_;
meta_block_range mbr_;
btree_block_range bbr_;
};
const std::vector range_factory::zeros_(MD_BLOCK_SIZE, 0);
class metadata_scanner {
public:
metadata_scanner(block_manager::ptr bm, uint64_t scan_begin, uint64_t scan_end,
bool check_for_strings)
: bm_(bm),
scan_begin_(scan_begin),
scan_end_(scan_end),
index_(scan_begin),
check_for_strings_(check_for_strings) {
if (scan_end_ <= scan_begin_)
throw std::runtime_error("badly formed region (end <= begin)");
// try to open metadata space-map (it's okay to fail)
try {
superblock_detail::superblock sb = read_superblock(bm);
tm_ = open_tm(bm, superblock_detail::SUPERBLOCK_LOCATION);
metadata_sm_ = open_metadata_sm(*tm_, &sb.metadata_space_map_root_);
tm_->set_sm(metadata_sm_);
} catch (std::exception &e) {
cerr << e.what() << endl;
}
// prefetch the first block
block_range const &r = read_block(index_++);
run_range_ = r.clone();
}
std::unique_ptr get_range() {
std::unique_ptr ret;
while (index_ < scan_end_) {
block_range const &r = read_block(index_++);
if (!run_range_->concat(r)) {
ret = std::move(run_range_);
run_range_ = r.clone();
break;
}
}
if (!ret) { // for the last run (index_ == scan_end_)
ret = std::move(run_range_);
run_range_.reset();
}
return ret;
}
map> const &get_strings() const {
return strings_;
}
private:
bool interesting_char(char c)
{
return isalnum(c) || ispunct(c);
}
unsigned printable_len(const char *b, const char *e)
{
const char *p = b;
while (p != e && interesting_char(*p))
p++;
return p - b;
}
// asci text within our metadata is a sure sign of corruption.
optional >
scan_strings(block_manager::read_ref rr)
{
vector r;
const char *data = reinterpret_cast(rr.data()), *end = data + MD_BLOCK_SIZE;
while (data < end) {
auto len = printable_len(data, end);
if (len >= 4)
r.push_back(string(data, data + len));
data += len + 1;
}
return r.size() ? optional>(r) : optional>();
}
block_range const &read_block(block_address b) {
block_manager::read_ref rr = bm_->read_lock(b);
int64_t ref_count;
try {
ref_count = metadata_sm_ ? static_cast(metadata_sm_->get_count(b)) : -1;
} catch (std::exception &e) {
ref_count = -1;
}
if (check_for_strings_) {
auto ss = scan_strings(rr);
if (ss) {
strings_.insert(make_pair(b, *ss));
}
}
return factory_.convert_to_range(rr, ref_count);
}
// note: space_map does not take the ownership of transaction_manager,
// so the transaction_manager must live in the same scope of space_map.
block_manager::ptr bm_;
transaction_manager::ptr tm_;
checked_space_map::ptr metadata_sm_;
uint64_t scan_begin_;
uint64_t scan_end_;
uint64_t index_;
std::unique_ptr run_range_;
range_factory factory_;
bool check_for_strings_;
map> strings_;
};
//-------------------------------------------------------------------
struct flags {
flags()
: exclusive_(true),
examine_corruption_(false)
{
}
boost::optional scan_begin_;
boost::optional scan_end_;
bool exclusive_;
bool examine_corruption_;
};
int scan_metadata_(string const &input,
std::ostream &out,
flags const &f) {
block_manager::ptr bm;
bm = open_bm(input, block_manager::READ_ONLY, f.exclusive_);
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();
metadata_scanner scanner(bm, scan_begin, scan_end, f.examine_corruption_);
std::unique_ptr r;
while ((r = scanner.get_range())) {
out << *r << std::endl;
}
if (f.examine_corruption_) {
auto ss = scanner.get_strings();
for (auto const &ps : ss) {
out << ps.first << ": ";
unsigned total = 0;
for (auto const &s : ps.second)
total += s.length();
out << total << " bytes of text\n";
}
}
return 0;
}
int scan_metadata(string const &input,
boost::optional output,
flags const &f) {
try {
if (output) {
std::ofstream out(output->c_str());
scan_metadata_(input, out, f);
} else
scan_metadata_(input, cout, f);
} catch (std::exception &e) {
cerr << e.what() << endl;
return 1;
}
return 0;
}
}
//---------------------------------------------------------------------------
thin_scan_cmd::thin_scan_cmd()
: command("thin_scan")
{
}
void
thin_scan_cmd::usage(std::ostream &out) const {
out << "Usage: " << get_name() << " [options] {device|file}\n"
<< "Options:\n"
<< " {-h|--help}\n"
<< " {-o|--output} \n"
<< " {--begin} \n"
<< " {--end} \n"
<< " {--examine-corruption}\n"
<< " {-V|--version}" << endl;
}
int
thin_scan_cmd::run(int argc, char **argv)
{
const char shortopts[] = "ho:V";
const struct option longopts[] = {
{ "help", no_argument, NULL, 'h'},
{ "output", required_argument, NULL, 'o'},
{ "version", no_argument, NULL, 'V'},
{ "begin", required_argument, NULL, 1},
{ "end", required_argument, NULL, 2},
{ "examine-corruption", no_argument, NULL, 3 },
{ NULL, no_argument, NULL, 0 }
};
boost::optional output;
flags f;
int c;
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch(c) {
case 'h':
usage(cout);
return 0;
case 'o':
output = optarg;
break;
case 'V':
cout << THIN_PROVISIONING_TOOLS_VERSION << endl;
return 0;
case 1:
try {
f.scan_begin_ = boost::lexical_cast(optarg);
} catch (std::exception &e) {
cerr << e.what() << endl;
return 1;
}
break;
case 2:
try {
f.scan_end_ = boost::lexical_cast(optarg);
} catch (std::exception &e) {
cerr << e.what() << endl;
return 1;
}
break;
case 3:
f.examine_corruption_ = true;
break;
default:
usage(cerr);
return 1;
}
}
if (argc == optind) {
cerr << "No input file provided." << endl;
usage(cerr);
return 1;
}
if (f.scan_begin_ && f.scan_end_ && (*f.scan_end_ <= *f.scan_begin_)) {
cerr << "badly formed region (end <= begin)" << endl;
return 1;
}
return scan_metadata(argv[optind], output, f);
}
//---------------------------------------------------------------------------