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
9
10# log(msg) - write message to kernel log
11#	msg - insightful words
12function log() {
13	echo "$1" > /dev/kmsg
14}
15
16# skip(msg) - testing can't proceed
17#	msg - explanation
18function skip() {
19	log "SKIP: $1"
20	echo "SKIP: $1" >&2
21	exit 4
22}
23
24# die(msg) - game over, man
25#	msg - dying words
26function die() {
27	log "ERROR: $1"
28	echo "ERROR: $1" >&2
29	exit 1
30}
31
32# set_dynamic_debug() - setup kernel dynamic debug
33#	TODO - push and pop this config?
34function set_dynamic_debug() {
35	cat << EOF > /sys/kernel/debug/dynamic_debug/control
36file kernel/livepatch/* +p
37func klp_try_switch_task -p
38EOF
39}
40
41# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
42#		    sleep $RETRY_INTERVAL between attempts
43#	cmd - command and its arguments to run
44function loop_until() {
45	local cmd="$*"
46	local i=0
47	while true; do
48		eval "$cmd" && return 0
49		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
50		sleep $RETRY_INTERVAL
51	done
52}
53
54function assert_mod() {
55	local mod="$1"
56
57	modprobe --dry-run "$mod" &>/dev/null
58}
59
60function is_livepatch_mod() {
61	local mod="$1"
62
63	if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
64		return 0
65	fi
66
67	return 1
68}
69
70function __load_mod() {
71	local mod="$1"; shift
72
73	local msg="% modprobe $mod $*"
74	log "${msg%% }"
75	ret=$(modprobe "$mod" "$@" 2>&1)
76	if [[ "$ret" != "" ]]; then
77		die "$ret"
78	fi
79
80	# Wait for module in sysfs ...
81	loop_until '[[ -e "/sys/module/$mod" ]]' ||
82		die "failed to load module $mod"
83}
84
85
86# load_mod(modname, params) - load a kernel module
87#	modname - module name to load
88#	params  - module parameters to pass to modprobe
89function load_mod() {
90	local mod="$1"; shift
91
92	assert_mod "$mod" ||
93		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
94
95	is_livepatch_mod "$mod" &&
96		die "use load_lp() to load the livepatch module $mod"
97
98	__load_mod "$mod" "$@"
99}
100
101# load_lp_nowait(modname, params) - load a kernel module with a livepatch
102#			but do not wait on until the transition finishes
103#	modname - module name to load
104#	params  - module parameters to pass to modprobe
105function load_lp_nowait() {
106	local mod="$1"; shift
107
108	assert_mod "$mod" ||
109		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
110
111	is_livepatch_mod "$mod" ||
112		die "module $mod is not a livepatch"
113
114	__load_mod "$mod" "$@"
115
116	# Wait for livepatch in sysfs ...
117	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
118		die "failed to load module $mod (sysfs)"
119}
120
121# load_lp(modname, params) - load a kernel module with a livepatch
122#	modname - module name to load
123#	params  - module parameters to pass to modprobe
124function load_lp() {
125	local mod="$1"; shift
126
127	load_lp_nowait "$mod" "$@"
128
129	# Wait until the transition finishes ...
130	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
131		die "failed to complete transition"
132}
133
134# load_failing_mod(modname, params) - load a kernel module, expect to fail
135#	modname - module name to load
136#	params  - module parameters to pass to modprobe
137function load_failing_mod() {
138	local mod="$1"; shift
139
140	local msg="% modprobe $mod $*"
141	log "${msg%% }"
142	ret=$(modprobe "$mod" "$@" 2>&1)
143	if [[ "$ret" == "" ]]; then
144		die "$mod unexpectedly loaded"
145	fi
146	log "$ret"
147}
148
149# unload_mod(modname) - unload a kernel module
150#	modname - module name to unload
151function unload_mod() {
152	local mod="$1"
153
154	# Wait for module reference count to clear ...
155	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
156		die "failed to unload module $mod (refcnt)"
157
158	log "% rmmod $mod"
159	ret=$(rmmod "$mod" 2>&1)
160	if [[ "$ret" != "" ]]; then
161		die "$ret"
162	fi
163
164	# Wait for module in sysfs ...
165	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
166		die "failed to unload module $mod (/sys/module)"
167}
168
169# unload_lp(modname) - unload a kernel module with a livepatch
170#	modname - module name to unload
171function unload_lp() {
172	unload_mod "$1"
173}
174
175# disable_lp(modname) - disable a livepatch
176#	modname - module name to unload
177function disable_lp() {
178	local mod="$1"
179
180	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
181	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
182
183	# Wait until the transition finishes and the livepatch gets
184	# removed from sysfs...
185	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
186		die "failed to disable livepatch $mod"
187}
188
189# set_pre_patch_ret(modname, pre_patch_ret)
190#	modname - module name to set
191#	pre_patch_ret - new pre_patch_ret value
192function set_pre_patch_ret {
193	local mod="$1"; shift
194	local ret="$1"
195
196	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
197	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
198
199	# Wait for sysfs value to hold ...
200	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
201		die "failed to set pre_patch_ret parameter for $mod module"
202}
203
204# check_result() - verify dmesg output
205#	TODO - better filter, out of order msgs, etc?
206function check_result {
207	local expect="$*"
208	local result
209
210	result=$(dmesg | grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | sed 's/^\[[ 0-9.]*\] //')
211
212	if [[ "$expect" == "$result" ]] ; then
213		echo "ok"
214	else
215		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
216		die "livepatch kselftest(s) failed"
217	fi
218}
219