#!/bin/zsh
#
# This script is intended to wrap start-stop-daemon. It will call the
# original start-stop-daemon with the supplied arguments unless the daemon
# to be started appears to exist as a runit service, in which case it will
# map the start-stop-daemon call to an sv(8) call.
#
# Copyright 2012-2022 András Korn.
#
# Licensed under the GPL v3, or, at your option, under the same license as
# the runit package.

# If called by non-root user, fall back to original start-stop-daemon
# unconditionally
[[ $UID -gt 0 ]] && exec /sbin/start-stop-daemon.real $@

set -A args $@

SVDIR=${SVDIR:-/etc/service}

unset mode signal exec timeout startas testmode oknodo quiet verbose command svstat candidates
oknodo=0
quiet=0

typeset -U candidates

while [[ -n "$1" ]]; do
	case "$1" in
		-S|--start)		mode=start;;
		-K|--stop)		mode=stop;;
		-T|--status)		mode=status;;
		-H|--help|-V|--version)	exec /sbin/start-stop-daemon.real $args;;
		-x|--exec)		shift; exec="$1";		candidates=($candidates ${1:t});;
		-s|--signal)		shift; signal=$1;;
		--signal=*)		signal="${1/--signal=/}";;
		-R|--retry)		shift; timeout="$1";;
		--retry=*)		timeout="${1/--retry=/}";;
		-a|--startas)		shift; startas="$1"		candidates=($acndidates ${1:t});;
		-t|--test)		testmode=1;;
		-o|--oknodo)		oknodo=1;;
		-q|--quiet)		quiet=1; exec >/dev/null;;
		-v|--verbose)		verbose=1;;
		-m|--make-pidfile)	make_pidfile=1;;
		--remove-pidfile)	remove_pidfile=1;;
		-n|--name)		shift; 				candidates=($candidates $1);;
		-p|--pidfile)		shift; pidfile="$1";		candidates=($candidates ${1:t:r});;
		--pidfile=*)		pidfile="${1#--pidfile=}";	candidates=($candidates ${1:t:r});;
		-u|--user|-g|--group|--pid|--ppid|-c|--chuid|-r|--chroot|-d|--chdir|-O|--output|-N|--nicelevel|-P|--procsched|-I|--iosched|-k|--umask) shift;; # ignored
		-b|--background|--nicelevel=*|--procsched=*|--iosched=*|--umask=*|-C|--no-close)	:;; # ignored
		--notify-wait)		echo "Warning: this version of start-stop-daemon.runit ignores --notify-wait." >&2;;
		--notify-timeout)	echo "Warning: this version of start-stop-daemon.runit ignores --notify-timeout." >&2;;
		--)			break;; # What follows is args to the daemon. Avoid parsing those accidentally.
		*)			command="$1"; break;; # Assume the previous was the last option; the rest is the name of the daemon plus args, of which we only care about the daemon.
	esac
	shift
done

# returns success if $1 appears to be the name of a runit service
function issvname() {
	[[ -d "$SVDIR/$1/supervise/." ]] && return 0
	# 'supervise' could still be a symlink to a directory that doesn't exist yet
	[[ -L $SVDIR/$1/supervise ]] && [[ ! -e $SVDIR/$1/supervise ]] && return 0
	return 1
}

# TODO: decide what to do if the runit service we're supposed to manage
# doesn't exist in the current svdir but does in other "runlevels"

