#!/bin/sh PRE15_RC_DUT_FAIL=10 PRE15_RC_RUNNER_FAIL=20 PRE15_RC_INFRA_BLOCKED=21 PRE15_RC_STOP=22 pre15_require_paths() { : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" PRE15_DUT=${PRE15_DUT:-$(CDPATH= cd -- "$PRE15_LIB_DIR/../../.." && pwd -P)} PRE15_ROOT=${PRE15_ROOT:-$(CDPATH= cd -- "$PRE15_DUT/.." && pwd -P)} PRE15_SCHEMA=${PRE15_SCHEMA:-$PRE15_DUT/tests/pre15/EVIDENCE-SCHEMA.json} PRE15_FREEBSD_SRC=${PRE15_FREEBSD_SRC:-${FREEBSD_SRC:-/work/build/freebsd-src}} PRE15_EVIDENCE_ROOT=${PRE15_EVIDENCE_ROOT:-${TMPDIR:-/tmp}/erofs-pre15-evidence} export PRE15_DUT PRE15_ROOT PRE15_SCHEMA PRE15_FREEBSD_SRC PRE15_EVIDENCE_ROOT } pre15_utc_now() { date -u '+%Y-%m-%dT%H:%M:%SZ' } pre15_tree_sha256() { python3 - "$1" <<'PY' from __future__ import annotations import hashlib import os from pathlib import Path import stat import sys root = Path(sys.argv[1]) if not root.is_dir(): raise SystemExit(f"tree root is not a directory: {root}") digest = hashlib.sha256() for current, directories, files in os.walk(root, followlinks=False): directories.sort(key=os.fsencode) files.sort(key=os.fsencode) current_path = Path(current) names = [(name, True) for name in directories] names.extend((name, False) for name in files) names.sort(key=lambda item: os.fsencode(item[0])) for name, is_directory in names: path = current_path / name metadata = path.lstat() relative = os.fsencode(path.relative_to(root)) if stat.S_ISLNK(metadata.st_mode): kind = b"L" elif is_directory: kind = b"D" elif stat.S_ISREG(metadata.st_mode): kind = b"F" else: raise SystemExit(f"unsupported tree entry: {path}") digest.update(kind + b"\0" + relative + b"\0") digest.update(f"{stat.S_IMODE(metadata.st_mode):04o}\0".encode("ascii")) if kind == b"L": digest.update(os.fsencode(os.readlink(path)) + b"\0") elif kind == b"F": digest.update(f"{metadata.st_size}\0".encode("ascii")) with path.open("rb") as source: for chunk in iter(lambda: source.read(1024 * 1024), b""): digest.update(chunk) digest.update(b"\0") print(digest.hexdigest()) PY } pre15_tree_metadata_sha256() { python3 - "$1" <<'PY' from __future__ import annotations import hashlib import os from pathlib import Path import stat import sys root = Path(sys.argv[1]) if not root.is_dir(): raise SystemExit(f"tree root is not a directory: {root}") digest = hashlib.sha256() for current, directories, files in os.walk(root, followlinks=False): directories.sort(key=os.fsencode) files.sort(key=os.fsencode) current_path = Path(current) names = [(name, True) for name in directories] names.extend((name, False) for name in files) names.sort(key=lambda item: os.fsencode(item[0])) for name, is_directory in names: path = current_path / name metadata = path.lstat() relative = os.fsencode(path.relative_to(root)) if stat.S_ISLNK(metadata.st_mode): kind = b"L" elif is_directory: kind = b"D" elif stat.S_ISREG(metadata.st_mode): kind = b"F" else: raise SystemExit(f"unsupported tree entry: {path}") digest.update(kind + b"\0" + relative + b"\0") fields = ( stat.S_IMODE(metadata.st_mode), metadata.st_size, metadata.st_mtime_ns, metadata.st_ctime_ns, metadata.st_dev, metadata.st_ino, ) digest.update(("\0".join(map(str, fields)) + "\0").encode("ascii")) if kind == b"L": digest.update(os.fsencode(os.readlink(path)) + b"\0") print(digest.hexdigest()) PY } pre15_fail_usage() { printf 'RUNNER_FAIL: %s\n' "$*" >&2 exit "$PRE15_RC_RUNNER_FAIL" } pre15_target_reached() { : "${PRE15_TARGET_MARKER_FILE:?target marker path is unavailable}" printf 'reached\n' > "$PRE15_TARGET_MARKER_FILE" } pre15_set_reason() { : "${PRE15_REASON_FILE:?reason path is unavailable}" printf '%s\n' "$*" > "$PRE15_REASON_FILE" } pre15_dut_fail() { pre15_set_reason "$*" exit "$PRE15_RC_DUT_FAIL" } pre15_runner_fail() { pre15_set_reason "$*" exit "$PRE15_RC_RUNNER_FAIL" } pre15_infra_blocked() { pre15_set_reason "$*" exit "$PRE15_RC_INFRA_BLOCKED" } pre15_gate_stop() { pre15_set_reason "$*" exit "$PRE15_RC_STOP" } pre15_register_resource() { : "${PRE15_OWNERSHIP_FILE:?ownership manifest is unavailable}" pre15_resource_kind=$1 pre15_resource_value=$2 pre15_resource_label=$3 case "$pre15_resource_kind$pre15_resource_value$pre15_resource_label" in *" "*|*" "*) pre15_runner_fail "resource records cannot contain tabs or newlines" ;; esac printf '%s\t%s\t%s\n' \ "$pre15_resource_kind" "$pre15_resource_value" "$pre15_resource_label" \ >> "$PRE15_OWNERSHIP_FILE" } pre15_own_path() { pre15_register_resource path "$1" "${2:-temporary path}" } pre15_own_pid() { case "$1" in ''|*[!0-9]*) pre15_runner_fail "invalid owned PID: $1" ;; esac pre15_register_resource pid "$1" "${2:-process}" } pre15_own_port() { case "$1" in ''|*[!0-9]*) pre15_runner_fail "invalid owned port: $1" ;; esac pre15_register_resource port "$1" "${2:-forwarded port}" } pre15_own_ssh_control() { pre15_register_resource ssh-control "$1" "${2:-SSH ControlMaster}" } pre15_own_base_image() { pre15_base_path=$1 test -f "$pre15_base_path" || pre15_infra_blocked "base image is absent: $pre15_base_path" pre15_base_hash=$(sha256sum "$pre15_base_path" | awk '{print $1}') pre15_register_resource base-image "$pre15_base_path" "$pre15_base_hash" } pre15_own_guest_mount() { case "$1" in /*) ;; *) pre15_runner_fail "guest mountpoint must be absolute: $1" ;; esac pre15_register_resource guest-mount "$1" "${2:-guest mount}" } pre15_own_guest_md() { case "$1" in md[0-9]*) ;; *) pre15_runner_fail "invalid guest md unit: $1" ;; esac pre15_register_resource guest-md "$1" "${2:-guest md}" } pre15_own_guest_kld() { case "$1" in [A-Za-z0-9_.-]*) ;; *) pre15_runner_fail "invalid guest KLD name: $1" ;; esac pre15_register_resource guest-kld "$1" "${2:-guest KLD}" } pre15_record_fixture() { pre15_fixture_label=$1 pre15_fixture_path=$2 test -f "$pre15_fixture_path" || pre15_runner_fail \ "fixture is absent: $pre15_fixture_path" pre15_fixture_hash=$(sha256sum "$pre15_fixture_path" | awk '{print $1}') printf '%s\t%s\t%s\n' \ "$pre15_fixture_hash" "$pre15_fixture_label" "$pre15_fixture_path" \ >> "$PRE15_FIXTURE_HASHES" } pre15_record_module() { pre15_module_path=$1 test -f "$pre15_module_path" || pre15_runner_fail \ "module is absent: $pre15_module_path" pre15_module_digest=$(sha256sum "$pre15_module_path" | awk '{print $1}') printf '%s\t%s\n' "$pre15_module_digest" "$pre15_module_path" \ > "$PRE15_MODULE_HASH" } pre15_port_is_free() { python3 - "$1" <<'PY' import socket import sys port = int(sys.argv[1]) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(0.2) raise SystemExit(0 if sock.connect_ex(("127.0.0.1", port)) != 0 else 1) PY } pre15_guest_ssh() { : "${PRE15_QEMU_SSH_PORT:?SSH port is unavailable}" : "${PRE15_QEMU_SSH_USER:?SSH user is unavailable}" : "${PRE15_QEMU_SSH_KEY:?SSH key is unavailable}" ssh -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -i "$PRE15_QEMU_SSH_KEY" -p "$PRE15_QEMU_SSH_PORT" \ "$PRE15_QEMU_SSH_USER@127.0.0.1" "$@" } pre15_guest_ssh_bounded() { pre15_guest_deadline=${PRE15_GUEST_COMMAND_TIMEOUT:-60} case "$pre15_guest_deadline" in ''|*[!0-9]*|0) pre15_runner_fail "invalid guest command deadline: $pre15_guest_deadline" ;; esac timeout -k 5 "$pre15_guest_deadline" ssh \ -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -i "$PRE15_QEMU_SSH_KEY" -p "$PRE15_QEMU_SSH_PORT" \ "$PRE15_QEMU_SSH_USER@127.0.0.1" "$@" } pre15_cleanup_note() { printf '%s\n' "$*" >> "$PRE15_CLEANUP_LOG" } pre15_cleanup_fail() { PRE15_CLEANUP_STATUS_RESULT=FAIL pre15_cleanup_note "FAIL $*" } pre15_cleanup_pid() { pre15_cleanup_pid_value=$1 pre15_cleanup_pid_label=$2 if ! kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then pre15_cleanup_note "PASS pid already exited: $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)" return fi if ! kill -TERM "$pre15_cleanup_pid_value" 2>/dev/null; then pre15_cleanup_fail "could not TERM PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)" return fi pre15_cleanup_wait=0 while kill -0 "$pre15_cleanup_pid_value" 2>/dev/null && \ test "$pre15_cleanup_wait" -lt 50; do sleep 0.1 pre15_cleanup_wait=$((pre15_cleanup_wait + 1)) done if kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then if ! kill -KILL "$pre15_cleanup_pid_value" 2>/dev/null; then pre15_cleanup_fail "could not KILL PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)" return fi sleep 0.2 fi if kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then pre15_cleanup_fail "PID survived cleanup: $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)" else pre15_cleanup_note "PASS stopped PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)" fi } pre15_cleanup_guest() { pre15_guest_kind=$1 pre15_guest_value=$2 case "$pre15_guest_kind" in guest-mount) if pre15_guest_ssh_bounded umount "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then pre15_cleanup_note "PASS unmounted guest path $pre15_guest_value" else pre15_cleanup_fail "guest unmount failed: $pre15_guest_value" fi ;; guest-md) if pre15_guest_ssh_bounded mdconfig -d -u "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then pre15_cleanup_note "PASS detached guest md $pre15_guest_value" else pre15_cleanup_fail "guest md detach failed: $pre15_guest_value" fi ;; guest-kld) if pre15_guest_ssh_bounded kldunload "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then pre15_cleanup_note "PASS unloaded guest KLD $pre15_guest_value" else pre15_cleanup_fail "guest KLD unload failed: $pre15_guest_value" fi ;; esac } pre15_cleanup_owned() { PRE15_CLEANUP_STATUS_RESULT=PASS : > "$PRE15_CLEANUP_LOG" pre15_reverse_file=$PRE15_RUN_DIR/.ownership-reverse.$$ awk '{ line[NR] = $0 } END { for (i = NR; i > 0; --i) print line[i] }' \ "$PRE15_OWNERSHIP_FILE" > "$pre15_reverse_file" while IFS="$(printf '\t')" read -r pre15_kind pre15_value pre15_label; do test -n "$pre15_kind" || continue case "$pre15_kind" in guest-mount|guest-md|guest-kld) if test -n "${PRE15_QEMU_SSH_PORT:-}"; then pre15_cleanup_guest "$pre15_kind" "$pre15_value" else pre15_cleanup_fail "guest resource has no SSH endpoint: $pre15_kind $pre15_value" fi ;; ssh-control) if test -S "$pre15_value"; then if timeout -k 5 "${PRE15_CLEANUP_COMMAND_TIMEOUT:-15}" \ ssh -S "$pre15_value" -O exit \ -p "${PRE15_QEMU_SSH_PORT:-22}" \ "${PRE15_QEMU_SSH_USER:-root}@127.0.0.1" \ >> "$PRE15_CLEANUP_LOG" 2>&1; then pre15_cleanup_note "PASS closed SSH control $pre15_value" else pre15_cleanup_fail "could not close SSH control $pre15_value" fi fi if test -e "$pre15_value" && ! rm -f -- "$pre15_value"; then pre15_cleanup_fail "could not remove SSH control $pre15_value" fi ;; pid) pre15_cleanup_pid "$pre15_value" "$pre15_label" ;; port) if pre15_port_is_free "$pre15_value"; then pre15_cleanup_note "PASS port is free: $pre15_value" else pre15_cleanup_fail "port remains in use: $pre15_value" fi ;; path) case "$pre15_value" in "$PRE15_RUN_DIR"/*) if test -e "$pre15_value" && ! rm -rf -- "$pre15_value"; then pre15_cleanup_fail "could not remove owned path $pre15_value" else pre15_cleanup_note "PASS removed owned path $pre15_value" fi ;; *) pre15_cleanup_fail "refused to remove path outside run directory: $pre15_value" ;; esac ;; base-image) if test ! -f "$pre15_value"; then pre15_cleanup_fail "base image disappeared: $pre15_value" else pre15_cleanup_base_hash=$(sha256sum "$pre15_value" | awk '{print $1}') if test "$pre15_cleanup_base_hash" = "$pre15_label"; then pre15_cleanup_note "PASS base image unchanged: $pre15_value" else pre15_cleanup_fail "base image hash changed: $pre15_value" fi fi ;; *) pre15_cleanup_fail "unknown ownership kind: $pre15_kind" ;; esac done < "$pre15_reverse_file" rm -f -- "$pre15_reverse_file" if grep -q '^FAIL ' "$PRE15_CLEANUP_LOG"; then PRE15_CLEANUP_STATUS_RESULT=FAIL fi } pre15_reason_text() { if test -s "$PRE15_REASON_FILE"; then head -n 1 "$PRE15_REASON_FILE" else printf '%s\n' "$1" fi } pre15_classify() { pre15_classify_rc=$1 pre15_classify_target=$2 pre15_classify_cleanup=$3 PRE15_TIMED_OUT_RESULT=0 if test "$pre15_classify_cleanup" != PASS; then PRE15_STATUS_RESULT=RUNNER_FAIL PRE15_ORIGIN_RESULT=runner PRE15_REASON_RESULT='owned resource cleanup failed' return fi case "$pre15_classify_rc" in 0) if test "$pre15_classify_target" = reached; then PRE15_STATUS_RESULT=PASS PRE15_ORIGIN_RESULT=none PRE15_REASON_RESULT='target reached and all oracles passed' else PRE15_STATUS_RESULT=RUNNER_FAIL PRE15_ORIGIN_RESULT=runner PRE15_REASON_RESULT='command exited zero without a target marker' fi ;; "$PRE15_RC_DUT_FAIL") if test "$pre15_classify_target" = reached; then PRE15_STATUS_RESULT=DUT_FAIL PRE15_ORIGIN_RESULT=dut PRE15_REASON_RESULT=$(pre15_reason_text 'DUT oracle mismatch') else PRE15_STATUS_RESULT=RUNNER_FAIL PRE15_ORIGIN_RESULT=runner PRE15_REASON_RESULT='DUT_FAIL was requested before the target marker' fi ;; "$PRE15_RC_INFRA_BLOCKED") PRE15_STATUS_RESULT=INFRA_BLOCKED PRE15_ORIGIN_RESULT=infrastructure PRE15_REASON_RESULT=$(pre15_reason_text 'infrastructure prerequisite failed') ;; "$PRE15_RC_STOP") PRE15_STATUS_RESULT=STOP PRE15_ORIGIN_RESULT=gate PRE15_REASON_RESULT=$(pre15_reason_text 'gate oracle returned STOP') ;; 124|137) PRE15_STATUS_RESULT=RUNNER_FAIL PRE15_ORIGIN_RESULT=runner PRE15_REASON_RESULT="hard timeout after ${PRE15_DEADLINE}s" PRE15_TIMED_OUT_RESULT=1 ;; *) PRE15_STATUS_RESULT=RUNNER_FAIL PRE15_ORIGIN_RESULT=runner PRE15_REASON_RESULT=$(pre15_reason_text "command exited $pre15_classify_rc") ;; esac } pre15_write_identity() { PRE15_DUT_HEAD=$(git -C "$PRE15_ROOT" rev-parse HEAD) PRE15_DUT_TREE=$(git -C "$PRE15_ROOT" rev-parse HEAD:repo-pre-15) PRE15_LINUX_SOURCE=$(git -C "$PRE15_ROOT" rev-parse HEAD:src-linux) pre15_freebsd_git_root=$(git -C "$PRE15_FREEBSD_SRC" \ rev-parse --show-toplevel 2>/dev/null || true) if test -n "$pre15_freebsd_git_root" && \ test "$(CDPATH= cd -- "$pre15_freebsd_git_root" && pwd -P)" = \ "$(CDPATH= cd -- "$PRE15_FREEBSD_SRC" && pwd -P)"; then pre15_freebsd_head=$(git -C "$PRE15_FREEBSD_SRC" rev-parse HEAD) pre15_freebsd_dirty=$(git -C "$PRE15_FREEBSD_SRC" status --short | sha256sum | awk '{print $1}') PRE15_FREEBSD_SOURCE="$pre15_freebsd_head dirty-sha256:$pre15_freebsd_dirty" else pre15_freebsd_metadata=$(pre15_tree_metadata_sha256 "$PRE15_FREEBSD_SRC") if test -n "${PRE15_FREEBSD_TREE_SHA256:-}" || \ test -n "${PRE15_FREEBSD_TREE_METADATA_SHA256:-}"; then case "${PRE15_FREEBSD_TREE_SHA256:-}" in ????????????????????????????????????????????????????????????????) case "$PRE15_FREEBSD_TREE_SHA256" in *[!0-9a-f]*) pre15_fail_usage 'cached FreeBSD tree SHA256 is invalid' ;; esac ;; *) pre15_fail_usage 'cached FreeBSD tree SHA256 is invalid' ;; esac case "${PRE15_FREEBSD_TREE_METADATA_SHA256:-}" in ????????????????????????????????????????????????????????????????) case "$PRE15_FREEBSD_TREE_METADATA_SHA256" in *[!0-9a-f]*) pre15_fail_usage 'cached FreeBSD metadata SHA256 is invalid' ;; esac ;; *) pre15_fail_usage 'cached FreeBSD metadata SHA256 is invalid' ;; esac test "$pre15_freebsd_metadata" = \ "$PRE15_FREEBSD_TREE_METADATA_SHA256" || \ pre15_fail_usage 'FreeBSD source changed after its content snapshot' pre15_freebsd_tree=$PRE15_FREEBSD_TREE_SHA256 else pre15_freebsd_tree=$(pre15_tree_sha256 "$PRE15_FREEBSD_SRC") fi PRE15_FREEBSD_SOURCE="tree-sha256:$pre15_freebsd_tree metadata-sha256:$pre15_freebsd_metadata non-git-snapshot" fi PRE15_WORKTREE_DIFF_SHA256=$(git -C "$PRE15_ROOT" diff --binary HEAD -- repo-pre-15 | sha256sum | awk '{print $1}') export PRE15_DUT_HEAD PRE15_DUT_TREE PRE15_LINUX_SOURCE PRE15_FREEBSD_SOURCE export PRE15_WORKTREE_DIFF_SHA256 PRE15_IDENTITY_HOST=$(uname -a) PRE15_IDENTITY_GIT_STATUS=$(git -C "$PRE15_ROOT" status --short | sha256sum | awk '{print $1}') export PRE15_IDENTITY_HOST PRE15_IDENTITY_GIT_STATUS python3 - "$PRE15_IDENTITY_PATH" <<'PY' import json import os from pathlib import Path import platform import sys identity = { "host_uname": os.environ["PRE15_IDENTITY_HOST"], "python": platform.python_version(), "git_status_sha256": os.environ["PRE15_IDENTITY_GIT_STATUS"], "freebsd_source_path": os.environ["PRE15_FREEBSD_SRC"], "freebsd_source_identity": os.environ["PRE15_FREEBSD_SOURCE"], } with Path(sys.argv[1]).open("x", encoding="ascii") as output: json.dump(identity, output, ensure_ascii=True, indent=2, sort_keys=True) output.write("\n") PY } pre15_new_run() { umask 077 mkdir -p "$PRE15_EVIDENCE_ROOT" pre15_run_stamp=$(date -u '+%Y%m%dT%H%M%SZ') pre15_run_attempt=0 while :; do PRE15_RUN_ID="$pre15_run_stamp-$PRE15_MODE-$PRE15_CASE_ID-$$-$pre15_run_attempt" PRE15_RUN_DIR=$PRE15_EVIDENCE_ROOT/$PRE15_RUN_ID if mkdir "$PRE15_RUN_DIR" 2>/dev/null; then break fi pre15_run_attempt=$((pre15_run_attempt + 1)) test "$pre15_run_attempt" -lt 100 || pre15_fail_usage \ "could not allocate immutable run directory" done PRE15_STDOUT_PATH=$PRE15_RUN_DIR/stdout.log PRE15_STDERR_PATH=$PRE15_RUN_DIR/stderr.log PRE15_TARGET_MARKER_FILE=$PRE15_RUN_DIR/target.marker PRE15_REASON_FILE=$PRE15_RUN_DIR/reason.txt PRE15_OWNERSHIP_FILE=$PRE15_RUN_DIR/ownership.tsv PRE15_CLEANUP_LOG=$PRE15_RUN_DIR/cleanup.log PRE15_FIXTURE_HASHES=$PRE15_RUN_DIR/fixture-hashes.tsv PRE15_MODULE_HASH=$PRE15_RUN_DIR/module-hash.tsv PRE15_ARGV_PATH=$PRE15_RUN_DIR/command-argv.json PRE15_IDENTITY_PATH=$PRE15_RUN_DIR/identity.json PRE15_MANIFEST_PATH=$PRE15_RUN_DIR/manifest.json PRE15_CASE_TMP=$PRE15_RUN_DIR/case-tmp : > "$PRE15_STDOUT_PATH" : > "$PRE15_STDERR_PATH" : > "$PRE15_OWNERSHIP_FILE" : > "$PRE15_CLEANUP_LOG" : > "$PRE15_FIXTURE_HASHES" : > "$PRE15_MODULE_HASH" mkdir "$PRE15_CASE_TMP" pre15_own_path "$PRE15_CASE_TMP" 'case temporary directory' export PRE15_RUN_ID PRE15_RUN_DIR PRE15_STDOUT_PATH PRE15_STDERR_PATH export PRE15_TARGET_MARKER_FILE PRE15_REASON_FILE PRE15_OWNERSHIP_FILE export PRE15_CLEANUP_LOG PRE15_FIXTURE_HASHES PRE15_MODULE_HASH export PRE15_ARGV_PATH PRE15_IDENTITY_PATH PRE15_MANIFEST_PATH PRE15_CASE_TMP } pre15_freeze_run() { find "$PRE15_RUN_DIR" -type f -exec chmod 0444 {} + find "$PRE15_RUN_DIR" -type d -exec chmod 0555 {} + } pre15_parent_signal() { pre15_cleanup_owned exit "$PRE15_RC_RUNNER_FAIL" } pre15_run_command() { pre15_require_paths PRE15_MODE=$1 PRE15_CASE_ID=$2 PRE15_DEADLINE=$3 shift 3 case "$PRE15_CASE_ID" in ''|*[!A-Za-z0-9._-]*) pre15_fail_usage "invalid case ID: $PRE15_CASE_ID" ;; esac case "$PRE15_DEADLINE" in ''|*[!0-9]*|0) pre15_fail_usage "invalid deadline: $PRE15_DEADLINE" ;; esac command -v timeout >/dev/null 2>&1 || pre15_fail_usage 'timeout is required' command -v python3 >/dev/null 2>&1 || pre15_fail_usage 'python3 is required' test -f "$PRE15_SCHEMA" || pre15_fail_usage "schema is absent: $PRE15_SCHEMA" pre15_new_run pre15_write_identity pre15_write_argv_json "$PRE15_ARGV_PATH" "$@" PRE15_START_UTC=$(pre15_utc_now) export PRE15_MODE PRE15_CASE_ID PRE15_DEADLINE PRE15_START_UTC trap pre15_parent_signal HUP INT TERM if timeout -k "${PRE15_TIMEOUT_KILL_AFTER:-10}" "$PRE15_DEADLINE" "$@" \ > "$PRE15_STDOUT_PATH" 2> "$PRE15_STDERR_PATH"; then PRE15_EXIT_CODE=0 else PRE15_EXIT_CODE=$? fi trap - HUP INT TERM if test -f "$PRE15_TARGET_MARKER_FILE" && \ test "$(cat "$PRE15_TARGET_MARKER_FILE")" = reached; then PRE15_TARGET_STATE=reached else PRE15_TARGET_STATE=not_reached fi pre15_cleanup_owned PRE15_CLEANUP_STATUS=$PRE15_CLEANUP_STATUS_RESULT pre15_classify "$PRE15_EXIT_CODE" "$PRE15_TARGET_STATE" "$PRE15_CLEANUP_STATUS" PRE15_STATUS=$PRE15_STATUS_RESULT PRE15_FAILURE_ORIGIN=$PRE15_ORIGIN_RESULT PRE15_REASON=$PRE15_REASON_RESULT PRE15_TIMED_OUT=$PRE15_TIMED_OUT_RESULT PRE15_END_UTC=$(pre15_utc_now) export PRE15_EXIT_CODE PRE15_TARGET_STATE PRE15_CLEANUP_STATUS PRE15_STATUS export PRE15_FAILURE_ORIGIN PRE15_REASON PRE15_TIMED_OUT PRE15_END_UTC pre15_write_manifest pre15_validate_manifest "$PRE15_MANIFEST_PATH" "$PRE15_SCHEMA" printf 'PRE15_RESULT status=%s cleanup=%s run_id=%s evidence=%s\n' \ "$PRE15_STATUS" "$PRE15_CLEANUP_STATUS" "$PRE15_RUN_ID" \ "$PRE15_MANIFEST_PATH" pre15_freeze_run case "$PRE15_STATUS" in PASS) return 0 ;; DUT_FAIL) return "$PRE15_RC_DUT_FAIL" ;; INFRA_BLOCKED) return "$PRE15_RC_INFRA_BLOCKED" ;; STOP) return "$PRE15_RC_STOP" ;; *) return "$PRE15_RC_RUNNER_FAIL" ;; esac } pre15_control_run() { pre15_control_dir=$1 pre15_control_deadline=$2 shift 2 ( PRE15_RUN_DIR=$pre15_control_dir PRE15_TARGET_MARKER_FILE=$pre15_control_dir/target.marker PRE15_REASON_FILE=$pre15_control_dir/reason.txt PRE15_OWNERSHIP_FILE=$pre15_control_dir/ownership.tsv PRE15_CLEANUP_LOG=$pre15_control_dir/cleanup.log PRE15_DEADLINE=$pre15_control_deadline export PRE15_RUN_DIR PRE15_TARGET_MARKER_FILE PRE15_REASON_FILE export PRE15_OWNERSHIP_FILE PRE15_CLEANUP_LOG PRE15_DEADLINE mkdir "$pre15_control_dir" : > "$PRE15_OWNERSHIP_FILE" : > "$PRE15_CLEANUP_LOG" if timeout -k 2 "$pre15_control_deadline" "$@" \ > "$pre15_control_dir/stdout.log" \ 2> "$pre15_control_dir/stderr.log"; then pre15_control_rc=0 else pre15_control_rc=$? fi if test -f "$PRE15_TARGET_MARKER_FILE" && \ test "$(cat "$PRE15_TARGET_MARKER_FILE")" = reached; then pre15_control_target=reached else pre15_control_target=not_reached fi pre15_cleanup_owned pre15_classify "$pre15_control_rc" "$pre15_control_target" \ "$PRE15_CLEANUP_STATUS_RESULT" printf '%s\t%s\t%s\t%s\n' "$PRE15_STATUS_RESULT" \ "$PRE15_ORIGIN_RESULT" "$PRE15_CLEANUP_STATUS_RESULT" \ "$PRE15_TIMED_OUT_RESULT" > "$pre15_control_dir/result.tsv" ) } pre15_find_case() { pre15_case_id=$1 case "$pre15_case_id" in ''|*[!A-Za-z0-9._-]*) pre15_fail_usage "invalid case ID: $pre15_case_id" ;; esac pre15_case_path=$PRE15_DUT/tests/pre15/cases/$pre15_case_id.sh test -f "$pre15_case_path" || pre15_fail_usage "unknown case: $pre15_case_id" printf '%s\n' "$pre15_case_path" }