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