| // 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 <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/unique_fd.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/zx/time.h> |
| #include <zircon/device/block.h> |
| #include <zircon/device/ramdisk.h> |
| #include <zircon/device/vfs.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <fs-management/ramdisk.h> |
| |
| #define RAMCTL_PATH "/dev/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(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 (stat(path, &buf) != 0 && (rc = wait_for_device_impl(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 |
| DIR* dir = opendir(path); |
| if (!dir) { |
| fprintf(stderr, "unable to open '%s'\n", path); |
| return ZX_ERR_NOT_FOUND; |
| } |
| auto close_dir = fbl::MakeAutoCall([&] { closedir(dir); }); |
| |
| // Wait for the next path segment to show up |
| rc = fdio_watch_directory(dirfd(dir), 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; |
| } |
| |
| // 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) { |
| 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(tmp, deadline); |
| } |
| |
| static int open_ramctl(void) { |
| int fd = open(RAMCTL_PATH, O_RDWR); |
| if (fd < 0) { |
| fprintf(stderr, "Could not open ramctl\n"); |
| } |
| return fd; |
| } |
| |
| static int finish_create(ramdisk_ioctl_config_response_t* response, char* out_path, ssize_t r) { |
| if (r < 0) { |
| fprintf(stderr, "Could not configure ramdev\n"); |
| return -1; |
| } |
| response->name[r] = 0; |
| |
| char path[PATH_MAX]; |
| auto cleanup = fbl::MakeAutoCall([&path, response]() { |
| snprintf(path, sizeof(path), "%s/%s", RAMCTL_PATH, response->name); |
| destroy_ramdisk(path); |
| }); |
| |
| // The ramdisk should have been created instantly, but it may take |
| // a moment for the block device driver to bind to it. |
| snprintf(path, sizeof(path), "%s/%s/%s", RAMCTL_PATH, response->name, BLOCK_EXTENSION); |
| if (wait_for_device(path, ZX_SEC(3)) != ZX_OK) { |
| fprintf(stderr, "Error waiting for driver\n"); |
| return -1; |
| } |
| |
| // TODO(security): SEC-70. This may overflow |out_path|. |
| strcpy(out_path, path); |
| cleanup.cancel(); |
| return 0; |
| } |
| |
| int create_ramdisk(uint64_t blk_size, uint64_t blk_count, char* out_path) { |
| fbl::unique_fd fd(open_ramctl()); |
| if (fd.get() < 0) { |
| return -1; |
| } |
| ramdisk_ioctl_config_t config = {}; |
| config.blk_size = blk_size; |
| config.blk_count = blk_count; |
| memset(config.type_guid, 0, ZBI_PARTITION_GUID_LEN); |
| ramdisk_ioctl_config_response_t response; |
| return finish_create(&response, out_path, |
| ioctl_ramdisk_config(fd.get(), &config, &response)); |
| } |
| |
| int create_ramdisk_with_guid(uint64_t blk_size, uint64_t blk_count, const uint8_t* type_guid, |
| size_t guid_len, char* out_path) { |
| fbl::unique_fd fd(open_ramctl()); |
| if (fd.get() < 0) { |
| return -1; |
| } |
| if (type_guid == NULL || guid_len < ZBI_PARTITION_GUID_LEN) { |
| return -1; |
| } |
| ramdisk_ioctl_config_t config = {}; |
| config.blk_size = blk_size; |
| config.blk_count = blk_count; |
| memcpy(config.type_guid, type_guid, ZBI_PARTITION_GUID_LEN); |
| ramdisk_ioctl_config_response_t response; |
| return finish_create(&response, out_path, |
| ioctl_ramdisk_config(fd.get(), &config, &response)); |
| } |
| |
| int create_ramdisk_from_vmo(zx_handle_t vmo, char* out_path) { |
| fbl::unique_fd fd(open_ramctl()); |
| if (fd.get() < 0) { |
| return -1; |
| } |
| ramdisk_ioctl_config_response_t response; |
| return finish_create(&response, out_path, |
| ioctl_ramdisk_config_vmo(fd.get(), &vmo, &response)); |
| } |
| |
| int sleep_ramdisk(const char* ramdisk_path, uint64_t block_count) { |
| fbl::unique_fd fd(open(ramdisk_path, O_RDWR)); |
| if (fd.get() < 0) { |
| fprintf(stderr, "Could not open ramdisk\n"); |
| return -1; |
| } |
| |
| ssize_t r = ioctl_ramdisk_sleep_after(fd.get(), &block_count); |
| if (r != ZX_OK) { |
| fprintf(stderr, "Could not set ramdisk interrupt on path %s: %ld\n", ramdisk_path, r); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int wake_ramdisk(const char* ramdisk_path) { |
| fbl::unique_fd fd(open(ramdisk_path, O_RDWR)); |
| if (fd.get() < 0) { |
| fprintf(stderr, "Could not open ramdisk\n"); |
| return -1; |
| } |
| |
| ssize_t r = ioctl_ramdisk_wake_up(fd.get()); |
| if (r != ZX_OK) { |
| fprintf(stderr, "Could not wake ramdisk\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int get_ramdisk_blocks(const char* ramdisk_path, ramdisk_blk_counts_t* counts) { |
| fbl::unique_fd fd(open(ramdisk_path, O_RDWR)); |
| if (fd.get() < 0) { |
| fprintf(stderr, "Could not open ramdisk\n"); |
| return -1; |
| } |
| if (ioctl_ramdisk_get_blk_counts(fd.get(), counts) < 0) { |
| fprintf(stderr, "Could not get blk counts\n"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int destroy_ramdisk(const char* ramdisk_path) { |
| fbl::unique_fd ramdisk(open(ramdisk_path, O_RDWR)); |
| if (!ramdisk) { |
| fprintf(stderr, "Could not open ramdisk\n"); |
| return -1; |
| } |
| ssize_t r = ioctl_ramdisk_unlink(ramdisk.get()); |
| if (r != ZX_OK) { |
| fprintf(stderr, "Could not shut off ramdisk\n"); |
| return -1; |
| } |
| return 0; |
| } |