#!/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"

fixtures_only=0
if test "${1:-}" = --fixtures-only; then
	fixtures_only=1
	shift
fi
test "$#" -eq 0 || pre15_runner_fail 'B17 accepts only --fixtures-only'

fixture_dir=$PRE15_DUT/tests/pre15/fixtures
spec=$fixture_dir/B17-xattr-spec.json
generator=$fixture_dir/B17-xattr-generate.py
oracle=$fixture_dir/B17-xattr-oracle.py
seed=$fixture_dir/B17-xattr-seed.erofs
artifacts=$PRE15_RUN_DIR/artifacts
first=$PRE15_CASE_TMP/first
second=$PRE15_CASE_TMP/second
rebuilt=$PRE15_CASE_TMP/rebuilt-seed.erofs
rebuild_work=$PRE15_CASE_TMP/rebuild-work

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

pre15_record_fixture b17-spec "$spec"
pre15_record_fixture b17-generator "$generator"
pre15_record_fixture b17-oracle "$oracle"
pre15_record_fixture b17-seed "$seed"
pre15_record_fixture b17-case "$PRE15_DUT/tests/pre15/cases/B17-xattr-integrity.sh"
mkdir -p "$artifacts"

if python3 -B "$generator" rebuild-seed --spec "$spec" --seed "$seed" \
	--output "$rebuilt" --work "$rebuild_work" \
	>"$artifacts/rebuild-seed.json" 2>"$artifacts/rebuild-seed.stderr"; then
	:
else
	pre15_runner_fail 'B17 tracked seed is not reproducible'
fi

if python3 -B "$generator" generate --spec "$spec" --seed "$seed" \
	--output "$first" >"$artifacts/generate-first.json" \
	2>"$artifacts/generate-first.stderr"; then
	:
else
	pre15_runner_fail 'B17 first fixture generation failed'
fi
if python3 -B "$generator" generate --spec "$spec" --seed "$seed" \
	--output "$second" >"$artifacts/generate-second.json" \
	2>"$artifacts/generate-second.stderr"; then
	:
else
	pre15_runner_fail 'B17 second fixture generation failed'
fi

if cmp "$first/fixture-index.json" "$second/fixture-index.json" && \
	find "$first" -type f ! -name fixture-index.json -printf '%f\n' | sort | \
	while IFS= read -r name; do cmp "$first/$name" "$second/$name" || exit 1; done
then
	:
else
	pre15_runner_fail 'B17 fixture generation is not byte reproducible'
fi

if python3 -B "$oracle" --spec "$spec" --fixtures "$first" \
	--report "$artifacts/oracle-first.json" \
	>"$artifacts/oracle-first.stdout" 2>"$artifacts/oracle-first.stderr"; then
	:
else
	pre15_runner_fail 'B17 independent first oracle replay failed'
fi
if python3 -B "$oracle" --spec "$spec" --fixtures "$second" \
	--report "$artifacts/oracle-second.json" \
	>"$artifacts/oracle-second.stdout" 2>"$artifacts/oracle-second.stderr"; then
	:
else
	pre15_runner_fail 'B17 independent second oracle replay failed'
fi

cp "$first/fixture-index.json" "$artifacts/B17-fixture-index.json"
pre15_record_fixture b17-generated-index "$artifacts/B17-fixture-index.json"

if python3 - "$artifacts/B17-fixture-index.json" \
	"$artifacts/oracle-first.json" "$artifacts/B17-matrix.tsv" <<'PY'
import json
from pathlib import Path
import sys

index = json.loads(Path(sys.argv[1]).read_text(encoding="ascii"))
report = json.loads(Path(sys.argv[2]).read_text(encoding="ascii"))
if index["fixture_count"] != 25 or index["legal_count"] != 10 or index["damaged_count"] != 15:
    raise SystemExit("B17 fixture cardinality changed")
if report["status"] != "PASS" or report["legal_passed"] != 10 or report["damaged_passed"] != 15:
    raise SystemExit("B17 oracle matrix is incomplete")
lines = ["id\tclass\terrno\treject"]
for item in report["results"]:
    lines.append(
        f"{item['id']}\t{item['class']}\t{item['actual_errno']}\t{item['actual_reject']}"
    )
Path(sys.argv[3]).write_text("\n".join(lines) + "\n", encoding="ascii")
print(
    f"B17 fixtures: {index['fixture_count']} "
    f"({index['legal_count']} legal, {index['damaged_count']} damaged)"
)
print(f"B17 fixture-set SHA256: {index['fixture_set_sha256']}")
PY
then
	:
else
	pre15_runner_fail 'B17 fixture matrix summary failed'
fi

pre15_target_reached
if test "$fixtures_only" -eq 1; then
	printf '%s\n' 'B17 fixture gate: READY'
	exit 0
fi

if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$artifacts/B17-source-check.json" <<'PY'
from pathlib import Path
import json
import subprocess
import sys

root = Path(sys.argv[1])
dut = Path(sys.argv[2])
output = Path(sys.argv[3])
baseline = "98d76e4e73ed9a760b7f684d453b301c73167c22"


