#!/bin/sh set -eu root=$(CDPATH= cd -- "$(dirname -- "$0")/../../../.." && pwd -P) input=$root/repo-pre-15/tests/pre15/gates/P15-006-input.json mode= base= output= oracle= while test "$#" -gt 0; do case "$1" in --base) test "$#" -ge 2 || { printf 'missing --base value\n' >&2; exit 2; } mode=base base=$2 shift 2 ;; --worktree) mode=worktree shift ;; --output) test "$#" -ge 2 || { printf 'missing --output value\n' >&2; exit 2; } output=$2 shift 2 ;; --oracle) test "$#" -ge 2 || { printf 'missing --oracle value\n' >&2; exit 2; } oracle=$2 shift 2 ;; *) printf 'unknown argument: %s\n' "$1" >&2 exit 2 ;; esac done test -n "$mode" || { printf 'use --base COMMIT or --worktree\n' >&2; exit 2; } test -n "$output" || { printf 'missing --output DIR\n' >&2; exit 2; } test -f "$input" || { printf 'missing gate input: %s\n' "$input" >&2; exit 2; } for tool in cc git python3; do command -v "$tool" >/dev/null 2>&1 || { printf 'missing required host tool: %s\n' "$tool" >&2 exit 2 } done python3 - "$root" "$input" "$mode" "$base" "$output" "$oracle" <<'PY' from __future__ import annotations import hashlib import json from pathlib import Path import re import struct import subprocess import sys from typing import Any ROOT = Path(sys.argv[1]) INPUT = Path(sys.argv[2]) MODE = sys.argv[3] REQUESTED_BASE = sys.argv[4] OUTPUT = Path(sys.argv[5]) ORACLE = Path(sys.argv[6]) if sys.argv[6] else None SPEC = json.loads(INPUT.read_text(encoding="ascii")) UINT64_MAX = (1 << 64) - 1 UINT32_MAX = (1 << 32) - 1 NULL_ADDR = UINT64_MAX META_NID = 1 << 63 MAPPED = 0x0001 META = 0x0002 PARTIAL_MAPPED = 0x0004 PARTIAL_REF = 0x0008 FRAGMENT = 0x0010 EIO = 5 ENODEV = 19 EOPNOTSUPP = 45 EOVERFLOW = 84 EINTEGRITY = 97 PLEN_PARTIAL = 1 << 27 PLEN_FMT_BIT = 28 PCLUSTER_MAX = 1024 * 1024 PLEN_MASK = (PCLUSTER_MAX << 1) - 1 PCLUSTER_MAX_DSIZE = 12 * 1024 * 1024 COMPRESSION_MAX = 4 COMPRESSION_SHIFTED = 4 COMPRESSION_INTERLACED = 5 COMPRESSION_RUNTIME_MAX = 6 def run(argv: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]: return subprocess.run(argv, check=False, text=True, **kwargs) def sha256_bytes(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def canonical(value: Any) -> bytes: return json.dumps( value, ensure_ascii=True, separators=(",", ":"), sort_keys=True ).encode("ascii") def extract_function(text: str, name: str) -> str: match = re.search(r"^" + re.escape(name) + r"\s*\(", text, re.MULTILINE) if not match: raise SystemExit(f"function not found: {name}") start = text.rfind("\n\n", 0, match.start()) + 2 brace = text.find("{", match.end()) if brace < 0: raise SystemExit(f"function body not found: {name}") depth = 0 state = "code" index = brace while index < len(text): char = text[index] following = text[index + 1] if index + 1 < len(text) 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 text[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 source_at(path: str, resolved: str) -> str: if MODE == "base": completed = run( ["git", "-C", str(ROOT), "show", f"{resolved}:{path}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if completed.returncode != 0: raise SystemExit(f"cannot read {path} at {resolved}: {completed.stderr}") return completed.stdout return (ROOT / path).read_text(encoding="utf-8") if SPEC.get("schema") != 1 or SPEC.get("candidate") != "P15-006": raise SystemExit("invalid P15-006 input schema") if OUTPUT.exists(): raise SystemExit(f"refusing to overwrite output: {OUTPUT}") OUTPUT.mkdir(parents=True) rev = REQUESTED_BASE if MODE == "base" else "HEAD" resolved_run = run( ["git", "-C", str(ROOT), "rev-parse", f"{rev}^{{commit}}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if resolved_run.returncode != 0: raise SystemExit(f"cannot resolve {rev}: {resolved_run.stderr}") resolved = resolved_run.stdout.strip() if MODE == "base" and resolved != SPEC["required_base"]: raise SystemExit( f"P15-006 baseline mismatch: expected {SPEC['required_base']}, got {resolved}" ) sources = {path: source_at(path, resolved) for path in SPEC["source_paths"]} data_source = sources["repo-pre-15/src/data.c"] zmap_source = sources["repo-pre-15/src/zmap.c"] internal_source = sources["repo-pre-15/src/internal.h"] candidate_mode = re.search( r"^erofs_map_blocks\s*\([^)]*struct erofs_map_blocks \*map\s*\)", data_source, re.MULTILINE | re.DOTALL, ) is not None if candidate_mode and not re.search( r"^erofs_map_blocks_legacy\s*\(", data_source, re.MULTILINE ): raise SystemExit("candidate map adapter has no file-local legacy target") if not candidate_mode and MODE == "worktree" and ORACLE is not None: raise SystemExit("candidate replay requested but common map adapter is absent") source_hashes = { path: sha256_bytes(text.encode("utf-8")) for path, text in sources.items() } protected_hashes: dict[str, str] = {} for key, expected_hash in SPEC["protected_function_sha256"].items(): filename, function = key.split(":", 1) text = data_source if filename == "data.c" else zmap_source actual_hash = sha256_bytes(extract_function(text, function).encode("utf-8")) protected_hashes[key] = actual_hash if actual_hash != expected_hash: raise SystemExit(f"protected producer/GEOM body changed: {key}") if candidate_mode: legacy_body = extract_function(data_source, "erofs_map_blocks_legacy") expected_legacy_hash = SPEC["candidate_legacy_map_sha256"] else: legacy_body = extract_function(data_source, "erofs_map_blocks") expected_legacy_hash = SPEC["baseline_legacy_map_sha256"] if sha256_bytes(legacy_body.encode("utf-8")) != expected_legacy_hash: raise SystemExit("plain/chunk legacy producer body changed") map_match = re.search( r"^struct erofs_map_blocks \{\n.*?^\};\n", internal_source, re.MULTILINE | re.DOTALL, ) if map_match is None: raise SystemExit("struct erofs_map_blocks is absent") map_struct_hash = sha256_bytes(map_match.group(0).encode("utf-8")) if map_struct_hash != SPEC["map_struct_sha256"]: raise SystemExit("map object fields or ordering changed before B07 producers") def tuple_record(**values: int) -> dict[str, int]: record = { "m_la": 0, "m_pa": 0, "m_llen": 0, "m_plen": 0, "m_deviceid": 0, "m_flags": 0, "m_algorithmformat": 0, "errno": 0, "acquire_count": 0, "release_count": 0, } record.update(values) return record def data_case( case_id: str, category: str, subcategories: list[str], request: int, *, layout: int = 0, size: int = 12288, startblk: int = 32, inode_off: int = 0, inode_isize: int = 64, xattr_isize: int = 0, chunkbits: int = 12, chunkformat: int = 0, nid: int = 1, block_size: int = 4096, blkszbits: int = 12, blocks: int = 1 << 20, device_id_mask: int = 0xFFFF, metabox_present: bool = False, metabox_size: int = 0, reader_base: int = 0, reader_bytes: bytes = b"", reader_error: bool = False, ) -> dict[str, Any]: return { "id": case_id, "engine": "data", "category": category, "subcategories": subcategories, "request": request, "sbi": { "block_size": block_size, "blkszbits": blkszbits, "blocks": blocks, "device_id_mask": device_id_mask, "metabox_present": metabox_present, "metabox_size": metabox_size, }, "inode": { "nid": nid, "size": size, "datalayout": layout, "startblk": startblk, "inode_off": inode_off, "inode_isize": inode_isize, "xattr_isize": xattr_isize, "chunkbits": chunkbits, "chunkformat": chunkformat, }, "reader": { "base": reader_base, "bytes": reader_bytes, "error": reader_error, }, } def raw32(block: int) -> bytes: return struct.pack(" bytes: high = (block >> 32) & 0xFFFF if use_48bit else 0 return struct.pack(" list[dict[str, Any]]: cases = [ data_case("plain-start", "plain", ["plain-boundaries"], 0), data_case("plain-block-last", "plain", ["plain-boundaries"], 4095), data_case("plain-second-start", "plain", ["plain-boundaries"], 4096), data_case("plain-final-byte", "plain", ["plain-boundaries"], 12287), data_case("plain-eof", "post-EOF", ["plain-boundaries"], 12288), data_case("plain-post-eof", "post-EOF", ["plain-boundaries"], 12305), data_case("plain-empty", "post-EOF", ["plain-boundaries"], 0, size=0), data_case("plain-hole-start", "hole", ["plain-boundaries"], 0, startblk=NULL_ADDR), data_case("plain-hole-edge", "hole", ["plain-boundaries"], 4095, startblk=NULL_ADDR), data_case( "plain-shift-overflow", "overflow", ["plain-overflow"], 0, startblk=(UINT64_MAX >> 12) + 1, ), data_case( "plain-add-overflow", "overflow", ["plain-overflow"], 4096, startblk=UINT64_MAX >> 12, ), data_case("plain-invalid-layout", "invalid", ["plain-boundaries"], 0, layout=7), data_case("inline-head-start", "inline", ["inline-boundaries"], 0, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-head-last", "inline", ["inline-boundaries"], 4095, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-tail-start", "inline", ["inline-boundaries"], 4096, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-tail-middle", "inline", ["inline-boundaries"], 4500, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-final-byte", "inline", ["inline-boundaries"], 4999, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-eof", "post-EOF", ["inline-boundaries"], 5000, layout=2, size=5000, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-head-hole", "hole", ["inline-boundaries"], 0, layout=2, size=5000, startblk=NULL_ADDR, inode_off=8192, xattr_isize=32), data_case("inline-one-block", "inline", ["inline-boundaries"], 0, layout=2, size=4096, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-one-byte-tail-head", "inline", ["inline-boundaries"], 4095, layout=2, size=4097, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-one-byte-tail", "inline", ["inline-boundaries"], 4096, layout=2, size=4097, startblk=40, inode_off=8192, xattr_isize=32), data_case("inline-inode-add-overflow", "overflow", ["inline-overflow"], 4096, layout=2, size=5000, inode_off=UINT64_MAX - 31, inode_isize=64), data_case("inline-xattr-add-overflow", "overflow", ["inline-overflow"], 4096, layout=2, size=5000, inode_off=UINT64_MAX - 100, inode_isize=50, xattr_isize=100), data_case("inline-offset-add-overflow", "overflow", ["inline-overflow"], 6000, layout=2, size=8192, inode_off=UINT64_MAX - 1000), ] cases.extend([ data_case("chunk-raw32-start", "chunk", ["chunk-raw32"], 0, layout=4, reader_base=64, reader_bytes=raw32(77)), data_case("chunk-raw32-last", "chunk", ["chunk-raw32"], 4095, layout=4, reader_base=64, reader_bytes=raw32(77)), data_case("chunk-raw32-next", "chunk", ["chunk-raw32"], 4096, layout=4, reader_base=68, reader_bytes=raw32(78)), data_case("chunk-raw32-final", "chunk", ["chunk-raw32"], 9999, layout=4, size=10000, reader_base=72, reader_bytes=raw32(79)), data_case("chunk-raw32-hole", "hole", ["chunk-holes", "chunk-raw32"], 512, layout=4, reader_base=64, reader_bytes=raw32(UINT32_MAX)), data_case("chunk-index32-mask", "multidevice", ["chunk-index32", "device-mask"], 0, layout=4, chunkformat=0x20, device_id_mask=3, reader_base=64, reader_bytes=index_entry(77, 7, False)), data_case("chunk-index32-middle", "chunk", ["chunk-index32"], 9000, layout=4, size=20000, chunkbits=13, chunkformat=0x20, reader_base=72, reader_bytes=index_entry(77, 0, False)), data_case("chunk-index32-hole", "hole", ["chunk-holes", "chunk-index32"], 0, layout=4, chunkformat=0x20, reader_base=64, reader_bytes=index_entry(UINT32_MAX, 9, False)), data_case("chunk-index48-start", "multidevice", ["chunk-index48"], 0, layout=4, chunkformat=0x60, reader_base=64, reader_bytes=index_entry((1 << 32) + 5, 2, True)), data_case("chunk-index48-middle", "multidevice", ["chunk-index48"], 12345, layout=4, size=24576, chunkbits=13, chunkformat=0x60, reader_base=72, reader_bytes=index_entry((1 << 32) + 5, 2, True)), data_case("chunk-index48-hole", "hole", ["chunk-holes", "chunk-index48"], 0, layout=4, chunkformat=0x60, reader_base=64, reader_bytes=index_entry((1 << 48) - 1, 2, True)), data_case("chunk-device-mask-zero", "multidevice", ["chunk-index48", "device-mask"], 0, layout=4, chunkformat=0x60, device_id_mask=0, reader_base=64, reader_bytes=index_entry(81, 0xFFFF, True)), data_case("chunk-eof", "post-EOF", ["chunk-index32"], 4096, layout=4, size=4096, chunkformat=0x20), data_case("chunk-inode-add-overflow", "overflow", ["chunk-overflow", "cleanup-before-acquire"], 0, layout=4, inode_off=UINT64_MAX - 10, inode_isize=20, chunkformat=0x20), data_case("chunk-xattr-add-overflow", "overflow", ["chunk-overflow", "cleanup-before-acquire"], 0, layout=4, inode_off=UINT64_MAX - 100, inode_isize=50, xattr_isize=100, chunkformat=0x20), data_case("chunk-align-overflow", "overflow", ["chunk-overflow", "cleanup-before-acquire"], 0, layout=4, inode_off=UINT64_MAX - 3, inode_isize=0, chunkformat=0x20), data_case("chunk-index-multiply-overflow", "overflow", ["chunk-overflow", "cleanup-before-acquire"], UINT64_MAX - 1, layout=4, size=UINT64_MAX, chunkbits=0, chunkformat=0x20), data_case("chunk-image-size-overflow", "overflow", ["chunk-overflow", "cleanup-before-acquire"], 0, layout=4, blocks=(UINT64_MAX >> 17) + 1, block_size=1 << 17, blkszbits=17, chunkformat=0x20), data_case("chunk-primary-bounds", "bounds", ["chunk-bounds", "cleanup-before-acquire"], 0, layout=4, inode_off=4096, inode_isize=0, blocks=1, chunkformat=0x20), data_case("chunk-primary-tail-bounds", "bounds", ["chunk-bounds", "cleanup-before-acquire"], 0, layout=4, inode_off=4090, inode_isize=0, blocks=1, chunkformat=0x20), data_case("chunk-metabox-missing", "bounds", ["chunk-bounds", "cleanup-before-acquire"], 0, layout=4, nid=META_NID | 2, chunkformat=0x20), data_case("chunk-metabox-bounds", "bounds", ["chunk-bounds", "cleanup-before-acquire"], 0, layout=4, nid=META_NID | 2, inode_off=64, chunkformat=0x20, metabox_present=True, metabox_size=71), data_case("chunk-reader-short", "bounds", ["chunk-bounds", "cleanup-before-acquire"], 0, layout=4, chunkformat=0x20, reader_base=64, reader_bytes=b"\0\0\0\0", reader_error=True), data_case("chunk-shift-overflow", "overflow", ["chunk-overflow", "cleanup-after-acquire"], 0, layout=4, chunkformat=0x60, block_size=1 << 17, blkszbits=17, reader_base=64, reader_bytes=index_entry(1 << 47, 0, True)), data_case("chunk-offset-overflow", "overflow", ["chunk-overflow", "cleanup-after-acquire"], 4096, layout=4, size=8192, chunkbits=13, chunkformat=0x60, reader_base=64, reader_bytes=index_entry(UINT64_MAX >> 12, 4, True)), ]) return cases def z_advise_for_recsize(recsz: int, extra: int = 0) -> int: shift = {4: 0, 8: 1, 16: 2, 32: 3}[recsz] return 1 | (shift << 1) | extra def zmap_case( case_id: str, category: str, subcategories: list[str], request: int, *, recsz: int = 16, records: list[dict[str, int]] | None = None, base_pa: int = 0, size: int = 4096, inode_off: int = 0, inode_isize: int = 0, xattr_isize: int = 0, z_extents: int | None = None, z_advise_extra: int = 0, z_lclusterbits: int = 12, available_compr_algs: int = 1, packed_size: int = 1 << 20, packed_nid: int = 999, reader_error: bool = False, ) -> dict[str, Any]: records = records or [] if z_extents is None: z_extents = len(records) return { "id": case_id, "engine": "zmap", "category": category, "subcategories": subcategories, "request": request, "recsz": recsz, "base_pa": base_pa, "records": records, "reader_error": reader_error, "sbi": { "block_size": 4096, "blkszbits": 12, "available_compr_algs": available_compr_algs, "packed_size": packed_size, "packed_nid": packed_nid, }, "inode": { "nid": 2, "size": size, "datalayout": 1, "inode_off": inode_off, "inode_isize": inode_isize, "xattr_isize": xattr_isize, "z_advise": z_advise_for_recsize(recsz, z_advise_extra), "z_lclusterbits": z_lclusterbits, "z_extents": z_extents, }, } def rec(plen: int, pstart: int, lstart: int = 0) -> dict[str, int]: return {"plen": plen, "pstart": pstart, "lstart": lstart} def make_zmap_cases() -> list[dict[str, Any]]: compressed_1k = (1 << PLEN_FMT_BIT) | 1024 compressed_2k = (1 << PLEN_FMT_BIT) | 2048 ext4 = [rec(compressed_1k, 0), rec(compressed_2k, 0)] ext8 = [rec(compressed_1k, 0x20000), rec(compressed_2k, 0x30000)] ext16 = [ rec(compressed_1k, 0x40000, 0), rec(compressed_2k, 0x50000, 4096), rec(compressed_1k, 0x60000, 8192), ] cases = [ zmap_case("extent4-start", "compressed", ["extent-4", "compressed-flags"], 0, recsz=4, records=ext4, base_pa=0x10000, size=8192), zmap_case("extent4-first-last", "compressed", ["extent-4"], 4095, recsz=4, records=ext4, base_pa=0x10000, size=8192), zmap_case("extent4-second-start", "compressed", ["extent-4"], 4096, recsz=4, records=ext4, base_pa=0x10000, size=8192), zmap_case("extent4-second-middle", "compressed", ["extent-4"], 5000, recsz=4, records=ext4, base_pa=0x10000, size=8192), zmap_case("extent8-start", "compressed", ["extent-8", "compressed-flags"], 0, recsz=8, records=ext8, size=8192), zmap_case("extent8-second-start", "compressed", ["extent-8"], 4096, recsz=8, records=ext8, size=8192), zmap_case("extent8-hole", "hole", ["extent-8", "compressed-flags"], 4096, recsz=8, records=[ext8[0], rec(0, 0, 4096)], size=8192), zmap_case("extent16-start", "compressed", ["extent-16", "compressed-flags"], 0, records=ext16, size=12288), zmap_case("extent16-first-last", "compressed", ["extent-16"], 4095, records=ext16, size=12288), zmap_case("extent16-second-start", "compressed", ["extent-16"], 4096, records=ext16, size=12288), zmap_case("extent16-second-middle", "compressed", ["extent-16"], 7000, records=ext16, size=12288), zmap_case("extent16-third-start", "compressed", ["extent-16"], 8192, records=ext16, size=12288), zmap_case("extent16-physical-48bit", "compressed", ["extent-16"], 0, records=[rec(compressed_1k, (1 << 40) + 9, 0)]), zmap_case("extent32-logical-64bit", "compressed", ["extent-32"], 1 << 32, recsz=32, records=[rec(compressed_1k, 0x70000, 0), rec(compressed_2k, 0x80000, 1 << 32)], size=(1 << 32) + 4096), zmap_case("extent-partial-reference", "partial-reference", ["extent-16", "compressed-flags"], 0, records=[rec(PLEN_PARTIAL | compressed_1k, 0x90000, 0)]), zmap_case("extent-fragment-tail", "fragment", ["extent-16", "compressed-flags"], 4096, records=[rec(8192, 0, 4096)], size=6144, z_advise_extra=0x20), zmap_case("extent-interlaced-shifted", "compressed", ["extent-16", "compressed-flags"], 0, records=[rec(4096, 0xA0000, 0)], z_advise_extra=0x10), zmap_case("extent-algorithm-one", "compressed", ["extent-16", "compressed-flags"], 0, records=[rec((2 << PLEN_FMT_BIT) | 1024, 0xB0000, 0)], available_compr_algs=3), zmap_case("extent-algorithm-unavailable", "invalid", ["compressed-invalid", "cleanup-after-acquire"], 0, records=[rec((2 << PLEN_FMT_BIT) | 1024, 0xB0000, 0)], available_compr_algs=1), zmap_case("extent-algorithm-runtime-invalid", "invalid", ["compressed-invalid", "cleanup-after-acquire"], 0, records=[rec((7 << PLEN_FMT_BIT) | 1024, 0xC0000, 0)]), zmap_case("extent-plen-too-large", "invalid", ["compressed-invalid", "cleanup-after-acquire"], 0, records=[rec(PCLUSTER_MAX + 1, 0xD0000, 0)]), zmap_case("extent-full-short-logical", "invalid", ["compressed-invalid", "cleanup-after-acquire"], 0, records=[rec((1 << PLEN_FMT_BIT) | 8192, 0xE0000, 0)]), zmap_case("extent-pa-add-overflow", "overflow", ["compressed-overflow", "cleanup-after-acquire"], 0, records=[rec(compressed_1k, UINT64_MAX - 511, 0)]), zmap_case("extent-physical-48bit-limit", "bounds", ["compressed-bounds", "cleanup-after-acquire"], 0, records=[rec(compressed_1k, (1 << 60), 0)]), zmap_case("extent-reader-short", "bounds", ["compressed-bounds", "cleanup-before-acquire"], 0, records=[rec(compressed_1k, 0xF0000, 0)], reader_error=True), zmap_case("extent-table-overflow", "overflow", ["compressed-overflow", "cleanup-before-acquire"], 0, records=[rec(compressed_1k, 0x100000, 0)], inode_off=UINT64_MAX - 3), zmap_case("extent-record-multiply-overflow", "overflow", ["compressed-overflow", "cleanup-before-acquire"], 0, recsz=32, records=[], size=UINT64_MAX, z_extents=UINT64_MAX), zmap_case("extent-fragment-bounds", "bounds", ["compressed-bounds", "cleanup-after-acquire"], 4096, records=[rec(8192, 0, 4096)], size=6144, z_advise_extra=0x20, packed_size=1024), zmap_case("compressed-eof", "post-EOF", ["extent-16"], 4096, records=[rec(compressed_1k, 0x110000, 0)]), zmap_case("compressed-post-eof", "post-EOF", ["extent-16"], 4113, records=[rec(compressed_1k, 0x110000, 0)]), ] return cases def make_device_cases() -> list[dict[str, Any]]: def device( case_id: str, *, pa: int, plen: int, deviceid: int, primary_blocks: int = 1024, extra: list[dict[str, Any]] | None = None, flatdev: bool = False, devs_present: bool = True, ) -> dict[str, Any]: return { "id": case_id, "subcategories": ["device-resolution"], "input": {"pa": pa, "plen": plen, "deviceid": deviceid}, "sbi": { "blkszbits": 12, "primary_blocks": primary_blocks, "extra": extra or [], "flatdev": flatdev, "devs_present": devs_present, }, } good = lambda blocks, uniaddr=0: { "blocks": blocks, "uniaddr": uniaddr, "provider": True } missing = lambda blocks, uniaddr=0: { "blocks": blocks, "uniaddr": uniaddr, "provider": False } return [ device("device-primary", pa=4096, plen=512, deviceid=0), device("device-explicit-one", pa=4096, plen=512, deviceid=1, extra=[good(32)]), device("device-explicit-two", pa=8192, plen=1024, deviceid=2, extra=[good(32), good(64)]), device("device-id-out-of-range", pa=0, plen=1, deviceid=3, extra=[good(32), good(32)]), device("device-table-missing", pa=0, plen=1, deviceid=1, extra=[good(32)], devs_present=False), device("device-explicit-bounds", pa=4096 * 32 - 128, plen=256, deviceid=1, extra=[good(32)]), device("device-explicit-provider-missing", pa=0, plen=1, deviceid=1, extra=[missing(32)]), device("device-flat-explicit", pa=512, plen=512, deviceid=1, extra=[good(32, 100)], flatdev=True), device("device-flat-add-overflow", pa=UINT64_MAX - 1024, plen=512, deviceid=1, extra=[good(32, 1)], flatdev=True), device("device-flat-primary", pa=4096, plen=512, deviceid=0, primary_blocks=32, extra=[good(32, 100)], flatdev=True), device("device-flat-implicit-extra", pa=100 * 4096 + 512, plen=512, deviceid=0, primary_blocks=32, extra=[good(32, 100)], flatdev=True), device("device-separate-implicit-extra", pa=100 * 4096 + 512, plen=512, deviceid=0, primary_blocks=32, extra=[good(32, 100)]), device("device-blocks-shift-overflow", pa=0, plen=1, deviceid=1, extra=[good((UINT64_MAX >> 12) + 1)]), ] def add_u64(left: int, right: int) -> tuple[bool, int]: value = left + right return (value > UINT64_MAX, value & UINT64_MAX) def roundup_u64(value: int, alignment: int) -> tuple[bool, int]: overflow, rounded = add_u64(value, alignment - 1) return overflow, rounded & ~(alignment - 1) def model_data(case: dict[str, Any]) -> dict[str, int]: request = case["request"] inode = case["inode"] sbi = case["sbi"] result = tuple_record(m_la=request) if request >= inode["size"]: return result remain = inode["size"] - request layout = inode["datalayout"] block_size = sbi["block_size"] if layout == 0: run_len = min(remain, block_size - (request & (block_size - 1))) result["m_llen"] = result["m_plen"] = run_len if inode["startblk"] == NULL_ADDR: return result if inode["startblk"] > (UINT64_MAX >> sbi["blkszbits"]): result["errno"] = EINTEGRITY return result overflow, pa = add_u64(inode["startblk"] << sbi["blkszbits"], request) if overflow: result["errno"] = EINTEGRITY return result result["m_pa"] = pa result["m_flags"] = MAPPED return result if layout == 2: tail_start = 0 if inode["size"] == 0 else ( (inode["size"] + block_size - 1) & ~(block_size - 1) ) - block_size if request < tail_start: run_len = min( remain, tail_start - request, block_size - (request & (block_size - 1)), ) result["m_llen"] = result["m_plen"] = run_len if inode["startblk"] == NULL_ADDR: return result if inode["startblk"] > (UINT64_MAX >> sbi["blkszbits"]): result["errno"] = EINTEGRITY return result overflow, pa = add_u64( inode["startblk"] << sbi["blkszbits"], request ) if overflow: result["errno"] = EINTEGRITY return result result["m_pa"] = pa result["m_flags"] = MAPPED return result run_len = min( remain, block_size - ((request - tail_start) & (block_size - 1)) ) result["m_llen"] = result["m_plen"] = run_len overflow, pa = add_u64(inode["inode_off"], inode["inode_isize"]) if not overflow: overflow, pa = add_u64(pa, inode["xattr_isize"]) if not overflow: overflow, pa = add_u64(pa, request - tail_start) if overflow: result["m_pa"] = pa result["errno"] = EINTEGRITY return result result["m_pa"] = pa result["m_flags"] = MAPPED | META return result if layout != 4: result["errno"] = EOPNOTSUPP return result chunk_size = 1 << inode["chunkbits"] chunk_index = request >> inode["chunkbits"] chunk_offset = request & (chunk_size - 1) entry_size = 8 if inode["chunkformat"] & 0x20 else 4 overflow, index_base = add_u64(inode["inode_off"], inode["inode_isize"]) if overflow: result["errno"] = EOVERFLOW return result overflow, index_base = add_u64(index_base, inode["xattr_isize"]) if overflow: result["errno"] = EOVERFLOW return result overflow, index_base = roundup_u64(index_base, entry_size) if overflow: result["errno"] = EOVERFLOW return result if chunk_index > (UINT64_MAX - index_base) // entry_size: result["errno"] = EOVERFLOW return result index_offset = index_base + chunk_index * entry_size if inode["nid"] & META_NID: if ( not sbi["metabox_present"] or index_offset > sbi["metabox_size"] or entry_size > sbi["metabox_size"] - index_offset ): result["errno"] = EINTEGRITY return result else: if sbi["blocks"] > (UINT64_MAX >> sbi["blkszbits"]): result["errno"] = EOVERFLOW return result image_size = sbi["blocks"] << sbi["blkszbits"] if index_offset > image_size or entry_size > image_size - index_offset: result["errno"] = EINTEGRITY return result reader = case["reader"] raw = reader["bytes"] if ( reader["error"] or index_offset < reader["base"] or entry_size > len(raw) or index_offset - reader["base"] > len(raw) - entry_size ): result["errno"] = EIO return result entry = raw[index_offset - reader["base"] : index_offset - reader["base"] + entry_size] result["acquire_count"] = result["release_count"] = 1 if inode["chunkformat"] & 0x20: high, raw_device, low = struct.unpack(" (UINT64_MAX >> sbi["blkszbits"]): result["errno"] = EOVERFLOW return result overflow, pa = add_u64(block << sbi["blkszbits"], chunk_offset) if overflow: result["errno"] = EOVERFLOW return result result["m_pa"] = pa result["m_flags"] = MAPPED return result def extent_table_pos(inode: dict[str, int], recsz: int) -> tuple[int, int]: overflow, pos = add_u64(inode["inode_off"], inode["inode_isize"]) if not overflow: overflow, pos = add_u64(pos, inode["xattr_isize"]) if not overflow: overflow, pos = roundup_u64(pos, 8) if not overflow: overflow, pos = add_u64(pos, 8) if not overflow: overflow, pos = roundup_u64(pos, recsz) return (EINTEGRITY if overflow else 0, pos) def record_pos( inode: dict[str, int], table_pos: int, recsz: int, index: int ) -> tuple[int, int]: if index >= inode["z_extents"] or index > UINT64_MAX // recsz: return EINTEGRITY, 0 overflow, pos = add_u64(table_pos, index * recsz) return (EINTEGRITY if overflow else 0, pos) def read_extent( case: dict[str, Any], table_pos: int, recsz: int, index: int, counters: dict[str, int] ) -> tuple[int, dict[str, int] | None]: error, _ = record_pos(case["inode"], table_pos, recsz, index) if error: return error, None if case["reader_error"] or index >= len(case["records"]): return EIO, None counters["acquire"] += 1 counters["release"] += 1 return 0, case["records"][index] def model_zmap(case: dict[str, Any]) -> dict[str, int]: request = case["request"] inode = case["inode"] sbi = case["sbi"] result = tuple_record(m_la=request) if request >= inode["size"]: result["m_llen"] = request + 1 - inode["size"] result["m_la"] = inode["size"] return result recsz = case["recsz"] error, table_pos = extent_table_pos(inode, recsz) if error: result["errno"] = error return result pos = table_pos logical_end = inode["size"] cluster_size = 1 << inode["z_lclusterbits"] counters = {"acquire": 0, "release": 0} result["m_flags"] = 0 last = False lstart = logical_end if recsz <= 8: if recsz <= 4: if case["reader_error"]: result["errno"] = EIO return result counters["acquire"] += 1 counters["release"] += 1 pa = case["base_pa"] overflow, pos = add_u64(pos, 8) if overflow: result["errno"] = EINTEGRITY return result lstart = 0 extent_index = 0 else: lstart = request & ~(cluster_size - 1) extent_index = lstart >> inode["z_lclusterbits"] pa = NULL_ADDR while True: error, extent = read_extent(case, pos, recsz, extent_index, counters) if error: result["errno"] = error result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result assert extent is not None result["m_plen"] = extent["plen"] if pa != NULL_ADDR: result["m_pa"] = pa overflow, pa = add_u64(pa, result["m_plen"] & PLEN_MASK) if overflow: result["errno"] = EINTEGRITY result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result else: result["m_pa"] = extent["pstart"] & UINT32_MAX if extent_index == UINT64_MAX: result["errno"] = EINTEGRITY result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result extent_index += 1 overflow, next_lstart = add_u64(lstart, cluster_size) if overflow: result["errno"] = EINTEGRITY result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result lstart = next_lstart if lstart > request: break overflow, rounded_end = roundup_u64(logical_end, cluster_size) if overflow: result["errno"] = EINTEGRITY result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result last = lstart >= rounded_end logical_end = min(lstart, logical_end) lstart -= cluster_size else: left, right = 0, inode["z_extents"] while left < right: middle = left + (right - left) // 2 error, extent = read_extent(case, table_pos, recsz, middle, counters) if error: result["errno"] = error result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result assert extent is not None logical = extent["lstart"] & (UINT64_MAX if recsz == 32 else UINT32_MAX) physical = extent["pstart"] if logical > request: right = middle if logical > logical_end: result["errno"] = EINTEGRITY result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] return result logical_end = logical else: left = middle + 1 if request == logical: right = min(left + 1, right) lstart = logical result["m_plen"] = extent["plen"] result["m_pa"] = physical last = left >= inode["z_extents"] if lstart < logical_end: result["m_la"] = lstart if last and inode["z_advise"] & 0x20: result["m_flags"] = FRAGMENT fragment_offset = result["m_plen"] if recsz > 4: fragment_offset |= result["m_pa"] << 32 elif result["m_plen"] & PLEN_MASK: result["m_flags"] = MAPPED fmt = result["m_plen"] >> PLEN_FMT_BIT if result["m_plen"] & PLEN_PARTIAL: result["m_flags"] |= PARTIAL_REF result["m_plen"] &= PLEN_MASK if fmt: result["m_algorithmformat"] = fmt - 1 elif ( inode["z_advise"] & 0x10 and ((result["m_pa"] | result["m_plen"]) & (sbi["block_size"] - 1)) == 0 ): result["m_algorithmformat"] = COMPRESSION_INTERLACED else: result["m_algorithmformat"] = COMPRESSION_SHIFTED result["m_llen"] = logical_end - result["m_la"] result["acquire_count"] = counters["acquire"] result["release_count"] = counters["release"] sanity_error = 0 if result["m_flags"] & FRAGMENT: if ( result["m_flags"] & (MAPPED | META) or sbi["packed_nid"] == inode["nid"] or fragment_offset > sbi["packed_size"] or result["m_llen"] > sbi["packed_size"] - fragment_offset ): sanity_error = EINTEGRITY elif result["m_flags"] & MAPPED: algorithm = result["m_algorithmformat"] & 0xFF if algorithm >= COMPRESSION_RUNTIME_MAX: sanity_error = EOPNOTSUPP elif algorithm < COMPRESSION_MAX: if not (sbi["available_compr_algs"] & (1 << algorithm)): sanity_error = EINTEGRITY elif not (result["m_flags"] & (PARTIAL_MAPPED | PARTIAL_REF)) and result["m_llen"] < result["m_plen"]: sanity_error = EINTEGRITY elif result["m_llen"] > result["m_plen"]: sanity_error = EINTEGRITY if not sanity_error and ( result["m_plen"] > PCLUSTER_MAX or result["m_llen"] > PCLUSTER_MAX_DSIZE ): sanity_error = EOPNOTSUPP if not sanity_error and not (result["m_flags"] & META): overflow, physical_end = add_u64(result["m_pa"], result["m_plen"]) if overflow or (physical_end >> sbi["blkszbits"]) >= (1 << 48): sanity_error = EINTEGRITY if sanity_error: result["errno"] = sanity_error result["m_llen"] = 0 return result def model_device(case: dict[str, Any]) -> dict[str, int]: state = case["sbi"] map_state = dict(case["input"]) extras = state["extra"] if state["devs_present"] else None selected = 0 def check_range(device: dict[str, Any], offset: int, length: int) -> int: if device["blocks"] > (UINT64_MAX >> state["blkszbits"]): return EINTEGRITY overflow, end = add_u64(offset, length) if overflow: return EINTEGRITY return EINTEGRITY if end > device["blocks"] << state["blkszbits"] else 0 primary = {"blocks": state["primary_blocks"], "uniaddr": 0, "provider": True} device_id = map_state["deviceid"] if device_id: if extras is None or device_id > len(extras): error = ENODEV else: chosen = extras[device_id - 1] error = check_range(chosen, map_state["pa"], map_state["plen"]) if not error and state["flatdev"]: if chosen["uniaddr"] > (UINT64_MAX >> state["blkszbits"]): error = EINTEGRITY else: overflow, mapped = add_u64( map_state["pa"], chosen["uniaddr"] << state["blkszbits"] ) if overflow: error = EINTEGRITY else: map_state["pa"] = mapped elif not error: if not chosen["provider"]: error = ENODEV else: selected = device_id elif not state["extra"]: error = 0 elif state["flatdev"]: error = check_range(primary, map_state["pa"], map_state["plen"]) if error: for chosen in state["extra"]: if not chosen["uniaddr"] or chosen["uniaddr"] > (UINT64_MAX >> state["blkszbits"]): continue start = chosen["uniaddr"] << state["blkszbits"] if map_state["pa"] < start: continue relative = map_state["pa"] - start error = check_range(chosen, relative, map_state["plen"]) if not error: break if relative < chosen["blocks"] << state["blkszbits"]: break else: error = EINTEGRITY else: error = 0 for index, chosen in enumerate(state["extra"], 1): if not chosen["uniaddr"]: continue if chosen["uniaddr"] > (UINT64_MAX >> state["blkszbits"]): error = EINTEGRITY break start = chosen["uniaddr"] << state["blkszbits"] if map_state["pa"] >= start and map_state["pa"] - start < chosen["blocks"] << state["blkszbits"]: relative = map_state["pa"] - start error = check_range(chosen, relative, map_state["plen"]) if error: break if not chosen["provider"]: error = ENODEV break map_state["pa"] = relative selected = index break return { "m_pa": map_state["pa"], "m_plen": map_state["plen"], "m_deviceid": map_state["deviceid"], "selected_device": selected, "errno": error, } def extent_bytes(case: dict[str, Any]) -> tuple[int, bytes]: error, table_pos = extent_table_pos(case["inode"], case["recsz"]) if error: return 0, b"" output = bytearray() if case["recsz"] == 4: output.extend(struct.pack("> 32) & UINT32_MAX, extent["lstart"] & UINT32_MAX, (extent["lstart"] >> 32) & UINT32_MAX, b"\0" * 12, ) output.extend(packed[: case["recsz"]]) return table_pos, bytes(output) MAP_CASES = make_data_cases() + make_zmap_cases() DEVICE_CASES = make_device_cases() MODEL_RECORDS = [] for case in MAP_CASES: expected = model_data(case) if case["engine"] == "data" else model_zmap(case) if case["engine"] == "data": fixture = case["reader"]["bytes"] else: _, fixture = extent_bytes(case) MODEL_RECORDS.append( { "id": case["id"], "engine": case["engine"], "category": case["category"], "subcategories": case["subcategories"], "fixture_sha256": sha256_bytes(fixture), "expected": expected, } ) MODEL_DEVICES = [ {"id": case["id"], "expected": model_device(case)} for case in DEVICE_CASES ] MODEL_SHA256 = sha256_bytes(canonical({"maps": MODEL_RECORDS, "devices": MODEL_DEVICES})) if SPEC["expected_map_case_count"] != len(MAP_CASES): raise SystemExit( f"map case count is not frozen: actual={len(MAP_CASES)} " f"configured={SPEC['expected_map_case_count']}" ) if SPEC["expected_device_case_count"] != len(DEVICE_CASES): raise SystemExit("device case count is not frozen") if SPEC["expected_model_sha256"] != MODEL_SHA256: raise SystemExit( f"independent model digest mismatch: actual={MODEL_SHA256} " f"configured={SPEC['expected_model_sha256']}" ) COMMON_C = r''' #include #include #include #include #include #include #define EIO 5 #define ENODEV 19 #define EINVAL 22 #define EOPNOTSUPP 45 #define EOVERFLOW 84 #define EINTEGRITY 97 #define UINT64_MAX_VALUE UINT64_MAX #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define roundup2(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) #define rounddown2(x, y) ((x) & ~((y) - 1)) #define bzero(ptr, len) memset((ptr), 0, (len)) #define le16toh(value) (value) #define le32toh(value) (value) typedef uint64_t erofs_off_t; typedef uint64_t erofs_blk_t; typedef uint64_t erofs_nid_t; static uint32_t le32dec(const void *pointer) { const unsigned char *bytes = pointer; return ((uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 | (uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24); } static uint64_t le64dec(const void *pointer) { const unsigned char *bytes = pointer; return ((uint64_t)le32dec(bytes) | (uint64_t)le32dec(bytes + 4) << 32); } #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_CHUNK_FORMAT_INDEXES 0x20 #define EROFS_CHUNK_FORMAT_48BIT 0x40 #define EROFS_BLOCK_MAP_ENTRY_SIZE 4 #define EROFS_DIRENT_NID_METABOX (1ULL << 63) #define EROFS_NULL_ADDR UINT64_MAX #define EROFS_MAP_MAPPED 0x0001 #define EROFS_MAP_META 0x0002 #define EROFS_MAP_PARTIAL_MAPPED 0x0004 #define EROFS_MAP_PARTIAL_REF 0x0008 #define EROFS_MAP_FRAGMENT 0x0010 #define EROFS_MAP_FULL(f) (!((f) & (EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF))) #define EROFS_GET_BLOCKS_FIEMAP 0x0001 #define EROFS_GET_BLOCKS_READMORE 0x0002 #define EROFS_GET_BLOCKS_FINDTAIL 0x0004 #define Z_EROFS_COMPRESSION_LZ4 0 #define Z_EROFS_COMPRESSION_LZMA 1 #define Z_EROFS_COMPRESSION_DEFLATE 2 #define Z_EROFS_COMPRESSION_ZSTD 3 #define Z_EROFS_COMPRESSION_MAX 4 #define Z_EROFS_COMPRESSION_SHIFTED 4 #define Z_EROFS_COMPRESSION_INTERLACED 5 #define Z_EROFS_COMPRESSION_RUNTIME_MAX 6 #define Z_EROFS_PCLUSTER_MAX_SIZE (1024 * 1024) #define Z_EROFS_PCLUSTER_MAX_DSIZE (12 * 1024 * 1024) #define Z_EROFS_ADVISE_EXTENTS 0x0001 #define Z_EROFS_ADVISE_INTERLACED_PCLUSTER 0x0010 #define Z_EROFS_ADVISE_FRAGMENT_PCLUSTER 0x0020 #define Z_EROFS_ADVISE_EXTRECSZ_BIT 1 #define Z_EROFS_ADVISE_EXTRECSZ_MASK 0x3 #define Z_EROFS_EXTENT_PLEN_PARTIAL (1U << 27) #define Z_EROFS_EXTENT_PLEN_FMT_BIT 28 #define Z_EROFS_EXTENT_PLEN_MASK ((Z_EROFS_PCLUSTER_MAX_SIZE << 1) - 1) struct erofs_inode_chunk_index { uint16_t startblk_hi; uint16_t device_id; uint32_t startblk_lo; } __attribute__((packed)); struct z_erofs_extent { uint32_t plen, pstart_lo, pstart_hi, lstart_lo, lstart_hi; uint8_t reserved[12]; } __attribute__((packed)); struct z_erofs_map_header { uint32_t word0; uint16_t h_advise; uint8_t h_algorithmtype; uint8_t h_clusterbits; } __attribute__((packed)); struct erofs_device_info { void *devvp; void *cp; erofs_blk_t blocks; erofs_blk_t uniaddr; }; 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_inode { erofs_nid_t nid; uint64_t size; erofs_off_t inode_off; erofs_blk_t startblk; uint8_t datalayout; uint8_t inode_isize; uint32_t xattr_isize; uint16_t z_advise; uint8_t z_algorithmtype[2]; uint8_t z_lclusterbits; uint16_t z_idata_size; erofs_off_t z_fragmentoff; uint64_t z_tailextent_headlcn; uint64_t z_extents; bool z_initialized; uint16_t chunkformat; uint8_t chunkbits; bool fragment; }; struct erofs_sb_info { uint64_t block_size; uint8_t blkszbits; erofs_blk_t blocks; uint16_t device_id_mask; struct erofs_inode *metabox_en; uint16_t available_compr_algs; uint64_t packed_nid; struct erofs_inode *packed_inode; struct erofs_device_info dif0; struct erofs_device_info *devs; unsigned int extra_devices; bool flatdev; }; struct erofs_map_dev { struct erofs_device_info *m_dif; erofs_off_t m_pa; unsigned int m_deviceid; uint64_t m_plen; }; struct erofs_buf { void *data; void (*release)(void *); }; #define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL } static const unsigned char *gate_bytes; static size_t gate_bytes_len; static erofs_off_t gate_bytes_base; static bool gate_reader_error; static unsigned int gate_acquires; static unsigned int gate_releases; static bool erofs_nid_in_metabox(erofs_nid_t nid) { return ((nid & EROFS_DIRENT_NID_METABOX) != 0); } static bool __attribute__((unused)) erofs_inode_is_data_compressed(unsigned int datalayout) { return (datalayout == EROFS_INODE_COMPRESSED_FULL || datalayout == EROFS_INODE_COMPRESSED_COMPACT); } 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 *data; (void)sbi; (void)nid; if (gate_reader_error || off < gate_bytes_base || off - gate_bytes_base > gate_bytes_len || len > gate_bytes_len - (size_t)(off - gate_bytes_base)) return (EIO); data = malloc(len == 0 ? 1 : len); if (data == NULL) return (EIO); if (len != 0) memcpy(data, gate_bytes + (off - gate_bytes_base), len); buf->data = data; buf->release = NULL; ++gate_acquires; return (0); } static void erofs_brelse(void *data) { if (data != NULL) { ++gate_releases; free(data); } } static void erofs_put_metabuf(struct erofs_buf *buf) { if (buf != NULL && buf->data != NULL) { void *data = buf->data; buf->data = NULL; buf->release = NULL; erofs_brelse(data); } } static unsigned int z_erofs_extent_recsize(unsigned int advise) { return (4U << ((advise >> Z_EROFS_ADVISE_EXTRECSZ_BIT) & Z_EROFS_ADVISE_EXTRECSZ_MASK)); } static int z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi) { (void)sbi; return (vi->z_initialized ? 0 : EINTEGRITY); } static int z_erofs_map_blocks_fo(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { (void)sbi; (void)vi; (void)map; (void)flags; return (EOPNOTSUPP); } ''' def c_bytes(data: bytes) -> str: return ", ".join(f"0x{byte:02x}" for byte in data) if data else "0" def c_u64(value: int) -> str: return f"UINT64_C({value})" zmap_names = [ "z_erofs_extent_add", "z_erofs_extent_roundup", "z_erofs_extent_table_pos", "z_erofs_extent_record_pos", "z_erofs_read_extent", "z_erofs_extent_lstart", "z_erofs_map_blocks_ext", "z_erofs_map_sanity_check", "z_erofs_map_blocks_iter", ] program = COMMON_C program += "int z_erofs_map_blocks_iter(struct erofs_sb_info *, struct erofs_inode *, struct erofs_map_blocks *, int);\n" program += extract_function(data_source, "erofs_inline_tail_start") program += extract_function(data_source, "erofs_map_blocks_chunk") program += legacy_body if candidate_mode: program += extract_function(data_source, "erofs_map_blocks") for name in zmap_names: program += extract_function(zmap_source, name) program += "\nint\nmain(void)\n{\n\tint error;\n" for index, case in enumerate(MAP_CASES): if case["engine"] == "data": raw = case["reader"]["bytes"] reader_base = case["reader"]["base"] reader_error = case["reader"]["error"] inode = case["inode"] sbi = case["sbi"] else: reader_base, raw = extent_bytes(case) reader_error = case["reader_error"] inode = case["inode"] sbi = { "block_size": case["sbi"]["block_size"], "blkszbits": case["sbi"]["blkszbits"], "blocks": 0, "device_id_mask": 0xFFFF, "metabox_present": False, "metabox_size": 0, } packed_size = case.get("sbi", {}).get("packed_size", 1 << 20) packed_nid = case.get("sbi", {}).get("packed_nid", 999) available = case.get("sbi", {}).get("available_compr_algs", 1) program += f''' {{ static const unsigned char bytes[] = {{ {c_bytes(raw)} }}; struct erofs_inode metabox = {{ .size = {c_u64(sbi['metabox_size'])} }}; struct erofs_inode packed = {{ .nid = {c_u64(packed_nid)}, .size = {c_u64(packed_size)} }}; struct erofs_sb_info sbi = {{ .block_size = {c_u64(sbi['block_size'])}, .blkszbits = {sbi['blkszbits']}, .blocks = {c_u64(sbi['blocks'])}, .device_id_mask = {sbi['device_id_mask']}, .metabox_en = {('&metabox' if sbi['metabox_present'] else 'NULL')}, .available_compr_algs = {available}, .packed_nid = {c_u64(packed_nid)}, .packed_inode = &packed, }}; struct erofs_inode vi = {{ .nid = {c_u64(inode['nid'])}, .size = {c_u64(inode['size'])}, .inode_off = {c_u64(inode['inode_off'])}, .startblk = {c_u64(inode.get('startblk', 0))}, .datalayout = {inode['datalayout']}, .inode_isize = {inode['inode_isize']}, .xattr_isize = {inode['xattr_isize']}, .z_advise = {inode.get('z_advise', 0)}, .z_lclusterbits = {inode.get('z_lclusterbits', 12)}, .z_extents = {c_u64(inode.get('z_extents', 0))}, .z_initialized = true, .chunkformat = {inode.get('chunkformat', 0)}, .chunkbits = {inode.get('chunkbits', 12)}, }}; struct erofs_map_blocks map = {{ .m_pa = UINT64_C(0xcccccccccccccccc), .m_la = {c_u64(case['request'])}, .m_plen = UINT64_C(0xdddddddddddddddd), .m_llen = UINT64_C(0xeeeeeeeeeeeeeeee), .m_deviceid = 0xaaaa, .m_algorithmformat = 0x55, .m_flags = 0xbbbbbbbb, }}; (void)metabox; gate_bytes = bytes; gate_bytes_len = {len(raw)}; gate_bytes_base = {c_u64(reader_base)}; gate_reader_error = {'true' if reader_error else 'false'}; gate_acquires = gate_releases = 0; ''' if candidate_mode: program += "\t\terror = erofs_map_blocks(&sbi, &vi, &map);\n" elif case["engine"] == "data": program += f''' {{ erofs_off_t pa = 0; unsigned int device = 0; size_t run = 0; bool hole = false, metadata = false; error = erofs_map_blocks(&sbi, &vi, {c_u64(case['request'])}, &pa, &device, &run, &hole, &metadata); map = (struct erofs_map_blocks){{ .m_pa = pa, .m_la = {c_u64(case['request'])}, .m_plen = run, .m_llen = run, .m_deviceid = device, .m_flags = (error == 0 && run != 0 && !hole ? EROFS_MAP_MAPPED : 0) | (metadata ? EROFS_MAP_META : 0), }}; }} ''' else: program += "\t\tmap = (struct erofs_map_blocks){ .m_la = map.m_la };\n" program += "\t\terror = z_erofs_map_blocks_iter(&sbi, &vi, &map, 0);\n" program += f''' printf("tuple\\t{case['id']}\\t%llu\\t%llu\\t%llu\\t%llu\\t%u\\t%u\\t%d\\t%d\\t%u\\t%u\\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, gate_acquires, gate_releases); }} ''' program += "\treturn (0);\n}\n" device_program = COMMON_C for name in ["erofs_check_device_range", "erofs_fill_from_devinfo", "erofs_map_dev"]: device_program += extract_function(data_source, name) device_program += "\nint\nmain(void)\n{\n" for case in DEVICE_CASES: state = case["sbi"] extras = state["extra"] entries = [] for extra in extras: entries.append( "{ .devvp = %s, .cp = %s, .blocks = %s, .uniaddr = %s }" % ( "(void *)1" if extra["provider"] else "NULL", "(void *)1" if extra["provider"] else "NULL", c_u64(extra["blocks"]), c_u64(extra["uniaddr"]), ) ) device_program += f''' {{ struct erofs_device_info devs[{max(1, len(extras))}] = {{ {', '.join(entries) if entries else '{ 0 }'} }}; struct erofs_sb_info sbi = {{ .blkszbits = {state['blkszbits']}, .blocks = {c_u64(state['primary_blocks'])}, .dif0 = {{ .devvp = (void *)1, .cp = (void *)1, .blocks = {c_u64(state['primary_blocks'])} }}, .devs = {('devs' if state['devs_present'] else 'NULL')}, .extra_devices = {len(extras)}, .flatdev = {'true' if state['flatdev'] else 'false'}, }}; struct erofs_map_dev map = {{ .m_pa = {c_u64(case['input']['pa'])}, .m_plen = {c_u64(case['input']['plen'])}, .m_deviceid = {case['input']['deviceid']} }}; int selected = -1; int error = erofs_map_dev(&sbi, &map); if (map.m_dif == &sbi.dif0) selected = 0; ''' for index in range(len(extras)): device_program += f"\t\telse if (map.m_dif == &devs[{index}])\n\t\t\tselected = {index + 1};\n" device_program += f''' printf("device\\t{case['id']}\\t%llu\\t%llu\\t%u\\t%d\\t%d\\n", (unsigned long long)map.m_pa, (unsigned long long)map.m_plen, map.m_deviceid, selected, error); }} ''' device_program += "\treturn (0);\n}\n" adapter_program = "" expected_adapter_lines = [ "adapter\t1\t65\t74566\t4097\t2049\t48879\t31\t5\t0\t1", "adapter\t3\t67\t74568\t4099\t2051\t48879\t31\t5\t0\t1", ] if candidate_mode: adapter_program = COMMON_C + r''' static unsigned int gate_adapter_calls; int z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { (void)sbi; if (flags != 0) return (EINVAL); ++gate_adapter_calls; map->m_la = 64 + vi->datalayout; map->m_pa = 74565 + vi->datalayout; map->m_llen = 4096 + vi->datalayout; map->m_plen = 2048 + vi->datalayout; map->m_deviceid = 0xbeef; map->m_algorithmformat = Z_EROFS_COMPRESSION_INTERLACED; map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_META | EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF | EROFS_MAP_FRAGMENT; return (0); } ''' adapter_program += extract_function(data_source, "erofs_inline_tail_start") adapter_program += extract_function(data_source, "erofs_map_blocks_chunk") adapter_program += legacy_body adapter_program += extract_function(data_source, "erofs_map_blocks") adapter_program += r''' int main(void) { const unsigned int layouts[] = { EROFS_INODE_COMPRESSED_FULL, EROFS_INODE_COMPRESSED_COMPACT, }; struct erofs_sb_info sbi = { .block_size = 4096, .blkszbits = 12 }; unsigned int index; for (index = 0; index < sizeof(layouts) / sizeof(layouts[0]); ++index) { struct erofs_inode vi = { .datalayout = layouts[index], .size = 8192 }; struct erofs_map_blocks map = { .m_la = 123, .m_pa = UINT64_MAX, .m_llen = UINT64_MAX, .m_plen = UINT64_MAX, .m_deviceid = 0xffff, .m_algorithmformat = -1, .m_flags = UINT32_MAX, }; int error; gate_adapter_calls = 0; error = erofs_map_blocks(&sbi, &vi, &map); printf("adapter\t%u\t%llu\t%llu\t%llu\t%llu\t%u\t%u\t%d\t%d\t%u\n", layouts[index], (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, gate_adapter_calls); } return (0); } ''' def compile_and_run(name: str, source: str) -> list[str]: source_path = OUTPUT / f"{name}.c" binary_path = OUTPUT / name source_path.write_text(source, encoding="ascii") compile_flags = ["cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror"] if name in {"P15-006-device-extractor", "P15-006-adapter-extractor"}: compile_flags.append("-Wno-unused-function") compile_run = run( compile_flags + ["-o", str(binary_path), str(source_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (OUTPUT / f"{name}.compile.stdout").write_text(compile_run.stdout, encoding="utf-8") (OUTPUT / f"{name}.compile.stderr").write_text(compile_run.stderr, encoding="utf-8") if compile_run.returncode != 0: raise SystemExit(f"{name} compilation failed") execute = run([str(binary_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (OUTPUT / f"{name}.stdout").write_text(execute.stdout, encoding="utf-8") (OUTPUT / f"{name}.stderr").write_text(execute.stderr, encoding="utf-8") if execute.returncode != 0: raise SystemExit(f"{name} execution failed") return execute.stdout.splitlines() actual_lines = compile_and_run("P15-006-map-extractor", program) device_lines = compile_and_run("P15-006-device-extractor", device_program) adapter_lines = ( compile_and_run("P15-006-adapter-extractor", adapter_program) if candidate_mode else [] ) actual_maps: dict[str, dict[str, int]] = {} for line in actual_lines: fields = line.split("\t") if len(fields) != 12 or fields[0] != "tuple": raise SystemExit(f"invalid tuple extractor line: {line!r}") values = [int(value) for value in fields[2:]] actual_maps[fields[1]] = dict(zip(SPEC["tuple_fields"], values, strict=True)) actual_devices: dict[str, dict[str, int]] = {} device_fields = ["m_pa", "m_plen", "m_deviceid", "selected_device", "errno"] for line in device_lines: fields = line.split("\t") if len(fields) != 7 or fields[0] != "device": raise SystemExit(f"invalid device extractor line: {line!r}") actual_devices[fields[1]] = dict( zip(device_fields, [int(value) for value in fields[2:]], strict=True) ) expected_maps = {record["id"]: record for record in MODEL_RECORDS} expected_devices = {record["id"]: record for record in MODEL_DEVICES} failures: list[dict[str, Any]] = [] if candidate_mode and adapter_lines != expected_adapter_lines: failures.append({ "field": "adapter-all-fields-and-flags", "expected": expected_adapter_lines, "actual": adapter_lines, }) if set(actual_maps) != set(expected_maps): failures.append({"field": "map-case-set", "actual": sorted(actual_maps), "expected": sorted(expected_maps)}) if set(actual_devices) != set(expected_devices): failures.append({"field": "device-case-set", "actual": sorted(actual_devices), "expected": sorted(expected_devices)}) tuple_format = SPEC["tuple_byte_layout"] records_output = [] for case in MAP_CASES: case_id = case["id"] actual = actual_maps.get(case_id, tuple_record(errno=-1)) expected = expected_maps[case_id]["expected"] for field in SPEC["tuple_fields"]: if actual[field] != expected[field]: failures.append({"id": case_id, "field": field, "expected": expected[field], "actual": actual[field]}) packed = struct.pack(tuple_format, *(actual[field] for field in SPEC["tuple_fields"])) if actual["acquire_count"] != actual["release_count"]: failures.append({"id": case_id, "field": "cleanup-balance"}) if actual["errno"] < 0: failures.append({"id": case_id, "field": "negative-errno"}) if actual["errno"] == 0 and case["request"] < case["inode"]["size"] and actual["m_llen"] == 0: failures.append({"id": case_id, "field": "H07-positive-run"}) records_output.append({ **expected_maps[case_id], "actual": actual, "tuple_hex": packed.hex(), }) devices_output = [] for case in DEVICE_CASES: case_id = case["id"] actual = actual_devices.get(case_id, {field: -1 for field in device_fields}) expected = expected_devices[case_id]["expected"] for field in device_fields: if actual[field] != expected[field]: failures.append({"id": case_id, "field": field, "expected": expected[field], "actual": actual[field]}) if actual["errno"] < 0: failures.append({"id": case_id, "field": "negative-errno"}) devices_output.append({"id": case_id, "expected": expected, "actual": actual}) coverage = sorted({case["category"] for case in MAP_CASES}) subcategories = sorted({value for case in MAP_CASES + DEVICE_CASES for value in case["subcategories"]}) if set(coverage) != set(SPEC["required_categories"]): failures.append({"field": "category-coverage", "actual": coverage}) if set(subcategories) != set(SPEC["required_subcategories"]): failures.append({"field": "subcategory-coverage", "actual": subcategories}) tuple_bytes = b"".join(bytes.fromhex(record["tuple_hex"]) for record in records_output) tuple_bytes_sha256 = sha256_bytes(tuple_bytes) oracle_equal: bool | None = None if ORACLE is not None: oracle_data = json.loads(ORACLE.read_text(encoding="ascii")) oracle_records = {record["id"]: record for record in oracle_data["records"]} oracle_equal = ( oracle_data.get("model_sha256") == MODEL_SHA256 and oracle_data.get("tuple_bytes_sha256") == tuple_bytes_sha256 and set(oracle_records) == set(actual_maps) and all( oracle_records[case_id]["tuple_hex"] == next( record["tuple_hex"] for record in records_output if record["id"] == case_id ) for case_id in actual_maps ) ) if not oracle_equal: failures.append({"field": "frozen-byte-oracle"}) tuple_output = { "schema": 1, "candidate": "P15-006", "mode": MODE, "adapter_mode": candidate_mode, "model_sha256": MODEL_SHA256, "tuple_byte_layout": tuple_format, "tuple_bytes_sha256": tuple_bytes_sha256, "coverage": coverage, "subcategories": subcategories, "records": records_output, "authoritative_sources": [ "independent arithmetic decoder over declared on-disk records", "compiled map and zmap function bodies extracted from the frozen/current FreeBSD source", "fixed-width tuple byte encoding replayed unchanged after B07a", ], } device_output = { "schema": 1, "status": "PASS" if not [failure for failure in failures if failure.get("id", "").startswith("device-")] else "FAIL", "records": devices_output, "protected_function_sha256": protected_hashes, } status = "GO" if not failures else "STOP" adapter_probe = { "status": ( "PASS" if candidate_mode and adapter_lines == expected_adapter_lines else "NOT_APPLICABLE" if not candidate_mode else "FAIL" ), "full_and_compact_dispatch": candidate_mode and adapter_lines == expected_adapter_lines, "flag_mask": SPEC["required_adapter_flag_mask"], "records": adapter_lines, } result = { "schema": 1, "gate": "G02", "candidate": "P15-006", "status": status, "mode": MODE, "requested_base": REQUESTED_BASE or None, "resolved_head": resolved, "adapter_mode": candidate_mode, "adapter_probe": adapter_probe, "map_case_count": len(MAP_CASES), "device_case_count": len(DEVICE_CASES), "coverage": coverage, "subcategories": subcategories, "model_sha256": MODEL_SHA256, "tuple_bytes_sha256": tuple_bytes_sha256, "oracle_equal": oracle_equal, "map_struct_sha256": map_struct_hash, "qemu": "NOT_RUN", "full_feature_suite": "NOT_RUN", "failures": failures, } (OUTPUT / "tuples.json").write_text(json.dumps(tuple_output, indent=2, sort_keys=True) + "\n", encoding="ascii") (OUTPUT / "devices.json").write_text(json.dumps(device_output, indent=2, sort_keys=True) + "\n", encoding="ascii") (OUTPUT / "source-sha256.json").write_text(json.dumps(source_hashes, indent=2, sort_keys=True) + "\n", encoding="ascii") (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_bytes(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