#!/bin/sh
set -eu

: "${PRE15_DUT:?PRE15_DUT is required}"
: "${PRE15_ROOT:?PRE15_ROOT is required}"
: "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}"
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
: "${PRE15_SMOKE_CODEC:?PRE15_SMOKE_CODEC is required}"
. "$PRE15_LIB_DIR/runner.sh"

fixture_dir=$PRE15_DUT/tests/pre15/fixtures
generator=$fixture_dir/SMOKE-generate.py
kldsym_probe=$fixture_dir/SMOKE-kldsym.c
kld_builder=$fixture_dir/B28-build-kld.sh
artifacts=$PRE15_RUN_DIR/artifacts
source_first=$PRE15_CASE_TMP/source-first
source_second=$PRE15_CASE_TMP/source-second
image=$PRE15_CASE_TMP/SMOKE-$PRE15_SMOKE_CODEC.erofs
module=$PRE15_CASE_TMP/SMOKE-$PRE15_SMOKE_CODEC-erofs.ko

case "$PRE15_SMOKE_CODEC" in
plain)
	config=0
	uuid=00000000-0000-4000-8000-000000000100
	mkfs_codec=
	case_timeout=180
	;;
lz4)
	config=0
	uuid=00000000-0000-4000-8000-000000000104
	mkfs_codec='-zlz4 -C4096'
	case_timeout=240
	;;
lzma)
	config=0
	uuid=00000000-0000-4000-8000-0000000001a0
	mkfs_codec='-zlzma,level=6,dictsize=65536 -C4096'
	case_timeout=300
	;;
zstd)
	config=1
	uuid=00000000-0000-4000-8000-00000000025d
	mkfs_codec='-zzstd,level=3,dictsize=65536 -C4096'
	case_timeout=300
	;;
*) pre15_runner_fail "unknown smoke codec: $PRE15_SMOKE_CODEC" ;;
esac

for tool in clang cmp diff file mkfs.erofs nm python3 scp sha256sum timeout; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing smoke tool: $tool"
done
for fixture in "$generator" "$kldsym_probe" "$kld_builder" \
    "$PRE15_DUT/tests/pre15/cases/SMOKE-common.sh"; do
	pre15_record_fixture "smoke-$(basename "$fixture")" "$fixture"
done
mkdir -p "$artifacts"
if ! python3 -B "$generator" --output "$source_first" \
    >"$artifacts/source-first.json" 2>"$artifacts/source-first.stderr" || \
    ! python3 -B "$generator" --output "$source_second" \
    >"$artifacts/source-second.json" 2>"$artifacts/source-second.stderr"; then
	pre15_runner_fail 'smoke fixture generation failed'
fi
if ! diff -qr "$source_first" "$source_second" \
    >"$artifacts/source-repeat.diff" || \
    ! cmp "$artifacts/source-first.json" "$artifacts/source-second.json"; then
	pre15_runner_fail 'smoke source fixture is not byte reproducible'
fi
if ! timeout -k 5 120 sh -c \
    "mkfs.erofs -d0 -T0 --all-time --all-root --workers=1 --sort=path -x-1 -U$uuid $mkfs_codec '$image' '$source_first'" \
    >"$artifacts/mkfs.stdout" 2>"$artifacts/mkfs.stderr"; then
	pre15_runner_fail "$PRE15_SMOKE_CODEC smoke image generation failed"
fi
if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \
    "$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" "$config" \
    >"$artifacts/kld-build.stdout" 2>"$artifacts/kld-build.stderr"; then
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke cross-target KLD build failed"
fi
pre15_record_module "$module"
sha256sum "$image" >"$artifacts/image-sha256.txt"
sha256sum "$module" >"$artifacts/kld-sha256.txt"
file "$module" >"$artifacts/kld-file.txt"
nm -u "$module" | LC_ALL=C sort >"$artifacts/kld-nm-u.txt"
pre15_target_reached

test "${PRE15_MODE:-}" = qemu || \
	pre15_runner_fail 'smoke cases require QEMU mode'
: "${PRE15_QEMU_CONTROL_PATH:?QEMU control path is required}"
: "${PRE15_QEMU_SSH_KEY:?QEMU SSH key is required}"
: "${PRE15_QEMU_SSH_PORT:?QEMU SSH port is required}"
: "${PRE15_QEMU_SSH_USER:?QEMU SSH user is required}"

pre15_scp()
{
	timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" scp -O -q \
		-o BatchMode=yes -o StrictHostKeyChecking=no \
		-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
		-o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \
		-i "$PRE15_QEMU_SSH_KEY" -P "$PRE15_QEMU_SSH_PORT" \
		"$1" "$PRE15_QEMU_SSH_USER@127.0.0.1:$2"
}

remote_root=/root/pre15-smoke-$PRE15_SMOKE_CODEC
pre15_guest_ssh_bounded rm -rf "$remote_root"
pre15_guest_ssh_bounded mkdir -p "$remote_root"
if ! pre15_scp "$image" "$remote_root/image.erofs" || \
    ! pre15_scp "$module" "$remote_root/erofs.ko" || \
    ! pre15_scp "$kldsym_probe" "$remote_root/SMOKE-kldsym.c"; then
	pre15_infra_blocked "$PRE15_SMOKE_CODEC smoke transfer failed"
