blob: 3ef1f93ef6ef924d1c0a32e1342a4327d17f0ceb [file] [log] [blame]
// Copyright 2019 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.
// Tests minfs backup superblock behavior.
#include <lib/cksum.h>
#include <unistd.h>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "src/storage/lib/block_client/cpp/reader.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/fsck.h"
namespace minfs {
namespace {
using ::block_client::FakeBlockDevice;
using ::testing::_;
constexpr size_t abm_block = 5;
constexpr size_t ibm_block = 6;
constexpr size_t data_block = 7;
constexpr size_t integrity_block = 8;
// Mock TransactionHandler class to be used in superblock tests.
class MockTransactionHandler : public fs::DeviceTransactionHandler {
public:
explicit MockTransactionHandler(block_client::BlockDevice* device) { device_ = device; }
MockTransactionHandler(const MockTransactionHandler&) = delete;
MockTransactionHandler(MockTransactionHandler&&) = delete;
MockTransactionHandler& operator=(const MockTransactionHandler&) = delete;
MockTransactionHandler& operator=(MockTransactionHandler&&) = delete;
// fs::TransactionHandler Interface.
uint64_t BlockNumberToDevice(uint64_t block_num) const final { return block_num; }
block_client::BlockDevice* GetDevice() final { return device_; }
private:
block_client::BlockDevice* device_;
};
void CreateAndRegisterVmo(block_client::BlockDevice* device, size_t blocks, zx::vmo* vmo,
storage::OwnedVmoid* vmoid) {
fuchsia_hardware_block::wire::BlockInfo info = {};
ASSERT_EQ(device->BlockGetInfo(&info), ZX_OK);
ASSERT_EQ(zx::vmo::create(blocks * info.block_size, 0, vmo), ZX_OK);
ASSERT_EQ(device->BlockAttachVmo(*vmo, &vmoid->GetReference(device)), ZX_OK);
}
void FillSuperblockFields(Superblock* info) {
info->magic0 = kMinfsMagic0;
info->magic1 = kMinfsMagic1;
info->major_version = kMinfsCurrentMajorVersion;
info->flags = kMinfsFlagClean;
info->block_size = kMinfsBlockSize;
info->inode_size = kMinfsInodeSize;
info->dat_block = data_block;
info->integrity_start_block = integrity_block;
info->ibm_block = ibm_block;
info->abm_block = abm_block;
info->ino_block = abm_block;
info->block_count = 4;
info->inode_count = 4;
info->alloc_block_count = 2;
info->alloc_inode_count = 2;
info->generation_count = 0;
info->oldest_minor_version = kMinfsCurrentMinorVersion;
minfs::UpdateChecksum(info);
}
// Fills write request for the respective block locations.
void FillWriteRequest(MockTransactionHandler* transaction_handler, uint32_t first_block_location,
uint32_t second_block_location, vmoid_t vmoid,
block_fifo_request_t* out_requests) {
out_requests[0].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
out_requests[0].vmoid = vmoid;
out_requests[0].length = 1;
out_requests[0].vmo_offset = 0;
out_requests[0].dev_offset = first_block_location;
out_requests[1].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
out_requests[1].vmoid = vmoid;
out_requests[1].length = 1;
out_requests[1].vmo_offset = 1;
out_requests[1].dev_offset = second_block_location;
}
// Tests the alloc_*_counts bitmap reconstruction.
TEST(SuperblockTest, TestBitmapReconstruction) {
Superblock info = {};
FillSuperblockFields(&info);
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler = std::make_unique<MockTransactionHandler>(&device);
uint8_t block[minfs::kMinfsBlockSize];
memset(block, 0, sizeof(block));
// Fill up the entire bitmap sparsely with random 1 and 0.
// 0xFF = 8 bits set.
block[0] = 0xFF;
block[30] = 0xFF;
block[100] = 0xFF;
block[5000] = 0xFF;
zx::vmo vmo;
block_fifo_request_t request[2];
storage::OwnedVmoid vmoid;
ASSERT_NO_FATAL_FAILURE(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_EQ(vmo.write(block, 0, kMinfsBlockSize), ZX_OK);
ASSERT_EQ(vmo.write(block, kMinfsBlockSize, kMinfsBlockSize), ZX_OK);
// Write abm_block and ibm_block to disk.
FillWriteRequest(transaction_handler.get(), abm_block, ibm_block, vmoid.get(), request);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Reconstruct alloc_*_counts from respective bitmaps.
ASSERT_TRUE(ReconstructAllocCounts(transaction_handler.get(), &device, &info).is_ok());
// Confirm that alloc_*_counts are updated correctly.
ASSERT_EQ(info.alloc_block_count, 32u);
ASSERT_EQ(info.alloc_inode_count, 32u);
// Write all bits unset for abm_block and ibm_block.
memset(block, 0, sizeof(block));
// Write the bitmaps to disk.
ASSERT_EQ(vmo.write(block, 0, sizeof(block)), ZX_OK);
ASSERT_EQ(vmo.write(block, sizeof(block), sizeof(block)), ZX_OK);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Reconstruct alloc_*_counts from respective bitmaps.
ASSERT_TRUE(ReconstructAllocCounts(transaction_handler.get(), &device, &info).is_ok());
// Confirm the alloc_*_counts are updated correctly.
ASSERT_EQ(info.alloc_block_count, 0u);
ASSERT_EQ(info.alloc_inode_count, 0u);
memset(block, 0, sizeof(block));
// Fill up the entire bitmap sparsely with random 1 and 0.
block[0] = 0x88;
block[30] = 0xAA;
block[100] = 0x44;
block[5000] = 0x2C;
// Write the bitmaps on disk.
ASSERT_EQ(vmo.write(block, 0, sizeof(block)), ZX_OK);
ASSERT_EQ(vmo.write(block, sizeof(block), sizeof(block)), ZX_OK);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Reconstruct alloc_*_counts from respective bitmaps.
ASSERT_TRUE(ReconstructAllocCounts(transaction_handler.get(), &device, &info).is_ok());
// Confirm the alloc_*_counts are updated correctly.
ASSERT_EQ(info.alloc_block_count, 11u);
ASSERT_EQ(info.alloc_inode_count, 11u);
}
// Tests corrupt superblock and corrupt backup superblock.
TEST(SuperblockTest, TestCorruptSuperblockWithoutCorrection) {
Superblock info = {};
FillSuperblockFields(&info);
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler = std::make_unique<MockTransactionHandler>(&device);
Superblock backup;
memcpy(&backup, &info, sizeof(backup));
// Corrupt original Superblock.
info.major_version = 0xdeadbeef;
// Corrupt backup Superblock.
backup.major_version = 0x55;
// Write superblock and backup to disk.
block_fifo_request_t request[2];
zx::vmo vmo;
storage::OwnedVmoid vmoid;
ASSERT_NO_FATAL_FAILURE(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_EQ(vmo.write(&info, 0, kMinfsBlockSize), ZX_OK);
ASSERT_EQ(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize), ZX_OK);
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Try to correct the corrupted superblock.
auto info_or =
RepairSuperblock(transaction_handler.get(), &device, info.dat_block + info.block_count);
ASSERT_TRUE(info_or.is_error());
// Read back the superblock and backup superblock.
block_client::Reader reader(device);
ASSERT_EQ(reader.Read(kSuperblockStart * kMinfsBlockSize, kMinfsBlockSize, &info), ZX_OK);
ASSERT_EQ(reader.Read(kNonFvmSuperblockBackup * kMinfsBlockSize, kMinfsBlockSize, &backup),
ZX_OK);
// Confirm that the superblock is not updated by backup.
ASSERT_NE(memcmp(&info, &backup, sizeof(backup)), 0);
ASSERT_EQ(0xdeadbeef, info.major_version);
ASSERT_EQ(backup.major_version, 0x55u);
}
// Tests corrupt superblock and non-corrupt backup superblock.
TEST(SuperblockTest, TestCorruptSuperblockWithCorrection) {
Superblock info = {};
FillSuperblockFields(&info);
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler = std::make_unique<MockTransactionHandler>(&device);
Superblock backup;
memcpy(&backup, &info, sizeof(backup));
// Corrupt original Superblock.
info.major_version = 0xdeadbeef;
// Write superblock and backup to disk.
block_fifo_request_t request[2];
zx::vmo vmo;
storage::OwnedVmoid vmoid;
ASSERT_NO_FATAL_FAILURE(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_EQ(vmo.write(&info, 0, kMinfsBlockSize), ZX_OK);
ASSERT_EQ(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize), ZX_OK);
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Try to correct the corrupted superblock.
auto info_or =
RepairSuperblock(transaction_handler.get(), &device, info.dat_block + info.block_count);
ASSERT_TRUE(info_or.is_ok());
info = info_or.value();
// Read back the superblock and backup superblock.
block_client::Reader reader(device);
ASSERT_EQ(reader.Read(kSuperblockStart * kMinfsBlockSize, kMinfsBlockSize, &info), ZX_OK);
ASSERT_EQ(reader.Read(kNonFvmSuperblockBackup * kMinfsBlockSize, kMinfsBlockSize, &backup),
ZX_OK);
// Confirm that the superblock is updated by backup.
ASSERT_EQ(memcmp(&info, &backup, sizeof(backup)), 0);
}
// Tests if Repair of corrupted superblock reconstructs the bitmaps correctly.
TEST(SuperblockTest, TestRepairSuperblockWithBitmapReconstruction) {
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler = std::make_unique<MockTransactionHandler>(&device);
Superblock backup;
FillSuperblockFields(&backup);
backup.alloc_block_count = 0;
backup.alloc_inode_count = 0;
minfs::UpdateChecksum(&backup);
Superblock info = {};
// Write corrupted superblock and backup to disk.
block_fifo_request_t request[2];
zx::vmo vmo;
storage::OwnedVmoid vmoid;
ASSERT_NO_FATAL_FAILURE(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_EQ(vmo.write(&info, 0, kMinfsBlockSize), ZX_OK);
ASSERT_EQ(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize), ZX_OK);
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
uint8_t block[minfs::kMinfsBlockSize];
memset(block, 0, sizeof(block));
// Fill up the entire bitmap sparsely with random 1 and 0.
block[0] = 0xFF;
block[30] = 0xFF;
block[100] = 0xFF;
block[5000] = 0xFF;
// Write abm_block and ibm_block to disk.
ASSERT_EQ(vmo.write(block, 0, kMinfsBlockSize), ZX_OK);
ASSERT_EQ(vmo.write(block, kMinfsBlockSize, kMinfsBlockSize), ZX_OK);
FillWriteRequest(transaction_handler.get(), abm_block, ibm_block, vmoid.get(), request);
ASSERT_EQ(device.FifoTransaction(request, 2), ZX_OK);
// Try to correct the corrupted superblock.
auto info_or =
RepairSuperblock(transaction_handler.get(), &device, backup.dat_block + backup.block_count);
ASSERT_TRUE(info_or.is_ok());
info = info_or.value();
// Read back the superblock and backup superblock.
block_client::Reader reader(device);
ASSERT_EQ(reader.Read(kSuperblockStart * kMinfsBlockSize, kMinfsBlockSize, &info), ZX_OK);
ASSERT_EQ(reader.Read(kNonFvmSuperblockBackup * kMinfsBlockSize, kMinfsBlockSize, &backup),
ZX_OK);
// Confirm that alloc_*_counts are updated correctly in superblock and backup from bitmaps.
ASSERT_GT(info.alloc_block_count, 0u);
ASSERT_GT(info.alloc_inode_count, 0u);
ASSERT_GT(backup.alloc_block_count, 0u);
ASSERT_GT(backup.alloc_inode_count, 0u);
}
TEST(SuperblockTest, UnsupportedBlockSize) {
ASSERT_DEATH(
{
Superblock info = {};
info.block_size = kMinfsBlockSize - 1;
info.BlockSize();
},
_);
}
TEST(SuperblockTest, SupportedBlockSize) {
Superblock info = {};
info.block_size = kMinfsBlockSize;
info.BlockSize();
}
TEST(SuperblockTest, GetFvmFlag) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_FALSE(info.GetFlagFvm());
SetMinfsFlagFvm(info);
ASSERT_TRUE(info.GetFlagFvm());
}
TEST(SuperblockTest, InodeBitmapStartBlock) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_EQ(info.InodeBitmapStartBlock(), info.ibm_block);
SetMinfsFlagFvm(info);
ASSERT_EQ(info.InodeBitmapStartBlock(), kFVMBlockInodeBmStart);
}
TEST(SuperblockTest, DataBitmapStartBlock) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_EQ(info.DataBitmapStartBlock(), info.abm_block);
SetMinfsFlagFvm(info);
ASSERT_EQ(info.DataBitmapStartBlock(), kFVMBlockDataBmStart);
}
TEST(SuperblockTest, InodeTableStartBlock) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_EQ(info.InodeTableStartBlock(), info.ino_block);
SetMinfsFlagFvm(info);
ASSERT_EQ(info.InodeTableStartBlock(), kFVMBlockInodeStart);
}
TEST(SuperblockTest, DataStartBlock) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_EQ(info.DataStartBlock(), info.dat_block);
SetMinfsFlagFvm(info);
ASSERT_EQ(info.DataStartBlock(), kFVMBlockDataStart);
}
TEST(SuperblockTest, BackupSuperblock) {
Superblock info;
FillSuperblockFields(&info);
ASSERT_EQ(info.BackupSuperblockStart(), kNonFvmSuperblockBackup);
SetMinfsFlagFvm(info);
ASSERT_EQ(info.BackupSuperblockStart(), kFvmSuperblockBackup);
}
} // namespace
} // namespace minfs