#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" controls=$PRE15_DUT/tests/pre15/fixtures/B01-runner-controls.json pre15_record_fixture runner-control-contract "$controls" pre15_record_fixture evidence-schema "$PRE15_DUT/tests/pre15/EVIDENCE-SCHEMA.json" command -v qemu-system-x86_64 >/dev/null 2>&1 || \ pre15_infra_blocked 'qemu-system-x86_64 is required for the early-exit control' command -v ssh >/dev/null 2>&1 || \ pre15_infra_blocked 'ssh is required for the connection-failure control' pre15_control_run "$PRE15_CASE_TMP/known-good" 5 \ sh -c 'printf "reached\n" > "$PRE15_TARGET_MARKER_FILE"' pre15_control_run "$PRE15_CASE_TMP/dut-mismatch" 5 \ sh -c 'printf "reached\n" > "$PRE15_TARGET_MARKER_FILE"; exit 10' pre15_control_run "$PRE15_CASE_TMP/guest-command-failure" 5 sh -c 'exit 1' pre15_control_run "$PRE15_CASE_TMP/ssh-failure" 5 sh -c \ 'if ssh -o BatchMode=yes -o ConnectTimeout=1 -p 1 root@127.0.0.1 true >/dev/null 2>&1; then exit 20; fi; exit 21' pre15_control_run "$PRE15_CASE_TMP/qemu-early-exit" 5 sh -c \ 'qemu-system-x86_64 --pre15-invalid-option >/dev/null 2>&1; qemu_rc=$?; test "$qemu_rc" -ne 0 || exit 20; exit 21' timeout_pid_file=$PRE15_CASE_TMP/timeout.pid export timeout_pid_file pre15_control_run "$PRE15_CASE_TMP/timeout" 1 sh -c \ 'printf "%s\n" "$$" > "$timeout_pid_file"; exec sleep 30' timeout_pid=$(cat "$timeout_pid_file") if kill -0 "$timeout_pid" 2>/dev/null; then pre15_runner_fail "timeout control left PID $timeout_pid alive" fi pre15_control_run "$PRE15_CASE_TMP/owned-cleanup" 5 sh -c ' sleep 30 >/dev/null 2>&1 & owned_pid=$! printf "pid\t%s\tselftest-owned-sleep\n" "$owned_pid" >> "$PRE15_OWNERSHIP_FILE" printf "%s\n" "$owned_pid" > "$PRE15_RUN_DIR/owned.pid" printf "reached\n" > "$PRE15_TARGET_MARKER_FILE" ' owned_pid=$(cat "$PRE15_CASE_TMP/owned-cleanup/owned.pid") if kill -0 "$owned_pid" 2>/dev/null; then pre15_runner_fail "owned cleanup left PID $owned_pid alive" fi outside_path=$PRE15_CASE_TMP/cleanup-must-refuse printf 'owned by outer selftest\n' > "$outside_path" export outside_path pre15_control_run "$PRE15_CASE_TMP/cleanup-failure" 5 sh -c ' printf "path\t%s\toutside-control-boundary\n" "$outside_path" >> "$PRE15_OWNERSHIP_FILE" printf "reached\n" > "$PRE15_TARGET_MARKER_FILE" ' test -f "$outside_path" || \ pre15_runner_fail 'cleanup safety control deleted a non-owned path' python3 - "$controls" "$PRE15_CASE_TMP" <<'PY' import json from pathlib import Path import sys contract = json.loads(Path(sys.argv[1]).read_text(encoding="ascii"))["controls"] root = Path(sys.argv[2]) for name, expected in contract.items(): actual = (root / name / "result.tsv").read_text(encoding="ascii").rstrip("\n").split("\t") if actual != expected: raise SystemExit(f"{name}: {actual!r}, expected {expected!r}") print(f"{name}: status={actual[0]} origin={actual[1]} cleanup={actual[2]} timeout={actual[3]}") PY mkdir -p "$PRE15_RUN_DIR/artifacts/controls" for control in \ cleanup-failure dut-mismatch guest-command-failure known-good \ owned-cleanup qemu-early-exit ssh-failure timeout; do mkdir "$PRE15_RUN_DIR/artifacts/controls/$control" cp "$PRE15_CASE_TMP/$control/result.tsv" \ "$PRE15_CASE_TMP/$control/stdout.log" \ "$PRE15_CASE_TMP/$control/stderr.log" \ "$PRE15_CASE_TMP/$control/cleanup.log" \ "$PRE15_RUN_DIR/artifacts/controls/$control/" done pre15_target_reached