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.
This commit is contained in:
David van Moolenbroek 2011-12-11 19:32:13 +01:00
parent f65e531ee4
commit e7db2d3588
24 changed files with 1925 additions and 9 deletions

View file

@ -182,7 +182,7 @@ do
des="audio" dev=audio
;;
14,0)
des="audio mixer" dev=mixer
des="faulty block device driver" dev=fbd
;;
15,0)
des="kernel log" dev=klog

View file

@ -23,7 +23,7 @@ case $#:$1 in
ttypa ttypb ttypc ttypd ttype ttypf \
ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 \
ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf \
eth klog random uds filter hello
eth klog random uds filter fbd hello
;;
0:|1:-\?)
cat >&2 <<EOF
@ -49,6 +49,7 @@ Where key is one of the following:
kbd # Make /dev/kbd
kbdaux # Make /dev/kbdaux
filter # Make /dev/filter
fbd # Make /dev/fbd
hello # Make /dev/hello
video # Make /dev/video
std # All standard devices
@ -267,6 +268,11 @@ do
$e mknod filter b 11 0
$e chmod 644 filter
;;
fbd)
# faulty block device driver
$e mknod fbd b 14 0
$e chmod 600 fbd
;;
hello)
# hello driver
$e mknod hello c 17 0

View file

@ -10,7 +10,7 @@ SUBDIR= aal add_route arp ash at autil awk \
comm compress cp crc cron crontab cut date \
dd decomp16 DESCRIBE dev2name devsize df dhcpd \
dhrystone diff dirname dis386 dis88 diskctl du dumpcore \
ed eject elle elvis env expand factor file \
ed eject elle elvis env expand factor fbdctl file \
find finger fingerd fix fold format fortune fsck.mfs \
ftp101 gcore gcov-pull getty grep head hexdump host \
hostaddr id ifconfig ifdef install \

4
commands/fbdctl/Makefile Normal file
View file

@ -0,0 +1,4 @@
PROG= fbdctl
MAN=
.include <bsd.prog.mk>

319
commands/fbdctl/fbdctl.c Normal file
View file

@ -0,0 +1,319 @@
/* fbdctl - FBD control tool - by D.C. van Moolenbroek */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <minix/ioctl.h>
#include <minix/u64.h>
#include <sys/ioc_fbd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
static int usage(char *name)
{
printf("usage:\n");
printf(" %s list\n", name);
printf(" %s add [-a start[-end]] [-s skip] [-c count] [-rw] "
"<action> [params]\n", name);
printf(" %s del N\n", name);
printf("\n");
printf("actions and params:\n");
printf(" corrupt [zero|persist|random]\n");
printf(" error [OK|EIO]\n");
printf(" misdir <start>-<end> <align>\n");
printf(" lost\n");
printf(" torn <lead>\n");
printf("use %s -d <device> to specify a device other than /dev/fbd\n",
name);
return EXIT_FAILURE;
}
static void print_rule(struct fbd_rule *rule)
{
printf("%-2d %04lX%08lX-%04lX%08lX %-4d %-5d %c%c ",
rule->num, ex64hi(rule->start), ex64lo(rule->start),
ex64hi(rule->end), ex64lo(rule->end), rule->skip,
rule->count, (rule->flags & FBD_FLAG_READ) ? 'r' : ' ',
(rule->flags & FBD_FLAG_WRITE) ? 'w' : ' ');
switch (rule->action) {
case FBD_ACTION_CORRUPT:
printf("%-7s ", "corrupt");
switch (rule->params.corrupt.type) {
case FBD_CORRUPT_ZERO: printf("zero"); break;
case FBD_CORRUPT_PERSIST: printf("persist"); break;
case FBD_CORRUPT_RANDOM: printf("random"); break;
default: printf("<unknown>");
}
break;
case FBD_ACTION_ERROR:
printf("%-7s ", "error");
switch (rule->params.error.code) {
case 0:
printf("OK");
break;
case EIO:
case -EIO:
printf("EIO");
break;
default:
printf("%d", rule->params.error.code);
}
break;
case FBD_ACTION_MISDIR:
printf("%-7s %04lX%08lX-%04lX%08lX %u",
"misdir", ex64hi(rule->params.misdir.start),
ex64lo(rule->params.misdir.start),
ex64hi(rule->params.misdir.end),
ex64lo(rule->params.misdir.end),
rule->params.misdir.align);
break;
case FBD_ACTION_LOSTTORN:
if (rule->params.losttorn.lead > 0)
printf("%-7s %u", "torn",
rule->params.losttorn.lead);
else
printf("%-7s", "lost");
}
printf("\n");
}
static int do_list(int fd)
{
struct fbd_rule rule;
int i;
printf("N Start End Skip Count RW Action Params\n");
for (i = 1; ; i++) {
rule.num = i;
if (ioctl(fd, FBDCGETRULE, &rule) < 0) {
if (errno == ENOENT)
continue;
break;
}
print_rule(&rule);
}
return EXIT_SUCCESS;
}
static int scan_hex64(char *input, u64_t *val)
{
u32_t lo, hi;
char buf[9];
int len;
len = strlen(input);
if (len < 1 || len > 16) return 0;
if (len > 8) {
memcpy(buf, input, len - 8);
buf[len - 8] = 0;
input += len - 8;
hi = strtoul(buf, NULL, 16);
}
else hi = 0;
lo = strtoul(input, NULL, 16);
*val = make64(lo, hi);
return 1;
}
static int scan_range(char *input, u64_t *start, u64_t *end, int need_end)
{
char *p;
if ((p = strchr(input, '-')) != NULL) {
*p++ = 0;
if (!scan_hex64(p, end)) return 0;
}
else if (need_end) return 0;
return scan_hex64(input, start);
}
static int do_add(int fd, int argc, char **argv, int off)
{
struct fbd_rule rule;
int c, r;
memset(&rule, 0, sizeof(rule));
while ((c = getopt(argc-off, argv+off, "a:s:c:rw")) != EOF) {
switch (c) {
case 'a':
if (!scan_range(optarg, &rule.start, &rule.end, 0))
return usage(argv[0]);
break;
case 's':
rule.skip = atoi(optarg);
break;
case 'c':
rule.count = atoi(optarg);
break;
case 'r':
rule.flags |= FBD_FLAG_READ;
break;
case 'w':
rule.flags |= FBD_FLAG_WRITE;
break;
default:
return usage(argv[0]);
}
}
optind += off; /* compensate for the shifted argc/argv */
if (optind >= argc) return usage(argv[0]);
/* default to reads and writes */
if (!rule.flags) rule.flags = FBD_FLAG_READ | FBD_FLAG_WRITE;
if (!strcmp(argv[optind], "corrupt")) {
if (optind+1 >= argc) return usage(argv[0]);
rule.action = FBD_ACTION_CORRUPT;
if (!strcmp(argv[optind+1], "zero"))
rule.params.corrupt.type = FBD_CORRUPT_ZERO;
else if (!strcmp(argv[optind+1], "persist"))
rule.params.corrupt.type = FBD_CORRUPT_PERSIST;
else if (!strcmp(argv[optind+1], "random"))
rule.params.corrupt.type = FBD_CORRUPT_RANDOM;
else return usage(argv[0]);
}
else if (!strcmp(argv[optind], "error")) {
if (optind+1 >= argc) return usage(argv[0]);
rule.action = FBD_ACTION_ERROR;
if (!strcmp(argv[optind+1], "OK"))
rule.params.error.code = 0;
else if (!strcmp(argv[optind+1], "EIO")) {
if (EIO > 0)
rule.params.error.code = -EIO;
else
rule.params.error.code = EIO;
}
else return usage(argv[0]);
}
else if (!strcmp(argv[optind], "misdir")) {
if (optind+2 >= argc) return usage(argv[0]);
rule.action = FBD_ACTION_MISDIR;
if (!scan_range(argv[optind+1], &rule.params.misdir.start,
&rule.params.misdir.end, 1))
return usage(argv[0]);
rule.params.misdir.align = atoi(argv[optind+2]);
if ((int) rule.params.misdir.align <= 0)
return usage(argv[0]);
}
else if (!strcmp(argv[optind], "lost")) {
rule.action = FBD_ACTION_LOSTTORN;
rule.params.losttorn.lead = 0;
}
else if (!strcmp(argv[optind], "torn")) {
if (optind+1 >= argc) return usage(argv[0]);
rule.action = FBD_ACTION_LOSTTORN;
rule.params.losttorn.lead = atoi(argv[optind+1]);
if ((int) rule.params.losttorn.lead <= 0)
return usage(argv[0]);
}
else return usage(argv[0]);
#if DEBUG
print_rule(&rule);
#endif
r = ioctl(fd, FBDCADDRULE, &rule);
if (r < 0) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("Added rule %d\n", r);
return EXIT_SUCCESS;
}
static int do_del(int fd, int argc, char **argv, int off)
{
fbd_rulenum_t num;
if (argc < off + 2)
return usage(argv[0]);
num = atoi(argv[off + 1]);
if (ioctl(fd, FBDCDELRULE, &num)) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("Deleted rule %d\n", num);
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
int r, fd, off = 1;
char *dev = "/dev/fbd";
if (argc < 2)
return usage(argv[0]);
if (!strcmp(argv[1], "-d")) {
if (argc < 4)
return usage(argv[0]);
dev = argv[2];
off += 2;
}
fd = open(dev, O_RDONLY);
if (fd < 0) {
perror(dev);
return EXIT_FAILURE;
}
else if (!strcmp(argv[off], "list"))
r = do_list(fd);
else if (!strcmp(argv[off], "add"))
r = do_add(fd, argc, argv, off);
else if (!strcmp(argv[off], "del"))
r = do_del(fd, argc, argv, off);
else
r = usage(argv[0]);
close(fd);
return r;
}

