#!/bin/sh
#Copyright (c) 2019-2022 Pierre Pronchery <khorben@EdgeBSD.org>
#This file is part of EdgeBSD NVMM
#This code is derived from software contributed to the EdgeBSD Project
#by Pierre Pronchery <khorben@EdgeBSD.org>
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



#variables
#essential
VENDOR="@VENDOR@"
PACKAGE="@PACKAGE@"
#executables
CAT="cat"
CHOWN="chown"
DD="dd"
DEBUG="_debug"
DISKLABEL="disklabel"
ECHO="echo"
case "$(uname -s)" in
	Darwin)
		ECHO="/bin/echo"
		;;
esac
FDISK="fdisk"
INSTALL="install"
INSTALLBOOT="installboot"
LN="ln -f"
MKDIR="mkdir"
MKSPARSE="_mksparse"
MKTEMP="mktemp"
MOUNT="mount"
NEWFS="newfs -O 2"
NVMM_XL="nvmm-xl"
PKG_ADD="pkg_add"
POSTINSTALL="postinstall"
PRINTF="printf"
PROGRESS="progress"
PWD_MKDB="pwd_mkdb"
RMDIR="rmdir"
SED="sed"
SHA1="sha1"
TAR="/rescue/tar"
TR="tr"
UMOUNT="umount"
UNAME="uname"
VNDCONFIG="vndconfig"
XEN_XL="xl"
ZCAT="zcat"
#settings
ARCH=$($UNAME -m)
DESTDIR=
DEVZERO="/dev/zero"
DIRECTORIES=
EDGEBSD_MIRROR=
FORCE=0
GROUPS=
HYPERVISOR=
INSTALLBOOT_OPTIONS=
KERNELS=
MOUNTPOINTS="tmpfs		/tmp	tmpfs	rw,noexec,-m=1777,-s=64m
kernfs		/kern	kernfs	rw
ptyfs		/dev/pts	ptyfs	rw
procfs		/proc	procfs	rw
/dev/cd0a	/cdrom	cd9660	ro,noauto
tmpfs		/var/shm	tmpfs	rw,-m1777,-sram%25"
NVMM_HOMEDIR="/home/xen"
NVMM_BRIDGEIF="bridge0"
NVMM_SYSCONFDIR="@SYSCONFDIR@/$VENDOR/$PACKAGE/hosts"
PACKAGES="vim"
PROGNAME="edgebsd-gueststat"
ROOT_DEVICE=
SERVICES="dhcpcd	YES	-qb
inetd	NO"
SETEXT=
SETS=
SYSCONFDIR="@SYSCONFDIR@"
SYSCTL_BOOTTIME="net.inet.ip.random_id=1"
TIMEZONE="UTC"
USERMGMT_SKEL_DIR="/etc/skel"
USERS=
VERBOSE=1
XEN_HOMEDIR="/home/xen"
XEN_BRIDGEIF="bridge0"
XEN_SYSCONFDIR="/usr/pkg/etc/xen"

#load local settings
[ -f "$SYSCONFDIR/$VENDOR/$PACKAGE/$PROGNAME.conf" ] &&
	. "$SYSCONFDIR/$VENDOR/$PACKAGE/$PROGNAME.conf"
[ -f "$HOME/.config/$VENDOR/$PACKAGE/$PROGNAME.conf" ] &&
	. "$HOME/.config/$VENDOR/$PACKAGE/$PROGNAME.conf"


#functions
#debug
_debug()
{
	[ $VERBOSE -ge 3 ] && echo "$@" 1>&2
	"$@"
}


#error
_error()
{
	echo "$PROGNAME: $@" 1>&2
	return 2
}


#info
_info()
{
	[ $VERBOSE -ge 2 ] && echo "$@" 1>&2
}


#mksparse
_mksparse()
{
	size="$2"
	filename="$3"

	if [ -f "$filename" -a $FORCE -eq 0 ]; then
		_error "$filename: Virtual Machine already exists"
		return 2
	fi
	$DD if="$DEVZERO" of="$filename" bs="1" count="1" seek="$((size - 1))"
}


