@@ -8,3 +8,6 @@ tpm2/SpaceTest.log
# Python bytecode and cache
__pycache__/
*.py[cod]
+
+# selftest install dir
+/kselftest_install/
@@ -9,6 +9,7 @@ TARGETS += clone3
TARGETS += core
TARGETS += cpufreq
TARGETS += cpu-hotplug
+TARGETS += dm-user
TARGETS += drivers/dma-buf
TARGETS += efivarfs
TARGETS += exec
new file mode 100644
@@ -0,0 +1,3 @@
+/daemon-example
+/daemon-parallel
+/daemon-short
new file mode 100644
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+.PHONY: all clean
+
+top_srcdir = ../../../..
+INCLUDES := -I../ -Iinclude/ -I$(top_srcdir)/usr/include
+CFLAGS := $(CFLAGS) -g -O2 -Wall -static -D_GNU_SOURCE -pthread $(INCLUDES)
+KSFT_KHDR_INSTALL := 1
+
+TEST_GEN_FILES := \
+ daemon-example \
+ daemon-parallel \
+ daemon-short
+
+TEST_PROGS := \
+ fio-rand-read-1G.fio \
+ fio-verify-1G.fio \
+ harness-fio.sh \
+ harness-fsstress.sh \
+ run.sh
+
+$(TEST_GEN_FILES): khdr
+
+include ../lib.mk
new file mode 100644
@@ -0,0 +1,20 @@
+dm-user Tests
+=============
+Tests for dm-user.
+
+Quick Start
+-----------
+It's probably a bad idea to just run this blindly, but all you need to do is:
+
+# make
+# ./run.sh
+
+Slow Start
+----------
+These tests use `dmsetup` to manage device mapper nodes, which is part of lvm2.
+Some use `fio`, and some use the `fsstress` from xfstests. Some of the tests
+also expect "/dev/vdb" to exist and to be at least 10G.
+
+I use a simple buildroot-based initramfs to run the tests. I've added an
+xfstests package to get fsstress, but I haven't sent out the patches yet. I
+run everything in QEMU.
new file mode 100644
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Google, Inc
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+#include <pthread.h>
+#include <linux/dm-user.h>
+#include <sys/prctl.h>
+#include "logging.h"
+
+#define SECTOR_SIZE 512
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+int write_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ once = write(fd, buf_c + total, len - total);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+int read_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ once = read(fd, buf_c + total, len - total);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+int simple_daemon(char *control_dev,
+ size_t block_bytes,
+ char *store)
+
+{
+ int control_fd = open(control_dev, O_RDWR);
+
+ if (control_fd < 0) {
+ ksft_print_msg("Unable to open control device %s\n", control_dev);
+ return RET_FAIL;
+ }
+
+ while (1) {
+ struct dm_user_message msg;
+ __u64 type;
+ char *base;
+
+ if (read_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to read msg");
+ return RET_FAIL;
+ }
+
+ base = store + msg.sector * SECTOR_SIZE;
+ if (base + msg.len > store + block_bytes) {
+ fprintf(stderr, "access out of bounds\n");
+ return RET_FAIL;
+ }
+
+ type = msg.type;
+ switch (type) {
+ case DM_USER_REQ_MAP_WRITE:
+ msg.type = DM_USER_RESP_SUCCESS;
+ if (read_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to read buf");
+ return RET_FAIL;
+ }
+ break;
+ case DM_USER_REQ_MAP_FLUSH:
+ /* Nothing extra to do on flush, we're in memory. */
+ case DM_USER_REQ_MAP_READ:
+ msg.type = DM_USER_RESP_SUCCESS;
+ break;
+ default:
+ msg.type = DM_USER_RESP_UNSUPPORTED;
+ break;
+ }
+
+ if (write_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to write msg");
+ return RET_FAIL;
+ }
+
+ if (type == DM_USER_REQ_MAP_READ) {
+ if (write_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to write buf");
+ return RET_FAIL;
+ }
+ }
+ }
+
+ /* The daemon doesn't actully terminate for this test. */
+ perror("Unable to read from control device");
+ return RET_FAIL;
+}
+
+void usage(char *prog)
+{
+ printf("Usage: %s\n", prog);
+ printf(" -h Display this help message\n");
+ printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+ VQUIET, VCRITICAL, VINFO);
+ printf(" -c <control dev> Control device to use for the test\n");
+ printf(" -s <sectors> The number of sectors in the device\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = RET_PASS;
+ int c;
+ char *control_dev = NULL;
+ long block_bytes = 1024;
+ char *store;
+
+ prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+
+ while ((c = getopt(argc, argv, "h:v:c:s:")) != -1) {
+ switch (c) {
+ case 'h':
+ usage(basename(argv[0]));
+ exit(0);
+ case 'v':
+ log_verbosity(atoi(optarg));
+ break;
+ case 'c':
+ control_dev = strdup(optarg);
+ break;
+ case 's':
+ block_bytes = atoi(optarg) * SECTOR_SIZE;
+ break;
+ default:
+ usage(basename(argv[0]));
+ exit(1);
+ }
+ }
+
+ ksft_print_header();
+ ksft_set_plan(1);
+ ksft_print_msg("%s: block_bytes=%zu\n",
+ basename(argv[0]),
+ block_bytes);
+
+ store = malloc(block_bytes);
+ for (size_t i = 0; i < block_bytes/sizeof(size_t); ++i)
+ ((size_t *)(store))[i] = i;
+
+ ret = simple_daemon(control_dev, block_bytes, store);
+
+ print_result(basename(argv[0]), ret);
+ exit(ret);
+ return ret;
+}
new file mode 100644
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Google, Inc
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+#include <pthread.h>
+#include <linux/dm-user.h>
+#include <sys/prctl.h>
+#include <sys/mman.h>
+#include "logging.h"
+
+#define SECTOR_SIZE 512
+#define MAX_WORKER_COUNT 256
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+struct test_context {
+ char *control_dev;
+ size_t block_bytes;
+ char *store;
+ long worker_count;
+ char *backing_path;
+};
+
+int write_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ once = write(fd, buf_c + total, len - total);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+int read_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ once = read(fd, buf_c + total, len - total);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+void *simple_daemon(void *context_uc)
+{
+ struct test_context *context = context_uc;
+ char *store = context->store;
+ int control_fd = open(context->control_dev, O_RDWR);
+
+ if (control_fd < 0) {
+ ksft_print_msg("Unable to open control device %s\n", context->control_dev);
+ return (void *)(RET_FAIL);
+ }
+
+ while (1) {
+ struct dm_user_message msg;
+ __u64 type;
+ char *base;
+
+ if (read_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return (void *)(RET_PASS);
+
+ perror("unable to read msg");
+ return (void *)(RET_FAIL);
+ }
+
+ base = store + msg.sector * SECTOR_SIZE;
+ if (base + msg.len > store + context->block_bytes) {
+ fprintf(stderr, "access out of bounds\n");
+ return (void *)(RET_FAIL);
+ }
+
+ type = msg.type;
+ switch (type) {
+ case DM_USER_REQ_MAP_READ:
+ msg.type = DM_USER_RESP_SUCCESS;
+ break;
+ case DM_USER_REQ_MAP_WRITE:
+ msg.type = DM_USER_RESP_SUCCESS;
+ if (read_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return (void *)(RET_PASS);
+
+ perror("unable to read buf");
+ return (void *)(RET_FAIL);
+ }
+ break;
+ case DM_USER_REQ_MAP_FLUSH:
+ msg.type = DM_USER_RESP_SUCCESS;
+ sync();
+ break;
+ default:
+ msg.type = DM_USER_RESP_UNSUPPORTED;
+ break;
+ }
+
+ if (write_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return (void *)(RET_PASS);
+
+ perror("unable to write msg");
+ return (void *)(RET_FAIL);
+ }
+
+ if (type == DM_USER_REQ_MAP_READ) {
+ if (write_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return (void *)(RET_PASS);
+
+ perror("unable to write buf");
+ return (void *)(RET_FAIL);
+ }
+ }
+ }
+
+ /* The daemon doesn't actully terminate for this test. */
+ perror("Unable to read from control device");
+ return (void *)(RET_FAIL);
+}
+
+void usage(char *prog)
+{
+ printf("Usage: %s\n", prog);
+ printf(" -h Display this help message\n");
+ printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+ VQUIET, VCRITICAL, VINFO);
+ printf(" -c <control dev> Control device to use for the test\n");
+ printf(" -s <sectors> The number of sectors in the device\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = RET_PASS;
+ int done = 0;
+ int c;
+ struct test_context context = {
+ .control_dev = NULL,
+ .block_bytes = 0,
+ .worker_count = 1,
+ .backing_path = NULL,
+ };
+ pthread_t daemon[MAX_WORKER_COUNT];
+ void *pthread_ret;
+
+ prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+
+ while ((c = getopt(argc, argv, "h:v:c:s:w:b:")) != -1) {
+ switch (c) {
+ case 'h':
+ usage(basename(argv[0]));
+ exit(0);
+ case 'v':
+ log_verbosity(atoi(optarg));
+ break;
+ case 'c':
+ context.control_dev = strdup(optarg);
+ break;
+ case 's':
+ context.block_bytes = atoi(optarg) * SECTOR_SIZE;
+ break;
+ case 'w':
+ context.worker_count = atoi(optarg);
+ break;
+ case 'b':
+ context.backing_path = strdup(optarg);
+ break;
+ default:
+ usage(basename(argv[0]));
+ exit(1);
+ }
+ }
+
+ ksft_print_header();
+ ksft_set_plan(1);
+ ksft_print_msg("%s: block_bytes=%zu\n",
+ basename(argv[0]),
+ context.block_bytes);
+
+ ret = RET_PASS;
+
+ if (context.backing_path == NULL) {
+ ksft_print_msg("Using an in-memory backing store\n");
+ context.store = malloc(context.block_bytes);
+ for (size_t i = 0; i < context.block_bytes/sizeof(size_t); ++i)
+ ((size_t *)(context.store))[i] = i;
+ } else {
+ int backing_fd = open(context.backing_path, O_RDWR);
+
+ ksft_print_msg("Using %s as a backing store\n", context.backing_path);
+ if (backing_fd < 0) {
+ perror("Unable to open backing store");
+ ksft_print_msg("Unable to open backing store %s\n", context.backing_path);
+ return RET_FAIL;
+ }
+
+ context.store = mmap(NULL, context.block_bytes,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ backing_fd, 0);
+ }
+
+ for (size_t i = 0; i < context.worker_count; ++i)
+ if (pthread_create(&daemon[i], NULL, &simple_daemon, &context) < 0)
+ ret = RET_ERROR;
+
+ while (!done) {
+ for (size_t i = 0; i < context.worker_count; ++i) {
+ if (pthread_tryjoin_np(daemon[i], &pthread_ret) == 0) {
+ if (pthread_ret != RET_PASS)
+ ret = RET_ERROR;
+ done = 1;
+ }
+ }
+
+ sleep(1);
+ }
+
+ print_result(basename(argv[0]), ret);
+ exit(ret);
+}
new file mode 100644
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Google, Inc
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+#include <pthread.h>
+#include <linux/dm-user.h>
+#include <sys/prctl.h>
+#include "logging.h"
+
+#define SECTOR_SIZE 512
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+int write_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ size_t max = len - total;
+
+ if (max > 3)
+ max = max / 3;
+
+ once = write(fd, buf_c + total, max);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+int read_all(int fd, void *buf, size_t len)
+{
+ char *buf_c = buf;
+ ssize_t total = 0;
+ ssize_t once;
+
+ while (total < len) {
+ size_t max = len - total;
+
+ if (max > 3)
+ max = max / 3;
+
+ once = read(fd, buf_c + total, max);
+ if (once <= 0)
+ return once;
+ total += once;
+ }
+
+ return total;
+}
+
+int simple_daemon(char *control_dev,
+ size_t block_bytes,
+ char *store)
+
+{
+ int control_fd = open(control_dev, O_RDWR);
+
+ if (control_fd < 0) {
+ ksft_print_msg("Unable to open control device %s\n", control_dev);
+ return RET_FAIL;
+ }
+
+ while (1) {
+ struct dm_user_message msg;
+ __u64 type;
+ char *base;
+
+ if (read_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to read msg");
+ return RET_FAIL;
+ }
+
+ base = store + msg.sector * SECTOR_SIZE;
+ if (base + msg.len > store + block_bytes) {
+ fprintf(stderr, "access out of bounds\n");
+ return RET_FAIL;
+ }
+
+ type = msg.type;
+ switch (type) {
+ case DM_USER_REQ_MAP_WRITE:
+ msg.type = DM_USER_RESP_SUCCESS;
+ if (read_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to read buf");
+ return RET_FAIL;
+ }
+ break;
+ case DM_USER_REQ_MAP_FLUSH:
+ /* Nothing extra to do on flush, we're in memory. */
+ case DM_USER_REQ_MAP_READ:
+ msg.type = DM_USER_RESP_SUCCESS;
+ break;
+ default:
+ msg.type = DM_USER_RESP_UNSUPPORTED;
+ break;
+ }
+
+ if (write_all(control_fd, &msg, sizeof(msg)) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to write msg");
+ return RET_FAIL;
+ }
+
+ if (type == DM_USER_REQ_MAP_READ) {
+ if (write_all(control_fd, base, msg.len) < 0) {
+ if (errno == ENOTBLK)
+ return RET_PASS;
+
+ perror("unable to write buf");
+ return RET_FAIL;
+ }
+ }
+ }
+
+ /* The daemon doesn't actully terminate for this test. */
+ perror("Unable to read from control device");
+ return RET_FAIL;
+}
+
+void usage(char *prog)
+{
+ printf("Usage: %s\n", prog);
+ printf(" -h Display this help message\n");
+ printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+ VQUIET, VCRITICAL, VINFO);
+ printf(" -c <control dev> Control device to use for the test\n");
+ printf(" -s <sectors> The number of sectors in the device\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = RET_PASS;
+ int c;
+ char *control_dev = NULL;
+ long block_bytes = 1024;
+ char *store;
+
+ prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+
+ while ((c = getopt(argc, argv, "h:v:c:s:")) != -1) {
+ switch (c) {
+ case 'h':
+ usage(basename(argv[0]));
+ exit(0);
+ case 'v':
+ log_verbosity(atoi(optarg));
+ break;
+ case 'c':
+ control_dev = strdup(optarg);
+ break;
+ case 's':
+ block_bytes = atoi(optarg) * SECTOR_SIZE;
+ break;
+ default:
+ usage(basename(argv[0]));
+ exit(1);
+ }
+ }
+
+ ksft_print_header();
+ ksft_set_plan(1);
+ ksft_print_msg("%s: block_bytes=%zu\n",
+ basename(argv[0]),
+ block_bytes);
+
+ store = malloc(block_bytes);
+ for (size_t i = 0; i < block_bytes/sizeof(size_t); ++i)
+ ((size_t *)(store))[i] = i;
+
+ ret = simple_daemon(control_dev, block_bytes, store);
+
+ print_result(basename(argv[0]), ret);
+ exit(ret);
+ return ret;
+}
new file mode 100644
@@ -0,0 +1,16 @@
+; fio-rand-read.job for fiotest
+
+[global]
+name=fio-rand-read-1G
+filename=fio-rand-read-1G
+rw=randread
+bs=4K
+direct=0
+numjobs=1
+time_based=1
+runtime=30
+
+[file1]
+size=1G
+ioengine=io_uring
+iodepth=16
new file mode 100644
@@ -0,0 +1,10 @@
+# The most basic form of data verification. Write the device randomly
+# in 4K chunks, then read it back and verify the contents.
+[write-and-verify]
+rw=randwrite
+bs=4k
+ioengine=libaio
+iodepth=16
+direct=1
+verify=crc32c
+size=1G
new file mode 100755
@@ -0,0 +1,45 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright 2020 Google, Inc
+
+# Just a fixed size for now, but it's passed to the tests and they're supposed
+# to respect it.
+SIZE=1024
+BLOCK=kselftest-dm-user-block
+CONTROL=kselftest-dm-user-control
+unset FIO
+
+while [ x"$1" != x"--" ]
+do
+ case "$1" in
+ "-s") SIZE="$2"; shift 2;;
+ "-f") FIO="$2"; shift 2;;
+ *) echo "$0: unknown argument $1" >&2; exit 1;;
+ esac
+done
+shift
+
+# Run the benchmark again via dm-user, to see what the overhead is.
+dmsetup create $BLOCK << EOF
+0 $SIZE user 0 $SIZE $CONTROL
+EOF
+
+dmsetup resume $BLOCK
+
+"$@" -s $SIZE -c /dev/dm-user/$CONTROL &
+
+yes | mkfs.ext2 /dev/mapper/$BLOCK
+mount /dev/mapper/$BLOCK /mnt
+cp "$FIO" /mnt/benchmark.fio
+(cd /mnt; fio benchmark.fio)
+umount /mnt
+
+# Mount again and read the whole thing, just to see if there's any corruption.
+mount /dev/mapper/$BLOCK /mnt
+find /mnt -type f | xargs cat > /dev/null
+umount /mnt
+
+dmsetup remove $BLOCK
+
+# Make sure the daemon actually responds to DM closing it.
+wait
new file mode 100755
@@ -0,0 +1,44 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright 2020 Google, Inc
+
+BLOCK=kselftest-dm-user-block
+CONTROL=kselftest-dm-user-control
+unset SIZE
+unset NPROC
+unset NOP
+
+while [ x"$1" != x"--" ]
+do
+ case "$1" in
+ "-s") SIZE="$2"; shift 2;;
+ "-n") NOP="$2"; shift 2;;
+ "-p") NPROC="$2"; shift 2;;
+ *) echo "$0: unknown argument $1" >&2; exit 1;;
+ esac
+done
+shift
+
+# Runs the fs stress tests
+dmsetup create $BLOCK << EOF
+0 $SIZE user 0 $SIZE $CONTROL
+EOF
+
+dmsetup resume $BLOCK
+
+"$@" -s $SIZE -c /dev/dm-user/$CONTROL &
+
+yes | mkfs.ext2 /dev/mapper/$BLOCK
+mount /dev/mapper/$BLOCK /mnt
+/usr/xfstests/ltp/fsstress -d /mnt/ -n "$NOP" -p "$NPROC"
+umount /mnt
+
+# Mount again and read the whole thing, just to see if there's any corruption.
+mount /dev/mapper/$BLOCK /mnt
+find /mnt -type f | xargs cat > /dev/null
+umount /mnt
+
+dmsetup remove $BLOCK
+
+# Make sure the daemon actually responds to DM closing it.
+wait
new file mode 100644
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/******************************************************************************
+ *
+ * Copyright © International Business Machines Corp., 2009
+ *
+ * DESCRIPTION
+ * Glibc independent futex library for testing kernel functionality.
+ *
+ * AUTHOR
+ * Darren Hart <dvhart@linux.intel.com>
+ *
+ * HISTORY
+ * 2009-Nov-6: Initial version by Darren Hart <dvhart@linux.intel.com>
+ *
+ *****************************************************************************/
+
+#ifndef _LOGGING_H
+#define _LOGGING_H
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/futex.h>
+#include "kselftest.h"
+
+/*
+ * Define PASS, ERROR, and FAIL strings with and without color escape
+ * sequences, default to no color.
+ */
+#define ESC 0x1B, '['
+#define BRIGHT '1'
+#define GREEN '3', '2'
+#define YELLOW '3', '3'
+#define RED '3', '1'
+#define ESCEND 'm'
+#define BRIGHT_GREEN ESC, BRIGHT, ';', GREEN, ESCEND
+#define BRIGHT_YELLOW ESC, BRIGHT, ';', YELLOW, ESCEND
+#define BRIGHT_RED ESC, BRIGHT, ';', RED, ESCEND
+#define RESET_COLOR ESC, '0', 'm'
+static const char PASS_COLOR[] = {BRIGHT_GREEN, ' ', 'P', 'A', 'S', 'S',
+ RESET_COLOR, 0};
+static const char ERROR_COLOR[] = {BRIGHT_YELLOW, 'E', 'R', 'R', 'O', 'R',
+ RESET_COLOR, 0};
+static const char FAIL_COLOR[] = {BRIGHT_RED, ' ', 'F', 'A', 'I', 'L',
+ RESET_COLOR, 0};
+static const char INFO_NORMAL[] = " INFO";
+static const char PASS_NORMAL[] = " PASS";
+static const char ERROR_NORMAL[] = "ERROR";
+static const char FAIL_NORMAL[] = " FAIL";
+const char *INFO = INFO_NORMAL;
+const char *PASS = PASS_NORMAL;
+const char *ERROR = ERROR_NORMAL;
+const char *FAIL = FAIL_NORMAL;
+
+/* Verbosity setting for INFO messages */
+#define VQUIET 0
+#define VCRITICAL 1
+#define VINFO 2
+#define VMAX VINFO
+int _verbose = VCRITICAL;
+
+/* Functional test return codes */
+#define RET_PASS 0
+#define RET_ERROR -1
+#define RET_FAIL -2
+
+/**
+ * log_color() - Use colored output for PASS, ERROR, and FAIL strings
+ * @use_color: use color (1) or not (0)
+ */
+void log_color(int use_color)
+{
+ if (use_color) {
+ PASS = PASS_COLOR;
+ ERROR = ERROR_COLOR;
+ FAIL = FAIL_COLOR;
+ } else {
+ PASS = PASS_NORMAL;
+ ERROR = ERROR_NORMAL;
+ FAIL = FAIL_NORMAL;
+ }
+}
+
+/**
+ * log_verbosity() - Set verbosity of test output
+ * @verbose: Enable (1) verbose output or not (0)
+ *
+ * Currently setting verbose=1 will enable INFO messages and 0 will disable
+ * them. FAIL and ERROR messages are always displayed.
+ */
+void log_verbosity(int level)
+{
+ if (level > VMAX)
+ level = VMAX;
+ else if (level < 0)
+ level = 0;
+ _verbose = level;
+}
+
+/**
+ * print_result() - Print standard PASS | ERROR | FAIL results
+ * @ret: the return value to be considered: 0 | RET_ERROR | RET_FAIL
+ *
+ * print_result() is primarily intended for functional tests.
+ */
+void print_result(const char *test_name, int ret)
+{
+ switch (ret) {
+ case RET_PASS:
+ ksft_test_result_pass("%s\n", test_name);
+ ksft_print_cnts();
+ return;
+ case RET_ERROR:
+ ksft_test_result_error("%s\n", test_name);
+ ksft_print_cnts();
+ return;
+ case RET_FAIL:
+ ksft_test_result_fail("%s\n", test_name);
+ ksft_print_cnts();
+ return;
+ }
+}
+
+/* log level macros */
+#define info(message, vargs...) \
+do { \
+ if (_verbose >= VINFO) \
+ fprintf(stderr, "\t%s: "message, INFO, ##vargs); \
+} while (0)
+
+#define error(message, err, args...) \
+do { \
+ if (_verbose >= VCRITICAL) {\
+ if (err) \
+ fprintf(stderr, "\t%s: %s: "message, \
+ ERROR, strerror(err), ##args); \
+ else \
+ fprintf(stderr, "\t%s: "message, ERROR, ##args); \
+ } \
+} while (0)
+
+#define fail(message, args...) \
+do { \
+ if (_verbose >= VCRITICAL) \
+ fprintf(stderr, "\t%s: "message, FAIL, ##args); \
+} while (0)
+
+#endif
new file mode 100755
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Palmer Dabbelt <palmerdabbelt@google.com>
+
+# Top-level run script for dm-user kernel self tests. This just runs a bunch
+# of different tests back to back, relying on the kernel selftest infrastructure
+# to tease out the success/failure of each. The tests all use the same global
+# directories and such, so it's not like there's a whole lot
+#
+# The actual test code should be fairly portable, but the scripts that run it
+# aren't. See the README for more information.
+
+# Runs various FIO scripts against an ext2-based filesystem backed by dm-user.
+if test -e /usr/bin/fio
+then
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-example
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-short
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 1
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 4
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 16
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 64
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 256
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 1 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 4 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 16 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 64 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-rand-read-1G.fio -- ./daemon-parallel -w 256 -b /dev/vdb
+
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-example
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-short
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 1
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 4
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 16
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 64
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 256
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 1 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 4 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 16 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 64 -b /dev/vdb
+ ./harness-fio.sh -s 3000000 -f fio-verify-1G.fio -- ./daemon-parallel -w 256 -b /dev/vdb
+else
+ echo "Unable to find /usr/bin/fio"
+fi
+
+# Runs fsstress from xfstests against an ext2-based filesystem backed by
+# dm-user.
+if test -e /usr/xfstests/ltp/fsstress
+then
+ ./harness-fsstress.sh -s 3000000 -p 1 -n 10000 -- ./daemon-example
+ ./harness-fsstress.sh -s 3000000 -p 4 -n 10000 -- ./daemon-example
+ ./harness-fsstress.sh -s 3000000 -p 16 -n 10000 -- ./daemon-example
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-example
+ ./harness-fsstress.sh -s 3000000 -p 256 -n 10000 -- ./daemon-example
+
+ ./harness-fsstress.sh -s 3000000 -p 1 -n 10000 -- ./daemon-short
+ ./harness-fsstress.sh -s 3000000 -p 4 -n 10000 -- ./daemon-short
+ ./harness-fsstress.sh -s 3000000 -p 16 -n 10000 -- ./daemon-short
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-short
+ ./harness-fsstress.sh -s 3000000 -p 256 -n 10000 -- ./daemon-short
+
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 1
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 4
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 16
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 64
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 256
+
+ ./harness-fsstress.sh -s 3000000 -p 1 -n 10000 -- ./daemon-parallel -w 1 -b /dev/vdb
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 1 -b /dev/vdb
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 4 -b /dev/vdb
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 16 -b /dev/vdb
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 64 -b /dev/vdb
+ ./harness-fsstress.sh -s 3000000 -p 64 -n 10000 -- ./daemon-parallel -w 256 -b /dev/vdb
+else
+ echo "Unable to find /usr/xfstests/ltp/fsstress"
+fi