Files
erofs-freebsd-out-tree/tests/pre15/cases/B32-cache-state.sh
T
2026-08-18 09:20:44 +02:00

602 lines
24 KiB
Bash
Executable File

#!/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<body>.*?)\n\};", internal, re.S
)
cache_match = re.search(
r"struct erofs_zextent_cache \{(?P<body>.*?)\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 & echo \$!") || \
pre15_infra_blocked "$label lifecycle process did not start"
case "$B32_LIFECYCLE_PID" in
''|*[!0-9]*) pre15_runner_fail "invalid B32 lifecycle PID: $B32_LIFECYCLE_PID" ;;
esac
pre15_guest_ssh_bounded \
"i=0; while test ! -f '$B32_CONTROL/ready'; do i=\$((i + 1)); test \$i -lt 600 || exit 1; sleep 0.1; done" || \
pre15_infra_blocked "$label lifecycle ready marker timed out"
}
b32_stop_lifecycle()
{
pre15_guest_ssh_bounded touch "$B32_CONTROL/exit"
pre15_guest_ssh_bounded \
"i=0; while kill -0 '$B32_LIFECYCLE_PID' 2>/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'