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" | head -n1 | 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 migcmdline=$@ 133 134 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 135 trap 'rm -f ${src_out} ${dst_out} ${src_outfifo} ${dst_outfifo} ${dst_incoming} ${src_qmp} ${dst_qmp} ${dst_infifo}' RETURN EXIT 136 137 dst_incoming=$(mktemp -u -t mig-helper-socket-incoming.XXXXXXXXXX) 138 src_out=$(mktemp -t mig-helper-stdout1.XXXXXXXXXX) 139 src_outfifo=$(mktemp -u -t mig-helper-fifo-stdout1.XXXXXXXXXX) 140 dst_out=$(mktemp -t mig-helper-stdout2.XXXXXXXXXX) 141 dst_outfifo=$(mktemp -u -t mig-helper-fifo-stdout2.XXXXXXXXXX) 142 src_qmp=$(mktemp -u -t mig-helper-qmp1.XXXXXXXXXX) 143 dst_qmp=$(mktemp -u -t mig-helper-qmp2.XXXXXXXXXX) 144 dst_infifo=$(mktemp -u -t mig-helper-fifo-stdin.XXXXXXXXXX) 145 src_qmpout=/dev/null 146 dst_qmpout=/dev/null 147 148 mkfifo ${src_outfifo} 149 mkfifo ${dst_outfifo} 150 151 eval "$migcmdline" \ 152 -chardev socket,id=mon,path=${src_qmp},server=on,wait=off \ 153 -mon chardev=mon,mode=control > ${src_outfifo} & 154 live_pid=$! 155 cat ${src_outfifo} | tee ${src_out} | grep -v "Now migrate the VM (quiet)" & 156 157 # Start the first destination QEMU machine in advance of the test 158 # reaching the migration point, since we expect at least one migration. 159 # Then destination machines are started after the test outputs 160 # subsequent "Now migrate the VM" messages. 161 do_migration || return $? 162 163 while ps -p ${live_pid} > /dev/null ; do 164 # Wait for test exit or further migration messages. 165 if ! grep -q -i "Now migrate the VM" < ${src_out} ; then 166 sleep 0.1 167 else 168 do_migration || return $? 169 fi 170 done 171 172 wait ${live_pid} 173 ret=$? 174 175 while (( $(jobs -r | wc -l) > 0 )); do 176 sleep 0.1 177 done 178 179 return $ret 180} 181 182do_migration () 183{ 184 # We have to use cat to open the named FIFO, because named FIFO's, 185 # unlike pipes, will block on open() until the other end is also 186 # opened, and that totally breaks QEMU... 187 mkfifo ${dst_infifo} 188 eval "$migcmdline" \ 189 -chardev socket,id=mon,path=${dst_qmp},server=on,wait=off \ 190 -mon chardev=mon,mode=control -incoming unix:${dst_incoming} \ 191 < <(cat ${dst_infifo}) > ${dst_outfifo} & 192 incoming_pid=$! 193 cat ${dst_outfifo} | tee ${dst_out} | grep -v "Now migrate the VM (quiet)" & 194 195 # The test must prompt the user to migrate, so wait for the 196 # "Now migrate VM" console message. 197 while ! grep -q -i "Now migrate the VM" < ${src_out} ; do 198 if ! ps -p ${live_pid} > /dev/null ; then 199 echo "ERROR: Test exit before migration point." >&2 200 echo > ${dst_infifo} 201 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 202 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 203 return 3 204 fi 205 sleep 0.1 206 done 207 208 # Wait until the destination has created the incoming and qmp sockets 209 while ! [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 210 while ! [ -S ${dst_qmp} ] ; do sleep 0.1 ; done 211 212 qmp ${src_qmp} '"migrate", "arguments": { "uri": "unix:'${dst_incoming}'" }' > ${src_qmpout} 213 214 # Wait for the migration to complete 215 migstatus=`qmp ${src_qmp} '"query-migrate"' | grep return` 216 while ! grep -q '"completed"' <<<"$migstatus" ; do 217 sleep 0.1 218 if ! migstatus=`qmp ${src_qmp} '"query-migrate"'`; then 219 echo "ERROR: Querying migration state failed." >&2 220 echo > ${dst_infifo} 221 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 222 return 2 223 fi 224 migstatus=`grep return <<<"$migstatus"` 225 if grep -q '"failed"' <<<"$migstatus"; then 226 echo "ERROR: Migration failed." >&2 227 echo > ${dst_infifo} 228 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 229 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 230 return 2 231 fi 232 done 233 234 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 235 236 # keypress to dst so getchar completes and test continues 237 echo > ${dst_infifo} 238 rm ${dst_infifo} 239 240 # Wait for the incoming socket being removed, ready for next destination 241 while [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 242 243 wait ${live_pid} 244 ret=$? 245 246 # Now flip the variables because destination machine becomes source 247 # for the next migration. 248 live_pid=${incoming_pid} 249 tmp=${src_out} 250 src_out=${dst_out} 251 dst_out=${tmp} 252 tmp=${src_outfifo} 253 src_outfifo=${dst_outfifo} 254 dst_outfifo=${tmp} 255 tmp=${src_qmp} 256 src_qmp=${dst_qmp} 257 dst_qmp=${tmp} 258 259 return $ret 260} 261 262run_panic () 263{ 264 if ! command -v ncat >/dev/null 2>&1; then 265 echo "${FUNCNAME[0]} needs ncat (netcat)" >&2 266 return 77 267 fi 268 269 if ! command -v jq >/dev/null 2>&1; then 270 echo "${FUNCNAME[0]} needs jq" >&2 271 return 77 272 fi 273 274 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 275 trap 'rm -f ${qmp}' RETURN EXIT 276 277 qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX) 278 279 # start VM stopped so we don't miss any events 280 eval "$@" -chardev socket,id=mon,path=${qmp},server=on,wait=off \ 281 -mon chardev=mon,mode=control -S & 282 283 panic_event_count=$(qmp_events ${qmp} | jq -c 'select(.event == "GUEST_PANICKED")' | wc -l) 284 if [ "$panic_event_count" -lt 1 ]; then 285 echo "FAIL: guest did not panic" 286 ret=3 287 else 288 # some QEMU versions report multiple panic events 289 echo "PASS: guest panicked" 290 ret=1 291 fi 292 293 return $ret 294} 295 296migration_cmd () 297{ 298 if [ "$MIGRATION" = "yes" ]; then 299 echo "run_migration" 300 fi 301} 302 303panic_cmd () 304{ 305 if [ "$PANIC" = "yes" ]; then 306 echo "run_panic" 307 fi 308} 309 310search_qemu_binary () 311{ 312 local save_path=$PATH 313 local qemucmd qemu 314 315 : "${QEMU_ARCH:=$ARCH_NAME}" 316 317 export PATH=$PATH:/usr/libexec 318 for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do 319 if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then 320 qemu="$qemucmd" 321 break 322 fi 323 done 324 325 if [ -z "$qemu" ]; then 326 echo "A QEMU binary was not found." >&2 327 echo "You can set a custom location by using the QEMU=<path> environment variable." >&2 328 return 2 329 fi 330 command -v $qemu 331 export PATH=$save_path 332} 333 334initrd_cleanup () 335{ 336 rm -f $KVM_UNIT_TESTS_ENV 337 if [ "$KVM_UNIT_TESTS_ENV_OLD" ]; then 338 export KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD" 339 else 340 unset KVM_UNIT_TESTS_ENV 341 fi 342 unset KVM_UNIT_TESTS_ENV_OLD 343} 344 345initrd_create () 346{ 347 if [ "$ENVIRON_DEFAULT" = "yes" ]; then 348 trap_exit_push 'initrd_cleanup' 349 [ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV" 350 export KVM_UNIT_TESTS_ENV=$(mktemp) 351 env_params 352 env_file 353 env_errata || return $? 354 fi 355 356 unset INITRD 357 [ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV" 358 359 return 0 360} 361 362env_add_params () 363{ 364 local p 365 366 for p in "$@"; do 367 if eval test -v $p; then 368 eval export "$p" 369 else 370 eval export "$p=" 371 fi 372 grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV 373 done 374} 375 376env_params () 377{ 378 local qemu have_qemu 379 local _ rest 380 381 qemu=$(search_qemu_binary) && have_qemu=1 382 383 if [ "$have_qemu" ]; then 384 if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then 385 [ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL 386 fi 387 QEMU_VERSION_STRING="$($qemu -h | head -1)" 388 IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING" 389 fi 390 env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO 391 392 KERNEL_VERSION_STRING=$(uname -r) 393 IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING" 394 IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest" 395 KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*} 396 KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*} 397 ! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL 398 ! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION 399 env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION 400} 401 402env_file () 403{ 404 local line var 405 406 [ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return 407 408 for line in $(grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD"); do 409 var=${line%%=*} 410 if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then 411 eval export "$line" 412 grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV 413 fi 414 done 415} 416 417env_errata () 418{ 419 local new_env 420 421 if [ "$ACCEL" = "tcg" ]; then 422 export "ERRATA_FORCE=y" 423 elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then 424 echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2 425 return 2 426 elif [ "$ERRATATXT" ]; then 427 env_generate_errata 428 fi 429 new_env=$(sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u) 430 echo "$new_env" >>$KVM_UNIT_TESTS_ENV 431} 432 433env_generate_errata () 434{ 435 local line commit minver errata rest v p s x have 436 437 for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do 438 commit=${line%:*} 439 minver=${line#*:} 440 441 test -z "$commit" && continue 442 errata="ERRATA_$commit" 443 [ -n "${!errata}" ] && continue 444 445 IFS=. read -r v p rest <<<"$minver" 446 IFS=- read -r s x <<<"$rest" 447 s=${s%%[!0-9]*} 448 x=${x%%[!0-9]*} 449 450 if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then 451 echo "Bad minimum kernel version in $ERRATATXT, $minver" 452 return 2 453 fi 454 ! [[ $s =~ ^[0-9]+$ ]] && unset $s 455 ! [[ $x =~ ^[0-9]+$ ]] && unset $x 456 457 if (( $KERNEL_VERSION > $v || 458 ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then 459 have=y 460 elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then 461 if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then 462 if (( $KERNEL_SUBLEVEL > $s )); then 463 have=y 464 elif (( $KERNEL_SUBLEVEL == $s )); then 465 if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then 466 if (( $KERNEL_EXTRAVERSION >= $x )); then 467 have=y 468 else 469 have=n 470 fi 471 elif [ "$x" ] && (( $x != 0 )); then 472 have=n 473 else 474 have=y 475 fi 476 else 477 have=n 478 fi 479 elif [ "$s" ] && (( $s != 0 )); then 480 have=n 481 else 482 have=y 483 fi 484 else 485 have=n 486 fi 487 eval export "$errata=$have" 488 done 489} 490 491trap_exit_push () 492{ 493 local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//") 494 trap -- "$1; $old_exit" EXIT 495} 496 497kvm_available () 498{ 499 [ -c /dev/kvm ] || 500 return 1 501 502 [ "$HOST" = "$ARCH_NAME" ] || 503 ( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) || 504 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 505} 506 507hvf_available () 508{ 509 [ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1 510 [ "$HOST" = "$ARCH_NAME" ] || 511 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 512} 513 514set_qemu_accelerator () 515{ 516 ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"} 517 ACCEL=${ACCEL%%,*} 518 519 if [ "$ACCEL" = "kvm" ] && ! kvm_available; then 520 echo "KVM is needed, but not available on this host" >&2 521 return 2 522 fi 523 if [ "$ACCEL" = "hvf" ] && ! hvf_available; then 524 echo "HVF is needed, but not available on this host" >&2 525 return 2 526 fi 527 528 if [ -z "$ACCEL" ]; then 529 if kvm_available; then 530 ACCEL="kvm" 531 elif hvf_available; then 532 ACCEL="hvf" 533 else 534 ACCEL="tcg" 535 fi 536 fi 537 538 return 0 539} 540