#!/bin/sh
set -eu

script=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)/B32-qemu-run.sh

if test "${1:-}" != --worker; then
	test "$#" -eq 1 || {
		printf '%s\n' 'usage: B32-qemu-run.sh TC168-cache-inflight|TC184-vnode-races' >&2
		exit 20
	}
	case "$1" in
	TC168-cache-inflight|TC184-vnode-races) ;;
	*) printf 'unknown B32 QEMU scenario: %s\n' "$1" >&2; exit 20 ;;
	esac
	total=${PRE15_QEMU_TIMEOUT:-1200}
	case "$total" in
	''|*[!0-9]*|0) printf '%s\n' 'invalid PRE15_QEMU_TIMEOUT' >&2; exit 20 ;;
	esac
	exec timeout -k 30 "$total" "$script" --worker "$1"
fi

shift
test "$#" -eq 1 || exit 20
scenario=$1
: "${PRE15_EVIDENCE_ROOT:?PRE15_EVIDENCE_ROOT is required}"
: "${PRE15_QEMU_BASE_IMAGE:?PRE15_QEMU_BASE_IMAGE is required}"
: "${PRE15_QEMU_SSH_KEY:?PRE15_QEMU_SSH_KEY is required}"

dut=$(CDPATH= cd -- "$(dirname -- "$script")/../../.." && pwd -P)
root=$(CDPATH= cd -- "$dut/.." && pwd -P)
lib=$dut/tests/pre15/lib
case_script=$dut/tests/pre15/cases/B32-cache-state.sh
freebsd_src=${PRE15_FREEBSD_SRC:-${FREEBSD_SRC:-/work/build/freebsd-src}}
user=${PRE15_QEMU_SSH_USER:-root}
boot_timeout=${PRE15_QEMU_BOOT_TIMEOUT:-180}
guest_timeout=${PRE15_GUEST_COMMAND_TIMEOUT:-60}
case_timeout=${PRE15_B32_QEMU_CASE_TIMEOUT:-900}
run_id=$(date -u '+%Y%m%dT%H%M%SZ')-qemu-B32-$scenario-$$
run=$PRE15_EVIDENCE_ROOT/$run_id
case_run=$run/case
case_tmp=$run/case-tmp
overlay=$run/guest-overlay.qcow2
control=$run/ssh-control
serial=$run/serial.log
qemu_log=$run/qemu.log
ownership=$case_run/ownership.tsv
cleanup_log=$case_run/cleanup.log
target_marker=$case_run/target.marker
reason_file=$case_run/reason.txt
fixture_hashes=$case_run/fixture-hashes.tsv
module_hash=$case_run/module-hash.tsv
qemu_pid=
port=
ssh_ready=0
cleanup_done=0
cleanup_status=PASS

mkdir -p "$case_run" "$case_tmp"
: >"$ownership"
: >"$fixture_hashes"
: >"$module_hash"
: >"$reason_file"

