blob: d961ba6886d2e20e41a18037e5c9c6aa2140d0ea [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/fvm/test_support.h"
#include <errno.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fidl/cpp/wire/sync_call.h>
#include <lib/fidl/cpp/wire/vector_view.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
#include "lib/fdio/directory.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
#include "src/storage/lib/fs_management/cpp/fvm.h"
namespace fvm {
namespace {
constexpr char kRamdiskCtlPath[] = "sys/platform/00:00:2d/ramctl";
constexpr zx::duration kDeviceWaitTime = zx::sec(30);
template <typename Protocol>
zx::result<fidl::ClientEnd<Protocol>> GetChannel(DeviceRef* device) {
fdio_cpp::UnownedFdioCaller caller(device->devfs_root_fd());
return component::ConnectAt<Protocol>(caller.directory(), device->path());
}
zx::result<fidl::ClientEnd<fuchsia_device::Controller>> GetController(DeviceRef* device) {
fdio_cpp::UnownedFdioCaller caller(device->devfs_root_fd());
std::string controller_path = std::string(device->path()).append("/device_controller");
return component::ConnectAt<fuchsia_device::Controller>(caller.directory(), controller_path);
}
zx_status_t RebindBlockDevice(DeviceRef* device) {
// We need to create a DirWatcher to wait for the block device's child to disappear.
fdio_cpp::UnownedFdioCaller caller(device->devfs_root_fd());
auto [client_end, server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
const fidl::OneWayStatus status =
fidl::WireCall(caller.directory())
->Open(fuchsia_io::OpenFlags::kDirectory, {},
fidl::StringView::FromExternal(device->path()),
fidl::ServerEnd<fuchsia_io::Node>(server_end.TakeChannel()));
EXPECT_OK(status);
if (!status.ok()) {
return status.status();
}
zx::result watcher = device_watcher::DirWatcher::Create(client_end);
EXPECT_OK(watcher);
if (watcher.is_error()) {
return watcher.error_value();
}
zx::result channel = GetController(device);
if (channel.is_error()) {
return channel.status_value();
}
const fidl::WireResult result = fidl::WireCall(channel.value())->Rebind({});
if (!result.ok()) {
ADD_FAILURE("('%s').Rebind(): %s", device->path(), result.status_string());
return result.status();
}
const fit::result response = result.value();
if (response.is_error()) {
ADD_FAILURE("('%s').Rebind(): %s", device->path(),
zx_status_get_string(response.error_value()));
return response.error_value();
}
if (zx_status_t status =
watcher.value().WaitForRemoval(fbl::String() /* any file */, kDeviceWaitTime);
status != ZX_OK) {
ADD_FAILURE("Watcher('%s').WaitForRemoval: %s", device->path(), zx_status_get_string(status));
return status;
}
return ZX_OK;
}
using FidlGuid = fuchsia_hardware_block_partition::wire::Guid;
} // namespace
// namespace
DeviceRef::DeviceRef(const fbl::unique_fd& devfs_root, const std::string& path)
: devfs_root_(devfs_root) {
path_.append(path);
}
std::unique_ptr<DeviceRef> DeviceRef::Create(const fbl::unique_fd& devfs_root,
const std::string& device_path) {
return std::make_unique<DeviceRef>(devfs_root, device_path);
}
std::unique_ptr<RamdiskRef> RamdiskRef::Create(const fbl::unique_fd& devfs_root,
uint64_t block_size, uint64_t block_count) {
if (!devfs_root.is_valid()) {
ADD_FAILURE("Bad devfs root handle.");
return nullptr;
}
if (block_size == 0 || block_count == 0) {
ADD_FAILURE("Attempting to create 0 sized ramdisk.");
return nullptr;
}
if (zx::result channel =
device_watcher::RecursiveWaitForFile(devfs_root.get(), kRamdiskCtlPath, kDeviceWaitTime);
channel.is_error()) {
ADD_FAILURE("Failed to wait for RamCtl. Reason: %s", channel.status_string());
return nullptr;
}
RamdiskClient* client;
if (zx_status_t status = ramdisk_create_at(devfs_root.get(), block_size, block_count, &client);
status != ZX_OK) {
ADD_FAILURE("Failed to create ramdisk. Reason: %s", zx_status_get_string(status));
return nullptr;
}
const char* path = ramdisk_get_path(client);
return std::make_unique<RamdiskRef>(devfs_root, path, client);
}
RamdiskRef::~RamdiskRef() { ramdisk_destroy(ramdisk_client_); }
zx_status_t RamdiskRef::Grow(uint64_t target_size) {
return ramdisk_grow(ramdisk_client_, target_size);
}
void BlockDeviceAdapter::WriteAt(const fbl::Array<uint8_t>& data, uint64_t offset) {
zx::result channel = GetChannel<fuchsia_hardware_block::Block>(device());
ASSERT_OK(channel.status_value());
ASSERT_OK(block_client::SingleWriteBytes(channel.value(), data.data(), data.size(), offset));
}
void BlockDeviceAdapter::ReadAt(uint64_t offset, fbl::Array<uint8_t>* out_data) {
zx::result channel = GetChannel<fuchsia_hardware_block::Block>(device());
ASSERT_OK(channel.status_value());
ASSERT_OK(
block_client::SingleReadBytes(channel.value(), out_data->data(), out_data->size(), offset));
}
void BlockDeviceAdapter::CheckContentsAt(const fbl::Array<uint8_t>& data, uint64_t offset) {
ASSERT_GT(data.size(), 0, "data::size must be greater than 0.");
fbl::Array<uint8_t> device_data(new uint8_t[data.size()], data.size());
ASSERT_NO_FAILURES(ReadAt(offset, &device_data));
ASSERT_BYTES_EQ(device_data.data(), data.data(), data.size());
}
zx_status_t BlockDeviceAdapter::WaitUntilVisible() const {
zx::result channel =
device_watcher::RecursiveWaitForFile(devfs_root_.get(), device()->path(), kDeviceWaitTime);
if (channel.is_error()) {
ADD_FAILURE("Block device did not become visible at %s: %s", device()->path(),
channel.status_string());
}
return channel.status_value();
}
zx_status_t BlockDeviceAdapter::Rebind() {
if (zx_status_t status = RebindBlockDevice(device()); status != ZX_OK) {
return status;
}
// Block device is visible again.
if (zx_status_t status = WaitUntilVisible(); status != ZX_OK) {
return status;
}
return ZX_OK;
}
zx::result<fidl::ClientEnd<fuchsia_device::Controller>> VPartitionAdapter::GetController() {
return fvm::GetController(device());
}
std::unique_ptr<VPartitionAdapter> VPartitionAdapter::Create(const fbl::unique_fd& devfs_root,
const std::string& name,
const Guid& guid, const Guid& type) {
if (name.empty() || type.size() == 0 || guid.size() == 0) {
ADD_FAILURE(
"Partition name(size=%lu), type(size=%lu) and guid(size=%lu) must be non "
"empty.\n"
"Partition {\n"
" name: %s\n"
" type: %s\n"
" guid: %s\n"
"}",
name.size(), type.size(), guid.size(), name.c_str(), type.ToString().c_str(),
guid.ToString().c_str());
return nullptr;
}
fs_management::PartitionMatcher matcher{
.type_guids = {uuid::Uuid(type.data())},
.instance_guids = {uuid::Uuid(guid.data())},
};
fdio_cpp::UnownedFdioCaller devfs_root_caller(devfs_root.get());
zx::result controller =
fs_management::OpenPartitionWithDevfs(devfs_root_caller.directory(), matcher, true);
if (controller.is_error()) {
ADD_FAILURE("Unable to obtain handle for partition.");
return nullptr;
}
fidl::WireResult topo_path = fidl::WireCall(controller.value())->GetTopologicalPath();
if (!topo_path.ok()) {
ADD_FAILURE("Failed to call topo path: %s", topo_path.status_string());
return nullptr;
}
if (topo_path->is_error()) {
ADD_FAILURE("Failed to get topo path: %d", topo_path->error_value());
return nullptr;
}
std::string relative_path = std::string(topo_path.value()->path.get());
constexpr std::string_view kDevPrefix = "/dev/";
if (!cpp20::starts_with(std::string_view(relative_path), kDevPrefix)) {
ADD_FAILURE("Bad topo path, doesn't start with /dev/: %s", relative_path.c_str());
return nullptr;
}
relative_path.erase(0, kDevPrefix.size());
return std::make_unique<VPartitionAdapter>(devfs_root, relative_path, name, guid, type);
}
VPartitionAdapter::~VPartitionAdapter() {
ASSERT_OK(
fs_management::DestroyPartitionWithDevfs(devfs_root_.get(),
{
.type_guids = {uuid::Uuid(type_.data())},
.instance_guids = {uuid::Uuid(guid_.data())},
},
false));
}
zx_status_t VPartitionAdapter::Extend(uint64_t offset, uint64_t length) {
zx::result channel = GetChannel<fuchsia_hardware_block_volume::Volume>(device());
if (channel.is_error()) {
return channel.error_value();
}
const fidl::WireResult result = fidl::WireCall(channel.value())->Extend(offset, length);
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse response = result.value();
return response.status;
}
std::unique_ptr<FvmAdapter> FvmAdapter::Create(const fbl::unique_fd& devfs_root,
uint64_t block_size, uint64_t block_count,
uint64_t slice_size, DeviceRef* device) {
return CreateGrowable(devfs_root, block_size, block_count, block_count, slice_size, device);
}
std::unique_ptr<FvmAdapter> FvmAdapter::CreateGrowable(const fbl::unique_fd& devfs_root,
uint64_t block_size,
uint64_t initial_block_count,
uint64_t maximum_block_count,
uint64_t slice_size, DeviceRef* device) {
if (device == nullptr) {
ADD_FAILURE("Create requires non-null device pointer.");
return nullptr;
}
{
zx::result channel = GetChannel<fuchsia_hardware_block::Block>(device);
if (channel.is_error()) {
ADD_FAILURE("ConnectAt(%s): %s", device->path(), channel.status_string());
return nullptr;
}
if (zx_status_t status =
fs_management::FvmInitPreallocated(channel.value(), initial_block_count * block_size,
maximum_block_count * block_size, slice_size);
status != ZX_OK) {
ADD_FAILURE("FvmInitPreallocated(%s): %s", device->path(), zx_status_get_string(status));
return nullptr;
}
}
{
zx::result channel = GetController(device);
if (channel.is_error()) {
ADD_FAILURE("ConnectAt(%s): %s", device->path(), channel.status_string());
return nullptr;
}
const fidl::WireResult result =
fidl::WireCall(channel.value())->Bind(fidl::StringView(kFvmDriverLib));
if (!result.ok()) {
ADD_FAILURE("Binding FVM driver failed: %s", result.FormatDescription().c_str());
return nullptr;
}
const fit::result response = result.value();
if (response.is_error()) {
ADD_FAILURE("Binding FVM driver failed: %s", zx_status_get_string(response.error_value()));
return nullptr;
}
}
fbl::StringBuffer<kPathMax> fvm_path;
fvm_path.AppendPrintf("%s/fvm", device->path());
if (zx::result channel =
device_watcher::RecursiveWaitForFile(devfs_root.get(), fvm_path.c_str(), kDeviceWaitTime);
channel.is_error()) {
ADD_FAILURE("Loading FVM driver: %s", channel.status_string());
return nullptr;
}
return std::make_unique<FvmAdapter>(devfs_root, fvm_path.c_str(), device);
}
FvmAdapter::~FvmAdapter() {
fs_management::FvmDestroyWithDevfs(devfs_root_.get(), block_device_->path());
}
zx_status_t FvmAdapter::AddPartition(const fbl::unique_fd& devfs_root, const std::string& name,
const Guid& guid, const Guid& type, uint64_t slice_count,
std::unique_ptr<VPartitionAdapter>* out_vpartition) {
FidlGuid fidl_guid, fidl_type;
memcpy(fidl_guid.value.data(), guid.data(), guid.size());
memcpy(fidl_type.value.data(), type.data(), type.size());
zx::result channel = GetChannel<fuchsia_hardware_block_volume::VolumeManager>(this);
if (channel.is_error()) {
return channel.status_value();
}
const fidl::WireResult result = fidl::WireCall(channel.value())
->AllocatePartition(slice_count, fidl_type, fidl_guid,
fidl::StringView::FromExternal(name), 0u);
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
return status;
}
auto vpartition = VPartitionAdapter::Create(devfs_root, name, guid, type);
if (vpartition == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (zx_status_t status = vpartition->WaitUntilVisible(); status != ZX_OK) {
return status;
}
if (out_vpartition != nullptr) {
*out_vpartition = std::move(vpartition);
}
return ZX_OK;
}
zx_status_t FvmAdapter::Rebind(fbl::Vector<VPartitionAdapter*> vpartitions) {
if (zx_status_t status = RebindBlockDevice(block_device_); status != ZX_OK) {
ADD_FAILURE("FvmAdapter block device rebind failed.");
return status;
}
// Bind the FVM to the block device.
zx::result channel = GetController(block_device_);
if (channel.is_error()) {
return channel.status_value();
}
const fidl::WireResult result =
fidl::WireCall(channel.value())->Bind(fidl::StringView(kFvmDriverLib));
if (!result.ok()) {
ADD_FAILURE("Rebinding FVM driver failed: %s", result.FormatDescription().c_str());
return result.status();
}
const fit::result response = result.value();
if (response.is_error()) {
ADD_FAILURE("Rebinding FVM driver failed: %s", zx_status_get_string(response.error_value()));
return response.error_value();
}
// Wait for FVM driver to become visible.
if (zx::result channel =
device_watcher::RecursiveWaitForFile(devfs_root_.get(), path(), kDeviceWaitTime);
channel.is_error()) {
ADD_FAILURE("Loading FVM driver: %s", channel.status_string());
return channel.status_value();
}
for (auto* vpartition : vpartitions) {
if (zx_status_t status = vpartition->WaitUntilVisible(); status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t FvmAdapter::Query(VolumeManagerInfo* out_info) const {
fdio_cpp::UnownedFdioCaller caller(devfs_root_.get());
zx::result<fidl::ClientEnd<fuchsia_hardware_block_volume::VolumeManager>> volume_manager =
component::ConnectAt<fuchsia_hardware_block_volume::VolumeManager>(caller.directory(),
path());
if (volume_manager.is_error()) {
ADD_FAILURE("Could not open FVM Volume Manager: %s\n",
zx_status_get_string(volume_manager.error_value()));
return volume_manager.error_value();
}
zx::result info = fs_management::FvmQuery(volume_manager.value());
if (info.is_error()) {
return info.error_value();
}
*out_info = info.value();
return ZX_OK;
}
fbl::Array<uint8_t> MakeRandomBuffer(size_t size, unsigned int* seed) {
fbl::Array data(new uint8_t[size], size);
for (size_t byte = 0; byte < size; ++byte) {
data[byte] = static_cast<uint8_t>(rand_r(seed));
}
return data;
}
bool IsConsistentAfterGrowth(const VolumeManagerInfo& before, const VolumeManagerInfo& after) {
// Frowing a FVM should not allocate any slices nor should it change the slice size.
return before.slice_size == after.slice_size &&
before.assigned_slice_count == after.assigned_slice_count;
}
} // namespace fvm