update
This commit is contained in:
Executable
+601
@@ -0,0 +1,601 @@
|
||||
#!/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'
|
||||
Reference in New Issue
Block a user