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