#gueststat
_gueststat()
{
	#parse options
	if [ $# -eq 0 ]; then
		_usage "A command must be specified"
		return $?
	fi

	case "$1" in
		create|update)
			;;
		*)
			_usage "$1: Unsupported command"
			return $?
			;;
	esac
	command="$1"
	shift
	_gueststat_$command "$@"
}

_gueststat_bootloader()
{
	dev="$1"
	default=1

	_info "Installing the bootloader..."
	#/boot
	if [ -f "$DESTDIR/usr/mdec/boot" ]; then
		$DEBUG $INSTALL -m 0444 "$DESTDIR/usr/mdec/boot" \
			"$DESTDIR/boot"				|| return 2

		#/boot.cfg
		$DEBUG $CAT > "$DESTDIR/boot.cfg" << EOF
menu=Boot normally:rndseed /etc/entropy-file;boot netbsd.gz
menu=Boot single user:rndseed /etc/entropy-file;boot netbsd.gz -s
menu=Drop to boot prompt:prompt
default=$default
timeout=5
clear=1
EOF
		[ $? -eq 0 ]					|| return 2
	fi

	#/prekern
	if [ -f "$DESTDIR/usr/mdec/prekern" ]; then
		$DEBUG $INSTALL -m 0444 "$DESTDIR/usr/mdec/prekern" \
			"$DESTDIR/prekern"			|| return 2
	fi

	#MBR
	if [ -f "$DESTDIR/usr/mdec/bootxx_ffsv2" ]; then
		$DEBUG $INSTALLBOOT $INSTALLBOOT_OPTIONS \
			-m "$ARCH" "/dev/${dev}a" \
			"$DESTDIR/usr/mdec/bootxx_ffsv2"	|| return 2
	fi
	return 0
}

_gueststat_create()
{
	console=0
	filename=
	options="a:cfH:k:O:o:qr:Ss:v"
	release=$($UNAME -r)
	release="${release%.*}"
	size=20000000000
	start=0
	_gueststat_options "$options" "$@"
	while getopts "$options" name; do
		case "$name" in
			a)
				ARCH="$OPTARG"
				;;
			c)
				if [ ! -n "$HYPERVISOR" ]; then
					_error "Hypervisor must be set first"
					return $?
				fi
				console=1
				start=1
				;;
			f)
				FORCE=1
				;;
			H)
				HYPERVISOR=$(echo "$OPTARG" | _tolower)
				;;
			k)
				KERNELS="$OPTARG"
				;;
			O)
				eval "${OPTARG%%=*}"="'${OPTARG#*=}'"
				;;
			o)
				filename="$OPTARG"
				;;
			r)
				release="$OPTARG"
				;;
			s)
				size="$OPTARG"
				;;
			S)
				if [ ! -n "$HYPERVISOR" ]; then
					_error "Hypervisor must be set first"
					return $?
				fi
				start=1
				;;
			q|v)
				;;
			*)
				_usage "$name: Unsupported option"
				return $?
				;;
		esac
	done
	shift $((OPTIND - 1))
	if [ $# -ne 1 ]; then
		_usage "create: Needs a hostname"
		return $?
	fi

	_gueststat_defaults

	_gueststat_create_hostname "$1"
}

_gueststat_create_hostname()
{
	hostname="$1"
	domain="${hostname#*.}"

	if [ "$hostname" = "$domain" ]; then
		_error "$hostname: Must be fully qualified"
		return $?
	fi
	hostname="${hostname%%.*}"
	filename=$(_gueststat_filename "$hostname" "$domain")
	[ $? -eq 0 -a -n "$filename" ]				|| return 2
	[ -z "$HYPERVISOR" ] || case "$HYPERVISOR" in
		nvmm|xen)
			_gueststat_create_vm_$HYPERVISOR	|| return 2
			;;
		*)
			_error "$HYPERVISOR: Unsupported hypervisor"
			return $?
			;;
	esac
	_info "Creating the volume..."
	$DEBUG $MKSPARSE -s "$size" "$filename"			|| return 2
	ksize="$((size / 512 - 63))"
	$DEBUG $FDISK -Ffau0s "169/63/$ksize" "$filename"	|| return 2
	dev="$(_gueststat_vndevice)"
	if [ $? -ne 0 -o -z "$dev" ]; then
		_error "$hostname: Could not obtain a VND"
		return 2
	fi
	$DEBUG $VNDCONFIG -c "$dev" "$filename"			|| return 2
	_gueststat_init "$hostname" "$domain" "$dev" "$size"
	res=$?
	$DEBUG $VNDCONFIG -u "$dev"				|| return 2
	[ $res -eq 0 ]						|| return 2
	if [ $start -ne 0 ]; then
		_gueststat_start "$hostname" "$domain"		|| return 2
	fi
	return 0
}

