#!/bin/sh

# List Guests
__guest_list() {
	local flags="$1"
	# Pager Check (more is default on FreeBSD)
	if [ -z $PAGER ]; then
		pager="more"
	fi
	(
	printf "%s^%s^%s^%s^%s\n" "Guest" "VMM?" "Running" "rcboot?" "Description"
	local guests="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | tr '\t' ',')"
	for guest in $guests; do
		local dataset="$(echo $guest | cut -f1 -d,)"
		local name="$(echo $guest | cut -f2 -d,)"

		local vmm="/dev/vmm/ioh-$name"
		if [ -e $vmm ]; then
			vmm="YES"
		else
			vmm="NO"
		fi
		local running=$(pgrep -fx "bhyve: ioh-$name")
		if [ -z "$running" ]; then
			running="NO"
		else
			running="YES"
		fi
		local boot="$(zfs get -H -o value iohyve:boot $dataset)"
		if [ $boot = '1' ]; then
			boot="YES"
		else
			boot="NO"
		fi
		local description="$(zfs get -H -o value iohyve:description $dataset)"
		printf "%s^%s^%s^%s^%s\n" "$name" "$vmm" "$running" "$boot" "$description"
	done
	) | column -ts^ | \
	(
		if [ "$flags" = '-l' ]
		then
			$PAGER
		else
			cat
		fi
	)
}

# Display info about all guests.
__guest_info() {
	local flags="$@"
	# Pager Check (more is default on FreeBSD)
	if [ -z $PAGER ]; then
		pager="more"
	fi
	# Poll to see what pools have active guests
	local guests="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | tr '\t' ',')"

	( # Begining of very large table of output.
	# Print common header
	printf "%s^%s^%s^%s^%s" "Guest" "Size" "CPU" "RAM" "Pool"

	# Print headers for -v flag
	if [ -n "$(echo "$flags" | grep 'v')" ]; then
		printf "^%s^%s^%s^%s" "OS" "Loader" "Tap" "Con"
	fi

	# Print headers for -s flag
	if [ -n "$(echo "$flags" | grep 's')" ]; then
		printf "^%s^%s^%s" "VMM?" "Running" "rcboot?"
	fi

	# Print headers for -d flag
	if [ -n "$(echo "$flags" | grep 'd')" ]; then
		printf "^%s" "Description"
	fi

	# Print new line for first guest.
	printf "\n"

	# Poll to see which guests are on $pool
	# Spider through guests
	for guest in $guests; do
		local dataset="$(echo $guest | cut -f1 -d,)"
		local name="$(echo $guest | cut -f2 -d,)"
		local disks="$(zfs list -H -r -t volume -o name $dataset)"
		local pool="$(echo $dataset | cut -f1 -d/)"

		# Poll variables for common section
		local cpu="$(zfs get -H -o value iohyve:cpu $dataset)"
		local ram="$(zfs get -H -o value iohyve:ram $dataset)"

		# Print common section (guest, size, cpu, raml)
		printf "%s^-^%s^%s^%s" "$name" "$cpu" "$ram" "$pool"

		# Get variable and print for verbose section (os, loader, tap, con)
		if [ -n "$(echo "$flags" | grep 'v')" ]; then
			local os="$(zfs get -H -o value iohyve:os $dataset)"
			local loader="$(zfs get -H -o value iohyve:loader $dataset)"
			local tap="$(zfs get -H -o value iohyve:tap $dataset)"
			local con="$(zfs get -H -o value iohyve:con $dataset)"

			printf "^%s^%s^%s^%s" "$os" "$loader" "$tap" "$con"
		fi

		# Get info, set variables, and print for status section (VMM, Running, rcboot)
		if [ -n "$(echo "$flags" | grep 's')" ]; then
			local vmm="/dev/vmm/ioh-$name"
			if [ -e $vmm ]; then
				vmm="YES"
			else
				vmm="NO"
			fi
			local running=$(pgrep -fx "bhyve: ioh-$name")
			if [ -z "$running" ]; then
				running="NO"
			else
				running="YES"
			fi
			local boot="$(zfs get -H -o value iohyve:boot $dataset)"
			if [ $boot = '1' ]; then
				boot="YES"
			else
				boot="NO"
			fi

			printf "^%s^%s^%s" "$vmm" "$running" "$boot"
		fi

		# Print description if flag set
		if [ -n "$(echo "$flags" | grep 'd')" ]; then
			local description="$(zfs get -H -o value iohyve:description $dataset)"
			printf "^%s" "$description"
		fi

		# Print new line for next guest.
		printf "\n"

		for disk in $disks; do
			local diskname="$(echo $disk | rev | cut -f1 -d/ | rev)"
			local size="$(zfs get -H -o value iohyve:size $disk)"
			printf "^%s^%s^-^-^%s" "$name/$diskname" "$size" "$pool"
			if [ -n "$(echo "$flags" | grep 'v')" ]; then
				printf "^-^-^-^-"
			fi
			if [ -n "$(echo "$flags" | grep 's')" ]; then
				printf "^-^-^-"
			fi
			if [ -n "$(echo "$flags" | grep 'd')" ]; then
				local description="$(zfs get -H -o value iohyve:description $disk)"
				printf "^%s" "$description"
			fi
			printf "\n"
		done
	done
	) | column -ts^ | \
	(
		if [ -n "$(echo "$flags" | grep 'l')" ]
		then
			$PAGER
		else
			cat
		fi
	)
}

