blob: a000129e7b4be679c24ec785570ea66b1527b0bd [file] [log] [blame]
// Copyright 2017 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 <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.hardware.ramdisk/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/watcher.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zbi-format/partition.h>
#include <lib/zx/channel.h>
#include <lib/zx/job.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <memory>
#include <utility>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include <ramdevice-client/ramdisk.h>
constexpr char kRamctlDevPath[] = "/dev";
constexpr char kRamctlPath[] = "sys/platform/ram-disk/ramctl";
constexpr char kBlockExtension[] = "block";
constexpr zx::duration kDeviceWaitTime = zx::sec(10);
struct ramdisk_client {
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(ramdisk_client);
static zx_status_t Create(int dev_root_fd, std::string_view instance_name, zx::duration duration,
std::unique_ptr<ramdisk_client>* out) {
fbl::String ramdisk_path = fbl::String::Concat({kRamctlPath, "/", instance_name});
fbl::String block_path = fbl::String::Concat({ramdisk_path, "/", kBlockExtension});
fbl::String path;
fbl::unique_fd dirfd;
if (dev_root_fd > -1) {
dirfd.reset(dup(dev_root_fd));
path = block_path;
} else {
dirfd.reset(open(kRamctlDevPath, O_RDONLY | O_DIRECTORY));
path = fbl::String::Concat({kRamctlDevPath, "/", block_path});
}
if (!dirfd) {
return ZX_ERR_BAD_STATE;
}
fdio_cpp::UnownedFdioCaller caller(dirfd);
zx::result channel =
device_watcher::RecursiveWaitForFile(dirfd.get(), ramdisk_path.c_str(), duration);
if (channel.is_error()) {
return channel.status_value();
}
fidl::ClientEnd ramdisk_interface =
fidl::ClientEnd<fuchsia_hardware_ramdisk::Ramdisk>(std::move(channel.value()));
std::string controller_path = std::string(ramdisk_path.c_str()) + "/device_controller";
zx::result ramdisk_controller =
device_watcher::RecursiveWaitForFile(dirfd.get(), controller_path.c_str(), duration);
if (ramdisk_controller.is_error()) {
return ramdisk_controller.error_value();
}
fidl::ClientEnd ramdisk_controller_interface =
fidl::ClientEnd<fuchsia_device::Controller>(std::move(ramdisk_controller.value()));
// If binding to the block interface fails, ensure we still try to tear down the
// ramdisk driver.
auto cleanup = fit::defer([&ramdisk_controller_interface]() {
ramdisk_client::DestroyByHandle(ramdisk_controller_interface);
});
channel = device_watcher::RecursiveWaitForFile(dirfd.get(), block_path.c_str(), duration);
if (channel.is_error()) {
return channel.error_value();
}
controller_path = std::string(block_path.c_str()) + "/device_controller";
zx::result controller =
device_watcher::RecursiveWaitForFile(dirfd.get(), controller_path.c_str(), duration);
if (controller.is_error()) {
return controller.error_value();
}
cleanup.cancel();
*out = std::unique_ptr<ramdisk_client>(new ramdisk_client(
std::move(dirfd), std::move(path), std::move(block_path), std::move(ramdisk_interface),
std::move(ramdisk_controller_interface),
fidl::ClientEnd<fuchsia_hardware_block::Block>{std::move(channel.value())},
fidl::ClientEnd<fuchsia_device::Controller>(std::move(controller.value()))));
return ZX_OK;
}
static zx_status_t CreateV2(fidl::ClientEnd<fuchsia_io::Directory> outgoing_directory,
zx::eventpair lifeline, std::unique_ptr<ramdisk_client>* out) {
auto [svc_dir, server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
if (zx_status_t status = fdio_open3_at(outgoing_directory.channel().get(), "svc",
uint64_t{fuchsia_io::wire::kPermReadable},
server.TakeChannel().release());
status != ZX_OK)
return status;
zx::result ramdisk_interface = component::ConnectAt<fuchsia_hardware_ramdisk::Ramdisk>(svc_dir);
if (ramdisk_interface.is_error())
return ramdisk_interface.status_value();
zx::result volume_interface =
component::ConnectAt<fuchsia_hardware_block_volume::Volume>(svc_dir);
if (volume_interface.is_error())
return volume_interface.status_value();
fidl::ClientEnd<fuchsia_hardware_block::Block> block_interface(volume_interface->TakeChannel());
// Bind the outgoing directory to the namespace.
static std::atomic<int> counter;
// This deliberately binds to the top-level because there is/was watcher code that only worked
// at the top-level of the local namespace (opening intermediate local directories is not
// supported).
std::string bind_path = "/ramdisk-" + std::to_string(counter++);
fdio_ns_t* ns;
if (zx_status_t status = fdio_ns_get_installed(&ns); status != ZX_OK)
return status;
if (zx_status_t status =
fdio_ns_bind(ns, bind_path.c_str(), outgoing_directory.TakeHandle().release());
status != ZX_OK)
return status;
fbl::String ram_disk_path = bind_path + "/svc/fuchsia.hardware.block.volume.Volume";
*out = std::unique_ptr<ramdisk_client>(new ramdisk_client(
std::move(bind_path), std::move(ram_disk_path), *std::move(ramdisk_interface),
std::move(block_interface), std::move(lifeline)));
return ZX_OK;
}
zx_status_t Rebind() {
if (!block_controller_)
return ZX_ERR_NOT_SUPPORTED;
const fidl::WireResult result = fidl::WireCall(block_controller_)->Rebind({});
if (!result.ok()) {
return result.status();
}
const fit::result response = result.value();
if (response.is_error()) {
return response.error_value();
}
ramdisk_interface_.reset();
// Ramdisk paths have the form: /dev/.../ramctl/ramdisk-xxx/block.
// To rebind successfully, first, we rebind the "ramdisk-xxx" path,
// and then we wait for "block" to rebind.
char ramdisk_path[PATH_MAX];
// Wait for the "ramdisk-xxx" path to rebind.
{
const char* sep = strrchr(relative_path_.c_str(), '/');
strlcpy(ramdisk_path, relative_path_.c_str(), sep - relative_path_.c_str() + 1);
zx::result channel =
device_watcher::RecursiveWaitForFile(dev_root_fd_.get(), ramdisk_path, kDeviceWaitTime);
if (channel.is_error()) {
return channel.error_value();
}
ramdisk_interface_.channel() = std::move(channel.value());
}
// Wait for the "block" path to rebind.
strlcpy(ramdisk_path, relative_path_.c_str(), sizeof(ramdisk_path));
zx::result channel =
device_watcher::RecursiveWaitForFile(dev_root_fd_.get(), ramdisk_path, kDeviceWaitTime);
if (channel.is_error()) {
return channel.error_value();
}
block_interface_.channel() = std::move(channel.value());
return ZX_OK;
}
zx_status_t Destroy() {
if (!ramdisk_interface_) {
return ZX_ERR_BAD_STATE;
}
if (ramdisk_controller_) {
if (zx_status_t status = DestroyByHandle(ramdisk_controller_); status != ZX_OK) {
return status;
}
}
if (!bind_path_.empty()) {
fdio_ns_t* ns;
if (fdio_ns_get_installed(&ns) == ZX_OK)
fdio_ns_unbind(ns, bind_path_.c_str());
bind_path_.clear();
}
block_interface_.reset();
return ZX_OK;
}
// Destroy all open handles to the ramdisk, while leaving the ramdisk itself attached.
// After calling this method, destroying this object will have no effect.
zx_status_t Forget() {
if (!ramdisk_interface_) {
return ZX_ERR_BAD_STATE;
}
ramdisk_interface_.reset();
block_interface_.reset();
return ZX_OK;
}
zx::result<fidl::ClientEnd<fuchsia_hardware_block::Block>> Connect() const {
if (block_controller_) {
auto channel = device_watcher::RecursiveWaitForFile(dev_root_fd_.get(),
relative_path_.c_str(), kDeviceWaitTime);
if (channel.is_error()) {
return channel.take_error();
}
return zx::ok(fidl::ClientEnd<fuchsia_hardware_block::Block>(std::move(channel).value()));
}
return component::Connect<fuchsia_hardware_block::Block>(path());
}
fidl::UnownedClientEnd<fuchsia_device::Controller> controller_interface() const {
return ramdisk_controller_;
}
fidl::UnownedClientEnd<fuchsia_hardware_ramdisk::Ramdisk> ramdisk_interface() const {
return ramdisk_interface_.borrow();
}
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> block_interface() const {
return block_interface_.borrow();
}
fidl::UnownedClientEnd<fuchsia_device::Controller> block_controller_interface() const {
return block_controller_.borrow();
}
const fbl::String& path() const { return path_; }
~ramdisk_client() { Destroy(); }
private:
ramdisk_client(fbl::unique_fd dev_root_fd, fbl::String path, fbl::String relative_path,
fidl::ClientEnd<fuchsia_hardware_ramdisk::Ramdisk> ramdisk_interface,
fidl::ClientEnd<fuchsia_device::Controller> ramdisk_controller,
fidl::ClientEnd<fuchsia_hardware_block::Block> block_interface,
fidl::ClientEnd<fuchsia_device::Controller> block_controller)
: dev_root_fd_(std::move(dev_root_fd)),
path_(std::move(path)),
relative_path_(std::move(relative_path)),
ramdisk_interface_(std::move(ramdisk_interface)),
ramdisk_controller_(std::move(ramdisk_controller)),
block_interface_(std::move(block_interface)),
block_controller_(std::move(block_controller)) {}
ramdisk_client(std::string bind_path, fbl::String path,
fidl::ClientEnd<fuchsia_hardware_ramdisk::Ramdisk> ramdisk_interface,
fidl::ClientEnd<fuchsia_hardware_block::Block> block_interface,
zx::eventpair lifeline)
: path_(std::move(path)),
ramdisk_interface_(std::move(ramdisk_interface)),
block_interface_(std::move(block_interface)),
lifeline_(std::move(lifeline)),
bind_path_(std::move(bind_path)) {}
static zx_status_t DestroyByHandle(fidl::ClientEnd<fuchsia_device::Controller>& ramdisk) {
const fidl::WireResult result = fidl::WireCall(ramdisk)->ScheduleUnbind();
if (!result.ok()) {
return result.status();
}
const fit::result response = result.value();
if (response.is_error()) {
return response.error_value();
}
return ZX_OK;
}
const fbl::unique_fd dev_root_fd_;
// The fully qualified path.
const fbl::String path_;
// The path relative to dev_root_fd_.
const fbl::String relative_path_;
fidl::ClientEnd<fuchsia_hardware_ramdisk::Ramdisk> ramdisk_interface_;
fidl::ClientEnd<fuchsia_device::Controller> ramdisk_controller_;
fidl::ClientEnd<fuchsia_hardware_block::Block> block_interface_;
fidl::ClientEnd<fuchsia_device::Controller> block_controller_;
// v2 only:
zx::eventpair lifeline_;
std::string bind_path_;
};
namespace {
zx::result<fidl::ClientEnd<fuchsia_hardware_ramdisk::RamdiskController>> open_ramctl(
int dev_root_fd) {
fbl::unique_fd dirfd;
if (dev_root_fd > -1) {
dirfd.reset(dup(dev_root_fd));
} else {
dirfd.reset(open(kRamctlDevPath, O_RDONLY | O_DIRECTORY));
}
if (!dirfd) {
return zx::error(ZX_ERR_BAD_STATE);
}
fdio_cpp::FdioCaller caller(std::move(dirfd));
return component::ConnectAt<fuchsia_hardware_ramdisk::RamdiskController>(caller.directory(),
kRamctlPath);
}
zx_status_t ramdisk_create_legacy(int dev_root_fd, uint64_t blk_size, uint64_t blk_count,
const uint8_t* type_guid, size_t guid_len, ramdisk_client** out) {
if (type_guid == nullptr || guid_len < ZBI_PARTITION_GUID_LEN) {
return ZX_ERR_INVALID_ARGS;
}
zx::result ramctl = open_ramctl(dev_root_fd);
if (ramctl.is_error()) {
return ramctl.status_value();
}
const fidl::WireResult wire_result =
fidl::WireCall(ramctl.value())
->Create(blk_size, blk_count,
fidl::ObjectView<fuchsia_hardware_ramdisk::wire::Guid>::FromExternal(
reinterpret_cast<fuchsia_hardware_ramdisk::wire::Guid*>(
const_cast<uint8_t*>(type_guid))));
if (!wire_result.ok()) {
return wire_result.status();
}
const fit::result result = wire_result.value();
if (result.is_error()) {
return result.error_value();
}
std::unique_ptr<ramdisk_client> client;
if (zx_status_t status =
ramdisk_client::Create(dev_root_fd, result->name.get(), kDeviceWaitTime, &client);
status != ZX_OK) {
return status;
}
*out = client.release();
return ZX_OK;
}
zx_status_t ramdisk_create_legacy_with_vmo(int dev_root_fd, zx_handle_t raw_vmo,
uint64_t block_size, const uint8_t* type_guid,
size_t guid_len, ramdisk_client** out) {
if (type_guid == nullptr || guid_len < ZBI_PARTITION_GUID_LEN) {
return ZX_ERR_INVALID_ARGS;
}
zx::vmo vmo(raw_vmo);
zx::result ramctl = open_ramctl(dev_root_fd);
if (ramctl.is_error()) {
return ramctl.status_value();
}
const fidl::WireResult wire_result =
fidl::WireCall(ramctl.value())
->CreateFromVmoWithParams(
std::move(vmo), block_size,
fidl::ObjectView<fuchsia_hardware_ramdisk::wire::Guid>::FromExternal(
reinterpret_cast<fuchsia_hardware_ramdisk::wire::Guid*>(
const_cast<uint8_t*>(type_guid))));
if (!wire_result.ok()) {
return wire_result.status();
}
const fit::result result = wire_result.value();
if (result.is_error()) {
return result.error_value();
}
std::unique_ptr<ramdisk_client> client;
if (zx_status_t status =
ramdisk_client::Create(dev_root_fd, result->name.get(), kDeviceWaitTime, &client);
status != ZX_OK) {
return status;
}
*out = client.release();
return ZX_OK;
}
} // namespace
__EXPORT zx_handle_t ramdisk_get_block_interface(const ramdisk_client_t* client) {
return client->block_interface().channel()->get();
}
__EXPORT zx_handle_t ramdisk_get_block_controller_interface(const ramdisk_client_t* client) {
return client->block_controller_interface().channel()->get();
}
__EXPORT const char* ramdisk_get_path(const ramdisk_client_t* client) {
return client->path().c_str();
}
__EXPORT zx_status_t ramdisk_sleep_after(const ramdisk_client* client, uint64_t block_count) {
const fidl::WireResult result =
fidl::WireCall(client->ramdisk_interface())->SleepAfter(block_count);
if (!result.ok()) {
return result.status();
}
return ZX_OK;
}
__EXPORT zx_status_t ramdisk_wake(const ramdisk_client* client) {
const fidl::WireResult result = fidl::WireCall(client->ramdisk_interface())->Wake();
if (!result.ok()) {
return result.status();
}
return ZX_OK;
}
__EXPORT zx_status_t ramdisk_set_flags(const ramdisk_client* client, uint32_t flags) {
const fidl::WireResult result =
fidl::WireCall(client->ramdisk_interface())
->SetFlags(static_cast<fuchsia_hardware_ramdisk::wire::RamdiskFlag>(flags));
if (!result.ok()) {
return result.status();
}
return ZX_OK;
}
__EXPORT zx_status_t ramdisk_get_block_counts(const ramdisk_client* client,
ramdisk_block_write_counts_t* out_counts) {
static_assert(sizeof(ramdisk_block_write_counts_t) ==
sizeof(fuchsia_hardware_ramdisk::wire::BlockWriteCounts),
"Cannot convert between C library / FIDL block counts");
const fidl::WireResult result = fidl::WireCall(client->ramdisk_interface())->GetBlockCounts();
if (!result.ok()) {
return result.status();
}
const fidl::WireResponse response = result.value();
memcpy(out_counts, &response.counts, sizeof(ramdisk_block_write_counts_t));
return ZX_OK;
}
__EXPORT zx_status_t ramdisk_rebind(ramdisk_client_t* client) { return client->Rebind(); }
__EXPORT zx_status_t ramdisk_destroy(ramdisk_client* client) {
zx_status_t status = client->Destroy();
delete client;
return status;
}
__EXPORT zx_status_t ramdisk_forget(ramdisk_client* client) {
zx_status_t status = client->Forget();
delete client;
return status;
}
__EXPORT zx_status_t ramdisk_create_with_options(const ramdisk_options* options,
ramdisk_client_t** out) {
zx::vmo vmo(options->vmo);
if (!options->v2) {
static constexpr uint8_t null_type_guid[16] = {0};
const uint8_t* type_guid = options->type_guid ? options->type_guid : null_type_guid;
if (vmo.is_valid()) {
return ramdisk_create_legacy_with_vmo(options->devfs_root_fd, vmo.get(), options->block_size,
type_guid, 16, out);
}
return ramdisk_create_legacy(options->devfs_root_fd, options->block_size, options->block_count,
type_guid, 16, out);
}
fbl::unique_fd dir(
options->svc_root_fd > 0
? (openat(options->svc_root_fd, "fuchsia.hardware.ramdisk.Service", O_RDONLY))
: open("/svc/fuchsia.hardware.ramdisk.Service", O_RDONLY));
if (!dir)
return ZX_ERR_NOT_FOUND;
std::string instance_name;
zx_status_t status = fdio_watch_directory(
dir.get(),
[](int, int event, const char* name, void* cookie) {
if (event == WATCH_EVENT_ADD_FILE && strcmp(name, ".") != 0) {
*reinterpret_cast<std::string*>(cookie) = name;
return ZX_ERR_STOP;
}
return ZX_OK;
},
ZX_TIME_INFINITE, &instance_name);
if (status != ZX_ERR_STOP)
return status == ZX_OK ? ZX_ERR_NOT_FOUND : status;
zx::result<fuchsia_hardware_ramdisk::Service::ServiceClient> service;
if (options->svc_root_fd > 0) {
fdio_cpp::UnownedFdioCaller caller(options->svc_root_fd);
service = component::OpenServiceAt<fuchsia_hardware_ramdisk::Service>(caller.directory(),
instance_name);
} else {
service = component::OpenService<fuchsia_hardware_ramdisk::Service>(instance_name);
}
if (service.is_error())
return service.status_value();
zx::result controller = service->connect_controller();
if (controller.is_error())
return controller.status_value();
fidl::Arena arena;
fuchsia_hardware_ramdisk::wire::Guid guid;
auto fidl_options = fuchsia_hardware_ramdisk::wire::Options::Builder(arena);
if (options->block_size > 0)
fidl_options.block_size(options->block_size);
if (options->block_count > 0)
fidl_options.block_count(options->block_count);
if (options->type_guid) {
memcpy(&guid.value.data_, options->type_guid, 16);
fidl_options.type_guid(guid);
ZX_ASSERT(fidl_options.has_type_guid());
}
if (vmo.is_valid())
fidl_options.vmo(std::move(vmo));
fidl_options.publish(true);
const fidl::WireResult result = fidl::WireCall(*controller)->Create(fidl_options.Build());
if (!result.ok())
return result.status();
fit::result response = result.value();
if (response.is_error())
return response.error_value();
std::unique_ptr<ramdisk_client> client;
if (zx_status_t status = ramdisk_client::CreateV2(std::move(response->outgoing),
std::move(response->lifeline), &client);
status != ZX_OK) {
return status;
}
*out = client.release();
return ZX_OK;
}
namespace ramdevice_client {
zx::result<Ramdisk> Ramdisk::Create(int block_size, uint64_t block_count,
std::optional<int> svc_root_fd,
const Ramdisk::Options& options) {
ramdisk_client_t* client;
ramdisk_options_t ramdisk_options{
.block_size = static_cast<uint32_t>(block_size),
.block_count = block_count,
.type_guid = options.type_guid ? options.type_guid->data() : nullptr,
.v2 = true,
.svc_root_fd = svc_root_fd ? *svc_root_fd : -1,
};
if (zx::result<> result = zx::make_result(ramdisk_create_with_options(&ramdisk_options, &client));
result.is_error()) {
FX_LOGS(ERROR) << "Could not create ramdisk for test: " << result.status_string();
return result.take_error();
}
return zx::ok(Ramdisk(client));
}
zx::result<Ramdisk> Ramdisk::CreateLegacy(int block_size, uint64_t block_count,
std::optional<int> devfs_root_fd,
const Ramdisk::Options& options) {
ramdisk_client_t* client;
ramdisk_options_t ramdisk_options{
.block_size = static_cast<uint32_t>(block_size),
.block_count = block_count,
.type_guid = options.type_guid ? options.type_guid->data() : nullptr,
.v2 = false,
.devfs_root_fd = devfs_root_fd ? *devfs_root_fd : -1,
};
if (zx::result<> result = zx::make_result(ramdisk_create_with_options(&ramdisk_options, &client));
result.is_error()) {
FX_LOGS(ERROR) << "Could not create ramdisk for test: " << result.status_string();
return result.take_error();
}
return zx::ok(Ramdisk(client));
}
zx::result<Ramdisk> Ramdisk::CreateWithVmo(zx::vmo vmo, uint64_t block_size,
std::optional<int> svc_root_fd,
const Ramdisk::Options& options) {
ramdisk_client_t* client;
ramdisk_options_t ramdisk_options{
.block_size = static_cast<uint32_t>(block_size),
.type_guid = options.type_guid ? options.type_guid->data() : nullptr,
.vmo = vmo.release(),
.v2 = true,
.svc_root_fd = svc_root_fd ? *svc_root_fd : -1,
};
if (zx::result result = zx::make_result(ramdisk_create_with_options(&ramdisk_options, &client));
result.is_error()) {
FX_LOGS(ERROR) << "Could not create ramdisk for test: " << result.status_string();
return result.take_error();
}
return zx::ok(Ramdisk(client));
}
zx::result<Ramdisk> Ramdisk::CreateLegacyWithVmo(zx::vmo vmo, uint64_t block_size,
std::optional<int> devfs_root_fd,
const Ramdisk::Options& options) {
ramdisk_client_t* client;
ramdisk_options_t ramdisk_options{
.block_size = static_cast<uint32_t>(block_size),
.type_guid = options.type_guid ? options.type_guid->data() : nullptr,
.vmo = vmo.release(),
.v2 = false,
.devfs_root_fd = devfs_root_fd ? *devfs_root_fd : -1,
};
if (zx::result result = zx::make_result(ramdisk_create_with_options(&ramdisk_options, &client));
result.is_error()) {
FX_LOGS(ERROR) << "Could not create ramdisk for test: " << result.status_string();
return result.take_error();
}
return zx::ok(Ramdisk(client));
}
zx::result<fidl::ClientEnd<fuchsia_hardware_block::Block>> Ramdisk::ConnectBlock() const {
return client_->Connect();
}
fidl::UnownedClientEnd<fuchsia_device::Controller> Ramdisk::LegacyController() const {
return client_->block_controller_interface();
}
} // namespace ramdevice_client