#! /bin/sh

#set -x
CONFIG=${NBD_CONFIG-/etc/enbd.conf}
[ ! -r $CONFIG ] && exit 0

export PATH=/sbin:/usr/sbin:/usr/local/sbin:/usr/bin:/bin:/usr/local/bin

SERVERNAME=enbd-server
CLIENTNAME=enbd-client
SSTATDNAME=enbd-sstatd
CSTATDNAME=enbd-cstatd

MODULENAME=enbd
PROCFLNAME=nbdinfo

cmd=eval
if [ "$DEBUG" != "" ] && [ "$DEBUG" -gt 0 ] ; then
  cmd=echo
fi

#########################################################################
# look in path for a particular executable and return its full path
#
find_foo() {
   local foo="$1"
   IFS=':' ; set -- $PATH ; IFS=' '
   for i; do
     if [ -x "$i/$foo" ] ; then
       echo "$i/$foo"
       return 0
     fi
   done
   return 1
}

#########################################################################
# look for enbd-server in path
#
find_server() {
   find_foo $SERVERNAME
}
#########################################################################
# look for enbd-client in path
#
find_client() {
   find_foo $CLIENTNAME
}
#########################################################################
# see if a given host responds to pings. 
#
can_contact() {
   ping -c 1 $1 | grep -vqs "100% packet loss" && return 0
   return 1
}

#########################################################################
# replace a couple of minor funcs not in /bin.
#
mydirname() {
   echo $1 | sed -e 's,/[^/]*$,,'
}
mybasename() {
   echo $1 | sed -e 's,^.*/,,'
}
#########################################################################
# look at a given pidfile. Return OK if nonexistent or stale and
# put our pid in the pidfile.
# Return BAD if it contains a pid of a running process,
#
try_lock() {
   local pidfile="$1"
   if [ -s "$pidfile" ] ; then

      local pid="`head -1 $pidfile`"

      # grrr ... tr is not always available at boot, so can't trim easily

      for p in $pid ""; do
        pid=$p ; break 
      done

      case $pid in
        *[^0-9]*) echo bad pid $pid read from $pidfile && return 1 ;;
      esac

      if [ -n "$pid" ] && [ "$pid" -gt 0 ] ; then
	kill -0 "$pid" 2>&1 >/dev/null && return 1
      fi
   fi
   rm -f $1
   local mytmp="`mydirname $pidfile`/.try_lock_`mybasename $pidfile`.$$"
   touch $mytmp || return 1
   if ! echo $$ >> $mytmp ; then
     rm -f $mytmp
     return 1
   fi
   if ! ln $mytmp $pidfile; then
     rm -f $mytmp
     return 1
   fi
   rm -f $mytmp
   return 0
}
#########################################################################
# test that a given server is ready to receive our client
# Be quiet if all OK, otherwise make noise
#
client_ready() {
      if [ ! -e /proc/$PROCFLNAME ] ; then
        echo "module not loaded"
        return 1
      fi
      local client
      eval client="`find_client | head -1`"
      if [ -z "$client" ] ; then
        echo "$CLIENTNAME cannot be found locally"
        return 2
      fi
      local server="$1"
      if ! can_contact $server ; then
        echo "$server cannot be contacted"
        return 3
      fi
      return 0
}
#########################################################################
# separate process to launch a given enbd-client id
#
start_client() {
    local id=$1
    echo -n starting $CLIENTNAME $id
    shift
    try_lock /var/run/${CLIENTNAME}-$id.pid || return
    #IFS=':' ; set -- $2 ; IFS=' '
    local server="$1"
    local port="$2"
    shift 2
    local args="$*"
    local message="`client_ready $server $port`"
    if [ -n "$message" ]; then
      echo "$CLIENTNAME $id to server $server : start delayed because $message"
      sleep 30
      local oldmessage="$message"
      message="`client_ready $server $port`"
      while [ -n "$message" ]; do
        if [ "$oldmessage" != "$message" ]; then
          echo "$CLIENTNAME $id to server $server : start delayed because $message"
        fi
        sleep 30
        local oldmessage="$message"
        message="`client_ready $server $port`"
      done
    fi
    $cmd $CLIENTNAME ${server}:$port $args &
    # the executable will write this itself in >= 2.4.24
    #local pid=$!
    #$cmd "echo $pid > /var/run/${CLIENTNAME}-$id.pid"
    ## tell the server to restart its ${SERVERNAME}
    #$cmd telnet $server nbd
    echo done
}
#########################################################################
# how to do lookup and murder via a pidfile
#
stop_foo() {
    local pidfile=$1
    [ ! -s $pidfile ]  && return 1
    pid="`head -1 $pidfile`"
    for sig in USR1 TERM 9; do
      $cmd "kill -$sig $pid >/dev/null 2>&1"
      sleep 1
      $cmd "fuser -k -$sig $pidfile >/dev/null 2>&1"
      sleep 1
      if ! kill -0 $pid >/dev/null 2>&1 ; then
        #echo removing $pidfile because process $pid is dead
        #$cmd rm -f $pidfile
        return 0
      fi
    done
    return 1
}
#################################################################
# list our interface IP addresses
#
my_interface_ips() {
     /sbin/ifconfig \
   | grep 'inet addr:' \
   | sed -e 's/.*inet addr:\([0-9.][0-9.]*\).*/\1/'
}

