| // 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 "blobfs-checker.h" |
| |
| #include <lib/sync/completion.h> |
| |
| #include <blobfs/common.h> |
| #include <blobfs/format.h> |
| #include <blobfs/mkfs.h> |
| #include <block-client/cpp/fake-device.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "blobfs.h" |
| #include "test/blob_utils.h" |
| #include "utils.h" |
| |
| namespace blobfs { |
| namespace { |
| |
| using block_client::FakeBlockDevice; |
| |
| constexpr uint32_t kBlockSize = 512; |
| constexpr uint32_t kNumBlocks = 400 * kBlobfsBlockSize / kBlockSize; |
| |
| // Expose access to ReloadSuperblock(). This allows tests to alter the |
| // Superblock on disk and force blobfs to reload it before running a check. |
| class TestBlobfs : public Blobfs { |
| public: |
| zx_status_t Reload() { return ReloadSuperblock(); } |
| }; |
| |
| class BlobfsCheckerTest : public zxtest::Test { |
| public: |
| void SetUp() final { |
| auto device = std::make_unique<FakeBlockDevice>(kNumBlocks, kBlockSize); |
| ASSERT_TRUE(device); |
| ASSERT_OK(FormatFilesystem(device.get())); |
| loop_.StartThread(); |
| |
| MountOptions options; |
| ASSERT_OK( |
| Blobfs::Create(loop_.dispatcher(), std::move(device), &options, zx::resource(), &fs_)); |
| srand(zxtest::Runner::GetInstance()->random_seed()); |
| } |
| |
| // UpdateSuperblock writes the provided superblock to the block device and |
| // forces blobfs to reload immediately. |
| zx_status_t UpdateSuperblock(Superblock& superblock) { |
| size_t superblock_size = kBlobfsBlockSize * SuperblockBlocks(superblock); |
| DeviceBlockWrite(fs_->Device(), &superblock, superblock_size, kSuperblockOffset); |
| return static_cast<TestBlobfs*>(fs_.get())->Reload(); |
| } |
| |
| // Sync waits for blobfs to sync with the underlying block device. |
| zx_status_t Sync() { |
| sync_completion_t completion; |
| fs_->Sync([&completion](zx_status_t status) { sync_completion_signal(&completion); }); |
| return sync_completion_wait(&completion, zx::duration::infinite().get()); |
| } |
| |
| protected: |
| async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| std::unique_ptr<Blobfs> fs_; |
| }; |
| |
| // AddRandomBlob creates and writes a random blob to the file system as a child |
| // of the provided Vnode. |
| void AddRandomBlob(fs::Vnode* node) { |
| std::unique_ptr<BlobInfo> info; |
| ASSERT_NO_FAILURES(GenerateRandomBlob("", 1024, &info)); |
| memmove(info->path, info->path + 1, strlen(info->path)); // Remove leading slash. |
| |
| fbl::RefPtr<fs::Vnode> file; |
| ASSERT_OK(node->Create(&file, info->path, 0)); |
| |
| size_t actual; |
| EXPECT_OK(file->Truncate(info->size_data)); |
| EXPECT_OK(file->Write(info->data.get(), info->size_data, 0, &actual)); |
| EXPECT_EQ(actual, info->size_data); |
| EXPECT_OK(file->Close()); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestEmpty) { |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_OK(checker.Check()); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestNonEmpty) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| fs::Vnode* root_node = root.get(); |
| for (unsigned i = 0; i < 3; i++) { |
| AddRandomBlob(root_node); |
| } |
| EXPECT_OK(Sync()); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_OK(checker.Check()); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestInodeWithUnallocatedBlock) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| fs::Vnode* root_node = root.get(); |
| for (unsigned i = 0; i < 3; i++) { |
| AddRandomBlob(root_node); |
| } |
| EXPECT_OK(Sync()); |
| |
| Extent e(1, 1); |
| fs_->GetAllocator()->FreeBlocks(e); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| // TODO(https://bugs.fuchsia.dev/45924): determine why running this test on an |
| // empty blobfs fails on ASAN QEMU bot. |
| TEST_F(BlobfsCheckerTest, TestAllocatedBlockCountTooHigh) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| AddRandomBlob(root.get()); |
| EXPECT_OK(Sync()); |
| |
| Superblock superblock = fs_->Info(); |
| superblock.alloc_block_count++; |
| ASSERT_OK(UpdateSuperblock(superblock)); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestAllocatedBlockCountTooLow) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| fs::Vnode* root_node = root.get(); |
| for (unsigned i = 0; i < 3; i++) { |
| AddRandomBlob(root_node); |
| } |
| EXPECT_OK(Sync()); |
| |
| Superblock superblock = fs_->Info(); |
| superblock.alloc_block_count = 2; |
| UpdateSuperblock(superblock); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestFewerThanMinimumBlocksAllocated) { |
| Extent e(0, 1); |
| fs_->GetAllocator()->FreeBlocks(e); |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestAllocatedInodeCountTooHigh) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| AddRandomBlob(root.get()); |
| EXPECT_OK(Sync()); |
| |
| Superblock superblock = fs_->Info(); |
| superblock.alloc_inode_count++; |
| UpdateSuperblock(superblock); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(BlobfsCheckerTest, TestAllocatedInodeCountTooLow) { |
| fbl::RefPtr<fs::Vnode> root; |
| ASSERT_OK(fs_->OpenRootNode(&root)); |
| fs::Vnode* root_node = root.get(); |
| for (unsigned i = 0; i < 3; i++) { |
| AddRandomBlob(root_node); |
| } |
| EXPECT_OK(Sync()); |
| |
| Superblock superblock = fs_->Info(); |
| superblock.alloc_inode_count = 2; |
| UpdateSuperblock(superblock); |
| |
| BlobfsChecker checker(std::move(fs_)); |
| ASSERT_STATUS(checker.Check(), ZX_ERR_BAD_STATE); |
| } |
| |
| } // namespace |
| } // namespace blobfs |