blob: b652c1ef0248fe21bb1d83976bbb8ffbf1bc2625 [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 <fcntl.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/service_client.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 "src/lib/storage/block_client/cpp/remote_block_device.h"
#include "src/lib/storage/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());
}
template <typename Protocol>
fidl::UnownedClientEnd<Protocol> GetChannel(VPartitionAdapter* device) {
fdio_cpp::UnownedFdioCaller caller(device->fd());
return caller.borrow_as<Protocol>();
}
zx_status_t RebindBlockDevice(DeviceRef* device) {
// We need to create a DirWatcher to wait for the block device's child to disappear.
std::unique_ptr<device_watcher::DirWatcher> watcher;
fbl::unique_fd dir_fd(
openat(device->devfs_root_fd().get(), device->path(), O_RDONLY | O_DIRECTORY));
if (zx_status_t status = device_watcher::DirWatcher::Create(dir_fd.get(), &watcher);
status != ZX_OK) {
ADD_FAILURE("DirWatcher::Create('%s'): %s", device->path(), zx_status_get_string(status));
return status;
}
zx::result channel = GetChannel<fuchsia_hardware_block::Block>(device);
if (channel.is_error()) {
return channel.status_value();
}
const fidl::WireResult result = fidl::WireCall(channel.value())->RebindDevice();
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
ADD_FAILURE("('%s').Rebind(): %s", device->path(), zx_status_get_string(status));
return status;
}
if (zx_status_t status = watcher->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_status_t status =
wait_for_device_at(devfs_root.get(), kRamdiskCtlPath, kDeviceWaitTime.get());
status != ZX_OK) {
ADD_FAILURE("Failed to wait for RamCtl. Reason: %s", zx_status_get_string(status));
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_status_t status =
wait_for_device_at(devfs_root_.get(), device()->path(), kDeviceWaitTime.get());
if (status != ZX_OK) {
ADD_FAILURE("Block device did not become visible at: %s", device()->path());
}
return status;
}
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;
}
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;
}
std::string out_path;
fs_management::PartitionMatcher matcher{
.type_guids = {uuid::Uuid(type.data())},
.instance_guids = {uuid::Uuid(guid.data())},
};
zx::result device_fd_or = fs_management::OpenPartitionWithDevfs(devfs_root.get(), matcher,
kDeviceWaitTime.get(), &out_path);
if (device_fd_or.is_error()) {
ADD_FAILURE("Unable to obtain handle for partition.");
return nullptr;
}
return std::make_unique<VPartitionAdapter>(devfs_root, out_path.c_str(),
std::move(device_fd_or.value()), name, guid, type);
}
VPartitionAdapter::~VPartitionAdapter() {
fs_management::DestroyPartitionWithDevfs(devfs_root_.get(),
{
.type_guids = {uuid::Uuid(type_.data())},
.instance_guids = {uuid::Uuid(guid_.data())},
});
}
zx_status_t VPartitionAdapter::Extend(uint64_t offset, uint64_t length) {
fidl::UnownedClientEnd channel = GetChannel<fuchsia_hardware_block_volume::Volume>(this);
const fidl::WireResult result = fidl::WireCall(channel)->Extend(offset, length);
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse response = result.value();
return response.status;
}
zx_status_t VPartitionAdapter::Reconnect() {
fs_management::PartitionMatcher matcher{
.type_guids = {uuid::Uuid(type_.data())},
.instance_guids = {uuid::Uuid(guid_.data())},
};
zx::result fd = fs_management::OpenPartitionWithDevfs(devfs_root_.get(), matcher,
zx::duration::infinite().get(), &path_);
if (fd.is_error()) {
return fd.status_value();
}
fd_ = std::move(fd.value());
return ZX_OK;
}
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 = GetChannel<fuchsia_device::Controller>(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 (wait_for_device_at(devfs_root.get(), fvm_path.c_str(), kDeviceWaitTime.get()) != ZX_OK) {
ADD_FAILURE("Loading FVM driver timeout.");
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 = GetChannel<fuchsia_device::Controller>(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_status_t status = wait_for_device_at(devfs_root_.get(), path(), kDeviceWaitTime.get());
status != ZX_OK) {
ADD_FAILURE("Loading FVM driver timeout.");
return status;
}
for (auto* vpartition : vpartitions) {
if (zx_status_t status = vpartition->Reconnect(); status != ZX_OK) {
return status;
}
if (zx_status_t status = vpartition->WaitUntilVisible(); status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t FvmAdapter::Query(VolumeManagerInfo* out_info) const {
fbl::unique_fd fd(openat(devfs_root_.get(), path(), O_RDONLY));
zx::result info = fs_management::FvmQuery(fd.get());
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