blob: eae8952bd5cf977b3ec13644fedb590ad3aaca91 [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/minfs/inspector/minfs_inspector.h"
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <iostream>
#include <disk_inspector/inspector_transaction_handler.h>
#include <disk_inspector/vmo_buffer_factory.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "src/storage/lib/vfs/cpp/journal/format.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/runner.h"
namespace minfs {
namespace {
using block_client::FakeBlockDevice;
constexpr uint64_t kBlockCount = 1 << 15;
constexpr uint32_t kBlockSize = 512;
class MinfsInspectorTest : public testing::Test {
public:
MinfsInspectorTest() : vfs_loop_(&kAsyncLoopConfigAttachToCurrentThread) {}
static std::unique_ptr<MinfsInspector> CreateMinfsInspector(
std::unique_ptr<block_client::BlockDevice> device) {
std::unique_ptr<disk_inspector::InspectorTransactionHandler> inspector_handler;
EXPECT_EQ(disk_inspector::InspectorTransactionHandler::Create(
std::move(device), kMinfsBlockSize, &inspector_handler),
ZX_OK);
auto buffer_factory = std::make_unique<disk_inspector::VmoBufferFactory>(
inspector_handler.get(), kMinfsBlockSize);
auto result = MinfsInspector::Create(std::move(inspector_handler), std::move(buffer_factory));
EXPECT_TRUE(result.is_ok());
return result.take_value();
}
// Initialize a MinfsInspector from a created fake block device formatted into a fresh minfs
// partition and journal entries.
std::unique_ptr<MinfsInspector> SetupMinfsInspector() {
auto temp = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
// Format the device.
auto bcache_or = Bcache::Create(std::move(temp), kBlockCount);
EXPECT_TRUE(bcache_or.is_ok());
EXPECT_TRUE(Mkfs(bcache_or.value().get()).is_ok());
// Write journal info to the device by creating a minfs and waiting for it to finish.
MountOptions options = {};
auto fs_or =
minfs::Runner::Create(vfs_loop_.dispatcher(), std::move(bcache_or.value()), options);
EXPECT_TRUE(fs_or.is_ok());
sync_completion_t completion;
fs_or->minfs().Sync([&completion](zx_status_t status) { sync_completion_signal(&completion); });
EXPECT_EQ(sync_completion_wait(&completion, zx::duration::infinite().get()), ZX_OK);
// We only care about the disk format written into the fake block device, so we destroy the
// minfs/bcache used to format it.
bcache_or = zx::ok(Runner::Destroy(std::move(fs_or.value())));
return CreateMinfsInspector(Bcache::Destroy(std::move(bcache_or.value())));
}
// Initialize a MinfsInspector from an zero-ed out block device. This simulates
// corruption to various metadata. Allows copying |count| bytes of |data| to
// the start of the fake block device.
static std::unique_ptr<MinfsInspector> BadSetupMinfsInspector(void* data, uint64_t count) {
auto temp = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
if (count > 0) {
zx::vmo buffer;
EXPECT_EQ(zx::vmo::create(count, 0, &buffer), ZX_OK);
EXPECT_EQ(buffer.write(data, 0, count), ZX_OK);
storage::OwnedVmoid vmoid;
EXPECT_EQ(temp->BlockAttachVmo(buffer, &vmoid.GetReference(temp.get())), ZX_OK);
std::vector<block_fifo_request_t> reqs = {{
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0},
.reqid = 0x0,
.group = 0,
.vmoid = vmoid.get(),
.length = static_cast<uint32_t>(count / kBlockSize),
.vmo_offset = 0,
.dev_offset = 0,
}};
EXPECT_EQ(temp->FifoTransaction(reqs.data(), 1), ZX_OK);
}
return CreateMinfsInspector(std::move(temp));
}
private:
async::Loop vfs_loop_;
};
TEST_F(MinfsInspectorTest, CreateWithoutError) { SetupMinfsInspector(); }
TEST_F(MinfsInspectorTest, CreateWithoutErrorOnBadSuperblock) {
BadSetupMinfsInspector(nullptr, 0);
}
TEST_F(MinfsInspectorTest, InspectSuperblock) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
EXPECT_EQ(sb.magic0, kMinfsMagic0);
EXPECT_EQ(sb.magic1, kMinfsMagic1);
EXPECT_EQ(sb.major_version, kMinfsCurrentMajorVersion);
EXPECT_EQ(sb.flags, kMinfsFlagClean);
EXPECT_EQ(sb.block_size, kMinfsBlockSize);
EXPECT_EQ(sb.inode_size, kMinfsInodeSize);
EXPECT_EQ(sb.alloc_block_count, 2u);
EXPECT_EQ(sb.alloc_block_count, 2u);
}
TEST_F(MinfsInspectorTest, GetInodeCount) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
EXPECT_EQ(inspector->GetInodeCount(), sb.inode_count);
}
TEST_F(MinfsInspectorTest, InspectInode) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
// The fresh minfs device should have 2 allocated inodes, empty inode 0 and
// allocated inode 1.
ASSERT_EQ(sb.alloc_inode_count, 2u);
auto result = inspector->InspectInodeRange(0, 3);
ASSERT_TRUE(result.is_ok());
std::vector<Inode> inodes = result.take_value();
// 0th inode is uninitialized.
Inode inode = inodes[0];
EXPECT_EQ(inode.magic, 0u);
EXPECT_EQ(inode.size, 0u);
EXPECT_EQ(inode.block_count, 0u);
EXPECT_EQ(inode.link_count, 0u);
// 1st inode is initialized and is the root directory.
inode = inodes[1];
EXPECT_EQ(inode.magic, kMinfsMagicDir);
EXPECT_EQ(inode.size, kMinfsBlockSize);
EXPECT_EQ(inode.block_count, 1u);
EXPECT_EQ(inode.link_count, 2u);
// 2nd inode is uninitialized.
inode = inodes[2];
EXPECT_EQ(inode.magic, 0u);
EXPECT_EQ(inode.size, 0u);
EXPECT_EQ(inode.block_count, 0u);
EXPECT_EQ(inode.link_count, 0u);
}
TEST_F(MinfsInspectorTest, CheckInodeAllocated) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
ASSERT_TRUE(sb.alloc_inode_count < sb.inode_count);
uint32_t max_samples = 10;
uint32_t num_inodes_to_sample = (sb.inode_count < max_samples) ? sb.inode_count : max_samples;
auto result = inspector->InspectInodeAllocatedInRange(0, num_inodes_to_sample);
ASSERT_TRUE(result.is_ok());
std::vector<uint64_t> allocated_indices = result.take_value();
ASSERT_EQ(allocated_indices.size(), sb.alloc_inode_count);
for (uint32_t i = 0; i < sb.alloc_inode_count; ++i) {
EXPECT_EQ(allocated_indices[i], i);
}
}
TEST_F(MinfsInspectorTest, InspectJournalSuperblock) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
auto result = inspector->InspectJournalSuperblock();
ASSERT_TRUE(result.is_ok());
fs::JournalInfo journal_info = result.take_value();
EXPECT_EQ(journal_info.magic, fs::kJournalMagic);
EXPECT_EQ(journal_info.start_block, 8ul);
}
TEST_F(MinfsInspectorTest, GetJournalEntryCount) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
uint64_t expected_count = JournalBlocks(sb) - fs::kJournalMetadataBlocks;
EXPECT_EQ(inspector->GetJournalEntryCount(), expected_count);
}
// This ends up being a special case because we group both the journal superblock
// and the journal entries in a single vmo, 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_F(MinfsInspectorTest, GetJournalEntryCountWithNoJournalBlocks) {
Superblock superblock = {};
superblock.integrity_start_block = 0;
superblock.dat_block = superblock.integrity_start_block + kBackupSuperblockBlocks;
std::unique_ptr<MinfsInspector> inspector =
BadSetupMinfsInspector(&superblock, sizeof(superblock));
EXPECT_EQ(inspector->GetJournalEntryCount(), 0ul);
}
template <typename T>
void LoadAndUnwrapJournalEntry(MinfsInspector* inspector, uint64_t index, T* out_value) {
auto result = inspector->InspectJournalEntryAs<T>(index);
ASSERT_TRUE(result.is_ok());
*out_value = result.take_value();
}
TEST_F(MinfsInspectorTest, InspectJournalEntryAs) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
// First four entry blocks should be header, payload, payload, commit.
fs::JournalHeaderBlock header;
LoadAndUnwrapJournalEntry<fs::JournalHeaderBlock>(inspector.get(), 0, &header);
EXPECT_EQ(header.prefix.magic, fs::kJournalEntryMagic);
EXPECT_EQ(header.prefix.sequence_number, 0ul);
EXPECT_EQ(header.prefix.flags, fs::kJournalPrefixFlagHeader);
EXPECT_EQ(header.payload_blocks, 2ul);
fs::JournalPrefix prefix;
LoadAndUnwrapJournalEntry<fs::JournalPrefix>(inspector.get(), 1, &prefix);
EXPECT_NE(prefix.magic, fs::kJournalEntryMagic);
LoadAndUnwrapJournalEntry<fs::JournalPrefix>(inspector.get(), 2, &prefix);
EXPECT_NE(prefix.magic, fs::kJournalEntryMagic);
fs::JournalCommitBlock commit;
LoadAndUnwrapJournalEntry<fs::JournalCommitBlock>(inspector.get(), 3, &commit);
EXPECT_EQ(commit.prefix.magic, fs::kJournalEntryMagic);
EXPECT_EQ(commit.prefix.sequence_number, 0ul);
EXPECT_EQ(commit.prefix.flags, fs::kJournalPrefixFlagCommit);
}
TEST_F(MinfsInspectorTest, InspectBackupSuperblock) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
auto result = inspector->InspectBackupSuperblock();
ASSERT_TRUE(result.is_ok());
Superblock sb = result.take_value();
EXPECT_EQ(sb.magic0, kMinfsMagic0);
EXPECT_EQ(sb.magic1, kMinfsMagic1);
EXPECT_EQ(sb.major_version, kMinfsCurrentMajorVersion);
EXPECT_EQ(sb.flags, kMinfsFlagClean);
EXPECT_EQ(sb.block_size, kMinfsBlockSize);
EXPECT_EQ(sb.inode_size, kMinfsInodeSize);
EXPECT_EQ(sb.alloc_block_count, 2u);
EXPECT_EQ(sb.alloc_block_count, 2u);
}
TEST_F(MinfsInspectorTest, WriteSuperblock) {
std::unique_ptr<MinfsInspector> inspector = SetupMinfsInspector();
Superblock sb = inspector->InspectSuperblock();
// Test original values are correct.
EXPECT_EQ(sb.magic0, kMinfsMagic0);
EXPECT_EQ(sb.magic1, kMinfsMagic1);
EXPECT_EQ(sb.major_version, kMinfsCurrentMajorVersion);
// Edit values and write.
sb.magic0 = 0;
sb.major_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(edit_sb.magic0, 0u);
EXPECT_EQ(edit_sb.magic1, kMinfsMagic1);
EXPECT_EQ(edit_sb.major_version, 0u);
// Test reloading from disk.
ASSERT_EQ(inspector->ReloadSuperblock(), ZX_OK);
Superblock reload_sb = inspector->InspectSuperblock();
EXPECT_EQ(reload_sb.magic0, 0u);
EXPECT_EQ(reload_sb.magic1, kMinfsMagic1);
EXPECT_EQ(reload_sb.major_version, 0u);
}
} // namespace
} // namespace minfs