| // Copyright 2020 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/blobfs/blobfs_inspector.h" |
| |
| #include <iostream> |
| #include <memory> |
| |
| #include <disk_inspector/buffer_factory.h> |
| #include <fs/journal/format.h> |
| #include <fs/journal/initializer.h> |
| #include <fs/transaction/legacy_transaction_handler.h> |
| #include <gtest/gtest.h> |
| #include <safemath/checked_math.h> |
| #include <storage/buffer/array_buffer.h> |
| |
| #include "src/storage/blobfs/blob-layout.h" |
| #include "src/storage/blobfs/common.h" |
| #include "src/storage/blobfs/format.h" |
| |
| namespace blobfs { |
| namespace { |
| |
| constexpr uint64_t kBlockCount = 1 << 10; |
| |
| class FakeTransactionHandler : public fs::LegacyTransactionHandler { |
| public: |
| explicit FakeTransactionHandler(std::unique_ptr<storage::ArrayBuffer> fake_device) |
| : fake_device_(std::move(fake_device)) {} |
| FakeTransactionHandler(const FakeTransactionHandler&) = delete; |
| FakeTransactionHandler(FakeTransactionHandler&&) = default; |
| FakeTransactionHandler& operator=(const FakeTransactionHandler&) = delete; |
| FakeTransactionHandler& operator=(FakeTransactionHandler&&) = default; |
| |
| // TransactionHandler interface: |
| uint32_t FsBlockSize() const final { return fake_device_->BlockSize(); } |
| |
| uint64_t BlockNumberToDevice(uint64_t block_num) const final { return block_num; } |
| |
| zx_status_t RunOperation(const storage::Operation& operation, |
| storage::BlockBuffer* buffer) final { |
| ValidateOperation(operation, buffer); |
| switch (operation.type) { |
| case storage::OperationType::kRead: |
| memcpy(buffer->Data(operation.vmo_offset), fake_device_->Data(operation.dev_offset), |
| operation.length * fake_device_->BlockSize()); |
| break; |
| case storage::OperationType::kWrite: |
| memcpy(fake_device_->Data(operation.dev_offset), buffer->Data(operation.vmo_offset), |
| operation.length * fake_device_->BlockSize()); |
| break; |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| uint32_t DeviceBlockSize() const final { return fake_device_->BlockSize(); } |
| |
| block_client::BlockDevice* GetDevice() final { return nullptr; } |
| |
| zx_status_t Transaction(block_fifo_request_t* requests, size_t count) final { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void ValidateOperation(const storage::Operation& operation, storage::BlockBuffer* buffer) { |
| ASSERT_NE(fake_device_, nullptr); |
| ASSERT_GE(buffer->capacity(), operation.vmo_offset + operation.length) |
| << "Operation goes past input buffer length"; |
| ASSERT_GE(fake_device_->capacity(), operation.dev_offset + operation.length) |
| << "Operation goes past device buffer length"; |
| |
| ASSERT_NE(operation.type, storage::OperationType::kTrim) << "Trim operation is not supported\n"; |
| } |
| |
| storage::BlockBuffer* GetDeviceBuffer() { return fake_device_.get(); } |
| |
| private: |
| std::unique_ptr<storage::ArrayBuffer> fake_device_; |
| }; |
| |
| class ArrayBufferFactory : public disk_inspector::BufferFactory { |
| public: |
| explicit ArrayBufferFactory(uint32_t block_size) : block_size_(block_size) {} |
| ArrayBufferFactory(const ArrayBufferFactory&) = delete; |
| ArrayBufferFactory(ArrayBufferFactory&&) = default; |
| ArrayBufferFactory& operator=(const ArrayBufferFactory&) = delete; |
| ArrayBufferFactory& operator=(ArrayBufferFactory&&) = default; |
| |
| fit::result<std::unique_ptr<storage::BlockBuffer>, zx_status_t> CreateBuffer( |
| size_t capacity) const final { |
| return fit::ok(std::make_unique<storage::ArrayBuffer>(capacity, block_size_)); |
| } |
| |
| private: |
| uint32_t block_size_; |
| }; |
| |
| // Initialize a FakeTransactionHandler backed by a buffer representing a |
| // a fresh Blobfs partition and journal entries. |
| void CreateFakeBlobfsHandler(std::unique_ptr<FakeTransactionHandler>* handler) { |
| auto device = std::make_unique<storage::ArrayBuffer>(kBlockCount, kBlobfsBlockSize); |
| |
| // Superblock. |
| Superblock superblock; |
| InitializeSuperblock( |
| kBlockCount, {.blob_layout_format = BlobLayoutFormat::kCompactMerkleTreeAtEnd}, &superblock); |
| memcpy(device->Data(kSuperblockOffset), &superblock, sizeof(superblock)); |
| |
| // Allocation bitmap. |
| RawBitmap block_bitmap; |
| ASSERT_EQ(block_bitmap.Reset(BlockMapBlocks(superblock) * kBlobfsBlockBits), ZX_OK); |
| block_bitmap.Set(0, kStartBlockMinimum); |
| uint64_t bitmap_length = BlockMapBlocks(superblock) * kBlobfsBlockSize; |
| memcpy(device->Data(BlockMapStartBlock(superblock)), GetRawBitmapData(block_bitmap, 0), |
| bitmap_length); |
| |
| // Node map. |
| uint64_t nodemap_length = NodeMapBlocks(superblock) * kBlobfsBlockSize; |
| memset(device->Data(NodeMapStartBlock(superblock)), 0, nodemap_length); |
| |
| // Journal. |
| fs::WriteBlocksFn write_blocks_fn = [&device, &superblock](fbl::Span<const uint8_t> buffer, |
| uint64_t block_offset, |
| uint64_t block_count) { |
| uint64_t size = safemath::CheckMul<uint64_t>(block_count, kBlobfsBlockSize).ValueOrDie(); |
| ZX_ASSERT((block_offset + block_count) <= JournalBlocks(superblock)); |
| ZX_ASSERT(buffer.size() >= size); |
| memcpy(device->Data(JournalStartBlock(superblock) + block_offset), buffer.data(), size); |
| return ZX_OK; |
| }; |
| ASSERT_EQ(fs::MakeJournal(JournalBlocks(superblock), write_blocks_fn), ZX_OK); |
| |
| *handler = std::make_unique<FakeTransactionHandler>(std::move(device)); |
| } |
| |
| // Initialize a BlobfsInspector from an zero-ed out block device. This simulates |
| // corruption to various metadata. |
| void CreateBadFakeBlobfsHandler(std::unique_ptr<FakeTransactionHandler>* handler) { |
| auto device = std::make_unique<storage::ArrayBuffer>(kBlockCount, kBlobfsBlockSize); |
| memset(device->Data(0), 0, kBlockCount * kBlobfsBlockSize); |
| *handler = std::make_unique<FakeTransactionHandler>(std::move(device)); |
| } |
| |
| void CreateBlobfsInspector(std::unique_ptr<FakeTransactionHandler> handler, |
| std::unique_ptr<BlobfsInspector>* out) { |
| auto buffer_factory = std::make_unique<ArrayBufferFactory>(kBlobfsBlockSize); |
| auto result = BlobfsInspector::Create(std::move(handler), std::move(buffer_factory)); |
| ASSERT_TRUE(result.is_ok()); |
| *out = std::move(result.value()); |
| } |
| |
| TEST(BlobfsInspector, CreateWithoutError) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| } |
| |
| TEST(BlobfsInspector, CreateWithoutErrorOnBadSuperblock) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateBadFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| } |
| |
| TEST(BlobfsInspector, InspectSuperblock) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| Superblock sb = inspector->InspectSuperblock(); |
| |
| EXPECT_EQ(kBlobfsMagic0, sb.magic0); |
| EXPECT_EQ(kBlobfsMagic1, sb.magic1); |
| EXPECT_EQ(kBlobfsCurrentFormatVersion, sb.format_version); |
| EXPECT_EQ(kBlobFlagClean, sb.flags); |
| EXPECT_EQ(kBlobfsBlockSize, sb.block_size); |
| EXPECT_EQ(1ul, sb.alloc_block_count); |
| EXPECT_EQ(0ul, sb.alloc_inode_count); |
| EXPECT_EQ(0ul, sb.reserved2); |
| } |
| |
| TEST(BlobfsInspector, GetInodeCount) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| Superblock sb = inspector->InspectSuperblock(); |
| EXPECT_EQ(sb.inode_count, inspector->GetInodeCount()); |
| } |
| |
| TEST(BlobfsInspector, InspectInode) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| |
| auto* superblock = |
| reinterpret_cast<Superblock*>(handler->GetDeviceBuffer()->Data(kSuperblockOffset)); |
| superblock->alloc_inode_count = 2; |
| |
| // Create the first node to be an inode. |
| auto inode = |
| reinterpret_cast<Inode*>(handler->GetDeviceBuffer()->Data(NodeMapStartBlock(*superblock))); |
| inode[0].header.flags = kBlobFlagAllocated; |
| inode[0].block_count = 5; |
| inode[0].extent_count = 42; |
| |
| // Create the second node to be an extent. |
| auto extent = reinterpret_cast<ExtentContainer*>( |
| handler->GetDeviceBuffer()->Data(NodeMapStartBlock(*superblock))); |
| extent[1].header.flags = kBlobFlagAllocated | kBlobFlagExtentContainer; |
| extent[1].previous_node = 10; |
| extent[1].extent_count = 123; |
| |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| Superblock sb = inspector->InspectSuperblock(); |
| // The fresh Blobfs device should have 2 allocated inodes. |
| ASSERT_EQ(2ul, sb.alloc_inode_count); |
| |
| auto result = inspector->InspectInodeRange(0, 3); |
| ASSERT_TRUE(result.is_ok()); |
| std::vector<Inode> inodes = std::move(result.value()); |
| EXPECT_TRUE(inodes[0].header.IsAllocated()); |
| EXPECT_TRUE(inodes[0].header.IsInode()); |
| EXPECT_EQ(5u, inodes[0].block_count); |
| EXPECT_EQ(42u, inodes[0].extent_count); |
| |
| EXPECT_TRUE(inodes[1].header.IsAllocated()); |
| EXPECT_FALSE(inodes[1].header.IsInode()); |
| EXPECT_EQ(10u, inodes[1].AsExtentContainer()->previous_node); |
| EXPECT_EQ(123u, inodes[1].AsExtentContainer()->extent_count); |
| |
| EXPECT_FALSE(inodes[2].header.IsAllocated()); |
| } |
| |
| TEST(BlobfsInspector, InspectJournalSuperblock) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| auto result = inspector->InspectJournalSuperblock(); |
| ASSERT_TRUE(result.is_ok()); |
| fs::JournalInfo journal_info = result.value(); |
| |
| EXPECT_EQ(fs::kJournalMagic, journal_info.magic); |
| EXPECT_EQ(0u, journal_info.start_block); |
| } |
| |
| TEST(BlobfsInspector, GetJournalEntryCount) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| Superblock sb = inspector->InspectSuperblock(); |
| uint64_t expected_count = JournalBlocks(sb) - fs::kJournalMetadataBlocks; |
| EXPECT_EQ(expected_count, inspector->GetJournalEntryCount()); |
| } |
| |
| // This ends up being a special case because we group both the journal superblock |
| // and the journal entries in a single buffer, so we cannot just naively subtract |
| // the number of superblocks from the size of the buffer in the case in which |
| // the buffer is uninitialized/have capacity of zero. |
| TEST(BlobfsInspector, GetJournalEntryCountWithNoJournalBlocks) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateBadFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| EXPECT_EQ(0ul, inspector->GetJournalEntryCount()); |
| } |
| |
| template <typename T> |
| void LoadAndUnwrapJournalEntry(BlobfsInspector* inspector, uint64_t index, T* out_value) { |
| auto result = inspector->InspectJournalEntryAs<T>(index); |
| ASSERT_TRUE(result.is_ok()); |
| *out_value = std::move(result.value()); |
| } |
| |
| TEST(BlobfsInspector, InspectJournalEntryAs) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| |
| Superblock superblock = |
| *reinterpret_cast<Superblock*>(handler->GetDeviceBuffer()->Data(kSuperblockOffset)); |
| |
| uint64_t journal_entry_start = JournalStartBlock(superblock) + fs::kJournalMetadataBlocks; |
| |
| // Write header to backing buffer. |
| auto header_ptr = reinterpret_cast<fs::JournalHeaderBlock*>( |
| handler->GetDeviceBuffer()->Data(journal_entry_start)); |
| header_ptr->prefix.magic = fs::kJournalEntryMagic; |
| header_ptr->prefix.sequence_number = 0; |
| header_ptr->prefix.flags = fs::kJournalPrefixFlagHeader; |
| header_ptr->payload_blocks = 2; |
| |
| // Write commit to backing buffer. |
| auto commit_ptr = reinterpret_cast<fs::JournalCommitBlock*>( |
| handler->GetDeviceBuffer()->Data(journal_entry_start + 3)); |
| commit_ptr->prefix.magic = fs::kJournalEntryMagic; |
| commit_ptr->prefix.sequence_number = 0; |
| commit_ptr->prefix.flags = fs::kJournalPrefixFlagCommit; |
| |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| // First four entry blocks should be header, payload, payload, commit. |
| fs::JournalHeaderBlock header; |
| LoadAndUnwrapJournalEntry(inspector.get(), 0, &header); |
| EXPECT_EQ(fs::kJournalEntryMagic, header.prefix.magic); |
| EXPECT_EQ(0ul, header.prefix.sequence_number); |
| EXPECT_EQ(fs::kJournalPrefixFlagHeader, header.prefix.flags); |
| EXPECT_EQ(2ul, header.payload_blocks); |
| |
| fs::JournalPrefix prefix; |
| LoadAndUnwrapJournalEntry(inspector.get(), 1, &prefix); |
| EXPECT_NE(fs::kJournalEntryMagic, prefix.magic); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), 2, &prefix); |
| EXPECT_NE(fs::kJournalEntryMagic, prefix.magic); |
| |
| fs::JournalCommitBlock commit; |
| LoadAndUnwrapJournalEntry(inspector.get(), 3, &commit); |
| EXPECT_EQ(fs::kJournalEntryMagic, commit.prefix.magic); |
| EXPECT_EQ(0ul, commit.prefix.sequence_number); |
| EXPECT_EQ(fs::kJournalPrefixFlagCommit, commit.prefix.flags); |
| } |
| |
| TEST(BlobfsInspector, InspectDataBlockAllocatedInRange) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| Superblock superblock = |
| *reinterpret_cast<Superblock*>(handler->GetDeviceBuffer()->Data(kSuperblockOffset)); |
| |
| uint64_t bytes_to_set = 10; |
| uint64_t bits_to_sample = bytes_to_set * 8; |
| |
| // Set alternating bits to true. |
| void* block_map_start = handler->GetDeviceBuffer()->Data(BlockMapStartBlock(superblock)); |
| memset(block_map_start, 0xaa, bytes_to_set); |
| |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| auto result = inspector->InspectDataBlockAllocatedInRange(0, bits_to_sample); |
| ASSERT_TRUE(result.is_ok()); |
| |
| std::vector<uint64_t> allocated_indices = std::move(result.value()); |
| |
| ASSERT_EQ(allocated_indices.size(), bits_to_sample / 2); |
| for (uint32_t i = 0; i < allocated_indices.size(); ++i) { |
| EXPECT_EQ((2u * i) + 1u, allocated_indices[i]); |
| } |
| } |
| |
| TEST(BlobfsInspector, WriteSuperblock) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| Superblock sb = inspector->InspectSuperblock(); |
| // Test original values are correct. |
| EXPECT_EQ(kBlobfsMagic0, sb.magic0); |
| EXPECT_EQ(kBlobfsMagic1, sb.magic1); |
| EXPECT_EQ(kBlobfsCurrentFormatVersion, sb.format_version); |
| |
| // Edit values and write. |
| sb.magic0 = 0; |
| sb.format_version = 0; |
| auto result = inspector->WriteSuperblock(sb); |
| ASSERT_TRUE(result.is_ok()); |
| |
| // Test if superblock is saved in memory. |
| Superblock edit_sb = inspector->InspectSuperblock(); |
| EXPECT_EQ(0ul, edit_sb.magic0); |
| EXPECT_EQ(kBlobfsMagic1, edit_sb.magic1); |
| EXPECT_EQ(0u, edit_sb.format_version); |
| |
| // Test reloading from disk. |
| ASSERT_EQ(inspector->ReloadSuperblock(), ZX_OK); |
| Superblock reload_sb = inspector->InspectSuperblock(); |
| EXPECT_EQ(0ul, reload_sb.magic0); |
| EXPECT_EQ(kBlobfsMagic1, reload_sb.magic1); |
| EXPECT_EQ(0u, reload_sb.format_version); |
| } |
| |
| std::vector<Inode> AlternateAddInodesAndExtentContainers(uint64_t inode_count) { |
| std::vector<Inode> write_inodes; |
| for (uint32_t i = 0; i < inode_count; ++i) { |
| Inode inode = {}; |
| if (i % 2 == 0) { |
| inode.header.flags = kBlobFlagAllocated; |
| inode.block_count = i; |
| } else { |
| inode.AsExtentContainer()->header.flags = kBlobFlagAllocated | kBlobFlagExtentContainer; |
| inode.AsExtentContainer()->previous_node = i; |
| } |
| write_inodes.emplace_back(inode); |
| } |
| return write_inodes; |
| } |
| |
| TEST(BlobfsInspector, WriteInodes) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| uint64_t start_index = 12; |
| // Test inodes in multiple blocks. |
| uint64_t inode_count = 2 * kBlobfsInodesPerBlock; |
| uint64_t end_index = start_index + inode_count; |
| |
| // Sanity check that nothing is allocated at the start. |
| auto inspect_result = inspector->InspectInodeRange(0, end_index); |
| ASSERT_TRUE(inspect_result.is_ok()); |
| std::vector<Inode> initial_inodes = std::move(inspect_result.value()); |
| ASSERT_EQ(initial_inodes.size(), end_index); |
| |
| for (uint64_t i = 0; i < end_index; ++i) { |
| ASSERT_FALSE(initial_inodes[i].header.IsAllocated()); |
| } |
| |
| // Actual write. |
| std::vector<Inode> write_inodes = AlternateAddInodesAndExtentContainers(inode_count); |
| auto write_result = inspector->WriteInodes(write_inodes, start_index); |
| ASSERT_TRUE(write_result.is_ok()); |
| |
| // Test reading back the written inodes. |
| auto final_inspect_result = inspector->InspectInodeRange(0, start_index + inode_count); |
| ASSERT_TRUE(final_inspect_result.is_ok()); |
| std::vector<Inode> final_inodes = std::move(final_inspect_result.value()); |
| ASSERT_EQ(final_inodes.size(), end_index); |
| |
| for (uint64_t i = 0; i < start_index; ++i) { |
| EXPECT_FALSE(final_inodes[i].header.IsAllocated()); |
| } |
| |
| for (uint64_t i = 0; i < inode_count; ++i) { |
| Inode inode = final_inodes[start_index + i]; |
| EXPECT_TRUE(inode.header.IsAllocated()); |
| if (i % 2 == 0) { |
| ASSERT_TRUE(inode.header.IsInode()); |
| EXPECT_EQ(inode.block_count, i); |
| } else { |
| ASSERT_TRUE(inode.header.IsExtentContainer()); |
| EXPECT_EQ(inode.AsExtentContainer()->previous_node, i); |
| } |
| } |
| } |
| |
| TEST(BlobfsInspector, WriteJournalSuperblock) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| uint64_t magic = 1234; |
| uint64_t start_block = 42; |
| |
| // Make sure superblock original values are correct. |
| auto inspect_result = inspector->InspectJournalSuperblock(); |
| ASSERT_TRUE(inspect_result.is_ok()); |
| fs::JournalInfo journal_info = inspect_result.value(); |
| ASSERT_EQ(fs::kJournalMagic, journal_info.magic); |
| ASSERT_EQ(0ul, journal_info.start_block); |
| |
| fs::JournalInfo new_journal_info = {}; |
| new_journal_info.magic = magic; |
| new_journal_info.start_block = start_block; |
| auto write_result = inspector->WriteJournalSuperblock(new_journal_info); |
| ASSERT_TRUE(write_result.is_ok()); |
| |
| // Re-inspect that values have changed. |
| inspect_result = inspector->InspectJournalSuperblock(); |
| ASSERT_TRUE(inspect_result.is_ok()); |
| journal_info = inspect_result.value(); |
| ASSERT_EQ(magic, journal_info.magic); |
| ASSERT_EQ(start_block, journal_info.start_block); |
| } |
| |
| TEST(BlobfsInspector, WriteJournalEntryBlocks) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| uint64_t start_index = 0; |
| uint64_t payload_blocks = 2; |
| |
| // Make sure original values are zero. |
| // First four entry blocks should be header, payload, payload, commit. |
| fs::JournalHeaderBlock header; |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index, &header); |
| EXPECT_EQ(0ul, header.prefix.magic); |
| |
| fs::JournalPrefix prefix; |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 1, &prefix); |
| EXPECT_EQ(0ul, prefix.magic); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 2, &prefix); |
| EXPECT_EQ(0ul, prefix.magic); |
| |
| fs::JournalCommitBlock commit; |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 3, &commit); |
| EXPECT_EQ(0ul, commit.prefix.magic); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 4, &commit); |
| EXPECT_EQ(0ul, commit.prefix.magic); |
| |
| auto buffer_result = inspector->GetBufferFactory()->CreateBuffer(4); |
| ASSERT_TRUE(buffer_result.is_ok()); |
| std::unique_ptr<storage::BlockBuffer> buffer = buffer_result.take_value(); |
| |
| // Write header to backing buffer. |
| auto header_ptr = reinterpret_cast<fs::JournalHeaderBlock*>(buffer->Data(0)); |
| header_ptr->prefix.magic = fs::kJournalEntryMagic; |
| header_ptr->prefix.flags = fs::kJournalPrefixFlagHeader; |
| header_ptr->payload_blocks = payload_blocks; |
| |
| // Write commit to backing buffer. |
| auto commit_ptr = reinterpret_cast<fs::JournalCommitBlock*>(buffer->Data(3)); |
| commit_ptr->prefix.magic = fs::kJournalEntryMagic; |
| commit_ptr->prefix.flags = fs::kJournalPrefixFlagCommit; |
| |
| auto write_result = inspector->WriteJournalEntryBlocks(buffer.get(), start_index); |
| ASSERT_TRUE(write_result.is_ok()); |
| |
| // Re-read written blocks and the block after to make sure it has not changed. |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index, &header); |
| EXPECT_EQ(header.prefix.magic, fs::kJournalEntryMagic); |
| EXPECT_EQ(header.prefix.flags, fs::kJournalPrefixFlagHeader); |
| EXPECT_EQ(payload_blocks, header.payload_blocks); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 1, &prefix); |
| EXPECT_EQ(0ul, prefix.magic); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 2, &prefix); |
| EXPECT_EQ(0ul, prefix.magic); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 3, &commit); |
| EXPECT_EQ(fs::kJournalEntryMagic, commit.prefix.magic); |
| EXPECT_EQ(fs::kJournalPrefixFlagCommit, commit.prefix.flags); |
| |
| LoadAndUnwrapJournalEntry(inspector.get(), start_index + 4, &prefix); |
| EXPECT_EQ(0ul, prefix.magic); |
| } |
| |
| TEST(BlobfsInspector, WriteBlockAllocationBits) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| uint64_t start_index = 25; |
| uint64_t bits_to_sample = 100; |
| uint64_t end_index = start_index + bits_to_sample; |
| uint64_t bits_to_write = 50; |
| |
| // Make sure original values are not set. |
| { |
| auto inspect_result = inspector->InspectDataBlockAllocatedInRange(start_index, end_index); |
| ASSERT_TRUE(inspect_result.is_ok()); |
| // No values should be set. |
| ASSERT_EQ(0ul, std::move(inspect_result.value()).size()); |
| } |
| |
| // Write bits. |
| { |
| auto write_result = |
| inspector->WriteDataBlockAllocationBits(true, start_index, start_index + bits_to_write); |
| ASSERT_TRUE(write_result.is_ok()); |
| } |
| |
| // Re-inspect for changes. |
| { |
| auto inspect_result = inspector->InspectDataBlockAllocatedInRange(start_index, end_index); |
| ASSERT_TRUE(inspect_result.is_ok()); |
| std::vector<uint64_t> allocated_indices = std::move(inspect_result.value()); |
| ASSERT_EQ(bits_to_write, allocated_indices.size()); |
| |
| for (uint64_t i = 0; i < allocated_indices.size(); ++i) { |
| EXPECT_EQ(start_index + i, allocated_indices[i]); |
| } |
| } |
| } |
| |
| TEST(BlobfsInspector, WriteDataBlocks) { |
| std::unique_ptr<FakeTransactionHandler> handler; |
| CreateFakeBlobfsHandler(&handler); |
| |
| Superblock superblock = |
| *reinterpret_cast<Superblock*>(handler->GetDeviceBuffer()->Data(kSuperblockOffset)); |
| uint64_t start_offset = 25; |
| uint64_t blocks_to_write = 10; |
| storage::BlockBuffer* device = handler->GetDeviceBuffer(); |
| |
| std::unique_ptr<BlobfsInspector> inspector; |
| CreateBlobfsInspector(std::move(handler), &inspector); |
| |
| auto buffer_result = inspector->GetBufferFactory()->CreateBuffer(blocks_to_write); |
| ASSERT_TRUE(buffer_result.is_ok()); |
| std::unique_ptr<storage::BlockBuffer> buffer = buffer_result.take_value(); |
| |
| memset(buffer->Data(0), 0xab, blocks_to_write * kBlobfsBlockSize); |
| |
| auto write_result = inspector->WriteDataBlocks(buffer.get(), start_offset); |
| ASSERT_TRUE(write_result.is_ok()); |
| |
| EXPECT_EQ(memcmp(buffer->Data(0), device->Data(DataStartBlock(superblock) + start_offset), |
| blocks_to_write * kBlobfsBlockSize), |
| 0); |
| } |
| |
| } // namespace |
| } // namespace blobfs |