blob: 6f7d46e33c114ee5f02fd9ab5a99ee4fd9ff2ccc [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/blobfs/blobfs-checker.h"
#include <lib/sync/completion.h>
#include <block-client/cpp/fake-device.h>
#include <gtest/gtest.h>
#include "src/storage/blobfs/blob.h"
#include "src/storage/blobfs/blobfs.h"
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/mkfs.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/test/unit/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 testing::Test {
public:
void SetUp() override {
auto device = std::make_unique<FakeBlockDevice>(kNumBlocks, kBlockSize);
ASSERT_TRUE(device);
ASSERT_EQ(FormatFilesystem(device.get(), FilesystemOptions{}), ZX_OK);
loop_.StartThread();
ASSERT_EQ(
Blobfs::Create(loop_.dispatcher(), std::move(device), MountOptions(), zx::resource(), &fs_),
ZX_OK);
srand(testing::UnitTest::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());
}
// AddRandomBlob creates and writes a random blob to the file system as a child
// of the provided Vnode. Optionally returns the block the blob starts at if block_out is
// provided, and the size of the blob if size_out is provided.
void AddRandomBlob(fs::Vnode* node, uint64_t* block_out = nullptr, uint64_t* size_out = nullptr) {
std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 1024);
memmove(info->path, info->path + 1, strlen(info->path)); // Remove leading slash.
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(node->Create(info->path, 0, &file), ZX_OK);
size_t actual;
EXPECT_EQ(file->Truncate(info->size_data), ZX_OK);
EXPECT_EQ(file->Write(info->data.get(), info->size_data, 0, &actual), ZX_OK);
EXPECT_EQ(actual, info->size_data);
EXPECT_EQ(file->Close(), ZX_OK);
if (block_out) {
auto blob = fbl::RefPtr<Blob>::Downcast(file);
// Get the block that contains the blob.
*block_out = fs_->GetNode(blob->Ino())->extents[0].Start() + DataStartBlock(fs_->Info());
}
if (size_out) {
*size_out = info->size_data;
}
}
// Creates and writes a corrupt blob to the file system as a child of the provided Vnode.
void AddCorruptBlob(fs::Vnode* node) {
uint64_t block, size;
AddRandomBlob(node, &block, &size);
// Unmount.
std::unique_ptr<block_client::BlockDevice> device = Blobfs::Destroy(std::move(fs_));
// Read the block that contains the blob.
storage::VmoBuffer buffer;
ASSERT_EQ(buffer.Initialize(device.get(), 1, kBlobfsBlockSize, "test_buffer"), ZX_OK);
block_fifo_request_t request = {
.opcode = BLOCKIO_READ,
.vmoid = buffer.vmoid(),
.length = kBlobfsBlockSize / kBlockSize,
.vmo_offset = 0,
.dev_offset = block * kBlobfsBlockSize / kBlockSize,
};
ASSERT_EQ(device->FifoTransaction(&request, 1), ZX_OK);
// Flip a random bit of the data.
auto blob_data = static_cast<uint8_t*>(buffer.Data(0));
size_t rand_index = rand() % size;
uint8_t old_val = blob_data[rand_index];
while ((blob_data[rand_index] = static_cast<uint8_t>(rand())) == old_val) {
}
// Write the block back.
request.opcode = BLOCKIO_WRITE;
ASSERT_EQ(device->FifoTransaction(&request, 1), ZX_OK);
// Remount.
ASSERT_EQ(
Blobfs::Create(loop_.dispatcher(), std::move(device), MountOptions(), zx::resource(), &fs_),
ZX_OK);
}
std::unique_ptr<Blobfs> get_fs_unique() { return std::move(fs_); }
Blobfs* get_fs() { return fs_.get(); }
protected:
bool enable_paging = false;
private:
async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
std::unique_ptr<Blobfs> fs_;
};
class BlobfsCheckerPagedTest : public BlobfsCheckerTest {
public:
void SetUp() override {
enable_paging = true;
BlobfsCheckerTest::SetUp();
}
};
void RunTestEmpty(BlobfsCheckerTest* t) {
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_OK);
}
TEST_F(BlobfsCheckerTest, TestEmpty) { RunTestEmpty(this); }
TEST_F(BlobfsCheckerPagedTest, TestEmpty) { RunTestEmpty(this); }
void RunTestNonEmpty(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
for (unsigned i = 0; i < 3; i++) {
t->AddRandomBlob(root_node);
}
EXPECT_EQ(t->Sync(), ZX_OK);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_OK);
}
TEST_F(BlobfsCheckerTest, TestNonEmpty) { RunTestNonEmpty(this); }
TEST_F(BlobfsCheckerPagedTest, TestNonEmpty) { RunTestNonEmpty(this); }
void RunTestInodeWithUnallocatedBlock(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
for (unsigned i = 0; i < 3; i++) {
t->AddRandomBlob(root_node);
}
EXPECT_EQ(t->Sync(), ZX_OK);
Extent e(1, 1);
t->get_fs()->GetAllocator()->FreeBlocks(e);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestInodeWithUnallocatedBlock) { RunTestInodeWithUnallocatedBlock(this); }
TEST_F(BlobfsCheckerPagedTest, TestInodeWithUnallocatedBlock) {
RunTestInodeWithUnallocatedBlock(this);
}
// TODO(https://bugs.fuchsia.dev/45924): determine why running this test on an
// empty blobfs fails on ASAN QEMU bot.
void RunTestAllocatedBlockCountTooHigh(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
t->AddRandomBlob(root.get());
EXPECT_EQ(t->Sync(), ZX_OK);
Superblock superblock = t->get_fs()->Info();
superblock.alloc_block_count++;
ASSERT_EQ(t->UpdateSuperblock(superblock), ZX_OK);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestAllocatedBlockCountTooHigh) {
RunTestAllocatedBlockCountTooHigh(this);
}
TEST_F(BlobfsCheckerPagedTest, TestAllocatedBlockCountTooHigh) {
RunTestAllocatedBlockCountTooHigh(this);
}
void RunTestAllocatedBlockCountTooLow(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
for (unsigned i = 0; i < 3; i++) {
t->AddRandomBlob(root_node);
}
EXPECT_EQ(t->Sync(), ZX_OK);
Superblock superblock = t->get_fs()->Info();
superblock.alloc_block_count = 2;
t->UpdateSuperblock(superblock);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestAllocatedBlockCountTooLow) { RunTestAllocatedBlockCountTooLow(this); }
TEST_F(BlobfsCheckerPagedTest, TestAllocatedBlockCountTooLow) {
RunTestAllocatedBlockCountTooLow(this);
}
void RunTestFewerThanMinimumBlocksAllocated(BlobfsCheckerTest* t) {
Extent e(0, 1);
t->get_fs()->GetAllocator()->FreeBlocks(e);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestFewerThanMinimumBlocksAllocated) {
RunTestFewerThanMinimumBlocksAllocated(this);
}
TEST_F(BlobfsCheckerPagedTest, TestFewerThanMinimumBlocksAllocated) {
RunTestFewerThanMinimumBlocksAllocated(this);
}
void RunTestAllocatedInodeCountTooHigh(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
t->AddRandomBlob(root.get());
EXPECT_EQ(t->Sync(), ZX_OK);
Superblock superblock = t->get_fs()->Info();
superblock.alloc_inode_count++;
t->UpdateSuperblock(superblock);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestAllocatedInodeCountTooHigh) {
RunTestAllocatedInodeCountTooHigh(this);
}
TEST_F(BlobfsCheckerPagedTest, TestAllocatedInodeCountTooHigh) {
RunTestAllocatedInodeCountTooHigh(this);
}
void RunTestAllocatedInodeCountTooLow(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
for (unsigned i = 0; i < 3; i++) {
t->AddRandomBlob(root_node);
}
EXPECT_EQ(t->Sync(), ZX_OK);
Superblock superblock = t->get_fs()->Info();
superblock.alloc_inode_count = 2;
t->UpdateSuperblock(superblock);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestAllocatedInodeCountTooLow) { RunTestAllocatedInodeCountTooLow(this); }
TEST_F(BlobfsCheckerPagedTest, TestAllocatedInodeCountTooLow) {
RunTestAllocatedInodeCountTooLow(this);
}
void RunTestCorruptBlobs(BlobfsCheckerTest* t) {
fbl::RefPtr<fs::Vnode> root;
for (unsigned i = 0; i < 5; i++) {
// Need to get the root node inside the loop because adding a corrupt blob causes us to change
// the Blobfs instance. The only feasible way right now to corrupt a blob *after* it has been
// written out involves unmounting and then remounting the file system.
ASSERT_EQ(t->get_fs()->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
if (i % 2 == 0) {
t->AddRandomBlob(root_node);
} else {
t->AddCorruptBlob(root_node);
}
}
EXPECT_EQ(t->Sync(), ZX_OK);
BlobfsChecker checker(t->get_fs_unique());
ASSERT_EQ(checker.Check(), ZX_ERR_BAD_STATE);
}
TEST_F(BlobfsCheckerTest, TestCorruptBlobs) { RunTestCorruptBlobs(this); }
TEST_F(BlobfsCheckerPagedTest, TestCorruptBlobs) { RunTestCorruptBlobs(this); }
} // namespace
} // namespace blobfs