File: kpatch-test

package info (click to toggle)
kpatch 0.9.10-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,716 kB
  • sloc: ansic: 9,716; sh: 2,592; makefile: 260; asm: 35
file content (399 lines) | stat: -rwxr-xr-x 9,623 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#!/bin/bash
#
# kpatch integration test framework
#
# Copyright (C) 2014 Josh Poimboeuf <jpoimboe@redhat.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
# 02110-1301, USA.
#
#
# This is a basic integration test framework for kpatch, which tests building,
# loading, and unloading patches, as well as any other related custom tests.
#
# This script looks for test input files in the current directory.  It expects
# certain file naming conventions:
#
# - foo.patch: patch that should build successfully
#
# - bar-FAIL.patch: patch that should fail to build
#
# - foo-LOADED.test: executable which tests whether the foo.patch module is
#   loaded.  It will be used to test that loading/unloading the patch module
#   works as expected.
#
# Any other *.test files will be executed after all the patch modules have been
# built from the *.patch files.  They can be used for more custom tests above
# and beyond the simple loading and unloading tests.

shopt -s nullglob

# shellcheck disable=SC2046
SCRIPTDIR=$(readlink -f $(dirname $(type -p "$0")))
ROOTDIR=$(readlink -f "$SCRIPTDIR/../..")
KPATCH="sudo $ROOTDIR/kpatch/kpatch"
unset CCACHE_HASHDIR
KPATCHBUILD="$ROOTDIR"/kpatch-build/kpatch-build
ERROR=0
LOG=test.log
DYNDEBUG_CONTROL=/sys/kernel/debug/dynamic_debug/control
DYNDEBUG_ENABLED=1
ARCHVERSION="$(uname -r)"
rm -f ./*.log

PATCHDIR="${PATCHDIR:-$PWD}"
declare -a PATCH_LIST
declare -a TEST_LIST

usage() {
	echo "usage: $0 [options] [patch1 ... patchN]" >&2
	echo "		patchN  	Pathnames of patches to test" >&2
	echo "		-h, --help	Show this help message" >&2
	echo "		-c, --cached	Don't rebuild patch modules" >&2
	echo "		-d, --directory	Patch directory" >&2
	echo "		-q, --quick	Test combined patch and -FAIL patches only" >&2
	echo "		--system-kpatch-tools	Use kpatch tools installed in the system" >&2
	echo "		--kpatch-build-opts	Additional options to pass to kpatch-build" >&2
}

options=$(getopt -o hcd:q -l "help,cached,directory,quick,system-kpatch-tools,kpatch-build-opts:" -- "$@") || exit 1

eval set -- "$options"

while [[ $# -gt 0 ]]; do
	case "$1" in
	-h|--help)
		usage
		exit 0
		;;
	-c|--cached)
		SKIPBUILD=1
		;;
	-d|--directory)
		PATCHDIR="$2"
		shift
		;;
	-q|--quick)
		QUICK=1
		;;
	--system-kpatch-tools)
		KPATCH="sudo kpatch"
		KPATCHBUILD="kpatch-build"
		;;
	--kpatch-build-opts)
		KPATCHBUILD_OPTS=$2
		shift
		;;
	*)
		[[ "$1" = "--" ]] && shift && continue
		PATCH_LIST+=("$1")
		;;
	esac
	shift
done

if [[ ${#PATCH_LIST[@]} = 0 ]]; then
	PATCH_LIST=("$PATCHDIR"/*.patch)
	TEST_LIST=("$PATCHDIR"/*.test)
	if [[ ${#PATCH_LIST[@]} = 0 ]]; then
		echo "No patches found!"
		exit 1
	fi
else
	for file in "${PATCH_LIST[@]}"; do
		prefix=${file%%.patch}
		[[ -e "$prefix-FAIL.test" ]]   && TEST_LIST+=("$prefix-FAIL.test")
		[[ -e "$prefix-LOADED.test" ]] && TEST_LIST+=("$prefix-LOADED.test")
	done
fi

error() {
	echo "ERROR: $*" |tee -a $LOG >&2
	ERROR=$((ERROR + 1))
}

log() {
	echo "$@" |tee -a $LOG
}

unload_all() {
	$KPATCH unload --all
}

build_module() {
	file=$1
	prefix=$(basename "${file%%.patch}")
	modname="test-$prefix"
	module="${modname}.ko"

	if [[ $prefix =~ -FAIL ]]; then
		shouldfail=1
	else
		shouldfail=0
	fi

	if [[ $SKIPBUILD -eq 1 ]]; then
		skip=0
		[[ $shouldfail -eq 1 ]] && skip=1
		[[ -e $module ]] && skip=1
		[[ $skip -eq 1 ]] && log "skipping build: $prefix" && return
	fi

	log "build: $prefix"

	# shellcheck disable=SC2086
	# KPATCHBUILD_OPTS may contain several space-separated options,
	# it should remain without quotes.
	if ! $KPATCHBUILD $KPATCHBUILD_OPTS -n "$modname" "$file" >> $LOG 2>&1; then
		if [[ $shouldfail -eq 0 ]]; then
			error "$prefix: build failed"
			cp "$HOME/.kpatch/build.log" "$prefix.log"
		fi
	else
		[[ $shouldfail -eq 1 ]] && error "$prefix: build succeeded when it should have failed"
	fi
}

run_load_test() {
	file=$1
	prefix=$(basename "${file%%.patch}")
	modname="test-$prefix"
	module="${modname}.ko"
	testprog=$(dirname "$1")/"$prefix-LOADED.test"

	[[ $prefix =~ -FAIL ]] && return

	if [[ ! -e $module ]]; then
		log "can't find $module, skipping"
		return
	fi

	if [[ -e $testprog ]]; then
		log "load test: $prefix"
	else
		log "load test: $prefix (no test prog)"
	fi


	if [[ -e $testprog ]] && $testprog >> $LOG 2>&1; then
		error "$prefix: $testprog succeeded before kpatch load"
		return
	fi

	if ! $KPATCH load "$module" >> $LOG 2>&1; then
		error "$prefix: kpatch load failed"
		return
	fi

	if [[ -e $testprog ]] && ! $testprog >> $LOG 2>&1; then
		error "$prefix: $testprog failed after kpatch load"
	fi

	if ! $KPATCH unload "$module" >> $LOG 2>&1; then
		error "$prefix: kpatch unload failed"
		return
	fi

	if [[ -e $testprog ]] && $testprog >> $LOG 2>&1; then
		error "$prefix: $testprog succeeded after kpatch unload"
		return
	fi
}

run_custom_test() {
	testprog=$1
	prefix=$(basename "${testprog%%.test}")

	[[ $testprog = *-LOADED.test ]] && return

	log "custom test: $prefix"

	if ! $testprog >> $LOG 2>&1; then
		error "$prefix: test failed"
	fi
}

build_combined_module() {

	if [[ $SKIPBUILD -eq 1 ]] && [[ -e test-COMBINED.ko ]]; then
		log "skipping build: combined"
		return
	fi

	declare -a COMBINED_LIST
	for file in "${PATCH_LIST[@]}"; do
		[[ $file =~ -FAIL ]] && log "combine: skipping $file" && continue
		COMBINED_LIST+=("$file")
	done
	if [[ ${#COMBINED_LIST[@]} -le 1 ]]; then
		log "skipping build: combined (only ${#PATCH_LIST[@]} patch(es))"
		return
	fi

	log "build: combined module"

	# shellcheck disable=SC2086
	if ! $KPATCHBUILD $KPATCHBUILD_OPTS -n test-COMBINED "${COMBINED_LIST[@]}" >> $LOG 2>&1; then
		error "combined build failed"
		cp "$HOME/.kpatch/build.log" combined.log
	fi
}

run_combined_test() {
	if [[ ! -e test-COMBINED.ko ]]; then
		log "can't find test-COMBINED.ko, skipping"
		return
	fi

	log "load test: combined module"

	unload_all

	for testprog in "${TEST_LIST[@]}"; do
		[[ $testprog != *-LOADED.test ]] && continue
		if $testprog >> $LOG 2>&1; then
			error "combined: $testprog succeeded before kpatch load"
			return
		fi
	done

	if ! $KPATCH load test-COMBINED.ko >> $LOG 2>&1; then
		error "combined: kpatch load failed"
		return
	fi

	for testprog in "${TEST_LIST[@]}"; do
		[[ $testprog != *-LOADED.test ]] && continue
		[ -e "${testprog/-LOADED.test/.patch.disabled}" ] && continue
		if ! $testprog >> $LOG 2>&1; then
			error "combined: $testprog failed after kpatch load"
		fi
	done

	if ! $KPATCH unload test-COMBINED.ko >> $LOG 2>&1; then
		error "combined: kpatch unload failed"
		return
	fi

	for testprog in "${TEST_LIST[@]}"; do
		[[ $testprog != *-LOADED.test ]] && continue
		if $testprog >> $LOG 2>&1; then
			error "combined: $testprog succeeded after kpatch unload"
			return
		fi
	done

}

# save existing dmesg so we can detect new content
save_dmesg() {
	SAVED_DMESG="kpatch-test timestamp: $(date --rfc-3339=ns)"
	echo "$SAVED_DMESG" > /dev/kmsg
}

# new dmesg entries since our saved entry
new_dmesg() {
	if ! dmesg --notime | awk -v last="$SAVED_DMESG" 'p; $0 == last{p=1} END {exit !p}'; then
		error "dmesg overflow, try increasing kernel log buffer size"
	fi
}

kernel_version_gte() {
	[  "${ARCHVERSION//-*/}" = "$(echo -e "${ARCHVERSION//-*}\\n$1" | sort -rV | head -n1)" ]
}

