#!/bin/bash # set _FORCE_LOCAL environment variable to run blockwise unit tests even on local # nfs. Some tests will fail because nfs is eager to write for example 4095 bytes # in O_DIRECT mode. BW_UNIT=./unit-utils-io STRACE=strace MNT_DIR=./mnt_bwunit LOCAL_FILE=./blockwise_localfile # $1 path to scsi debug bdev scsi_debug_teardown() { local _tries=15; while [ -b "$1" -a $_tries -gt 0 ]; do rmmod scsi_debug 2> /dev/null if [ -b "$1" ]; then sleep .1 _tries=$((_tries-1)) fi done test ! -b "$1" || rmmod scsi_debug } cleanup() { if [ -d "$MNT_DIR" ] ; then umount -f $MNT_DIR 2>/dev/null rmdir $MNT_DIR 2>/dev/null fi rm -f $LOCAL_FILE 2> /dev/null scsi_debug_teardown "$DEV" || exit 100 } fail() { if [ -n "$1" ] ; then echo "FAIL $1" ; else echo "FAIL" ; fi cleanup exit 100 } fail_count() { echo "$MSG[FAIL]" FAILS=$((FAILS+1)) } warn_count() { echo "$MSG[WARNING]" WARNS=$((WARNS+1)) } skip() { echo "TEST SKIPPED: $1" cleanup exit 0 } add_device() { modprobe scsi_debug $@ delay=0 if [ $? -ne 0 ] ; then echo "This kernel seems to not support proper scsi_debug module, test skipped." exit 77 fi DEV=$(grep -l -e scsi_debug /sys/block/*/device/model | cut -f4 -d /) DEV="/dev/$DEV" [ -b $DEV ] || fail "Cannot find $DEV." } falloc() { dd if=/dev/zero of=$2 bs=1M count=$1 2> /dev/null } run_all_in_fs() { for file in $(ls img_fs_*.img.xz) ; do echo "Run tests in $file put on top block device." xz -d -c $file | dd of=$DEV bs=1M 2>/dev/null || fail "bad image" [ ! -d $MNT_DIR ] && mkdir $MNT_DIR mount $DEV $MNT_DIR if [ $? -ne 0 ]; then echo "Mounting image $file failed, skipped." continue; fi rm -rf $MNT_DIR/* 2>/dev/null local tfile=$MNT_DIR/bwunit_tstfile falloc $DEVSIZEMB $tfile || fail "enospc?" local iobsize=$(stat -c "%o" $tfile) test -n "$iobsize" -a $iobsize -gt 0 || fail local oldbsize=$BSIZE BSIZE=$iobsize run_all $tfile BSIZE=$oldbsize umount $MNT_DIR done } trunc_file() { test $1 -eq 0 || truncate -c -s $1 $2 2>/dev/null || dd if=/dev/zero of=$2 bs=$1 count=1 2>/dev/null || fail "Failed to truncate test file $2." } RUN() { local _res=$1 shift local _dev=$1 shift local _fn=$1 shift local _type="bdev" local _fsize=0 test -b $_dev || { _type="file" _fsize=$(stat -c "%s" $_dev) } case "$_res" in P) MSG="Testing $_fn on $_type with params $@ [expecting TRUE]..." $BW_UNIT $_dev $_fn $@ if [ $? -ne 0 ]; then if [ $_type = "file" ]; then warn_count else fail_count fi trunc_file $_fsize $_dev test -z "$STRACE" || $STRACE -o ./$BW_UNIT-fail-$FAILS-should-pass.log $BW_UNIT $_dev $_fn $@ 2> /dev/null else MSG="$MSG[OK]" fi ;; F) MSG="Testing $_fn on $_type with params $@ [expecting FALSE]..." $BW_UNIT $_dev $_fn $@ 2> /dev/null if [ $? -eq 0 ]; then if [ $_type = "file" ]; then warn_count else fail_count fi trunc_file $_fsize $_dev test -z "$STRACE" || $STRACE -o ./$BW_UNIT-fail-$FAILS-should-fail.log $BW_UNIT $_dev $_fn $@ 2> /dev/null else MSG="$MSG[OK]" fi ;; *) fail "Internal test error" ;; esac trunc_file $_fsize $_dev } run_all() { if [ -b "$1" ]; then BD_FAIL="F" else BD_FAIL="P" fi # buffer io support only blocksize aligned ios # device/file fn_name length RUN "P" $1 read_buffer $BSIZE RUN "P" $1 read_buffer $((2*BSIZE)) RUN "F" $1 read_buffer $((BSIZE-1)) RUN "F" $1 read_buffer $((BSIZE+1)) RUN "P" $1 read_buffer 0 RUN "P" $1 write_buffer $BSIZE RUN "P" $1 write_buffer $((2*BSIZE)) RUN "F" $1 write_buffer $((BSIZE-1)) RUN "F" $1 write_buffer $((BSIZE+1)) RUN "F" $1 write_buffer 0 # basic blockwise functions # device/file fn_name length bsize RUN "P" $1 read_blockwise 0 $BSIZE RUN "P" $1 read_blockwise $((BSIZE)) $BSIZE RUN "P" $1 read_blockwise $((BSIZE-1)) $BSIZE RUN "P" $1 read_blockwise $((BSIZE+1)) $BSIZE RUN "P" $1 read_blockwise $((DEVSIZE)) $BSIZE RUN "P" $1 read_blockwise $((DEVSIZE-1)) $BSIZE RUN "F" $1 read_blockwise $((DEVSIZE+1)) $BSIZE RUN "P" $1 write_blockwise 0 $BSIZE RUN "P" $1 write_blockwise $((BSIZE)) $BSIZE RUN "P" $1 write_blockwise $((BSIZE-1)) $BSIZE RUN "P" $1 write_blockwise $((BSIZE+1)) $BSIZE RUN "P" $1 write_blockwise $((DEVSIZE)) $BSIZE RUN "P" $1 write_blockwise $((DEVSIZE-1)) $BSIZE RUN "$BD_FAIL" $1 write_blockwise $((DEVSIZE+1)) $BSIZE # seek variant blockwise functions # device/file fn_name length bsize offset RUN "P" $1 read_lseek_blockwise 0 $BSIZE 0 RUN "P" $1 read_lseek_blockwise 0 $BSIZE 1 RUN "P" $1 read_lseek_blockwise 0 $BSIZE $((DEVSIZE)) # length = 0 is significant here RUN "P" $1 read_lseek_blockwise 0 $BSIZE $((DEVSIZE+1)) # beginning of device RUN "P" $1 read_lseek_blockwise 1 $BSIZE 0 RUN "P" $1 read_lseek_blockwise 1 $BSIZE 1 RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((BSIZE/2)) # somewhere in the 'middle' RUN "P" $1 read_lseek_blockwise 1 $BSIZE $BSIZE RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((BSIZE+1)) RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((2*BSIZE-1)) RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((BSIZE+BSIZE/2-1)) # cross-sector tests RUN "P" $1 read_lseek_blockwise 2 $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise 2 $BSIZE $((2*BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((2*BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+2)) $BSIZE $((2*BSIZE-1)) # including one whole sector RUN "P" $1 read_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE)) RUN "P" $1 read_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE+1)) RUN "P" $1 read_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE+1)) RUN "P" $1 read_lseek_blockwise $((3*BSIZE-2)) $BSIZE $((BSIZE+1)) # hiting exactly the sector boundary RUN "P" $1 read_lseek_blockwise $((BSIZE-1)) $BSIZE 1 RUN "P" $1 read_lseek_blockwise $((BSIZE-1)) $BSIZE $((BSIZE+1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((BSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((2*BSIZE-1)) # device end RUN "P" $1 read_lseek_blockwise 1 $BSIZE $((DEVSIZE-1)) RUN "P" $1 read_lseek_blockwise $((BSIZE-1)) $BSIZE $((DEVSIZE-BSIZE+1)) RUN "P" $1 read_lseek_blockwise $((BSIZE)) $BSIZE $((DEVSIZE-BSIZE)) RUN "P" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((DEVSIZE-BSIZE-1)) # this must fail on both device and file RUN "F" $1 read_lseek_blockwise 1 $BSIZE $((DEVSIZE)) RUN "F" $1 read_lseek_blockwise $((BSIZE-1)) $BSIZE $((DEVSIZE-BSIZE+2)) RUN "F" $1 read_lseek_blockwise $((BSIZE)) $BSIZE $((DEVSIZE-BSIZE+1)) RUN "F" $1 read_lseek_blockwise $((BSIZE+1)) $BSIZE $((DEVSIZE-BSIZE)) RUN "P" $1 write_lseek_blockwise 0 $BSIZE 0 # TODO: this may pass but must not write a byte (write(0) is undefined). # Test it with underlying dm-error or phony read/write syscalls. # Skipping read is optimization. # HINT: currently it performs useless write and read as well RUN "P" $1 write_lseek_blockwise 0 $BSIZE 1 RUN "P" $1 write_lseek_blockwise 0 $BSIZE $BSIZE # beginning of device RUN "P" $1 write_lseek_blockwise 1 $BSIZE 0 RUN "P" $1 write_lseek_blockwise 1 $BSIZE 1 RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((BSIZE/2)) # somewhere in the 'middle' RUN "P" $1 write_lseek_blockwise 1 $BSIZE $BSIZE RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((BSIZE+1)) RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((2*BSIZE-1)) RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((BSIZE+BSIZE/2-1)) # cross-sector tests RUN "P" $1 write_lseek_blockwise 2 $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise 2 $BSIZE $((2*BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((2*BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+2)) $BSIZE $((2*BSIZE-1)) # including one whole sector RUN "P" $1 write_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE)) RUN "P" $1 write_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE+1)) RUN "P" $1 write_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+2)) $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((2*BSIZE)) $BSIZE $((BSIZE+1)) RUN "P" $1 write_lseek_blockwise $((3*BSIZE-2)) $BSIZE $((BSIZE+1)) # hiting exactly the sector boundary RUN "P" $1 write_lseek_blockwise $((BSIZE-1)) $BSIZE 1 RUN "P" $1 write_lseek_blockwise $((BSIZE-1)) $BSIZE $((BSIZE+1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((BSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((2*BSIZE-1)) # device end RUN "P" $1 write_lseek_blockwise 1 $BSIZE $((DEVSIZE-1)) RUN "P" $1 write_lseek_blockwise $((BSIZE-1)) $BSIZE $((DEVSIZE-BSIZE+1)) RUN "P" $1 write_lseek_blockwise $((BSIZE)) $BSIZE $((DEVSIZE-BSIZE)) RUN "P" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((DEVSIZE-BSIZE-1)) # this must fail on device, but pass on file (which is unfortunate and maybe design mistake) RUN "$BD_FAIL" $1 write_lseek_blockwise 1 $BSIZE $((DEVSIZE)) RUN "$BD_FAIL" $1 write_lseek_blockwise $((BSIZE-1)) $BSIZE $((DEVSIZE-BSIZE+2)) RUN "$BD_FAIL" $1 write_lseek_blockwise $((BSIZE)) $BSIZE $((DEVSIZE-BSIZE+1)) RUN "$BD_FAIL" $1 write_lseek_blockwise $((BSIZE+1)) $BSIZE $((DEVSIZE-BSIZE)) } which $STRACE > /dev/null 2>&1 || unset STRACE test -x $BW_UNIT || skip "Run \"make `basename $BW_UNIT`\" first" FAILS=0 WARNS=0 DEVSIZEMB=2 DEVSIZE=$((DEVSIZEMB*1024*1024)) PAGE_SIZE=$(getconf PAGE_SIZE) echo "System PAGE_SIZE=$PAGE_SIZE" echo "Run tests in local filesystem" falloc $DEVSIZEMB $LOCAL_FILE || fail "Failed to create file in local filesystem." BSIZE=$(stat -c "%o" $LOCAL_FILE) if [ $BSIZE -gt $((512*1024)) ]; then echo "Detected file block size: $BSIZE bytes" echo "Tuning it down to system page size ($PAGE_SIZE bytes)" BSIZE=$PAGE_SIZE fi run_all $LOCAL_FILE [ $(id -u) -eq 0 ] || { echo "WARNING: You must be root to run remaining tests." test $FAILS -eq 0 || fail "($FAILS wrong result(s) in total)" cleanup exit 0 } DEVBSIZE=512 BSIZE=$DEVBSIZE EXP=0 DEVSIZEMBIMG=32 echo "# Create classic 512B drive" echo "# (logical_block_size=$DEVBSIZE, physical_block_size=$((DEVBSIZE*(1<