_gueststat_create_vm_nvmm()
{
	template="template.$($UNAME -s | _tolower).${ARCH}"

	_info "Configuring the Virtual Machine... (NVMM)"
	if [ -f "$NVMM_SYSCONFDIR/$hostname" -a $FORCE -eq 0 ]; then
		_error "$hostname: Host already configured"
		return $?
	fi
	if [ ! -f "$NVMM_SYSCONFDIR/$template" ]; then
		_error "$template: Template missing for this platform"
		return $?
	fi
	macaddress="$(_gueststat_macaddress "$hostname" "$domain")"
	$DEBUG $SED -e "s,@@HOSTNAME@@,$hostname," \
		-e "s,@@DOMAINNAME@@,$domain," \
		-e "s,@@BRIDGEINTERFACE@@,$NVMM_BRIDGEIF," \
		-e "s,@@MACADDRESS@@,$macaddress," \
		-- "$NVMM_SYSCONFDIR/$template" \
		> "$NVMM_SYSCONFDIR/$hostname.$domain"		|| return 2
	$DEBUG $MKDIR -p -- "$NVMM_HOMEDIR/hosts/$domain"	|| return 2
	$DEBUG $MKDIR -- "$NVMM_HOMEDIR/hosts/$domain/$hostname"|| return 2
}

_gueststat_create_vm_xen()
{
	template="template.$($UNAME -s | _tolower).${ARCH}"

	_info "Configuring the Virtual Machine... (Xen)"
	if [ -f "$XEN_SYSCONFDIR/$hostname" -a $FORCE -eq 0 ]; then
		_error "$hostname: Host already configured"
		return $?
	fi
	if [ ! -f "$XEN_SYSCONFDIR/$template" ]; then
		_error "$template: Template missing for this platform"
		return $?
	fi
	macaddress="$(_gueststat_macaddress "$hostname" "$domain")"
	$DEBUG $SED -e "s,@@HOSTNAME@@,$hostname," \
		-e "s,@@DOMAINNAME@@,$domain," \
		-e "s,@@BRIDGEINTERFACE@@,$XEN_BRIDGEIF," \
		-e "s,@@MACADDRESS@@,$macaddress," \
		-- "$XEN_SYSCONFDIR/$template" \
		> "$XEN_SYSCONFDIR/$hostname.$domain"		|| return 2
	$DEBUG $MKDIR -p -- "$XEN_HOMEDIR/hosts/$domain"	|| return 2
	$DEBUG $MKDIR -- "$XEN_HOMEDIR/hosts/$domain/$hostname"	|| return 2
}

_gueststat_defaults()
{
	case "$release" in
		6|7|8)
			[ -n "$SETS" ] || SETS="base etc games man misc modules text"
			;;
		9)
			[ -n "$SETS" ] || SETS="base etc games man misc modules rescue text"
			;;
		*)
			_error "$release: Unsupported release"
			return $?
			;;
	esac
	[ -n "$EDGEBSD_MIRROR" ] || EDGEBSD_MIRROR="/home/ftp/pub/EdgeBSD/EdgeBSD-$release"

	case "$release/$ARCH" in
		9/amd64)
			[ -n "$KERNELS" ] || KERNELS="GENERIC
GENERIC_KASLR	kaslr
XEN3_DOM0	xen"
			[ -n "$SETEXT" ] || SETEXT=".tar.xz"
			;;
		9/i386)
			[ -n "$KERNELS" ] || KERNELS="GENERIC
