]> Zhao Yanbai Git Server - minix.git/commitdiff
Add fbd -- Faulty Block Device driver
authorDavid van Moolenbroek <david@minix3.org>
Sun, 11 Dec 2011 18:32:13 +0000 (19:32 +0100)
committerDavid van Moolenbroek <david@minix3.org>
Sun, 11 Dec 2011 21:45:46 +0000 (22:45 +0100)
This driver can be loaded as an overlay on top of a real block
device, and can then be used to generate block-level failures for
certain transfer requests. Specifically, a rule-based system allows
the user to introduce (overt and silent) data corruption and errors.

It exposes itself through /dev/fbd, and a file system can be mounted
on top of it. The new fbdctl(8) tool can be used to control the
driver; see ``man fbdctl'' for details. It also comes with a test
set, located in test/fbdtest.

24 files changed:
commands/DESCRIBE/DESCRIBE.sh
commands/MAKEDEV/MAKEDEV.sh
commands/Makefile
commands/fbdctl/Makefile [new file with mode: 0644]
commands/fbdctl/fbdctl.c [new file with mode: 0644]
commands/profile/sprofalyze.pl
common/include/minix/dmap.h
common/include/sys/Makefile.inc
common/include/sys/ioc_fbd.h [new file with mode: 0644]
docs/UPDATING
drivers/Makefile
drivers/fbd/Makefile [new file with mode: 0644]
drivers/fbd/action.c [new file with mode: 0644]
drivers/fbd/action.h [new file with mode: 0644]
drivers/fbd/fbd.c [new file with mode: 0644]
drivers/fbd/rule.c [new file with mode: 0644]
drivers/fbd/rule.h [new file with mode: 0644]
etc/system.conf
lib/libbdev/ipc.c
man/man8/Makefile
man/man8/fbdctl.8 [new file with mode: 0644]
test/fbdtest/Makefile [new file with mode: 0644]
test/fbdtest/rwblocks.c [new file with mode: 0644]
test/fbdtest/test.sh [new file with mode: 0755]

index ac98174fcdaec5481d691a82b6376e0ade721a30..63560ae368e30a665f5af944bdc3ddc5b37460b6 100644 (file)
@@ -182,7 +182,7 @@ do
        des="audio" dev=audio
        ;;
     14,0)
-       des="audio mixer" dev=mixer
+       des="faulty block device driver" dev=fbd
        ;;
     15,0)
        des="kernel log" dev=klog
index 847783da4936c24909bcf8f61a7e3905e5027f63..15f74e3685248a607639a49ef97af2e7c76b00cc 100644 (file)
@@ -23,7 +23,7 @@ case $#:$1 in
        ttypa ttypb ttypc ttypd ttype ttypf \
        ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 \
        ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf \
-       eth klog random uds filter hello
+       eth klog random uds filter fbd hello
     ;;
 0:|1:-\?)
     cat >&2 <<EOF
@@ -49,6 +49,7 @@ Where key is one of the following:
   kbd                     # Make /dev/kbd
   kbdaux                  # Make /dev/kbdaux
   filter                  # Make /dev/filter
+  fbd                     # Make /dev/fbd
   hello                   # Make /dev/hello
   video                   # Make /dev/video
   std                    # All standard devices
@@ -267,6 +268,11 @@ do
        $e mknod filter b 11 0
        $e chmod 644 filter
        ;;
+    fbd)
+       # faulty block device driver
+       $e mknod fbd b 14 0
+       $e chmod 600 fbd
+       ;;
     hello)
        # hello driver
        $e mknod hello c 17 0
index f3e377ac5e2e3fe2358e3b9c26c40596b219ef08..78387078ebff3dbc75beb7c55e1d8ba858384e7c 100644 (file)
@@ -10,7 +10,7 @@ SUBDIR=       aal add_route arp ash at autil awk \
        comm compress cp crc cron crontab cut date \
        dd decomp16 DESCRIBE dev2name devsize df dhcpd \
        dhrystone diff dirname  dis386 dis88 diskctl du dumpcore \
-       ed eject elle elvis env expand factor file \
+       ed eject elle elvis env expand factor fbdctl file \
        find finger fingerd fix fold format fortune fsck.mfs \
        ftp101 gcore gcov-pull getty grep head hexdump host \
        hostaddr id ifconfig ifdef install \
diff --git a/commands/fbdctl/Makefile b/commands/fbdctl/Makefile
new file mode 100644 (file)
index 0000000..76c99c3
--- /dev/null
@@ -0,0 +1,4 @@
+PROG=  fbdctl
+MAN=
+
+.include <bsd.prog.mk>
diff --git a/commands/fbdctl/fbdctl.c b/commands/fbdctl/fbdctl.c
new file mode 100644 (file)
index 0000000..5e951f0
--- /dev/null
@@ -0,0 +1,319 @@
+/* fbdctl - FBD control tool - by D.C. van Moolenbroek */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <minix/ioctl.h>
+#include <minix/u64.h>
+#include <sys/ioc_fbd.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static int usage(char *name)
+{
+       printf("usage:\n");
+       printf("  %s list\n", name);
+       printf("  %s add [-a start[-end]] [-s skip] [-c count] [-rw] "
+               "<action> [params]\n", name);
+       printf("  %s del N\n", name);
+       printf("\n");
+       printf("actions and params:\n");
+       printf("  corrupt [zero|persist|random]\n");
+       printf("  error [OK|EIO]\n");
+       printf("  misdir <start>-<end> <align>\n");
+       printf("  lost\n");
+       printf("  torn <lead>\n");
+       printf("use %s -d <device> to specify a device other than /dev/fbd\n",
+               name);
+
+       return EXIT_FAILURE;
+}
+
+static void print_rule(struct fbd_rule *rule)
+{
+       printf("%-2d %04lX%08lX-%04lX%08lX %-4d %-5d %c%c ",
+               rule->num, ex64hi(rule->start), ex64lo(rule->start),
+               ex64hi(rule->end), ex64lo(rule->end), rule->skip,
+               rule->count, (rule->flags & FBD_FLAG_READ) ? 'r' : ' ',
+               (rule->flags & FBD_FLAG_WRITE) ? 'w' : ' ');
+
+       switch (rule->action) {
+       case FBD_ACTION_CORRUPT:
+               printf("%-7s ", "corrupt");
+               switch (rule->params.corrupt.type) {
+               case FBD_CORRUPT_ZERO: printf("zero"); break;
+               case FBD_CORRUPT_PERSIST: printf("persist"); break;
+               case FBD_CORRUPT_RANDOM: printf("random"); break;
+               default: printf("<unknown>");
+               }
+               break;
+
+       case FBD_ACTION_ERROR:
+               printf("%-7s ", "error");
+
+               switch (rule->params.error.code) {
+               case 0:
+                       printf("OK");
+                       break;
+               case EIO:
+               case -EIO:
+                       printf("EIO");
+                       break;
+               default:
+                       printf("%d", rule->params.error.code);
+               }
+
+               break;
+
+       case FBD_ACTION_MISDIR:
+               printf("%-7s %04lX%08lX-%04lX%08lX %u",
+                       "misdir", ex64hi(rule->params.misdir.start),
+                       ex64lo(rule->params.misdir.start),
+                       ex64hi(rule->params.misdir.end),
+                       ex64lo(rule->params.misdir.end),
+                       rule->params.misdir.align);
+               break;
+
+       case FBD_ACTION_LOSTTORN:
+               if (rule->params.losttorn.lead > 0)
+                       printf("%-7s %u", "torn",
+                               rule->params.losttorn.lead);
+               else
+                       printf("%-7s", "lost");
+       }
+
+       printf("\n");
+}
+
+static int do_list(int fd)
+{
+       struct fbd_rule rule;
+       int i;
+
+       printf("N  Start        End          Skip Count RW Action  Params\n");
+
+       for (i = 1; ; i++) {
+               rule.num = i;
+
+               if (ioctl(fd, FBDCGETRULE, &rule) < 0) {
+                       if (errno == ENOENT)
+                               continue;
+                       break;
+               }
+
+               print_rule(&rule);
+       }
+
+       return EXIT_SUCCESS;
+}
+
+static int scan_hex64(char *input, u64_t *val)
+{
+       u32_t lo, hi;
+       char buf[9];
+       int len;
+
+       len = strlen(input);
+
+       if (len < 1 || len > 16) return 0;
+
+       if (len > 8) {
+               memcpy(buf, input, len - 8);
+               buf[len - 8] = 0;
+               input += len - 8;
+
+               hi = strtoul(buf, NULL, 16);
+       }
+       else hi = 0;
+
+       lo = strtoul(input, NULL, 16);
+
+       *val = make64(lo, hi);
+
+       return 1;
+}
+
+static int scan_range(char *input, u64_t *start, u64_t *end, int need_end)
+{
+       char *p;
+
+       if ((p = strchr(input, '-')) != NULL) {
+               *p++ = 0;
+
+               if (!scan_hex64(p, end)) return 0;
+       }
+       else if (need_end) return 0;
+
+       return scan_hex64(input, start);
+}
+
+static int do_add(int fd, int argc, char **argv, int off)
+{
+       struct fbd_rule rule;
+       int c, r;
+
+       memset(&rule, 0, sizeof(rule));
+
+       while ((c = getopt(argc-off, argv+off, "a:s:c:rw")) != EOF) {
+               switch (c) {
+               case 'a':
+                       if (!scan_range(optarg, &rule.start, &rule.end, 0))
+                               return usage(argv[0]);
+                       break;
+               case 's':
+                       rule.skip = atoi(optarg);
+                       break;
+               case 'c':
+                       rule.count = atoi(optarg);
+                       break;
+               case 'r':
+                       rule.flags |= FBD_FLAG_READ;
+                       break;
+               case 'w':
+                       rule.flags |= FBD_FLAG_WRITE;
+                       break;
+               default:
+                       return usage(argv[0]);
+               }
+       }
+
+       optind += off; /* compensate for the shifted argc/argv */
+
+       if (optind >= argc) return usage(argv[0]);
+
+       /* default to reads and writes */
+       if (!rule.flags) rule.flags = FBD_FLAG_READ | FBD_FLAG_WRITE;
+
+       if (!strcmp(argv[optind], "corrupt")) {
+               if (optind+1 >= argc) return usage(argv[0]);
+
+               rule.action = FBD_ACTION_CORRUPT;
+
+               if (!strcmp(argv[optind+1], "zero"))
+                       rule.params.corrupt.type = FBD_CORRUPT_ZERO;
+               else if (!strcmp(argv[optind+1], "persist"))
+                       rule.params.corrupt.type = FBD_CORRUPT_PERSIST;
+               else if (!strcmp(argv[optind+1], "random"))
+                       rule.params.corrupt.type = FBD_CORRUPT_RANDOM;
+               else return usage(argv[0]);
+       }
+       else if (!strcmp(argv[optind], "error")) {
+               if (optind+1 >= argc) return usage(argv[0]);
+
+               rule.action = FBD_ACTION_ERROR;
+
+               if (!strcmp(argv[optind+1], "OK"))
+                       rule.params.error.code = 0;
+               else if (!strcmp(argv[optind+1], "EIO")) {
+                       if (EIO > 0)
+                               rule.params.error.code = -EIO;
+                       else
+                               rule.params.error.code = EIO;
+               }
+               else return usage(argv[0]);
+       }
+       else if (!strcmp(argv[optind], "misdir")) {
+               if (optind+2 >= argc) return usage(argv[0]);
+
+               rule.action = FBD_ACTION_MISDIR;
+
+               if (!scan_range(argv[optind+1], &rule.params.misdir.start,
+                               &rule.params.misdir.end, 1))
+                       return usage(argv[0]);
+
+               rule.params.misdir.align = atoi(argv[optind+2]);
+
+               if ((int) rule.params.misdir.align <= 0)
+                       return usage(argv[0]);
+       }
+       else if (!strcmp(argv[optind], "lost")) {
+               rule.action = FBD_ACTION_LOSTTORN;
+
+               rule.params.losttorn.lead = 0;
+       }
+       else if (!strcmp(argv[optind], "torn")) {
+               if (optind+1 >= argc) return usage(argv[0]);
+
+               rule.action = FBD_ACTION_LOSTTORN;
+
+               rule.params.losttorn.lead = atoi(argv[optind+1]);
+
+               if ((int) rule.params.losttorn.lead <= 0)
+                       return usage(argv[0]);
+       }
+       else return usage(argv[0]);
+
+#if DEBUG
+       print_rule(&rule);
+#endif
+
+       r = ioctl(fd, FBDCADDRULE, &rule);
+
+       if (r < 0) {
+               perror("ioctl");
+
+               return EXIT_FAILURE;
+       }
+
+       printf("Added rule %d\n", r);
+
+       return EXIT_SUCCESS;
+}
+
+static int do_del(int fd, int argc, char **argv, int off)
+{
+       fbd_rulenum_t num;
+
+       if (argc < off + 2)
+               return usage(argv[0]);
+
+       num = atoi(argv[off + 1]);
+
+       if (ioctl(fd, FBDCDELRULE, &num)) {
+               perror("ioctl");
+
+               return EXIT_FAILURE;
+       }
+
+       printf("Deleted rule %d\n", num);
+
+       return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+       int r, fd, off = 1;
+       char *dev = "/dev/fbd";
+
+       if (argc < 2)
+               return usage(argv[0]);
+
+       if (!strcmp(argv[1], "-d")) {
+               if (argc < 4)
+                       return usage(argv[0]);
+
+               dev = argv[2];
+
+               off += 2;
+       }
+
+       fd = open(dev, O_RDONLY);
+       if (fd < 0) {
+               perror(dev);
+
+               return EXIT_FAILURE;
+       }
+
+       else if (!strcmp(argv[off], "list"))
+               r = do_list(fd);
+       else if (!strcmp(argv[off], "add"))
+               r = do_add(fd, argc, argv, off);
+       else if (!strcmp(argv[off], "del"))
+               r = do_del(fd, argc, argv, off);
+       else
+               r = usage(argv[0]);
+
+       close(fd);
+
+       return r;
+}
index 1632d894e3ccabb56423c142ff4da9e5db524491..1721c0dc0727dc907e8c243f010600c0217c98d7 100755 (executable)
@@ -54,6 +54,7 @@ drivers/dec21140A/dec21140A
 drivers/dp8390/dp8390
 drivers/dpeth/dpeth
 drivers/e1000/e1000
