#!/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_LIB_DIR/runner.sh"

baseline=bf72cdffbc2b58c2b6468e20391eea14096b5a83
fixture_dir=$PRE15_DUT/tests/pre15/fixtures
generator=$fixture_dir/B14-chunk-fixtures.py
kld_builder=$fixture_dir/B14-build-kld.sh
probe=$fixture_dir/B14-chunk-probe.c
spec=$fixture_dir/B14-chunk-spec.json
artifacts=$PRE15_RUN_DIR/artifacts
first=$PRE15_CASE_TMP/first
second=$PRE15_CASE_TMP/second

for tool in cmp fsck.erofs git mkfs.erofs python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing B14 host tool: $tool"
done

pre15_record_fixture b14-generator "$generator"
pre15_record_fixture b14-kld-builder "$kld_builder"
pre15_record_fixture b14-probe "$probe"
pre15_record_fixture b14-spec "$spec"
pre15_record_fixture b14-case "$PRE15_DUT/tests/pre15/cases/B14-chunk-types.sh"
pre15_record_fixture b14-inode "$PRE15_DUT/src/inode.c"
pre15_record_fixture b14-data "$PRE15_DUT/src/data.c"
pre15_record_fixture b14-vnops "$PRE15_DUT/src/erofs_vnops.c"
pre15_record_fixture b14-linux-inode "$PRE15_ROOT/src-linux/inode.c"

mkdir -p "$artifacts"
if ! python3 -B "$generator" generate --output "$first" --spec "$spec" \
    >"$artifacts/generate-first.stdout" 2>"$artifacts/generate-first.stderr"; then
	pre15_runner_fail 'B14 first fixture generation failed'
fi
if ! python3 -B "$generator" generate --output "$second" --spec "$spec" \
    >"$artifacts/generate-second.stdout" 2>"$artifacts/generate-second.stderr"; then
	pre15_runner_fail 'B14 repeat fixture generation failed'
fi
if ! cmp "$first/manifest.json" "$second/manifest.json" || \
    ! cmp "$first/SHA256SUMS" "$second/SHA256SUMS"; then
	pre15_runner_fail 'B14 repeat generation is not byte deterministic'
fi

for name in B14-slot1.blob B14-good32.erofs B14-good48.erofs \
    B14-bad-index.erofs B14-bad-index48.erofs B14-bad-reserved.erofs \
    B14-bad-format.erofs B14-bad-48-no-index.erofs; do
	pre15_record_fixture "b14-$name" "$first/$name"
done
pre15_target_reached

if ! python3 -B "$generator" audit --root "$PRE15_ROOT" --dut "$PRE15_DUT" \
    --baseline "$baseline" --spec "$spec" --report "$artifacts/B14-source-report.json" \
    >"$artifacts/audit.stdout" 2>"$artifacts/audit.stderr"; then
	pre15_dut_fail 'B14 source or Linux/FreeBSD semantic audit failed'
fi
if ! python3 -B "$generator" verify --output "$first" --spec "$spec" \
    >"$artifacts/verify-first.stdout" 2>"$artifacts/verify-first.stderr"; then
	pre15_dut_fail 'B14 fixture oracle failed'
fi
if ! python3 -B "$generator" verify --output "$second" --spec "$spec" \
    >"$artifacts/verify-second.stdout" 2>"$artifacts/verify-second.stderr"; then
	pre15_runner_fail 'B14 repeat fixture verification failed'
fi

if test "${PRE15_MODE:-host}" != qemu; then
	printf '%s\n' 'TC174 host fixture/source oracle PASS' \
		'QEMU runtime NOT_RUN in host mode'
	exit 0
fi

for tool in awk cc clang file nm scp tar; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing B14 QEMU tool: $tool"
done

: "${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}"

fixture_archive=$PRE15_CASE_TMP/B14-fixtures.tar.gz
module=$PRE15_CASE_TMP/B14-erofs.ko
if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \
    "$PRE15_CASE_TMP/kld-work" >"$artifacts/B14-kld-build.stdout" \
    2>"$artifacts/B14-kld-build.stderr"; then
	pre15_dut_fail 'B14 cross-target KLD build failed'
fi
pre15_record_module "$module"
file "$module" >"$artifacts/B14-kld-file.txt"
sha256sum "$module" >"$artifacts/B14-kld-sha256.txt"
nm -u "$module" | LC_ALL=C sort >"$artifacts/B14-kld-nm-u.txt"
tar -C "$first" -czf "$fixture_archive" .

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"
}

if ! pre15_scp "$fixture_archive" /root/B14-fixtures.tar.gz || \
    ! pre15_scp "$probe" /root/B14-chunk-probe.c || \
    ! pre15_scp "$module" /root/B14-erofs.ko; then
	pre15_infra_blocked 'could not transfer B14 module or fixtures to the guest'
fi
if ! pre15_guest_ssh_bounded \
    'rm -rf /root/pre15-b14-fixtures && mkdir /root/pre15-b14-fixtures && tar -xzf /root/B14-fixtures.tar.gz -C /root/pre15-b14-fixtures'; then
	pre15_infra_blocked 'could not prepare B14 guest directories'
fi
pre15_guest_ssh_bounded sha256 /root/B14-erofs.ko \
    >"$artifacts/B14-guest-module-sha256.txt"
if pre15_guest_ssh_bounded kldstat -n erofs >/dev/null 2>&1; then
	pre15_infra_blocked 'FreeBSD guest already has an EROFS module loaded'
