blob: 5b1608dffa4580f75eb6a90b6f73fc1f9473b784 [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 "blobfs.h"
#include <lib/sync/completion.h>
#include <blobfs/format.h>
#include <blobfs/mkfs.h>
#include <block-client/cpp/fake-device.h>
#include <storage/buffer/vmo_buffer.h>
#include <zxtest/zxtest.h>
#include "directory.h"
#include "test/blob_utils.h"
namespace blobfs {
namespace {
using block_client::FakeBlockDevice;
class MockBlockDevice : public FakeBlockDevice {
public:
MockBlockDevice(uint64_t block_count, uint32_t block_size)
: FakeBlockDevice(block_count, block_size) {}
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;
}
constexpr uint32_t kBlockSize = 512;
constexpr uint32_t kNumBlocks = 400 * kBlobfsBlockSize / kBlockSize;
std::unique_ptr<MockBlockDevice> CreateAndFormatDevice() {
auto device = std::make_unique<MockBlockDevice>(kNumBlocks, kBlockSize);
EXPECT_OK(FormatFilesystem(device.get()));
if (CURRENT_TEST_HAS_FAILURES()) {
return nullptr;
}
return device;
}
class BlobfsTest : public zxtest::Test {
public:
void SetUp() final {
MountOptions options;
std::unique_ptr<MockBlockDevice> device = CreateAndFormatDevice();
ASSERT_TRUE(device);
device_ = device.get();
loop_.StartThread();
ASSERT_OK(
Blobfs::Create(loop_.dispatcher(), std::move(device), &options, zx::resource(), &fs_));
srand(zxtest::Runner::GetInstance()->random_seed());
}
protected:
async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
MockBlockDevice* device_ = nullptr;
std::unique_ptr<Blobfs> fs_;
};
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_OK(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"));
// 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_OK(fs_->RunOperation(operation, &buffer));
// Read the superblock with the clean flag unset.
operation.type = storage::OperationType::kRead;
ASSERT_OK(fs_->RunOperation(operation, &buffer));
Superblock* info = reinterpret_cast<Superblock*>(buffer.Data(0));
EXPECT_EQ(0, (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_OK(device->ReadBlock(0, kBlobfsBlockSize, &block));
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(kBlobFlagClean, (info->flags & kBlobFlagClean));
}
// Tests reading a well known location.
TEST_F(BlobfsTest, RunOperationExpectedRead) {
storage::VmoBuffer buffer;
ASSERT_OK(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"));
// Read the first block.
storage::Operation operation = {};
operation.type = storage::OperationType::kRead;
operation.length = 1;
ASSERT_OK(fs_->RunOperation(operation, &buffer));
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_OK(buffer.Initialize(fs_.get(), 1, kBlobfsBlockSize, "source"));
memcpy(buffer.Data(0), data, kBlobfsBlockSize);
storage::Operation operation = {};
operation.type = storage::OperationType::kWrite;
operation.dev_offset = 1;
operation.length = 1;
ASSERT_OK(fs_->RunOperation(operation, &buffer));
memset(buffer.Data(0), 'a', kBlobfsBlockSize);
operation.type = storage::OperationType::kRead;
ASSERT_OK(fs_->RunOperation(operation, &buffer));
ASSERT_BYTES_EQ(data, buffer.Data(0), kBlobfsBlockSize);
}
TEST_F(BlobfsTest, TrimsData) {
fbl::RefPtr<fs::Vnode> root;
ASSERT_OK(fs_->OpenRootNode(&root));
fs::Vnode* root_node = root.get();
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(root_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_OK(file->Close());
EXPECT_FALSE(device_->saw_trim());
ASSERT_OK(root_node->Unlink(info->path, false));
sync_completion_t completion;
fs_->Sync([&completion](zx_status_t status) { sync_completion_signal(&completion); });
EXPECT_OK(sync_completion_wait(&completion, zx::duration::infinite().get()));
ASSERT_TRUE(device_->saw_trim());
}
} // namespace
} // namespace blobfs