blob: 6b3dbc5f160f5e84328e5aa5c69544d561043236 [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/adapter/blobfs_partition.h"
#include <lib/fpromise/result.h>
#include <zircon/hw/gpt.h>
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <memory>
#include <string>
#include <safemath/safe_conversions.h>
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/fvm/format.h"
#include "src/storage/volume_image/address_descriptor.h"
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/utils/block_utils.h"
#include "src/storage/volume_image/utils/guid.h"
#include "src/storage/volume_image/utils/reader.h"
#include "src/storage/volume_image/volume_descriptor.h"
namespace storage::volume_image {
namespace {
// Expected label for blobfs volume
constexpr std::string_view kBlobfsLabel = "blobfs";
// Expected GUID for blobfs instance.
constexpr std::array<uint8_t, kGuidLength> kBlobfsTypeGuid = GUID_BLOB_VALUE;
// The FVM version of Blobfs has an extra block after the superblock, which is the backup
// superblock. This reader 'injects' a copy of the superblock on the block following the superblock.
// This is the backup superblock.
class BackupSuperblockReader final : public Reader {
public:
explicit BackupSuperblockReader(std::unique_ptr<Reader> reader) : reader_(std::move(reader)) {}
uint64_t length() const final { return reader_->length() + blobfs::kBlobfsBlockSize; }
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
if (offset + buffer.size() > blobfs::kBlobfsBlockSize) {
// Read the first part with the original offset if it crosses the boundary.
if (offset < blobfs::kBlobfsBlockSize) {
uint64_t non_adjusted_bytes = blobfs::kBlobfsBlockSize - offset;
if (auto read_result = reader_->Read(offset, buffer.subspan(0, non_adjusted_bytes));
read_result.is_error()) {
return read_result.take_error_result();
}
// Now update the buffer view.
buffer = buffer.subspan(non_adjusted_bytes);
offset += non_adjusted_bytes;
}
offset -= blobfs::kBlobfsBlockSize;
}
// The first superblock does not need any adjustment.
return reader_->Read(offset, buffer);
}
private:
std::unique_ptr<Reader> reader_;
};
// For blobfs we need to replace contents from the superblock, to make it look like its fvm based
// blobfs.
class PatchedSuperblockReader final : public Reader {
public:
explicit PatchedSuperblockReader(std::unique_ptr<Reader> reader) : reader_(std::move(reader)) {}
uint64_t length() const final { return reader_->length(); }
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
if (auto read_result = reader_->Read(offset, buffer); read_result.is_error()) {
return read_result.take_error_result();
}
if (offset < blobfs::kBlobfsBlockSize) {
uint64_t content_bytes =
std::min(static_cast<uint64_t>(buffer.size()), blobfs::kBlobfsBlockSize - offset);
memset(buffer.data(), 0, content_bytes);
if (offset < sizeof(blobfs::Superblock)) {
uint64_t content_offset = offset % sizeof(blobfs::Superblock);
uint64_t remaining_bytes = std::min(
buffer.size(), static_cast<size_t>(sizeof(blobfs::Superblock) - content_offset));
memcpy(buffer.data(), reinterpret_cast<const uint8_t*>(&superblock_) + content_offset,
remaining_bytes);
}
}
return fpromise::ok();
}
blobfs::Superblock& superblock() { return superblock_; }
private:
blobfs::Superblock superblock_;
std::unique_ptr<Reader> reader_;
};
} // namespace
fpromise::result<Partition, std::string> CreateBlobfsFvmPartition(
std::unique_ptr<Reader> source_image, const PartitionOptions& partition_options,
const FvmOptions& fvm_options) {
if (fvm_options.slice_size % blobfs::kBlobfsBlockSize != 0) {
return fpromise::error(
"Fvm slice size must be a multiple of blobfs block size. Expected blobfs_block_size: " +
std::to_string(blobfs::kBlobfsBlockSize) +
" fvm_slice_size: " + std::to_string(fvm_options.slice_size) + ".");
}
if (2 * blobfs::kBlobfsBlockSize > fvm_options.slice_size) {
return fpromise::error(
"Blobfs Superblock and Backup Superblock must fit within the first slice. Expected slice "
"size of at least " +
std::to_string(2 * blobfs::kBlobfsBlockSize) + ", but found " +
std::to_string(fvm_options.slice_size) + ".");
}
// Load blobfs superblock to obtain extent sizes and such.
blobfs::Superblock superblock = {};
if (auto sb_read_result =
source_image->Read(0, cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&superblock),
sizeof(blobfs::Superblock)));
sb_read_result.is_error()) {
return sb_read_result.take_error_result();
}
// Minor validation that we are actually dealing with a blobfs superblock.
if (superblock.magic0 != blobfs::kBlobfsMagic0) {
return fpromise::error(
"Found bad magic0(" + std::to_string(superblock.magic0) +
") value in blobfs superblock(Expected: " + std::to_string(blobfs::kBlobfsMagic0) + ").");
}
if (superblock.magic1 != blobfs::kBlobfsMagic1) {
return fpromise::error(
"Found bad magic1(" + std::to_string(superblock.magic1) +
") value in blobfs superblock(Expected: " + std::to_string(blobfs::kBlobfsMagic1) + ").");
}
// Helper to calculate slice count.
auto get_slice_count = [&fvm_options](const auto& mapping) {
auto extent_size = std::max(mapping.count, mapping.size.value_or(0));
return GetBlockCount(mapping.target, extent_size, fvm_options.slice_size);
};
uint64_t accumulated_slices = 0;
VolumeDescriptor volume;
volume.block_size = blobfs::kBlobfsBlockSize;
volume.size = source_image->length();
volume.encryption = EncryptionType::kNone;
volume.name = kBlobfsLabel;
memcpy(volume.type.data(), kBlobfsTypeGuid.data(), volume.type.size());
memcpy(volume.instance.data(), fvm::kPlaceHolderInstanceGuid.data(), volume.instance.size());
AddressDescriptor address;
// Currently there is a limitation on the host tool, since it meets the existing requirements and
// simplifies the process.
//
// That is, mappings do not share slices. Which is why, instead of mapping the superblock
// to two different target offsets we need to use a wrapper on reader.
AddressMap superblock_mapping;
superblock_mapping.source = 0;
superblock_mapping.count = 2 * blobfs::kBlobfsBlockSize;
superblock_mapping.target = 0;
superblock_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
address.mappings.push_back(superblock_mapping);
accumulated_slices += get_slice_count(superblock_mapping);
// All blocks below this, need to account for an extra block inserted at runtime, which is the
// backup superblock.
uint64_t min_data_blocks =
GetBlockCount(blobfs::kFVMDataStart, partition_options.min_data_bytes.value_or(0),
blobfs::kBlobfsBlockSize);
AddressMap block_map_mapping;
block_map_mapping.source =
(blobfs::BlockMapStartBlock(superblock) + 1) * blobfs::kBlobfsBlockSize;
block_map_mapping.target = blobfs::kFVMBlockMapStart * blobfs::kBlobfsBlockSize;
block_map_mapping.count =
blobfs::BlocksRequiredForBits(superblock.data_block_count) * blobfs::kBlobfsBlockSize;
block_map_mapping.size =
std::max(block_map_mapping.count,
static_cast<uint64_t>(blobfs::BlocksRequiredForBits(min_data_blocks) *
blobfs::kBlobfsBlockSize));
block_map_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
address.mappings.push_back(block_map_mapping);
accumulated_slices += get_slice_count(block_map_mapping);
AddressMap inode_mapping;
inode_mapping.source = (blobfs::NodeMapStartBlock(superblock) + 1) * blobfs::kBlobfsBlockSize;
inode_mapping.target = blobfs::kFVMNodeMapStart * blobfs::kBlobfsBlockSize;
inode_mapping.count =
blobfs::BlocksRequiredForInode(superblock.inode_count) * blobfs::kBlobfsBlockSize;
inode_mapping.size = blobfs::BlocksRequiredForInode(std::max(
superblock.inode_count, partition_options.min_inode_count.value_or(0))) *
blobfs::kBlobfsBlockSize;
inode_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
address.mappings.push_back(inode_mapping);
accumulated_slices += get_slice_count(inode_mapping);
AddressMap data_mapping;
data_mapping.source = (blobfs::DataStartBlock(superblock) + 1) * blobfs::kBlobfsBlockSize;
data_mapping.target = blobfs::kFVMDataStart * blobfs::kBlobfsBlockSize;
data_mapping.count = superblock.data_block_count * blobfs::kBlobfsBlockSize;
data_mapping.size = std::max(data_mapping.count, partition_options.min_data_bytes.value_or(0));
address.mappings.push_back(data_mapping);
accumulated_slices += get_slice_count(data_mapping);
AddressMap journal_mapping;
journal_mapping.source = (blobfs::JournalStartBlock(superblock) + 1) * blobfs::kBlobfsBlockSize;
journal_mapping.target = blobfs::kFVMJournalStart * blobfs::kBlobfsBlockSize;
journal_mapping.count = blobfs::JournalBlocks(superblock) * blobfs::kBlobfsBlockSize;
accumulated_slices += get_slice_count(journal_mapping);
// Add any leftover space to the journal.
if (partition_options.max_bytes.has_value()) {
uint64_t max_slices = partition_options.max_bytes.value() / fvm_options.slice_size;
uint64_t available_slices =
accumulated_slices > max_slices ? 0 : max_slices - accumulated_slices;
// If there are more bytes available than the original image had reserved, increase the journal
// size to match all remaining space.
journal_mapping.size =
(available_slices + get_slice_count(journal_mapping)) * fvm_options.slice_size;
}
address.mappings.push_back(journal_mapping);
accumulated_slices = 0;
for (const auto& mapping : address.mappings) {
accumulated_slices += get_slice_count(mapping);
}
uint64_t accumulated_bytes = accumulated_slices * fvm_options.slice_size;
if (partition_options.max_bytes.has_value() &&
accumulated_bytes > partition_options.max_bytes.value()) {
return fpromise::error("Blobfs FVM Partition allocated " + std::to_string(accumulated_slices) +
"(" + std::to_string(accumulated_bytes) +
" bytes) exceeding provided upperbound |max_bytes|(" +
std::to_string(partition_options.max_bytes.value()) + ").");
}
auto patched_superblock_reader =
std::make_unique<PatchedSuperblockReader>(std::move(source_image));
auto& patched_superblock = patched_superblock_reader->superblock();
memcpy(&patched_superblock, &superblock, sizeof(blobfs::Superblock));
patched_superblock.flags |= blobfs::kBlobFlagFVM;
patched_superblock.inode_count = safemath::checked_cast<uint32_t>(
get_slice_count(inode_mapping) * fvm_options.slice_size / blobfs::kBlobfsInodeSize);
patched_superblock.journal_block_count =
get_slice_count(journal_mapping) * fvm_options.slice_size / blobfs::kBlobfsBlockSize;
patched_superblock.data_block_count =
get_slice_count(data_mapping) * fvm_options.slice_size / blobfs::kBlobfsBlockSize;
patched_superblock.slice_size = fvm_options.slice_size;
patched_superblock.abm_slices =
safemath::checked_cast<uint32_t>(get_slice_count(block_map_mapping));
patched_superblock.ino_slices = safemath::checked_cast<uint32_t>(get_slice_count(inode_mapping));
patched_superblock.dat_slices = safemath::checked_cast<uint32_t>(get_slice_count(data_mapping));
patched_superblock.journal_slices =
safemath::checked_cast<uint32_t>(get_slice_count(journal_mapping));
auto reader_with_backup_superblock =
std::make_unique<BackupSuperblockReader>(std::move(patched_superblock_reader));
return fpromise::ok(Partition(volume, address, std::move(reader_with_backup_superblock)));
}
} // namespace storage::volume_image