File: //lib/runit/start-stop-daemon.runit
#!/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