#!/bin/bash
#
# xendomains	Starts and stops secondary Xen domains
#
# chkconfig: 35 99 00
# description: Starts and stops secondary Xen domains
#
### BEGIN INIT INFO
# Provides:          xendomains
# Required-Start:    $syslog $remote_fs xend
# Should-Start:
# Required-Stop:     $syslog $remote_fs xend
# Should-Stop:
# Default-Start:     3 5
# Default-Stop:      0 1 2 4 6
# Short-Description: Starts and stops secondary Xen domains
# Description:       Starts and stops secondary Xen domains automatically
#                    when domain 0 starts and stops.
### END INIT INFO

# FIXME:  "xm shutdown -w" does not wait if domain is non-responsive.  This
#         leads to erroneous successful result when shutting it down, and
#         then another errnoneous "others: shutting down" step.
# FIXME:  Watch out for domain names with invalid characters.

. /etc/rc.status
rc_reset

LOCKFILE=/var/lock/subsys/xendomains
XENDOM_CONFIG=/etc/sysconfig/xendomains
RETCODE_FILE=/var/lock/subsys/xendomains.rc.$$

shopt -s dotglob nullglob

smart_term=1
if [ -z "$esc" ]; then
	smart_term=0
	rc_timer_on()
	{
		(trap "exit 0" TERM; sleep $1) & _rc_timer_pid=$!
	}
	rc_timer_off()
	{
		if test -n "$_rc_timer_pid" ; then
			kill -TERM $_rc_timer_pid > /dev/null 2>&1
		fi
		unset _rc_timer_pid
	}
fi

xendomains_abort()
{
	echo -n "xendomains "
	rc_failed $1
	rc_status -v
	rc_exit
}

if [ "$1" == status ]; then
	if [ ! -e /proc/xen/privcmd ]; then
		xendomains_abort 3
	fi
	if [ ! -r "$XENDOM_CONFIG" ]; then
		xendomains_abort 3
	fi
else
	if [ `id -u` != 0 ]; then
		xendomains_abort 4
	fi
	if [ ! -e /proc/xen/privcmd ]; then
		if [ "$1" == stop ] ||
		   [ "$1" == restart ]; then
			xendomains_abort 0
		else
			xendomains_abort 6
		fi
	fi
	if [ ! -r "$XENDOM_CONFIG" ]; then
		xendomains_abort 6
	fi
fi

. "$XENDOM_CONFIG"

##
# Returns 0 (success) if the given parameter names a directory, and that
# directory is not empty.
#
contains_something()
{
	dirfiles=( "$1"/* )
	test ${#dirfiles[@]} != 0
}

# read name from xen config file
rdname()
{
	NM=`grep '^name[	 ]*=' $1 | sed -e 's/^name[	 ]*=[	 ]*"\([^"]*\)".*$/\1/' -e 's/%[id]/[0-9]*/g'`
}

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

parseln()
{
	name=`echo "$1" | cut -c0-17`
	name=${name%% *}
	rest=`echo "$1" | cut -c18- `
	read id mem vcpu state tm < <(echo "$rest")
}

is_running()
{
	rdname "$1"
	RC=1
	while read LN; do
		parseln "$LN"
		[ $id = 0 ] && continue
		case "$name" in
		 ($NM)
			RC=0
			;;
		esac
	done < <(xm list | grep -v '^Name')
	return $RC
}

start()
{

	if [ -f "$LOCKFILE" ]; then
		echo -n "xendomains already running (lockfile exists)"
		rc_reset
		rc_status -v
		return 0;
	fi

	local printed=0

	if [ "$XENDOMAINS_RESTORE" = "true" ] &&
	   contains_something "$XENDOMAINS_SAVE"; then
		mkdir -p $(dirname "$LOCKFILE")
		touch "$LOCKFILE"
		echo "Restoring saved Xen domains"
		printed=1
		for dom in "$XENDOMAINS_SAVE"/*; do
			echo -n "	${dom##*/}: "
			xm restore "$dom" >/dev/null 2>&1
			if [ $? -ne 0 ]; then
				rc_failed
			else
				rc_reset
				rm -f "$dom"
			fi
			rc_status -v
		done
	fi

	# We expect config scripts for auto starting domains to be in
	# XENDOMAINS_AUTO - they could just be symlinks to files elsewhere
	if contains_something "$XENDOMAINS_AUTO"; then
		touch "$LOCKFILE"
		echo "Starting auto Xen domains"
		printed=1
		# Create all domains with config files in XENDOMAINS_AUTO.
		# TODO: We should record which domain name belongs
		# so we have the option to selectively shut down / migrate later
		for dom in "$XENDOMAINS_AUTO"/*; do
			echo -n "	${dom##*/}: "
			if is_running "$dom"; then
				rc_status -s
			else
				xm create --quiet --defconfig "$dom"
				if [ $? -ne 0 ]; then
					rc_failed
				else
					usleep $XENDOMAINS_CREATE_USLEEP
					rc_reset
				fi
				rc_status -v
			fi
		done
	fi

	if [ $printed -eq 0 ]; then
		echo -n "Starting xendomains"
		rc_failed 6  # not configured
		rc_status -v
	fi
}

