134 lines
5.4 KiB
Plaintext
134 lines
5.4 KiB
Plaintext
# row253-supervisor-begin
|
|
IFS= read -r -d '' TESTER_SUPERVISOR <<'MOSAIC_TESTER_SUPERVISOR' || :
|
|
set +m
|
|
seconds="$1" setsid_bin="$2" sleep_bin="$3"; shift 3
|
|
child="" timer="" verified=0
|
|
stat_fields() {
|
|
local pid="$1" raw="" rest
|
|
# EOF is expected: stat has no NUL. Decide on content, not read's status.
|
|
IFS= read -r -d '' raw < "/proc/$pid/stat" 2>/dev/null || :
|
|
[ -n "$raw" ] || { [ -e "/proc/$pid/stat" ] && return 1; return 3; }
|
|
rest="${raw##*) }"
|
|
[ "$rest" != "$raw" ] || return 1
|
|
read -r ST_STATE ST_PARENT ST_GROUP ST_SESSION _ <<< "$rest"
|
|
[[ "$ST_STATE" =~ ^[A-Za-z]$ && "$ST_SESSION" =~ ^[0-9]+$ && "$ST_GROUP" =~ ^[0-9]+$ ]]
|
|
}
|
|
clock_cs() {
|
|
local boot spare
|
|
read -r boot spare < /proc/uptime || return 1
|
|
[[ "$boot" =~ ^([0-9]+)\.([0-9]{2})$ ]] || return 1
|
|
CLOCK_CS=$((10#${BASH_REMATCH[1]} * 100 + 10#${BASH_REMATCH[2]}))
|
|
}
|
|
clean_session() {
|
|
local entry pid rc live until=""
|
|
while :; do
|
|
live=0
|
|
for entry in /proc/[0-9]*/stat; do
|
|
pid="${entry%/stat}"; pid="${pid##*/}"
|
|
rc=0; stat_fields "$pid" || rc=$?
|
|
[ "$rc" != 3 ] || continue
|
|
[ "$rc" = 0 ] || return 1
|
|
[ "$ST_SESSION" = "$child" ] || continue
|
|
case "$ST_STATE" in Z|X) continue ;; esac
|
|
live=1
|
|
if ! kill -KILL "$pid" 2>/dev/null; then
|
|
rc=0; stat_fields "$pid" || rc=$?
|
|
[ "$rc" = 3 ] || { [ "$rc" = 0 ] && { [ "$ST_STATE" = Z ] || [ "$ST_STATE" = X ]; }; } || return 1
|
|
fi
|
|
done
|
|
[ "$live" != 0 ] || return 0
|
|
clock_cs || return 1
|
|
# Inventory pass 1 is outside the bounded confirmation/retry budget.
|
|
[ -n "$until" ] || until=$((CLOCK_CS + 25))
|
|
[ "$CLOCK_CS" -lt "$until" ] || return 1
|
|
"$sleep_bin" 0.01
|
|
done
|
|
}
|
|
finish() {
|
|
local rc="$?"
|
|
trap - EXIT HUP INT TERM
|
|
if [ "$verified" = 1 ]; then
|
|
clean_session || rc=86
|
|
elif [ -n "$child" ]; then
|
|
kill -KILL "$child" 2>/dev/null || :
|
|
fi
|
|
[ -z "$child" ] || wait "$child" 2>/dev/null || :
|
|
if [ -n "$timer" ]; then kill -KILL "$timer" 2>/dev/null || :; wait "$timer" 2>/dev/null || :; fi
|
|
exit "$rc"
|
|
}
|
|
trap finish EXIT
|
|
trap 'exit 88' HUP INT TERM
|
|
"$sleep_bin" "$seconds" & timer=$!
|
|
"$setsid_bin" "$@" & child=$!
|
|
# Allow session creation the whole inner budget, rather than a 200 ms margin.
|
|
while :; do
|
|
rc=0; stat_fields "$child" || rc=$?
|
|
[ "$rc" = 0 ] || exit 85
|
|
if [ "$ST_SESSION" = "$child" ]; then verified=1; break; fi
|
|
kill -0 "$timer" 2>/dev/null || exit 81
|
|
"$sleep_bin" 0.002
|
|
done
|
|
[ "$verified" = 1 ] || exit 85
|
|
while kill -0 "$child" 2>/dev/null; do
|
|
kill -0 "$timer" 2>/dev/null || exit 81
|
|
"$sleep_bin" 0.01
|
|
done
|
|
kill -0 "$timer" 2>/dev/null || exit 81
|
|
rc=0; wait "$child" || rc=$?
|
|
[ "$rc" = 0 ] || exit 87
|
|
exit 0
|
|
MOSAIC_TESTER_SUPERVISOR
|
|
# row253-supervisor-end
|
|
|
|
tester_tools() {
|
|
local input target p st magic
|
|
TS_BASH="" TS_SETSID="" TS_SLEEP="" TS_TIMEOUT="" TS_HEAD=""
|
|
for input in /bin/bash /usr/bin/setsid /usr/bin/sleep /usr/bin/timeout /usr/bin/head; do
|
|
target="$(realpath -e -- "$input" 2>/dev/null)" || return 1
|
|
case "$target" in /home/*|/root/*|/run/user/*) return 1 ;; esac
|
|
[ -f "$target" ] && [ -x "$target" ] && [ ! -L "$target" ] || return 1
|
|
p="$target"
|
|
while :; do
|
|
st="$(stat -c '%u %a' -- "$p" 2>/dev/null)" || return 1
|
|
[ "${st% *}" = 0 ] && [[ "${st#* }" =~ ^[0-7]{3,4}$ ]] && (( (8#${st#* } & 8#022) == 0 )) || return 1
|
|
[ "$p" != / ] || break
|
|
p="${p%/*}"; [ -n "$p" ] || p=/
|
|
done
|
|
magic="$(/usr/bin/od -An -tx1 -N4 -- "$target" 2>/dev/null | tr -d ' \n')"
|
|
[ "$magic" = 7f454c46 ] || return 1
|
|
case "$input" in
|
|
/bin/bash) TS_BASH="$target" ;; /usr/bin/setsid) TS_SETSID="$target" ;;
|
|
/usr/bin/sleep) TS_SLEEP="$target" ;; /usr/bin/timeout) TS_TIMEOUT="$target" ;;
|
|
/usr/bin/head) TS_HEAD="$target" ;;
|
|
esac
|
|
done
|
|
}
|
|
tester_capture_reason() {
|
|
case "$1" in
|
|
81) echo inner-timeout ;; 82) echo outer-timeout ;; 83) echo output-limit ;;
|
|
84) echo supervisor-tools ;; 85) echo inner-session ;; 86) echo inner-cleanup ;;
|
|
*) echo inner-command ;;
|
|
esac
|
|
}
|
|
tester_capture() (
|
|
local inner="$1" outer="$2" maximum="$3" user="$4" home="$5" path="$6" file notice child="" rc=0; shift 6
|
|
tester_tools || exit 84
|
|
file="$(mktemp)" || exit 88
|
|
notice="$(mktemp)" || { rm -f -- "$file"; exit 88; }
|
|
trap 'if [ -n "$child" ]; then kill -KILL -- "-$child" 2>/dev/null || :; wait "$child" 2>/dev/null || :; fi; rm -f -- "$file" "$notice"' EXIT
|
|
trap 'exit 88' HUP INT TERM
|
|
LC_ALL=C "$TS_TIMEOUT" --verbose --signal=KILL "${outer}s" "$TS_BASH" -c '
|
|
set -o pipefail
|
|
maximum="$1"; head_bin="$2"; shift 2
|
|
"$@" 2>/dev/null | "$head_bin" -c "$((maximum + 1))"
|
|
' -- "$maximum" "$TS_HEAD" sudo -u "$user" -H env -i "HOME=$home" "PATH=$path" "$TS_BASH" -c "$TESTER_SUPERVISOR" -- "$inner" "$TS_SETSID" "$TS_SLEEP" "$@" > "$file" 2> "$notice" &
|
|
child=$!
|
|
wait "$child" 2>/dev/null || rc=$?
|
|
kill -KILL -- "-$child" 2>/dev/null || :
|
|
# Only GNU timeout and the trusted head can write this root-side channel.
|
|
if [ "$rc" = 137 ] && [[ "$(<"$notice")" == *': sending signal KILL to command '* ]]; then exit 82; fi
|
|
[ "$(wc -c < "$file")" -le "$maximum" ] || exit 83
|
|
[ "$rc" = 0 ] || { case "$rc" in 81|85|86|87|88) exit "$rc" ;; *) exit 87 ;; esac; }
|
|
cat -- "$file"
|
|
)
|