GENERIC_KASLR	kaslr
XEN3_DOM0	xen"
			[ -n "$SETEXT" ] || SETEXT=".tar.xz"
			;;
		9)
			[ -n "$SETEXT" ] || SETEXT=".tgz"
			;;
		*/amd64|*/i386)
			[ -n "$KERNELS" ] || KERNELS="GENERIC
XEN3_DOM0	xen"
			;;
		*)
			[ -n "$SETEXT" ] || SETEXT=".tgz"
			;;
	esac

	case "$HYPERVISOR" in
		nvmm)
			INSTALLBOOT_OPTIONS="-o console=com0,speed=115200"
			[ -n "$ROOT_DEVICE" ] || ROOT_DEVICE="ld0"
			;;
		xen)
			[ -n "$ROOT_DEVICE" ] || ROOT_DEVICE="xbd0"
			;;
		"")
			;;
		*)
			_error "$HYPERVISOR: Unsupported hypervisor"
			return $?
			;;
	esac

	case $VERBOSE in
		0|1|2)
			DD="dd msgfmt=quiet"
			NEWFS="$NEWFS -V 0"
			;;
		3)
			FDISK="$FDISK -v"
			INSTALLBOOT="$INSTALLBOOT -v"
			NEWFS="$NEWFS -V 2"
			;;
		*)
			FDISK="$FDISK -vv"
			PKG_ADD="$PKG_ADD -v"
			;;
	esac
}

_gueststat_directories()
{
	directories="$1"

	[ $# -eq 1 -a -n "$directories" ]			|| return 0
	_info "Creating directories..."
	while read directory mode owner group junk; do
		[ -n "$mode" ] || mode=0755
		$DEBUG $MKDIR -p -m "$mode" -- \
			"$DESTDIR$directory"			|| return 2
		if [ -n "$owner" ]; then
			$DEBUG $CHOWN "$owner:$group" \
				"$DESTDIR$directory"		|| return 2
		fi
	done << EOF
$directories
EOF
	return 0
}

_gueststat_init()
{
	ret=0
	hostname="$1"
	domain="$2"
	dev="$3"
	size="$4"
	cylinders="$((size / 2048))"
	sectors="$((size / 512))"

	_info "Initializing the volume..."
	$DEBUG $DISKLABEL -R "${dev}" "/dev/stdin" << EOF
type: vnd
disk: vnd
label: fictitious
flags:
bytes/sector: 512
sectors/track: 32
tracks/cylinder: 64
sectors/cylinder: 2048
cylinders: $cylinders
total sectors: $sectors
rpm: 3600
interleave: 1
trackskew: 0
cylinderskew: 0
headswitch: 0           # microseconds
track-to-track seek: 0  # microseconds
drivedata: 0

4 partitions:
 a:  $((sectors - 63))        63     4.2BSD      0     0     0
 c:  $((sectors - 63))        63     unused      0     0
 d:  $((sectors))         0     unused      0     0
EOF
	[ $? -eq 0 ]						|| return 2
	$DEBUG $NEWFS "${dev}a"					|| return 2
	DESTDIR=$(_gueststat_mount "$dev")
	ret=$?
	if [ $ret -eq 0 ]; then
		(_gueststat_sets_install "$SETS" &&
		 _gueststat_kernels "$KERNELS" &&
		 _gueststat_bootloader "$dev" &&
		 _gueststat_groups "$GROUPS" &&
		 _gueststat_users "$USERS" &&
		 _gueststat_packages "$PACKAGES" &&
		 _gueststat_services "$SERVICES" &&
		 _gueststat_directories "$DIRECTORIES" &&
		 _gueststat_files)				|| ret=$?
		$DEBUG $UMOUNT "$DESTDIR"
	fi
	$DEBUG $RMDIR -- "$DESTDIR"				|| return 2
	return $ret
}

_gueststat_filename()
{
	hostname="$1"
	domain="$2"

	case "$HYPERVISOR" in
		nvmm)
			echo "$NVMM_HOMEDIR/hosts/$domain/$hostname/hd0.img"
			;;
		xen)
			echo "$XEN_HOMEDIR/hosts/$domain/$hostname/hd0.img"
			;;
		"")
			echo "${hostname}.${domain}-hd0.img"
			;;
		*)
			_error "$HYPERVISOR: Unsupported hypervisor"
			return 2
			;;
	esac
}