fi
if ! pre15_guest_ssh_bounded kldload /root/B14-erofs.ko \
    >"$artifacts/B14-kldload.stdout" 2>"$artifacts/B14-kldload.stderr"; then
	if grep -q 'module already loaded or in kernel' \
	    "$artifacts/B14-kldload.stderr"; then
		pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
	fi
	pre15_dut_fail 'B14 exact-source KLD failed to load'
fi
pre15_own_guest_kld erofs 'B14 exact-source KLD'
if ! pre15_guest_ssh_bounded cc -Wall -Wextra -Werror \
    -o /root/B14-chunk-probe /root/B14-chunk-probe.c; then
	pre15_infra_blocked 'could not compile the B14 guest errno probe'
fi

pre15_attach_md()
{
	pre15_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode -f "$1") || \
		pre15_dut_fail "could not attach B14 provider: $1"
	case "$pre15_md" in
	md[0-9]*) ;;
	*) pre15_runner_fail "unexpected mdconfig output: $pre15_md" ;;
	esac
	pre15_md=${pre15_md#md}
	pre15_own_guest_md "$pre15_md" "$2"
	printf '%s\n' "$pre15_md"
}

pre15_mount_image()
{
	pre15_image=$1
	pre15_mountpoint=$2
	pre15_label=$3
	pre15_primary=$(pre15_attach_md "/root/pre15-b14-fixtures/$pre15_image" \
		"$pre15_label primary")
	pre15_guest_ssh_bounded mkdir -p "$pre15_mountpoint"
	if ! pre15_guest_ssh_bounded mount -t erofs -o ro \
	    -o "device.1=/dev/md$blob_md" "/dev/md$pre15_primary" \
	    "$pre15_mountpoint"; then
		pre15_dut_fail "$pre15_label mount failed"
	fi
	pre15_own_guest_mount "$pre15_mountpoint" "$pre15_label mount"
}

blob_md=$(pre15_attach_md /root/pre15-b14-fixtures/B14-slot1.blob \
	'B14 external slot 1')
pre15_mount_image B14-good32.erofs /mnt/pre15-b14-good32 'B14 good32'
pre15_guest_ssh_bounded cmp /root/pre15-b14-fixtures/source/real-dir/entry.txt \
    /mnt/pre15-b14-good32/chunk-dir/entry.txt
pre15_guest_ssh_bounded cmp /root/pre15-b14-fixtures/source/target.txt \
    /mnt/pre15-b14-good32/chunk-link
pre15_guest_ssh_bounded /root/B14-chunk-probe readlink-pass \
    /mnt/pre15-b14-good32/chunk-link target.txt
pre15_guest_ssh_bounded stat -f '%HT %z' /mnt/pre15-b14-good32/chunk-char \
    >"$artifacts/B14-char-stat.txt"
pre15_guest_ssh_bounded /root/B14-chunk-probe open-unsupported \
    /mnt/pre15-b14-good32/chunk-char
pre15_guest_ssh_bounded ls -1A /mnt/pre15-b14-good32/chunk-dir \
    >"$artifacts/B14-directory-list.txt"
if test "$(cat "$artifacts/B14-directory-list.txt")" != entry.txt; then
	pre15_dut_fail 'chunk directory readdir returned the wrong set'
fi

pre15_mount_image B14-good48.erofs /mnt/pre15-b14-good48 'B14 good48'
pre15_guest_ssh_bounded cmp /root/pre15-b14-fixtures/source/real-dir/entry.txt \
    /mnt/pre15-b14-good48/chunk-dir/entry.txt
pre15_guest_ssh_bounded /root/B14-chunk-probe readlink-pass \
    /mnt/pre15-b14-good48/chunk-link target.txt

pre15_mount_image B14-bad-index.erofs /mnt/pre15-b14-bad-index 'B14 bad index'
pre15_guest_ssh_bounded /root/B14-chunk-probe readlink-integrity \
    /mnt/pre15-b14-bad-index/chunk-link

pre15_mount_image B14-bad-index48.erofs /mnt/pre15-b14-bad-index48 \
    'B14 bad 48-bit index'
pre15_guest_ssh_bounded /root/B14-chunk-probe readlink-integrity \
    /mnt/pre15-b14-bad-index48/chunk-link

pre15_mount_image B14-bad-reserved.erofs /mnt/pre15-b14-bad-reserved \
    'B14 bad reserved'
pre15_guest_ssh_bounded /root/B14-chunk-probe stat-integrity \
    /mnt/pre15-b14-bad-reserved/chunk-dir

pre15_mount_image B14-bad-format.erofs /mnt/pre15-b14-bad-format \
    'B14 bad format'
pre15_guest_ssh_bounded /root/B14-chunk-probe stat-unsupported \
    /mnt/pre15-b14-bad-format/chunk-link

pre15_mount_image B14-bad-48-no-index.erofs /mnt/pre15-b14-bad-48-no-index \
    'B14 bad 48-bit without indexes'
pre15_guest_ssh_bounded /root/B14-chunk-probe stat-integrity \
    /mnt/pre15-b14-bad-48-no-index/chunk-link

if pre15_guest_ssh_bounded truss -f -o /root/B14-md-ebusy.truss \
    mdconfig -d -u "$blob_md"; then
	pre15_dut_fail 'external GEOM provider detached while B14 mounts were active'
fi
if ! pre15_guest_ssh_bounded grep -Eq 'ERR#16' /root/B14-md-ebusy.truss; then
	pre15_dut_fail 'external GEOM detach did not return exact EBUSY'
fi
pre15_guest_ssh_bounded cat /root/B14-md-ebusy.truss \
    >"$artifacts/B14-md-ebusy.truss"
pre15_guest_ssh_bounded dmesg >"$artifacts/B14-dmesg.txt"
printf '%s\n' 'TC174 QEMU directory/symlink/multidevice/48-bit PASS'
