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
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
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
$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
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 \
--- /dev/null
+PROG= fbdctl
+MAN=
+
+.include <bsd.prog.mk>
--- /dev/null
+/* 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;
+}
drivers/dp8390/dp8390
drivers/dpeth/dpeth
drivers/e1000/e1000
+drivers/fbd/fbd
drivers/filter/filter
drivers/floppy/floppy
drivers/fxp/fxp
#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) */
.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
--- /dev/null
+/* 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 */
+20111212:
+ After a successful "make world", issue the following commands:
+ # cd /dev
+ # MAKEDEV fbd
+
20111109:
Switch to NetBSD passwd system.
# 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
--- /dev/null
+# 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>
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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 */
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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 */
;
uid 0;
};
+
+service fbd
+{
+ ipc
+ SYSTEM VFS RS DS VM
+ ahci
+ at_wini
+ bios_wini
+ ;
+};
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. */
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 \
--- /dev/null
+.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>
--- /dev/null
+# Makefile for rwblocks
+.include <bsd.own.mk>
+
+PROG= rwblocks
+SRCS= rwblocks.c
+
+MAN=
+
+BINDIR?=/usr/sbin
+
+.include <bsd.prog.mk>
--- /dev/null
+/* 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;
+}
--- /dev/null
+#!/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."