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