#! /bin/sh set -e case $# in 2) ;; *) echo >&2 "usage: $0 {start|stop|restart|status} HOST"; exit 1 ;; esac op=$1 host=$2 writefile () { file=$1; shift echo "$*" >"$file.new" mv "$file.new" "$file" } runssh () { ssh -T -oControlPath="./$host.ctrl" "$@"; } clobber () { ## Shut down an existing connection if there is one. if [ -S "$host.ctrl" ]; then runssh -Oexit "$host" >/dev/null 2>/dev/null || : fi ## If there's still a socket, then work out what to do. if [ -e "$host.ctrl" ]; then ## If the connection's still running then we have a problem. if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then echo >&2 "$0: failed to kill existing connection to $host" exit 2 fi ## Remove the stale socket. rm -f "$host.ctrl" fi } stopit () { ## Initial shutdown protocol. writefile "$host.state" stopping if [ -f "$host.pid" ]; then kill $(cat "$host.pid") 2>/dev/null || :; fi rm -f "$host.pid" ## Clobber the existing connection, if there is one. clobber ## Update the state. rm -f "$host.state" "$host.pid" } daemon () { ## There doesn't seem to be a better way of getting this. :-( read pid <"$host.daemonpipe" rm -f "$host.daemonpipe" ## Set up shop. trap 'rm -f "$host.pid"; stopit' EXIT INT TERM writefile "$host.pid" "$pid" ## Initial delay. delay=0 ## Not waiting on a pipe yet. kidcat=nil ## Keep the connection up for as long as we can. while [ -f "$host.pid" ]; do ## Maybe back off before trying another connection. case $delay in 0) delay=1 ;; *) writefile "$host.state" \ "wait until $(date -d+${delay}sec +"%Y-%m-%d %H:%M:%S %z")" sleep $delay delay=$(( 2*$delay )) if [ $delay -gt 120 ]; then delay=120; fi ;; esac ## Prepare a pipe so that we can wait for SSH to finish. This is a ## rotten hack. case $kidcat in nil) ;; *) kill $kidcat >/dev/null 2>&1 || :; kidcat=nil ;; esac rm -f "$host.pipe"; mkfifo -m600 "$host.pipe" cat $host.pipe >/dev/null& kidcat=$! ## Start a new connection. writefile "$host.state" starting if ! runssh -MNnf "$host" >"$host.pipe"; then continue; fi if ! runssh -Ocheck "$host" >/dev/null 2>&1; then echo "connection to $host apparently stillborn" continue fi writefile "$host.state" connected delay=0 ## Wait until it gets torn down. wait $kidcat >/dev/null 2>&1 || : rm -f "$host.pipe" clobber writefile "$host.state" disconnected done } startit () { ## If there's already a connection then we have nothing to do. if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then echo >&2 "$0: already connected to $host" exit 0 fi ## Start a daemon which makes connections for us. This is remarkably ## tricky. rm -f "$host.daemonpipe"; mkfifo -m600 "$host.daemonpipe" { daemon& echo $! >"$host.daemonpipe"; } 2>&1 | logger -pdaemon.notice& } ## Main dispatch. case "$op" in start) startit ;; stop) stopit ;; restart) stopit; startit ;; status) if [ -f "$host.state" ] then cat "$host.state" else echo "down" fi ;; *) echo >&2 "usage: $0 {start|stop|restart|status} HOST" exit 1 ;; esac