#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" gate=$PRE15_DUT/tests/pre15/gates/P15-005.sh tuple_oracle=$PRE15_DUT/tests/pre15/fixtures/B06-tuples.json ownership_oracle=$PRE15_DUT/tests/pre15/fixtures/B06-ownership.json artifacts=$PRE15_RUN_DIR/artifacts for tool in cc python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B06 host tool: $tool" done pre15_record_fixture b06-gate "$gate" pre15_record_fixture b06-tuple-oracle "$tuple_oracle" pre15_record_fixture b06-ownership-oracle "$ownership_oracle" for source in internal.h data.c decompressor.c inode.c super.c xattr.c zdata.c zmap.c; do pre15_record_fixture "b06-$source" "$PRE15_DUT/src/$source" done mkdir -p "$artifacts" pre15_target_reached if "$gate" --worktree --oracle "$tuple_oracle" \ --output "$artifacts/gate" >"$artifacts/gate.stdout" \ 2>"$artifacts/gate.stderr"; then : else pre15_dut_fail 'B06 G02 candidate replay failed' fi if python3 - "$PRE15_ROOT" "$ownership_oracle" "$artifacts/gate" \ "$artifacts/ownership-check.json" <<'PY' from __future__ import annotations import hashlib import json from pathlib import Path import re import sys root = Path(sys.argv[1]) expected = json.loads(Path(sys.argv[2]).read_text(encoding="ascii")) gate_dir = Path(sys.argv[3]) output = Path(sys.argv[4]) result = json.loads((gate_dir / "result.json").read_text(encoding="ascii")) ownership = json.loads((gate_dir / "ownership.json").read_text(encoding="ascii")) if result["status"] != "GO" or result["tuple_status"] != "PASS": raise SystemExit(f"candidate tuple result is not GO: {result!r}") if not result["object_mode"] or result["oracle_equal"] is not True: raise SystemExit(f"candidate did not replay the frozen object oracle: {result!r}") if ownership["status"] != "PASS" or ownership["failures"]: raise SystemExit(f"candidate ownership result failed: {ownership['failures']!r}") if ownership["consumers"] != expected["consumers"]: raise SystemExit("ownership consumer set changed") if ownership["direct_metadata_functions"] != expected["direct_metadata_functions"]: raise SystemExit("direct metadata consumer inventory changed") if ownership["helper_paths"]["paths"] != expected["helper_paths"]: raise SystemExit("actual helper lifecycle paths changed") metric_order = expected["metric_order"] actual_metrics = { item["function"]: [item[field] for field in metric_order] for item in ownership["candidate_contract"]["function_metrics"] } if actual_metrics != expected["function_metrics"]: raise SystemExit("per-function ownership metrics changed") def extract_function(source: str, name: str) -> str: match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE) if not match: raise SystemExit(f"backend function missing: {name}") start = source.rfind("\n\n", 0, match.start()) + 2 brace = source.find("{", match.end()) depth = 0 state = "code" index = brace while index < len(source): char = source[index] following = source[index + 1] if index + 1 < len(source) else "" if state == "code": if char == "/" and following == "*": state = "block" index += 2 continue if char == "/" and following == "/": state = "line" index += 2 continue if char == '"': state = "string" elif char == "'": state = "character" elif char == "{": depth += 1 elif char == "}": depth -= 1 if depth == 0: return source[start : index + 1] + "\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 backend function: {name}") backend_hashes = {} for key, frozen_hash in expected["freebsd_backend_function_sha256"].items(): filename, function = key.split(":", 1) source = (root / "repo-pre-15" / "src" / filename).read_text(encoding="utf-8") current_hash = hashlib.sha256( extract_function(source, function).encode("utf-8") ).hexdigest() backend_hashes[key] = current_hash if current_hash != frozen_hash: raise SystemExit(f"FreeBSD backend lifecycle changed: {key}") internal_path = root / "repo-pre-15" / "src" / "internal.h" internal = internal_path.read_text(encoding="utf-8") match = re.search( r"^struct erofs_map_blocks \{\n.*?^\};\n", internal, re.MULTILINE | re.DOTALL, ) if not match: raise SystemExit("missing legacy map tuple object") map_hash = hashlib.sha256(match.group(0).encode("utf-8")).hexdigest() if map_hash != expected["map_blocks_sha256"]: raise SystemExit("B07 map objectization leaked into B06") required = [ "struct erofs_buf {", "#define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL }", "void erofs_put_metabuf(struct erofs_buf *buf);", "int erofs_bread(struct erofs_sb_info *sbi, erofs_off_t off, size_t len, void **bufp);", "void erofs_brelse(void *buf);", ] for marker in required: if marker not in internal: raise SystemExit(f"required B06/raw API marker missing: {marker}") if re.search( r"erofs_read_metadata\s*\([^;]*void\s*\*\*bufp\s*\)\s*;", internal, re.MULTILINE | re.DOTALL, ): raise SystemExit("legacy metadata void-pointer API remains") for filename in ("dir.c", "namei.c"): text = (root / "repo-pre-15" / "src" / filename).read_text(encoding="utf-8") if "struct erofs_buf" in text or "erofs_put_metabuf" in text: raise SystemExit(f"raw directory consumer was objectized: {filename}") summary = { "status": "PASS", "tuple_cases": result["case_count"], "tuple_oracle_equal": result["oracle_equal"], "ownership_paths": result["ownership_path_count"], "helper_paths": len(expected["helper_paths"]), "function_metrics": len(actual_metrics), "freebsd_backend_functions": len(backend_hashes), "map_blocks_sha256": map_hash, "qemu": result["qemu"], "full_feature_suite": result["full_feature_suite"], } with output.open("x", encoding="ascii") as stream: json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True) stream.write("\n") print("B06 ownership PASS tuples=14 paths=22 helper=5 functions=28") PY then : else pre15_dut_fail 'B06 ownership fixture comparison failed' fi sha256sum "$artifacts/gate/result.json" "$artifacts/gate/tuples.json" \ "$artifacts/gate/ownership.json" "$artifacts/ownership-check.json" \ >"$artifacts/SHA256SUMS" printf 'B06 PASS explicit metadata ownership and frozen tuple replay\n'