_gueststat_files()
{
	#/etc/daily.conf
	$DEBUG $CAT > "$DESTDIR/etc/daily.conf" << EOF
#	\$NetBSD: daily.conf,v 1.4 2000/10/01 05:53:01 lukem Exp \$
#
# see daily.conf(5) for more information.

if [ -r /etc/defaults/daily.conf ]; then
	. /etc/defaults/daily.conf
fi

# Add local overrides below
#
fetch_pkg_vulnerabilities=YES
EOF
	[ $? -eq 0 ]						|| return 2

	#/etc/fstab
	$DEBUG $CAT > "$DESTDIR/etc/fstab" << EOF
# NetBSD /etc/fstab
# See /usr/share/examples/fstab/ for more examples.
/dev/${ROOT_DEVICE}a		/	ffs	rw,log		 1 1
EOF
	[ $? -eq 0 ]						|| return 2
	while read device mountpoint line; do
		$DEBUG $MKDIR -p -- "$DESTDIR/$mountpoint"	|| return 2
		echo "$device	$mountpoint	$line" >> "$DESTDIR/etc/fstab"
		[ $? -eq 0 ]					|| return 2
	done << EOF
$MOUNTPOINTS
EOF
	[ $? -eq 0 ]						|| return 2

	#/etc/localtime
	[ -n "$TIMEZONE" ] || TIMEZONE="UTC"
	if [ ! -f "$DESTDIR/usr/share/zoneinfo/$TIMEZONE" ]; then
		_error "$TIMEZONE: Invalid or unknown timezone"
		return $?
	fi
	$DEBUG $LN -s "/usr/share/zoneinfo/$TIMEZONE" \
		"$DESTDIR/etc/localtime"			|| return 2

	#/etc/sysctl.conf
	$DEBUG $CAT > "$DESTDIR/etc/sysctl.conf" << EOF
#!/sbin/sysctl -f
#
# \$NetBSD: sysctl.conf,v 1.8 2011/09/25 21:47:22 christos Exp \$
#
# sysctl(8) variables to set at boot time.

# Default on panic: dump core and reboot. See savecore(8) for information.
# Switch this to 1 if you want to enter the kernel debugger on crashes
# instead. See ddb(4) for an introduction and also try the "help" command
# at the db> prompt.
# If you understand the implication and want to change the behaviour before
# /etc/rc.d/sysctl is run, use the kernel option DDB_ONPANIC, see options(4).
ddb.onpanic?=0

# Default core name template:
#kern.defcorename=%n.core

# Number of kernel threads to use for NFS client
#vfs.nfs.iothreads=4

# Default tty/pty character queue sizes. Should be bumped to 32K or so if
# used in networking (ppp/pppoe)
#kern.tty.qsize=1024

$SYSCTL_BOOTTIME
EOF
	[ $? -eq 0 ]						|| return 2

	#/etc/usermgmt.conf
	$DEBUG $CAT > "$DESTDIR/etc/usermgmt.conf" << EOF
base_dir	/home
expire		0
gid_range	500..999
group		=uid
homeperm	0700
inactive	0
preserve	false
range		500..999
skel_dir	$USERMGMT_SKEL_DIR
shell		/bin/sh
EOF
	[ $? -eq 0 ]						|| return 2
}

_gueststat_groups()
{
	groups="$1"

	[ -n "$groups" ]					|| return 0
	$DEBUG $CAT >> "$DESTDIR/etc/group" << EOF
$groups
EOF
	[ $? -eq 0 ]						|| return 2
}

