blob: 0b41e0b40d84e89cb5d5e2e80d2188e4be5dcae3 [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/volume_image/fvm/fvm_descriptor.h"
#include <lib/stdcompat/span.h>
#include <zircon/assert.h>
#include <cstdint>
#include <functional>
#include <iostream>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <fbl/algorithm.h>
#include <safemath/safe_conversions.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/metadata.h"
#include "src/storage/volume_image/address_descriptor.h"
#include "src/storage/volume_image/fvm/options.h"
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/utils/block_utils.h"
namespace storage::volume_image {
namespace {
std::string ToSizeString(uint64_t bytes) {
constexpr int kByteToMegabyte = 1 << 20;
std::string size_str = std::to_string(static_cast<double>(bytes) / kByteToMegabyte);
return size_str.append(" [MB]");
}
} // namespace
namespace internal {
fvm::Header MakeHeader(const FvmOptions& options, uint64_t slice_count) {
if (options.max_volume_size.has_value()) {
return fvm::Header::FromGrowableDiskSize(
fvm::kMaxUsablePartitions,
options.target_volume_size.value_or(options.max_volume_size.value()),
options.max_volume_size.value(), options.slice_size);
}
if (options.target_volume_size.has_value()) {
return fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, options.target_volume_size.value(),
options.slice_size);
}
return fvm::Header::FromSliceCount(fvm::kMaxUsablePartitions, slice_count, options.slice_size);
}
} // namespace internal
FvmDescriptor::Builder::Builder(FvmDescriptor descriptor)
: options_(std::move(descriptor.options_)),
accumulated_slices_(descriptor.slice_count_),
metadata_allocated_size_(descriptor.metadata_required_size_) {
for (auto it = descriptor.partitions_.begin(); it != descriptor.partitions_.end();) {
partitions_.emplace_back(std::move(descriptor.partitions_.extract(it++).value()));
}
}
FvmDescriptor::Builder& FvmDescriptor::Builder::AddPartition(Partition partition) {
partitions_.emplace_back(std::move(partition));
return *this;
}
FvmDescriptor::Builder& FvmDescriptor::Builder::SetOptions(const FvmOptions& options) {
options_ = options;
return *this;
}
fpromise::result<FvmDescriptor, std::string> FvmDescriptor::Builder::Build() {
FvmDescriptor descriptor;
if (!options_.has_value()) {
return fpromise::error("FVM Options were not set.");
}
if (options_->slice_size == 0) {
return fpromise::error("FVM's slice_size must be greater than zero.");
}
if (options_->max_volume_size.has_value() &&
options_->max_volume_size.value() < options_->target_volume_size.value_or(0)) {
std::string error = "FVM's max_volume_size(";
error.append(ToSizeString(options_->max_volume_size.value()))
.append(") is smaller than target_volume_size(")
.append(ToSizeString(options_->target_volume_size.value()))
.append(").");
return fpromise::error(error);
}
accumulated_slices_ = 0;
// Check for duplicated partition entries <Name, InstanceGUID> unique pair.
for (auto& partition : partitions_) {
auto it = descriptor.partitions_.find(partition);
if (it != descriptor.partitions_.end()) {
std::string error = "Partition already exists, could not add partition " +
partition.volume().name + " and instance guid " +
Guid::ToString(partition.volume().instance).value() +
" failed.\n Partition" + it->volume().name + " and instance guid " +
Guid::ToString(it->volume().instance).value() + " was added before.";
partitions_.clear();
return fpromise::error(error);
}
// Update accumulated slice count, and check for overlapping extents.
std::set<const AddressMap*, std::function<bool(const AddressMap*, const AddressMap*)>> extents(
[](auto* a, auto* b) { return a->target < b->target; });
for (const auto& mapping : partition.address().mappings) {
for (auto it = extents.lower_bound(&mapping); it != extents.end(); ++it) {
auto* current_extent = *it;
// We are past the end of the extent.
if (current_extent->target >= mapping.target + mapping.count) {
break;
}
if (current_extent->target + current_extent->count > mapping.target) {
// Get the other mapping
return fpromise::error("Address descriptor of " + partition.volume().name +
" contains overlapping mappings. Conflict between " +
mapping.DebugString() + " and " + current_extent->DebugString());
}
}
extents.insert(&mapping);
uint64_t required_size = mapping.size.value_or(mapping.count);
accumulated_slices_ += GetBlockCount(mapping.target, required_size, options_->slice_size);
}
descriptor.partitions_.emplace(std::move(partition));
}
partitions_.clear();
fvm::Header header = internal::MakeHeader(*options_, accumulated_slices_);
metadata_allocated_size_ = 2 * header.GetMetadataAllocatedBytes();
uint64_t minimum_size = metadata_allocated_size_ + accumulated_slices_ * options_->slice_size;
// We are not allowed to exceed the target disk size when set.
if (minimum_size > options_->target_volume_size.value_or(std::numeric_limits<uint64_t>::max())) {
std::string error =
"Failed to build FVMDescriptor. Image does not fit in target volume size. Minimum size "
"is " +
ToSizeString(minimum_size) + " and target size is " +
ToSizeString(options_->target_volume_size.value()) + ".";
return fpromise::error(error);
}
descriptor.metadata_required_size_ = metadata_allocated_size_;
descriptor.slice_count_ = accumulated_slices_;
descriptor.options_ = std::move(options_.value());
return fpromise::ok(std::move(descriptor));
}
fpromise::result<void, std::string> FvmDescriptor::WriteBlockImage(Writer& writer) const {
fvm::Header header = internal::MakeHeader(options_, slice_count_);
std::vector<fvm::VPartitionEntry> vpartitions;
vpartitions.reserve(partitions_.size());
std::vector<fvm::SliceEntry> slices;
slices.reserve(slice_count_);
// Partitions start at index 1.
size_t current_vpartition = 1;
for (const auto& partition : partitions()) {
fvm::VPartitionEntry vpartition = {};
uint64_t partition_slices = 0;
memcpy(vpartition.unsafe_name, partition.volume().name.data(), partition.volume().name.size());
memcpy(vpartition.type, partition.volume().type.data(), partition.volume().type.size());
memcpy(vpartition.guid, partition.volume().instance.data(), partition.volume().instance.size());
vpartition.flags = 0;
for (const auto& mapping : partition.address().mappings) {
// Slice info for each mapping.
uint64_t size = std::max(mapping.count, mapping.size.value_or(0));
uint64_t slice_count = GetBlockCount(mapping.target, size, options_.slice_size);
partition_slices += slice_count;
uint64_t start_slice = GetBlockFromBytes(mapping.target, options_.slice_size);
if (!IsOffsetBlockAligned(mapping.target, options_.slice_size)) {
return fpromise::error("Partition " + partition.volume().name +
" contains unaligned mapping " + std::to_string(mapping.target) +
". FVM Sparse Image requires slice aligned extent |vslice_start|.");
}
// Slice entry for each slice in the mapping.
for (uint64_t vslice_offset = 0; vslice_offset < slice_count; ++vslice_offset) {
slices.emplace_back(current_vpartition, start_slice + vslice_offset);
}
}
vpartition.slices = safemath::checked_cast<uint32_t>(partition_slices);
vpartitions.push_back(vpartition);
current_vpartition++;
}
// At this point we've written all the slice contents, now write the metadata.
auto fvm_metadata_or = fvm::Metadata::Synthesize(header, vpartitions.data(), vpartitions.size(),
slices.data(), slices.size());
if (fvm_metadata_or.is_error()) {
return fpromise::error(
"FvmDescriptor::WriteBlockImage failed to synthesize fvm metadata with error code : " +
std::to_string(fvm_metadata_or.status_value()));
}
auto fvm_metadata = std::move(fvm_metadata_or.value());
auto metadata_view = cpp20::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(fvm_metadata.Get()->data()), fvm_metadata.Get()->size());
auto metadata_write_result = writer.Write(
fvm_metadata.GetHeader().GetSuperblockOffset(fvm::SuperblockType::kPrimary), metadata_view);
if (metadata_write_result.is_error()) {
return metadata_write_result.take_error_result();
}
auto secondary_metadata_write_result = writer.Write(
fvm_metadata.GetHeader().GetSuperblockOffset(fvm::SuperblockType::kSecondary), metadata_view);
if (secondary_metadata_write_result.is_error()) {
return secondary_metadata_write_result.take_error_result();
}
// Now write the data for each slice starting at physical slice 1, since it 1-indexed.
// This is achieved by streaming the data into the slices, in the same order they are read.
//
// Slices that are prefilled will have 0, and slices that are allocated but not used will be
// skipped.
//
// In order to guarantee that image has the right size, if the last slice of the image is not
// written, the last block of the last slice, will be filled with zeroes. This will force the
// image to have the right size, if its growing dynamically.
uint64_t current_physical_slice = 1;
std::vector<uint8_t> slice_buffer;
slice_buffer.resize(options_.slice_size, 0);
for (const auto& partition : partitions()) {
for (const auto& mapping : partition.address().mappings) {
uint64_t size = std::max(mapping.count, mapping.size.value_or(0));
uint64_t slice_count = GetBlockCount(mapping.target, size, options_.slice_size);
uint64_t data_slice_count = GetBlockCount(mapping.target, mapping.count, options_.slice_size);
// Check if we should fill non data backed slices in this partition mapping explicitly.
auto fill_value_it = mapping.options.find(EnumAsString(AddressMapOption::kFill));
std::optional<uint8_t> fill_value = std::nullopt;
if (fill_value_it != mapping.options.end()) {
fill_value = static_cast<uint8_t>(fill_value_it->second);
}
for (uint64_t slice = 0; slice < slice_count; ++slice) {
auto slice_data_view = cpp20::span<uint8_t>(slice_buffer);
if (slice < data_slice_count) {
// Byte offset of current slice.
const uint64_t slice_offset = slice * options_.slice_size;
const uint64_t vslice_offset =
volume_image::GetBlockFromBytes(mapping.target, options_.slice_size) *
options_.slice_size +
slice_offset;
// Check if the start of the extent is not aligned with the slice.
// All extents targets are slice aligned, but that may not be the case when reading
// from the source.
const uint64_t data_vslice_start =
mapping.target > vslice_offset ? mapping.target - vslice_offset : 0;
// Check if the extent data does not end in slice boundary.
const uint64_t data_vslice_end = mapping.target + mapping.count - vslice_offset;
const uint64_t vslice_end = vslice_offset + options_.slice_size - vslice_offset;
uint64_t data_length = data_vslice_end < vslice_end ? data_vslice_end - data_vslice_start
: vslice_end - data_vslice_start;
slice_data_view = slice_data_view.subspan(data_vslice_start, data_length);
auto read_result = partition.reader()->Read(
mapping.source + slice_offset + data_vslice_start, slice_data_view);
if (read_result.is_error()) {
return read_result.take_error_result();
}
}
// Skip all allocated slices that are not backed by data if we dont need to fill.
if (slice >= data_slice_count && !fill_value.has_value()) {
current_physical_slice += slice_count - data_slice_count;
break;
}
// Finally write current slice.
const uint64_t physical_slice_offset = header.GetSliceDataOffset(current_physical_slice);
auto write_result = writer.Write(physical_slice_offset, slice_buffer);
if (write_result.is_error()) {
return write_result.take_error_result();
}
// Clean up the buffer for next read.
memset(slice_buffer.data(), fill_value.value_or(0), slice_buffer.size());
current_physical_slice++;
}
}
}
// Account for slice zero.
ZX_ASSERT(slices.size() + 1 == current_physical_slice);
return fpromise::ok();
}
} // namespace storage::volume_image