]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - tests/functions.sh
libblkid: check status for the current CDROM slot
[thirdparty/util-linux.git] / tests / functions.sh
index 1f4d51878b72b19b57adaff3d2e99a6be716a0de..0af8b96b77f9a7527baa95013d5224bbabd1b054 100644 (file)
@@ -45,18 +45,55 @@ function ts_cd {
        fi
 }
 
+function ts_separator {
+       local header="$1"
+       echo >> $TS_OUTPUT
+       if [ -z "$header" ]; then
+               echo "============================================" >> $TS_OUTPUT
+       else
+               echo "=====$header================================" >> $TS_OUTPUT
+       fi
+}
+
 function ts_report {
-       if [ "$TS_PARALLEL" == "yes" ]; then
-               echo "$TS_TITLE $1"
+       local desc=
+
+       if [ "$TS_PARSABLE" != "yes" ]; then
+               if [ $TS_NSUBTESTS -ne 0 ] && [ -z "$TS_SUBNAME" ]; then
+                       desc=$(printf "%11s...")
+               fi
+               echo "$desc$1"
+               return
+       fi
+
+       if [ -n "$TS_SUBNAME" ]; then
+               desc=$(printf "%s: [%02d] %s" "$TS_DESC" "$TS_NSUBTESTS" "$TS_SUBNAME")
        else
-               echo "$1"
+               desc=$TS_DESC
        fi
+       printf "%13s: %-45s ...%s\n" "$TS_COMPONENT" "$desc" "$1"
 }
 
 function ts_check_test_command {
-       if [ ! -x "$1" ]; then
-               ts_skip "${1##*/} not found"
-       fi
+       case "$1" in
+       */*)
+               # paths
+               if [ ! -x "$1" ]; then
+                       ts_skip "${1##*/} not found"
+               fi
+               ;;
+       *)
+               # just command names (e.g. --use-system-commands)
+               local cmd=$1
+               type "$cmd" >/dev/null 2>&1
+               if [ $? -ne 0 ]; then
+                       if [ "$TS_NOSKIP_COMMANDS" = "yes" ]; then
+                               ts_failed "missing in PATH: $cmd"
+                       fi
+                       ts_skip "missing in PATH: $cmd"
+               fi
+               ;;
+       esac
 }
 
 function ts_check_prog {
@@ -80,12 +117,12 @@ function ts_check_losetup {
        ts_skip "no loop-device support"
 }
 
-function ts_skip_subtest {
+function ts_report_skip {
        ts_report " SKIPPED ($1)"
 }
 
 function ts_skip {
-       ts_skip_subtest "$1"
+       ts_report_skip "$1"
 
        ts_cleanup_on_exit
        exit 0
@@ -119,7 +156,7 @@ function ts_failed {
        exit $?
 }
 
-function ts_ok_subtest {
+function ts_report_ok {
        if [ x"$1" == x"" ]; then
                ts_report " OK"
        else
@@ -128,7 +165,7 @@ function ts_ok_subtest {
 }
 
 function ts_ok {
-       ts_ok_subtest "$1"
+       ts_report_ok "$1"
        exit 0
 }
 
@@ -137,6 +174,17 @@ function ts_log {
        [ "$TS_VERBOSE" == "yes" ] && echo "$1"
 }
 
+function ts_logerr {
+       echo "$1" >> $TS_ERRLOG
+       [ "$TS_VERBOSE" == "yes" ] && echo "$1"
+}
+
+function ts_log_both {
+       echo "$1" >> $TS_OUTPUT
+       echo "$1" >> $TS_ERRLOG
+       [ "$TS_VERBOSE" == "yes" ] && echo "$1"
+}
+
 function ts_has_option {
        NAME="$1"
        ALL="$2"
@@ -158,36 +206,50 @@ function ts_has_option {
        fi
 
        # or just check the global command line options
-       echo -n $ALL | sed 's/ //g' | awk 'BEGIN { FS="="; RS="--" } /('$NAME'$|'$NAME'=)/ { print "yes" }'
+       if [[ $ALL =~ ([$' \t\n']|^)--$NAME([$'= \t\n']|$) ]]; then
+               echo yes
+               return
+       fi
+
+       # or the _global_ env, e.g TS_OPT_parsable="yes"
+       eval local env_opt=\$TS_OPT_${v_name}
+       if [ "$env_opt" = "yes" ]; then echo "yes"; fi
 }
 
 function ts_option_argument {
        NAME="$1"
        ALL="$2"
-       echo -n $ALL | sed 's/ //g' | awk 'BEGIN { FS="="; RS="--" } /'$NAME'=/ { print $2 }'
+
+       # last option wins!
+       echo "$ALL" | sed -n "s/.*[ \t\n]--$NAME=\([^ \t\n]*\).*/\1/p" | tail -n 1
 }
 
 function ts_init_core_env {
+       TS_SUBNAME=""
        TS_NS="$TS_COMPONENT/$TS_TESTNAME"
        TS_OUTPUT="$TS_OUTDIR/$TS_TESTNAME"
+       TS_ERRLOG="$TS_OUTDIR/$TS_TESTNAME.err"
        TS_VGDUMP="$TS_OUTDIR/$TS_TESTNAME.vgdump"
        TS_DIFF="$TS_DIFFDIR/$TS_TESTNAME"
        TS_EXPECTED="$TS_TOPDIR/expected/$TS_NS"
+       TS_EXPECTED_ERR="$TS_TOPDIR/expected/$TS_NS.err"
        TS_MOUNTPOINT="$TS_OUTDIR/${TS_TESTNAME}-mnt"
 }
 
 function ts_init_core_subtest_env {
        TS_NS="$TS_COMPONENT/$TS_TESTNAME-$TS_SUBNAME"
        TS_OUTPUT="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME"
+       TS_ERRLOG="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME.err"
        TS_VGDUMP="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME.vgdump"
        TS_DIFF="$TS_DIFFDIR/$TS_TESTNAME-$TS_SUBNAME"
        TS_EXPECTED="$TS_TOPDIR/expected/$TS_NS"
+       TS_EXPECTED_ERR="$TS_TOPDIR/expected/$TS_NS.err"
        TS_MOUNTPOINT="$TS_OUTDIR/${TS_TESTNAME}-${TS_SUBNAME}-mnt"
 
-       rm -f $TS_OUTPUT $TS_VGDUMP
+       rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP
        [ -d "$TS_OUTDIR" ]  || mkdir -p "$TS_OUTDIR"
 
-       touch $TS_OUTPUT
+       touch $TS_OUTPUT $TS_ERRLOG
        [ -n "$TS_VALGRIND_CMD" ] && touch $TS_VGDUMP
 }
 
@@ -199,8 +261,9 @@ function ts_init_env {
        LANGUAGE="POSIX"
        LC_ALL="POSIX"
        CHARSET="UTF-8"
+       ASAN_OPTIONS="detect_leaks=0"
 
-       export LANG LANGUAGE LC_ALL CHARSET
+       export LANG LANGUAGE LC_ALL CHARSET ASAN_OPTIONS
 
        mydir=$(ts_canonicalize "$mydir")
 
@@ -222,8 +285,20 @@ function ts_init_env {
        top_srcdir=$(ts_abspath $top_srcdir)
        top_builddir=$(ts_abspath $top_builddir)
 
-       # some ul commands search other ul commands in $PATH
-       export PATH="$top_builddir:$PATH"
+       # We use helpser always from build tree
+       ts_helpersdir="${top_builddir}/"
+
+       TS_USE_SYSTEM_COMMANDS=$(ts_has_option "use-system-commands" "$*")
+       if [ "$TS_USE_SYSTEM_COMMANDS" == "yes" ]; then
+               # Don't define anything, just follow current PATH
+               ts_commandsdir=""
+       else
+               # The default is to use commands from build tree
+               ts_commandsdir="${top_builddir}/"
+
+               # some ul commands search other ul commands in $PATH
+               export PATH="$ts_commandsdir:$PATH"
+       fi
 
        TS_SCRIPT="$mydir/$(basename $0)"
        TS_SUBDIR=$(dirname $TS_SCRIPT)
@@ -239,17 +314,31 @@ function ts_init_env {
        TS_OUTDIR="$top_builddir/tests/output/$TS_COMPONENT"
        TS_DIFFDIR="$top_builddir/tests/diff/$TS_COMPONENT"
 
+       TS_NOLOCKS=$(ts_has_option "nolocks" "$*")
+       TS_LOCKDIR="$top_builddir/tests/output"
+
+       # Don't lock if flock(1) is missing
+       type "flock" >/dev/null 2>&1 || TS_NOLOCKS="yes"
+
        ts_init_core_env
 
+       TS_NOSKIP_COMMANDS=$(ts_has_option "noskip-commands" "$*")
        TS_VERBOSE=$(ts_has_option "verbose" "$*")
+       TS_SHOWDIFF=$(ts_has_option "show-diff" "$*")
        TS_PARALLEL=$(ts_has_option "parallel" "$*")
        TS_KNOWN_FAIL=$(ts_has_option "known-fail" "$*")
        TS_SKIP_LOOPDEVS=$(ts_has_option "skip-loopdevs" "$*")
+       TS_PARSABLE=$(ts_has_option "parsable" "$*")
+       [ "$TS_PARSABLE" = "yes" ] || TS_PARSABLE="$TS_PARALLEL"
 
-       tmp=$( ts_has_option "memcheck" "$*")
+       tmp=$( ts_has_option "memcheck-valgrind" "$*")
        if [ "$tmp" == "yes" -a -f /usr/bin/valgrind ]; then
                TS_VALGRIND_CMD="/usr/bin/valgrind"
        fi
+       tmp=$( ts_has_option "memcheck-asan" "$*")
+       if [ "$tmp" == "yes" ]; then
+               TS_ENABLE_ASAN="yes"
+       fi
 
        BLKID_FILE="$TS_OUTDIR/${TS_TESTNAME}.blkidtab"
 
@@ -257,6 +346,7 @@ function ts_init_env {
        declare -a TS_SUID_USER
        declare -a TS_SUID_GROUP
        declare -a TS_LOOP_DEVS
+       declare -a TS_LOCKFILE_FD
 
        if [ -f $TS_TOPDIR/commands.sh ]; then
                . $TS_TOPDIR/commands.sh
@@ -264,15 +354,17 @@ function ts_init_env {
 
        export BLKID_FILE
 
-       rm -f $TS_OUTPUT $TS_VGDUMP
+       rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP
        [ -d "$TS_OUTDIR" ]  || mkdir -p "$TS_OUTDIR"
 
-       touch $TS_OUTPUT
+       touch $TS_OUTPUT $TS_ERRLOG
        [ -n "$TS_VALGRIND_CMD" ] && touch $TS_VGDUMP
 
        if [ "$TS_VERBOSE" == "yes" ]; then
                echo
                echo "     script: $TS_SCRIPT"
+               echo "   commands: $ts_commandsdir"
+               echo "    helpers: $ts_helpersdir"
                echo "    sub dir: $TS_SUBDIR"
                echo "    top dir: $TS_TOPDIR"
                echo "       self: $TS_SELF"
@@ -282,8 +374,9 @@ function ts_init_env {
                echo "  namespace: $TS_NS"
                echo "    verbose: $TS_VERBOSE"
                echo "     output: $TS_OUTPUT"
+               echo "  error log: $TS_ERRLOG"
                echo "   valgrind: $TS_VGDUMP"
-               echo "   expected: $TS_EXPECTED"
+               echo "   expected: $TS_EXPECTED{.err}"
                echo " mountpoint: $TS_MOUNTPOINT"
                echo
        fi
@@ -292,17 +385,12 @@ function ts_init_env {
 function ts_init_subtest {
 
        TS_SUBNAME="$1"
-
        ts_init_core_subtest_env
-
-       [ $TS_NSUBTESTS -eq 0 ] && echo
        TS_NSUBTESTS=$(( $TS_NSUBTESTS + 1 ))
 
-       if [ "$TS_PARALLEL" == "yes" ]; then
-               TS_TITLE=$(printf "%13s: %-30s ...\n%16s: %-27s ..." "$TS_COMPONENT" "$TS_DESC" "" "$TS_SUBNAME")
-       else
-               TS_TITLE=$(printf "%16s: %-27s ..." "" "$TS_SUBNAME")
-               echo -n "$TS_TITLE"
+       if [ "$TS_PARSABLE" != "yes" ]; then
+               [ $TS_NSUBTESTS -eq 1 ] && echo
+               printf "%16s: %-27s ..." "" "$TS_SUBNAME"
        fi
 }
 
@@ -312,11 +400,8 @@ function ts_init {
        local is_fake=$( ts_has_option "fake" "$*")
        local is_force=$( ts_has_option "force" "$*")
 
-       if [ "$TS_PARALLEL" == "yes" ]; then
-               TS_TITLE=$(printf "%13s: %-30s ..." "$TS_COMPONENT" "$TS_DESC")
-       else
-               TS_TITLE=$(printf "%13s: %-30s ..." "$TS_COMPONENT" "$TS_DESC")
-               echo -n "$TS_TITLE"
+       if [ "$TS_PARSABLE" != "yes" ]; then
+               printf "%13s: %-30s ..." "$TS_COMPONENT" "$TS_DESC"
        fi
 
        [ "$is_fake" == "yes" ] && ts_skip "fake mode"
@@ -350,60 +435,100 @@ function ts_init_py {
        export PYTHON="python${PYTHON_MAJOR_VERSION}"
 }
 
-function ts_valgrind {
-       if [ -z "$TS_VALGRIND_CMD" ]; then
-               $*
-       else
-               $TS_VALGRIND_CMD --tool=memcheck --leak-check=full \
-                                --leak-resolution=high --num-callers=20 \
-                                --log-file="$TS_VGDUMP" $*
+function ts_run {
+       declare -a args
+
+       #
+       # ASAN mode
+       #
+       if [ "$TS_ENABLE_ASAN" == "yes" ]; then
+               args+=(env ASAN_OPTIONS=detect_leaks=1)
        fi
-}
 
-function ts_gen_diff {
-       local res=0
+       #
+       # valgrind mode
+       #
+       if [ -n "$TS_VALGRIND_CMD" ]; then
+               args+=(libtool --mode=execute "$TS_VALGRIND_CMD" --tool=memcheck --leak-check=full)
+               args+=(--leak-resolution=high --num-callers=20 --log-file="$TS_VGDUMP")
+       fi
 
-       if [ -s "$TS_OUTPUT" ]; then
+       "${args[@]}" "$@"
+}
 
-               # remove libtool lt- prefixes
-               sed --in-place 's/^lt\-\(.*\: \)/\1/g' $TS_OUTPUT
+function ts_gen_diff_from {
+       local res=0
+       local expected="$1"
+       local output="$2"
+       local difffile="$3"
 
-               [ -d "$TS_DIFFDIR" ] || mkdir -p "$TS_DIFFDIR"
-               diff -u $TS_EXPECTED $TS_OUTPUT > $TS_DIFF
+       diff -u $expected $output > $difffile
 
-               if [ -s $TS_DIFF ]; then
-                       res=1
-               else
-                       rm -f $TS_DIFF;
+       if [ $? -ne 0 ] || [ -s $difffile ]; then
+               res=1
+               if [ "$TS_SHOWDIFF" == "yes" -a "$TS_KNOWN_FAIL" != "yes" ]; then
+                       echo
+                       echo "diff-{{{"
+                       cat $difffile
+                       echo "}}}-diff"
+                       echo
                fi
        else
-               res=1
+               rm -f $difffile;
        fi
+
        return $res
 }
 
-function tt_gen_mem_report {
-       [ -z "$TS_VALGRIND_CMD" ] && echo "$1"
+function ts_gen_diff {
+       local status_out=0
+       local status_err=0
+
+       [ -f "$TS_OUTPUT" ] || return 1
+       [ -f "$TS_EXPECTED" ] || TS_EXPECTED=/dev/null
+
+       # remove libtool lt- prefixes
+       sed --in-place 's/^lt\-\(.*\: \)/\1/g' $TS_OUTPUT
+       sed --in-place 's/^lt\-\(.*\: \)/\1/g' $TS_ERRLOG
+
+       [ -d "$TS_DIFFDIR" ] || mkdir -p "$TS_DIFFDIR"
+
+       ts_gen_diff_from $TS_EXPECTED $TS_OUTPUT $TS_DIFF
+       status_out=$?
+
+       # error log is fully optional
+       [ -f "$TS_EXPECTED_ERR" ] || TS_EXPECTED_ERR=/dev/null
+       [ -f "$TS_ERRLOG" ] || TS_ERRLOG=/dev/null
+
+       ts_gen_diff_from $TS_EXPECTED_ERR $TS_ERRLOG $TS_DIFF.err
+       status_err=$?
+
+       if [ $status_out -ne 0 -o $status_err -ne 0 ]; then
+               return 1
+       fi
+       return 0
+}
 
-       grep -q -E 'ERROR SUMMARY: [1-9]' $TS_VGDUMP &> /dev/null
-       if [ $? -eq 0 ]; then
-               echo "mem-error detected!"
+function tt_gen_mem_report {
+       if [ -n "$TS_VALGRIND_CMD" ]; then
+               grep -q -E 'ERROR SUMMARY: [1-9]' $TS_VGDUMP &> /dev/null
+               if [ $? -eq 0 ]; then
+                       echo "mem-error detected!"
+               fi
+       else
+               echo "$1"
        fi
 }
 
 function ts_finalize_subtest {
        local res=0
 
-       if [ -s "$TS_EXPECTED" ]; then
-               ts_gen_diff
-               if [ $? -eq 1 ]; then
-                       ts_failed_subtest "$1"
-                       res=1
-               else
-                       ts_ok_subtest "$(tt_gen_mem_report "$1")"
-               fi
+       ts_gen_diff
+       if [ $? -eq 1 ]; then
+               ts_failed_subtest "$1"
+               res=1
        else
-               ts_skip_subtest "output undefined"
+               ts_report_ok "$(tt_gen_mem_report "$1")"
        fi
 
        [ $res -ne 0 ] && TS_NSUBFAILED=$(( $TS_NSUBFAILED + 1 ))
@@ -414,27 +539,26 @@ function ts_finalize_subtest {
        return $res
 }
 
+function ts_skip_subtest {
+       ts_report_skip "$1"
+       # reset environment back to parental test
+       ts_init_core_env
+
+}
+
 function ts_finalize {
        ts_cleanup_on_exit
 
        if [ $TS_NSUBTESTS -ne 0 ]; then
-               printf "%11s..."
-               if [ $TS_NSUBFAILED -ne 0 ]; then
+               if ! ts_gen_diff || [ $TS_NSUBFAILED -ne 0 ]; then
                        ts_failed "$TS_NSUBFAILED from $TS_NSUBTESTS sub-tests"
                else
                        ts_ok "all $TS_NSUBTESTS sub-tests PASSED"
                fi
        fi
 
-       if [ -s $TS_EXPECTED ]; then
-               ts_gen_diff
-               if [ $? -eq 1 ]; then
-                       ts_failed "$1"
-               fi
-               ts_ok "$1"
-       fi
-
-       ts_skip "output undefined"
+       ts_gen_diff || ts_failed "$1"
+       ts_ok "$1"
 }
 
 function ts_die {
@@ -454,11 +578,13 @@ function ts_cleanup_on_exit {
                ts_device_deinit "$dev"
        done
        unset TS_LOOP_DEVS
+
+       ts_scsi_debug_rmmod
 }
 
 function ts_image_md5sum {
        local img=${1:-"$TS_OUTDIR/${TS_TESTNAME}.img"}
-       echo $(md5sum "$img" | awk '{printf $1}') $(basename "$img")
+       echo $("$TS_HELPER_MD5" < "$img") $(basename "$img")
 }
 
 function ts_image_init {
@@ -500,16 +626,34 @@ function ts_device_deinit {
        fi
 }
 
+function ts_blkidtag_by_devname()
+{
+       local tag=$1
+       local dev=$2
+       local out
+       local rval
+
+       out=$($TS_CMD_BLKID -p -s "$tag" -o value "$dev")
+       rval=$?
+       printf "%s\n" "$out"
+
+       test -n "$out" -a "$rval" = "0"
+       return $?
+}
+
 function ts_uuid_by_devname {
-       echo $($TS_CMD_BLKID -p -s UUID -o value $1)
+       ts_blkidtag_by_devname "UUID" "$1"
+       return $?
 }
 
 function ts_label_by_devname {
-       echo $($TS_CMD_BLKID -p -s LABEL -o value $1)
+       ts_blkidtag_by_devname "LABEL" "$1"
+       return $?
 }
 
 function ts_fstype_by_devname {
-       echo $($TS_CMD_BLKID -p -s TYPE -o value $1)
+       ts_blkidtag_by_devname "TYPE" "$1"
+       return $?
 }
 
 function ts_device_has {
@@ -518,24 +662,24 @@ function ts_device_has {
        local DEV="$3"
        local vl=""
 
-       case $TAG in
-               "TYPE") vl=$(ts_fstype_by_devname $DEV);;
-               "LABEL") vl=$(ts_label_by_devname $DEV);;
-               "UUID") vl=$(ts_uuid_by_devname $DEV);;
-               *) return 1;;
-       esac
-
-       if [ "$vl" == "$VAL" ]; then
-               return 0
-       fi
-       return 1
+       vl=$(ts_blkidtag_by_devname "$TAG" "$DEV")
+       test $? = 0 -a "$vl" = "$VAL"
+       return $?
 }
 
-function ts_device_has_uuid {
-       ts_uuid_by_devname "$1" | egrep -q '^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$'
+function ts_is_uuid()
+{
+       printf "%s\n" "$1" | egrep -q '^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$'
        return $?
 }
 
+function ts_udevadm_settle()
+{
+       local dev=$1 # optional, might be empty
+       shift        # all other args are tags, LABEL, UUID, ...
+       udevadm settle
+}
+
 function ts_mount {
        local out
        local result
@@ -563,10 +707,10 @@ function ts_mount {
 function ts_is_mounted {
        local DEV=$(ts_canonicalize "$1")
 
-       grep -q $DEV /proc/mounts && return 0
+       grep -q "\(^\| \)$DEV " /proc/mounts && return 0
 
        if [ "${DEV#/dev/loop/}" != "$DEV" ]; then
-               grep -q "/dev/loop${DEV#/dev/loop/}" /proc/mounts && return 0
+               grep -q "^/dev/loop${DEV#/dev/loop/} " /proc/mounts && return 0
        fi
        return 1
 }
@@ -577,6 +721,7 @@ function ts_fstab_open {
 
 function ts_fstab_close {
        echo "# -->" >> /etc/fstab
+       sync /etc/fstab 2>/dev/null
 }
 
 function ts_fstab_addline {
@@ -588,13 +733,19 @@ function ts_fstab_addline {
        echo "$SPEC   $MNT   $FS   $OPT   0   0" >> /etc/fstab
 }
 
+function ts_fstab_lock {
+       ts_lock "fstab"
+}
+
 function ts_fstab_add {
+       ts_fstab_lock
        ts_fstab_open
        ts_fstab_addline $*
        ts_fstab_close
 }
 
 function ts_fstab_clean {
+       ts_have_lock "fstab" || return 0
        sed --in-place "
 /# <!-- util-linux/!b
 :a
@@ -604,51 +755,195 @@ function ts_fstab_clean {
 }
 s/# <!-- util-linux.*-->//;
 /^$/d" /etc/fstab
+
+       sync /etc/fstab 2>/dev/null
+       ts_unlock "fstab"
 }
 
 function ts_fdisk_clean {
        local DEVNAME=$1
 
        # remove non comparable parts of fdisk output
-       if [ x"${DEVNAME}" != x"" ]; then
-               sed -i -e "s:${DEVNAME}:<removed>:g;" $TS_OUTPUT
+       if [ -n "${DEVNAME}" ]; then
+               sed -i -e "s@${DEVNAME}@<removed>@;" $TS_OUTPUT $TS_ERRLOG
+       fi
+
+       sed -i \
+               -e 's/Disk identifier:.*/Disk identifier: <removed>/' \
+               -e 's/Created a new.*/Created a new <removed>./' \
+               -e 's/^Device[[:blank:]]*Start/Device             Start/' \
+               -e 's/^Device[[:blank:]]*Boot/Device     Boot/' \
+               -e 's/Welcome to fdisk.*/Welcome to fdisk <removed>./' \
+               -e 's/typescript file.*/typescript file <removed>./' \
+               -e 's@^\(I/O size (minimum/op.* bytes /\) [1-9][0-9]* @\1 <removed> @' \
+               $TS_OUTPUT $TS_ERRLOG
+}
+
+
+# https://stackoverflow.com/questions/41603787/how-to-find-next-available-file-descriptor-in-bash
+function ts_find_free_fd()
+{
+       local rco
+       local rci
+       for fd in {3..200}; do
+               rco="$(true 2>/dev/null >&${fd}; echo $?)"
+               rci="$(true 2>/dev/null <&${fd}; echo $?)"
+               if [[ "${rco}${rci}" = "11" ]]; then
+                       echo "$fd"
+                       return 0
+               fi
+       done
+       return 1
+}
+
+function ts_get_lock_fd {
+       local resource=$1
+       local fd
+
+       for fd in "${!TS_LOCKFILE_FD[@]}"; do
+               if [ "${TS_LOCKFILE_FD["$fd"]}" = "$resource" ]; then
+                       echo "$fd"
+                       return 0
+               fi
+       done
+       return 1
+}
+
+function ts_have_lock {
+       local resource=$1
+
+       test "$TS_NOLOCKS" = "yes" && return 0
+       ts_get_lock_fd "$resource" >/dev/null && return 0
+       return 1
+}
+
+function ts_lock {
+       local resource="$1"
+       local lockfile="${TS_LOCKDIR}/${resource}.lock"
+       local fd
+
+       if [ "$TS_NOLOCKS" == "yes" ]; then
+               return 0
        fi
 
-       sed -i -e 's/Disk identifier:.*/Disk identifier: <removed>/g' \
-              -e 's/Created a new.*/Created a new <removed>./g' \
-              -e 's/^Device[[:blank:]]*Start/Device             Start/g' \
-              -e 's/^Device[[:blank:]]*Boot/Device     Boot/g' \
-              -e 's/^Device[[:blank:]]*Flag/Device     Flag/g' \
-              -e 's/Welcome to fdisk.*/Welcome to fdisk <removed>./g' \
-              -e 's/typescript file.*/typescript file <removed>./g' \
-              $TS_OUTPUT
+       # Don't lock again
+       fd=$(ts_get_lock_fd "$resource")
+       if [ -n "$fd" ]; then
+               echo "[$$ $TS_TESTNAME] ${resource} already locked!"
+               return 0
+       fi
+
+       fd=$(ts_find_free_fd) || ts_skip "failed to find lock fd"
+
+       eval "exec $fd>$lockfile"
+       flock --exclusive "$fd" || ts_skip "failed to lock $resource"
+
+       TS_LOCKFILE_FD["$fd"]="$resource"
+       ###echo "[$$ $TS_TESTNAME] Locked   $resource"
+}
+
+function ts_unlock {
+       local resource="$1"
+       local lockfile="${TS_LOCKDIR}/${resource}.lock"
+       local fd
+
+       if [ "$TS_NOLOCKS" == "yes" ]; then
+               return 0
+       fi
+
+       fd=$(ts_get_lock_fd "$resource")
+       if [ -n "$fd" ]; then
+               eval "exec $fd<&-"
+               TS_LOCKFILE_FD["$fd"]=""
+               ###echo "[$$ $TS_TESTNAME] Unlocked $resource"
+       else
+               echo "[$$ $TS_TESTNAME] unlocking unlocked $resource!?"
+       fi
 }
 
 function ts_scsi_debug_init {
        local devname
+       local t
        TS_DEVICE="none"
 
+       ts_lock "scsi_debug"
+
        # dry run is not really reliable, real modprobe may still fail
        modprobe --dry-run --quiet scsi_debug &>/dev/null \
                || ts_skip "missing scsi_debug module (dry-run)"
 
        # skip if still in use or removal of modules not supported at all
-       modprobe -r scsi_debug &>/dev/null \
-               || ts_skip "cannot remove scsi_debug module (rmmod)"
+       # We don't want a slow timeout here so we don't use ts_scsi_debug_rmmod!
+       modprobe -r scsi_debug &>/dev/null
+       if [ "$?" -eq 1 ]; then
+               ts_unlock "scsi_debug"
+               ts_skip "cannot remove scsi_debug module (rmmod)"
+       fi
 
        modprobe -b scsi_debug "$@" &>/dev/null \
                || ts_skip "cannot load scsi_debug module (modprobe)"
 
        # it might be still not loaded, modprobe.conf or whatever
-       lsmod | grep -q "^scsi_debug " \
+       lsmod 2>/dev/null | grep -q "^scsi_debug " \
                || ts_skip "scsi_debug module not loaded (lsmod)"
 
        udevadm settle
 
-       devname=$(grep --with-filename scsi_debug /sys/block/*/device/model | awk -F '/' '{print $4}')
-       [ "x${devname}" == "x" ] && ts_die "cannot find scsi_debug device"
+       # wait for device if udevadm settle does not work
+       for t in 0 0.02 0.05 0.1 1; do
+               sleep $t
+               devname=$(grep --with-filename scsi_debug /sys/block/*/device/model) && break
+       done
+       [ -n "${devname}" ] || ts_die "timeout waiting for scsi_debug device"
 
+       devname=$(echo $devname | awk -F '/' '{print $4}')
        TS_DEVICE="/dev/${devname}"
+
+       # TODO validate that device is really up, for now just a warning on stderr
+       test -b $TS_DEVICE || echo "warning: scsi_debug device is still down" >&2
+}
+
+# automatically called once in ts_cleanup_on_exit()
+function ts_scsi_debug_rmmod {
+       local err=1
+       local t
+       local lastmsg
+
+       # We must not run if we don't have the lock
+       ts_have_lock "scsi_debug" || return 0
+
+       # Return early most importantly in case we are not root or the module does
+       # not exist at all.
+       [ $UID -eq 0 ] || return 0
+       [ -n "$TS_DEVICE" ] || return 0
+       lsmod 2>/dev/null | grep -q "^scsi_debug " || return 0
+
+       udevadm settle
+
+       # wait for successful rmmod if udevadm settle does not work
+       for t in 0 0.02 0.05 0.1 1; do
+               sleep $t
+               lastmsg="$(modprobe -r scsi_debug 2>&1)" && err=0 && break
+       done
+
+       if [ "$err" = "1" ]; then
+               ts_log "rmmod failed: '$lastmsg'"
+               ts_log "timeout removing scsi_debug module (rmmod)"
+               return 1
+       fi
+       if lsmod | grep -q "^scsi_debug "; then
+               ts_log "BUG! scsi_debug still loaded"
+               return 1
+       fi
+
+       # TODO Do we need to validate that all devices are gone?
+       udevadm settle
+       test -b "$TS_DEVICE" && echo "warning: scsi_debug device is still up" >&2
+
+       # TODO unset TS_DEVICE, check that nobody uses it later, e.g. ts_fdisk_clean
+
+       ts_unlock "scsi_debug"
+       return 0
 }
 
 function ts_resolve_host {
@@ -683,22 +978,41 @@ function ts_init_socket_to_file {
        ts_check_prog "socat"
        rm -f "$socket" "$outfile"
 
+       # if socat is too old for these options we'll skip it below
        socat -u UNIX-LISTEN:$socket,fork,max-children=1,backlog=128 \
-               STDOUT > "$outfile" &
+               STDOUT > "$outfile" 2>/dev/null &
        pid=$!
 
        # check for running background process
-       if [ "$pid" -le "0" ] || ! kill -s 0 "$pid"; then
+       if [ "$pid" -le "0" ] || ! kill -s 0 "$pid" &>/dev/null; then
                ts_skip "unable to run socat"
        fi
        # wait for the socket listener
-       if ! socat -u /dev/null UNIX-CONNECT:$socket,retry=30,interval=0.1; then
-               kill -9 "$pid"
-               ts_skip "timeout waiting for socket"
+       if ! socat -u /dev/null UNIX-CONNECT:$socket,retry=30,interval=0.1 &>/dev/null; then
+               kill -9 "$pid" &>/dev/null
+               ts_skip "timeout waiting for socat socket"
        fi
        # check socket again
-       if ! socat -u /dev/null UNIX-CONNECT:$socket; then
-               kill -9 "$pid"
-               ts_skip "socket stopped listening"
+       if ! socat -u /dev/null UNIX-CONNECT:$socket &>/dev/null; then
+               kill -9 "$pid" &>/dev/null
+               ts_skip "socat socket stopped listening"
+       fi
+}
+
+function ts_has_mtab_support {
+       grep -q '#define USE_LIBMOUNT_SUPPORT_MTAB' ${top_builddir}/config.h
+       if [ $? == 0 ]; then
+               echo "yes"
+       else
+               echo "no"
+       fi
+}
+
+function ts_has_ncurses_support {
+       grep -q '#define HAVE_LIBNCURSES' ${top_builddir}/config.h
+       if [ $? == 0 ]; then
+               echo "yes"
+       else
+               echo "no"
        fi
 }