1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <dirent.h>
6#include <errno.h>
7#include <fcntl.h>
8#include <inttypes.h>
9#include <stdbool.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/stat.h>
14#include <sys/types.h>
15#include <unistd.h>
16
17#include <fbl/auto_call.h>
18#include <fbl/unique_fd.h>
19#include <lib/fdio/watcher.h>
20#include <lib/zx/time.h>
21#include <zircon/device/block.h>
22#include <zircon/device/ramdisk.h>
23#include <zircon/device/vfs.h>
24#include <zircon/process.h>
25#include <zircon/status.h>
26#include <zircon/syscalls.h>
27#include <zircon/types.h>
28
29#include <fs-management/ramdisk.h>
30
31#define RAMCTL_PATH "/dev/misc/ramctl"
32#define BLOCK_EXTENSION "block"
33
34static zx_status_t driver_watcher_cb(int dirfd, int event, const char* fn, void* cookie) {
35    char* wanted = static_cast<char*>(cookie);
36    if (event == WATCH_EVENT_ADD_FILE && strcmp(fn, wanted) == 0) {
37        return ZX_ERR_STOP;
38    }
39    return ZX_OK;
40}
41
42static zx_status_t wait_for_device_impl(char* path, const zx::time& deadline) {
43    zx_status_t rc;
44
45    // Peel off last path segment
46    char* sep = strrchr(path, '/');
47    if (path[0] == '\0' || (!sep)) {
48        fprintf(stderr, "invalid device path '%s'\n", path);
49        return ZX_ERR_BAD_PATH;
50    }
51    char* last = sep + 1;
52
53    *sep = '\0';
54    auto restore_path = fbl::MakeAutoCall([sep] { *sep = '/'; });
55
56    // Recursively check the path up to this point
57    struct stat buf;
58    if (stat(path, &buf) != 0 && (rc = wait_for_device_impl(path, deadline)) != ZX_OK) {
59        fprintf(stderr, "failed to bind '%s': %s\n", path, zx_status_get_string(rc));
60        return rc;
61    }
62
63    // Early exit if this segment is empty
64    if (last[0] == '\0') {
65        return ZX_OK;
66    }
67
68    // Open the parent directory
69    DIR* dir = opendir(path);
70    if (!dir) {
71        fprintf(stderr, "unable to open '%s'\n", path);
72        return ZX_ERR_NOT_FOUND;
73    }
74    auto close_dir = fbl::MakeAutoCall([&] { closedir(dir); });
75
76    // Wait for the next path segment to show up
77    rc = fdio_watch_directory(dirfd(dir), driver_watcher_cb, deadline.get(), last);
78    if (rc != ZX_ERR_STOP) {
79        fprintf(stderr, "error when waiting for '%s': %s\n", last, zx_status_get_string(rc));
80        return rc;
81    }
82
83    return ZX_OK;
84}
85
86// TODO(aarongreen): This is more generic than just fs-management, or even block devices.  Move this
87// (and its tests) out of ramdisk and to somewhere else?
88zx_status_t wait_for_device(const char* path, zx_duration_t timeout) {
89    if (!path || timeout == 0) {
90        fprintf(stderr, "invalid args: path='%s', timeout=%" PRIu64 "\n", path, timeout);
91        return ZX_ERR_INVALID_ARGS;
92    }
93
94    // Make a mutable copy
95    char tmp[PATH_MAX];
96    snprintf(tmp, sizeof(tmp), "%s", path);
97    zx::time deadline = zx::deadline_after(zx::duration(timeout));
98    return wait_for_device_impl(tmp, deadline);
99}
100
101static int open_ramctl(void) {
102    int fd = open(RAMCTL_PATH, O_RDWR);
103    if (fd < 0) {
104        fprintf(stderr, "Could not open ramctl\n");
105    }
106    return fd;
107}
108
109static int finish_create(ramdisk_ioctl_config_response_t* response, char* out_path, ssize_t r) {
110    if (r < 0) {
111        fprintf(stderr, "Could not configure ramdev\n");
112        return -1;
113    }
114    response->name[r] = 0;
115
116    char path[PATH_MAX];
117    auto cleanup = fbl::MakeAutoCall([&path, response]() {
118        snprintf(path, sizeof(path), "%s/%s", RAMCTL_PATH, response->name);
119        destroy_ramdisk(path);
120    });
121
122    // The ramdisk should have been created instantly, but it may take
123    // a moment for the block device driver to bind to it.
124    snprintf(path, sizeof(path), "%s/%s/%s", RAMCTL_PATH, response->name, BLOCK_EXTENSION);
125    if (wait_for_device(path, ZX_SEC(3)) != ZX_OK) {
126        fprintf(stderr, "Error waiting for driver\n");
127        return -1;
128    }
129
130    // TODO(security): SEC-70.  This may overflow |out_path|.
131    strcpy(out_path, path);
132    cleanup.cancel();
133    return 0;
134}
135
136int create_ramdisk(uint64_t blk_size, uint64_t blk_count, char* out_path) {
137    fbl::unique_fd fd(open_ramctl());
138    if (fd.get() < 0) {
139        return -1;
140    }
141    ramdisk_ioctl_config_t config = {};
142    config.blk_size = blk_size;
143    config.blk_count = blk_count;
144    memset(config.type_guid, 0, ZBI_PARTITION_GUID_LEN);
145    ramdisk_ioctl_config_response_t response;
146    return finish_create(&response, out_path,
147                         ioctl_ramdisk_config(fd.get(), &config, &response));
148}
149
150int create_ramdisk_with_guid(uint64_t blk_size, uint64_t blk_count, const uint8_t* type_guid,
151                             size_t guid_len, char* out_path) {
152    fbl::unique_fd fd(open_ramctl());
153    if (fd.get() < 0) {
154        return -1;
155    }
156    if (type_guid == NULL || guid_len < ZBI_PARTITION_GUID_LEN) {
157        return -1;
158    }
159    ramdisk_ioctl_config_t config = {};
160    config.blk_size = blk_size;
161    config.blk_count = blk_count;
162    memcpy(config.type_guid, type_guid, ZBI_PARTITION_GUID_LEN);
163    ramdisk_ioctl_config_response_t response;
164    return finish_create(&response, out_path,
165                         ioctl_ramdisk_config(fd.get(), &config, &response));
166}
167
168int create_ramdisk_from_vmo(zx_handle_t vmo, char* out_path) {
169    fbl::unique_fd fd(open_ramctl());
170    if (fd.get() < 0) {
171        return -1;
172    }
173    ramdisk_ioctl_config_response_t response;
174    return finish_create(&response, out_path,
175                         ioctl_ramdisk_config_vmo(fd.get(), &vmo, &response));
176}
177
178int sleep_ramdisk(const char* ramdisk_path, uint64_t block_count) {
179    fbl::unique_fd fd(open(ramdisk_path, O_RDWR));
180    if (fd.get() < 0) {
181        fprintf(stderr, "Could not open ramdisk\n");
182        return -1;
183    }
184
185    ssize_t r = ioctl_ramdisk_sleep_after(fd.get(), &block_count);
186    if (r != ZX_OK) {
187        fprintf(stderr, "Could not set ramdisk interrupt on path %s: %ld\n", ramdisk_path, r);
188        return -1;
189    }
190    return 0;
191}
192
193int wake_ramdisk(const char* ramdisk_path) {
194    fbl::unique_fd fd(open(ramdisk_path, O_RDWR));
195    if (fd.get() < 0) {
196        fprintf(stderr, "Could not open ramdisk\n");
197        return -1;
198    }
199
200    ssize_t r = ioctl_ramdisk_wake_up(fd.get());
201    if (r != ZX_OK) {
202        fprintf(stderr, "Could not wake ramdisk\n");
203        return -1;
204    }
205
206    return 0;
207}
208
209int get_ramdisk_blocks(const char* ramdisk_path, ramdisk_blk_counts_t* counts) {
210    fbl::unique_fd fd(open(ramdisk_path, O_RDWR));
211    if (fd.get() < 0) {
212        fprintf(stderr, "Could not open ramdisk\n");
213        return -1;
214    }
215    if (ioctl_ramdisk_get_blk_counts(fd.get(), counts) < 0) {
216        fprintf(stderr, "Could not get blk counts\n");
217        return -1;
218    }
219    return 0;
220}
221
222int destroy_ramdisk(const char* ramdisk_path) {
223    fbl::unique_fd ramdisk(open(ramdisk_path, O_RDWR));
224    if (!ramdisk) {
225        fprintf(stderr, "Could not open ramdisk\n");
226        return -1;
227    }
228    ssize_t r = ioctl_ramdisk_unlink(ramdisk.get());
229    if (r != ZX_OK) {
230        fprintf(stderr, "Could not shut off ramdisk\n");
231        return -1;
232    }
233    return 0;
234}
235