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