1#!/bin/bash
2
3#
4# Copyright (c) International Business Machines  Corp., 2005
5# Authors: Avantika Mathur (mathurav@us.ibm.com)
6#          Matt Helsley (matthltc@us.ibm.com)
7#
8# This library is free software; you can redistribute it and/or
9# modify it under the terms of the GNU Lesser General Public
10# License as published by the Free Software Foundation; either
11# version 2.1 of the License, or (at your option) any later version.
12#
13# This library is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16# Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public
19# License along with this library; if not, write to the Free Software
20# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21#
22
23if tst_kvercmp 2 6 15 ; then
24       tst_resm TCONF "System kernel version is less than 2.6.15"
25       tst_resm TCONF "Cannot execute test"
26       exit 0
27fi
28
29
30test_setup()
31{
32	#######################################################################
33	## Configure
34	#######################################################################
35	dopts='-dEBb'
36
37	## Remove logged test state depending on results. 0 means do not remove,
38	## 1 means OK to remove.
39	# rm saved state from tests that appear to have cleaned up properly?
40	rm_ok=1
41	# rm saved state from tests that don't appear to have fully cleaned up?
42	rm_err=0
43
44	#######################################################################
45	## Initialize some variables
46	#######################################################################
47	TCID="$0"
48	TST_COUNT=0
49
50	test_dirs=( move bind rbind regression )  #cloneNS
51	nfailed=0
52	nsucceeded=0
53
54	# set the LTPROOT directory
55	cd `dirname $0`
56	LTPROOT="${PWD}"
57	echo "${LTPROOT}" | grep testscripts > /dev/null 2>&1
58	if [ $? -eq 0 ]; then
59		cd ..
60		LTPROOT="${PWD}"
61	fi
62
63	FS_BIND_ROOT="${LTPROOT}/testcases/bin/fs_bind"
64
65	total=0 # total number of tests
66	for dir in "${test_dirs[@]}" ; do
67		((total += `ls "${FS_BIND_ROOT}/${dir}/test"* | wc -l`))
68	done
69	TST_TOTAL=${total}
70
71	# set the PATH to include testcases/bin
72	LTPBIN="${LTPROOT}/testcases/bin"
73	PATH="${PATH}:/usr/sbin:${LTPBIN}:${FS_BIND_ROOT}/bin"
74
75	# Results directory
76	resdir="${LTPROOT}/results/fs_bind"
77	if [ ! -d "${resdir}" ]; then
78		mkdir -p "${resdir}" 2> /dev/null
79	fi
80
81	TMPDIR="${TMPDIR:-/tmp}"
82	# A temporary directory where we can do stuff and that is
83	# safe to remove
84	sandbox="${TMPDIR}/sandbox"
85
86	ERR_MSG=""
87
88	export LTPBIN PATH FS_BIND_ROOT ERR_MSG TCID TST_COUNT TST_TOTAL
89
90	if [ ! -d "${resdir}" ]; then
91		tst_brkm TBROK true "$0: failed to make results directory"
92		exit 1
93	fi
94}
95
96test_prereqs()
97{
98	# Must be root to run the containers testsuite
99	if [ $UID != 0 ]; then
100		tst_brkm TBROK true "FAILED: Must be root to execute this script"
101		exit 1
102	fi
103
104	mkdir "${sandbox}" >& /dev/null
105	if [ ! -d "${sandbox}" -o ! -x "${sandbox}" ]; then
106		tst_brkm TBROK true "$0: failed to make directory \"${sandbox}\""
107		exit -1
108	fi
109
110	mount --bind "${sandbox}" "${sandbox}" >& /dev/null
111	if [ $? -ne 0 ]; then
112		tst_brkm TBROK true "$0: failed to perform bind mount on directory \"${sandbox}\""
113		exit 1
114	fi
115
116	mount --make-private "${sandbox}" >& /dev/null
117	if [ $? -ne 0 ]; then
118		tst_brkm TBROK true "$0: failed to make private mountpoint on directory \"${sandbox}\""
119		exit 1
120	fi
121
122	local mnt_bind=1
123	local mnt_move=1
124
125	pushd "${sandbox}" > /dev/null && {
126		mkdir bind_test move_test && {
127			mount --bind bind_test bind_test && {
128				mnt_bind=0
129				mount --move bind_test move_test && {
130					mnt_move=0
131					umount move_test
132				} || {
133					# bind mount succeeded but move mount
134					# failed
135					umount bind_test
136				}
137			} || {
138				# mount failed -- check if it's because we
139				# don't have privileges we need
140				if [ $? -eq 32 ]; then
141					tst_brkm TBROK true "$0 requires the privilege to use the mount command"
142					exit 32
143				fi
144			}
145			rmdir bind_test move_test
146		}
147		popd > /dev/null
148	}
149
150	if [ ${mnt_bind} -eq 1 -o ${mnt_move} -eq 1 ]; then
151		tst_brkm TBROK true "$0: requires that mount support the --bind and --move options"
152		exit 1
153	fi
154
155	tst_kvercmp 2 6 15
156	X=$?
157	if [ $X -lt 0 ]; then
158		tst_brkm TBROK "$0: failed to get the running kernel version"
159		exit 1
160	elif [ $X -lt 1 ]; then
161		tst_resm TWARN "$0: the remaining tests require 2.6.15 or later"
162		tst_exit 0
163		exit
164	else
165		tst_resm TINFO "$0: kernel >= 2.6.15 detected -- continuing"
166	fi
167
168	mount --make-shared "${sandbox}" > /dev/null 2>&1 || "${FS_BIND_ROOT}/bin/smount" "${sandbox}" shared
169	umount "${sandbox}" || {
170		tst_resm TFAIL "$0: failed to umount simplest shared subtree"
171		exit 1
172	}
173	tst_resm TPASS "$0: umounted simplest shared subtree"
174
175}
176
177# mounts we are concerned with in a well-defined order (helps diff)
178# returns grep return codes
179grep_proc_mounts()
180{
181	local rc=0
182
183	# Save the pipefail shell option
184	shopt -o -q pipefail
185	local save=$?
186	set -o pipefail
187
188	# Grep /proc/mounts which is often more up-to-date than mounts
189	# We use pipefail because if the grep fails we want to pass that along
190	grep -F "${sandbox}" /proc/mounts | sort -b
191	rc=$?
192
193	# Restore the pipefail shell options
194	[ $save -eq 0 ] && shopt -o -s pipefail || shopt -o -u pipefail
195
196	return $rc
197}
198
199# Record the mount state
200save_proc_mounts()
201{
202	touch "$2/proc_mounts.before" >& /dev/null
203	if [ $? -ne 0 ]; then
204		tst_brkm TBROK true "$1: failed to record proc mounts"
205		return 1
206	fi
207
208	grep_proc_mounts 2> /dev/null > "$2/proc_mounts.before"
209	return 0
210}
211
212# Compare mount list after the test with the list from before.
213# If there are no differences then remove the before list and silently
214# return 0. Else print the differences to stderr and return 1.
215check_proc_mounts()
216{
217	local tname="$1"
218
219	if [ ! -r "$2/proc_mounts.before" ]; then
220		tst_brkm TBROK true "${tname}: Could not find pre-test proc mount list"
221		return 1
222	fi
223
224	grep_proc_mounts 2> /dev/null > "$2/proc_mounts.after"
225	# If the mounts are the same then just return
226	diff ${dopts} -q "$2/proc_mounts.before" "$2/proc_mounts.after" >& /dev/null
227	if [ $? -eq 0 ]; then
228		[ $rm_ok -eq 1 ] && rm -f "$2/proc_mounts."{before,after}
229		return 0
230	fi
231
232	tst_resm TWARN "${tname}: did not properly clean up its proc mounts"
233	diff ${dopts} -U 0 "$2/proc_mounts.before" "$2/proc_mounts.after" | grep -vE '^\@\@' 1>&2
234	[ $rm_err -eq 1 ] && rm -f "$2/proc_mounts."{before,after}
235	return 1
236}
237
238# Undo leftover mounts
239restore_proc_mounts()
240{
241	#local tname="$1"
242
243	# do lazy umounts -- we're assuming that tests will only leave
244	# new mounts around and will never remove mounts outside the test
245	# directory
246	( while grep_proc_mounts ; do
247		grep_proc_mounts | awk '{print $2}' | xargs -r -n 1 umount -l
248	done ) >& /dev/null
249
250	# mount list and exit with 0
251	[ $rm_err -eq 1 ] && rm -f "$2/proc_mounts."{before,after} 1>&2 # >& /dev/null
252	return 0
253	# if returning error do this:
254	# tst_brkm TBROK true "${tname}: failed to restore mounts"
255}
256
257# mounts we are concerned with in a well-defined order (helps diff)
258# returns grep return codes
259grep_mounts()
260{
261	local rc=0
262
263	# Save the pipefail shell option
264	shopt -o -q pipefail
265	local save=$?
266	set -o pipefail
267
268	# Grep mount command output (which tends to come from /etc/mtab)
269	# We use pipefail because if the grep fails we want to pass that along
270	mount | grep -F "${sandbox}" | sort -b
271	rc=$?
272
273	# Restore the pipefail shell options
274	[ $save -eq 0 ] && shopt -o -s pipefail || shopt -o -u pipefail
275
276	return $rc
277}
278
279# Record the mount state
280save_mounts()
281{
282	touch "$2/mtab.before" >& /dev/null
283	if [ $? -ne 0 ]; then
284		tst_brkm TBROK true "$1: failed to record mtab mounts"
285		return 1
286	fi
287
288	grep_mounts 2> /dev/null > "$2/mtab.before"
289	return 0
290}
291
292# Compare mount list after the test with the list from before.
293# If there are no differences then remove the before list and silently
294# return 0. Else print the differences to stderr and return 1.
295check_mounts()
296{
297	local tname="$1"
298
299	if [ ! -r "$2/mtab.before" ]; then
300		tst_brkm TBROK true "${tname}: Could not find pre-test mtab mount list"
301		return 1
302	fi
303
304	grep_mounts 2> /dev/null > "$2/mtab.after"
305	# If the mounts are the same then just return
306	diff ${dopts} -q "$2/mtab.before" "$2/mtab.after" >& /dev/null
307	if [ $? -eq 0 ]; then
308		[ $rm_ok -eq 1 ] && rm -f "$2/mtab."{before,after}
309		return 0
310	fi
311
312	tst_resm TWARN "${tname}: did not properly clean up its mtab mounts"
313	diff ${dopts} -U 0 "$2/mtab.before" "$2/mtab.after" | grep -vE '^\@\@' 1>&2
314	[ $rm_err -eq 1 ] && rm -f "$2/mtab."{before,after}
315	return 1
316}
317
318# Undo leftover mounts
319restore_mounts()
320{
321	#local tname="$1"
322
323	# do lazy umounts -- we're assuming that tests will only leave
324	# new mounts around and will never remove mounts outside the test
325	# directory
326	( while grep_mounts ; do
327		grep_mounts | awk '{print $3}' | xargs -r -n 1 umount -l
328	done ) >& /dev/null
329
330	# mount list and exit with 0
331	[ $rm_err -eq 1 ] && rm -f "$2/mtab."{before,after} 1>&2 # >& /dev/null
332	return 0
333	# if returning error do this:
334	# tst_brkm TBROK true "${tname}: failed to restore mounts"
335}
336
337# Record the sandbox state
338# We don't save full sandbox state -- just the names of files and dirs present
339save_sandbox()
340{
341	local when="before"
342	local tname="$1"
343
344	if [ -e "$2/files.before" ]; then
345		if [ -e "$2/files.after" ]; then
346			tst_brkm TBROK true "${tname}: stale catalog of \"${sandbox}\""
347			return 1
348		fi
349		when="after"
350	fi
351
352	( find "${sandbox}" -type d -print | sort > "$2/dirs.$when"
353	  find "${sandbox}" -type f -print | sort | \
354		grep -vE '^'"$2"'/(dirs|files)\.(before|after)$' > "$2/files.$when" ) >& /dev/null
355	return 0
356}
357
358# Save sandbox after test and then compare. If the sandbox state is not
359# clean then print the differences to stderr and return 1. Else remove all
360# saved sandbox state and silently return 0
361check_sandbox()
362{
363	local tname="$1"
364
365	if [ ! -r "$2/files.before" -o ! -r "$2/dirs.before" ]; then
366		tst_brkm TBROK true "${tname} missing saved catalog of \"${sandbox}\""
367		return 1
368	fi
369
370	save_sandbox "${tname} (check)" "$2"
371
372	( diff ${dopts} -q "$2/dirs.before" "$2/dirs.after" && \
373	  diff ${dopts} -q "$2/files.before" "$2/files.after" )  >& /dev/null \
374	  && {
375		[ $rm_ok -eq 1 ] && rm -f "$2/"{files,dirs}.{before,after}
376		return 0
377	}
378
379	tst_resm TWARN "${tname} did not properly clean up \"${sandbox}\""
380	diff ${dopts} -U 0 "$2/dirs.before" "$2/dirs.after" 1>&2
381	diff ${dopts} -U 0 "$2/files.before" "$2/files.after" 1>&2
382	[ $rm_err -eq 1 ] && rm -f "$2/"{files,dirs}.{before,after} 1>&2
383	return 1
384}
385
386# Robust sandbox cleanup
387clean_sandbox()
388{
389	local tname="$1"
390
391	{ rm -rf "${sandbox}" ; mkdir "${sandbox}" ; } >& /dev/null
392	if [ ! -d "${sandbox}" -o ! -x "${sandbox}" ]; then
393		tst_brkm TBROK true "$tname: failed to make directory \"${sandbox}\""
394		return 1
395	fi
396	return 0
397}
398
399# Check file for non-whitespace chars
400is_file_empty()
401{
402	awk '/^[[:space:]]*$/  { next }
403	      { exit 1; }' < "$1"
404}
405
406#
407# Run the specified test script.
408#
409# Return 1 if the test was broken but should not stop the remaining test
410#	categories from being run.
411# Return 2 if the test was broken and no further tests should be run.
412# Return 0 otherwise (if the test was broken but it shouldn't affect other
413#	test runs)
414# Note that this means the return status is not the success or failure of the
415#	test itself.
416#
417run_test()
418{
419	local t="$1"
420	local tname="$(basename "$(dirname "$t")")/$(basename "$t")"
421	local log="$resdir/$tname/log"
422	local errlog="$resdir/$tname/err"
423	local do_break=0
424
425	ERR_MSG=""
426
427	# Pre-test
428	mkdir -p "$resdir/$tname"
429	if [ ! -d "$resdir/$tname" -o ! -x "$resdir/$tname" ]; then
430		tst_brkm TBROK true "$0: can't make or use \"$resdir/$tname\" as a log directory"
431		return 1
432	fi
433
434	save_sandbox "$tname" "$resdir/$tname" || do_break=1
435	save_mounts "$tname" "$resdir/$tname" || do_break=1
436	save_proc_mounts "$tname" "$resdir/$tname" || do_break=1
437	mount --bind "${sandbox}" "${sandbox}" >& /dev/null || do_break=1
438	mount --make-private "${sandbox}" >& /dev/null || do_break=1
439
440	if [ $do_break -eq 1 ]; then
441		tst_brkm TBROK true "$tname: failed to save pre-test state of \"${sandbox}\""
442		return 2
443	fi
444	pushd "${sandbox}" > /dev/null
445
446	# Run the test
447	(
448		TCID="$tname"
449		declare -r TST_COUNT
450		export LTPBIN PATH FS_BIND_ROOT ERR_MSG TCID TST_COUNT TST_TOTAL
451		"$t" #> "$log" 2> "$errlog"
452	)
453	local rc=$?
454	TCID="$0"
455
456	# Post-test
457	popd > /dev/null
458	if [ $rc -ne 0 ]; then
459		#echo "FAILED"
460		((nfailed++))
461	else
462		#echo "SUCCEEDED"
463		((nsucceeded++))
464	fi
465	umount -l "${sandbox}" >& /dev/null
466	check_proc_mounts "$tname" "$resdir/$tname" || \
467	restore_proc_mounts "$tname" "$resdir/$tname" || do_break=1
468	check_mounts "$tname" "$resdir/$tname" || \
469	restore_mounts "$tname" "$resdir/$tname" || do_break=1
470	check_sandbox "$tname" "$resdir/$tname"
471	clean_sandbox "$tname" || do_break=1
472	if [ $do_break -eq 1 ]; then
473		tst_brkm TBROK true "$tname: failed to restore pre-test state of \"${sandbox}\""
474		return 2
475	fi
476
477	# If we succeeded and the error log is empty remove it
478	if [ $rc -eq 0 -a -w "$errlog" ] && is_file_empty "$errlog" ; then
479		rm -f "$errlog"
480	fi
481	return 0
482}
483
484main()
485{
486	TST_COUNT=1
487	for dir in "${test_dirs[@]}" ; do
488		tests=( $(find "${FS_BIND_ROOT}/${dir}" -type f -name 'test*') )
489		clean_sandbox "$0" || break
490		for t in "${tests[@]}" ; do
491			run_test "$t"
492			local rc=$?
493
494			if [ $rc -ne 0 ]; then
495				break $rc
496			fi
497
498			((TST_COUNT++))
499		done
500	done
501	rm -rf "${sandbox}"
502	return 0
503
504	skipped=$((total - nsucceeded - nfailed))
505	if [ $nfailed -eq 0 -a $skipped -eq 0 ]; then
506		# Use PASSED for the summary rather than SUCCEEDED to make it
507		# easy to determine 100% success from a calling script
508		summary="PASSED"
509	else
510		# Use FAILED to make it easy to find > 0% failure from a
511		# calling script
512		summary="FAILED"
513	fi
514	cat - <<-EOF
515		*********************************
516		RESULTS SUMMARY:
517
518			passed:  $nsucceeded/$total
519			failed:  $nfailed/$total
520			skipped: $skipped/$total
521			summary: $summary
522
523		*********************************
524	EOF
525}
526
527test_setup || exit 1
528test_prereqs || exit 1
529declare -r FS_BIND_ROOT
530declare -r TST_TOTAL
531main  #2> "$resdir/errors" 1> "$resdir/summary"
532