| // 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; |
| } |