#!/bin/sh
set -eu

dut=${1:?DUT path is required}
freebsd_src=${2:?FreeBSD source path is required}
base=${3:?base image is required}
askpass=${4:?askpass path is required}
output=${5:?output path is required}
protected_pid=26318
protected_port=9222
builder=$dut/tests/pre15/fixtures/B12-build-kld.sh

test ! -e "$output" || {
	printf '%s\n' "B12 QEMU output exists: $output" >&2
	exit 20
}
mkdir -p "$output"
work=$(mktemp -d /tmp/pre15-b12-qemu.XXXXXX)
overlay=$work/guest-overlay.qcow2
module=$work/B12-erofs-zstdio0.ko
qemu_pid=
port=
guest_ready=0
module_loaded=0

protected_start=$(awk '{print $22}' /proc/$protected_pid/stat)
protected_cmd=$(sha256sum /proc/$protected_pid/cmdline | awk '{print $1}')
stat -c 'path=%n size=%s inode=%i mode=%f mtime=%Y ctime=%Z' "$base" \
    >"$output/base-metadata-before.txt"

ssh_options()
{
	printf '%s\n' \
		-q \
		-o BatchMode=no \
		-o PubkeyAuthentication=no \
		-o PreferredAuthentications=keyboard-interactive,password \
		-o NumberOfPasswordPrompts=1 \
		-o StrictHostKeyChecking=no \
		-o UserKnownHostsFile=/dev/null \
		-o ConnectTimeout=5 \
		-p "$port"
}

guest()
{
	timeout -k 5 "${B12_GUEST_TIMEOUT:-60}" env DISPLAY=:0 \
	    SSH_ASKPASS="$askpass" SSH_ASKPASS_REQUIRE=force \
	    ssh $(ssh_options) root@127.0.0.1 "$@"
}

copy_to_guest()
{
	timeout -k 5 "${B12_COPY_TIMEOUT:-120}" env DISPLAY=:0 \
	    SSH_ASKPASS="$askpass" SSH_ASKPASS_REQUIRE=force \
	    scp -O -q \
	    -o BatchMode=no \
	    -o PubkeyAuthentication=no \
	    -o PreferredAuthentications=keyboard-interactive,password \
	    -o NumberOfPasswordPrompts=1 \
	    -o StrictHostKeyChecking=no \
	    -o UserKnownHostsFile=/dev/null \
	    -o ConnectTimeout=5 \
	    -P "$port" "$1" root@127.0.0.1:"$2"
}

port_free()
{
	python3 - "$1" <<'PY'
import socket
import sys

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.settimeout(0.2)
    raise SystemExit(0 if sock.connect_ex(("127.0.0.1", int(sys.argv[1]))) != 0 else 1)
PY
}

cleanup()
{
	set +e
	if test "$guest_ready" = 1 && test "$module_loaded" = 1; then
		guest kldunload /root/B12-erofs-zstdio0.ko \
		    >>"$output/cleanup.log" 2>&1
		module_loaded=0
	fi
	if test -n "$qemu_pid" && kill -0 "$qemu_pid" 2>/dev/null; then
		kill -TERM "$qemu_pid" 2>/dev/null
		wait_count=0
		while kill -0 "$qemu_pid" 2>/dev/null && test "$wait_count" -lt 20; do
			sleep 1
			wait_count=$((wait_count + 1))
		done
		if kill -0 "$qemu_pid" 2>/dev/null; then
			kill -KILL "$qemu_pid" 2>/dev/null
		fi
		wait "$qemu_pid" 2>/dev/null
	fi
	rm -rf "$work"
}
trap cleanup EXIT HUP INT TERM

for tool in awk clang file nm python3 qemu-img qemu-system-x86_64 scp \
    sha256sum ssh stat timeout; do
	command -v "$tool" >/dev/null 2>&1 || {
		printf '%s\n' "missing B12 QEMU tool: $tool" >&2
		exit 21
	}
done
test -d "$freebsd_src/sys"
test -f "$base"
test -x "$askpass"
test -r /proc/$protected_pid/stat

timeout -k 10 600 /bin/sh "$builder" "$dut" "$freebsd_src" "$module" \
    "$work/kld-work" >"$output/build.stdout" 2>"$output/build.stderr"
file "$module" >"$output/module.file"
sha256sum "$module" >"$output/module.sha256"
nm -g "$module" | LC_ALL=C sort >"$output/module.nm-global"
nm -u "$module" | LC_ALL=C sort >"$output/module.nm-u"