+drivers/fbd/fbd
 drivers/filter/filter
 drivers/floppy/floppy
 drivers/fxp/fxp
index e68936d1b8c9a5c563c96d02b132c7ad3502d31c..562a20ab88253b6887f3b7dda2411138af833e4c 100644 (file)
@@ -30,7 +30,7 @@ enum dev_style { STYLE_NDEV, STYLE_DEV, STYLE_DEVA, STYLE_TTY, STYLE_CTTY,
 #define FILTER_MAJOR             11    /* 11 = /dev/filter (filter driver)   */
                                        /* 12 = /dev/c3                       */
 #define AUDIO_MAJOR              13    /* 13 = /dev/audio  (audio driver)    */
-                                       /* 14 = not used                      */
+#define FBD_MAJOR                14    /* 14 = /dev/fbd    (faulty block dev)*/
 #define LOG_MAJOR                15    /* 15 = /dev/klog   (log driver)      */
 #define RANDOM_MAJOR             16    /* 16 = /dev/random (random driver)   */
 #define HELLO_MAJOR              17    /* 17 = /dev/hello  (hello driver)    */
index 2f62740f1c22742c3a7268cfb3cd3a1be0a8854a..1dd24da7b120f78934be716079f0453eeebb384d 100644 (file)
@@ -3,7 +3,7 @@
 .PATH: ${MINIXSRCDIR}/common/include/sys
 
 INCS+=         elf32.h elf64.h elf_common.h elf_generic.h \
-       ioc_block.h ioc_file.h ioc_tape.h ioc_disk.h \
+       ioc_block.h ioc_fbd.h ioc_file.h ioc_tape.h ioc_disk.h \
        ioc_memory.h ioc_sound.h ioc_tty.h \
        kbdio.h mtio.h svrctl.h video.h vm.h procfs.h elf_core.h exec_elf.h
 
diff --git a/common/include/sys/ioc_fbd.h b/common/include/sys/ioc_fbd.h
new file mode 100644 (file)
index 0000000..da72667
--- /dev/null
@@ -0,0 +1,66 @@
+/*     sys/ioc_fbd.h - Faulty Block Device ioctl() command codes.
+ *
+ */
+
+#ifndef _S_I_FBD_H
+#define _S_I_FBD_H
+
+/* FBD rule structure. */
+
+typedef int fbd_rulenum_t;
+
+struct fbd_rule {
+       fbd_rulenum_t num;      /* rule number (1-based) */
+       u64_t start;            /* start position of area to match */
+       u64_t end;              /* end position (exclusive) (0 = up to EOF) */
+       int flags;              /* match read and/or write requests */
+       unsigned int skip;      /* # matches to skip before activating */
+       unsigned int count;     /* # times left to trigger (0 = no limit) */
+       int action;             /* action type to perform when triggered */
+
+       union {                 /* action parameters */
+               struct {
+                       int type;       /* corruption type */
+               } corrupt;
+
+               struct {
+                       int code;       /* error code to return (OK, EIO..) */
+               } error;
+
+               struct {
+                       u64_t start;    /* start position of target area */
+                       u64_t end;      /* end position of area (excl) */
+                       u32_t align;    /* alignment to use in target area */
+               } misdir;
+
+               struct {
+                       u32_t lead;     /* # bytes to process normally */
+               } losttorn;
+       } params;
+};
+
+/* Match flags. */
+#define FBD_FLAG_READ  0x1             /* match read requests */
+#define FBD_FLAG_WRITE 0x2             /* match write requests */
+
+/* Action types. */
+enum {
+       FBD_ACTION_CORRUPT,             /* write or return corrupt data */
+       FBD_ACTION_ERROR,               /* return an I/O error */
+       FBD_ACTION_MISDIR,              /* perform a misdirected write */
+       FBD_ACTION_LOSTTORN             /* perform a lost or torn write */
+};
+
+/* Corruption types. */
+enum {
+       FBD_CORRUPT_ZERO,               /* write or return ..zeroed data */
+       FBD_CORRUPT_PERSIST,            /* ..consequently the same bad data */
+       FBD_CORRUPT_RANDOM              /* ..new random data every time */
+};
+
+/* The I/O control requests. */
+#define FBDCADDRULE    _IOW('F', 1, struct fbd_rule)   /* add a rule */
+#define FBDCDELRULE    _IOW('F', 2, fbd_rulenum_t)     /* delete a rule */
+#define FBDCGETRULE    _IORW('F', 3, struct fbd_rule)  /* retrieve a rule */
+
+#endif /* _S_I_FBD_H */
index acf2e2e7f0c232982976cbd247800a86d6612cdc..293931c4cf4597d57b5a736af42964a5378f7573 100644 (file)
@@ -1,3 +1,8 @@
+20111212:
+       After a successful "make world", issue the following commands:
+       # cd /dev
+       # MAKEDEV fbd
+
 20111109:
        Switch to NetBSD passwd system.
 
index ad932c7b5765f6765bb19bc5e5d4f6b05d4cdc5e..3b00bc8eca85db5771919419615cde6a51610d9c 100644 (file)
@@ -12,7 +12,7 @@ SUBDIR=       at_wini bios_wini floppy log tty pci .WAIT ramdisk .WAIT memory
 
 # memory driver must be last for ramdisk image
 SUBDIR+= ahci amddev atl2 at_wini audio bios_wini dec21140A dp8390 dpeth \
-       e1000 filter floppy fxp hello lance log orinoco pci printer \
+       e1000 fbd filter floppy fxp hello lance log orinoco pci printer \
        random readclock rtl8139 rtl8169 ti1225 tty vbox acpi \
        .WAIT ramdisk .WAIT memory
 
diff --git a/drivers/fbd/Makefile b/drivers/fbd/Makefile
new file mode 100644 (file)
index 0000000..8ec7130
--- /dev/null
@@ -0,0 +1,22 @@
+# Makefile for the Faulty Block Device (FBD)
+
+.include <bsd.own.mk>
+
+PROG=  fbd
+SRCS=  fbd.c rule.c action.c
+
+DPADD+=        ${LIBBLOCKDRIVER} ${LIBSYS}
+LDADD+=        -lblockdriver -lsys -lc
+CPPFLAGS+=     -DDEBUG=0
+
+# The FBD driver requires NetBSD libc.
+.if ${COMPILER_TYPE} != "gnu"
+CC:=clang
+COMPILER_TYPE:=gnu
+.endif
+
+MAN=
+
+BINDIR?= /usr/sbin
+
+.include <minix.service.mk>
diff --git a/drivers/fbd/action.c b/drivers/fbd/action.c
new file mode 100644 (file)
index 0000000..9ed6d92
--- /dev/null
@@ -0,0 +1,302 @@
+#include <minix/drivers.h>
+#include <sys/ioc_fbd.h>
+#include <assert.h>
+
+#include "rule.h"
+
+/*===========================================================================*
+ *                             get_rand                                     *
+ *===========================================================================*/
+PRIVATE u32_t get_rand(u32_t max)
+{
+       /* Las Vegas algorithm for getting a random number in the range from
+        * 0 to max, inclusive.
+        */
+       u32_t val, top;
+
+       /* Get an initial random number. */
+       val = lrand48() ^ (lrand48() << 1);
+
+       /* Make 'max' exclusive. If it wraps, we can use the full width. */
+       if (++max == 0) return val;
+
+       /* Find the largest multiple of the given range, and return a random
+        * number from the range, throwing away any random numbers not below
+        * this largest multiple.
+        */
+       top = (((u32_t) -1) / max) * max;
+
+       while (val >= top)
+               val = lrand48() ^ (lrand48() << 1);
+
+       return val % max;
+}
+
+/*===========================================================================*
+ *                             get_range                                    *
+ *===========================================================================*/
+PRIVATE size_t get_range(struct fbd_rule *rule, u64_t pos, size_t *size,
+       u64_t *skip)
+{
+       /* Compute the range within the given request range that is affected
+        * by the given rule, and optionally the number of bytes preceding
+        * the range that are also affected by the rule.
+        */
+       u64_t delta;
+       size_t off;
+       int to_eof;
+
+       to_eof = cmp64(rule->start, rule->end) >= 0;
+
+       if (cmp64(pos, rule->start) > 0) {
+               if (skip != NULL) *skip = sub64(pos, rule->start);
+
+               off = 0;
+       }
+       else {
+               if (skip != NULL) *skip = cvu64(0);
+
+               delta = sub64(rule->start, pos);
+
+               assert(ex64hi(delta) == 0);
+
+               off = ex64lo(delta);
+       }
+
+       if (!to_eof) {
+               assert(cmp64(pos, rule->end) < 0);
+
+               delta = sub64(rule->end, pos);
+
+               if (cmp64u(delta, *size) < 0)
+                       *size = ex64lo(delta);
+       }
+
+       assert(*size > off);
+
+       *size -= off;
+
+       return off;
+}
+
+/*===========================================================================*
+ *                             limit_range                                  *
+ *===========================================================================*/
+PRIVATE void limit_range(iovec_t *iov, unsigned *count, size_t size)
+{
+       /* Limit the given vector to the given size.
+        */
+       size_t chunk;
+       int i;
+
+       for (i = 0; i < *count && size > 0; i++) {
+               chunk = MIN(iov[i].iov_size, size);
+
+               if (chunk == size)
+                       iov[i].iov_size = size;
+
+               size -= chunk;
+       }
+
+       *count = i;
+}
+
+/*===========================================================================*
+ *                             action_io_corrupt                            *
+ *===========================================================================*/
+PRIVATE void action_io_corrupt(struct fbd_rule *rule, char *buf, size_t size,
+       u64_t pos, int UNUSED(flag))
+{
+       u64_t skip;
+       u32_t val;
+
+       buf += get_range(rule, pos, &size, &skip);
+
+       switch (rule->params.corrupt.type) {
+       case FBD_CORRUPT_ZERO:
+               memset(buf, 0, size);
+               break;
+
+       case FBD_CORRUPT_PERSIST:
+               /* Non-dword-aligned positions and sizes are not supported;
+                * not by us, and not by the driver.
+                */
+               if (ex64lo(pos) & (sizeof(val) - 1)) break;
+               if (size & (sizeof(val) - 1)) break;
+
+               /* Consistently produce the same pattern for the same range. */
+               val = ex64lo(skip);
+
+               for ( ; size >= sizeof(val); size -= sizeof(val)) {
+                       *((u32_t *) buf) = val ^ 0xdeadbeefUL;
+
+                       val += sizeof(val);
+                       buf += sizeof(val);
+               }
+
+               break;
+
+       case FBD_CORRUPT_RANDOM:
+               while (size--)
+                       *buf++ = get_rand(255);
+
+               break;
+
+       default:
+               printf("FBD: unknown corruption type %d\n",
+                       rule->params.corrupt.type);
+       }
+}
+
+/*===========================================================================*
+ *                             action_pre_error                             *
+ *===========================================================================*/
+PRIVATE void action_pre_error(struct fbd_rule *rule, iovec_t *iov,
+       unsigned *count, size_t *size, u64_t *pos)
+{
+       /* Limit the request to the part that precedes the matched range. */
+       *size = get_range(rule, *pos, size, NULL);
+
+       limit_range(iov, count, *size);
+}
+
+/*===========================================================================*
+ *                             action_post_error                            *
+ *===========================================================================*/
+PRIVATE void action_post_error(struct fbd_rule *rule, size_t UNUSED(osize),
+       int *result)
+{
+       /* Upon success of the first part, return the specified error code. */
+       if (*result >= 0 && rule->params.error.code != OK)
+               *result = rule->params.error.code;
+}
+
+/*===========================================================================*
+ *                             action_pre_misdir                            *
+ *===========================================================================*/
+PRIVATE void action_pre_misdir(struct fbd_rule *rule, iovec_t *UNUSED(iov),
+       unsigned *UNUSED(count), size_t *UNUSED(size), u64_t *pos)
+{
+       /* Randomize the request position to fall within the range (and have
+        * the alignment) given by the rule.
+        */
+       u32_t range, choice;
+
+       /* Unfortunately, we cannot interpret 0 as end as "up to end of disk"
+        * here, because we have no idea about the actual disk size, and the
+        * resulting address must of course be valid..
+        */
+       range = div64u(add64u(sub64(rule->params.misdir.end,
+               rule->params.misdir.start), 1), rule->params.misdir.align);
+
+       if (range > 0)
+               choice = get_rand(range - 1);
+       else
+               choice = 0;
+
+       *pos = add64(rule->params.misdir.start,
+               mul64u(choice, rule->params.misdir.align));
+}
+
+/*===========================================================================*
+ *                             action_pre_losttorn                          *
+ *===========================================================================*/
+PRIVATE void action_pre_losttorn(struct fbd_rule *rule, iovec_t *iov,
+       unsigned *count, size_t *size, u64_t *UNUSED(pos))
+{
+       if (*size > rule->params.losttorn.lead)
+               *size = rule->params.losttorn.lead;
+
+       limit_range(iov, count, *size);
+}
+
+/*===========================================================================*
+ *                             action_post_losttorn                         *
+ *===========================================================================*/
+PRIVATE void action_post_losttorn(struct fbd_rule *UNUSED(rule), size_t osize,
+       int *result)
+{
+       /* On success, pretend full completion. */
+
+       if (*result < 0) return;
+
+       *result = osize;
+}
+
+/*===========================================================================*
+ *                             action_mask                                  *
+ *===========================================================================*/
+PUBLIC int action_mask(struct fbd_rule *rule)
+{
+       /* Return the hook mask for the given rule's action type. */
+
+       switch (rule->action) {
+       case FBD_ACTION_CORRUPT:        return IO_HOOK;
+       case FBD_ACTION_ERROR:          return PRE_HOOK | POST_HOOK;
+       case FBD_ACTION_MISDIR:         return PRE_HOOK;
+       case FBD_ACTION_LOSTTORN:       return PRE_HOOK | POST_HOOK;
+       default:
+               printf("FBD: unknown action type %d\n", rule->action);
+               return 0;
+       }
+}
+
+/*===========================================================================*
+ *                             action_pre_hook                              *
+ *===========================================================================*/
+PUBLIC void action_pre_hook(struct fbd_rule *rule, iovec_t *iov,
+       unsigned *count, size_t *size, u64_t *pos)
+{
+       switch (rule->action) {
+       case FBD_ACTION_ERROR:
+               action_pre_error(rule, iov, count, size, pos);
+               break;
+
+       case FBD_ACTION_MISDIR:
+               action_pre_misdir(rule, iov, count, size, pos);
+               break;
+
+       case FBD_ACTION_LOSTTORN:
+               action_pre_losttorn(rule, iov, count, size, pos);
+               break;
+
+       default:
+               printf("FBD: bad action type %d for PRE hook\n", rule->action);
+       }
+}
+
+/*===========================================================================*
+ *                             action_io_hook                               *
+ *===========================================================================*/
+PUBLIC void action_io_hook(struct fbd_rule *rule, char *buf, size_t size,
+       u64_t pos, int flag)
+{
+       switch (rule->action) {
+       case FBD_ACTION_CORRUPT:
+               action_io_corrupt(rule, buf, size, pos, flag);
+               break;
+
+       default:
+               printf("FBD: bad action type %d for IO hook\n", rule->action);
+       }
+}
+
+/*===========================================================================*
+ *                             action_post_hook                             *
+ *===========================================================================*/
+PUBLIC void action_post_hook(struct fbd_rule *rule, size_t osize, int *result)
+{
+       switch (rule->action) {
+       case FBD_ACTION_ERROR:
+               action_post_error(rule, osize, result);
+               return;
+
+       case FBD_ACTION_LOSTTORN:
+               action_post_losttorn(rule, osize, result);
+               return;
+
+       default:
+               printf("FBD: bad action type %d for POST hook\n",
+                       rule->action);
+       }
+}
diff --git a/drivers/fbd/action.h b/drivers/fbd/action.h
new file mode 100644 (file)
index 0000000..1d7fca5
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef _FBD_ACTION_H
+#define _FBD_ACTION_H
+
+extern int action_mask(struct fbd_rule *rule);
+
+extern void action_pre_hook(struct fbd_rule *rule, iovec_t *iov,
+       unsigned *count, size_t *size, u64_t *pos);
+extern void action_io_hook(struct fbd_rule *rule, char *buf, size_t size,
+       u64_t pos, int flag);
+extern void action_post_hook(struct fbd_rule *rule, size_t osize, int *result);
+
+#endif /* _FBD_ACTION_H */
diff --git a/drivers/fbd/fbd.c b/drivers/fbd/fbd.c
new file mode 100644 (file)
index 0000000..075048c
--- /dev/null
@@ -0,0 +1,456 @@
+/* Faulty Block Device (fault injection proxy), by D.C. van Moolenbroek */
+#include <stdlib.h>
+#include <minix/drivers.h>
+#include <minix/blockdriver.h>
+#include <minix/drvlib.h>
+#include <minix/ioctl.h>
+#include <sys/ioc_fbd.h>
+#include <minix/ds.h>
+#include <minix/optset.h>
+#include <assert.h>
+
+#include "rule.h"
+
+/* Constants. */
+#define BUF_SIZE (NR_IOREQS * CLICK_SIZE)      /* 256k */
+
+/* Function declarations. */
+PRIVATE int fbd_open(dev_t minor, int access);
+PRIVATE int fbd_close(dev_t minor);
+PRIVATE int fbd_transfer(dev_t minor, int do_write, u64_t position,
+       endpoint_t endpt, iovec_t *iov, unsigned int nr_req, int flags);
+PRIVATE int fbd_ioctl(dev_t minor, unsigned int request, endpoint_t endpt,
+       cp_grant_id_t grant);
+
+/* Variables. */
+PRIVATE char *fbd_buf;                 /* scratch buffer */
+
+PRIVATE char driver_label[32] = "";    /* driver DS label */
+PRIVATE dev_t driver_minor = -1;       /* driver's partition minor to use */
+PRIVATE endpoint_t driver_endpt;       /* driver endpoint */
+
+/* Entry points to this driver. */
+PRIVATE struct blockdriver fbd_dtab = {
+       BLOCKDRIVER_TYPE_OTHER, /* do not handle partition requests */
+       fbd_open,               /* open or mount request, initialize device */
+       fbd_close,              /* release device */
+       fbd_transfer,           /* do the I/O */
+       fbd_ioctl,              /* perform I/O control request */
+       NULL,                   /* nothing to clean up */
+       NULL,                   /* we will not be asked about partitions */
+       NULL,                   /* we will not be asked for geometry */
+       NULL,                   /* ignore leftover hardware interrupts */
+       NULL,                   /* ignore alarms */
+       NULL,                   /* ignore other messages */
+       NULL                    /* no multithreading support */
+};
+
+/* Options supported by this driver. */
+PRIVATE struct optset optset_table[] = {
+       { "label",      OPT_STRING,     driver_label,   sizeof(driver_label) },
+       { "minor",      OPT_INT,        &driver_minor,  10                   },
+       { NULL,         0,              NULL,           0                    }
+};
+
+/*===========================================================================*
+ *                             sef_cb_init_fresh                            *
+ *===========================================================================*/
+PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *UNUSED(info))
+{
+       clock_t uptime;
+       int r;
+
+       /* Parse the given parameters. */
+       if (env_argc > 1)
+               optset_parse(optset_table, env_argv[1]);
+
+       if (driver_label[0] == '\0')
+               panic("no driver label given");
+
+       if (ds_retrieve_label_endpt(driver_label, &driver_endpt))
+               panic("unable to resolve driver label");
+
+       if (driver_minor > 255)
+               panic("no or invalid driver minor given");
+
+#if DEBUG
+       printf("FBD: driver label '%s' (endpt %d), minor %d\n",
+               driver_label, driver_endpt, driver_minor);
+#endif
+
+       /* Initialize resources. */
+       fbd_buf = alloc_contig(BUF_SIZE, 0, NULL);
+
+       assert(fbd_buf != NULL);
+
+       if ((r = getuptime(&uptime)) != OK)
+               panic("getuptime failed (%d)\n", r);
+
+       srand48(uptime);
+
+       /* Announce we are up! */
+       blockdriver_announce(type);
+
+       return OK;
+}
+
+/*===========================================================================*
+ *                             sef_cb_signal_handler                        *
+ *===========================================================================*/
+PRIVATE void sef_cb_signal_handler(int signo)
+{
+       /* Terminate immediately upon receiving a SIGTERM. */
+       if (signo != SIGTERM) return;
+
+#if DEBUG
+       printf("FBD: shutting down\n");
+#endif
+
+       /* Clean up resources. */
+       free_contig(fbd_buf, BUF_SIZE);
+
+       exit(0);
+}
+
+/*===========================================================================*
+ *                             sef_local_startup                            *
+ *===========================================================================*/
+PRIVATE void sef_local_startup(void)
+{
+       /* Register init callbacks. */
+       sef_setcb_init_fresh(sef_cb_init_fresh);
+       sef_setcb_init_restart(sef_cb_init_fresh);
+       sef_setcb_init_lu(sef_cb_init_fresh);
+
+       /* Register signal callback. */
+       sef_setcb_signal_handler(sef_cb_signal_handler);
+
+       /* Let SEF perform startup. */
+       sef_startup();
+}
+
+/*===========================================================================*
+ *                             main                                         *
+ *===========================================================================*/
+PUBLIC int main(int argc, char **argv)
+{
+       /* SEF local startup. */
+       env_setargs(argc, argv);
+       sef_local_startup();
+
+       /* Call the generic receive loop. */
+       blockdriver_task(&fbd_dtab);
+
+       return OK;
+}
+
+/*===========================================================================*
+ *                             fbd_open                                     *
+ *===========================================================================*/
+PRIVATE int fbd_open(dev_t UNUSED(minor), int access)
+{
+       /* Open a device. */
+       message m;
+       int r;
+
+       /* We simply forward this request to the real driver. */
+       memset(&m, 0, sizeof(m));
+       m.m_type = BDEV_OPEN;
+       m.BDEV_MINOR = driver_minor;
+       m.BDEV_ACCESS = access;
+       m.BDEV_ID = 0;
+
+       if ((r = sendrec(driver_endpt, &m)) != OK)
+               panic("sendrec to driver failed (%d)\n", r);
+
+       if (m.m_type != BDEV_REPLY)
+               panic("invalid reply from driver (%d)\n", m.m_type);
+
+       return m.BDEV_STATUS;
+}
+
+/*===========================================================================*
+ *                             fbd_close                                    *
+ *===========================================================================*/
+PRIVATE int fbd_close(dev_t UNUSED(minor))
+{
+       /* Close a device. */
+       message m;
+       int r;
+
+       /* We simply forward this request to the real driver. */
+       memset(&m, 0, sizeof(m));
+       m.m_type = BDEV_CLOSE;
+       m.BDEV_MINOR = driver_minor;
+       m.BDEV_ID = 0;
+
+       if ((r = sendrec(driver_endpt, &m)) != OK)
+               panic("sendrec to driver failed (%d)\n", r);
+
+       if (m.m_type != BDEV_REPLY)
+               panic("invalid reply from driver (%d)\n", m.m_type);
+
+       return m.BDEV_STATUS;
+}
+
+/*===========================================================================*
+ *                             fbd_ioctl                                    *
+ *===========================================================================*/
+PRIVATE int fbd_ioctl(dev_t UNUSED(minor), unsigned int request,
+       endpoint_t endpt, cp_grant_id_t grant)
+{
+       /* Handle an I/O control request. */
+       cp_grant_id_t gid;
+       message m;
+       int r;
+
+       /* We only handle the FBD requests, and pass on everything else. */
+       switch (request) {
+       case FBDCADDRULE:
+       case FBDCDELRULE:
+       case FBDCGETRULE:
+               return rule_ctl(request, endpt, grant);
+       }
+
+       assert(grant != GRANT_INVALID);
+
+       gid = cpf_grant_indirect(driver_endpt, endpt, grant);
+       assert(gid != GRANT_INVALID);
+
+       memset(&m, 0, sizeof(m));
+       m.m_type = BDEV_IOCTL;
+       m.BDEV_MINOR = driver_minor;
+       m.BDEV_REQUEST = request;
+       m.BDEV_GRANT = gid;
+       m.BDEV_ID = 0;
+
+       if ((r = sendrec(driver_endpt, &m)) != OK)
+               panic("sendrec to driver failed (%d)\n", r);
+
+       if (m.m_type != BDEV_REPLY)
+               panic("invalid reply from driver (%d)\n", m.m_type);
+
+       cpf_revoke(gid);
+
+       return m.BDEV_STATUS;
+}
+
+/*===========================================================================*
+ *                             fbd_transfer_direct                          *
+ *===========================================================================*/
+PRIVATE ssize_t fbd_transfer_direct(int do_write, u64_t position,
+       endpoint_t endpt, iovec_t *iov, unsigned int count, int flags)
+{
+       /* Forward the entire transfer request, without any intervention. */
+       iovec_s_t iovec[NR_IOREQS];
+       cp_grant_id_t grant;
+       message m;
+       int i, r;
+
+       for (i = 0; i < count; i++) {
+               iovec[i].iov_size = iov[i].iov_size;
+               iovec[i].iov_grant = cpf_grant_indirect(driver_endpt, endpt,
+                       iov[i].iov_addr);
+               assert(iovec[i].iov_grant != GRANT_INVALID);
+       }
+
+       grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec,
+               count * sizeof(iovec[0]), CPF_READ);
+       assert(grant != GRANT_INVALID);
+
+       m.m_type = do_write ? BDEV_SCATTER : BDEV_GATHER;
+       m.BDEV_MINOR = driver_minor;
+       m.BDEV_COUNT = count;
+       m.BDEV_GRANT = grant;
+       m.BDEV_FLAGS = flags;
+       m.BDEV_ID = 0;
+       m.BDEV_POS_LO = ex64lo(position);
+       m.BDEV_POS_HI = ex64hi(position);
+
+       if ((r = sendrec(driver_endpt, &m)) != OK)
+               panic("sendrec to driver failed (%d)\n", r);
+
+       if (m.m_type != BDEV_REPLY)
+               panic("invalid reply from driver (%d)\n", m.m_type);
+
+       cpf_revoke(grant);
+
+       for (i = 0; i < count; i++)
+               cpf_revoke(iovec[i].iov_grant);
+
+       return m.BDEV_STATUS;
+}
+
+/*===========================================================================*
+ *                             fbd_transfer_copy                            *
+ *===========================================================================*/
+PRIVATE ssize_t fbd_transfer_copy(int do_write, u64_t position,
+       endpoint_t endpt, iovec_t *iov, unsigned int count, size_t size,
+       int flags)
+{
+       /* Interpose on the request. */
+       iovec_s_t iovec[NR_IOREQS];
+       struct vscp_vec vscp_vec[SCPVEC_NR];
+       cp_grant_id_t grant;
+       size_t off, len;
+       message m;
+       char *ptr;
+       int i, j, r;
+       ssize_t rsize;
+
+       assert(count > 0 && count <= SCPVEC_NR);
+
+       if (size > BUF_SIZE) {
+               printf("FBD: allocating memory for %d bytes\n", size);
+
+               ptr = alloc_contig(size, 0, NULL);
+
+               assert(ptr != NULL);
+       }
+       else ptr = fbd_buf;
+
+       /* For write operations, first copy in the data to write. */
+       if (do_write) {
+               for (i = off = 0; i < count; i++) {
+                       len = iov[i].iov_size;
+
+                       vscp_vec[i].v_from = endpt;
+                       vscp_vec[i].v_to = SELF;
+                       vscp_vec[i].v_gid = iov[i].iov_addr;
+                       vscp_vec[i].v_offset = 0;
+                       vscp_vec[i].v_addr = (vir_bytes) (ptr + off);
+                       vscp_vec[i].v_bytes = len;
+
+                       off += len;
+               }
+
+               if ((r = sys_vsafecopy(vscp_vec, i)) != OK)
+                       panic("vsafecopy failed (%d)\n", r);
+
+               /* Trigger write hook. */
+               rule_io_hook(ptr, size, position, FBD_FLAG_WRITE);
+       }
+
+       /* Allocate grants for the data, in the same chunking as the original
+        * vector. This avoids performance fluctuations with bad hardware as
+        * observed with the filter driver.
+        */
+       for (i = off = 0; i < count; i++) {
+               len = iov[i].iov_size;
+
+               iovec[i].iov_size = len;
+               iovec[i].iov_grant = cpf_grant_direct(driver_endpt,
+                       (vir_bytes) (ptr + off), len,
+                       do_write ? CPF_READ : CPF_WRITE);
+               assert(iovec[i].iov_grant != GRANT_INVALID);
+
+               off += len;
+       }
+
+       grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec,
+               count * sizeof(iovec[0]), CPF_READ);
+       assert(grant != GRANT_INVALID);
+
+       m.m_type = do_write ? BDEV_SCATTER : BDEV_GATHER;
+       m.BDEV_MINOR = driver_minor;
+       m.BDEV_COUNT = count;
+       m.BDEV_GRANT = grant;
+       m.BDEV_FLAGS = flags;
+       m.BDEV_ID = 0;
+       m.BDEV_POS_LO = ex64lo(position);
+       m.BDEV_POS_HI = ex64hi(position);
+
+       if ((r = sendrec(driver_endpt, &m)) != OK)
+               panic("sendrec to driver failed (%d)\n", r);
+
+       if (m.m_type != BDEV_REPLY)
+               panic("invalid reply from driver (%d)\n", m.m_type);
+
+       cpf_revoke(grant);
+
+       for (i = 0; i < count; i++)
+               cpf_revoke(iovec[i].iov_grant);
+
+       /* For read operations, finish by copying out the data read. */
+       if (!do_write) {
+               /* Trigger read hook. */
+               rule_io_hook(ptr, size, position, FBD_FLAG_READ);
+
+               /* Upon success, copy back whatever has been processed. */
+               rsize = m.BDEV_STATUS;
+               for (i = j = off = 0; rsize > 0 && i < count; i++) {
+                       len = MIN(rsize, iov[i].iov_size);
+
+                       vscp_vec[j].v_from = SELF;
+                       vscp_vec[j].v_to = endpt;
+                       vscp_vec[j].v_gid = iov[i].iov_addr;
+                       vscp_vec[j].v_offset = 0;
+                       vscp_vec[j].v_addr = (vir_bytes) (ptr + off);
+                       vscp_vec[j].v_bytes = len;
+
+                       off += len;
+                       rsize -= len;
+                       j++;
+               }
+
+               if (j > 0 && (r = sys_vsafecopy(vscp_vec, j)) != OK)
+                       panic("vsafecopy failed (%d)\n", r);
+       }
+
+       if (ptr != fbd_buf)
+               free_contig(ptr, size);
+
+       return m.BDEV_STATUS;
+}
+
+/*===========================================================================*
+ *                             fbd_transfer                                 *
+ *===========================================================================*/
+PRIVATE int fbd_transfer(dev_t UNUSED(minor), int do_write, u64_t position,
+       endpoint_t endpt, iovec_t *iov, unsigned int nr_req, int flags)
+{
+       /* Transfer data from or to the device. */
+       unsigned count;
+       size_t size, osize;
+       int i, hooks;
+       ssize_t r;
+
+       /* Compute the total size of the request. */
+       for (size = i = 0; i < nr_req; i++)
+               size += iov[i].iov_size;
+
+       osize = size;
+       count = nr_req;
+
+       hooks = rule_find(position, size,
+               do_write ? FBD_FLAG_WRITE : FBD_FLAG_READ);
+
+#if DEBUG
+       printf("FBD: %s operation for pos %lx:%08lx size %u -> hooks %x\n",
+               do_write ? "write" : "read", ex64hi(position),
+               ex64lo(position), size, hooks);
+#endif
+
+       if (hooks & PRE_HOOK)
+               rule_pre_hook(iov, &count, &size, &position);
+
+       if (count > 0) {
+               if (hooks & IO_HOOK) {
+                       r = fbd_transfer_copy(do_write, position, endpt, iov,
+                               count, size, flags);
+               } else {
+                       r = fbd_transfer_direct(do_write, position, endpt, iov,
+                               count, flags);
+               }
+       }
+       else r = 0;
+
+       if (hooks & POST_HOOK)
+               rule_post_hook(osize, &r);
+
+#if DEBUG
+       printf("FBD: returning %d\n", r);
+#endif
+
+       return r;
+}
diff --git a/drivers/fbd/rule.c b/drivers/fbd/rule.c
new file mode 100644 (file)
index 0000000..272bd63
--- /dev/null
@@ -0,0 +1,185 @@
+#include <minix/drivers.h>
+#include <minix/ioctl.h>
+#include <sys/ioc_fbd.h>
+
+#include "action.h"
+#include "rule.h"
+
+PRIVATE struct fbd_rule rules[MAX_RULES];
+PRIVATE struct fbd_rule *matches[MAX_RULES];
+PRIVATE int nr_matches;
+
+/*===========================================================================*
+ *                             rule_ctl                                     *
+ *===========================================================================*/
+PUBLIC int rule_ctl(int request, endpoint_t endpt, cp_grant_id_t grant)
+{
+       /* Handle an I/O control request regarding rules. */
+       fbd_rulenum_t i;
+       int r;
+
+       /* Note that any of the safecopy calls may fail if the ioctl is
+        * improperly defined in userland; never panic if they fail!
+        */
+       switch (request) {
+       case FBDCADDRULE:
+               /* Find a free rule slot. */
+               for (i = 1; i <= MAX_RULES; i++)
+                       if (rules[i-1].num == 0)
+                               break;
+
+               if (i == MAX_RULES+1)
+                       return ENOMEM;
+
+               /* Copy in the rule. */
+               if ((r = sys_safecopyfrom(endpt, grant, 0,
+                               (vir_bytes) &rules[i-1], sizeof(rules[0]),
+                               D)) != OK)
+                       return r;
+
+               /* Mark the rule as active, and return its number. */
+               rules[i-1].num = i;
+
+               return i;
+
+       case FBDCDELRULE:
+               /* Copy in the given rule number. */
+               if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &i,
+                               sizeof(i), D)) != OK)
+                       return r;
+
+               /* Fail if the given rule number is not valid or in use.
+                * Allow the caller to determine the maximum rule number.
+                */
+               if (i <= 0 || i > MAX_RULES) return EINVAL;
+
+               if (rules[i-1].num != i) return ENOENT;
+
+               /* Mark the rule as not active. */
+               rules[i-1].num = 0;
+
+               return OK;
+
+       case FBDCGETRULE:
+               /* Copy in just the rule number from the given structure. */
+               if ((r = sys_safecopyfrom(endpt, grant,
+                               offsetof(struct fbd_rule, num), (vir_bytes) &i,
+                               sizeof(i), D)) != OK)
+                       return r;
+
+               /* Fail if the given rule number is not valid or in use.
+                * Allow the caller to determine the maximum rule number.
+                */
+               if (i <= 0 || i > MAX_RULES) return EINVAL;
+
+               if (rules[i-1].num != i) return ENOENT;
+
+               /* Copy out the entire rule as is. */
+               return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rules[i-1],
+                               sizeof(rules[0]), D);
+
+       default:
+               return EINVAL;
+       }
+}
+
+/*===========================================================================*
+ *                             rule_match                                   *
+ *===========================================================================*/
+PRIVATE int rule_match(struct fbd_rule *rule, u64_t pos, size_t size, int flag)
+{
+       /* Check whether the given rule matches the given parameters. As side
+        * effect, update counters in the rule as appropriate.
+        */
+
+       /* Ranges must overlap (start < pos+size && end > pos). */
+       if (cmp64(rule->start, add64u(pos, size)) >= 0 ||
+                       (cmp64u(rule->end, 0) && cmp64(rule->end, pos) <= 0))
+               return FALSE;
+
+       /* Flags must match. */
+       if (!(rule->flags & flag)) return FALSE;
+
+       /* This is a match, but is it supposed to trigger yet? */
+       if (rule->skip > 0) {
+               rule->skip--;
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/*===========================================================================*
+ *                             rule_find                                    *
+ *===========================================================================*/
+PUBLIC int rule_find(u64_t pos, size_t size, int flag)
+{
+       /* Find all matching rules, and return a hook mask. */
+       struct fbd_rule *rule;
+       int i, hooks;
+
+       nr_matches = 0;
+       hooks = 0;
+
+       for (i = 0; i < MAX_RULES; i++) {
+               rule = &rules[i];
+
+               if (rule->num == 0) continue;
+
+               if (!rule_match(rule, pos, size, flag))
+                       continue;
+
+               matches[nr_matches++] = rule;
+
+               /* If the rule has a limited lifetime, update it now. */
+               if (rule->count > 0) {
+                       rule->count--;
+
+                       /* Disable the rule from future matching. */
+                       if (rule->count == 0)
+                               rule->num = 0;
+               }
+
+               hooks |= action_mask(rule);
+       }
+
+       return hooks;
+}
+
+/*===========================================================================*
+ *                             rule_pre_hook                                *
+ *===========================================================================*/
+PUBLIC void rule_pre_hook(iovec_t *iov, unsigned *count, size_t *size,
+       u64_t *pos)
+{
+       int i;
+
+       for (i = 0; i < nr_matches; i++)
+               if (action_mask(matches[i]) & PRE_HOOK)
+                       action_pre_hook(matches[i], iov, count, size, pos);
+}
+
+/*===========================================================================*
+ *                             rule_io_hook                                 *
+ *===========================================================================*/
+PUBLIC void rule_io_hook(char *buf, size_t size, u64_t pos, int flag)
+{
+       int i;
+
+       for (i = 0; i < nr_matches; i++)
+               if (action_mask(matches[i]) & IO_HOOK)
+                       action_io_hook(matches[i], buf, size, pos, flag);
+}
+
+/*===========================================================================*
+ *                             rule_post_hook                               *
+ *===========================================================================*/
+PUBLIC void rule_post_hook(size_t osize, int *result)
+{
+       int i;
+
+       for (i = 0; i < nr_matches; i++)
+               if (action_mask(matches[i]) & POST_HOOK)
+                       action_post_hook(matches[i], osize, result);
+}
diff --git a/drivers/fbd/rule.h b/drivers/fbd/rule.h
new file mode 100644 (file)
index 0000000..bf3e6df
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef _FBD_RULE_H
+#define _FBD_RULE_H
+
+#define MAX_RULES      16
+
+extern int rule_ctl(int request, endpoint_t endpt, cp_grant_id_t grant);
+
+extern int rule_find(u64_t pos, size_t size, int flag);
+
+extern void rule_pre_hook(iovec_t *iov, unsigned *count, size_t *size,
+       u64_t *pos);
+extern void rule_io_hook(char *buf, size_t size, u64_t pos, int flag);
+extern void rule_post_hook(size_t osize, int *result);
+
+#define PRE_HOOK       0x1
+#define IO_HOOK                0x2
+#define POST_HOOK      0x4
+
+#endif /* _FBD_RULE_H */
index ead66bbdf508f5efcc6e09f109659ade26a54607..e9ebbf49a0a10347e2f63790be341064ced17551 100644 (file)
@@ -540,3 +540,13 @@ service vbox
        ;
        uid     0;
 };
+
+service fbd
+{
+       ipc
+               SYSTEM VFS RS DS VM
+               ahci
+               at_wini
+               bios_wini
+       ;
+};
index 9a81ca5889958e3be8fd67a55cc9bbd29b2ba932..ce6403e37471cf08ab120c93701133550f1e2cd7 100644 (file)
@@ -38,7 +38,7 @@ static int bdev_recover(dev_t dev, int update_endpt)
   endpoint_t endpt;
   int r, nr_tries;
 
-  printf("bdev: recovering from a driver crash on major %d\n", major(dev));
+  printf("bdev: recovering from a driver restart on major %d\n", major(dev));
 
   for (nr_tries = 0; nr_tries < RECOVER_TRIES; nr_tries++) {
        /* First update the endpoint, if necessary. */
index 1a26413a3b2c5f2d8659bf280eafddd4e57e9acd..ac58a8b22b9945378a837117d92eb72075fb3b92 100644 (file)
@@ -1,7 +1,7 @@
 MAN=   add_route.8 backup.8 badblocks.8 boot.8 btrace.8 \
        cdprobe.8 checkhier.8 chown.8 cleantmp.8 config.8 cron.8 \
-       dhcpd.8 diskctl.8 dosminix.8 elvprsv.8 fdisk.8 fingerd.8 ftpd.8 \
-       getty.8 halt.8 hgfs.8 httpd.8 ifconfig.8 inet.8 init.8 \
+       dhcpd.8 diskctl.8 dosminix.8 elvprsv.8 fbdctl.8 fdisk.8 fingerd.8 \
+       ftpd.8 getty.8 halt.8 hgfs.8 httpd.8 ifconfig.8 inet.8 init.8 \
        installboot.8 intr.8 irdpd.8 loadramdisk.8 MAKEDEV.8 \
        mknod.8 monitor.8 netconf.8 newroot.8 nonamed.8 \
        ossdevlinks.8 part.8 partition.8 \
diff --git a/man/man8/fbdctl.8 b/man/man8/fbdctl.8
new file mode 100644 (file)
index 0000000..ab37458
--- /dev/null
@@ -0,0 +1,109 @@
+.TH FBDCTL 8
+.SH NAME
+fbdctl \- Faulty Block Device rule management interface
+.SH SYNOPSIS
+\fBfbdctl\fR \fBadd\fR [\fB-d\fR \fIdevice\fR]
+[\fB-a\fR \fIstart\fR[\fB-\fR\fIend\fR]] [\fB-s\fR \fIskip\fR]
+[\fB-c\fR \fIcount\fR] [\fB-rw\fR] \fIaction\fR [\fIparams\fR]
+.PP
+\fBfbdctl\fR \fBdel\fR [\fB-d\fR \fIdevice\fR] \fIrulenum\fR
+.PP
+\fBfbdctl\fR \fBlist\fR [\fB-d\fR \fIdevice\fR]
+.SH DESCRIPTION
+The Faulty Block Device (FBD) driver is an interposing block device driver
+which can simulate certain disk-level I/O corruption and errors, based on a
+user-provided set of rules. The \fBfbdctl\fR tool allows one to add, delete,
+and list rules on a running FBD driver instance.
+.PP
+The \fBadd\fR subcommand adds a new rule, which will perform a predefined
+faulty action on a disk transfer when triggered. See the ACTIONS subsection
+below. The \fBdel\fR subcommands deletes an existing rule, based on its rule
+number. All currently active rules and their corresponding rule numbers can be
+viewed with the \fBlist\fR subcommand.
+.SH OPTIONS
+.TP 10
+\fB-d\fR \fIdevice\fR
+By default, \fBfbdctl\fR operates on \fB/dev/fbd\fR. With this option, one can
+specify a different device node. This is useful when using multiple FBD
+instances at the same time. The user would have to create extra device nodes
+first in that case.
+.TP 10
+\fB-a\fR [\fIstart\fR[\fB-\fR\fIend\fR]]
+When adding a rule, this option specifies the disk address range for which the
+rule triggers. That is, the rule will trigger when an I/O operation overlaps
+with the given range. Both \fIstart\fR and \fIend\fR are expected to be
+hexadecimal numbers, without a "0x" prefix. The \fIend\fR address is exclusive.
+If no \fIend\fR address is given, the rule will affect the disk from the
+starting address to the disk end. If this option is not provided at all,
+the rule will affect the entire disk.
+.TP 10
+\fB-s\fR \fIskip\fR
+This option makes the new rule refrain from triggering for the given number
+of times it matches. The \fIskip\fR value must be a positive decimal number.
+If this option is omitted, the value is set to zero, meaning the rule will
+start triggering immediately.
+.TP 10
+\fB-c\fR \fIcount\fR
+This option makes the new rule trigger for this many I/O operations when
+matched, after which it will be removed automatically. The \fIcount\fR value
+must be a positive decimal number. If this option is omitted, or a value of
+zero is given, the rule is permanent and will not be removed automatically.
+.TP 10
+\fB-r\fR, \fB-w\fR
+These options allow one to make the new rule trigger on read or write
+operations only. By default, or when both are specified, the new rule will
+trigger for both read and write I/O operations.
+.SH ACTIONS
+The following actions are supported. They are executed when the rule matches
+for a transfer request, and is triggered as a result. Note that the exact
+meaning of the rule's disk address range (as given by the \fB-a\fR option)
+depends on the action type.
+.TP 10
+\fBcorrupt\fR [\fBzero\fR|\fBpersist\fR|\fBrandom\fR]
+In the part of the transfer that matches the disk address range given for the
+rule (i.e., the intersection of the rule range and the transfer range), the
+data will be corrupted. The following corruption policies are supported: the
+data is set to \fBzero\fR, it is \fBpersist\fRently set to the same garbage
+data for the same disk locations, or it is set to different \fBrandom\fR data
+in every transfer.
+.TP 10
+\fBerror\fR [\fBOK\fR|\fBEIO\fR]
+Only the part of the transfer up to the start of the rule's disk address range
+will be performed, after which the given error code is returned. The \fBOK\fR
+code effectively simulates an end-of-disk condition, whereas the \fBEIO\fR code
+simulates generic disk-level I/O failure.
+.TP 10
+\fBmisdir\fR \fIstart\fR\fB-\fR\fIend\fR \fIalign\fR
+Transfer requests that match this rule, will be \fBmisdirected\fR in their
+entirety, to a random location in the given address range, and with the given
+disk byte alignment within that range. The \fIstart\fR and \fIend\fR parameters
+are specified just like the \fB-a\fR option, although the \fIend\fR parameter
+is compulsory here (since the driver does not know the disk end). The
+\fIalign\fR value must be a positive nonzero decimal number, and should be a
+multiple of the medium sector size; a typical value would be 4096.
+.TP 10
+\fBlost\fR
+Transfer requests that match this rule, will be \fBlost\fR in their entirety.
+That is, they will not actually be performed, and yet report successful
+completion.
+.TP 10
+\fBtorn\fR \fIlead\fR
+Transfer requests that match this rule, will be \fBtorn\fR: only the first
+\fIlead\fR bytes of the transfer request will actually be performed, and yet
+completion of the full transfer is reported. The \fIlead\fR value must be a
+positive nonzero decimal number. A \fBtorn\fR action with a \fIlead\fR value of
+zero would be the same as the \fBlost\fR action.
+.SH EXAMPLES
+.TP 10
+.B fbdctl add -a 2000-3000 corrupt zero
+# Zero out the 4096 bytes starting from disk address 0x2000 in any transfer
+that involves any of those bytes.
+.TP 10
+.B fbdctl add -s 9 -c 1 -r error EIO
+# Fail the tenth read request with an I/O error.
+.TP 10
+.B fbdctl add -a A0000-B0000 -w misdir D0000-E0000 4096
+# Misdirect write requests that overlap with the first range, to fall somewhere
+in the second range, at 4K-block-granular alignment.
+.SH AUTHOR
+David van Moolenbroek <david@minix3.org>
diff --git a/test/fbdtest/Makefile b/test/fbdtest/Makefile
new file mode 100644 (file)
index 0000000..4591ff9
--- /dev/null
@@ -0,0 +1,11 @@
+# Makefile for rwblocks
+.include <bsd.own.mk>
+
+PROG=  rwblocks
+SRCS=  rwblocks.c
+
+MAN=
+
+BINDIR?=/usr/sbin
+
+.include <bsd.prog.mk>
diff --git a/test/fbdtest/rwblocks.c b/test/fbdtest/rwblocks.c
new file mode 100644 (file)
index 0000000..74aba62
--- /dev/null
@@ -0,0 +1,190 @@
+/* Simple block pattern reader/writer for testing FBD */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define BLOCK_SIZE 4096        /* set to match root FS to prevent partial I/O */
+
+static int flush_buf(int fd, char *buf, size_t size, size_t write_size)
+{
+       ssize_t r;
+
+       while (write_size <= size) {
+               if ((r = write(fd, buf, write_size)) != write_size) {
+                       if (r < 0)
+                               perror("write");
+                       else
+                               fprintf(stderr, "short write (%d < %d)\n",
+                                       r, write_size);
+
+                       return EXIT_FAILURE;
+               }
+
+               sync();
+
+               buf += write_size;
+               size -= write_size;
+       }
+
+       return EXIT_SUCCESS;
+}
+
+static int write_pattern(int fd, char *pattern, int write_size)
+{
+       char *buf, *ptr;
+       size_t size;
+       int r, count, nblocks;
+
+       /* Only write sizes that are a multiple or a
+        * divisor of the block size, are supported.
+        */
+       nblocks = write_size / BLOCK_SIZE;
+       if (!nblocks) nblocks = 1;
+       size = nblocks * BLOCK_SIZE;
+
+       if ((buf = malloc(size)) == NULL) {
+               perror("malloc");
+
+               return EXIT_FAILURE;
+       }
+
+       count = 0;
+
+       do {
+               ptr = &buf[count * BLOCK_SIZE];
+
+               switch (*pattern) {
+               case 'A':
+               case 'B':
+               case 'C':
+               case 'D':
+               case 'U':
+                       memset(ptr, *pattern, BLOCK_SIZE);
+                       break;
+
+               case '0':
+                       memset(ptr, 0, BLOCK_SIZE);
+                       break;
+
+               case '\0':
+                       memset(ptr, 0, BLOCK_SIZE);
+                       ptr[0] = 'E';
+                       ptr[1] = 'O';
+                       ptr[2] = 'F';
+               }
+
+               if (++count == nblocks) {
+                       if ((r = flush_buf(fd, buf, size, write_size)) !=
+                                       EXIT_SUCCESS) {
+                               free(buf);
+
+                               return r;
+                       }
+
+                       count = 0;
+               }
+       } while (*pattern++);
+
+       if (count > 0)
+               r = flush_buf(fd, buf, count * BLOCK_SIZE, write_size);
+       else
+               r = EXIT_SUCCESS;
+
+       free(buf);
+
+       return r;
+}
+
+static int read_pattern(int fd)
+{
+       char buf[BLOCK_SIZE];
+       unsigned int i, val;
+       ssize_t r;
+
+       for (;;) {
+               memset(buf, '?', sizeof(buf));
+
+               if ((r = read(fd, buf, sizeof(buf))) != sizeof(buf)) {
+                       putchar('#');
+
+                       if (!r) break; /* stop at hard EOF */
+
+                       lseek(fd, sizeof(buf), SEEK_CUR);
+
+                       continue;
+               }
+
+               if (buf[0] == 'E' && buf[1] == 'O' && buf[2] == 'F') {
+                       for (i = 3; i < sizeof(buf); i++)
+                               if (buf[i] != 0) break;
+
+                       if (i == sizeof(buf)) break;
+               }
+
+               for (i = 1; i < sizeof(buf); i++)
+                       if (buf[i] != buf[0]) break;
+
+               if (i == sizeof(buf)) {
+                       switch (buf[0]) {
+                       case 'A':
+                       case 'B':
+                       case 'C':
+                       case 'D':
+                       case 'U':
+                       case '?':
+                               printf("%c", buf[0]);
+                               break;
+
+                       case '\0':
+                               printf("0");
+                               break;
+
+                       default:
+                               printf("X");
+                       }
+
+                       continue;
+               }
+
+               for (i = val = 0; i < sizeof(buf); i++)
+                       val += buf[i];
+
+               printf("%c", 'a' + val % 26);
+       }
+
+       printf("\n");
+
+       return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+       int fd, r;
+
+       if (argc < 2) {
+               fprintf(stderr, "usage: %s <device> [pattern [writesz]]\n",
+                       argv[0]);
+
+               return EXIT_FAILURE;
+       }
+
+       fd = open(argv[1], (argc > 2) ? O_WRONLY : O_RDONLY);
+       if (fd < 0) {
+               perror("open");
+
+               return EXIT_FAILURE;
+       }
+
+       if (argc > 2)
+               r = write_pattern(fd, argv[2],
+                       argv[3] ? atoi(argv[3]) : BLOCK_SIZE);
+       else
+               r = read_pattern(fd);
+
+       close(fd);
+
+       return r;
+}
diff --git a/test/fbdtest/test.sh b/test/fbdtest/test.sh
new file mode 100755 (executable)
index 0000000..304caf0
--- /dev/null
@@ -0,0 +1,199 @@
+#!/bin/sh
+
+# This test set tests the some of the basic functionality of the Faulty Block
+# Device driver. It takes a writable device as input - a small (sub)partition
+# suffices for this purpose. All information on the given device WILL BE LOST,
+# so USE AT YOUR OWN RISK.
+#
+# Currently, a reasonable subset of supported read and write fault injection is
+# tested. Since injection of write faults was the original goal for this
+# driver, the test set for this part of FBD functionality is relatively large.
+#
+# Testing of read faults works as follows. First, a known pattern is written to
+# the actual device. Then FBD is loaded as an overlay over the device. A fault
+# injection rule is set on FBD, and the disk pattern is read back from the FBD
+# device (/dev/fbd). FBD is then unloaded. The test succeeds if the pattern
+# that was read back, matches a certain expected pattern.
+#
+# Testing of write faults works as follows. First, a known pattern is written
+# to the actual device. Then FBD is loaded as an overlay over the device. A
+# fault injection rule is set on FBD, and another pattern is written to the FBD
+# device (/dev/fbd). FBD is unloaded, and the resulting disk pattern is read
+# back from the actual device. This resulting pattern should match a certain
+# expected pattern.
+#
+# Since all raw block I/O requests go through the root file server, this test
+# set heavily depends on the behavior of that root file server. It has been
+# tested with MFS, and may not work with any other file server type. It assumes
+# that a 4K block size is used, and that the file server translates raw block
+# requests to aligned 4K-multiples. The test set also makes assumptions about
+# merging pages in write operations, flushing only upon a sync call, etcetera.
+# Unfortunately, this dependency on the root file server precludes the test set
+# from properly exercising all possible options of FBD.
+
+RWBLOCKS=./rwblocks
+
+devtopair() {
+  label=`awk "/^$(stat -f '%Hr' $1) / "'{print $2}' /proc/dmap`
+  if [ ! -z "$label" ]; then echo "label=$label,minor=`stat -f '%Lr' $1`"; fi
+}
+
+if [ ! -b "$1" ]; then
+  echo "usage: $0 device" >&2
+  exit 1
+fi
+
+PAIR=$(devtopair $1)
+if [ -z "$PAIR" ]; then
+  echo "driver not found for $1" >&2
+  exit 1
+fi
+
+if [ ! -x $RWBLOCKS ]; then
+  make || exit 1
+fi
+
+if [ "`stat -f '%k' /`" != "4096" ]; then
+  echo "The root file system is not using a 4K block size." >&2
+  exit 1
+fi
+
+read -p "This will overwrite the contents of $1. Are you sure? [y/N] " RESP
+case $RESP in
+  [yY]*)
+    ;;
+  *)
+    echo "Hmpf. Okay. Aborting test.."
+    exit 0
+esac
+
+DEV="$1"
+LAST=
+SUCCESS=0
+TOTAL=0
+
+read_test() {
+  OPT=
+  if [ "$1" = "-last" -o "$1" = "-notlast" ]; then
+    OPT=$1
+    shift
+  fi
+  PAT=$1
+  EXP=$2
+  shift 2
+  $RWBLOCKS $DEV $PAT
+  service up /usr/sbin/fbd -dev /dev/fbd -args "$PAIR" || exit 1
+  fbdctl add $@ >/dev/null
+  #fbdctl list
+  RES="`$RWBLOCKS /dev/fbd`"
+  service down fbd
+  echo -n "$RES: "
+  if echo "$RES" | egrep "^$EXP\$" >/dev/null 2>&1; then
+    if [ "$OPT" = "-last" -a "$RES" != "$LAST" ]; then
+      echo FAILURE
+    elif [ "$OPT" = "-notlast" -a "$RES" = "$LAST" ]; then
+      echo FAILURE
+    else
+      echo SUCCESS
+      SUCCESS=`expr $SUCCESS + 1`
+      LAST="$RES"
+    fi
+  else
+    echo FAILURE
+  fi
+  TOTAL=`expr $TOTAL + 1`
+}
+
+write_test() {
+  OPT=
+  if [ "$1" = "-last" -o "$1" = "-notlast" ]; then
+    OPT=$1
+    shift
+  fi
+  PAT=$1
+  EXP=$2
+  WS=$3
+  shift 3
+  $RWBLOCKS $DEV UUUUUUUUUUUUUUUU
+  service up /usr/sbin/fbd -dev /dev/fbd -args "$PAIR" || exit 1
+  fbdctl add $@ >/dev/null
+  #fbdctl list
+  $RWBLOCKS /dev/fbd $PAT $WS
+  service down fbd
+  RES="`$RWBLOCKS $DEV`"
+  echo -n "$RES: "
+  if echo "$RES" | egrep "^$EXP\$" >/dev/null 2>&1; then
+    if [ "$OPT" = "-last" -a "$RES" != "$LAST" ]; then
+      echo FAILURE
+    elif [ "$OPT" = "-notlast" -a "$RES" = "$LAST" ]; then
+      echo FAILURE
+    else
+      echo SUCCESS
+      SUCCESS=`expr $SUCCESS + 1`
+      LAST="$RES"
+    fi
+  else
+    echo FAILURE
+  fi
+  TOTAL=`expr $TOTAL + 1`
+}
+
+read_test AAAAAAAAAAAAAAAA A0AAAAAAAAAAAAAA -a 1000-2000 -r corrupt zero
+
+read_test       AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA' -a 2000-4000 -r corrupt persist
+read_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA' -a 2000-4000 -r corrupt persist
+
+read_test          AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA' -a 5000-8000 -r corrupt random
+read_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA' -a 5000-8000 -r corrupt random
+
+read_test AAAAAAAAAAAAAAAA 'A[a-z]AAAAAAAAAAAAAA' -a 1100-1200 -r corrupt zero
+
+read_test AAAAAAAAAAAAAAAA 'AA#AAAAAAAAAAAAA' -a 2000-3000 -r error EIO
+read_test AAAAAAAAABAAABAA 'AAAAAAAAAB###BAA' -a A800-C800 -r error EIO
+
+read_test ABBBAAAAAAAAAAAA 'ABBB#' -a 4000 -r error OK
+
+write_test AAAAAAAAAAAAAAAA A0AAAAAAAAAAAAAA   512 -a 1000-2000 -w corrupt zero
+write_test AAAAAAAAAAAAAAAA A0AAAAAAAAAAAAAA  4096 -a 1000-2000 -w corrupt zero
+write_test AAAAAAAAAAAAAAAA A0AAAAAAAAAAAAAA 16384 -a 1000-2000 -w corrupt zero
+
+write_test       AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA'   512 -a 2000-4000 -w corrupt persist
+write_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA'   512 -a 2000-4000 -w corrupt persist
+write_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA'  4096 -a 2000-4000 -w corrupt persist
+write_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA'  4096 -a 2000-4000 -w corrupt persist
+write_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA' 16384 -a 2000-4000 -w corrupt persist
+write_test -last AAAAAAAAAAAAAAAA 'AA[a-z][a-z]AAAAAAAAAAAA' 16384 -a 2000-4000 -w corrupt persist
+
+write_test          AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA'   512 -a 5000-8000 -w corrupt random
+write_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA'   512 -a 5000-8000 -w corrupt random
+write_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA'  4096 -a 5000-8000 -w corrupt random
+write_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA'  4096 -a 5000-8000 -w corrupt random
+write_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA' 16384 -a 5000-8000 -w corrupt random
+write_test -notlast AAAAAAAAAAAAAAAA 'AAAAA[a-z][a-z][a-z]AAAAAAAA' 16384 -a 5000-8000 -w corrupt random
+
+write_test AAAAAAAAAAAAAAAA 'A[a-z]AAAAAAAAAAAAAA'   512 -a 1100-1200 -w corrupt zero
+write_test AAAAAAAAAAAAAAAA 'A[a-z]AAAAAAAAAAAAAA'  4096 -a 1100-1200 -w corrupt zero
+write_test AAAAAAAAAAAAAAAA 'A[a-z]AAAAAAAAAAAAAA' 16384 -a 1100-1200 -w corrupt zero
+
+write_test AAAAAAAAAAAAAAAA AAAUUUUUUUUUUUUU   512 -a 3000 -w error EIO
+write_test AAAAAAAAAAAAAAAA AAAUUUUUUUUUUUUU  4096 -a 3000 -w error EIO
+write_test AAAAAAAAAAAAAAAA AAAUUUUUUUUUUUUU 16384 -a 3000 -w error EIO
+
+write_test AAAAAAAAAAAAABAA AAAAAABAAAAAAUAA        4096 -a D000-E000 -w misdir 6000-7000 4096
+write_test AAAAAAAAAAAAABAA 'AAAAAA(AB|BA)AAAAAUAA' 4096 -a D000-E000 -w misdir 6000-8000 4096
+write_test AAAAAAAAAAAAABAA 'AAAAAA(AB|BA)AAAAAUAA' 4096 -a D000-E000 -w misdir 6000-8000 4096
+write_test AAAAAAAAAAAAABAA 'AAAAAA(AB|BA)AAAAAUAA' 4096 -a D000-E000 -w misdir 6000-8000 4096
+
+write_test AAAAAAAAABAAAAAA AAAAAAAAAUAAAAAA   512 -a 9000-A000 -w lost
+write_test AAAAAAAAABAAAAAA AAAAAAAAAUAAAAAA  4096 -a 9000-A000 -w lost
+write_test AAAAAAAAABAAAAAA AAAAAAAAUUUUAAAA 16384 -a 9000-A000 -w lost
+
+write_test AAAAAAAAAAABAAAA 'AAAAAAAAAAA[a-z]AAAA'   512 -a B000-C000 -w torn 512
+write_test AAAAAAAAAAABAAAA 'AAAAAAAAAAA[a-z]AAAA'  4096 -a B000-C000 -w torn 512
+write_test AAAAAAAAAAABAAAA 'AAAAAAAA[a-z]UUUAAAA' 16384 -a B000-C000 -w torn 512
+
+write_test AAAAAAAAAAABAAAA AAAAAAAAAAABAAAA   512 -a B000-C000 -w torn 4096
+write_test AAAAAAAAAAABAAAA AAAAAAAAAAABAAAA  4096 -a B000-C000 -w torn 4096
+write_test AAAAAAAAAAABAAAA AAAAAAAAAUUUAAAA 16384 -a B000-C000 -w torn 4096
+
+echo "$SUCCESS out of $TOTAL tests succeeded."