blob: 07310355090b19a159929c7557d3d3037017ab30 [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.
#include "src/storage/blobfs/blobfs.h"
#include <lib/sync/completion.h>
#include <zircon/errors.h>
#include <block-client/cpp/fake-device.h>
#include <gtest/gtest.h>
#include <storage/buffer/vmo_buffer.h>
#include "src/storage/blobfs/blob.h"
#include "src/storage/blobfs/directory.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/fsck.h"
#include "src/storage/blobfs/mkfs.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/transaction.h"
namespace blobfs {
namespace {
using ::block_client::FakeBlockDevice;
constexpr uint32_t kBlockSize = 512;
constexpr uint32_t kNumBlocks = 400 * kBlobfsBlockSize / kBlockSize;
class MockBlockDevice : public FakeBlockDevice {
public:
MockBlockDevice(uint64_t block_count, uint32_t block_size)
: FakeBlockDevice(block_count, block_size) {}
static std::unique_ptr<MockBlockDevice> CreateAndFormat(const FilesystemOptions& options,
uint64_t num_blocks) {
auto device = std::make_unique<MockBlockDevice>(num_blocks, kBlockSize);
EXPECT_EQ(FormatFilesystem(device.get(), options), ZX_OK);
return device;
}
bool saw_trim() const { return saw_trim_; }
zx_status_t FifoTransaction(block_fifo_request_t* requests, size_t count) final;
zx_status_t BlockGetInfo(fuchsia_hardware_block_BlockInfo* info) const final;
private:
bool saw_trim_ = false;
};
zx_status_t MockBlockDevice::FifoTransaction(block_fifo_request_t* requests, size_t count) {
for (size_t i = 0; i < count; i++) {
if (requests[i].opcode == BLOCKIO_TRIM) {
saw_trim_ = true;
return ZX_OK;
}
}
return FakeBlockDevice::FifoTransaction(requests, count);
}
zx_status_t MockBlockDevice::BlockGetInfo(fuchsia_hardware_block_BlockInfo* info) const {
zx_status_t status = FakeBlockDevice::BlockGetInfo(info);
if (status == ZX_OK) {
info->flags |= fuchsia_hardware_block_FLAG_TRIM_SUPPORT;
}
return status;
}
template <uint64_t oldest_revision, uint64_t num_blocks = kNumBlocks,
typename Device = MockBlockDevice>
class BlobfsTestAtRevision : public testing::Test {
public:
void SetUp() final {
FilesystemOptions options{.oldest_revision = oldest_revision};
auto device = Device::CreateAndFormat(options, num_blocks);
ASSERT_TRUE(device);
device_ = device.get();
loop_.StartThread();
ASSERT_EQ(Blobfs::Create(loop_.dispatcher(), std::move(device), GetMountOptions(),
zx::resource(), &fs_),
ZX_OK);
srand(testing::UnitTest::GetInstance()->random_seed());
}
protected:
virtual MountOptions GetMountOptions() const { return MountOptions(); }
async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
Device* device_ = nullptr;
std::unique_ptr<Blobfs> fs_;
};
using BlobfsTest = BlobfsTestAtRevision<blobfs::kBlobfsCurrentRevision>;
static_assert(blobfs::kBlobfsCurrentRevision > 0u);
using BlobfsTestAtPastRevision = BlobfsTestAtRevision<blobfs::kBlobfsCurrentRevision - 1>;
using BlobfsTestAtFutureRevision = BlobfsTestAtRevision<blobfs::kBlobfsCurrentRevision + 1>;
TEST_F(BlobfsTest, GetDevice) { ASSERT_EQ(device_, fs_->GetDevice()); }
TEST_F(BlobfsTest, BlockNumberToDevice) {
ASSERT_EQ(42 * kBlobfsBlockSize / kBlockSize, fs_->BlockNumberToDevice(42));
}
TEST_F(BlobfsTest, CleanFlag) {
// Scope all operations while the filesystem is alive to ensure they
// don't have dangling references once it is destroyed.
{
storage::VmoBuffer buffer;
ASSERT_EQ(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"), ZX_OK);
// Write the superblock with the clean flag unset on Blobfs::Create in Setup.
storage::Operation operation = {};
memcpy(buffer.Data(0), &fs_->Info(), sizeof(Superblock));
operation.type = storage::OperationType::kWrite;
operation.dev_offset = 0;
operation.length = 1;
ASSERT_EQ(fs_->RunOperation(operation, &buffer), ZX_OK);
// Read the superblock with the clean flag unset.
operation.type = storage::OperationType::kRead;
ASSERT_EQ(fs_->RunOperation(operation, &buffer), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(buffer.Data(0));
EXPECT_EQ(0u, (info->flags & kBlobFlagClean));
}
// Destroy the blobfs instance to force writing of the clean bit.
auto device = Blobfs::Destroy(std::move(fs_));
// Read the superblock, verify the clean flag is set.
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(kBlobFlagClean, (info->flags & kBlobFlagClean));
}
// Tests that the driver does *not* update the oldest_revision field when mounting a filesystem that
// has a lesser oldest_revision.
// For now, this test must be disabled because Blobfs will upgrade to the latest revision.
TEST_F(BlobfsTestAtPastRevision, DISABLED_OldestRevisionNotUpdated) {
// Destroy the blobfs instance to claim the block device.
auto device = Blobfs::Destroy(std::move(fs_));
// Read the superblock, verify the oldest revision is set to the past revision, rather than the
// current driver revision.
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(blobfs::kBlobfsCurrentRevision - 1, info->oldest_revision);
}
// Tests that the driver updates the oldest_revision field when mounting a filesystem that has a
// greater oldest_revision.
TEST_F(BlobfsTestAtFutureRevision, OldestRevisionUpdated) {
// Destroy the blobfs instance to claim the block device.
auto device = Blobfs::Destroy(std::move(fs_));
// Read the superblock, verify the oldest revision is set to the current revision, rather than the
// future revision which blobfs was formatted on.
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(blobfs::kBlobfsCurrentRevision, info->oldest_revision);
}
// Tests reading a well known location.
TEST_F(BlobfsTest, RunOperationExpectedRead) {
storage::VmoBuffer buffer;
ASSERT_EQ(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"), ZX_OK);
// Read the first block.
storage::Operation operation = {};
operation.type = storage::OperationType::kRead;
operation.length = 1;
ASSERT_EQ(fs_->RunOperation(operation, &buffer), ZX_OK);
uint64_t* data = reinterpret_cast<uint64_t*>(buffer.Data(0));
EXPECT_EQ(kBlobfsMagic0, data[0]);
EXPECT_EQ(kBlobfsMagic1, data[1]);
}
// Tests that we can read back what we write.
TEST_F(BlobfsTest, RunOperationReadWrite) {
char data[kBlobfsBlockSize] = "something to test";
storage::VmoBuffer buffer;
ASSERT_EQ(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"), ZX_OK);
memcpy(buffer.Data(0), data, kBlobfsBlockSize);
storage::Operation operation = {};
operation.type = storage::OperationType::kWrite;
operation.dev_offset = 1;
operation.length = 1;
ASSERT_EQ(fs_->RunOperation(operation, &buffer), ZX_OK);
memset(buffer.Data(0), 'a', kBlobfsBlockSize);
operation.type = storage::OperationType::kRead;
ASSERT_EQ(fs_->RunOperation(operation, &buffer), ZX_OK);
ASSERT_EQ(memcmp(data, buffer.Data(0), kBlobfsBlockSize), 0);
}
TEST_F(BlobfsTest, TrimsData) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
std::unique_ptr<BlobInfo> info;
GenerateRandomBlob("", 1024, GetBlobLayoutFormat(fs_->Info()), &info);
memmove(info->path, info->path + 1, strlen(info->path)); // Remove leading slash.
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root_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(file->Close(), ZX_OK);
EXPECT_FALSE(device_->saw_trim());
ASSERT_EQ(root_node->Unlink(info->path, false), ZX_OK);
sync_completion_t completion;
fs_->Sync([&completion](zx_status_t status) { sync_completion_signal(&completion); });
EXPECT_EQ(sync_completion_wait(&completion, zx::duration::infinite().get()), ZX_OK);
ASSERT_TRUE(device_->saw_trim());
}
TEST_F(BlobfsTest, GetNodeWithAnInvalidNodeIndexIsAnError) {
uint32_t invalid_node_index = kMaxNodeId - 1;
auto node = fs_->GetNode(invalid_node_index);
EXPECT_EQ(node.status_value(), ZX_ERR_INVALID_ARGS);
}
TEST_F(BlobfsTest, FreeInodeWithAnInvalidNodeIndexIsAnError) {
BlobTransaction transaction;
uint32_t invalid_node_index = kMaxNodeId - 1;
EXPECT_EQ(fs_->FreeInode(invalid_node_index, transaction), ZX_ERR_INVALID_ARGS);
}
TEST_F(BlobfsTest, BlockIteratorByNodeIndexWithAnInvalidNodeIndexIsAnError) {
uint32_t invalid_node_index = kMaxNodeId - 1;
auto block_iterator = fs_->BlockIteratorByNodeIndex(invalid_node_index);
EXPECT_EQ(block_iterator.status_value(), ZX_ERR_INVALID_ARGS);
}
class MockFvmDevice : public block_client::FakeFVMBlockDevice {
public:
using FakeFVMBlockDevice::FakeFVMBlockDevice;
static std::unique_ptr<MockFvmDevice> CreateAndFormat(const FilesystemOptions& options,
uint64_t num_blocks) {
auto device = std::make_unique<MockFvmDevice>(num_blocks, kBlockSize, /*slice_size=*/32768,
/*slice_capacity=*/500);
EXPECT_EQ(FormatFilesystem(device.get(), options), ZX_OK);
return device;
}
};
using BlobfsTestWithOldRevisionAndFvmDevice =
BlobfsTestAtRevision<blobfs::kBlobfsRevisionBackupSuperblock - 1, kNumBlocks, MockFvmDevice>;
TEST_F(BlobfsTestWithOldRevisionAndFvmDevice, OldInstanceTriggersWriteToBackupSuperblock) {
ASSERT_TRUE(fs_->Info().flags & kBlobFlagFVM);
ASSERT_EQ(fs_->Info().oldest_revision, kBlobfsCurrentRevision);
auto device = Blobfs::Destroy(std::move(fs_));
ASSERT_EQ(Fsck(std::move(device), MountOptions{.writability = Writability::ReadOnlyDisk}), ZX_OK);
}
using BlobfsTestWithLargeDevice =
BlobfsTestAtRevision<blobfs::kBlobfsCurrentRevision,
/*num_blocks=*/2560 * kBlobfsBlockSize / kBlockSize>;
TEST_F(BlobfsTestWithLargeDevice, WritingBlobLargerThanWritebackCapacitySucceeds) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
std::unique_ptr<BlobInfo> info;
GenerateRealisticBlob("", (fs_->WriteBufferBlockCount() + 1) * kBlobfsBlockSize,
GetBlobLayoutFormat(fs_->Info()), &info);
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root_node->Create(info->path + 1, 0, &file), ZX_OK);
auto blob = fbl::RefPtr<Blob>::Downcast(std::move(file));
// Force no compression so that we have finer control over the size.
EXPECT_EQ(blob->PrepareWrite(info->size_data, /*compress=*/false), ZX_OK);
size_t actual;
// If this starts to fail with an ERR_NO_SPACE error it could be because WriteBufferBlockCount()
// has changed and is now returning something too big for the device we're using in this test.
EXPECT_EQ(blob->Write(info->data.get(), info->size_data, 0, &actual), ZX_OK);
sync_completion_t sync;
blob->Sync([&](zx_status_t status) {
EXPECT_EQ(status, ZX_OK);
sync_completion_signal(&sync);
});
sync_completion_wait(&sync, ZX_TIME_INFINITE);
EXPECT_EQ(blob->Close(), ZX_OK);
blob.reset();
ASSERT_EQ(root_node->Lookup(info->path + 1, &file), ZX_OK);
auto buffer = std::make_unique<uint8_t[]>(info->size_data);
EXPECT_EQ(file->Read(buffer.get(), info->size_data, 0, &actual), ZX_OK);
EXPECT_EQ(memcmp(buffer.get(), info->data.get(), info->size_data), 0);
}
#ifndef NDEBUG
class FsckAtEndOfEveryTransactionTest : public BlobfsTest {
protected:
MountOptions GetMountOptions() const override {
MountOptions options = BlobfsTest::GetMountOptions();
options.fsck_at_end_of_every_transaction = true;
return options;
}
};
TEST_F(FsckAtEndOfEveryTransactionTest, FsckAtEndOfEveryTransaction) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
fs::Vnode* root_node = root.get();
std::unique_ptr<BlobInfo> info;
GenerateRealisticBlob("", 500123, GetBlobLayoutFormat(fs_->Info()), &info);
{
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root_node->Create(info->path + 1, 0, &file), ZX_OK);
EXPECT_EQ(file->Truncate(info->size_data), ZX_OK);
size_t actual;
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);
}
EXPECT_EQ(root_node->Unlink(info->path + 1, false), ZX_OK);
}
#endif // !defined(NDEBUG)
} // namespace
} // namespace blobfs