# Try to infer runit service name. If our parent is an initscript, use its basename
foundsvname=0
read -A cmdline </proc/$PPID/cmdline
while [[ -n "$cmdline[1]" ]]; do
	[[ "${cmdline[1]:h}" = /etc/init.d ]] && svname=${cmdline[1]:t} && break
	if [[ "${cmdline[1]:h}" =~ /etc/rc[0-6S]\.d ]]; then
		svname=${cmdline[1]:t}
		svname=${svname#[SK][0-9][0-9]}
		break
	fi
	shift cmdline
done
[[ -z "$svname" ]] && [[ "${$(readlink -f /proc/$PPID/exe):h}" =~ init\.d ]] && svname=$(</proc/$PPID/comm)

# if not, try other heuristics
if [[ $foundsvname = 0 ]]; then
	candidates=($svname $command ${candidates:t} $startas $exec)
	while [[ -n "$candidates[1]" ]]; do
		if issvname ${candidates[1]:t}; then
			svname=${candidates[1]:t}
			foundsvname=1
			break
		else
			shift candidates
		fi
	done
fi

# if still not found, call real start-stop-daemon
if [[ "$foundsvname" = 0 ]]; then
	exec /sbin/start-stop-daemon.real $args
fi

# otherwise, do what we've been asked to
[[ "$quiet" = "0" ]] && [[ "$verbose" = "1" ]] && echo "start-stop-daemon.runit: will act on $svname service." >&2

function sendsig() {
	case "$signal" in
		HUP|1)		sv hup $svname;;
		INT|2)		sv interrupt $svname;;
		QUIT|3)		sv quit $svname;;
		KILL|9)		sv d $svname; sv kill $svname;;
		USR1|10)	sv 1 $svname;;
		USR2|12)	sv 2 $svname;;
		ALRM|14)	sv alarm $svname;;
		TERM|15)	sv down $svname;;
		CONT|18)	sv cont $svname;;
		STOP|19)	sv pause $svname;;
		*)		echo "$0: ERROR: don't know how to send $signal signal to $svname." >&2; exit 3;;
	esac
}

function wait_until_exited() {
	counter=0
	while [[ $counter -le $timeout ]]; do
		read svstat < $SVDIR/$svname/supervise/stat
		[[ "$svstat" = down ]] && return 0
		sleep 1
		((counter++))
	done
	return 1
}

function possibly_remove_pidfile_and_exit() {
	[[ $remove_pidfile = 1 ]] && [[ -n "$pidfile" ]] && rm "$pidfile"
	exit $1
}

function do_stop() {
	[[ $(<$SVDIR/$svname/supervise/stat) = down ]] || sv once $svname	# no point waiting for it to stop if runit always restarts it
	if [[ $timeout =~ / ]]; then
# handle complex schedule
		OLDIFS="$IFS"
		IFS=/
		echo $timeout | read -A schedule
		IFS="$OLDIFS"
		while [[ -n "$schedule[1]" ]]; do
			signal=$schedule[1]
			sendsig
			shift schedule
			timeout=$schedule[1]
			wait_until_exited && possibly_remove_pidfile_and_exit 0
			shift schedule
		done
		exit 2
	elif [[ -z "$signal" ]]; then	# simple timeout
		if [[ $timeout =~ ^[0-9]+$ ]]; then
			export SVWAIT=$timeout
		fi
		if sv stop $svname; then
			possibly_remove_pidfile_and_exit 0
		else
			possibly_remove_pidfile_and_exit 1
		fi
	else
		sendsig
		[[ -z "$timeout" ]] && exit 0
		if wait_until_exited; then
			possibly_remove_pidfile_and_exit 0
		else
			possibly_remove_pidfile_and_exit 1
		fi
	fi
}

svstat=$({sv status $SVDIR/$svname || echo none} | tail -n 1 | cut -d: -f1)

case "$mode" in
	start)
		[[ "$svstat" = run ]] && [[ "$oknodo" = "0" ]] && exit 1 # Emulate start-stop-daemon semantics
		[[ -z "$testmode" ]] && [[ ! "$svstat" = "none" ]] && sv start $svname
		# chrooted processes won't be able to read symlinked pidfile; maybe we should copy it? But how to avoid race conditions?
		[[ $make_pidfile = 1 ]] && [[ -n "$pidfile" ]] && ln -sf $SVDIR/$svname/supervise/pid "$pidfile"
		exit 0
		;;
	stop)
		[[ "$svstat" = none ]] && exit 0
		[[ "$svstat" = down ]] && [[ "$oknodo" = "1" ]] && exit 1 # Emulate start-stop-daemon semantics
		[[ -z "$testmode" ]] && do_stop # handles --retry and --signal, therefore separate function
		;;
	status)
		case "$svstat" in
# States are complex; we only handle the most basic cases here and bail on
# the rest (e.g. "finish" cannot be correctly reported as "running" or "not
# running")
			run)
				exit 0
				;;
			down|none)
				exit 3
				;;
			*)
				exit 4
				;;
		esac
		;;
esac
exit 0