_gueststat_kernels()
{
	kernels="$1"

	[ $# -eq 1 -a -n "$kernels" ]				|| return 0
	_info "Installing the kernels..."
	echo "$kernels" | while read config suffix; do
		kernel="netbsd"
		[ -n "$suffix" ] && kernel="$kernel.$suffix"
		kernel="$kernel.gz"
		$DEBUG $INSTALL -m 0755 \
			"$EDGEBSD_MIRROR/$ARCH/binary/kernel/netbsd-$config.gz" \
			"$DESTDIR/$kernel"			|| return 2
	done
}

_gueststat_macaddress()
{
	hostname="$1"
	domain="$2"

	hash="$($ECHO -n "$hostname.$domain" | $SHA1)"
	hash1="${hash#??}" 1>&2
	hash0="${hash%$hash1}"
	hash2="${hash1#??}" 1>&2
	hash1="${hash1%$hash2}"
	hash3="${hash2#??}" 1>&2
	hash2="${hash2%$hash3}"
	$ECHO -n "00:0e:22:$hash0:$hash1:$hash2"
}

_gueststat_mount()
{
	dev="$1"

	if [ -n "$DESTDIR" ]; then
		$DEBUG $MKDIR -p -- "$DESTDIR"			|| return 2
	else
		DESTDIR=$($MKTEMP -d)
		[ $? -eq 0 ]					|| return 2
	fi
	$DEBUG $MOUNT "/dev/${dev}a" "$DESTDIR"			|| return 2
	echo "$DESTDIR"
}

_gueststat_options()
{
	options="$1"
	shift

	while getopts "$options" name; do
		case "$name" in
			q)
				VERBOSE=0
				;;
			v)
				VERBOSE=$((VERBOSE + 1))
				;;
		esac
	done
}

_gueststat_packages()
{
	packages="$1"

	[ -n "$packages" ]					|| return 0
	_info "Installing the packages..."
	for package in $packages; do
		PKG_PATH="$EDGEBSD_MIRROR/$ARCH/binary/packages/All" \
			  $DEBUG $PKG_ADD -P "$DESTDIR" "$package"
		ret=$?
		[ $ret -eq 0 ]					|| return $?
	done
	return 0
}

_gueststat_services()
{
	services="$1"

	#/etc/rc.conf
	$DEBUG $CAT > "$DESTDIR/etc/rc.conf" << EOF
#	\$NetBSD: rc.conf,v 1.97 2014/07/14 12:29:48 mbalmer Exp \$
#
# See rc.conf(5) for more information.
#
# Use program=YES to enable program, NO to disable it. program_flags are
# passed to the program on the command line.
#

# Load the defaults in from /etc/defaults/rc.conf (if it's readable).
# These can be overridden below.
#
if [ -r /etc/defaults/rc.conf ]; then
	. /etc/defaults/rc.conf
fi

# If this is not set to YES, the system will drop into single-user mode.
#
rc_configured=YES

# Add local overrides below.
#
clear_tmp=NO
fsck_flags="-pP"
hostname=$hostname.$domain
newsyslog=YES
no_swap=YES
random_file=/etc/entropy-file
savecore=NO
virecover=NO

#services
EOF
	[ -z "$services" ] || while read service enabled flags; do
		[ -n "$service" -a -n "$enabled" ] || continue
		line="$service=$enabled"
		[ -n "$flags" ] && line="$line	${service}_flags=\"$flags\""
		echo "$line" >> "$DESTDIR/etc/rc.conf"		|| return 2
		if [ -f "$DESTDIR/usr/pkg/share/examples/rc.d/$service" ]; then
			$DEBUG $INSTALL -m 0755 \
				"$DESTDIR/usr/pkg/share/examples/rc.d/$service" \
				"$DESTDIR/etc/rc.d/$service"	|| return 2
		fi
	done << EOF
$services
EOF
	[ $? -eq 0 ]						|| return 2
}

_gueststat_sets_install()
{
	sets="$1"

	_info "Installing the sets..."
	for set in $sets; do
		(cd "$DESTDIR" &&
		 _progress -p "$($PRINTF "%-7s " "${set}")" \
			 -f "$EDGEBSD_MIRROR/$ARCH/binary/sets/${set}$SETEXT" \
			 $TAR -xzpf -)				|| return 2
	done
	return 0
}

