blob: 5dabf4d2e766ee696eb5df5e3194ac623e883247 [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/minfs_partition.h"
#include <lib/fpromise/result.h>
#include <zircon/hw/gpt.h>
#include <cstdint>
#include <iostream>
#include <safemath/checked_math.h>
#include "src/storage/fvm/format.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/fsck.h"
#include "src/storage/minfs/transaction_limits.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"
namespace storage::volume_image {
namespace {
// Expected label for minfs volume
constexpr std::string_view kMinfsLabel = "data";
// Expected GUID for minfs instance.
constexpr std::array<uint8_t, kGuidLength> kMinfsTypeGuid = GUID_DATA_VALUE;
// For minfs we need to replace contents from the superblock, to make it look like its fvm based
// minfs.
class PatchedSuperblockReader final : public Reader {
public:
explicit PatchedSuperblockReader(std::unique_ptr<Reader> reader, uint64_t superblock_offset)
: superblock_offset_(superblock_offset), 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 + buffer.size() > superblock_offset_) ||
!(offset <= superblock_offset_ + minfs::kMinfsBlockSize)) {
return fpromise::ok();
}
uint64_t bytes_before_superblock =
superblock_offset_ > offset ? superblock_offset_ - offset : 0;
uint64_t bytes_until_block_end =
std::min(static_cast<uint64_t>(offset + buffer.size() - bytes_before_superblock),
superblock_offset_ + minfs::kMinfsBlockSize) -
std::max(superblock_offset_, offset);
memset(buffer.data() + bytes_before_superblock, 0, bytes_until_block_end);
// now the superblock contents.
if (offset <= superblock_offset_ + sizeof(minfs::Superblock)) {
uint64_t bytes_since_superblock_offset =
offset > superblock_offset_ ? offset - superblock_offset_ : 0;
uint64_t bytes_remaining_from_superblock =
sizeof(minfs::Superblock) - bytes_since_superblock_offset;
uint64_t bytes_remaining_in_buffer =
buffer.size() - bytes_before_superblock - bytes_since_superblock_offset;
uint64_t bytes_to_read = std::min(bytes_remaining_in_buffer, bytes_remaining_from_superblock);
memcpy(buffer.data() + bytes_before_superblock,
reinterpret_cast<const uint8_t*>(&superblock_) + bytes_since_superblock_offset,
bytes_to_read);
}
return fpromise::ok();
}
minfs::Superblock& superblock() { return superblock_; }
private:
minfs::Superblock superblock_;
uint64_t superblock_offset_;
std::unique_ptr<Reader> reader_;
};
} // namespace
fpromise::result<Partition, std::string> CreateMinfsFvmPartition(
std::unique_ptr<Reader> source_image, const PartitionOptions& partition_options,
const FvmOptions& fvm_options) {
if (fvm_options.slice_size % minfs::kMinfsBlockSize != 0) {
return fpromise::error(
"Fvm slice size must be a multiple of minfs block size. Expected minfs_block_size: " +
std::to_string(minfs::kMinfsBlockSize) +
" fvm_slice_size: " + std::to_string(fvm_options.slice_size) + ".");
}
// Load minfs superblock to obtain extent sizes and such.
minfs::Superblock superblock = {};
if (auto sb_read_result = source_image->Read(
0,
cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&superblock), sizeof(minfs::Superblock)));
sb_read_result.is_error()) {
return sb_read_result.take_error_result();
}
// Minor validation that we are actually dealing with a minfs superblock.
if (superblock.magic0 != minfs::kMinfsMagic0) {
return fpromise::error(
"Found bad magic0(" + std::to_string(superblock.magic0) +
") value in minfs superblock(Expected: " + std::to_string(minfs::kMinfsMagic0) + ").");
}
if (superblock.magic1 != minfs::kMinfsMagic1) {
return fpromise::error(
"Found bad magic1(" + std::to_string(superblock.magic1) +
") value in minfs superblock(Expected: " + std::to_string(minfs::kMinfsMagic1) + ").");
}
// 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);
};
VolumeDescriptor volume;
volume.block_size = minfs::kMinfsBlockSize;
volume.size = source_image->length();
volume.encryption = EncryptionType::kZxcrypt;
volume.name = kMinfsLabel;
memcpy(volume.type.data(), kMinfsTypeGuid.data(), volume.type.size());
memcpy(volume.instance.data(), fvm::kPlaceHolderInstanceGuid.data(), volume.instance.size());
AddressDescriptor address;
AddressMap superblock_mapping;
superblock_mapping.source = 0;
superblock_mapping.target = 0;
superblock_mapping.count = sizeof(minfs::Superblock);
superblock_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
AddressMap inode_bitmap_mapping;
inode_bitmap_mapping.source =
static_cast<uint64_t>(superblock.ibm_block) * minfs::kMinfsBlockSize;
inode_bitmap_mapping.target =
static_cast<uint64_t>(minfs::kFVMBlockInodeBmStart) * minfs::kMinfsBlockSize;
inode_bitmap_mapping.count =
static_cast<uint64_t>(superblock.abm_block - superblock.ibm_block) * minfs::kMinfsBlockSize;
inode_bitmap_mapping.size =
std::max(inode_bitmap_mapping.count, static_cast<uint64_t>(minfs::BlocksRequiredForBits(
partition_options.min_inode_count.value_or(0))) *
minfs::kMinfsBlockSize);
inode_bitmap_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
AddressMap data_bitmap_mapping;
data_bitmap_mapping.source = static_cast<uint64_t>(superblock.abm_block) * minfs::kMinfsBlockSize;
data_bitmap_mapping.target =
static_cast<uint64_t>(minfs::kFVMBlockDataBmStart * minfs::kMinfsBlockSize);
data_bitmap_mapping.count =
static_cast<uint64_t>(superblock.ino_block - superblock.abm_block) * minfs::kMinfsBlockSize;
data_bitmap_mapping.size =
std::max(data_bitmap_mapping.count,
minfs::BlocksRequiredForBits(GetBlockCount(
minfs::kFVMBlockDataBmStart * minfs::kMinfsBlockSize,
partition_options.min_data_bytes.value_or(0), minfs::kMinfsBlockSize)));
data_bitmap_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
AddressMap inode_mapping;
inode_mapping.source = static_cast<uint64_t>(superblock.ino_block) * minfs::kMinfsBlockSize;
inode_mapping.target = static_cast<uint64_t>(minfs::kFVMBlockInodeStart) * minfs::kMinfsBlockSize;
inode_mapping.count =
static_cast<uint64_t>(superblock.integrity_start_block - superblock.ino_block) *
minfs::kMinfsBlockSize;
inode_mapping.size =
std::max(inode_mapping.count,
minfs::BlocksRequiredForInode(partition_options.min_inode_count.value_or(0)) *
minfs::kMinfsBlockSize);
inode_mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
AddressMap data_mapping;
data_mapping.source = static_cast<uint64_t>(superblock.dat_block) * minfs::kMinfsBlockSize;
data_mapping.target = static_cast<uint64_t>(minfs::kFVMBlockDataStart) * minfs::kMinfsBlockSize;
data_mapping.count = static_cast<uint64_t>(superblock.block_count) * minfs::kMinfsBlockSize;
data_mapping.size =
std::max(data_mapping.count,
GetBlockCount(superblock.dat_block, partition_options.min_data_bytes.value_or(0),
minfs::kMinfsBlockSize) *
minfs::kMinfsBlockSize);
AddressMap integrity_mapping;
integrity_mapping.source =
static_cast<uint64_t>(superblock.integrity_start_block) * minfs::kMinfsBlockSize;
integrity_mapping.target =
static_cast<uint64_t>(minfs::kFvmSuperblockBackup) * minfs::kMinfsBlockSize;
integrity_mapping.count =
static_cast<uint64_t>(superblock.dat_block - superblock.integrity_start_block) *
minfs::kMinfsBlockSize;
auto patched_superblock_reader =
std::make_unique<PatchedSuperblockReader>(std::move(source_image), 0);
auto& patched_superblock = patched_superblock_reader->superblock();
patched_superblock = superblock;
patched_superblock.slice_size = safemath::checked_cast<uint32_t>(fvm_options.slice_size);
patched_superblock.flags |= minfs::kMinfsFlagFVM;
patched_superblock.ibm_slices =
safemath::checked_cast<uint32_t>(get_slice_count(inode_bitmap_mapping));
patched_superblock.abm_slices =
safemath::checked_cast<uint32_t>(get_slice_count(data_bitmap_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.inode_count = safemath::checked_cast<uint32_t>(
get_slice_count(inode_mapping) * fvm_options.slice_size / minfs::kMinfsInodeSize);
patched_superblock.block_count = safemath::checked_cast<uint32_t>(
get_slice_count(data_mapping) * fvm_options.slice_size / minfs::kMinfsBlockSize);
patched_superblock.ibm_block = minfs::kFVMBlockInodeBmStart;
patched_superblock.abm_block = minfs::kFVMBlockDataBmStart;
patched_superblock.ino_block = minfs::kFVMBlockInodeStart;
patched_superblock.integrity_start_block = minfs::kFvmSuperblockBackup;
patched_superblock.dat_block = minfs::kFVMBlockDataStart;
// Calculate recommended journal slices based on the patched superblock.
minfs::TransactionLimits limits(patched_superblock);
integrity_mapping.size = std::max(
integrity_mapping.count,
static_cast<uint64_t>(limits.GetRecommendedIntegrityBlocks()) * minfs::kMinfsBlockSize);
patched_superblock.integrity_slices =
safemath::checked_cast<uint32_t>(get_slice_count(integrity_mapping));
minfs::UpdateChecksum(&patched_superblock);
auto patched_superblock_and_backup_superblock_reader = std::make_unique<PatchedSuperblockReader>(
std::move(patched_superblock_reader),
superblock.integrity_start_block * minfs::kMinfsBlockSize);
patched_superblock_and_backup_superblock_reader->superblock() = patched_superblock;
address.mappings.push_back(superblock_mapping);
address.mappings.push_back(inode_bitmap_mapping);
address.mappings.push_back(data_bitmap_mapping);
address.mappings.push_back(inode_mapping);
address.mappings.push_back(integrity_mapping);
address.mappings.push_back(data_mapping);
uint64_t 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("Minfs 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()) + ").");
}
return fpromise::ok(
Partition(volume, address, std::move(patched_superblock_and_backup_superblock_reader)));
}
} // namespace storage::volume_image