#!/bin/bash
#
# Copyright (c) International Business Machines Corp., 2005
# Authors: Avantika Mathur (mathurav@us.ibm.com)
# Matt Helsley (matthltc@us.ibm.com)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
if tst_kvercmp 2 6 15 ; then
tst_resm TCONF "System kernel version is less than 2.6.15"
tst_resm TCONF "Cannot execute test"
exit 0
fi
test_setup()
{
#######################################################################
## Configure
#######################################################################
dopts='-dEBb'
## Remove logged test state depending on results. 0 means do not remove,
## 1 means OK to remove.
# rm saved state from tests that appear to have cleaned up properly?
rm_ok=1
# rm saved state from tests that don't appear to have fully cleaned up?
rm_err=0
#######################################################################
## Initialize some variables
#######################################################################
TCID="$0"
TST_COUNT=0
test_dirs=( move bind rbind regression ) #cloneNS
nfailed=0
nsucceeded=0
# set the LTPROOT directory
cd `dirname $0`
LTPROOT="${PWD}"
echo "${LTPROOT}" | grep testscripts > /dev/null 2>&1
if [ $? -eq 0 ]; then
cd ..
LTPROOT="${PWD}"
fi
FS_BIND_ROOT="${LTPROOT}/testcases/bin/fs_bind"
total=0 # total number of tests
for dir in "${test_dirs[@]}" ; do
((total += `ls "${FS_BIND_ROOT}/${dir}/test"* | wc -l`))
done
TST_TOTAL=${total}
# set the PATH to include testcases/bin
LTPBIN="${LTPROOT}/testcases/bin"
PATH="${PATH}:/usr/sbin:${LTPBIN}:${FS_BIND_ROOT}/bin"
# Results directory
resdir="${LTPROOT}/results/fs_bind"
if [ ! -d "${resdir}" ]; then
mkdir -p "${resdir}" 2> /dev/null
fi
TMPDIR="${TMPDIR:-/tmp}"
# A temporary directory where we can do stuff and that is
# safe to remove
sandbox="${TMPDIR}/sandbox"
ERR_MSG=""
export LTPBIN PATH FS_BIND_ROOT ERR_MSG TCID TST_COUNT TST_TOTAL
if [ ! -d "${resdir}" ]; then
tst_brkm TBROK true "$0: failed to make results directory"
exit 1
fi
}
test_prereqs()
{
# Must be root to run the containers testsuite
if [ $UID != 0 ]; then
tst_brkm TBROK true "FAILED: Must be root to execute this script"
exit 1
fi
mkdir "${sandbox}" >& /dev/null
if [ ! -d "${sandbox}" -o ! -x "${sandbox}" ]; then
tst_brkm TBROK true "$0: failed to make directory \"${sandbox}\""
exit -1
fi
mount --bind "${sandbox}" "${sandbox}" >& /dev/null
if [ $? -ne 0 ]; then
tst_brkm TBROK true "$0: failed to perform bind mount on directory \"${sandbox}\""
exit 1
fi
mount --make-private "${sandbox}" >& /dev/null
if [ $? -ne 0 ]; then
tst_brkm TBROK true "$0: failed to make private mountpoint on directory \"${sandbox}\""
exit 1
fi
local mnt_bind=1
local mnt_move=1
pushd "${sandbox}" > /dev/null && {
mkdir bind_test move_test && {
mount --bind bind_test bind_test && {
mnt_bind=0
mount --move bind_test move_test && {
mnt_move=0
umount move_test
} || {
# bind mount succeeded but move mount
# failed
umount bind_test
}
} || {
# mount failed -- check if it's because we
# don't have privileges we need
if [ $? -eq 32 ]; then
tst_brkm TBROK true "$0 requires the privilege to use the mount command"
exit 32
fi
}
rmdir bind_test move_test
}
popd > /dev/null
}
if [ ${mnt_bind} -eq 1 -o ${mnt_move} -eq 1 ]; then
tst_brkm TBROK true "$0: requires that mount support the --bind and --move options"
exit 1
fi
tst_kvercmp 2 6 15
X=$?
if [ $X -lt 0 ]; then
tst_brkm TBROK "$0: failed to get the running kernel version"
exit 1
elif [ $X -lt 1 ]; then
tst_resm TWARN "$0: the remaining tests require 2.6.15 or later"
tst_exit 0
exit
else
tst_resm TINFO "$0: kernel >= 2.6.15 detected -- continuing"
fi
mount --make-shared "${sandbox}" > /dev/null 2>&1 || "${FS_BIND_ROOT}/bin/smount" "${sandbox}" shared
umount "${sandbox}" || {
tst_resm TFAIL "$0: failed to umount simplest shared subtree"
exit 1
}
tst_resm TPASS "$0: umounted simplest shared subtree"
}
# mounts we are concerned with in a well-defined order (helps diff)
# returns grep return codes
grep_proc_mounts()
{
local rc=0
# Save the pipefail shell option
shopt -o -q pipefail
local save=$?
set -o pipefail
# Grep /proc/mounts which is often more up-to-date than mounts
# We use pipefail because if the grep fails we want to pass that along
grep -F "${sandbox}" /proc/mounts | sort -b
rc=$?
# Restore the pipefail shell options
[ $save -eq 0 ] && shopt -o -s pipefail || shopt -o -u pipefail
return $rc
}
# Record the mount state
save_proc_mounts()
{
touch "$2/proc_mounts.before" >& /dev/null
if [ $? -ne 0 ]; then
tst_brkm TBROK true "$1: failed to record proc mounts"
return 1
fi
grep_proc_mounts 2> /dev/null > "$2/proc_mounts.before"
return 0
}
# Compare mount list after the test with the list from before.
# If there are no differences then remove the before list and silently
# return 0. Else print the differences to stderr and return 1.
check_proc_mounts()
{
local tname="$1"
if [ ! -r "$2/proc_mounts.before" ]; then
tst_brkm TBROK true "${tname}: Could not find pre-test proc mount list"
return 1
fi
grep_proc_mounts 2> /dev/null > "$2/proc_mounts.after"
# If the mounts are the same then just return
diff ${dopts} -q "$2/proc_mounts.before" "$2/proc_mounts.after" >& /dev/null
if [ $? -eq 0 ]; then
[ $rm_ok -eq 1 ] && rm -f "$2/proc_mounts."{before,after}
return 0
fi
tst_resm TWARN "${tname}: did not properly clean up its proc mounts"
diff ${dopts} -U 0 "$2/proc_mounts.before" "$2/proc_mounts.after" | grep -vE '^\@\@' 1>&2
[ $rm_err -eq 1 ] && rm -f "$2/proc_mounts."{before,after}
return 1
}
# Undo leftover mounts
restore_proc_mounts()
{
#local tname="$1"
# do lazy umounts -- we're assuming that tests will only leave
# new mounts around and will never remove mounts outside the test
# directory
( while grep_proc_mounts ; do
grep_proc_mounts | awk '{print $2}' | xargs -r -n 1 umount -l
done ) >& /dev/null
# mount list and exit with 0
[ $rm_err -eq 1 ] && rm -f "$2/proc_mounts."{before,after} 1>&2 # >& /dev/null
return 0
# if returning error do this:
# tst_brkm TBROK true "${tname}: failed to restore mounts"
}
# mounts we are concerned with in a well-defined order (helps diff)
# returns grep return codes
grep_mounts()
{
local rc=0
# Save the pipefail shell option
shopt -o -q pipefail
local save=$?
set -o pipefail
# Grep mount command output (which tends to come from /etc/mtab)
# We use pipefail because if the grep fails we want to pass that along
mount | grep -F "${sandbox}" | sort -b
rc=$?
# Restore the pipefail shell options
[ $save -eq 0 ] && shopt -o -s pipefail || shopt -o -u pipefail
return $rc
}
# Record the mount state
save_mounts()
{
touch "$2/mtab.before" >& /dev/null
if [ $? -ne 0 ]; then
tst_brkm TBROK true "$1: failed to record mtab mounts"
return 1
fi
grep_mounts 2> /dev/null > "$2/mtab.before"
return 0
}
# Compare mount list after the test with the list from before.
# If there are no differences then remove the before list and silently
# return 0. Else print the differences to stderr and return 1.
check_mounts()
{
local tname="$1"
if [ ! -r "$2/mtab.before" ]; then
tst_brkm TBROK true "${tname}: Could not find pre-test mtab mount list"
return 1
fi
grep_mounts 2> /dev/null > "$2/mtab.after"
# If the mounts are the same then just return
diff ${dopts} -q "$2/mtab.before" "$2/mtab.after" >& /dev/null
if [ $? -eq 0 ]; then
[ $rm_ok -eq 1 ] && rm -f "$2/mtab."{before,after}
return 0
fi
tst_resm TWARN "${tname}: did not properly clean up its mtab mounts"
diff ${dopts} -U 0 "$2/mtab.before" "$2/mtab.after" | grep -vE '^\@\@' 1>&2
[ $rm_err -eq 1 ] && rm -f "$2/mtab."{before,after}
return 1
}
# Undo leftover mounts
restore_mounts()
{
#local tname="$1"
# do lazy umounts -- we're assuming that tests will only leave
# new mounts around and will never remove mounts outside the test
# directory
( while grep_mounts ; do
grep_mounts | awk '{print $3}' | xargs -r -n 1 umount -l
done ) >& /dev/null
# mount list and exit with 0
[ $rm_err -eq 1 ] && rm -f "$2/mtab."{before,after} 1>&2 # >& /dev/null
return 0
# if returning error do this:
# tst_brkm TBROK true "${tname}: failed to restore mounts"
}
# Record the sandbox state
# We don't save full sandbox state -- just the names of files and dirs present
save_sandbox()
{
local when="before"
local tname="$1"
if [ -e "$2/files.before" ]; then
if [ -e "$2/files.after" ]; then
tst_brkm TBROK true "${tname}: stale catalog of \"${sandbox}\""
return 1
fi
when="after"
fi
( find "${sandbox}" -type d -print | sort > "$2/dirs.$when"
find "${sandbox}" -type f -print | sort | \
grep -vE '^'"$2"'/(dirs|files)\.(before|after)$' > "$2/files.$when" ) >& /dev/null
return 0
}
# Save sandbox after test and then compare. If the sandbox state is not
# clean then print the differences to stderr and return 1. Else remove all
# saved sandbox state and silently return 0
check_sandbox()
{
local tname="$1"
if [ ! -r "$2/files.before" -o ! -r "$2/dirs.before" ]; then
tst_brkm TBROK true "${tname} missing saved catalog of \"${sandbox}\""
return 1
fi
save_sandbox "${tname} (check)" "$2"
( diff ${dopts} -q "$2/dirs.before" "$2/dirs.after" && \
diff ${dopts} -q "$2/files.before" "$2/files.after" ) >& /dev/null \
&& {
[ $rm_ok -eq 1 ] && rm -f "$2/"{files,dirs}.{before,after}
return 0
}
tst_resm TWARN "${tname} did not properly clean up \"${sandbox}\""
diff ${dopts} -U 0 "$2/dirs.before" "$2/dirs.after" 1>&2
diff ${dopts} -U 0 "$2/files.before" "$2/files.after" 1>&2
[ $rm_err -eq 1 ] && rm -f "$2/"{files,dirs}.{before,after} 1>&2
return 1
}
# Robust sandbox cleanup
clean_sandbox()
{
local tname="$1"
{ rm -rf "${sandbox}" ; mkdir "${sandbox}" ; } >& /dev/null
if [ ! -d "${sandbox}" -o ! -x "${sandbox}" ]; then
tst_brkm TBROK true "$tname: failed to make directory \"${sandbox}\""
return 1
fi
return 0
}
# Check file for non-whitespace chars
is_file_empty()
{
awk '/^[[:space:]]*$/ { next }
{ exit 1; }' < "$1"
}
#
# Run the specified test script.
#
# Return 1 if the test was broken but should not stop the remaining test
# categories from being run.
# Return 2 if the test was broken and no further tests should be run.
# Return 0 otherwise (if the test was broken but it shouldn't affect other
# test runs)
# Note that this means the return status is not the success or failure of the
# test itself.
#
run_test()
{
local t="$1"
local tname="$(basename "$(dirname "$t")")/$(basename "$t")"
local log="$resdir/$tname/log"
local errlog="$resdir/$tname/err"
local do_break=0
ERR_MSG=""
# Pre-test
mkdir -p "$resdir/$tname"
if [ ! -d "$resdir/$tname" -o ! -x "$resdir/$tname" ]; then
tst_brkm TBROK true "$0: can't make or use \"$resdir/$tname\" as a log directory"
return 1
fi
save_sandbox "$tname" "$resdir/$tname" || do_break=1
save_mounts "$tname" "$resdir/$tname" || do_break=1
save_proc_mounts "$tname" "$resdir/$tname" || do_break=1
mount --bind "${sandbox}" "${sandbox}" >& /dev/null || do_break=1
mount --make-private "${sandbox}" >& /dev/null || do_break=1
if [ $do_break -eq 1 ]; then
tst_brkm TBROK true "$tname: failed to save pre-test state of \"${sandbox}\""
return 2
fi
pushd "${sandbox}" > /dev/null
# Run the test
(
TCID="$tname"
declare -r TST_COUNT
export LTPBIN PATH FS_BIND_ROOT ERR_MSG TCID TST_COUNT TST_TOTAL
"$t" #> "$log" 2> "$errlog"
)
local rc=$?
TCID="$0"
# Post-test
popd > /dev/null
if [ $rc -ne 0 ]; then
#echo "FAILED"
((nfailed++))
else
#echo "SUCCEEDED"
((nsucceeded++))
fi
umount -l "${sandbox}" >& /dev/null
check_proc_mounts "$tname" "$resdir/$tname" || \
restore_proc_mounts "$tname" "$resdir/$tname" || do_break=1
check_mounts "$tname" "$resdir/$tname" || \
restore_mounts "$tname" "$resdir/$tname" || do_break=1
check_sandbox "$tname" "$resdir/$tname"
clean_sandbox "$tname" || do_break=1
if [ $do_break -eq 1 ]; then
tst_brkm TBROK true "$tname: failed to restore pre-test state of \"${sandbox}\""
return 2
fi
# If we succeeded and the error log is empty remove it
if [ $rc -eq 0 -a -w "$errlog" ] && is_file_empty "$errlog" ; then
rm -f "$errlog"
fi
return 0
}
main()
{
TST_COUNT=1
for dir in "${test_dirs[@]}" ; do
tests=( $(find "${FS_BIND_ROOT}/${dir}" -type f -name 'test*') )
clean_sandbox "$0" || break
for t in "${tests[@]}" ; do
run_test "$t"
local rc=$?
if [ $rc -ne 0 ]; then
break $rc
fi
((TST_COUNT++))
done
done
rm -rf "${sandbox}"
return 0
skipped=$((total - nsucceeded - nfailed))
if [ $nfailed -eq 0 -a $skipped -eq 0 ]; then
# Use PASSED for the summary rather than SUCCEEDED to make it
# easy to determine 100% success from a calling script
summary="PASSED"
else
# Use FAILED to make it easy to find > 0% failure from a
# calling script
summary="FAILED"
fi
cat - <<-EOF
*********************************
RESULTS SUMMARY:
passed: $nsucceeded/$total
failed: $nfailed/$total
skipped: $skipped/$total
summary: $summary
*********************************
EOF
}
test_setup || exit 1
test_prereqs || exit 1
declare -r FS_BIND_ROOT
declare -r TST_TOTAL
main #2> "$resdir/errors" 1> "$resdir/summary"