#!/bin/sh
# Success hull for s6-rc oneshots, see svctl(8):
#   hull [-h] [-T ms] [-c container|host|!container] UNIT PROG [ARGS...]
# Runs PROG. A failure is recorded in /run/svctl/failed/UNIT ("exit N",
# "signal N", "timeout", "terminated") and reported as success, with -h passed
# on. An unmet condition records /run/svctl/skipped/UNIT and succeeds. Success
# of an up script removes old records; a down script keeps a failure record,
# so that a stopped failed unit stays failed.
set -u

RUNDIR=/run/svctl
INIT_ENV=/run/init_env

usage() {
	echo "usage: hull [-h] [-T ms] [-c container|host|!container] UNIT PROG [ARGS...]" >&2
	exit 100
}

hard=0 limit=0 cond=''
while getopts hT:c: opt; do
	case $opt in
	h) hard=1 ;;
	T) limit=$OPTARG ;;
	c) cond=$OPTARG ;;
	*) usage ;;
	esac
done
shift $((OPTIND - 1))
[ $# -ge 2 ] || usage
case $limit in '' | *[!0-9]*) usage ;; esac
unit=$1
shift
case $unit in '' | .* | */*) usage ;; esac

record() { # DIR TEXT
	mkdir -p "$RUNDIR/$1"
	printf '%s\n' "$2" >"$RUNDIR/$1/.$unit.$$" && mv -f "$RUNDIR/$1/.$unit.$$" "$RUNDIR/$1/$unit"
	echo "hull: $unit: $2" >&2
}

if [ -n "$cond" ]; then
	container=0
	[ -s "$INIT_ENV/container" ] && container=1
	case $cond:$container in
	container:1 | host:0 | '!container:0') ;;
	container:0 | host:1 | '!container:1') record skipped "$cond"; exit 0 ;;
	*) usage ;;
	esac
fi
rm -f "$RUNDIR/skipped/$unit"

# PROG runs in the background so that SIGTERM (time limit of s6-rc) is
# handled at once; fd 3 hands it the original stdin
trap 'kill -TERM "$child" 2>/dev/null; record failed terminated; exit 143' TERM
exec 3<&0
if [ "$limit" -gt 0 ]; then
	timeout -s TERM "$((limit / 1000)).$(printf '%03d' $((limit % 1000)))" "$@" <&3 3<&- &
else
	"$@" <&3 3<&- &
fi
child=$!
exec 3<&-
wait "$child"
rc=$?
trap - TERM

if [ "$rc" = 0 ]; then
	# s6-rc lists the unit as up while its down script runs, and marks the
	# change only after the script has ended
	if ! s6-rc -l /run/s6-rc --no-lock -a list 2>/dev/null | grep -qxF -- "$unit"; then
		rm -f "$RUNDIR/failed/$unit"
	fi
	exit 0
fi
if [ "$limit" -gt 0 ] && [ "$rc" = 124 ]; then
	record failed timeout
elif [ "$rc" -gt 128 ]; then
	record failed "signal $((rc - 128))"
else
	record failed "exit $rc"
fi
[ "$hard" = 1 ] && exit "$rc"
exit 0