_gueststat_sets_update()
{
	sets="$1"
	etc=0

	_info "Updating the sets..."
	for set in $sets; do
		if [ "$set" = "etc" -a -f "$DESTDIR/etc/mtree/set.$set" ]; then
			etc=1
			continue
		fi
		(cd "$DESTDIR" &&
		 _progress -p "$($PRINTF "%-7s " "${set}")" \
			 -f "$EDGEBSD_MIRROR/$ARCH/binary/sets/${set}$SETEXT" \
			 $TAR -xzpf -)				|| return 2
	done
	if [ $etc -ne 0 ]; then
		$DEBUG $POSTINSTALL -a "$ARCH" -d "$DESTDIR" \
			-s "$EDGEBSD_MIRROR/$ARCH/binary/sets/etc$SETEXT" \
			fix					|| return 2
	fi
	return 0
}

_gueststat_start()
{
	hostname="$1"
	domain="$2"
	options=

	case "$HYPERVISOR" in
		nvmm|xen)
			_gueststat_start_$HYPERVISOR "$hostname" "$domain"
			return $?
			;;
		*)
			_error "$HYPERVISOR: Unsupported hypervisor"
			return 2
			;;
	esac
}

_gueststat_start_nvmm()
{
	hostname="$1"
	domain="$2"
	options=

	_info "Starting the Virtual Machine... (NVMM)"
	[ $console -ne 0 ] && options="-c"
	$DEBUG $NVMM_XL create $options "$NVMM_SYSCONFDIR/$hostname.$domain" \
								|| return 2
}

_gueststat_start_xen()
{
	hostname="$1"
	domain="$2"
	options=

	_info "Starting the Virtual Machine... (Xen)"
	[ $console -ne 0 ] && options="-c"
	$DEBUG $XEN_XL create $options "$XEN_SYSCONFDIR/$hostname.$domain" \
								|| return 2
}

_gueststat_update()
{
	console=0
	filename=
	options="a:cH:O:o:qr:Sv"
	release=$($UNAME -r)
	release="${release%.*}"
	start=0

	_gueststat_options "$options" "$@"
	while getopts "$options" name; do
		case "$name" in
			a)
				ARCH="$OPTARG"
				;;
			c)
				if [ ! -n "$HYPERVISOR" ]; then
					_error "Hypervisor must be set first"
					return $?
				fi
				console=1
				start=1
				;;
			H)
				HYPERVISOR=$(echo "$OPTARG" | _tolower)
				;;
			O)
				eval "${OPTARG%%=*}"="'${OPTARG#*=}'"
				;;
			o)
				filename="$OPTARG"
				;;
			r)
				release="$OPTARG"
				;;
			S)
				if [ ! -n "$HYPERVISOR" ]; then
					_error "Hypervisor must be set first"
					return $?
				fi
				start=1
				;;
			q|v)
				;;
			*)
				_usage "$name: Unsupported option"
				return $?
				;;
		esac
	done
	shift $((OPTIND - 1))
	if [ $# -ne 1 ]; then
		_usage "update: Needs a hostname"
		return $?
	fi

	_gueststat_defaults
	PKG_ADD="$PKG_ADD -u"

	_gueststat_update_hostname "$1"
}

_gueststat_update_hostname()
{
	hostname="$1"
	domain="${hostname#*.}"
	res=0

	if [ "$hostname" = "$domain" ]; then
		_error "$hostname: Must be fully qualified"
		return $?
	fi
	hostname="${hostname%%.*}"
	if [ -z "$filename" ]; then
		filename=$(_gueststat_filename "$hostname" "$domain")
		[ $? -eq 0 -a -n "$filename" ]			|| return 2
	fi
	dev="$(_gueststat_vndevice)"
	if [ $? -ne 0 -o -z "$dev" ]; then
		_error "$hostname: Could not obtain a VND"
		return 2
	fi
	$DEBUG $VNDCONFIG -c "$dev" "$filename"			|| return 2
	DESTDIR=$(_gueststat_mount "$dev")
	if [ $? -eq 0 ]; then
		(_gueststat_sets_update "$SETS" &&
		 _gueststat_kernels "$KERNELS" &&
		 _gueststat_bootloader "$dev" &&
		 _gueststat_packages "$PACKAGES" &&
		 _gueststat_services "$SERVICES" &&
		 _gueststat_directories "$DIRECTORIES" &&
		 _gueststat_files)				|| res=$?
		$DEBUG $UMOUNT "$DESTDIR"
		$DEBUG $RMDIR -- "$DESTDIR"
	fi
	$DEBUG $VNDCONFIG -u "$dev"				|| return 2
	[ $res -eq 0 ]						|| return 2
	if [ $start -ne 0 ]; then
		_gueststat_start "$hostname" "$domain"		|| return 2
	fi
	return 0
}

