Filter driver by Wu Bingzheng et al
This commit is contained in:
parent
f197bcb435
commit
be2087ecf9
17 changed files with 2856 additions and 2 deletions
|
@ -23,7 +23,7 @@ case $#:$1 in
|
||||||
ttypa ttypb ttypc ttypd ttype ttypf \
|
ttypa ttypb ttypc ttypd ttype ttypf \
|
||||||
ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 \
|
ttyq0 ttyq1 ttyq2 ttyq3 ttyq4 ttyq5 ttyq6 ttyq7 ttyq8 ttyq9 \
|
||||||
ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf \
|
ttyqa ttyqb ttyqc ttyqd ttyqe ttyqf \
|
||||||
eth klog random rescue
|
eth klog random rescue filter
|
||||||
;;
|
;;
|
||||||
0:|1:-\?)
|
0:|1:-\?)
|
||||||
cat >&2 <<EOF
|
cat >&2 <<EOF
|
||||||
|
@ -48,6 +48,7 @@ Where key is one of the following:
|
||||||
kbd # Make /dev/kbd
|
kbd # Make /dev/kbd
|
||||||
kbdaux # Make /dev/kbdaux
|
kbdaux # Make /dev/kbdaux
|
||||||
rescue # Make /dev/rescue
|
rescue # Make /dev/rescue
|
||||||
|
filter # Make /dev/filter
|
||||||
video # Make /dev/video
|
video # Make /dev/video
|
||||||
std # All standard devices
|
std # All standard devices
|
||||||
EOF
|
EOF
|
||||||
|
@ -259,6 +260,11 @@ do
|
||||||
$e mknod klog c 15 0
|
$e mknod klog c 15 0
|
||||||
$e chmod 600 klog
|
$e chmod 600 klog
|
||||||
;;
|
;;
|
||||||
|
filter)
|
||||||
|
# filter driver
|
||||||
|
$e mknod filter b 11 0
|
||||||
|
$e chmod 644 filter
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "$0: don't know about $dev" >&2
|
echo "$0: don't know about $dev" >&2
|
||||||
ex=1
|
ex=1
|
||||||
|
|
|
@ -28,6 +28,7 @@ all install depend clean:
|
||||||
cd ./dpeth && $(MAKE) $@
|
cd ./dpeth && $(MAKE) $@
|
||||||
cd ./log && $(MAKE) $@
|
cd ./log && $(MAKE) $@
|
||||||
cd ./bios_wini && $(MAKE) $@
|
cd ./bios_wini && $(MAKE) $@
|
||||||
|
cd ./filter && $(MAKE) $@
|
||||||
cd ./random && $(MAKE) $@
|
cd ./random && $(MAKE) $@
|
||||||
cd ./readclock && $(MAKE) $@
|
cd ./readclock && $(MAKE) $@
|
||||||
cd ./dp8390 && $(MAKE) $@
|
cd ./dp8390 && $(MAKE) $@
|
||||||
|
|
31
drivers/filter/Makefile
Normal file
31
drivers/filter/Makefile
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Makefile for filter driver
|
||||||
|
DRIVER = filter
|
||||||
|
|
||||||
|
# programs, flags, etc.
|
||||||
|
CC = cc
|
||||||
|
CFLAGS = -DDEBUG=1 -DDEBUG2=0
|
||||||
|
LDFLAGS =
|
||||||
|
LIBS = -lsys
|
||||||
|
|
||||||
|
OBJ = main.o sum.o driver.o util.o optset.o crc.o md5.o
|
||||||
|
|
||||||
|
# build local binary
|
||||||
|
all build: $(DRIVER)
|
||||||
|
$(DRIVER): $(OBJ)
|
||||||
|
$(CC) -o $@ $(LDFLAGS) $(OBJ) $(LIBS)
|
||||||
|
|
||||||
|
# install with other drivers
|
||||||
|
install: /usr/sbin/$(DRIVER)
|
||||||
|
/usr/sbin/$(DRIVER): $(DRIVER)
|
||||||
|
install -o root -c $? $@
|
||||||
|
|
||||||
|
# clean up local files
|
||||||
|
clean:
|
||||||
|
rm -f *.o *.bak $(DRIVER)
|
||||||
|
|
||||||
|
depend:
|
||||||
|
mkdep "$(CC) -E $(CPPFLAGS)" *.c > .depend
|
||||||
|
|
||||||
|
# Include generated dependencies.
|
||||||
|
include .depend
|
||||||
|
|
88
drivers/filter/crc.c
Normal file
88
drivers/filter/crc.c
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/* CRC32 implementation taken from cksum.c */
|
||||||
|
|
||||||
|
/* Copyright 1991 by Vincent Archer
|
||||||
|
* You may freely redistribute this software, in source or binary
|
||||||
|
* form, provided that you do not alter this copyright mention in any
|
||||||
|
* way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
unsigned long crctab[] = {
|
||||||
|
0x7fffffff,
|
||||||
|
0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||||
|
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e,
|
||||||
|
0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||||
|
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
||||||
|
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0,
|
||||||
|
0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63,
|
||||||
|
0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa,
|
||||||
|
0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75,
|
||||||
|
0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180,
|
||||||
|
0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||||
|
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87,
|
||||||
|
0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||||
|
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5,
|
||||||
|
0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||||
|
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
|
||||||
|
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b,
|
||||||
|
0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
||||||
|
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||||
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541,
|
||||||
|
0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc,
|
||||||
|
0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f,
|
||||||
|
0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||||
|
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e,
|
||||||
|
0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||||
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c,
|
||||||
|
0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||||
|
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b,
|
||||||
|
0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2,
|
||||||
|
0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671,
|
||||||
|
0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||||
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
||||||
|
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767,
|
||||||
|
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||||
|
0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
|
||||||
|
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795,
|
||||||
|
0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||||
|
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b,
|
||||||
|
0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||||
|
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82,
|
||||||
|
0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d,
|
||||||
|
0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8,
|
||||||
|
0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff,
|
||||||
|
0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee,
|
||||||
|
0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
||||||
|
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||||
|
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c,
|
||||||
|
0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||||
|
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02,
|
||||||
|
0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned long compute_crc( unsigned char *b, size_t n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned long s = 0;
|
||||||
|
int aux = 0;
|
||||||
|
|
||||||
|
while (n-- > 0) {
|
||||||
|
/* Compute the index to the crc table */
|
||||||
|
i = (s >> 24) ^ ((unsigned int) (*b++));
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
/* Replace an intermediate zero with the next value
|
||||||
|
* from the sequence */
|
||||||
|
i = aux++;
|
||||||
|
if (aux >= sizeof(crctab) / sizeof(crctab[0])) aux = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New checksum value */
|
||||||
|
s = (s << 8) ^ crctab[i];
|
||||||
|
}
|
||||||
|
return(s);
|
||||||
|
}
|
||||||
|
|
6
drivers/filter/crc.h
Normal file
6
drivers/filter/crc.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef _CRC_H
|
||||||
|
#define _CRC_H
|
||||||
|
|
||||||
|
extern unsigned long compute_crc(unsigned char *b, size_t n);
|
||||||
|
|
||||||
|
#endif /* _CRC_H */
|
930
drivers/filter/driver.c
Normal file
930
drivers/filter/driver.c
Normal file
|
@ -0,0 +1,930 @@
|
||||||
|
/* Filter driver - lowest layer - disk driver management */
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
|
||||||
|
/* Drivers. */
|
||||||
|
static struct {
|
||||||
|
char *label;
|
||||||
|
int minor;
|
||||||
|
endpoint_t endpt;
|
||||||
|
|
||||||
|
int problem; /* one of BD_* */
|
||||||
|
int error; /* one of E*, only relevant if problem>0 */
|
||||||
|
int retries;
|
||||||
|
int kills;
|
||||||
|
} driver[2];
|
||||||
|
|
||||||
|
/* State variables. */
|
||||||
|
static endpoint_t self_ep;
|
||||||
|
static asynmsg_t amsgtable[2];
|
||||||
|
|
||||||
|
static int size_known = 0;
|
||||||
|
static u64_t disk_size;
|
||||||
|
|
||||||
|
static int problem_stats[BD_LAST] = { 0 };
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* driver_open *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int driver_open(int which)
|
||||||
|
{
|
||||||
|
/* Perform an open or close operation on the driver. This is
|
||||||
|
* unfinished code: we should never be doing a blocking sendrec() to
|
||||||
|
* the driver.
|
||||||
|
*/
|
||||||
|
message msg;
|
||||||
|
cp_grant_id_t gid;
|
||||||
|
struct partition part;
|
||||||
|
sector_t sectors;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
msg.m_type = DEV_OPEN;
|
||||||
|
msg.DEVICE = driver[which].minor;
|
||||||
|
msg.IO_ENDPT = self_ep;
|
||||||
|
r = sendrec(driver[which].endpt, &msg);
|
||||||
|
|
||||||
|
if (r != OK) {
|
||||||
|
/* Should we restart the driver now? */
|
||||||
|
printf("Filter: driver_open: sendrec returned %d\n", r);
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.m_type != TASK_REPLY || msg.REP_STATUS != OK) {
|
||||||
|
printf("Filter: driver_open: sendrec returned %d, %d\n",
|
||||||
|
msg.m_type, msg.REP_STATUS);
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Take the opportunity to retrieve the hard disk size. */
|
||||||
|
gid = cpf_grant_direct(driver[which].endpt,
|
||||||
|
(vir_bytes) &part, sizeof(part), CPF_WRITE);
|
||||||
|
if(!GRANT_VALID(gid))
|
||||||
|
panic(__FILE__, "invalid grant", gid);
|
||||||
|
|
||||||
|
msg.m_type = DEV_IOCTL_S;
|
||||||
|
msg.REQUEST = DIOCGETP;
|
||||||
|
msg.DEVICE = driver[which].minor;
|
||||||
|
msg.IO_ENDPT = self_ep;
|
||||||
|
msg.IO_GRANT = (char *) gid;
|
||||||
|
|
||||||
|
r = sendrec(driver[which].endpt, &msg);
|
||||||
|
|
||||||
|
cpf_revoke(gid);
|
||||||
|
|
||||||
|
if (r != OK || msg.m_type != TASK_REPLY || msg.REP_STATUS != OK) {
|
||||||
|
/* Not sure what to do here, either. */
|
||||||
|
printf("Filter: ioctl(DIOCGETP) returned (%d, %d)\n",
|
||||||
|
r, msg.m_type);
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!size_known) {
|
||||||
|
disk_size = part.size;
|
||||||
|
size_known = 1;
|
||||||
|
sectors = div64u(disk_size, SECTOR_SIZE);
|
||||||
|
if(cmp64(mul64u(sectors, SECTOR_SIZE), disk_size)) {
|
||||||
|
printf("Filter: partition too large\n");
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: partition size: 0x%s / %lu sectors\n",
|
||||||
|
print64(disk_size), sectors);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
if(cmp64(disk_size, part.size)) {
|
||||||
|
printf("Filter: partition size mismatch (%s != %s)\n",
|
||||||
|
print64(part.size), print64(disk_size));
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* driver_close *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int driver_close(int which)
|
||||||
|
{
|
||||||
|
message msg;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
msg.m_type = DEV_CLOSE;
|
||||||
|
msg.DEVICE = driver[which].minor;
|
||||||
|
msg.IO_ENDPT = self_ep;
|
||||||
|
r = sendrec(driver[which].endpt, &msg);
|
||||||
|
|
||||||
|
if (r != OK) {
|
||||||
|
/* Should we restart the driver now? */
|
||||||
|
printf("Filter: driver_close: sendrec returned %d\n", r);
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.m_type != TASK_REPLY || msg.REP_STATUS != OK) {
|
||||||
|
printf("Filter: driver_close: sendrec returned %d, %d\n",
|
||||||
|
msg.m_type, msg.REP_STATUS);
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* driver_init *
|
||||||
|
*===========================================================================*/
|
||||||
|
void driver_init(void)
|
||||||
|
{
|
||||||
|
/* Initialize the driver layer. */
|
||||||
|
int r;
|
||||||
|
|
||||||
|
self_ep = getprocnr();
|
||||||
|
|
||||||
|
memset(driver, 0, sizeof(driver));
|
||||||
|
|
||||||
|
/* Endpoints unknown. */
|
||||||
|
driver[DRIVER_MAIN].endpt = NONE;
|
||||||
|
driver[DRIVER_BACKUP].endpt = NONE;
|
||||||
|
|
||||||
|
/* Get disk driver's and this proc's endpoint. */
|
||||||
|
driver[DRIVER_MAIN].label = MAIN_LABEL;
|
||||||
|
driver[DRIVER_MAIN].minor = MAIN_MINOR;
|
||||||
|
|
||||||
|
r = ds_retrieve_u32(driver[DRIVER_MAIN].label,
|
||||||
|
(u32_t *) &driver[DRIVER_MAIN].endpt);
|
||||||
|
if (r != OK) {
|
||||||
|
printf("Filter: failed to get main disk driver's endpoint: "
|
||||||
|
"%d\n", r);
|
||||||
|
bad_driver(DRIVER_MAIN, BD_DEAD, EFAULT);
|
||||||
|
check_driver(DRIVER_MAIN);
|
||||||
|
}
|
||||||
|
else if (driver_open(DRIVER_MAIN) != OK) {
|
||||||
|
panic(__FILE__, "unhandled driver_open failure", NO_NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(USE_MIRROR) {
|
||||||
|
driver[DRIVER_BACKUP].label = BACKUP_LABEL;
|
||||||
|
driver[DRIVER_BACKUP].minor = BACKUP_MINOR;
|
||||||
|
|
||||||
|
if(!strcmp(driver[DRIVER_MAIN].label,
|
||||||
|
driver[DRIVER_BACKUP].label)) {
|
||||||
|
panic(__FILE__, "same driver: not tested", NO_NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = ds_retrieve_u32(driver[DRIVER_BACKUP].label,
|
||||||
|
(u32_t *) &driver[DRIVER_BACKUP].endpt);
|
||||||
|
if (r != OK) {
|
||||||
|
printf("Filter: failed to get backup disk driver's "
|
||||||
|
"endpoint: %d\n", r);
|
||||||
|
bad_driver(DRIVER_BACKUP, BD_DEAD, EFAULT);
|
||||||
|
check_driver(DRIVER_BACKUP);
|
||||||
|
}
|
||||||
|
else if (driver_open(DRIVER_BACKUP) != OK) {
|
||||||
|
panic(__FILE__, "unhandled driver_open failure",
|
||||||
|
NO_NUM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* driver_shutdown *
|
||||||
|
*===========================================================================*/
|
||||||
|
void driver_shutdown(void)
|
||||||
|
{
|
||||||
|
/* Clean up. */
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: %u driver deaths, %u protocol errors, "
|
||||||
|
"%u data errors\n", problem_stats[BD_DEAD],
|
||||||
|
problem_stats[BD_PROTO], problem_stats[BD_DATA]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(driver_close(DRIVER_MAIN) != OK)
|
||||||
|
printf("Filter: DEV_CLOSE failed on shutdown (1)\n");
|
||||||
|
|
||||||
|
if(USE_MIRROR)
|
||||||
|
if(driver_close(DRIVER_BACKUP) != OK)
|
||||||
|
printf("Filter: DEV_CLOSE failed on shutdown (2)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* get_raw_size *
|
||||||
|
*===========================================================================*/
|
||||||
|
u64_t get_raw_size(void)
|
||||||
|
{
|
||||||
|
/* Return the size of the raw disks as used by the filter driver.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return disk_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* reset_kills *
|
||||||
|
*===========================================================================*/
|
||||||
|
void reset_kills(void)
|
||||||
|
{
|
||||||
|
/* Reset kill and retry statistics. */
|
||||||
|
driver[DRIVER_MAIN].kills = 0;
|
||||||
|
driver[DRIVER_MAIN].retries = 0;
|
||||||
|
driver[DRIVER_BACKUP].kills = 0;
|
||||||
|
driver[DRIVER_BACKUP].retries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* bad_driver *
|
||||||
|
*===========================================================================*/
|
||||||
|
int bad_driver(int which, int type, int error)
|
||||||
|
{
|
||||||
|
/* A disk driver has died or produced an error. Mark it so that we can
|
||||||
|
* deal with it later, and return RET_REDO to indicate that the
|
||||||
|
* current operation is to be retried. Also store an error code to
|
||||||
|
* return to the user if the situation is unrecoverable.
|
||||||
|
*/
|
||||||
|
driver[which].problem = type;
|
||||||
|
driver[which].error = error;
|
||||||
|
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* new_driver_ep *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int new_driver_ep(int which)
|
||||||
|
{
|
||||||
|
/* See if a new driver instance has already been started for the given
|
||||||
|
* driver, by retrieving its entry from DS.
|
||||||
|
*/
|
||||||
|
int r;
|
||||||
|
endpoint_t endpt;
|
||||||
|
|
||||||
|
r = ds_retrieve_u32(driver[which].label, (u32_t *) &endpt);
|
||||||
|
|
||||||
|
if (r != OK) {
|
||||||
|
printf("Filter: DS query for %s failed\n",
|
||||||
|
driver[which].label);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpt == driver[which].endpt) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: same endpoint for %s\n", driver[which].label);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: new enpdoint for %s: %d -> %d\n", driver[which].label,
|
||||||
|
driver[which].endpt, endpt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
driver[which].endpt = endpt;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_problem *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int check_problem(int which, int problem, int retries, int *tell_rs)
|
||||||
|
{
|
||||||
|
/* A problem has occurred with a driver. Update statistics, and decide
|
||||||
|
* what to do. If EAGAIN is returned, the driver should be restarted;
|
||||||
|
* any other result will be passed up.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: check_driver processing driver %d, problem %d\n",
|
||||||
|
which, problem);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
problem_stats[problem]++;
|
||||||
|
|
||||||
|
if(new_driver_ep(which)) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: check_problem: noticed a new driver\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(driver_open(which) == OK) {
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: open OK -> no recovery\n");
|
||||||
|
#endif
|
||||||
|
return OK;
|
||||||
|
} else {
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: open not OK -> recovery\n");
|
||||||
|
#endif
|
||||||
|
problem = BD_PROTO;
|
||||||
|
problem_stats[problem]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the driver has died, we always need to restart it. If it has
|
||||||
|
* been giving problems, we first retry the request, up to N times,
|
||||||
|
* after which we kill and restart the driver. We restart the driver
|
||||||
|
* up to M times, after which we remove the driver from the mirror
|
||||||
|
* configuration. If we are not set up to do mirroring, we can only
|
||||||
|
* do one thing, and that is continue to limp along with the bad
|
||||||
|
* driver..
|
||||||
|
*/
|
||||||
|
switch(problem) {
|
||||||
|
case BD_PROTO:
|
||||||
|
case BD_DATA:
|
||||||
|
driver[which].retries++;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: disk driver %d has had "
|
||||||
|
"%d/%d retry attempts, %d/%d kills\n", which,
|
||||||
|
driver[which].retries, NR_RETRIES,
|
||||||
|
driver[which].kills, NR_RESTARTS);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (driver[which].retries < NR_RETRIES) {
|
||||||
|
if(retries == 1) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: not restarting; retrying "
|
||||||
|
"(retries %d/%d, kills %d/%d)\n",
|
||||||
|
driver[which].retries, NR_RETRIES,
|
||||||
|
driver[which].kills, NR_RESTARTS);
|
||||||
|
#endif
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: restarting (retries %d/%d, "
|
||||||
|
"kills %d/%d, internal retry %d)\n",
|
||||||
|
driver[which].retries, NR_RETRIES,
|
||||||
|
driver[which].kills, NR_RESTARTS, retries);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: disk driver %d has reached error "
|
||||||
|
"threshold, restarting driver\n", which);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*tell_rs = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BD_DEAD:
|
||||||
|
/* Can't kill that which is already dead.. */
|
||||||
|
*tell_rs = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(__FILE__, "invalid problem", problem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* At this point, the driver will be restarted. */
|
||||||
|
driver[which].retries = 0;
|
||||||
|
driver[which].kills++;
|
||||||
|
|
||||||
|
if (driver[which].kills < NR_RESTARTS)
|
||||||
|
return EAGAIN;
|
||||||
|
|
||||||
|
/* We've reached the maximum number of restarts for this driver. */
|
||||||
|
if (USE_MIRROR) {
|
||||||
|
printf("Filter: kill threshold reached, disabling mirroring\n");
|
||||||
|
|
||||||
|
USE_MIRROR = 0;
|
||||||
|
|
||||||
|
if (which == DRIVER_MAIN) {
|
||||||
|
driver[DRIVER_MAIN] = driver[DRIVER_BACKUP];
|
||||||
|
|
||||||
|
/* This is not necessary. */
|
||||||
|
strcpy(MAIN_LABEL, BACKUP_LABEL);
|
||||||
|
MAIN_MINOR = BACKUP_MINOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver[DRIVER_BACKUP].endpt = NONE;
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* We tried, we really did. But now we give up. Tell the user.
|
||||||
|
*/
|
||||||
|
printf("Filter: kill threshold reached, returning error\n");
|
||||||
|
|
||||||
|
if (driver[which].error == EAGAIN) return EIO;
|
||||||
|
|
||||||
|
return driver[which].error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* restart_driver *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void restart_driver(int which, int tell_rs)
|
||||||
|
{
|
||||||
|
/* Restart the given driver. Block until the new instance is up.
|
||||||
|
*/
|
||||||
|
message msg;
|
||||||
|
endpoint_t endpt;
|
||||||
|
int r, w = 0;
|
||||||
|
|
||||||
|
if (tell_rs) {
|
||||||
|
/* Tell RS to refresh or restart the driver */
|
||||||
|
msg.m_type = RS_REFRESH;
|
||||||
|
msg.RS_CMD_ADDR = driver[which].label;
|
||||||
|
msg.RS_CMD_LEN = strlen(driver[which].label);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: asking RS to refresh %s..\n",
|
||||||
|
driver[which].label);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
r = sendrec(RS_PROC_NR, &msg);
|
||||||
|
|
||||||
|
if (r != OK || msg.m_type != OK)
|
||||||
|
panic(__FILE__, "RS request failed", r);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: RS call succeeded\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait until the new driver instance is up, and get its endpoint. */
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: endpoint update driver %d; old endpoint %d\n",
|
||||||
|
which, driver[which].endpt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(w) flt_sleep(1);
|
||||||
|
w = 1;
|
||||||
|
|
||||||
|
r = ds_retrieve_u32(driver[which].label, (u32_t *) &endpt);
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
if (r != OK)
|
||||||
|
printf("Filter: DS request failed (%d)\n", r);
|
||||||
|
else if (endpt == driver[which].endpt)
|
||||||
|
printf("Filter: DS returned same endpoint\n");
|
||||||
|
else
|
||||||
|
printf("Filter: DS request OK, new endpoint\n");
|
||||||
|
#endif
|
||||||
|
} while (r != OK || endpt == driver[which].endpt);
|
||||||
|
|
||||||
|
driver[which].endpt = endpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_driver *
|
||||||
|
*===========================================================================*/
|
||||||
|
int check_driver(int which)
|
||||||
|
{
|
||||||
|
/* See if the given driver has been troublesome, and if so, deal with
|
||||||
|
* it.
|
||||||
|
*/
|
||||||
|
int problem, tell_rs;
|
||||||
|
int r, retries = 0;
|
||||||
|
|
||||||
|
problem = driver[which].problem;
|
||||||
|
|
||||||
|
if (problem == BD_NONE)
|
||||||
|
return OK;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(retries) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: check_driver: retry number %d\n",
|
||||||
|
retries);
|
||||||
|
#endif
|
||||||
|
problem = BD_PROTO;
|
||||||
|
}
|
||||||
|
retries++;
|
||||||
|
driver[which].problem = BD_NONE;
|
||||||
|
|
||||||
|
/* Decide what to do: continue operation, restart the driver,
|
||||||
|
* or return an error.
|
||||||
|
*/
|
||||||
|
r = check_problem(which, problem, retries, &tell_rs);
|
||||||
|
if (r != EAGAIN)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Restarting the driver it is. First tell RS (if necessary),
|
||||||
|
* then wait for the new driver instance to come up.
|
||||||
|
*/
|
||||||
|
restart_driver(which, tell_rs);
|
||||||
|
|
||||||
|
/* Finally, open the device on the new driver */
|
||||||
|
} while (driver_open(which) != OK);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: check_driver restarted driver %d, endpoint %d\n",
|
||||||
|
which, driver[which].endpt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_senda *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int flt_senda(message *mess, int which)
|
||||||
|
{
|
||||||
|
/* Send a message to one driver. Can only return OK at the moment. */
|
||||||
|
int r;
|
||||||
|
asynmsg_t *amp;
|
||||||
|
|
||||||
|
/* Fill in the last bits of the message. */
|
||||||
|
mess->DEVICE = driver[which].minor;
|
||||||
|
mess->IO_ENDPT = self_ep;
|
||||||
|
|
||||||
|
/* Send the message asynchronously. */
|
||||||
|
amp = &amsgtable[which];
|
||||||
|
amp->dst = driver[which].endpt;
|
||||||
|
amp->msg = *mess;
|
||||||
|
amp->flags = AMF_VALID;
|
||||||
|
r = senda(amsgtable, 2);
|
||||||
|
|
||||||
|
if(r != OK)
|
||||||
|
panic(__FILE__, "senda returned error", r);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_senda *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int check_senda(int which)
|
||||||
|
{
|
||||||
|
/* Check whether an earlier senda resulted in an error indicating the
|
||||||
|
* message never got delivered. Only in that case can we reliably say
|
||||||
|
* that the driver died. Return BD_DEAD in this case, and BD_PROTO
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
asynmsg_t *amp;
|
||||||
|
|
||||||
|
amp = &amsgtable[which];
|
||||||
|
|
||||||
|
if ((amp->flags & AMF_DONE) &&
|
||||||
|
(amp->result == EDEADSRCDST || amp->result == EDSTDIED)) {
|
||||||
|
|
||||||
|
return BD_DEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BD_PROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_receive *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int flt_receive(message *mess, int which)
|
||||||
|
{
|
||||||
|
/* Receive a message from one or either driver, unless a timeout
|
||||||
|
* occurs. Can only return OK or RET_REDO.
|
||||||
|
*/
|
||||||
|
int r;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
r = receive(ANY, mess);
|
||||||
|
if(r != OK)
|
||||||
|
panic(__FILE__, "receive returned error", r);
|
||||||
|
|
||||||
|
if(mess->m_source == CLOCK && is_notify(mess->m_type)) {
|
||||||
|
if (mess->NOTIFY_TIMESTAMP < flt_alarm(-1)) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: SKIPPING old alarm "
|
||||||
|
"notification\n");
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: timeout waiting for disk driver %d "
|
||||||
|
"reply!\n", which);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* If we're waiting for either driver,
|
||||||
|
* both are at fault.
|
||||||
|
*/
|
||||||
|
if (which < 0) {
|
||||||
|
bad_driver(DRIVER_MAIN,
|
||||||
|
check_senda(DRIVER_MAIN), EFAULT);
|
||||||
|
|
||||||
|
return bad_driver(DRIVER_BACKUP,
|
||||||
|
check_senda(DRIVER_BACKUP), EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, just report the one not replying as dead.
|
||||||
|
*/
|
||||||
|
return bad_driver(which, check_senda(which), EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mess->m_source != driver[DRIVER_MAIN].endpt &&
|
||||||
|
mess->m_source != driver[DRIVER_BACKUP].endpt) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: got STRAY message %d from %d\n",
|
||||||
|
mess->m_type, mess->m_source);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are waiting for a reply from one specific driver. */
|
||||||
|
if (which >= 0) {
|
||||||
|
/* If the message source is that driver, good. */
|
||||||
|
if (mess->m_source == driver[which].endpt)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* This should probably be treated as a real protocol
|
||||||
|
* error. We do not abort any receives (not even paired
|
||||||
|
* receives) except because of timeouts. Getting here
|
||||||
|
* means a driver replied at least the timeout period
|
||||||
|
* later than expected, which should be enough reason
|
||||||
|
* to kill it really. The other explanation is that it
|
||||||
|
* is actually violating the protocol and sending bogus
|
||||||
|
* messages...
|
||||||
|
*/
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: got UNEXPECTED reply from %d\n",
|
||||||
|
mess->m_source);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We got a message from one of the drivers, and we didn't
|
||||||
|
* care which one we wanted to receive from. A-OK.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_sendrec *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int flt_sendrec(message *mess, int which)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = flt_senda(mess, which);
|
||||||
|
if(r != OK)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if(check_senda(which) == BD_DEAD) {
|
||||||
|
return bad_driver(which, BD_DEAD, EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set alarm. */
|
||||||
|
flt_alarm(DRIVER_TIMEOUT);
|
||||||
|
|
||||||
|
r = flt_receive(mess, which);
|
||||||
|
|
||||||
|
/* Clear the alarm. */
|
||||||
|
flt_alarm(0);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* do_sendrec_both *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int do_sendrec_both(message *m1, message *m2)
|
||||||
|
{
|
||||||
|
/* If USEE_MIRROR is set, call flt_sendrec() to both drivers.
|
||||||
|
* Otherwise, only call flt_sendrec() to the main driver.
|
||||||
|
* This function will only return either OK or RET_REDO.
|
||||||
|
*/
|
||||||
|
int r, which = -1;
|
||||||
|
message ma, mb;
|
||||||
|
|
||||||
|
/* If the two disks use the same driver, call flt_sendrec() twice
|
||||||
|
* sequentially. Such a setup is not very useful though.
|
||||||
|
*/
|
||||||
|
if (!strcmp(driver[DRIVER_MAIN].label, driver[DRIVER_BACKUP].label)) {
|
||||||
|
if ((r = flt_sendrec(m1, DRIVER_MAIN)) != OK) return r;
|
||||||
|
return flt_sendrec(m2, DRIVER_BACKUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the two disks use different drivers, call flt_senda()
|
||||||
|
* twice, and then flt_receive(), and distinguish the return
|
||||||
|
* messages by means of m_source.
|
||||||
|
*/
|
||||||
|
if ((r = flt_senda(m1, DRIVER_MAIN)) != OK) return r;
|
||||||
|
if ((r = flt_senda(m2, DRIVER_BACKUP)) != OK) return r;
|
||||||
|
|
||||||
|
/* Set alarm. */
|
||||||
|
flt_alarm(DRIVER_TIMEOUT);
|
||||||
|
|
||||||
|
/* The message received by the 1st flt_receive() may not be
|
||||||
|
* from DRIVER_MAIN.
|
||||||
|
*/
|
||||||
|
if ((r = flt_receive(&ma, -1)) != OK) {
|
||||||
|
flt_alarm(0);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ma.m_source == driver[DRIVER_MAIN].endpt) {
|
||||||
|
which = DRIVER_BACKUP;
|
||||||
|
} else if (ma.m_source == driver[DRIVER_BACKUP].endpt) {
|
||||||
|
which = DRIVER_MAIN;
|
||||||
|
} else {
|
||||||
|
panic(__FILE__, "message from unexpected source",
|
||||||
|
ma.m_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = flt_receive(&mb, which);
|
||||||
|
|
||||||
|
/* Clear the alarm. */
|
||||||
|
flt_alarm(0);
|
||||||
|
|
||||||
|
if(r != OK)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (ma.m_source == driver[DRIVER_MAIN].endpt) {
|
||||||
|
*m1 = ma;
|
||||||
|
*m2 = mb;
|
||||||
|
} else {
|
||||||
|
*m1 = mb;
|
||||||
|
*m2 = ma;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* do_sendrec_one *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int do_sendrec_one(message *m1, message *m2)
|
||||||
|
{
|
||||||
|
/* Only talk to the main driver. If something goes wrong, it will
|
||||||
|
* be fixed elsewhere.
|
||||||
|
* This function will only return either OK or RET_REDO.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return flt_sendrec(m1, DRIVER_MAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* paired_sendrec *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int paired_sendrec(message *m1, message *m2, int both)
|
||||||
|
{
|
||||||
|
/* Sendrec with the disk driver. If the disk driver is down, and was
|
||||||
|
* restarted, redo the request, until the driver works fine, or can't
|
||||||
|
* be restarted again.
|
||||||
|
*/
|
||||||
|
int r;
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
printf("paired_sendrec(%d) - <%d,%x:%x,%d> - %x,%x\n",
|
||||||
|
both, m1->m_type, m1->HIGHPOS, m1->POSITION, m1->COUNT,
|
||||||
|
m1->IO_GRANT, m2->IO_GRANT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (both)
|
||||||
|
r = do_sendrec_both(m1, m2);
|
||||||
|
else
|
||||||
|
r = do_sendrec_one(m1, m2);
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
if (r != OK)
|
||||||
|
printf("paired_sendrec about to return %d\n", r);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* paired_grant *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void paired_grant(char *buf1, char *buf2, size_t size, int request,
|
||||||
|
cp_grant_id_t *gids, int both)
|
||||||
|
{
|
||||||
|
/* Create memory grants. If USE_MIRROR, grant to both drivers,
|
||||||
|
* otherwise only to the main one.
|
||||||
|
*/
|
||||||
|
cp_grant_id_t gid;
|
||||||
|
int access;
|
||||||
|
|
||||||
|
access = (request == FLT_WRITE) ? CPF_READ : CPF_WRITE;
|
||||||
|
|
||||||
|
if(driver[DRIVER_MAIN].endpt > 0) {
|
||||||
|
gid = cpf_grant_direct(driver[DRIVER_MAIN].endpt,
|
||||||
|
(vir_bytes) buf1, size, access);
|
||||||
|
if(!GRANT_VALID(gid))
|
||||||
|
panic(__FILE__, "invalid grant", gid);
|
||||||
|
gids[0] = gid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (both) {
|
||||||
|
if(driver[DRIVER_BACKUP].endpt > 0) {
|
||||||
|
gid = cpf_grant_direct(driver[DRIVER_BACKUP].endpt,
|
||||||
|
(vir_bytes) buf2, size, access);
|
||||||
|
if(!GRANT_VALID(gid))
|
||||||
|
panic(__FILE__, "invalid grant", gid);
|
||||||
|
gids[1] = gid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* paired_revoke *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void paired_revoke(cp_grant_id_t gid1, cp_grant_id_t gid2, int both)
|
||||||
|
{
|
||||||
|
cpf_revoke(gid1);
|
||||||
|
|
||||||
|
if (both)
|
||||||
|
cpf_revoke(gid2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* read_write *
|
||||||
|
*===========================================================================*/
|
||||||
|
int read_write(u64_t pos, char *bufa, char *bufb, size_t *sizep, int request)
|
||||||
|
{
|
||||||
|
message m1, m2;
|
||||||
|
cp_grant_id_t gids[2];
|
||||||
|
int r, both;
|
||||||
|
|
||||||
|
gids[0] = gids[1] = GRANT_INVALID;
|
||||||
|
|
||||||
|
/* Send two requests only if mirroring is enabled and the given request
|
||||||
|
* is either FLT_READ2 or FLT_WRITE.
|
||||||
|
*/
|
||||||
|
both = (USE_MIRROR && request != FLT_READ);
|
||||||
|
|
||||||
|
m1.m_type = (request == FLT_WRITE) ? DEV_WRITE_S : DEV_READ_S;
|
||||||
|
m1.COUNT = *sizep;
|
||||||
|
m1.POSITION = ex64lo(pos);
|
||||||
|
m1.HIGHPOS = ex64hi(pos);
|
||||||
|
m2 = m1;
|
||||||
|
|
||||||
|
paired_grant(bufa, bufb, *sizep, request, gids, both);
|
||||||
|
m1.IO_GRANT = (char *) gids[0];
|
||||||
|
m2.IO_GRANT = (char *) gids[1];
|
||||||
|
|
||||||
|
r = paired_sendrec(&m1, &m2, both);
|
||||||
|
|
||||||
|
paired_revoke(gids[0], gids[1], both);
|
||||||
|
|
||||||
|
if(r != OK) {
|
||||||
|
#if DEBUG
|
||||||
|
if (r != RET_REDO)
|
||||||
|
printf("Filter: paired_sendrec returned %d\n", r);
|
||||||
|
#endif
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.m_type != TASK_REPLY || m1.REP_STATUS < 0) {
|
||||||
|
printf("Filter: unexpected/invalid reply from main driver: "
|
||||||
|
"(%x, %d)\n", m1.m_type, m1.REP_STATUS);
|
||||||
|
|
||||||
|
return bad_driver(DRIVER_MAIN, BD_PROTO,
|
||||||
|
(m1.m_type == TASK_REPLY) ? m1.REP_STATUS : EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.REP_STATUS != *sizep) {
|
||||||
|
printf("Filter: truncated reply %u to I/O request of size "
|
||||||
|
"0x%x at 0x%s; size 0x%s\n",
|
||||||
|
m1.REP_STATUS, *sizep,
|
||||||
|
print64(pos), print64(disk_size));
|
||||||
|
|
||||||
|
/* If the driver returned a value *larger* than we requested,
|
||||||
|
* OR if we did NOT exceed the disk size, then we should
|
||||||
|
* report the driver for acting strangely!
|
||||||
|
*/
|
||||||
|
if (m1.REP_STATUS > *sizep ||
|
||||||
|
cmp64(add64u(pos, *sizep), disk_size) < 0)
|
||||||
|
return bad_driver(DRIVER_MAIN, BD_PROTO, EFAULT);
|
||||||
|
|
||||||
|
/* Return the actual size. */
|
||||||
|
*sizep = m1.REP_STATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (both) {
|
||||||
|
if (m2.m_type != TASK_REPLY || m2.REP_STATUS < 0) {
|
||||||
|
printf("Filter: unexpected/invalid reply from "
|
||||||
|
"backup driver (%x, %d)\n",
|
||||||
|
m2.m_type, m2.REP_STATUS);
|
||||||
|
|
||||||
|
return bad_driver(DRIVER_BACKUP, BD_PROTO,
|
||||||
|
m2.m_type == TASK_REPLY ? m2.REP_STATUS :
|
||||||
|
EFAULT);
|
||||||
|
}
|
||||||
|
if (m2.REP_STATUS != *sizep) {
|
||||||
|
printf("Filter: truncated reply from backup driver\n");
|
||||||
|
|
||||||
|
/* As above */
|
||||||
|
if (m2.REP_STATUS > *sizep ||
|
||||||
|
cmp64(add64u(pos, *sizep), disk_size) < 0)
|
||||||
|
return bad_driver(DRIVER_BACKUP, BD_PROTO,
|
||||||
|
EFAULT);
|
||||||
|
|
||||||
|
/* Return the actual size. */
|
||||||
|
if (*sizep >= m2.REP_STATUS)
|
||||||
|
*sizep = m2.REP_STATUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
101
drivers/filter/inc.h
Normal file
101
drivers/filter/inc.h
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/* Filter driver - general include file */
|
||||||
|
#define _MINIX 1
|
||||||
|
#define _SYSTEM 1
|
||||||
|
#include <minix/config.h>
|
||||||
|
#include <minix/const.h>
|
||||||
|
#include <minix/type.h>
|
||||||
|
#include <minix/com.h>
|
||||||
|
#include <minix/ipc.h>
|
||||||
|
#include <sys/ioc_disk.h>
|
||||||
|
#include <minix/sysutil.h>
|
||||||
|
#include <minix/syslib.h>
|
||||||
|
#include <minix/partition.h>
|
||||||
|
#include <minix/ds.h>
|
||||||
|
#include <minix/callnr.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ST_XOR, /* XOR-based checksums */
|
||||||
|
ST_CRC, /* CRC32-based checksums */
|
||||||
|
ST_MD5 /* MD5-based checksums */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
FLT_WRITE, /* write to up to two disks */
|
||||||
|
FLT_READ, /* read from one disk */
|
||||||
|
FLT_READ2 /* read from both disks */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Something was wrong and the disk driver has been restarted/refreshed,
|
||||||
|
* so the request needs to be redone.
|
||||||
|
*/
|
||||||
|
#define RET_REDO 1
|
||||||
|
|
||||||
|
/* The cases where the disk driver need to be restarted/refreshed by RS.
|
||||||
|
* BD_DEAD: the disk driver has died. Restart it.
|
||||||
|
* BD_PROTO: a protocol error has occurred. Refresh it.
|
||||||
|
* BD_DATA: a data error has occurred. Refresh it.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
BD_NONE,
|
||||||
|
BD_DEAD,
|
||||||
|
BD_PROTO,
|
||||||
|
BD_DATA,
|
||||||
|
BD_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DRIVER_MAIN 0
|
||||||
|
#define DRIVER_BACKUP 1
|
||||||
|
|
||||||
|
/* Requests for more than this many bytes need to go through malloc(). */
|
||||||
|
#define BUF_SIZE (128 * 1024)
|
||||||
|
#define SBUF_SIZE (BUF_SIZE * 2)
|
||||||
|
|
||||||
|
#define LABEL_SIZE 32
|
||||||
|
|
||||||
|
typedef unsigned long sector_t;
|
||||||
|
|
||||||
|
/* main.c */
|
||||||
|
extern int USE_CHECKSUM;
|
||||||
|
extern int USE_MIRROR;
|
||||||
|
extern int BAD_SUM_ERROR;
|
||||||
|
extern int USE_SUM_LAYOUT;
|
||||||
|
extern int SUM_TYPE;
|
||||||
|
extern int SUM_SIZE;
|
||||||
|
extern int NR_SUM_SEC;
|
||||||
|
extern int NR_RETRIES;
|
||||||
|
extern int NR_RESTARTS;
|
||||||
|
extern int DRIVER_TIMEOUT;
|
||||||
|
|
||||||
|
extern char MAIN_LABEL[LABEL_SIZE];
|
||||||
|
extern char BACKUP_LABEL[LABEL_SIZE];
|
||||||
|
extern int MAIN_MINOR;
|
||||||
|
extern int BACKUP_MINOR;
|
||||||
|
|
||||||
|
/* sum.c */
|
||||||
|
extern void sum_init(void);
|
||||||
|
extern int transfer(u64_t pos, char *buffer, size_t *sizep, int flag_rw);
|
||||||
|
extern u64_t convert(u64_t size);
|
||||||
|
|
||||||
|
/* driver.c */
|
||||||
|
extern void driver_init(void);
|
||||||
|
extern void driver_shutdown(void);
|
||||||
|
extern u64_t get_raw_size(void);
|
||||||
|
extern void reset_kills(void);
|
||||||
|
extern int check_driver(int which);
|
||||||
|
extern int bad_driver(int which, int type, int error);
|
||||||
|
extern int read_write(u64_t pos, char *bufa, char *bufb, size_t *sizep,
|
||||||
|
int flag_rw);
|
||||||
|
|
||||||
|
/* util.c */
|
||||||
|
extern char *flt_malloc(size_t size, char *sbuf, size_t ssize);
|
||||||
|
extern void flt_free(char *buf, size_t size, char *sbuf);
|
||||||
|
extern char *print64(u64_t p);
|
||||||
|
extern clock_t flt_alarm(clock_t dt);
|
||||||
|
extern void flt_sleep(int secs);
|
446
drivers/filter/main.c
Normal file
446
drivers/filter/main.c
Normal file
|
@ -0,0 +1,446 @@
|
||||||
|
/* Filter driver - top layer - block interface */
|
||||||
|
|
||||||
|
/* This is a filter driver, which lays above disk driver, and forwards
|
||||||
|
* messages between disk driver and its callers. The filter can detect
|
||||||
|
* corrupted data (toggled by USE_CHECKSUM) and recover it (toggled
|
||||||
|
* by USE_MIRROR). These two functions are independent from each other.
|
||||||
|
* The mirroring function requires two disks, on separate disk drivers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
#include "optset.h"
|
||||||
|
|
||||||
|
#define _POSIX_SOURCE 1
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
/* Global settings. */
|
||||||
|
int USE_CHECKSUM = 0; /* enable checksumming */
|
||||||
|
int USE_MIRROR = 0; /* enable mirroring */
|
||||||
|
|
||||||
|
int BAD_SUM_ERROR = 1; /* bad checksums are considered a driver error */
|
||||||
|
|
||||||
|
int USE_SUM_LAYOUT = 0; /* use checksumming layout on disk */
|
||||||
|
int NR_SUM_SEC = 8; /* number of checksums per checksum sector */
|
||||||
|
|
||||||
|
int SUM_TYPE = 0; /* use XOR, CRC or MD5 */
|
||||||
|
int SUM_SIZE = 0; /* size of the stored checksum */
|
||||||
|
|
||||||
|
int NR_RETRIES = 3; /* number of times the request will be retried (N) */
|
||||||
|
int NR_RESTARTS = 3; /* number of times a driver will be restarted (M) */
|
||||||
|
int DRIVER_TIMEOUT = 5; /* timeout in seconds to declare a driver dead (T) */
|
||||||
|
|
||||||
|
char MAIN_LABEL[LABEL_SIZE] = ""; /* main disk driver label */
|
||||||
|
char BACKUP_LABEL[LABEL_SIZE] = ""; /* backup disk driver label */
|
||||||
|
int MAIN_MINOR = -1; /* main partition minor nr */
|
||||||
|
int BACKUP_MINOR = -1; /* backup partition minor nr */
|
||||||
|
|
||||||
|
struct optset optset_table[] = {
|
||||||
|
{ "label0", OPT_STRING, MAIN_LABEL, LABEL_SIZE },
|
||||||
|
{ "label1", OPT_STRING, BACKUP_LABEL, LABEL_SIZE },
|
||||||
|
{ "minor0", OPT_INT, &MAIN_MINOR, 10 },
|
||||||
|
{ "minor1", OPT_INT, &BACKUP_MINOR, 10 },
|
||||||
|
{ "sum_sec", OPT_INT, &NR_SUM_SEC, 10 },
|
||||||
|
{ "layout", OPT_BOOL, &USE_SUM_LAYOUT, 1 },
|
||||||
|
{ "nolayout", OPT_BOOL, &USE_SUM_LAYOUT, 0 },
|
||||||
|
{ "sum", OPT_BOOL, &USE_CHECKSUM, 1 },
|
||||||
|
{ "nosum", OPT_BOOL, &USE_CHECKSUM, 0 },
|
||||||
|
{ "mirror", OPT_BOOL, &USE_MIRROR, 1 },
|
||||||
|
{ "nomirror", OPT_BOOL, &USE_MIRROR, 0 },
|
||||||
|
{ "xor", OPT_BOOL, &SUM_TYPE, ST_XOR },
|
||||||
|
{ "crc", OPT_BOOL, &SUM_TYPE, ST_CRC },
|
||||||
|
{ "md5", OPT_BOOL, &SUM_TYPE, ST_MD5 },
|
||||||
|
{ "sumerr", OPT_BOOL, &BAD_SUM_ERROR, 1 },
|
||||||
|
{ "nosumerr", OPT_BOOL, &BAD_SUM_ERROR, 0 },
|
||||||
|
{ "retries", OPT_INT, &NR_RETRIES, 10 },
|
||||||
|
{ "N", OPT_INT, &NR_RETRIES, 10 },
|
||||||
|
{ "restarts", OPT_INT, &NR_RESTARTS, 10 },
|
||||||
|
{ "M", OPT_INT, &NR_RESTARTS, 10 },
|
||||||
|
{ "timeout", OPT_INT, &DRIVER_TIMEOUT, 10 },
|
||||||
|
{ "T", OPT_INT, &DRIVER_TIMEOUT, 10 },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Request message. */
|
||||||
|
static message m_in;
|
||||||
|
static endpoint_t who_e; /* m_source */
|
||||||
|
static endpoint_t proc_e; /* IO_ENDPT */
|
||||||
|
static cp_grant_id_t grant_id; /* IO_GRANT */
|
||||||
|
|
||||||
|
/* Data buffers. */
|
||||||
|
static char *buf_array, *buffer; /* contiguous buffer */
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* carry *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int carry(size_t size, int flag_rw)
|
||||||
|
{
|
||||||
|
/* Carry data between caller proc and filter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (flag_rw == FLT_WRITE)
|
||||||
|
return sys_safecopyfrom(proc_e, grant_id, 0,
|
||||||
|
(vir_bytes) buffer, size, D);
|
||||||
|
else
|
||||||
|
return sys_safecopyto(proc_e, grant_id, 0,
|
||||||
|
(vir_bytes) buffer, size, D);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* vcarry *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int vcarry(int grants, iovec_t *iov, int flag_rw, size_t size)
|
||||||
|
{
|
||||||
|
/* Carry data between caller proc and filter, through grant-vector.
|
||||||
|
*/
|
||||||
|
char *bufp;
|
||||||
|
int i, r;
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bufp = buffer;
|
||||||
|
for(i = 0; i < grants && size > 0; i++) {
|
||||||
|
bytes = MIN(size, iov[i].iov_size);
|
||||||
|
|
||||||
|
if (flag_rw == FLT_WRITE)
|
||||||
|
r = sys_safecopyfrom(proc_e,
|
||||||
|
(vir_bytes) iov[i].iov_addr, 0,
|
||||||
|
(vir_bytes) bufp, bytes, D);
|
||||||
|
else
|
||||||
|
r = sys_safecopyto(proc_e,
|
||||||
|
(vir_bytes) iov[i].iov_addr, 0,
|
||||||
|
(vir_bytes) bufp, bytes, D);
|
||||||
|
|
||||||
|
if(r != OK)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
bufp += bytes;
|
||||||
|
size -= bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* do_rdwt *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int do_rdwt(int flag_rw)
|
||||||
|
{
|
||||||
|
size_t size, size_ret;
|
||||||
|
u64_t pos;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
pos = make64(m_in.POSITION, m_in.HIGHPOS);
|
||||||
|
size = m_in.COUNT;
|
||||||
|
|
||||||
|
if (rem64u(pos, SECTOR_SIZE) != 0 || size % SECTOR_SIZE != 0) {
|
||||||
|
printf("Filter: unaligned request from caller!\n");
|
||||||
|
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = flt_malloc(size, buf_array, BUF_SIZE);
|
||||||
|
|
||||||
|
if(flag_rw == FLT_WRITE)
|
||||||
|
carry(size, flag_rw);
|
||||||
|
|
||||||
|
reset_kills();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
size_ret = size;
|
||||||
|
r = transfer(pos, buffer, &size_ret, flag_rw);
|
||||||
|
if(r != RET_REDO)
|
||||||
|
break;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: transfer yielded RET_REDO, checking drivers\n");
|
||||||
|
#endif
|
||||||
|
if((r = check_driver(DRIVER_MAIN)) != OK) break;
|
||||||
|
if((r = check_driver(DRIVER_BACKUP)) != OK) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(r == OK && flag_rw == FLT_READ)
|
||||||
|
carry(size_ret, flag_rw);
|
||||||
|
|
||||||
|
flt_free(buffer, size, buf_array);
|
||||||
|
return r != OK ? r : size_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* do_vrdwt *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int do_vrdwt(int flag_rw)
|
||||||
|
{
|
||||||
|
size_t size, size_ret, bytes;
|
||||||
|
int grants;
|
||||||
|
int r, i;
|
||||||
|
u64_t pos;
|
||||||
|
iovec_t iov_proc[NR_IOREQS];
|
||||||
|
|
||||||
|
/* Extract informations. */
|
||||||
|
grants = m_in.COUNT;
|
||||||
|
if((r = sys_safecopyfrom(who_e, grant_id, 0, (vir_bytes) iov_proc,
|
||||||
|
grants * sizeof(iovec_t), D)) != OK) {
|
||||||
|
panic(__FILE__, "copying in grant vector failed", r);
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = make64(m_in.POSITION, m_in.HIGHPOS);
|
||||||
|
for(size = 0, i = 0; i < grants; i++)
|
||||||
|
size += iov_proc[i].iov_size;
|
||||||
|
|
||||||
|
if (rem64u(pos, SECTOR_SIZE) != 0 || size % SECTOR_SIZE != 0) {
|
||||||
|
printf("Filter: unaligned request from caller!\n");
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = flt_malloc(size, buf_array, BUF_SIZE);
|
||||||
|
|
||||||
|
if(flag_rw == FLT_WRITE)
|
||||||
|
vcarry(grants, iov_proc, flag_rw, size);
|
||||||
|
|
||||||
|
reset_kills();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
size_ret = size;
|
||||||
|
r = transfer(pos, buffer, &size_ret, flag_rw);
|
||||||
|
if(r != RET_REDO)
|
||||||
|
break;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: transfer yielded RET_REDO, checking drivers\n");
|
||||||
|
#endif
|
||||||
|
if((r = check_driver(DRIVER_MAIN)) != OK) break;
|
||||||
|
if((r = check_driver(DRIVER_BACKUP)) != OK) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(r != OK) {
|
||||||
|
flt_free(buffer, size, buf_array);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(flag_rw == FLT_READ)
|
||||||
|
vcarry(grants, iov_proc, flag_rw, size_ret);
|
||||||
|
|
||||||
|
/* Set the result-iovec. */
|
||||||
|
for(i = 0; i < grants && size_ret > 0; i++) {
|
||||||
|
bytes = MIN(size_ret, iov_proc[i].iov_size);
|
||||||
|
|
||||||
|
iov_proc[i].iov_size -= bytes;
|
||||||
|
size_ret -= bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy the caller's grant-table back. */
|
||||||
|
if((r = sys_safecopyto(who_e, grant_id, 0, (vir_bytes) iov_proc,
|
||||||
|
grants * sizeof(iovec_t), D)) != OK) {
|
||||||
|
panic(__FILE__, "copying out grant vector failed", r);
|
||||||
|
}
|
||||||
|
|
||||||
|
flt_free(buffer, size, buf_array);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* do_ioctl *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int do_ioctl(message *m)
|
||||||
|
{
|
||||||
|
struct partition sizepart;
|
||||||
|
|
||||||
|
switch(m->REQUEST) {
|
||||||
|
case DIOCSETP:
|
||||||
|
case DIOCTIMEOUT:
|
||||||
|
case DIOCOPENCT:
|
||||||
|
/* These do not make sense for us. */
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
case DIOCGETP:
|
||||||
|
memset(&sizepart, 0, sizeof(sizepart));
|
||||||
|
|
||||||
|
/* The presented disk size is the raw partition size,
|
||||||
|
* corrected for space needed for checksums.
|
||||||
|
*/
|
||||||
|
sizepart.size = convert(get_raw_size());
|
||||||
|
|
||||||
|
if(sys_safecopyto(proc_e, (vir_bytes) grant_id, 0,
|
||||||
|
(vir_bytes) &sizepart,
|
||||||
|
sizeof(struct partition), D) != OK) {
|
||||||
|
printf("Filter: DIOCGETP safecopyto failed\n");
|
||||||
|
return EIO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Filter: unknown ioctl request: %d!\n", m->REQUEST);
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* parse_arguments *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int parse_arguments(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
if(argc != 2)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
optset_parse(optset_table, argv[1]);
|
||||||
|
|
||||||
|
if (MAIN_LABEL[0] == 0 || MAIN_MINOR < 0 || MAIN_MINOR > 255)
|
||||||
|
return EINVAL;
|
||||||
|
if (USE_MIRROR && (BACKUP_LABEL[0] == 0 ||
|
||||||
|
BACKUP_MINOR < 0 || BACKUP_MINOR > 255))
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
/* Checksumming implies a checksum layout. */
|
||||||
|
if (USE_CHECKSUM)
|
||||||
|
USE_SUM_LAYOUT = 1;
|
||||||
|
|
||||||
|
/* Determine the checksum size for the chosen checksum type. */
|
||||||
|
switch (SUM_TYPE) {
|
||||||
|
case ST_XOR:
|
||||||
|
SUM_SIZE = 16; /* compatibility */
|
||||||
|
break;
|
||||||
|
case ST_CRC:
|
||||||
|
SUM_SIZE = 4;
|
||||||
|
break;
|
||||||
|
case ST_MD5:
|
||||||
|
SUM_SIZE = 16;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NR_SUM_SEC <= 0 || SUM_SIZE * NR_SUM_SEC > SECTOR_SIZE)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter starting. Configuration:\n");
|
||||||
|
printf(" USE_CHECKSUM : %3s ", USE_CHECKSUM ? "yes" : "no");
|
||||||
|
printf(" USE_MIRROR : %3s\n", USE_MIRROR ? "yes" : "no");
|
||||||
|
|
||||||
|
if (USE_CHECKSUM) {
|
||||||
|
printf(" BAD_SUM_ERROR : %3s ",
|
||||||
|
BAD_SUM_ERROR ? "yes" : "no");
|
||||||
|
printf(" NR_SUM_SEC : %3d\n", NR_SUM_SEC);
|
||||||
|
|
||||||
|
printf(" SUM_TYPE : ");
|
||||||
|
|
||||||
|
switch (SUM_TYPE) {
|
||||||
|
case ST_XOR: printf("xor"); break;
|
||||||
|
case ST_CRC: printf("crc"); break;
|
||||||
|
case ST_MD5: printf("md5"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" SUM_SIZE : %3d\n", SUM_SIZE);
|
||||||
|
}
|
||||||
|
else printf(" USE_SUM_LAYOUT : %3s\n", USE_SUM_LAYOUT ? "yes" : "no");
|
||||||
|
|
||||||
|
printf(" N : %3dx M : %3dx T : %3ds\n",
|
||||||
|
NR_RETRIES, NR_RESTARTS, DRIVER_TIMEOUT);
|
||||||
|
|
||||||
|
printf(" MAIN_LABEL / MAIN_MINOR : %19s / %d\n",
|
||||||
|
MAIN_LABEL, MAIN_MINOR);
|
||||||
|
if (USE_MIRROR) {
|
||||||
|
printf(" BACKUP_LABEL / BACKUP_MINOR : %15s / %d\n",
|
||||||
|
BACKUP_LABEL, BACKUP_MINOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Convert timeout seconds to ticks. */
|
||||||
|
DRIVER_TIMEOUT *= sys_hz();
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* got_signal *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void got_signal(void)
|
||||||
|
{
|
||||||
|
sigset_t set;
|
||||||
|
|
||||||
|
/* See if PM sent us a SIGTERM. */
|
||||||
|
if (getsigset(&set) != 0) return;
|
||||||
|
|
||||||
|
if (!sigismember(&set, SIGTERM)) return;
|
||||||
|
|
||||||
|
/* If so, shut down this driver. */
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: shutdown...\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
driver_shutdown();
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* main *
|
||||||
|
*===========================================================================*/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
message m_out;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = parse_arguments(argc, argv);
|
||||||
|
if(r != OK) {
|
||||||
|
printf("Filter: wrong argument!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf_array = alloc_contig(BUF_SIZE, 0, NULL)) == NULL)
|
||||||
|
panic(__FILE__, "no memory available", NO_NUM);
|
||||||
|
|
||||||
|
sum_init();
|
||||||
|
|
||||||
|
driver_init();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* Wait for request. */
|
||||||
|
if(receive(ANY, &m_in) != OK) {
|
||||||
|
panic(__FILE__, "receive failed", NO_NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: got request %d from %d\n",
|
||||||
|
m_in.m_type, m_in.m_source);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (is_notify(m_in.m_type) && m_in.m_source == PM_PROC_NR)
|
||||||
|
got_signal();
|
||||||
|
|
||||||
|
who_e = m_in.m_source;
|
||||||
|
proc_e = m_in.IO_ENDPT;
|
||||||
|
grant_id = (cp_grant_id_t) m_in.IO_GRANT;
|
||||||
|
|
||||||
|
/* Forword the request message to the drivers. */
|
||||||
|
switch(m_in.m_type) {
|
||||||
|
case DEV_OPEN: /* open/close is a noop for filter. */
|
||||||
|
case DEV_CLOSE: r = OK; break;
|
||||||
|
case DEV_READ_S: r = do_rdwt(FLT_READ); break;
|
||||||
|
case DEV_WRITE_S: r = do_rdwt(FLT_WRITE); break;
|
||||||
|
case DEV_GATHER_S: r = do_vrdwt(FLT_READ); break;
|
||||||
|
case DEV_SCATTER_S: r = do_vrdwt(FLT_WRITE); break;
|
||||||
|
case DEV_IOCTL_S: r = do_ioctl(&m_in); break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Filter: ignoring unknown request %d from %d\n",
|
||||||
|
m_in.m_type, m_in.m_source);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: replying with code %d\n", r);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Send back reply message. */
|
||||||
|
m_out.m_type = TASK_REPLY;
|
||||||
|
m_out.REP_ENDPT = proc_e;
|
||||||
|
m_out.REP_STATUS = r;
|
||||||
|
send(who_e, &m_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
315
drivers/filter/md5.c
Normal file
315
drivers/filter/md5.c
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* This code implements the MD5 message-digest algorithm.
|
||||||
|
* The algorithm is due to Ron Rivest. This code was
|
||||||
|
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||||
|
* This code is in the public domain; do with it what you wish.
|
||||||
|
*
|
||||||
|
* Equivalent code is available from RSA Data Security, Inc.
|
||||||
|
* This code has been tested against that, and is equivalent,
|
||||||
|
* except that you don't need to include two pages of legalese
|
||||||
|
* with every copy.
|
||||||
|
*
|
||||||
|
* To compute the message digest of a chunk of bytes, declare an
|
||||||
|
* MD5Context structure, pass it to MD5Init, call MD5Update as
|
||||||
|
* needed on buffers full of bytes, and then call MD5Final, which
|
||||||
|
* will fill a supplied 16-byte array with the digest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This code was modified in 1997 by Jim Kingdon of Cyclic Software to
|
||||||
|
not require an integer type which is exactly 32 bits. This work
|
||||||
|
draws on the changes for the same purpose by Tatu Ylonen
|
||||||
|
<ylo@cs.hut.fi> as part of SSH, but since I didn't actually use
|
||||||
|
that code, there is no copyright issue. I hereby disclaim
|
||||||
|
copyright in any changes I have made; this code remains in the
|
||||||
|
public domain. */
|
||||||
|
|
||||||
|
#ifdef TEST
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h> /* for memcpy() and memset() */
|
||||||
|
|
||||||
|
#include "md5.h"
|
||||||
|
|
||||||
|
/* Little-endian byte-swapping routines. Note that these do not
|
||||||
|
depend on the size of datatypes such as uint32, nor do they require
|
||||||
|
us to detect the endianness of the machine we are running on. It
|
||||||
|
is possible they should be macros for speed, but I would be
|
||||||
|
surprised if they were a performance bottleneck for MD5. */
|
||||||
|
|
||||||
|
static uint32
|
||||||
|
getu32 (const unsigned char *addr)
|
||||||
|
{
|
||||||
|
return (((((unsigned long)addr[3] << 8) | addr[2]) << 8)
|
||||||
|
| addr[1]) << 8 | addr[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
putu32 (uint32 data, unsigned char *addr)
|
||||||
|
{
|
||||||
|
addr[0] = (unsigned char)data;
|
||||||
|
addr[1] = (unsigned char)(data >> 8);
|
||||||
|
addr[2] = (unsigned char)(data >> 16);
|
||||||
|
addr[3] = (unsigned char)(data >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
||||||
|
* initialization constants.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
MD5Init (ctx)
|
||||||
|
struct MD5Context *ctx;
|
||||||
|
{
|
||||||
|
ctx->buf[0] = 0x67452301;
|
||||||
|
ctx->buf[1] = 0xefcdab89;
|
||||||
|
ctx->buf[2] = 0x98badcfe;
|
||||||
|
ctx->buf[3] = 0x10325476;
|
||||||
|
|
||||||
|
ctx->bits[0] = 0;
|
||||||
|
ctx->bits[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update context to reflect the concatenation of another buffer full
|
||||||
|
* of bytes.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
MD5Update (ctx, buf, len)
|
||||||
|
struct MD5Context *ctx;
|
||||||
|
unsigned char const *buf;
|
||||||
|
unsigned len;
|
||||||
|
{
|
||||||
|
uint32 t;
|
||||||
|
|
||||||
|
/* Update bitcount */
|
||||||
|
|
||||||
|
t = ctx->bits[0];
|
||||||
|
if ((ctx->bits[0] = (t + ((uint32)len << 3)) & 0xffffffff) < t)
|
||||||
|
ctx->bits[1]++; /* Carry from low to high */
|
||||||
|
ctx->bits[1] += len >> 29;
|
||||||
|
|
||||||
|
t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
|
||||||
|
|
||||||
|
/* Handle any leading odd-sized chunks */
|
||||||
|
|
||||||
|
if ( t ) {
|
||||||
|
unsigned char *p = ctx->in + t;
|
||||||
|
|
||||||
|
t = 64-t;
|
||||||
|
if (len < t) {
|
||||||
|
memcpy(p, buf, len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(p, buf, t);
|
||||||
|
MD5Transform (ctx->buf, ctx->in);
|
||||||
|
buf += t;
|
||||||
|
len -= t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process data in 64-byte chunks */
|
||||||
|
|
||||||
|
while (len >= 64) {
|
||||||
|
memcpy(ctx->in, buf, 64);
|
||||||
|
MD5Transform (ctx->buf, ctx->in);
|
||||||
|
buf += 64;
|
||||||
|
len -= 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle any remaining bytes of data. */
|
||||||
|
|
||||||
|
memcpy(ctx->in, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Final wrapup - pad to 64-byte boundary with the bit pattern
|
||||||
|
* 1 0* (64-bit count of bits processed, MSB-first)
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
MD5Final (digest, ctx)
|
||||||
|
unsigned char digest[16];
|
||||||
|
struct MD5Context *ctx;
|
||||||
|
{
|
||||||
|
unsigned count;
|
||||||
|
unsigned char *p;
|
||||||
|
|
||||||
|
/* Compute number of bytes mod 64 */
|
||||||
|
count = (ctx->bits[0] >> 3) & 0x3F;
|
||||||
|
|
||||||
|
/* Set the first char of padding to 0x80. This is safe since there is
|
||||||
|
always at least one byte free */
|
||||||
|
p = ctx->in + count;
|
||||||
|
*p++ = 0x80;
|
||||||
|
|
||||||
|
/* Bytes of padding needed to make 64 bytes */
|
||||||
|
count = 64 - 1 - count;
|
||||||
|
|
||||||
|
/* Pad out to 56 mod 64 */
|
||||||
|
if (count < 8) {
|
||||||
|
/* Two lots of padding: Pad the first block to 64 bytes */
|
||||||
|
memset(p, 0, count);
|
||||||
|
MD5Transform (ctx->buf, ctx->in);
|
||||||
|
|
||||||
|
/* Now fill the next block with 56 bytes */
|
||||||
|
memset(ctx->in, 0, 56);
|
||||||
|
} else {
|
||||||
|
/* Pad block to 56 bytes */
|
||||||
|
memset(p, 0, count-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append length in bits and transform */
|
||||||
|
putu32(ctx->bits[0], ctx->in + 56);
|
||||||
|
putu32(ctx->bits[1], ctx->in + 60);
|
||||||
|
|
||||||
|
MD5Transform (ctx->buf, ctx->in);
|
||||||
|
putu32(ctx->buf[0], digest);
|
||||||
|
putu32(ctx->buf[1], digest + 4);
|
||||||
|
putu32(ctx->buf[2], digest + 8);
|
||||||
|
putu32(ctx->buf[3], digest + 12);
|
||||||
|
memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef ASM_MD5
|
||||||
|
|
||||||
|
/* The four core functions - F1 is optimized somewhat */
|
||||||
|
|
||||||
|
/* #define F1(x, y, z) (x & y | ~x & z) */
|
||||||
|
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||||
|
#define F2(x, y, z) F1(z, x, y)
|
||||||
|
#define F3(x, y, z) (x ^ y ^ z)
|
||||||
|
#define F4(x, y, z) (y ^ (x | ~z))
|
||||||
|
|
||||||
|
/* This is the central step in the MD5 algorithm. */
|
||||||
|
#define MD5STEP(f, w, x, y, z, data, s) \
|
||||||
|
( w += f(x, y, z) + data, w &= 0xffffffff, w = w<<s | w>>(32-s), w += x )
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
||||||
|
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
||||||
|
* the data and converts bytes into longwords for this routine.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
MD5Transform (buf, inraw)
|
||||||
|
uint32 buf[4];
|
||||||
|
const unsigned char inraw[64];
|
||||||
|
{
|
||||||
|
register uint32 a, b, c, d;
|
||||||
|
uint32 in[16];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 16; ++i)
|
||||||
|
in[i] = getu32 (inraw + 4 * i);
|
||||||
|
|
||||||
|
a = buf[0];
|
||||||
|
b = buf[1];
|
||||||
|
c = buf[2];
|
||||||
|
d = buf[3];
|
||||||
|
|
||||||
|
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
|
||||||
|
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
|
||||||
|
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
|
||||||
|
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
|
||||||
|
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
|
||||||
|
|
||||||
|
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
|
||||||
|
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
|
||||||
|
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
|
||||||
|
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
|
||||||
|
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
|
||||||
|
|
||||||
|
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
|
||||||
|
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
|
||||||
|
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
|
||||||
|
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
|
||||||
|
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
|
||||||
|
|
||||||
|
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
|
||||||
|
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
|
||||||
|
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
|
||||||
|
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
|
||||||
|
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
|
||||||
|
|
||||||
|
buf[0] += a;
|
||||||
|
buf[1] += b;
|
||||||
|
buf[2] += c;
|
||||||
|
buf[3] += d;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TEST
|
||||||
|
/* Simple test program. Can use it to manually run the tests from
|
||||||
|
RFC1321 for example. */
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct MD5Context context;
|
||||||
|
unsigned char checksum[16];
|
||||||
|
int i;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
fprintf (stderr, "usage: %s string-to-hash\n", argv[0]);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
for (j = 1; j < argc; ++j)
|
||||||
|
{
|
||||||
|
printf ("MD5 (\"%s\") = ", argv[j]);
|
||||||
|
MD5Init (&context);
|
||||||
|
MD5Update (&context, (unsigned char *)argv[j], strlen (argv[j]));
|
||||||
|
MD5Final (checksum, &context);
|
||||||
|
for (i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
printf ("%02x", (unsigned int) checksum[i]);
|
||||||
|
}
|
||||||
|
printf ("\n");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* TEST */
|
26
drivers/filter/md5.h
Normal file
26
drivers/filter/md5.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/* See md5.c for explanation and copyright information. */
|
||||||
|
|
||||||
|
#ifndef MD5_H
|
||||||
|
#define MD5_H
|
||||||
|
|
||||||
|
/* Unlike previous versions of this code, uint32 need not be exactly
|
||||||
|
32 bits, merely 32 bits or more. Choosing a data type which is 32
|
||||||
|
bits instead of 64 is not important; speed is considerably more
|
||||||
|
important. ANSI guarantees that "unsigned long" will be big enough,
|
||||||
|
and always using it seems to have few disadvantages. */
|
||||||
|
typedef unsigned long uint32;
|
||||||
|
|
||||||
|
struct MD5Context {
|
||||||
|
uint32 buf[4];
|
||||||
|
uint32 bits[2];
|
||||||
|
unsigned char in[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
void MD5Init(struct MD5Context *context);
|
||||||
|
void MD5Update(struct MD5Context *context,
|
||||||
|
unsigned char const *buf, unsigned len);
|
||||||
|
void MD5Final(unsigned char digest[16],
|
||||||
|
struct MD5Context *context);
|
||||||
|
void MD5Transform(uint32 buf[4], const unsigned char in[64]);
|
||||||
|
|
||||||
|
#endif /* !MD5_H */
|
128
drivers/filter/optset.c
Normal file
128
drivers/filter/optset.c
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/* This file provides functionality to parse strings of comma-separated
|
||||||
|
* options, each being either a single key name or a key=value pair, where the
|
||||||
|
* value may be enclosed in quotes. A table of optset entries is provided to
|
||||||
|
* determine which options are recognized, how to parse their values, and where
|
||||||
|
* to store those. Unrecognized options are silently ignored; improperly
|
||||||
|
* formatted options are silently set to reasonably acceptable values.
|
||||||
|
*
|
||||||
|
* The entry points into this file are:
|
||||||
|
* optset_parse parse the given options string using the given table
|
||||||
|
*
|
||||||
|
* Created:
|
||||||
|
* May 2009 (D.C. van Moolenbroek)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _MINIX 1
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <minix/config.h>
|
||||||
|
#include <minix/const.h>
|
||||||
|
|
||||||
|
#include "optset.h"
|
||||||
|
|
||||||
|
FORWARD _PROTOTYPE( void optset_parse_entry, (struct optset *entry,
|
||||||
|
char *ptr, int len) );
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* optset_parse_entry *
|
||||||
|
*===========================================================================*/
|
||||||
|
PRIVATE void optset_parse_entry(entry, ptr, len)
|
||||||
|
struct optset *entry;
|
||||||
|
char *ptr;
|
||||||
|
int len;
|
||||||
|
{
|
||||||
|
/* Parse and store the value of a single option.
|
||||||
|
*/
|
||||||
|
char *dst;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
switch (entry->os_type) {
|
||||||
|
case OPT_BOOL:
|
||||||
|
*((int *) entry->os_ptr) = entry->os_val;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPT_STRING:
|
||||||
|
if (len >= entry->os_val)
|
||||||
|
len = entry->os_val - 1;
|
||||||
|
|
||||||
|
dst = (char *) entry->os_ptr;
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
memcpy(dst, ptr, len);
|
||||||
|
dst[len] = 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPT_INT:
|
||||||
|
if (len > 0)
|
||||||
|
val = strtol(ptr, NULL, entry->os_val);
|
||||||
|
else
|
||||||
|
val = 0;
|
||||||
|
|
||||||
|
*((int *) entry->os_ptr) = val;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* optset_parse *
|
||||||
|
*===========================================================================*/
|
||||||
|
PUBLIC void optset_parse(table, string)
|
||||||
|
struct optset *table;
|
||||||
|
char *string;
|
||||||
|
{
|
||||||
|
/* Parse a string of options, using the provided table of optset entries.
|
||||||
|
*/
|
||||||
|
char *p, *kptr, *vptr;
|
||||||
|
int i, klen, vlen;
|
||||||
|
|
||||||
|
for (p = string; *p; ) {
|
||||||
|
/* Get the key name for the field. */
|
||||||
|
for (kptr = p, klen = 0; *p && *p != '=' && *p != ','; p++, klen++);
|
||||||
|
|
||||||
|
if (*p == '=') {
|
||||||
|
/* The field has an associated value. */
|
||||||
|
vptr = ++p;
|
||||||
|
|
||||||
|
/* If the first character after the '=' is a quote character,
|
||||||
|
* find a matching quote character followed by either a comma
|
||||||
|
* or the terminating null character, and use the string in
|
||||||
|
* between. Otherwise, use the string up to the next comma or
|
||||||
|
* the terminating null character.
|
||||||
|
*/
|
||||||
|
if (*p == '\'' || *p == '"') {
|
||||||
|
p++;
|
||||||
|
|
||||||
|
for (vlen = 0; *p && (*p != *vptr ||
|
||||||
|
(p[1] && p[1] != ',')); p++, vlen++);
|
||||||
|
|
||||||
|
if (*p) p++;
|
||||||
|
vptr++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for (vlen = 0; *p && *p != ','; p++, vlen++);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vptr = NULL;
|
||||||
|
vlen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == ',') p++;
|
||||||
|
|
||||||
|
/* Find a matching entry for this key in the given table. If found,
|
||||||
|
* call optset_parse_entry() on it. Silently ignore the option
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
for (i = 0; table[i].os_name != NULL; i++) {
|
||||||
|
if (strlen(table[i].os_name) == klen &&
|
||||||
|
!strncasecmp(table[i].os_name, kptr, klen)) {
|
||||||
|
|
||||||
|
optset_parse_entry(&table[i], vptr, vlen);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
drivers/filter/optset.h
Normal file
30
drivers/filter/optset.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef _OPTSET_H
|
||||||
|
#define _OPTSET_H
|
||||||
|
|
||||||
|
enum {
|
||||||
|
OPT_BOOL,
|
||||||
|
OPT_STRING,
|
||||||
|
OPT_INT
|
||||||
|
};
|
||||||
|
|
||||||
|
/* An entry for the parser of an options set. The 'os_name' field must point
|
||||||
|
* to a string, which is treated case-insensitively; the last entry of a table
|
||||||
|
* must have NULL name. The 'os_type' field must be set to one of the OPT_
|
||||||
|
* values defined above. The 'os_ptr' field must point to the field that is to
|
||||||
|
* receive the value of a recognized option. For OPT_STRING, it must point to a
|
||||||
|
* string of a size set in 'os_val'; the resulting string may be truncated, but
|
||||||
|
* will always be null-terminated. For OPT_BOOL, it must point to an int which
|
||||||
|
* will be set to the value in 'os_val' if the option is present. For OPT_INT,
|
||||||
|
* it must point to an int which will be set to the provided option value;
|
||||||
|
* 'os_val' is then a base passed to strtol().
|
||||||
|
*/
|
||||||
|
struct optset {
|
||||||
|
char *os_name;
|
||||||
|
int os_type;
|
||||||
|
void *os_ptr;
|
||||||
|
int os_val;
|
||||||
|
};
|
||||||
|
|
||||||
|
_PROTOTYPE( void optset_parse, (struct optset *table, char *string) );
|
||||||
|
|
||||||
|
#endif /* _OPTSET_H */
|
613
drivers/filter/sum.c
Normal file
613
drivers/filter/sum.c
Normal file
|
@ -0,0 +1,613 @@
|
||||||
|
/* Filter driver - middle layer - checksumming */
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
#include "crc.h"
|
||||||
|
#include "md5.h"
|
||||||
|
|
||||||
|
#define GROUP_SIZE (SECTOR_SIZE * NR_SUM_SEC)
|
||||||
|
#define SEC2SUM_NR(nr) ((nr)/NR_SUM_SEC*(NR_SUM_SEC+1) + NR_SUM_SEC)
|
||||||
|
#define LOG2PHYS(nr) ((nr)/NR_SUM_SEC*(NR_SUM_SEC+1) + (nr)%NR_SUM_SEC)
|
||||||
|
|
||||||
|
#define POS2SEC(nr) div64u((nr), SECTOR_SIZE)
|
||||||
|
#define SEC2POS(nr) mul64u((nr), SECTOR_SIZE)
|
||||||
|
|
||||||
|
/* Data buffers. */
|
||||||
|
static char *ext_array, *ext_buffer; /* interspersed buffer */
|
||||||
|
static char *rb0_array; /* write readback buffer for disk 0 */
|
||||||
|
static char *rb1_array; /* write readback buffer for disk 1 */
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* sum_init *
|
||||||
|
*===========================================================================*/
|
||||||
|
void sum_init(void)
|
||||||
|
{
|
||||||
|
/* Initialize buffers. */
|
||||||
|
|
||||||
|
ext_array = alloc_contig(SBUF_SIZE, 0, NULL);
|
||||||
|
rb0_array = alloc_contig(SBUF_SIZE, 0, NULL);
|
||||||
|
rb1_array = alloc_contig(SBUF_SIZE, 0, NULL);
|
||||||
|
|
||||||
|
if (ext_array == NULL || rb0_array == NULL || rb1_array == NULL)
|
||||||
|
panic(__FILE__, "no memory available", NO_NUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* calc_sum *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void calc_sum(unsigned sector, char *data, char *sum)
|
||||||
|
{
|
||||||
|
/* Compute the checksum for a sector. The sector number must be part
|
||||||
|
* of the checksum in some way.
|
||||||
|
*/
|
||||||
|
unsigned long crc, *p, *q;
|
||||||
|
int i, j;
|
||||||
|
struct MD5Context ctx;
|
||||||
|
|
||||||
|
switch(SUM_TYPE) {
|
||||||
|
case ST_XOR:
|
||||||
|
/* Basic XOR checksum */
|
||||||
|
p = (unsigned long *) data;
|
||||||
|
|
||||||
|
memset(sum, 0, SUM_SIZE);
|
||||||
|
for(i = 0; i < SECTOR_SIZE / SUM_SIZE; i++) {
|
||||||
|
q = (unsigned long *) sum;
|
||||||
|
for(j = 0; j < SUM_SIZE / sizeof(*p); j++) {
|
||||||
|
*q ^= *p;
|
||||||
|
q++;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q = (unsigned long *) sum;
|
||||||
|
*q ^= sector;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ST_CRC:
|
||||||
|
/* CRC32 checksum */
|
||||||
|
|
||||||
|
crc = compute_crc((unsigned char *) data, SECTOR_SIZE);
|
||||||
|
|
||||||
|
q = (unsigned long *) sum;
|
||||||
|
|
||||||
|
*q = crc ^ sector;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ST_MD5:
|
||||||
|
/* MD5 checksum */
|
||||||
|
|
||||||
|
MD5Init(&ctx);
|
||||||
|
MD5Update(&ctx, (unsigned char *) data, SECTOR_SIZE);
|
||||||
|
MD5Update(&ctx, (unsigned char *) §or, sizeof(sector));
|
||||||
|
MD5Final((unsigned char *) sum, &ctx);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(__FILE__, "invalid checksum type", SUM_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* read_sectors *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int read_sectors(char *buf, sector_t phys_sector, int count)
|
||||||
|
{
|
||||||
|
/* Read 'count' sectors starting at 'phys_sector' into 'buf'. If an
|
||||||
|
* EOF occurs, zero-fill the remaining part of the buffer.
|
||||||
|
*/
|
||||||
|
size_t size, wsize;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
size = wsize = count * SECTOR_SIZE;
|
||||||
|
|
||||||
|
r = read_write(SEC2POS(phys_sector), buf, buf, &size, FLT_READ);
|
||||||
|
|
||||||
|
if (r != OK)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (size != wsize) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: EOF reading sector %lu\n", phys_sector);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset(buf + size, 0, wsize - size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* make_group_sum *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void make_group_sum(char *bufp, char *sump, sector_t sector, int index,
|
||||||
|
int count)
|
||||||
|
{
|
||||||
|
/* Compute checksums for 'count' sectors within a group, starting at
|
||||||
|
* sector 'index' into the group, which has logical sector number
|
||||||
|
* 'sector'. The 'bufp' pointer points to the same first sector to
|
||||||
|
* start checksumming; 'sump' is a pointer to the checksum sector.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sump += index * SUM_SIZE;
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
calc_sum(sector, bufp, sump);
|
||||||
|
|
||||||
|
bufp += SECTOR_SIZE;
|
||||||
|
|
||||||
|
sump += SUM_SIZE;
|
||||||
|
sector++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_group_sum *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int check_group_sum(char *bufp, char *sump, sector_t sector, int index,
|
||||||
|
int count)
|
||||||
|
{
|
||||||
|
/* Check checksums in a group. Parameters are the same as in
|
||||||
|
* make_group_sum(). Return OK if all checksums check out, or RET_REDO
|
||||||
|
* upon failure.
|
||||||
|
*/
|
||||||
|
char sum_buffer[SECTOR_SIZE];
|
||||||
|
|
||||||
|
sump += index * SUM_SIZE;
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
calc_sum(sector, bufp, sum_buffer);
|
||||||
|
|
||||||
|
if (memcmp(sum_buffer, sump, SUM_SIZE)) {
|
||||||
|
printf("Filter: BAD CHECKSUM at sector %lu\n", sector);
|
||||||
|
|
||||||
|
if (BAD_SUM_ERROR)
|
||||||
|
return bad_driver(DRIVER_MAIN, BD_DATA, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufp += SECTOR_SIZE;
|
||||||
|
sump += SUM_SIZE;
|
||||||
|
sector++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* make_sum *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int make_sum(sector_t current_sector, sector_t sectors_left)
|
||||||
|
{
|
||||||
|
/* Compute checksums over all data in the buffer with expanded data.
|
||||||
|
* As side effect, possibly read in first and last checksum sectors
|
||||||
|
* and data to fill the gap between the last data sector and the last
|
||||||
|
* checksum sector.
|
||||||
|
*/
|
||||||
|
sector_t sector_in_group, group_left;
|
||||||
|
size_t size, gap;
|
||||||
|
char *extp;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* See the description of the extended buffer in transfer(). A number
|
||||||
|
* of points are relevant for this function in particular:
|
||||||
|
*
|
||||||
|
* 1) If the "xx" head of the buffer does not cover an entire group,
|
||||||
|
* we need to copy in the first checksum sector so that we can
|
||||||
|
* modify it.
|
||||||
|
* 2) We can generate checksums for the full "yyyyy" groups without
|
||||||
|
* copying in the corresponding checksum sectors first, because
|
||||||
|
* those sectors will be overwritten entirely anyway.
|
||||||
|
* 3) We copy in not only the checksum sector for the group containing
|
||||||
|
* the "zzz" tail data, but also all the data between "zzz" and the
|
||||||
|
* last checksum sector. This allows us to write all the data in
|
||||||
|
* the buffer in one operation. In theory, we could verify the
|
||||||
|
* checksum of the data in this gap for extra early failure
|
||||||
|
* detection, but we currently do not do this.
|
||||||
|
*
|
||||||
|
* If points 1 and 3 cover the same group (implying a small, unaligned
|
||||||
|
* write operation), the read operation is done only once. Whether
|
||||||
|
* point 1 or 3 is skipped depends on whether there is a gap before
|
||||||
|
* the checksum sector.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sector_in_group = current_sector % NR_SUM_SEC;
|
||||||
|
group_left = NR_SUM_SEC - sector_in_group;
|
||||||
|
|
||||||
|
extp = ext_buffer;
|
||||||
|
|
||||||
|
/* This loop covers points 1 and 2. */
|
||||||
|
while (sectors_left >= group_left) {
|
||||||
|
size = group_left * SECTOR_SIZE;
|
||||||
|
|
||||||
|
if (sector_in_group > 0) {
|
||||||
|
if ((r = read_sectors(extp + size,
|
||||||
|
LOG2PHYS(current_sector) + group_left,
|
||||||
|
1)) != OK)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
else memset(extp + size, 0, SECTOR_SIZE);
|
||||||
|
|
||||||
|
make_group_sum(extp, extp + size, current_sector,
|
||||||
|
sector_in_group, group_left);
|
||||||
|
|
||||||
|
extp += size + SECTOR_SIZE;
|
||||||
|
|
||||||
|
sectors_left -= group_left;
|
||||||
|
current_sector += group_left;
|
||||||
|
|
||||||
|
sector_in_group = 0;
|
||||||
|
group_left = NR_SUM_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The remaining code covers point 3. */
|
||||||
|
if (sectors_left > 0) {
|
||||||
|
size = sectors_left * SECTOR_SIZE;
|
||||||
|
|
||||||
|
if (group_left != NR_SUM_SEC - sector_in_group)
|
||||||
|
panic(__FILE__, "group_left assertion", 0);
|
||||||
|
|
||||||
|
gap = group_left - sectors_left;
|
||||||
|
|
||||||
|
if (gap <= 0)
|
||||||
|
panic(__FILE__, "gap assertion", 0);
|
||||||
|
|
||||||
|
if ((r = read_sectors(extp + size,
|
||||||
|
LOG2PHYS(current_sector) + sectors_left,
|
||||||
|
gap + 1)) != OK)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
make_group_sum(extp, extp + size + gap * SECTOR_SIZE,
|
||||||
|
current_sector, sector_in_group, sectors_left);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_sum *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int check_sum(sector_t current_sector, size_t bytes_left)
|
||||||
|
{
|
||||||
|
/* Check checksums of all data in the buffer with expanded data.
|
||||||
|
* Return OK if all checksums are okay, or RET_REDO upon failure.
|
||||||
|
*/
|
||||||
|
sector_t sector_in_group;
|
||||||
|
size_t size, groupbytes_left;
|
||||||
|
int count;
|
||||||
|
char *extp;
|
||||||
|
|
||||||
|
extp = ext_buffer;
|
||||||
|
|
||||||
|
sector_in_group = current_sector % NR_SUM_SEC;
|
||||||
|
groupbytes_left = (NR_SUM_SEC - sector_in_group) * SECTOR_SIZE;
|
||||||
|
|
||||||
|
while (bytes_left > 0) {
|
||||||
|
size = MIN(bytes_left, groupbytes_left);
|
||||||
|
count = size / SECTOR_SIZE;
|
||||||
|
|
||||||
|
if (check_group_sum(extp, extp + groupbytes_left,
|
||||||
|
current_sector, sector_in_group, count))
|
||||||
|
return RET_REDO;
|
||||||
|
|
||||||
|
extp += size + SECTOR_SIZE;
|
||||||
|
|
||||||
|
bytes_left -= MIN(size + SECTOR_SIZE, bytes_left);
|
||||||
|
current_sector += count;
|
||||||
|
|
||||||
|
sector_in_group = 0;
|
||||||
|
groupbytes_left = GROUP_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* check_write *
|
||||||
|
*===========================================================================*/
|
||||||
|
static int check_write(u64_t pos, size_t size)
|
||||||
|
{
|
||||||
|
/* Read back the data just written, from both disks if mirroring is
|
||||||
|
* enabled, and check the result against the original. Return OK on
|
||||||
|
* success; report the malfunctioning driver and return RET_REDO
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
char *rb0_buffer, *rb1_buffer;
|
||||||
|
size_t orig_size;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
return OK;
|
||||||
|
|
||||||
|
rb0_buffer = rb1_buffer =
|
||||||
|
flt_malloc(size, rb0_array, SBUF_SIZE);
|
||||||
|
if (USE_MIRROR)
|
||||||
|
rb1_buffer = flt_malloc(size, rb1_array, SBUF_SIZE);
|
||||||
|
|
||||||
|
orig_size = size;
|
||||||
|
|
||||||
|
r = read_write(pos, rb0_buffer, rb1_buffer, &size, FLT_READ2);
|
||||||
|
|
||||||
|
if (r != OK) {
|
||||||
|
if (USE_MIRROR) flt_free(rb1_buffer, orig_size, rb1_array);
|
||||||
|
flt_free(rb0_buffer, orig_size, rb0_array);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we get a size smaller than what we requested, then we somehow
|
||||||
|
* succeeded in writing past the disk end, and now fail to read it all
|
||||||
|
* back. This is not an error, and we just compare the part that we
|
||||||
|
* did manage to read back in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (memcmp(ext_buffer, rb0_buffer, size)) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: readback from disk 0 failed (size %d)\n",
|
||||||
|
size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return bad_driver(DRIVER_MAIN, BD_DATA, EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USE_MIRROR && memcmp(ext_buffer, rb1_buffer, size)) {
|
||||||
|
#if DEBUG
|
||||||
|
printf("Filter: readback from disk 1 failed (size %d)\n",
|
||||||
|
size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return bad_driver(DRIVER_BACKUP, BD_DATA, EFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USE_MIRROR) flt_free(rb1_buffer, orig_size, rb1_array);
|
||||||
|
flt_free(rb0_buffer, orig_size, rb0_array);
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* expand *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void expand(sector_t first_sector, char *buffer, sector_t sectors_left)
|
||||||
|
{
|
||||||
|
/* Expand the contiguous data in 'buffer' to interspersed format in
|
||||||
|
* 'ext_buffer'. The checksum areas are not touched.
|
||||||
|
*/
|
||||||
|
char *srcp, *dstp;
|
||||||
|
sector_t group_left;
|
||||||
|
size_t size;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
srcp = buffer;
|
||||||
|
dstp = ext_buffer;
|
||||||
|
|
||||||
|
group_left = NR_SUM_SEC - first_sector % NR_SUM_SEC;
|
||||||
|
|
||||||
|
while (sectors_left > 0) {
|
||||||
|
count = MIN(sectors_left, group_left);
|
||||||
|
size = count * SECTOR_SIZE;
|
||||||
|
|
||||||
|
memcpy(dstp, srcp, size);
|
||||||
|
|
||||||
|
srcp += size;
|
||||||
|
dstp += size + SECTOR_SIZE;
|
||||||
|
|
||||||
|
sectors_left -= count;
|
||||||
|
group_left = NR_SUM_SEC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* collapse *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void collapse(sector_t first_sector, char *buffer, size_t *sizep)
|
||||||
|
{
|
||||||
|
/* Collapse the interspersed data in 'ext_buffer' to contiguous format
|
||||||
|
* in 'buffer'. As side effect, adjust the given size to reflect the
|
||||||
|
* resulting contiguous data size.
|
||||||
|
*/
|
||||||
|
char *srcp, *dstp;
|
||||||
|
size_t size, bytes_left, groupbytes_left;
|
||||||
|
|
||||||
|
srcp = ext_buffer;
|
||||||
|
dstp = buffer;
|
||||||
|
|
||||||
|
bytes_left = *sizep;
|
||||||
|
groupbytes_left =
|
||||||
|
(NR_SUM_SEC - first_sector % NR_SUM_SEC) * SECTOR_SIZE;
|
||||||
|
|
||||||
|
while (bytes_left > 0) {
|
||||||
|
size = MIN(bytes_left, groupbytes_left);
|
||||||
|
|
||||||
|
memcpy(dstp, srcp, size);
|
||||||
|
|
||||||
|
srcp += size + SECTOR_SIZE;
|
||||||
|
dstp += size;
|
||||||
|
|
||||||
|
bytes_left -= MIN(size + SECTOR_SIZE, bytes_left);
|
||||||
|
groupbytes_left = GROUP_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*sizep = dstp - buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* expand_sizes *
|
||||||
|
*===========================================================================*/
|
||||||
|
static size_t expand_sizes(sector_t first_sector, sector_t nr_sectors,
|
||||||
|
size_t *req_size)
|
||||||
|
{
|
||||||
|
/* Compute the size of the data area including interspersed checksum
|
||||||
|
* sectors (req_size) and the size of the data area including
|
||||||
|
* interspersed and trailing checksum sectors (the return value).
|
||||||
|
*/
|
||||||
|
sector_t last_sector, sum_sector, phys_sector;
|
||||||
|
|
||||||
|
last_sector = LOG2PHYS(first_sector + nr_sectors - 1);
|
||||||
|
|
||||||
|
sum_sector = SEC2SUM_NR(first_sector + nr_sectors - 1);
|
||||||
|
|
||||||
|
phys_sector = LOG2PHYS(first_sector);
|
||||||
|
|
||||||
|
*req_size = (last_sector - phys_sector + 1) * SECTOR_SIZE;
|
||||||
|
|
||||||
|
return (sum_sector - phys_sector + 1) * SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* collapse_size *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void collapse_size(sector_t first_sector, size_t *sizep)
|
||||||
|
{
|
||||||
|
/* Compute the size of the contiguous user data written to disk, given
|
||||||
|
* the result size of the write operation with interspersed checksums.
|
||||||
|
*/
|
||||||
|
sector_t sector_in_group;
|
||||||
|
size_t sectors_from_group_base, nr_sum_secs, nr_data_secs;
|
||||||
|
|
||||||
|
sector_in_group = first_sector % NR_SUM_SEC;
|
||||||
|
|
||||||
|
sectors_from_group_base = *sizep / SECTOR_SIZE + sector_in_group;
|
||||||
|
|
||||||
|
nr_sum_secs = sectors_from_group_base / (NR_SUM_SEC+1);
|
||||||
|
|
||||||
|
nr_data_secs = sectors_from_group_base - sector_in_group - nr_sum_secs;
|
||||||
|
|
||||||
|
*sizep = nr_data_secs * SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* transfer *
|
||||||
|
*===========================================================================*/
|
||||||
|
int transfer(u64_t pos, char *buffer, size_t *sizep, int flag_rw)
|
||||||
|
{
|
||||||
|
/* Transfer data in interspersed-checksum format. When writing, first
|
||||||
|
* compute checksums, and read back the written data afterwards. When
|
||||||
|
* reading, check the stored checksums afterwards.
|
||||||
|
*/
|
||||||
|
sector_t first_sector, nr_sectors;
|
||||||
|
size_t ext_size, req_size, res_size;
|
||||||
|
u64_t phys_pos;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* If we don't use checksums or even checksum layout, simply pass on
|
||||||
|
* the request to the drivers as is.
|
||||||
|
*/
|
||||||
|
if (!USE_SUM_LAYOUT)
|
||||||
|
return read_write(pos, buffer, buffer, sizep, flag_rw);
|
||||||
|
|
||||||
|
/* The extended buffer (for checksumming) essentially looks like this:
|
||||||
|
*
|
||||||
|
* ------------------------------
|
||||||
|
* |xx|C|yyyyy|C|yyyyy|C|zzz |C|
|
||||||
|
* ------------------------------
|
||||||
|
*
|
||||||
|
* In this example, "xxyyyyyyyyyyzzz" is our actual data. The data is
|
||||||
|
* split up into groups, so that each group is followed by a checksum
|
||||||
|
* sector C containing the checksums for all data sectors in that
|
||||||
|
* group. The head and tail of the actual data may cover parts of
|
||||||
|
* groups; the remaining data (nor their checksums) are not to be
|
||||||
|
* modified.
|
||||||
|
*
|
||||||
|
* The entire buffer is written or read in one operation: the
|
||||||
|
* read_write() call below. In order to write, we may first have to
|
||||||
|
* read some data; see the description in make_sum().
|
||||||
|
*
|
||||||
|
* Some points of interest here:
|
||||||
|
* - We need a buffer large enough to hold the all user and non-user
|
||||||
|
* data, from the first "xx" to the last checksum sector. This size
|
||||||
|
* is ext_size.
|
||||||
|
* - For writing, we need to expand the user-provided data from
|
||||||
|
* contiguous layout to interspersed format. The size of the user
|
||||||
|
* data after expansion is req_size.
|
||||||
|
* - For reading, we need to collapse the user-requested data from
|
||||||
|
* interspersed to contiguous format. For writing, we still need to
|
||||||
|
* compute the contiguous result size to return to the user.
|
||||||
|
* - In both cases, the result size may be different from the
|
||||||
|
* requested write size, because an EOF (as in, disk end) may occur
|
||||||
|
* and the resulting size is less than the requested size.
|
||||||
|
* - If we only follow the checksum layout, and do not do any
|
||||||
|
* checksumming, ext_size is reduced to req_size.
|
||||||
|
*/
|
||||||
|
|
||||||
|
first_sector = POS2SEC(pos);
|
||||||
|
nr_sectors = *sizep / SECTOR_SIZE;
|
||||||
|
phys_pos = SEC2POS(LOG2PHYS(first_sector));
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: transfer: pos 0x%lx:0x%lx -> phys_pos 0x%lx:0x%lx\n",
|
||||||
|
ex64hi(pos), ex64lo(pos), ex64hi(phys_pos), ex64lo(phys_pos));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Compute the size for the buffer and for the user data after
|
||||||
|
* expansion.
|
||||||
|
*/
|
||||||
|
ext_size = expand_sizes(first_sector, nr_sectors, &req_size);
|
||||||
|
|
||||||
|
if (!USE_CHECKSUM)
|
||||||
|
ext_size = req_size;
|
||||||
|
|
||||||
|
ext_buffer = flt_malloc(ext_size, ext_array, SBUF_SIZE);
|
||||||
|
|
||||||
|
if (flag_rw == FLT_WRITE) {
|
||||||
|
expand(first_sector, buffer, nr_sectors);
|
||||||
|
|
||||||
|
if (USE_CHECKSUM && make_sum(first_sector, nr_sectors))
|
||||||
|
return RET_REDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform the actual I/O. */
|
||||||
|
res_size = ext_size;
|
||||||
|
r = read_write(phys_pos, ext_buffer, ext_buffer, &res_size, flag_rw);
|
||||||
|
|
||||||
|
#if DEBUG2
|
||||||
|
printf("Filter: transfer: read_write(%x:%x, %u, %d) = %d, %u\n",
|
||||||
|
ex64hi(phys_pos), ex64lo(phys_pos), ext_size, flag_rw, r,
|
||||||
|
res_size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (r != OK) {
|
||||||
|
flt_free(ext_buffer, ext_size, ext_array);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Limit the resulting size to the user data part of the buffer.
|
||||||
|
* The resulting size may already be less, due to an EOF.
|
||||||
|
*/
|
||||||
|
*sizep = MIN(req_size, res_size);
|
||||||
|
|
||||||
|
if (flag_rw == FLT_WRITE) {
|
||||||
|
if (USE_CHECKSUM && check_write(phys_pos, res_size))
|
||||||
|
return RET_REDO;
|
||||||
|
|
||||||
|
collapse_size(first_sector, sizep);
|
||||||
|
}
|
||||||
|
else { /* FLT_READ */
|
||||||
|
if (USE_CHECKSUM && check_sum(first_sector, *sizep))
|
||||||
|
return RET_REDO;
|
||||||
|
|
||||||
|
collapse(first_sector, buffer, sizep);
|
||||||
|
}
|
||||||
|
|
||||||
|
flt_free(ext_buffer, ext_size, ext_array);
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* convert *
|
||||||
|
*===========================================================================*/
|
||||||
|
u64_t convert(u64_t size)
|
||||||
|
{
|
||||||
|
/* Given a raw disk size, subtract the amount of disk space used for
|
||||||
|
* checksums, resulting in the user-visible disk size.
|
||||||
|
*/
|
||||||
|
sector_t sectors;
|
||||||
|
|
||||||
|
if (!USE_SUM_LAYOUT)
|
||||||
|
return size;
|
||||||
|
|
||||||
|
sectors = POS2SEC(size);
|
||||||
|
|
||||||
|
return SEC2POS(sectors / (NR_SUM_SEC + 1) * NR_SUM_SEC);
|
||||||
|
}
|
109
drivers/filter/util.c
Normal file
109
drivers/filter/util.c
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/* Filter driver - utility functions */
|
||||||
|
|
||||||
|
#include "inc.h"
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
static clock_t next_alarm;
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_malloc *
|
||||||
|
*===========================================================================*/
|
||||||
|
char *flt_malloc(size_t size, char *sbuf, size_t ssize)
|
||||||
|
{
|
||||||
|
/* Allocate a buffer for 'size' bytes. If 'size' is equal to or less
|
||||||
|
* than 'ssize', return the static buffer 'sbuf', otherwise, use
|
||||||
|
* malloc() to allocate memory dynamically.
|
||||||
|
*/
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (size <= ssize)
|
||||||
|
return sbuf;
|
||||||
|
|
||||||
|
p = alloc_contig(size, 0, NULL);
|
||||||
|
if (p == NULL)
|
||||||
|
panic(__FILE__, "out of memory", size);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_free *
|
||||||
|
*===========================================================================*/
|
||||||
|
void flt_free(char *buf, size_t size, char *sbuf)
|
||||||
|
{
|
||||||
|
/* Free a buffer previously allocated with flt_malloc().
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(buf != sbuf)
|
||||||
|
munmap(buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* print64 *
|
||||||
|
*===========================================================================*/
|
||||||
|
char *print64(u64_t p)
|
||||||
|
{
|
||||||
|
#define NB 10
|
||||||
|
static int n = 0;
|
||||||
|
static char buf[NB][100];
|
||||||
|
u32_t lo = ex64lo(p), hi = ex64hi(p);
|
||||||
|
n = (n+1) % NB;
|
||||||
|
if(!hi) sprintf(buf[n], "%lx", lo);
|
||||||
|
else sprintf(buf[n], "%lx%08lx", hi, lo);
|
||||||
|
return buf[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_alarm *
|
||||||
|
*===========================================================================*/
|
||||||
|
clock_t flt_alarm(clock_t dt)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if(dt < 0)
|
||||||
|
return next_alarm;
|
||||||
|
|
||||||
|
r = sys_setalarm(dt, 0);
|
||||||
|
|
||||||
|
if(r != OK)
|
||||||
|
panic(__FILE__, "sys_setalarm failed", r);
|
||||||
|
|
||||||
|
if(dt == 0) {
|
||||||
|
if(!next_alarm)
|
||||||
|
panic(__FILE__, "clearing unset alarm", r);
|
||||||
|
next_alarm = 0;
|
||||||
|
} else {
|
||||||
|
if(next_alarm)
|
||||||
|
panic(__FILE__, "overwriting alarm", r);
|
||||||
|
if ((r = getuptime(&next_alarm)) != OK)
|
||||||
|
panic(__FILE__, "getuptime failed", r);
|
||||||
|
next_alarm += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next_alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* got_alarm *
|
||||||
|
*===========================================================================*/
|
||||||
|
static void got_alarm(int sig)
|
||||||
|
{
|
||||||
|
/* Do nothing. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===========================================================================*
|
||||||
|
* flt_sleep *
|
||||||
|
*===========================================================================*/
|
||||||
|
void flt_sleep(int secs)
|
||||||
|
{
|
||||||
|
/* Sleep for the given number of seconds. Don't use sleep(), as that
|
||||||
|
* will end up calling select() to VFS. This implementation could be
|
||||||
|
* improved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
signal(SIGALRM, got_alarm);
|
||||||
|
alarm(secs);
|
||||||
|
|
||||||
|
pause();
|
||||||
|
}
|
|
@ -464,3 +464,25 @@ driver osscore
|
||||||
;
|
;
|
||||||
uid 0;
|
uid 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
driver filter
|
||||||
|
{
|
||||||
|
system
|
||||||
|
SETALARM # 24
|
||||||
|
TIMES # 25
|
||||||
|
GETINFO # 26
|
||||||
|
SAFECOPYFROM # 31
|
||||||
|
SAFECOPYTO # 32
|
||||||
|
SETGRANT # 34
|
||||||
|
SYSCTL # 44
|
||||||
|
;
|
||||||
|
ipc
|
||||||
|
SYSTEM PM VFS RS DS VM
|
||||||
|
at_wini
|
||||||
|
bios_wini
|
||||||
|
;
|
||||||
|
control
|
||||||
|
at_wini
|
||||||
|
bios_wini
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
|
@ -38,6 +38,8 @@ enum dev_style { STYLE_DEV, STYLE_NDEV, STYLE_TTY, STYLE_CLONE };
|
||||||
|
|
||||||
#define RESCUE_MAJOR 9 /* major device for rescue */
|
#define RESCUE_MAJOR 9 /* major device for rescue */
|
||||||
|
|
||||||
|
#define FILTER_MAJOR 11 /* major device for filter driver */
|
||||||
|
|
||||||
#define LOG_MAJOR 15 /* major device for log driver */
|
#define LOG_MAJOR 15 /* major device for log driver */
|
||||||
# define IS_KLOG_DEV 0 /* minor device for /dev/klog */
|
# define IS_KLOG_DEV 0 /* minor device for /dev/klog */
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ PRIVATE struct dmap init_dmap[] = {
|
||||||
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /* 8 = /dev/c1 */
|
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /* 8 = /dev/c1 */
|
||||||
DT(0, 0, 0, 0, DMAP_MUTABLE, "") /* 9 = not used */
|
DT(0, 0, 0, 0, DMAP_MUTABLE, "") /* 9 = not used */
|
||||||
DT(0, no_dev, 0, 0, DMAP_MUTABLE, "") /*10 = /dev/c2 */
|
DT(0, no_dev, 0, 0, DMAP_MUTABLE, "") /*10 = /dev/c2 */
|
||||||
DT(0, 0, 0, 0, DMAP_MUTABLE, "") /*11 = not used */
|
DT(0, no_dev, 0, 0, DMAP_MUTABLE, "") /*11 = /dev/filter*/
|
||||||
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /*12 = /dev/c3 */
|
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /*12 = /dev/c3 */
|
||||||
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /*13 = /dev/audio */
|
DT(0, no_dev, 0, NONE, DMAP_MUTABLE, "") /*13 = /dev/audio */
|
||||||
DT(0, 0, 0, 0, DMAP_MUTABLE, "") /*14 = not used */
|
DT(0, 0, 0, 0, DMAP_MUTABLE, "") /*14 = not used */
|
||||||
|
|
Loading…
Reference in a new issue