752 lines
22 KiB
Bash
752 lines
22 KiB
Bash
#!/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"
|
|
}
|