1#!/bin/sh
2# SPDX-License-Identifier: GPL-2.0-or-later
3# Copyright (c) Linux Test Project, 2014-2019
4# Author: Cyril Hrubis <chrubis@suse.cz>
5#
6# LTP test library for shell.
7
8[ -n "$TST_LIB_LOADED" ] && return 0
9
10export TST_PASS=0
11export TST_FAIL=0
12export TST_BROK=0
13export TST_WARN=0
14export TST_CONF=0
15export TST_COUNT=1
16export TST_ITERATIONS=1
17export TST_TMPDIR_RHOST=0
18export TST_LIB_LOADED=1
19
20. tst_ansi_color.sh
21. tst_security.sh
22
23# default trap function
24trap "tst_brk TBROK 'test interrupted'" INT
25
26_tst_do_exit()
27{
28	local ret=0
29	TST_DO_EXIT=1
30
31	if [ -n "$TST_CLEANUP" -a -z "$TST_NO_CLEANUP" ]; then
32		$TST_CLEANUP
33	fi
34
35	if [ "$TST_NEEDS_DEVICE" = 1 -a "$TST_DEVICE_FLAG" = 1 ]; then
36		if ! tst_device release "$TST_DEVICE"; then
37			tst_res TWARN "Failed to release device '$TST_DEVICE'"
38		fi
39	fi
40
41	if [ "$TST_NEEDS_TMPDIR" = 1 -a -n "$TST_TMPDIR" ]; then
42		cd "$LTPROOT"
43		rm -r "$TST_TMPDIR"
44		[ "$TST_TMPDIR_RHOST" = 1 ] && tst_cleanup_rhost
45	fi
46
47	if [ -n "$_tst_setup_timer_pid" ]; then
48		kill $_tst_setup_timer_pid 2>/dev/null
49		wait $_tst_setup_timer_pid 2>/dev/null
50	fi
51
52	if [ $TST_FAIL -gt 0 ]; then
53		ret=$((ret|1))
54	fi
55
56	if [ $TST_BROK -gt 0 ]; then
57		ret=$((ret|2))
58	fi
59
60	if [ $TST_WARN -gt 0 ]; then
61		ret=$((ret|4))
62	fi
63
64	if [ $TST_CONF -gt 0 -a $TST_PASS -eq 0 ]; then
65		ret=$((ret|32))
66	fi
67
68	if [ $TST_BROK -gt 0 -o $TST_FAIL -gt 0 -o $TST_WARN -gt 0 ]; then
69		_tst_check_security_modules
70	fi
71
72	echo
73	echo "Summary:"
74	echo "passed   $TST_PASS"
75	echo "failed   $TST_FAIL"
76	echo "skipped  $TST_CONF"
77	echo "warnings $TST_WARN"
78
79	exit $ret
80}
81
82_tst_inc_res()
83{
84	case "$1" in
85	TPASS) TST_PASS=$((TST_PASS+1));;
86	TFAIL) TST_FAIL=$((TST_FAIL+1));;
87	TBROK) TST_BROK=$((TST_BROK+1));;
88	TWARN) TST_WARN=$((TST_WARN+1));;
89	TCONF) TST_CONF=$((TST_CONF+1));;
90	TINFO) ;;
91	*) tst_brk TBROK "Invalid res type '$1'";;
92	esac
93}
94
95tst_res()
96{
97	local res=$1
98	shift
99
100	tst_color_enabled
101	local color=$?
102
103	_tst_inc_res "$res"
104
105	printf "$TST_ID $TST_COUNT "
106	tst_print_colored $res "$res: "
107	echo "$@"
108}
109
110tst_brk()
111{
112	local res=$1
113	shift
114
115	if [ "$TST_DO_EXIT" = 1 ]; then
116		tst_res TWARN "$@"
117		return
118	fi
119
120	tst_res "$res" "$@"
121	_tst_do_exit
122}
123
124ROD_SILENT()
125{
126	local tst_out="$(tst_rod $@ 2>&1)"
127	if [ $? -ne 0 ]; then
128		echo "$tst_out"
129		tst_brk TBROK "$@ failed"
130	fi
131}
132
133ROD()
134{
135	tst_rod "$@"
136	if [ $? -ne 0 ]; then
137		tst_brk TBROK "$@ failed"
138	fi
139}
140
141_tst_expect_pass()
142{
143	local fnc="$1"
144	shift
145
146	tst_rod "$@"
147	if [ $? -eq 0 ]; then
148		tst_res TPASS "$@ passed as expected"
149		return 0
150	else
151		$fnc TFAIL "$@ failed unexpectedly"
152		return 1
153	fi
154}
155
156_tst_expect_fail()
157{
158	local fnc="$1"
159	shift
160
161	# redirect stderr since we expect the command to fail
162	tst_rod "$@" 2> /dev/null
163	if [ $? -ne 0 ]; then
164		tst_res TPASS "$@ failed as expected"
165		return 0
166	else
167		$fnc TFAIL "$@ passed unexpectedly"
168		return 1
169	fi
170}
171
172EXPECT_PASS()
173{
174	_tst_expect_pass tst_res "$@"
175}
176
177EXPECT_PASS_BRK()
178{
179	_tst_expect_pass tst_brk "$@"
180}
181
182EXPECT_FAIL()
183{
184	_tst_expect_fail tst_res "$@"
185}
186
187EXPECT_FAIL_BRK()
188{
189	_tst_expect_fail tst_brk "$@"
190}
191
192TST_RETRY_FN_EXP_BACKOFF()
193{
194	local tst_fun="$1"
195	local tst_exp=$2
196	local tst_sec=$(($3 * 1000000))
197	local tst_delay=1
198
199	_tst_multiply_timeout tst_sec
200
201	if [ $# -ne 3 ]; then
202		tst_brk TBROK "TST_RETRY_FN_EXP_BACKOFF expects 3 parameters"
203	fi
204
205	if ! tst_is_int "$tst_sec"; then
206		tst_brk TBROK "TST_RETRY_FN_EXP_BACKOFF: tst_sec must be integer ('$tst_sec')"
207	fi
208
209	while true; do
210		$tst_fun
211		if [ "$?" = "$tst_exp" ]; then
212			break
213		fi
214
215		if [ $tst_delay -lt $tst_sec ]; then
216			tst_sleep ${tst_delay}us
217			tst_delay=$((tst_delay*2))
218		else
219			tst_brk TBROK "\"$tst_fun\" timed out"
220		fi
221	done
222
223	return $tst_exp
224}
225
226TST_RETRY_FUNC()
227{
228	if [ $# -ne 2 ]; then
229		tst_brk TBROK "TST_RETRY_FUNC expects 2 parameters"
230	fi
231
232	TST_RETRY_FN_EXP_BACKOFF "$1" "$2" 1
233	return $2
234}
235
236TST_RTNL_CHK()
237{
238	local msg1="RTNETLINK answers: Function not implemented"
239	local msg2="RTNETLINK answers: Operation not supported"
240	local msg3="RTNETLINK answers: Protocol not supported"
241	local output="$($@ 2>&1 || echo 'LTP_ERR')"
242	local msg
243
244	echo "$output" | grep -q "LTP_ERR" || return 0
245
246	for msg in "$msg1" "$msg2" "$msg3"; do
247		echo "$output" | grep -q "$msg" && tst_brk TCONF "'$@': $msg"
248	done
249
250	tst_brk TBROK "$@ failed: $output"
251}
252
253tst_mount()
254{
255	local mnt_opt mnt_err
256
257	if [ -n "$TST_FS_TYPE" ]; then
258		mnt_opt="-t $TST_FS_TYPE"
259		mnt_err=" $TST_FS_TYPE type"
260	fi
261
262	ROD_SILENT mkdir -p $TST_MNTPOINT
263	mount $mnt_opt $TST_DEVICE $TST_MNTPOINT $TST_MNT_PARAMS
264	local ret=$?
265
266	if [ $ret -eq 32 ]; then
267		tst_brk TCONF "Cannot mount${mnt_err}, missing driver?"
268	fi
269
270	if [ $ret -ne 0 ]; then
271		tst_brk TBROK "Failed to mount device${mnt_err}: mount exit = $ret"
272	fi
273}
274
275tst_umount()
276{
277	local device="${1:-$TST_DEVICE}"
278	local i=0
279
280	[ -z "$device" ] && return
281
282	if ! grep -q "$device" /proc/mounts; then
283		tst_res TINFO "The $device is not mounted, skipping umount"
284		return
285	fi
286
287	while [ "$i" -lt 50 ]; do
288		if umount "$device" > /dev/null; then
289			return
290		fi
291
292		i=$((i+1))
293
294		tst_res TINFO "umount($device) failed, try $i ..."
295		tst_res TINFO "Likely gvfsd-trash is probing newly mounted "\
296		              "fs, kill it to speed up tests."
297
298		tst_sleep 100ms
299	done
300
301	tst_res TWARN "Failed to umount($device) after 50 retries"
302}
303
304tst_mkfs()
305{
306	local fs_type=${1:-$TST_FS_TYPE}
307	local device=${2:-$TST_DEVICE}
308	[ $# -ge 1 ] && shift
309	[ $# -ge 1 ] && shift
310	local fs_opts="$@"
311
312	if [ -z "$fs_type" ]; then
313		tst_brk TBROK "No fs_type specified"
314	fi
315
316	if [ -z "$device" ]; then
317		tst_brk TBROK "No device specified"
318	fi
319
320	tst_require_cmds mkfs.$fs_type
321
322	tst_res TINFO "Formatting $device with $fs_type extra opts='$fs_opts'"
323	ROD_SILENT mkfs.$fs_type $fs_opts $device
324}
325
326tst_cmd_available()
327{
328	if type command > /dev/null 2>&1; then
329		command -v $1 > /dev/null 2>&1 || return 1
330	else
331		which $1 > /dev/null 2>&1
332		if [ $? -eq 0 ]; then
333			return 0
334		elif [ $? -eq 127 ]; then
335			tst_brk TCONF "missing which command"
336		else
337			return 1
338		fi
339	fi
340}
341
342tst_require_cmds()
343{
344	local cmd
345	for cmd in $*; do
346		tst_cmd_available $cmd || tst_brk TCONF "'$cmd' not found"
347	done
348}
349
350tst_check_cmds()
351{
352	local cmd
353	for cmd; do
354		if ! tst_cmd_available $cmd; then
355			tst_res TCONF "'$cmd' not found"
356			return 1
357		fi
358	done
359	return 0
360}
361
362tst_require_drivers()
363{
364	[ $# -eq 0 ] && return 0
365
366	local drv
367
368	drv="$(tst_check_drivers $@ 2>&1)"
369
370	[ $? -ne 0 ] && tst_brk TCONF "$drv driver not available"
371	return 0
372}
373
374tst_is_int()
375{
376	[ "$1" -eq "$1" ] 2>/dev/null
377	return $?
378}
379
380tst_is_num()
381{
382	echo "$1" | grep -Eq '^[-+]?[0-9]+\.?[0-9]*$'
383}
384
385tst_usage()
386{
387	if [ -n "$TST_USAGE" ]; then
388		$TST_USAGE
389	else
390		echo "usage: $0"
391		echo "OPTIONS"
392	fi
393
394	echo "-h      Prints this help"
395	echo "-i n    Execute test n times"
396}
397
398_tst_resstr()
399{
400	echo "$TST_PASS$TST_FAIL$TST_CONF"
401}
402
403_tst_rescmp()
404{
405	local res=$(_tst_resstr)
406
407	if [ "$1" = "$res" ]; then
408		tst_brk TBROK "Test didn't report any results"
409	fi
410}
411
412_tst_multiply_timeout()
413{
414	[ $# -ne 1 ] && tst_brk TBROK "_tst_multiply_timeout expect 1 parameter"
415	eval "local timeout=\$$1"
416
417	LTP_TIMEOUT_MUL=${LTP_TIMEOUT_MUL:-1}
418
419	local err="LTP_TIMEOUT_MUL must be number >= 1!"
420
421	tst_is_num "$LTP_TIMEOUT_MUL" || tst_brk TBROK "$err ($LTP_TIMEOUT_MUL)"
422
423	if ! tst_is_int "$LTP_TIMEOUT_MUL"; then
424		LTP_TIMEOUT_MUL=$(echo "$LTP_TIMEOUT_MUL" | cut -d. -f1)
425		LTP_TIMEOUT_MUL=$((LTP_TIMEOUT_MUL+1))
426		tst_res TINFO "ceiling LTP_TIMEOUT_MUL to $LTP_TIMEOUT_MUL"
427	fi
428
429	[ "$LTP_TIMEOUT_MUL" -ge 1 ] || tst_brk TBROK "$err ($LTP_TIMEOUT_MUL)"
430	[ "$timeout" -ge 1 ] || tst_brk TBROK "timeout need to be >= 1 ($timeout)"
431
432	eval "$1='$((timeout * LTP_TIMEOUT_MUL))'"
433	return 0
434}
435
436_tst_setup_timer()
437{
438	TST_TIMEOUT=${TST_TIMEOUT:-300}
439
440	if [ "$TST_TIMEOUT" = -1 ]; then
441		tst_res TINFO "Timeout per run is disabled"
442		return
443	fi
444
445	if ! tst_is_int "$TST_TIMEOUT" || [ "$TST_TIMEOUT" -lt 1 ]; then
446		tst_brk TBROK "TST_TIMEOUT must be int >= 1! ($TST_TIMEOUT)"
447	fi
448
449	local sec=$TST_TIMEOUT
450	_tst_multiply_timeout sec
451	local h=$((sec / 3600))
452	local m=$((sec / 60 % 60))
453	local s=$((sec % 60))
454	local pid=$$
455
456	tst_res TINFO "timeout per run is ${h}h ${m}m ${s}s"
457
458	sleep $sec && tst_res TBROK "test killed, timeout! If you are running on slow machine, try exporting LTP_TIMEOUT_MUL > 1" && kill -9 -$pid &
459
460	_tst_setup_timer_pid=$!
461}
462
463_tst_require_root()
464{
465	if [ "$(id -ru)" != 0 ]; then
466		tst_brk TCONF "Must be super/root for this test!"
467	fi
468}
469
470tst_run()
471{
472	local _tst_i
473	local _tst_data
474	local _tst_max
475	local _tst_name
476
477	if [ -n "$TST_TEST_PATH" ]; then
478		for _tst_i in $(grep '^[^#]*\bTST_' "$TST_TEST_PATH" | sed 's/.*TST_//; s/[="} \t\/:`].*//'); do
479			case "$_tst_i" in
480			DISABLE_APPARMOR|DISABLE_SELINUX);;
481			SETUP|CLEANUP|TESTFUNC|ID|CNT|MIN_KVER);;
482			OPTS|USAGE|PARSE_ARGS|POS_ARGS);;
483			NEEDS_ROOT|NEEDS_TMPDIR|TMPDIR|NEEDS_DEVICE|DEVICE);;
484			NEEDS_CMDS|NEEDS_MODULE|MODPATH|DATAROOT);;
485			NEEDS_DRIVERS|FS_TYPE|MNTPOINT|MNT_PARAMS);;
486			IPV6|IPVER|TEST_DATA|TEST_DATA_IFS);;
487			RETRY_FUNC|RETRY_FN_EXP_BACKOFF|TIMEOUT);;
488			NET_MAX_PKT);;
489			*) tst_res TWARN "Reserved variable TST_$_tst_i used!";;
490			esac
491		done
492
493		for _tst_i in $(grep '^[^#]*\b_tst_' "$TST_TEST_PATH" | sed 's/.*_tst_//; s/[="} \t\/:`].*//'); do
494			tst_res TWARN "Private variable or function _tst_$_tst_i used!"
495		done
496	fi
497
498	OPTIND=1
499
500	while getopts ":hi:$TST_OPTS" _tst_name $TST_ARGS; do
501		case $_tst_name in
502		'h') tst_usage; exit 0;;
503		'i') TST_ITERATIONS=$OPTARG;;
504		'?') tst_usage; exit 2;;
505		*) $TST_PARSE_ARGS "$_tst_name" "$OPTARG";;
506		esac
507	done
508
509	if ! tst_is_int "$TST_ITERATIONS"; then
510		tst_brk TBROK "Expected number (-i) not '$TST_ITERATIONS'"
511	fi
512
513	if [ "$TST_ITERATIONS" -le 0 ]; then
514		tst_brk TBROK "Number of iterations (-i) must be > 0"
515	fi
516
517	[ "$TST_NEEDS_ROOT" = 1 ] && _tst_require_root
518
519	[ "$TST_DISABLE_APPARMOR" = 1 ] && tst_disable_apparmor
520	[ "$TST_DISABLE_SELINUX" = 1 ] && tst_disable_selinux
521
522	tst_require_cmds $TST_NEEDS_CMDS
523	tst_require_drivers $TST_NEEDS_DRIVERS
524
525	if [ -n "$TST_MIN_KVER" ]; then
526		tst_kvcmp -lt "$TST_MIN_KVER" && \
527			tst_brk TCONF "test requires kernel $TST_MIN_KVER+"
528	fi
529
530	_tst_setup_timer
531
532	[ "$TST_NEEDS_DEVICE" = 1 ] && TST_TMPDIR=1
533
534	if [ "$TST_NEEDS_TMPDIR" = 1 ]; then
535		if [ -z "$TMPDIR" ]; then
536			export TMPDIR="/tmp"
537		fi
538
539		TST_TMPDIR=$(mktemp -d "$TMPDIR/LTP_$TST_ID.XXXXXXXXXX")
540
541		chmod 777 "$TST_TMPDIR"
542
543		TST_STARTWD=$(pwd)
544
545		cd "$TST_TMPDIR"
546	fi
547
548	TST_MNTPOINT="${TST_MNTPOINT:-mntpoint}"
549	if [ "$TST_NEEDS_DEVICE" = 1 ]; then
550
551		TST_DEVICE=$(tst_device acquire)
552
553		if [ ! -b "$TST_DEVICE" -o $? -ne 0 ]; then
554			unset TST_DEVICE
555			tst_brk TBROK "Failed to acquire device"
556		fi
557
558		TST_DEVICE_FLAG=1
559	fi
560
561	if [ -n "$TST_NEEDS_MODULE" ]; then
562		for tst_module in "$TST_NEEDS_MODULE" \
563		                  "$LTPROOT/testcases/bin/$TST_NEEDS_MODULE" \
564		                  "$TST_STARTWD/$TST_NEEDS_MODULE"; do
565
566				if [ -f "$tst_module" ]; then
567					TST_MODPATH="$tst_module"
568					break
569				fi
570		done
571
572		if [ -z "$TST_MODPATH" ]; then
573			tst_brk TCONF "Failed to find module '$TST_NEEDS_MODULE'"
574		else
575			tst_res TINFO "Found module at '$TST_MODPATH'"
576		fi
577	fi
578
579	if [ -n "$TST_SETUP" ]; then
580		$TST_SETUP
581	fi
582
583	#TODO check that test reports some results for each test function call
584	while [ $TST_ITERATIONS -gt 0 ]; do
585		if [ -n "$TST_TEST_DATA" ]; then
586			tst_require_cmds cut tr wc
587			_tst_max=$(( $(echo $TST_TEST_DATA | tr -cd "$TST_TEST_DATA_IFS" | wc -c) +1))
588			for _tst_i in $(seq $_tst_max); do
589				_tst_data="$(echo "$TST_TEST_DATA" | cut -d"$TST_TEST_DATA_IFS" -f$_tst_i)"
590				_tst_run_tests "$_tst_data"
591			done
592		else
593			_tst_run_tests
594		fi
595		TST_ITERATIONS=$((TST_ITERATIONS-1))
596	done
597
598	_tst_do_exit
599}
600
601_tst_run_tests()
602{
603	local _tst_data="$1"
604	local _tst_i
605
606	for _tst_i in $(seq ${TST_CNT:-1}); do
607		if type ${TST_TESTFUNC}1 > /dev/null 2>&1; then
608			_tst_run_test "$TST_TESTFUNC$_tst_i" $_tst_i "$_tst_data"
609		else
610			_tst_run_test "$TST_TESTFUNC" $_tst_i "$_tst_data"
611		fi
612	done
613}
614
615_tst_run_test()
616{
617	local _tst_res=$(_tst_resstr)
618	local _tst_fnc="$1"
619	shift
620
621	$_tst_fnc "$@"
622	_tst_rescmp "$_tst_res"
623	TST_COUNT=$((TST_COUNT+1))
624}
625
626if [ -z "$TST_ID" ]; then
627	_tst_filename=$(basename $0) || \
628		tst_brk TCONF "Failed to set TST_ID from \$0 ('$0'), fix it with setting TST_ID before sourcing tst_test.sh"
629	TST_ID=${_tst_filename%%.*}
630fi
631export TST_ID="$TST_ID"
632
633if [ -z "$LTPROOT" ]; then
634	export LTPROOT="$PWD"
635	export TST_DATAROOT="$LTPROOT/datafiles"
636else
637	export TST_DATAROOT="$LTPROOT/testcases/data/$TST_ID"
638fi
639
640if [ -z "$TST_NO_DEFAULT_RUN" ]; then
641	if TST_TEST_PATH=$(which $0) 2>/dev/null; then
642		if ! grep -q tst_run "$TST_TEST_PATH"; then
643			tst_brk TBROK "Test $0 must call tst_run!"
644		fi
645	fi
646
647	if [ -z "$TST_TESTFUNC" ]; then
648		tst_brk TBROK "TST_TESTFUNC is not defined"
649	fi
650
651	TST_TEST_DATA_IFS="${TST_TEST_DATA_IFS:- }"
652
653	if [ -n "$TST_CNT" ]; then
654		if ! tst_is_int "$TST_CNT"; then
655			tst_brk TBROK "TST_CNT must be integer"
656		fi
657
658		if [ "$TST_CNT" -le 0 ]; then
659			tst_brk TBROK "TST_CNT must be > 0"
660		fi
661	fi
662
663	if [ -n "$TST_POS_ARGS" ]; then
664		if ! tst_is_int "$TST_POS_ARGS"; then
665			tst_brk TBROK "TST_POS_ARGS must be integer"
666		fi
667
668		if [ "$TST_POS_ARGS" -le 0 ]; then
669			tst_brk TBROK "TST_POS_ARGS must be > 0"
670		fi
671	fi
672
673	TST_ARGS="$@"
674
675	while getopts ":hi:$TST_OPTS" tst_name; do
676		case $tst_name in
677		'h') TST_PRINT_HELP=1;;
678		*);;
679		esac
680	done
681
682	shift $((OPTIND - 1))
683
684	if [ -n "$TST_POS_ARGS" ]; then
685		if [ -z "$TST_PRINT_HELP" -a $# -ne "$TST_POS_ARGS" ]; then
686			tst_brk TBROK "Invalid number of positional parameters:"\
687					  "have ($@) $#, expected ${TST_POS_ARGS}"
688		fi
689	else
690		if [ -z "$TST_PRINT_HELP" -a $# -ne 0 ]; then
691			tst_brk TBROK "Unexpected positional arguments '$@'"
692		fi
693	fi
694fi
695