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:
parent
f65e531ee4
commit
e7db2d3588
24 changed files with 1925 additions and 9 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
4
commands/fbdctl/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
PROG= fbdctl
|
||||
MAN=
|
||||
|
||||
.include <bsd.prog.mk>
|
319
commands/fbdctl/fbdctl.c
Normal file
319
commands/fbdctl/fbdctl.c
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
66
common/include/sys/ioc_fbd.h
Normal file
66
common/include/sys/ioc_fbd.h
Normal 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 */
|
|
@ -1,3 +1,8 @@
|
|||
20111212:
|
||||
After a successful "make world", issue the following commands:
|
||||
# cd /dev
|
||||
# MAKEDEV fbd
|
||||
|
||||
20111109:
|
||||
Switch to NetBSD passwd system.
|
||||
|
||||
|
|
|
@ -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
22
drivers/fbd/Makefile
Normal 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
302
drivers/fbd/action.c
Normal 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
12
drivers/fbd/action.h
Normal 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
456
drivers/fbd/fbd.c
Normal 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
185
drivers/fbd/rule.c
Normal 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
19
drivers/fbd/rule.h
Normal 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 */
|
|
@ -540,3 +540,13 @@ service vbox
|
|||
;
|
||||
uid 0;
|
||||
};
|
||||
|
||||
service fbd
|
||||
{
|
||||
ipc
|
||||
SYSTEM VFS RS DS VM
|
||||
ahci
|
||||
at_wini
|
||||
bios_wini
|
||||
;
|
||||
};
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
109
man/man8/fbdctl.8
Normal 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
11
test/fbdtest/Makefile
Normal 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
190
test/fbdtest/rwblocks.c
Normal 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
199
test/fbdtest/test.sh
Executable 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."
|
Loading…
Reference in a new issue