# Create guest
__guest_create() {
	local name="$2"
	local pool="${4-$(zfs get -H -s local,received -o name -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | head -n1 | cut -f1 -d/)}"
	local size="$3"
	if [ -z "$pool" ]; then
		local pool="$(zfs list -H | cut -d '/' -f-3 | grep /iohyve/ISO | grep -v /iohyve/ISO/ | cut -f1 -d/ |tail -n1)"
	fi
	if [ -z "$size" ]; then
		printf "missing argument\nusage:\n"
		printf "\tcreate <name> <size> [pool]\n"
		exit 1
	fi
	local description="$(date)"

	# Check if guest with this name already exists
	if [ -n "$(zfs get -H -o value -s local,received -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep ^$name$)" ]; then
		echo "Error: Guest with this name already exists"
		exit 1
	fi

	local taplast="$(__tap_last)"
	local conlast="$(__console_last)"
	echo "Creating $name..."
	zfs create -o iohyve:name=$name \
		-o iohyve:size=$size \
		-o iohyve:ram=512M \
		-o iohyve:cpu=1 \
		-o iohyve:tap=tap$taplast \
		-o iohyve:con=nmdm$conlast \
		-o iohyve:persist=1 \
		-o iohyve:boot=0 \
		-o iohyve:loader=bhyveload \
		-o iohyve:os=default \
		-o "iohyve:description=$description" \
		-o iohyve:bargs=-A_-H_-P \
		-o iohyve:template=NO \
		-o iohyve:utc=YES \
		-o iohyve:bhyve_path=/usr/sbin/bhyve \
		-o iohyve:vnc=NO \
		-o iohyve:vnc_ip=127.0.0.1 \
		-o iohvye:vnc_port=5900 \
		-o iohyve:vnc_w=800 \
		-o iohyve:vnc_h=600 \
		-o iohyve:vnc_wait=NO \
		-o iohyve:vnc_tablet=NO \
		$pool/iohyve/$name

	zfs create -V $size -o volmode=dev $pool/iohyve/$name/disk0

}