View file

@ -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

View file

@ -30,7 +30,7 @@ enum dev_style { STYLE_NDEV, STYLE_DEV, STYLE_DEVA, STYLE_TTY, STYLE_CTTY,
#define FILTER_MAJOR 11 /* 11 = /dev/filter (filter driver) */
/* 12 = /dev/c3 */
#define AUDIO_MAJOR 13 /* 13 = /dev/audio (audio driver) */
/* 14 = not used */
#define FBD_MAJOR 14 /* 14 = /dev/fbd (faulty block dev)*/
#define LOG_MAJOR 15 /* 15 = /dev/klog (log driver) */
#define RANDOM_MAJOR 16 /* 16 = /dev/random (random driver) */
#define HELLO_MAJOR 17 /* 17 = /dev/hello (hello driver) */

View file

@ -3,7 +3,7 @@
.PATH: ${MINIXSRCDIR}/common/include/sys
INCS+= elf32.h elf64.h elf_common.h elf_generic.h \
ioc_block.h ioc_file.h ioc_tape.h ioc_disk.h \
ioc_block.h ioc_fbd.h ioc_file.h ioc_tape.h ioc_disk.h \
ioc_memory.h ioc_sound.h ioc_tty.h \
kbdio.h mtio.h svrctl.h video.h vm.h procfs.h elf_core.h exec_elf.h

View file

@ -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 */

View file

@ -1,3 +1,8 @@
20111212:
After a successful "make world", issue the following commands:
# cd /dev
# MAKEDEV fbd
20111109:
Switch to NetBSD passwd system.

View file

@ -12,7 +12,7 @@ SUBDIR= at_wini bios_wini floppy log tty pci .WAIT ramdisk .WAIT memory
# memory driver must be last for ramdisk image
SUBDIR+= ahci amddev atl2 at_wini audio bios_wini dec21140A dp8390 dpeth \
e1000 filter floppy fxp hello lance log orinoco pci printer \
e1000 fbd filter floppy fxp hello lance log orinoco pci printer \
random readclock rtl8139 rtl8169 ti1225 tty vbox acpi \
.WAIT ramdisk .WAIT memory

22
drivers/fbd/Makefile Normal file
View file

@ -0,0 +1,22 @@
# Makefile for the Faulty Block Device (FBD)
.include <bsd.own.mk>
PROG= fbd
SRCS= fbd.c rule.c action.c
DPADD+= ${LIBBLOCKDRIVER} ${LIBSYS}
LDADD+= -lblockdriver -lsys -lc
CPPFLAGS+= -DDEBUG=0
# The FBD driver requires NetBSD libc.
.if ${COMPILER_TYPE} != "gnu"
CC:=clang
COMPILER_TYPE:=gnu
.endif
MAN=
BINDIR?= /usr/sbin
.include <minix.service.mk>

302
drivers/fbd/action.c Normal file
View file

@ -0,0 +1,302 @@
#include <minix/drivers.h>
#include <sys/ioc_fbd.h>
#include <assert.h>
#include "rule.h"
/*===========================================================================*
* get_rand *
*===========================================================================*/
PRIVATE u32_t get_rand(u32_t max)
{
/* Las Vegas algorithm for getting a random number in the range from
* 0 to max, inclusive.
*/
u32_t val, top;
/* Get an initial random number. */
val = lrand48() ^ (lrand48() << 1);
/* Make 'max' exclusive. If it wraps, we can use the full width. */
if (++max == 0) return val;
/* Find the largest multiple of the given range, and return a random
* number from the range, throwing away any random numbers not below
* this largest multiple.
*/
top = (((u32_t) -1) / max) * max;
while (val >= top)
val = lrand48() ^ (lrand48() << 1);
return val % max;
}
/*===========================================================================*
* get_range *
*===========================================================================*/
PRIVATE size_t get_range(struct fbd_rule *rule, u64_t pos, size_t *size,
u64_t *skip)
{
/* Compute the range within the given request range that is affected
* by the given rule, and optionally the number of bytes preceding
* the range that are also affected by the rule.
*/
u64_t delta;
size_t off;
int to_eof;
to_eof = cmp64(rule->start, rule->end) >= 0;
if (cmp64(pos, rule->start) > 0) {
if (skip != NULL) *skip = sub64(pos, rule->start);
off = 0;
}
else {
if (skip != NULL) *skip = cvu64(0);
delta = sub64(rule->start, pos);
assert(ex64hi(delta) == 0);
off = ex64lo(delta);
}
if (!to_eof) {
assert(cmp64(pos, rule->end) < 0);
delta = sub64(rule->end, pos);
if (cmp64u(delta, *size) < 0)
*size = ex64lo(delta);
}
assert(*size > off);
*size -= off;
return off;
}
/*===========================================================================*
* limit_range *
*===========================================================================*/
PRIVATE void limit_range(iovec_t *iov, unsigned *count, size_t size)
{
/* Limit the given vector to the given size.
*/
size_t chunk;
int i;
for (i = 0; i < *count && size > 0; i++) {
chunk = MIN(iov[i].iov_size, size);
if (chunk == size)
iov[i].iov_size = size;
size -= chunk;
}
*count = i;
}
/*===========================================================================*
* action_io_corrupt *
*===========================================================================*/
PRIVATE void action_io_corrupt(struct fbd_rule *rule, char *buf, size_t size,
u64_t pos, int UNUSED(flag))
{
u64_t skip;
u32_t val;
buf += get_range(rule, pos, &size, &skip);
switch (rule->params.corrupt.type) {
case FBD_CORRUPT_ZERO:
memset(buf, 0, size);
break;
case FBD_CORRUPT_PERSIST:
/* Non-dword-aligned positions and sizes are not supported;
* not by us, and not by the driver.
*/
if (ex64lo(pos) & (sizeof(val) - 1)) break;
if (size & (sizeof(val) - 1)) break;
/* Consistently produce the same pattern for the same range. */
val = ex64lo(skip);
for ( ; size >= sizeof(val); size -= sizeof(val)) {
*((u32_t *) buf) = val ^ 0xdeadbeefUL;
val += sizeof(val);
buf += sizeof(val);
}
break;
case FBD_CORRUPT_RANDOM:
while (size--)
*buf++ = get_rand(255);
break;
default:
printf("FBD: unknown corruption type %d\n",
rule->params.corrupt.type);
}
}
/*===========================================================================*
* action_pre_error *
*===========================================================================*/
PRIVATE void action_pre_error(struct fbd_rule *rule, iovec_t *iov,
unsigned *count, size_t *size, u64_t *pos)
{
/* Limit the request to the part that precedes the matched range. */
*size = get_range(rule, *pos, size, NULL);
limit_range(iov, count, *size);
}
/*===========================================================================*
* action_post_error *
*===========================================================================*/
PRIVATE void action_post_error(struct fbd_rule *rule, size_t UNUSED(osize),
int *result)
{
/* Upon success of the first part, return the specified error code. */
if (*result >= 0 && rule->params.error.code != OK)
*result = rule->params.error.code;
}
/*===========================================================================*
* action_pre_misdir *
*===========================================================================*/
PRIVATE void action_pre_misdir(struct fbd_rule *rule, iovec_t *UNUSED(iov),
unsigned *UNUSED(count), size_t *UNUSED(size), u64_t *pos)
{
/* Randomize the request position to fall within the range (and have
* the alignment) given by the rule.
*/
u32_t range, choice;
/* Unfortunately, we cannot interpret 0 as end as "up to end of disk"
* here, because we have no idea about the actual disk size, and the
* resulting address must of course be valid..
*/
range = div64u(add64u(sub64(rule->params.misdir.end,
rule->params.misdir.start), 1), rule->params.misdir.align);
if (range > 0)
choice = get_rand(range - 1);
else
choice = 0;
*pos = add64(rule->params.misdir.start,
mul64u(choice, rule->params.misdir.align));
}
/*===========================================================================*
* action_pre_losttorn *
*===========================================================================*/
PRIVATE void action_pre_losttorn(struct fbd_rule *rule, iovec_t *iov,
unsigned *count, size_t *size, u64_t *UNUSED(pos))
{
if (*size > rule->params.losttorn.lead)
*size = rule->params.losttorn.lead;
limit_range(iov, count, *size);
}
/*===========================================================================*
* action_post_losttorn *
*===========================================================================*/
PRIVATE void action_post_losttorn(struct fbd_rule *UNUSED(rule), size_t osize,
int *result)
{
/* On success, pretend full completion. */
if (*result < 0) return;
*result = osize;
}
/*===========================================================================*
* action_mask *
*===========================================================================*/
PUBLIC int action_mask(struct fbd_rule *rule)
{
/* Return the hook mask for the given rule's action type. */
switch (rule->action) {
case FBD_ACTION_CORRUPT: return IO_HOOK;
case FBD_ACTION_ERROR: return PRE_HOOK | POST_HOOK;
case FBD_ACTION_MISDIR: return PRE_HOOK;
case FBD_ACTION_LOSTTORN: return PRE_HOOK | POST_HOOK;
default:
printf("FBD: unknown action type %d\n", rule->action);
return 0;
}
}
/*===========================================================================*
* action_pre_hook *
*===========================================================================*/
PUBLIC void action_pre_hook(struct fbd_rule *rule, iovec_t *iov,
unsigned *count, size_t *size, u64_t *pos)
{
switch (rule->action) {
case FBD_ACTION_ERROR:
action_pre_error(rule, iov, count, size, pos);
break;
case FBD_ACTION_MISDIR:
action_pre_misdir(rule, iov, count, size, pos);
break;
case FBD_ACTION_LOSTTORN:
action_pre_losttorn(rule, iov, count, size, pos);
break;
default:
printf("FBD: bad action type %d for PRE hook\n", rule->action);
}
}
/*===========================================================================*
* action_io_hook *
*===========================================================================*/
PUBLIC void action_io_hook(struct fbd_rule *rule, char *buf, size_t size,
u64_t pos, int flag)
{
switch (rule->action) {
case FBD_ACTION_CORRUPT:
action_io_corrupt(rule, buf, size, pos, flag);
break;
default:
printf("FBD: bad action type %d for IO hook\n", rule->action);
}
}
/*===========================================================================*
* action_post_hook *
*===========================================================================*/
PUBLIC void action_post_hook(struct fbd_rule *rule, size_t osize, int *result)
{
switch (rule->action) {
case FBD_ACTION_ERROR:
action_post_error(rule, osize, result);
return;
case FBD_ACTION_LOSTTORN:
action_post_losttorn(rule, osize, result);
return;
default:
printf("FBD: bad action type %d for POST hook\n",
rule->action);
}
}

12
drivers/fbd/action.h Normal file
View file

@ -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 */

456
drivers/fbd/fbd.c Normal file
View file

@ -0,0 +1,456 @@
/* Faulty Block Device (fault injection proxy), by D.C. van Moolenbroek */
#include <stdlib.h>
#include <minix/drivers.h>
#include <minix/blockdriver.h>
#include <minix/drvlib.h>
#include <minix/ioctl.h>
#include <sys/ioc_fbd.h>
#include <minix/ds.h>
#include <minix/optset.h>
#include <assert.h>
#include "rule.h"
/* Constants. */
#define BUF_SIZE (NR_IOREQS * CLICK_SIZE) /* 256k */
/* Function declarations. */
PRIVATE int fbd_open(dev_t minor, int access);
PRIVATE int fbd_close(dev_t minor);
PRIVATE int fbd_transfer(dev_t minor, int do_write, u64_t position,
endpoint_t endpt, iovec_t *iov, unsigned int nr_req, int flags);
PRIVATE int fbd_ioctl(dev_t minor, unsigned int request, endpoint_t endpt,
cp_grant_id_t grant);
/* Variables. */
PRIVATE char *fbd_buf; /* scratch buffer */
PRIVATE char driver_label[32] = ""; /* driver DS label */
PRIVATE dev_t driver_minor = -1; /* driver's partition minor to use */
PRIVATE endpoint_t driver_endpt; /* driver endpoint */
/* Entry points to this driver. */
PRIVATE struct blockdriver fbd_dtab = {
BLOCKDRIVER_TYPE_OTHER, /* do not handle partition requests */
fbd_open, /* open or mount request, initialize device */
fbd_close, /* release device */
fbd_transfer, /* do the I/O */
fbd_ioctl, /* perform I/O control request */
NULL, /* nothing to clean up */
NULL, /* we will not be asked about partitions */
NULL, /* we will not be asked for geometry */
NULL, /* ignore leftover hardware interrupts */
NULL, /* ignore alarms */
NULL, /* ignore other messages */
NULL /* no multithreading support */
};
/* Options supported by this driver. */
PRIVATE struct optset optset_table[] = {
{ "label", OPT_STRING, driver_label, sizeof(driver_label) },
{ "minor", OPT_INT, &driver_minor, 10 },
{ NULL, 0, NULL, 0 }
};
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *UNUSED(info))
{
clock_t uptime;
int r;
/* Parse the given parameters. */
if (env_argc > 1)
optset_parse(optset_table, env_argv[1]);
if (driver_label[0] == '\0')
panic("no driver label given");
if (ds_retrieve_label_endpt(driver_label, &driver_endpt))
panic("unable to resolve driver label");
if (driver_minor > 255)
panic("no or invalid driver minor given");
#if DEBUG
printf("FBD: driver label '%s' (endpt %d), minor %d\n",
driver_label, driver_endpt, driver_minor);
#endif
/* Initialize resources. */
fbd_buf = alloc_contig(BUF_SIZE, 0, NULL);
assert(fbd_buf != NULL);
if ((r = getuptime(&uptime)) != OK)
panic("getuptime failed (%d)\n", r);
srand48(uptime);
/* Announce we are up! */
blockdriver_announce(type);
return OK;
}
/*===========================================================================*
* sef_cb_signal_handler *
*===========================================================================*/
PRIVATE void sef_cb_signal_handler(int signo)
{
/* Terminate immediately upon receiving a SIGTERM. */
if (signo != SIGTERM) return;
#if DEBUG
printf("FBD: shutting down\n");
#endif
/* Clean up resources. */
free_contig(fbd_buf, BUF_SIZE);
exit(0);
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
PRIVATE void sef_local_startup(void)
{
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_restart(sef_cb_init_fresh);
sef_setcb_init_lu(sef_cb_init_fresh);
/* Register signal callback. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* main *
*===========================================================================*/
PUBLIC int main(int argc, char **argv)
{
/* SEF local startup. */
env_setargs(argc, argv);
sef_local_startup();
/* Call the generic receive loop. */
blockdriver_task(&fbd_dtab);
return OK;
}
/*===========================================================================*
* fbd_open *
*===========================================================================*/
PRIVATE int fbd_open(dev_t UNUSED(minor), int access)
{
/* Open a device. */
message m;
int r;
/* We simply forward this request to the real driver. */
memset(&m, 0, sizeof(m));
m.m_type = BDEV_OPEN;
m.BDEV_MINOR = driver_minor;
m.BDEV_ACCESS = access;
m.BDEV_ID = 0;
if ((r = sendrec(driver_endpt, &m)) != OK)
panic("sendrec to driver failed (%d)\n", r);
if (m.m_type != BDEV_REPLY)
panic("invalid reply from driver (%d)\n", m.m_type);
return m.BDEV_STATUS;
}
/*===========================================================================*
* fbd_close *
*===========================================================================*/
PRIVATE int fbd_close(dev_t UNUSED(minor))
{
/* Close a device. */
message m;
int r;
/* We simply forward this request to the real driver. */
memset(&m, 0, sizeof(m));
m.m_type = BDEV_CLOSE;
m.BDEV_MINOR = driver_minor;
m.BDEV_ID = 0;
if ((r = sendrec(driver_endpt, &m)) != OK)
panic("sendrec to driver failed (%d)\n", r);
if (m.m_type != BDEV_REPLY)
panic("invalid reply from driver (%d)\n", m.m_type);
return m.BDEV_STATUS;
}
/*===========================================================================*
* fbd_ioctl *
*===========================================================================*/
PRIVATE int fbd_ioctl(dev_t UNUSED(minor), unsigned int request,
endpoint_t endpt, cp_grant_id_t grant)
{
/* Handle an I/O control request. */
cp_grant_id_t gid;
message m;
int r;
/* We only handle the FBD requests, and pass on everything else. */
switch (request) {
case FBDCADDRULE:
case FBDCDELRULE:
case FBDCGETRULE:
return rule_ctl(request, endpt, grant);
}
assert(grant != GRANT_INVALID);
gid = cpf_grant_indirect(driver_endpt, endpt, grant);
assert(gid != GRANT_INVALID);
memset(&m, 0, sizeof(m));
m.m_type = BDEV_IOCTL;
m.BDEV_MINOR = driver_minor;
m.BDEV_REQUEST = request;
m.BDEV_GRANT = gid;
m.BDEV_ID = 0;
if ((r = sendrec(driver_endpt, &m)) != OK)
panic("sendrec to driver failed (%d)\n", r);
if (m.m_type != BDEV_REPLY)
panic("invalid reply from driver (%d)\n", m.m_type);
cpf_revoke(gid);
return m.BDEV_STATUS;
}
/*===========================================================================*
* fbd_transfer_direct *
*===========================================================================*/
PRIVATE ssize_t fbd_transfer_direct(int do_write, u64_t position,
endpoint_t endpt, iovec_t *iov, unsigned int count, int flags)
{
/* Forward the entire transfer request, without any intervention. */
iovec_s_t iovec[NR_IOREQS];
cp_grant_id_t grant;
message m;
int i, r;
for (i = 0; i < count; i++) {
iovec[i].iov_size = iov[i].iov_size;
iovec[i].iov_grant = cpf_grant_indirect(driver_endpt, endpt,
iov[i].iov_addr);
assert(iovec[i].iov_grant != GRANT_INVALID);
}
grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec,
count * sizeof(iovec[0]), CPF_READ);
assert(grant != GRANT_INVALID);
m.m_type = do_write ? BDEV_SCATTER : BDEV_GATHER;
m.BDEV_MINOR = driver_minor;
m.BDEV_COUNT = count;
m.BDEV_GRANT = grant;
m.BDEV_FLAGS = flags;
m.BDEV_ID = 0;
m.BDEV_POS_LO = ex64lo(position);
m.BDEV_POS_HI = ex64hi(position);
if ((r = sendrec(driver_endpt, &m)) != OK)
panic("sendrec to driver failed (%d)\n", r);
if (m.m_type != BDEV_REPLY)
panic("invalid reply from driver (%d)\n", m.m_type);
cpf_revoke(grant);
for (i = 0; i < count; i++)
cpf_revoke(iovec[i].iov_grant);
return m.BDEV_STATUS;
}
/*===========================================================================*
* fbd_transfer_copy *
*===========================================================================*/
PRIVATE ssize_t fbd_transfer_copy(int do_write, u64_t position,
endpoint_t endpt, iovec_t *iov, unsigned int count, size_t size,
int flags)
{
/* Interpose on the request. */
iovec_s_t iovec[NR_IOREQS];
struct vscp_vec vscp_vec[SCPVEC_NR];
cp_grant_id_t grant;
size_t off, len;
message m;
char *ptr;
int i, j, r;
ssize_t rsize;
assert(count > 0 && count <= SCPVEC_NR);
if (size > BUF_SIZE) {
printf("FBD: allocating memory for %d bytes\n", size);
ptr = alloc_contig(size, 0, NULL);
assert(ptr != NULL);
}
else ptr = fbd_buf;
/* For write operations, first copy in the data to write. */
if (do_write) {
for (i = off = 0; i < count; i++) {
len = iov[i].iov_size;
vscp_vec[i].v_from = endpt;
vscp_vec[i].v_to = SELF;
vscp_vec[i].v_gid = iov[i].iov_addr;
vscp_vec[i].v_offset = 0;
vscp_vec[i].v_addr = (vir_bytes) (ptr + off);
vscp_vec[i].v_bytes = len;
off += len;
}
if ((r = sys_vsafecopy(vscp_vec, i)) != OK)
panic("vsafecopy failed (%d)\n", r);
/* Trigger write hook. */
rule_io_hook(ptr, size, position, FBD_FLAG_WRITE);
}
/* Allocate grants for the data, in the same chunking as the original
* vector. This avoids performance fluctuations with bad hardware as
* observed with the filter driver.
*/
for (i = off = 0; i < count; i++) {
len = iov[i].iov_size;
iovec[i].iov_size = len;
iovec[i].iov_grant = cpf_grant_direct(driver_endpt,
(vir_bytes) (ptr + off), len,
do_write ? CPF_READ : CPF_WRITE);
assert(iovec[i].iov_grant != GRANT_INVALID);
off += len;
}
grant = cpf_grant_direct(driver_endpt, (vir_bytes) iovec,
count * sizeof(iovec[0]), CPF_READ);
assert(grant != GRANT_INVALID);
m.m_type = do_write ? BDEV_SCATTER : BDEV_GATHER;
m.BDEV_MINOR = driver_minor;
m.BDEV_COUNT = count;
m.BDEV_GRANT = grant;
m.BDEV_FLAGS = flags;
m.BDEV_ID = 0;
m.BDEV_POS_LO = ex64lo(position);
m.BDEV_POS_HI = ex64hi(position);
if ((r = sendrec(driver_endpt, &m)) != OK)
panic("sendrec to driver failed (%d)\n", r);
if (m.m_type != BDEV_REPLY)
panic("invalid reply from driver (%d)\n", m.m_type);
cpf_revoke(grant);
for (i = 0; i < count; i++)
cpf_revoke(iovec[i].iov_grant);
/* For read operations, finish by copying out the data read. */
if (!do_write) {
/* Trigger read hook. */
rule_io_hook(ptr, size, position, FBD_FLAG_READ);
/* Upon success, copy back whatever has been processed. */
rsize = m.BDEV_STATUS;
for (i = j = off = 0; rsize > 0 && i < count; i++) {
len = MIN(rsize, iov[i].iov_size);
vscp_vec[j].v_from = SELF;
vscp_vec[j].v_to = endpt;
vscp_vec[j].v_gid = iov[i].iov_addr;
vscp_vec[j].v_offset = 0;
vscp_vec[j].v_addr = (vir_bytes) (ptr + off);
vscp_vec[j].v_bytes = len;
off += len;
rsize -= len;
j++;
}
if (j > 0 && (r = sys_vsafecopy(vscp_vec, j)) != OK)
panic("vsafecopy failed (%d)\n", r);
}
if (ptr != fbd_buf)
free_contig(ptr, size);
return m.BDEV_STATUS;
}
/*===========================================================================*
* fbd_transfer *
*===========================================================================*/
PRIVATE int fbd_transfer(dev_t UNUSED(minor), int do_write, u64_t position,
endpoint_t endpt, iovec_t *iov, unsigned int nr_req, int flags)
{
/* Transfer data from or to the device. */
unsigned count;
size_t size, osize;
int i, hooks;
ssize_t r;
/* Compute the total size of the request. */
for (size = i = 0; i < nr_req; i++)
size += iov[i].iov_size;
osize = size;
count = nr_req;
hooks = rule_find(position, size,
do_write ? FBD_FLAG_WRITE : FBD_FLAG_READ);
#if DEBUG
printf("FBD: %s operation for pos %lx:%08lx size %u -> hooks %x\n",
do_write ? "write" : "read", ex64hi(position),
ex64lo(position), size, hooks);
#endif
if (hooks & PRE_HOOK)
rule_pre_hook(iov, &count, &size, &position);
if (count > 0) {
if (hooks & IO_HOOK) {
r = fbd_transfer_copy(do_write, position, endpt, iov,
count, size, flags);
} else {
r = fbd_transfer_direct(do_write, position, endpt, iov,
count, flags);
}
}
else r = 0;
if (hooks & POST_HOOK)
rule_post_hook(osize, &r);
#if DEBUG
printf("FBD: returning %d\n", r);
#endif
return r;
}

185
drivers/fbd/rule.c Normal file
View file

@ -0,0 +1,185 @@
#include <minix/drivers.h>
#include <minix/ioctl.h>
#include <sys/ioc_fbd.h>
#include "action.h"
#include "rule.h"
PRIVATE struct fbd_rule rules[MAX_RULES];
PRIVATE struct fbd_rule *matches[MAX_RULES];
PRIVATE int nr_matches;
/*===========================================================================*
* rule_ctl *
*===========================================================================*/
PUBLIC int rule_ctl(int request, endpoint_t endpt, cp_grant_id_t grant)
{
/* Handle an I/O control request regarding rules. */
fbd_rulenum_t i;
int r;
/* Note that any of the safecopy calls may fail if the ioctl is
* improperly defined in userland; never panic if they fail!
*/
switch (request) {
case FBDCADDRULE:
/* Find a free rule slot. */
for (i = 1; i <= MAX_RULES; i++)
if (rules[i-1].num == 0)
break;
if (i == MAX_RULES+1)
return ENOMEM;
/* Copy in the rule. */
if ((r = sys_safecopyfrom(endpt, grant, 0,
(vir_bytes) &rules[i-1], sizeof(rules[0]),
D)) != OK)
return r;
/* Mark the rule as active, and return its number. */
rules[i-1].num = i;
return i;
case FBDCDELRULE:
/* Copy in the given rule number. */
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &i,
sizeof(i), D)) != OK)
return r;
/* Fail if the given rule number is not valid or in use.
* Allow the caller to determine the maximum rule number.
*/
if (i <= 0 || i > MAX_RULES) return EINVAL;
if (rules[i-1].num != i) return ENOENT;
/* Mark the rule as not active. */
rules[i-1].num = 0;
return OK;
case FBDCGETRULE:
/* Copy in just the rule number from the given structure. */
if ((r = sys_safecopyfrom(endpt, grant,
offsetof(struct fbd_rule, num), (vir_bytes) &i,
sizeof(i), D)) != OK)
return r;
/* Fail if the given rule number is not valid or in use.
* Allow the caller to determine the maximum rule number.
*/
if (i <= 0 || i > MAX_RULES) return EINVAL;
if (rules[i-1].num != i) return ENOENT;
/* Copy out the entire rule as is. */
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rules[i-1],
sizeof(rules[0]), D);
default:
return EINVAL;
}
}
/*===========================================================================*
* rule_match *
*===========================================================================*/
PRIVATE int rule_match(struct fbd_rule *rule, u64_t pos, size_t size, int flag)
{
/* Check whether the given rule matches the given parameters. As side
* effect, update counters in the rule as appropriate.
*/
/* Ranges must overlap (start < pos+size && end > pos). */
if (cmp64(rule->start, add64u(pos, size)) >= 0 ||
(cmp64u(rule->end, 0) && cmp64(rule->end, pos) <= 0))
return FALSE;
/* Flags must match. */
if (!(rule->flags & flag)) return FALSE;
/* This is a match, but is it supposed to trigger yet? */
if (rule->skip > 0) {
rule->skip--;
return FALSE;
}
return TRUE;
}
/*===========================================================================*
* rule_find *
*===========================================================================*/
PUBLIC int rule_find(u64_t pos, size_t size, int flag)
{
/* Find all matching rules, and return a hook mask. */
struct fbd_rule *rule;
int i, hooks;
nr_matches = 0;
hooks = 0;
for (i = 0; i < MAX_RULES; i++) {
rule = &rules[i];
if (rule->num == 0) continue;
if (!rule_match(rule, pos, size, flag))
continue;
matches[nr_matches++] = rule;
/* If the rule has a limited lifetime, update it now. */
if (rule->count > 0) {
rule->count--;
/* Disable the rule from future matching. */
if (rule->count == 0)
rule->num = 0;
}
hooks |= action_mask(rule);
}
return hooks;
}
/*===========================================================================*
* rule_pre_hook *
*===========================================================================*/
PUBLIC void rule_pre_hook(iovec_t *iov, unsigned *count, size_t *size,
u64_t *pos)
{
int i;
for (i = 0; i < nr_matches; i++)
if (action_mask(matches[i]) & PRE_HOOK)
action_pre_hook(matches[i], iov, count, size, pos);
}
/*===========================================================================*
* rule_io_hook *
*===========================================================================*/
PUBLIC void rule_io_hook(char *buf, size_t size, u64_t pos, int flag)
{
int i;
for (i = 0; i < nr_matches; i++)
if (action_mask(matches[i]) & IO_HOOK)
action_io_hook(matches[i], buf, size, pos, flag);
}
/*===========================================================================*
* rule_post_hook *
*===========================================================================*/
PUBLIC void rule_post_hook(size_t osize, int *result)
{
int i;
for (i = 0; i < nr_matches; i++)
if (action_mask(matches[i]) & POST_HOOK)
action_post_hook(matches[i], osize, result);
}

