You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
7.3 KiB
324 lines
7.3 KiB
#!/sbin/openrc-run |
|
# vim: set ft=sh: ts=4: |
|
# source: https://github.com/jirutka/qemu-openrc |
|
|
|
VERSION='0.10.0' |
|
VM_NAME="${RC_SVCNAME#qemu.}" |
|
|
|
: ${user:=qemu} |
|
: ${group:=qemu} |
|
: ${pidfile:=/run/qemu/${VM_NAME}/qemu.pid} |
|
: ${logfile:=/var/log/qemu/${VM_NAME}.log} |
|
: ${shutdown_timeout:=40} |
|
|
|
: ${system_type:=x86_64} |
|
: ${enable_kvm:=yes} |
|
: ${cpu_model:=host} |
|
: ${smp_cpus:=1} |
|
: ${smp_cpus_max:=$smp_cpus} |
|
: ${memory:=1G} |
|
: ${memory_max:=$memory} |
|
: ${memory_slots:=2} |
|
: ${memory_hugepages:=no} |
|
: ${rtc_base:=utc} |
|
: ${vga:=std} |
|
: ${vnc_listen:=0.0.0.0} |
|
: ${hugepages_path:=/dev/hugepages} |
|
: ${monitor_socket:=/run/qemu/${VM_NAME}/monitor.sock} |
|
: ${guest_agent_socket:=/run/qemu/${VM_NAME}/ga.sock} |
|
: ${extra_args:=} |
|
|
|
name="VM $VM_NAME" |
|
description="QEMU virtual machine \"$VM_NAME\"" |
|
|
|
extra_commands='forcestop version' |
|
description_forcestop='Force stop the system' |
|
description_version='Show version of this script' |
|
|
|
extra_started_commands='reset resume suspend vmstatus' |
|
description_reset='Reset the system' |
|
description_resume='Resume suspended VM' |
|
description_suspend='Suspend running VM' |
|
description_vmstatus='Show status reported by QEMU' |
|
|
|
command="/usr/bin/qemu-system-$system_type" |
|
command_args=" |
|
-name $VM_NAME,process=$VM_NAME |
|
-nodefaults |
|
-no-user-config |
|
-cpu $cpu_model |
|
-overcommit mem-lock=off |
|
-rtc base=$rtc_base |
|
-smp cpus=$smp_cpus,maxcpus=$smp_cpus_max |
|
-device virtio-balloon |
|
-vga $vga |
|
-device virtio-rng-pci |
|
-device virtio-scsi-pci,id=scsi |
|
-monitor unix:$monitor_socket,server,nowait |
|
-chardev socket,path=$guest_agent_socket,server,nowait,id=qga0 |
|
-device isa-serial,chardev=qga0" |
|
command_background='yes' |
|
|
|
start_stop_daemon_args=" |
|
--user=$user |
|
--wait=100 |
|
--stdout=$logfile |
|
--stderr=$logfile" |
|
|
|
required_files="$command" |
|
|
|
depend() { |
|
need net |
|
after iptables ip6tables ebtables |
|
} |
|
|
|
start_pre() { |
|
if [ "$RC_SVCNAME" = 'qemu' ]; then |
|
eerror '' |
|
eerror 'You are not supposed to run this runscript directly. Instead, you should' |
|
eerror 'create a symlink for the VM you want to run as well as a copy of the' |
|
eerror 'configuration file and modify it appropriately, like so:' |
|
eerror '' |
|
eerror ' ln -s qemu /etc/init.d/qemu.example' |
|
eerror ' cp /etc/conf.d/qemu /etc/conf.d/qemu.example' |
|
return 1 |
|
fi |
|
|
|
if yesno "$enable_kvm"; then |
|
command_args_push '-enable-kvm' |
|
fi |
|
if [ "$memory" = "$memory_max" ]; then |
|
memory_slots="0" |
|
fi |
|
command_args_push "-m size=$memory,slots=$memory_slots,maxmem=$memory_max" |
|
|
|
if yesno "$memory_hugepages"; then |
|
command_args_push "-mem-path $hugepages_path" |
|
fi |
|
if [ -n "$vnc_display" ]; then |
|
command_args_push "-display vnc=${vnc_listen}:${vnc_display}${vnc_password:+",password"}" |
|
fi |
|
command_args_push "$(net_args) $(disk_args) $(cdrom_args) $extra_args" |
|
|
|
if yesno "$EINFO_VERBOSE"; then |
|
einfo "Command: $command $(printf '%s ' $command_args)" |
|
fi |
|
|
|
local path; for path in "$pidfile" "$monitor_socket" "$logfile"; do |
|
# checkpath doesn't create intermediate directories |
|
mkdir -p "$(dirname "$path")" |
|
checkpath -d -m 0750 -o $user:$group "$(dirname "$path")" |
|
done |
|
|
|
return 0 |
|
} |
|
|
|
start_post() { |
|
ewaitfile 5 "$monitor_socket" || { |
|
eerror 'Monitor socket has not been created!'; return 1 |
|
} |
|
if [ -n "$vnc_password" ]; then |
|
qemush "set_password vnc $vnc_password" || eerror 'Failed to set VNC password!' |
|
fi |
|
} |
|
|
|
stop() { |
|
local count=0 |
|
local retval=0 |
|
|
|
ebegin "Stopping $name" |
|
|
|
if is_running && qemush "${guest_agent_socket}" "{'execute': 'guest-exec', 'arguments': {'path': '/sbin/halt', 'arg': ['-p']}}"; then |
|
count="$shutdown_timeout" |
|
|
|
printf " Waiting $count seconds for VM shutdown via guest agent " |
|
while is_running && [ $count -gt 0 ]; do |
|
sleep 1 |
|
printf '.' |
|
count=$(( count - 1 )) |
|
done |
|
printf '\n' |
|
fi |
|
|
|
if is_running && qemush 'system_powerdown'; then |
|
count="$shutdown_timeout" |
|
|
|
printf " Waiting $count seconds for VM shutdown via ACPI" |
|
while is_running && [ $count -gt 0 ]; do |
|
sleep 1 |
|
printf '.' |
|
count=$(( count - 1 )) |
|
done |
|
printf '\n' |
|
fi |
|
|
|
if [ $count -eq 0 ]; then |
|
ewarn 'Failed to shutdown VM gracefully using ACPI, stopping it with force' |
|
|
|
start-stop-daemon --stop \ |
|
--quiet --retry 'SIGKILL/5' \ |
|
--pidfile "$pidfile" --exec "$command" |
|
retval="$?" |
|
fi |
|
|
|
eend $retval |
|
} |
|
|
|
stop_post() { |
|
[ -S "$monitor_socket" ] && rm -f "$monitor_socket" |
|
[ -f "$pidfile" ] && rm -f "$pidfile" |
|
return 0 |
|
} |
|
|
|
forcestop() { |
|
ebegin "Force stopping $name" |
|
|
|
start-stop-daemon --stop \ |
|
--quiet --retry 'SIGKILL/3' \ |
|
--pidfile "$pidfile" --exec "$command" |
|
local retval="$?" |
|
|
|
if [ $retval -eq 0 ]; then |
|
if service_started "$RC_SVCNAME"; then |
|
mark_service_stopped "$RC_SVCNAME" |
|
fi |
|
stop_post |
|
fi |
|
|
|
eend $retval |
|
} |
|
|
|
reset() { |
|
ebegin "Resetting $name" |
|
|
|
qemush 'system_reset' |
|
eend $? |
|
} |
|
|
|
resume() { |
|
ebegin "Resuming suspended $name" |
|
|
|
qemush 'cont' |
|
eend $? |
|
} |
|
|
|
suspend() { |
|
ebegin "Suspending $name" |
|
|
|
qemush 'stop' |
|
eend $? |
|
} |
|
|
|
vmstatus() { |
|
qemush_show 'info status' | tr -d '\r' | xargs einfo |
|
} |
|
|
|
version() { |
|
echo "qemu-openrc $VERSION" |
|
} |
|
|
|
#-------------------------------- Helpers ------------------------------- |
|
|
|
is_running() { |
|
[ -e "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null |
|
} |
|
|
|
command_args_push() { |
|
command_args="$command_args $@" |
|
} |
|
|
|
disk_args() { |
|
local idx opts |
|
|
|
for idx in $(seq 0 9); do |
|
# comma-separated key=value pairs; contains trailing comma if not empty |
|
opts=$(set | sed -En "s/^disk${idx}_(.*)/\1/p" | tr $'\n' ',') |
|
|
|
if [ -n "$opts" ]; then |
|
echo "-drive id=hd${idx},${opts}media=disk,if=none" |
|
echo "-device scsi-hd,drive=hd${idx},scsi-id=${idx}" |
|
fi |
|
done |
|
} |
|
|
|
cdrom_args() { |
|
local idx file |
|
|
|
for idx in $(seq 0 9); do |
|
file=$(getval "cdrom${idx}_file") |
|
|
|
if [ -n "$file" ]; then |
|
echo "-drive id=cdr${idx},media=cdrom,if=none,file='$file',readonly,cache=none" |
|
echo "-device ide-cd,drive=cdr${idx}" |
|
fi |
|
done |
|
} |
|
|
|
net_args() { |
|
local idx net_id net_type opts mac dev |
|
|
|
for idx in $(seq 0 9); do |
|
net_id="net${idx}" |
|
net_type=$(getval "$net_id") |
|
|
|
if [ "$net_type" = 'bridge' ]; then |
|
check_bridge "$(getval ${net_id}_br br0)" |
|
fi |
|
|
|
if [ -n "$net_type" ]; then |
|
# comma-separated key=value pairs; contains trailing comma if not empty |
|
opts=$(set | sed -En "s/^net${idx}_(.*)/\1/p" \ |
|
| grep -Ev '^(mac|device)=.*$' \ |
|
| tr $'\n' ',') |
|
mac=$(getval ${net_id}_mac "$(gen_macaddr ${VM_NAME}#${idx})") |
|
dev=$(getval ${net_id}_device virtio-net-pci) |
|
|
|
echo "-netdev ${net_type},${opts}id=hostnet${idx}" |
|
echo "-device ${dev},id=${net_id},netdev=hostnet${idx},mac=${mac}" |
|
fi |
|
done |
|
} |
|
|
|
check_bridge() { |
|
local name="$1" |
|
|
|
if [ ! -e "/sys/class/net/$name" ]; then |
|
ewarn "WARNING: Bridge $name does not exist" |
|
return 1 |
|
fi |
|
|
|
if [ "$user" != 'root' ] \ |
|
&& ! grep -q "^allow\s*$name\W*" /etc/qemu/bridge.conf 2>/dev/null; then |
|
ewarn "WARNING: Bridge $name must be allowed in /etc/qemu/bridge.conf" |
|
return 1 |
|
fi |
|
} |
|
|
|
qemush() { |
|
local IFS=$'\n' |
|
socket_path="${monitor_socket}" |
|
case "$1" in |
|
/*) socket_path="$1"; shift; |
|
esac |
|
printf "%b\n" "$*" | socat - "UNIX-CONNECT:${socket_path}" 1>/dev/null |
|
} |
|
|
|
qemush_show() { |
|
local IFS=$'\n' |
|
socket_path="${monitor_socket}" |
|
case "$1" in |
|
/*) socket_path="$1"; shift; |
|
esac |
|
printf "%b\n" "$*" | socat - "UNIX-CONNECT:${socket_path}" | tail -n +3 | head -n -1 |
|
} |
|
|
|
gen_macaddr() { |
|
printf "$1" | md5sum | sed -E 's/^(..)(..)(..)(..).*$/52:54:\1:\2:\3:\4/' |
|
} |
|
|
|
getval() { |
|
local var_name="$1" |
|
local default="${2:-}" |
|
|
|
eval "printf '%s\n' \"\${$var_name:-$default}\"" |
|
}
|
|
|