pmacs/scripts/check-sigint-deliverable

64 lines
2.6 KiB
Bash
Executable File

#!/bin/sh
# Is SIGINT deliverable to this process tree?
#
# WHY THIS EXISTS. A shell running a command in the background without
# job control sets SIGINT (and SIGQUIT) to SIG_IGN in the child; nohup
# adds SIGHUP. SIG_IGN is inherited across fork AND survives exec, so
# the disposition reaches every descendant --- cargo, a test binary, and
# anything either of them spawns. Tests that signal a child then wait
# for it to die will hang until their own deadline and report that as a
# teardown defect. One lane spent nine framing revisions on exactly that
# misreading; see docs/gpu-probe-sigint-framing.md §4c.
#
# INTERFACE (docs/gpu-probe-sigint-framing.md §7c) --- a validated
# (status, token) PAIR. Callers rely on both halves and MUST NOT
# re-derive the classification:
#
# 0 safe stdout: pmacs-sigint-v1:safe no diagnostic
# 1 ignored stdout: pmacs-sigint-v1:ignored canonical diagnostic
# 2 error stdout: pmacs-sigint-v1:error distinct diagnostic
#
# The token is the ONLY thing on stdout; diagnostics go to stderr. Any
# other pair --- including a plausible status with no token --- is a
# BOUNDARY error for the caller, mapped to 2.
#
# WHY A TOKEN AND NOT A STATUS ALONE. CI proved a status-only ABI
# unsound: on macOS a shell that cannot execute this file exits 1,
# which the old ABI read as `ignored`, so a broken guard told the
# operator their environment ignores SIGINT. No exit status can prove
# this script ran; a token it must have printed can.
#
# `error` is never folded into `ignored`. "Your environment ignores
# SIGINT" and "the guard could not run" are different problems, and
# conflating them fails callers for the wrong reason.
#
# THE PROBE. A child sends itself SIGINT. Deliverable => the trap runs
# => 23. Ignored => the kill is a no-op => the child falls through to
# `exit 0`. The `|| exit 24` arms matter: without them a FAILED kill
# would also fall through to `exit 0` and be misread as `ignored`,
# which is the one wrong answer this helper exists to avoid.
#
# POSIX shell only --- trap, kill, $$ --- so no /proc and no sigaction:
# the mechanism is not Linux-specific and adds no unsafe code.
probe_status=0
sh -c 'trap "exit 23" 2 || exit 24; kill -INT "$$" || exit 24; exit 0' \
|| probe_status=$?
case "$probe_status" in
23)
echo 'pmacs-sigint-v1:safe'
exit 0
;;
0)
echo 'pmacs-sigint-v1:ignored'
echo 'pmacs: SIGINT is ignored; run this command with SIGINT deliverable' >&2
exit 1
;;
*)
echo 'pmacs-sigint-v1:error'
echo "pmacs: could not determine whether SIGINT is deliverable (probe status $probe_status)" >&2
exit 2
;;
esac