blob: 4e628b7920cb05143d2b17377644374f5e8ac469 [file] [log] [blame]
// Copyright 2021 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_image_extend.h"
#include <iostream>
#include <memory>
#include <string>
#include <variant>
#include <vector>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/metadata.h"
#include "src/storage/fvm/metadata_buffer.h"
namespace storage::volume_image {
namespace {
// Conforms to a the MetadataBuffer interface required, and allows to inject and unowned buffer if
// necessary. Why is useful for testing.
class MetadataBufferView final : public fvm::MetadataBuffer {
public:
MetadataBufferView() : data_(std::vector<uint8_t>()) {}
explicit MetadataBufferView(fbl::Span<uint8_t> data) : data_(data) {}
std::unique_ptr<MetadataBuffer> Create(size_t size) const final {
auto view = std::make_unique<MetadataBufferView>();
std::get<std::vector<uint8_t>>(view->data_).resize(size);
return std::move(view);
}
void* data() const final {
return std::visit([](auto& a) { return static_cast<void*>(a.data()); }, data_);
}
size_t size() const final {
return std::visit([](auto& a) { return a.size(); }, data_);
}
private:
mutable std::variant<fbl::Span<uint8_t>, std::vector<uint8_t>> data_;
};
fit::result<fvm::Metadata, std::string> GetMetadata(
const Reader& source_image, std::vector<uint8_t>& primary_metadata_buffer,
std::vector<uint8_t>& secondary_metadata_buffer) {
fvm::Header header = {};
auto header_view = fbl::Span<uint8_t>(reinterpret_cast<uint8_t*>(&header), sizeof(fvm::Header));
if (auto header_read_result = source_image.Read(0, header_view); header_read_result.is_error()) {
return header_read_result.take_error_result();
}
if (header.magic != fvm::kMagic) {
return fit::error("|source_image| must be a valid FVM block image. FVM Magic mismatch, found " +
std::to_string(header.magic) + " expected " + std::to_string(fvm::kMagic));
}
primary_metadata_buffer.resize(header.GetMetadataAllocatedBytes());
if (auto primary_metadata_read_result = source_image.Read(
header.GetSuperblockOffset(fvm::SuperblockType::kPrimary), primary_metadata_buffer);
primary_metadata_read_result.is_error()) {
return primary_metadata_read_result.take_error_result();
}
auto primary_metadata = std::make_unique<MetadataBufferView>(primary_metadata_buffer);
secondary_metadata_buffer.resize(header.GetMetadataAllocatedBytes());
if (auto secondary_metadata_read_result = source_image.Read(
header.GetSuperblockOffset(fvm::SuperblockType::kPrimary), secondary_metadata_buffer);
secondary_metadata_read_result.is_error()) {
return secondary_metadata_read_result.take_error_result();
}
auto secondary_metadata = std::make_unique<MetadataBufferView>(secondary_metadata_buffer);
auto metadata = fvm::Metadata::Create(std::move(primary_metadata), std::move(secondary_metadata));
if (metadata.is_error()) {
return fit::error("Failed to create FVM Metadata from image. Error Code: " +
std::to_string(metadata.error_value()));
}
return fit::ok(std::move(metadata.value()));
}
} // namespace
fit::result<void, std::string> FvmImageExtend(const Reader& source_image, const FvmOptions& options,
Writer& target_image) {
std::vector<uint8_t> primary_metadata_buffer;
std::vector<uint8_t> secondary_metadata_buffer;
auto metadata_or = GetMetadata(source_image, primary_metadata_buffer, secondary_metadata_buffer);
if (metadata_or.is_error()) {
return metadata_or.take_error_result();
}
const auto& header = metadata_or.value().GetHeader();
// At this point we know we have a valid header and metadata, so we can check the validity of the
// options.
// Calculate the minimum size for the metadata if target disk size is set.
if (!options.target_volume_size.has_value()) {
return fit::error("Must provide a target size to extend to.");
}
if (options.target_volume_size.value() < source_image.length()) {
return fit::error("Cannot extend a source image to a smaller image size.");
}
// If someone chose to do the extend 'in-place' then, which is the usual case, we need to be
// careful in the order in which we do the operations. First move all slices(if necessary) to
// match the new offset. Starting from the last slice to the first one, so there is no data
// overwritten.
auto new_header = fvm::Header::FromDiskSize(
header.GetPartitionTableEntryCount(), options.target_volume_size.value(), header.slice_size);
// At most we read 64 Kb at a time, or one slice slice, whichever is smaller.
// If updating this value, make sure that big slice test, uses a slice bigger than this.
constexpr uint64_t kMaxBufferSize = 64u << 10;
std::vector<uint8_t> read_buffer;
read_buffer.resize(std::min(header.slice_size, kMaxBufferSize));
for (uint64_t pslice = header.pslice_count; pslice >= 1; --pslice) {
auto& slice_entry = metadata_or.value().GetSliceEntry(pslice);
// If the slice is not allocated to any partition, dont bother.
if (!slice_entry.IsAllocated()) {
continue;
}
uint64_t read_slice_start = metadata_or.value().GetHeader().GetSliceDataOffset(pslice);
uint64_t write_slice_start = new_header.GetSliceDataOffset(pslice);
uint64_t moved_bytes = 0;
// The size of the slice is arbitrary, so we se a maximum buffer size, and stream the contents.
while (moved_bytes < metadata_or.value().GetHeader().slice_size) {
auto chunk_view =
fbl::Span<uint8_t>(read_buffer)
.subspan(0, std::min(kMaxBufferSize, metadata_or.value().GetHeader().slice_size));
if (auto read_result = source_image.Read(read_slice_start + moved_bytes, chunk_view);
read_result.is_error()) {
return read_result.take_error_result();
}
if (auto write_result = target_image.Write(write_slice_start + moved_bytes, chunk_view);
write_result.is_error()) {
return write_result.take_error_result();
}
moved_bytes += chunk_view.size();
}
}
// Now we copy the new metadata over, which is the old metadata, plus new entries, this can be
// done by recreating the metadata.
auto new_metadata = metadata_or.value().CopyWithNewDimensions(new_header);
if (new_metadata.is_error()) {
return fit::error("Failed to synthesize metadata for extended FVM. Error code: " +
std::to_string(new_metadata.error_value()));
}
// Now write both copies into the new place.
if (auto write_primary_metadata_result = target_image.Write(
new_header.GetSuperblockOffset(fvm::SuperblockType::kPrimary),
fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(new_metadata->Get()->data()),
new_metadata->Get()->size()));
write_primary_metadata_result.is_error()) {
return write_primary_metadata_result.take_error_result();
}
if (auto write_secondary_metadata_result = target_image.Write(
new_header.GetSuperblockOffset(fvm::SuperblockType::kSecondary),
fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(new_metadata->Get()->data()),
new_metadata->Get()->size()));
write_secondary_metadata_result.is_error()) {
return write_secondary_metadata_result.take_error_result();
}
return fit::ok();
}
fit::result<uint64_t, std::string> FvmImageGetTrimmedSize(const Reader& source_image) {
std::vector<uint8_t> primary_metadata_buffer;
std::vector<uint8_t> secondary_metadata_buffer;
auto metadata_or = GetMetadata(source_image, primary_metadata_buffer, secondary_metadata_buffer);
if (metadata_or.is_error()) {
return metadata_or.take_error_result();
}
const auto& header = metadata_or.value().GetHeader();
std::optional<uint64_t> last_allocated_slice;
for (uint64_t pslice = header.pslice_count; pslice > 0; --pslice) {
const auto& slice_entry = metadata_or.value().GetSliceEntry(pslice);
if (slice_entry.IsAllocated()) {
last_allocated_slice = pslice;
break;
}
}
uint64_t last_offset =
last_allocated_slice.has_value()
? header.GetSliceDataOffset(last_allocated_slice.value()) + header.slice_size
: header.GetSliceDataOffset(1);
if (last_offset < header.GetSuperblockOffset(fvm::SuperblockType::kPrimary)) {
last_offset = header.GetSuperblockOffset(fvm::SuperblockType::kPrimary) +
header.GetMetadataAllocatedBytes();
}
if (last_offset < header.GetSuperblockOffset(fvm::SuperblockType::kSecondary)) {
last_offset = header.GetSuperblockOffset(fvm::SuperblockType::kSecondary) +
header.GetMetadataAllocatedBytes();
}
return fit::ok(last_offset);
}
} // namespace storage::volume_image