#!/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=083acc65f842c409c5a2e089e50060d0062b677c fixture_dir=$PRE15_DUT/tests/pre15/fixtures spec=$fixture_dir/B23-explicit-spec.json generator=$fixture_dir/B23-explicit-generate.py oracle=$fixture_dir/B23-explicit-oracle.py probe=$fixture_dir/B23-explicit-probe.c kld_builder=$fixture_dir/B23-build-kld.sh zmap=$PRE15_DUT/src/zmap.c linux_zmap=$PRE15_ROOT/src-linux/zmap.c artifacts=$PRE15_RUN_DIR/artifacts first=$PRE15_CASE_TMP/first second=$PRE15_CASE_TMP/second for tool in cc diff git mkfs.erofs python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B23 host tool: $tool" done pre15_record_fixture b23-spec "$spec" pre15_record_fixture b23-generator "$generator" pre15_record_fixture b23-oracle "$oracle" pre15_record_fixture b23-probe "$probe" pre15_record_fixture b23-kld-builder "$kld_builder" pre15_record_fixture b23-case "$PRE15_DUT/tests/pre15/cases/B23-explicit-table.sh" pre15_record_fixture b23-zmap "$zmap" pre15_record_fixture b23-linux-zmap "$linux_zmap" mkdir -p "$artifacts" if ! python3 -B "$generator" --spec "$spec" --output "$first" \ >"$artifacts/B23-generate-first.stdout" \ 2>"$artifacts/B23-generate-first.stderr"; then pre15_runner_fail 'B23 first fixture generation failed' fi if ! python3 -B "$generator" --spec "$spec" --output "$second" \ >"$artifacts/B23-generate-second.stdout" \ 2>"$artifacts/B23-generate-second.stderr"; then pre15_runner_fail 'B23 repeat fixture generation failed' fi if ! diff -u "$first/SHA256SUMS" "$second/SHA256SUMS" \ >"$artifacts/B23-repeat-sums.diff" || \ ! diff -u "$first/fixture-manifest.json" "$second/fixture-manifest.json" \ >"$artifacts/B23-repeat-manifest.diff"; then pre15_runner_fail 'B23 fixture generation is not byte deterministic' fi if ! python3 -B "$oracle" --spec "$spec" --fixtures "$first" \ --report "$artifacts/B23-oracle-first.json" \ >"$artifacts/B23-oracle-first.stdout" \ 2>"$artifacts/B23-oracle-first.stderr"; then pre15_runner_fail 'B23 independent fixture oracle failed' fi if ! python3 -B "$oracle" --spec "$spec" --fixtures "$second" \ --report "$artifacts/B23-oracle-second.json" \ >"$artifacts/B23-oracle-second.stdout" \ 2>"$artifacts/B23-oracle-second.stderr"; then pre15_runner_fail 'B23 repeat fixture oracle failed' fi cp "$first/SHA256SUMS" "$artifacts/B23-fixture-SHA256SUMS" cp "$first/fixture-manifest.json" "$artifacts/B23-fixture-manifest.json" pre15_record_fixture b23-generated-sums "$artifacts/B23-fixture-SHA256SUMS" if ! cc -std=c11 -Wall -Wextra -Werror -c "$probe" \ -o "$PRE15_CASE_TMP/B23-explicit-probe.o" \ >"$artifacts/B23-probe-compile.stdout" \ 2>"$artifacts/B23-probe-compile.stderr"; then pre15_runner_fail 'B23 guest probe does not compile on the host' fi if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" "$spec" \ "$artifacts/B23-source-audit.json" <<'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] spec_path = Path(sys.argv[4]) report_path = Path(sys.argv[5]) 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 B23 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}") start = source.rfind("\n", 0, source.rfind("\n", 0, match.start())) + 1 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}") spec = json.loads(spec_path.read_text(encoding="ascii")) current = (dut / "src/zmap.c").read_text(encoding="utf-8") before = committed("src/zmap.c") validator = function(current, "z_erofs_validate_extent_table") old_validator = function(before, "z_erofs_validate_extent_table") if validator == old_validator: raise SystemExit("B23 validator did not change") if function(current, "z_erofs_read_extent") != function(before, "z_erofs_read_extent"): raise SystemExit("B23 changed the local record decoder") for name in ("z_erofs_map_blocks_ext", "z_erofs_fill_inode"): if function(current, name) != function(before, name): raise SystemExit(f"B23 changed {name}") if "#define Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE (64 * 1024)" not in current: raise SystemExit("B23 fixed validation chunk bound is missing") required = ( "z_erofs_read_extent(sbi, vi, record_pos, recsz, &ext)", "while (scan_pos < table_end)", "Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE", "erofs_read_metadata(sbi, vi->nid, scan_pos, chunk_len, &buf)", "memcpy(&ext, (const char *)buf.data + offset, recsz)", "z_erofs_extent_lstart(&ext, recsz)", "erofs_put_metabuf(&buf)", "return (index == vi->z_extents ? 0 : EINTEGRITY)", ) if not all(marker in validator for marker in required): raise SystemExit("B23 validator is missing a bounded-scan contract marker") if validator.count("z_erofs_read_extent(") != 1: raise SystemExit("B23 short tail probe count changed") if validator.count("erofs_read_metadata(") != 1: raise SystemExit("B23 long-table reader is not chunk-scoped") if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", validator): raise SystemExit("B23 introduced Linux negative errno") allowed = { "repo-pre-15/src/zmap.c", "repo-pre-15/tests/pre15/cases/B23-explicit-table.sh", "repo-pre-15/tests/pre15/fixtures/B23-build-kld.sh", "repo-pre-15/tests/pre15/fixtures/B23-explicit-generate.py", "repo-pre-15/tests/pre15/fixtures/B23-explicit-oracle.py", "repo-pre-15/tests/pre15/fixtures/B23-explicit-probe.c", "repo-pre-15/tests/pre15/fixtures/B23-explicit-spec.json", } 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() ) if changed != allowed: raise SystemExit(f"B23 write set differs: {sorted(changed ^ allowed)}") for path in ( "src/erofs_fs.h", "src/data.c", "src/decompressor.c", "src/decompressor_lz4.c", "src/decompressor_lzma.c", "src/decompressor_deflate.c", "src/decompressor_zstd.c", "src/internal.h", ): if (dut / path).read_text(encoding="utf-8") != committed(path): raise SystemExit(f"B23 changed an excluded provider/codec/ABI file: {path}") linux = (root / "src-linux/zmap.c").read_text(encoding="utf-8") for marker in ( "recsz <= offsetof(struct z_erofs_extent, pstart_hi)", "le64_to_cpu(*(__le64 *)ext)", "le32_to_cpu(ext->pstart_lo)", "le32_to_cpu(ext->pstart_hi) << 32", "le32_to_cpu(ext->lstart_hi) << 32", "erofs_inode_in_metabox(inode)", ): if marker not in linux: raise SystemExit(f"Linux explicit-table anchor changed: {marker}") report = { "baseline": baseline, "batch": "B23", "candidates": spec["candidates"], "chunk_size": spec["chunk_size"], "decompressor_files_unchanged": True, "freebsd_positive_errno": True, "linux_record_decode_audited": True, "ondisk_header_unchanged": True, "primary_metabox_reader_unchanged": True, "status": "PASS", "test": spec["test"], "write_set": sorted(changed), } report_path.write_text( json.dumps(report, ensure_ascii=True, indent=2, sort_keys=True) + "\n", encoding="ascii", ) print("B23 source/write-set/Linux-FreeBSD audit PASS") PY then : else pre15_dut_fail 'B23 source, write-set, or cross-tree audit failed' fi sha256sum "$artifacts/B23-fixture-SHA256SUMS" \ "$artifacts/B23-fixture-manifest.json" \ "$artifacts/B23-oracle-first.json" \ "$artifacts/B23-oracle-second.json" \ "$artifacts/B23-source-audit.json" \ >"$artifacts/SHA256SUMS" pre15_target_reached if test "${PRE15_MODE:-host}" != qemu; then printf '%s\n' \ 'TC171 host real-fixture, independent oracle, and bounded source audit PASS' \ 'QEMU exact-source mapped/backing runtime NOT_RUN in host mode' \ 'Full feature suite NOT_RUN' exit 0 fi for tool in awk clang file nm scp tar; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B23 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}" fixture_archive=$PRE15_CASE_TMP/B23-fixtures.tar.gz module=$PRE15_CASE_TMP/B23-erofs.ko if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \ "$PRE15_CASE_TMP/kld-work" >"$artifacts/B23-kld-build.stdout" \ 2>"$artifacts/B23-kld-build.stderr"; then pre15_dut_fail 'B23 cross-target zstdio0 KLD build failed' fi pre15_record_module "$module" file "$module" >"$artifacts/B23-kld-file.txt" sha256sum "$module" >"$artifacts/B23-kld-sha256.txt" nm -u "$module" | LC_ALL=C sort >"$artifacts/B23-kld-nm-u.txt" tar -C "$first" -czf "$fixture_archive" . 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 "$fixture_archive" /root/B23-fixtures.tar.gz || \ ! pre15_scp "$probe" /root/B23-explicit-probe.c || \ ! pre15_scp "$module" /root/B23-erofs.ko; then pre15_infra_blocked 'could not transfer B23 module, probe, or fixtures' fi if ! pre15_guest_ssh_bounded \ 'rm -rf /root/pre15-b23-fixtures && mkdir /root/pre15-b23-fixtures && tar -xzf /root/B23-fixtures.tar.gz -C /root/pre15-b23-fixtures'; then pre15_infra_blocked 'could not prepare B23 guest fixtures' 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/B23-erofs.ko \ >"$artifacts/B23-kldload.stdout" 2>"$artifacts/B23-kldload.stderr"; then if grep -q 'module already loaded or in kernel' "$artifacts/B23-kldload.stderr"; then pre15_infra_blocked 'guest kernel already owns the erofs.1 interface' fi pre15_dut_fail 'B23 exact-source KLD failed to load' fi pre15_own_guest_kld erofs 'B23 exact-source KLD' if ! pre15_guest_ssh_bounded cc -std=c11 -Wall -Wextra -Werror \ -o /root/B23-explicit-probe /root/B23-explicit-probe.c; then pre15_infra_blocked 'could not compile the B23 guest probe' fi python3 -B - "$first/fixture-manifest.json" >"$PRE15_CASE_TMP/B23-qemu-cases.tsv" <<'PY' import json from pathlib import Path import sys manifest = json.loads(Path(sys.argv[1]).read_text(encoding="ascii")) for name, case in sorted(manifest["cases"].items()): offsets = ",".join(str(value) for value in case["probe_offsets"]) print( name, case["image"], case["expected_errno"], case["file_size"], offsets, case["target_marker"], sep="\t", ) PY pre15_attach_md() { pre15_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode -f "$1") || \ pre15_dut_fail "could not attach B23 provider: $1" case "$pre15_md" in md[0-9]*) ;; *) pre15_runner_fail "unexpected mdconfig output: $pre15_md" ;; esac pre15_md=${pre15_md#md} pre15_own_guest_md "$pre15_md" "$2" printf '%s\n' "$pre15_md" } while IFS="$(printf '\t')" read -r case_id image_name expected_errno \ file_size offsets target_marker; do mountpoint=/mnt/pre15-b23-$case_id unit=$(pre15_attach_md "/root/pre15-b23-fixtures/$image_name" \ "B23 $case_id primary") pre15_guest_ssh_bounded mkdir -p "$mountpoint" if ! pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/md$unit" \ "$mountpoint"; then pre15_dut_fail "B23 $case_id mount failed before $target_marker" fi pre15_own_guest_mount "$mountpoint" "B23 $case_id mount" if test "$expected_errno" -eq 0; then old_ifs=$IFS IFS=, set -- $offsets IFS=$old_ifs if ! pre15_guest_ssh_bounded /root/B23-explicit-probe pass \ "$mountpoint/target.bin" "$file_size" "$@"; then pre15_dut_fail "B23 mapped positive failed at $target_marker" fi else if ! pre15_guest_ssh_bounded /root/B23-explicit-probe errno \ "$mountpoint/target.bin" "$expected_errno"; then pre15_dut_fail "B23 corruption result failed at $target_marker" fi fi done <"$PRE15_CASE_TMP/B23-qemu-cases.tsv" pre15_guest_ssh_bounded dmesg >"$artifacts/B23-dmesg.txt" if grep -Eq 'panic:|Fatal trap|lock order reversal|KDB: stack backtrace' \ "$artifacts/B23-dmesg.txt"; then pre15_dut_fail 'B23 runtime produced a kernel diagnostic' fi printf '%s\n' \ 'TC171 QEMU explicit primary/metabox mapped and corruption runtime PASS' \ 'All errno values are positive FreeBSD ABI values; full feature suite NOT_RUN'