From e7db2d358879fa5bc123280e43681a93ca8a674c Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Sun, 11 Dec 2011 19:32:13 +0100 Subject: [PATCH] Add fbd -- Faulty Block Device driver 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. --- commands/DESCRIBE/DESCRIBE.sh | 2 +- commands/MAKEDEV/MAKEDEV.sh | 8 +- commands/Makefile | 2 +- commands/fbdctl/Makefile | 4 + commands/fbdctl/fbdctl.c | 319 ++++++++++++++++++++++ commands/profile/sprofalyze.pl | 1 + common/include/minix/dmap.h | 2 +- common/include/sys/Makefile.inc | 2 +- common/include/sys/ioc_fbd.h | 66 +++++ docs/UPDATING | 5 + drivers/Makefile | 2 +- drivers/fbd/Makefile | 22 ++ drivers/fbd/action.c | 302 +++++++++++++++++++++ drivers/fbd/action.h | 12 + drivers/fbd/fbd.c | 456 ++++++++++++++++++++++++++++++++ drivers/fbd/rule.c | 185 +++++++++++++ drivers/fbd/rule.h | 19 ++ etc/system.conf | 10 + lib/libbdev/ipc.c | 2 +- man/man8/Makefile | 4 +- man/man8/fbdctl.8 | 109 ++++++++ test/fbdtest/Makefile | 11 + test/fbdtest/rwblocks.c | 190 +++++++++++++ test/fbdtest/test.sh | 199 ++++++++++++++ 24 files changed, 1925 insertions(+), 9 deletions(-) create mode 100644 commands/fbdctl/Makefile create mode 100644 commands/fbdctl/fbdctl.c create mode 100644 common/include/sys/ioc_fbd.h create mode 100644 drivers/fbd/Makefile create mode 100644 drivers/fbd/action.c create mode 100644 drivers/fbd/action.h create mode 100644 drivers/fbd/fbd.c create mode 100644 drivers/fbd/rule.c create mode 100644 drivers/fbd/rule.h create mode 100644 man/man8/fbdctl.8 create mode 100644 test/fbdtest/Makefile create mode 100644 test/fbdtest/rwblocks.c create mode 100755 test/fbdtest/test.sh diff --git a/commands/DESCRIBE/DESCRIBE.sh b/commands/DESCRIBE/DESCRIBE.sh index ac98174fc..63560ae36 100644 --- a/commands/DESCRIBE/DESCRIBE.sh +++ b/commands/DESCRIBE/DESCRIBE.sh @@ -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 diff --git a/commands/MAKEDEV/MAKEDEV.sh b/commands/MAKEDEV/MAKEDEV.sh index 847783da4..15f74e368 100644 --- a/commands/MAKEDEV/MAKEDEV.sh +++ b/commands/MAKEDEV/MAKEDEV.sh @@ -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 < diff --git a/commands/fbdctl/fbdctl.c b/commands/fbdctl/fbdctl.c new file mode 100644 index 000000000..5e951f061 --- /dev/null +++ b/commands/fbdctl/fbdctl.c @@ -0,0 +1,319 @@ +/* fbdctl - FBD control tool - by D.C. van Moolenbroek */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int usage(char *name) +{ + printf("usage:\n"); + printf(" %s list\n", name); + printf(" %s add [-a start[-end]] [-s skip] [-c count] [-rw] " + " [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 - \n"); + printf(" lost\n"); + printf(" torn \n"); + printf("use %s -d 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(""); + } + 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; +} diff --git a/commands/profile/sprofalyze.pl b/commands/profile/sprofalyze.pl index 1632d894e..1721c0dc0 100755 --- a/commands/profile/sprofalyze.pl +++ b/commands/profile/sprofalyze.pl @@ -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 diff --git a/common/include/minix/dmap.h b/common/include/minix/dmap.h index e68936d1b..562a20ab8 100644 --- a/common/include/minix/dmap.h +++ b/common/include/minix/dmap.h @@ -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) */ diff --git a/common/include/sys/Makefile.inc b/common/include/sys/Makefile.inc index 2f62740f1..1dd24da7b 100644 --- a/common/include/sys/Makefile.inc +++ b/common/include/sys/Makefile.inc @@ -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 index 000000000..da7266798 --- /dev/null +++ b/common/include/sys/ioc_fbd.h @@ -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 */ diff --git a/docs/UPDATING b/docs/UPDATING index acf2e2e7f..293931c4c 100644 --- a/docs/UPDATING +++ b/docs/UPDATING @@ -1,3 +1,8 @@ +20111212: + After a successful "make world", issue the following commands: + # cd /dev + # MAKEDEV fbd + 20111109: Switch to NetBSD passwd system. diff --git a/drivers/Makefile b/drivers/Makefile index ad932c7b5..3b00bc8ec 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -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 index 000000000..8ec71301e --- /dev/null +++ b/drivers/fbd/Makefile @@ -0,0 +1,22 @@ +# Makefile for the Faulty Block Device (FBD) + +.include + +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 diff --git a/drivers/fbd/action.c b/drivers/fbd/action.c new file mode 100644 index 000000000..9ed6d921a --- /dev/null +++ b/drivers/fbd/action.c @@ -0,0 +1,302 @@ +#include +#include +#include + +#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 index 000000000..1d7fca577 --- /dev/null +++ b/drivers/fbd/action.h @@ -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 index 000000000..075048ca7 --- /dev/null +++ b/drivers/fbd/fbd.c @@ -0,0 +1,456 @@ +/* Faulty Block Device (fault injection proxy), by D.C. van Moolenbroek */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000..272bd63df --- /dev/null +++ b/drivers/fbd/rule.c @@ -0,0 +1,185 @@ +#include +#include +#include + +#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 index 000000000..bf3e6dfc6 --- /dev/null +++ b/drivers/fbd/rule.h @@ -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 */ diff --git a/etc/system.conf b/etc/system.conf index ead66bbdf..e9ebbf49a 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -540,3 +540,13 @@ service vbox ; uid 0; }; + +service fbd +{ + ipc + SYSTEM VFS RS DS VM + ahci + at_wini + bios_wini + ; +}; diff --git a/lib/libbdev/ipc.c b/lib/libbdev/ipc.c index 9a81ca588..ce6403e37 100644 --- a/lib/libbdev/ipc.c +++ b/lib/libbdev/ipc.c @@ -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. */ diff --git a/man/man8/Makefile b/man/man8/Makefile index 1a26413a3..ac58a8b22 100644 --- a/man/man8/Makefile +++ b/man/man8/Makefile @@ -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 index 000000000..ab37458e7 --- /dev/null +++ b/man/man8/fbdctl.8 @@ -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 diff --git a/test/fbdtest/Makefile b/test/fbdtest/Makefile new file mode 100644 index 000000000..4591ff970 --- /dev/null +++ b/test/fbdtest/Makefile @@ -0,0 +1,11 @@ +# Makefile for rwblocks +.include + +PROG= rwblocks +SRCS= rwblocks.c + +MAN= + +BINDIR?=/usr/sbin + +.include diff --git a/test/fbdtest/rwblocks.c b/test/fbdtest/rwblocks.c new file mode 100644 index 000000000..74aba62de --- /dev/null +++ b/test/fbdtest/rwblocks.c @@ -0,0 +1,190 @@ +/* Simple block pattern reader/writer for testing FBD */ + +#include +#include +#include +#include +#include + +#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 [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 index 000000000..304caf096 --- /dev/null +++ b/test/fbdtest/test.sh @@ -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."