started some real work.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.perltidyrc Sat Aug 08 01:20:27 2009 +0200
@@ -0,0 +1,1 @@
+--paren-tightness=2
--- a/kvmtool Wed Jan 14 20:26:10 2009 +0100
+++ b/kvmtool Sat Aug 08 01:20:27 2009 +0200
@@ -1,201 +1,130 @@
-#! /bin/bash
-# $Id$
-# $URL$
-# status: in development
-# 2008 Heiko Schlittermann
-
-# Should be written in Perl!
+#! /usr/bin/perl
+# © 2009 Heiko Schlittermann
-# Structure of virtual machine directories
-# $BASE (see below, hard coded into this script)
-# |
-# +--<vm1>
-# | +--conf
-# | | +--options
-# | | +--id
-# | +--var
-# | | +--pid
-# | | +--running_options
-# | +--<image1>
-# | +-- ....
-# | +--<imageN>
-# |
-# +--<vm2>
-# | ....
-# +--<vmN>
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use File::Basename;
+use Perl6::Slurp;
+use feature qw(:5.10);
+use if $ENV{DEBUG} => "Smart::Comments";
+my $ME = basename $0;
+my $opt_dev = "/dev/kvm";
+my $opt_dir = "/var/lib/kvm";
+
+my $KVM = "kvm";
+my $CONFIG = "%s.kvm.conf";
+my $PIDFILE = "$ENV{HOME}/.kvm-%s.pid";
+my $KILL = 15;
-action="${1?}"; shift # mandatory
-vm="$1" ; shift # optional
-
-# no user service able parts below
-
-PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-unset ${!LC_*} LANG CDPATH
+sub _start(@);
+sub _kill(@);
-ME=$(basename $0)
-BASE=/kvm
-KVM=$(command -v kvm)
-NC=$(command -v nc)
-FUSER=$(command -v fuser)
-VNCVIEWER=$(command -v vncviewer)
-TIMEOUT=30
+MAIN: {
-
-for f in "/etc/$ME.conf" ~/.$ME; do test -r "$f" && source "$f"; done
-
-
-die() { echo $ME: "$@" >&2; exit 1; }
-warn() { echo $ME: "$@" >&2; }
-silently() { "$@" &>/dev/null; }
+ GetOptions(
+ "h|help" => sub { pod2usage(-verbose => 1, -exit => 0) },
+ "m|man" => sub { pod2usage(-verbose => 2, -exit => 0) },
+ "dev=s" => \$opt_dev,
+ "dir=s" => \$opt_dir,
+ ) or pod2usage(-exit => 1);
-debug() { echo "DEBUG: $@" >&2; }
+ my $cmd = shift;
+
+ -w $opt_dev or die "$ME: write access to $opt_dev: $!\n";
+ -w $opt_dir or die "$ME: write access to $opt_dir: $!\n";
-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
+ _start(@ARGV) if $cmd eq "start";
+ _kill(@ARGV) if $cmd eq "kill";
+ die "internal error\n";
+
}
-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
-}
-
-monitor() {
- local port=$1; shift
- local command="$1"; shift
+sub get_config($) {
+ defined(my $kvm = shift) or pod2usage;
+ my @config = slurp sprintf($CONFIG, $kvm), { chomp => 1 };
+ (shift @config) =~ /^#\s+kvm/
+ or die "wrong file format („# kvm” expected)\n";
- # check, if the monitor is available, kill if somebody
- # else is using it
- local client=$(netstat -tn | egrep ":$port[[:space:]]+EST" | tr -s ' ' | cut -f4 -d' ')
- test "${client%:*}" = 127.0.0.1 && fuser -k ${client#*:}/tcp
+ if (not /^-pidfile/ ~~ @config) {
+ push @config, sprintf "-pidfile $PIDFILE", $kvm;
+ }
- local pid0 pid1
- (
- local a=$tmp/a.fifo; mkfifo $a
- trap "rm -v $a" EXIT
- exec 10<>$a
- $NC localhost $port <&10 | {
- read || exit # banner
- while read -d' '; do test "$REPLY" = "(qemu)" && break; done
- echo "$command" >&10
- while read -d' '; do test "$REPLY" = "(qemu)" && break; done
- }
- ) & pid0=$!
- ( sleep $TIMEOUT; kill $pid0 ) & pid1=$!
- wait $pid0
- silently kill $pid0 $pid1
+ if (not /^-name/ ~~ @config) {
+ push @config, "-name $kvm";
+ };
+
+ return @config;
}
-### MAIN
+sub _kill(@) {
+ my $kvm = shift;
+ my @config = get_config($kvm);
-# sanity
-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"
+ (/^-pidfile\s+(?<pidfile>.*)/ ~~ @config);
+ my $pid = slurp $+{pidfile}, { chomp => 1 };
-unique_ids || die "duplicate ids (id: $ERR)"
-convert_dirs
-
-trap 'test "$TMP" && rm -fr $TMP' EXIT
+ kill $KILL => $pid;
+ waitpid($pid, 0);
+ unlink $+{pidfile};
-for vm in ${vm:-$(echo $BASE/*)}; do
- vm=$(basename $vm)
- conf=$BASE/$vm/conf
- var=$BASE/$vm/var
-
- tmp=$(mktemp -d)
- TMP="$TMP $tmp"
+ exit;
+}
- 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
+sub _start(@) {
+ my $kvm = shift;
+ my @config = get_config($kvm);
- id=$(cat $conf/id)
- port=$((4000 + id))
- pid=$(cat $var/pid 2>/dev/null)
-
- case "$action" in
+ defined(my $pid = fork) or die "Can't fork: $!\n";
- start)
- 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)"
+ if ($pid == 0) {
+ exec $KVM "kvm-$KVM" => map { split } @config;
+ die "Can't exec: $!";
+ }
+
+ exit;
+}
+
+__END__
+
+=head1 NAME
+
+kvmtool - tool to start and stop kvm w/o any X11
- if silently 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
+=head1 SYNOPSIS
+
+ kvmtool start <name>
+
+=head1 DESCRITPTION
+
+=head1 OPTIONS
- test "$DISPLAY" && test "$VNCVIEWER" && test -t \
- && ( silently exec $VNCVIEWER :$id </dev/null & )
+=over
+
+=item B<--dev>=I<device>
+
+Pathname of the KVM device. (default: /dev/kvm)
+
+=item B<--dir>=I<dir>
+
+Directory for meta share information about KVMs.
- ;;
- stop)
- {
- echo "stopping (powerdown) $vm"
- monitor $port "system_powerdown"
- silently fuser $var/pid && $0 quit $vm
- } &
- ;;
- quit)
- {
- echo "quit $vm"
- monitor $port "quit"
- silently fuser $var/pid && $0 kill $vm
- } &
- ;;
- kill)
- echo "killing $vm"
- kill $pid
- ;;
- freeze)
- {
- echo "freezing $vm"
- monitor $port "savevm HIBERNATED"
- monitor $port "quit"
- echo "-loadvm HIBERNATED" >$var/frozen
- } &
- ;;
- qemu)
- echo "DO *NOT* USE \"quit\", use ^C for exiting!"
- $NC localhost $port
- ;;
+=item B<-h>|B<--help>
+
+Show a short help page.
+
+=item B<-m>|B<--man>
+
+Show this manual page.
- list)
- echo "$vm:" $(silently fuser $var/pid \
- && echo "running (pid: $pid)" \
- || echo "not running")
- ;;
+=back
+
+=head1 AUTHOR
- *) die "unknown command \"$action\""
- ;;
-
- esac
+ Heiko Schlittermann <hs@schlittermann.de>
-done
+=cut
-wait
-
-# vim:ts=4 sw=4 et:
+# vim:sts=4 aw ai sm:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/kvmtool.sh Sat Aug 08 01:20:27 2009 +0200
@@ -0,0 +1,201 @@
+#! /bin/bash
+# $Id$
+# $URL$
+# status: in development
+# 2008 Heiko Schlittermann
+
+# Should be written in Perl!
+
+# Structure of virtual machine directories
+# $BASE (see below, hard coded into this script)
+# |
+# +--<vm1>
+# | +--conf
+# | | +--options
+# | | +--id
+# | +--var
+# | | +--pid
+# | | +--running_options
+# | +--<image1>
+# | +-- ....
+# | +--<imageN>
+# |
+# +--<vm2>
+# | ....
+# +--<vmN>
+
+
+action="${1?}"; shift # mandatory
+vm="$1" ; shift # optional
+
+# no user service able parts below
+
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+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
+
+
+for f in "/etc/$ME.conf" ~/.$ME; do test -r "$f" && source "$f"; done
+
+
+die() { echo $ME: "$@" >&2; exit 1; }
+warn() { echo $ME: "$@" >&2; }
+silently() { "$@" &>/dev/null; }
+
+debug() { echo "DEBUG: $@" >&2; }
+
+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
+}
+
+monitor() {
+ local port=$1; shift
+ local command="$1"; shift
+
+ # check, if the monitor is available, kill if somebody
+ # else is using it
+ local client=$(netstat -tn | egrep ":$port[[:space:]]+EST" | tr -s ' ' | cut -f4 -d' ')
+ test "${client%:*}" = 127.0.0.1 && fuser -k ${client#*:}/tcp
+
+ local pid0 pid1
+ (
+ local a=$tmp/a.fifo; mkfifo $a
+ trap "rm -v $a" EXIT
+ exec 10<>$a
+ $NC localhost $port <&10 | {
+ read || exit # banner
+ while read -d' '; do test "$REPLY" = "(qemu)" && break; done
+ echo "$command" >&10
+ while read -d' '; do test "$REPLY" = "(qemu)" && break; done
+ }
+ ) & pid0=$!
+ ( sleep $TIMEOUT; kill $pid0 ) & pid1=$!
+ wait $pid0
+ silently kill $pid0 $pid1
+}
+
+### MAIN
+
+# sanity
+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"
+
+unique_ids || die "duplicate ids (id: $ERR)"
+convert_dirs
+
+trap 'test "$TMP" && rm -fr $TMP' EXIT
+
+for vm in ${vm:-$(echo $BASE/*)}; do
+ vm=$(basename $vm)
+ conf=$BASE/$vm/conf
+ var=$BASE/$vm/var
+
+ tmp=$(mktemp -d)
+ TMP="$TMP $tmp"
+
+ 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)
+ port=$((4000 + id))
+ pid=$(cat $var/pid 2>/dev/null)
+
+ case "$action" in
+
+ start)
+ 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)"
+
+ if silently 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 \
+ && ( silently exec $VNCVIEWER :$id </dev/null & )
+
+ ;;
+ stop)
+ {
+ echo "stopping (powerdown) $vm"
+ monitor $port "system_powerdown"
+ silently fuser $var/pid && $0 quit $vm
+ } &
+ ;;
+ quit)
+ {
+ echo "quit $vm"
+ monitor $port "quit"
+ silently fuser $var/pid && $0 kill $vm
+ } &
+ ;;
+ kill)
+ echo "killing $vm"
+ kill $pid
+ ;;
+ freeze)
+ {
+ echo "freezing $vm"
+ monitor $port "savevm HIBERNATED"
+ monitor $port "quit"
+ echo "-loadvm HIBERNATED" >$var/frozen
+ } &
+ ;;
+ qemu)
+ echo "DO *NOT* USE \"quit\", use ^C for exiting!"
+ $NC localhost $port
+ ;;
+
+ list)
+ echo "$vm:" $(silently fuser $var/pid \
+ && echo "running (pid: $pid)" \
+ || echo "not running")
+ ;;
+
+ *) die "unknown command \"$action\""
+ ;;
+
+ esac
+
+done
+
+wait
+
+# vim:ts=4 sw=4 et: