xref: /kvm-unit-tests/scripts/arch-run.bash (revision 16bff5dbf92897f7ea756b5bda2bb79a5b6f28ce)
1##############################################################################
2# run_qemu translates the ambiguous exit status in Table1 to that in Table2.
3# Table3 simply documents the complete status table.
4#
5# Table1: Before fixup
6# --------------------
7# 0      - Unexpected exit from QEMU (possible signal), or the unittest did
8#          not use debug-exit
9# 1      - most likely unittest succeeded, or QEMU failed
10#
11# Table2: After fixup
12# -------------------
13# 0      - Everything succeeded
14# 1      - most likely QEMU failed
15#
16# Table3: Complete table
17# ----------------------
18# 0      - SUCCESS
19# 1      - most likely QEMU failed
20# 2      - most likely a run script failed
21# 3      - most likely the unittest failed
22# 124    - most likely the unittest timed out
23# 127    - most likely the unittest called abort()
24# 1..127 - FAILURE (could be QEMU, a run script, or the unittest)
25# >= 128 - Signal (signum = status - 128)
26##############################################################################
27run_qemu ()
28{
29	local stdout errors ret sig
30
31	initrd_create || return $?
32	echo -n "$@"
33	[ "$ENVIRON_DEFAULT" = "yes" ] && echo -n " #"
34	echo " $INITRD"
35
36	# stdout to {stdout}, stderr to $errors and stderr
37	exec {stdout}>&1
38	errors=$("${@}" $INITRD </dev/null 2> >(tee /dev/stderr) > /dev/fd/$stdout)
39	ret=$?
40	exec {stdout}>&-
41
42	[ $ret -eq 134 ] && echo "QEMU Aborted" >&2
43
44	if [ "$errors" ]; then
45		sig=$(grep 'terminating on signal' <<<"$errors")
46		if [ "$sig" ]; then
47			sig=$(sed 's/.*terminating on signal \([0-9][0-9]*\).*/\1/' <<<"$sig")
48		fi
49	fi
50
51	if [ $ret -eq 0 ]; then
52		# Some signals result in a zero return status, but the
53		# error log tells the truth.
54		if [ "$sig" ]; then
55			((ret=sig+128))
56		else
57			# Exiting with zero (non-debugexit) is an error
58			ret=1
59		fi
60	elif [ $ret -eq 1 ]; then
61		# Even when ret==1 (unittest success) if we also got stderr
62		# logs, then we assume a QEMU failure. Otherwise we translate
63		# status of 1 to 0 (SUCCESS)
64		if [ -z "$(echo "$errors" | grep -vi warning)" ]; then
65			ret=0
66		fi
67	fi
68
69	return $ret
70}
71
72run_qemu_status ()
73{
74	local stdout ret
75
76	exec {stdout}>&1
77	lines=$(run_qemu "$@" > >(tee /dev/fd/$stdout))
78	ret=$?
79	exec {stdout}>&-
80
81	if [ $ret -eq 1 ]; then
82		testret=$(grep '^EXIT: ' <<<"$lines" | sed 's/.*STATUS=\([0-9][0-9]*\).*/\1/')
83		if [ "$testret" ]; then
84			if [ $testret -eq 1 ]; then
85				ret=0
86			else
87				ret=$testret
88			fi
89		fi
90	fi
91
92	return $ret
93}
94
95timeout_cmd ()
96{
97	local s
98
99	if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then
100		if [ "$CONFIG_EFI" = 'y' ]; then
101			s=${TIMEOUT: -1}
102			if [ "$s" = 's' ]; then
103				TIMEOUT=${TIMEOUT:0:-1}
104				((TIMEOUT += 10)) # Add 10 seconds for booting UEFI
105				TIMEOUT="${TIMEOUT}s"
106			fi
107		fi
108		echo "timeout -k 1s --foreground $TIMEOUT"
109	fi
110}
111
112qmp ()
113{
114	echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | ncat -U $1
115}
116
117qmp_events ()
118{
119	while ! test -S "$1"; do sleep 0.1; done
120	echo '{ "execute": "qmp_capabilities" }{ "execute": "cont" }' |
121		ncat --no-shutdown -U $1 |
122		jq -c 'select(has("event"))'
123}
124
125run_migration ()
126{
127	if ! command -v ncat >/dev/null 2>&1; then
128		echo "${FUNCNAME[0]} needs ncat (netcat)" >&2
129		return 77
130	fi
131
132	migsock=$(mktemp -u -t mig-helper-socket.XXXXXXXXXX)
133	migout1=$(mktemp -t mig-helper-stdout1.XXXXXXXXXX)
134	qmp1=$(mktemp -u -t mig-helper-qmp1.XXXXXXXXXX)
135	qmp2=$(mktemp -u -t mig-helper-qmp2.XXXXXXXXXX)
136	fifo=$(mktemp -u -t mig-helper-fifo.XXXXXXXXXX)
137	qmpout1=/dev/null
138	qmpout2=/dev/null
139
140	trap 'kill 0; exit 2' INT TERM
141	trap 'rm -f ${migout1} ${migsock} ${qmp1} ${qmp2} ${fifo}' RETURN EXIT
142
143	eval "$@" -chardev socket,id=mon1,path=${qmp1},server=on,wait=off \
144		-mon chardev=mon1,mode=control | tee ${migout1} &
145
146	# We have to use cat to open the named FIFO, because named FIFO's, unlike
147	# pipes, will block on open() until the other end is also opened, and that
148	# totally breaks QEMU...
149	mkfifo ${fifo}
150	eval "$@" -chardev socket,id=mon2,path=${qmp2},server=on,wait=off \
151		-mon chardev=mon2,mode=control -incoming unix:${migsock} < <(cat ${fifo}) &
152	incoming_pid=`jobs -l %+ | awk '{print$2}'`
153
154	# The test must prompt the user to migrate, so wait for the "migrate" keyword
155	while ! grep -q -i "migrate" < ${migout1} ; do
156		sleep 1
157	done
158
159	qmp ${qmp1} '"migrate", "arguments": { "uri": "unix:'${migsock}'" }' > ${qmpout1}
160
161	# Wait for the migration to complete
162	migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
163	while ! grep -q '"completed"' <<<"$migstatus" ; do
164		sleep 1
165		migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
166		if grep -q '"failed"' <<<"$migstatus" ; then
167			echo "ERROR: Migration failed." >&2
168			echo > ${fifo}
169			qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
170			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
171			return 2
172		fi
173	done
174	qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
175	echo > ${fifo}
176	wait $incoming_pid
177	ret=$?
178
179	while (( $(jobs -r | wc -l) > 0 )); do
180		sleep 0.5
181	done
182
183	return $ret
184}
185
186run_panic ()
187{
188	if ! command -v ncat >/dev/null 2>&1; then
189		echo "${FUNCNAME[0]} needs ncat (netcat)" >&2
190		return 77
191	fi
192
193	if ! command -v jq >/dev/null 2>&1; then
194		echo "${FUNCNAME[0]} needs jq" >&2
195		return 77
196	fi
197
198	qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX)
199
200	trap 'kill 0; exit 2' INT TERM
201	trap 'rm -f ${qmp}' RETURN EXIT
202
203	# start VM stopped so we don't miss any events
204	eval "$@" -chardev socket,id=mon1,path=${qmp},server=on,wait=off \
205		-mon chardev=mon1,mode=control -S &
206
207	panic_event_count=$(qmp_events ${qmp} | jq -c 'select(.event == "GUEST_PANICKED")' | wc -l)
208	if [ "$panic_event_count" -lt 1 ]; then
209		echo "FAIL: guest did not panic"
210		ret=3
211	else
212		# some QEMU versions report multiple panic events
213		echo "PASS: guest panicked"
214		ret=1
215	fi
216
217	return $ret
218}
219
220migration_cmd ()
221{
222	if [ "$MIGRATION" = "yes" ]; then
223		echo "run_migration"
224	fi
225}
226
227panic_cmd ()
228{
229	if [ "$PANIC" = "yes" ]; then
230		echo "run_panic"
231	fi
232}
233
234search_qemu_binary ()
235{
236	local save_path=$PATH
237	local qemucmd qemu
238
239	: "${QEMU_ARCH:=$ARCH_NAME}"
240
241	export PATH=$PATH:/usr/libexec
242	for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do
243		if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then
244			qemu="$qemucmd"
245			break
246		fi
247	done
248
249	if [ -z "$qemu" ]; then
250		echo "A QEMU binary was not found." >&2
251		echo "You can set a custom location by using the QEMU=<path> environment variable." >&2
252		return 2
253	fi
254	command -v $qemu
255	export PATH=$save_path
256}
257
258initrd_create ()
259{
260	if [ "$ENVIRON_DEFAULT" = "yes" ]; then
261		trap_exit_push 'rm -f $KVM_UNIT_TESTS_ENV; [ "$KVM_UNIT_TESTS_ENV_OLD" ] && export KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD" || unset KVM_UNIT_TESTS_ENV; unset KVM_UNIT_TESTS_ENV_OLD'
262		[ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV"
263		export KVM_UNIT_TESTS_ENV=$(mktemp)
264		env_params
265		env_file
266		env_errata || return $?
267	fi
268
269	unset INITRD
270	[ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV"
271
272	return 0
273}
274
275env_add_params ()
276{
277	local p
278
279	for p in "$@"; do
280		if eval test -v $p; then
281			eval export "$p"
282		else
283			eval export "$p="
284		fi
285		grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV
286	done
287}
288
289env_params ()
290{
291	local qemu have_qemu
292	local _ rest
293
294	qemu=$(search_qemu_binary) && have_qemu=1
295
296	if [ "$have_qemu" ]; then
297		if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then
298			[ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL
299		fi
300		QEMU_VERSION_STRING="$($qemu -h | head -1)"
301		IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING"
302	fi
303	env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO
304
305	KERNEL_VERSION_STRING=$(uname -r)
306	IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING"
307	IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest"
308	KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*}
309	KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*}
310	! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL
311	! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION
312	env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION
313}
314
315env_file ()
316{
317	local line var
318
319	[ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return
320
321	for line in $(grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD"); do
322		var=${line%%=*}
323		if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then
324			eval export "$line"
325			grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV
326		fi
327	done
328}
329
330env_errata ()
331{
332	if [ "$ACCEL" = "tcg" ]; then
333		export "ERRATA_FORCE=y"
334	elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then
335		echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2
336		return 2
337	elif [ "$ERRATATXT" ]; then
338		env_generate_errata
339	fi
340	sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u >>$KVM_UNIT_TESTS_ENV
341}
342
343env_generate_errata ()
344{
345	local line commit minver errata rest v p s x have
346
347	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
348		commit=${line%:*}
349		minver=${line#*:}
350
351		test -z "$commit" && continue
352		errata="ERRATA_$commit"
353		[ -n "${!errata}" ] && continue
354
355		IFS=. read -r v p rest <<<"$minver"
356		IFS=- read -r s x <<<"$rest"
357		s=${s%%[!0-9]*}
358		x=${x%%[!0-9]*}
359
360		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
361			echo "Bad minimum kernel version in $ERRATATXT, $minver"
362			return 2
363		fi
364		! [[ $s =~ ^[0-9]+$ ]] && unset $s
365		! [[ $x =~ ^[0-9]+$ ]] && unset $x
366
367		if (( $KERNEL_VERSION > $v ||
368		      ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then
369			have=y
370		elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then
371			if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then
372				if (( $KERNEL_SUBLEVEL > $s )); then
373					have=y
374				elif (( $KERNEL_SUBLEVEL == $s )); then
375					if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then
376						if (( $KERNEL_EXTRAVERSION >= $x )); then
377							have=y
378						else
379							have=n
380						fi
381					elif [ "$x" ] && (( $x != 0 )); then
382						have=n
383					else
384						have=y
385					fi
386				else
387					have=n
388				fi
389			elif [ "$s" ] && (( $s != 0 )); then
390				have=n
391			else
392				have=y
393			fi
394		else
395			have=n
396		fi
397		eval export "$errata=$have"
398	done
399}
400
401trap_exit_push ()
402{
403	local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
404	trap -- "$1; $old_exit" EXIT
405}
406
407kvm_available ()
408{
409	[ -c /dev/kvm ] ||
410		return 1
411
412	[ "$HOST" = "$ARCH_NAME" ] ||
413		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
414		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
415}
416
417hvf_available ()
418{
419	[ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1
420	[ "$HOST" = "$ARCH_NAME" ] ||
421		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
422}
423
424set_qemu_accelerator ()
425{
426	ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"}
427	ACCEL=${ACCEL%%,*}
428
429	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
430		echo "KVM is needed, but not available on this host" >&2
431		return 2
432	fi
433	if [ "$ACCEL" = "hvf" ] && ! hvf_available; then
434		echo "HVF is needed, but not available on this host" >&2
435		return 2
436	fi
437
438	if [ -z "$ACCEL" ]; then
439		if kvm_available; then
440			ACCEL="kvm"
441		elif hvf_available; then
442			ACCEL="hvf"
443		else
444			ACCEL="tcg"
445		fi
446	fi
447
448	return 0
449}
450