#! /bin/bash # This is a unit test program for kpartx, in particular for deleting partitions. # # The rationale is the following: # # 1) kpartx should delete all mappings it created beforehand. # 2) kpartx should handle partitions on dm devices and other devices # (e.g. loop devices) equally well. # 3) kpartx should only delete "partitions", which are single-target # linear mappings into a block device. Other maps should not be touched. # 4) kpartx should only delete mappings it created itself beforehand. # In particular, it shouldn't delete LVM LVs, even if they are fully # contained in the block device at hand and thus look like partitions # in the first place. (For historical compatibility reasons, we allow # such mappings to be deleted with the -f/--force flag). # 5) DM map names may be changed, thus kpartx shouldn't rely on them to # check whether a mapping is a partition of a particular device. It is # legal for a partition of /dev/loop0 to be named "loop0". # Note: This program tries hard to clean up, but if tests fail, # stale DM or loop devices may keep lurking around. # Set WORKDIR in environment to existing dir to for persistence # WARNING: existing files will be truncated. # If empty, test will be done in temporary dir : ${WORKDIR:=} # Set this environment variable to test an alternative kpartx executable : ${KPARTX:=} # Options to pass to kpartx always : ${KPARTX_OPTS:=-s} # Time to wait for device nodes to appear (microseconds) # Waiting is only needed if "s" is not in $KPARTX_OPTS : ${WAIT_US:=0} # IMPORTANT: The ERR trap is essential for this program to work correctly! #trap 'LINE=$LINENO; trap - ERR; echo "== error in $BASH_COMMAND on line $LINE ==" >&2; ls -la /dev/mapper; read a; exit 1' ERR trap 'cleanup' 0 CLEANUP=: cleanup() { trap - ERR trap - 0 if [[ $OK ]]; then echo == all ${ALL_PASS} tests completed successfully == >&2 else printf "== Final Results ==\n%s of %s failed!\n\n" "${ALL_FAIL}" "$(( ALL_PASS + ALL_FAIL ))" >&2 fi eval "$CLEANUP" &>/dev/null } push_cleanup() { CLEANUP="$@;$CLEANUP" } pop_cleanup() { # CAUTION: simplistic CLEANUP=${CLEANUP#*;} } step() { STEP="$@" if [ $PASS -gt 0 -o $FAIL -gt 0 ] then printf "PASS: %s FAIL: %s\n\n" "${PASS}" "${FAIL}" PASS=0 FAIL=0 fi echo == Test step: $STEP == >&2 } mk_partitions() { parted -s $1 mklabel msdos parted -s -- $1 mkpart prim ext2 1MiB -1s } wipe_ptable() { dd if=/dev/zero of=$1 bs=1b count=1 } usleep() { if which usleep >/dev/null 2>&1 then env usleep "${1}" else sleep $( echo "${1}" / 1000000.0 | bc ) fi } invoke_kpartx() { $KPARTX $KPARTX_OPTS "${@}" usleep "${WAIT_US}" } assert_exists() { local result=0 while [ "${1}" ] do if [ -b "$(readlink -f "${1}")" ] then PASS=$(( PASS + 1 )) ALL_PASS=$(( ALL_PASS + 1 )) else FAIL=$(( FAIL + 1 )) ALL_FAIL=$(( ALL_FAIL + 1 )) echo "assert_exists: Device does not exist when it should: ${1}" result=1 fi shift done return $result } assert_unexists() { local result=0 while [ "${1}" ] do if [ ! -b "$(readlink -f "${1}")" ] then PASS=$(( PASS + 1 )) ALL_PASS=$(( ALL_PASS + 1 )) else FAIL=$(( FAIL + 1 )) ALL_FAIL=$(( ALL_FAIL + 1 )) echo "assert_exists: Device exists when it should not: ${1}" result=1 fi shift done return $result } PASS=0 ALL_PASS=0 FAIL=0 ALL_FAIL=0 step preparation [[ $UID -eq 0 ]] [[ $KPARTX ]] || { if [[ -x $PWD/kpartx/kpartx ]]; then KPARTX=$PWD/kpartx/kpartx else KPARTX=$(which kpartx) fi } [[ $KPARTX ]] FILE1=kpartx1 FILE2=kpartx2 FILE3=kpartx3 FILE4=kpartx4 SIZE=$((1024*1024*1024)) # use bytes as units here SECTSIZ=512 OFFS=32 # offset of linear mapping into dev, sectors VG=kpvg # volume group name LV=kplv # logical vol name LVMCONF='devices { filter = [ "a|/dev/loop.*|", r".*" ] }' OK= [[ $WORKDIR ]] || { WORKDIR=$(mktemp -d /tmp/kpartx-XXXXXX) push_cleanup 'rm -rf $WORKDIR' } push_cleanup "cd $PWD" cd "$WORKDIR" step "create loop devices" truncate -s $SIZE $FILE1 truncate -s $SIZE $FILE2 truncate -s $SIZE $FILE3 truncate -s $SIZE $FILE4 LO1=$(losetup -f $FILE1 --show) push_cleanup 'losetup -d $LO1' LO2=$(losetup -f $FILE2 --show) push_cleanup 'losetup -d $LO2' LO3=$(losetup -f $FILE3 --show) push_cleanup 'losetup -d $LO3' LO4=$(losetup -f $FILE4 --show) push_cleanup 'losetup -d $LO4' [[ $LO1 && $LO2 && $LO3 && $LO4 && -b $LO1 && -b $LO2 && -b $LO3 && -b $LO4 ]] DEV1=$(stat -c "%t:%T" $LO1) DEV2=$(stat -c "%t:%T" $LO2) DEV3=$(stat -c "%t:%T" $LO3) usleep $WAIT_US step "create DM devices (spans)" # Create two linear mappings spanning two loopdevs. # One of them gets a pathological name colliding with # the loop device name. # These mappings must not be removed by kpartx. # They also serve as DM devices to test partition removal on those. TABLE="\ 0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS $((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS" SPAN1=kpt SPAN2=$(basename $LO2) dmsetup create $SPAN1 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN1' dmsetup create $SPAN2 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN2' # This is a non-kpartx pseudo "partition" mapping USER1=user1 push_cleanup 'dmsetup remove -f $USER1' dmsetup create $USER1 <&2 usleep $WAIT_US assert_exists $SPAN2P1 $LO1P1 $LO2P1 assert_unexists $SPAN1P1 invoke_kpartx -d /dev/mapper/$SPAN2 usleep $WAIT_US assert_exists $LO1P1 $LO2P1 assert_unexists $SPAN2P1 step "rename partitions on loop device" invoke_kpartx -u -p -spam $LO2 assert_unexists $LO2P1 assert_exists ${LO2P1//-foo/-spam} step "rename partitions on loop device back" invoke_kpartx -u -p -foo $LO2 assert_exists $LO2P1 assert_unexists ${LO2P1//-foo/-spam} step "rename partitions on loop device to default" invoke_kpartx -u $LO2 #read a assert_unexists $LO2P1 assert_exists ${LO2P1//-foo/p} # $LO1 ends in a digit step "rename partitions on loop device back from default" invoke_kpartx -u -p -foo $LO2 assert_exists $LO2P1 assert_unexists ${LO2P1//-foo/p} step "rename partitions on loop devices" invoke_kpartx -u -p spam $LO2 step "delete partitions on loop devices" invoke_kpartx -d $LO3 # This will also delete the loop device invoke_kpartx -d $FILE2 invoke_kpartx -d $LO1 usleep $WAIT_US # ls -l /dev/mapper assert_unexists $LO1P1 pop_cleanup assert_unexists $LO2P1 pop_cleanup # spans should not have been removed # LVs neither assert_exists /dev/mapper/$SPAN1 /dev/mapper/$SPAN2 /dev/mapper/$USER1 /dev/mapper/$VG-$LV step "delete partitions on $LO3 with -f" invoke_kpartx -f -d $LO3 # -d -f should delete the LV, too assert_unexists /dev/mapper/$VG-$LV assert_exists /dev/mapper/$SPAN1 /dev/mapper/$SPAN2 step "test kpartx creation/deletion on an image file with no existing loopdev" losetup -d $LO4 OUTPUT=$(invoke_kpartx -v -a $FILE4 2>&1) read loop dm < \ <(sed -n 's/^add map \(loop[0-9]*\)p1 ([0-9]*:\([0-9]*\)).*$/\1 dm-\2/p' \ <<<$OUTPUT) [[ $dm && $loop ]] push_cleanup "dmsetup remove -f /dev/$dm" push_cleanup "losetup -d /dev/$loop" assert_exists /dev/mapper/${loop}p1 invoke_kpartx -d $FILE4 assert_unexists /dev/mapper/${loop}p1 # /dev/$loop is _not_ automatically deleted assert_exists /dev/${loop} [ $ALL_FAIL -eq 0 ] && OK=yes