_gueststat_users()
{
	users="$1"

	[ -n "$users" ]						|| return 0
	$DEBUG $CAT >> "$DESTDIR/etc/master.passwd" << EOF
$users
EOF
	[ $? -eq 0 ]						|| return 2
	$DEBUG $PWD_MKDB -d "$DESTDIR" "$DESTDIR/etc/master.passwd" \
								|| return 2
}

_gueststat_vndevice()
{
	#XXX ugly and will break after 16 VMs running
	devices=$($DEBUG $VNDCONFIG -l -m 16)
	while read device junk; do
		if [ "$junk" = "not in use" ]; then
			echo "${device%:}"
			return 0
		fi
	done << EOF
$devices
EOF
	return 2
}


#progress
_progress()
{(
	cat="$CAT"
	options=
	filename=
	prefix=

	while getopts "ef:p:z" name; do
		case "$name" in
			e)
				options="-e $options"
				;;
			f)
				#XXX must be last
				options="$options -f"
				filename="$OPTARG"
				;;
			p)
				prefix="$OPTARG"
				;;
			z)
				options="-z $options"
				cat="$ZCAT"
				;;
			*)
				exit 1
				;;
		esac
	done
	shift $((OPTIND - 1))
	if [ $# -lt 1 ]; then
		return 1
	fi
	#only use progress(1) if in interactive and verbose mode
	if [ -n "$PS1" -a $VERBOSE -ge 1 ]; then
		$DEBUG $PROGRESS -p "$prefix" $options "$filename" -- "$@"
		ret=$?
		echo
		return $ret
	else
		$DEBUG $cat -- "$filename" | "$@"
	fi
)}


#tolower
_tolower()
{
	$TR A-Z a-z
}


#usage
_usage()
{
	[ $# -ge 1 ] && echo "$PROGNAME: $@" 1>&2
	echo "Usage: $PROGNAME command [-qv][options...]" 1>&2
	echo "  -q	Operate in quiet mode" 1>&2
	echo "  -v	Increase verbosity (default: 1)" 1>&2
	echo 1>&2
	echo "Commands available:" 1>&2
	echo "  create [-cfS][-a arch][-H hypervisor][-k kernels][-o filename][-r release][-s size] hostname" 1>&2
	echo "  -a	Set the target architecture" 1>&2
	echo "  -H	Set the target hypervisor (NVMM or Xen)" 1>&2
	echo "  -S	Start the image (sets Xen mode)" 1>&2
	echo "  -c	Start the image and enable the console (sets Xen mode)" 1>&2
	echo "  -f	Overwrite the image if existing" 1>&2
	echo "  -k	Set the kernels" 1>&2
	echo "  -o	Set the output filename" 1>&2
	echo "  -r	Set the target EdgeBSD release (6, 7, 8, or 9)" 1>&2
	echo "  -s	Set the size of the target image (default: 20 GB)" 1>&2
	echo 1>&2
	echo "  update [-cS][-a arch][-H hypervisor][-o filename][-r release] hostname" 1>&2
	echo "  -a	Set the target architecture" 1>&2
	echo "  -H	Set the target hypervisor (NVMM or Xen)" 1>&2
	echo "  -S	Start the image (sets Xen mode)" 1>&2
	echo "  -c	Start the image and enable the console (sets Xen mode)" 1>&2
	echo "  -o	Set the output filename" 1>&2
	echo "  -r	Set the target EdgeBSD release (6, 7, 8, or 9)" 1>&2
	return 1
}


#main
_gueststat "$@"
