blob: 575d8b80a4ee69320fd7cb5e988be87d19f25d26 [file] [log] [blame]
// Copyright 2025 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/fvm/fvm_test_instance.h"
#include <fidl/fuchsia.driver.test/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/fd.h>
#include <bind/fuchsia/platform/cpp/bind.h>
#include <zxtest/zxtest.h>
#include "src/storage/lib/block_server/fake_server.h"
#include "src/storage/lib/fs_management/cpp/fvm.h"
#include "src/storage/lib/fs_management/cpp/mount.h"
namespace fvm {
class DriverBlockConnector : public BlockConnector {
public:
explicit DriverBlockConnector(fidl::ClientEnd<fuchsia_device::Controller> controller)
: controller_(std::move(controller)) {
zx::result partition_server = fidl::CreateEndpoints(&partition_);
EXPECT_OK(partition_server);
EXPECT_TRUE(
fidl::WireCall(controller_)->ConnectToDeviceFidl(partition_server->TakeChannel()).ok());
}
~DriverBlockConnector() override = default;
fidl::ClientEnd<fuchsia_hardware_block::Block> connect_block() const override {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_block::Block>();
EXPECT_OK(endpoints);
auto [block_client, server] = std::move(endpoints.value());
EXPECT_TRUE(fidl::WireCall(controller_)->ConnectToDeviceFidl(server.TakeChannel()).ok());
return std::move(block_client);
}
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> as_block() const override {
return fidl::UnownedClientEnd<fuchsia_hardware_block::Block>(partition_.channel().borrow());
}
fidl::UnownedClientEnd<fuchsia_hardware_block_volume::Volume> as_volume() const override {
return fidl::UnownedClientEnd<fuchsia_hardware_block_volume::Volume>(
partition_.channel().borrow());
}
static zx::result<std::unique_ptr<BlockConnector>> Create(
fidl::ClientEnd<fuchsia_device::Controller> controller) {
return zx::ok(std::make_unique<DriverBlockConnector>(std::move(controller)));
}
private:
fidl::ClientEnd<fuchsia_device::Controller> controller_;
fidl::ClientEnd<fuchsia_hardware_block_partition::Partition> partition_;
};
void DriverFvmInstance::SetUp() {
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
loop_->StartThread();
auto realm_builder = component_testing::RealmBuilder::Create();
driver_test_realm::Setup(realm_builder);
realm_ = std::make_unique<component_testing::RealmRoot>(realm_builder.Build(loop_->dispatcher()));
zx::result dtr = realm_->component().Connect<fuchsia_driver_test::Realm>();
ASSERT_OK(dtr);
fidl::Arena arena;
auto args_builder = fuchsia_driver_test::wire::RealmArgs::Builder(arena);
args_builder.root_driver("fuchsia-boot:///platform-bus#meta/platform-bus.cm");
args_builder.software_devices(std::vector{
fuchsia_driver_test::wire::SoftwareDevice{
.device_name = "ram-disk",
.device_id = bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_RAM_DISK,
},
});
fidl::WireResult result = fidl::WireCall(*dtr)->Start(args_builder.Build());
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().is_ok());
auto [devfs_client, server] = fidl::Endpoints<fuchsia_io::Node>::Create();
fidl::UnownedClientEnd<fuchsia_io::Directory> exposed(
realm_->component().exposed().unowned_channel());
ASSERT_OK(fidl::WireCall(exposed)
->Open("dev-topological", fuchsia_io::kPermReadable, {}, server.TakeChannel())
.status());
ASSERT_OK(
fdio_fd_create(devfs_client.TakeChannel().release(), devfs_root_.reset_and_get_address()));
ASSERT_OK(
device_watcher::RecursiveWaitForFile(devfs_root().get(), "sys/platform/ram-disk/ramctl"));
}
void DriverFvmInstance::TearDown() { ramdisk_.Reset(); }
void DriverFvmInstance::CreateRamdisk(uint64_t block_size, uint64_t block_count) {
if (ramdisk_.is_valid()) {
ramdisk_.Reset();
// We assume if the caller didn't destroy the ramdisk itself it wants a completely new disk.
vmo_ = {};
}
if (!vmo_) {
ASSERT_OK(zx::vmo::create(block_size * block_count, 0, &vmo_));
}
zx::vmo duplicate_vmo;
ASSERT_OK(vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_vmo));
zx::result ramdisk = ramdevice_client::Ramdisk::CreateLegacyWithVmo(
std::move(duplicate_vmo), block_size, devfs_root_.get());
ASSERT_OK(ramdisk.status_value());
ramdisk_ = std::move(*ramdisk);
}
void DriverFvmInstance::CreateFvm(uint64_t block_size, uint64_t block_count, uint64_t slice_size) {
CreateRamdisk(block_size, block_count);
ASSERT_OK(fs_management::FvmInitPreallocated(GetRamdiskPartition(), block_count * block_size,
block_count * block_size, slice_size));
StartFvm();
}
void DriverFvmInstance::StartFvm() {
auto resp = fidl::WireCall(GetRamdiskControllerInterface())->Bind(kFvmDriverLib);
ASSERT_OK(resp.status());
if (!resp->is_ok()) {
ASSERT_OK(resp->error_value());
}
ASSERT_OK(device_watcher::RecursiveWaitForFile(devfs_root().get(), GetFvmPath().c_str()));
}
void DriverFvmInstance::RestartFvm() {
auto resp = fidl::WireCall(GetRamdiskControllerInterface())->Rebind(kFvmDriverLib);
ASSERT_OK(resp.status());
ASSERT_TRUE(resp->is_ok());
ASSERT_OK(device_watcher::RecursiveWaitForFile(devfs_root().get(), GetFvmPath().c_str()));
}
void DriverFvmInstance::RestartFvmWithNewDiskSize(uint64_t block_size, uint64_t block_count) {
auto resp = fidl::WireCall(GetRamdiskPartition())->GetInfo();
ASSERT_OK(resp.status());
ASSERT_TRUE(resp->is_ok());
uint32_t found_block_size = resp->value()->info.block_size;
ASSERT_EQ(found_block_size, block_size);
ramdisk_.Reset();
zx::vmo vmo;
ASSERT_OK(vmo_.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, block_count * block_size, &vmo));
vmo_ = std::move(vmo);
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(block_size, block_count));
ASSERT_NO_FATAL_FAILURE(StartFvm());
}
fuchsia_hardware_block_volume::wire::VolumeManagerInfo DriverFvmInstance::GetFvmInfo() const {
zx::result fvm = GetVolumeManager();
EXPECT_OK(fvm);
zx::result info = fs_management::FvmQuery(fvm->borrow());
EXPECT_OK(info);
return *info;
}
zx::result<std::unique_ptr<BlockConnector>> DriverFvmInstance::AllocatePartition(
const AllocatePartitionRequest& request) const {
zx::result fvm = GetVolumeManager();
if (fvm.is_error()) {
return fvm.take_error();
}
fdio_cpp::UnownedFdioCaller caller(devfs_root());
zx::result controller = fs_management::FvmAllocatePartitionWithDevfs(
caller.directory(), *fvm, request.slice_count, request.type, request.guid, request.name,
request.flags);
if (controller.is_error()) {
return controller.take_error();
}
return DriverBlockConnector::Create(std::move(controller.value()));
}
zx::result<std::unique_ptr<BlockConnector>> DriverFvmInstance::OpenPartition(
std::string_view label) const {
fdio_cpp::UnownedFdioCaller caller(devfs_root());
zx::result controller =
fs_management::OpenPartitionWithDevfs(caller.directory(), {.labels = {label}}, true);
if (controller.is_error()) {
return controller.take_error();
}
return DriverBlockConnector::Create(std::move(controller.value()));
}
void DriverFvmInstance::DestroyPartition(std::string_view label) const {
zx::result partition = OpenPartition(label);
ASSERT_OK(partition);
fidl::WireResult result = fidl::WireCall(partition->as_volume())->Destroy();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
fidl::ClientEnd<fuchsia_hardware_block::Block> DriverFvmInstance::GetRamdiskPartition() const {
zx::result result = ramdisk_.ConnectBlock();
ZX_ASSERT(result.status_value() == ZX_OK);
EXPECT_OK(result);
return std::move(result).value();
}
fidl::UnownedClientEnd<fuchsia_device::Controller>
DriverFvmInstance::GetRamdiskControllerInterface() const {
return ramdisk_.LegacyController();
}
zx::result<fidl::ClientEnd<fuchsia_hardware_block_volume::VolumeManager>>
DriverFvmInstance::GetVolumeManager() const {
fdio_cpp::UnownedFdioCaller caller(devfs_root());
return component::ConnectAt<fuchsia_hardware_block_volume::VolumeManager>(caller.directory(),
GetFvmPath());
}
zx::result<std::unique_ptr<BlockConnector>> DriverFvmInstance::OpenPartitionNoWait(
std::string_view label) const {
fdio_cpp::UnownedFdioCaller caller(devfs_root());
zx::result controller =
fs_management::OpenPartitionWithDevfs(caller.directory(), {.labels = {label}}, false);
if (controller.is_error()) {
return controller.take_error();
}
return DriverBlockConnector::Create(std::move(controller.value()));
}
std::string DriverFvmInstance::GetFvmPath() const { return std::string(ramdisk_.path()) + "/fvm"; }
class ComponentBlockConnector : public BlockConnector {
public:
~ComponentBlockConnector() = default;
static std::unique_ptr<BlockConnector> Create(const fs_management::MountedVolume* volume) {
auto connector = std::make_unique<ComponentBlockConnector>();
connector->volume_ = volume;
fidl::ServerEnd server_end =
fidl::Endpoints<fuchsia_io::Directory>::Create(&connector->svc_dir_);
EXPECT_TRUE(fidl::WireCall(connector->volume_->ExportRoot())
->Open("svc", fuchsia_io::kPermReadable, {}, server_end.TakeChannel())
.ok());
connector->partition_ = fidl::ClientEnd<fuchsia_hardware_block_volume::Volume>(
connector->connect_block().TakeChannel());
return std::move(connector);
}
fidl::ClientEnd<fuchsia_hardware_block::Block> connect_block() const override {
zx::result volume = component::ConnectAt<fuchsia_hardware_block_volume::Volume>(svc_dir_);
EXPECT_OK(volume);
return fidl::ClientEnd<fuchsia_hardware_block::Block>(volume->TakeChannel());
}
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> as_block() const override {
return fidl::UnownedClientEnd<fuchsia_hardware_block::Block>(partition_.channel().borrow());
}
fidl::UnownedClientEnd<fuchsia_hardware_block_volume::Volume> as_volume() const override {
return partition_.borrow();
}
private:
const fs_management::MountedVolume* volume_;
fidl::ClientEnd<fuchsia_io::Directory> svc_dir_;
fidl::ClientEnd<fuchsia_hardware_block_volume::Volume> partition_;
};
class ComponentFvmInstance : public FvmInstance {
public:
void SetUp() override {}
void TearDown() override {}
void CreateRamdisk(uint64_t block_size, uint64_t block_count) override {
if (!vmo_) {
ASSERT_OK(zx::vmo::create(block_size * block_count, 0, &vmo_));
}
zx::vmo duplicate_vmo;
ASSERT_OK(vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_vmo));
// This will also cause the destructor to run for any previous device.
device_ = std::make_unique<block_server::FakeServer>(
block_server::PartitionInfo{
.block_count = block_count,
.block_size = static_cast<uint32_t>(block_size),
.max_transfer_size = 524288,
},
std::move(duplicate_vmo));
auto [block_client, block_server] =
fidl::Endpoints<fuchsia_hardware_block_volume::Volume>::Create();
block_ = fidl::ClientEnd<fuchsia_hardware_block::Block>(block_client.TakeChannel());
device_->Serve(std::move(block_server));
}
void CreateFvm(uint64_t block_size, uint64_t block_count, uint64_t slice_size) override {
CreateRamdisk(block_size, block_count);
ASSERT_OK(fs_management::FvmInitPreallocated(GetRamdiskPartition(), block_count * block_size,
block_count * block_size, slice_size));
StartFvm();
}
void StartFvm() override {
ASSERT_TRUE(device_);
auto [block_client, block_server] =
fidl::Endpoints<fuchsia_hardware_block_volume::Volume>::Create();
device_->Serve(std::move(block_server));
zx::result fs = fs_management::MountMultiVolume(
fidl::ClientEnd<fuchsia_hardware_block::Block>(block_client.TakeChannel()), component_, {});
ASSERT_OK(fs);
fvm_ = std::make_unique<fs_management::StartedMultiVolumeFilesystem>(std::move(*fs));
auto info = GetFvmInfo();
slice_size_ = info.slice_size;
}
void RestartFvm() override {
if (fvm_) {
ASSERT_OK(fvm_->Unmount());
ASSERT_OK(component_.DestroyChild());
fvm_ = {};
}
StartFvm();
}
void RestartFvmWithNewDiskSize(uint64_t block_size, uint64_t block_count) override {
if (fvm_) {
ASSERT_OK(fvm_->Unmount());
ASSERT_OK(component_.DestroyChild());
fvm_ = {};
}
zx::vmo vmo;
ASSERT_OK(vmo_.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, block_count * block_size, &vmo));
vmo_ = std::move(vmo);
CreateRamdisk(block_size, block_count);
StartFvm();
}
fuchsia_hardware_block_volume::wire::VolumeManagerInfo GetFvmInfo() const override {
EXPECT_TRUE(fvm_);
zx::result volumes =
component::ConnectAt<fuchsia_fs_startup::Volumes>(fvm_->ServiceDirectory());
EXPECT_OK(volumes);
fidl::WireResult info = fidl::WireCall(*volumes)->GetInfo();
EXPECT_TRUE(info.ok());
EXPECT_TRUE(info->is_ok());
return *info.value()->info;
}
zx::result<std::unique_ptr<BlockConnector>> AllocatePartition(
const AllocatePartitionRequest& request) const override {
EXPECT_TRUE(fvm_);
fidl::Array<uint8_t, 16> type_guid = fidl::Array<uint8_t, 16>{1, 2, 3, 4};
memcpy(type_guid.data(), request.type.bytes(), 16);
fidl::Arena arena;
zx::result volume = fvm_->CreateVolume(request.name,
fuchsia_fs_startup::wire::CreateOptions::Builder(arena)
.type_guid(type_guid)
.initial_size(request.slice_count * slice_size_)
.Build(),
{});
if (volume.is_error()) {
return volume.take_error();
}
return zx::ok(ComponentBlockConnector::Create(volume.value()));
}
zx::result<std::unique_ptr<BlockConnector>> OpenPartition(std::string_view label) const override {
EXPECT_TRUE(fvm_);
std::string name = std::string(label);
const fs_management::MountedVolume* volume = fvm_->GetVolume(name);
if (volume == nullptr) {
zx::result opened_volume = fvm_->OpenVolume(name, {});
if (opened_volume.is_error()) {
return opened_volume.take_error();
}
volume = opened_volume.value();
}
return zx::ok(ComponentBlockConnector::Create(volume));
}
void DestroyPartition(std::string_view label) const override {
ASSERT_OK(fvm_->RemoveVolume(label));
}
fidl::ClientEnd<fuchsia_hardware_block::Block> GetRamdiskPartition() const override {
auto [block_client, block_server] =
fidl::Endpoints<fuchsia_hardware_block_volume::Volume>::Create();
device_->Serve(std::move(block_server));
return fidl::ClientEnd<fuchsia_hardware_block::Block>(block_client.TakeChannel());
}
private:
zx::vmo vmo_;
std::unique_ptr<block_server::FakeServer> device_;
fidl::ClientEnd<fuchsia_hardware_block::Block> block_;
fs_management::FsComponent component_ =
fs_management::FsComponent::FromDiskFormat(fs_management::kDiskFormatFvm);
std::unique_ptr<fs_management::StartedMultiVolumeFilesystem> fvm_;
uint64_t slice_size_;
};
std::unique_ptr<FvmInstance> CreateFvmInstance(FvmImplementation impl) {
switch (impl) {
case FvmImplementation::kDriver:
return std::make_unique<DriverFvmInstance>();
case FvmImplementation::kComponent:
return std::make_unique<ComponentFvmInstance>();
}
}
} // namespace fvm