#########################################################################
# separate process to stop a given enbd-client id
#
stop_client() {

    local id=$1
    local server=$2
    local port=$3

    local ipaddrs="`my_interface_ips`"

    echo -n stopping $CLIENTNAME $id
    if stop_foo /var/run/${CLIENTNAME}-$id.pid; then
       ( echo notice client-stop $port $ipaddrs; echo quit ) | socket $server $SSTATDNAME
       echo done
       return 
    fi
    echo failed
}
#########################################################################
# stop all known enbd-clients
#
stop_all_clients() {
    for pidfile in /var/run/${CLIENTNAME}-*.pid; do

       local id
       id=${pidfile#/var/run/${CLIENTNAME}-}
       id=${id%.pid}

       [ -e /var/run/${CLIENTNAME}-$id.pid ] || continue

       cat $CONFIG | grep -v '^[ ]*$' | while read line; do

         LINE=""
         for word in $line "#"; do
             case $word in
              [#]*) break;;
             esac
             LINE="$LINE $word"
         done
         line=$LINE

         echo "$line" | while read target client_id rest; do

	   [ client = "$target" ] || continue
	   [ "$client_id" = "$id" ] || continue

           echo "$rest" | while read device server port options; do
               stop_client $id $server $port
	   done
         done
       done
    done
}
#########################################################################
# try and see if the partition or file we want exists yet
#
resource_available() {
    for i; do
      [ ! -e $i ] && return 1 
      head -1c $i >/dev/null 2>&1 || return 2
    done
    return 0
}
#########################################################################
# test that a given client is ready to talk to our server
# Be quiet if all OK, otherwise make noise
#
server_ready() {
      local server
      eval server="`find_server | head -1`"
      if [ -z "$server" ] ; then
        echo "$SERVERNAME cannot be found locally"
        return 2
      fi
      for resource; do
        if ! resource_available $resource ; then
          echo "$resource is not available"
          return 3
        fi
      done
      return 0
}
#########################################################################
# separate process to start a given enbd-server id
#
start_server() {
    local id=$1
    local port=$2
    try_lock /var/run/${SERVERNAME}-$id.pid || return
    echo -n starting $SERVERNAME $id
    local resources="`IFS=','; set -- $3; IFS=' '; echo $*`" 
    shift 3
    options="$*"
    local message="`server_ready $resources`"
    if [ -n "$message" ]; then
      echo "$SERVERNAME $id : start delayed because $message"
      sleep 30
      local oldmessage="$message"
      message="`server_ready $resources`"
      while [ -n "$message" ]; do
        if [ "$oldmessage" != "$message" ]; then
          echo "$SERVERNAME $id : start delayed because $message"
        fi
        sleep 30
        local oldmessage="$message"
        message="`server_ready $resources`"
      done
    fi
    $cmd $SERVERNAME $port $resources $options &
    # the executable will write this itself in >= 2.4.24
    #local pid=$!
    #$cmd "echo $pid > /var/run/${SERVERNAME}-$id.pid"
    # the executable will do this itself in >= 2.4.24
    ## revise our connections and signal them 
    #[ -r /var/state/enbd/server-$id.ipaddr ] && \
    #myipaddr="`hostname -i`" && \
    #grep -qws enbd-cstatd /etc/services 2>/dev/null && \
    #cat /var/state/enbd/server-$id.ipaddr | while read ipaddr whatever ; do
    #   case "$ipaddr" in 
    #     ""|"#"*)  continue ;;
    #   esac
    #   echo $myipaddr | socket $ipaddr $CSTATDNAME
    #done
    echo done
}
#########################################################################
# separate process to stop a given enbd-server id
#
stop_server() {
    local id=$1
    echo -n stopping $SERVERNAME $id
    stop_foo /var/run/${SERVERNAME}-$id.pid && echo done && return 
    echo failed
}
#########################################################################
# stop all known enbd-servers
#
stop_all_servers() {
    for pidfile in /var/run/${SERVERNAME}-*.pid; do
       local id
       id=${pidfile#/var/run/${SERVERNAME}-}
       id=${id%.pid}
       [ -e /var/run/${SERVERNAME}-$id.pid ] && stop_server $id 
    done
}
#########################################################################
# remove the enbd module
#
stop_module() {
    echo -n removing nbd module
    for i in 1 2 3; do sleep 1; echo -n . ; done;
    $cmd modprobe -r $MODULENAME >/dev/null 2>&1
    echo done
}
#########################################################################
# insert the enbd module
#
start_module() {
    local id=$1
    shift
    options="$*"
    $cmd modprobe $MODULENAME $options
}
#########################################################################
# remove all enbd modules
#
stop_all_modules() {
    stop_module
}

#########################################################################
# usage message
#
usage() {
     echo usage $0 '{ start | stop | restart | force-reload } [ { module | server | client | all } [ id | -a ] ]'
}

#########################################################################
# main business. no args
#
main() {
  do_what="$1"
  to_what="$2"
  to_which="$3"

  [ "$to_what" = all ] && to_what=""
  [ "$to_which" = "-a" ] && to_which=""

  case $do_what in 
  start|stop)
     cat $CONFIG | grep -v '^[ ]*$' | while read line; do

      # grrr ... tr/sed is not always available at boot, so can't trim easily
      # so do it primitively

      LINE=""
      for word in $line "#"; do
        case $word in
          [#]*) break;;
        esac
        LINE="$LINE $word"
      done
      line=$LINE

       COMMANDS=`echo "$line" | while read target id rest; do

	 [ -z "$target" ] && continue
	 [ -z "$id" ] && continue

	 if [ -z "$to_what" -o "$to_what" = "$target" ] \
	 && [ -z "$to_which" -o "$to_which" = "$id" ] ; then
         case $target in
	  module)
               echo "$COMMANDS\n${do_what}_module $id $rest"
	     ;;
	  client) echo "$rest" | while read device server port options; do
               echo "${do_what}_client $id $server $port -i $id $options $device"
	     done
	     ;;
          server) echo "$rest" | while read port resource options; do
               echo "${do_what}_server $id $port $resource -i $id $options"
	     done
	     ;;
           *) usage
             exit 1
             ;;
         esac
         fi
       done`
       $cmd $COMMANDS
     done
  ;;
  restart)
     shift
     main stop  $*
     sleep 4
     main start $*
  ;;
  reload|force-reload)
     main restart
  ;;
  *)
     usage
     exit 1
  ;;
  esac
}

main $*

