From c16e503faa79f75a5587ca98538fc20b0cad53d6 Mon Sep 17 00:00:00 2001 From: Palmer Dabbelt Date: Wed, 28 Oct 2020 19:14:02 -0700 Subject: [PATCH] dmuserd: Simple dm-user daemon This provides a block device via dm-user, with all accesses backed by in-memory storage. It's essentially the same as what I have in selftests, with the kselftests stuff removed so it'll build in Android. Test: mkfs.f2fs, dd, fsck.f2fs Signed-off-by: Palmer Dabbelt Change-Id: I68515d6e9001c2f6d199d394e67ebe528b382406 --- fs_mgr/tools/Android.bp | 12 ++ fs_mgr/tools/dmuserd.cpp | 319 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 fs_mgr/tools/dmuserd.cpp diff --git a/fs_mgr/tools/Android.bp b/fs_mgr/tools/Android.bp index 4d4aae41a..d6ccc4bae 100644 --- a/fs_mgr/tools/Android.bp +++ b/fs_mgr/tools/Android.bp @@ -29,3 +29,15 @@ cc_binary { cflags: ["-Werror"], } + +cc_binary { + name: "dmuserd", + srcs: ["dmuserd.cpp"], + + shared_libs: [ + "libbase", + "liblog", + ], + + cflags: ["-Werror"], +} diff --git a/fs_mgr/tools/dmuserd.cpp b/fs_mgr/tools/dmuserd.cpp new file mode 100644 index 000000000..92f587860 --- /dev/null +++ b/fs_mgr/tools/dmuserd.cpp @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 + +#define _LARGEFILE64_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SECTOR_SIZE ((__u64)512) +#define BUFFER_BYTES 4096 + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +/* This should be replaced with linux/dm-user.h. */ +#ifndef _LINUX_DM_USER_H +#define _LINUX_DM_USER_H + +#include + +#define DM_USER_REQ_MAP_READ 0 +#define DM_USER_REQ_MAP_WRITE 1 +#define DM_USER_REQ_MAP_FLUSH 2 +#define DM_USER_REQ_MAP_DISCARD 3 +#define DM_USER_REQ_MAP_SECURE_ERASE 4 +#define DM_USER_REQ_MAP_WRITE_SAME 5 +#define DM_USER_REQ_MAP_WRITE_ZEROES 6 +#define DM_USER_REQ_MAP_ZONE_OPEN 7 +#define DM_USER_REQ_MAP_ZONE_CLOSE 8 +#define DM_USER_REQ_MAP_ZONE_FINISH 9 +#define DM_USER_REQ_MAP_ZONE_APPEND 10 +#define DM_USER_REQ_MAP_ZONE_RESET 11 +#define DM_USER_REQ_MAP_ZONE_RESET_ALL 12 + +#define DM_USER_REQ_MAP_FLAG_FAILFAST_DEV 0x00001 +#define DM_USER_REQ_MAP_FLAG_FAILFAST_TRANSPORT 0x00002 +#define DM_USER_REQ_MAP_FLAG_FAILFAST_DRIVER 0x00004 +#define DM_USER_REQ_MAP_FLAG_SYNC 0x00008 +#define DM_USER_REQ_MAP_FLAG_META 0x00010 +#define DM_USER_REQ_MAP_FLAG_PRIO 0x00020 +#define DM_USER_REQ_MAP_FLAG_NOMERGE 0x00040 +#define DM_USER_REQ_MAP_FLAG_IDLE 0x00080 +#define DM_USER_REQ_MAP_FLAG_INTEGRITY 0x00100 +#define DM_USER_REQ_MAP_FLAG_FUA 0x00200 +#define DM_USER_REQ_MAP_FLAG_PREFLUSH 0x00400 +#define DM_USER_REQ_MAP_FLAG_RAHEAD 0x00800 +#define DM_USER_REQ_MAP_FLAG_BACKGROUND 0x01000 +#define DM_USER_REQ_MAP_FLAG_NOWAIT 0x02000 +#define DM_USER_REQ_MAP_FLAG_CGROUP_PUNT 0x04000 +#define DM_USER_REQ_MAP_FLAG_NOUNMAP 0x08000 +#define DM_USER_REQ_MAP_FLAG_HIPRI 0x10000 +#define DM_USER_REQ_MAP_FLAG_DRV 0x20000 +#define DM_USER_REQ_MAP_FLAG_SWAP 0x40000 + +#define DM_USER_RESP_SUCCESS 0 +#define DM_USER_RESP_ERROR 1 +#define DM_USER_RESP_UNSUPPORTED 2 + +struct dm_user_message { + __u64 seq; + __u64 type; + __u64 flags; + __u64 sector; + __u64 len; + __u8 buf[]; +}; + +#endif + +static bool verbose = false; + +size_t write_all(int fd, void* buf, size_t len) { + char* buf_c = (char*)buf; + ssize_t total = 0; + ssize_t once; + + while (total < len) { + once = write(fd, buf_c + total, len - total); + if (once < 0) return once; + if (once == 0) { + errno = ENOSPC; + return 0; + } + total += once; + } + + return total; +} + +size_t read_all(int fd, void* buf, size_t len) { + char* buf_c = (char*)buf; + ssize_t total = 0; + ssize_t once; + + while (total < len) { + once = read(fd, buf_c + total, len - total); + if (once < 0) return once; + if (once == 0) { + errno = ENOSPC; + return 0; + } + total += once; + } + + return total; +} + +int not_splice(int from, int to, __u64 count) { + while (count > 0) { + char buf[BUFFER_BYTES]; + __u64 max = count > BUFFER_BYTES ? BUFFER_BYTES : count; + + if (read_all(from, buf, max) <= 0) { + perror("Unable to read"); + return -EIO; + } + + if (write_all(to, buf, max) <= 0) { + perror("Unable to write"); + return -EIO; + } + + count -= max; + } + + return 0; +} + +int simple_daemon(char* control_path, char* backing_path) { + int control_fd = open(control_path, O_RDWR); + if (control_fd < 0) { + fprintf(stderr, "Unable to open control device %s\n", control_path); + return -1; + } + + int backing_fd = open(backing_path, O_RDWR); + if (backing_fd < 0) { + fprintf(stderr, "Unable to open backing device %s\n", backing_path); + return -1; + } + + while (1) { + struct dm_user_message msg; + char* base; + __u64 type; + + if (verbose) std::cerr << "dmuserd: Waiting for message...\n"; + + if (read_all(control_fd, &msg, sizeof(msg)) < 0) { + if (errno == ENOTBLK) return 0; + + perror("unable to read msg"); + return -1; + } + + if (verbose) { + std::string type; + switch (msg.type) { + case DM_USER_REQ_MAP_WRITE: + type = "write"; + break; + case DM_USER_REQ_MAP_READ: + type = "read"; + break; + case DM_USER_REQ_MAP_FLUSH: + type = "flush"; + break; + default: + /* + * FIXME: Can't I do "whatever"s here rather that + * std::string("whatever")? + */ + type = std::string("(unknown, id=") + std::to_string(msg.type) + ")"; + break; + } + + std::string flags; + if (msg.flags & DM_USER_REQ_MAP_FLAG_SYNC) { + if (!flags.empty()) flags += "|"; + flags += "S"; + } + if (msg.flags & DM_USER_REQ_MAP_FLAG_META) { + if (!flags.empty()) flags += "|"; + flags += "M"; + } + if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { + if (!flags.empty()) flags += "|"; + flags += "FUA"; + } + if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH) { + if (!flags.empty()) flags += "|"; + flags += "F"; + } + + std::cerr << "dmuserd: Got " << type << " request " << flags << " for sector " + << std::to_string(msg.sector) << " with length " << std::to_string(msg.len) + << "\n"; + } + + type = msg.type; + switch (type) { + case DM_USER_REQ_MAP_READ: + msg.type = DM_USER_RESP_SUCCESS; + break; + case DM_USER_REQ_MAP_WRITE: + if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH || + msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { + if (fsync(backing_fd) < 0) { + perror("Unable to fsync(), just sync()ing instead"); + sync(); + } + } + msg.type = DM_USER_RESP_SUCCESS; + if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) { + perror("Unable to seek"); + return -1; + } + if (not_splice(control_fd, backing_fd, msg.len) < 0) { + if (errno == ENOTBLK) return 0; + std::cerr << "unable to handle write data\n"; + return -1; + } + if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { + if (fsync(backing_fd) < 0) { + perror("Unable to fsync(), just sync()ing instead"); + sync(); + } + } + break; + case DM_USER_REQ_MAP_FLUSH: + msg.type = DM_USER_RESP_SUCCESS; + if (fsync(backing_fd) < 0) { + perror("Unable to fsync(), just sync()ing instead"); + sync(); + } + break; + default: + std::cerr << "dmuserd: unsupported op " << std::to_string(msg.type) << "\n"; + msg.type = DM_USER_RESP_UNSUPPORTED; + break; + } + + if (verbose) std::cerr << "dmuserd: Responding to message\n"; + + if (write_all(control_fd, &msg, sizeof(msg)) < 0) { + if (errno == ENOTBLK) return 0; + perror("unable to write msg"); + return -1; + } + + switch (type) { + case DM_USER_REQ_MAP_READ: + if (verbose) std::cerr << "dmuserd: Sending read data\n"; + if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) { + perror("Unable to seek"); + return -1; + } + if (not_splice(backing_fd, control_fd, msg.len) < 0) { + if (errno == ENOTBLK) return 0; + std::cerr << "unable to handle read data\n"; + return -1; + } + break; + } + } + + /* The daemon doesn't actully terminate for this test. */ + perror("Unable to read from control device"); + return -1; +} + +void usage(char* prog) { + printf("Usage: %s\n", prog); + printf(" Handles block requests in userspace, backed by memory\n"); + printf(" -h Display this help message\n"); + printf(" -c Control device to use for the test\n"); + printf(" -b The file to use as a backing store, otherwise memory\n"); + printf(" -v Enable verbose mode\n"); +} + +int main(int argc, char* argv[]) { + char* control_path = NULL; + char* backing_path = NULL; + char* store; + int c; + + prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0); + + while ((c = getopt(argc, argv, "h:c:s:b:v")) != -1) { + switch (c) { + case 'h': + usage(basename(argv[0])); + exit(0); + case 'c': + control_path = strdup(optarg); + break; + case 'b': + backing_path = strdup(optarg); + break; + case 'v': + verbose = true; + break; + default: + usage(basename(argv[0])); + exit(1); + } + } + + int r = simple_daemon(control_path, backing_path); + if (r) fprintf(stderr, "simple_daemon() errored out\n"); + return r; +}