Artifact [9ad1985cc9]

Artifact 9ad1985cc975799ff8105461b6b3f522d4c7da14:


#! /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