# Install guest
__guest_install() {
	# Check if vmm and nmdm modules are running
	if ! $(kldstat -q -m vmm); then
		echo "Kernel module vmmm not loaded, use iohyve setup kmod=1 to load it."
		echo "Guest will likely not boot until it's loaded."
	fi
	if ! $(kldstat -q -m nmdm); then
		echo "Kernel module nmdm not loaded."
		echo "If you intend to use serial console, use iohyve setup kmod=1 to load it."
	fi
	local name="$2"
	local iso="$3"
	
	if [ -z "$iso" ]; then
		printf "missing argument\nusage:\n"
		printf "\tinstall <name> <ISO>\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	local dataset="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local mountpoint="$(zfs get -H -o value mountpoint $dataset)"
	local loader="$(zfs get -H -o value iohyve:loader $dataset)"
	# Check if guest is a template
	local template="$(zfs get -H -o value iohyve:template $dataset)"
	if [ $template = "YES" ]; then
		echo "$name is a template. Cannot start"
		exit 1
	fi
	if [ $loader = "uefi" ]; then
		__guest_uefi "$name" "$iso"
		exit 0
	fi
	# Check if bhyve path is set, set default if not
	local bhyve_path="$(zfs get -H -o value iohyve:bhyve_path $dataset)"
	if [ "$bhyve_path" = "-" ]; then
		bhyve_path=/usr/sbin/bhyve
	fi
	# Check if guest exists
	if [ -d $mountpoint ]; then
		# Check to make sure guest isn't running
		local running=$(pgrep -fx "bhyve: ioh-$name")
		if [ -z "$running" ]; then
			local ram="$(zfs get -H -o value iohyve:ram $dataset)"
			local con="$(zfs get -H -o value iohyve:con $dataset)"
			local cpu="$(zfs get -H -o value iohyve:cpu $dataset)"
			local bargexist="$(zfs get -H -o value iohyve:bargs $dataset)"
			local bargs="$(echo $bargexist | sed -e 's/_/ /g')"
			echo "Installing $name..."
			# Set install prop
			zfs set iohyve:install=yes $dataset
			# Load from CD
			__guest_load "$name" "/iohyve/ISO/$iso/$iso"
			# Prepare and start guest
			pci="$(__guest_prepare $name) ahci-cd,/iohyve/ISO/$iso/$iso"
			local pci_args=$(__guest_get_bhyve_cmd "$pci" )
			$bhyve_path -c $cpu $bargs -m $ram $pci_args -lcom1,/dev/${con}A ioh-$name &
		else
			echo "Guest is already running."
		fi
	else
		echo "Not a valid guest name"
	fi
}

# Load guest
__guest_load() {
	local name="$1"
	local media="$2"

	if [ -z "$media" ]; then
		printf "missing argument\nusage:\n"
		printf "\tload <name> <path/to/bootdisk>\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	local disk="${3-$(zfs get -H -t volume -o name,value iohyve:name | cut -d '/' -f-4 | grep iohyve | grep "$(printf 'disk0\t')$name$" | cut -f1)}"
	local dataset="$(zfs get -H -o name,value -s local,received -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local ram="$(zfs get -H -o value iohyve:ram $dataset)"
	local con="$(zfs get -H -o value iohyve:con $dataset)"
	local loader="$(zfs get -H -o value iohyve:loader $dataset)"
	local install="$(zfs get -H -o value iohyve:install $dataset)"
	local os="$(zfs get -H -o value iohyve:os $dataset)"
	local autogrub="$(zfs get -H -o value iohyve:autogrub $dataset)"
	local bargexist="$(zfs get -H -o value iohyve:bargs $dataset)"
	local bargs="$(echo $bargexist | sed -e 's/_/ /g')"
	#Testing if -S is in the bargs settings. If then pass -S to bhyveload.
	local test_for_wire_memory="-S"
	case $bargs in
		*${test_for_wire_memory}*) local wire_memory="-S" ;;
		*) local wire_memory="" ;;
	esac
	# Check for loader already present as a zombie
	if [ ! -z $(pgrep -f "ioh-$name") ]; then
		echo "This guest appears to be already running/loaded."
		echo "Use stop, destroy, or forcekill to remove zombie processes."
		exit 1
	fi
	if [ $loader = "grub-bhyve" ]; then
		echo "GRUB Process does not run in background...."
		echo "If your terminal appears to be hanging, check iohyve console $name in second terminal to complete GRUB process..."
		if [ $install = "yes" ]; then
			if [ $os = "openbsd61" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 (cd0)/6.1/amd64/bsd.rd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd60" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 (cd0)/6.0/amd64/bsd.rd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd59" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 (cd0)/5.9/amd64/bsd.rd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd58" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 (cd0)/5.8/amd64/bsd.rd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd57" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 (cd0)/5.7/amd64/bsd.rd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "netbsd" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'knetbsd -h -r cd0a (cd0)/netbsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "debian" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r cd0  -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "d8lvm" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r cd0 -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "ubuntu" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r cd0 -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "centos6" ] || [ $os = "centos7" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'linux (cd0)/isolinux/vmlinuz\ninitrd (cd0)/isolinux/initrd.img\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "arch" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'linux (cd0)/arch/boot/x86_64/vmlinuz archisobasedir=arch archisolabel=ARCH_'$(date +%Y%m)' ro\ninitrd (cd0)/arch/boot/x86_64/archiso.img\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "custom" ]; then
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			else
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r cd0 -c /dev/${con}A -M $ram ioh-$name
			fi
		elif [ $install = "no" ]; then
			if [ $os = "openbsd61" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 -r sd0a (hd0,openbsd1)/bsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd60" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 -r sd0a (hd0,openbsd1)/bsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd59" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 -r sd0a (hd0,openbsd1)/bsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd58" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 -r sd0a (hd0,openbsd1)/bsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "openbsd57" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'kopenbsd -h com0 -r sd0a (hd0,openbsd1)/bsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "netbsd" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				printf 'knetbsd -h -r wd0a (hd0,msdos1)/netbsd\nboot\n' > /iohyve/$name/grub.cfg
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "debian" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r hd0,msdos1 -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "ubuntu" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r hd0,msdos1 -d /grub -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "centos6" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r hd0,msdos1 -d /grub -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "centos7" ]; then
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r hd0,msdos1 -d /grub2 -c /dev/${con}A -M $ram ioh-$name
			elif [ $os = "custom" ]; then
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r host -d /iohyve/$name -c /dev/${con}A -M $ram ioh-$name
			else
				printf '\(hd0\)\ /dev/zvol/'$disk'\n' > /iohyve/$name/device.map
				printf '\(cd0\)\ '$media'\n' >> /iohyve/$name/device.map
				grub-bhyve $wire_memory -m /iohyve/$name/device.map -r hd0,msdos1 -c /dev/${con}A -M $ram ioh-$name
			fi
		fi
	else
		bhyveload $wire_memory -m $ram -d $media -c /dev/${con}A ioh-$name
	fi
}

