| // 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 |