blob: 58352cbea0cf3a6a6cd873d7735934a1a95e7518 [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 <memory>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fbl/auto_call.h>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include <fuchsia/device/c/fidl.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <fuchsia/hardware/ramdisk/c/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/watcher.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <zircon/boot/image.h>
#include <zircon/device/block.h>
#include <zircon/device/vfs.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <ramdevice-client/ramdisk.h>
#define RAMCTL_DEV_PATH "/dev"
#define RAMCTL_PATH "misc/ramctl"
#define BLOCK_EXTENSION "block"
static zx_status_t driver_watcher_cb(int dirfd, int event, const char* fn, void* cookie) {
char* wanted = static_cast<char*>(cookie);
if (event == WATCH_EVENT_ADD_FILE && strcmp(fn, wanted) == 0) {
return ZX_ERR_STOP;
}
return ZX_OK;
}
static zx_status_t wait_for_device_impl(int dir_fd, char* path, const zx::time& deadline) {
zx_status_t rc;
// Peel off last path segment
char* sep = strrchr(path, '/');
if (path[0] == '\0' || (!sep)) {
fprintf(stderr, "invalid device path '%s'\n", path);
return ZX_ERR_BAD_PATH;
}
char* last = sep + 1;
*sep = '\0';
auto restore_path = fbl::MakeAutoCall([sep] { *sep = '/'; });
// Recursively check the path up to this point
struct stat buf;
if (fstatat(dir_fd, path, &buf, 0) != 0 &&
(rc = wait_for_device_impl(dir_fd, path, deadline)) != ZX_OK) {
fprintf(stderr, "failed to bind '%s': %s\n", path, zx_status_get_string(rc));
return rc;
}
// Early exit if this segment is empty
if (last[0] == '\0') {
return ZX_OK;
}
// Open the parent directory
fbl::unique_fd parent_dir(openat(dir_fd, path, O_RDONLY | O_DIRECTORY));
if (!parent_dir) {
fprintf(stderr, "unable to open '%s'\n", path);
return ZX_ERR_NOT_FOUND;
}
// Wait for the next path segment to show up
rc = fdio_watch_directory(parent_dir.get(), driver_watcher_cb, deadline.get(), last);
if (rc != ZX_ERR_STOP) {
fprintf(stderr, "error when waiting for '%s': %s\n", last, zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t wait_for_device_at(int dirfd, const char* path, zx_duration_t timeout) {
if (!path || timeout == 0) {
fprintf(stderr, "invalid args: path='%s', timeout=%" PRIu64 "\n", path, timeout);
return ZX_ERR_INVALID_ARGS;
}
// Make a mutable copy
char tmp[PATH_MAX];
snprintf(tmp, sizeof(tmp), "%s", path);
zx::time deadline = zx::deadline_after(zx::duration(timeout));
return wait_for_device_impl(dirfd, tmp, deadline);
}
struct ramdisk_client {
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(ramdisk_client);
static zx_status_t Create(int dev_root_fd, const char* instance_name,
zx::duration duration,
std::unique_ptr<ramdisk_client>* out) {
fbl::String ramdisk_path = fbl::StringPrintf("%s/%s", RAMCTL_PATH, instance_name);
fbl::String block_path = fbl::String::Concat({ramdisk_path, "/", BLOCK_EXTENSION});
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(RAMCTL_DEV_PATH, O_RDONLY | O_DIRECTORY));
path = fbl::String::Concat({RAMCTL_DEV_PATH, "/", block_path});
}
if (!dirfd) {
return ZX_ERR_BAD_STATE;
}
fbl::unique_fd ramdisk_fd(openat(dirfd.get(), ramdisk_path.c_str(), O_RDWR));
if (!ramdisk_fd) {
return ZX_ERR_BAD_STATE;
}
zx_handle_t ramdisk_interface_raw;
zx_status_t status = fdio_get_service_handle(ramdisk_fd.release(), &ramdisk_interface_raw);
if (status != ZX_OK) {
return status;
}
zx::channel ramdisk_interface(ramdisk_interface_raw);
// If binding to the block interface fails, ensure we still try to tear down the
// ramdisk driver.
auto cleanup = fbl::MakeAutoCall([&ramdisk_interface]() {
ramdisk_client::DestroyByHandle(std::move(ramdisk_interface));
});
status = wait_for_device_at(dirfd.get(), block_path.c_str(), duration.get());
if (status != ZX_OK) {
return status;
}
fbl::unique_fd block_fd(openat(dirfd.get(), block_path.c_str(), O_RDWR));
if (!block_fd) {
return ZX_ERR_BAD_STATE;
}
cleanup.cancel();
*out = std::unique_ptr<ramdisk_client>(
new ramdisk_client(std::move(path), std::move(block_path),
std::move(ramdisk_interface), std::move(dirfd),
std::move(block_fd)));
return ZX_OK;
}
zx_status_t Rebind() {
fzl::FdioCaller disk_client(std::move(block_fd_));
zx_status_t io_status, status;
io_status = fuchsia_hardware_block_BlockRebindDevice(disk_client.borrow_channel(),
&status);
if (io_status != ZX_OK) {
return io_status;
} else if (status != ZX_OK) {
return status;
}
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.
// Wait for the "ramdisk-xxx" path to rebind.
const char* sep = strrchr(relative_path_.c_str(), '/');
char ramdisk_path[PATH_MAX];
strlcpy(ramdisk_path, relative_path_.c_str(), sep - relative_path_.c_str() + 1);
status = wait_for_device_impl(dev_root_fd_.get(), ramdisk_path,
zx::deadline_after(zx::sec(3)));
if (status != ZX_OK) {
return status;
}
fbl::unique_fd ramdisk_fd(openat(dev_root_fd_.get(), ramdisk_path, O_RDWR));
if (!ramdisk_fd) {
return ZX_ERR_BAD_STATE;
}
zx_handle_t ramdisk_interface;
status = fdio_get_service_handle(ramdisk_fd.release(), &ramdisk_interface);
if (status != ZX_OK) {
return status;
}
ramdisk_interface_.reset(ramdisk_interface);
// Wait for the "block" path to rebind.
strlcpy(ramdisk_path, relative_path_.c_str(), sizeof(ramdisk_path));
status = wait_for_device_impl(dev_root_fd_.get(), ramdisk_path,
zx::deadline_after(zx::sec(3)));
if (status != ZX_OK) {
return status;
}
block_fd_.reset(openat(dev_root_fd_.get(), relative_path_.c_str(), O_RDWR));
if (!block_fd_) {
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
}
zx_status_t Destroy() {
if (!ramdisk_interface_) {
return ZX_ERR_BAD_STATE;
}
zx_status_t status = DestroyByHandle(std::move(ramdisk_interface_));
if (status != ZX_OK) {
return status;
}
block_fd_.reset();
return ZX_OK;
}
const zx::channel& ramdisk_interface() const { return ramdisk_interface_; }
const fbl::unique_fd& block_fd() const { return block_fd_; }
const fbl::String& path() const { return path_; }
~ramdisk_client() { Destroy(); }
private:
ramdisk_client(fbl::String path, fbl::String relative_path, zx::channel ramdisk_interface,
fbl::unique_fd dev_root_fd, fbl::unique_fd block_fd)
: path_(std::move(path)), relative_path_(relative_path),
ramdisk_interface_(std::move(ramdisk_interface)), dev_root_fd_(std::move(dev_root_fd)),
block_fd_(std::move(block_fd)) {}
static zx_status_t DestroyByHandle(zx::channel ramdisk) {
zx_status_t call_status;
zx_status_t status = fuchsia_device_ControllerUnbind(ramdisk.get(), &call_status);
if (status != ZX_OK) {
return status;
}
return call_status;
}
// The fully qualified path.
fbl::String path_;
// The path relative to dev_root_fd_.
fbl::String relative_path_;
zx::channel ramdisk_interface_;
fbl::unique_fd dev_root_fd_;
fbl::unique_fd block_fd_;
};
// TODO(aarongreen): This is more generic than just fs-management, or even block devices. Move this
// (and its tests) out of ramdisk and to somewhere else?
zx_status_t wait_for_device(const char* path, zx_duration_t timeout) {
return wait_for_device_at(/*dirfd=*/-1, path, timeout);
}
static zx_status_t open_ramctl(int dev_root_fd, zx::channel* out_ramctl) {
fbl::unique_fd dirfd;
if (dev_root_fd > -1) {
dirfd.reset(dup(dev_root_fd));
} else {
dirfd.reset(open(RAMCTL_DEV_PATH, O_RDONLY | O_DIRECTORY));
}
if (!dirfd) {
return ZX_ERR_BAD_STATE;
}
fbl::unique_fd fd(openat(dirfd.get(), RAMCTL_PATH, O_RDWR));
if (!fd) {
return ZX_ERR_BAD_STATE;
}
zx_handle_t ramctl_interface_raw;
zx_status_t status = fdio_get_service_handle(fd.release(), &ramctl_interface_raw);
if (status != ZX_OK) {
return status;
}
out_ramctl->reset(ramctl_interface_raw);
return ZX_OK;
}
static const fuchsia_hardware_ramdisk_GUID* fidl_guid(const uint8_t* type_guid) {
static_assert(sizeof(fuchsia_hardware_ramdisk_GUID) == ZBI_PARTITION_GUID_LEN,
"Byte array cannot be reinterpreted as FIDL GUID");
return reinterpret_cast<const fuchsia_hardware_ramdisk_GUID*>(type_guid);
}
static zx_status_t ramdisk_create_with_guid_internal(int dev_root_fd, uint64_t blk_size,
uint64_t blk_count, const uint8_t* type_guid,
ramdisk_client** out) {
zx::channel ramctl;
zx_status_t status = open_ramctl(dev_root_fd, &ramctl);
if (status != ZX_OK) {
return status;
}
char name[fuchsia_hardware_ramdisk_MAX_NAME_LENGTH + 1];
size_t name_len = 0;
zx_status_t io_status = fuchsia_hardware_ramdisk_RamdiskControllerCreate(
ramctl.get(), blk_size, blk_count, fidl_guid(type_guid), &status, name, sizeof(name) - 1,
&name_len);
if (io_status != ZX_OK) {
return io_status;
} else if (status != ZX_OK) {
return status;
}
// Always force 'name' to be null-terminated.
name[name_len] = '\0';
std::unique_ptr<ramdisk_client> client;
status = ramdisk_client::Create(dev_root_fd, name, zx::sec(3), &client);
if (status != ZX_OK) {
return status;
}
*out = client.release();
return ZX_OK;
}
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);
}
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);
}
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);
}
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, type_guid, out);
}
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);
}
zx_status_t ramdisk_create_at_from_vmo(int dev_root_fd, zx_handle_t raw_vmo, ramdisk_client** out) {
zx::vmo vmo(raw_vmo);
zx::channel ramctl;
zx_status_t status = open_ramctl(dev_root_fd, &ramctl);
if (status != ZX_OK) {
return status;
}
char name[fuchsia_hardware_ramdisk_MAX_NAME_LENGTH + 1];
size_t name_len = 0;
zx_status_t io_status = fuchsia_hardware_ramdisk_RamdiskControllerCreateFromVmo(
ramctl.get(), vmo.release(), &status, name, sizeof(name) - 1, &name_len);
if (io_status != ZX_OK) {
return io_status;
} else if (status != ZX_OK) {
return status;
}
// Always force 'name' to be null-terminated.
name[name_len] = '\0';
std::unique_ptr<ramdisk_client> client;
status = ramdisk_client::Create(dev_root_fd, name, zx::sec(3), &client);
if (status != ZX_OK) {
return status;
}
*out = client.release();
return ZX_OK;
}
int ramdisk_get_block_fd(const ramdisk_client_t* client) {
return client->block_fd().get();
}
const char* ramdisk_get_path(const ramdisk_client_t* client) {
return client->path().c_str();
}
zx_status_t ramdisk_sleep_after(const ramdisk_client* client, uint64_t block_count) {
zx_status_t status;
zx_status_t io_status = fuchsia_hardware_ramdisk_RamdiskSleepAfter(
client->ramdisk_interface().get(), block_count, &status);
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
zx_status_t ramdisk_wake(const ramdisk_client* client) {
zx_status_t status;
zx_status_t io_status =
fuchsia_hardware_ramdisk_RamdiskWake(client->ramdisk_interface().get(), &status);
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
zx_status_t ramdisk_grow(const ramdisk_client* client, uint64_t required_size) {
zx_status_t status;
zx_status_t io_status = fuchsia_hardware_ramdisk_RamdiskGrow(
client->ramdisk_interface().get(), required_size, &status);
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
zx_status_t ramdisk_set_flags(const ramdisk_client* client, uint32_t flags) {
zx_status_t status;
zx_status_t io_status =
fuchsia_hardware_ramdisk_RamdiskSetFlags(client->ramdisk_interface().get(), flags, &status);
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
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_BlockWriteCounts),
"Cannot convert between C library / FIDL block counts");
zx_status_t status;
zx_status_t io_status = fuchsia_hardware_ramdisk_RamdiskGetBlockCounts(
client->ramdisk_interface().get(), &status,
reinterpret_cast<fuchsia_hardware_ramdisk_BlockWriteCounts*>(out_counts));
if (io_status != ZX_OK) {
return io_status;
}
return status;
}
zx_status_t ramdisk_rebind(ramdisk_client_t* client) {
return client->Rebind();
}
zx_status_t ramdisk_destroy(ramdisk_client* client) {
zx_status_t status = client->Destroy();
delete client;
return status;
}