Package: runit
Version: 2.1.2-45
Severity: wishlist

Hi,

Back in 2012 I sent Gerrit a start-stop-daemon.runit script (see
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=678985) that could be used
as a drop-in replacement for the real start-stop-daemon. It's a fairly
feature complete wrapper around the real start-stop-daemon that manages
runit services if they are present and passes calls to the real
start-stop-daemon if they are not.

I dpkg-divert /sbin/start-stop-daemon to /sbin/start-stop-daemon.real on all
my systems and install this script as /sbin/start-stop-daemon.

This way, initscript work transparently whether a service is managed by
runit or not. Without this script, care must be taken to avoid the real
start-stop-daemon starting daemons like e.g. ntpd or smartd alongside a
runit-managed instance.

The script used to be included in runit under /usr/share/doc until Dmitry
removed in 2016 because it was "unused".

I think it can be useful even if the package doesn't use it explicitly, but
a case could be made for runit to perform this diversion on install and ship
my script as /sbin/start-stop-daemon.

The only drawback (that I can see) is that this would introduce a dependency
on zsh, because that's what I wrote the script in. Maybe Suggests: zsh, make
installing my script both optional and contingent on zsh being present? (Of
course, people could still shoot themselves in the foot by removing zsh
afterwards. Can dpkg run a trigger when zsh is uninstalled?)

I'm attaching the current version of the script, which contains some
improvements over the 2012 version.

András

-- 
          Government corruption is always reported in the past tense.
#!/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-6]S\.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

Reply via email to