blob: 6fda0b40343a53cfdb7db86cee6272adf086ad6b [file] [log] [blame] [edit]
// Copyright 2024 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/lib/paver/block-devices.h"
#include <dirent.h>
#include <fidl/fuchsia.io/cpp/natural_types.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/watcher.h>
#include <lib/fit/defer.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <fbl/unique_fd.h>
#include "src/storage/lib/paver/pave-logging.h"
namespace paver {
namespace {
zx::result<fbl::unique_fd> open_in_dir(int dir_fd, const std::string& filename) {
zx::channel partition_local, partition_remote;
if (zx_status_t status = zx::channel::create(0, &partition_local, &partition_remote);
status != ZX_OK) {
return zx::error(status);
}
fbl::unique_fd fd;
if (zx_status_t status = fdio_open3_fd_at(dir_fd, filename.c_str(),
static_cast<uint64_t>(fuchsia_io::kPermReadable),
fd.reset_and_get_address());
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(fd));
}
zx::result<std::unique_ptr<VolumeConnector>> CreateDevfsVolumeConnector(int dir_fd,
std::string filename) {
fidl::ClientEnd<fuchsia_device::Controller> controller;
zx::result controller_server = fidl::CreateEndpoints(&controller);
if (controller_server.is_error()) {
return controller_server.take_error();
}
filename.append("/device_controller");
fdio_cpp::UnownedFdioCaller caller(dir_fd);
if (zx_status_t status = fdio_service_connect_at(caller.borrow_channel(), filename.c_str(),
controller_server->TakeChannel().release());
status != ZX_OK) {
ERROR("Failed to connect to device_controller: %s", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(std::make_unique<DevfsVolumeConnector>(std::move(controller)));
}
} // namespace
DevfsVolumeConnector::DevfsVolumeConnector(fidl::ClientEnd<fuchsia_device::Controller> controller)
: controller_(std::move(controller)) {}
zx::result<fidl::ClientEnd<fuchsia_hardware_block_volume::Volume>> DevfsVolumeConnector::Connect()
const {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_block_volume::Volume>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto [client, server] = std::move(*endpoints);
if (fidl::OneWayStatus result = controller_->ConnectToDeviceFidl(server.TakeChannel());
!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(client));
}
zx::result<fidl::ClientEnd<fuchsia_storage_partitions::Partition>>
DevfsVolumeConnector::PartitionManagement() const {
ZX_ASSERT_MSG(false, "Called PartitionManagement on a DevfsVolumeConnector");
}
fidl::UnownedClientEnd<fuchsia_device::Controller> DevfsVolumeConnector::Controller() const {
return controller_.client_end().borrow();
}
fidl::ClientEnd<fuchsia_device::Controller> DevfsVolumeConnector::TakeController() {
return controller_.TakeClientEnd();
}
DirBasedVolumeConnector::DirBasedVolumeConnector(fbl::unique_fd dir,
std::string volume_connector_path)
: dir_(std::move(dir)), volume_connector_path_(std::move(volume_connector_path)) {}
zx::result<fidl::ClientEnd<fuchsia_hardware_block_volume::Volume>>
DirBasedVolumeConnector::Connect() const {
fdio_cpp::UnownedFdioCaller caller(dir_);
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_block_volume::Volume>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto [client, server] = std::move(*endpoints);
if (zx_status_t status = fdio_service_connect_at(
caller.borrow_channel(), volume_connector_path_.c_str(), server.TakeChannel().release());
status != ZX_OK) {
LOG("Failed to connect to volume service: %s\n", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(std::move(client));
}
zx::result<fidl::ClientEnd<fuchsia_storage_partitions::Partition>>
DirBasedVolumeConnector::PartitionManagement() const {
ZX_ASSERT_MSG(false, "Called PartitionManagement on a VolumeServiceBasedVolumeConnector");
}
fidl::UnownedClientEnd<fuchsia_device::Controller> DirBasedVolumeConnector::Controller() const {
ZX_ASSERT_MSG(false, "Called Controller on a non-DevfsVolumeConnector");
}
fidl::ClientEnd<fuchsia_device::Controller> DirBasedVolumeConnector::TakeController() {
ZX_ASSERT_MSG(false, "Called TakeController on a non-DevfsVolumeConnector");
}
PartitionServiceBasedVolumeConnector::PartitionServiceBasedVolumeConnector(
fbl::unique_fd service_dir)
: DirBasedVolumeConnector(std::move(service_dir), std::string("volume")) {}
zx::result<fidl::ClientEnd<fuchsia_storage_partitions::Partition>>
PartitionServiceBasedVolumeConnector::PartitionManagement() const {
fdio_cpp::UnownedFdioCaller caller(dir_);
zx::result endpoints = fidl::CreateEndpoints<fuchsia_storage_partitions::Partition>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto [client, server] = std::move(*endpoints);
if (zx_status_t status = fdio_service_connect_at(caller.borrow_channel(), "partition",
server.TakeChannel().release());
status != ZX_OK) {
LOG("Failed to connect to partition service: %s\n", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(std::move(client));
}
BlockDevices::BlockDevices(fbl::unique_fd root, Variant variant)
: root_(std::move(root)), variant_(variant) {}
zx::result<BlockDevices> BlockDevices::CreateDevfs(fbl::unique_fd devfs_root) {
if (!devfs_root) {
if (zx_status_t status = fdio_open3_fd("/dev", static_cast<uint64_t>(fuchsia_io::kPermReadable),
devfs_root.reset_and_get_address());
status != ZX_OK) {
ERROR("Failed to open /dev: %s\n", zx_status_get_string(status));
return zx::error(status);
}
}
return zx::ok(BlockDevices(std::move(devfs_root), Variant::kDevfs));
}
zx::result<BlockDevices> BlockDevices::CreateFromFshostBlockDir(fbl::unique_fd block_dir) {
if (!block_dir) {
if (zx_status_t status =
fdio_open3_fd("/block", static_cast<uint64_t>(fuchsia_io::kPermReadable),
block_dir.reset_and_get_address());
status != ZX_OK) {
ERROR("Failed to open /block: %s\n", zx_status_get_string(status));
return zx::error(status);
}
}
return zx::ok(BlockDevices(std::move(block_dir), Variant::kFshostBlockDir));
}
zx::result<BlockDevices> BlockDevices::CreateFromPartitionService(
fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto [client, server] = std::move(*endpoints);
if (zx_status_t status = fdio_open3_at(
svc_root.handle()->get(), "fuchsia.storage.partitions.PartitionService",
static_cast<uint64_t>(fuchsia_io::wire::kPermReadable), server.TakeChannel().release());
status != ZX_OK) {
ERROR("Failed to open partition service: %s\n", zx_status_get_string(status));
return zx::error(status);
}
fbl::unique_fd partition_dir;
if (zx_status_t status =
fdio_fd_create(client.TakeChannel().release(), partition_dir.reset_and_get_address());
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(BlockDevices(std::move(partition_dir), Variant::kPartitionService));
}
zx::result<BlockDevices> BlockDevices::CreateFromSkipBlockService(
fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto [client, server] = std::move(*endpoints);
if (zx_status_t status = fdio_open3_at(
svc_root.handle()->get(), "fuchsia.hardware.skipblock.Service",
static_cast<uint64_t>(fuchsia_io::wire::kPermReadable), server.TakeChannel().release());
status != ZX_OK) {
ERROR("Failed to open skip block service: %s\n", zx_status_get_string(status));
return zx::error(status);
}
fbl::unique_fd skip_block_dir;
if (zx_status_t status =
fdio_fd_create(client.TakeChannel().release(), skip_block_dir.reset_and_get_address());
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(BlockDevices(std::move(skip_block_dir), Variant::kSkipBlockService));
}
BlockDevices BlockDevices::CreateEmpty() { return BlockDevices({}, {}); }
BlockDevices BlockDevices::Duplicate() const { return BlockDevices(root_.duplicate(), variant_); }
bool BlockDevices::IsStorageHost() const { return variant_ != Variant::kDevfs; }
zx::result<std::vector<std::unique_ptr<VolumeConnector>>> BlockDevices::OpenAllPartitions(
fit::function<bool(const zx::channel&)> filter) const {
return OpenAllPartitionsInner(std::move(filter), /*limit=*/std::numeric_limits<size_t>::max());
}
zx::result<std::unique_ptr<VolumeConnector>> BlockDevices::OpenPartition(
fit::function<bool(const zx::channel&)> filter) const {
zx::result results = OpenAllPartitionsInner(std::move(filter), /*limit=*/1);
if (results.is_error()) {
return results.take_error();
}
if (results->empty()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(std::move(results->front()));
}
zx::result<std::vector<std::unique_ptr<VolumeConnector>>> BlockDevices::OpenAllPartitionsInner(
fit::function<bool(const zx::channel&)> filter, size_t limit) const {
if (!root_) {
return zx::error(ZX_ERR_NOT_FOUND);
}
const char* path = ".";
int parent_fd = root_.get();
if (variant_ == Variant::kDevfs) {
path = "class/block";
}
fbl::unique_fd dir_fd;
if (zx_status_t status =
fdio_open3_fd_at(parent_fd, path, static_cast<uint64_t>(fuchsia_io::kPermReadable),
dir_fd.reset_and_get_address());
status != ZX_OK) {
return zx::error(status);
}
DIR* dir = fdopendir(dir_fd.duplicate().release());
if (dir == nullptr) {
ERROR("Cannot inspect block devices: %s\n", strerror(errno));
return zx::error(ZX_ERR_INTERNAL);
}
const auto closer = fit::defer([dir]() { closedir(dir); });
std::vector<std::unique_ptr<VolumeConnector>> results;
struct dirent* de;
while (results.size() < limit && (de = readdir(dir)) != nullptr) {
if (std::string_view{de->d_name} == ".") {
continue;
}
std::string filename(de->d_name, strnlen(de->d_name, sizeof(de->d_name)));
std::unique_ptr<VolumeConnector> connector;
switch (variant_) {
case Variant::kDevfs: {
zx::result result = CreateDevfsVolumeConnector(dir_fd.get(), filename);
if (result.is_error()) {
return result.take_error();
}
connector = *std::move(result);
break;
}
case Variant::kFshostBlockDir: {
zx::result fd = open_in_dir(dir_fd.get(), filename);
if (fd.is_error()) {
return fd.take_error();
}
connector = std::make_unique<DirBasedVolumeConnector>(
*std::move(fd), std::string("fuchsia.hardware.block.volume.Volume"));
break;
}
case Variant::kPartitionService: {
zx::result fd = open_in_dir(dir_fd.get(), filename);
if (fd.is_error()) {
return fd.take_error();
}
connector = std::make_unique<PartitionServiceBasedVolumeConnector>(*std::move(fd));
break;
}
case Variant::kSkipBlockService: {
zx::result fd = open_in_dir(dir_fd.get(), filename);
if (fd.is_error()) {
return fd.take_error();
}
connector =
std::make_unique<DirBasedVolumeConnector>(*std::move(fd), std::string("skipblock"));
break;
}
}
zx::result partition = connector->Connect();
if (partition.is_error()) {
return partition.take_error();
}
if (filter(partition->channel())) {
results.push_back(std::move(connector));
}
}
return zx::ok(std::move(results));
}
zx::result<std::unique_ptr<VolumeConnector>> BlockDevices::WaitForPartition(
fit::function<bool(const zx::channel&)> filter, zx_duration_t timeout,
const char* devfs_suffix) const {
if (!root_) {
return zx::error(ZX_ERR_NOT_FOUND);
}
struct CallbackInfo {
std::unique_ptr<VolumeConnector> out_partition;
fit::function<bool(const zx::channel&)> filter;
Variant variant;
};
CallbackInfo info = {
.out_partition = {},
.filter = std::move(filter),
.variant = variant_,
};
auto cb = [](int dirfd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if ((strcmp(filename, ".") == 0) || strcmp(filename, "..") == 0) {
return ZX_OK;
}
auto info = static_cast<CallbackInfo*>(cookie);
std::unique_ptr<VolumeConnector> connector;
switch (info->variant) {
case Variant::kDevfs: {
zx::result result = CreateDevfsVolumeConnector(dirfd, filename);
if (result.is_error()) {
return result.status_value();
}
connector = *std::move(result);
break;
}
case Variant::kFshostBlockDir: {
zx::result fd = open_in_dir(dirfd, filename);
if (fd.is_error()) {
return fd.status_value();
}
connector = std::make_unique<DirBasedVolumeConnector>(
*std::move(fd), std::string("fuchsia.hardware.block.volume.Volume"));
break;
}
case Variant::kPartitionService: {
zx::result fd = open_in_dir(dirfd, filename);
if (fd.is_error()) {
return fd.status_value();
}
connector = std::make_unique<PartitionServiceBasedVolumeConnector>(*std::move(fd));
break;
}
case Variant::kSkipBlockService: {
zx::result fd = open_in_dir(dirfd, filename);
if (fd.is_error()) {
return fd.status_value();
}
connector =
std::make_unique<DirBasedVolumeConnector>(*std::move(fd), std::string("skipblock"));
break;
}
}
zx::result volume = connector->Connect();
if (volume.is_error()) {
return volume.status_value();
}
if (!info->filter(volume->channel())) {
// ZX_OK means keep going
return ZX_OK;
}
info->out_partition = std::move(connector);
return ZX_ERR_STOP;
};
fbl::unique_fd dir_fd;
if (variant_ == Variant::kDevfs) {
if (zx_status_t status = fdio_open3_fd_at(root_.get(), devfs_suffix,
static_cast<uint64_t>(fuchsia_io::kPermReadable),
dir_fd.reset_and_get_address());
status != ZX_OK) {
ERROR("Failed to open /dev/%s: %s\n", devfs_suffix, zx_status_get_string(status));
return zx::error(status);
}
} else {
dir_fd = root_.duplicate();
}
zx_time_t deadline = zx_deadline_after(timeout);
if (zx_status_t status = fdio_watch_directory(dir_fd.get(), cb, deadline, &info);
status != ZX_ERR_STOP) {
return zx::error(status);
}
return zx::ok(std::move(info.out_partition));
}
} // namespace paver