blob: 18940913a3402153e78deb5ba82eacc3024633f0 [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 <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.hardware.ramdisk/cpp/wire.h>
#include <inttypes.h>
#include <lib/component/incoming/cpp/protocol.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/watcher.h>
#include <lib/fit/defer.h>
#include <lib/zbi-format/partition.h>
#include <lib/zx/channel.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;
}
zx_status_t Rebind() {
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 (zx_status_t status = DestroyByHandle(ramdisk_controller_); status != ZX_OK) {
return status;
}
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;
}
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)) {}
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_;
};
static 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);
}
static zx_status_t ramdisk_create_with_guid_internal(
int dev_root_fd, uint64_t blk_size, uint64_t blk_count,
fidl::ObjectView<fuchsia_hardware_ramdisk::wire::Guid> type_guid, ramdisk_client** out) {
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, 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;
}
__EXPORT
zx_status_t ramdisk_create_at(int dev_root_fd, uint64_t blk_size, uint64_t blk_count,
ramdisk_client** out) {
return ramdisk_create_with_guid_internal(dev_root_fd, blk_size, blk_count, nullptr, out);
}
__EXPORT
zx_status_t ramdisk_create(uint64_t blk_size, uint64_t blk_count, ramdisk_client** out) {
return ramdisk_create_at(/*dev_root_fd=*/-1, blk_size, blk_count, out);
}
__EXPORT
zx_status_t ramdisk_create_with_guid(uint64_t blk_size, uint64_t blk_count,
const uint8_t* type_guid, size_t guid_len,
ramdisk_client** out) {
return ramdisk_create_at_with_guid(/*dev_root_fd=*/-1, blk_size, blk_count, type_guid, guid_len,
out);
}
__EXPORT
zx_status_t ramdisk_create_at_with_guid(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;
}
return ramdisk_create_with_guid_internal(
dev_root_fd, 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))),
out);
}
__EXPORT
zx_status_t ramdisk_create_from_vmo(zx_handle_t raw_vmo, ramdisk_client** out) {
return ramdisk_create_at_from_vmo(/*dev_root_fd=*/-1, raw_vmo, out);
}
__EXPORT
zx_status_t ramdisk_create_from_vmo_with_params(zx_handle_t raw_vmo, uint64_t block_size,
const uint8_t* type_guid, size_t guid_len,
ramdisk_client** out) {
return ramdisk_create_at_from_vmo_with_params(/*dev_root_fd=*/-1, raw_vmo, block_size, type_guid,
guid_len, out);
}
__EXPORT
zx_status_t ramdisk_create_at_from_vmo(int dev_root_fd, zx_handle_t vmo, ramdisk_client** out) {
return ramdisk_create_at_from_vmo_with_params(dev_root_fd, vmo, /*block_size*/ 0,
/*type_guid*/ nullptr, /*guid_len*/ 0, out);
}
__EXPORT
zx_status_t ramdisk_create_at_from_vmo_with_params(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;
}
__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_grow(const ramdisk_client* client, uint64_t required_size) {
const fidl::WireResult result = fidl::WireCall(client->ramdisk_interface())->Grow(required_size);
if (!result.ok()) {
return result.status();
}
if (!result->is_ok()) {
return result->error_value();
}
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;
}