338 lines
12 KiB
Bash
Executable File
338 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
: "${PRE15_DUT:?PRE15_DUT is required}"
|
|
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
|
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
|
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
|
. "$PRE15_LIB_DIR/runner.sh"
|
|
|
|
baseline=fb79d17edba7c47fae6e60b2a92771a8f3a2fcdf
|
|
zmap=$PRE15_DUT/src/zmap.c
|
|
internal=$PRE15_DUT/src/internal.h
|
|
linux_zmap=$PRE15_ROOT/src-linux/zmap.c
|
|
oracle=$PRE15_DUT/tests/pre15/fixtures/B07-map-oracle.json
|
|
gate_input=$PRE15_DUT/tests/pre15/gates/P15-006-input.json
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
|
|
for tool in git python3 sha256sum; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B24 host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b24-zmap "$zmap"
|
|
pre15_record_fixture b24-internal "$internal"
|
|
pre15_record_fixture b24-linux-zmap "$linux_zmap"
|
|
pre15_record_fixture b24-map-oracle "$oracle"
|
|
pre15_record_fixture b24-gate-input "$gate_input"
|
|
pre15_record_fixture b24-case \
|
|
"$PRE15_DUT/tests/pre15/cases/B24-zmap-order.sh"
|
|
|
|
mkdir -p "$artifacts"
|
|
pre15_target_reached
|
|
|
|
if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" "$oracle" \
|
|
"$gate_input" "$artifacts/B24-zmap-order.json" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
root = Path(sys.argv[1])
|
|
dut = Path(sys.argv[2])
|
|
baseline = sys.argv[3]
|
|
oracle_path = Path(sys.argv[4])
|
|
gate_input_path = Path(sys.argv[5])
|
|
report_path = Path(sys.argv[6])
|
|
|
|
|
|
def committed(path: str) -> str:
|
|
completed = subprocess.run(
|
|
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/{path}"],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
if completed.returncode != 0:
|
|
raise SystemExit(f"cannot read B24 baseline {path}: {completed.stderr}")
|
|
return completed.stdout
|
|
|
|
|
|
def function(source: str, name: str) -> str:
|
|
match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
|
|
if match is None:
|
|
raise SystemExit(f"missing function: {name}")
|
|
name_line = source.rfind("\n", 0, match.start()) + 1
|
|
start = source.rfind("\n", 0, name_line - 1) + 1
|
|
brace = source.find("{", match.end())
|
|
if brace < 0:
|
|
raise SystemExit(f"missing function body: {name}")
|
|
depth = 0
|
|
state = "code"
|
|
index = brace
|
|
while index < len(source):
|
|
char = source[index]
|
|
following = source[index + 1] if index + 1 < len(source) else ""
|
|
if state == "code":
|
|
if char == "/" and following == "*":
|
|
state = "block"
|
|
index += 2
|
|
continue
|
|
if char == "/" and following == "/":
|
|
state = "line"
|
|
index += 2
|
|
continue
|
|
if char == '"':
|
|
state = "string"
|
|
elif char == "'":
|
|
state = "character"
|
|
elif char == "{":
|
|
depth += 1
|
|
elif char == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[start : index + 1]
|
|
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}")
|
|
|
|
|
|
function_order = [
|
|
"z_erofs_index_base",
|
|
"z_erofs_index_advance",
|
|
"z_erofs_lcluster_count",
|
|
"z_erofs_lcluster_pos",
|
|
"z_erofs_lcn_advance",
|
|
"z_erofs_compact_pblk",
|
|
"z_erofs_fragment_offset",
|
|
"z_erofs_post_eof_len",
|
|
"z_erofs_physical_end",
|
|
"z_erofs_read_index",
|
|
"z_erofs_load_full_lcluster",
|
|
"decode_compactedbits",
|
|
"get_compacted_la_distance",
|
|
"z_erofs_load_compact_lcluster",
|
|
"z_erofs_load_lcluster_from_disk",
|
|
"z_erofs_extent_lookback",
|
|
"z_erofs_get_extent_compressedlen",
|
|
"z_erofs_get_extent_decompressedlen",
|
|
"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_validate_extent_table",
|
|
"z_erofs_map_blocks_fo",
|
|
"z_erofs_map_blocks_ext",
|
|
"z_erofs_fill_inode",
|
|
"z_erofs_map_sanity_check",
|
|
"z_erofs_map_blocks",
|
|
]
|
|
linux_algorithm_order = [
|
|
"z_erofs_load_full_lcluster",
|
|
"decode_compactedbits",
|
|
"get_compacted_la_distance",
|
|
"z_erofs_load_compact_lcluster",
|
|
"z_erofs_load_lcluster_from_disk",
|
|
"z_erofs_extent_lookback",
|
|
"z_erofs_get_extent_compressedlen",
|
|
"z_erofs_get_extent_decompressedlen",
|
|
"z_erofs_map_blocks_fo",
|
|
"z_erofs_map_blocks_ext",
|
|
"z_erofs_fill_inode",
|
|
"z_erofs_map_sanity_check",
|
|
]
|
|
|
|
current = (dut / "src/zmap.c").read_text(encoding="utf-8")
|
|
before = committed("src/zmap.c")
|
|
current_internal = (dut / "src/internal.h").read_text(encoding="utf-8")
|
|
before_internal = committed("src/internal.h")
|
|
linux = (root / "src-linux/zmap.c").read_text(encoding="utf-8")
|
|
|
|
moved_start = before.index("static int\nz_erofs_extent_add(")
|
|
moved_end = before.index("static int\nz_erofs_map_blocks_ext(", moved_start)
|
|
moved = before[moved_start:moved_end]
|
|
without_moved = before[:moved_start] + before[moved_end:]
|
|
insert_at = without_moved.index("static int\nz_erofs_map_blocks_fo(")
|
|
expected = without_moved[:insert_at] + moved + without_moved[insert_at:]
|
|
if current != expected:
|
|
raise SystemExit("zmap.c differs from the exact B24 validator-group move")
|
|
|
|
before_functions = {name: function(before, name) for name in function_order}
|
|
current_functions = {name: function(current, name) for name in function_order}
|
|
for name in function_order:
|
|
if current_functions[name] != before_functions[name]:
|
|
raise SystemExit(f"B24 changed function text instead of moving it: {name}")
|
|
|
|
def definition_position(source: str, name: str) -> int:
|
|
match = re.search(
|
|
r"^(?:" + re.escape(name) + r"|(?:static\s+)?"
|
|
r"[A-Za-z_][A-Za-z0-9_ *]*\b" + re.escape(name) + r")\s*\(",
|
|
source,
|
|
re.MULTILINE,
|
|
)
|
|
if match is None:
|
|
raise SystemExit(f"missing definition position: {name}")
|
|
return match.start()
|
|
|
|
|
|
positions = [definition_position(current, name) for name in function_order]
|
|
if positions != sorted(positions):
|
|
raise SystemExit("B24 zmap definition order does not match the reviewed order")
|
|
for source, label in ((current, "FreeBSD"), (linux, "Linux")):
|
|
positions = [definition_position(source, name) for name in linux_algorithm_order]
|
|
if positions != sorted(positions):
|
|
raise SystemExit(f"{label} common zmap algorithm order drifted")
|
|
|
|
def direct_calls(functions: dict[str, str]) -> Counter[tuple[str, str]]:
|
|
calls: Counter[tuple[str, str]] = Counter()
|
|
for caller, body in functions.items():
|
|
brace = body.find("{")
|
|
for callee in re.findall(r"\b([A-Za-z_][A-Za-z0-9_]*)\s*\(", body[brace + 1 :]):
|
|
if callee not in {"if", "for", "while", "switch", "return", "sizeof"}:
|
|
calls[(caller, callee)] += 1
|
|
return calls
|
|
|
|
|
|
before_calls = direct_calls(before_functions)
|
|
current_calls = direct_calls(current_functions)
|
|
if current_calls != before_calls:
|
|
raise SystemExit("B24 changed the direct-call multiset")
|
|
|
|
global_definitions = sorted(
|
|
name for name, body in current_functions.items() if not body.startswith("static ")
|
|
)
|
|
if global_definitions != ["z_erofs_fill_inode", "z_erofs_map_blocks"]:
|
|
raise SystemExit(f"unexpected zmap global definitions: {global_definitions}")
|
|
if current_internal != before_internal:
|
|
raise SystemExit("B24 changed internal.h despite no removable zmap global")
|
|
inode_source = (dut / "src/inode.c").read_text(encoding="utf-8")
|
|
data_source = (dut / "src/data.c").read_text(encoding="utf-8")
|
|
if inode_source.count("z_erofs_fill_inode(sbi, vi)") != 1:
|
|
raise SystemExit("eager z_erofs_fill_inode no longer has its inode consumer")
|
|
if data_source.count("z_erofs_map_blocks(sbi, vi, &next)") != 1:
|
|
raise SystemExit("compressed map backend no longer has its common adapter consumer")
|
|
for prototype in (
|
|
"int z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi);",
|
|
"int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n"
|
|
" struct erofs_map_blocks *map);",
|
|
):
|
|
if current_internal.count(prototype) != 1:
|
|
raise SystemExit(f"required FreeBSD zmap prototype drifted: {prototype!r}")
|
|
if "int z_erofs_map_blocks_iter(struct inode *inode, struct erofs_map_blocks *map," not in linux:
|
|
raise SystemExit("Linux zmap iterator naming anchor changed")
|
|
|
|
for marker in ("erofs_read_metadata(", "erofs_put_metabuf(", "goto out;"):
|
|
if current.count(marker) != before.count(marker):
|
|
raise SystemExit(f"B24 changed provider I/O or cleanup marker count: {marker}")
|
|
if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", current):
|
|
raise SystemExit("B24 introduced Linux negative errno")
|
|
|
|
oracle = json.loads(oracle_path.read_text(encoding="ascii"))
|
|
gate_input = json.loads(gate_input_path.read_text(encoding="ascii"))
|
|
records = oracle.get("records", [])
|
|
if oracle.get("map_case_count") != 80 or len(records) != 80:
|
|
raise SystemExit("frozen G02/B07 map tuple denominator changed")
|
|
if gate_input.get("expected_device_case_count") != 13:
|
|
raise SystemExit("frozen G02/B07 device denominator changed")
|
|
tuple_bytes = b"".join(bytes.fromhex(record["tuple_hex"]) for record in records)
|
|
tuple_digest = hashlib.sha256(tuple_bytes).hexdigest()
|
|
if tuple_digest != oracle.get("tuple_bytes_sha256"):
|
|
raise SystemExit("frozen G02/B07 tuple bytes no longer match their digest")
|
|
if len({record["id"] for record in records}) != 80:
|
|
raise SystemExit("frozen G02/B07 tuple IDs are not unique")
|
|
|
|
changed = set(
|
|
subprocess.run(
|
|
["git", "-C", str(root), "diff", "--name-only", baseline, "--", "repo-pre-15"],
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
).stdout.splitlines()
|
|
)
|
|
changed.update(
|
|
subprocess.run(
|
|
[
|
|
"git", "-C", str(root), "ls-files", "--others", "--exclude-standard",
|
|
"--", "repo-pre-15",
|
|
],
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
).stdout.splitlines()
|
|
)
|
|
allowed = {
|
|
"repo-pre-15/src/zmap.c",
|
|
"repo-pre-15/tests/pre15/cases/B24-zmap-order.sh",
|
|
}
|
|
if changed != allowed:
|
|
raise SystemExit(f"B24 write set differs: {sorted(changed ^ allowed)}")
|
|
|
|
function_digest = hashlib.sha256(
|
|
"\n\n".join(current_functions[name] for name in function_order).encode("utf-8")
|
|
).hexdigest()
|
|
report = {
|
|
"baseline": baseline,
|
|
"batch": "B24",
|
|
"candidate": "P15-016",
|
|
"direct_call_edges": sum(current_calls.values()),
|
|
"freebsd_backend_name": "z_erofs_map_blocks",
|
|
"freebsd_positive_errno": True,
|
|
"function_count": len(function_order),
|
|
"function_text_sha256": function_digest,
|
|
"global_definitions": global_definitions,
|
|
"linux_iterator_name": "z_erofs_map_blocks_iter",
|
|
"map_cases": len(records),
|
|
"device_cases": gate_input["expected_device_case_count"],
|
|
"provider_io_unchanged": True,
|
|
"metadata_cleanup_unchanged": True,
|
|
"status": "PASS",
|
|
"tuple_bytes_sha256": tuple_digest,
|
|
"write_set": sorted(changed),
|
|
}
|
|
report_path.write_text(
|
|
json.dumps(report, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
|
|
encoding="ascii",
|
|
)
|
|
print(
|
|
"B24 order/callgraph PASS "
|
|
f"functions={len(function_order)} calls={sum(current_calls.values())} globals=2"
|
|
)
|
|
print(
|
|
"B24 map tuples PASS "
|
|
f"maps={len(records)} devices={gate_input['expected_device_case_count']} "
|
|
f"sha256={tuple_digest}"
|
|
)
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B24 order, callgraph, visibility, or tuple proof failed'
|
|
fi
|
|
|
|
sha256sum "$artifacts/B24-zmap-order.json" >"$artifacts/SHA256SUMS"
|
|
printf '%s\n' \
|
|
'B24 host PASS exact validator move and Linux algorithm order' \
|
|
'B24 host PASS frozen G02 map tuples; QEMU and full feature suite NOT_RUN'
|