support_klp_replace()
{
	if kernel_is_rhel; then
		rhel_kernel_version_gte 4.18.0-193.el8
	else
		kernel_version_gte 5.1.0
	fi
}

kernel_is_rhel() {
	[[ "$ARCHVERSION" =~ \.el[789] ]]
}

rhel_kernel_version_gte() {
        [  "${ARCHVERSION}" = "$(echo -e "${ARCHVERSION}\\n$1" | sort -rV | head -n1)" ]
}

# shellcheck disable=SC1091
source /etc/os-release
if [[ "${ID}" == "rhel" && "${VERSION_ID%%.*}" == "7" && "${VERSION_ID##*.}" -le "6" ]]; then
	DYNDEBUG_ENABLED=0
	echo "Dynamic debug is not supported on '${PRETTY_NAME}', disabling."
fi

if ! support_klp_replace ; then
    	KPATCHBUILD_OPTS="$KPATCHBUILD_OPTS -R"
	echo "KLP replace is not supported on '${PRETTY_NAME}', disabling."
fi

for file in "${PATCH_LIST[@]}"; do
	if [[ $QUICK != 1 || "$file" =~ -FAIL ]]; then
		build_module "$file"
	fi
done

build_combined_module

unload_all

save_dmesg

if [ "${DYNDEBUG_ENABLED}" == "1" ]; then
	prev_dyndebug=$(sudo sh -c "grep klp_try_switch_task ${DYNDEBUG_CONTROL}" | awk '{print $3;}')
	sudo sh -c "echo 'func klp_try_switch_task +p' > ${DYNDEBUG_CONTROL} 2>/dev/null"
fi

if [[ $QUICK != 1 ]]; then
	for file in "${PATCH_LIST[@]}"; do
		run_load_test "$file"
	done
fi

run_combined_test

if [[ $QUICK != 1 ]]; then
	for testprog in "${TEST_LIST[@]}"; do
		if [[ ! $testprog =~ -FAIL ]]; then
			unload_all
			run_custom_test "$testprog"
		fi
	done
fi


unload_all

if [ "${DYNDEBUG_ENABLED}" == "1" ]; then
	sudo sh -c "echo \"func klp_try_switch_task ${prev_dyndebug}\" > ${DYNDEBUG_CONTROL} 2>/dev/null"
fi

if new_dmesg | grep -q "Call Trace"; then
	new_dmesg > dmesg.log
	error "kernel error detected in printk buffer"
fi

if [[ $ERROR -gt 0 ]]; then
	log "$ERROR errors encountered"
	echo "see test.log for more information"
else
	log "SUCCESS"
fi

exit $ERROR