Files
erofs-freebsd-out-tree/tests/pre15/gates/P15-006.sh
T
2026-08-18 09:20:44 +02:00

1713 lines
67 KiB
Bash
Executable File

#!/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("<I", block & UINT32_MAX)
def index_entry(block: int, device: int, use_48bit: bool) -> bytes:
high = (block >> 32) & 0xFFFF if use_48bit else 0
return struct.pack("<HHI", high, device & 0xFFFF, block & UINT32_MAX)
def make_data_cases() -> 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("<HHI", entry)
block = low | ((high << 32) if inode["chunkformat"] & 0x40 else 0)
address_mask = (1 << 48) - 1 if inode["chunkformat"] & 0x40 else UINT32_MAX
else:
block = struct.unpack("<I", entry)[0]
raw_device = 0
address_mask = UINT32_MAX
run_len = min(chunk_size - chunk_offset, inode["size"] - request)
result["m_llen"] = result["m_plen"] = run_len
if not ((block ^ NULL_ADDR) & address_mask):
return result
result["m_deviceid"] = raw_device & sbi["device_id_mask"]
if block > (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("<Q", case["base_pa"]))
for extent in case["records"]:
packed = struct.pack(
"<IIIII12s",
extent["plen"] & UINT32_MAX,
extent["pstart"] & UINT32_MAX,
(extent["pstart"] >> 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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