# Boot guest
__guest_boot() {
	local name="$1"
	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tboot <name> [runmode] [pcidevices]\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	# runmode (runonce/persist)
	#   0 = once
	#   1 = persist regular (stop if guest is powering off)
	#   2 = always persist (start again even if guest is powering off)
	local runmode="$2"
	local pci="$3"
	local dataset="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local ram="$(zfs get -H -o value iohyve:ram $dataset)"
	local con="$(zfs get -H -o value iohyve:con $dataset)"
	local cpu="$(zfs get -H -o value iohyve:cpu $dataset)"
	local persist="$(zfs get -H -o value iohyve:persist $dataset)"
	local bargexist="$(zfs get -H -o value iohyve:bargs $dataset)"
	local bargs="$(echo $bargexist | sed -e 's/_/ /g')"
	# Check if bhyve path is set, set default if not
	local bhyve_path="$(zfs get -H -o value iohyve:bhyve_path $dataset)"
	if [ "$bhyve_path" = "-" ]; then
		bhyve_path=/usr/sbin/bhyve
	fi
	# Set install prop
	zfs set iohyve:install=no $dataset
	# Generate list of bhyve -s commands for all devices
	local pci_args=$(__guest_get_bhyve_cmd "$pci" )
	# Handle the starting of the guest inside a spawned subshell so the guest
	# can be restarted automatically if the guest reboots or crashes
	local runstate="1"
	(
		while [ $runstate = "1" ]
		do
			__guest_load "$name" "/dev/zvol/$dataset/disk0"
			$bhyve_path -c $cpu $bargs -m $ram $pci_args -lcom1,/dev/${con}A ioh-$name &
			local vmpid=$!
			wait $vmpid
			vmrc=$?
			sleep 5
			if [ $runmode == "0" ]; then
				runstate="0"
			elif [ $vmrc == "1" ] && [ $runmode != 2 ]; then
				# VM has been powered off
				runstate="0"
			else
				if [ $(zfs get -H -o value iohyve:persist $dataset) != 1 ]; then
					runstate="0"
				fi
			fi
		done
		bhyvectl --destroy --vm=ioh-$name
		# Resetting the flag so that a vm which we stopped by abusing zfs set/get
		# as as an IPC mechanism is persistent again next time we start it
		if [ -n "$persist" ]; then
			zfs set iohyve:persist="$persist" $dataset
		fi
	) &
}

