1#!/bin/bash
2# Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
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
17# Kselftest framework requirement - SKIP code is 4.
18ksft_skip=4
19
20TEST_NAME="sysctl"
21TEST_DRIVER="test_${TEST_NAME}"
22TEST_DIR=$(dirname $0)
23TEST_FILE=$(mktemp)
24
25# This represents
26#
27# TEST_ID:TEST_COUNT:ENABLED
28#
29# TEST_ID: is the test id number
30# TEST_COUNT: number of times we should run the test
31# ENABLED: 1 if enabled, 0 otherwise
32#
33# Once these are enabled please leave them as-is. Write your own test,
34# we have tons of space.
35ALL_TESTS="0001:1:1"
36ALL_TESTS="$ALL_TESTS 0002:1:1"
37ALL_TESTS="$ALL_TESTS 0003:1:1"
38ALL_TESTS="$ALL_TESTS 0004:1:1"
39ALL_TESTS="$ALL_TESTS 0005:3:1"
40
41test_modprobe()
42{
43       if [ ! -d $DIR ]; then
44               echo "$0: $DIR not present" >&2
45               echo "You must have the following enabled in your kernel:" >&2
46               cat $TEST_DIR/config >&2
47               exit $ksft_skip
48       fi
49}
50
51function allow_user_defaults()
52{
53	if [ -z $DIR ]; then
54		DIR="/sys/module/test_sysctl/"
55	fi
56	if [ -z $DEFAULT_NUM_TESTS ]; then
57		DEFAULT_NUM_TESTS=50
58	fi
59	if [ -z $SYSCTL ]; then
60		SYSCTL="/proc/sys/debug/test_sysctl"
61	fi
62	if [ -z $PROD_SYSCTL ]; then
63		PROD_SYSCTL="/proc/sys"
64	fi
65	if [ -z $WRITES_STRICT ]; then
66		WRITES_STRICT="${PROD_SYSCTL}/kernel/sysctl_writes_strict"
67	fi
68}
69
70function check_production_sysctl_writes_strict()
71{
72	echo -n "Checking production write strict setting ... "
73	if [ ! -e ${WRITES_STRICT} ]; then
74		echo "FAIL, but skip in case of old kernel" >&2
75	else
76		old_strict=$(cat ${WRITES_STRICT})
77		if [ "$old_strict" = "1" ]; then
78			echo "ok"
79		else
80			echo "FAIL, strict value is 0 but force to 1 to continue" >&2
81			echo "1" > ${WRITES_STRICT}
82		fi
83	fi
84
85	if [ -z $PAGE_SIZE ]; then
86		PAGE_SIZE=$(getconf PAGESIZE)
87	fi
88	if [ -z $MAX_DIGITS ]; then
89		MAX_DIGITS=$(($PAGE_SIZE/8))
90	fi
91	if [ -z $INT_MAX ]; then
92		INT_MAX=$(getconf INT_MAX)
93	fi
94	if [ -z $UINT_MAX ]; then
95		UINT_MAX=$(getconf UINT_MAX)
96	fi
97}
98
99test_reqs()
100{
101	uid=$(id -u)
102	if [ $uid -ne 0 ]; then
103		echo $msg must be run as root >&2
104		exit $ksft_skip
105	fi
106
107	if ! which perl 2> /dev/null > /dev/null; then
108		echo "$0: You need perl installed"
109		exit $ksft_skip
110	fi
111	if ! which getconf 2> /dev/null > /dev/null; then
112		echo "$0: You need getconf installed"
113		exit $ksft_skip
114	fi
115	if ! which diff 2> /dev/null > /dev/null; then
116		echo "$0: You need diff installed"
117		exit $ksft_skip
118	fi
119}
120
121function load_req_mod()
122{
123	if [ ! -d $DIR ]; then
124		if ! modprobe -q -n $TEST_DRIVER; then
125			echo "$0: module $TEST_DRIVER not found [SKIP]"
126			exit $ksft_skip
127		fi
128		modprobe $TEST_DRIVER
129		if [ $? -ne 0 ]; then
130			exit
131		fi
132	fi
133}
134
135reset_vals()
136{
137	VAL=""
138	TRIGGER=$(basename ${TARGET})
139	case "$TRIGGER" in
140		int_0001)
141			VAL="60"
142			;;
143		int_0002)
144			VAL="1"
145			;;
146		uint_0001)
147			VAL="314"
148			;;
149		string_0001)
150			VAL="(none)"
151			;;
152		*)
153			;;
154	esac
155	echo -n $VAL > $TARGET
156}
157
158set_orig()
159{
160	if [ ! -z $TARGET ]; then
161		echo "${ORIG}" > "${TARGET}"
162	fi
163}
164
165set_test()
166{
167	echo "${TEST_STR}" > "${TARGET}"
168}
169
170verify()
171{
172	local seen
173	seen=$(cat "$1")
174	if [ "${seen}" != "${TEST_STR}" ]; then
175		return 1
176	fi
177	return 0
178}
179
180verify_diff_w()
181{
182	echo "$TEST_STR" | diff -q -w -u - $1
183	return $?
184}
185
186test_rc()
187{
188	if [[ $rc != 0 ]]; then
189		echo "Failed test, return value: $rc" >&2
190		exit $rc
191	fi
192}
193
194test_finish()
195{
196	set_orig
197	rm -f "${TEST_FILE}"
198
199	if [ ! -z ${old_strict} ]; then
200		echo ${old_strict} > ${WRITES_STRICT}
201	fi
202	exit $rc
203}
204
205run_numerictests()
206{
207	echo "== Testing sysctl behavior against ${TARGET} =="
208
209	rc=0
210
211	echo -n "Writing test file ... "
212	echo "${TEST_STR}" > "${TEST_FILE}"
213	if ! verify "${TEST_FILE}"; then
214		echo "FAIL" >&2
215		exit 1
216	else
217		echo "ok"
218	fi
219
220	echo -n "Checking sysctl is not set to test value ... "
221	if verify "${TARGET}"; then
222		echo "FAIL" >&2
223		exit 1
224	else
225		echo "ok"
226	fi
227
228	echo -n "Writing sysctl from shell ... "
229	set_test
230	if ! verify "${TARGET}"; then
231		echo "FAIL" >&2
232		exit 1
233	else
234		echo "ok"
235	fi
236
237	echo -n "Resetting sysctl to original value ... "
238	set_orig
239	if verify "${TARGET}"; then
240		echo "FAIL" >&2
241		exit 1
242	else
243		echo "ok"
244	fi
245
246	# Now that we've validated the sanity of "set_test" and "set_orig",
247	# we can use those functions to set starting states before running
248	# specific behavioral tests.
249
250	echo -n "Writing entire sysctl in single write ... "
251	set_orig
252	dd if="${TEST_FILE}" of="${TARGET}" bs=4096 2>/dev/null
253	if ! verify "${TARGET}"; then
254		echo "FAIL" >&2
255		rc=1
256	else
257		echo "ok"
258	fi
259
260	echo -n "Writing middle of sysctl after synchronized seek ... "
261	set_test
262	dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 skip=1 2>/dev/null
263	if ! verify "${TARGET}"; then
264		echo "FAIL" >&2
265		rc=1
266	else
267		echo "ok"
268	fi
269
270	echo -n "Writing beyond end of sysctl ... "
271	set_orig
272	dd if="${TEST_FILE}" of="${TARGET}" bs=20 seek=2 2>/dev/null
273	if verify "${TARGET}"; then
274		echo "FAIL" >&2
275		rc=1
276	else
277		echo "ok"
278	fi
279
280	echo -n "Writing sysctl with multiple long writes ... "
281	set_orig
282	(perl -e 'print "A" x 50;'; echo "${TEST_STR}") | \
283		dd of="${TARGET}" bs=50 2>/dev/null
284	if verify "${TARGET}"; then
285		echo "FAIL" >&2
286		rc=1
287	else
288		echo "ok"
289	fi
290	test_rc
291}
292
293# Your test must accept digits 3 and 4 to use this
294run_limit_digit()
295{
296	echo -n "Checking ignoring spaces up to PAGE_SIZE works on write ..."
297	reset_vals
298
299	LIMIT=$((MAX_DIGITS -1))
300	TEST_STR="3"
301	(perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
302		dd of="${TARGET}" 2>/dev/null
303
304	if ! verify "${TARGET}"; then
305		echo "FAIL" >&2
306		rc=1
307	else
308		echo "ok"
309	fi
310	test_rc
311
312	echo -n "Checking passing PAGE_SIZE of spaces fails on write ..."
313	reset_vals
314
315	LIMIT=$((MAX_DIGITS))
316	TEST_STR="4"
317	(perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
318		dd of="${TARGET}" 2>/dev/null
319
320	if verify "${TARGET}"; then
321		echo "FAIL" >&2
322		rc=1
323	else
324		echo "ok"
325	fi
326	test_rc
327}
328
329# You are using an int
330run_limit_digit_int()
331{
332	echo -n "Testing INT_MAX works ..."
333	reset_vals
334	TEST_STR="$INT_MAX"
335	echo -n $TEST_STR > $TARGET
336
337	if ! verify "${TARGET}"; then
338		echo "FAIL" >&2
339		rc=1
340	else
341		echo "ok"
342	fi
343	test_rc
344
345	echo -n "Testing INT_MAX + 1 will fail as expected..."
346	reset_vals
347	let TEST_STR=$INT_MAX+1
348	echo -n $TEST_STR > $TARGET 2> /dev/null
349
350	if verify "${TARGET}"; then
351		echo "FAIL" >&2
352		rc=1
353	else
354		echo "ok"
355	fi
356	test_rc
357
358	echo -n "Testing negative values will work as expected..."
359	reset_vals
360	TEST_STR="-3"
361	echo -n $TEST_STR > $TARGET 2> /dev/null
362	if ! verify "${TARGET}"; then
363		echo "FAIL" >&2
364		rc=1
365	else
366		echo "ok"
367	fi
368	test_rc
369}
370
371# You used an int array
372run_limit_digit_int_array()
373{
374	echo -n "Testing array works as expected ... "
375	TEST_STR="4 3 2 1"
376	echo -n $TEST_STR > $TARGET
377
378	if ! verify_diff_w "${TARGET}"; then
379		echo "FAIL" >&2
380		rc=1
381	else
382		echo "ok"
383	fi
384	test_rc
385
386	echo -n "Testing skipping trailing array elements works ... "
387	# Do not reset_vals, carry on the values from the last test.
388	# If we only echo in two digits the last two are left intact
389	TEST_STR="100 101"
390	echo -n $TEST_STR > $TARGET
391	# After we echo in, to help diff we need to set on TEST_STR what
392	# we expect the result to be.
393	TEST_STR="100 101 2 1"
394
395	if ! verify_diff_w "${TARGET}"; then
396		echo "FAIL" >&2
397		rc=1
398	else
399		echo "ok"
400	fi
401	test_rc
402
403	echo -n "Testing PAGE_SIZE limit on array works ... "
404	# Do not reset_vals, carry on the values from the last test.
405	# Even if you use an int array, you are still restricted to
406	# MAX_DIGITS, this is a known limitation. Test limit works.
407	LIMIT=$((MAX_DIGITS -1))
408	TEST_STR="9"
409	(perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
410		dd of="${TARGET}" 2>/dev/null
411
412	TEST_STR="9 101 2 1"
413	if ! verify_diff_w "${TARGET}"; then
414		echo "FAIL" >&2
415		rc=1
416	else
417		echo "ok"
418	fi
419	test_rc
420
421	echo -n "Testing exceeding PAGE_SIZE limit fails as expected ... "
422	# Do not reset_vals, carry on the values from the last test.
423	# Now go over limit.
424	LIMIT=$((MAX_DIGITS))
425	TEST_STR="7"
426	(perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
427		dd of="${TARGET}" 2>/dev/null
428
429	TEST_STR="7 101 2 1"
430	if verify_diff_w "${TARGET}"; then
431		echo "FAIL" >&2
432		rc=1
433	else
434		echo "ok"
435	fi
436	test_rc
437}
438
439# You are using an unsigned int
440run_limit_digit_uint()
441{
442	echo -n "Testing UINT_MAX works ..."
443	reset_vals
444	TEST_STR="$UINT_MAX"
445	echo -n $TEST_STR > $TARGET
446
447	if ! verify "${TARGET}"; then
448		echo "FAIL" >&2
449		rc=1
450	else
451		echo "ok"
452	fi
453	test_rc
454
455	echo -n "Testing UINT_MAX + 1 will fail as expected..."
456	reset_vals
457	TEST_STR=$(($UINT_MAX+1))
458	echo -n $TEST_STR > $TARGET 2> /dev/null
459
460	if verify "${TARGET}"; then
461		echo "FAIL" >&2
462		rc=1
463	else
464		echo "ok"
465	fi
466	test_rc
467
468	echo -n "Testing negative values will not work as expected ..."
469	reset_vals
470	TEST_STR="-3"
471	echo -n $TEST_STR > $TARGET 2> /dev/null
472
473	if verify "${TARGET}"; then
474		echo "FAIL" >&2
475		rc=1
476	else
477		echo "ok"
478	fi
479	test_rc
480}
481
482run_stringtests()
483{
484	echo -n "Writing entire sysctl in short writes ... "
485	set_orig
486	dd if="${TEST_FILE}" of="${TARGET}" bs=1 2>/dev/null
487	if ! verify "${TARGET}"; then
488		echo "FAIL" >&2
489		rc=1
490	else
491		echo "ok"
492	fi
493
494	echo -n "Writing middle of sysctl after unsynchronized seek ... "
495	set_test
496	dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 2>/dev/null
497	if verify "${TARGET}"; then
498		echo "FAIL" >&2
499		rc=1
500	else
501		echo "ok"
502	fi
503
504	echo -n "Checking sysctl maxlen is at least $MAXLEN ... "
505	set_orig
506	perl -e 'print "A" x ('"${MAXLEN}"'-2), "B";' | \
507		dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
508	if ! grep -q B "${TARGET}"; then
509		echo "FAIL" >&2
510		rc=1
511	else
512		echo "ok"
513	fi
514
515	echo -n "Checking sysctl keeps original string on overflow append ... "
516	set_orig
517	perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
518		dd of="${TARGET}" bs=$(( MAXLEN - 1 )) 2>/dev/null
519	if grep -q B "${TARGET}"; then
520		echo "FAIL" >&2
521		rc=1
522	else
523		echo "ok"
524	fi
525
526	echo -n "Checking sysctl stays NULL terminated on write ... "
527	set_orig
528	perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
529		dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
530	if grep -q B "${TARGET}"; then
531		echo "FAIL" >&2
532		rc=1
533	else
534		echo "ok"
535	fi
536
537	echo -n "Checking sysctl stays NULL terminated on overwrite ... "
538	set_orig
539	perl -e 'print "A" x ('"${MAXLEN}"'-1), "BB";' | \
540		dd of="${TARGET}" bs=$(( $MAXLEN + 1 )) 2>/dev/null
541	if grep -q B "${TARGET}"; then
542		echo "FAIL" >&2
543		rc=1
544	else
545		echo "ok"
546	fi
547
548	test_rc
549}
550
551sysctl_test_0001()
552{
553	TARGET="${SYSCTL}/int_0001"
554	reset_vals
555	ORIG=$(cat "${TARGET}")
556	TEST_STR=$(( $ORIG + 1 ))
557
558	run_numerictests
559	run_limit_digit
560}
561
562sysctl_test_0002()
563{
564	TARGET="${SYSCTL}/string_0001"
565	reset_vals
566	ORIG=$(cat "${TARGET}")
567	TEST_STR="Testing sysctl"
568	# Only string sysctls support seeking/appending.
569	MAXLEN=65
570
571	run_numerictests
572	run_stringtests
573}
574
575sysctl_test_0003()
576{
577	TARGET="${SYSCTL}/int_0002"
578	reset_vals
579	ORIG=$(cat "${TARGET}")
580	TEST_STR=$(( $ORIG + 1 ))
581
582	run_numerictests
583	run_limit_digit
584	run_limit_digit_int
585}
586
587sysctl_test_0004()
588{
589	TARGET="${SYSCTL}/uint_0001"
590	reset_vals
591	ORIG=$(cat "${TARGET}")
592	TEST_STR=$(( $ORIG + 1 ))
593
594	run_numerictests
595	run_limit_digit
596	run_limit_digit_uint
597}
598
599sysctl_test_0005()
600{
601	TARGET="${SYSCTL}/int_0003"
602	reset_vals
603	ORIG=$(cat "${TARGET}")
604
605	run_limit_digit_int_array
606}
607
608list_tests()
609{
610	echo "Test ID list:"
611	echo
612	echo "TEST_ID x NUM_TEST"
613	echo "TEST_ID:   Test ID"
614	echo "NUM_TESTS: Number of recommended times to run the test"
615	echo
616	echo "0001 x $(get_test_count 0001) - tests proc_dointvec_minmax()"
617	echo "0002 x $(get_test_count 0002) - tests proc_dostring()"
618	echo "0003 x $(get_test_count 0003) - tests proc_dointvec()"
619	echo "0004 x $(get_test_count 0004) - tests proc_douintvec()"
620	echo "0005 x $(get_test_count 0005) - tests proc_douintvec() array"
621}
622
623test_reqs
624
625usage()
626{
627	NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
628	let NUM_TESTS=$NUM_TESTS+1
629	MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
630	echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
631	echo "		 [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>"
632	echo "           [ all ] [ -h | --help ] [ -l ]"
633	echo ""
634	echo "Valid tests: 0001-$MAX_TEST"
635	echo ""
636	echo "    all     Runs all tests (default)"
637	echo "    -t      Run test ID the number amount of times is recommended"
638	echo "    -w      Watch test ID run until it runs into an error"
639	echo "    -c      Run test ID once"
640	echo "    -s      Run test ID x test-count number of times"
641	echo "    -l      List all test ID list"
642	echo " -h|--help  Help"
643	echo
644	echo "If an error every occurs execution will immediately terminate."
645	echo "If you are adding a new test try using -w <test-ID> first to"
646	echo "make sure the test passes a series of tests."
647	echo
648	echo Example uses:
649	echo
650	echo "$TEST_NAME.sh            -- executes all tests"
651	echo "$TEST_NAME.sh -t 0002    -- Executes test ID 0002 number of times is recomended"
652	echo "$TEST_NAME.sh -w 0002    -- Watch test ID 0002 run until an error occurs"
653	echo "$TEST_NAME.sh -s 0002    -- Run test ID 0002 once"
654	echo "$TEST_NAME.sh -c 0002 3  -- Run test ID 0002 three times"
655	echo
656	list_tests
657	exit 1
658}
659
660function test_num()
661{
662	re='^[0-9]+$'
663	if ! [[ $1 =~ $re ]]; then
664		usage
665	fi
666}
667
668function get_test_count()
669{
670	test_num $1
671	TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
672	LAST_TWO=${TEST_DATA#*:*}
673	echo ${LAST_TWO%:*}
674}
675
676function get_test_enabled()
677{
678	test_num $1
679	TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
680	echo ${TEST_DATA#*:*:}
681}
682
683function run_all_tests()
684{
685	for i in $ALL_TESTS ; do
686		TEST_ID=${i%:*:*}
687		ENABLED=$(get_test_enabled $TEST_ID)
688		TEST_COUNT=$(get_test_count $TEST_ID)
689		if [[ $ENABLED -eq "1" ]]; then
690			test_case $TEST_ID $TEST_COUNT
691		fi
692	done
693}
694
695function watch_log()
696{
697	if [ $# -ne 3 ]; then
698		clear
699	fi
700	date
701	echo "Running test: $2 - run #$1"
702}
703
704function watch_case()
705{
706	i=0
707	while [ 1 ]; do
708
709		if [ $# -eq 1 ]; then
710			test_num $1
711			watch_log $i ${TEST_NAME}_test_$1
712			${TEST_NAME}_test_$1
713		else
714			watch_log $i all
715			run_all_tests
716		fi
717		let i=$i+1
718	done
719}
720
721function test_case()
722{
723	NUM_TESTS=$DEFAULT_NUM_TESTS
724	if [ $# -eq 2 ]; then
725		NUM_TESTS=$2
726	fi
727
728	i=0
729	while [ $i -lt $NUM_TESTS ]; do
730		test_num $1
731		watch_log $i ${TEST_NAME}_test_$1 noclear
732		RUN_TEST=${TEST_NAME}_test_$1
733		$RUN_TEST
734		let i=$i+1
735	done
736}
737
738function parse_args()
739{
740	if [ $# -eq 0 ]; then
741		run_all_tests
742	else
743		if [[ "$1" = "all" ]]; then
744			run_all_tests
745		elif [[ "$1" = "-w" ]]; then
746			shift
747			watch_case $@
748		elif [[ "$1" = "-t" ]]; then
749			shift
750			test_num $1
751			test_case $1 $(get_test_count $1)
752		elif [[ "$1" = "-c" ]]; then
753			shift
754			test_num $1
755			test_num $2
756			test_case $1 $2
757		elif [[ "$1" = "-s" ]]; then
758			shift
759			test_case $1 1
760		elif [[ "$1" = "-l" ]]; then
761			list_tests
762		elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
763			usage
764		else
765			usage
766		fi
767	fi
768}
769
770test_reqs
771allow_user_defaults
772check_production_sysctl_writes_strict
773test_modprobe
774load_req_mod
775
776trap "test_finish" EXIT
777
778parse_args $@
779
780exit 0
781