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" 64ALL_TESTS="$ALL_TESTS 0010:1:1" 65ALL_TESTS="$ALL_TESTS 0011:1:1" 66 67# Kselftest framework requirement - SKIP code is 4. 68ksft_skip=4 69 70test_modprobe() 71{ 72 if [ ! -d $DIR ]; then 73 echo "$0: $DIR not present" >&2 74 echo "You must have the following enabled in your kernel:" >&2 75 cat $TEST_DIR/config >&2 76 exit $ksft_skip 77 fi 78} 79 80function allow_user_defaults() 81{ 82 if [ -z $DEFAULT_KMOD_DRIVER ]; then 83 DEFAULT_KMOD_DRIVER="test_module" 84 fi 85 86 if [ -z $DEFAULT_KMOD_FS ]; then 87 DEFAULT_KMOD_FS="xfs" 88 fi 89 90 if [ -z $PROC_DIR ]; then 91 PROC_DIR="/proc/sys/kernel/" 92 fi 93 94 if [ -z $MODPROBE_LIMIT ]; then 95 MODPROBE_LIMIT=50 96 fi 97 98 if [ -z $DIR ]; then 99 DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/" 100 fi 101 102 if [ -z $DEFAULT_NUM_TESTS ]; then 103 DEFAULT_NUM_TESTS=150 104 fi 105 106 MODPROBE_LIMIT_FILE="${PROC_DIR}/kmod-limit" 107} 108 109test_reqs() 110{ 111 if ! which modprobe 2> /dev/null > /dev/null; then 112 echo "$0: You need modprobe installed" >&2 113 exit $ksft_skip 114 fi 115 116 if ! which kmod 2> /dev/null > /dev/null; then 117 echo "$0: You need kmod installed" >&2 118 exit $ksft_skip 119 fi 120 121 # kmod 19 has a bad bug where it returns 0 when modprobe 122 # gets called *even* if the module was not loaded due to 123 # some bad heuristics. For details see: 124 # 125 # A work around is possible in-kernel but its rather 126 # complex. 127 KMOD_VERSION=$(kmod --version | awk '{print $3}') 128 if [[ $KMOD_VERSION -le 19 ]]; then 129 echo "$0: You need at least kmod 20" >&2 130 echo "kmod <= 19 is buggy, for details see:" >&2 131 echo "http://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/libkmod/libkmod-module.c?id=fd44a98ae2eb5eb32161088954ab21e58e19dfc4" >&2 132 exit $ksft_skip 133 fi 134 135 uid=$(id -u) 136 if [ $uid -ne 0 ]; then 137 echo $msg must be run as root >&2 138 exit $ksft_skip 139 fi 140} 141 142function load_req_mod() 143{ 144 trap "test_modprobe" EXIT 145 146 if [ ! -d $DIR ]; then 147 # Alanis: "Oh isn't it ironic?" 148 modprobe $TEST_DRIVER 149 fi 150} 151 152test_finish() 153{ 154 echo "$MODPROBE" > /proc/sys/kernel/modprobe 155 echo "Test completed" 156} 157 158errno_name_to_val() 159{ 160 case "$1" in 161 # kmod calls modprobe and upon of a module not found 162 # modprobe returns just 1... However in the kernel we 163 # *sometimes* see 256... 164 MODULE_NOT_FOUND) 165 echo 256;; 166 SUCCESS) 167 echo 0;; 168 -EPERM) 169 echo -1;; 170 -ENOENT) 171 echo -2;; 172 -EINVAL) 173 echo -22;; 174 -ERR_ANY) 175 echo -123456;; 176 *) 177 echo invalid;; 178 esac 179} 180 181errno_val_to_name() 182 case "$1" in 183 256) 184 echo MODULE_NOT_FOUND;; 185 0) 186 echo SUCCESS;; 187 -1) 188 echo -EPERM;; 189 -2) 190 echo -ENOENT;; 191 -22) 192 echo -EINVAL;; 193 -123456) 194 echo -ERR_ANY;; 195 *) 196 echo invalid;; 197 esac 198 199config_set_test_case_driver() 200{ 201 if ! echo -n 1 >$DIR/config_test_case; then 202 echo "$0: Unable to set to test case to driver" >&2 203 exit 1 204 fi 205} 206 207config_set_test_case_fs() 208{ 209 if ! echo -n 2 >$DIR/config_test_case; then 210 echo "$0: Unable to set to test case to fs" >&2 211 exit 1 212 fi 213} 214 215config_num_threads() 216{ 217 if ! echo -n $1 >$DIR/config_num_threads; then 218 echo "$0: Unable to set to number of threads" >&2 219 exit 1 220 fi 221} 222 223config_get_modprobe_limit() 224{ 225 if [[ -f ${MODPROBE_LIMIT_FILE} ]] ; then 226 MODPROBE_LIMIT=$(cat $MODPROBE_LIMIT_FILE) 227 fi 228 echo $MODPROBE_LIMIT 229} 230 231config_num_thread_limit_extra() 232{ 233 MODPROBE_LIMIT=$(config_get_modprobe_limit) 234 let EXTRA_LIMIT=$MODPROBE_LIMIT+$1 235 config_num_threads $EXTRA_LIMIT 236} 237 238# For special characters use printf directly, 239# refer to kmod_test_0001 240config_set_driver() 241{ 242 if ! echo -n $1 >$DIR/config_test_driver; then 243 echo "$0: Unable to set driver" >&2 244 exit 1 245 fi 246} 247 248config_set_fs() 249{ 250 if ! echo -n $1 >$DIR/config_test_fs; then 251 echo "$0: Unable to set driver" >&2 252 exit 1 253 fi 254} 255 256config_get_driver() 257{ 258 cat $DIR/config_test_driver 259} 260 261config_get_test_result() 262{ 263 cat $DIR/test_result 264} 265 266config_reset() 267{ 268 if ! echo -n "1" >"$DIR"/reset; then 269 echo "$0: reset should have worked" >&2 270 exit 1 271 fi 272} 273 274config_show_config() 275{ 276 echo "----------------------------------------------------" 277 cat "$DIR"/config 278 echo "----------------------------------------------------" 279} 280 281config_trigger() 282{ 283 if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then 284 echo "$1: FAIL - loading should have worked" 285 config_show_config 286 exit 1 287 fi 288 echo "$1: OK! - loading kmod test" 289} 290 291config_trigger_want_fail() 292{ 293 if echo "1" > $DIR/trigger_config 2>/dev/null; then 294 echo "$1: FAIL - test case was expected to fail" 295 config_show_config 296 exit 1 297 fi 298 echo "$1: OK! - kmod test case failed as expected" 299} 300 301config_expect_result() 302{ 303 RC=$(config_get_test_result) 304 RC_NAME=$(errno_val_to_name $RC) 305 306 ERRNO_NAME=$2 307 ERRNO=$(errno_name_to_val $ERRNO_NAME) 308 309 if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then 310 if [[ $RC -ge 0 ]]; then 311 echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2 312 config_show_config 313 exit 1 314 fi 315 elif [[ $RC != $ERRNO ]]; then 316 echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2 317 config_show_config 318 exit 1 319 fi 320 echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME" 321} 322 323kmod_defaults_driver() 324{ 325 config_reset 326 modprobe -r $DEFAULT_KMOD_DRIVER 327 config_set_driver $DEFAULT_KMOD_DRIVER 328} 329 330kmod_defaults_fs() 331{ 332 config_reset 333 modprobe -r $DEFAULT_KMOD_FS 334 config_set_fs $DEFAULT_KMOD_FS 335 config_set_test_case_fs 336} 337 338kmod_test_0001_driver() 339{ 340 NAME='\000' 341 342 kmod_defaults_driver 343 config_num_threads 1 344 printf '\000' >"$DIR"/config_test_driver 345 config_trigger ${FUNCNAME[0]} 346 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND 347} 348 349kmod_test_0001_fs() 350{ 351 NAME='\000' 352 353 kmod_defaults_fs 354 config_num_threads 1 355 printf '\000' >"$DIR"/config_test_fs 356 config_trigger ${FUNCNAME[0]} 357 config_expect_result ${FUNCNAME[0]} -EINVAL 358} 359 360kmod_test_0001() 361{ 362 kmod_test_0001_driver 363 kmod_test_0001_fs 364} 365 366kmod_test_0002_driver() 367{ 368 NAME="nope-$DEFAULT_KMOD_DRIVER" 369 370 kmod_defaults_driver 371 config_set_driver $NAME 372 config_num_threads 1 373 config_trigger ${FUNCNAME[0]} 374 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND 375} 376 377kmod_test_0002_fs() 378{ 379 NAME="nope-$DEFAULT_KMOD_FS" 380 381 kmod_defaults_fs 382 config_set_fs $NAME 383 config_trigger ${FUNCNAME[0]} 384 config_expect_result ${FUNCNAME[0]} -EINVAL 385} 386 387kmod_test_0002() 388{ 389 kmod_test_0002_driver 390 kmod_test_0002_fs 391} 392 393kmod_test_0003() 394{ 395 kmod_defaults_fs 396 config_num_threads 1 397 config_trigger ${FUNCNAME[0]} 398 config_expect_result ${FUNCNAME[0]} SUCCESS 399} 400 401kmod_test_0004() 402{ 403 kmod_defaults_fs 404 config_num_threads 2 405 config_trigger ${FUNCNAME[0]} 406 config_expect_result ${FUNCNAME[0]} SUCCESS 407} 408 409kmod_test_0005() 410{ 411 kmod_defaults_driver 412 config_trigger ${FUNCNAME[0]} 413 config_expect_result ${FUNCNAME[0]} SUCCESS 414} 415 416kmod_test_0006() 417{ 418 kmod_defaults_fs 419 config_trigger ${FUNCNAME[0]} 420 config_expect_result ${FUNCNAME[0]} SUCCESS 421} 422 423kmod_test_0007() 424{ 425 kmod_test_0005 426 kmod_test_0006 427} 428 429kmod_test_0008() 430{ 431 kmod_defaults_driver 432 MODPROBE_LIMIT=$(config_get_modprobe_limit) 433 let EXTRA=$MODPROBE_LIMIT/6 434 config_num_thread_limit_extra $EXTRA 435 config_trigger ${FUNCNAME[0]} 436 config_expect_result ${FUNCNAME[0]} SUCCESS 437} 438 439kmod_test_0009() 440{ 441 kmod_defaults_fs 442 MODPROBE_LIMIT=$(config_get_modprobe_limit) 443 let EXTRA=$MODPROBE_LIMIT/4 444 config_num_thread_limit_extra $EXTRA 445 config_trigger ${FUNCNAME[0]} 446 config_expect_result ${FUNCNAME[0]} SUCCESS 447} 448 449kmod_test_0010() 450{ 451 kmod_defaults_driver 452 config_num_threads 1 453 echo "/KMOD_TEST_NONEXISTENT" > /proc/sys/kernel/modprobe 454 config_trigger ${FUNCNAME[0]} 455 config_expect_result ${FUNCNAME[0]} -ENOENT 456 echo "$MODPROBE" > /proc/sys/kernel/modprobe 457} 458 459kmod_test_0011() 460{ 461 kmod_defaults_driver 462 config_num_threads 1 463 # This causes the kernel to not even try executing modprobe. The error 464 # code is still -ENOENT like when modprobe doesn't exist, so we can't 465 # easily test for the exact difference. But this still is a useful test 466 # since there was a bug where request_module() returned 0 in this case. 467 echo > /proc/sys/kernel/modprobe 468 config_trigger ${FUNCNAME[0]} 469 config_expect_result ${FUNCNAME[0]} -ENOENT 470 echo "$MODPROBE" > /proc/sys/kernel/modprobe 471} 472 473list_tests() 474{ 475 echo "Test ID list:" 476 echo 477 echo "TEST_ID x NUM_TEST" 478 echo "TEST_ID: Test ID" 479 echo "NUM_TESTS: Number of recommended times to run the test" 480 echo 481 echo "0001 x $(get_test_count 0001) - Simple test - 1 thread for empty string" 482 echo "0002 x $(get_test_count 0002) - Simple test - 1 thread for modules/filesystems that do not exist" 483 echo "0003 x $(get_test_count 0003) - Simple test - 1 thread for get_fs_type() only" 484 echo "0004 x $(get_test_count 0004) - Simple test - 2 threads for get_fs_type() only" 485 echo "0005 x $(get_test_count 0005) - multithreaded tests with default setup - request_module() only" 486 echo "0006 x $(get_test_count 0006) - multithreaded tests with default setup - get_fs_type() only" 487 echo "0007 x $(get_test_count 0007) - multithreaded tests with default setup test request_module() and get_fs_type()" 488 echo "0008 x $(get_test_count 0008) - multithreaded - push kmod_concurrent over max_modprobes for request_module()" 489 echo "0009 x $(get_test_count 0009) - multithreaded - push kmod_concurrent over max_modprobes for get_fs_type()" 490 echo "0010 x $(get_test_count 0010) - test nonexistent modprobe path" 491 echo "0011 x $(get_test_count 0011) - test completely disabling module autoloading" 492} 493 494usage() 495{ 496 NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .) 497 let NUM_TESTS=$NUM_TESTS+1 498 MAX_TEST=$(printf "%04d\n" $NUM_TESTS) 499 echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |" 500 echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>" 501 echo " [ all ] [ -h | --help ] [ -l ]" 502 echo "" 503 echo "Valid tests: 0001-$MAX_TEST" 504 echo "" 505 echo " all Runs all tests (default)" 506 echo " -t Run test ID the number amount of times is recommended" 507 echo " -w Watch test ID run until it runs into an error" 508 echo " -s Run test ID once" 509 echo " -c Run test ID x test-count number of times" 510 echo " -l List all test ID list" 511 echo " -h|--help Help" 512 echo 513 echo "If an error every occurs execution will immediately terminate." 514 echo "If you are adding a new test try using -w <test-ID> first to" 515 echo "make sure the test passes a series of tests." 516 echo 517 echo Example uses: 518 echo 519 echo "${TEST_NAME}.sh -- executes all tests" 520 echo "${TEST_NAME}.sh -t 0008 -- Executes test ID 0008 number of times is recommended" 521 echo "${TEST_NAME}.sh -w 0008 -- Watch test ID 0008 run until an error occurs" 522 echo "${TEST_NAME}.sh -s 0008 -- Run test ID 0008 once" 523 echo "${TEST_NAME}.sh -c 0008 3 -- Run test ID 0008 three times" 524 echo 525 list_tests 526 exit 1 527} 528 529function test_num() 530{ 531 re='^[0-9]+$' 532 if ! [[ $1 =~ $re ]]; then 533 usage 534 fi 535} 536 537function get_test_data() 538{ 539 test_num $1 540 local field_num=$(echo $1 | sed 's/^0*//') 541 echo $ALL_TESTS | awk '{print $'$field_num'}' 542} 543 544function get_test_count() 545{ 546 TEST_DATA=$(get_test_data $1) 547 LAST_TWO=${TEST_DATA#*:*} 548 echo ${LAST_TWO%:*} 549} 550 551function get_test_enabled() 552{ 553 TEST_DATA=$(get_test_data $1) 554 echo ${TEST_DATA#*:*:} 555} 556 557function run_all_tests() 558{ 559 for i in $ALL_TESTS ; do 560 TEST_ID=${i%:*:*} 561 ENABLED=$(get_test_enabled $TEST_ID) 562 TEST_COUNT=$(get_test_count $TEST_ID) 563 if [[ $ENABLED -eq "1" ]]; then 564 test_case $TEST_ID $TEST_COUNT 565 fi 566 done 567} 568 569function watch_log() 570{ 571 if [ $# -ne 3 ]; then 572 clear 573 fi 574 date 575 echo "Running test: $2 - run #$1" 576} 577 578function watch_case() 579{ 580 i=0 581 while [ 1 ]; do 582 583 if [ $# -eq 1 ]; then 584 test_num $1 585 watch_log $i ${TEST_NAME}_test_$1 586 ${TEST_NAME}_test_$1 587 else 588 watch_log $i all 589 run_all_tests 590 fi 591 let i=$i+1 592 done 593} 594 595function test_case() 596{ 597 NUM_TESTS=$DEFAULT_NUM_TESTS 598 if [ $# -eq 2 ]; then 599 NUM_TESTS=$2 600 fi 601 602 i=0 603 while [ $i -lt $NUM_TESTS ]; do 604 test_num $1 605 watch_log $i ${TEST_NAME}_test_$1 noclear 606 RUN_TEST=${TEST_NAME}_test_$1 607 $RUN_TEST 608 let i=$i+1 609 done 610} 611 612function parse_args() 613{ 614 if [ $# -eq 0 ]; then 615 run_all_tests 616 else 617 if [[ "$1" = "all" ]]; then 618 run_all_tests 619 elif [[ "$1" = "-w" ]]; then 620 shift 621 watch_case $@ 622 elif [[ "$1" = "-t" ]]; then 623 shift 624 test_num $1 625 test_case $1 $(get_test_count $1) 626 elif [[ "$1" = "-c" ]]; then 627 shift 628 test_num $1 629 test_num $2 630 test_case $1 $2 631 elif [[ "$1" = "-s" ]]; then 632 shift 633 test_case $1 1 634 elif [[ "$1" = "-l" ]]; then 635 list_tests 636 elif [[ "$1" = "-h" || "$1" = "--help" ]]; then 637 usage 638 else 639 usage 640 fi 641 fi 642} 643 644test_reqs 645allow_user_defaults 646load_req_mod 647 648MODPROBE=$(</proc/sys/kernel/modprobe) 649trap "test_finish" EXIT 650 651parse_args $@ 652 653exit 0 654