cleanup()
{
	test "$cleanup_done" -eq 0 || return
	cleanup_done=1
	if test "$ssh_ready" -eq 1; then
		PRE15_QEMU_SSH_PORT=$port
		PRE15_QEMU_SSH_USER=$user
		PRE15_QEMU_SSH_KEY=$PRE15_QEMU_SSH_KEY
		PRE15_QEMU_CONTROL_PATH=$control
		PRE15_GUEST_COMMAND_TIMEOUT=$guest_timeout
		PRE15_OWNERSHIP_FILE=$ownership
		PRE15_CLEANUP_LOG=$cleanup_log
		PRE15_RUN_DIR=$case_run
		export PRE15_QEMU_SSH_PORT PRE15_QEMU_SSH_USER PRE15_QEMU_SSH_KEY
		export PRE15_QEMU_CONTROL_PATH PRE15_GUEST_COMMAND_TIMEOUT
		export PRE15_OWNERSHIP_FILE PRE15_CLEANUP_LOG PRE15_RUN_DIR
		. "$lib/runner.sh"
		pre15_cleanup_owned || cleanup_status=FAIL
		test "${PRE15_CLEANUP_STATUS_RESULT:-FAIL}" = PASS || cleanup_status=FAIL
	else
		printf '%s\n' 'PASS no guest resources reached before SSH readiness' \
		    >"$cleanup_log"
	fi
	if test -S "$control"; then
		timeout -k 5 15 ssh -S "$control" -O exit -p "${port:-22}" \
		    "$user@127.0.0.1" >>"$run/host-cleanup.log" 2>&1 || true
	fi
	if test -n "$qemu_pid" && test "$qemu_pid" != 26318 && \
	    kill -0 "$qemu_pid" 2>/dev/null; then
		kill -TERM "$qemu_pid" 2>/dev/null || cleanup_status=FAIL
		wait_count=0
		while kill -0 "$qemu_pid" 2>/dev/null && test "$wait_count" -lt 100; do
			sleep 0.1
			wait_count=$((wait_count + 1))
		done
		if kill -0 "$qemu_pid" 2>/dev/null; then
			kill -KILL "$qemu_pid" 2>/dev/null || cleanup_status=FAIL
			sleep 0.2
		fi
	fi
	if test -n "$qemu_pid" && kill -0 "$qemu_pid" 2>/dev/null; then
		cleanup_status=FAIL
		printf 'FAIL owned QEMU PID survived: %s\n' "$qemu_pid" \
		    >>"$run/host-cleanup.log"
	else
		printf 'PASS owned QEMU PID stopped: %s\n' "${qemu_pid:-not-started}" \
		    >>"$run/host-cleanup.log"
	fi
	if test -f "$overlay"; then
		rm -f -- "$overlay" || cleanup_status=FAIL
	fi
	test ! -e "$overlay" || cleanup_status=FAIL
	printf 'overlay_removed=%s\n' "$(test ! -e "$overlay" && printf yes || printf no)" \
	    >>"$run/host-cleanup.log"
	rm -rf -- "$case_tmp"
	stat -c 'path=%n size=%s mode=%a inode=%i mtime=%Y ctime=%Z' \
	    "$PRE15_QEMU_BASE_IMAGE" >"$run/base-identity-after.txt" || \
	    cleanup_status=FAIL
	if ! cmp -s "$run/base-identity-before.txt" "$run/base-identity-after.txt"; then
		cleanup_status=FAIL
		printf '%s\n' 'FAIL protected base metadata changed' \
		    >>"$run/host-cleanup.log"
	else
		printf '%s\n' 'PASS protected base metadata unchanged' \
		    >>"$run/host-cleanup.log"
	fi
	if test -n "$port"; then
		if timeout 10s python3 -B - "$port" <<'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
		then
			printf 'PASS dynamic port free: %s\n' "$port" \
			    >>"$run/host-cleanup.log"
		else
			cleanup_status=FAIL
			printf 'FAIL dynamic port remains open: %s\n' "$port" \
			    >>"$run/host-cleanup.log"
		fi
	fi
	printf '%s\n' "$cleanup_status" >"$run/cleanup.status"
}

finish()
{
	rc=$?
	trap - EXIT HUP INT TERM
	cleanup
	if test "$cleanup_status" != PASS; then
		printf '%s\n' 'RUNNER_FAIL owned cleanup or base metadata check failed' \
		    >>"$run/RESULT.txt"
		rc=20
	fi
	exit "$rc"
}

trap finish EXIT HUP INT TERM

for tool in cmp qemu-img qemu-system-x86_64 ssh stat timeout; do
	command -v "$tool" >/dev/null 2>&1 || {
		printf 'INFRA_BLOCKED missing QEMU tool: %s\n' "$tool" >"$run/RESULT.txt"
		exit 21
	}
done
test -f "$PRE15_QEMU_BASE_IMAGE" || {
	printf '%s\n' 'INFRA_BLOCKED protected base image is absent' >"$run/RESULT.txt"
	exit 21
}
test -f "$PRE15_QEMU_SSH_KEY" || {
	printf '%s\n' 'INFRA_BLOCKED SSH key is absent' >"$run/RESULT.txt"
	exit 21
}

stat -c 'path=%n size=%s mode=%a inode=%i mtime=%Y ctime=%Z' \
    "$PRE15_QEMU_BASE_IMAGE" >"$run/base-identity-before.txt"
timeout -k 5 30 qemu-img info --output=json "$PRE15_QEMU_BASE_IMAGE" \
    >"$run/base-qemu-img-info.json"
timeout -k 5 30 qemu-img create -f qcow2 -F qcow2 \
    -b "$PRE15_QEMU_BASE_IMAGE" "$overlay" >"$run/qemu-img-create.stdout" \
    2>"$run/qemu-img-create.stderr"

port=$(timeout 10s python3 -B - <<'PY'
import socket

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 != 9222:
        print(port)
        break
PY
)
python3 -B - "$run/qemu-argv.json" "$overlay" "$port" "$serial" <<'PY'
import json
from pathlib import Path
import sys

argv = [
    "qemu-system-x86_64", "-accel", "tcg,thread=multi", "-cpu", "qemu64",
    "-m", "6144", "-smp", "4",
    "-drive", f"file={sys.argv[2]},if=virtio,format=qcow2",
    "-netdev", f"user,id=net0,hostfwd=tcp:127.0.0.1:{sys.argv[3]}-:22",
    "-device", "virtio-net-pci,netdev=net0", "-display", "none",
    "-serial", f"file:{sys.argv[4]}", "-monitor", "none",
]
Path(sys.argv[1]).write_text(json.dumps(argv, separators=(",", ":")) + "\n", encoding="ascii")
PY

qemu-system-x86_64 -accel tcg,thread=multi -cpu qemu64 -m 6144 -smp 4 \
    -drive "file=$overlay,if=virtio,format=qcow2" \
    -netdev "user,id=net0,hostfwd=tcp:127.0.0.1:$port-:22" \
    -device virtio-net-pci,netdev=net0 -display none -serial "file:$serial" \
    -monitor none >"$run/qemu.stdout" 2>"$qemu_log" &
