| // 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 |