livepatch: Move tests from lib/livepatch to selftests/livepatch
[linux-2.6-block.git] / tools / testing / selftests / livepatch / functions.sh
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
7 MAX_RETRIES=600
8 RETRY_INTERVAL=".1"     # seconds
9 KLP_SYSFS_DIR="/sys/kernel/livepatch"
10
11 # Kselftest framework requirement - SKIP code is 4
12 ksft_skip=4
13
14 # log(msg) - write message to kernel log
15 #       msg - insightful words
16 function log() {
17         echo "$1" > /dev/kmsg
18 }
19
20 # skip(msg) - testing can't proceed
21 #       msg - explanation
22 function skip() {
23         log "SKIP: $1"
24         echo "SKIP: $1" >&2
25         exit $ksft_skip
26 }
27
28 # root test
29 function 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
39 function 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
46 function 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()
52 function cleanup_dmesg_file() {
53         rm -f "$SAVED_DMESG"
54 }
55
56 function 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
62 function 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
71 function 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
78 function 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
100 function 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.
109 function 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
120 function 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
130 function 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
144 function __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
163 function 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
176 function 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
192 function 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
205 function 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
219 function 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
239 function unload_lp() {
240         unload_mod "$1"
241 }
242
243 # disable_lp(modname) - disable a livepatch
244 #       modname - module name to unload
245 function 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
260 function 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
272 function 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?
282 function 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
310 function 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
326 function 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 }