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:
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."
|
||||
Reference in New Issue
Block a user