| // 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 <fuchsia/hardware/block/partition/c/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fidl/llcpp/sync_call.h> |
| #include <lib/fidl/llcpp/vector_view.h> |
| #include <lib/zx/time.h> |
| #include <zircon/status.h> |
| |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <string> |
| |
| #include <sdk/lib/device-watcher/cpp/device-watcher.h> |
| #include <zxtest/zxtest.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); |
| |
| 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(), device->path(), O_RDONLY | O_DIRECTORY)); |
| zx_status_t status = device_watcher::DirWatcher::Create(std::move(dir_fd), &watcher); |
| if (status != ZX_OK) { |
| ADD_FAILURE("DirWatcher create failed. Path: %s", device->path()); |
| return status; |
| } |
| |
| zx_status_t fidl_status = |
| fuchsia_hardware_block_BlockRebindDevice(device->channel()->get(), &status); |
| |
| if (fidl_status != ZX_OK || status != ZX_OK) { |
| ADD_FAILURE("Block device rebind failed. Path: %s", device->path()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return fidl_status; |
| } |
| status = watcher->WaitForRemoval(fbl::String() /* any file */, kDeviceWaitTime); |
| if (status != ZX_OK) { |
| ADD_FAILURE("Wait for removal failed.Path: %s", device->path()); |
| return status; |
| } |
| device->Reconnect(); |
| return status; |
| } |
| |
| fidl::VectorView<uint8_t> ToFidlVector(const fbl::Array<uint8_t>& data) { |
| return fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(data.data()), data.size()); |
| } |
| |
| using FidlGuid = fuchsia_hardware_block_partition::wire::Guid; |
| |
| zx::unowned_channel GetChannel(int fd) { |
| if (fd < 0) { |
| return zx::unowned_channel(); |
| } |
| fdio_cpp::UnownedFdioCaller caller(fd); |
| return zx::unowned_channel(caller.borrow_channel()); |
| } |
| |
| } // namespace |
| |
| // namespace |
| |
| DeviceRef::DeviceRef(const fbl::unique_fd& devfs_root, const std::string& path, fbl::unique_fd fd) |
| : devfs_root_(devfs_root.get()), fd_(std::move(fd)), channel_(GetChannel(fd_.get())) { |
| path_.append(path.c_str()); |
| } |
| |
| void DeviceRef::Reconnect() { |
| ASSERT_FALSE(path_.empty(), "Attempt to reconnect device with unset path."); |
| fd_.reset(openat(devfs_root_, path_.c_str(), O_RDWR)); |
| ASSERT_TRUE(fd_.is_valid(), "Failed to reconnect to device."); |
| channel_ = GetChannel(fd_.get()); |
| } |
| |
| std::unique_ptr<DeviceRef> DeviceRef::Create(const fbl::unique_fd& devfs_root, |
| const std::string& device_path) { |
| fbl::unique_fd device_fd(openat(devfs_root.get(), device_path.c_str(), O_RDWR)); |
| if (!device_fd.is_valid()) { |
| ADD_FAILURE("Unable to obtain handle to block_device at %s. Reason: %s", device_path.c_str(), |
| strerror(errno)); |
| return nullptr; |
| } |
| |
| return std::make_unique<DeviceRef>(devfs_root, device_path, std::move(device_fd)); |
| } |
| |
| 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; |
| } |
| |
| zx_status_t status = wait_for_device_at(devfs_root.get(), kRamdiskCtlPath, kDeviceWaitTime.get()); |
| if (status != ZX_OK) { |
| ADD_FAILURE("Failed to wait for RamCtl. Reason: %s", zx_status_get_string(status)); |
| return nullptr; |
| } |
| |
| RamdiskClient* client; |
| if ((status = ramdisk_create_at(devfs_root.get(), block_size, block_count, &client)) != ZX_OK) { |
| ADD_FAILURE("Failed to create ramdisk. Reason: %s", zx_status_get_string(status)); |
| return nullptr; |
| } |
| const char* path = ramdisk_get_path(client); |
| fbl::unique_fd device_fd(openat(devfs_root.get(), path, O_RDWR)); |
| if (!device_fd.is_valid()) { |
| ADD_FAILURE("Error: Unable to obtain handle to block_device at %s. Reason: %s", path, |
| strerror(errno)); |
| return nullptr; |
| } |
| |
| return std::make_unique<RamdiskRef>(devfs_root, path, std::move(device_fd), 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) { |
| const fidl::WireResult result = |
| fidl::WireCall<fuchsia_io::File>(device()->channel())->WriteAt(ToFidlVector(data), offset); |
| ASSERT_OK(result.status(), "Failed to communicate with block device."); |
| const fitx::result response = result.value(); |
| ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value())); |
| ASSERT_EQ(data.size(), response.value()->actual_count); |
| } |
| |
| void BlockDeviceAdapter::ReadAt(uint64_t offset, fbl::Array<uint8_t>* out_data) { |
| const fidl::WireResult result = |
| fidl::WireCall<fuchsia_io::File>(device()->channel())->ReadAt(out_data->size(), offset); |
| ASSERT_OK(result.status(), "Failed to communicate with block device."); |
| const fitx::result response = result.value(); |
| ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value())); |
| const fidl::VectorView data = response.value()->data; |
| memcpy(out_data->data(), data.data(), data.count()); |
| } |
| |
| 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_, 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() { |
| zx_status_t status; |
| |
| if ((status = RebindBlockDevice(device())) != ZX_OK) { |
| return status; |
| } |
| |
| // Block device is visible again. |
| if ((status = WaitUntilVisible()) != 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_guid = type.data(), |
| .instance_guid = guid.data(), |
| }; |
| auto 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; |
| } |
| auto channel = GetChannel(device_fd_or->get()); |
| return std::make_unique<VPartitionAdapter>(devfs_root, std::move(channel), out_path.c_str(), |
| *std::move(device_fd_or), name, guid, type); |
| } |
| |
| VPartitionAdapter::~VPartitionAdapter() { |
| fs_management::DestroyPartitionWithDevfs(devfs_root_, guid_.data(), type_.data()); |
| } |
| |
| zx_status_t VPartitionAdapter::Extend(uint64_t offset, uint64_t length) { |
| zx_status_t status; |
| zx_status_t fidl_status = |
| fuchsia_hardware_block_volume_VolumeExtend(channel_->get(), offset, length, &status); |
| if (fidl_status != ZX_OK) { |
| return fidl_status; |
| } |
| return status; |
| } |
| |
| void VPartitionAdapter::Reconnect() { |
| fs_management::PartitionMatcher matcher{ |
| .type_guid = type_.data(), |
| .instance_guid = guid_.data(), |
| }; |
| auto fd_or = fs_management::OpenPartitionWithDevfs(devfs_root_, &matcher, |
| zx::duration::infinite().get(), &path_); |
| ASSERT_EQ(fd_or.status_value(), ZX_OK); |
| fd_ = *std::move(fd_or); |
| channel_ = GetChannel(fd_.get()); |
| } |
| |
| 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; |
| } |
| |
| if (!device->channel()->is_valid()) { |
| ADD_FAILURE("Invalid device handle."); |
| return nullptr; |
| } |
| |
| if (fs_management::FvmInitPreallocated(device->fd(), initial_block_count * block_size, |
| maximum_block_count * block_size, slice_size) != ZX_OK) { |
| return nullptr; |
| } |
| |
| zx_status_t status = ZX_OK; |
| auto resp = |
| fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(device->channel()->get())) |
| ->Bind(::fidl::StringView(kFvmDriverLib)); |
| zx_status_t fidl_status = resp.status(); |
| if (resp->is_error()) { |
| status = resp->error_value(); |
| } |
| |
| if (fidl_status != ZX_OK || status != ZX_OK) { |
| ADD_FAILURE("Binding FVM driver failed. Reason: %s", |
| zx_status_get_string((fidl_status != ZX_OK) ? fidl_status : status)); |
| 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; |
| } |
| |
| fbl::unique_fd device_fd(openat(devfs_root.get(), fvm_path.c_str(), O_RDWR)); |
| if (!device_fd.is_valid()) { |
| ADD_FAILURE("Failed to acquire handle for fvm."); |
| return nullptr; |
| } |
| |
| return std::make_unique<FvmAdapter>(devfs_root, fvm_path.c_str(), std::move(device_fd), device); |
| } |
| |
| FvmAdapter::~FvmAdapter() { |
| fs_management::FvmDestroyWithDevfs(devfs_root_, 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_partition) { |
| FidlGuid fidl_guid, fidl_type; |
| memcpy(fidl_guid.value.data(), guid.data(), guid.size()); |
| memcpy(fidl_type.value.data(), type.data(), type.size()); |
| |
| auto response = |
| fidl::WireCall(fidl::UnownedClientEnd<fuchsia_hardware_block_volume::VolumeManager>(channel_)) |
| ->AllocatePartition(slice_count, fidl_type, fidl_guid, |
| fidl::StringView::FromExternal(name), 0u); |
| if (response.status() != ZX_OK) { |
| return response.status(); |
| } |
| |
| if (response.value().status != ZX_OK) { |
| return response.value().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_partition != nullptr) { |
| *out_partition = std::move(vpartition); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t FvmAdapter::Rebind(fbl::Vector<VPartitionAdapter*> vpartitions) { |
| zx_status_t status = RebindBlockDevice(block_device_); |
| |
| if (status != ZX_OK) { |
| ADD_FAILURE("FvmAdapter block device rebind failed."); |
| return status; |
| } |
| |
| auto resp = fidl::WireCall<fuchsia_device::Controller>( |
| zx::unowned_channel(block_device_->channel()->get())) |
| ->Bind(::fidl::StringView(kFvmDriverLib)); |
| zx_status_t fidl_status = resp.status(); |
| status = ZX_OK; |
| if (resp->is_error()) { |
| status = resp->error_value(); |
| } |
| |
| // Bind the FVM to the block device. |
| if (fidl_status != ZX_OK || status != ZX_OK) { |
| ADD_FAILURE("Rebinding FVM driver failed."); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return fidl_status; |
| } |
| |
| // Wait for FVM driver to become visible. |
| if ((status = wait_for_device_at(devfs_root_, path(), kDeviceWaitTime.get())) != ZX_OK) { |
| ADD_FAILURE("Loading FVM driver timeout."); |
| return status; |
| } |
| |
| // Acquire new FD for the FVM driver. |
| Reconnect(); |
| |
| for (auto* vpartition : vpartitions) { |
| // Reopen them, since all the channels have been closed. |
| vpartition->Reconnect(); |
| if ((status = vpartition->WaitUntilVisible()) != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FvmAdapter::Query(VolumeManagerInfo* info) const { |
| if (auto info_or = fs_management::FvmQuery(fd()); info_or.is_error()) |
| return info_or.error_value(); |
| else |
| *info = *std::move(info_or); |
| 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 |