# The serial console needs a controlling terminal, which script(1) makes. # The guests are emulated, so no accelerator is needed here. if [ -t 0 ]; then skip 1 "stdin is a terminal, a console refusal cannot be tested here" else "$VMM" create co0 64M >/dev/null 2>&1 refuses 'needs a terminal' "console refuses without a terminal" console co0 fi if ! command -v script >/dev/null 2>&1; then skip 8 "no script(1) to allocate a pty" return 0 fi "$VMM" create co1 64M >/dev/null 2>&1 cfg co1 </dev/null 2>&1; then skip 8 "co1 did not start" return 0 fi # Ctrl-] raises SIGINT in the pty's whole foreground process group, this # wrapper included. A no-op trap keeps the wrapper alive to report; a # child gets default handling either way, so vmm's own trap fires. cat > "$WORK/console-run" < "$WORK/tty.before" "$VMM" console co1 echo \$? > "$WORK/console.rc" stty -g > "$WORK/tty.after" EOF chmod +x "$WORK/console-run" # One Ctrl-] per second until the console takes it and the pipe breaks, # so a slow attach costs a retry. timeout is the backstop: a console # that never detaches must fail, not hang. ( i=0 while [ "$i" -lt 15 ]; do sleep 1 printf '\035' i=$((i + 1)) done ) | timeout 60 script -q -c "$WORK/console-run" /dev/null \ > "$WORK/console.out" 2>&1 || : if grep -q 'Ctrl-] detaches' "$WORK/console.out"; then ok "console attaches and names the pty" else notok "console attaches and names the pty" "$(cat "$WORK/console.out")" fi rc=$(cat "$WORK/console.rc" 2>/dev/null || echo missing) if [ "$rc" = 130 ]; then ok "Ctrl-] detaches, and says it was interrupted" else notok "Ctrl-] detaches, and says it was interrupted" "rc=$rc" fi # A botched console leaves the terminal raw. if cmp -s "$WORK/tty.before" "$WORK/tty.after"; then ok "the terminal is handed back exactly as it was" else notok "the terminal is handed back exactly as it was" \ "before: $(cat "$WORK/tty.before" 2>/dev/null)" \ "after: $(cat "$WORK/tty.after" 2>/dev/null)" fi exits 0 "detaching leaves the guest running" status co1 # SIGTERM reaches console_restore with the terminal still open, and the # copy processes must all go with it. Ctrl-] cannot show this: it # signals the whole foreground group and takes them down anyway. rm -f "$WORK/term.wrapper" "$WORK/term.rc" cat > "$WORK/console-term" < "$WORK/term.wrapper" "$VMM" console co1 echo \$? > "$WORK/term.rc" sleep 20 EOF chmod +x "$WORK/console-term" timeout 60 script -q -c "$WORK/console-term" /dev/null \ > "$WORK/console-term.out" 2>&1 & term_job=$! i=0 while ! grep -q 'Ctrl-] detaches' "$WORK/console-term.out" 2>/dev/null; do [ "$i" -lt 15 ] || break sleep 1 i=$((i + 1)) done pts=$(tr -d '\r' < "$WORK/console-term.out" | sed -n 's/^console co1 (\([^)]*\)).*/\1/p') # vmm and the subshells it forks carry one argv between them, so the # console is the match that is the wrapper's own child. Signalling any # other one leaves vmm attached and proves nothing. wrapper=$(cat "$WORK/term.wrapper" 2>/dev/null || echo 0) termpid= for cmdline in /proc/[0-9]*/cmdline; do p=${cmdline#/proc/} p=${p%/cmdline} line= IFS= read -r line 2>/dev/null < "$cmdline" || : case $line in *"$VMM"consoleco1) ;; *) continue ;; esac if [ "$(awk '{print $4}' "/proc/$p/stat" 2>/dev/null)" = "$wrapper" ]; then termpid=$p fi done kill -TERM "$termpid" 2>/dev/null || : i=0 while [ ! -s "$WORK/term.rc" ] && [ "$i" -lt 10 ]; do sleep 1 i=$((i + 1)) done holding= if [ -n "$pts" ]; then for fd in /proc/[0-9]*/fd/0 /proc/[0-9]*/fd/1; do if [ "$(readlink "$fd" 2>/dev/null)" = "$pts" ]; then p=${fd#/proc/} holding="$holding ${p%%/*}" fi done fi if [ -z "$termpid" ] || [ -z "$pts" ] || [ ! -s "$WORK/term.rc" ]; then skip 1 "the console did not attach and exit, cannot check teardown" elif [ -z "$holding" ]; then ok "a terminated console leaves nothing holding the guest pty" else notok "a terminated console leaves nothing holding the guest pty" \ "pty $pts still open by pid:$holding" fi kill "$term_job" 2>/dev/null || : wait "$term_job" 2>/dev/null || : # Closing QEMU's pty makes both copies report EIO. That is an expected # detach, not an error for the user's terminal. rm -f "$WORK/console.rc" ( while :; do printf '\n' sleep 1 done ) | timeout 60 script -q -c "$WORK/console-run" /dev/null \ > "$WORK/console-die.out" 2>&1 & console_job=$! i=0 while ! grep -q 'Ctrl-] detaches' "$WORK/console-die.out" 2>/dev/null; do [ "$i" -lt 15 ] || break sleep 1 i=$((i + 1)) done "$VMM" kill co1 >/dev/null 2>&1 || : wait "$console_job" 2>/dev/null || : rc=$(cat "$WORK/console.rc" 2>/dev/null || echo missing) case $rc in 0 | 130) ok "guest death detaches the console" ;; *) notok "guest death detaches the console" "rc=$rc" ;; esac if grep -q 'cat:' "$WORK/console-die.out"; then notok "guest death does not print cat errors" \ "$(cat "$WORK/console-die.out")" else ok "guest death does not print cat errors" fi # The writer half of the console. No OS is needed to answer, only 15 # bytes of real mode code that poll the 16550 status register, read the # byte and write it straight back: # BA FD 03 mov dx,0x3fd | EC in al,dx | A8 01 test al,1 | 74 F8 jz -8 # BA F8 03 mov dx,0x3f8 | EC in al,dx | EE out dx,al | EB F1 jmp -15 # SeaBIOS will not boot a disk of one sector, so the image is padded. if command -v qemu-system-x86_64 >/dev/null 2>&1; then "$VMM" create co2 64M >/dev/null 2>&1 dd if=/dev/zero of="$VMMDIR/co2/disk.qcow2" bs=1M count=1 2>/dev/null printf '\272\375\003\354\250\001\164\370\272\370\003\354\356\353\361' | dd of="$VMMDIR/co2/disk.qcow2" conv=notrunc 2>/dev/null printf '\125\252' | dd of="$VMMDIR/co2/disk.qcow2" bs=1 seek=510 conv=notrunc 2>/dev/null cfg co2 </dev/null 2>&1; then # One line per second until the console is attached to take # one, as above. The console turns the terminal's own echo # off, so whatever comes back came back from the guest. ( i=0 while [ "$i" -lt 5 ]; do sleep 1 printf 'ECHO123' i=$((i + 1)) done sleep 2 printf '\035' ) | timeout 60 script -q -c "$VMM console co2" /dev/null \ > "$WORK/echo.out" 2>&1 || : if grep -q ECHO123 "$WORK/echo.out"; then ok "a keystroke reaches the guest and comes back" else notok "a keystroke reaches the guest and comes back" \ "$(cat "$WORK/echo.out")" fi "$VMM" kill co2 >/dev/null 2>&1 || : else skip 1 "co2 did not start" fi else skip 1 "no qemu-system-x86_64 for the echo guest" fi