344 lines
13 KiB
Bash
Executable File
344 lines
13 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/B16-symlink-spec.json
|
|
probe=$fixture_dir/B16-symlink-probe.c
|
|
kld_builder=$fixture_dir/B16-build-kld.sh
|
|
gate_script=$PRE15_DUT/tests/pre15/gates/P15-019.sh
|
|
gate_input=$PRE15_DUT/tests/pre15/gates/P15-019-input.json
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
gate_clone=$PRE15_CASE_TMP/gate-clone
|
|
first=$PRE15_CASE_TMP/first
|
|
second=$PRE15_CASE_TMP/second
|
|
|
|
for tool in cmp fsck.erofs git mkfs.erofs python3 sha256sum timeout; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B16 host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b16-spec "$spec"
|
|
pre15_record_fixture b16-probe "$probe"
|
|
pre15_record_fixture b16-kld-builder "$kld_builder"
|
|
pre15_record_fixture b16-case "$PRE15_DUT/tests/pre15/cases/B16-symlink.sh"
|
|
pre15_record_fixture b16-gate "$gate_script"
|
|
pre15_record_fixture b16-gate-input "$gate_input"
|
|
pre15_record_fixture b16-internal "$PRE15_DUT/src/internal.h"
|
|
pre15_record_fixture b16-inode "$PRE15_DUT/src/inode.c"
|
|
pre15_record_fixture b16-data "$PRE15_DUT/src/data.c"
|
|
pre15_record_fixture b16-vnops "$PRE15_DUT/src/erofs_vnops.c"
|
|
pre15_record_fixture b16-linux-inode "$PRE15_ROOT/src-linux/inode.c"
|
|
mkdir -p "$artifacts"
|
|
|
|
if ! git clone -q --shared --no-checkout "$PRE15_ROOT" "$gate_clone" \
|
|
>"$artifacts/gate-clone.stdout" 2>"$artifacts/gate-clone.stderr" || \
|
|
! git -C "$gate_clone" checkout -q --detach \
|
|
aea34aba38a298d493d0a584f3985cf3589f358e \
|
|
>"$artifacts/gate-checkout.stdout" 2>"$artifacts/gate-checkout.stderr"; then
|
|
pre15_runner_fail 'could not materialize the frozen P15-019 gate commit'
|
|
fi
|
|
|
|
frozen_gate=$gate_clone/repo-pre-15/tests/pre15/gates/P15-019.sh
|
|
if ! timeout -k 10 240 "$frozen_gate" \
|
|
--base d645feb720c7022d2138d2a62eb72c022eb75351 --output "$first" \
|
|
>"$artifacts/gate-first.stdout" 2>"$artifacts/gate-first.stderr"; then
|
|
pre15_runner_fail 'first frozen P15-019 gate replay failed'
|
|
fi
|
|
if ! timeout -k 10 240 "$frozen_gate" \
|
|
--base d645feb720c7022d2138d2a62eb72c022eb75351 --output "$second" \
|
|
>"$artifacts/gate-second.stdout" 2>"$artifacts/gate-second.stderr"; then
|
|
pre15_runner_fail 'second frozen P15-019 gate replay failed'
|
|
fi
|
|
for name in result.json cases.json commands.json semantics.json cleanup.json \
|
|
fixtures/SHA256SUMS; do
|
|
if ! cmp "$first/host/$name" "$second/host/$name"; then
|
|
pre15_runner_fail "P15-019 repeat output differs: $name"
|
|
fi
|
|
done
|
|
|
|
if ! python3 -B - "$spec" "$gate_script" "$gate_input" \
|
|
"$first/host/result.json" "$first/host/cases.json" \
|
|
"$first/host/fixtures/SHA256SUMS" \
|
|
"$PRE15_DUT/src/internal.h" "$PRE15_DUT/src/inode.c" \
|
|
"$PRE15_DUT/src/data.c" "$PRE15_DUT/src/erofs_vnops.c" \
|
|
"$PRE15_ROOT/src-linux/inode.c" \
|
|
>"$artifacts/B16-host-audit.json" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def function(source: str, name: str) -> str:
|
|
match = re.search(rf"\n{name}\([^;]*?\n\{{", source, re.DOTALL)
|
|
if match is None:
|
|
raise SystemExit(f"missing function: {name}")
|
|
start = match.start() + 1
|
|
brace = source.index("{", 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]
|
|
raise SystemExit(f"unterminated function: {name}")
|
|
|
|
|
|
spec = json.loads(Path(sys.argv[1]).read_text(encoding="ascii"))
|
|
gate = Path(sys.argv[2])
|
|
gate_input = Path(sys.argv[3])
|
|
result = json.loads(Path(sys.argv[4]).read_text(encoding="ascii"))
|
|
cases = json.loads(Path(sys.argv[5]).read_text(encoding="ascii"))
|
|
sums = Path(sys.argv[6])
|
|
internal = Path(sys.argv[7]).read_text(encoding="utf-8")
|
|
inode = Path(sys.argv[8]).read_text(encoding="utf-8")
|
|
data = Path(sys.argv[9]).read_text(encoding="utf-8")
|
|
vnops = Path(sys.argv[10]).read_text(encoding="utf-8")
|
|
linux = Path(sys.argv[11]).read_text(encoding="utf-8")
|
|
|
|
if spec.get("schema") != 1 or spec.get("batch") != "B16" or spec.get("test") != "TC166-symlink-layouts":
|
|
raise SystemExit("B16 spec identity changed")
|
|
if sha256(gate) != spec["gate"]["script_sha256"] or sha256(gate_input) != spec["gate"]["input_sha256"]:
|
|
raise SystemExit("tracked P15-019 gate differs from the GO decision")
|
|
if result.get("decision") != "GO" or result.get("case_count") != 35:
|
|
raise SystemExit("frozen P15-019 gate did not reproduce GO/35")
|
|
if result.get("fixture_set_sha256") != spec["fixture_set_sha256"] or sha256(sums) != spec["fixture_set_sha256"]:
|
|
raise SystemExit("B16 fixture-set hash differs from G03")
|
|
|
|
expected_pairs = {
|
|
(layout, case)
|
|
for layout in spec["layouts"]
|
|
for case in spec["cases"]
|
|
}
|
|
actual_pairs = {(item["layout"], item["case"]) for item in cases}
|
|
if actual_pairs != expected_pairs:
|
|
raise SystemExit("B16 fixture matrix is incomplete")
|
|
for item in cases:
|
|
layout = spec["layouts"][item["layout"]]
|
|
if item["inode"]["layout"] != layout["layout"]:
|
|
raise SystemExit("B16 fixture layout differs")
|
|
|
|
validator = function(data, "erofs_validate_symlink_target")
|
|
readlink = function(data, "erofs_readlink_target")
|
|
read_inode = function(inode, "erofs_read_inode")
|
|
markers = (
|
|
"vi->vtype != VLNK",
|
|
"vi->size == 0",
|
|
"vi->size > MAXPATHLEN",
|
|
"erofs_read_data(sbi, vi, 0, (size_t)vi->size, &target)",
|
|
"memchr(target, '\\0', (size_t)vi->size)",
|
|
"erofs_brelse(target)",
|
|
)
|
|
if not all(marker in validator for marker in markers):
|
|
raise SystemExit("B16 bounded validator is incomplete")
|
|
if "uiomove" in validator:
|
|
raise SystemExit("B16 validator performs partial caller transfer")
|
|
if not (
|
|
read_inode.index("vi->size == 0")
|
|
< read_inode.index("z_erofs_fill_inode(sbi, vi)")
|
|
and read_inode.index("vi->size > MAXPATHLEN")
|
|
< read_inode.index("z_erofs_fill_inode(sbi, vi)")
|
|
and read_inode.index("erofs_validate_symlink_target(sbi, vi)")
|
|
> read_inode.index("z_erofs_fill_inode(sbi, vi)")
|
|
):
|
|
raise SystemExit("B16 size/content validation order changed")
|
|
if "erofs_validate_symlink_target" not in internal:
|
|
raise SystemExit("B16 validator prototype is missing")
|
|
if "vi->size == 0" not in readlink or "vi->size > MAXPATHLEN" not in readlink or "erofs_read_uio" not in readlink:
|
|
raise SystemExit("B16 VOP readlink guard/path changed")
|
|
if "return (erofs_readlink_target(ap->a_vp, ap->a_uio));" not in vnops:
|
|
raise SystemExit("FreeBSD VOP_READLINK adapter changed")
|
|
if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", internal + inode + data + vnops):
|
|
raise SystemExit("B16 introduced Linux negative errno")
|
|
if "i_link" in internal + inode + data + vnops or "page_get_link" in internal + inode + data + vnops:
|
|
raise SystemExit("B16 copied the Linux page/i_link implementation")
|
|
for marker in (
|
|
"vi->datalayout == EROFS_INODE_FLAT_INLINE",
|
|
"kmemdup_nul(bptr + ofs, inode->i_size, GFP_KERNEL)",
|
|
".get_link = page_get_link",
|
|
".get_link = simple_get_link",
|
|
"return -EFSCORRUPTED",
|
|
):
|
|
if marker not in linux:
|
|
raise SystemExit("Linux symlink semantic anchor changed")
|
|
|
|
report = {
|
|
"status": "PASS",
|
|
"candidate": "P15-019",
|
|
"test": "TC166-symlink-layouts",
|
|
"fixture_count": len(cases),
|
|
"fixture_set_sha256": result["fixture_set_sha256"],
|
|
"layouts": sorted(spec["layouts"]),
|
|
"errno": spec["errno"],
|
|
"linux_path": "fast inline i_link validation; page_get_link otherwise; negative errno",
|
|
"freebsd_path": "vnode VOP_READLINK; bounded pre-publication scan; GEOM/map; positive errno",
|
|
}
|
|
print(json.dumps(report, indent=2, sort_keys=True))
|
|
PY
|
|
then
|
|
pre15_runner_fail 'B16 independent fixture/source audit failed'
|
|
fi
|
|
|
|
cp "$first/host/result.json" "$artifacts/P15-019-result.json"
|
|
cp "$first/host/cases.json" "$artifacts/P15-019-cases.json"
|
|
cp "$first/host/fixtures/SHA256SUMS" "$artifacts/B16-SHA256SUMS"
|
|
pre15_record_fixture b16-generated-sums "$artifacts/B16-SHA256SUMS"
|
|
pre15_target_reached
|
|
|
|
if test "${PRE15_MODE:-host}" != qemu; then
|
|
printf '%s\n' \
|
|
'TC166 host all-layout fixture and independent source oracle PASS' \
|
|
'QEMU runtime NOT_RUN in host mode'
|
|
exit 0
|
|
fi
|
|
|
|
for tool in awk cc clang file nm scp tar; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B16 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/B16-fixtures.tar.gz
|
|
module=$PRE15_CASE_TMP/B16-erofs.ko
|
|
if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \
|
|
"$PRE15_CASE_TMP/kld-work" >"$artifacts/B16-kld-build.stdout" \
|
|
2>"$artifacts/B16-kld-build.stderr"; then
|
|
pre15_dut_fail 'B16 cross-target KLD build failed'
|
|
fi
|
|
pre15_record_module "$module"
|
|
file "$module" >"$artifacts/B16-kld-file.txt"
|
|
sha256sum "$module" >"$artifacts/B16-kld-sha256.txt"
|
|
nm -u "$module" | LC_ALL=C sort >"$artifacts/B16-kld-nm-u.txt"
|
|
tar -C "$first/host/fixtures" -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/B16-fixtures.tar.gz || \
|
|
! pre15_scp "$probe" /root/B16-symlink-probe.c || \
|
|
! pre15_scp "$module" /root/B16-erofs.ko; then
|
|
pre15_infra_blocked 'could not transfer B16 module or fixtures to the guest'
|
|
fi
|
|
if ! pre15_guest_ssh_bounded \
|
|
'rm -rf /root/pre15-b16-fixtures && mkdir /root/pre15-b16-fixtures && tar -xzf /root/B16-fixtures.tar.gz -C /root/pre15-b16-fixtures'; then
|
|
pre15_infra_blocked 'could not prepare B16 guest fixtures'
|
|
fi
|
|
if pre15_guest_ssh_bounded kldstat -n erofs >/dev/null 2>&1; then
|
|
pre15_infra_blocked 'FreeBSD guest already has an EROFS module loaded'
|
|
fi
|
|
if ! pre15_guest_ssh_bounded kldload /root/B16-erofs.ko \
|
|
>"$artifacts/B16-kldload.stdout" 2>"$artifacts/B16-kldload.stderr"; then
|
|
if grep -q 'module already loaded or in kernel' "$artifacts/B16-kldload.stderr"; then
|
|
pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
|
|
fi
|
|
pre15_dut_fail 'B16 exact-source KLD failed to load'
|
|
fi
|
|
pre15_own_guest_kld erofs 'B16 exact-source KLD'
|
|
if ! pre15_guest_ssh_bounded cc -std=c11 -Wall -Wextra -Werror \
|
|
-o /root/B16-symlink-probe /root/B16-symlink-probe.c; then
|
|
pre15_infra_blocked 'could not compile the B16 guest probe'
|
|
fi
|
|
|
|
pre15_attach_md()
|
|
{
|
|
pre15_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode -f "$1") || \
|
|
pre15_dut_fail "could not attach B16 provider: $1"
|
|
case "$pre15_md" in
|
|
md[0-9]*) ;;
|
|
*) pre15_runner_fail "unexpected mdconfig output: $pre15_md" ;;
|
|
esac
|
|
pre15_md=${pre15_md#md}
|
|
pre15_own_guest_md "$pre15_md" "$2"
|
|
printf '%s\n' "$pre15_md"
|
|
}
|
|
|
|
pre15_mount_case()
|
|
{
|
|
pre15_layout=$1
|
|
pre15_case=$2
|
|
pre15_mountpoint=/mnt/pre15-b16-$pre15_layout-$pre15_case
|
|
pre15_options=
|
|
if test "$pre15_layout" = chunk; then
|
|
pre15_blob=$(pre15_attach_md \
|
|
"/root/pre15-b16-fixtures/$pre15_layout-$pre15_case.blob" \
|
|
"B16 $pre15_layout $pre15_case blob")
|
|
pre15_options="-o device.1=/dev/md$pre15_blob"
|
|
fi
|
|
pre15_primary=$(pre15_attach_md \
|
|
"/root/pre15-b16-fixtures/$pre15_layout-$pre15_case.erofs" \
|
|
"B16 $pre15_layout $pre15_case primary")
|
|
pre15_guest_ssh_bounded mkdir -p "$pre15_mountpoint"
|
|
if ! pre15_guest_ssh_bounded mount -t erofs -o ro $pre15_options \
|
|
"/dev/md$pre15_primary" "$pre15_mountpoint"; then
|
|
pre15_dut_fail "B16 $pre15_layout $pre15_case mount failed"
|
|
fi
|
|
pre15_own_guest_mount "$pre15_mountpoint" \
|
|
"B16 $pre15_layout $pre15_case mount"
|
|
printf '%s\n' "$pre15_mountpoint/link"
|
|
}
|
|
|
|
for layout in chunk compressed fragment inline plain; do
|
|
case "$layout" in
|
|
compressed) short_length=64 ;;
|
|
*) short_length=31 ;;
|
|
esac
|
|
for case_id in normal-short normal-max empty nul-middle too-long; do
|
|
path=$(pre15_mount_case "$layout" "$case_id")
|
|
case "$case_id" in
|
|
normal-short)
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
readlink-pass "$path" "$short_length"
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
concurrent "$path" "$short_length" 16 20
|
|
;;
|
|
normal-max)
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
readlink-pass "$path" 1024
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
follow-errno "$path" 63
|
|
;;
|
|
empty|nul-middle)
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
readlink-errno "$path" 97
|
|
;;
|
|
too-long)
|
|
pre15_guest_ssh_bounded /root/B16-symlink-probe \
|
|
readlink-errno "$path" 63
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
|
|
pre15_guest_ssh_bounded dmesg >"$artifacts/B16-dmesg.txt"
|
|
if grep -Eq 'panic:|Fatal trap|lock order reversal|KDB: stack backtrace' \
|
|
"$artifacts/B16-dmesg.txt"; then
|
|
pre15_dut_fail 'B16 runtime produced a kernel diagnostic'
|
|
fi
|
|
printf '%s\n' 'TC166 QEMU all-layout symlink validation PASS'
|