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