#!/bin/bash
#
# /etc/init.d/virtdomains
# Start / stop domains automatically when domain 0 boots / shuts down.
#
# chkconfig: 345 99 00
# description: Start / stop Xen domains.
#
# This script offers fairly basic functionality.  It should work on Redhat
# but also on LSB-compliant SuSE releases and on Debian with the LSB package
# installed.  (LSB is the Linux Standard Base)
#
# Based on the example in the "Designing High Quality Integrated Linux
# Applications HOWTO" by Avi Alkalay
# <http://www.tldp.org/HOWTO/HighQuality-Apps-HOWTO/>
#
# Revised by T.Nonogaki, Stray Penguin
# <http://www.asahi-net.or.jp/~aa4t-nngk/>
# Rev.0.1.2
#
### BEGIN INIT INFO
# Provides:          virtdomains
# Required-Start:    $syslog $remote_fs xend
# Should-Start:
# Required-Stop:     $syslog $remote_fs xend
# Should-Stop:
# Default-Start:     3 4 5
# Default-Stop:      0 1 2 6
# Default-Enabled:   yes
# Short-Description: Start/stop secondary xen domains
# Description:       Start / stop domains automatically when domain 0 
#                    boots / shuts down.
### END INIT INFO

# Correct exit code would probably be 5, but it's enough 
# if xend complains if we're not running as privileged domain
if ! [ -e /proc/xen/privcmd ]; then
	exit 0
fi

LOCKFILE=/var/lock/subsys/virtdomains
XENDOM_CONFIG=/etc/sysconfig/virtdomains

test -r $XENDOM_CONFIG || { echo "$XENDOM_CONFIG not existing";
	if [ "$1" = "stop" ]; then exit 0;
	else exit 6; fi; }

. $XENDOM_CONFIG

# Use the SUSE rc_ init script functions;
# emulate them on LSB, RH and other systems
if test -e /etc/rc.status; then
    # SUSE rc script library
    . /etc/rc.status
else    
    _cmd=$1
    declare -a _SMSG
    if test "${_cmd}" = "status"; then
	_SMSG=(running dead dead unused unknown)
	_RC_UNUSED=3
    else
	_SMSG=(done failed failed missed failed skipped unused failed failed)
	_RC_UNUSED=6
    fi
    if test -e /etc/init.d/functions; then
	# REDHAT
	. /etc/init.d/functions
	echo_rc()
	{
	    #echo -n "  [${_SMSG[${_RC_RV}]}] "
	    if test ${_RC_RV} = 0; then
		success "  [${_SMSG[${_RC_RV}]}] "
		echo
	    else
		failure "  [${_SMSG[${_RC_RV}]}] "
		echo
	    fi
	}
    elif test -e /lib/lsb/init-functions; then
	# LSB    
    	. /lib/lsb/init-functions
        if alias log_success_msg >/dev/null 2>/dev/null; then
	  echo_rc()
	  {
	       echo "  [${_SMSG[${_RC_RV}]}] "
	  }
        else
	  echo_rc()
	  {
	    if test ${_RC_RV} = 0; then
		log_success_msg "  [${_SMSG[${_RC_RV}]}] "
	    else
		log_failure_msg "  [${_SMSG[${_RC_RV}]}] "
	    fi
	  }
        fi
    else    
	# emulate it
	echo_rc()
	{
	    echo "  [${_SMSG[${_RC_RV}]}] "
	}
    fi
    rc_reset() { _RC_RV=0; }
    rc_failed()
    {
	if test -z "$1"; then 
	    _RC_RV=1;
	elif test "$1" != "0"; then 
	    _RC_RV=$1; 
    	fi
	return ${_RC_RV}
    }
    rc_check()
    {
	return rc_failed $?
    }	
    rc_status()
    {
	rc_failed $?
	if test "$1" = "-r"; then _RC_RV=0; shift; fi
	if test "$1" = "-s"; then rc_failed 5; echo_rc; rc_failed 3; shift; fi
	if test "$1" = "-u"; then rc_failed ${_RC_UNUSED}; echo_rc; rc_failed 3; shift; fi
	if test "$1" = "-v"; then echo_rc; shift; fi
	if test "$1" = "-r"; then _RC_RV=0; shift; fi
	return ${_RC_RV}
    }
    rc_exit() { exit ${_RC_RV}; }
    rc_active() 
    {
	if test -z "$RUNLEVEL"; then read RUNLEVEL REST < <(/sbin/runlevel); fi
	if test -e /etc/init.d/S[0-9][0-9]${1}; then return 0; fi
	return 1
    }
fi

if ! which usleep &>/dev/null
then
  usleep()
  {
    if [ -n "$1" ]
    then
      sleep $(( $1 / 1000000 ))
    fi
  }