#  $1:  The state string
is_zombie()
{
	[ "$1" == "-b---d" ] || [ "$1" == "-----d" ]
}

any_non_zombies()
{
	while read LN; do
		parseln "$LN"
		[ $id = 0 ] && continue
		is_zombie "$state" || return 0
	done < <(xm list | grep -v '^Name')
	return 1
}

migrate_with_watchdog()
{
	(xm migrate "$@" ; echo $? > "$RETCODE_FILE") >/dev/null 2>&1 &
	watchdog_xm $!
}

save_with_watchdog()
{
	(xm save "$@" ; echo $? > "$RETCODE_FILE") >/dev/null 2>&1 &
	watchdog_xm $!
}

shutdown_with_watchdog()
{
	(xm shutdown "$@" ; echo $? > "$RETCODE_FILE") >/dev/null 2>&1 &
	watchdog_xm $!
}

get_return_code()
{
	rc=127
	[ -r "$RETCODE_FILE" ] && rc=`head -c10 "$RETCODE_FILE"`
	rm -f "$RETCODE_FILE"
	return $rc
}

#  $1:  The PID to wait on.
watchdog_xm()
{
	local col=$((COLUMNS-11))
	if [ -z "$XENDOMAINS_STOP_MAXWAIT" ] || [ "$XENDOMAINS_STOP_MAXWAIT" = "0" ]; then
		wait $1 >/dev/null 2>&1
		get_return_code
		return
	fi

	rc_timer_on $XENDOMAINS_STOP_MAXWAIT $col
	while true; do
		# Prefer "jobs" over "ps":  faster and no false positives
		pid=`jobs -l | grep " $1 Running"`
		if [ -z "$pid" ]; then
			break
		fi
		pid=`jobs -l | grep " $_rc_timer_pid Running"`
		if [ -z "$pid" ]; then
			disown $1  # To avoid the "Terminated..." message
			kill $1 >/dev/null 2>&1
		fi
		sleep 1
	done
	rc_timer_off
	if [ $smart_term -ne 0 ]; then
		echo -en "\015${esc}[${col}C      "
	fi
	get_return_code
}