qemu-img create -q -f qcow2 -F qcow2 -b "$base" "$overlay"
port=$(python3 - "$protected_port" <<'PY'
import socket
import sys

protected = int(sys.argv[1])
while True:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind(("127.0.0.1", 0))
        port = sock.getsockname()[1]
    if port != protected:
        print(port)
        break
PY
)
printf '%s\n' "$port" >"$output/owned-port.txt"
qemu-system-x86_64 -accel tcg,thread=multi -cpu qemu64 \
    -m 3072 -smp 2 \
    -drive "file=$overlay,if=virtio,format=qcow2,cache=none" \
    -netdev "user,id=net0,hostfwd=tcp:127.0.0.1:$port-:22" \
    -device virtio-net-pci,netdev=net0 -display none \
    -serial "file:$output/serial.log" -monitor none \
    -D "$output/qemu.log" >"$output/qemu-process.log" 2>&1 &
qemu_pid=$!
printf '%s\n' "$qemu_pid" >"$output/owned-pid.txt"
printf '%s\n' \
    "qemu-system-x86_64 -m 3072 -smp 2 -drive owned-overlay -port $port" \
    >"$output/qemu-argv-summary.txt"

boot_wait=0
while test "$boot_wait" -lt "${B12_BOOT_TIMEOUT:-300}"; do
	if ! kill -0 "$qemu_pid" 2>/dev/null; then
		printf '%s\n' 'owned QEMU exited before SSH readiness' >&2
		exit 21
	fi
	if B12_GUEST_TIMEOUT=10 guest true >/dev/null 2>&1; then
		guest_ready=1
		break
	fi
	sleep 2
	boot_wait=$((boot_wait + 2))
done
test "$guest_ready" = 1 || {
	printf '%s\n' 'owned QEMU SSH boot deadline expired' >&2
	exit 21
}

guest uname -a >"$output/guest-uname.txt"
guest sysctl -n kern.osreldate >"$output/guest-osreldate.txt"
test "$(tr -d '\r\n' < "$output/guest-osreldate.txt")" = 1500068 || {
	printf '%s\n' 'guest OSREL is not exact FreeBSD 15 ABI 1500068' >&2
	exit 10
}
if guest kldstat -q -m erofs >/dev/null 2>&1; then
	printf '%s\n' 'guest already has an EROFS module loaded' >&2
	exit 21
fi
copy_to_guest "$module" /root/B12-erofs-zstdio0.ko
guest sha256 -q /root/B12-erofs-zstdio0.ko >"$output/guest-module.sha256"
test "$(awk '{print $1}' "$output/module.sha256")" = \
    "$(tr -d '\r\n' < "$output/guest-module.sha256")"
guest dmesg >"$output/dmesg-before.txt"
guest kldload /root/B12-erofs-zstdio0.ko >"$output/kldload.stdout" \
    2>"$output/kldload.stderr"
module_loaded=1
guest kldstat -n B12-erofs-zstdio0.ko >"$output/kldstat-loaded.txt"
guest kldunload /root/B12-erofs-zstdio0.ko >"$output/kldunload.stdout" \
    2>"$output/kldunload.stderr"
module_loaded=0
guest dmesg >"$output/dmesg-after.txt"
diff -u "$output/dmesg-before.txt" "$output/dmesg-after.txt" \
    >"$output/dmesg.diff" || true
sed -n '/^+++ /d; /^+/s/^+//p' "$output/dmesg.diff" \
    >"$output/dmesg-added.txt"
if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free|link_elf_obj: symbol .* undefined' \
    "$output/dmesg-added.txt"; then
	printf '%s\n' 'B12 exact-ABI load produced a kernel diagnostic' >&2
	exit 10
fi

cleanup
trap - EXIT HUP INT TERM
stat -c 'path=%n size=%s inode=%i mode=%f mtime=%Y ctime=%Z' "$base" \
    >"$output/base-metadata-after.txt"
cmp "$output/base-metadata-before.txt" "$output/base-metadata-after.txt"
test -r /proc/$protected_pid/stat
test "$(awk '{print $22}' /proc/$protected_pid/stat)" = "$protected_start"
test "$(sha256sum /proc/$protected_pid/cmdline | awk '{print $1}')" = "$protected_cmd"
port_free "$port"
cat >"$output/result.json" <<EOF
{
  "candidate": "P15-087",
  "exact_abi_osreldate": 1500068,
  "kld_load": "PASS",
  "kld_unload": "PASS",
  "kernel_log": "PASS",
  "owned_overlay_removed": true,
  "owned_pid_stopped": true,
  "owned_port": $port,
  "owned_port_free_after": true,
  "protected_pid": $protected_pid,
  "protected_port": $protected_port,
  "schema": 1,
  "status": "PASS"
}
EOF
printf '%s\n' 'B12 exact-ABI QEMU load/unload PASS'
