blob: 70113b4b219be90c29431bbba55da6cc5ae5b46f [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/fit/function.h>
#include <lib/fpromise/result.h>
#include <zircon/hw/gpt.h>
#include <array>
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string_view>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/fvm/format.h"
#include "src/storage/volume_image/fvm/options.h"
#include "src/storage/volume_image/utils/block_utils.h"
#include "src/storage/volume_image/utils/fd_reader.h"
#include "src/storage/volume_image/utils/guid.h"
#include "src/storage/volume_image/utils/reader.h"
namespace storage::volume_image {
namespace {
constexpr std::string_view kBlobfsImagePath =
STORAGE_VOLUME_IMAGE_ADAPTER_TEST_IMAGE_PATH "test_blobfs.blk";
std::array<uint8_t, kGuidLength> kBlobfsTypeGuid = GUID_BLOB_VALUE;
std::array<uint8_t, kGuidLength> kBlobfsInstanceGuid = fvm::kPlaceHolderInstanceGuid;
FvmOptions MakeFvmOptions(uint64_t slice_size) {
FvmOptions options;
options.slice_size = slice_size;
return options;
}
constexpr uint64_t kSliceSize = UINT64_C(32) * (1u << 10);
class FakeReader final : public Reader {
public:
uint64_t length() const final { return 0; }
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
memset(buffer.data(), 0, buffer.size());
if (offset == 0) {
memcpy(buffer.data(), &superblock_, sizeof(superblock_));
return fpromise::ok();
}
return fpromise::ok();
}
blobfs::Superblock& superblock() { return superblock_; }
private:
blobfs::Superblock superblock_ = {};
};
TEST(BlobfsPartitionTest, BackupSuperblockDoesntFitInFirstSliceIsError) {
auto fvm_options = MakeFvmOptions(blobfs::kBlobfsBlockSize);
PartitionOptions partition_options;
auto fake_reader = std::make_unique<FakeReader>();
ASSERT_TRUE(
CreateBlobfsFvmPartition(std::move(fake_reader), partition_options, fvm_options).is_error());
}
TEST(BlobfsPartitionTest, SliceSizeNotMultipleOfBlobfsBlockSizeIsError) {
auto fvm_options = MakeFvmOptions(blobfs::kBlobfsBlockSize - 1);
PartitionOptions partition_options;
auto fake_reader = std::make_unique<FakeReader>();
ASSERT_TRUE(
CreateBlobfsFvmPartition(std::move(fake_reader), partition_options, fvm_options).is_error());
}
TEST(BlobfsPartitionTest, ImageWithBadMagicIsError) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto fake_reader = std::make_unique<FakeReader>();
fake_reader->superblock().magic0 = blobfs::kBlobfsMagic0;
fake_reader->superblock().magic1 = 1;
ASSERT_TRUE(
CreateBlobfsFvmPartition(std::move(fake_reader), partition_options, fvm_options).is_error());
fake_reader = std::make_unique<FakeReader>();
fake_reader->superblock().magic0 = 0;
fake_reader->superblock().magic1 = blobfs::kBlobfsMagic1;
ASSERT_TRUE(
CreateBlobfsFvmPartition(std::move(fake_reader), partition_options, fvm_options).is_error());
}
std::optional<AddressMap> FindMappingStartingAt(uint64_t target_offset,
const AddressDescriptor& address) {
for (const auto& mapping : address.mappings) {
if (mapping.target == target_offset) {
return mapping;
}
}
return std::nullopt;
}
void CheckSuperblock(const blobfs::Superblock& actual_superblock,
const blobfs::Superblock& original_superblock, const FvmOptions& fvm_options,
const PartitionOptions& partition_options) {
// This should not be altered at all.
EXPECT_EQ(actual_superblock.magic0, original_superblock.magic0);
EXPECT_EQ(actual_superblock.magic1, original_superblock.magic1);
EXPECT_EQ(actual_superblock.block_size, original_superblock.block_size);
EXPECT_EQ(actual_superblock.alloc_block_count, original_superblock.alloc_block_count);
EXPECT_EQ(actual_superblock.alloc_inode_count, original_superblock.alloc_inode_count);
EXPECT_EQ(actual_superblock.major_version, original_superblock.major_version);
EXPECT_EQ(actual_superblock.oldest_minor_version, original_superblock.oldest_minor_version);
// The FVM flag MUST be set.
EXPECT_TRUE((actual_superblock.flags & blobfs::kBlobFlagFVM) != 0);
// This are updated as a result of slice allocation, and aligning blocks with slices.
// At the very least contain enough for the original data.
// Also check that partition paramters are honored.
uint64_t min_data_blocks =
GetBlockCount(blobfs::kFVMDataStart, partition_options.min_data_bytes.value_or(0),
blobfs::kBlobfsBlockSize);
// Depending on slice alignment it might be a bit more
EXPECT_GE(actual_superblock.inode_count, std::max(partition_options.min_inode_count.value_or(0),
original_superblock.inode_count));
EXPECT_GE(actual_superblock.data_block_count,
std::max(original_superblock.data_block_count, min_data_blocks));
EXPECT_GE(actual_superblock.journal_block_count, original_superblock.journal_block_count);
// This should match the result of the sb files above, but aligned to slices.
EXPECT_EQ(actual_superblock.slice_size, fvm_options.slice_size);
EXPECT_EQ(actual_superblock.ino_slices,
GetBlockCount(blobfs::kFVMNodeMapStart * blobfs::kBlobfsBlockSize,
actual_superblock.inode_count * blobfs::kBlobfsInodeSize,
fvm_options.slice_size));
EXPECT_EQ(actual_superblock.dat_slices,
actual_superblock.data_block_count * blobfs::kBlobfsBlockSize / fvm_options.slice_size);
EXPECT_EQ(
actual_superblock.journal_slices,
actual_superblock.journal_block_count * blobfs::kBlobfsBlockSize / fvm_options.slice_size);
EXPECT_EQ(actual_superblock.abm_slices,
GetBlockCount(blobfs::kFVMBlockMapStart,
blobfs::BlocksRequiredForBits(actual_superblock.data_block_count) *
blobfs::kBlobfsBlockSize,
fvm_options.slice_size));
// Check if there are leftover bytes, they are assigned to the journal.
if (partition_options.max_bytes.has_value()) {
uint64_t max_slices_for_leftovers =
GetBlockCount(0, partition_options.max_bytes.value(), fvm_options.slice_size);
// slices for the journal, from original image.
uint64_t min_journal_slices = GetBlockCount(
blobfs::kFVMJournalStart,
original_superblock.journal_block_count * blobfs::kBlobfsBlockSize, fvm_options.slice_size);
uint64_t slices_before_leftovers = 1 + actual_superblock.abm_slices +
actual_superblock.dat_slices + actual_superblock.ino_slices +
min_journal_slices;
if (max_slices_for_leftovers > slices_before_leftovers) {
uint64_t delta = max_slices_for_leftovers - slices_before_leftovers;
EXPECT_EQ(actual_superblock.journal_slices, delta + min_journal_slices);
}
}
}
void CheckNonSuperBlockMapping(const Partition& partition, const Reader& original_reader) {
std::vector<uint8_t> original_mapping_contents;
std::vector<uint8_t> mapping_contents;
// Now check that the reader has the correct data for the rest of the mappings.
for (uint64_t mapping_index = 1; mapping_index < partition.address().mappings.size();
++mapping_index) {
const auto& mapping = partition.address().mappings[mapping_index];
SCOPED_TRACE(testing::Message() << "Comparing mapping index " << mapping_index
<< " mapping: \n " << mapping.DebugString());
// Only count bytes are backed by source data.
mapping_contents.resize(mapping.count, 0);
original_mapping_contents.resize(mapping.count, 0);
// Adjust source for the extra block added for the backup superblock.
auto original_read_result =
original_reader.Read(mapping.source - blobfs::kBlobfsBlockSize, original_mapping_contents);
ASSERT_TRUE(original_read_result.is_ok()) << original_read_result.error();
auto read_result = partition.reader()->Read(mapping.source, mapping_contents);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
EXPECT_TRUE(memcmp(mapping_contents.data(), original_mapping_contents.data(),
mapping_contents.size()) == 0);
}
}
void CheckJournalMapping(const Partition& partition, const Reader& original_reader,
const blobfs::Superblock& original_superblock) {
std::vector<uint8_t> original_mapping_contents;
std::vector<uint8_t> mapping_contents;
auto mapping_or = FindMappingStartingAt(blobfs::kFVMJournalStart * blobfs::kBlobfsBlockSize,
partition.address());
ASSERT_TRUE(mapping_or.has_value());
auto mapping = mapping_or.value();
// Only count bytes are backed by source data.
mapping_contents.resize(mapping.count, 0);
original_mapping_contents.resize(mapping.count, 0);
// Adjust source for the extra block added for the backup superblock.
auto original_read_result = original_reader.Read(
blobfs::JournalStartBlock(original_superblock) * blobfs::kBlobfsBlockSize,
original_mapping_contents);
ASSERT_TRUE(original_read_result.is_ok()) << original_read_result.error();
auto read_result = partition.reader()->Read(mapping.source, mapping_contents);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
EXPECT_TRUE(memcmp(mapping_contents.data(), original_mapping_contents.data(),
mapping_contents.size()) == 0);
}
void CheckPartition(const Partition& partition) {
EXPECT_EQ(partition.volume().name, "blobfs");
EXPECT_THAT(partition.volume().instance, testing::ElementsAreArray(kBlobfsInstanceGuid));
EXPECT_THAT(partition.volume().type, testing::ElementsAreArray(kBlobfsTypeGuid));
auto superblock_mapping = FindMappingStartingAt(0, partition.address());
ASSERT_TRUE(superblock_mapping.has_value());
EXPECT_EQ(superblock_mapping->source, 0u);
EXPECT_EQ(superblock_mapping->count, 2 * blobfs::kBlobfsBlockSize);
EXPECT_THAT(superblock_mapping->options,
testing::Contains(testing::Pair(EnumAsString(AddressMapOption::kFill), 0u)));
// 5 total different regions, including superblock region.
ASSERT_EQ(partition.address().mappings.size(), 5u);
// Check that mappings for all the regions exist.
auto inode_mapping = FindMappingStartingAt(blobfs::kFVMNodeMapStart * blobfs::kBlobfsBlockSize,
partition.address());
ASSERT_TRUE(inode_mapping.has_value());
EXPECT_THAT(inode_mapping->options,
testing::Contains(testing::Pair(EnumAsString(AddressMapOption::kFill), 0u)));
auto bitmap_mapping = FindMappingStartingAt(blobfs::kFVMBlockMapStart * blobfs::kBlobfsBlockSize,
partition.address());
ASSERT_TRUE(bitmap_mapping.has_value());
EXPECT_THAT(bitmap_mapping->options,
testing::Contains(testing::Pair(EnumAsString(AddressMapOption::kFill), 0u)));
// This two just need to exist, they should not be zeroed like the above.
ASSERT_TRUE(
FindMappingStartingAt(blobfs::kFVMDataStart * blobfs::kBlobfsBlockSize, partition.address())
.has_value());
ASSERT_TRUE(FindMappingStartingAt(blobfs::kFVMJournalStart * blobfs::kBlobfsBlockSize,
partition.address())
.has_value());
}
struct SuperBlocks {
std::vector<uint8_t> original_superblock;
std::vector<uint8_t> actual_superblock;
};
std::optional<SuperBlocks> ReadSuperblocks(const Reader& original_blobfs,
const Reader& blobfs_with_backup) {
std::vector<uint8_t> original_superblock;
original_superblock.resize(blobfs::kBlobfsBlockSize, 0);
auto original_superblock_result = original_blobfs.Read(0, original_superblock);
EXPECT_TRUE(original_superblock_result.is_ok()) << original_superblock_result.error();
std::vector<uint8_t> superblock;
superblock.resize(blobfs::kBlobfsBlockSize, 0);
auto superblock_result = blobfs_with_backup.Read(0, superblock);
EXPECT_TRUE(superblock_result.is_ok()) << superblock_result.error();
std::vector<uint8_t> backup_superblock;
backup_superblock.resize(blobfs::kBlobfsBlockSize, 0);
auto backup_superblock_result =
blobfs_with_backup.Read(blobfs::kBlobfsBlockSize, backup_superblock);
EXPECT_TRUE(backup_superblock_result.is_ok()) << backup_superblock_result.error();
if (testing::Test::HasFailure()) {
return std::nullopt;
}
// Check that superblock and backupsuperblock are equal.
EXPECT_TRUE(memcmp(backup_superblock.data(), superblock.data(), backup_superblock.size()) == 0);
// Check that reading both superblocks together has the same result.
std::vector<uint8_t> both_superblocks;
both_superblocks.resize(2 * blobfs::kBlobfsBlockSize, 0);
auto both_superblocks_result = blobfs_with_backup.Read(0, both_superblocks);
EXPECT_TRUE(both_superblocks_result.is_ok()) << both_superblocks_result.error();
if (testing::Test::HasFailure()) {
return std::nullopt;
}
EXPECT_TRUE(memcmp(both_superblocks.data(), both_superblocks.data() + blobfs::kBlobfsBlockSize,
blobfs::kBlobfsBlockSize) == 0);
if (testing::Test::HasFailure()) {
return std::nullopt;
}
return SuperBlocks{original_superblock, superblock};
}
TEST(BlobfsPartitionTest, PartitionDataAndReaderIsCorrect) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
}
TEST(BlobfsPartitionTest, PartitionDataAndReaderIsCorrectWithMinimumInodeCountHigherThanImage) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
blobfs::Superblock sb_tmp = {};
auto read_result = original_blobfs_reader.Read(
0, cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&sb_tmp), sizeof(sb_tmp)));
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
// Add as many inodes such that at least an extra slice is allocated.
partition_options.min_inode_count =
sb_tmp.inode_count + GetBlockCount(0, fvm_options.slice_size, blobfs::kBlobfsInodeSize);
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
// Adjust the expected inode count such that it matches the amount of slices that would be
// required for the requested options.
auto expected_inode_count =
GetBlockCount(blobfs::kFVMNodeMapStart,
blobfs::BlocksRequiredForInode(partition_options.min_inode_count.value()) *
blobfs::kBlobfsBlockSize,
blobfs::kBlobfsInodeSize);
ASSERT_EQ(sb->inode_count, expected_inode_count);
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
ASSERT_NO_FATAL_FAILURE(CheckJournalMapping(partition, original_blobfs_reader, *original_sb));
}
TEST(BlobfsPartitionTest, PartitionDataAndReaderIsCorrectWithMinimumInodeCountLowerThanImage) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
partition_options.min_inode_count = 0;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
ASSERT_NE(sb->inode_count, 0u);
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
ASSERT_NO_FATAL_FAILURE(CheckJournalMapping(partition, original_blobfs_reader, *original_sb));
}
TEST(BlobfsPartitionTest, PartitionDataAndReaderIsCorrectWithMinimumDataBytesHigherThanImage) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
blobfs::Superblock sb_tmp = {};
auto read_result = original_blobfs_reader.Read(
0, cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&sb_tmp), sizeof(sb_tmp)));
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
// Add an extra slice worth of blocks.
partition_options.min_data_bytes =
(sb_tmp.data_block_count +
GetBlockCount(0, fvm_options.slice_size, blobfs::kBlobfsBlockSize)) *
blobfs::kBlobfsBlockSize;
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
ASSERT_GE(sb->data_block_count,
GetBlockCount(blobfs::kFVMDataStart, partition_options.min_data_bytes.value(),
blobfs::kBlobfsBlockSize));
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
ASSERT_NO_FATAL_FAILURE(CheckJournalMapping(partition, original_blobfs_reader, *original_sb));
}
TEST(BlobfsPartitionTest, PartitionDataAndReaderIsCorrectWithMinimumDataBytessLowerThanImage) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
// Add an extra slice worth of blocks.
partition_options.min_data_bytes = 0;
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
ASSERT_NE(sb->data_block_count, 0u);
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
ASSERT_NO_FATAL_FAILURE(CheckJournalMapping(partition, original_blobfs_reader, *original_sb));
}
TEST(BlobfsPartitionTest,
PartitionDataAndReaderIsCorrectWithMaxAllocatedBytesForLeftOverHigherThanImage) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
// Set it to an absurd amount, this should only be reflected on journal slices.
partition_options.max_bytes = 10u * (1u << 30);
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_ok()) << partition_or.error();
auto partition = partition_or.take_value();
ASSERT_NO_FATAL_FAILURE(CheckPartition(partition));
auto maybe_superblocks = ReadSuperblocks(original_blobfs_reader, *partition.reader());
ASSERT_TRUE(maybe_superblocks.has_value());
auto [original_superblock, superblock] = maybe_superblocks.value();
// Check the fields in the superblock are at least as big as those in the original image, and
// the allocation counts remain unchanged.
const blobfs::Superblock* sb = reinterpret_cast<blobfs::Superblock*>(superblock.data());
const blobfs::Superblock* original_sb =
reinterpret_cast<blobfs::Superblock*>(original_superblock.data());
// The actual value will be matched in the |CheckSuperblock|, but we can at least verify, that
// is bigger than would have been for the original journal blocks.
uint64_t old_blocks =
GetBlockCount(blobfs::kFVMJournalStart,
GetBlockCount(0, original_sb->journal_block_count * blobfs::kBlobfsBlockSize,
fvm_options.slice_size) *
fvm_options.slice_size,
blobfs::kBlobfsBlockSize);
ASSERT_GT(sb->journal_block_count, old_blocks);
ASSERT_NO_FATAL_FAILURE(CheckSuperblock(*sb, *original_sb, fvm_options, partition_options));
ASSERT_NO_FATAL_FAILURE(CheckNonSuperBlockMapping(partition, original_blobfs_reader));
ASSERT_NO_FATAL_FAILURE(CheckJournalMapping(partition, original_blobfs_reader, *original_sb));
}
TEST(BlobfsPartitionTest, ExceedingMaxBytesIsError) {
auto fvm_options = MakeFvmOptions(kSliceSize);
PartitionOptions partition_options;
auto original_blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(original_blobfs_reader_or.is_ok()) << original_blobfs_reader_or.error();
auto original_blobfs_reader = original_blobfs_reader_or.take_value();
// Number of mappings - 1 slices, so this will be 4.
partition_options.max_bytes = 4 * fvm_options.slice_size;
auto blobfs_reader_or = FdReader::Create(kBlobfsImagePath);
ASSERT_TRUE(blobfs_reader_or.is_ok()) << blobfs_reader_or.error();
std::unique_ptr<Reader> blobfs_reader = std::make_unique<FdReader>(blobfs_reader_or.take_value());
auto partition_or =
CreateBlobfsFvmPartition(std::move(blobfs_reader), partition_options, fvm_options);
ASSERT_TRUE(partition_or.is_error());
}
} // namespace
} // namespace storage::volume_image