qemu_pid=$!
printf '%s\n' "$qemu_pid" >"$run/qemu.pid"
test "$qemu_pid" != 26318 || {
	printf '%s\n' 'RUNNER_FAIL QEMU PID collided with protected PID' >"$run/RESULT.txt"
	exit 20
}

boot_marker=$run/ssh-ready
boot_start=$(date -u '+%s')
set +e
timeout -s KILL "$boot_timeout" env B32_QEMU_PID="$qemu_pid" \
    B32_QEMU_PORT="$port" B32_QEMU_USER="$user" \
    B32_QEMU_KEY="$PRE15_QEMU_SSH_KEY" B32_QEMU_MARKER="$boot_marker" \
    /bin/sh -c '
	while kill -0 "$B32_QEMU_PID" 2>/dev/null; do
		if timeout -s KILL 8 ssh -o BatchMode=yes \
		    -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
		    -o ConnectTimeout=5 -i "$B32_QEMU_KEY" \
		    -p "$B32_QEMU_PORT" "$B32_QEMU_USER@127.0.0.1" true \
		    >/dev/null 2>&1; then
			: >"$B32_QEMU_MARKER"
			exit 0
		fi
		sleep 1
	done
	exit 21
'
boot_rc=$?
set -e
boot_end=$(date -u '+%s')
printf 'start_epoch=%s\nend_epoch=%s\nelapsed_seconds=%s\ndeadline_seconds=%s\nexit=%s\n' \
    "$boot_start" "$boot_end" "$((boot_end - boot_start))" "$boot_timeout" \
    "$boot_rc" >"$run/boot-timing.txt"
if test -f "$boot_marker"; then
	ssh_ready=1
elif ! kill -0 "$qemu_pid" 2>/dev/null; then
	printf '%s\n' 'INFRA_BLOCKED QEMU exited before guest SSH' >"$run/RESULT.txt"
	exit 21
else
	printf 'INFRA_BLOCKED guest SSH not ready within absolute %ss boot deadline\n' \
	    "$boot_timeout" >"$run/RESULT.txt"
	exit 21
fi

if ! timeout -k 5 30 ssh -MNf -o BatchMode=yes -o StrictHostKeyChecking=no \
    -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -o ControlMaster=yes \
    -o "ControlPath=$control" -i "$PRE15_QEMU_SSH_KEY" -p "$port" \
    "$user@127.0.0.1"; then
	printf '%s\n' 'INFRA_BLOCKED SSH ControlMaster failed' >"$run/RESULT.txt"
	exit 21
fi

export PRE15_MODE=qemu
export PRE15_DUT=$dut PRE15_ROOT=$root PRE15_LIB_DIR=$lib
export PRE15_CASE_TMP=$case_tmp PRE15_RUN_DIR=$case_run
export PRE15_FREEBSD_SRC=$freebsd_src PRE15_B32_SCENARIO=$scenario
export PRE15_QEMU_CONTROL_PATH=$control PRE15_QEMU_SSH_PORT=$port
export PRE15_QEMU_SSH_USER=$user PRE15_QEMU_SSH_KEY
export PRE15_GUEST_COMMAND_TIMEOUT=$guest_timeout
export PRE15_OWNERSHIP_FILE=$ownership PRE15_CLEANUP_LOG=$cleanup_log
export PRE15_TARGET_MARKER_FILE=$target_marker PRE15_REASON_FILE=$reason_file
export PRE15_FIXTURE_HASHES=$fixture_hashes PRE15_MODULE_HASH=$module_hash

set +e
timeout -k 10 "$case_timeout" /bin/sh "$case_script" \
    >"$case_run/stdout.log" 2>"$case_run/stderr.log"
case_rc=$?
set -e
case "$case_rc" in
0)
	if test -f "$target_marker"; then
		printf '%s PASS target=reached\n' "$scenario" >"$run/RESULT.txt"
	else
		printf '%s\n' 'RUNNER_FAIL case exited zero without target marker' \
		    >"$run/RESULT.txt"
		case_rc=20
	fi
	;;
10) printf '%s DUT_FAIL\n' "$scenario" >"$run/RESULT.txt" ;;
20) printf '%s RUNNER_FAIL\n' "$scenario" >"$run/RESULT.txt" ;;
21) printf '%s INFRA_BLOCKED\n' "$scenario" >"$run/RESULT.txt" ;;
124|137) printf '%s RUNNER_FAIL case-timeout=%ss\n' "$scenario" "$case_timeout" >"$run/RESULT.txt"; case_rc=20 ;;
*) printf '%s RUNNER_FAIL exit=%s\n' "$scenario" "$case_rc" >"$run/RESULT.txt"; case_rc=20 ;;
esac
exit "$case_rc"