__guest_prepare() {
	local name="$1"
	local dataset="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local pci="$(__zfs_get_pcidev_conf $dataset)"
	# Setup tap if needed
	__tap_setup $dataset
	# Add tap interface
	pci="$pci $(__tap_pci $dataset)"
	# Add disk as second PCI device
	pci="ahci-hd,/dev/zvol/$dataset/disk0 $pci"
	# Add Hostbridge and lpc as the first PCI devices
	pci="hostbridge lpc $pci"
	# Return the list of pci devices
	echo $pci
}

# Start guest (combine load and boot)
__guest_start() {
	# Check if vmm and nmdm modules are running
	if ! $(kldstat -q -m vmm); then
		echo "Kernel module vmmm not loaded, use iohyve setup kmod=1 to load it."
		echo "Guest will likely not boot until it's loaded."
	fi
	if ! $(kldstat -q -m nmdm); then
		echo "Kernel module nmdm not loaded."
		echo "If you intend to use serial console, use iohyve setup kmod=1 to load it."
	fi
	local name="$2"
	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tstart <name> [-s | -a]\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	local flag="$3"
	local pci=""
	local runmode="1"
	local dataset="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local loader="$(zfs get -H -o value iohyve:loader $dataset)"
	# Check if guest is template
	local template="$(zfs get -H -o value iohyve:template $dataset)"
	if [ $template = "YES" ]; then
		echo "$name is a template. Cannot start"
		exit 1
	fi
	# Check if loader is UEFI
	if [ $loader = "uefi" ]; then
		__guest_uefi "$name" "$flag"
		exit 0
	fi
	local mountpoint="$(zfs get -H -o value mountpoint $dataset)"

	# Check if guest exists
	if [ -d $mountpoint ]; then
		# Check to make sure guest isn't running
		local running=$(pgrep -fx "bhyve: ioh-$name")
		if [ -z "$running" ]; then
			case "$flag" in
				-s)	runmode="0"	# single - start only once
					;;
				-a) 	runmode="2"	# always - persist regardless what
					;;
				*)	runmode="1"	# persist - persists until guest is powering off
					;;
			esac
			echo "Starting $name... (Takes 15 seconds for FreeBSD guests)"
			# Prepare and boot guest
			pci="$(__guest_prepare $name)"
			__guest_boot "$name" "$runmode" "$pci"
		else
			echo "Guest is already running."
		fi
	else
		echo "Not a valid guest name"
	fi
}

