1#!/bin/bash 2# Copyright (C) 2017 Luis R. Rodriguez <[email protected]> 3# 4# This program is free software; you can redistribute it and/or modify it 5# under the terms of the GNU General Public License as published by the Free 6# Software Foundation; either version 2 of the License, or at your option any 7# later version; or, when distributed separately from the Linux kernel or 8# when incorporated into other software packages, subject to the following 9# license: 10# 11# This program is free software; you can redistribute it and/or modify it 12# under the terms of copyleft-next (version 0.3.1 or later) as published 13# at http://copyleft-next.org/. 14 15# This performs a series tests against the proc sysctl interface. 16 17TEST_NAME="sysctl" 18TEST_DRIVER="test_${TEST_NAME}" 19TEST_DIR=$(dirname $0) 20TEST_FILE=$(mktemp) 21 22# This represents 23# 24# TEST_ID:TEST_COUNT:ENABLED 25# 26# TEST_ID: is the test id number 27# TEST_COUNT: number of times we should run the test 28# ENABLED: 1 if enabled, 0 otherwise 29# 30# Once these are enabled please leave them as-is. Write your own test, 31# we have tons of space. 32ALL_TESTS="0001:1:1" 33ALL_TESTS="$ALL_TESTS 0002:1:1" 34ALL_TESTS="$ALL_TESTS 0003:1:1" 35 36test_modprobe() 37{ 38 if [ ! -d $DIR ]; then 39 echo "$0: $DIR not present" >&2 40 echo "You must have the following enabled in your kernel:" >&2 41 cat $TEST_DIR/config >&2 42 exit 1 43 fi 44} 45 46function allow_user_defaults() 47{ 48 if [ -z $DIR ]; then 49 DIR="/sys/module/test_sysctl/" 50 fi 51 if [ -z $DEFAULT_NUM_TESTS ]; then 52 DEFAULT_NUM_TESTS=50 53 fi 54 if [ -z $SYSCTL ]; then 55 SYSCTL="/proc/sys/debug/test_sysctl" 56 fi 57 if [ -z $PROD_SYSCTL ]; then 58 PROD_SYSCTL="/proc/sys" 59 fi 60 if [ -z $WRITES_STRICT ]; then 61 WRITES_STRICT="${PROD_SYSCTL}/kernel/sysctl_writes_strict" 62 fi 63} 64 65function check_production_sysctl_writes_strict() 66{ 67 echo -n "Checking production write strict setting ... " 68 if [ ! -e ${WRITES_STRICT} ]; then 69 echo "FAIL, but skip in case of old kernel" >&2 70 else 71 old_strict=$(cat ${WRITES_STRICT}) 72 if [ "$old_strict" = "1" ]; then 73 echo "ok" 74 else 75 echo "FAIL, strict value is 0 but force to 1 to continue" >&2 76 echo "1" > ${WRITES_STRICT} 77 fi 78 fi 79 80 if [ -z $PAGE_SIZE ]; then 81 PAGE_SIZE=$(getconf PAGESIZE) 82 fi 83 if [ -z $MAX_DIGITS ]; then 84 MAX_DIGITS=$(($PAGE_SIZE/8)) 85 fi 86 if [ -z $INT_MAX ]; then 87 INT_MAX=$(getconf INT_MAX) 88 fi 89} 90 91test_reqs() 92{ 93 uid=$(id -u) 94 if [ $uid -ne 0 ]; then 95 echo $msg must be run as root >&2 96 exit 0 97 fi 98 99 if ! which perl 2> /dev/null > /dev/null; then 100 echo "$0: You need perl installed" 101 exit 1 102 fi 103 if ! which getconf 2> /dev/null > /dev/null; then 104 echo "$0: You need getconf installed" 105 exit 1 106 fi 107} 108 109function load_req_mod() 110{ 111 trap "test_modprobe" EXIT 112 113 if [ ! -d $DIR ]; then 114 modprobe $TEST_DRIVER 115 if [ $? -ne 0 ]; then 116 exit 117 fi 118 fi 119} 120 121reset_vals() 122{ 123 VAL="" 124 TRIGGER=$(basename ${TARGET}) 125 case "$TRIGGER" in 126 int_0001) 127 VAL="60" 128 ;; 129 int_0002) 130 VAL="1" 131 ;; 132 string_0001) 133 VAL="(none)" 134 ;; 135 *) 136 ;; 137 esac 138 echo -n $VAL > $TARGET 139} 140 141set_orig() 142{ 143 if [ ! -z $TARGET ]; then 144 echo "${ORIG}" > "${TARGET}" 145 fi 146} 147 148set_test() 149{ 150 echo "${TEST_STR}" > "${TARGET}" 151} 152 153verify() 154{ 155 local seen 156 seen=$(cat "$1") 157 if [ "${seen}" != "${TEST_STR}" ]; then 158 return 1 159 fi 160 return 0 161} 162 163test_rc() 164{ 165 if [[ $rc != 0 ]]; then 166 echo "Failed test, return value: $rc" >&2 167 exit $rc 168 fi 169} 170 171test_finish() 172{ 173 set_orig 174 rm -f "${TEST_FILE}" 175 176 if [ ! -z ${old_strict} ]; then 177 echo ${old_strict} > ${WRITES_STRICT} 178 fi 179 exit $rc 180} 181 182run_numerictests() 183{ 184 echo "== Testing sysctl behavior against ${TARGET} ==" 185 186 rc=0 187 188 echo -n "Writing test file ... " 189 echo "${TEST_STR}" > "${TEST_FILE}" 190 if ! verify "${TEST_FILE}"; then 191 echo "FAIL" >&2 192 exit 1 193 else 194 echo "ok" 195 fi 196 197 echo -n "Checking sysctl is not set to test value ... " 198 if verify "${TARGET}"; then 199 echo "FAIL" >&2 200 exit 1 201 else 202 echo "ok" 203 fi 204 205 echo -n "Writing sysctl from shell ... " 206 set_test 207 if ! verify "${TARGET}"; then 208 echo "FAIL" >&2 209 exit 1 210 else 211 echo "ok" 212 fi 213 214 echo -n "Resetting sysctl to original value ... " 215 set_orig 216 if verify "${TARGET}"; then 217 echo "FAIL" >&2 218 exit 1 219 else 220 echo "ok" 221 fi 222 223 # Now that we've validated the sanity of "set_test" and "set_orig", 224 # we can use those functions to set starting states before running 225 # specific behavioral tests. 226 227 echo -n "Writing entire sysctl in single write ... " 228 set_orig 229 dd if="${TEST_FILE}" of="${TARGET}" bs=4096 2>/dev/null 230 if ! verify "${TARGET}"; then 231 echo "FAIL" >&2 232 rc=1 233 else 234 echo "ok" 235 fi 236 237 echo -n "Writing middle of sysctl after synchronized seek ... " 238 set_test 239 dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 skip=1 2>/dev/null 240 if ! verify "${TARGET}"; then 241 echo "FAIL" >&2 242 rc=1 243 else 244 echo "ok" 245 fi 246 247 echo -n "Writing beyond end of sysctl ... " 248 set_orig 249 dd if="${TEST_FILE}" of="${TARGET}" bs=20 seek=2 2>/dev/null 250 if verify "${TARGET}"; then 251 echo "FAIL" >&2 252 rc=1 253 else 254 echo "ok" 255 fi 256 257 echo -n "Writing sysctl with multiple long writes ... " 258 set_orig 259 (perl -e 'print "A" x 50;'; echo "${TEST_STR}") | \ 260 dd of="${TARGET}" bs=50 2>/dev/null 261 if verify "${TARGET}"; then 262 echo "FAIL" >&2 263 rc=1 264 else 265 echo "ok" 266 fi 267 test_rc 268} 269 270# Your test must accept digits 3 and 4 to use this 271run_limit_digit() 272{ 273 echo -n "Checking ignoring spaces up to PAGE_SIZE works on write ..." 274 reset_vals 275 276 LIMIT=$((MAX_DIGITS -1)) 277 TEST_STR="3" 278 (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \ 279 dd of="${TARGET}" 2>/dev/null 280 281 if ! verify "${TARGET}"; then 282 echo "FAIL" >&2 283 rc=1 284 else 285 echo "ok" 286 fi 287 test_rc 288 289 echo -n "Checking passing PAGE_SIZE of spaces fails on write ..." 290 reset_vals 291 292 LIMIT=$((MAX_DIGITS)) 293 TEST_STR="4" 294 (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \ 295 dd of="${TARGET}" 2>/dev/null 296 297 if verify "${TARGET}"; then 298 echo "FAIL" >&2 299 rc=1 300 else 301 echo "ok" 302 fi 303 test_rc 304} 305 306# You are using an int 307run_limit_digit_int() 308{ 309 echo -n "Testing INT_MAX works ..." 310 reset_vals 311 TEST_STR="$INT_MAX" 312 echo -n $TEST_STR > $TARGET 313 314 if ! verify "${TARGET}"; then 315 echo "FAIL" >&2 316 rc=1 317 else 318 echo "ok" 319 fi 320 test_rc 321 322 echo -n "Testing INT_MAX + 1 will fail as expected..." 323 reset_vals 324 let TEST_STR=$INT_MAX+1 325 echo -n $TEST_STR > $TARGET 2> /dev/null 326 327 if verify "${TARGET}"; then 328 echo "FAIL" >&2 329 rc=1 330 else 331 echo "ok" 332 fi 333 test_rc 334 335 echo -n "Testing negative values will work as expected..." 336 reset_vals 337 TEST_STR="-3" 338 echo -n $TEST_STR > $TARGET 2> /dev/null 339 if ! verify "${TARGET}"; then 340 echo "FAIL" >&2 341 rc=1 342 else 343 echo "ok" 344 fi 345 test_rc 346} 347 348run_stringtests() 349{ 350 echo -n "Writing entire sysctl in short writes ... " 351 set_orig 352 dd if="${TEST_FILE}" of="${TARGET}" bs=1 2>/dev/null 353 if ! verify "${TARGET}"; then 354 echo "FAIL" >&2 355 rc=1 356 else 357 echo "ok" 358 fi 359 360 echo -n "Writing middle of sysctl after unsynchronized seek ... " 361 set_test 362 dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 2>/dev/null 363 if verify "${TARGET}"; then 364 echo "FAIL" >&2 365 rc=1 366 else 367 echo "ok" 368 fi 369 370 echo -n "Checking sysctl maxlen is at least $MAXLEN ... " 371 set_orig 372 perl -e 'print "A" x ('"${MAXLEN}"'-2), "B";' | \ 373 dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null 374 if ! grep -q B "${TARGET}"; then 375 echo "FAIL" >&2 376 rc=1 377 else 378 echo "ok" 379 fi 380 381 echo -n "Checking sysctl keeps original string on overflow append ... " 382 set_orig 383 perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \ 384 dd of="${TARGET}" bs=$(( MAXLEN - 1 )) 2>/dev/null 385 if grep -q B "${TARGET}"; then 386 echo "FAIL" >&2 387 rc=1 388 else 389 echo "ok" 390 fi 391 392 echo -n "Checking sysctl stays NULL terminated on write ... " 393 set_orig 394 perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \ 395 dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null 396 if grep -q B "${TARGET}"; then 397 echo "FAIL" >&2 398 rc=1 399 else 400 echo "ok" 401 fi 402 403 echo -n "Checking sysctl stays NULL terminated on overwrite ... " 404 set_orig 405 perl -e 'print "A" x ('"${MAXLEN}"'-1), "BB";' | \ 406 dd of="${TARGET}" bs=$(( $MAXLEN + 1 )) 2>/dev/null 407 if grep -q B "${TARGET}"; then 408 echo "FAIL" >&2 409 rc=1 410 else 411 echo "ok" 412 fi 413 414 test_rc 415} 416 417sysctl_test_0001() 418{ 419 TARGET="${SYSCTL}/int_0001" 420 reset_vals 421 ORIG=$(cat "${TARGET}") 422 TEST_STR=$(( $ORIG + 1 )) 423 424 run_numerictests 425 run_limit_digit 426} 427 428sysctl_test_0002() 429{ 430 TARGET="${SYSCTL}/string_0001" 431 reset_vals 432 ORIG=$(cat "${TARGET}") 433 TEST_STR="Testing sysctl" 434 # Only string sysctls support seeking/appending. 435 MAXLEN=65 436 437 run_numerictests 438 run_stringtests 439} 440 441sysctl_test_0003() 442{ 443 TARGET="${SYSCTL}/int_0002" 444 reset_vals 445 ORIG=$(cat "${TARGET}") 446 TEST_STR=$(( $ORIG + 1 )) 447 448 run_numerictests 449 run_limit_digit 450 run_limit_digit_int 451} 452 453list_tests() 454{ 455 echo "Test ID list:" 456 echo 457 echo "TEST_ID x NUM_TEST" 458 echo "TEST_ID: Test ID" 459 echo "NUM_TESTS: Number of recommended times to run the test" 460 echo 461 echo "0001 x $(get_test_count 0001) - tests proc_dointvec_minmax()" 462 echo "0002 x $(get_test_count 0002) - tests proc_dostring()" 463 echo "0003 x $(get_test_count 0003) - tests proc_dointvec()" 464} 465 466test_reqs 467 468usage() 469{ 470 NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .) 471 let NUM_TESTS=$NUM_TESTS+1 472 MAX_TEST=$(printf "%04d\n" $NUM_TESTS) 473 echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |" 474 echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>" 475 echo " [ all ] [ -h | --help ] [ -l ]" 476 echo "" 477 echo "Valid tests: 0001-$MAX_TEST" 478 echo "" 479 echo " all Runs all tests (default)" 480 echo " -t Run test ID the number amount of times is recommended" 481 echo " -w Watch test ID run until it runs into an error" 482 echo " -c Run test ID once" 483 echo " -s Run test ID x test-count number of times" 484 echo " -l List all test ID list" 485 echo " -h|--help Help" 486 echo 487 echo "If an error every occurs execution will immediately terminate." 488 echo "If you are adding a new test try using -w <test-ID> first to" 489 echo "make sure the test passes a series of tests." 490 echo 491 echo Example uses: 492 echo 493 echo "$TEST_NAME.sh -- executes all tests" 494 echo "$TEST_NAME.sh -t 0002 -- Executes test ID 0002 number of times is recomended" 495 echo "$TEST_NAME.sh -w 0002 -- Watch test ID 0002 run until an error occurs" 496 echo "$TEST_NAME.sh -s 0002 -- Run test ID 0002 once" 497 echo "$TEST_NAME.sh -c 0002 3 -- Run test ID 0002 three times" 498 echo 499 list_tests 500 exit 1 501} 502 503function test_num() 504{ 505 re='^[0-9]+$' 506 if ! [[ $1 =~ $re ]]; then 507 usage 508 fi 509} 510 511function get_test_count() 512{ 513 test_num $1 514 TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') 515 LAST_TWO=${TEST_DATA#*:*} 516 echo ${LAST_TWO%:*} 517} 518 519function get_test_enabled() 520{ 521 test_num $1 522 TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}') 523 echo ${TEST_DATA#*:*:} 524} 525 526function run_all_tests() 527{ 528 for i in $ALL_TESTS ; do 529 TEST_ID=${i%:*:*} 530 ENABLED=$(get_test_enabled $TEST_ID) 531 TEST_COUNT=$(get_test_count $TEST_ID) 532 if [[ $ENABLED -eq "1" ]]; then 533 test_case $TEST_ID $TEST_COUNT 534 fi 535 done 536} 537 538function watch_log() 539{ 540 if [ $# -ne 3 ]; then 541 clear 542 fi 543 date 544 echo "Running test: $2 - run #$1" 545} 546 547function watch_case() 548{ 549 i=0 550 while [ 1 ]; do 551 552 if [ $# -eq 1 ]; then 553 test_num $1 554 watch_log $i ${TEST_NAME}_test_$1 555 ${TEST_NAME}_test_$1 556 else 557 watch_log $i all 558 run_all_tests 559 fi 560 let i=$i+1 561 done 562} 563 564function test_case() 565{ 566 NUM_TESTS=$DEFAULT_NUM_TESTS 567 if [ $# -eq 2 ]; then 568 NUM_TESTS=$2 569 fi 570 571 i=0 572 while [ $i -lt $NUM_TESTS ]; do 573 test_num $1 574 watch_log $i ${TEST_NAME}_test_$1 noclear 575 RUN_TEST=${TEST_NAME}_test_$1 576 $RUN_TEST 577 let i=$i+1 578 done 579} 580 581function parse_args() 582{ 583 if [ $# -eq 0 ]; then 584 run_all_tests 585 else 586 if [[ "$1" = "all" ]]; then 587 run_all_tests 588 elif [[ "$1" = "-w" ]]; then 589 shift 590 watch_case $@ 591 elif [[ "$1" = "-t" ]]; then 592 shift 593 test_num $1 594 test_case $1 $(get_test_count $1) 595 elif [[ "$1" = "-c" ]]; then 596 shift 597 test_num $1 598 test_num $2 599 test_case $1 $2 600 elif [[ "$1" = "-s" ]]; then 601 shift 602 test_case $1 1 603 elif [[ "$1" = "-l" ]]; then 604 list_tests 605 elif [[ "$1" = "-h" || "$1" = "--help" ]]; then 606 usage 607 else 608 usage 609 fi 610 fi 611} 612 613test_reqs 614allow_user_defaults 615check_production_sysctl_writes_strict 616load_req_mod 617 618trap "test_finish" EXIT 619 620parse_args $@ 621 622exit 0 623