| // 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/metadata.h" |
| |
| #include <lib/zx/status.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| #include <fbl/span.h> |
| #include <safemath/checked_math.h> |
| |
| #include "src/storage/fvm/format.h" |
| #include "src/storage/fvm/fvm.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 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 Metadata::BytesNeeded(const fvm::Header& header) { |
| return header.GetMetadataAllocatedBytes(); |
| } |
| |
| Metadata::Metadata(std::unique_ptr<MetadataBuffer> data, SuperblockType active_header) |
| : data_(std::move(data)), active_header_(active_header) {} |
| |
| Metadata::Metadata(Metadata&& o) noexcept { MoveFrom(std::move(o)); } |
| |
| Metadata& Metadata::operator=(Metadata&& o) noexcept { |
| MoveFrom(std::move(o)); |
| return *this; |
| } |
| |
| void Metadata::MoveFrom(Metadata&& o) { |
| data_ = std::move(o.data_); |
| active_header_ = o.active_header_; |
| } |
| |
| bool Metadata::CheckValidity(uint64_t disk_size, uint64_t disk_block_size) const { |
| std::string header_err; |
| bool valid = GetHeader().IsValid(disk_size, disk_block_size, header_err); |
| if (!valid) { |
| fprintf(stderr, "Invalid header: %s\n", header_err.c_str()); |
| } |
| return valid; |
| } |
| |
| void Metadata::UpdateHash() { |
| ::fvm::UpdateHash(static_cast<uint8_t*>(data_->data()), GetHeader().GetMetadataUsedBytes()); |
| } |
| |
| size_t Metadata::GetInactiveHeaderOffset() const { |
| return GetHeader().GetSuperblockOffset(inactive_header()); |
| } |
| |
| void Metadata::SwitchActiveHeaders() { active_header_ = OppositeHeader(active_header_); } |
| |
| Header& Metadata::GetHeader() const { |
| return *reinterpret_cast<Header*>(static_cast<uint8_t*>(data_->data())); |
| } |
| |
| VPartitionEntry& Metadata::GetPartitionEntry(unsigned idx) const { |
| const Header& header = GetHeader(); |
| size_t offset = MetadataOffset(SuperblockType::kPrimary) + header.GetPartitionEntryOffset(idx); |
| ZX_ASSERT(offset + sizeof(VPartitionEntry) <= data_->size()); |
| if (idx > header.GetPartitionTableEntryCount()) { |
| fprintf(stderr, |
| "fatal: Accessing out-of-bounds partition (idx %u, table has %lu usable entries)\n", |
| idx, header.GetPartitionTableEntryCount()); |
| ZX_ASSERT(idx <= header.GetPartitionTableEntryCount()); |
| } |
| return *reinterpret_cast<VPartitionEntry*>(reinterpret_cast<uint8_t*>(data_->data()) + offset); |
| } |
| |
| SliceEntry& Metadata::GetSliceEntry(unsigned idx) const { |
| const Header& header = GetHeader(); |
| size_t offset = MetadataOffset(SuperblockType::kPrimary) + header.GetSliceEntryOffset(idx); |
| ZX_ASSERT(offset + sizeof(SliceEntry) <= data_->size()); |
| if (idx > header.GetAllocationTableUsedEntryCount()) { |
| fprintf(stderr, "fatal: Accessing out-of-bounds slice (idx %u, table has %lu usable entries)\n", |
| idx, header.GetAllocationTableUsedEntryCount()); |
| ZX_ASSERT(idx <= header.GetAllocationTableUsedEntryCount()); |
| } |
| return *reinterpret_cast<SliceEntry*>(reinterpret_cast<uint8_t*>(data_->data()) + offset); |
| } |
| |
| size_t Metadata::MetadataOffset(SuperblockType type) const { |
| return GetHeader().GetSuperblockOffset(type); |
| } |
| |
| const MetadataBuffer* Metadata::Get() const { return data_.get(); } |
| |
| zx::status<Metadata> Metadata::CopyWithNewDimensions(const Header& dimensions) const { |
| if (BytesNeeded(dimensions) < data_->size()) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| const Header& header = GetHeader(); |
| if (dimensions.fvm_partition_size < header.fvm_partition_size || |
| dimensions.GetPartitionTableEntryCount() < header.GetPartitionTableEntryCount() || |
| dimensions.GetAllocationTableUsedEntryCount() < header.GetAllocationTableUsedEntryCount() || |
| dimensions.GetAllocationTableAllocatedEntryCount() < |
| header.GetAllocationTableAllocatedEntryCount()) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| Header new_header = header; |
| new_header.fvm_partition_size = dimensions.fvm_partition_size; |
| new_header.pslice_count = dimensions.pslice_count; |
| new_header.vpartition_table_size = dimensions.vpartition_table_size; |
| new_header.allocation_table_size = dimensions.allocation_table_size; |
| |
| // TODO(fxbug.dev/59980) The first entries in the partition/slice tables must be unused. |
| // |Synthesize()| expects an array that does *not* include the empty zero entries. |
| // Remove this after we support zero-indexing. |
| const VPartitionEntry* partitions = nullptr; |
| size_t num_partitions = header.GetPartitionTableEntryCount(); |
| if (num_partitions <= 1) { |
| // Both 0 and 1 partitions count as having no partitions to copy. |
| num_partitions = 0; |
| } else { |
| partitions = &GetPartitionEntry(1u); |
| } |
| const SliceEntry* slices = nullptr; |
| size_t num_slices = header.GetAllocationTableUsedEntryCount(); |
| if (num_slices <= 1) { |
| // Both 0 and 1 slices count as having no slices to copy. |
| num_slices = 0; |
| } else { |
| slices = &GetSliceEntry(1u); |
| } |
| |
| return Synthesize(new_header, partitions, num_partitions, slices, num_slices); |
| } |
| |
| zx::status<Metadata> Metadata::Create(std::unique_ptr<MetadataBuffer> data_a, |
| std::unique_ptr<MetadataBuffer> data_b) { |
| return Create(std::numeric_limits<uint64_t>::max(), kBlockSize, std::move(data_a), |
| std::move(data_b)); |
| } |
| |
| zx::status<Metadata> Metadata::Create(size_t disk_size, size_t disk_block_size, |
| std::unique_ptr<MetadataBuffer> data_a, |
| std::unique_ptr<MetadataBuffer> data_b) { |
| if (data_a->size() < sizeof(Header)) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| if (data_b->size() < sizeof(Header)) { |
| 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 Header* header = reinterpret_cast<const Header*>(data_a->data()); |
| size_t meta_size = header->GetMetadataAllocatedBytes(); |
| if (meta_size > data_a->size() || meta_size > data_b->size()) { |
| fprintf(stderr, "fvm: Metadata (%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<SuperblockType> active_header = |
| PickValidHeader(disk_size, disk_block_size, data_a->data(), data_b->data(), meta_size); |
| if (!active_header) { |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| |
| std::unique_ptr<MetadataBuffer> data; |
| switch (active_header.value()) { |
| case SuperblockType::kPrimary: |
| data = std::move(data_a); |
| break; |
| case SuperblockType::kSecondary: |
| data = std::move(data_b); |
| break; |
| } |
| return zx::ok(Metadata(std::move(data), active_header.value())); |
| } |
| |
| zx::status<Metadata> Metadata::Synthesize(const fvm::Header& header, |
| const VPartitionEntry* partitions, size_t num_partitions, |
| const SliceEntry* slices, size_t num_slices) { |
| if (num_partitions > header.GetPartitionTableEntryCount() || |
| num_slices > header.GetAllocationTableUsedEntryCount() || header.slice_size == 0) { |
| 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/slice tables must be unused. |
| // Remove this after we support zero-indexing. |
| std::vector<VPartitionEntry> actual_partitions(0); |
| if (num_partitions > 0) { |
| ZX_ASSERT(partitions != nullptr); |
| actual_partitions = std::vector<VPartitionEntry>(num_partitions + 1); |
| actual_partitions[0].Release(); |
| for (size_t i = 0; i < num_partitions; ++i) { |
| actual_partitions[i + 1] = partitions[i]; |
| } |
| } |
| std::vector<SliceEntry> actual_slices(0); |
| if (num_slices > 0) { |
| ZX_ASSERT(slices != nullptr); |
| actual_slices = std::vector<SliceEntry>(num_slices + 1); |
| actual_slices[0].Release(); |
| for (size_t i = 0; i < num_slices; ++i) { |
| actual_slices[i + 1] = slices[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> slices_span = ContainerToSpan(actual_slices); |
| |
| 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::kBlockSize, header_span); |
| write_metadata(header.GetPartitionTableOffset(), header.GetPartitionTableByteSize(), |
| partitions_span); |
| write_metadata(header.GetAllocationTableOffset(), header.GetAllocationTableAllocatedByteSize(), |
| slices_span); |
| // TODO(fxbug.dev/59567): Synthesize snapshot metadata. |
| |
| ::fvm::UpdateHash(buf.get(), header.GetMetadataUsedBytes()); |
| |
| Metadata metadata(std::make_unique<HeapMetadataBuffer>(std::move(buf), buffer_size), |
| SuperblockType::kPrimary); |
| if (!metadata.CheckValidity()) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| return zx::ok(std::move(metadata)); |
| } |
| |
| } // namespace fvm |