# Start a UEFI enabled bhyve instance.
# This is experimental, use with caution.
__guest_uefi() {
	local name="$1"
	local media="$2"

	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tuefi <name> <ISO>\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	local dataset="$(zfs get -H -s local,received -o name,value -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	local ram="$(zfs get -H -o value iohyve:ram $dataset)"
	local con="$(zfs get -H -o value iohyve:con $dataset)"
	local cpu="$(zfs get -H -o value iohyve:cpu $dataset)"
	local fw="$(zfs get -H -o value iohyve:fw $dataset)"
	local bargs="$(zfs get -H -o value iohyve:bargs $dataset | sed -e 's/_/ /g')"
	local utc="$(zfs get -H -o value iohyve:utc $dataset)"
	local vnc="$(zfs get -H -o value iohyve:vnc $dataset)"
	local vnc_ip="$(zfs get -H -o value iohyve:vnc_ip $dataset)"
	local vnc_port="$(zfs get -H -o value iohyve:vnc_port $dataset)"
	local vnc_w="$(zfs get -H -o value iohyve:vnc_w $dataset)"
	local vnc_h="$(zfs get -H -o value iohyve:vnc_h $dataset)"
	local vnc_wait="$(zfs get -H -o value iohyve:vnc_wait $dataset)"
	local vnc_tablet="$(zfs get -H -o value iohyve:vnc_tablet $dataset)"
	local sectorsize="$(zfs get -H -o value iohyve:sectorsize $dataset)"
	local pool="$(zfs list -H -t volume -o name | grep iohyve/$name | cut -d '/' -f1)"

	pci="$(__guest_prepare $name)"
	#Set sectorsize if set
	if [ ! $sectorsize = '-' ]; then
		pci=`echo $pci | sed -e "s/disk0/disk0,sectorsize=${sectorsize}/g"`
	else
		#Display Warning because the addition of sectorsize breaks backwards compability. Then continue with boot.
		echo "Warning! Sectorsize has not been set. This was previously a hardcoded value, to set the value to the previously hardcoded value use command \"iohyve set $name sectorsize=512\" "
	fi
	#set ISO media if install has been used.
	if [ ! -z $media ]; then
		pci="$pci ahci-cd,/iohyve/ISO/$media/$media"
	fi
	#echo $pci
	# Make sure everything is in order...
	if [ $fw = '-' ]; then
		echo "You must set a firmware file property to use UEFI..."
		exit 1
	fi
	local mountpoint="$(zfs get -H -o value mountpoint $dataset)"
	# Check if bhyve path is set, set default if not
	local bhyve_path="$(zfs get -H -o value iohyve:bhyve_path $dataset)"
	if [ "$bhyve_path" = "-" ]; then
		bhyve_path=/usr/sbin/bhyve
	fi
	# Check if guest exists
	if [ -d $mountpoint ]; then
		# Check to make sure guest isn't running
		local running=$(pgrep -fx "bhyve: ioh-$name")
		if [ -z "$running" ]; then
			# build -u argument
			if [ $utc = "YES" ]; then
				utcline="-u"
			else
				utcline=""
			fi
			# Check for vnc
			if [ $vnc = "YES" ]; then
				# build vnc arguments
				if [ $vnc_wait = "YES" ]; then
					vncline="fbuf,tcp=${vnc_ip}:${vnc_port},w=${vnc_w},h=${vnc_h},wait"
				else
					vncline="fbuf,tcp=${vnc_ip}:${vnc_port},w=${vnc_w},h=${vnc_h}"
				fi
				# build tablet argument
				if [ $vnc_tablet = "YES" ]; then
					tabletline="xhci,tablet"
				else
					tabletline=""
				fi
			else
				vncline=""
			fi
			pci="$pci $vncline $tabletline "
			#remove LPC needs to be hardcoded to -s 31,lpc
			pci=`echo $pci | sed -e "s/lpc//g"`
			local pci_args=$(__guest_get_bhyve_cmd "$pci" )
			pci_args="$pci_args -s 31,lpc"
			$bhyve_path -c $cpu $bargs -m $ram $pci_args $utcline -l com1,/dev/${con}A -l bootrom,/iohyve/Firmware/$fw/$fw ioh-$name &
		else
			echo "Guest is already running."
		fi
	else
		echo "Not a valid guest name"
	fi
}

# Gracefully stop a guest
__guest_stop() {
	local name="$2"
	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tstop <name>\n"
		exit 1
	fi

	local pid=$(pgrep -fx "bhyve: ioh-$name")
	echo "Stopping $name..."
#	zfs set iohyve:persist=0 $pool/iohyve/$name
	if [ -n "$pid" ]; then
		kill $pid
	else
		echo "$name not running"
	fi
#	sleep 20
#	bhyvectl --destroy --vm=ioh-$name
}

# Force kill -9 everyting matching $name and destroy
# THIS WILL KILL EVERYTHING MATCHING $NAME
__guest_forcekill() {
	local name="$2"
	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tforcekill <name>\n"
		exit 1
	fi
	local pids="$(pgrep -f $name)"
	for apid in "$pids"; do
		kill -9 $apid
	done
	bhyvectl --destroy --vm=ioh-$name
}

# Gracefully shut down all guests via ACPI (Does not destroy)
__guest_scram() {
	echo "Shutting down all guests..."
	local pids="$(pgrep -f ioh-)"
	for apid in "$pids"; do
		kill $apid
	done
	wait_for_pids $pids
}

