# 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 <<EOF
MEM=256
CPUS=1
HWACCEL=no
EOF

if ! "$VMM" start 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" <<EOF
#!/bin/sh
trap ':' INT
stty -g > "$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" <<EOF
#!/bin/sh
echo \$\$ > "$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 <<EOF
ARCH=x86_64
HWACCEL=no
IMAGE_FORMAT=raw
MEM=256
CPUS=1
EOF
	if "$VMM" start 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