def committed(name: str) -> str:
    completed = subprocess.run(
        ["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/src/{name}"],
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    if completed.returncode != 0:
        raise SystemExit(f"cannot read B17 baseline {name}: {completed.stderr}")
    return completed.stdout


def replace_once(source: str, old: str, new: str, label: str) -> str:
    count = source.count(old)
    if count != 1:
        raise SystemExit(f"{label}: expected one baseline transform, found {count}")
    return source.replace(old, new, 1)


current = {
    name: (dut / "src" / name).read_text(encoding="utf-8")
    for name in ("internal.h", "super.c", "xattr.c")
}
base = {name: committed(name) for name in current}

expected_internal = replace_once(
    base["internal.h"],
    "\tuint8_t xattr_prefix_count;\n",
    "\tuint8_t xattr_prefix_count;\n\tuint8_t xattr_filter_reserved;\n",
    "raw xattr filter state",
)

expected_super = replace_once(
    base["super.c"],
    "\t/* A non-zero reserved value disables the current name-filter format. */\n"
    "\tif (erofs_sb_has_xattr_filter(sbi) && dsb->xattr_filter_reserved != 0)\n"
    "\t\tsbi->feature_compat &= ~EROFS_FEATURE_COMPAT_XATTR_FILTER;\n",
    "\t/* Preserve the raw feature declaration and gate its format at use sites. */\n"
    "\tsbi->xattr_filter_reserved = dsb->xattr_filter_reserved;\n",
    "raw feature preservation",
)

expected_xattr = replace_once(
    base["xattr.c"],
    "\tif (entry_size > remaining)\n\t\treturn (EINTEGRITY);\n",
    "\tif (entry_size > remaining)\n\t\treturn (EINTEGRITY);\n"
    "\tif (memchr(entry->e_name, '\\0', entry->e_name_len) != NULL)\n"
    "\t\treturn (EINTEGRITY);\n",
    "inline name NUL validation",
)
expected_xattr = replace_once(
    expected_xattr,
    "\terror = erofs_xattr_read_backing(sbi, backing_en, off, entry_size,\n"
    "\t    entrybuf);\n"
    "\tif (error != 0)\n"
    "\t\treturn (error);\n"
    "\tif (entry_sizep != NULL)\n",
    "\terror = erofs_xattr_read_backing(sbi, backing_en, off, entry_size,\n"
    "\t    entrybuf);\n"
    "\tif (error != 0)\n"
    "\t\treturn (error);\n"
    "\terror = erofs_xattr_validate_entry(entrybuf->data, entry_size, NULL,\n"
    "\t    value_sizep);\n"
    "\tif (error != 0) {\n"
    "\t\terofs_put_metabuf(entrybuf);\n"
    "\t\treturn (error);\n"
    "\t}\n"
    "\tif (entry_sizep != NULL)\n",
    "shared name NUL validation",
)
expected_xattr = replace_once(
    expected_xattr,
    "\tif (!erofs_sb_has_xattr_filter(sbi))\n\t\treturn (0);\n",
    "\tif (!erofs_sb_has_xattr_filter(sbi) ||\n"
    "\t    sbi->xattr_filter_reserved != 0)\n"
    "\t\treturn (0);\n",
    "xattr filter use-site gate",
)
expected_xattr = replace_once(
    expected_xattr,
    "\t\tprefix = buf.data;\n"
    "\t\tinfix_len = len - sizeof(*prefix);\n"
    "\t\tsbi->xattr_prefixes[i].base_index = prefix->base_index;\n",
    "\t\tprefix = buf.data;\n"
    "\t\tinfix_len = len - sizeof(*prefix);\n"
    "\t\tif (memchr(prefix->infix, '\\0', infix_len) != NULL) {\n"
    "\t\t\terror = EINTEGRITY;\n"
    "\t\t\tgoto fail;\n"
    "\t\t}\n"
    "\t\tsbi->xattr_prefixes[i].base_index = prefix->base_index;\n",
    "long-prefix NUL validation",
)

expected = {
    "internal.h": expected_internal,
    "super.c": expected_super,
    "xattr.c": expected_xattr,
}
for name in expected:
    if current[name] != expected[name]:
        raise SystemExit(f"{name} differs from the exact B17 ledger transforms")

diff = subprocess.run(
    ["git", "-C", str(root), "diff", baseline, "--", "repo-pre-15/src/internal.h",
     "repo-pre-15/src/super.c", "repo-pre-15/src/xattr.c"],
    check=True,
    text=True,
    stdout=subprocess.PIPE,
).stdout
for forbidden in ("xxh32", "bloom", "cache owner", "cache waiter"):
    if forbidden in diff.lower():
        raise SystemExit(f"B17 source diff contains out-of-scope marker: {forbidden}")
if "return (-E" in "".join(current.values()):
    raise SystemExit("negative errno entered the FreeBSD B17 source")

result = {
    "status": "PASS",
    "baseline": baseline,
    "changed_sources": sorted(expected),
    "raw_feature_preserved": True,
    "reserved_gated_at_use": True,
    "inline_name_nul": True,
    "shared_name_nul": True,
    "prefix_infix_nul": True,
    "acl_empty_suffix_preserved": True,
}
output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii")
print("B17 source: exact raw-feature and name-integrity transforms present")
PY
then
	:
else
	pre15_dut_fail 'B17 source does not match the fixture-authorized transforms'
fi

printf '%s\n' 'B17 source gate: PASS'
