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 # This is too complex for ${var/search/replace} 48 # shellcheck disable=SC2001 49 sig=$(sed 's/.*terminating on signal \([0-9][0-9]*\).*/\1/' <<<"$sig") 50 fi 51 fi 52 53 if [ $ret -eq 0 ]; then 54 # Some signals result in a zero return status, but the 55 # error log tells the truth. 56 if [ "$sig" ]; then 57 ((ret=sig+128)) 58 else 59 # Exiting with zero (non-debugexit) is an error 60 ret=1 61 fi 62 elif [ $ret -eq 1 ]; then 63 # Even when ret==1 (unittest success) if we also got stderr 64 # logs, then we assume a QEMU failure. Otherwise we translate 65 # status of 1 to 0 (SUCCESS) 66 if [ "$errors" ]; then 67 if ! grep -qvi warning <<<"$errors" ; then 68 ret=0 69 fi 70 else 71 ret=0 72 fi 73 fi 74 75 return $ret 76} 77 78run_qemu_status () 79{ 80 local stdout ret 81 82 exec {stdout}>&1 83 lines=$(run_qemu "$@" > >(tee /dev/fd/$stdout)) 84 ret=$? 85 exec {stdout}>&- 86 87 if [ $ret -eq 1 ]; then 88 testret=$(grep '^EXIT: ' <<<"$lines" | head -n1 | sed 's/.*STATUS=\([0-9][0-9]*\).*/\1/') 89 if [ "$testret" ]; then 90 if [ $testret -eq 1 ]; then 91 ret=0 92 else 93 ret=$testret 94 fi 95 fi 96 fi 97 98 return $ret 99} 100 101timeout_cmd () 102{ 103 local s 104 105 if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then 106 if [ "$CONFIG_EFI" = 'y' ]; then 107 s=${TIMEOUT: -1} 108 if [ "$s" = 's' ]; then 109 TIMEOUT=${TIMEOUT:0:-1} 110 ((TIMEOUT += 10)) # Add 10 seconds for booting UEFI 111 TIMEOUT="${TIMEOUT}s" 112 fi 113 fi 114 echo "timeout -k 1s --foreground $TIMEOUT" 115 fi 116} 117 118qmp () 119{ 120 echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | ncat -U $1 121} 122 123qmp_events () 124{ 125 while ! test -S "$1"; do sleep 0.1; done 126 echo '{ "execute": "qmp_capabilities" }{ "execute": "cont" }' | 127 ncat --no-shutdown -U $1 | 128 jq -c 'select(has("event"))' 129} 130 131filter_quiet_msgs () 132{ 133 grep -v "Now migrate the VM (quiet)" | 134 grep -v "Begin continuous migration (quiet)" | 135 grep -v "End continuous migration (quiet)" | 136 grep -v "Skipped VM migration (quiet)" 137} 138 139seen_migrate_msg () 140{ 141 if [ $skip_migration -eq 1 ]; then 142 grep -q -e "Now migrate the VM" -e "Begin continuous migration" < $1 143 else 144 grep -q -e "Now migrate the VM" -e "Begin continuous migration" -e "Skipped VM migration" < $1 145 fi 146} 147 148run_migration () 149{ 150 if ! command -v ncat >/dev/null 2>&1; then 151 echo "${FUNCNAME[0]} needs ncat (netcat)" >&2 152 return 77 153 fi 154 155 migcmdline=("$@") 156 157 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 158 trap 'rm -f ${src_out} ${dst_out} ${src_outfifo} ${dst_outfifo} ${dst_incoming} ${src_qmp} ${dst_qmp} ${src_infifo} ${dst_infifo}' RETURN EXIT 159 160 dst_incoming=$(mktemp -u -t mig-helper-socket-incoming.XXXXXXXXXX) 161 src_out=$(mktemp -t mig-helper-stdout1.XXXXXXXXXX) 162 src_outfifo=$(mktemp -u -t mig-helper-fifo-stdout1.XXXXXXXXXX) 163 dst_out=$(mktemp -t mig-helper-stdout2.XXXXXXXXXX) 164 dst_outfifo=$(mktemp -u -t mig-helper-fifo-stdout2.XXXXXXXXXX) 165 src_qmp=$(mktemp -u -t mig-helper-qmp1.XXXXXXXXXX) 166 dst_qmp=$(mktemp -u -t mig-helper-qmp2.XXXXXXXXXX) 167 src_infifo=$(mktemp -u -t mig-helper-fifo-stdin1.XXXXXXXXXX) 168 dst_infifo=$(mktemp -u -t mig-helper-fifo-stdin2.XXXXXXXXXX) 169 src_qmpout=/dev/null 170 dst_qmpout=/dev/null 171 skip_migration=0 172 continuous_migration=0 173 174 mkfifo ${src_outfifo} 175 mkfifo ${dst_outfifo} 176 177 # Holding both ends of the input fifo open prevents opens from 178 # blocking and readers getting EOF when a writer closes it. 179 # These fds appear to be unused to shellcheck so quieten the warning. 180 mkfifo ${src_infifo} 181 mkfifo ${dst_infifo} 182 # shellcheck disable=SC2034 183 exec {src_infifo_fd}<>${src_infifo} 184 # shellcheck disable=SC2034 185 exec {dst_infifo_fd}<>${dst_infifo} 186 187 "${migcmdline[@]}" \ 188 -chardev socket,id=mon,path=${src_qmp},server=on,wait=off \ 189 -mon chardev=mon,mode=control \ 190 < ${src_infifo} > ${src_outfifo} & 191 live_pid=$! 192 # Shellcheck complains about useless cat but it is clearer than a 193 # redirect in this case. 194 # shellcheck disable=SC2002 195 cat ${src_outfifo} | tee ${src_out} | filter_quiet_msgs & 196 197 # Start the first destination QEMU machine in advance of the test 198 # reaching the migration point, since we expect at least one migration. 199 # Then destination machines are started after the test outputs 200 # subsequent "Now migrate the VM" messages. 201 do_migration || return $? 202 203 while ps -p ${live_pid} > /dev/null ; do 204 if [ ${continuous_migration} -eq 1 ] ; then 205 do_migration || return $? 206 elif ! seen_migrate_msg ${src_out} ; then 207 sleep 0.1 208 elif grep -q "Begin continuous migration" < ${src_out} ; then 209 do_migration || return $? 210 elif grep -q "Now migrate the VM" < ${src_out} ; then 211 do_migration || return $? 212 elif [ $skip_migration -eq 0 ] && grep -q "Skipped VM migration" < ${src_out} ; then 213 echo > ${src_infifo} # Resume src and carry on. 214 break; 215 fi 216 done 217 218 wait ${live_pid} 219 ret=$? 220 221 while (( $(jobs -r | wc -l) > 0 )); do 222 sleep 0.1 223 done 224 225 return $ret 226} 227 228do_migration () 229{ 230 "${migcmdline[@]}" \ 231 -chardev socket,id=mon,path=${dst_qmp},server=on,wait=off \ 232 -mon chardev=mon,mode=control -incoming unix:${dst_incoming} \ 233 < ${dst_infifo} > ${dst_outfifo} & 234 incoming_pid=$! 235 # Shellcheck complains about useless cat but it is clearer than a 236 # redirect in this case. 237 # shellcheck disable=SC2002 238 cat ${dst_outfifo} | tee ${dst_out} | filter_quiet_msgs & 239 240 # The test must prompt the user to migrate, so wait for the 241 # "Now migrate VM" or similar console message. 242 while [ ${continuous_migration} -eq 0 ] && ! seen_migrate_msg ${src_out} ; do 243 if ! ps -p ${live_pid} > /dev/null ; then 244 echo > ${dst_infifo} 245 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 246 echo "ERROR: Test exit before migration point." >&2 247 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 248 return 3 249 fi 250 sleep 0.1 251 done 252 253 if grep -q "Begin continuous migration" < ${src_out} ; then 254 if [ ${continuous_migration} -eq 1 ] ; then 255 echo > ${dst_infifo} 256 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 257 echo "ERROR: Continuous migration already begun." >&2 258 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 259 return 3 260 fi 261 continuous_migration=1 262 echo > ${src_infifo} 263 fi 264 265 # Wait until the destination has created the incoming and qmp sockets 266 while ! [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 267 while ! [ -S ${dst_qmp} ] ; do sleep 0.1 ; done 268 269 if [ $skip_migration -eq 0 ] && grep -q "Skipped VM migration" < ${src_out} ; then 270 # May not get any migrations, exit to main loop for now... 271 # No migrations today, shut down dst in an orderly manner... 272 if [ ${continuous_migration} -eq 1 ] ; then 273 echo > ${dst_infifo} 274 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 275 echo "ERROR: Can't skip in continuous migration." >&2 276 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 277 return 3 278 fi 279 echo > ${dst_infifo} 280 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 281 echo > ${src_infifo} # Resume src and carry on. 282 skip_migration=1 283 return 0 284 fi 285 286 qmp ${src_qmp} '"migrate", "arguments": { "uri": "unix:'${dst_incoming}'" }' > ${src_qmpout} 287 288 # Wait for the migration to complete 289 migstatus=$(qmp ${src_qmp} '"query-migrate"' | grep return) 290 while ! grep -q '"completed"' <<<"$migstatus" ; do 291 sleep 0.1 292 if ! migstatus=$(qmp ${src_qmp} '"query-migrate"'); then 293 echo "ERROR: Querying migration state failed." >&2 294 echo > ${dst_infifo} 295 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 296 return 2 297 fi 298 migstatus=$(grep return <<<"$migstatus") 299 if grep -q '"failed"' <<<"$migstatus"; then 300 echo "ERROR: Migration failed." >&2 301 echo > ${dst_infifo} 302 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 303 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 304 return 2 305 fi 306 done 307 308 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 309 310 # Should we end continuous migration? 311 if grep -q "End continuous migration" < ${src_out} ; then 312 if [ ${continuous_migration} -eq 0 ] ; then 313 echo "ERROR: Can't end continuous migration when not started." >&2 314 echo > ${dst_infifo} 315 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 316 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 317 return 3 318 fi 319 continuous_migration=0 320 echo > ${src_infifo} 321 fi 322 323 if [ ${continuous_migration} -eq 0 ]; then 324 # keypress to dst so getchar completes and test continues 325 echo > ${dst_infifo} 326 fi 327 328 # Wait for the incoming socket being removed, ready for next destination 329 while [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 330 331 wait ${live_pid} 332 ret=$? 333 334 # Now flip the variables because destination machine becomes source 335 # for the next migration. 336 live_pid=${incoming_pid} 337 tmp=${src_out} 338 src_out=${dst_out} 339 dst_out=${tmp} 340 tmp=${src_infifo} 341 src_infifo=${dst_infifo} 342 dst_infifo=${tmp} 343 tmp=${src_outfifo} 344 src_outfifo=${dst_outfifo} 345 dst_outfifo=${tmp} 346 tmp=${src_qmp} 347 src_qmp=${dst_qmp} 348 dst_qmp=${tmp} 349 350 return $ret 351} 352 353run_panic () 354{ 355 if ! command -v ncat >/dev/null 2>&1; then 356 echo "${FUNCNAME[0]} needs ncat (netcat)" >&2 357 return 77 358 fi 359 360 if ! command -v jq >/dev/null 2>&1; then 361 echo "${FUNCNAME[0]} needs jq" >&2 362 return 77 363 fi 364 365 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 366 trap 'rm -f ${qmp}' RETURN EXIT 367 368 qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX) 369 370 # start VM stopped so we don't miss any events 371 "$@" -chardev socket,id=mon,path=${qmp},server=on,wait=off \ 372 -mon chardev=mon,mode=control -S & 373 374 panic_event_count=$(qmp_events ${qmp} | jq -c 'select(.event == "GUEST_PANICKED")' | wc -l) 375 if [ "$panic_event_count" -lt 1 ]; then 376 echo "FAIL: guest did not panic" 377 ret=3 378 else 379 # some QEMU versions report multiple panic events 380 echo "PASS: guest panicked" 381 ret=1 382 fi 383 384 return $ret 385} 386 387migration_cmd () 388{ 389 if [ "$MIGRATION" = "yes" ]; then 390 echo "run_migration" 391 fi 392} 393 394panic_cmd () 395{ 396 if [ "$PANIC" = "yes" ]; then 397 echo "run_panic" 398 fi 399} 400 401search_qemu_binary () 402{ 403 local save_path=$PATH 404 local qemucmd qemu 405 406 : "${QEMU_ARCH:=$ARCH_NAME}" 407 408 export PATH=$PATH:/usr/libexec 409 for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do 410 if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then 411 qemu="$qemucmd" 412 break 413 fi 414 done 415 416 if [ -z "$qemu" ]; then 417 echo "A QEMU binary was not found." >&2 418 echo "You can set a custom location by using the QEMU=<path> environment variable." >&2 419 return 2 420 fi 421 command -v $qemu 422 export PATH=$save_path 423} 424 425initrd_cleanup () 426{ 427 rm -f $KVM_UNIT_TESTS_ENV 428 if [ "$KVM_UNIT_TESTS_ENV_OLD" ]; then 429 export KVM_UNIT_TESTS_ENV 430 KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD" 431 else 432 unset KVM_UNIT_TESTS_ENV 433 fi 434 unset KVM_UNIT_TESTS_ENV_OLD 435} 436 437initrd_create () 438{ 439 if [ "$ENVIRON_DEFAULT" = "yes" ]; then 440 trap_exit_push 'initrd_cleanup' 441 [ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV" 442 export KVM_UNIT_TESTS_ENV 443 KVM_UNIT_TESTS_ENV=$(mktemp) 444 env_params 445 env_file 446 env_errata || return $? 447 fi 448 449 unset INITRD 450 [ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV" 451 452 return 0 453} 454 455env_add_params () 456{ 457 local p 458 459 for p in "$@"; do 460 if eval test -v $p; then 461 eval export "$p" 462 else 463 eval export "$p=" 464 fi 465 grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV 466 done 467} 468 469env_params () 470{ 471 local qemu have_qemu 472 local _ rest 473 474 qemu=$(search_qemu_binary) && have_qemu=1 475 476 if [ "$have_qemu" ]; then 477 if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then 478 [ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL 479 fi 480 QEMU_VERSION_STRING="$($qemu -h | head -1)" 481 # Shellcheck does not see QEMU_MAJOR|MINOR|MICRO are used 482 # shellcheck disable=SC2034 483 IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING" 484 fi 485 env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO 486 487 KERNEL_VERSION_STRING=$(uname -r) 488 IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING" 489 IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest" 490 KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*} 491 KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*} 492 ! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL 493 ! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION 494 env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION 495} 496 497env_file () 498{ 499 local line var 500 501 [ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return 502 503 grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD" | while IFS= read -r line ; do 504 var=${line%%=*} 505 if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then 506 eval export "$line" 507 grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV 508 fi 509 done 510} 511 512env_errata () 513{ 514 local new_env 515 516 if [ "$ACCEL" = "tcg" ]; then 517 export "ERRATA_FORCE=y" 518 elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then 519 echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2 520 return 2 521 elif [ "$ERRATATXT" ]; then 522 env_generate_errata 523 fi 524 new_env=$(sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u) 525 echo "$new_env" >>$KVM_UNIT_TESTS_ENV 526} 527 528env_generate_errata () 529{ 530 local line commit minver errata rest v p s x have 531 532 for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do 533 commit=${line%:*} 534 minver=${line#*:} 535 536 test -z "$commit" && continue 537 errata="ERRATA_$commit" 538 [ -n "${!errata}" ] && continue 539 540 IFS=. read -r v p rest <<<"$minver" 541 IFS=- read -r s x <<<"$rest" 542 s=${s%%[!0-9]*} 543 x=${x%%[!0-9]*} 544 545 if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then 546 echo "Bad minimum kernel version in $ERRATATXT, $minver" 547 return 2 548 fi 549 ! [[ $s =~ ^[0-9]+$ ]] && unset $s 550 ! [[ $x =~ ^[0-9]+$ ]] && unset $x 551 552 if (( $KERNEL_VERSION > $v || 553 ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then 554 have=y 555 elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then 556 if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then 557 if (( $KERNEL_SUBLEVEL > $s )); then 558 have=y 559 elif (( $KERNEL_SUBLEVEL == $s )); then 560 if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then 561 if (( $KERNEL_EXTRAVERSION >= $x )); then 562 have=y 563 else 564 have=n 565 fi 566 elif [ "$x" ] && (( $x != 0 )); then 567 have=n 568 else 569 have=y 570 fi 571 else 572 have=n 573 fi 574 elif [ "$s" ] && (( $s != 0 )); then 575 have=n 576 else 577 have=y 578 fi 579 else 580 have=n 581 fi 582 eval export "$errata=$have" 583 done 584} 585 586trap_exit_push () 587{ 588 local old_exit 589 590 old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//") 591 trap -- "$1; $old_exit" EXIT 592} 593 594kvm_available () 595{ 596 [ -c /dev/kvm ] || 597 return 1 598 599 [ "$HOST" = "$ARCH_NAME" ] || 600 ( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) || 601 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 602} 603 604hvf_available () 605{ 606 [ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1 607 [ "$HOST" = "$ARCH_NAME" ] || 608 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 609} 610 611set_qemu_accelerator () 612{ 613 # Shellcheck does not see ACCEL_PROPS is used 614 # shellcheck disable=SC2034 615 ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"} 616 ACCEL=${ACCEL%%,*} 617 618 if [ "$ACCEL" = "kvm" ] && ! kvm_available; then 619 echo "KVM is needed, but not available on this host" >&2 620 return 2 621 fi 622 if [ "$ACCEL" = "hvf" ] && ! hvf_available; then 623 echo "HVF is needed, but not available on this host" >&2 624 return 2 625 fi 626 627 if [ -z "$ACCEL" ]; then 628 if kvm_available; then 629 ACCEL="kvm" 630 elif hvf_available; then 631 ACCEL="hvf" 632 else 633 ACCEL="tcg" 634 fi 635 fi 636 637 return 0 638} 639