stop()
{
	echo "Shutting down Xen domains"
	# Collect list of domains to shut down
	if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
		rdnames
	fi
	local printed=0
	while read LN; do
		parseln "$LN"
		[ $id = 0 ] && continue
		printed=1
		if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
			case "$name" in
			 ($NAMES)
				# nothing
				;;
			 (*)
				echo -n "	$name: "
				rc_status -s
				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 "	$name: "
				echo -n "sending sysrq '$sysrq'... "
				xm sysrq $id $sysrq
				if [ $? -ne 0 ]; then
					rc_failed
				else
					rc_reset
				fi
				rc_status -v
				# usleep just ignores empty arg
				usleep $XENDOMAINS_USLEEP
			done
		fi
		if is_zombie "$state"; then
			echo -n "	$name: "
			echo -n "destroying zombie... "
			xm destroy $id
			rc_reset
			rc_status -v
			continue
		fi
		# HVM domains currently can't migrate or save so don't even try.
                hvm=0
		vmpath=`xenstore-read /local/domain/$id/vm 2> /dev/null`
		if [ $? = 0 ]; then
			[ `xenstore-read "$vmpath/image/ostype" 2> /dev/null` = hvm ] && hvm=1
		fi
		if test -n "$XENDOMAINS_MIGRATE"; then
			echo -n "	$name: "
			echo -n "migrating... "
	                if [ $hvm != 0 ]; then
				echo -n "(unsupported for fully virtualized)"
				rc_status -s
			else
				migrate_with_watchdog $id $XENDOMAINS_MIGRATE
				if test $? -ne 0; then
					rc_failed
					rc_status -v
				else
					rc_reset
					rc_status -v
					continue
				fi
			fi
		fi
		if test -n "$XENDOMAINS_SAVE"; then
			echo -n "	$name: "
			echo -n "saving... "
	                if [ $hvm != 0 ]; then
				echo -n "(unsupported for fully virtualized)"
				rc_status -s
			else
				save_with_watchdog $id "$XENDOMAINS_SAVE/$name"
				if test $? -ne 0; then
					rm -f "$XENDOMAINS_SAVE/$name"
					rc_failed
					rc_status -v
				else
					rc_reset
					rc_status -v
					continue
				fi
			fi
		fi
		if test -n "$XENDOMAINS_SHUTDOWN"; then
			echo -n "	$name: "
			echo -n "shutting down... "
			shutdown_with_watchdog $id $XENDOMAINS_SHUTDOWN
			if test $? -ne 0; then
				rc_failed
			else
				rc_reset
			fi
			rc_status -v
		fi
	done < <(xm list | grep -v '^Name')

	# 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 [ -n "$XENDOMAINS_SHUTDOWN_ALL" ] && any_non_zombies ; then
		echo -n "	others: shutting down... "
		shutdown_with_watchdog $XENDOMAINS_SHUTDOWN_ALL
		if test $? -ne 0; then
			rc_failed
		else
			rc_reset
		fi
		rc_status -v
	fi

	if [ $printed -eq 0 ]; then
		echo -e "${rc_done_up}"
	fi

	# Unconditionally delete lock file
	rm -f "$LOCKFILE"
}

check_domain_up()
{
	while read LN; do
		parseln "$LN"
		[ $id = 0 ] && continue
		case "$name" in
		 ("$1")
			return 0
			;;
		esac
	done < <(xm list | grep -v "^Name")
	return 1
}

check_all_domains_up()
{
	if ! contains_something "$XENDOMAINS_AUTO" &&
	   ! contains_something "$XENDOMAINS_SAVE"; then
		rc_reset
		rc_status -v
		return
	fi
	echo
	for nm in "$XENDOMAINS_AUTO"/*; do
		rdname "$nm"
		echo -n "	$nm: "
		if check_domain_up "$NM"; then
			rc_reset
		else
			rc_failed 2
		fi
		rc_status -v
	done
	for nm in "$XENDOMAINS_SAVE"/*; do
		echo -n "	$nm: "
		rc_failed 3
		rc_status -v
	done
}

# 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.
restart()
{
	"$0" stop
	start
}

case "$1" in
 start)
	start
	;;

 stop)
	stop
	;;

 restart|reload)
	restart
	;;

 try-restart)
	"$0" status
	if [ $? = 0 ]; then
		"$0" restart
	else
		rc_reset
		rc_status -v
	fi
	;;

 status)
	echo -n "Checking status of Xen domains"
	if [ ! -f "$LOCKFILE" ]; then
		rc_failed 3
		rc_status -v
	else
		check_all_domains_up
	fi
	;;

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

rc_exit
