541 lines
19 KiB
Bash
541 lines
19 KiB
Bash
#!/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=e769e4ae32d4967c1f69067dc92628b60b62b648
|
|
fixture_dir=$PRE15_DUT/tests/pre15/fixtures
|
|
spec=$fixture_dir/B21-super-spec.json
|
|
generator=$fixture_dir/B21-super-generate.py
|
|
oracle=$fixture_dir/B21-super-oracle.py
|
|
qemu_probe=$fixture_dir/B21-qemu-probe.c
|
|
kld_builder=$fixture_dir/B28-build-kld.sh
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
first=$PRE15_CASE_TMP/first
|
|
second=$PRE15_CASE_TMP/second
|
|
|
|
for tool in cmp fsck.erofs git mkfs.erofs python3 sha256sum; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B21 host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b21-spec "$spec"
|
|
pre15_record_fixture b21-generator "$generator"
|
|
pre15_record_fixture b21-oracle "$oracle"
|
|
pre15_record_fixture b21-qemu-probe "$qemu_probe"
|
|
pre15_record_fixture b21-kld-builder "$kld_builder"
|
|
pre15_record_fixture b21-case "$PRE15_DUT/tests/pre15/cases/B21-super.sh"
|
|
pre15_record_fixture b21-super "$PRE15_DUT/src/super.c"
|
|
pre15_record_fixture b21-internal "$PRE15_DUT/src/internal.h"
|
|
pre15_record_fixture b21-xattr "$PRE15_DUT/src/xattr.c"
|
|
mkdir -p "$artifacts"
|
|
|
|
if python3 -B "$generator" --spec "$spec" --output "$first" \
|
|
--work "$PRE15_CASE_TMP/first-work" \
|
|
>"$artifacts/generate-first.json" \
|
|
2>"$artifacts/generate-first.stderr"; then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 first fixture generation failed'
|
|
fi
|
|
if python3 -B "$generator" --spec "$spec" --output "$second" \
|
|
--work "$PRE15_CASE_TMP/second-work" \
|
|
>"$artifacts/generate-second.json" \
|
|
2>"$artifacts/generate-second.stderr"; then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 second fixture generation failed'
|
|
fi
|
|
|
|
if cmp "$first/fixture-index.json" "$second/fixture-index.json" && \
|
|
find "$first" -type f ! -name fixture-index.json -printf '%f\n' | sort | \
|
|
while IFS= read -r name; do cmp "$first/$name" "$second/$name" || exit 1; done
|
|
then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 fixture generation is not byte reproducible'
|
|
fi
|
|
|
|
if python3 -B "$oracle" --fixtures "$first" \
|
|
--report "$artifacts/oracle-first.json" \
|
|
>"$artifacts/oracle-first.stdout" 2>"$artifacts/oracle-first.stderr" && \
|
|
python3 -B "$oracle" --fixtures "$second" \
|
|
--report "$artifacts/oracle-second.json" \
|
|
>"$artifacts/oracle-second.stdout" 2>"$artifacts/oracle-second.stderr" && \
|
|
cmp "$artifacts/oracle-first.json" "$artifacts/oracle-second.json"
|
|
then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 independent oracle replay failed'
|
|
fi
|
|
|
|
if fsck.erofs -d0 "$first/legal-control.erofs" \
|
|
>"$artifacts/legal-fsck.stdout" 2>"$artifacts/legal-fsck.stderr"; then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 legal control is not accepted by fsck.erofs'
|
|
fi
|
|
|
|
cp "$first/fixture-index.json" "$artifacts/B21-fixture-index.json"
|
|
pre15_record_fixture b21-generated-index "$artifacts/B21-fixture-index.json"
|
|
pre15_target_reached
|
|
|
|
if test "${PRE15_QEMU_TARGET_ONLY:-0}" = 1; then
|
|
:
|
|
else
|
|
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" \
|
|
"$artifacts/B21-source-check.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]
|
|
output = Path(sys.argv[4])
|
|
src = dut / "src"
|
|
|
|
|
|
def committed(name: str) -> str:
|
|
completed = subprocess.run(
|
|
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/src/{name}"],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
if completed.returncode != 0:
|
|
raise SystemExit(f"cannot read B21 baseline {name}: {completed.stderr}")
|
|
return completed.stdout
|
|
|
|
|
|
def replace_once(source: str, old: str, new: str, label: str) -> str:
|
|
count = source.count(old)
|
|
if count != 1:
|
|
raise SystemExit(f"{label}: expected one transform source, found {count}")
|
|
return source.replace(old, new, 1)
|
|
|
|
|
|
def extract_function(source: str, name: str) -> str:
|
|
match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
|
|
if not match:
|
|
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())
|
|
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 require_order(source: str, markers: list[str], label: str) -> None:
|
|
position = -1
|
|
for marker in markers:
|
|
position = source.find(marker, position + 1)
|
|
if position < 0:
|
|
raise SystemExit(f"{label} is missing ordered marker: {marker}")
|
|
|
|
|
|
current = {
|
|
name: (src / name).read_text(encoding="utf-8")
|
|
for name in ("internal.h", "super.c", "xattr.c")
|
|
}
|
|
base = {name: committed(name) for name in current}
|
|
|
|
if current["internal.h"] != base["internal.h"]:
|
|
raise SystemExit("B21 changed internal.h despite all required helpers existing")
|
|
for helper in (
|
|
"EROFS_FEATURE_FUNCS(fragments, incompat, INCOMPAT_FRAGMENTS)",
|
|
"EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM)",
|
|
"EROFS_FEATURE_FUNCS(plain_xattr_pfx, compat, COMPAT_PLAIN_XATTR_PFX)",
|
|
):
|
|
if current["internal.h"].count(helper) != 1:
|
|
raise SystemExit(f"B21 feature helper is missing or duplicated: {helper}")
|
|
|
|
expected_super = replace_once(
|
|
base["super.c"],
|
|
"\tif ((le32toh(dsb->feature_compat) & EROFS_FEATURE_COMPAT_SB_CHKSUM) == 0)\n",
|
|
"\tif (!erofs_sb_has_sb_chksum(sbi))\n",
|
|
"checksum helper",
|
|
)
|
|
expected_super = replace_once(
|
|
expected_super,
|
|
"\tif ((sbi->feature_incompat & EROFS_FEATURE_INCOMPAT_FRAGMENTS) != 0 &&\n"
|
|
"\t sbi->packed_nid > 0) {\n",
|
|
"\tif (erofs_sb_has_fragments(sbi) && sbi->packed_nid > 0) {\n",
|
|
"fragments helper",
|
|
)
|
|
old_read = extract_function(expected_super, "erofs_read_superblock")
|
|
new_read = extract_function(current["super.c"], "erofs_read_superblock")
|
|
expected_read = replace_once(
|
|
old_read,
|
|
"\tif (dsb->dirblkbits != 0)\n"
|
|
"\t\treturn (EOPNOTSUPP);\n"
|
|
"\tsbi->feature_compat = le32toh(dsb->feature_compat);\n",
|
|
"\tsbi->blkszbits = dsb->blkszbits;\n"
|
|
"\tsbi->block_size = 1u << sbi->blkszbits;\n"
|
|
"\tsbi->feature_compat = le32toh(dsb->feature_compat);\n"
|
|
"\terror = erofs_superblock_csum_verify(sbi, dsb);\n"
|
|
"\tif (error != 0)\n"
|
|
"\t\treturn (error);\n\n"
|
|
"\tif (dsb->dirblkbits != 0)\n"
|
|
"\t\treturn (EOPNOTSUPP);\n",
|
|
"checksum trust order",
|
|
)
|
|
dead_exception = """\t/*
|
|
\t * Narrowly allow one extra combination: long xattr prefixes enabled
|
|
\t * with non-plain prefix table stored in a packed inode, which adds
|
|
\t * the FRAGMENTS (0x20) incompat bit. This is NOT a declaration of
|
|
\t * general fragments support; per-inode data layout is still gated
|
|
\t * by plain/inline checks in erofs_read_inode().
|
|
\t */
|
|
\tif (unsupported != 0) {
|
|
\t\tif (unsupported != EROFS_FEATURE_INCOMPAT_FRAGMENTS ||
|
|
\t\t (sbi->feature_incompat &
|
|
\t\t\tEROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) == 0 ||
|
|
\t\t (sbi->feature_compat &
|
|
\t\t\tEROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) != 0 ||
|
|
\t\t sbi->packed_nid == 0)
|
|
\t\t\treturn (EOPNOTSUPP);
|
|
\t}
|
|
\tsbi->blkszbits = dsb->blkszbits;
|
|
\tsbi->block_size = 1u << sbi->blkszbits;
|
|
"""
|
|
expected_read = replace_once(
|
|
expected_read,
|
|
dead_exception,
|
|
"\tif (unsupported != 0)\n\t\treturn (EOPNOTSUPP);\n",
|
|
"dead fragments exception",
|
|
)
|
|
old_checksum_site = (
|
|
"\terror = erofs_superblock_csum_verify(sbi, dsb);\n"
|
|
"\tif (error != 0)\n"
|
|
"\t\treturn (error);\n"
|
|
)
|
|
if expected_read.count(old_checksum_site) != 2:
|
|
raise SystemExit("checksum trust-order transform did not produce two sites")
|
|
old_checksum_offset = expected_read.rfind(old_checksum_site)
|
|
expected_read = (
|
|
expected_read[:old_checksum_offset]
|
|
+ expected_read[old_checksum_offset + len(old_checksum_site) :]
|
|
)
|
|
if new_read != expected_read:
|
|
raise SystemExit("erofs_read_superblock differs from exact B21 transforms")
|
|
expected_super = expected_super.replace(old_read, expected_read, 1)
|
|
if current["super.c"] != expected_super:
|
|
raise SystemExit("super.c changed outside exact B21 transforms")
|
|
|
|
expected_xattr = replace_once(
|
|
base["xattr.c"],
|
|
"\tif ((sbi->feature_incompat & EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) ==\n"
|
|
"\t\t0 ||\n"
|
|
"\t sbi->xattr_prefix_count == 0)\n",
|
|
"\tif (!erofs_sb_has_xattr_prefixes(sbi) || sbi->xattr_prefix_count == 0)\n",
|
|
"xattr-prefix helper",
|
|
)
|
|
expected_xattr = replace_once(
|
|
expected_xattr,
|
|
"\tif ((sbi->feature_compat & EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) == 0) {\n",
|
|
"\tif (!erofs_sb_has_plain_xattr_pfx(sbi)) {\n",
|
|
"plain-prefix helper",
|
|
)
|
|
if current["xattr.c"] != expected_xattr:
|
|
raise SystemExit("xattr.c changed outside exact B21 helper conversions")
|
|
|
|
read_super = new_read
|
|
checksum_call = read_super.index("erofs_superblock_csum_verify(sbi, dsb)")
|
|
before_checksum = read_super[:checksum_call]
|
|
for protected in (
|
|
"dirblkbits",
|
|
"feature_incompat",
|
|
"packed_nid",
|
|
"extra_devices",
|
|
"sb_extslots",
|
|
"meta_blkaddr",
|
|
"xattr_blkaddr",
|
|
"xattr_prefix_start",
|
|
"xattr_prefix_count",
|
|
"blocks_root",
|
|
):
|
|
if protected in before_checksum:
|
|
raise SystemExit(f"protected field used before checksum: {protected}")
|
|
require_order(
|
|
read_super,
|
|
[
|
|
"dsb->magic",
|
|
"dsb->blkszbits < 9",
|
|
"sbi->feature_compat = le32toh(dsb->feature_compat)",
|
|
"erofs_superblock_csum_verify(sbi, dsb)",
|
|
"dsb->dirblkbits",
|
|
"sbi->feature_incompat = le32toh(dsb->feature_incompat)",
|
|
"unsupported = sbi->feature_incompat",
|
|
"sbi->sb_size = 128 + dsb->sb_extslots",
|
|
"sbi->xattr_prefix_start = le32toh(dsb->xattr_prefix_start)",
|
|
"erofs_validate_device_size",
|
|
"erofs_load_generation_seed",
|
|
"z_erofs_parse_cfgs",
|
|
],
|
|
"superblock trust order",
|
|
)
|
|
|
|
mountfs = extract_function(current["super.c"], "erofs_mountfs")
|
|
require_order(
|
|
mountfs,
|
|
[
|
|
"erofs_read_superblock",
|
|
"erofs_scan_devices",
|
|
"erofs_init_packed_inode",
|
|
"erofs_init_metabox_inode",
|
|
"erofs_read_inode(sbi, sbi->root_nid",
|
|
"erofs_xattr_prefixes_init",
|
|
"mp->mnt_data = sbi",
|
|
],
|
|
"FreeBSD mount publication",
|
|
)
|
|
if "fail:\n\terofs_sb_free(sbi);\n\treturn (error);" not in mountfs:
|
|
raise SystemExit("mount failure no longer funnels through erofs_sb_free")
|
|
sb_free = extract_function(current["super.c"], "erofs_sb_free")
|
|
require_order(
|
|
sb_free,
|
|
[
|
|
"z_erofs_extent_cache_fini",
|
|
"erofs_xattr_prefixes_cleanup",
|
|
"erofs_drop_internal_inodes",
|
|
"erofs_free_dev_context",
|
|
"erofs_release_device_info(&sbi->dif0)",
|
|
"free(sbi, M_EROFS)",
|
|
],
|
|
"mount cleanup",
|
|
)
|
|
release = extract_function(current["super.c"], "erofs_release_device_info")
|
|
require_order(
|
|
release,
|
|
["g_topology_lock", "g_vfs_close", "g_topology_unlock", "vrele", "dev_rel"],
|
|
"GEOM release",
|
|
)
|
|
if any("return (-E" in current[name] for name in current):
|
|
raise SystemExit("negative errno entered the FreeBSD B21 write set")
|
|
|
|
result = {
|
|
"baseline": baseline,
|
|
"checksum_before_protected_fields": True,
|
|
"dead_fragments_exception_removed": True,
|
|
"feature_helpers": True,
|
|
"geom_release_preserved": True,
|
|
"internal_h_unchanged": True,
|
|
"mount_cleanup_preserved": True,
|
|
"mount_publication_preserved": True,
|
|
"positive_errno_preserved": True,
|
|
"status": "PASS",
|
|
}
|
|
output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii")
|
|
print("B21 source: exact trust-order and feature-helper transforms present")
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B21 source contract failed'
|
|
fi
|
|
fi
|
|
|
|
if python3 - "$artifacts/B21-fixture-index.json" \
|
|
"$artifacts/oracle-first.json" "$artifacts/B21-matrix.tsv" <<'PY'
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
index = json.loads(Path(sys.argv[1]).read_text(encoding="ascii"))
|
|
oracle = json.loads(Path(sys.argv[2]).read_text(encoding="ascii"))
|
|
if index["case_count"] != 13 or index["legal_count"] != 1 or index["damaged_count"] != 12:
|
|
raise SystemExit("B21 fixture cardinality changed")
|
|
if oracle["status"] != "PASS" or oracle["passed_count"] != 13:
|
|
raise SystemExit("B21 oracle matrix is incomplete")
|
|
lines = ["id\tauthenticated\terrno\treject\tsha256"]
|
|
for fixture, result in zip(index["cases"], oracle["results"], strict=True):
|
|
lines.append(
|
|
f"{fixture['id']}\t{str(fixture['authenticated']).lower()}\t"
|
|
f"{result['actual_errno']}\t{result['actual_reject']}\t{fixture['sha256']}"
|
|
)
|
|
Path(sys.argv[3]).write_text("\n".join(lines) + "\n", encoding="ascii")
|
|
print(
|
|
f"B21 fixtures: {index['case_count']} "
|
|
f"({index['legal_count']} legal, {index['damaged_count']} damaged)"
|
|
)
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_runner_fail 'B21 fixture matrix summary failed'
|
|
fi
|
|
|
|
if test "${PRE15_MODE:-host}" != qemu; then
|
|
printf '%s\n' \
|
|
'B21 super gate: host PASS' \
|
|
'TC172 QEMU runtime NOT_RUN in host mode'
|
|
exit 0
|
|
fi
|
|
|
|
for tool in awk clang file nm scp tar timeout; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B21 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/B21-fixtures.tar.gz
|
|
module=$PRE15_CASE_TMP/B21-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/B21-kld-build.stdout" 2>"$artifacts/B21-kld-build.stderr"; then
|
|
pre15_dut_fail 'B21 cross-target zstdio0 KLD build failed'
|
|
fi
|
|
pre15_record_module "$module"
|
|
file "$module" >"$artifacts/B21-kld-file.txt"
|
|
sha256sum "$module" >"$artifacts/B21-kld-sha256.txt"
|
|
nm -u "$module" | LC_ALL=C sort >"$artifacts/B21-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/B21-fixtures.tar.gz || \
|
|
! pre15_scp "$qemu_probe" /root/B21-qemu-probe.c || \
|
|
! pre15_scp "$module" /root/B21-erofs-zstdio0.ko; then
|
|
pre15_infra_blocked 'could not transfer B21 module, probe, or fixtures'
|
|
fi
|
|
if ! pre15_guest_ssh_bounded \
|
|
'rm -rf /root/B21-fixtures && mkdir /root/B21-fixtures && tar -xzf /root/B21-fixtures.tar.gz -C /root/B21-fixtures && cc -O2 -Wall -Wextra -Werror -std=c17 -o /root/B21-qemu-probe /root/B21-qemu-probe.c'; then
|
|
pre15_infra_blocked 'could not prepare B21 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
|
|
if ! pre15_guest_ssh_bounded kldload /root/B21-erofs-zstdio0.ko \
|
|
>"$artifacts/B21-kldload.stdout" 2>"$artifacts/B21-kldload.stderr"; then
|
|
if grep -q 'module already loaded or in kernel' \
|
|
"$artifacts/B21-kldload.stderr"; then
|
|
pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
|
|
fi
|
|
pre15_dut_fail 'B21 exact-source zstdio0 KLD failed to load'
|
|
fi
|
|
pre15_own_guest_kld erofs 'B21 exact-source KLD'
|
|
pre15_guest_ssh_bounded dmesg >"$artifacts/B21-dmesg-before.txt"
|
|
pre15_guest_ssh_bounded mkdir -p /mnt/pre15-b21
|
|
|
|
b21_unown()
|
|
{
|
|
kind=$1
|
|
value=$2
|
|
tmp=$PRE15_CASE_TMP/ownership.$$
|
|
awk -F '\t' -v kind="$kind" -v value="$value" \
|
|
'!($1 == kind && $2 == value)' "$PRE15_OWNERSHIP_FILE" >"$tmp"
|
|
mv "$tmp" "$PRE15_OWNERSHIP_FILE"
|
|
}
|
|
|
|
printf 'id\texpected_errno\tactual_errno\treject\n' \
|
|
>"$artifacts/B21-qemu-matrix.tsv"
|
|
while IFS="$(printf '\t')" read -r id authenticated expected_errno reject fixture_hash; do
|
|
test "$id" != id || continue
|
|
b21_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \
|
|
-f "/root/B21-fixtures/$id.erofs") || \
|
|
pre15_dut_fail "$id md attach failed"
|
|
case "$b21_md" in
|
|
md[0-9]*) ;;
|
|
*) pre15_runner_fail "unexpected B21 md unit: $b21_md" ;;
|
|
esac
|
|
pre15_own_guest_md "$b21_md" "$id md"
|
|
result=$(pre15_guest_ssh_bounded /root/B21-qemu-probe \
|
|
"/dev/$b21_md" /mnt/pre15-b21) || \
|
|
pre15_infra_blocked "$id guest mount probe failed"
|
|
actual_errno=$(printf '%s\n' "$result" | sed -n 's/.* errno=\([0-9][0-9]*\).*/\1/p')
|
|
test -n "$actual_errno" || pre15_runner_fail "$id mount probe did not report errno"
|
|
if test "$actual_errno" = 0; then
|
|
pre15_own_guest_mount /mnt/pre15-b21 "$id mount"
|
|
fi
|
|
printf '%s\t%s\t%s\t%s\n' "$id" "$expected_errno" \
|
|
"$actual_errno" "$reject" >>"$artifacts/B21-qemu-matrix.tsv"
|
|
if test "$actual_errno" != "$expected_errno"; then
|
|
pre15_dut_fail "$id expected errno $expected_errno, received $actual_errno"
|
|
fi
|
|
if test "$actual_errno" = 0; then
|
|
pre15_guest_ssh_bounded find /mnt/pre15-b21 -mindepth 1 -maxdepth 1 \
|
|
-print >"$artifacts/B21-legal-readdir.txt"
|
|
pre15_guest_ssh_bounded umount /mnt/pre15-b21 || \
|
|
pre15_dut_fail "$id unmount failed"
|
|
b21_unown guest-mount /mnt/pre15-b21
|
|
fi
|
|
pre15_guest_ssh_bounded mdconfig -d -u "$b21_md" || \
|
|
pre15_dut_fail "$id md detach failed"
|
|
b21_unown guest-md "$b21_md"
|
|
done <"$artifacts/B21-matrix.tsv"
|
|
|
|
pre15_guest_ssh_bounded dmesg >"$artifacts/B21-dmesg-after.txt"
|
|
diff -u "$artifacts/B21-dmesg-before.txt" "$artifacts/B21-dmesg-after.txt" \
|
|
>"$artifacts/B21-dmesg.diff" || true
|
|
sed -n '/^+++ /d; /^+/s/^+//p' "$artifacts/B21-dmesg.diff" \
|
|
>"$artifacts/B21-dmesg-added.txt"
|
|
if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free' \
|
|
"$artifacts/B21-dmesg-added.txt"; then
|
|
pre15_dut_fail 'TC172 produced panic, WITNESS, or UAF evidence'
|
|
fi
|
|
printf '%s\n' \
|
|
'TC172-super-trust B21 QEMU PASS' \
|
|
'13/13 legal/damaged images returned exact mount errno with checksum trust order preserved'
|