blob: dd0c3487a25c26cb8dcc6ac39d4a64ce259eea03 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/fvm/snapshot_metadata.h"
#include <lib/zx/status.h>
#include <zircon/types.h>
#include <memory>
#include <vector>
#include <fbl/span.h>
#include <safemath/checked_math.h>
#include "src/storage/fvm/fvm.h"
#include "src/storage/fvm/snapshot_metadata_format.h"
namespace fvm {
namespace {
// Returns a byte view of a fixed size struct.
template <typename T>
fbl::Span<const uint8_t> FixedSizeStructToSpan(const T& typed_content) {
return fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(&typed_content), sizeof(T));
}
// Returns a byte view of an array of fixed size structs.
template <typename T>
fbl::Span<const uint8_t> ArrayToSpan(const T* typed_content, size_t count) {
return fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(typed_content),
count * sizeof(T));
}
// Returns a byte view of an array of structs.
template <typename T>
fbl::Span<const uint8_t> ContainerToSpan(const T& container) {
if (container.empty()) {
return fbl::Span<const uint8_t>();
}
return fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(container.data()),
container.size() * sizeof(*container.data()));
}
} // namespace
size_t SnapshotMetadata::BytesNeeded(const SnapshotMetadataHeader& header) {
return header.AllocatedMetadataBytes();
}
SnapshotMetadata::SnapshotMetadata(std::unique_ptr<MetadataBuffer> data,
SnapshotMetadataCopy active_header)
: data_(std::move(data)), active_header_(active_header) {}
SnapshotMetadata::SnapshotMetadata(SnapshotMetadata&& o) noexcept { MoveFrom(std::move(o)); }
SnapshotMetadata& SnapshotMetadata::operator=(SnapshotMetadata&& o) noexcept {
MoveFrom(std::move(o));
return *this;
}
void SnapshotMetadata::MoveFrom(SnapshotMetadata&& o) {
data_ = std::move(o.data_);
active_header_ = o.active_header_;
}
void SnapshotMetadata::UpdateHash() {
SnapshotMetadataHeader* header = &GetHeader();
bzero(header->hash, sizeof(header->hash));
digest::Digest digest;
const uint8_t* hash = digest.Hash(header, header->AllocatedMetadataBytes());
memcpy(header->hash, hash, sizeof(header->hash));
}
size_t SnapshotMetadata::GetInactiveHeaderOffset() const {
return GetHeader().HeaderOffset(inactive_header());
}
void SnapshotMetadata::SwitchActiveHeaders() { active_header_ = OppositeHeader(active_header_); }
SnapshotMetadataHeader& SnapshotMetadata::GetHeader() const {
return *reinterpret_cast<SnapshotMetadataHeader*>(static_cast<uint8_t*>(data_->data()));
}
PartitionSnapshotState& SnapshotMetadata::GetPartitionStateEntry(size_t idx) const {
const SnapshotMetadataHeader& header = GetHeader();
ZX_ASSERT(idx < header.PartitionStateTableNumEntries());
return reinterpret_cast<PartitionSnapshotState*>(reinterpret_cast<uint8_t*>(data_->data()) +
header.PartitionStateTableOffset())[idx];
}
SnapshotExtentType& SnapshotMetadata::GetExtentTypeEntry(size_t idx) const {
const SnapshotMetadataHeader& header = GetHeader();
ZX_ASSERT(idx < header.ExtentTypeTableNumEntries());
return reinterpret_cast<SnapshotExtentType*>(reinterpret_cast<uint8_t*>(data_->data()) +
header.ExtentTypeTableOffset())[idx];
}
const MetadataBuffer* SnapshotMetadata::Get() const { return data_.get(); }
zx::status<SnapshotMetadata> SnapshotMetadata::Create(std::unique_ptr<MetadataBuffer> data_a,
std::unique_ptr<MetadataBuffer> data_b) {
if (data_a->size() < sizeof(SnapshotMetadataHeader)) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
if (data_b->size() < sizeof(SnapshotMetadataHeader)) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
// For now just assume header is valid. It may contain nonsense, but PickValidHeader will check
// this, and we can at least check that the offset is reasonable so we don't overflow now.
const auto* header = reinterpret_cast<const SnapshotMetadataHeader*>(data_a->data());
size_t meta_size = header->AllocatedMetadataBytes();
if (meta_size > data_a->size() || meta_size > data_b->size()) {
fprintf(stderr, "fvm: SnapshotMetadata (%lu bytes) too large for buffers (%lu and %lu bytes)\n",
meta_size, data_a->size(), data_b->size());
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
std::optional<SnapshotMetadataCopy> active_header =
PickValid(data_a.get(), data_b.get(), meta_size);
if (!active_header) {
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
std::unique_ptr<MetadataBuffer> data;
switch (active_header.value()) {
case SnapshotMetadataCopy::kPrimary:
data = std::move(data_a);
break;
case SnapshotMetadataCopy::kSecondary:
data = std::move(data_b);
break;
}
return zx::ok(SnapshotMetadata(std::move(data), active_header.value()));
}
zx::status<SnapshotMetadata> SnapshotMetadata::Synthesize(const PartitionSnapshotState* partitions,
size_t num_partitions,
const SnapshotExtentType* extents,
size_t num_extents) {
SnapshotMetadataHeader header(num_partitions, num_extents);
if (header.PartitionStateTableNumEntries() < num_partitions + 1 ||
header.ExtentTypeTableNumEntries() < num_extents) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t buffer_size = BytesNeeded(header);
std::unique_ptr<uint8_t[]> buf(new uint8_t[buffer_size]);
// TODO(fxbug.dev/59980) The first entries in the partition state tables must be unused.
// Remove this after we support zero-indexing.
std::vector<PartitionSnapshotState> actual_partitions(0);
if (num_partitions > 0) {
ZX_ASSERT(partitions != nullptr);
actual_partitions = std::vector<PartitionSnapshotState>(num_partitions + 1);
actual_partitions[0].Release();
for (size_t i = 0; i < num_partitions; ++i) {
actual_partitions[i + 1] = partitions[i];
}
}
const fbl::Span<const uint8_t> header_span = FixedSizeStructToSpan(header);
const fbl::Span<const uint8_t> partitions_span = ContainerToSpan(actual_partitions);
const fbl::Span<const uint8_t> extents_span = ArrayToSpan(extents, num_extents);
auto write_metadata = [&](size_t offset, size_t sz, const fbl::Span<const uint8_t>& span) {
ZX_ASSERT(offset + sz <= buffer_size);
ZX_ASSERT(sz >= span.size());
if (!span.empty()) {
memcpy(buf.get() + offset, span.data(), span.size());
}
bzero(buf.get() + offset + span.size(), sz - span.size());
};
write_metadata(0, fvm::kSnapshotMetadataHeaderMaxSize, header_span);
write_metadata(header.PartitionStateTableOffset(), header.PartitionStateTableSizeBytes(),
partitions_span);
write_metadata(header.ExtentTypeTableOffset(), header.ExtentTypeTableSizeBytes(), extents_span);
SnapshotMetadata metadata(std::make_unique<HeapMetadataBuffer>(std::move(buf), buffer_size),
SnapshotMetadataCopy::kPrimary);
metadata.UpdateHash();
return zx::ok(std::move(metadata));
}
bool SnapshotMetadata::CheckHash(const void* metadata, size_t meta_size) {
ZX_ASSERT(meta_size >= sizeof(SnapshotMetadataHeader));
const auto* header = static_cast<const SnapshotMetadataHeader*>(metadata);
fbl::Span<const uint8_t> meta_span(static_cast<const uint8_t*>(metadata), meta_size);
fbl::Span<const uint8_t> before_hash =
meta_span.subspan(0, offsetof(SnapshotMetadataHeader, hash));
fbl::Span<const uint8_t> after_hash =
meta_span.subspan(offsetof(SnapshotMetadataHeader, hash) + sizeof(header->hash));
uint8_t empty_hash[sizeof(header->hash)];
bzero(empty_hash, sizeof(empty_hash));
digest::Digest digest;
digest.Init();
digest.Update(before_hash.data(), before_hash.size());
digest.Update(empty_hash, sizeof(empty_hash));
digest.Update(after_hash.data(), after_hash.size());
digest.Final();
return digest == header->hash;
}
std::optional<SnapshotMetadataCopy> SnapshotMetadata::PickValid(const void* a, const void* b,
size_t meta_size) {
const auto& header_a = *reinterpret_cast<const SnapshotMetadataHeader*>(a);
const auto& header_b = *reinterpret_cast<const SnapshotMetadataHeader*>(b);
std::string header_error;
bool a_valid = false;
if (header_a.IsValid(header_error)) {
if (CheckHash(&header_a, meta_size)) {
a_valid = true;
} else {
fprintf(stderr, "fvm: Primary snapshot meta has invalid content hash\n");
}
} else {
fprintf(stderr, "fvm: Primary snapshot meta is invalid: %s\n", header_error.c_str());
}
header_error.clear();
bool b_valid = false;
if (header_b.IsValid(header_error)) {
if (CheckHash(&header_b, meta_size)) {
b_valid = true;
} else {
fprintf(stderr, "fvm: Secondary snapshot meta has invalid content hash\n");
}
} else {
fprintf(stderr, "fvm: Secondary snapshot meta is invalid: %s\n", header_error.c_str());
}
// Decide if we should use the primary or the b copy of snapshot metadata.
if (!a_valid && !b_valid) {
return std::nullopt;
}
if (a_valid && !b_valid) {
return SnapshotMetadataCopy::kPrimary;
}
if (!a_valid && b_valid) {
return SnapshotMetadataCopy::kSecondary;
}
// Both valid, pick the newest.
return header_a.generation >= header_b.generation ? SnapshotMetadataCopy::kPrimary
: SnapshotMetadataCopy::kSecondary;
}
} // namespace fvm