| // 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 |