19
drivers/fbd/rule.h Normal file
View file

@ -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 */

View file

@ -540,3 +540,13 @@ service vbox
;
uid 0;
};
service fbd
{
ipc
SYSTEM VFS RS DS VM
ahci
at_wini
bios_wini
;
};

View file

@ -38,7 +38,7 @@ static int bdev_recover(dev_t dev, int update_endpt)
endpoint_t endpt;
int r, nr_tries;
printf("bdev: recovering from a driver crash on major %d\n", major(dev));
printf("bdev: recovering from a driver restart on major %d\n", major(dev));
for (nr_tries = 0; nr_tries < RECOVER_TRIES; nr_tries++) {
/* First update the endpoint, if necessary. */

View file

@ -1,7 +1,7 @@
MAN= add_route.8 backup.8 badblocks.8 boot.8 btrace.8 \
cdprobe.8 checkhier.8 chown.8 cleantmp.8 config.8 cron.8 \
dhcpd.8 diskctl.8 dosminix.8 elvprsv.8 fdisk.8 fingerd.8 ftpd.8 \
getty.8 halt.8 hgfs.8 httpd.8 ifconfig.8 inet.8 init.8 \
dhcpd.8 diskctl.8 dosminix.8 elvprsv.8 fbdctl.8 fdisk.8 fingerd.8 \
ftpd.8 getty.8 halt.8 hgfs.8 httpd.8 ifconfig.8 inet.8 init.8 \
installboot.8 intr.8 irdpd.8 loadramdisk.8 MAKEDEV.8 \
mknod.8 monitor.8 netconf.8 newroot.8 nonamed.8 \
ossdevlinks.8 part.8 partition.8 \

109
man/man8/fbdctl.8 Normal file
View file

@ -0,0 +1,109 @@
.TH FBDCTL 8
.SH NAME
fbdctl \- Faulty Block Device rule management interface
.SH SYNOPSIS
\fBfbdctl\fR \fBadd\fR [\fB-d\fR \fIdevice\fR]
[\fB-a\fR \fIstart\fR[\fB-\fR\fIend\fR]] [\fB-s\fR \fIskip\fR]
[\fB-c\fR \fIcount\fR] [\fB-rw\fR] \fIaction\fR [\fIparams\fR]
.PP
\fBfbdctl\fR \fBdel\fR [\fB-d\fR \fIdevice\fR] \fIrulenum\fR
.PP
\fBfbdctl\fR \fBlist\fR [\fB-d\fR \fIdevice\fR]
.SH DESCRIPTION
The Faulty Block Device (FBD) driver is an interposing block device driver
which can simulate certain disk-level I/O corruption and errors, based on a
user-provided set of rules. The \fBfbdctl\fR tool allows one to add, delete,
and list rules on a running FBD driver instance.
.PP
The \fBadd\fR subcommand adds a new rule, which will perform a predefined
faulty action on a disk transfer when triggered. See the ACTIONS subsection
below. The \fBdel\fR subcommands deletes an existing rule, based on its rule
number. All currently active rules and their corresponding rule numbers can be
viewed with the \fBlist\fR subcommand.
.SH OPTIONS
.TP 10
\fB-d\fR \fIdevice\fR
By default, \fBfbdctl\fR operates on \fB/dev/fbd\fR. With this option, one can
specify a different device node. This is useful when using multiple FBD
instances at the same time. The user would have to create extra device nodes
first in that case.
.TP 10
\fB-a\fR [\fIstart\fR[\fB-\fR\fIend\fR]]
When adding a rule, this option specifies the disk address range for which the
rule triggers. That is, the rule will trigger when an I/O operation overlaps
with the given range. Both \fIstart\fR and \fIend\fR are expected to be
hexadecimal numbers, without a "0x" prefix. The \fIend\fR address is exclusive.
If no \fIend\fR address is given, the rule will affect the disk from the
starting address to the disk end. If this option is not provided at all,
the rule will affect the entire disk.
.TP 10
\fB-s\fR \fIskip\fR
This option makes the new rule refrain from triggering for the given number
of times it matches. The \fIskip\fR value must be a positive decimal number.
If this option is omitted, the value is set to zero, meaning the rule will
start triggering immediately.
.TP 10
\fB-c\fR \fIcount\fR
This option makes the new rule trigger for this many I/O operations when
matched, after which it will be removed automatically. The \fIcount\fR value
must be a positive decimal number. If this option is omitted, or a value of
zero is given, the rule is permanent and will not be removed automatically.
.TP 10
\fB-r\fR, \fB-w\fR
These options allow one to make the new rule trigger on read or write
operations only. By default, or when both are specified, the new rule will
trigger for both read and write I/O operations.
.SH ACTIONS
The following actions are supported. They are executed when the rule matches
for a transfer request, and is triggered as a result. Note that the exact
meaning of the rule's disk address range (as given by the \fB-a\fR option)
depends on the action type.
.TP 10
\fBcorrupt\fR [\fBzero\fR|\fBpersist\fR|\fBrandom\fR]
In the part of the transfer that matches the disk address range given for the
rule (i.e., the intersection of the rule range and the transfer range), the
data will be corrupted. The following corruption policies are supported: the
data is set to \fBzero\fR, it is \fBpersist\fRently set to the same garbage
data for the same disk locations, or it is set to different \fBrandom\fR data
in every transfer.
.TP 10
\fBerror\fR [\fBOK\fR|\fBEIO\fR]
Only the part of the transfer up to the start of the rule's disk address range
will be performed, after which the given error code is returned. The \fBOK\fR
code effectively simulates an end-of-disk condition, whereas the \fBEIO\fR code
simulates generic disk-level I/O failure.
.TP 10
\fBmisdir\fR \fIstart\fR\fB-\fR\fIend\fR \fIalign\fR
Transfer requests that match this rule, will be \fBmisdirected\fR in their
entirety, to a random location in the given address range, and with the given
disk byte alignment within that range. The \fIstart\fR and \fIend\fR parameters
are specified just like the \fB-a\fR option, although the \fIend\fR parameter
is compulsory here (since the driver does not know the disk end). The
\fIalign\fR value must be a positive nonzero decimal number, and should be a
multiple of the medium sector size; a typical value would be 4096.
.TP 10
\fBlost\fR
Transfer requests that match this rule, will be \fBlost\fR in their entirety.
That is, they will not actually be performed, and yet report successful
completion.
.TP 10
\fBtorn\fR \fIlead\fR
Transfer requests that match this rule, will be \fBtorn\fR: only the first
\fIlead\fR bytes of the transfer request will actually be performed, and yet
completion of the full transfer is reported. The \fIlead\fR value must be a
positive nonzero decimal number. A \fBtorn\fR action with a \fIlead\fR value of
zero would be the same as the \fBlost\fR action.
.SH EXAMPLES
.TP 10
.B fbdctl add -a 2000-3000 corrupt zero
# Zero out the 4096 bytes starting from disk address 0x2000 in any transfer
that involves any of those bytes.
.TP 10
.B fbdctl add -s 9 -c 1 -r error EIO
# Fail the tenth read request with an I/O error.
.TP 10
.B fbdctl add -a A0000-B0000 -w misdir D0000-E0000 4096
# Misdirect write requests that overlap with the first range, to fall somewhere
in the second range, at 4K-block-granular alignment.
.SH AUTHOR
David van Moolenbroek <david@minix3.org>

11
test/fbdtest/Makefile Normal file
View file

@ -0,0 +1,11 @@
# Makefile for rwblocks
.include <bsd.own.mk>
PROG= rwblocks
SRCS= rwblocks.c
MAN=
BINDIR?=/usr/sbin
.include <bsd.prog.mk>

190
test/fbdtest/rwblocks.c Normal file
View file

@ -0,0 +1,190 @@
/* Simple block pattern reader/writer for testing FBD */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define BLOCK_SIZE 4096 /* set to match root FS to prevent partial I/O */
static int flush_buf(int fd, char *buf, size_t size, size_t write_size)
{
ssize_t r;
while (write_size <= size) {
if ((r = write(fd, buf, write_size)) != write_size) {
if (r < 0)
perror("write");
else
fprintf(stderr, "short write (%d < %d)\n",
r, write_size);
return EXIT_FAILURE;
}
sync();
buf += write_size;
size -= write_size;
}
return EXIT_SUCCESS;
}
static int write_pattern(int fd, char *pattern, int write_size)
{
char *buf, *ptr;
size_t size;
int r, count, nblocks;
/* Only write sizes that are a multiple or a
* divisor of the block size, are supported.
*/
nblocks = write_size / BLOCK_SIZE;
if (!nblocks) nblocks = 1;
size = nblocks * BLOCK_SIZE;
if ((buf = malloc(size)) == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
count = 0;
do {
ptr = &buf[count * BLOCK_SIZE];
switch (*pattern) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'U':
memset(ptr, *pattern, BLOCK_SIZE);
break;
case '0':
memset(ptr, 0, BLOCK_SIZE);
break;
case '\0':
memset(ptr, 0, BLOCK_SIZE);
ptr[0] = 'E';
ptr[1] = 'O';
ptr[2] = 'F';
}
if (++count == nblocks) {
if ((r = flush_buf(fd, buf, size, write_size)) !=
EXIT_SUCCESS) {
free(buf);
return r;
}
count = 0;
}
} while (*pattern++);
if (count > 0)
r = flush_buf(fd, buf, count * BLOCK_SIZE, write_size);
else
r = EXIT_SUCCESS;
free(buf);
return r;
}
static int read_pattern(int fd)
{
char buf[BLOCK_SIZE];
unsigned int i, val;
ssize_t r;
for (;;) {
memset(buf, '?', sizeof(buf));
if ((r = read(fd, buf, sizeof(buf))) != sizeof(buf)) {
putchar('#');
if (!r) break; /* stop at hard EOF */
lseek(fd, sizeof(buf), SEEK_CUR);
continue;
}
if (buf[0] == 'E' && buf[1] == 'O' && buf[2] == 'F') {
for (i = 3; i < sizeof(buf); i++)
if (buf[i] != 0) break;
if (i == sizeof(buf)) break;
}
for (i = 1; i < sizeof(buf); i++)
if (buf[i] != buf[0]) break;
if (i == sizeof(buf)) {
switch (buf[0]) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'U':
case '?':
printf("%c", buf[0]);
break;
case '\0':
printf("0");
break;
default:
printf("X");
}
continue;
}
for (i = val = 0; i < sizeof(buf); i++)
val += buf[i];
printf("%c", 'a' + val % 26);
}
printf("\n");
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
int fd, r;
if (argc < 2) {
fprintf(stderr, "usage: %s <device> [pattern [writesz]]\n",
argv[0]);
return EXIT_FAILURE;
}
fd = open(argv[1], (argc > 2) ? O_WRONLY : O_RDONLY);
if (fd < 0) {
perror("open");
return EXIT_FAILURE;
}
if (argc > 2)
r = write_pattern(fd, argv[2],
argv[3] ? atoi(argv[3]) : BLOCK_SIZE);
else
r = read_pattern(fd);
close(fd);
return r;
}

199
test/fbdtest/test.sh Executable file
View file

@ -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."