#!/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-006.sh input=$PRE15_DUT/tests/pre15/gates/P15-006-input.json oracle=$PRE15_DUT/tests/pre15/fixtures/B07-map-oracle.json artifacts=$PRE15_RUN_DIR/artifacts baseline=6673f51152a5195a8a8903aa801f820abce7936e b07b=d58f131cfe855728969aa3695d34a26a61b90277 for tool in cc git python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B07c host tool: $tool" done pre15_record_fixture b07c-gate "$gate" pre15_record_fixture b07c-input "$input" pre15_record_fixture b07c-oracle "$oracle" for source in internal.h data.c zmap.c zdata.c super.c; do pre15_record_fixture "b07c-$source" "$PRE15_DUT/src/$source" done mkdir -p "$artifacts" pre15_target_reached if "$gate" --base "$baseline" --oracle "$oracle" \ --output "$artifacts/baseline" >"$artifacts/baseline.stdout" \ 2>"$artifacts/baseline.stderr"; then : else pre15_dut_fail 'B07c frozen P15-006 baseline replay failed' fi if python3 - "$gate" "$artifacts/P15-006-B07c.py" <<'PY' from pathlib import Path import sys source = Path(sys.argv[1]).read_text(encoding="utf-8") marker = "<<'PY'\n" start = source.index(marker) + len(marker) end = source.rindex("\nPY\n") program = source[start:end] def replace_once(old: str, new: str) -> None: global program if program.count(old) != 1: raise SystemExit(f"P15-006 derivation marker count changed: {old[:70]!r}") program = program.replace(old, new) replace_once( '''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 candidate_mode and re.search( r"^erofs_map_blocks_legacy\\s*\\(", data_source, re.MULTILINE ): raise SystemExit("B07c candidate still has the legacy scatter adapter") ''', ) replace_once( '''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}") ''', '''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_function = ( "z_erofs_map_blocks" if key == "zmap.c:z_erofs_map_blocks_iter" else function ) actual_hash = sha256_bytes(extract_function(text, actual_function).encode("utf-8")) protected_hashes[key] = actual_hash allowed = { "data.c:erofs_map_blocks_chunk", "zmap.c:z_erofs_map_blocks_iter", } if actual_hash != expected_hash and key not in allowed: raise SystemExit(f"protected producer/GEOM body changed: {key}") ''', ) replace_once( '''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") ''', '''if candidate_mode: legacy_body = "" expected_legacy_hash = None else: legacy_body = extract_function(data_source, "erofs_map_blocks") expected_legacy_hash = SPEC["baseline_legacy_map_sha256"] if expected_legacy_hash is not None and sha256_bytes(legacy_body.encode("utf-8")) != expected_legacy_hash: raise SystemExit("plain/chunk legacy producer body changed") ''', ) replace_once( ' "z_erofs_map_blocks_iter",\n', ' "z_erofs_map_blocks",\n', ) replace_once( 'program += "int z_erofs_map_blocks_iter(struct erofs_sb_info *, struct erofs_inode *, struct erofs_map_blocks *, int);\\n"\n', 'program += "int z_erofs_map_blocks(struct erofs_sb_info *, struct erofs_inode *, struct erofs_map_blocks *);\\n"\n', ) replace_once( '''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") ''', '''program += extract_function(data_source, "erofs_inline_tail_start") program += extract_function(data_source, "erofs_map_blocks_chunk") if candidate_mode: program += extract_function(data_source, "erofs_map_blocks_flatmode") program += extract_function(data_source, "erofs_map_blocks") else: program += legacy_body ''', ) replace_once( '''int z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) ''', '''int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) ''', ) replace_once( ''' (void)sbi; if (flags != 0) return (EINVAL); ++gate_adapter_calls; ''', ''' (void)sbi; ++gate_adapter_calls; ''', ) replace_once( ''' 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 += extract_function(data_source, "erofs_inline_tail_start") adapter_program += extract_function(data_source, "erofs_map_blocks_chunk") adapter_program += extract_function(data_source, "erofs_map_blocks_flatmode") adapter_program += extract_function(data_source, "erofs_map_blocks") ''', ) Path(sys.argv[2]).write_text(program + "\n", encoding="ascii") PY then : else pre15_dut_fail 'B07c could not derive the current-consumer replay' fi if python3 "$artifacts/P15-006-B07c.py" "$PRE15_ROOT" "$input" \ worktree '' "$artifacts/candidate" "$oracle" \ >"$artifacts/candidate.stdout" 2>"$artifacts/candidate.stderr"; then : else pre15_dut_fail 'B07c common consumer replay failed' fi if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$oracle" \ "$artifacts/baseline" "$artifacts/candidate" "$artifacts/consumer-check.json" \ "$b07b" <<'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]) oracle = json.loads(Path(sys.argv[3]).read_text(encoding="ascii")) baseline = Path(sys.argv[4]) candidate = Path(sys.argv[5]) output = Path(sys.argv[6]) b07b = sys.argv[7] def load(directory: Path, name: str) -> dict: return json.loads((directory / name).read_text(encoding="ascii")) 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"missing function: {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] 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 function_names(source: str) -> set[str]: return set(re.findall( r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(", source, re.MULTILINE )) def committed_source(path: str) -> str: completed = subprocess.run( ["git", "-C", str(root), "show", f"{b07b}:repo-pre-15/src/{path}"], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if completed.returncode != 0: raise SystemExit(f"cannot read B07b source {path}: {completed.stderr}") return completed.stdout def replace_once(source: str, old: str, new: str, label: str) -> str: if source.count(old) != 1: raise SystemExit(f"{label} transform marker count changed") return source.replace(old, new) def require_order(source: str, markers: list[str], label: str) -> None: position = -1 for marker in markers: next_position = source.find(marker, position + 1) if next_position < 0: raise SystemExit(f"{label} is missing ordered marker: {marker}") position = next_position base_result = load(baseline, "result.json") base_tuples = load(baseline, "tuples.json") base_devices = load(baseline, "devices.json") result = load(candidate, "result.json") tuples = load(candidate, "tuples.json") devices = load(candidate, "devices.json") for label, record in (("baseline", base_result), ("candidate", result)): if record["status"] != "GO" or record["failures"]: raise SystemExit(f"{label} P15-006 replay did not GO: {record!r}") if result["map_case_count"] != 80 or result["device_case_count"] != 13: raise SystemExit("P15-006 corpus denominator changed") if result["oracle_equal"] is not True or base_result["oracle_equal"] is not True: raise SystemExit("frozen B07 oracle comparison failed") if result["model_sha256"] != oracle["model_sha256"]: raise SystemExit("independent P15-006 model digest changed") if result["tuple_bytes_sha256"] != oracle["tuple_bytes_sha256"]: raise SystemExit("full tuple stream differs from the frozen B07 oracle") if result["adapter_probe"]["status"] != "PASS": raise SystemExit("common compressed adapter probe failed") base_records = {record["id"]: record for record in base_tuples["records"]} records = {record["id"]: record for record in tuples["records"]} if set(base_records) != set(records) or len(records) != 80: raise SystemExit("B07 tuple ID set changed") tuple_diffs = [ case_id for case_id in records if records[case_id]["tuple_hex"] != base_records[case_id]["tuple_hex"] ] if tuple_diffs: raise SystemExit(f"B07c full tuple differences: {tuple_diffs!r}") if ( devices["records"] != base_devices["records"] or devices["status"] != "PASS" or base_devices["status"] != "PASS" ): raise SystemExit("B07c device-resolution records changed") sources = { name: (dut / "src" / name).read_text(encoding="utf-8") for name in ("internal.h", "data.c", "super.c", "zdata.c", "zmap.c") } old = {name: committed_source(name) for name in sources} joined = "\n".join(sources.values()) if re.search(r"\berofs_map_blocks_legacy\b|\bz_erofs_map_blocks_iter\b", joined): raise SystemExit("legacy map interface remains") common = extract_function(sources["data.c"], "erofs_map_blocks") read_data = extract_function(sources["data.c"], "erofs_read_data") read_uio = extract_function(sources["data.c"], "erofs_read_uio") zread = extract_function(sources["zdata.c"], "z_erofs_do_read") metabox = extract_function(sources["super.c"], "erofs_init_metabox_inode") backend = extract_function(sources["zmap.c"], "z_erofs_map_blocks") if "z_erofs_map_blocks(sbi, vi, &next)" not in common: raise SystemExit("common entry does not own compressed dispatch") if backend.count("EROFS_GET_BLOCKS_FIEMAP") != 2: raise SystemExit("compressed backend did not preserve FIEMAP semantics") if sources["data.c"].count("erofs_map_blocks(sbi, vi, &map)") != 2: raise SystemExit("data consumers did not both migrate") if zread.count("erofs_map_blocks(sbi, vi, &map)") != 1: raise SystemExit("z_erofs_do_read did not migrate") if metabox.count("erofs_map_blocks(sbi, sbi->metabox_en, &map)") != 1: raise SystemExit("metabox validation did not migrate") require_order( read_data, [ "map = (struct erofs_map_blocks) { .m_la = loff + done };", "error = erofs_map_blocks(sbi, vi, &map);", "if (error != 0)", "if (map.m_llen == 0)", "want = MIN((size_t)map.m_llen, len - done);", "(map.m_flags & EROFS_MAP_MAPPED) == 0", "(map.m_flags & EROFS_MAP_META) != 0", "erofs_read_metadata(sbi, vi->nid, map.m_pa", "erofs_read_physical(sbi, map.m_deviceid, map.m_pa", "erofs_put_metabuf(&buf);", "erofs_brelse(blk);", ], "erofs_read_data", ) require_order( read_uio, [ "map = (struct erofs_map_blocks) { .m_la = uio->uio_offset };", "error = erofs_map_blocks(sbi, vi, &map);", "if (error != 0)", "if (map.m_llen == 0)", "want = MIN((size_t)map.m_llen, (size_t)uio->uio_resid);", "(map.m_flags & EROFS_MAP_MAPPED) == 0", "error = uiomove(zerobuf, zlen, uio);", "(map.m_flags & EROFS_MAP_META) != 0", "erofs_read_metadata(sbi, vi->nid, map.m_pa, want", "erofs_read_physical(sbi, map.m_deviceid, map.m_pa", "error = uiomove(buf.data, want, uio);", "erofs_put_metabuf(&buf);", "error = uiomove(blk, want, uio);", "erofs_brelse(blk);", ], "erofs_read_uio", ) expected_internal = replace_once( old["internal.h"], "int z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n" " struct erofs_map_blocks *map, int flags);", "int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n" " struct erofs_map_blocks *map);", "internal.h", ) if sources["internal.h"] != expected_internal: raise SystemExit("B07c internal.h contains changes beyond backend closure") expected_zdata = replace_once( old["zdata.c"], "\t\tbzero(&map, sizeof(map));\n" "\t\tmap.m_la = loff + done;\n" "\t\terror = z_erofs_map_blocks_iter(sbi, vi, &map,\n" "\t\t EROFS_GET_BLOCKS_FIEMAP);", "\t\tmap = (struct erofs_map_blocks) { .m_la = loff + done };\n" "\t\terror = erofs_map_blocks(sbi, vi, &map);", "zdata.c", ) if sources["zdata.c"] != expected_zdata: raise SystemExit("B07c zdata.c contains changes beyond consumer migration") expected_super = replace_once( old["super.c"], "\t\t\tbzero(&map, sizeof(map));\n" "\t\t\tmap.m_la = sbi->metabox_en->size - 1;\n" "\t\t\terror = z_erofs_map_blocks_iter(sbi, sbi->metabox_en, &map,\n" "\t\t\t EROFS_GET_BLOCKS_FIEMAP);", "\t\t\tmap = (struct erofs_map_blocks) {\n" "\t\t\t\t.m_la = sbi->metabox_en->size - 1,\n" "\t\t\t};\n" "\t\t\terror = erofs_map_blocks(sbi, sbi->metabox_en, &map);", "super.c", ) if sources["super.c"] != expected_super: raise SystemExit("B07c super.c contains changes beyond metabox migration") old_backend = extract_function(old["zmap.c"], "z_erofs_map_blocks_iter") expected_backend = replace_once( old_backend, "z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n" " struct erofs_map_blocks *map, int flags)", "z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n" " struct erofs_map_blocks *map)", "zmap signature", ) expected_backend = expected_backend.replace( "z_erofs_map_blocks_ext(sbi, vi, map, flags)", "z_erofs_map_blocks_ext(sbi, vi, map,\n\t\t\t EROFS_GET_BLOCKS_FIEMAP)", ) expected_backend = expected_backend.replace( "z_erofs_map_blocks_fo(sbi, vi, map, flags)", "z_erofs_map_blocks_fo(sbi, vi, map,\n\t\t\t EROFS_GET_BLOCKS_FIEMAP)", ) if backend != expected_backend: raise SystemExit("compressed backend changed beyond FIEMAP interface closure") expected_zmap = old["zmap.c"].replace(old_backend, expected_backend) if sources["zmap.c"] != expected_zmap: raise SystemExit("B07c zmap.c contains changes outside the backend wrapper") old_data_functions = function_names(old["data.c"]) data_functions = function_names(sources["data.c"]) if old_data_functions - data_functions != {"erofs_map_blocks_legacy"}: raise SystemExit("B07c data.c removed functions beyond the legacy adapter") if data_functions - old_data_functions: raise SystemExit("B07c data.c added an unexpected function") changed_data_functions = { "erofs_map_blocks", "erofs_read_data", "erofs_read_uio", } for function in sorted(data_functions - changed_data_functions): if extract_function(old["data.c"], function) != extract_function( sources["data.c"], function ): raise SystemExit(f"B07c changed protected data.c function: {function}") cleanup = Counter( (record["actual"]["acquire_count"], record["actual"]["release_count"]) for record in tuples["records"] ) if any(acquire != release for acquire, release in cleanup): raise SystemExit(f"metadata cleanup became unbalanced: {cleanup!r}") if any(record["actual"]["errno"] < 0 for record in tuples["records"]): raise SystemExit("negative errno observed") tuple_bytes = b"".join( bytes.fromhex(record["tuple_hex"]) for record in tuples["records"] ) summary = { "status": "PASS", "final_id": "P15-006", "map_cases": 80, "producer_cases": 50, "compressed_cases": 30, "device_cases": 13, "full_tuple_diff_count": len(tuple_diffs), "tuple_bytes": len(tuple_bytes), "tuple_bytes_sha256": hashlib.sha256(tuple_bytes).hexdigest(), "model_sha256": result["model_sha256"], "legacy_callsite_count": 0, "data_consumers": 2, "compressed_consumers": 2, "backend_fiemap_dispatches": 2, "cleanup_distribution": { f"{acquire}:{release}": count for (acquire, release), count in sorted(cleanup.items()) }, "qemu": "NOT_RUN", "full_feature_suite": "NOT_RUN", } with output.open("x", encoding="ascii") as stream: json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True) stream.write("\n") print("B07c PASS full=80 diff=0 device=13 legacy=0 consumers=4") PY then : else pre15_dut_fail 'B07c consumer boundary check failed' fi sha256sum "$artifacts/baseline/result.json" \ "$artifacts/baseline/tuples.json" "$artifacts/baseline/devices.json" \ "$artifacts/candidate/result.json" "$artifacts/candidate/tuples.json" \ "$artifacts/candidate/devices.json" "$artifacts/consumer-check.json" \ >"$artifacts/SHA256SUMS" printf 'B07c PASS common consumers; legacy map interfaces absent\n'