blob: da7156c584a048543dbb9adf50a58a01263b3b21 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
#define _LARGEFILE64_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <iostream>
#include <string>
#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 <linux/types.h>
#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;
ssize_t write_all(int fd, void* buf, size_t len) {
char* buf_c = (char*)buf;
ssize_t total = 0;
ssize_t once;
while (total < static_cast<ssize_t>(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;
}
ssize_t read_all(int fd, void* buf, size_t len) {
char* buf_c = (char*)buf;
ssize_t total = 0;
ssize_t once;
while (total < static_cast<ssize_t>(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;
}
static int simple_daemon(const std::string& control_path, const std::string& backing_path) {
int control_fd = open(control_path.c_str(), O_RDWR);
if (control_fd < 0) {
fprintf(stderr, "Unable to open control device %s\n", control_path.c_str());
return -1;
}
int backing_fd = open(backing_path.c_str(), O_RDWR);
if (backing_fd < 0) {
fprintf(stderr, "Unable to open backing device %s\n", backing_path.c_str());
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 dev> Control device to use for the test\n");
printf(" -b <store path> The file to use as a backing store, otherwise memory\n");
printf(" -v Enable verbose mode\n");
}
int main(int argc, char* argv[]) {
std::string control_path;
std::string backing_path;
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 = optarg;
break;
case 'b':
backing_path = 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;
}