minix/drivers/fbd/fbd.c
David van Moolenbroek e7db2d3588 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.
2011-12-11 22:45:46 +01:00

457 lines
12 KiB
C

/* 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;
}