#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" baseline=8a8761af91654a025fd991d14da906c0b612bd7d fixture=$PRE15_DUT/tests/pre15/fixtures/B22-zmap-arithmetic.json kld_builder=$PRE15_DUT/tests/pre15/fixtures/B22-build-kld.sh zmap=$PRE15_DUT/src/zmap.c linux_zmap=$PRE15_ROOT/src-linux/zmap.c artifacts=$PRE15_RUN_DIR/artifacts for tool in cc git python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B22 host tool: $tool" done pre15_record_fixture b22-arithmetic "$fixture" pre15_record_fixture b22-kld-builder "$kld_builder" pre15_record_fixture b22-case "$PRE15_DUT/tests/pre15/cases/B22-zmap-arithmetic.sh" pre15_record_fixture b22-zmap "$zmap" pre15_record_fixture b22-linux-zmap "$linux_zmap" mkdir -p "$artifacts" pre15_target_reached if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" "$fixture" \ "$artifacts" <<'PY' from __future__ import annotations 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] fixture_path = Path(sys.argv[4]) artifacts = Path(sys.argv[5]) src = dut / "src" u32_max = (1 << 32) - 1 u64_max = (1 << 64) - 1 eintegrity = 97 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 B22 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}") def add(left: int, right: int) -> tuple[str, int | None]: value = left + right if value > u64_max: return ("EINTEGRITY", None) return ("PASS", value) def multiply(left: int, right: int) -> tuple[str, int | None]: value = left * right if value > u64_max: return ("EINTEGRITY", None) return ("PASS", value) def model(case: dict[str, object]) -> tuple[str, int | None]: operation = case["operation"] if operation == "compact_pblk": return add(int(case["base"]), int(case["nblk"])) if operation == "index_base": status, end = add(int(case["inode_off"]), int(case["inode_isize"])) if status != "PASS": return (status, None) status, end = add(int(end), int(case.get("xattr_isize", 0))) if status != "PASS": return (status, None) status, end = add(int(end), 7) if status != "PASS": return (status, None) end = int(end) & ~7 return add(end, 8) if operation == "index_advance": status, delta = multiply(int(case["count"]), int(case["unit"])) if status != "PASS": return (status, None) return add(int(case["position"]), int(delta)) if operation == "lcluster_count": bits = int(case["lclusterbits"]) if bits >= 64: return ("EINTEGRITY", None) size = int(case["size"]) count = size >> bits if size & ((1 << bits) - 1): return add(count, 1) return ("PASS", count) if operation == "lcluster_pos": bits = int(case["lclusterbits"]) if bits >= 64: return ("EINTEGRITY", None) status, base = multiply(int(case["lcn"]), 1 << bits) if status != "PASS": return (status, None) return add(int(base), int(case["clusterofs"])) if operation == "lcn_advance": return add(int(case["lcn"]), int(case["delta"])) if operation == "physical_end": status, pend = add(int(case["pa"]), int(case["plen"])) if status != "PASS": return (status, None) limit = (1 << 48) * int(case["block_size"]) if limit <= u64_max and int(pend) > limit: return ("EINTEGRITY", None) return ("PASS", None) if operation == "post_eof": la = int(case["la"]) size = int(case["size"]) if la < size: return ("EINTEGRITY", None) length = la - size + 1 return ("PASS", min(length, u64_max)) if operation == "fragment_offset": low = int(case["low"]) high = int(case["high"]) if low > u32_max or high > u32_max: return ("EINTEGRITY", None) return ("PASS", low | (high << 32)) raise SystemExit(f"unknown B22 operation: {operation}") fixture = json.loads(fixture_path.read_text(encoding="ascii")) if fixture.get("schema") != 1 or fixture.get("batch") != "B22": raise SystemExit("invalid B22 fixture identity") if fixture.get("test") != "TC170-zmap-arithmetic": raise SystemExit("invalid B22 test identity") if fixture.get("candidates") != [ "P15-047", "P15-059", "P15-071", "P15-079", "P15-084", ]: raise SystemExit("B22 candidate set changed") if fixture.get("errno") != { "corruption": eintegrity, "provider_io": "unchanged", "sign": "positive", }: raise SystemExit("B22 errno contract changed") cases = fixture.get("cases") if not isinstance(cases, list) or not cases: raise SystemExit("B22 fixture has no cases") names: set[str] = set() markers: set[str] = set() operations: set[str] = set() decoded: list[dict[str, object]] = [] for case in cases: if not isinstance(case, dict): raise SystemExit("B22 fixture case is not an object") name = str(case.get("name", "")) marker = str(case.get("target_marker", "")) if not name or name in names or not marker.startswith("TC170:"): raise SystemExit(f"invalid B22 case identity: {name!r}") names.add(name) markers.add(marker) operations.add(str(case.get("operation", ""))) status, value = model(case) if status != case.get("status"): raise SystemExit(f"B22 independent status mismatch: {name}") if status == "PASS" and value is not None and value != case.get("expected"): raise SystemExit(f"B22 independent value mismatch: {name}") mutated = case.get("mutated_fields") if status == "EINTEGRITY" and ( not isinstance(mutated, list) or len(mutated) != 1 ): raise SystemExit(f"B22 negative is not single-field: {name}") if status == "PASS" and mutated != []: raise SystemExit(f"B22 positive declares a mutation: {name}") decoded.append({"name": name, "status": status, "value": value}) required_markers = { "TC170:compact-pblk-32bit-crossing", "TC170:index-position", "TC170:delta-lcn", "TC170:physical-end-48bit", "TC170:post-eof", "TC170:fragment-high-bits", } if markers != required_markers: raise SystemExit("B22 target-marker set is incomplete") if operations != { "compact_pblk", "fragment_offset", "index_advance", "index_base", "lcluster_count", "lcluster_pos", "lcn_advance", "physical_end", "post_eof", }: raise SystemExit("B22 operation set is incomplete") current = (src / "zmap.c").read_text(encoding="utf-8") base = committed("src/zmap.c") linux = (root / "src-linux/zmap.c").read_text(encoding="utf-8") helpers = [ "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", ] for helper in helpers: if helper in base or current.count(helper) < 2: raise SystemExit(f"B22 helper is missing, duplicated, or pre-existing: {helper}") old_freebsd = ( "m->pblk = le32dec(in + packsize - sizeof(uint32_t)) + nblk;", "map->m_llen = map->m_la + 1 - vi->size;", "vi->z_fragmentoff |= map->m_pa << 32;", "lcn += m->delta[1];", "pos += lcn << amortizedshift;", "(pend >> sbi->blkszbits) >= (1ULL << 48)", ) if not all(anchor in base for anchor in old_freebsd): raise SystemExit("B22 baseline arithmetic anchors changed") if any(anchor in current for anchor in old_freebsd): raise SystemExit("B22 left an unchecked baseline arithmetic anchor") linux_anchors = ( "m->pblk = le32_to_cpu(*(__le32 *)in) + nblk;", "map->m_llen = map->m_la + 1 - inode->i_size;", "vi->z_fragmentoff |= map->m_pa << 32;", "lcn += m->delta[1];", "(pend >> sbi->blkszbits) >= BIT_ULL(48)", ) if not all(anchor in linux for anchor in linux_anchors): raise SystemExit("Linux zmap semantic anchor changed") if function(current, "z_erofs_read_index") != function(base, "z_erofs_read_index"): raise SystemExit("B22 changed the FreeBSD metadata/provider reader") if current.count("erofs_read_metadata") != base.count("erofs_read_metadata"): raise SystemExit("B22 changed metadata I/O call count") if current.count("erofs_put_metabuf") != base.count("erofs_put_metabuf"): raise SystemExit("B22 changed metadata release call count") for public in ("z_erofs_fill_inode", "z_erofs_map_blocks"): old_signature = function(base, public).split("{", 1)[0] new_signature = function(current, public).split("{", 1)[0] if old_signature != new_signature: raise SystemExit(f"B22 changed public zmap ABI: {public}") if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", current): raise SystemExit("B22 introduced Linux negative errno") legacy_mismatches: set[str] = set() for case in cases: operation = case["operation"] status = case["status"] expected = case.get("expected") if operation == "compact_pblk" and status == "PASS": old = (int(case["base"]) + int(case["nblk"])) & u32_max if old != expected: legacy_mismatches.add("compact-pblk") elif operation == "index_advance" and status == "EINTEGRITY": old = (int(case["position"]) + int(case["count"]) * int(case["unit"])) & u64_max if old <= u64_max: legacy_mismatches.add("index-position") elif operation == "lcluster_pos" and status == "EINTEGRITY": old = ((int(case["lcn"]) << int(case["lclusterbits"])) + int(case["clusterofs"])) & u64_max if old <= u64_max: legacy_mismatches.add("delta-lcn") elif operation == "physical_end" and case["name"] == "physical-last-48bit-block": pend = int(case["pa"]) + int(case["plen"]) if (pend >> 12) >= (1 << 48): legacy_mismatches.add("physical-end") elif operation == "post_eof" and case["name"] == "post-eof-saturates-empty-inode": old = ((int(case["la"]) + 1) & u64_max) - int(case["size"]) if old != expected: legacy_mismatches.add("post-eof") elif operation == "fragment_offset" and status == "EINTEGRITY": old = int(case["low"]) | ((int(case["high"]) << 32) & u64_max) if old <= u64_max: legacy_mismatches.add("fragment-high") if legacy_mismatches != { "compact-pblk", "delta-lcn", "fragment-high", "index-position", "physical-end", "post-eof", }: raise SystemExit("B22 fixtures do not distinguish every legacy bug") helper_source = "\n\n".join(function(current, name) for name in helpers) c_lines = [ "#include ", "#include ", "#define EINTEGRITY 97", "#define rounddown2(x, y) ((x) & ~((y) - 1))", "typedef uint64_t erofs_off_t;", "typedef uint64_t erofs_blk_t;", "struct z_erofs_map_header { uint8_t bytes[8]; };", "struct erofs_inode { erofs_off_t inode_off; unsigned int inode_isize; unsigned int xattr_isize; };", helper_source, "int main(void)", "{", "\tuint64_t value;", "\tint error, failures = 0;", ] def u64(value: object) -> str: return f"UINT64_C({int(value)})" for case in cases: name = str(case["name"]) operation = case["operation"] expected_error = 0 if case["status"] == "PASS" else eintegrity c_lines.append(f"\t/* {name} */") if operation == "compact_pblk": call = f"z_erofs_compact_pblk(UINT32_C({int(case['base'])}), {int(case['nblk'])}U, &value)" elif operation == "index_base": c_lines.extend([ "\t{", "\t\tstruct erofs_inode vi = {", f"\t\t\t.inode_off = {u64(case['inode_off'])},", f"\t\t\t.inode_isize = {int(case['inode_isize'])}U,", f"\t\t\t.xattr_isize = {int(case.get('xattr_isize', 0))}U,", "\t\t};", ]) call = "z_erofs_index_base(&vi, &value)" elif operation == "index_advance": c_lines.append(f"\tvalue = {u64(case['position'])};") call = f"z_erofs_index_advance(&value, {u64(case['count'])}, {u64(case['unit'])})" elif operation == "lcluster_count": call = f"z_erofs_lcluster_count({u64(case['size'])}, {int(case['lclusterbits'])}U, &value)" elif operation == "lcluster_pos": call = f"z_erofs_lcluster_pos({u64(case['lcn'])}, {int(case['lclusterbits'])}U, {u64(case['clusterofs'])}, &value)" elif operation == "lcn_advance": c_lines.append(f"\tvalue = {u64(case['lcn'])};") call = f"z_erofs_lcn_advance(&value, {u64(case['delta'])})" elif operation == "physical_end": call = f"z_erofs_physical_end({u64(case['pa'])}, {u64(case['plen'])}, {u64(case['block_size'])})" elif operation == "post_eof": call = f"z_erofs_post_eof_len({u64(case['la'])}, {u64(case['size'])}, &value)" elif operation == "fragment_offset": call = f"z_erofs_fragment_offset({u64(case['low'])}, {u64(case['high'])}, &value)" else: raise SystemExit(f"cannot generate C for operation: {operation}") c_lines.append(f"\terror = {call};") c_lines.append(f"\tif (error != {expected_error}) {{") c_lines.append(f"\t\tfprintf(stderr, \"{name}: error=%d\\n\", error);") c_lines.append("\t\tfailures++;\n\t}") if case["status"] == "PASS" and "expected" in case: c_lines.append(f"\tif (value != {u64(case['expected'])}) {{") c_lines.append(f"\t\tfprintf(stderr, \"{name}: value mismatch\\n\");") c_lines.append("\t\tfailures++;\n\t}") if operation == "index_base": c_lines.append("\t}") c_lines.extend([ "\tif (failures != 0)", "\t\treturn (1);", f"\tprintf(\"B22 extracted arithmetic PASS cases={len(cases)}\\n\");", "\treturn (0);", "}", ]) c_path = artifacts / "B22-zmap-arithmetic.c" binary = artifacts / "B22-zmap-arithmetic" c_path.write_text("\n".join(c_lines) + "\n", encoding="ascii") compiled = subprocess.run( ["cc", "-std=gnu11", "-Wall", "-Wextra", "-Werror", str(c_path), "-o", str(binary)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / "B22-compile.stdout").write_text(compiled.stdout, encoding="utf-8") (artifacts / "B22-compile.stderr").write_text(compiled.stderr, encoding="utf-8") if compiled.returncode != 0: raise SystemExit("B22 extracted arithmetic compilation failed") executed = subprocess.run( [str(binary)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / "B22-extracted.stdout").write_text(executed.stdout, encoding="utf-8") (artifacts / "B22-extracted.stderr").write_text(executed.stderr, encoding="utf-8") if executed.returncode != 0: raise SystemExit("B22 extracted arithmetic execution failed") result = { "status": "PASS", "batch": "B22", "test": "TC170-zmap-arithmetic", "baseline": baseline, "case_count": len(cases), "negative_count": sum(case["status"] == "EINTEGRITY" for case in cases), "target_markers": sorted(markers), "legacy_mismatches": sorted(legacy_mismatches), "freebsd_positive_errno": True, "linux_algorithm_locations_audited": True, "provider_io_calls_unchanged": True, "metadata_release_calls_unchanged": True, "decompressor_abi_unchanged": True, "ondisk_abi_unchanged": True, "qemu_scope": "exact-source KLD load plus extracted helper replay when PRE15_MODE=qemu", "full_feature_suite": "NOT_RUN", } (artifacts / "B22-decoded-cases.json").write_text( json.dumps(decoded, ensure_ascii=True, indent=2, sort_keys=True) + "\n", encoding="ascii", ) (artifacts / "B22-result.json").write_text( json.dumps(result, ensure_ascii=True, indent=2, sort_keys=True) + "\n", encoding="ascii", ) print( f"B22 host PASS cases={len(cases)} negatives={result['negative_count']} " f"markers={len(markers)}" ) PY then : else pre15_dut_fail 'B22 source, fixture, or extracted arithmetic check failed' fi sha256sum "$artifacts/B22-zmap-arithmetic.c" \ "$artifacts/B22-zmap-arithmetic" \ "$artifacts/B22-decoded-cases.json" "$artifacts/B22-result.json" \ > "$artifacts/SHA256SUMS" if test "${PRE15_MODE:-host}" != qemu; then printf '%s\n' \ 'TC170 host extracted zmap arithmetic and fixture oracle PASS' \ 'QEMU exact-source KLD load and FreeBSD helper replay NOT_RUN in host mode' exit 0 fi for tool in awk clang file nm scp; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B22 QEMU tool: $tool" done : "${PRE15_QEMU_CONTROL_PATH:?QEMU control path is required}" : "${PRE15_QEMU_SSH_KEY:?QEMU SSH key is required}" : "${PRE15_QEMU_SSH_PORT:?QEMU SSH port is required}" : "${PRE15_QEMU_SSH_USER:?QEMU SSH user is required}" module=$PRE15_CASE_TMP/B22-erofs.ko if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \ "$PRE15_CASE_TMP/kld-work" >"$artifacts/B22-kld-build.stdout" \ 2>"$artifacts/B22-kld-build.stderr"; then pre15_dut_fail 'B22 cross-target KLD build failed' fi pre15_record_module "$module" file "$module" > "$artifacts/B22-kld-file.txt" sha256sum "$module" > "$artifacts/B22-kld-sha256.txt" nm -u "$module" | LC_ALL=C sort > "$artifacts/B22-kld-nm-u.txt" pre15_scp() { timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" scp -O -q \ -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \ -i "$PRE15_QEMU_SSH_KEY" -P "$PRE15_QEMU_SSH_PORT" \ "$1" "$PRE15_QEMU_SSH_USER@127.0.0.1:$2" } if ! pre15_scp "$module" /root/B22-erofs.ko || \ ! pre15_scp "$artifacts/B22-zmap-arithmetic.c" \ /root/B22-zmap-arithmetic.c; then pre15_infra_blocked 'could not transfer B22 module or arithmetic source' fi if pre15_guest_ssh_bounded kldstat -n erofs >/dev/null 2>&1; then pre15_infra_blocked 'FreeBSD guest already has an EROFS module loaded' fi if ! pre15_guest_ssh_bounded kldload /root/B22-erofs.ko \ >"$artifacts/B22-kldload.stdout" 2>"$artifacts/B22-kldload.stderr"; then if grep -q 'module already loaded or in kernel' \ "$artifacts/B22-kldload.stderr"; then pre15_infra_blocked 'guest kernel already owns the erofs.1 interface' fi pre15_dut_fail 'B22 exact-source KLD failed to load' fi pre15_own_guest_kld erofs 'B22 exact-source KLD' if ! pre15_guest_ssh_bounded cc -std=gnu11 -Wall -Wextra -Werror \ /root/B22-zmap-arithmetic.c -o /root/B22-zmap-arithmetic; then pre15_infra_blocked 'could not compile B22 arithmetic helper in the guest' fi if ! pre15_guest_ssh_bounded /root/B22-zmap-arithmetic \ >"$artifacts/B22-guest-arithmetic.stdout" \ 2>"$artifacts/B22-guest-arithmetic.stderr"; then pre15_dut_fail 'B22 FreeBSD arithmetic helper replay failed' fi pre15_guest_ssh_bounded kldstat -n erofs > "$artifacts/B22-kldstat.txt" printf '%s\n' \ 'TC170 QEMU PASS exact-source KLD load and FreeBSD arithmetic helper replay' \ 'No full feature suite or shared GEOM provider was used'