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