blob: d9c731da96acda0a0f8ddcf3f44288728058567c [file] [log] [blame]
// 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