#!/bin/sh
set -eu

gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P)
input=$gate_dir/P15-052-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 git python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || {
		printf 'missing required host tool: %s\n' "$tool" >&2
		exit 2
	}
done
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 hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
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"))

U32_MAX = (1 << 32) - 1
U48_MAX = (1 << 48) - 1
U64_MAX = (1 << 64) - 1
I64_MAX = (1 << 63) - 1


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


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


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 extract_function(source: str, name: str) -> str:
    match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
    if match is None:
        raise SystemExit(f"function not found: {name}")
    start = source.rfind("\n\n", 0, match.start()) + 2
    brace = source.find("{", match.end())
    if brace < 0:
        raise SystemExit(f"function body not found: {name}")
    depth = 0
    state = "code"
    index = brace
    while index < len(source):
        char = source[index]
        following = source[index + 1] if index + 1 < len(source) else ""
        if state == "code":
            if char == "/" and following == "*":
                state = "block"
                index += 2
                continue
            if char == "/" and following == "/":
                state = "line"
                index += 2
                continue
            if char == '"':
                state = "string"
            elif char == "'":
                state = "character"
            elif char == "{":
                depth += 1
            elif char == "}":
                depth -= 1
                if depth == 0:
                    return source[start : index + 1] + "\n"
        elif state == "block" and char == "*" and following == "/":
            state = "code"
            index += 2
            continue
        elif state == "line" and char == "\n":
            state = "code"
        elif state in {"string", "character"}:
            if char == "\\":
                index += 2
                continue
            if (state == "string" and char == '"') or (
                state == "character" and char == "'"
            ):
                state = "code"
        index += 1
    raise SystemExit(f"unterminated function: {name}")


def line_number(source: str, needle: str) -> int:
    if source.count(needle) != 1:
        raise SystemExit(
            f"source needle must occur exactly once ({source.count(needle)}): {needle}"
        )
    return source.count("\n", 0, source.index(needle)) + 1


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

