#!/bin/sh
set -eu

gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P)
input=$gate_dir/P15-081-input.json
freebsd_src=${FREEBSD_SRC:-/work/build/freebsd-src}
base=
output=

while test "$#" -gt 0; do
	case "$1" in
	--base)
		test "$#" -ge 2 || { printf '%s\n' '--base requires a commit' >&2; exit 2; }
		base=$2
		shift 2
		;;
	--output)
		test "$#" -ge 2 || { printf '%s\n' '--output requires a directory' >&2; exit 2; }
		output=$2
		shift 2
		;;
	*)
		printf 'unknown argument: %s\n' "$1" >&2
		exit 2
		;;
	esac
done

test -n "$base" || { printf '%s\n' '--base is required' >&2; exit 2; }
test -n "$output" || { printf '%s\n' '--output is required' >&2; exit 2; }
test -f "$input" || { printf 'missing input: %s\n' "$input" >&2; exit 2; }
test -d "$freebsd_src/sys" || {
	printf 'missing FreeBSD source tree: %s\n' "$freebsd_src" >&2
	exit 2
}
for tool in cc dump.erofs fsck.erofs git mkfs.erofs python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || {
		printf 'missing required host tool: %s\n' "$tool" >&2
		exit 2
	}
done
test "$(id -u)" -eq 0 || {
	printf '%s\n' 'root is required to materialize all seven inode types' >&2
	exit 2
}
case "$output" in
/*) ;;
*) output=$PWD/$output ;;
esac
test ! -e "$output" || { printf 'refusing existing output: %s\n' "$output" >&2; exit 2; }
mkdir -p "$output"

python3 - "$root" "$input" "$base" "$output" "$freebsd_src" <<'PY'
from __future__ import annotations

import difflib
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import socket
import stat
import struct
import subprocess
import sys
import tempfile
from typing import Any


ROOT = Path(sys.argv[1])
INPUT = Path(sys.argv[2])
REQUESTED_BASE = sys.argv[3]
OUTPUT = Path(sys.argv[4])
FREEBSD_SRC = Path(sys.argv[5])
SPEC = json.loads(INPUT.read_text(encoding="ascii"))

SUPER = 1024
MAGIC = 0xE0F5E1E2
CRC32C_POLY = 0x82F63B78
S_IFMT = 0o170000
EINTEGRITY = 97
VTYPE_NUMBER = {
    "VNON": 0,
    "VREG": 1,
    "VDIR": 2,
    "VBLK": 3,
    "VCHR": 4,
    "VLNK": 5,
    "VSOCK": 6,
    "VFIFO": 7,
}


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def sha256_path(path: Path) -> str:
    return sha256_bytes(path.read_bytes())


def write_json(path: Path, value: Any) -> None:
    path.write_text(
        json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="ascii"
    )


def git(*args: str) -> str:
    return subprocess.check_output(
        ["git", "-C", str(ROOT), *args], text=True
    ).strip()


def source_at(commit: str, path: str) -> str:
    completed = subprocess.run(
        ["git", "-C", str(ROOT), "show", f"{commit}:{path}"],
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    if completed.returncode != 0:
        raise SystemExit(f"cannot read {path} at {commit}: {completed.stderr}")
    return completed.stdout


def run(argv: list[str], env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
    completed = subprocess.run(
        argv,
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        env=env,
    )
    return completed


def crc32c(data: bytes | bytearray, seed: int = 0xFFFFFFFF) -> int:
    value = seed
    for byte in data:
        value ^= byte
        for _ in range(8):
            value = (value >> 1) ^ (CRC32C_POLY if value & 1 else 0)
    return value & 0xFFFFFFFF


class Image:
    def __init__(self, data: bytes | bytearray):
        self.data = bytearray(data)
        if len(self.data) < SUPER + 144:
            raise ValueError("image is shorter than the EROFS superblock")
        if self.u32(SUPER) != MAGIC:
            raise ValueError("image has the wrong EROFS magic")
        self.block_bits = self.data[SUPER + 12]
        self.block_size = 1 << self.block_bits
        self.meta_blkaddr = self.u32(SUPER + 40)
        self.root_nid = self.u16(SUPER + 14)
        if self.root_nid == 0:
            self.root_nid = self.u64(SUPER + 112)
        if self.checksum_end > len(self.data):
            raise ValueError("image does not contain its checksummed range")

    @property
    def checksum_end(self) -> int:
        span = self.block_size
        if span > SUPER:
            span -= SUPER
        return SUPER + span

    def clone(self) -> "Image":
        return Image(self.data)

    def u16(self, offset: int) -> int:
        return struct.unpack_from("<H", self.data, offset)[0]

    def u32(self, offset: int) -> int:
        return struct.unpack_from("<I", self.data, offset)[0]

    def u64(self, offset: int) -> int:
        return struct.unpack_from("<Q", self.data, offset)[0]

    def put_u32(self, offset: int, value: int) -> None:
        struct.pack_into("<I", self.data, offset, value)

    def checksum_valid(self) -> bool:
        expected = self.u32(SUPER + 4)
        canonical = bytearray(self.data[SUPER : self.checksum_end])
        struct.pack_into("<I", canonical, 4, 0)
        return expected == crc32c(canonical)

    def update_checksum(self) -> None:
        self.put_u32(SUPER + 4, 0)
        self.put_u32(SUPER + 4, crc32c(self.data[SUPER : self.checksum_end]))
        if not self.checksum_valid():
            raise AssertionError("updated checksum is not valid")

    def inode(self, nid: int) -> dict[str, int]:
        offset = (self.meta_blkaddr << self.block_bits) + (nid << 5)
        if offset + 32 > len(self.data):
            raise ValueError(f"inode {nid} is outside the image")
        ifmt = self.u16(offset)
        version = ifmt & 1
        inode_size = 32 if version == 0 else 64
        if offset + inode_size > len(self.data):
            raise ValueError(f"inode {nid} is truncated")
        xattr_count = self.u16(offset + 2)
        xattr_size = 0 if xattr_count == 0 else 12 + (xattr_count - 1) * 4
        size = self.u32(offset + 8) if version == 0 else self.u64(offset + 8)
        return {
            "nid": nid,
            "offset": offset,
            "ifmt": ifmt,
            "layout": (ifmt >> 1) & 7,
            "inode_size": inode_size,
            "xattr_size": xattr_size,
            "mode": self.u16(offset + 4),
            "size": size,
            "startblk": self.u32(offset + 16),
        }

    def inode_data_offset(self, inode: dict[str, int]) -> int:
        if inode["layout"] == 2:
            return inode["offset"] + inode["inode_size"] + inode["xattr_size"]
        if inode["layout"] == 0:
            return inode["startblk"] << self.block_bits
        raise ValueError(f"unsupported directory layout {inode['layout']}")

    def directory(self, nid: int) -> dict[str, dict[str, Any]]:
        inode = self.inode(nid)
        if inode["mode"] & S_IFMT != stat.S_IFDIR:
            raise ValueError(f"inode {nid} is not a directory")
        data_offset = self.inode_data_offset(inode)
        size = inode["size"]
        if size < 12 or data_offset + size > len(self.data):
            raise ValueError("root directory bytes are outside the image")
        first_nameoff = self.u16(data_offset + 8)
        if first_nameoff == 0 or first_nameoff % 12 != 0 or first_nameoff >= size:
            raise ValueError("invalid first directory name offset")
        count = first_nameoff // 12
        records: dict[str, dict[str, Any]] = {}
        for index in range(count):
            entry = data_offset + index * 12
            nameoff = self.u16(entry + 8)
            endoff = self.u16(entry + 20) if index + 1 < count else size
            if not (first_nameoff <= nameoff < endoff <= size):
                raise ValueError(f"invalid directory name span at index {index}")
            raw = bytes(self.data[data_offset + nameoff : data_offset + endoff])
            name = raw.split(b"\0", 1)[0].decode("ascii")
            if not name or name in records:
                raise ValueError(f"invalid or duplicate directory name {name!r}")
            records[name] = {
                "entry_offset": entry,
                "nid": self.u64(entry),
                "file_type": self.data[entry + 10],
                "reserved": self.data[entry + 11],
                "nameoff": nameoff,
            }
        return records


def make_source(path: Path) -> None:
    previous_umask = os.umask(0o022)
    try:
        path.mkdir(mode=0o755)
        os.chmod(path, 0o755)
        (path / "regular").write_bytes(b"P15-081 real inode payload\n")
        os.chmod(path / "regular", 0o644)
        os.link(path / "regular", path / "regular-hard")
        (path / "subdir").mkdir(mode=0o755)
        os.chmod(path / "subdir", 0o755)
        os.symlink("regular", path / "symlink")
        os.mkfifo(path / "fifo", 0o600)
        os.chmod(path / "fifo", 0o600)
        os.mknod(path / "char", stat.S_IFCHR | 0o600, os.makedev(1, 3))
        os.chmod(path / "char", 0o600)
        os.mknod(path / "block", stat.S_IFBLK | 0o600, os.makedev(7, 0))
        os.chmod(path / "block", 0o600)
        sock = socket.socket(socket.AF_UNIX)
        try:
            sock.bind(str(path / "socket"))
        finally:
            sock.close()
        os.chmod(path / "socket", 0o600)
    finally:
        os.umask(previous_umask)


def build_seed(work: Path, output: Path) -> tuple[bytes, str, list[str]]:
    work.mkdir()
    source = work / "source"
    make_source(source)
    command = [
        "mkfs.erofs",
        "-d0",
        "-T0",
        "--all-time",
        "--all-root",
        "--workers=1",
        "-x-1",
        "-E",
        "noinline_data",
        "-U",
        SPEC["uuid"],
        str(output),
        str(source),
    ]
    env = dict(os.environ)
    env["SOURCE_DATE_EPOCH"] = "0"
    completed = run(command, env=env)
    if completed.returncode != 0:
        raise SystemExit(f"mkfs.erofs failed: {completed.stdout}")
    canonical = command[:-2] + ["SEED.erofs", "SOURCE"]
    return output.read_bytes(), completed.stdout, canonical


def extract_kind(output: str) -> str:
    match = re.search(r"^Size:.*?  ([A-Za-z ]+)$", output, re.MULTILINE)
    if match is None:
        raise ValueError(f"dump.erofs output has no inode kind:\n{output}")
    return match.group(1)


def extract_dump_nid(output: str) -> int:
    match = re.search(r"^NID: (\d+)\b", output, re.MULTILINE)
    if match is None:
        raise ValueError(f"dump.erofs output has no NID:\n{output}")
    return int(match.group(1))


def expected_errno(file_type: int, expected_file_type: int) -> int:
    if file_type < 1 or file_type > 7:
        return 0
    return 0 if file_type == expected_file_type else EINTEGRITY


if SPEC.get("schema") != 1 or SPEC.get("candidate") != "P15-081":
    raise SystemExit("invalid P15-081 gate input")
resolved = git("rev-parse", f"{REQUESTED_BASE}^{{commit}}")
if resolved != SPEC["required_base"]:
    raise SystemExit(f"P15-081 must replay {SPEC['required_base']}, got {resolved}")

sources = {path: source_at(resolved, path) for path in SPEC["source_sha256"]}
source_hashes = {
    path: sha256_bytes(text.encode("utf-8")) for path, text in sources.items()
}
for path, expected in SPEC["source_sha256"].items():
    if source_hashes[path] != expected:
        raise SystemExit(f"frozen source identity mismatch: {path}")

freebsd_head = subprocess.check_output(
    ["git", "-C", str(FREEBSD_SRC), "rev-parse", "HEAD"], text=True
).strip()
if freebsd_head != SPEC["freebsd_head"]:
    raise SystemExit(f"FreeBSD HEAD differs: {freebsd_head}")
freebsd_hashes = {}
for relative, expected in SPEC["freebsd_sha256"].items():
    path = FREEBSD_SRC / relative
    actual = sha256_path(path)
    freebsd_hashes[relative] = actual
    if actual != expected:
        raise SystemExit(f"FreeBSD source identity mismatch: {relative}")

mkfs_version_run = run(["mkfs.erofs", "-V"])
mkfs_version = mkfs_version_run.stdout.splitlines()[0]
if mkfs_version != SPEC["mkfs_version"]:
    raise SystemExit(
        f"mkfs.erofs version differs: expected {SPEC['mkfs_version']!r}, "
        f"got {mkfs_version!r}"
    )

namei = sources["repo-pre-15/src/namei.c"]
directory_source = sources["repo-pre-15/src/dir.c"]
inode_source = sources["repo-pre-15/src/inode.c"]
internal_source = sources["repo-pre-15/src/internal.h"]
vnops = sources["repo-pre-15/src/erofs_vnops.c"]
linux_namei = sources["src-linux/namei.c"]
linux_dir = sources["src-linux/dir.c"]
linux_ondisk = sources["src-linux/erofs_fs.h"]
source_anchors = {
    "freebsd_dirent_field": "*d_type = de->file_type;" in namei,
    "freebsd_inode_mode": "vi->vtype = IFTOVT(vi->mode);" in inode_source,
    "freebsd_lookup_vget_once": namei.count("erofs_vget(") == 1,
    "freebsd_dotdot_once": namei.count("error = vn_vget_ino(") == 1,
    "freebsd_hash_lookup": "error = vfs_hash_get(" in vnops,
    "freebsd_inode_decode_after_hash": vnops.index("error = vfs_hash_get(")
    < vnops.index("error = erofs_read_inode("),
    "linux_readdir_mapping": "fs_ftype_to_dtype(de->file_type)" in linux_dir,
    "linux_lookup_dirent_field": "*d_type = de->file_type;" in linux_namei,
    "linux_lookup_inode": "inode = erofs_iget(dir->i_sb, nid);" in linux_namei,
    "linux_generic_ft_contract": "EROFS file types should match generic FT_* types"
    in linux_ondisk,
}
if not all(source_anchors.values()):
    raise SystemExit(f"source entrypoint anchors changed: {source_anchors}")

freebsd_dirent = (FREEBSD_SRC / "sys/sys/dirent.h").read_text(encoding="utf-8")
freebsd_vnode = (FREEBSD_SRC / "sys/sys/vnode.h").read_text(encoding="utf-8")
if "#define\tDT_UNKNOWN\t 0" not in freebsd_dirent:
    raise SystemExit("FreeBSD DT_UNKNOWN contract changed")
if "#define\tIFTODT(mode)" not in freebsd_dirent:
    raise SystemExit("FreeBSD IFTODT contract changed")
if "int vfs_hash_get(" not in freebsd_vnode or "int\tvn_vget_ino(" not in freebsd_vnode:
    raise SystemExit("FreeBSD vnode lookup declarations changed")

type_by_name = {item["name"]: item for item in SPEC["file_types"]}
type_by_name["regular-hard"] = type_by_name["regular"]

fixtures = OUTPUT / "fixtures"
fixtures.mkdir()
with tempfile.TemporaryDirectory(prefix="p15-081-gate-") as temporary:
    temp = Path(temporary)
    first_bytes, first_stdout, canonical_command = build_seed(
        temp / "first", temp / "first.erofs"
    )
    second_bytes, second_stdout, second_command = build_seed(
        temp / "second", temp / "second.erofs"
    )
    if first_bytes != second_bytes or first_stdout != second_stdout:
        raise SystemExit("P15-081 seed generation is not byte reproducible")
    if canonical_command != second_command:
        raise SystemExit("P15-081 canonical mkfs command changed between runs")

    seed_path = fixtures / "seed.erofs"
    seed_path.write_bytes(first_bytes)
    (OUTPUT / "mkfs.stdout").write_text(first_stdout, encoding="utf-8")
    write_json(
        OUTPUT / "generator.json",
        {
            "command": canonical_command,
            "mkfs_version": mkfs_version,
            "repeat_byte_identical": True,
            "seed_sha256": sha256_bytes(first_bytes),
            "seed_size": len(first_bytes),
        },
    )

    seed = Image(first_bytes)
    if not seed.checksum_valid():
        raise SystemExit("generated seed checksum is invalid")
    seed_entries = seed.directory(seed.root_nid)
    required_names = {item["name"] for item in SPEC["file_types"]}
    required_names.add("regular-hard")
    missing = sorted(required_names - seed_entries.keys())
    if missing:
        raise SystemExit(f"seed directory is missing names: {missing}")

    seed_records = []
    for name in sorted(required_names):
        entry = seed_entries[name]
        inode = seed.inode(entry["nid"])
        expected_type = type_by_name[name]
        record = {
            "case": f"known-match-{name}",
            "class": "known-match",
            "expected_errno": 0,
            "file_type": entry["file_type"],
            "inode_mode": inode["mode"],
            "inode_mode_type": inode["mode"] & S_IFMT,
            "name": name,
            "nid": entry["nid"],
            "reserved": entry["reserved"],
            "vtype": expected_type["vtype"],
        }
        if entry["file_type"] != expected_type["erofs"]:
            raise SystemExit(f"mkfs emitted unexpected file type for {name}")
        if record["inode_mode_type"] != expected_type["mode_type"]:
            raise SystemExit(f"mkfs emitted unexpected inode mode for {name}")
        seed_records.append(record)

    if seed_entries[SPEC["hardlink"]["first"]]["nid"] != seed_entries[SPEC["hardlink"]["second"]]["nid"]:
        raise SystemExit("hardlink seed names do not share one inode")

    cases: list[dict[str, Any]] = []
    for item in SPEC["known_mismatches"]:
        cases.append(
            {
                "class": "known-mismatch",
                "field": "file_type",
                "id": f"known-mismatch-{item['name']}",
                "name": item["name"],
                "value": item["file_type"],
                "expected_errno": EINTEGRITY,
            }
        )
    for item in SPEC["tolerated"]:
        cases.append(
            {
                "class": "forward-compatible",
                "field": item["field"],
                "id": item["id"],
                "name": item["name"],
                "value": item["value"],
                "expected_errno": 0,
            }
        )

    case_records: list[dict[str, Any]] = []
    normal_entry_lines = [
        "id\tname\tnid\tdirent_file_type\tdirent_reserved\tinode_mode_type\tdump_dirent_type\tdump_inode_kind\tfsck\tcandidate_errno"
    ]
    fixture_digest = hashlib.sha256()
    for case in cases:
        image = seed.clone()
        entries = image.directory(image.root_nid)
        entry = entries[case["name"]]
        direct_offset = entry["entry_offset"] + (10 if case["field"] == "file_type" else 11)
        before = image.data[direct_offset]
        image.data[direct_offset] = case["value"]
        image.update_checksum()
        path = fixtures / f"{case['id']}.erofs"
        path.write_bytes(image.data)

        parsed = Image(path.read_bytes())
        if not parsed.checksum_valid():
            raise SystemExit(f"fixture checksum is invalid: {case['id']}")
        parsed_entry = parsed.directory(parsed.root_nid)[case["name"]]
        parsed_inode = parsed.inode(parsed_entry["nid"])
        expected_type = type_by_name[case["name"]]
        actual_errno = expected_errno(parsed_entry["file_type"], expected_type["erofs"])

        fsck = run(["fsck.erofs", "-d0", str(path)])
        fsck_clean = fsck.returncode == 0 and "<E>" not in fsck.stdout
        fsck_expected_clean = not (
            case["field"] == "file_type" and case["value"] > 7
        )
        dump_ls = run(["dump.erofs", "--ls", "--path=/", str(path)])
        dump_path = run(["dump.erofs", f"--path=/{case['name']}", str(path)])
        if dump_ls.returncode != 0 or dump_path.returncode != 0:
            raise SystemExit(f"normal path parser failed: {case['id']}")
        listing = re.search(
            rf"^\s+{parsed_entry['nid']}\s+(\d+)\s+{re.escape(case['name'])}$",
            dump_ls.stdout,
            re.MULTILINE,
        )
        if listing is None:
            raise SystemExit(f"dump listing did not reach dirent: {case['id']}")
        dump_dirent_type = int(listing.group(1))
        dump_kind = extract_kind(dump_path.stdout)
        dump_nid = extract_dump_nid(dump_path.stdout)

        record = {
            **case,
            "actual_errno": actual_errno,
            "checksum_valid": True,
            "direct_before": before,
            "direct_offset": direct_offset,
            "dump_dirent_type": dump_dirent_type,
            "dump_inode_kind": dump_kind,
            "dump_nid": dump_nid,
            "file_type": parsed_entry["file_type"],
            "fixture": path.name,
            "fixture_sha256": sha256_path(path),
            "fsck_clean": fsck_clean,
            "fsck_expected_clean": fsck_expected_clean,
            "inode_mode": parsed_inode["mode"],
            "inode_mode_type": parsed_inode["mode"] & S_IFMT,
            "nid": parsed_entry["nid"],
            "reserved": parsed_entry["reserved"],
            "vtype": expected_type["vtype"],
        }
        record["oracle_pass"] = all(
            (
                record["actual_errno"] == record["expected_errno"],
                record["fsck_clean"] == record["fsck_expected_clean"],
                record["dump_dirent_type"] == record["file_type"],
                record["dump_inode_kind"] == expected_type["kind"],
                record["dump_nid"] == record["nid"],
                record["inode_mode_type"] == expected_type["mode_type"],
            )
        )
        case_records.append(record)
        normal_entry_lines.append(
            "\t".join(
                str(value)
                for value in (
                    record["id"],
                    record["name"],
                    record["nid"],
                    record["file_type"],
                    record["reserved"],
                    record["inode_mode_type"],
                    record["dump_dirent_type"],
                    record["dump_inode_kind"],
                    "clean" if record["fsck_clean"] else "policy-reject",
                    record["actual_errno"],
                )
            )
        )
        fixture_digest.update(case["id"].encode("ascii"))
        fixture_digest.update(b"\0")
        fixture_digest.update(path.read_bytes())

    (OUTPUT / "normal-entry.tsv").write_text(
        "\n".join(normal_entry_lines) + "\n", encoding="ascii"
    )

    all_records = seed_records + case_records
    prototype = r'''#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define EINTEGRITY 97

enum prototype_vtype {
	VT_NON,
	VT_REG,
	VT_DIR,
	VT_BLK,
	VT_CHR,
	VT_LNK,
	VT_SOCK,
	VT_FIFO,
};

static bool
erofs_dirent_type_matches(uint8_t file_type, enum prototype_vtype vtype)
{
	switch (file_type) {
	case 1:
		return (vtype == VT_REG);
	case 2:
		return (vtype == VT_DIR);
	case 3:
		return (vtype == VT_CHR);
	case 4:
		return (vtype == VT_BLK);
	case 5:
		return (vtype == VT_FIFO);
	case 6:
		return (vtype == VT_SOCK);
	case 7:
		return (vtype == VT_LNK);
	default:
		return (true);
	}
}

int
main(int argc, char **argv)
{
	unsigned long file_type, vtype;
	int error;

	if (argc != 3)
		return (2);
	file_type = strtoul(argv[1], NULL, 0);
	vtype = strtoul(argv[2], NULL, 0);
	if (file_type > UINT8_MAX || vtype > VT_FIFO)
		return (2);
	error = erofs_dirent_type_matches((uint8_t)file_type,
	    (enum prototype_vtype)vtype) ? 0 : EINTEGRITY;
	printf("%d\n", error);
	return (0);
}
'''
    prototype_path = OUTPUT / "prototype.c"
    prototype_path.write_text(prototype, encoding="ascii")
    binary = temp / "prototype"
    compile_run = run(
        [
            "cc",
            "-std=c11",
            "-Wall",
            "-Wextra",
            "-Werror",
            str(prototype_path),
            "-o",
            str(binary),
        ]
    )
    if compile_run.returncode != 0:
        raise SystemExit(f"candidate prototype did not compile: {compile_run.stdout}")
    prototype_records = []
    for record in all_records:
        completed = run(
            [
                str(binary),
                str(record["file_type"]),
                str(VTYPE_NUMBER[record["vtype"]]),
            ]
        )
        if completed.returncode != 0:
            raise SystemExit(f"candidate prototype failed for {record['case'] if 'case' in record else record['id']}")
        observed = int(completed.stdout.strip())
        expected = record["expected_errno"]
        prototype_records.append(
            {
                "id": record.get("id", record.get("case")),
                "observed_errno": observed,
                "expected_errno": expected,
                "pass": observed == expected,
            }
        )

    helper = '''
bool
erofs_dirent_type_matches(uint8_t file_type, __enum_uint8(vtype) vtype)
{
\tswitch (file_type) {
\tcase EROFS_FT_REG_FILE:
\t\treturn (vtype == VREG);
\tcase EROFS_FT_DIR:
\t\treturn (vtype == VDIR);
\tcase EROFS_FT_CHRDEV:
\t\treturn (vtype == VCHR);
\tcase EROFS_FT_BLKDEV:
\t\treturn (vtype == VBLK);
\tcase EROFS_FT_FIFO:
\t\treturn (vtype == VFIFO);
\tcase EROFS_FT_SOCK:
\t\treturn (vtype == VSOCK);
\tcase EROFS_FT_SYMLINK:
\t\treturn (vtype == VLNK);
\tdefault:
\t\treturn (true);
\t}
}

'''
    inode_marker = "/*\n * Read and decode a disk inode."
    if inode_source.count(inode_marker) != 1:
        raise SystemExit("candidate inode insertion marker is ambiguous")
    candidate_inode = inode_source.replace(inode_marker, helper + inode_marker, 1)
    prototype_decl = (
        "bool erofs_dirent_type_matches(uint8_t file_type, "
        "__enum_uint8(vtype) vtype);\n"
    )
    internal_marker = "int erofs_read_inode(struct erofs_sb_info *sbi, erofs_nid_t nid,\n"
    if internal_source.count(internal_marker) != 1:
        raise SystemExit("candidate prototype insertion marker is ambiguous")
    candidate_internal = internal_source.replace(
        internal_marker, prototype_decl + internal_marker, 1
    )
    lookup_marker = "\tif (error != 0)\n\t\treturn (error);\n\t*ap->a_vpp = vp;\n"
    lookup_candidate = (
        "\tif (error != 0)\n"
        "\t\treturn (error);\n"
        "\tif ((cnp->cn_flags & ISDOTDOT) == 0 &&\n"
        "\t    !erofs_dirent_type_matches(dtype, VTOE(vp)->vtype)) {\n"
        "\t\tvput(vp);\n"
        "\t\treturn (EINTEGRITY);\n"
        "\t}\n"
        "\t*ap->a_vpp = vp;\n"
    )
    if namei.count(lookup_marker) != 1:
        raise SystemExit("candidate lookup insertion marker is ambiguous")
    candidate_namei = namei.replace(lookup_marker, lookup_candidate, 1)
    patch_lines = []
    for path, before, after in (
        ("src/inode.c", inode_source, candidate_inode),
        ("src/internal.h", internal_source, candidate_internal),
        ("src/namei.c", namei, candidate_namei),
    ):
        patch_lines.extend(
            difflib.unified_diff(
                before.splitlines(keepends=True),
                after.splitlines(keepends=True),
                fromfile=f"a/{path}",
                tofile=f"b/{path}",
            )
        )
    candidate_patch = "".join(patch_lines)
    (OUTPUT / "candidate.patch").write_text(candidate_patch, encoding="utf-8")

    lock_calls = re.findall(
        r"\b(?:erofs_vget|vn_vget_ino|vfs_hash_get|VOP_LOCK|vn_lock|lockmgr)\s*\(",
        helper + lookup_candidate,
    )
    cache_first = seed_entries[SPEC["hardlink"]["first"]]
    cache_second = seed_entries[SPEC["hardlink"]["second"]]
    cache_case = next(
        record for record in case_records if record["id"] == "known-mismatch-regular-hard"
    )
    cache_proof = {
        "first_lookup_name": SPEC["hardlink"]["first"],
        "second_lookup_name": SPEC["hardlink"]["second"],
        "different_namecache_keys": SPEC["hardlink"]["first"]
        != SPEC["hardlink"]["second"],
        "same_real_nid": cache_first["nid"] == cache_second["nid"],
        "second_dirent_known_mismatch": cache_case["actual_errno"] == EINTEGRITY,
        "normal_lookup_calls_existing_erofs_vget": namei.count("erofs_vget(") == 1,
        "erofs_vget_checks_hash_before_inode_read": source_anchors[
            "freebsd_inode_decode_after_hash"
        ],
    }
    lock_ledger = {
        "candidate_added_lock_or_vget_calls": lock_calls,
        "candidate_reads_immutable_vtype": "VTOE(vp)->vtype" in lookup_candidate,
        "dotdot_bypasses_validator":
        "(cnp->cn_flags & ISDOTDOT) == 0" in lookup_candidate,
        "mismatch_drops_locked_child": "vput(vp);" in lookup_candidate,
        "validator_before_vpp_publication": lookup_candidate.index(
            "erofs_dirent_type_matches"
        )
        < lookup_candidate.index("*ap->a_vpp = vp"),
        "validator_before_namecache_publication": candidate_namei.index(
            "erofs_dirent_type_matches"
        )
        < candidate_namei.index("cache_enter(dvp, vp, cnp)"),
        "readdir_has_no_vget": all(
            token not in directory_source
            for token in ("erofs_vget(", "vn_vget_ino(", "vfs_hash_get(")
        ),
        "parent_child_lock_sequence_unchanged": namei.count("erofs_vget(")
        == candidate_namei.count("erofs_vget(")
        and namei.count("error = vn_vget_ino(")
        == candidate_namei.count("error = vn_vget_ino("),
        "freebsd_contract_sha256": freebsd_hashes,
        "cache_hit_proof": cache_proof,
    }
    write_json(OUTPUT / "lock-ledger.json", lock_ledger)

    failures = []
    failures.extend(
        record["id"] for record in case_records if not record["oracle_pass"]
    )
    failures.extend(
        record["id"] for record in prototype_records if not record["pass"]
    )
    for key, value in lock_ledger.items():
        if key == "freebsd_contract_sha256":
            continue
        if key == "candidate_added_lock_or_vget_calls":
            if value:
                failures.append(key)
        elif key == "cache_hit_proof":
            if not all(value.values()):
                failures.append(key)
        elif value is not True:
            failures.append(key)
    status = "GO" if not failures else "STOP"

    oracle = {
        "cache_hit": cache_proof,
        "candidate_patch_sha256": sha256_bytes(candidate_patch.encode("utf-8")),
        "candidate_prototype_sha256": sha256_path(prototype_path),
        "case_records": case_records,
        "known_match_records": seed_records,
        "prototype_records": prototype_records,
        "source_anchors": source_anchors,
        "status": status,
    }
    write_json(OUTPUT / "oracle.json", oracle)
    write_json(OUTPUT / "source-sha256.json", source_hashes)
    write_json(
        OUTPUT / "freebsd-contract.json",
        {"head": freebsd_head, "sha256": freebsd_hashes},
    )

    fixture_index = {
        "fixture_count": len(list(fixtures.glob("*.erofs"))),
        "fixture_set_sha256": fixture_digest.hexdigest(),
        "fixtures": [
            {
                "name": path.name,
                "sha256": sha256_path(path),
                "size": path.stat().st_size,
            }
            for path in sorted(fixtures.glob("*.erofs"))
        ],
        "seed_repeat_byte_identical": True,
    }
    write_json(OUTPUT / "fixture-index.json", fixture_index)

    result = {
        "b11": "AUTHORIZED" if status == "GO" else "STOP-NO-SOURCE",
        "cache_hit_sequence_count": 1,
        "candidate": "P15-081",
        "failures": failures,
        "fixture_count": fixture_index["fixture_count"],
        "fixture_set_sha256": fixture_index["fixture_set_sha256"],
        "forward_compatible_case_count": len(SPEC["tolerated"]),
        "full_feature_suite": "NOT_RUN",
        "gate": "G11",
        "known_match_count": len(seed_records),
        "known_mismatch_count": len(SPEC["known_mismatches"]),
        "normal_entry_count": len(case_records),
        "prototype_case_count": len(prototype_records),
        "qemu": "NOT_RUN",
        "qemu_reason": "pre-source gate uses checksum-valid disk fixtures, independent host parsing, and a non-KLD prototype",
        "requested_base": REQUESTED_BASE,
        "resolved_base": resolved,
        "schema": 1,
        "status": status,
    }
    write_json(OUTPUT / "result.json", result)

hash_lines = []
for path in sorted(OUTPUT.rglob("*")):
    if path.is_file() and path.name != "SHA256SUMS":
        hash_lines.append(f"{sha256_path(path)}  {path.relative_to(OUTPUT)}")
(OUTPUT / "SHA256SUMS").write_text("\n".join(hash_lines) + "\n", encoding="ascii")
print(json.dumps(result, sort_keys=True))
if status != "GO":
    raise SystemExit(1)
PY
