kvmctl
changeset 3 d8aba2189a27
parent 2 30ac294f8ced
child 4 1ab9431a52bc
--- 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