#!/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" fixture_dir=$PRE15_DUT/tests/pre15/fixtures spec=$fixture_dir/B32-cache-state.json oracle=$fixture_dir/B32-cache-oracle.c generator=$fixture_dir/B32-cache-generate.py probe=$fixture_dir/B32-cache-probe.c qemu_runner=$fixture_dir/B32-qemu-run.sh kld_builder=$fixture_dir/B28-build-kld.sh artifacts=$PRE15_RUN_DIR/artifacts scenario=${PRE15_B32_SCENARIO:-TC168-cache-inflight} case "$scenario" in TC168-cache-inflight|TC184-vnode-races) ;; *) pre15_fail_usage "unknown B32 scenario: $scenario" ;; esac for tool in cc git python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B32 host tool: $tool" done for fixture in "$spec" "$oracle" "$generator" "$probe" "$qemu_runner" \ "$kld_builder"; do pre15_record_fixture "b32-$(basename "$fixture")" "$fixture" done for source in internal.h decompressor.c zdata.c zmap.c; do pre15_record_fixture "b32-$source" "$PRE15_DUT/src/$source" done mkdir -p "$artifacts" if test "${PRE15_QEMU_TARGET_ONLY:-0}" = 1; then pre15_target_reached else if ! python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$spec" \ "$artifacts/B32-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]) spec = json.loads(Path(sys.argv[3]).read_text(encoding="ascii")) report_path = Path(sys.argv[4]) baseline = spec["baseline"] implementation = "c6a502184da6e973b1f0fa2ff8c0290c041e7092" current_owners = { "internal.h": "aa20623132b6821c6ad4651b263fc0ac0f7a14c8", "decompressor.c": implementation, "zdata.c": "bc56f830918b76029871b60cca2e53992de70a2e", "zmap.c": "d09924362eb29819bd393e1e86602eb38c7608c6", } def fail(message: str) -> None: raise SystemExit(message) def committed(commit: str, path: str) -> str: completed = subprocess.run( ["git", "-C", str(root), "show", f"{commit}:repo-pre-15/{path}"], check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) if completed.returncode != 0: fail(f"cannot read B32 source {commit}:{path}: {completed.stderr.strip()}") return completed.stdout def function(source: str, name: str) -> str: match = re.search(rf"\n(?:static\s+)?[^\n]+\n{name}\([^{{]+\n\{{", source) if match is None: fail(f"cannot locate function {name}") start = match.start() + 1 brace = source.find("{", match.start()) depth = 0 for index in range(brace, len(source)): if source[index] == "{": depth += 1 elif source[index] == "}": depth -= 1 if depth == 0: return source[start : index + 1] fail(f"unterminated function {name}") expected_changed = { "repo-pre-15/src/decompressor.c", "repo-pre-15/src/internal.h", "repo-pre-15/src/zdata.c", "repo-pre-15/src/zmap.c", "repo-pre-15/tests/pre15/cases/B32-cache-state.sh", "repo-pre-15/tests/pre15/fixtures/B32-cache-generate.py", "repo-pre-15/tests/pre15/fixtures/B32-cache-oracle.c", "repo-pre-15/tests/pre15/fixtures/B32-cache-probe.c", "repo-pre-15/tests/pre15/fixtures/B32-qemu-run.sh", "repo-pre-15/tests/pre15/fixtures/B32-cache-state.json", } changed = subprocess.run( [ "git", "-C", str(root), "diff-tree", "--no-commit-id", "--name-only", "-r", implementation, ], check=True, stdout=subprocess.PIPE, text=True, ).stdout.splitlines() if set(changed) != expected_changed or len(changed) != len(expected_changed): fail(f"B32 implementation write set mismatch: {changed!r}") for name, commit in (("baseline", baseline), ("implementation", implementation)): if subprocess.check_output( ["git", "-C", str(root), "rev-parse", commit], text=True ).strip() != commit: fail(f"B32 {name} identity changed") sources = { name: (dut / "src" / name).read_text(encoding="utf-8") for name in ("internal.h", "decompressor.c", "zdata.c", "zmap.c") } implemented_sources = { name: committed(implementation, f"src/{name}") for name in sources } for name, owner in current_owners.items(): if sources[name] != committed(owner, f"src/{name}"): fail(f"current B32 source differs from accepted owner {owner}: {name}") required_key_tokens = { "nid": "cache->nid == vi->nid", "decoded_size": "cache->decoded_size == decoded_size", "m_pa": "cache->map.m_pa == map->m_pa", "m_la": "cache->map.m_la == map->m_la", "m_plen": "cache->map.m_plen == map->m_plen", "m_llen": "cache->map.m_llen == map->m_llen", "m_deviceid": "cache->map.m_deviceid == map->m_deviceid", "m_algorithmformat": "cache->map.m_algorithmformat == map->m_algorithmformat", "m_flags": "cache->map.m_flags == map->m_flags", } if list(required_key_tokens) != spec["key_fields"]: fail("B32 key fixture field order changed") def audit_b32_sources(label: str, source_set: dict[str, str]) -> None: internal = source_set["internal.h"] zdata = source_set["zdata.c"] decompressor = source_set["decompressor.c"] zmap = source_set["zmap.c"] map_match = re.search( r"struct erofs_map_blocks \{(?P.*?)\n\};", internal, re.S ) cache_match = re.search( r"struct erofs_zextent_cache \{(?P.*?)\n\};", internal, re.S ) if map_match is None or cache_match is None: fail(f"{label} canonical map/cache structures are absent") if map_match.start() > cache_match.start(): fail(f"{label} canonical map is not defined before embedded cache use") map_body = map_match.group("body") cache_body = cache_match.group("body") if "uint8_t m_algorithmformat;" not in map_body: fail(f"{label} m_algorithmformat is not an unsigned narrow type") if "struct erofs_map_blocks map;" not in cache_body: fail(f"{label} cache does not embed the canonical map") for duplicate in required_key_tokens: if duplicate in ("nid", "decoded_size"): continue if re.search( rf"\b{duplicate}\b", cache_body.replace("struct erofs_map_blocks map;", ""), ): fail(f"{label} cache duplicates canonical map field {duplicate}") for field in ("decoded_size", "waiters", "error", "state", "cv", "closing"): if not re.search(rf"\b{field}\b", cache_body): fail(f"{label} cache state is missing {field}") match_body = function(zdata, "z_erofs_extent_cache_match") for field, token in required_key_tokens.items(): if match_body.count(token) != 1: fail(f"{label} cache key does not compare {field} exactly once") for token in ( "cv_wait(&cache->cv, &sbi->z_extent_cache_lock)", "cv_broadcast(&cache->cv)", "cache->state = EROFS_ZCACHE_INFLIGHT", "cache->state = EROFS_ZCACHE_READY", "cache->state = EROFS_ZCACHE_FAILED", "cache->waiters != 0", "cache->error = error", "cache->closing = true", ): if token not in zdata: fail(f"{label} cache state-machine token is absent: {token}") if "cache->state == EROFS_ZCACHE_INFLIGHT || cache->waiters != 0" not in zdata: fail(f"{label} eviction does not protect registered waiter generations") if "z_erofs_extent_cache_eligible(sbi, vi, &map, decoded_len)" not in zdata: fail(f"{label} cache admission is not based on decoded size") if "Z_EROFS_CACHE_BYPASS" not in zdata or "free(decoded, M_EROFS);" not in zdata: fail(f"{label} no-cache fallback is absent") if ( "uint8_t algorithm;" not in decompressor or "(unsigned char)map->m_algorithmformat" in decompressor ): fail(f"{label} decompressor algorithm boundary is not unsigned narrow") for token in ( "(uint8_t)(map->m_plen >>", "(uint8_t)(fmt - 1)", "(uint8_t)(h->h_algorithmtype & 15)", "(uint8_t)(h->h_algorithmtype >> 4)", ): if token not in zmap: fail(f"{label} explicit on-disk conversion is absent: {token}") for forbidden in ("bitlock", "wait_on_bit", "workqueue", "work_struct", "folio"): if forbidden in "\n".join(source_set.values()): fail(f"{label} introduced Linux-only primitive: {forbidden}") audit_b32_sources("implementation", implemented_sources) audit_b32_sources("current", sources) baseline_zdata = committed(baseline, "src/zdata.c") for preserved in ("z_erofs_decode_length", "z_erofs_decode_extent"): if function(implemented_sources["zdata.c"], preserved) != function( baseline_zdata, preserved ): fail(f"B28 {preserved} changed outside B32 cache state") for untouched in ( "compress.h", "decompressor_lz4.c", "decompressor_lzma.c", "decompressor_deflate.c", "decompressor_zstd.c", ): if committed(implementation, f"src/{untouched}") != committed( baseline, f"src/{untouched}" ): fail(f"B32 changed B25/B27/B28 source outside its write set: {untouched}") current = (dut / "src" / untouched).read_text(encoding="utf-8") owner = current_owners["zmap.c"] if untouched == "decompressor_zstd.c" else baseline if current != committed(owner, f"src/{untouched}"): fail(f"current preserved codec source differs from accepted owner: {untouched}") report = { "algorithm_type": "uint8_t", "baseline": baseline, "batch": "B32", "cache_map_embedded": True, "current_owners": current_owners, "decoded_size_admission": True, "implementation": implementation, "key_fields": spec["key_fields"], "lock": "z_extent_cache_lock", "no_cache_fallback": True, "preserved": ["B25-positive-errno", "B27-trailing", "B28-partial-fallback"], "states": spec["states"], "write_set": sorted(changed), } report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii") print(json.dumps(report, indent=2, sort_keys=True)) PY then pre15_runner_fail 'B32 source, lock, key, or preservation audit failed' fi if ! cc -std=c11 -Wall -Wextra -Werror -pthread "$oracle" \ -o "$PRE15_CASE_TMP/B32-cache-oracle" \ >"$artifacts/oracle-build.stdout" 2>"$artifacts/oracle-build.stderr"; then pre15_runner_fail 'B32 host concurrency oracle did not compile' fi if ! timeout -k 5 60 "$PRE15_CASE_TMP/B32-cache-oracle" \ >"$artifacts/oracle.stdout" 2>"$artifacts/oracle.stderr"; then pre15_dut_fail 'B32 host concurrency oracle failed' fi sha256sum "$artifacts/B32-source-audit.json" "$artifacts/oracle.stdout" \ >"$artifacts/SHA256SUMS" fi pre15_target_reached if test "${PRE15_MODE:-host}" != qemu; then printf '%s\n' \ 'TC168-cache-inflight host PASS' \ 'Canonical key, one owner, waiter bytes/positive errno, retry, eviction, fallback, and shutdown PASS' \ 'TC126/TC127/TC129 host oracle NOT_RUN (not necessary)' \ 'QEMU and full feature suite NOT_RUN in host mode' exit 0 fi for tool in clang cmp diff file mkfs.erofs nm scp tar timeout; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B32 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}" first=$PRE15_CASE_TMP/fixtures-first second=$PRE15_CASE_TMP/fixtures-second if ! (umask 022 && timeout -k 5 180 python3 -B "$generator" --spec "$spec" \ --output "$first") >"$artifacts/generate-first.json" \ 2>"$artifacts/generate-first.stderr" || \ ! (umask 022 && timeout -k 5 180 python3 -B "$generator" --spec "$spec" \ --output "$second") >"$artifacts/generate-second.json" \ 2>"$artifacts/generate-second.stderr"; then pre15_runner_fail 'B32 fixture generation failed' fi if ! diff -qr "$first" "$second" >"$artifacts/fixture-repeat.diff"; then pre15_runner_fail 'B32 fixtures are not byte reproducible' fi cp "$first/fixture-index.json" "$artifacts/B32-fixture-index.json" module=$PRE15_CASE_TMP/B32-erofs-zstdio0.ko if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \ "$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" 0 \ >"$artifacts/kld-build.stdout" 2>"$artifacts/kld-build.stderr"; then pre15_dut_fail 'B32 cross-target zstdio0 KLD build failed' fi pre15_record_module "$module" file "$module" >"$artifacts/kld-file.txt" sha256sum "$module" >"$artifacts/kld-sha256.txt" nm -u "$module" | LC_ALL=C sort >"$artifacts/kld-nm-u.txt" fixture_archive=$PRE15_CASE_TMP/B32-fixtures.tar.gz 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" } pre15_guest_ssh_bounded() { timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" ssh -n -q \ -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \ -p "$PRE15_QEMU_SSH_PORT" \ "$PRE15_QEMU_SSH_USER@127.0.0.1" "$@" } if ! pre15_scp "$fixture_archive" /root/B32-fixtures.tar.gz || \ ! pre15_scp "$probe" /root/B32-cache-probe.c || \ ! pre15_scp "$module" /root/B32-erofs-zstdio0.ko; then pre15_infra_blocked 'could not transfer B32 module, probe, or fixtures' fi if ! pre15_guest_ssh_bounded \ 'rm -rf /root/B32-fixtures && mkdir /root/B32-fixtures && tar -xzf /root/B32-fixtures.tar.gz -C /root/B32-fixtures && cc -O2 -Wall -Wextra -Werror -pthread -o /root/B32-cache-probe /root/B32-cache-probe.c'; then pre15_infra_blocked 'could not prepare B32 guest fixtures/probe' fi if pre15_guest_ssh_bounded kldstat -q -m erofs >/dev/null 2>&1; then pre15_infra_blocked 'guest kernel already owns the erofs.1 interface' fi pre15_guest_ssh_bounded kldstat >"$artifacts/guest-kldstat-before-load.txt" pre15_guest_ssh_bounded dmesg >"$artifacts/guest-dmesg-before-load.txt" cache_tunable=vfs.erofs.decoded_cache.minimum_decode_work cache_tunable_before=$(pre15_guest_ssh_bounded kenv -q "$cache_tunable" || true) printf 'name=%s before=%s test-value=0\n' "$cache_tunable" \ "${cache_tunable_before:-unset}" >"$artifacts/cache-tunable.txt" if ! pre15_guest_ssh_bounded kenv "$cache_tunable=0"; then pre15_infra_blocked 'could not force B32 decoded-cache admission' fi if ! pre15_guest_ssh_bounded kldload /root/B32-erofs-zstdio0.ko \ >"$artifacts/kldload.stdout" 2>"$artifacts/kldload.stderr"; then if test -n "$cache_tunable_before"; then pre15_guest_ssh_bounded kenv "$cache_tunable=$cache_tunable_before" || true else pre15_guest_ssh_bounded kenv -u "$cache_tunable" || true fi if grep -q 'module already loaded or in kernel' \ "$artifacts/kldload.stderr"; then pre15_infra_blocked 'guest kernel already owns the erofs.1 interface' fi pre15_dut_fail 'B32 exact-source zstdio0 KLD failed to load' fi if test -n "$cache_tunable_before"; then pre15_guest_ssh_bounded kenv "$cache_tunable=$cache_tunable_before" || \ pre15_infra_blocked 'could not restore B32 cache tunable' else pre15_guest_ssh_bounded kenv -u "$cache_tunable" || \ pre15_infra_blocked 'could not remove B32 cache tunable override' fi cache_tunable_after=$(pre15_guest_ssh_bounded kenv -q "$cache_tunable" || true) printf 'after=%s\n' "${cache_tunable_after:-unset}" \ >>"$artifacts/cache-tunable.txt" if test "$cache_tunable_after" != "$cache_tunable_before"; then pre15_runner_fail 'B32 cache tunable was not restored exactly' fi if ! pre15_guest_ssh_bounded kldstat -q -m erofs; then pre15_dut_fail 'B32 kldload returned success without erofs.1 ownership' fi pre15_guest_ssh_bounded kldstat >"$artifacts/guest-kldstat-after-load.txt" pre15_guest_ssh_bounded dmesg >"$artifacts/guest-dmesg-before.txt" diff -u "$artifacts/guest-dmesg-before-load.txt" \ "$artifacts/guest-dmesg-before.txt" >"$artifacts/guest-dmesg-load.diff" || true sed -n '/^+++ /d; /^+/s/^+//p' "$artifacts/guest-dmesg-load.diff" \ >"$artifacts/guest-dmesg-load-added.txt" if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free|fatal trap|pager fault|linker.*(error|undefined)|undefined symbol' \ "$artifacts/guest-dmesg-load-added.txt"; then pre15_dut_fail 'B32 exact-source KLD load produced kernel or linker errors' fi pre15_own_guest_kld B32-erofs-zstdio0.ko 'B32 exact-source KLD' b32_unown() { kind=$1 value=$2 tmp=$PRE15_CASE_TMP/ownership.$$ awk -F ' ' -v kind="$kind" -v value="$value" \ '!($1 == kind && $2 == value)' "$PRE15_OWNERSHIP_FILE" >"$tmp" mv "$tmp" "$PRE15_OWNERSHIP_FILE" } b32_attach() { image=$1 label=$2 B32_MD=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \ -f "/root/B32-fixtures/images/$image") || \ pre15_dut_fail "$label md attach failed" case "$B32_MD" in md[0-9]*) ;; *) pre15_runner_fail "unexpected B32 md unit: $B32_MD" ;; esac pre15_own_guest_md "$B32_MD" "$label md" B32_MOUNT=/mnt/pre15-b32-$label pre15_guest_ssh_bounded mkdir -p "$B32_MOUNT" pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/$B32_MD" "$B32_MOUNT" || \ pre15_dut_fail "$label mount failed" pre15_own_guest_mount "$B32_MOUNT" "$label mount" } b32_detach() { if pre15_guest_ssh_bounded mount | grep -F " on $B32_MOUNT " >/dev/null; then pre15_guest_ssh_bounded umount "$B32_MOUNT" || \ pre15_dut_fail "$B32_MOUNT unmount failed" fi b32_unown guest-mount "$B32_MOUNT" pre15_guest_ssh_bounded mdconfig -d -u "$B32_MD" || \ pre15_dut_fail "$B32_MD detach failed" b32_unown guest-md "$B32_MD" } b32_start_lifecycle() { label=$1 B32_CONTROL=/tmp/B32-$label-control B32_LIFECYCLE_PID=$(pre15_guest_ssh_bounded \ "rm -rf '$B32_CONTROL'; mkdir '$B32_CONTROL'; nohup /root/B32-cache-probe lifecycle '$B32_MOUNT/payload.bin' '$B32_CONTROL' >/tmp/B32-$label.stdout 2>/tmp/B32-$label.stderr /dev/null; do i=\$((i + 1)); test \$i -lt 600 || exit 1; sleep 0.1; done" || \ pre15_infra_blocked 'B32 lifecycle process did not exit' } reference=/root/B32-fixtures/source/payload.bin b32_attach lzma-valid.erofs valid if ! pre15_guest_ssh_bounded /root/B32-cache-probe concurrent \ "$B32_MOUNT/payload.bin" "$reference" 0 64 8 0 65536 \ >"$artifacts/valid-concurrent.stdout" \ 2>"$artifacts/valid-concurrent.stderr"; then pre15_dut_fail 'TC168 valid same-key concurrent reads diverged' fi if ! grep -qx 'concurrent PASS workers=64 loops=8 assertions=512 errno=0 offset=0 length=65536' \ "$artifacts/valid-concurrent.stdout"; then pre15_runner_fail 'TC168 valid assertion count was not exactly 512' fi if test "$scenario" = TC184-vnode-races; then if ! pre15_guest_ssh_bounded /root/B32-cache-probe vnode-race \ "$B32_MOUNT/payload.bin" "$reference" 64 50 \ >"$artifacts/vnode-race.stdout" \ 2>"$artifacts/vnode-race.stderr"; then pre15_dut_fail 'TC184 lookup/open/read/reclaim loop diverged' fi if ! grep -qx 'vnode-race PASS workers=64 loops=50 assertions=3200' \ "$artifacts/vnode-race.stdout"; then pre15_runner_fail 'TC184 vnode assertion count was not exactly 3200' fi fi b32_detach if test "$scenario" = TC168-cache-inflight; then b32_attach lzma-truncated.erofs truncated if ! pre15_guest_ssh_bounded /root/B32-cache-probe concurrent \ "$B32_MOUNT/payload.bin" "$reference" 97 1 1 0 65536 \ >"$artifacts/failure-single.stdout" \ 2>"$artifacts/failure-single.stderr"; then pre15_dut_fail 'TC168 single corrupt read did not return EINTEGRITY' fi if ! grep -qx 'concurrent PASS workers=1 loops=1 assertions=1 errno=97 offset=0 length=65536' \ "$artifacts/failure-single.stdout"; then pre15_runner_fail 'TC168 single corrupt assertion count was not 1' fi if ! pre15_guest_ssh_bounded /root/B32-cache-probe concurrent \ "$B32_MOUNT/payload.bin" "$reference" 97 64 2 0 65536 \ >"$artifacts/failure-concurrent.stdout" \ 2>"$artifacts/failure-concurrent.stderr"; then pre15_dut_fail 'TC168 failure waiters did not receive EINTEGRITY' fi if ! grep -qx 'concurrent PASS workers=64 loops=2 assertions=128 errno=97 offset=0 length=65536' \ "$artifacts/failure-concurrent.stdout"; then pre15_runner_fail 'TC168 failure assertion count was not exactly 128' fi b32_detach else b32_attach lzma-valid.erofs normal-busy b32_start_lifecycle normal-busy if pre15_guest_ssh_bounded umount "$B32_MOUNT" \ >"$artifacts/normal-unmount.stdout" 2>"$artifacts/normal-unmount.stderr"; then pre15_dut_fail 'TC184 normal unmount with held vnode unexpectedly succeeded' fi if ! grep -qi 'busy' "$artifacts/normal-unmount.stderr"; then pre15_dut_fail 'TC184 normal unmount did not report EBUSY' fi b32_stop_lifecycle b32_detach b32_attach lzma-valid.erofs forced-held b32_start_lifecycle forced-held pre15_guest_ssh_bounded umount -f "$B32_MOUNT" || \ pre15_dut_fail 'TC184 held-vnode forced unmount command failed' b32_unown guest-mount "$B32_MOUNT" pre15_guest_ssh_bounded touch "$B32_CONTROL/read" pre15_guest_ssh_bounded \ "i=0; while test ! -f '$B32_CONTROL/result'; do i=\$((i + 1)); test \$i -lt 600 || exit 1; sleep 0.1; done" || \ pre15_infra_blocked 'TC184 held-vnode result timed out' pre15_guest_ssh_bounded cat "$B32_CONTROL/result" \ >"$artifacts/forced-held-result.txt" if ! grep -qx -- '-1 6' "$artifacts/forced-held-result.txt"; then pre15_dut_fail 'TC184 revoked held vnode did not return exact ENXIO' fi b32_detach b32_attach lzma-valid.erofs forced-closed b32_start_lifecycle forced-closed pre15_guest_ssh_bounded touch "$B32_CONTROL/close" pre15_guest_ssh_bounded \ "i=0; while test ! -f '$B32_CONTROL/closed'; do i=\$((i + 1)); test \$i -lt 600 || exit 1; sleep 0.1; done" || \ pre15_infra_blocked 'TC184 closed-descriptor marker timed out' pre15_guest_ssh_bounded umount -f "$B32_MOUNT" || \ pre15_dut_fail 'TC184 closed-descriptor forced unmount command failed' b32_unown guest-mount "$B32_MOUNT" pre15_guest_ssh_bounded touch "$B32_CONTROL/read" pre15_guest_ssh_bounded \ "i=0; while test ! -f '$B32_CONTROL/result'; do i=\$((i + 1)); test \$i -lt 600 || exit 1; sleep 0.1; done" || \ pre15_infra_blocked 'TC184 closed-descriptor result timed out' pre15_guest_ssh_bounded cat "$B32_CONTROL/result" \ >"$artifacts/forced-closed-result.txt" if ! grep -qx -- '-1 9' "$artifacts/forced-closed-result.txt"; then pre15_dut_fail 'TC184 closed descriptor did not return exact EBADF' fi b32_detach fi pre15_guest_ssh_bounded dmesg >"$artifacts/guest-dmesg-after.txt" diff -u "$artifacts/guest-dmesg-before.txt" \ "$artifacts/guest-dmesg-after.txt" >"$artifacts/guest-dmesg.diff" || true sed -n '/^+++ /d; /^+/s/^+//p' "$artifacts/guest-dmesg.diff" \ >"$artifacts/guest-dmesg-added.txt" if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free|fatal trap|pager fault|linker.*(error|undefined)|undefined symbol' \ "$artifacts/guest-dmesg-added.txt"; then pre15_dut_fail "$scenario produced kernel, linker, WITNESS, or UAF evidence" fi printf '%s PASS\n' "$scenario" printf '%s\n' \ 'QEMU used an owned overlay and dynamic forwarded port' \ 'KLD erofs.1 ownership and cache tunable restoration PASS' \ 'TC130 and full feature suite NOT_RUN'