sources = {
    path: source_at(resolved, path) for path in SPEC["source_sha256"]
}
source_hashes = {
    path: sha256(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}")

function_bodies: dict[str, str] = {}
for key, counts in SPEC["function_errno_counts"].items():
    path, function = key.rsplit(":", 1)
    body = extract_function(sources[path], function)
    function_bodies[key] = body
    for errno_name, expected_count in counts.items():
        actual_count = body.count(f"return ({errno_name});")
        if actual_count != expected_count:
            raise SystemExit(
                f"{key} {errno_name} count changed: {actual_count} != {expected_count}"
            )
    if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", body):
        raise SystemExit(f"negative errno entered FreeBSD function: {key}")

param_path = FREEBSD_SRC / "sys/amd64/include/param.h"
types_path = FREEBSD_SRC / "sys/sys/_types.h"
geom_path = FREEBSD_SRC / "sys/geom/geom.h"
for path in (param_path, types_path, geom_path):
    if not path.is_file():
        raise SystemExit(f"missing FreeBSD contract source: {path}")
param_source = param_path.read_text(encoding="utf-8")
types_source = types_path.read_text(encoding="utf-8")
geom_source = geom_path.read_text(encoding="utf-8")
page_match = re.search(r"^#define\s+PAGE_SHIFT\s+(\d+)\b", param_source, re.MULTILINE)
if page_match is None:
    raise SystemExit("PAGE_SHIFT is absent from amd64 param.h")
page_shift = int(page_match.group(1))
if page_shift != 12:
    raise SystemExit(f"unexpected audited amd64 PAGE_SHIFT: {page_shift}")
if "typedef\t__int64_t\t__off_t;" not in types_source:
    raise SystemExit("FreeBSD off_t source is not the audited signed 64-bit type")
if "off_t\t\t\tmediasize;" not in geom_source:
    raise SystemExit("GEOM mediasize is not the audited off_t field")

freebsd_head = subprocess.check_output(
    ["git", "-C", str(FREEBSD_SRC), "rev-parse", "HEAD"], text=True
).strip()
freebsd_contract = {
    "head": freebsd_head,
    "page_shift": page_shift,
    "off_t": "signed-64",
    "geom_mediasize": "off_t",
    "files": {
        str(path.relative_to(FREEBSD_SRC)): sha256(path.read_bytes())
        for path in (param_path, types_path, geom_path)
    },
}

# These are maxima after the current super/inode decode and mount-time media
# checks, not unconstrained C-structure values.
inode_slot_min = 32
inode_isize_max = 64
xattr_isize_max = 4 + 4 * ((1 << 16) - 2)
inode_off_max = I64_MAX - inode_slot_min
idx_base_max = inode_off_max + inode_isize_max + xattr_isize_max
entry_size_max = 8
chunkbits_min = 9
chunkbits_max = page_shift + 31
chunk_idx_max = I64_MAX >> chunkbits_min
blkaddr_max = U48_MAX
physical_max = blkaddr_max << page_shift
chunk_off_max = (1 << chunkbits_max) - 1
prefix_record_max = 256
prefix_off_max = U32_MAX * 4 + 255 * (3 + 2 + prefix_record_max)
xattr_base_max = U32_MAX << page_shift
xattr_relative_max = U32_MAX * 4

reachability: dict[str, tuple[bool, str, dict[str, int]]] = {
    "data.chunk.inode_plus_isize": (
        inode_off_max > U64_MAX - inode_isize_max,
        "inode_off is bounded by mounted backing size minus one compact inode",
        {"inode_off_max": inode_off_max, "inode_isize_max": inode_isize_max},
    ),
    "data.chunk.isize_plus_xattr": (
        inode_off_max + inode_isize_max > U64_MAX - xattr_isize_max,
        "inode offset plus maximum 64-byte inode and 16-bit xattr body stays below UINT64_MAX",
        {"metadata_base_max": inode_off_max + inode_isize_max, "xattr_isize_max": xattr_isize_max},
    ),
    "data.chunk.align": (
        idx_base_max > U64_MAX - (entry_size_max - 1),
        "validated backing and bounded inode/xattr sizes leave alignment headroom",
        {"idx_base_max": idx_base_max, "alignment_slack": entry_size_max - 1},
    ),
    "data.chunk.index_multiply": (
        chunk_idx_max > (U64_MAX - idx_base_max) // entry_size_max,
        "size <= OFF_MAX and chunkbits >= 9 bound the index product",
        {"idx_base_max": idx_base_max, "chunk_idx_max": chunk_idx_max, "entry_size_max": entry_size_max},
    ),
    "data.chunk.image_size_shift": (
        I64_MAX > U64_MAX,
        "mount validates blocks << blkszbits <= GEOM off_t mediasize",
        {"validated_image_bytes_max": I64_MAX},
    ),
    "data.chunk.physical_shift": (
        blkaddr_max > (U64_MAX >> page_shift),
        "decoded chunk block address is at most 48 bits and blkszbits is at most PAGE_SHIFT",
        {"blkaddr_max": blkaddr_max, "blkszbits_max": page_shift},
    ),
    "data.chunk.physical_plus_offset": (
        chunk_off_max > U64_MAX - physical_max,
        "48-bit block address and maximum validated chunkbits leave addition headroom",
        {"physical_max": physical_max, "chunk_off_max": chunk_off_max},
    ),
    "xattr.backing_size.shift": (
        I64_MAX > U64_MAX,
        "mounted primary backing bytes are already bounded by GEOM off_t mediasize",
        {"validated_backing_bytes_max": I64_MAX},
    ),
    "xattr.metadata.align": (
        prefix_off_max > U64_MAX - 3,
        "32-bit prefix start, 8-bit count, and bounded records cannot approach UINT64_MAX",
        {"prefix_off_max": prefix_off_max},
    ),
    "xattr.metadata.header_add": (
        False,
        "the preceding off <= UINT64_MAX-3 check and 4-byte roundup imply aligned off <= UINT64_MAX-3, so adding uint16_t cannot overflow",
        {"accepted_input_max": U64_MAX - 3, "aligned_off_max": U64_MAX - 3, "raw_len_size": 2},
    ),
    "xattr.shared.base_shift": (
        U32_MAX > (U64_MAX >> page_shift),
        "xattr_blkaddr is 32-bit and blkszbits is at most PAGE_SHIFT",
        {"xattr_blkaddr_max": U32_MAX, "blkszbits_max": page_shift},
    ),
    "xattr.shared.base_plus_relative": (
        xattr_relative_max > U64_MAX - xattr_base_max,
        "32-bit xattr block address and 32-bit shared ID cannot overflow the 64-bit sum",
        {"xattr_base_max": xattr_base_max, "relative_max": xattr_relative_max},
    ),
}

ledger = []
for target in SPEC["targets"]:
    target_id = target["id"]
    if target_id not in reachability:
        raise SystemExit(f"target has no reachability proof: {target_id}")
    body = function_bodies[f"{target['path']}:{target['function']}"]
    if body.count(target["needle"]) != 1:
        raise SystemExit(f"target source is absent or ambiguous: {target_id}")
    reachable, reason, bounds = reachability[target_id]
    ledger.append(
        {
            **target,
            "line": line_number(sources[target["path"]], target["needle"]),
            "source_unique": True,
            "target_marker_reached": reachable,
            "current_freebsd_return": "+EOVERFLOW",
            "candidate_freebsd_return": "+EINTEGRITY",
            "linux_semantic_return": "-EFSCORRUPTED (no direct checked counterpart)",
            "source_class": "on-disk contradiction",
            "replay_observation": reason,
            "provenance_bounds": bounds,
        }
    )

data_source = sources["repo-pre-15/src/data.c"]
xattr_source = sources["repo-pre-15/src/xattr.c"]
linux_xattr = sources["src-linux/xattr.c"]
control_anchors = (
    (xattr_source, "if (off > backing_size || (uint64_t)len > backing_size - off)\n\t\treturn (EINTEGRITY);"),
    (xattr_source, "if (off > INT64_MAX)\n\t\t\treturn (EOVERFLOW);"),
    (xattr_source, "error = EOPNOTSUPP;"),
    (data_source, "return (ENXIO);"),
    (data_source, "return (EIO);"),
    (data_source, "if (len == 0) {\n\t\treturn (0);\n\t}"),
    (linux_xattr, "ret = -EOPNOTSUPP;"),
    (linux_xattr, "ret = -ENOMEM;"),
    (linux_xattr, "return PTR_ERR(it->kaddr);"),
    (linux_xattr, "return -EFSCORRUPTED;"),
)
for source, anchor in control_anchors:
    if anchor not in source:
        raise SystemExit(f"errno preservation anchor is absent: {anchor}")

abi_reachable = False
abi_reason = (
    "erofs_xattr_read_backing checks off <= backing_size before off > INT64_MAX; "
    "for a mounted primary backing, backing_size <= GEOM signed off_t mediasize"
)
controls = []
for control in SPEC["preservation_controls"]:
    record: dict[str, Any] = dict(control)
    record["source_anchors_verified"] = True
    if control["id"] == "abi.off_gt_int64":
        record["target_marker_reached"] = abi_reachable
        record["replay_observation"] = abi_reason
    controls.append(record)

unreachable = [record["id"] for record in ledger if not record["target_marker_reached"]]
status = "GO" if not unreachable else "STOP"
stop_reasons = [
    {
        "id": target_id,
        "reason": next(
            record["replay_observation"] for record in ledger if record["id"] == target_id
        ),
    }
    for target_id in unreachable
]

ledger_document = {
    "schema": 1,
    "gate": "G06",
    "candidate": "P15-052",
    "status": status,
    "resolved_base": resolved,
    "targets": ledger,
    "preservation_controls": controls,
    "freebsd_contract": freebsd_contract,
    "prototype_identity": SPEC["prototype_identity"],
    "prototype_disposition": (
        "READY arithmetic vectors inject unconstrained internal or local values; "
        "they are not independently reachable on-disk fixtures"
    ),
}
(OUTPUT / "branch-ledger.json").write_text(
    json.dumps(ledger_document, indent=2, sort_keys=True) + "\n", encoding="ascii"
)

tsv = [
    "id\tfunction\tfield\tfreebsd_now\tfreebsd_candidate\tlinux_semantic\tmarker\tobservation"
]
for record in ledger:
    tsv.append(
        "\t".join(
            (
                record["id"],
                record["function"],
                record["field"],
                record["current_freebsd_return"],
                record["candidate_freebsd_return"],
                record["linux_semantic_return"],
                "reached" if record["target_marker_reached"] else "not-reached",
                record["replay_observation"],
            )
        )
    )
(OUTPUT / "branch-ledger.tsv").write_text("\n".join(tsv) + "\n", encoding="ascii")

control_tsv = ["id\tfreebsd\tlinux\tresult\tmarker"]
for record in controls:
    control_tsv.append(
        "\t".join(
            (
                record["id"],
                record["freebsd"],
                record["linux"],
                record["result"],
                (
                    "not-reached"
                    if record.get("target_marker_reached") is False
                    else "preserved"
                ),
            )
        )
    )
(OUTPUT / "preservation-ledger.tsv").write_text(
    "\n".join(control_tsv) + "\n", encoding="ascii"
)
(OUTPUT / "source-sha256.json").write_text(
    json.dumps(source_hashes, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
(OUTPUT / "freebsd-contract.json").write_text(
    json.dumps(freebsd_contract, indent=2, sort_keys=True) + "\n", encoding="ascii"
)

result = {
    "schema": 1,
    "gate": "G06",
    "candidate": "P15-052",
    "status": status,
    "requested_base": REQUESTED_BASE,
    "resolved_base": resolved,
    "target_count": len(ledger),
    "target_markers_reached": len(ledger) - len(unreachable),
    "target_markers_not_reached": len(unreachable),
    "source_unique_count": sum(record["source_unique"] for record in ledger),
    "preservation_control_count": len(controls),
    "abi_marker_reached": abi_reachable,
    "stop_reasons": stop_reasons,
    "b20": "STOP-NO-SOURCE",
    "b21": "NOT_RUN",
    "qemu": "NOT_RUN",
    "qemu_reason": "host-proven target reachability failure",
    "full_feature_suite": "NOT_RUN",
}
(OUTPUT / "result.json").write_text(
    json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii"
)

hash_lines = []
for path in sorted(OUTPUT.iterdir()):
    if path.is_file() and path.name != "SHA256SUMS":
        hash_lines.append(f"{sha256(path.read_bytes())}  {path.name}")
(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
