#!/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_SMOKE_CODEC:?PRE15_SMOKE_CODEC is required}" . "$PRE15_LIB_DIR/runner.sh" fixture_dir=$PRE15_DUT/tests/pre15/fixtures generator=$fixture_dir/SMOKE-generate.py kldsym_probe=$fixture_dir/SMOKE-kldsym.c kld_builder=$fixture_dir/B28-build-kld.sh artifacts=$PRE15_RUN_DIR/artifacts source_first=$PRE15_CASE_TMP/source-first source_second=$PRE15_CASE_TMP/source-second image=$PRE15_CASE_TMP/SMOKE-$PRE15_SMOKE_CODEC.erofs module=$PRE15_CASE_TMP/SMOKE-$PRE15_SMOKE_CODEC-erofs.ko case "$PRE15_SMOKE_CODEC" in plain) config=0 uuid=00000000-0000-4000-8000-000000000100 mkfs_codec= case_timeout=180 ;; lz4) config=0 uuid=00000000-0000-4000-8000-000000000104 mkfs_codec='-zlz4 -C4096' case_timeout=240 ;; lzma) config=0 uuid=00000000-0000-4000-8000-0000000001a0 mkfs_codec='-zlzma,level=6,dictsize=65536 -C4096' case_timeout=300 ;; zstd) config=1 uuid=00000000-0000-4000-8000-00000000025d mkfs_codec='-zzstd,level=3,dictsize=65536 -C4096' case_timeout=300 ;; *) pre15_runner_fail "unknown smoke codec: $PRE15_SMOKE_CODEC" ;; esac for tool in clang cmp diff file mkfs.erofs nm python3 scp sha256sum timeout; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing smoke tool: $tool" done for fixture in "$generator" "$kldsym_probe" "$kld_builder" \ "$PRE15_DUT/tests/pre15/cases/SMOKE-common.sh"; do pre15_record_fixture "smoke-$(basename "$fixture")" "$fixture" done mkdir -p "$artifacts" if ! python3 -B "$generator" --output "$source_first" \ >"$artifacts/source-first.json" 2>"$artifacts/source-first.stderr" || \ ! python3 -B "$generator" --output "$source_second" \ >"$artifacts/source-second.json" 2>"$artifacts/source-second.stderr"; then pre15_runner_fail 'smoke fixture generation failed' fi if ! diff -qr "$source_first" "$source_second" \ >"$artifacts/source-repeat.diff" || \ ! cmp "$artifacts/source-first.json" "$artifacts/source-second.json"; then pre15_runner_fail 'smoke source fixture is not byte reproducible' fi if ! timeout -k 5 120 sh -c \ "mkfs.erofs -d0 -T0 --all-time --all-root --workers=1 --sort=path -x-1 -U$uuid $mkfs_codec '$image' '$source_first'" \ >"$artifacts/mkfs.stdout" 2>"$artifacts/mkfs.stderr"; then pre15_runner_fail "$PRE15_SMOKE_CODEC smoke image generation failed" fi if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \ "$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" "$config" \ >"$artifacts/kld-build.stdout" 2>"$artifacts/kld-build.stderr"; then pre15_dut_fail "$PRE15_SMOKE_CODEC smoke cross-target KLD build failed" fi pre15_record_module "$module" sha256sum "$image" >"$artifacts/image-sha256.txt" sha256sum "$module" >"$artifacts/kld-sha256.txt" file "$module" >"$artifacts/kld-file.txt" nm -u "$module" | LC_ALL=C sort >"$artifacts/kld-nm-u.txt" pre15_target_reached test "${PRE15_MODE:-}" = qemu || \ pre15_runner_fail 'smoke cases require QEMU mode' : "${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}" 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" } remote_root=/root/pre15-smoke-$PRE15_SMOKE_CODEC pre15_guest_ssh_bounded rm -rf "$remote_root" pre15_guest_ssh_bounded mkdir -p "$remote_root" if ! pre15_scp "$image" "$remote_root/image.erofs" || \ ! pre15_scp "$module" "$remote_root/erofs.ko" || \ ! pre15_scp "$kldsym_probe" "$remote_root/SMOKE-kldsym.c"; then pre15_infra_blocked "$PRE15_SMOKE_CODEC smoke transfer failed" fi if test "$PRE15_SMOKE_CODEC" = zstd; then if ! pre15_guest_ssh_bounded \ "cc -O2 -Wall -Wextra -Werror -std=c17 '$remote_root/SMOKE-kldsym.c' -o '$remote_root/SMOKE-kldsym'"; then pre15_infra_blocked 'could not compile guest ZSTDIO capability probe' fi if ! pre15_guest_ssh_bounded "$remote_root/SMOKE-kldsym" \ ZSTD_DCtx_setParameter ZSTD_createDCtx_advanced \ ZSTD_decompressStream ZSTD_freeDCtx ZSTD_isError \ >"$artifacts/zstdio-capability.txt" 2>"$artifacts/zstdio-capability.stderr"; then pre15_infra_blocked 'running guest kernel lacks required ZSTDIO symbols' fi 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 "$remote_root/erofs.ko" \ >"$artifacts/kldload.stdout" 2>"$artifacts/kldload.stderr"; then 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 if test "$PRE15_SMOKE_CODEC" = zstd && \ grep -Eqi 'link_elf|symbol|not defined' "$artifacts/kldload.stderr"; then pre15_infra_blocked 'zstdio1 EROFS KLD cannot load on this guest kernel' fi pre15_dut_fail "$PRE15_SMOKE_CODEC smoke KLD failed to load" fi pre15_own_guest_kld erofs "$PRE15_SMOKE_CODEC smoke KLD" smoke_md=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \ -f "$remote_root/image.erofs") || \ pre15_dut_fail "$PRE15_SMOKE_CODEC smoke md attach failed" case "$smoke_md" in md[0-9]*) ;; *) pre15_runner_fail "unexpected smoke md unit: $smoke_md" ;; esac pre15_own_guest_md "$smoke_md" "$PRE15_SMOKE_CODEC smoke md" smoke_mount=/mnt/pre15-smoke-$PRE15_SMOKE_CODEC pre15_guest_ssh_bounded mkdir -p "$smoke_mount" if ! pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/$smoke_md" "$smoke_mount"; then pre15_dut_fail "$PRE15_SMOKE_CODEC smoke readonly mount failed" fi pre15_own_guest_mount "$smoke_mount" "$PRE15_SMOKE_CODEC smoke mount" python3 -B - "$artifacts/source-first.json" "$artifacts/expected-files.tsv" <<'PY' import json from pathlib import Path import sys manifest = json.loads(Path(sys.argv[1]).read_text(encoding="ascii")) lines = [f"{item['path']}\t{item['size']}\t{item['sha256']}" for item in manifest["files"]] Path(sys.argv[2]).write_text("\n".join(lines) + "\n", encoding="ascii") PY while IFS="$(printf '\t')" read -r path expected_size expected_hash; do actual_size=$(pre15_guest_ssh_bounded stat -f %z "$smoke_mount/$path") || \ pre15_dut_fail "$PRE15_SMOKE_CODEC smoke stat failed: $path" actual_hash=$(pre15_guest_ssh_bounded sha256 -q "$smoke_mount/$path") || \ pre15_dut_fail "$PRE15_SMOKE_CODEC smoke hash failed: $path" printf '%s\t%s\t%s\n' "$path" "$actual_size" "$actual_hash" \ >>"$artifacts/guest-files.tsv" test "$actual_size" = "$expected_size" && test "$actual_hash" = "$expected_hash" || \ pre15_dut_fail "$PRE15_SMOKE_CODEC smoke file mismatch: $path" done <"$artifacts/expected-files.tsv" pre15_guest_ssh_bounded \ "find '$smoke_mount' -mindepth 1 -maxdepth 1 -exec basename '{}' ';' | sort" \ >"$artifacts/guest-root-entries.txt" printf '%s\n' empty nested payload.bin >"$artifacts/expected-root-entries.txt" cmp "$artifacts/expected-root-entries.txt" "$artifacts/guest-root-entries.txt" || \ pre15_dut_fail "$PRE15_SMOKE_CODEC smoke root readdir set mismatch" if test "$PRE15_SMOKE_CODEC" = lz4; then dd if="$source_first/payload.bin" bs=1 skip=4093 count=131071 2>/dev/null | \ sha256sum | awk '{print $1}' >"$artifacts/expected-range-sha256.txt" pre15_guest_ssh_bounded \ "dd if='$smoke_mount/payload.bin' bs=1 skip=4093 count=131071 2>/dev/null | sha256 -q" \ >"$artifacts/guest-range-sha256.txt" cmp "$artifacts/expected-range-sha256.txt" "$artifacts/guest-range-sha256.txt" || \ pre15_dut_fail 'LZ4 smoke cross-pcluster partial range mismatch' fi pre15_guest_ssh_bounded dmesg >"$artifacts/guest-dmesg.txt" if grep -Eqi 'panic:|lock order reversal|witness.*warning|use-after-free' \ "$artifacts/guest-dmesg.txt"; then pre15_dut_fail "$PRE15_SMOKE_CODEC smoke observed kernel failure evidence" fi printf '%s\n' \ "SMOKE-$PRE15_SMOKE_CODEC PASS mode=zstdio$config deadline=$case_timeout" \ 'readonly mount, exact file hashes/sizes, readdir set, and owned cleanup exercised'