xref: /kvm-unit-tests/scripts/arch-run.bash (revision 6ca8c283190847bd2ff648232f98f70e4e6d3d0d)
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	echo -n "$@"
32	initrd_create &&
33		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	if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then
98		echo "timeout -k 1s --foreground $TIMEOUT"
99	fi
100}
101
102qmp ()
103{
104	echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | nc -U $1
105}
106
107run_migration ()
108{
109	if ! command -v nc >/dev/null 2>&1; then
110		echo "${FUNCNAME[0]} needs nc (netcat)" >&2
111		return 2
112	fi
113
114	migsock=`mktemp -u -t mig-helper-socket.XXXXXXXXXX`
115	migout1=`mktemp -t mig-helper-stdout1.XXXXXXXXXX`
116	qmp1=`mktemp -u -t mig-helper-qmp1.XXXXXXXXXX`
117	qmp2=`mktemp -u -t mig-helper-qmp2.XXXXXXXXXX`
118	fifo=`mktemp -u -t mig-helper-fifo.XXXXXXXXXX`
119	qmpout1=/dev/null
120	qmpout2=/dev/null
121
122	trap 'kill 0; exit 2' INT TERM
123	trap 'rm -f ${migout1} ${migsock} ${qmp1} ${qmp2} ${fifo}' RETURN EXIT
124
125	eval "$@" -chardev socket,id=mon1,path=${qmp1},server,nowait \
126		-mon chardev=mon1,mode=control | tee ${migout1} &
127
128	# We have to use cat to open the named FIFO, because named FIFO's, unlike
129	# pipes, will block on open() until the other end is also opened, and that
130	# totally breaks QEMU...
131	mkfifo ${fifo}
132	eval "$@" -chardev socket,id=mon2,path=${qmp2},server,nowait \
133		-mon chardev=mon2,mode=control -incoming unix:${migsock} < <(cat ${fifo}) &
134	incoming_pid=`jobs -l %+ | awk '{print$2}'`
135
136	# The test must prompt the user to migrate, so wait for the "migrate" keyword
137	while ! grep -q -i "migrate" < ${migout1} ; do
138		sleep 1
139	done
140
141	qmp ${qmp1} '"migrate", "arguments": { "uri": "unix:'${migsock}'" }' > ${qmpout1}
142
143	# Wait for the migration to complete
144	migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
145	while ! grep -q '"completed"' <<<"$migstatus" ; do
146		sleep 1
147		migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
148		if grep -q '"failed"' <<<"$migstatus" ; then
149			echo "ERROR: Migration failed." >&2
150			qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
151			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
152			return 2
153		fi
154	done
155	qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
156	echo > ${fifo}
157	wait $incoming_pid
158	ret=$?
159	wait
160	return $ret
161}
162
163migration_cmd ()
164{
165	if [ "$MIGRATION" = "yes" ]; then
166		echo "run_migration"
167	fi
168}
169
170search_qemu_binary ()
171{
172	local save_path=$PATH
173	local qemucmd qemu
174
175	export PATH=$PATH:/usr/libexec
176	for qemucmd in ${QEMU:-qemu-system-$ARCH_NAME qemu-kvm}; do
177		if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then
178			qemu="$qemucmd"
179			break
180		fi
181	done
182
183	if [ -z "$qemu" ]; then
184		echo "A QEMU binary was not found." >&2
185		echo "You can set a custom location by using the QEMU=<path> environment variable." >&2
186		return 2
187	fi
188	command -v $qemu
189	export PATH=$save_path
190}
191
192initrd_create ()
193{
194	local ret
195
196	env_add_errata
197	ret=$?
198
199	unset INITRD
200	[ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV"
201
202	return $ret
203}
204
205env_add_errata ()
206{
207	local line errata ret=1
208
209	if [ -f "$KVM_UNIT_TESTS_ENV" ] && grep -q '^ERRATA_' <(env); then
210		for line in $(grep '^ERRATA_' "$KVM_UNIT_TESTS_ENV"); do
211			errata=${line%%=*}
212			[ -n "${!errata}" ] && continue
213			eval export "$line"
214		done
215	elif [ ! -f "$KVM_UNIT_TESTS_ENV" ]; then
216		env_generate_errata
217	fi
218
219	if grep -q '^ERRATA_' <(env); then
220		export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV"
221		export KVM_UNIT_TESTS_ENV=$(mktemp)
222		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'
223		[ -f "$KVM_UNIT_TESTS_ENV_OLD" ] && grep -v '^ERRATA_' "$KVM_UNIT_TESTS_ENV_OLD" > $KVM_UNIT_TESTS_ENV
224		grep '^ERRATA_' <(env) >> $KVM_UNIT_TESTS_ENV
225		ret=0
226	fi
227
228	return $ret
229}
230
231env_generate_errata ()
232{
233	local kernel_version_string=$(uname -r)
234	local kernel_version kernel_patchlevel kernel_sublevel kernel_extraversion
235	local line commit minver errata rest v p s x have
236
237	IFS=. read -r kernel_version kernel_patchlevel rest <<<"$kernel_version_string"
238	IFS=- read -r kernel_sublevel kernel_extraversion <<<"$rest"
239	kernel_sublevel=${kernel_sublevel%%[!0-9]*}
240	kernel_extraversion=${kernel_extraversion%%[!0-9]*}
241
242	! [[ $kernel_sublevel =~ ^[0-9]+$ ]] && unset $kernel_sublevel
243	! [[ $kernel_extraversion =~ ^[0-9]+$ ]] && unset $kernel_extraversion
244
245	[ "$ENVIRON_DEFAULT" != "yes" ] && return
246	[ ! -f "$ERRATATXT" ] && return
247
248	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
249		commit=${line%:*}
250		minver=${line#*:}
251
252		test -z "$commit" && continue
253		errata="ERRATA_$commit"
254		[ -n "${!errata}" ] && continue
255
256		IFS=. read -r v p rest <<<"$minver"
257		IFS=- read -r s x <<<"$rest"
258		s=${s%%[!0-9]*}
259		x=${x%%[!0-9]*}
260
261		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
262			echo "Bad minimum kernel version in $ERRATATXT, $minver"
263			return 2
264		fi
265		! [[ $s =~ ^[0-9]+$ ]] && unset $s
266		! [[ $x =~ ^[0-9]+$ ]] && unset $x
267
268		if (( $kernel_version > $v ||
269		      ($kernel_version == $v && $kernel_patchlevel > $p) )); then
270			have=y
271		elif (( $kernel_version == $v && $kernel_patchlevel == $p )); then
272			if [ "$kernel_sublevel" ] && [ "$s" ]; then
273				if (( $kernel_sublevel > $s )); then
274					have=y
275				elif (( $kernel_sublevel == $s )); then
276					if [ "$kernel_extraversion" ] && [ "$x" ]; then
277						if (( $kernel_extraversion >= $x )); then
278							have=y
279						else
280							have=n
281						fi
282					elif [ "$x" ] && (( $x != 0 )); then
283						have=n
284					else
285						have=y
286					fi
287				else
288					have=n
289				fi
290			elif [ "$s" ] && (( $s != 0 )); then
291				have=n
292			else
293				have=y
294			fi
295		else
296			have=n
297		fi
298		eval export "$errata=$have"
299	done
300}
301
302trap_exit_push ()
303{
304	local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
305	trap -- "$1; $old_exit" EXIT
306}
307
308kvm_available ()
309{
310	[ -c /dev/kvm ] ||
311		return 1
312
313	[ "$HOST" = "$ARCH_NAME" ] ||
314		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
315		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
316}
317
318get_qemu_accelerator ()
319{
320	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
321		echo "KVM is needed, but not available on this host" >&2
322		return 2
323	fi
324
325	if [ "$ACCEL" ]; then
326		echo $ACCEL
327	elif kvm_available; then
328		echo kvm
329	else
330		echo tcg
331	fi
332}
333