#! /bin/bash
libdir="${libdir:-/usr/lib}"
sysconfdir="${sysconfdir:-/etc}"
statedir="${vardir:-/var}"
vmdir="${sysconfdir}/vm"
vmscriptsdir="${libdir}/vm/scripts"
vmscript_bridge="${vmscriptsdir}/net-bridge"
vmscript_route="${vmscriptsdir}/net-route"
piddir="${statedir}/run/kvm-mgmt/pid"
sockdir="${statedir}/run/kvm-mgmt"
name_len=10
disk_len=40
cd "${vmdir}" || exit 1
cmd="$1"
shift
function vm_exists () {
if [ -f "$1" ]; then
return 1
else
return 0
fi
}
function vm_state () {
local vm running
vm="$1"
running='0'
if echo info status | ( vm_opts "${vm}"; socat "unix-connect:${monfile}" stdio 2>/dev/null ) | grep '^VM status:' >/dev/null; then
running='1'
fi
if [ "${running}" = '1' ]; then
echo "running"
else
echo "halted"
fi
return 0
}
function vm_opts () {
local vm
unset qemu disk net mac cdrom boot ram procs prio console autoboot
vm="$1"
. "./DEFAULT" 2>/dev/null >/dev/null
. "./${vm}" 2>/dev/null >/dev/null
pidfile="${piddir}/${vm}.pid"
pid="$(cat "${pidfile}" 2>/dev/null | head -1)"
monfile="${sockdir}/${vm}.mon.sock"
serfile="${sockdir}/${vm}.ser.sock"
if [ -z "${console}" ]; then
console='curses'
fi
}
function vm_qemu_opts () {
local vm
vm="$1"
echo "-mem-path /dev/shm"
echo "-mem-prealloc"
echo "-cpu host"
(
vm_opts "${vm}"
echo "-qemu ${qemu:-qemu}"
echo "-pidfile ${pidfile}"
echo "-monitor unix:${monfile},server,nowait"
echo '-enable-kvm -machine type=pc,accel=kvm,kernel_irqchip=on'
diskif="${first_diskif:-virtio}"
for file in "${disk[@]}"; do
# Split name of disk and options
diskif_new="$(echo "${file}," | cut -f 2 -d ',')"
file="$(echo "${file}," | cut -f 1 -d ',')"
if [ -n "${diskif_new}" ]; then
diskif="${diskif_new}"
fi
echo "-drive file=${file},if=${diskif},format=raw,aio=native,cache=writeback"
diskif="${other_diskif:-virtio}"
done
idx='-1'
for netdev in "${net[@]}"; do
idx=$[${idx} + 1]
# Split name of network and options
netdev_opts_list="$(echo "${netdev}" | cut -f 2- -d ',')"
netdev="$(echo "${netdev}" | cut -f 1 -d ',')"
# Process options
## Default values
model='virtio'
## Process options
IFS=','
oidx='-'1
for option in "${netdev_opts_list}"; do
oidx=$[${oidx} + 1]
netdev_opts[${oidx}]="${option}"
done
unset IFS
for option in "${netdev_opts[@]}"; do
case "${option}" in
model=*)
model="$(echo "${option}" | cut -f 2 -d '=')"
;;
esac
done
ifmac="${mac[${idx}]}"
if [ -z "${ifmac}" ]; then
ifmac="52:54:$(echo "${vm}:${idx}" | openssl sha1 -hex -c | sed 's@.*= *@@' | cut -f 1-4 -d :)"
fi
# Return QEMU command line arguments
echo "-net nic,macaddr=${ifmac},model=${model},vlan=${idx}"
tmpscript="/tmp/script-$$${RANDOM}${RANDOM}${RANDOM}"
case "${netdev}" in
user)
echo "-net user,vlan=${idx}"
;;
nat)
sed 's|@@BRIDGE@@|'"natbr0"'|g' "${vmscript_bridge}" > "${tmpscript}"
chmod 755 "${tmpscript}"
echo "-net tap,ifname=${vm}-${idx},script=${tmpscript},vlan=${idx}"
;;
internal)
sed 's|@@BRIDGE@@|'"intbr0"'|g' "${vmscript_bridge}" > "${tmpscript}"
chmod 755 "${tmpscript}"
echo "-net tap,ifname=${vm}-${idx},script=${tmpscript},vlan=${idx}"
;;
*)
sed 's|@@IP@@|'"${netdev}"'|g' "${vmscript_route}" > "${tmpscript}"
chmod 755 "${tmpscript}"
echo "-net tap,ifname=${vm}-${idx},script=${tmpscript},vlan=${idx}"
;;
esac
done
if [ -n "${cdrom}" ]; then
echo "-cdrom ${cdrom}"
fi
if [ -n "${boot}" ]; then
echo "-boot ${boot}"
fi
if [ -n "${ram}" ]; then
echo "-m ${ram}"
fi
if [ -n "${procs}" ]; then
echo "-smp ${procs}"
fi
if [ -n "${prio}" ]; then
echo "-prio ${prio}"
fi
case "$(echo "${console}" | cut -f 1 -d ,)" in
vnc)
echo "-display vnc=$(echo "${console}" | cut -f 2- -d ,)"
;;
serial)
echo "-display none -vga none -serial unix:${serfile},server,nowait"
case "${console}" in
*,only)
;;
*)
echo "-device sga"
;;
esac
;;
curses|*)
echo "-display curses"
;;
esac
)
}
function list () {
local opt opt_verbose opt_parseable opt_all verbose_fmt_str state
opt_all='0'
opt_verbose='0'
opt_parseable='0'
while getopts avp opt; do
case "${opt}" in
a)
opt_all='1'
;;
v)
opt_verbose='1'
;;
p)
opt_parseable='1'
;;
'?'|':')
return 1
;;
esac
done
if [ "${opt_verbose}" = '1' -a "${opt_parseable}" = '1' ]; then
echo "vm: list -p and -v are mutually exclusive" >&2
return 1
fi
if [ "${opt_verbose}" = '1' ]; then
verbose_fmt_str="%6s %-${name_len}s %-10s %-${disk_len}s %s\n"
opt_trunc_disk='1'
printf "${verbose_fmt_str}" 'PID' 'NAME' 'STATUS' 'DISK' 'IP'
fi
if [ "${opt_parseable}" = '1' ]; then
opt_verbose='1'
opt_trunc_disk='0'
verbose_fmt_str='%s|%s|%s|%s|%s\n'
fi
for vm in *; do
if [ "${vm}" = 'DEFAULT' ]; then
continue
fi
if [ ! -f "${vm}" ]; then
continue
fi
state="$(vm_state "${vm}")"
if [ "${opt_all}" = '0' ]; then
if [ "${state}" != 'running' ]; then
continue
fi
fi
if [ "${opt_verbose}" = '0' ]; then
echo "${vm}"
else
(
vm_opts "${vm}"
if [ "${state}" != 'running' ]; then
pid=''
fi
if [ -z "${pid}" ]; then
pid='-'
fi
disk_display="${disk[0]}"
if [ "${opt_trunc_disk}" = '1' ]; then
if [ "${#disk_display}" -gt "${disk_len}" ]; then
disk_display="...${disk_display:(-$[${disk_len} - 3])}"
fi
fi
printf "${verbose_fmt_str}" "${pid}" "${vm}" "${state}" "${disk_display}" "${net[0]}"
)
fi
done | sort -r -k 4
return 0
}
function start () {
local vm
local qemu_vm_opts tmpscript
vm="$1"
shift
if [ -z "${vm}" ]; then
echo "Usage: vm start <vm>" >&2
return 1
fi
if [ "$(vm_state "${vm}")" != 'halted' ]; then
echo "VM is not halted: $(vm_state "${vm}")"
return 1
fi
# Load KVM kernel module
modprobe kvm_amd >/dev/null 2>/dev/null || modprobe kvm_intel >/dev/null 2>/dev/null
qemu_vm_opts="$(vm_qemu_opts "${vm}")"
qemu="$(echo "${qemu_vm_opts}" | awk '/^-qemu /{ gsub("^-qemu ", ""); print; exit }')"
qemu_vm_opts="$(echo "${qemu_vm_opts}" | grep -v '^-qemu ')"
prio="$(echo "${qemu_vm_opts}" | awk '/^-prio /{ print $2; exit }')"
if [ -z "${prio}" ]; then
prio='1'
else
qemu_vm_opts="$(echo "${qemu_vm_opts}" | grep -v '^-prio ')"
fi
mkdir -p "${piddir}" >/dev/null 2>/dev/null
if ( vm_opts "${vm}"; [ "${console}" != 'curses' ] ); then
nice -n "${prio}" ${qemu} -daemonize ${qemu_vm_opts} || return 1
else
screen -dmS "vm-${vm}" -t "${vm}" nice -n "${prio}" ${qemu} ${qemu_vm_opts} || return 1
fi
return 0
}
function stop () {
local vm pid
vm="$1"
shift
if [ -z "${vm}" ]; then
echo "Usage: vm stop <vm>" >&2
exit 1
fi
if [ "$(vm_state "${vm}")" != 'running' ]; then
echo "VM is not running: $(vm_state "${vm}")"
exit 1
fi
(
vm_opts "${vm}"
if ! echo quit | socat "unix-connect:${monfile}" stdio >/dev/null 2>/dev/null; then
if [ -z "${pid}" ]; then
echo "ERROR: Unable to determine PID ?!?" >&2
exit 1
fi
kill "${pid}" || exit 1
exit 0
else
exit 0
fi
) || return 1
return 0
}
function console () {
local vm
vm="$1"
shift
if [ -z "${vm}" ]; then
echo "Usage: vm console <vm>" >&2
exit 1
fi
if [ "$1" = 'wait-until-running' ]; then
for try in {1..33} _fail_; do
if [ "${try}" = '_fail_' ]; then
echo "VM is not running: $(vm_state "${vm}")"
exit 1
fi
if [ "$(vm_state "${vm}")" = 'running' ]; then
break
fi
sleep 0.3
done
else
if [ "$(vm_state "${vm}")" != 'running' ]; then
echo "VM is not running: $(vm_state "${vm}")"
exit 1
fi
fi
(
vm_opts "${vm}"
case "$(echo "${console}" | cut -f 1 -d ,)" in
serial)
exec socat "unix-connect:${serfile}" stdio,echo=0,raw,escape=29
;;
vnc)
vncaddress="$(echo 'info vnc' | socat "unix-connect:${monfile}" stdio 2>/dev/null | awk '/address: /{ print $2; exit }' | sed 's@^0\.0\.0\.0:@'"$(hostname)"':@')"
vncviewer ${vncaddress} || echo "Unable to run: vncviewer ${vncaddress}"
;;
curses|*)
screen -x -p "${vm}" -S "vm-${vm}" || exit 1
;;
esac
) || return 1
return 0
}
function monitor_console () {
local vm
vm="$1"
shift
if [ -z "${vm}" ]; then
echo "Usage: vm console <vm>" >&2
exit 1
fi
if [ "$(vm_state "${vm}")" != 'running' ]; then
echo "VM is not running: $(vm_state "${vm}")"
exit 1
fi
(
vm_opts "${vm}"
exec socat "unix-connect:${monfile}" stdio,echo=0,raw,escape=3
) || return 1
return 0
}
case "${cmd}" in
list)
list "$@" || exit 1
;;
start|boot)
console='0'
if [ "$1" = "-C" ]; then
shift
console='1'
fi
start "$@" || exit 1
if [ "${console}" = '1' ]; then
console "$@" wait-until-running
fi
;;
stop)
stop "$@" || exit 1
;;
console)
console "$@" || exit 1
;;
monitor_console)
monitor_console "$@" || exit 1
;;
autoboot)
for vm in *; do
if [ "${vm}" = 'DEFAULT' ]; then
continue
fi
if [ ! -f "${vm}" ]; then
continue
fi
(
vm_opts "${vm}"
if [ "${autoboot}" != '1' ]; then
exit 0
fi
if [ "$(vm_state "${vm}")" = 'running' ]; then
exit 0
fi
start "${vm}"
)
done
;;
stopall)
for vm in *; do
if [ "${vm}" = 'DEFAULT' ]; then
continue
fi
if [ ! -f "${vm}" ]; then
continue
fi
if [ "$(vm_state "${vm}")" != 'running' ]; then
continue
fi
stop "${vm}"
done
;;
*)
echo "usage: vm list [-vap]" >&2
echo " vm start [-C] <vm>" >&2
echo " vm stop <vm>" >&2
echo " vm console <vm>" >&2
exit 1
;;
esac