#!/bin/sh
set -eu

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

gate=$PRE15_DUT/tests/pre15/gates/P15-006.sh
input=$PRE15_DUT/tests/pre15/gates/P15-006-input.json
oracle=$PRE15_DUT/tests/pre15/fixtures/B07-map-oracle.json
map_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-map-runs.json
io_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-io-caps.json
b07c_case=$PRE15_DUT/tests/pre15/cases/B07c-map-consumers.sh
artifacts=$PRE15_RUN_DIR/artifacts
baseline=6673f51152a5195a8a8903aa801f820abce7936e
b07c=55c609db13fc9b0f21a3c7c9ec5cb9574fd18276

for tool in cc git python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || \
		pre15_infra_blocked "missing B08 host tool: $tool"
done

pre15_record_fixture b08-gate "$gate"
pre15_record_fixture b08-input "$input"
pre15_record_fixture b08-b07-oracle "$oracle"
pre15_record_fixture b08-map-runs "$map_fixture"
pre15_record_fixture b08-io-caps "$io_fixture"
pre15_record_fixture b08-b07c-case "$b07c_case"
pre15_record_fixture b08-data "$PRE15_DUT/src/data.c"

mkdir -p "$artifacts"
pre15_target_reached

if "$gate" --base "$baseline" --oracle "$oracle" \
	--output "$artifacts/baseline" >"$artifacts/baseline.stdout" \
	2>"$artifacts/baseline.stderr"; then
	:
else
	pre15_dut_fail 'B08 frozen P15-006 baseline replay failed'
fi

if python3 - "$b07c_case" "$artifacts/derive-B07c.py" <<'PY'
from pathlib import Path
import sys


source = Path(sys.argv[1]).read_text(encoding="utf-8")
marker = 'if python3 - "$gate" "$artifacts/P15-006-B07c.py" <<\'PY\'\n'
start = source.index(marker) + len(marker)
end = source.index("\nPY\nthen", start)
Path(sys.argv[2]).write_text(source[start:end] + "\n", encoding="ascii")
PY
then
	:
else
	pre15_dut_fail 'B08 could not recover the proven B07c replay adapter'
fi

if python3 "$artifacts/derive-B07c.py" "$gate" \
	"$artifacts/P15-006-B08.py" >"$artifacts/derive.stdout" \
	2>"$artifacts/derive.stderr"; then
	:
else
	pre15_dut_fail 'B08 could not derive the current map replay'
fi

candidate_rc=0
python3 "$artifacts/P15-006-B08.py" "$PRE15_ROOT" "$input" \
	worktree '' "$artifacts/candidate" "$oracle" \
	>"$artifacts/candidate.stdout" 2>"$artifacts/candidate.stderr" || \
	candidate_rc=$?
if test "$candidate_rc" -ne 1; then
	pre15_dut_fail 'B08 old-run oracle did not reject exactly the intended change'
fi

if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$oracle" "$map_fixture" \
	"$io_fixture" "$artifacts/baseline" "$artifacts/candidate" \
	"$artifacts/B08-result.json" "$b07c" "$artifacts" <<'PY'
from __future__ import annotations

from collections import Counter
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import sys


root = Path(sys.argv[1])
dut = Path(sys.argv[2])
oracle_path = Path(sys.argv[3])
map_fixture_path = Path(sys.argv[4])
io_fixture_path = Path(sys.argv[5])
baseline = Path(sys.argv[6])
candidate = Path(sys.argv[7])
output = Path(sys.argv[8])
b07c = sys.argv[9]
artifacts = Path(sys.argv[10])


def load(path: Path) -> dict:
    return json.loads(path.read_text(encoding="ascii"))


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


def extract_function(source: str, name: str) -> str:
    match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
    if not match:
        raise SystemExit(f"missing function: {name}")
    name_line = source.rfind("\n", 0, match.start()) + 1
    start = source.rfind("\n", 0, name_line - 1) + 1
    brace = source.find("{", match.end())
    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]
        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 function_names(source: str) -> set[str]:
    return set(re.findall(
        r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(", source, re.MULTILINE
    ))


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


def require_order(source: str, markers: list[str], label: str) -> None:
    position = -1
    for marker in markers:
        next_position = source.find(marker, position + 1)
        if next_position < 0:
            raise SystemExit(f"{label} is missing ordered marker: {marker}")
        position = next_position


def compile_and_run(name: str, program: str) -> list[str]:
    source_path = artifacts / f"{name}.c"
    binary_path = artifacts / name
    source_path.write_text(program, encoding="ascii")
    compiled = subprocess.run(
        [
            "cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror",
            str(source_path), "-o", str(binary_path),
        ],
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    (artifacts / f"{name}.compile.stdout").write_text(
        compiled.stdout, encoding="utf-8"
    )
    (artifacts / f"{name}.compile.stderr").write_text(
        compiled.stderr, encoding="utf-8"
    )
    if compiled.returncode != 0:
        raise SystemExit(f"{name} compilation failed")
    executed = subprocess.run(
        [str(binary_path)],
        check=False,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    (artifacts / f"{name}.stdout").write_text(
        executed.stdout, encoding="utf-8"
    )
    (artifacts / f"{name}.stderr").write_text(
        executed.stderr, encoding="utf-8"
    )
    if executed.returncode != 0:
        raise SystemExit(f"{name} execution failed")
    return executed.stdout.splitlines()


oracle = load(oracle_path)
map_fixture = load(map_fixture_path)
io_fixture = load(io_fixture_path)
base_result = load(baseline / "result.json")
base_tuples = load(baseline / "tuples.json")
base_devices = load(baseline / "devices.json")
result = load(candidate / "result.json")
tuples = load(candidate / "tuples.json")
devices = load(candidate / "devices.json")

if sha256_bytes(oracle_path.read_bytes()) != map_fixture["b07_oracle_sha256"]:
    raise SystemExit("B08 map fixture is not anchored to the frozen B07 oracle")
if map_fixture["schema"] != 1 or map_fixture["candidate"] != "P15-074":
    raise SystemExit("invalid B08 map fixture identity")
if io_fixture["schema"] != 1 or io_fixture["candidate"] != "P15-074":
    raise SystemExit("invalid B08 I/O fixture identity")
if (
    base_result["status"] != "GO"
    or base_result["oracle_equal"] is not True
    or base_result["map_case_count"] != 80
    or base_result["device_case_count"] != 13
):
    raise SystemExit("frozen B07 control replay did not GO")
if result["status"] != "STOP" or result["map_case_count"] != 80:
    raise SystemExit("B08 candidate did not expose the intended old-run delta")
if result["device_case_count"] != 13 or result["model_sha256"] != oracle["model_sha256"]:
    raise SystemExit("B08 candidate changed the independent corpus denominator")
if result["adapter_probe"]["status"] != "PASS":
    raise SystemExit("B08 common map adapter probe failed")
if devices["status"] != "PASS" or devices["records"] != base_devices["records"]:
    raise SystemExit("B08 changed one of the 13 frozen device records")

tuple_fields = (
    "m_la", "m_pa", "m_llen", "m_plen", "m_deviceid", "m_flags",
    "m_algorithmformat", "errno", "acquire_count", "release_count",
)
tuple_layout = map_fixture["b07_tuple_layout"]
if tuple_layout != oracle["tuple_byte_layout"]:
    raise SystemExit("B08 tuple byte layout drifted")
oracle_records = {record["id"]: record for record in oracle["records"]}
actual_records = {record["id"]: record for record in tuples["records"]}
base_records = {record["id"]: record for record in base_tuples["records"]}
transforms = {record["id"]: record for record in map_fixture["b07_transforms"]}
if len(oracle_records) != 80 or set(actual_records) != set(oracle_records):
    raise SystemExit("B08 map tuple ID set changed")
if set(base_records) != set(oracle_records):
    raise SystemExit("B08 baseline tuple ID set changed")

expected_bytes = bytearray()
actual_bytes = bytearray()
changed_ids = []
for record in oracle["records"]:
    case_id = record["id"]
    if base_records[case_id]["tuple_hex"] != record["tuple_hex"]:
        raise SystemExit(f"frozen B07 baseline drifted: {case_id}")
    expected = dict(zip(
        tuple_fields,
        struct.unpack(tuple_layout, bytes.fromhex(record["tuple_hex"])),
        strict=True,
    ))
    if case_id in transforms:
        expected["m_llen"] = transforms[case_id]["m_llen"]
        expected["m_plen"] = transforms[case_id]["m_plen"]
    expected_tuple = struct.pack(
        tuple_layout, *(expected[field] for field in tuple_fields)
    )
    actual_tuple = bytes.fromhex(actual_records[case_id]["tuple_hex"])
    if actual_tuple != expected_tuple:
        raise SystemExit(f"B08 transformed tuple mismatch: {case_id}")
    if actual_tuple != bytes.fromhex(record["tuple_hex"]):
        changed_ids.append(case_id)
    expected_bytes.extend(expected_tuple)
    actual_bytes.extend(actual_tuple)

if set(changed_ids) != set(transforms):
    raise SystemExit(f"B08 changed unexpected B07 tuples: {changed_ids!r}")
transformed_hash = sha256_bytes(bytes(expected_bytes))
if bytes(actual_bytes) != bytes(expected_bytes):
    raise SystemExit("B08 full transformed tuple stream mismatch")
if transformed_hash != map_fixture["expected_transformed_tuple_sha256"]:
    raise SystemExit("B08 transformed tuple fixture digest mismatch")
allowed_failures = []
for failure in result["failures"]:
    if failure.get("field") == "frozen-byte-oracle" and "id" not in failure:
        continue
    if failure.get("id") in transforms and failure.get("field") in {
        "m_llen", "m_plen"
    }:
        continue
    allowed_failures.append(failure)
if allowed_failures:
    raise SystemExit(f"B08 old oracle reported unrelated failures: {allowed_failures!r}")

data_source = (dut / "src/data.c").read_text(encoding="utf-8")
old_data = committed_data()
expected_preamble = old_data[:old_data.index("static erofs_off_t\n")].replace(
    "#include <sys/systm.h>\n",
    "#include <sys/systm.h>\n#include <sys/_maxphys.h>\n",
)
if data_source[:data_source.index("static erofs_off_t\n")] != expected_preamble:
    raise SystemExit("B08 data.c preamble changed beyond the MAXPHYS header")
old_functions = function_names(old_data)
data_functions = function_names(data_source)
if data_functions != old_functions:
    raise SystemExit("B08 added or removed a data.c function")
changed_functions = {
    "erofs_map_blocks_flatmode", "erofs_read_data", "erofs_read_uio"
}
for function in sorted(data_functions - changed_functions):
    if extract_function(data_source, function) != extract_function(
        old_data, function
    ):
        raise SystemExit(f"B08 changed out-of-scope data.c function: {function}")
for function in changed_functions:
    if extract_function(data_source, function) == extract_function(
        old_data, function
    ):
        raise SystemExit(f"B08 did not change required function: {function}")

flatmode = extract_function(data_source, "erofs_map_blocks_flatmode")
read_data = extract_function(data_source, "erofs_read_data")
read_uio = extract_function(data_source, "erofs_read_uio")
if flatmode.count("map->m_llen = remain;") != 1:
    raise SystemExit("plain mapping does not expose the EOF run")
if flatmode.count("map->m_llen = MIN(remain, tail_start - loff);") != 1:
    raise SystemExit("pre-inline mapping does not stop at the inline tail")
if "MAXPHYS" in flatmode:
    raise SystemExit("B08 incorrectly capped the map contract")
if read_data.count("(uint64_t)MAXPHYS") != 1:
    raise SystemExit("erofs_read_data lacks one MAXPHYS cap")
if read_uio.count("(uint64_t)MAXPHYS") != 1:
    raise SystemExit("erofs_read_uio lacks one MAXPHYS cap")
require_order(
    read_data,
    [
        "if (erofs_inode_is_data_compressed(vi->datalayout))",
        "return (z_erofs_read_data(sbi, vi, loff, len, bufp));",
        "error = erofs_map_blocks(sbi, vi, &map);",
        "MIN(map.m_llen, (uint64_t)MAXPHYS)",
        "erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
        "erofs_brelse(blk);",
    ],
    "erofs_read_data",
)
require_order(
    read_uio,
    [
        "if (erofs_inode_is_data_compressed(vi->datalayout))",
        "return (z_erofs_read_uio(sbi, vi, uio));",
        "error = erofs_map_blocks(sbi, vi, &map);",
        "MIN(map.m_llen, (uint64_t)MAXPHYS)",
        "erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
        "error = uiomove(blk, want, uio);",
        "erofs_brelse(blk);",
    ],
    "erofs_read_uio",
)

map_common = r'''
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#define EOPNOTSUPP 45
#define EOVERFLOW 84
#define EINTEGRITY 97
#define EROFS_NULL_ADDR UINT32_MAX
#define EROFS_INODE_FLAT_PLAIN 0
#define EROFS_INODE_COMPRESSED_FULL 1
#define EROFS_INODE_FLAT_INLINE 2
#define EROFS_INODE_COMPRESSED_COMPACT 3
#define EROFS_INODE_CHUNK_BASED 4
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define roundup2(x, y) (((x) + ((y) - 1)) & ~((y) - 1))

typedef uint64_t erofs_off_t;
typedef uint64_t erofs_blk_t;

struct erofs_sb_info {
	uint64_t block_size;
	unsigned int blkszbits;
};

struct erofs_inode {
	uint64_t size;
	erofs_off_t inode_off;
	erofs_blk_t startblk;
	uint8_t datalayout;
	uint8_t inode_isize;
	uint32_t xattr_isize;
};

struct erofs_map_blocks {
	erofs_off_t m_pa, m_la;
	uint64_t m_plen, m_llen;
	unsigned short m_deviceid;
	char m_algorithmformat;
	unsigned int m_flags;
};

static bool
erofs_inode_is_data_compressed(unsigned int layout)
{
	return (layout == EROFS_INODE_COMPRESSED_FULL ||
	    layout == EROFS_INODE_COMPRESSED_COMPACT);
}

static int
erofs_map_blocks_chunk(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map)
{
	(void)sbi;
	(void)vi;
	(void)map;
	return (EOPNOTSUPP);
}

static int
z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map)
{
	(void)sbi;
	(void)vi;
	(void)map;
	return (EOPNOTSUPP);
}
'''
map_program = map_common
map_program += extract_function(data_source, "erofs_inline_tail_start") + "\n"
map_program += flatmode + "\n"
map_program += extract_function(data_source, "erofs_map_blocks") + "\n"
map_program += "\nint\nmain(void)\n{\n\tint error;\n"
for case in map_fixture["map_cases"]:
    inode = case["inode"]
    map_program += f'''
	{{
		struct erofs_sb_info sbi = {{ .block_size = 4096, .blkszbits = 12 }};
		struct erofs_inode vi = {{
			.size = UINT64_C({inode['size']}),
			.inode_off = UINT64_C({inode.get('inode_off', 0)}),
			.startblk = UINT64_C({inode['startblk']}),
			.datalayout = {inode['layout']},
			.inode_isize = {inode.get('inode_isize', 0)},
			.xattr_isize = {inode.get('xattr_isize', 0)},
		}};
		struct erofs_map_blocks map = {{ .m_la = UINT64_C({case['request']}) }};
		error = erofs_map_blocks(&sbi, &vi, &map);
		printf("map\\t{case['id']}\\t%llu\\t%llu\\t%llu\\t%llu\\t%u\\t%u\\t%d\\t%d\\n",
		    (unsigned long long)map.m_la, (unsigned long long)map.m_pa,
		    (unsigned long long)map.m_llen, (unsigned long long)map.m_plen,
		    map.m_deviceid, map.m_flags, (int)map.m_algorithmformat, error);
	}}
'''
map_program += "\treturn (0);\n}\n"
map_lines = compile_and_run("B08-map-extractor", map_program)
map_actual = {}
for line in map_lines:
    fields = line.split("\t")
    if len(fields) != 10 or fields[0] != "map":
        raise SystemExit(f"invalid B08 map extractor line: {line!r}")
    map_actual[fields[1]] = dict(zip(
        (
            "m_la", "m_pa", "m_llen", "m_plen", "m_deviceid",
            "m_flags", "m_algorithmformat", "errno",
        ),
        [int(value) for value in fields[2:]],
        strict=True,
    ))
if set(map_actual) != {case["id"] for case in map_fixture["map_cases"]}:
    raise SystemExit("B08 custom map case set changed")
positive_runs = 0
for case in map_fixture["map_cases"]:
    actual = map_actual[case["id"]]
    expected = {**case["expected"], "m_algorithmformat": 0}
    if actual != expected:
        raise SystemExit(
            f"B08 custom map mismatch {case['id']}: {actual!r} != {expected!r}"
        )
    if (
        actual["errno"] == 0
        and case["request"] < case["inode"]["size"]
    ):
        if actual["m_llen"] == 0:
            raise SystemExit(f"B08 non-EOF zero run: {case['id']}")
        positive_runs += 1

maxphys = io_fixture["harness_maxphys"]
consumer_common = f'''
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define EIO 5
#define ENOMEM 12
#define EINVAL 22
#define EOPNOTSUPP 45
#define EOVERFLOW 84
#define EINTEGRITY 97
#define PAGE_SIZE 4096
#define MAXPHYS {maxphys}
#define M_EROFS 0
#define M_WAITOK 0
#define EROFS_INODE_FLAT_PLAIN 0
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
#define EROFS_BUF_INITIALIZER {{ .data = NULL, .release = NULL }}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define bzero(ptr, len) memset((ptr), 0, (len))

typedef uint64_t erofs_off_t;
typedef uint64_t erofs_nid_t;

struct erofs_sb_info {{ int unused; }};
struct erofs_inode {{
	erofs_nid_t nid;
	uint64_t size;
	uint8_t datalayout;
}};
struct erofs_map_blocks {{
	erofs_off_t m_pa, m_la;
	uint64_t m_plen, m_llen;
	unsigned short m_deviceid;
	char m_algorithmformat;
	unsigned int m_flags;
}};
struct erofs_buf {{
	void *data;
	void (*release)(void *);
}};
struct uio {{
	int64_t uio_offset;
	size_t uio_resid;
	unsigned char *buffer;
	size_t moved;
}};

static size_t gate_allocations;

static void *
gate_malloc(size_t size)
{{
	void *buffer = malloc(size);
	if (buffer != NULL)
		++gate_allocations;
	return (buffer);
}}

static void
gate_free(void *buffer)
{{
	if (buffer != NULL) {{
		--gate_allocations;
		free(buffer);
	}}
}}

#define malloc(size, type, flags) gate_malloc(size)
#define free(buffer, type) gate_free(buffer)

#define GATE_MAX_CALLS 32
#define GATE_PHYSICAL_BASE UINT64_C(1048576)
static bool gate_hole;
static unsigned int gate_map_calls;
static unsigned int gate_physical_calls;
static erofs_off_t gate_physical_offsets[GATE_MAX_CALLS];
static size_t gate_physical_lengths[GATE_MAX_CALLS];

static void
gate_reset(bool hole)
{{
	gate_hole = hole;
	gate_map_calls = 0;
	gate_physical_calls = 0;
	memset(gate_physical_offsets, 0, sizeof(gate_physical_offsets));
	memset(gate_physical_lengths, 0, sizeof(gate_physical_lengths));
}}

static bool
erofs_inode_is_data_compressed(unsigned int layout)
{{
	(void)layout;
	return (false);
}}

static int
erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map)
{{
	struct erofs_map_blocks next = {{ .m_la = map->m_la }};
	(void)sbi;
	++gate_map_calls;
	if (next.m_la < vi->size) {{
		next.m_pa = GATE_PHYSICAL_BASE + next.m_la;
		next.m_llen = vi->size - next.m_la;
		next.m_plen = next.m_llen;
		if (!gate_hole)
			next.m_flags = EROFS_MAP_MAPPED;
	}}
	*map = next;
	return (0);
}}

static int
erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id,
    erofs_off_t off, size_t len, void **bufp)
{{
	unsigned char *buffer;
	size_t index;
	(void)sbi;
	(void)device_id;
	if (len > MAXPHYS || gate_physical_calls == GATE_MAX_CALLS)
		return (EINVAL);
	gate_physical_offsets[gate_physical_calls] = off;
	gate_physical_lengths[gate_physical_calls] = len;
	++gate_physical_calls;
	buffer = gate_malloc(len);
	if (buffer == NULL)
		return (ENOMEM);
	for (index = 0; index < len; ++index)
		buffer[index] = (unsigned char)(off - GATE_PHYSICAL_BASE + index);
	*bufp = buffer;
	return (0);
}}

static int
erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid,
    erofs_off_t off, size_t len, struct erofs_buf *buf)
{{
	(void)sbi;
	(void)nid;
	(void)off;
	(void)len;
	(void)buf;
	return (EIO);
}}

static void
erofs_put_metabuf(struct erofs_buf *buf)
{{
	(void)buf;
}}

static void
erofs_brelse(void *buffer)
{{
	gate_free(buffer);
}}

static int
uiomove(const void *source, size_t len, struct uio *uio)
{{
	if (len > uio->uio_resid)
		return (EINVAL);
	memcpy(uio->buffer + uio->moved, source, len);
	uio->moved += len;
	uio->uio_resid -= len;
	uio->uio_offset += (int64_t)len;
	return (0);
}}

static int
z_erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    erofs_off_t loff, size_t len, void **bufp)
{{
	(void)sbi;
	(void)vi;
	(void)loff;
	(void)len;
	(void)bufp;
	return (EOPNOTSUPP);
}}

static int
z_erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct uio *uio)
{{
	(void)sbi;
	(void)vi;
	(void)uio;
	return (EOPNOTSUPP);
}}

static int
gate_check_data(const unsigned char *buffer, size_t length,
    uint64_t offset, bool hole)
{{
	size_t index;
	for (index = 0; index < length; ++index) {{
		unsigned char expected = hole ? 0 : (unsigned char)(offset + index);
		if (buffer[index] != expected)
			return (1);
	}}
	return (0);
}}

static int
gate_fail(const char *id, const char *reason)
{{
	fprintf(stderr, "%s: %s\\n", id, reason);
	return (1);
}}
'''
consumer_program = consumer_common
consumer_program += read_data + "\n"
consumer_program += read_uio + "\n"
consumer_program += "\nint\nmain(void)\n{\n"
for scenario in io_fixture["scenarios"]:
    scenario_id = scenario["id"]
    hole = scenario["mapping"] == "hole"
    expected_offsets = scenario["expected_physical_offsets"]
    expected_lengths = scenario["expected_physical_lengths"]
    checks = []
    for index, (offset, length) in enumerate(zip(
        expected_offsets, expected_lengths, strict=True
    )):
        checks.append(
            f"\t\tif (gate_physical_offsets[{index}] != UINT64_C({offset}) || "
            f"gate_physical_lengths[{index}] != {length})\n"
            f"\t\t\treturn (gate_fail(\"{scenario_id}\", \"physical call tuple\"));\n"
        )
    call_checks = "".join(checks)
    if scenario["api"] == "read_data":
        success_check = ""
        if scenario["expected_errno"] == 0:
            success_check = f'''
		if (buffer == NULL || gate_check_data(buffer, {scenario['length']},
		    UINT64_C({scenario['offset']}), {'true' if hole else 'false'}) != 0)
			return (gate_fail("{scenario_id}", "returned data"));
'''
        else:
            success_check = f'''
		if (buffer != NULL)
			return (gate_fail("{scenario_id}", "error buffer ownership"));
'''
        consumer_program += f'''
	{{
		struct erofs_sb_info sbi = {{ 0 }};
		struct erofs_inode vi = {{
			.nid = 1, .size = UINT64_C({scenario['inode_size']}),
			.datalayout = EROFS_INODE_FLAT_PLAIN,
		}};
		void *buffer = NULL;
		int error;

		gate_reset({'true' if hole else 'false'});
		error = erofs_read_data(&sbi, &vi, UINT64_C({scenario['offset']}),
		    {scenario['length']}, &buffer);
		if (error != {scenario['expected_errno']})
			return (gate_fail("{scenario_id}", "errno"));
		if (gate_map_calls != {scenario['expected_map_calls']} ||
		    gate_physical_calls != {len(expected_lengths)})
			return (gate_fail("{scenario_id}", "call count"));
{call_checks}{success_check}		gate_free(buffer);
		if (gate_allocations != 0)
			return (gate_fail("{scenario_id}", "allocation balance"));
		printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n",
		    gate_map_calls, gate_physical_calls);
	}}
'''
    elif scenario["api"] == "read_uio":
        consumer_program += f'''
	{{
		struct erofs_sb_info sbi = {{ 0 }};
		struct erofs_inode vi = {{
			.nid = 1, .size = UINT64_C({scenario['inode_size']}),
			.datalayout = EROFS_INODE_FLAT_PLAIN,
		}};
		unsigned char *buffer = gate_malloc({scenario['length']});
		struct uio uio = {{
			.uio_offset = {scenario['offset']}, .uio_resid = {scenario['length']},
			.buffer = buffer, .moved = 0,
		}};
		int error;

		if (buffer == NULL)
			return (gate_fail("{scenario_id}", "test allocation"));
		memset(buffer, 0xa5, {scenario['length']});
		gate_reset({'true' if hole else 'false'});
		error = erofs_read_uio(&sbi, &vi, &uio);
		if (error != {scenario['expected_errno']} ||
		    uio.uio_offset != {scenario['expected_final_offset']} ||
		    uio.uio_resid != {scenario['expected_resid']})
			return (gate_fail("{scenario_id}", "uio result"));
		if (gate_map_calls != {scenario['expected_map_calls']} ||
		    gate_physical_calls != {len(expected_lengths)})
			return (gate_fail("{scenario_id}", "call count"));
{call_checks}		if (uio.moved != {scenario['length'] - scenario['expected_resid']} ||
		    gate_check_data(buffer, uio.moved, UINT64_C({scenario['offset']}),
		    {'true' if hole else 'false'}) != 0)
			return (gate_fail("{scenario_id}", "moved data"));
		gate_free(buffer);
		if (gate_allocations != 0)
			return (gate_fail("{scenario_id}", "allocation balance"));
		printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n",
		    gate_map_calls, gate_physical_calls);
	}}
'''
    else:
        raise SystemExit(f"unknown B08 I/O API: {scenario['api']}")
consumer_program += "\treturn (0);\n}\n"
io_lines = compile_and_run("B08-io-extractor", consumer_program)
expected_io_ids = [scenario["id"] for scenario in io_fixture["scenarios"]]
actual_io_ids = []
for line in io_lines:
    fields = line.split("\t")
    if len(fields) != 5 or fields[0] != "io" or fields[2] != "PASS":
        raise SystemExit(f"invalid B08 I/O extractor line: {line!r}")
    actual_io_ids.append(fields[1])
if actual_io_ids != expected_io_ids:
    raise SystemExit("B08 I/O scenario order or denominator changed")

cleanup = Counter(
    (record["actual"]["acquire_count"], record["actual"]["release_count"])
    for record in tuples["records"]
)
if any(acquire != release for acquire, release in cleanup):
    raise SystemExit(f"B08 metadata cleanup became unbalanced: {cleanup!r}")
if any(record["actual"]["errno"] < 0 for record in tuples["records"]):
    raise SystemExit("B08 observed a negative errno")

summary = {
    "status": "PASS",
    "final_id": "P15-074",
    "b07_map_cases": 80,
    "b07_unchanged_tuples": 80 - len(changed_ids),
    "b07_intended_run_tuples": len(changed_ids),
    "b07_unexpected_tuple_differences": 0,
    "b07_device_cases": 13,
    "b07_device_differences": 0,
    "b07_model_sha256": result["model_sha256"],
    "transformed_tuple_bytes": len(actual_bytes),
    "transformed_tuple_sha256": sha256_bytes(bytes(actual_bytes)),
    "custom_map_cases": len(map_fixture["map_cases"]),
    "custom_positive_runs": positive_runs,
    "io_scenarios": len(io_fixture["scenarios"]),
    "harness_maxphys": maxphys,
    "max_observed_physical_io": max(
        length
        for scenario in io_fixture["scenarios"]
        for length in scenario["expected_physical_lengths"]
    ),
    "cleanup_distribution": {
        f"{acquire}:{release}": count
        for (acquire, release), count in sorted(cleanup.items())
    },
    "compressed_scope": "UNCHANGED",
    "vnode_backed_scope": "NOT_ADDED",
    "qemu": "NOT_RUN",
    "full_feature_suite": "NOT_RUN",
}
with output.open("x", encoding="ascii") as stream:
    json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True)
    stream.write("\n")
print(
    "B08 PASS map=80 unchanged=73 intended=7 device=13 "
    f"custom={len(map_fixture['map_cases'])} io={len(io_fixture['scenarios'])}"
)
PY
then
	:
else
	pre15_dut_fail 'B08 plain-run/MAXPHYS boundary check failed'
fi

sha256sum "$artifacts/baseline/result.json" \
	"$artifacts/baseline/tuples.json" "$artifacts/baseline/devices.json" \
	"$artifacts/candidate/result.json" "$artifacts/candidate/tuples.json" \
	"$artifacts/candidate/devices.json" "$artifacts/B08-result.json" \
	"$artifacts/B08-map-extractor" "$artifacts/B08-io-extractor" \
	>"$artifacts/SHA256SUMS"
printf 'B08 PASS contiguous plain/pre-inline runs with MAXPHYS-capped I/O\n'