fi
if test "$PRE15_SMOKE_CODEC" = zstd; then
	if ! pre15_guest_ssh_bounded \
	    "cc -O2 -Wall -Wextra -Werror -std=c17 '$remote_root/SMOKE-kldsym.c' -o '$remote_root/SMOKE-kldsym'"; then
		pre15_infra_blocked 'could not compile guest ZSTDIO capability probe'
	fi
	if ! pre15_guest_ssh_bounded "$remote_root/SMOKE-kldsym" \
	    ZSTD_DCtx_setParameter ZSTD_createDCtx_advanced \
	    ZSTD_decompressStream ZSTD_freeDCtx ZSTD_isError \
	    >"$artifacts/zstdio-capability.txt" 2>"$artifacts/zstdio-capability.stderr"; then
		pre15_infra_blocked 'running guest kernel lacks required ZSTDIO symbols'
	fi
fi
if pre15_guest_ssh_bounded kldstat -q -m erofs >/dev/null 2>&1; then
	pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
fi
if ! pre15_guest_ssh_bounded kldload "$remote_root/erofs.ko" \
    >"$artifacts/kldload.stdout" 2>"$artifacts/kldload.stderr"; then
	if grep -q 'module already loaded or in kernel' \
	    "$artifacts/kldload.stderr"; then
		pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
	fi
	if test "$PRE15_SMOKE_CODEC" = zstd && \
	    grep -Eqi 'link_elf|symbol|not defined' "$artifacts/kldload.stderr"; then
		pre15_infra_blocked 'zstdio1 EROFS KLD cannot load on this guest kernel'
	fi
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke KLD failed to load"
fi
pre15_own_guest_kld erofs "$PRE15_SMOKE_CODEC smoke KLD"
smoke_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \
    -f "$remote_root/image.erofs") || \
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke md attach failed"
case "$smoke_md" in
md[0-9]*) ;;
*) pre15_runner_fail "unexpected smoke md unit: $smoke_md" ;;
esac
pre15_own_guest_md "$smoke_md" "$PRE15_SMOKE_CODEC smoke md"
smoke_mount=/mnt/pre15-smoke-$PRE15_SMOKE_CODEC
pre15_guest_ssh_bounded mkdir -p "$smoke_mount"
if ! pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/$smoke_md" "$smoke_mount"; then
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke readonly mount failed"
fi
pre15_own_guest_mount "$smoke_mount" "$PRE15_SMOKE_CODEC smoke mount"

python3 -B - "$artifacts/source-first.json" "$artifacts/expected-files.tsv" <<'PY'
import json
from pathlib import Path
import sys

manifest = json.loads(Path(sys.argv[1]).read_text(encoding="ascii"))
lines = [f"{item['path']}\t{item['size']}\t{item['sha256']}" for item in manifest["files"]]
Path(sys.argv[2]).write_text("\n".join(lines) + "\n", encoding="ascii")
PY
while IFS="$(printf '\t')" read -r path expected_size expected_hash; do
	actual_size=$(pre15_guest_ssh_bounded stat -f %z "$smoke_mount/$path") || \
		pre15_dut_fail "$PRE15_SMOKE_CODEC smoke stat failed: $path"
	actual_hash=$(pre15_guest_ssh_bounded sha256 -q "$smoke_mount/$path") || \
		pre15_dut_fail "$PRE15_SMOKE_CODEC smoke hash failed: $path"
	printf '%s\t%s\t%s\n' "$path" "$actual_size" "$actual_hash" \
		>>"$artifacts/guest-files.tsv"
	test "$actual_size" = "$expected_size" && test "$actual_hash" = "$expected_hash" || \
		pre15_dut_fail "$PRE15_SMOKE_CODEC smoke file mismatch: $path"
done <"$artifacts/expected-files.tsv"
pre15_guest_ssh_bounded \
    "find '$smoke_mount' -mindepth 1 -maxdepth 1 -exec basename '{}' ';' | sort" \
    >"$artifacts/guest-root-entries.txt"
printf '%s\n' empty nested payload.bin >"$artifacts/expected-root-entries.txt"
cmp "$artifacts/expected-root-entries.txt" "$artifacts/guest-root-entries.txt" || \
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke root readdir set mismatch"

if test "$PRE15_SMOKE_CODEC" = lz4; then
	dd if="$source_first/payload.bin" bs=1 skip=4093 count=131071 2>/dev/null | \
		sha256sum | awk '{print $1}' >"$artifacts/expected-range-sha256.txt"
	pre15_guest_ssh_bounded \
	    "dd if='$smoke_mount/payload.bin' bs=1 skip=4093 count=131071 2>/dev/null | sha256 -q" \
	    >"$artifacts/guest-range-sha256.txt"
	cmp "$artifacts/expected-range-sha256.txt" "$artifacts/guest-range-sha256.txt" || \
		pre15_dut_fail 'LZ4 smoke cross-pcluster partial range mismatch'
fi

pre15_guest_ssh_bounded dmesg >"$artifacts/guest-dmesg.txt"
if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free' \
    "$artifacts/guest-dmesg.txt"; then
	pre15_dut_fail "$PRE15_SMOKE_CODEC smoke observed kernel failure evidence"
fi
printf '%s\n' \
	"SMOKE-$PRE15_SMOKE_CODEC PASS mode=zstdio$config deadline=$case_timeout" \
	'readonly mount, exact file hashes/sizes, readdir set, and owned cleanup exercised'