# Destroy guest
__guest_destroy() {
	local name="$2"
	if [ -z "$name" ]; then
		printf "missing argument\nusage:\n"
		printf "\tdestroy <name>\n"
		exit 1
	fi
	echo "Destroying $name..."
#	zfs set iohyve:persist=0 $pool/iohyve/$name
	bhyvectl --force-poweroff --vm=ioh-$name
	bhyvectl --destroy --vm=ioh-$name
}

# Rename the guest
__guest_rename() {
	local name="$2"
	local newname="$3"
	if [ -z "$newname" ]; then
		printf "missing argument\nusage:\n"
		printf "\trename <name> <newname>\n"
		exit 1
	fi

	if ! __guest_exist $name; then
		echo "guest $name doesn't exist"
		return 1
	fi

	local dataset="$(zfs get -H -o name,value -s local,received -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$name$" | cut -f1)"
	# Check if guest is template
	local template="$(zfs get -H -o value iohyve:template $dataset)"
	if [ $template = "YES" ]; then
		echo "$name is a template. Cannot rename"
		exit 1
	fi
	local path="$(echo $dataset | rev | cut -f2- -d/ | rev)"
	echo "Renaming $name to $newname..."
	zfs rename -f $dataset $path/$newname
	zfs set iohyve:name=$newname $path/$newname
}

# Delete guest
__guest_delete() {
	local flagone="$2"
	local flagtwo="$3"
	if [ $flagone = "-f" ]; then
		if [ -z "$flagtwo" ]; then
			printf "missing argument\nusage:\n"
			printf "\tdelete [-f] <name>\n"
			exit 1
		fi

		if ! __guest_exist $flagtwo; then
			echo "guest $flagtwo doesn't exist"
			return 1
		fi

		local target_dataset="$(zfs get -H -o name,value -s local,received -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$flagtwo$" | cut -f1)"
		# Check if guest is template
		local template="$(zfs get -H -o value iohyve:template $target_dataset)"
		if [ $template = "YES" ]; then
			echo "$flagtwo is a template. Cannot delete"
			exit 1
		fi
		echo ""
		echo "[WARNING] Forcing permanent deletion of $flagtwo"
		echo "Location: $target_dataset including children and clones"
		echo ""
		echo "Hit Ctrl+C in the next 10 seconds to cancel..."
		sleep 10
		echo "Deleting $flagtwo at $target_dataset..."
		zfs destroy -rR $target_dataset
	else
		if [ -z "$flagone" ]; then
			printf "missing argument\nusage:\n"
			printf "\tdelete [-f] <name>\n"
			exit 1
		fi

		if ! __guest_exist $flagone; then
			echo "guest $flagone doesn't exist"
			return 1
		fi

		local target_dataset="$(zfs get -H -o name,value -s local,received -t filesystem iohyve:name | cut -d '/' -f-3 | grep iohyve | grep "$(printf '\t')$flagone$" | cut -f1)"
		# Check if guest is template
		local template="$(zfs get -H -o value iohyve:template $target_dataset)"
		if [ $template = "YES" ]; then
			echo "$flagone is a template. Cannot delete"
			exit 1
		fi
		echo ""
		echo "[WARNING] Are you sure you want to permanently delete $flagone and all child datasets?"
		read -p "Location: $target_dataset [Y/N]? " an </dev/tty
		case "$an" in
			y|Y) echo "Deleting $flagone at $target_dataset..."; zfs destroy -r $target_dataset
 			;;
			*) echo "$flagone not deleted..."
		esac
	fi
}

__guest_get_bhyve_cmd() {
	local devices="$1"
	local pci_slot_count=0
	for device in $devices ; do
		echo "-s $pci_slot_count,$device"
		pci_slot_count=$(( pci_slot_count + 1 ))
                if [ $pci_slot_count -eq 31 ]; then
                    break;
                fi
	done
}

__guest_exist() {
	zfs get -H -s local,received -o name,value iohyve:name | cut -d '/' -f-3 | grep iohyve | cut -f2 | grep ^$1$ > /dev/null
}
