xref: /kvm-unit-tests/scripts/arch-run.bash (revision f07210607d4da37b907bffe6b1286f6ff3e07487)
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	live_pid=`jobs -l %+ | grep "eval" | awk '{print$2}'`
146
147	# We have to use cat to open the named FIFO, because named FIFO's, unlike
148	# pipes, will block on open() until the other end is also opened, and that
149	# totally breaks QEMU...
150	mkfifo ${fifo}
151	eval "$@" -chardev socket,id=mon2,path=${qmp2},server=on,wait=off \
152		-mon chardev=mon2,mode=control -incoming unix:${migsock} < <(cat ${fifo}) &
153	incoming_pid=`jobs -l %+ | awk '{print$2}'`
154
155	# The test must prompt the user to migrate, so wait for the "migrate" keyword
156	while ! grep -q -i "Now migrate the VM" < ${migout1} ; do
157		if ! ps -p ${live_pid} > /dev/null ; then
158			echo "ERROR: Test exit before migration point." >&2
159			echo > ${fifo}
160			qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
161			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
162			return 3
163		fi
164		sleep 1
165	done
166
167	qmp ${qmp1} '"migrate", "arguments": { "uri": "unix:'${migsock}'" }' > ${qmpout1}
168
169	# Wait for the migration to complete
170	migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
171	while ! grep -q '"completed"' <<<"$migstatus" ; do
172		sleep 1
173		if ! migstatus=`qmp ${qmp1} '"query-migrate"'`; then
174			echo "ERROR: Querying migration state failed." >&2
175			echo > ${fifo}
176			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
177			return 2
178		fi
179		migstatus=`grep return <<<"$migstatus"`
180		if grep -q '"failed"' <<<"$migstatus"; then
181			echo "ERROR: Migration failed." >&2
182			echo > ${fifo}
183			qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
184			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
185			return 2
186		fi
187	done
188	qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
189	echo > ${fifo}
190	wait $incoming_pid
191	ret=$?
192
193	while (( $(jobs -r | wc -l) > 0 )); do
194		sleep 0.5
195	done
196
197	return $ret
198}
199
200run_panic ()
201{
202	if ! command -v ncat >/dev/null 2>&1; then
203		echo "${FUNCNAME[0]} needs ncat (netcat)" >&2
204		return 77
205	fi
206
207	if ! command -v jq >/dev/null 2>&1; then
208		echo "${FUNCNAME[0]} needs jq" >&2
209		return 77
210	fi
211
212	qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX)
213
214	trap 'kill 0; exit 2' INT TERM
215	trap 'rm -f ${qmp}' RETURN EXIT
216
217	# start VM stopped so we don't miss any events
218	eval "$@" -chardev socket,id=mon1,path=${qmp},server=on,wait=off \
219		-mon chardev=mon1,mode=control -S &
220
221	panic_event_count=$(qmp_events ${qmp} | jq -c 'select(.event == "GUEST_PANICKED")' | wc -l)
222	if [ "$panic_event_count" -lt 1 ]; then
223		echo "FAIL: guest did not panic"
224		ret=3
225	else
226		# some QEMU versions report multiple panic events
227		echo "PASS: guest panicked"
228		ret=1
229	fi
230
231	return $ret
232}
233
234migration_cmd ()
235{
236	if [ "$MIGRATION" = "yes" ]; then
237		echo "run_migration"
238	fi
239}
240
241panic_cmd ()
242{
243	if [ "$PANIC" = "yes" ]; then
244		echo "run_panic"
245	fi
246}
247
248search_qemu_binary ()
249{
250	local save_path=$PATH
251	local qemucmd qemu
252
253	: "${QEMU_ARCH:=$ARCH_NAME}"
254
255	export PATH=$PATH:/usr/libexec
256	for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do
257		if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then
258			qemu="$qemucmd"
259			break
260		fi
261	done
262
263	if [ -z "$qemu" ]; then
264		echo "A QEMU binary was not found." >&2
265		echo "You can set a custom location by using the QEMU=<path> environment variable." >&2
266		return 2
267	fi
268	command -v $qemu
269	export PATH=$save_path
270}
271
272initrd_create ()
273{
274	if [ "$ENVIRON_DEFAULT" = "yes" ]; then
275		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'
276		[ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV"
277		export KVM_UNIT_TESTS_ENV=$(mktemp)
278		env_params
279		env_file
280		env_errata || return $?
281	fi
282
283	unset INITRD
284	[ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV"
285
286	return 0
287}
288
289env_add_params ()
290{
291	local p
292
293	for p in "$@"; do
294		if eval test -v $p; then
295			eval export "$p"
296		else
297			eval export "$p="
298		fi
299		grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV
300	done
301}
302
303env_params ()
304{
305	local qemu have_qemu
306	local _ rest
307
308	qemu=$(search_qemu_binary) && have_qemu=1
309
310	if [ "$have_qemu" ]; then
311		if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then
312			[ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL
313		fi
314		QEMU_VERSION_STRING="$($qemu -h | head -1)"
315		IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING"
316	fi
317	env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO
318
319	KERNEL_VERSION_STRING=$(uname -r)
320	IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING"
321	IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest"
322	KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*}
323	KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*}
324	! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL
325	! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION
326	env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION
327}
328
329env_file ()
330{
331	local line var
332
333	[ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return
334
335	for line in $(grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD"); do
336		var=${line%%=*}
337		if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then
338			eval export "$line"
339			grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV
340		fi
341	done
342}
343
344env_errata ()
345{
346	if [ "$ACCEL" = "tcg" ]; then
347		export "ERRATA_FORCE=y"
348	elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then
349		echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2
350		return 2
351	elif [ "$ERRATATXT" ]; then
352		env_generate_errata
353	fi
354	sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u >>$KVM_UNIT_TESTS_ENV
355}
356
357env_generate_errata ()
358{
359	local line commit minver errata rest v p s x have
360
361	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
362		commit=${line%:*}
363		minver=${line#*:}
364
365		test -z "$commit" && continue
366		errata="ERRATA_$commit"
367		[ -n "${!errata}" ] && continue
368
369		IFS=. read -r v p rest <<<"$minver"
370		IFS=- read -r s x <<<"$rest"
371		s=${s%%[!0-9]*}
372		x=${x%%[!0-9]*}
373
374		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
375			echo "Bad minimum kernel version in $ERRATATXT, $minver"
376			return 2
377		fi
378		! [[ $s =~ ^[0-9]+$ ]] && unset $s
379		! [[ $x =~ ^[0-9]+$ ]] && unset $x
380
381		if (( $KERNEL_VERSION > $v ||
382		      ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then
383			have=y
384		elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then
385			if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then
386				if (( $KERNEL_SUBLEVEL > $s )); then
387					have=y
388				elif (( $KERNEL_SUBLEVEL == $s )); then
389					if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then
390						if (( $KERNEL_EXTRAVERSION >= $x )); then
391							have=y
392						else
393							have=n
394						fi
395					elif [ "$x" ] && (( $x != 0 )); then
396						have=n
397					else
398						have=y
399					fi
400				else
401					have=n
402				fi
403			elif [ "$s" ] && (( $s != 0 )); then
404				have=n
405			else
406				have=y
407			fi
408		else
409			have=n
410		fi
411		eval export "$errata=$have"
412	done
413}
414
415trap_exit_push ()
416{
417	local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
418	trap -- "$1; $old_exit" EXIT
419}
420
421kvm_available ()
422{
423	[ -c /dev/kvm ] ||
424		return 1
425
426	[ "$HOST" = "$ARCH_NAME" ] ||
427		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
428		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
429}
430
431hvf_available ()
432{
433	[ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1
434	[ "$HOST" = "$ARCH_NAME" ] ||
435		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
436}
437
438set_qemu_accelerator ()
439{
440	ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"}
441	ACCEL=${ACCEL%%,*}
442
443	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
444		echo "KVM is needed, but not available on this host" >&2
445		return 2
446	fi
447	if [ "$ACCEL" = "hvf" ] && ! hvf_available; then
448		echo "HVF is needed, but not available on this host" >&2
449		return 2
450	fi
451
452	if [ -z "$ACCEL" ]; then
453		if kvm_available; then
454			ACCEL="kvm"
455		elif hvf_available; then
456			ACCEL="hvf"
457		else
458			ACCEL="tcg"
459		fi
460	fi
461
462	return 0
463}
464