1#!/bin/bash
2#
3# /etc/init.d/xendomains
4# Start / stop domains automatically when domain 0 boots / shuts down.
5#
6# chkconfig: 345 99 00
7# description: Start / stop Xen domains.
8#
9# This script offers fairly basic functionality.  It should work on Redhat
10# but also on LSB-compliant SuSE releases and on Debian with the LSB package
11# installed.  (LSB is the Linux Standard Base)
12#
13# Based on the example in the "Designing High Quality Integrated Linux
14# Applications HOWTO" by Avi Alkalay
15# <http://www.tldp.org/HOWTO/HighQuality-Apps-HOWTO/>
16#
17### BEGIN INIT INFO
18# Provides:          xendomains
19# Required-Start:    $syslog $remote_fs xenstored xenconsoled
20# Should-Start:      xend
21# Required-Stop:     $syslog $remote_fs xenstored xenconsoled
22# Should-Stop:       xend
23# Default-Start:     2 3 5
24# Default-Stop:      0 1 6
25# Short-Description: Start/stop secondary xen domains
26# Description:       Start / stop domains automatically when domain 0
27#                    boots / shuts down.
28### END INIT INFO
29
30. @XEN_SCRIPT_DIR@/hotplugpath.sh
31
32CMD=${sbindir}/xl
33HEADCOMP="Xen saved domain"
34$CMD list &> /dev/null
35if test $? -ne 0
36then
37	exit $?
38fi
39
40$CMD list &> /dev/null
41if test $? -ne 0
42then
43	exit 0;
44fi
45
46# Correct exit code would probably be 5, but it's enough
47# if xend complains if we're not running as privileged domain
48if [ ! -e /dev/xen/privcmd ] && [ ! -e /proc/xen/privcmd ]; then
49	exit 0
50fi
51
52# RHEL-based systems only shutdown a service if they find a lockfile
53# in /var/lock/subsys
54if [[ -d ${XEN_LOCK_DIR}/subsys ]] ; then
55    LOCKFILE=${XEN_LOCK_DIR}/subsys/xendomains
56else
57    LOCKFILE=${XEN_LOCK_DIR}/xendomains
58fi
59
60XENDOM_CONFIG=@CONFIG_DIR@/@CONFIG_LEAF_DIR@/xendomains
61
62test -r $XENDOM_CONFIG || { echo "$XENDOM_CONFIG not existing";
63	if [ "$1" = "stop" ]; then exit 0;
64	else exit 6; fi; }
65
66. $XENDOM_CONFIG
67
68# Use the SUSE rc_ init script functions;
69# emulate them on LSB, RH and other systems
70if test -e /etc/rc.status; then
71    # SUSE rc script library
72    . /etc/rc.status
73else
74    _cmd=$1
75    declare -a _SMSG
76    if test "${_cmd}" = "status"; then
77	_SMSG=(running dead dead unused unknown)
78	_RC_UNUSED=3
79    else
80	_SMSG=(done failed failed missed failed skipped unused failed failed)
81	_RC_UNUSED=6
82    fi
83    if test -e /etc/init.d/functions; then
84	# REDHAT
85	. /etc/init.d/functions
86	echo_rc()
87	{
88	    #echo -n "  [${_SMSG[${_RC_RV}]}] "
89	    if test ${_RC_RV} = 0; then
90		success "  [${_SMSG[${_RC_RV}]}] "
91	    else
92		failure "  [${_SMSG[${_RC_RV}]}] "
93	    fi
94	}
95    elif test -e /lib/lsb/init-functions; then
96	# LSB
97	. /lib/lsb/init-functions
98        if alias log_success_msg >/dev/null 2>/dev/null; then
99	  echo_rc()
100	  {
101	       echo "  [${_SMSG[${_RC_RV}]}] "
102	  }
103        else
104	  echo_rc()
105	  {
106	    if test ${_RC_RV} = 0; then
107		log_success_msg "  [${_SMSG[${_RC_RV}]}] "
108	    else
109		log_failure_msg "  [${_SMSG[${_RC_RV}]}] "
110	    fi
111	  }
112        fi
113    else
114	# emulate it
115	echo_rc()
116	{
117	    echo "  [${_SMSG[${_RC_RV}]}] "
118	}
119    fi
120    rc_reset() { _RC_RV=0; }
121    rc_failed()
122    {
123	if test -z "$1"; then
124	    _RC_RV=1;
125	elif test "$1" != "0"; then
126	    _RC_RV=$1;
127	fi
128	return ${_RC_RV}
129    }
130    rc_check()
131    {
132	return rc_failed $?
133    }
134    rc_status()
135    {
136	rc_failed $?
137	if test "$1" = "-r"; then _RC_RV=0; shift; fi
138	if test "$1" = "-s"; then rc_failed 5; echo_rc; rc_failed 3; shift; fi
139	if test "$1" = "-u"; then rc_failed ${_RC_UNUSED}; echo_rc; rc_failed 3; shift; fi
140	if test "$1" = "-v"; then echo_rc; shift; fi
141	if test "$1" = "-r"; then _RC_RV=0; shift; fi
142	return ${_RC_RV}
143    }
144    rc_exit() { exit ${_RC_RV}; }
145    rc_active()
146    {
147	if test -z "$RUNLEVEL"; then read RUNLEVEL REST < <(/sbin/runlevel); fi
148	if test -e /etc/init.d/S[0-9][0-9]${1}; then return 0; fi
149	return 1
150    }
151fi
152
153if ! which usleep >&/dev/null
154then
155  usleep()
156  {
157    if [ -n "$1" ]
158    then
159      sleep $(( $1 / 1000000 ))
160    fi
161  }
162fi
163
164# Reset status of this service
165rc_reset
166
167##
168# Returns 0 (success) if the given parameter names a directory, and that
169# directory is not empty.
170#
171contains_something()
172{
173  if [ -d "$1" ] && [ `/bin/ls $1 | wc -l` -gt 0 ]
174  then
175    return 0
176  else
177    return 1
178  fi
179}
180
181# read name from xen config file
182rdname()
183{
184    NM=$($CMD create --quiet --dryrun --defconfig "$1" |
185         sed -n 's/^.*(name \(.*\))$/\1/p;s/^.*"name": "\(.*\)",$/\1/p')
186}
187
188rdnames()
189{
190    NAMES=
191    if ! contains_something "$XENDOMAINS_AUTO"
192    then
193	return
194    fi
195    for dom in $XENDOMAINS_AUTO/*; do
196	rdname $dom
197	if test -z $NAMES; then
198	    NAMES=$NM;
199	else
200	    NAMES="$NAMES|$NM"
201	fi
202    done
203}
204
205# get xenstore domain id (or 0 if no xenstore domain)
206get_xsdomid()
207{
208    XS_DOMID=`${bindir}/xenstore-read /tool/xenstored/domid 2>/dev/null`
209    if test $? -ne 0; then
210        XS_DOMID=0
211    fi
212}
213
214LIST_GREP='(domain\|(domid\|(name\|^    {$\|"name":\|^        "domid":'
215parseln()
216{
217    if [[ "$1" =~ '(domain' ]] || [[ "$1" = "{" ]]; then
218        name=;id=
219    elif [[ "$1" =~ '(name' ]]; then
220        name=$(echo $1 | sed -e 's/^.*(name \(.*\))$/\1/')
221    elif [[ "$1" =~ '(domid' ]]; then
222        id=$(echo $1 | sed -e 's/^.*(domid \(.*\))$/\1/')
223    elif [[ "$1" =~ '"name":' ]]; then
224        name=$(echo $1 | sed -e 's/^.*"name": "\(.*\)",$/\1/')
225    elif [[ "$1" =~ '"domid":' ]]; then
226        id=$(echo $1 | sed -e 's/^.*"domid": \(.*\),$/\1/')
227    fi
228
229    [ -n "$name" -a -n "$id" ] && return 0 || return 1
230}
231
232is_running()
233{
234    get_xsdomid
235    rdname $1
236    RC=1
237    name=;id=
238    while read LN; do
239	parseln "$LN" || continue
240	if test $id = 0; then continue; fi
241	if test $id = $XS_DOMID; then continue; fi
242	case $name in
243	    ($NM)
244		RC=0
245		;;
246	esac
247    done < <($CMD list -l | grep "$LIST_GREP")
248    return $RC
249}
250
251start()
252{
253    if [ -f $LOCKFILE ]; then
254	echo -e "xendomains already running (lockfile exists)"
255	return;
256    fi
257
258    mkdir -p $(dirname "$LOCKFILE")
259    touch $LOCKFILE
260
261    saved_domains=" "
262    if [ "$XENDOMAINS_RESTORE" = "true" ] &&
263       contains_something "$XENDOMAINS_SAVE"
264    then
265	echo -n "Restoring Xen domains:"
266	saved_domains=`ls $XENDOMAINS_SAVE`
267        for dom in $XENDOMAINS_SAVE/*; do
268            if [ -f $dom ] ; then
269                HEADER=`head -c 16 $dom | head -n 1 2> /dev/null`
270                if [ "$HEADER" = "$HEADCOMP" ]; then
271                    echo -n " ${dom##*/}"
272                    XMR=`$CMD restore $dom 2>&1 1>/dev/null`
273                    #$CMD restore $dom
274                    if [ $? -ne 0 ]; then
275                        echo -e "\nAn error occurred while restoring domain ${dom##*/}:\n$XMR"
276                        rc_failed $?
277                        echo -e '!'
278                    else
279                        # mv $dom ${dom%/*}/.${dom##*/}
280                        rm $dom
281                    fi
282                fi
283            fi
284        done
285	echo -e
286    fi
287
288    if contains_something "$XENDOMAINS_AUTO"
289    then
290	echo -n "Starting auto Xen domains:"
291	# We expect config scripts for auto starting domains to be in
292	# XENDOMAINS_AUTO - they could just be symlinks to files elsewhere
293
294	# Create all domains with config files in XENDOMAINS_AUTO.
295	# TODO: We should record which domain name belongs
296	# so we have the option to selectively shut down / migrate later
297	# If a domain statefile from $XENDOMAINS_SAVE matches a domain name
298	# in $XENDOMAINS_AUTO, do not try to start that domain; if it didn't
299	# restore correctly it requires administrative attention.
300	for dom in $XENDOMAINS_AUTO/*; do
301	    echo -n " ${dom##*/}"
302	    shortdom=$(echo $dom | sed -n 's/^.*\/\(.*\)$/\1/p')
303	    echo $saved_domains | grep -w $shortdom > /dev/null
304	    if [ $? -eq 0 ] || is_running $dom; then
305		echo -n "(skip)"
306	    else
307		XMC=`$CMD create --quiet --defconfig $dom`
308		if [ $? -ne 0 ]; then
309		    echo -e "\nAn error occurred while creating domain ${dom##*/}: $XMC\n"
310		    rc_failed $?
311		    echo -e '!'
312		else
313		    usleep $XENDOMAINS_CREATE_USLEEP
314		fi
315	    fi
316	done
317    fi
318}
319
320all_zombies()
321{
322    get_xsdomid
323    name=;id=
324    while read LN; do
325	parseln "$LN" || continue
326	if test $id = 0; then continue; fi
327	if test $id = $XS_DOMID; then continue; fi
328	if test "$state" != "-b---d" -a "$state" != "-----d"; then
329	    return 1;
330	fi
331    done < <($CMD list -l | grep "$LIST_GREP")
332    return 0
333}
334
335# Wait for max $XENDOMAINS_STOP_MAXWAIT for $CMD $1 to finish;
336# if it has not exited by that time kill it, so the init script will
337# succeed within a finite amount of time; if $2 is nonnull, it will
338# kill the command as well as soon as no domain (except for zombies)
339# are left (used for shutdown --all). Third parameter, if any, suppresses
340# output of dots per working state (formatting issues)
341watchdog_xencmd()
342{
343    if test -z "$XENDOMAINS_STOP_MAXWAIT" -o "$XENDOMAINS_STOP_MAXWAIT" = "0"; then
344	exit
345    fi
346
347    usleep 20000
348    for no in `seq 0 $XENDOMAINS_STOP_MAXWAIT`; do
349	# exit if $CMD save/migrate/shutdown is finished
350	PSAX=`ps axlw | grep "$CMD $1" | grep -v grep`
351	if test -z "$PSAX"; then exit; fi
352	if ! test -n "$3"; then echo -n '.'; fi
353	sleep 1
354	# go to kill immediately if there's only zombies left
355	if all_zombies && test -n "$2"; then break; fi
356    done
357    sleep 1
358    read PSF PSUID PSPID PSPPID < <(echo "$PSAX")
359    # kill $CMD $1
360    kill $PSPID >/dev/null 2>&1
361
362    echo -e .
363}
364
365stop()
366{
367    exec 3>&2 2> /dev/null
368
369    # Collect list of domains to shut down
370    if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
371	rdnames
372    fi
373    get_xsdomid
374    echo -n "Shutting down Xen domains:"
375    name=;id=
376    while read LN; do
377	parseln "$LN" || continue
378	if test $id = 0; then continue; fi
379	if test $id = $XS_DOMID; then continue; fi
380	echo -n " $name"
381	if test "$XENDOMAINS_AUTO_ONLY" = "true"; then
382	    eval "
383	    case \"\$name\" in
384		($NAMES)
385		    # nothing
386		    ;;
387		(*)
388		    echo -e '(skip)'
389		    continue
390		    ;;
391	    esac
392	    "
393	fi
394	# XENDOMAINS_SYSRQ chould be something like just "s"
395	# or "s e i u" or even "s e s i u o"
396	# for the latter, you should set XENDOMAINS_USLEEP to 1200000 or so
397	if test -n "$XENDOMAINS_SYSRQ"; then
398	    for sysrq in $XENDOMAINS_SYSRQ; do
399		echo -n "(SR-$sysrq)"
400		XMR=`$CMD sysrq $id $sysrq 2>&1 1>/dev/null`
401		if test $? -ne 0; then
402		    echo -e "\nAn error occurred while doing sysrq on domain:\n$XMR\n"
403		    rc_failed $?
404		    echo -n '!'
405		fi
406		# usleep just ignores empty arg
407		usleep $XENDOMAINS_USLEEP
408	    done
409	fi
410	if test "$state" = "-b---d" -o "$state" = "-----d"; then
411	    echo -n "(zomb)"
412	    continue
413	fi
414	if test -n "$XENDOMAINS_MIGRATE"; then
415	    echo -n "(migr)"
416	    watchdog_xencmd migrate &
417	    WDOG_PID=$!
418	    XMR=`$CMD migrate $id $XENDOMAINS_MIGRATE 2>&1 1>/dev/null`
419	    if test $? -ne 0; then
420		echo -e "\nAn error occurred while migrating domain:\n$XMR\n"
421		rc_failed $?
422		echo -e '!'
423
424		kill $WDOG_PID >/dev/null 2>&1
425	    else
426		kill $WDOG_PID >/dev/null 2>&1
427
428		echo -e .
429		usleep 1000
430		continue
431	    fi
432	fi
433	if test -n "$XENDOMAINS_SAVE"; then
434	    echo -n "(save)"
435	    watchdog_xencmd save &
436	    WDOG_PID=$!
437	    mkdir -p "$XENDOMAINS_SAVE"
438	    XMR=`$CMD save $id $XENDOMAINS_SAVE/$name 2>&1 1>/dev/null`
439	    if test $? -ne 0; then
440		echo -e "\nAn error occurred while saving domain:\n$XMR\n"
441		rc_failed $?
442		echo -e '!'
443		kill $WDOG_PID >/dev/null 2>&1
444	    else
445		kill $WDOG_PID >/dev/null 2>&1
446		echo -e .
447		usleep 1000
448		continue
449	    fi
450	fi
451	if test -n "$XENDOMAINS_SHUTDOWN"; then
452	    # XENDOMAINS_SHUTDOWN should be "--wait"
453	    echo -n "(shut)"
454	    watchdog_xencmd shutdown &
455	    WDOG_PID=$!
456	    XMR=`$CMD shutdown $XENDOMAINS_SHUTDOWN $id 2>&1 1>/dev/null`
457	    if test $? -ne 0; then
458		echo -e "\nAn error occurred while shutting down domain:\n$XMR\n"
459		rc_failed $?
460		echo -e '!'
461	    fi
462	    kill $WDOG_PID >/dev/null 2>&1
463	fi
464    done < <($CMD list -l | grep "$LIST_GREP")
465
466    # NB. this shuts down ALL Xen domains (politely), not just the ones in
467    # AUTODIR/*
468    # This is because it's easier to do ;-) but arguably if this script is run
469    # on system shutdown then it's also the right thing to do.
470    if ! all_zombies && test -n "$XENDOMAINS_SHUTDOWN_ALL"; then
471	# XENDOMAINS_SHUTDOWN_ALL should be "--all --wait"
472	echo -n " SHUTDOWN_ALL "
473	watchdog_xencmd shutdown 1 false &
474	WDOG_PID=$!
475	XMR=`$CMD shutdown $XENDOMAINS_SHUTDOWN_ALL 2>&1 1>/dev/null`
476	if test $? -ne 0; then
477	    echo -e "\nAn error occurred while shutting down all domains: $XMR\n"
478	    rc_failed $?
479	    echo -e '!'
480	fi
481	kill $WDOG_PID >/dev/null 2>&1
482    fi
483
484    # Unconditionally delete lock file
485    rm -f $LOCKFILE
486
487    exec 2>&3
488}
489
490check_domain_up()
491{
492    name=;id=
493    while read LN; do
494	parseln "$LN" || continue
495	if test $id = 0; then continue; fi
496	case $name in
497	    ($1)
498		return 0
499		;;
500	esac
501    done < <($CMD list -l | grep "$LIST_GREP")
502    return 1
503}
504
505check_all_auto_domains_up()
506{
507    if ! contains_something "$XENDOMAINS_AUTO"
508    then
509      return 0
510    fi
511    missing=
512    for nm in $XENDOMAINS_AUTO/*; do
513	rdname $nm
514	found=0
515	if check_domain_up "$NM"; then
516	    echo -n " $name"
517	else
518	    missing="$missing $NM"
519	fi
520    done
521    if test -n "$missing"; then
522	echo -n " MISS AUTO:$missing"
523	return 1
524    fi
525    return 0
526}
527
528check_all_saved_domains_up()
529{
530    if ! contains_something "$XENDOMAINS_SAVE"
531    then
532      return 0
533    fi
534    missing=`/bin/ls $XENDOMAINS_SAVE`
535    echo -n " MISS SAVED: " $missing
536    return 1
537}
538
539# This does NOT necessarily restart all running domains: instead it
540# stops all running domains and then boots all the domains specified in
541# AUTODIR.  If other domains have been started manually then they will
542# not get restarted.
543# Commented out to avoid confusion!
544
545restart()
546{
547    stop
548    start
549}
550
551reload()
552{
553    restart
554}
555
556
557case "$1" in
558    start)
559	start
560	rc_status
561	if test -f $LOCKFILE; then rc_status -v; fi
562	;;
563
564    stop)
565	stop
566	rc_status -v
567	;;
568
569    restart)
570	restart
571	;;
572    reload)
573	reload
574	;;
575
576    status)
577	echo -n "Checking for xendomains:"
578	if test ! -f $LOCKFILE; then
579	    rc_failed 3
580	else
581	    check_all_auto_domains_up
582	    rc_status
583	    check_all_saved_domains_up
584	    rc_status
585	fi
586	rc_status -v
587	;;
588
589    *)
590	echo "Usage: $0 {start|stop|restart|reload|status}"
591	rc_failed 3
592	rc_status -v
593	;;
594esac
595
596rc_exit
597