fi

# Reset status of this service
rc_reset

##
# Returns 0 (success) if the given parameter names a directory, and that
# directory is not empty.
#
contains_something()
{
  if [ -d "$1" ] && [ `/bin/ls $1 | wc -l` -gt 0 ]
  then
    return 0
  else
    return 1
  fi
}

# read name from xen config file
rdname()
{
    NM=$(env LANG=C virsh dominfo ${1##*/} 2>/dev/null |
         awk '/^Name:[[:space:]]+/ { print $2; exit; }')
}

rdnames()
{
    NAMES=
    if ! contains_something "$XENDOMAINS_AUTO"
    then 
	return
    fi
    for dom in $XENDOMAINS_AUTO/*; do
	rdname $dom
	if test -z $NAMES; then 
	    NAMES=$NM; 
	else
	    NAMES="$NAMES|$NM"
	fi
    done
}

parseln()
{
    local numf
    name=`echo "$@" | cut -c0-17`
    name=${name%% *}
    rest=`echo "$@" | cut -c18-`
    numf=`echo $rest | wc -w`
    if [ $numf -eq 5 ]; then
        read id mem vcpu state tm < <(echo "$rest")
    elif [ $numf -eq 4 ]; then
        read mem vcpu state tm < <(echo "$rest")
        id=""
    else
        read mem vcpu tm < <(echo "$rest")
        id=""
        state=""
    fi
}

is_running()
{
    rdname $1
    RC=1
    while read LN; do
	parseln "$LN"
	if [ "$id" = "0" -o -z "$id" ]; then continue; fi
	case $name in 
	     $NM)
		RC=0
		break
		;;
	esac
    done < <(xm list | grep -Ev 'Name[[:space:]]+ID')
    return $RC
}

is_running_by_virsh()
{
    rdname $1
    RC=1
    while read LN; do
	read id name state < <(echo "$LN")
	if [ "$id" = "0" -o "$state" = "shut off" ]; then continue; fi
	case $name in 
	     $NM)
		RC=0
		break
		;;
	esac
    done < <(env LANG=C virsh list --all 2>/dev/null |
	grep -E '^ +' | grep -Ev '^ *Id +Name')
    return $RC
}

start() 
{
    local RETVAL
    if [ -f $LOCKFILE ]; then
	echo -n "virtdomains already running (lockfile exists)"
	rc_failed
	return; 
    fi

    saved_domains=" "
    if [ "$XENDOMAINS_RESTORE" = "true" ] &&
       contains_something "$XENDOMAINS_SAVE"
    then
        [ -d $(dirname "$LOCKFILE") ] || 
           mkdir -p $(dirname "$LOCKFILE")
	echo -n "Restoring Xen domains: "
	saved_domains=`ls $XENDOMAINS_SAVE`
	for dom in $XENDOMAINS_SAVE/*; do
	    echo -n "${dom##*/} "
	    xm restore $dom &>/dev/null
	    RETVAL=$?
	    if [ $RETVAL -ne 0 ]; then
		rc_failed $RETVAL
		echo -n '!'
	    else
		touch $LOCKFILE
		# mv $dom ${dom%/*}/.${dom##*/}
		rm $dom
	    fi
	echo -n ". "
	done
	HAVE_SAVE=1
    fi

    if contains_something "$XENDOMAINS_AUTO"
    then
	# We expect files for auto starting domains to be in XENDOMAINS_AUTO
	# - each file name must match that of the domain but file itself can
	# be empty

	# Create all domains with named files in XENDOMAINS_AUTO.
	# TODO: We should record which domain name belongs 
	# so we have the option to selectively shut down / migrate later
	# If a domain statefile from $XENDOMAINS_SAVE matches a domain name
	# in $XENDOMAINS_AUTO, do not try to start that domain; if it didn't 
	# restore correctly it requires administrative attention.
	[ $HAVE_SAVE -eq 1 ] && echo
	echo -n "Starting auto Xen domains: "
	for dom in $XENDOMAINS_AUTO/*; do
	    dom=$(echo $dom | sed -n 's/^.*\/\(.*\)$/\1/p')
	    echo -n "$dom"
	    echo $saved_domains | grep -w $dom >/dev/null
	    if [ $? -eq 0 ]; then
		echo -n "(skip)"
	    elif is_running $dom && is_running_by_virsh $dom; then
		echo -n "(skip)"
	    else
		echo -n " "
		xm start $dom &>/dev/null
		RETVAL=$?
		if [ $RETVAL -ne 0 ]; then
		    rc_failed $RETVAL
		    echo -n '!'
		else
		    touch $LOCKFILE
		    usleep $XENDOMAINS_CREATE_USLEEP
		fi
		HAVE_AUTO=1
	    fi
	    echo -n ". "
	done
	[ $HAVE_AUTO -eq 1 ] || echo
    fi	
}

# Return TRUE if it seems only zombie domains (not in shutdown process)
# are left, FALSE if no domains left or found any shutting-down healthily.
all_zombies()
{
    while read LN; do
	parseln "$LN"
	if [ "$id" = "0" -o -z "$id" ]; then continue; fi
	if test "$state" != "-b---d" -a "$state" != "-----d"; then
	    return 1
	fi
    done < <(xm list | grep -Ev 'Name[[:space:]]+ID')
    return 0
}

# Return TRUE if it seems only zombie domains (not in shutdown process)
# left, FALSE if no domains left or found any shutting-down healthily.
# FIXME: State obtained via virsh is not so strict as xm.
# Are these check conditions correct?
all_zombies_by_virsh()
{
    local all=0
    local zomb=0
    while read LN; do
	read id name state < <(echo "$LN")
	if [ "$id" = "0" -o "$state" = "shut off" ]; then continue; fi
	: $((all++))
	if test "$state" = "no state"; then
	    : $((zomb++))
	fi
    done < <(env LANG=C virsh list --all 2>/dev/null |
	grep -E '^ +' | grep -Ev '^ *Id +Name')
    [ $all -eq 0 ] && return 1
    [ $all -eq $zomb ] && return 0
    return 1
}

# Call syntax: watchdog_xm -c $act {-d $dom|-f}
# Wait for max $XENDOMAINS_STOP_MAXWAIT for `xm $dom $id' to finish;
# if it has not exited by that time kill it, so the init script will
# succeed within a finite amount of time; if $force (-f) is nonnull,
# it will kill the command as well as soon as no domain (except for
# zombies) are left (used for shutdown --all).
watchdog_xm()
{
    local act dom
    local force=0
    if test -z "$XENDOMAINS_STOP_MAXWAIT" -o "$XENDOMAINS_STOP_MAXWAIT" = "0"; then
	exit
    fi
    while getopts "c:d:f" opt; do
	case $opt in
	    c)
		act=$OPTARG;;
	    d)
		dom=$OPTARG;;
	    f)
		force=1
		dom=$XENDOMAINS_SHUTDOWN_ALL;;
	esac
    done
    shift $(($OPTIND -1))
    usleep 300000
    for no in `seq 0 $XENDOMAINS_STOP_MAXWAIT`; do
	# exit if xm save/migrate/shutdown is finished
	PSAX=`ps axlw | grep "xm $act $dom" | grep -v grep`
	[ -z "$PSAX" ] && exit
	echo -n "."; sleep 1
	# go to kill immediately if there's only zombies left
	if [ $force -eq 1 ]; then
	    #if all_zombies || all_zombies_by_virsh; then
	    if all_zombies; then
		#echo "Domain $dom seems zombie"
		break
	    fi
	fi
    done
    sleep 1
    read PSF PSUID PSPID PSPPID < <(echo "$PSAX")
    # kill `xm $act $dom' process
    kill $PSPID &>/dev/null
    exit
}

stop()
{
    local RETVAL
    # Collect list of domains to shut down
    if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
	rdnames
    fi
    echo -n "Shutting down Xen domains: "
    while read LN; do
	parseln "$LN"
	if [ "$id" = "0" -o -z "$id" ]; then continue; fi
	echo -n "$name"
	if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
	    case $name in
		$NAMES)
		    # nothing
		    ;;
		*)
		    echo "(skip). "
		    continue
		    ;;
	    esac
	fi
	# XENDOMAINS_SYSRQ chould be something like just "s" 
	# or "s e i u" or even "s e s i u o"
	# for the latter, you should set XENDOMAINS_USLEEP to 1200000 or so
	if test -n "$XENDOMAINS_SYSRQ"; then
	    for sysrq in $XENDOMAINS_SYSRQ; do
		echo -n "(SR-$sysrq)"
		xm sysrq $id $sysrq &>/dev/null
		RETVAL=$?
		if [ $RETVAL -ne 0 ]; then
		    rc_failed $RETVAL
		    echo -n '!'
		fi
		# usleep just ignores empty arg
		usleep $XENDOMAINS_USLEEP
	    done
	fi
	if test "$state" = "-b---d" -o "$state" = "-----d"; then
	    echo -n "(zomb). "
	    continue
	fi
	if test -n "$XENDOMAINS_MIGRATE"; then
	    echo -n "(migr)"
	    watchdog_xm -c migrate -d $name &
	    WDOG_PID=$!
	    xm migrate $name $XENDOMAINS_MIGRATE &>/dev/null
	    RETVAL=$?
	    if [ $RETVAL -ne 0 ]; then
		rc_failed $RETVAL
		echo -n '!. '
		kill $WDOG_PID &>/dev/null
	    else
		#kill $WDOG_PID &>/dev/null
		echo -n ". "
		continue
	    fi
	fi
	if test -n "$XENDOMAINS_SAVE"; then
	    echo -n "(save)"
	    watchdog_xm -c save -d $name &
	    WDOG_PID=$!
	    mkdir -p "$XENDOMAINS_SAVE"
	    xm save $name ${XENDOMAINS_SAVE}/$name &>/dev/null
	    RETVAL=$?
	    if [ $RETVAL -ne 0 ]; then
		rc_failed $RETVAL
		echo -n '!. '
		kill $WDOG_PID &>/dev/null
	    else
		#kill $WDOG_PID &>/dev/null
		echo -n ". "
		continue
	    fi
	fi
	if test -n "$XENDOMAINS_SHUTDOWN"; then
	    # XENDOMAINS_SHUTDOWN should be "--halt --wait"
	    echo -n "(shut)"
	    watchdog_xm -c shutdown -d $name &
	    WDOG_PID=$!
	    xm shutdown $name $XENDOMAINS_SHUTDOWN &>/dev/null
	    RETVAL=$?
	    if [ $RETVAL -ne 0 ]; then
		rc_failed $RETVAL
		echo -n '!'
		kill $WDOG_PID &>/dev/null
	    fi
	    echo -n ". "
	fi
    done < <(xm list | grep -Ev 'Name[[:space:]]+ID')

    # NB. this shuts down ALL Xen domains (politely), not just the ones in
    # AUTODIR/*
    # This is because it's easier to do ;-) but arguably if this script is run
    # on system shutdown then it's also the right thing to do.
    if test -n "$XENDOMAINS_SHUTDOWN_ALL"; then
	#if ! all_zombies && ! all_zombies_by_virsh; then
	if ! all_zombies; then
	    # XENDOMAINS_SHUTDOWN_ALL should be "--all --halt --wait"
	    echo
	    echo -n "SHUTDOWN_ALL "
	    watchdog_xm -c shutdown -f &
	    WDOG_PID=$!
	    xm shutdown $XENDOMAINS_SHUTDOWN_ALL >/dev/null
	    RETVAL=$?
	    if [ $RETVAL -ne 0 ]; then
		rc_failed $RETVAL
		echo -n '!'
		kill $WDOG_PID >/dev/null 2>&1
	    fi
	fi
    fi

    # Unconditionally delete lock file
    rm -f $LOCKFILE
}

check_domain_up()
{
    while read LN; do
	parseln "$LN"
	if [ "$id" = "0" -o -z "$id" ]; then continue; fi
	case $name in 
	    $1)
		return 0
		;;
	esac
    done < <(xm list | grep -Ev 'Name[[:space:]]+ID')
    return 1
}

check_all_auto_domains_up()
{
    if ! contains_something "$XENDOMAINS_AUTO"
    then
      return 0
    fi
    missing=
    for nm in $XENDOMAINS_AUTO/*; do
	rdname $nm
	found=0
	if check_domain_up "$NM"; then 
	    echo -n " $name"
	else 
	    missing="$missing $NM"
	fi
    done
    if test -n "$missing"; then
	echo -n " MISS AUTO:$missing"
	return 1
    fi
    return 0
}

check_all_saved_domains_up()
{
    if ! contains_something "$XENDOMAINS_SAVE" 
    then
      return 0
    fi
    missing=`/bin/ls $XENDOMAINS_SAVE`
    echo -n " MISS SAVED: " $missing
    return 1
}

# This does NOT necessarily restart all running domains: instead it
# stops all running domains and then boots all the domains specified in
# AUTODIR.  If other domains have been started manually then they will
# not get restarted.
# Commented out to avoid confusion!

restart()
{
    stop
    rc_status -v
    start
    rc_status
}

reload()
{
    restart
}

HAVE_SAVE=0
HAVE_AUTO=0

case "$1" in
    start)
	start
	rc_status
	if test -f $LOCKFILE; then rc_status -v; fi
	;;

    stop)
	stop
	rc_status -v
	;;

    restart)
	restart
	;;
    reload)
	reload
	;;

    status)
	echo -n "Checking for xendomains:" 
	if test ! -f $LOCKFILE; then 
	    rc_failed 3
	else
	    check_all_auto_domains_up
	    rc_status
	    check_all_saved_domains_up
	    rc_status
	fi
	rc_status -v
	;;

    *)
	echo "Usage: $0 {start|stop|restart|reload|status}"
	rc_failed 3
	rc_status -v
	;;
esac

rc_exit
