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