thin-provisioning-tools/caching/superblock.cc
Joe Thornber 88b0c2ebae [cache-restore] Fix crash with --override-metadata-version flag.
If the actual metadata version is 1, then the dirty bitset will not be
present.  But the superblock was trying to write it because of the
overridden md version.
2017-08-29 13:17:45 +01:00

442 lines
12 KiB
C++

#include "base/bits.h"
#include "caching/superblock.h"
using namespace base;
using namespace caching;
using namespace superblock_damage;
//----------------------------------------------------------------
namespace {
using namespace base;
struct superblock_disk {
le32 csum;
le32 flags;
le64 blocknr;
__u8 uuid[16];
le64 magic;
le32 version;
__u8 policy_name[CACHE_POLICY_NAME_SIZE];
le32 policy_hint_size;
__u8 metadata_space_map_root[SPACE_MAP_ROOT_SIZE];
le64 mapping_root;
le64 hint_root;
le64 discard_root;
le64 discard_block_size;
le64 discard_nr_blocks;
le32 data_block_size; /* in 512-byte sectors */
le32 metadata_block_size; /* in 512-byte sectors */
le32 cache_blocks;
le32 compat_flags;
le32 compat_ro_flags;
le32 incompat_flags;
le32 read_hits;
le32 read_misses;
le32 write_hits;
le32 write_misses;
le32 policy_version[CACHE_POLICY_VERSION_SIZE];
le64 dirty_root; // format 2 only
} __attribute__ ((packed));
struct superblock_traits {
typedef superblock_disk disk_type;
typedef superblock value_type;
static void unpack(superblock_disk const &disk, superblock &value);
static void pack(superblock const &value, superblock_disk &disk);
};
uint32_t const SUPERBLOCK_MAGIC = 06142003;
uint32_t const VERSION_BEGIN = 1;
uint32_t const VERSION_END = 3;
}
//----------------------------------------------------------------
superblock_flags::superblock_flags()
: unhandled_flags_(0)
{
}
superblock_flags::superblock_flags(uint32_t bits)
{
if (bits & (1 << CLEAN_SHUTDOWN_BIT)) {
flags_.insert(CLEAN_SHUTDOWN);
bits &= ~(1 << CLEAN_SHUTDOWN_BIT);
}
if (bits & (1u << NEEDS_CHECK_BIT)) {
flags_.insert(NEEDS_CHECK);
bits &= ~(1u << NEEDS_CHECK_BIT);
}
unhandled_flags_ = bits;
}
void
superblock_flags::set_flag(superblock_flags::flag f)
{
flags_.insert(f);
}
void
superblock_flags::clear_flag(superblock_flags::flag f)
{
flags_.erase(f);
}
bool
superblock_flags::get_flag(flag f) const
{
return flags_.find(f) != flags_.end();
}
uint32_t
superblock_flags::encode() const
{
uint32_t r = 0;
if (get_flag(CLEAN_SHUTDOWN))
r = r | (1 << CLEAN_SHUTDOWN_BIT);
if (get_flag(NEEDS_CHECK))
r = r | (1u << NEEDS_CHECK_BIT);
return r;
}
uint32_t
superblock_flags::get_unhandled_flags() const
{
return unhandled_flags_;
}
//----------------------------------------------------------------
superblock::superblock()
: csum(0),
blocknr(SUPERBLOCK_LOCATION),
magic(SUPERBLOCK_MAGIC),
version(VERSION_END - 1u),
policy_hint_size(4),
mapping_root(0),
hint_root(0),
discard_root(0),
discard_block_size(0),
discard_nr_blocks(0),
data_block_size(0),
metadata_block_size(8),
cache_blocks(0),
compat_flags(0),
compat_ro_flags(0),
incompat_flags(0),
read_hits(0),
read_misses(0),
write_hits(0),
write_misses(0)
{
::memset(uuid, 0, sizeof(uuid));
::memset(policy_name, 0, sizeof(policy_name));
::memset(policy_version, 0, sizeof(policy_version));
::memset(metadata_space_map_root, 0, sizeof(metadata_space_map_root));
}
//----------------------------------------------------------------
void
superblock_traits::unpack(superblock_disk const &disk, superblock &core)
{
core.compat_flags = to_cpu<uint32_t>(disk.compat_flags);
core.compat_ro_flags = to_cpu<uint32_t>(disk.compat_ro_flags);
core.incompat_flags = to_cpu<uint32_t>(disk.incompat_flags);
core.csum = to_cpu<uint32_t>(disk.csum);
core.flags = superblock_flags(to_cpu<uint32_t>(disk.flags));
core.blocknr = to_cpu<uint64_t>(disk.blocknr);
::memcpy(core.uuid, disk.uuid, sizeof(core.uuid));
core.magic = to_cpu<uint64_t>(disk.magic);
core.version = to_cpu<uint32_t>(disk.version);
::memcpy(core.policy_name, disk.policy_name, sizeof(core.policy_name));
for (unsigned i = 0; i < CACHE_POLICY_VERSION_SIZE; i++)
core.policy_version[i] = to_cpu<uint32_t>(disk.policy_version[i]);
core.policy_hint_size = test_bit(core.incompat_flags, VARIABLE_HINT_SIZE_BIT) ?
to_cpu<uint32_t>(disk.policy_hint_size) : 4;
::memcpy(core.metadata_space_map_root,
disk.metadata_space_map_root,
sizeof(core.metadata_space_map_root));
core.mapping_root = to_cpu<uint64_t>(disk.mapping_root);
core.hint_root = to_cpu<uint64_t>(disk.hint_root);
core.discard_root = to_cpu<uint64_t>(disk.discard_root);
core.discard_block_size = to_cpu<uint64_t>(disk.discard_block_size);
core.discard_nr_blocks = to_cpu<uint64_t>(disk.discard_nr_blocks);
core.data_block_size = to_cpu<uint32_t>(disk.data_block_size);
core.metadata_block_size = to_cpu<uint32_t>(disk.metadata_block_size);
core.cache_blocks = to_cpu<uint32_t>(disk.cache_blocks);
core.read_hits = to_cpu<uint32_t>(disk.read_hits);
core.read_misses = to_cpu<uint32_t>(disk.read_misses);
core.write_hits = to_cpu<uint32_t>(disk.write_hits);
core.write_misses = to_cpu<uint32_t>(disk.write_misses);
if (core.version >= 2)
core.dirty_root = to_cpu<uint64_t>(disk.dirty_root);
}
void
superblock_traits::pack(superblock const &sb, superblock_disk &disk)
{
// We adjust some of the flags in the superblock, so make a copy
superblock core(sb);
disk.csum = to_disk<le32>(core.csum);
disk.flags = to_disk<le32>(core.flags.encode());
disk.blocknr = to_disk<le64>(core.blocknr);
::memcpy(disk.uuid, core.uuid, sizeof(disk.uuid));
disk.magic = to_disk<le64>(core.magic);
disk.version = to_disk<le32>(core.version);
::memcpy(disk.policy_name, core.policy_name, sizeof(disk.policy_name));
for (unsigned i = 0; i < CACHE_POLICY_VERSION_SIZE; i++)
disk.policy_version[i] = to_disk<le32>(core.policy_version[i]);
if (core.policy_hint_size != 4) {
set_bit(core.incompat_flags, VARIABLE_HINT_SIZE_BIT);
disk.policy_hint_size = to_disk<le32>(core.policy_hint_size);
} else {
clear_bit(core.incompat_flags, VARIABLE_HINT_SIZE_BIT);
disk.policy_hint_size = to_disk<le32>(0u);
}
::memcpy(disk.metadata_space_map_root,
core.metadata_space_map_root,
sizeof(disk.metadata_space_map_root));
disk.mapping_root = to_disk<le64>(core.mapping_root);
disk.hint_root = to_disk<le64>(core.hint_root);
disk.discard_root = to_disk<le64>(core.discard_root);
disk.discard_block_size = to_disk<le64>(core.discard_block_size);
disk.discard_nr_blocks = to_disk<le64>(core.discard_nr_blocks);
disk.data_block_size = to_disk<le32>(core.data_block_size);
disk.metadata_block_size = to_disk<le32>(core.metadata_block_size);
disk.cache_blocks = to_disk<le32>(core.cache_blocks);
disk.compat_flags = to_disk<le32>(core.compat_flags);
disk.compat_ro_flags = to_disk<le32>(core.compat_ro_flags);
disk.incompat_flags = to_disk<le32>(core.incompat_flags);
disk.read_hits = to_disk<le32>(core.read_hits);
disk.read_misses = to_disk<le32>(core.read_misses);
disk.write_hits = to_disk<le32>(core.write_hits);
disk.write_misses = to_disk<le32>(core.write_misses);
// The version may be overridden, meaning the dirty root may not
// actually be present.
if (core.version >= 2 && core.dirty_root)
disk.dirty_root = to_disk<le64>(*core.dirty_root);
}
//--------------------------------
superblock_corrupt::superblock_corrupt(std::string const &desc)
: damage(desc)
{
}
void
superblock_corrupt::visit(damage_visitor &v) const
{
v.visit(*this);
}
superblock_invalid::superblock_invalid(std::string const &desc)
: damage(desc)
{
}
void
superblock_invalid::visit(damage_visitor &v) const
{
v.visit(*this);
}
//--------------------------------
// anonymous namespace doesn't work for some reason
namespace validator {
using namespace persistent_data;
uint32_t const VERSION = 1;
unsigned const SECTOR_TO_BLOCK_SHIFT = 3;
uint32_t const SUPERBLOCK_CSUM_SEED = 9031977;
struct sb_validator : public bcache::validator {
virtual void check(void const *raw, block_address location) const {
superblock_disk const *sbd = reinterpret_cast<superblock_disk const *>(raw);
crc32c sum(SUPERBLOCK_CSUM_SEED);
sum.append(&sbd->flags, MD_BLOCK_SIZE - sizeof(uint32_t));
if (sum.get_sum() != to_cpu<uint32_t>(sbd->csum))
throw checksum_error("bad checksum in superblock");
}
virtual bool check_raw(void const *raw) const {
superblock_disk const *sbd = reinterpret_cast<superblock_disk const *>(raw);
crc32c sum(SUPERBLOCK_CSUM_SEED);
sum.append(&sbd->flags, MD_BLOCK_SIZE - sizeof(uint32_t));
if (sum.get_sum() != to_cpu<uint32_t>(sbd->csum))
return false;
return true;
}
virtual void prepare(void *raw, block_address location) const {
superblock_disk *sbd = reinterpret_cast<superblock_disk *>(raw);
crc32c sum(SUPERBLOCK_CSUM_SEED);
sum.append(&sbd->flags, MD_BLOCK_SIZE - sizeof(uint32_t));
sbd->csum = to_disk<base::le32>(sum.get_sum());
}
};
bcache::validator::ptr mk_v() {
return bcache::validator::ptr(new sb_validator);
}
}
//--------------------------------
superblock
caching::read_superblock(block_manager<>::ptr bm, block_address location)
{
using namespace validator;
superblock sb;
block_manager<>::read_ref r = bm->read_lock(location, mk_v());
superblock_disk const *sbd = reinterpret_cast<superblock_disk const *>(r.data());
superblock_traits::unpack(*sbd, sb);
return sb;
}
void
caching::write_superblock(block_manager<>::ptr bm, superblock const &sb, block_address location)
{
using namespace validator;
block_manager<>::write_ref w = bm->superblock_zero(location, mk_v());
superblock_traits::pack(sb, *reinterpret_cast<superblock_disk *>(w.data()));
}
void
caching::check_superblock(superblock const &sb,
block_address nr_metadata_blocks,
damage_visitor &visitor)
{
if (sb.flags.get_unhandled_flags()) {
ostringstream msg;
msg << "invalid flags: " << sb.flags.get_unhandled_flags();
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.blocknr >= nr_metadata_blocks) {
ostringstream msg;
msg << "blocknr out of bounds: " << sb.blocknr << " >= " << nr_metadata_blocks;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.magic != SUPERBLOCK_MAGIC) {
ostringstream msg;
msg << "magic in incorrect: " << sb.magic;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.version >= VERSION_END) {
ostringstream msg;
msg << "version incorrect: " << sb.version;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.version < VERSION_BEGIN) {
ostringstream msg;
msg << "version incorrect: " << sb.version;
visitor.visit(superblock_invalid(msg.str()));
}
if (::strnlen((char const *) sb.policy_name, CACHE_POLICY_NAME_SIZE) == CACHE_POLICY_NAME_SIZE) {
visitor.visit(superblock_invalid("policy name is not null terminated"));
}
if (sb.policy_hint_size % 4 || sb.policy_hint_size > 128) {
ostringstream msg;
msg << "policy hint size invalid: " << sb.policy_hint_size;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.metadata_block_size != 8) {
ostringstream msg;
msg << "metadata block size incorrect: " << sb.metadata_block_size;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.compat_flags != 0) {
ostringstream msg;
msg << "compat_flags invalid (can only be 0): " << sb.compat_flags;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.compat_ro_flags != 0) {
ostringstream msg;
msg << "compat_ro_flags invalid (can only be 0): " << sb.compat_ro_flags;
visitor.visit(superblock_invalid(msg.str()));
}
if (sb.incompat_flags & ~(1 << VARIABLE_HINT_SIZE_BIT)) {
ostringstream msg;
msg << "incompat_flags invalid (can only be 0): " << sb.incompat_flags;
visitor.visit(superblock_invalid(msg.str()));
}
}
void
caching::check_superblock(persistent_data::block_manager<>::ptr bm,
block_address nr_metadata_blocks,
damage_visitor &visitor)
{
superblock sb;
try {
sb = read_superblock(bm, SUPERBLOCK_LOCATION);
} catch (std::exception const &e) {
// FIXME: what if it fails due to a zero length file? Not
// really a corruption, so much as an io error. Should we
// separate these?
visitor.visit(superblock_corrupt(e.what()));
}
check_superblock(sb, nr_metadata_blocks, visitor);
}
//----------------------------------------------------------------