blob: 264829de30f598d39ae5e29d182520f347981141 [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 <block-client/cpp/fake-device.h>
#include <minfs/fsck.h>
#include <minfs/superblock.h>
#include <zxtest/zxtest.h>
#include "minfs/format.h"
#include "minfs_private.h"
namespace minfs {
namespace {
using block_client::FakeBlockDevice;
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:
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_BlockInfo info = {};
ASSERT_OK(device->BlockGetInfo(&info));
ASSERT_OK(zx::vmo::create(blocks * info.block_size, 0, vmo));
ASSERT_OK(device->BlockAttachVmo(*vmo, &vmoid->GetReference(device)));
}
void FillSuperblockFields(Superblock* info) {
constexpr uint32_t kDefaultAllocCount = 2;
info->magic0 = kMinfsMagic0;
info->magic1 = kMinfsMagic1;
info->version_major = kMinfsMajorVersion;
info->version_minor = kMinfsMinorVersion;
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 = 1;
info->inode_count = 1;
info->alloc_block_count = kDefaultAllocCount;
info->alloc_inode_count = kDefaultAllocCount;
info->generation_count = 0;
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].opcode = BLOCKIO_WRITE;
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].opcode = BLOCKIO_WRITE;
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::unique_ptr<MockTransactionHandler>(new 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_FAILURES(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_OK(vmo.write(block, 0, kMinfsBlockSize));
ASSERT_OK(vmo.write(block, kMinfsBlockSize, kMinfsBlockSize));
// Write abm_block and ibm_block to disk.
FillWriteRequest(transaction_handler.get(), abm_block, ibm_block, vmoid.get(), request);
ASSERT_OK(device.FifoTransaction(request, 2));
// Reconstruct alloc_*_counts from respective bitmaps.
zx_status_t status = ReconstructAllocCounts(transaction_handler.get(), &device, &info);
ASSERT_EQ(status, ZX_OK);
// Confirm that alloc_*_counts are updated correctly.
ASSERT_EQ(32, info.alloc_block_count);
ASSERT_EQ(32, info.alloc_inode_count);
// Write all bits unset for abm_block and ibm_block.
memset(block, 0, sizeof(block));
// Write the bitmaps to disk.
ASSERT_OK(vmo.write(block, 0, sizeof(block)));
ASSERT_OK(vmo.write(block, sizeof(block), sizeof(block)));
ASSERT_OK(device.FifoTransaction(request, 2));
// Reconstruct alloc_*_counts from respective bitmaps.
status = ReconstructAllocCounts(transaction_handler.get(), &device, &info);
ASSERT_EQ(status, ZX_OK);
// Confirm the alloc_*_counts are updated correctly.
ASSERT_EQ(0, info.alloc_block_count);
ASSERT_EQ(0, info.alloc_inode_count);
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_OK(vmo.write(block, 0, sizeof(block)));
ASSERT_OK(vmo.write(block, sizeof(block), sizeof(block)));
ASSERT_OK(device.FifoTransaction(request, 2));
// Reconstruct alloc_*_counts from respective bitmaps.
status = ReconstructAllocCounts(transaction_handler.get(), &device, &info);
ASSERT_EQ(status, ZX_OK);
// Confirm the alloc_*_counts are updated correctly.
ASSERT_EQ(11, info.alloc_block_count);
ASSERT_EQ(11, info.alloc_inode_count);
}
// Tests corrupt superblock and corrupt backup superblock.
TEST(SuperblockTest, TestCorruptSuperblockWithoutCorrection) {
Superblock info = {};
FillSuperblockFields(&info);
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler =
std::unique_ptr<MockTransactionHandler>(new MockTransactionHandler(&device));
Superblock backup;
memcpy(&backup, &info, sizeof(backup));
// Corrupt original Superblock.
info.version_major = 0xdeadbeef;
info.version_minor = 0xdeadbeef;
// Corrupt backup Superblock.
backup.version_major = 0x55;
backup.version_minor = 0x55;
// Write superblock and backup to disk.
block_fifo_request_t request[2];
zx::vmo vmo;
storage::OwnedVmoid vmoid;
ASSERT_NO_FAILURES(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_OK(vmo.write(&info, 0, kMinfsBlockSize));
ASSERT_OK(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize));
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_OK(device.FifoTransaction(request, 2));
// Try to correct the corrupted superblock.
zx_status_t status = RepairSuperblock(transaction_handler.get(), &device,
info.dat_block + info.block_count, &info);
ASSERT_NE(status, ZX_OK);
// Read back the superblock and backup superblock.
ASSERT_OK(device.ReadBlock(kSuperblockStart, kMinfsBlockSize, &info));
ASSERT_OK(device.ReadBlock(kNonFvmSuperblockBackup, kMinfsBlockSize, &backup));
// Confirm that the superblock is not updated by backup.
ASSERT_BYTES_NE(&info, &backup, sizeof(backup));
ASSERT_EQ(0xdeadbeef, info.version_major);
ASSERT_EQ(0xdeadbeef, info.version_minor);
ASSERT_EQ(0x55, backup.version_major);
ASSERT_EQ(0x55, backup.version_minor);
}
// Tests corrupt superblock and non-corrupt backup superblock.
TEST(SuperblockTest, TestCorruptSuperblockWithCorrection) {
Superblock info = {};
FillSuperblockFields(&info);
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler =
std::unique_ptr<MockTransactionHandler>(new MockTransactionHandler(&device));
Superblock backup;
memcpy(&backup, &info, sizeof(backup));
// Corrupt original Superblock.
info.version_major = 0xdeadbeef;
info.version_minor = 0xdeadbeef;
// Write superblock and backup to disk.
block_fifo_request_t request[2];
zx::vmo vmo;
storage::OwnedVmoid vmoid;
ASSERT_NO_FAILURES(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_OK(vmo.write(&info, 0, kMinfsBlockSize));
ASSERT_OK(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize));
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_OK(device.FifoTransaction(request, 2));
// Try to correct the corrupted superblock.
zx_status_t status = RepairSuperblock(transaction_handler.get(), &device,
info.dat_block + info.block_count, &info);
ASSERT_EQ(status, ZX_OK);
// Read back the superblock and backup superblock.
ASSERT_OK(device.ReadBlock(kSuperblockStart, kMinfsBlockSize, &info));
ASSERT_OK(device.ReadBlock(kNonFvmSuperblockBackup, kMinfsBlockSize, &backup));
// Confirm that the superblock is updated by backup.
ASSERT_BYTES_EQ(&info, &backup, sizeof(backup));
}
// Tests if Repair of corrupted superblock reconstructs the bitmaps correctly.
TEST(SuperblockTest, TestRepairSuperblockWithBitmapReconstruction) {
FakeBlockDevice device = FakeBlockDevice(100, kMinfsBlockSize);
auto transaction_handler =
std::unique_ptr<MockTransactionHandler>(new 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_FAILURES(CreateAndRegisterVmo(&device, 2, &vmo, &vmoid));
ASSERT_OK(vmo.write(&info, 0, kMinfsBlockSize));
ASSERT_OK(vmo.write(&backup, kMinfsBlockSize, kMinfsBlockSize));
FillWriteRequest(transaction_handler.get(), kSuperblockStart, kNonFvmSuperblockBackup,
vmoid.get(), request);
ASSERT_OK(device.FifoTransaction(request, 2));
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_OK(vmo.write(block, 0, kMinfsBlockSize));
ASSERT_OK(vmo.write(block, kMinfsBlockSize, kMinfsBlockSize));
FillWriteRequest(transaction_handler.get(), abm_block, ibm_block, vmoid.get(), request);
ASSERT_OK(device.FifoTransaction(request, 2));
// Try to correct the corrupted superblock.
zx_status_t status = RepairSuperblock(transaction_handler.get(), &device,
backup.dat_block + backup.block_count, &info);
ASSERT_EQ(status, ZX_OK);
// Read back the superblock and backup superblock.
ASSERT_OK(device.ReadBlock(kSuperblockStart, kMinfsBlockSize, &info));
ASSERT_OK(device.ReadBlock(kNonFvmSuperblockBackup, kMinfsBlockSize, &backup));
// Confirm that alloc_*_counts are updated correctly in superblock and backup from bitmaps.
ASSERT_GT(info.alloc_block_count, 0);
ASSERT_GT(info.alloc_inode_count, 0);
ASSERT_GT(backup.alloc_block_count, 0);
ASSERT_GT(backup.alloc_inode_count, 0);
}
TEST(SuperblockTest, UnsupportedBlockSize) {
ASSERT_DEATH(([]() {
Superblock info = {};
info.block_size = kMinfsBlockSize - 1;
info.BlockSize();
}));
}
TEST(SuperblockTest, SupportedBlockSize) {
ASSERT_NO_DEATH(([]() {
Superblock info = {};
info.block_size = kMinfsBlockSize;
info.BlockSize();
}));
}
} // namespace
} // namespace minfs