--- a/kvmctl Fri Oct 24 15:00:59 2008 +0000
+++ b/kvmctl Sat Oct 25 22:20:50 2008 +0000
@@ -1,132 +1,177 @@
#! /bin/bash
-# status: in development
# $Id$
# $URL$
-# Heiko Schlittermann
+# status: in development
+# 2008 Heiko Schlittermann
# Structure of virtual machine directories
-# $DIR/ (see below, hard coded into this script)
-#
-# $DIR/<vm1>/conv/cmdline # KVM options (ro)
-# # example:
-# # -hda rootfs.img
-# # Note: paths are relative to $DIR/<vm1>
-# id # KVM id (ro)
-# # Note: has to be unique among all VMs
-# $DIR/<vm1>/var/pid
-# frozen
-# .cmdline
-# $DIR/<vm1>/<image>
-#
-# $DIR/<vm2>/...
-# ... ...
-#
-# <vmN>/...
+# $BASE (see below, hard coded into this script)
+# |
+# +--<vm1>
+# | +--conf
+# | | +--options
+# | | +--id
+# | +--var
+# | | +--pid
+# | | +--running_options
+# | +--<image1>
+# | +-- ....
+# | +--<imageN>
+# |
+# +--<vm2>
+# | ....
+# +--<vmN>
-DIR=/kvm
action="${1?}"; shift
-vm="$1"; shift
+vm="$1" ; shift
# no user service able parts below
-unset ${!LC_*} LANG
-unset CDPATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-
-#set +m # switch off job control
+unset ${!LC_*} LANG CDPATH
ME=$(basename $0)
+BASE=/kvm
KVM=$(command -v kvm)
NC=$(command -v nc)
FUSER=$(command -v fuser)
VNCVIEWER=$(command -v vncviewer)
+TIMEOUT=30
-die() { echo $ME: "$@" >&2; exit 1; }
-warn() { echo $ME: "$@" >&2; }
+
+for f in "/etc/$ME.conf" ~/.$ME; do test -r "$f" && source "$f"; done
+
+
+die() { echo $ME: "$@" >&2; exit 1; }
+warn() { echo $ME: "$@" >&2; }
+silent() { "$@" &>/dev/null; }
+
+
+unique_ids() {
+ local dups=$(cat $BASE/*/conf/id 2>/dev/null | sort -n | uniq -c \
+ | egrep -v '^[[:space:]]+1 ' | tr -s ' ' | cut -f2 -d:)
+ test "$dups" || return 0
+ ERR="$dups" && return 1
+}
+convert_dirs() {
+ for dir in $BASE/*; do
+ test -d $dir/kvm || test -d $dir/conf || continue
+ local old=$dir/kvm
+ local new=$dir/conf
+ test -d $old && mv $old $new
+ test -f $new/cmdline && mv $new/cmdline $new/options
+ done
+}
### MAIN
# sanity
-test "$NC" || die "need nc (netcat)"
-test "$KVM" || die "need kvm"
-test "$FUSER" || die "need fuser"
+test -x "$NC" || die "need nc (netcat)"
+test -x "$KVM" || die "need kvm/qemu/kqemu"
+test -x "$FUSER" || die "need fuser"
+test -d "$BASE" || die "base dir $BASE does not exist"
+test -d "$BASE/$vm" || die "vm $vm does not exist"
-dups=$(cat $DIR/*/conf/id | sort -n | uniq -c \
- | egrep -v '^[[:space:]]+1 ' | tr -s ' ' | cut -f2 -d:)
-test "$dups" && die "duplicate ids (id: $dups)"
+unique_ids || die "duplicate ids (id: $ERR)"
+convert_dirs
-for vm in ${vm:-$(echo $DIR/*)}; do
+trap 'test "$TMP" && rm -fr $TMP' EXIT
+
+for vm in ${vm:-$(echo $BASE/*)}; do
vm=$(basename $vm)
- conf=$DIR/$vm/conf
- var=$DIR/$vm/var
+ conf=$BASE/$vm/conf
+ var=$BASE/$vm/var
- # silent conversion
- test -d $DIR/$vm/kvm && ! test -d $DIR/$vm/conf \
- && mv $DIR/$vm/kvm $DIR/$vm/conf
+ tmp=$(mktemp -d)
+ TMP="$TMP $tmp"
- test -d $vm || { warn "no such file or directory: $vm"; continue; }
- test -d $conf || continue
- test -f $conf/id || { warn "need id file (vm: $vm)" && continue; }
- test -f $conf/cmdline || { warn "need cmdline file (vm: $vm)" && continue; }
+ test -d $conf || continue
+ test -f $conf/id || { warn "need id file (vm: $vm)" && continue; }
+ test -f $conf/options || { warn "need options file (vm: $vm)" && continue; }
test -d $var || mkdir $var || exit 2
id=$(cat $conf/id)
- monitor=$((4000 + id))
+ port=$((4000 + id))
pid=$(cat $var/pid 2>/dev/null)
case "$action" in
start)
- echo "starting $vm"
- cmdline="$(grep -v '^#' $conf/cmdline) \
- -pidfile var/pid \
- -name "$vm" \
- -vnc :$id \
- -usb -usbdevice tablet \
- -monitor tcp:localhost:$monitor,server,nowait \
- -daemonize \
- $@"
- echo "$cmdline" > $var/.cmdline
- test -f $var/frozen && cmdline="$cmdline $(cat $var/frozen)"
+ echo "starting $vm"
+ options="$(grep -v '^#' $conf/options) \
+ -pidfile var/pid \
+ -name "$vm" \
+ -vnc :$id \
+ -monitor tcp:localhost:$port,server,nowait \
+ $@"
+ options=$(echo "" $options) # shorten it
+ echo "$options" > $var/running_options
+ test -f $var/frozen && options="$options $(cat $var/frozen)"
- ( cd $DIR/$vm && $KVM $cmdline )
- pid=$(cat $var/pid)
- echo "started, pid: $pid, display :$id, monitor tcp:127.0.0.1:$monitor"
+ if silent fuser $var/pid; then
+ echo "running, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port"
+ else
+ ( cd $BASE/$vm && $KVM $options -daemonize )
+ pid=$(cat $var/pid 2>/dev/null)
+ test "$pid" || { warn "serious problem ($vm), no pid" && continue; }
+ echo "started, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port"
+ fi
- test "$DISPLAY" && test "$VNCVIEWER" && test -t \
- && ( $VNCVIEWER :$id &>/dev/null </dev/null & )
+ test "$DISPLAY" && test "$VNCVIEWER" && test -t \
+ && ( silent exec $VNCVIEWER :$id </dev/null & )
- ;;
- stop) echo "stopping (powerdown) $vm"
- #{
- { echo system_powerdown | $NC localhost $monitor; } & pid0=$!
- #( sleep 30; kill $pid0 &>/dev/null ) & pid1=$!
-echo $pid0
- wait $pid0
-echo $?
- #kill $pid1 2>/dev/null
- #kill -0 $pid 2>/dev/null && $0 kill $vm
- #} &
- ;;
- quit) echo "quit $vm"
- $FUSER -k $monitor/tcp
- echo quit | $NC 127.0.0.1 $monitor &
- ;;
- kill) echo "killing $vm"
- warn "killing vm $vm (id: $id, pid: $pid)"
- kill $pid &
- ;;
+ ;;
+ stop)
+ {
+ echo "stopping (powerdown) $vm"
+ a=$tmp/a; b=$tmp/b; mkfifo $a $b
+ ( $NC localhost $port 0<>$a 1<>$b ) & pid0=$!
+ read <$b # we should not send anything before we got the first line
+ echo -e 'system_powerdown\n' >$a
+ ( cat <$b & sleep $TIMEOUT; silent kill $! $pid0 ) & pid1=$!
+ wait $pid0
+ kill $pid1 2>/dev/null
+ silent fuser $var/pid && $0 quit $vm
+ } &
+ ;;
+ quit)
+ {
+ echo "quit $vm"
+ a=$tmp/a; b=$tmp/b; mkfifo $a $b
+ ( $NC localhost $port 0<>$a 1<>$b ) & pid0=$!
+ read <$b
+ echo -e 'quit\n' >$a
+ ( cat <$b & sleep $TIMEOUT; silent kill $! $0 ) & pid1=$!
+ wait $pid0
+ kill $pid1 2>/dev/null
+ silent fuser $var/pid && $0 kill $vm
+ } &
+ ;;
+ kill)
+ echo "killing $vm"
+ kill $pid
+ ;;
freeze) echo "freezing $vm"
- fuser -k $monitor/tcp
- echo -e "savevm HIBERNATE\nquit" | $NC localhost $monitor &
- echo "-loadvm HIBERNATE" >$var/frozen
- ;;
+ fuser -k $port/tcp
+ echo -e "savevm HIBERNATE\nquit" | $NC localhost $port &
+ echo "-loadvm HIBERNATE" >$var/frozen
+ ;;
#status) echo "status $vm: "
qemu)
- echo "DO *NOT* USE \"quit\", use ^C for exiting!"
- $NC localhost $monitor
- ;;
+ echo "DO *NOT* USE \"quit\", use ^C for exiting!"
+ $NC localhost $port
+ ;;
+
+ list)
+ echo "$vm:" $(silent fuser $var/pid \
+ && echo "running (pid: $pid)" \
+ || echo "not running")
+ ;;
+
+ *) die "unknown command \"$action\""
+ ;;
+
esac
done