# HG changeset patch # User heiko # Date 1224973250 0 # Node ID d8aba2189a2784b28401d4f811c96704f43ceed7 # Parent 30ac294f8cedaf64989dad4da6678eb862117f09 - seems to work even better, but's it's not perfect diff -r 30ac294f8ced -r d8aba2189a27 kvmctl --- 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//conv/cmdline # KVM options (ro) -# # example: -# # -hda rootfs.img -# # Note: paths are relative to $DIR/ -# id # KVM id (ro) -# # Note: has to be unique among all VMs -# $DIR//var/pid -# frozen -# .cmdline -# $DIR// -# -# $DIR//... -# ... ... -# -# /... +# $BASE (see below, hard coded into this script) +# | +# +-- +# | +--conf +# | | +--options +# | | +--id +# | +--var +# | | +--pid +# | | +--running_options +# | +-- +# | +-- .... +# | +-- +# | +# +-- +# | .... +# +-- -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 ) & 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