1#!/bin/bash
2#
3# Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; either version 2 of the License, or at your option any
8# later version; or, when distributed separately from the Linux kernel or
9# when incorporated into other software packages, subject to the following
10# license:
11#
12# This program is free software; you can redistribute it and/or modify it
13# under the terms of copyleft-next (version 0.3.1 or later) as published
14# at http://copyleft-next.org/.
15
16# This is a stress test script for kmod, the kernel module loader. It uses
17# test_kmod which exposes a series of knobs for the API for us so we can
18# tweak each test in userspace rather than in kernelspace.
19#
20# The way kmod works is it uses the kernel's usermode helper API to eventually
21# call /sbin/modprobe. It has a limit of the number of concurrent calls
22# possible. The kernel interface to load modules is request_module(), however
23# mount uses get_fs_type(). Both behave slightly differently, but the
24# differences are important enough to test each call separately. For this
25# reason test_kmod starts by providing tests for both calls.
26#
27# The test driver test_kmod assumes a series of defaults which you can
28# override by exporting to your environment prior running this script.
29# For instance this script assumes you do not have xfs loaded upon boot.
30# If this is false, export DEFAULT_KMOD_FS="ext4" prior to running this
31# script if the filesystem module you don't have loaded upon bootup
32# is ext4 instead. Refer to allow_user_defaults() for a list of user
33# override variables possible.
34#
35# You'll want at least 4 GiB of RAM to expect to run these tests
36# without running out of memory on them. For other requirements refer
37# to test_reqs()
38
39set -e
40
41TEST_NAME="kmod"
42TEST_DRIVER="test_${TEST_NAME}"
43TEST_DIR=$(dirname $0)
44
45# This represents
46#
47# TEST_ID:TEST_COUNT:ENABLED
48#
49# TEST_ID: is the test id number
50# TEST_COUNT: number of times we should run the test
51# ENABLED: 1 if enabled, 0 otherwise
52#
53# Once these are enabled please leave them as-is. Write your own test,
54# we have tons of space.
55ALL_TESTS="0001:3:1"
56ALL_TESTS="$ALL_TESTS 0002:3:1"
57ALL_TESTS="$ALL_TESTS 0003:1:1"
58ALL_TESTS="$ALL_TESTS 0004:1:1"
59ALL_TESTS="$ALL_TESTS 0005:10:1"
60ALL_TESTS="$ALL_TESTS 0006:10:1"
61ALL_TESTS="$ALL_TESTS 0007:5:1"
62ALL_TESTS="$ALL_TESTS 0008:150:1"
63ALL_TESTS="$ALL_TESTS 0009:150:1"
64
65# Kselftest framework requirement - SKIP code is 4.
66ksft_skip=4
67
68test_modprobe()
69{
70       if [ ! -d $DIR ]; then
71               echo "$0: $DIR not present" >&2
72               echo "You must have the following enabled in your kernel:" >&2
73               cat $TEST_DIR/config >&2
74               exit $ksft_skip
75       fi
76}
77
78function allow_user_defaults()
79{
80	if [ -z $DEFAULT_KMOD_DRIVER ]; then
81		DEFAULT_KMOD_DRIVER="test_module"
82	fi
83
84	if [ -z $DEFAULT_KMOD_FS ]; then
85		DEFAULT_KMOD_FS="xfs"
86	fi
87
88	if [ -z $PROC_DIR ]; then
89		PROC_DIR="/proc/sys/kernel/"
90	fi
91
92	if [ -z $MODPROBE_LIMIT ]; then
93		MODPROBE_LIMIT=50
94	fi
95
96	if [ -z $DIR ]; then
97		DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
98	fi
99
100	if [ -z $DEFAULT_NUM_TESTS ]; then
101		DEFAULT_NUM_TESTS=150
102	fi
103
104	MODPROBE_LIMIT_FILE="${PROC_DIR}/kmod-limit"
105}
106
107test_reqs()
108{
109	if ! which modprobe 2> /dev/null > /dev/null; then
110		echo "$0: You need modprobe installed" >&2
111		exit $ksft_skip
112	fi
113
114	if ! which kmod 2> /dev/null > /dev/null; then
115		echo "$0: You need kmod installed" >&2
116		exit $ksft_skip
117	fi
118
119	# kmod 19 has a bad bug where it returns 0 when modprobe
120	# gets called *even* if the module was not loaded due to
121	# some bad heuristics. For details see:
122	#
123	# A work around is possible in-kernel but its rather
124	# complex.
125	KMOD_VERSION=$(kmod --version | awk '{print $3}')
126	if [[ $KMOD_VERSION  -le 19 ]]; then
127		echo "$0: You need at least kmod 20" >&2
128		echo "kmod <= 19 is buggy, for details see:" >&2
129		echo "http://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/libkmod/libkmod-module.c?id=fd44a98ae2eb5eb32161088954ab21e58e19dfc4" >&2
130		exit $ksft_skip
131	fi
132
133	uid=$(id -u)
134	if [ $uid -ne 0 ]; then
135		echo $msg must be run as root >&2
136		exit $ksft_skip
137	fi
138}
139
140function load_req_mod()
141{
142	trap "test_modprobe" EXIT
143
144	if [ ! -d $DIR ]; then
145		# Alanis: "Oh isn't it ironic?"
146		modprobe $TEST_DRIVER
147	fi
148}
149
150test_finish()
151{
152	echo "Test completed"
153}
154
155errno_name_to_val()
156{
157	case "$1" in
158	# kmod calls modprobe and upon of a module not found
159	# modprobe returns just 1... However in the kernel we
160	# *sometimes* see 256...
161	MODULE_NOT_FOUND)
162		echo 256;;
163	SUCCESS)
164		echo 0;;
165	-EPERM)
166		echo -1;;
167	-ENOENT)
168		echo -2;;
169	-EINVAL)
170		echo -22;;
171	-ERR_ANY)
172		echo -123456;;
173	*)
174		echo invalid;;
175	esac
176}
177
178errno_val_to_name()
179	case "$1" in
180	256)
181		echo MODULE_NOT_FOUND;;
182	0)
183		echo SUCCESS;;
184	-1)
185		echo -EPERM;;
186	-2)
187		echo -ENOENT;;
188	-22)
189		echo -EINVAL;;
190	-123456)
191		echo -ERR_ANY;;
192	*)
193		echo invalid;;
194	esac
195
196config_set_test_case_driver()
197{
198	if ! echo -n 1 >$DIR/config_test_case; then
199		echo "$0: Unable to set to test case to driver" >&2
200		exit 1
201	fi
202}
203
204config_set_test_case_fs()
205{
206	if ! echo -n 2 >$DIR/config_test_case; then
207		echo "$0: Unable to set to test case to fs" >&2
208		exit 1
209	fi
210}
211
212config_num_threads()
213{
214	if ! echo -n $1 >$DIR/config_num_threads; then
215		echo "$0: Unable to set to number of threads" >&2
216		exit 1
217	fi
218}
219
220config_get_modprobe_limit()
221{
222	if [[ -f ${MODPROBE_LIMIT_FILE} ]] ; then
223		MODPROBE_LIMIT=$(cat $MODPROBE_LIMIT_FILE)
224	fi
225	echo $MODPROBE_LIMIT
226}
227
228config_num_thread_limit_extra()
229{
230	MODPROBE_LIMIT=$(config_get_modprobe_limit)
231	let EXTRA_LIMIT=$MODPROBE_LIMIT+$1
232	config_num_threads $EXTRA_LIMIT
233}
234
235# For special characters use printf directly,
236# refer to kmod_test_0001
237config_set_driver()
238{
239	if ! echo -n $1 >$DIR/config_test_driver; then
240		echo "$0: Unable to set driver" >&2
241		exit 1
242	fi
243}
244
245config_set_fs()
246{
247	if ! echo -n $1 >$DIR/config_test_fs; then
248		echo "$0: Unable to set driver" >&2
249		exit 1
250	fi
251}
252
253config_get_driver()
254{
255	cat $DIR/config_test_driver
256}
257
258config_get_test_result()
259{
260	cat $DIR/test_result
261}
262
263config_reset()
264{
265	if ! echo -n "1" >"$DIR"/reset; then
266		echo "$0: reset should have worked" >&2
267		exit 1
268	fi
269}
270
271config_show_config()
272{
273	echo "----------------------------------------------------"
274	cat "$DIR"/config
275	echo "----------------------------------------------------"
276}
277
278config_trigger()
279{
280	if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
281		echo "$1: FAIL - loading should have worked"
282		config_show_config
283		exit 1
284	fi
285	echo "$1: OK! - loading kmod test"
286}
287
288config_trigger_want_fail()
289{
290	if echo "1" > $DIR/trigger_config 2>/dev/null; then
291		echo "$1: FAIL - test case was expected to fail"
292		config_show_config
293		exit 1
294	fi
295	echo "$1: OK! - kmod test case failed as expected"
296}
297
298config_expect_result()
299{
300	RC=$(config_get_test_result)
301	RC_NAME=$(errno_val_to_name $RC)
302
303	ERRNO_NAME=$2
304	ERRNO=$(errno_name_to_val $ERRNO_NAME)
305
306	if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then
307		if [[ $RC -ge 0 ]]; then
308			echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2
309			config_show_config
310			exit 1
311		fi
312	elif [[ $RC != $ERRNO ]]; then
313		echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2
314		config_show_config
315		exit 1
316	fi
317	echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
318}
319
320kmod_defaults_driver()
321{
322	config_reset
323	modprobe -r $DEFAULT_KMOD_DRIVER
324	config_set_driver $DEFAULT_KMOD_DRIVER
325}
326
327kmod_defaults_fs()
328{
329	config_reset
330	modprobe -r $DEFAULT_KMOD_FS
331	config_set_fs $DEFAULT_KMOD_FS
332	config_set_test_case_fs
333}
334
335kmod_test_0001_driver()
336{
337	NAME='\000'
338
339	kmod_defaults_driver
340	config_num_threads 1
341	printf '\000' >"$DIR"/config_test_driver
342	config_trigger ${FUNCNAME[0]}
343	config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND
344}
345
346kmod_test_0001_fs()
347{
348	NAME='\000'
349
350	kmod_defaults_fs
351	config_num_threads 1
352	printf '\000' >"$DIR"/config_test_fs
353	config_trigger ${FUNCNAME[0]}
354	config_expect_result ${FUNCNAME[0]} -EINVAL
355}
356
357kmod_test_0001()
358{
359	kmod_test_0001_driver
360	kmod_test_0001_fs
361}
362
363kmod_test_0002_driver()
364{
365	NAME="nope-$DEFAULT_KMOD_DRIVER"
366
367	kmod_defaults_driver
368	config_set_driver $NAME
369	config_num_threads 1
370	config_trigger ${FUNCNAME[0]}
371	config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND
372}
373
374kmod_test_0002_fs()
375{
376	NAME="nope-$DEFAULT_KMOD_FS"
377
378	kmod_defaults_fs
379	config_set_fs $NAME
380	config_trigger ${FUNCNAME[0]}
381	config_expect_result ${FUNCNAME[0]} -EINVAL
382}
383
384kmod_test_0002()
385{
386	kmod_test_0002_driver
387	kmod_test_0002_fs
388}
389
390kmod_test_0003()
391{
392	kmod_defaults_fs
393	config_num_threads 1
394	config_trigger ${FUNCNAME[0]}
395	config_expect_result ${FUNCNAME[0]} SUCCESS
396}
397
398kmod_test_0004()
399{
400	kmod_defaults_fs
401	config_num_threads 2
402	config_trigger ${FUNCNAME[0]}
403	config_expect_result ${FUNCNAME[0]} SUCCESS
404}
405
406kmod_test_0005()
407{
408	kmod_defaults_driver
409	config_trigger ${FUNCNAME[0]}
410	config_expect_result ${FUNCNAME[0]} SUCCESS
411}
412
413kmod_test_0006()
414{
415	kmod_defaults_fs
416	config_trigger ${FUNCNAME[0]}
417	config_expect_result ${FUNCNAME[0]} SUCCESS
418}
419
420kmod_test_0007()
421{
422	kmod_test_0005
423	kmod_test_0006
424}
425
426kmod_test_0008()
427{
428	kmod_defaults_driver
429	MODPROBE_LIMIT=$(config_get_modprobe_limit)
430	let EXTRA=$MODPROBE_LIMIT/6
431	config_num_thread_limit_extra $EXTRA
432	config_trigger ${FUNCNAME[0]}
433	config_expect_result ${FUNCNAME[0]} SUCCESS
434}
435
436kmod_test_0009()
437{
438	kmod_defaults_fs
439	MODPROBE_LIMIT=$(config_get_modprobe_limit)
440	let EXTRA=$MODPROBE_LIMIT/4
441	config_num_thread_limit_extra $EXTRA
442	config_trigger ${FUNCNAME[0]}
443	config_expect_result ${FUNCNAME[0]} SUCCESS
444}
445
446list_tests()
447{
448	echo "Test ID list:"
449	echo
450	echo "TEST_ID x NUM_TEST"
451	echo "TEST_ID:   Test ID"
452	echo "NUM_TESTS: Number of recommended times to run the test"
453	echo
454	echo "0001 x $(get_test_count 0001) - Simple test - 1 thread  for empty string"
455	echo "0002 x $(get_test_count 0002) - Simple test - 1 thread  for modules/filesystems that do not exist"
456	echo "0003 x $(get_test_count 0003) - Simple test - 1 thread  for get_fs_type() only"
457	echo "0004 x $(get_test_count 0004) - Simple test - 2 threads for get_fs_type() only"
458	echo "0005 x $(get_test_count 0005) - multithreaded tests with default setup - request_module() only"
459	echo "0006 x $(get_test_count 0006) - multithreaded tests with default setup - get_fs_type() only"
460	echo "0007 x $(get_test_count 0007) - multithreaded tests with default setup test request_module() and get_fs_type()"
461	echo "0008 x $(get_test_count 0008) - multithreaded - push kmod_concurrent over max_modprobes for request_module()"
462	echo "0009 x $(get_test_count 0009) - multithreaded - push kmod_concurrent over max_modprobes for get_fs_type()"
463}
464
465usage()
466{
467	NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
468	let NUM_TESTS=$NUM_TESTS+1
469	MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
470	echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
471	echo "		 [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>"
472	echo "           [ all ] [ -h | --help ] [ -l ]"
473	echo ""
474	echo "Valid tests: 0001-$MAX_TEST"
475	echo ""
476	echo "    all     Runs all tests (default)"
477	echo "    -t      Run test ID the number amount of times is recommended"
478	echo "    -w      Watch test ID run until it runs into an error"
479	echo "    -s      Run test ID once"
480	echo "    -c      Run test ID x test-count number of times"
481	echo "    -l      List all test ID list"
482	echo " -h|--help  Help"
483	echo
484	echo "If an error every occurs execution will immediately terminate."
485	echo "If you are adding a new test try using -w <test-ID> first to"
486	echo "make sure the test passes a series of tests."
487	echo
488	echo Example uses:
489	echo
490	echo "${TEST_NAME}.sh		-- executes all tests"
491	echo "${TEST_NAME}.sh -t 0008	-- Executes test ID 0008 number of times is recommended"
492	echo "${TEST_NAME}.sh -w 0008	-- Watch test ID 0008 run until an error occurs"
493	echo "${TEST_NAME}.sh -s 0008	-- Run test ID 0008 once"
494	echo "${TEST_NAME}.sh -c 0008 3	-- Run test ID 0008 three times"
495	echo
496	list_tests
497	exit 1
498}
499
500function test_num()
501{
502	re='^[0-9]+$'
503	if ! [[ $1 =~ $re ]]; then
504		usage
505	fi
506}
507
508function get_test_count()
509{
510	test_num $1
511	TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
512	LAST_TWO=${TEST_DATA#*:*}
513	echo ${LAST_TWO%:*}
514}
515
516function get_test_enabled()
517{
518	test_num $1
519	TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
520	echo ${TEST_DATA#*:*:}
521}
522
523function run_all_tests()
524{
525	for i in $ALL_TESTS ; do
526		TEST_ID=${i%:*:*}
527		ENABLED=$(get_test_enabled $TEST_ID)
528		TEST_COUNT=$(get_test_count $TEST_ID)
529		if [[ $ENABLED -eq "1" ]]; then
530			test_case $TEST_ID $TEST_COUNT
531		fi
532	done
533}
534
535function watch_log()
536{
537	if [ $# -ne 3 ]; then
538		clear
539	fi
540	date
541	echo "Running test: $2 - run #$1"
542}
543
544function watch_case()
545{
546	i=0
547	while [ 1 ]; do
548
549		if [ $# -eq 1 ]; then
550			test_num $1
551			watch_log $i ${TEST_NAME}_test_$1
552			${TEST_NAME}_test_$1
553		else
554			watch_log $i all
555			run_all_tests
556		fi
557		let i=$i+1
558	done
559}
560
561function test_case()
562{
563	NUM_TESTS=$DEFAULT_NUM_TESTS
564	if [ $# -eq 2 ]; then
565		NUM_TESTS=$2
566	fi
567
568	i=0
569	while [ $i -lt $NUM_TESTS ]; do
570		test_num $1
571		watch_log $i ${TEST_NAME}_test_$1 noclear
572		RUN_TEST=${TEST_NAME}_test_$1
573		$RUN_TEST
574		let i=$i+1
575	done
576}
577
578function parse_args()
579{
580	if [ $# -eq 0 ]; then
581		run_all_tests
582	else
583		if [[ "$1" = "all" ]]; then
584			run_all_tests
585		elif [[ "$1" = "-w" ]]; then
586			shift
587			watch_case $@
588		elif [[ "$1" = "-t" ]]; then
589			shift
590			test_num $1
591			test_case $1 $(get_test_count $1)
592		elif [[ "$1" = "-c" ]]; then
593			shift
594			test_num $1
595			test_num $2
596			test_case $1 $2
597		elif [[ "$1" = "-s" ]]; then
598			shift
599			test_case $1 1
600		elif [[ "$1" = "-l" ]]; then
601			list_tests
602		elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
603			usage
604		else
605			usage
606		fi
607	fi
608}
609
610test_reqs
611allow_user_defaults
612load_req_mod
613
614trap "test_finish" EXIT
615
616parse_args $@
617
618exit 0
619