xref: /linux/tools/testing/selftests/livepatch/functions.sh (revision c4bbe83d27c2446a033cc0381c3fb6be5e8c41c7)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
4
5# Shell functions for the rest of the scripts.
6
7MAX_RETRIES=600
8RETRY_INTERVAL=".1"	# seconds
9KLP_SYSFS_DIR="/sys/kernel/livepatch"
10
11# Kselftest framework requirement - SKIP code is 4
12ksft_skip=4
13
14# log(msg) - write message to kernel log
15#	msg - insightful words
16function log() {
17	echo "$1" > /dev/kmsg
18}
19
20# skip(msg) - testing can't proceed
21#	msg - explanation
22function skip() {
23	log "SKIP: $1"
24	echo "SKIP: $1" >&2
25	exit $ksft_skip
26}
27
28# root test
29function is_root() {
30	uid=$(id -u)
31	if [ $uid -ne 0 ]; then
32		echo "skip all tests: must be run as root" >&2
33		exit $ksft_skip
34	fi
35}
36
37# die(msg) - game over, man
38#	msg - dying words
39function die() {
40	log "ERROR: $1"
41	echo "ERROR: $1" >&2
42	exit 1
43}
44
45# save existing dmesg so we can detect new content
46function save_dmesg() {
47	SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX)
48	dmesg > "$SAVED_DMESG"
49}
50
51# cleanup temporary dmesg file from save_dmesg()
52function cleanup_dmesg_file() {
53	rm -f "$SAVED_DMESG"
54}
55
56function push_config() {
57	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
58			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
59	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
60}
61
62function pop_config() {
63	if [[ -n "$DYNAMIC_DEBUG" ]]; then
64		echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
65	fi
66	if [[ -n "$FTRACE_ENABLED" ]]; then
67		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
68	fi
69}
70
71function set_dynamic_debug() {
72        cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
73		file kernel/livepatch/* +p
74		func klp_try_switch_task -p
75		EOF
76}
77
78function set_ftrace_enabled() {
79	local can_fail=0
80	if [[ "$1" == "--fail" ]] ; then
81		can_fail=1
82		shift
83	fi
84
85	local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
86	local result=$(sysctl --values kernel.ftrace_enabled)
87
88	if [[ "$result" != "$1" ]] ; then
89		if [[ $can_fail -eq 1 ]] ; then
90			echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg
91			return
92		fi
93
94		skip "failed to set kernel.ftrace_enabled = $1"
95	fi
96
97	echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
98}
99
100function cleanup() {
101	pop_config
102	cleanup_dmesg_file
103}
104
105# setup_config - save the current config and set a script exit trap that
106#		 restores the original config.  Setup the dynamic debug
107#		 for verbose livepatching output and turn on
108#		 the ftrace_enabled sysctl.
109function setup_config() {
110	is_root
111	push_config
112	set_dynamic_debug
113	set_ftrace_enabled 1
114	trap cleanup EXIT INT TERM HUP
115}
116
117# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
118#		    sleep $RETRY_INTERVAL between attempts
119#	cmd - command and its arguments to run
120function loop_until() {
121	local cmd="$*"
122	local i=0
123	while true; do
124		eval "$cmd" && return 0
125		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
126		sleep $RETRY_INTERVAL
127	done
128}
129
130function is_livepatch_mod() {
131	local mod="$1"
132
133	if [[ ! -f "test_modules/$mod.ko" ]]; then
134		die "Can't find \"test_modules/$mod.ko\", try \"make\""
135	fi
136
137	if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
138		return 0
139	fi
140
141	return 1
142}
143
144function __load_mod() {
145	local mod="$1"; shift
146
147	local msg="% insmod test_modules/$mod.ko $*"
148	log "${msg%% }"
149	ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
150	if [[ "$ret" != "" ]]; then
151		die "$ret"
152	fi
153
154	# Wait for module in sysfs ...
155	loop_until '[[ -e "/sys/module/$mod" ]]' ||
156		die "failed to load module $mod"
157}
158
159
160# load_mod(modname, params) - load a kernel module
161#	modname - module name to load
162#	params  - module parameters to pass to insmod
163function load_mod() {
164	local mod="$1"; shift
165
166	is_livepatch_mod "$mod" &&
167		die "use load_lp() to load the livepatch module $mod"
168
169	__load_mod "$mod" "$@"
170}
171
172# load_lp_nowait(modname, params) - load a kernel module with a livepatch
173#			but do not wait on until the transition finishes
174#	modname - module name to load
175#	params  - module parameters to pass to insmod
176function load_lp_nowait() {
177	local mod="$1"; shift
178
179	is_livepatch_mod "$mod" ||
180		die "module $mod is not a livepatch"
181
182	__load_mod "$mod" "$@"
183
184	# Wait for livepatch in sysfs ...
185	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
186		die "failed to load module $mod (sysfs)"
187}
188
189# load_lp(modname, params) - load a kernel module with a livepatch
190#	modname - module name to load
191#	params  - module parameters to pass to insmod
192function load_lp() {
193	local mod="$1"; shift
194
195	load_lp_nowait "$mod" "$@"
196
197	# Wait until the transition finishes ...
198	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
199		die "failed to complete transition"
200}
201
202# load_failing_mod(modname, params) - load a kernel module, expect to fail
203#	modname - module name to load
204#	params  - module parameters to pass to insmod
205function load_failing_mod() {
206	local mod="$1"; shift
207
208	local msg="% insmod test_modules/$mod.ko $*"
209	log "${msg%% }"
210	ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
211	if [[ "$ret" == "" ]]; then
212		die "$mod unexpectedly loaded"
213	fi
214	log "$ret"
215}
216
217# unload_mod(modname) - unload a kernel module
218#	modname - module name to unload
219function unload_mod() {
220	local mod="$1"
221
222	# Wait for module reference count to clear ...
223	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
224		die "failed to unload module $mod (refcnt)"
225
226	log "% rmmod $mod"
227	ret=$(rmmod "$mod" 2>&1)
228	if [[ "$ret" != "" ]]; then
229		die "$ret"
230	fi
231
232	# Wait for module in sysfs ...
233	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
234		die "failed to unload module $mod (/sys/module)"
235}
236
237# unload_lp(modname) - unload a kernel module with a livepatch
238#	modname - module name to unload
239function unload_lp() {
240	unload_mod "$1"
241}
242
243# disable_lp(modname) - disable a livepatch
244#	modname - module name to unload
245function disable_lp() {
246	local mod="$1"
247
248	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
249	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
250
251	# Wait until the transition finishes and the livepatch gets
252	# removed from sysfs...
253	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
254		die "failed to disable livepatch $mod"
255}
256
257# set_pre_patch_ret(modname, pre_patch_ret)
258#	modname - module name to set
259#	pre_patch_ret - new pre_patch_ret value
260function set_pre_patch_ret {
261	local mod="$1"; shift
262	local ret="$1"
263
264	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
265	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
266
267	# Wait for sysfs value to hold ...
268	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
269		die "failed to set pre_patch_ret parameter for $mod module"
270}
271
272function start_test {
273	local test="$1"
274
275	save_dmesg
276	echo -n "TEST: $test ... "
277	log "===== TEST: $test ====="
278}
279
280# check_result() - verify dmesg output
281#	TODO - better filter, out of order msgs, etc?
282function check_result {
283	local expect="$*"
284	local result
285
286	# Note: when comparing dmesg output, the kernel log timestamps
287	# help differentiate repeated testing runs.  Remove them with a
288	# post-comparison sed filter.
289
290	result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \
291		 grep -e 'livepatch:' -e 'test_klp' | \
292		 grep -v '\(tainting\|taints\) kernel' | \
293		 sed 's/^\[[ 0-9.]*\] //')
294
295	if [[ "$expect" == "$result" ]] ; then
296		echo "ok"
297	else
298		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
299		die "livepatch kselftest(s) failed"
300	fi
301
302	cleanup_dmesg_file
303}
304
305# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
306# path permissions
307#	modname - livepatch module creating the sysfs interface
308#	rel_path - relative path of the sysfs interface
309#	expected_rights - expected access rights
310function check_sysfs_rights() {
311	local mod="$1"; shift
312	local rel_path="$1"; shift
313	local expected_rights="$1"; shift
314
315	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
316	local rights=$(/bin/stat --format '%A' "$path")
317	if test "$rights" != "$expected_rights" ; then
318		die "Unexpected access rights of $path: $expected_rights vs. $rights"
319	fi
320}
321
322# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value
323#	modname - livepatch module creating the sysfs interface
324#	rel_path - relative path of the sysfs interface
325#	expected_value - expected value read from the file
326function check_sysfs_value() {
327	local mod="$1"; shift
328	local rel_path="$1"; shift
329	local expected_value="$1"; shift
330
331	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
332	local value=`cat $path`
333	if test "$value" != "$expected_value" ; then
334		die "Unexpected value in $path: $expected_value vs. $value"
335	fi
336}
337