1713 lines
67 KiB
Bash
Executable File
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
|