#!/bin/sh

#
# Copyright (c) 2012 Michael Dexter <editor@callfortesting.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

echo "Version 1.0"
echo "This script will present with a menu that allows you to patch"
echo "FreeBSD 9.0 release or stable sources on a running FreeBSD 9.0"
echo "system with a known-good version of the BHyVe Hypervisor and"
echo "virtio virtual networking devices."
echo ""
echo "You must have the system sources in /usr/src/ plus the the"
echo "'subversion' and 'binutils' packages for this to work."
echo "The first step will allow you to add these utilities."
echo ""
echo "About BHyVe Versioning"
echo ""
echo "Neel's downloadable image was build with svn v225757 which"
echo "was built on FreeBSD 9.0Beta2 v225598"
echo "The FreeBSD 9.0Beta2 revision of virtio is v225697 but this script
echo "currently pulls the -current version of virtio.""
echo "The BHyVe version is entered as a variable in this script"

# TO DO: Make this FreeBSD 8, 9 and 10 aware

# Variables

# BHyVe Build Variables

BHYVEVER=225757         # BHyVe v225757 
#VIRTIOVER=225697	# VirtIO v225697
PACKAGENAME=bhyve-0.0.1r$BHYVEVER
SRC=/usr/src           # FreeBSD Sources
WORK=/usr/src-bhyve     # Union Mounted Working Directory
#HOSTNIC=em0		# Change this to match your system
                        # if you do not 'read' it in!

# Guest Build Variables

GUESTDIR=/usr/bhyve-guest
GUESTNAME=myguest
ROOTSIZE=400k		# 400MB root image
# Change this to a regional or LAN mirror to save bandwidth
FTPURL=ftp://ftp.FreeBSD.org/pub/FreeBSD/releases/amd64/9.0-RELEASE/base.txz
GUESTIP=192.168.1.151   # Change these to suit your network
GUESTNETMASK=255.255.255.0
GUESTGW=192.168.1.1
GUESTSEARCHDOMAIN=cft.lv
GUESTDNS=192.168.1.1

echo ""
echo "This script will patch your system sources in $SRC with BHyVe"
echo "version $BHYVEVER using the temp diretory $WORK plus"
echo "/mnt and /usr/obj/"
echo "It will put a BHyVe package named $WORK/$PACKAGENAME.tar"
echo "in $GUESTDIR"
echo ""
echo "Enter your active network device such as 'fxp0' or 'em0':"
read HOSTNIC

# Functions

required_packages(){

pkg_add -r subversion
pkg_add -r binutils
#rehash
echo ""
echo "Completed: 1. Add the subversion and binutils Packages"
echo "Next: 2. Configure the Host's /boot/loader.conf (reboot required)"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

host_loader(){

cat > /boot/loader.conf << EOF
hw.physmem="0x100000000"
debug.witness.watch="0"
#vmm_load="YES"
#if_tap_load="YES"
#bridgestp_load="YES"
#if_bridge_load="YES"
EOF
echo ""
cat /boot/loader.conf
echo ""
echo "You MUST reboot for the hw.physmem setting to take effect"
echo ""
echo "Some of these are commented out because they will be set with"
echo "the vmprep.sh script"
echo ""
echo "Completed: 2. Configure the Host's /boot/loader.conf (reboot required)"
echo "Next: 3. Retrieve BHyVe Sources and Set-up the Working Directory"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

retrieve_and_setup(){

echo "Running mount to see if the union fs is mounted"
mount

echo "Trying to unmount $WORK. Errors are okay"
umount $WORK
rm -rf $WORK
mkdir $WORK

echo "Mounting $SRC on $WORK"
mount -t unionfs -o noatime -o below $SRC $WORK

echo "Listing $WORK directory contents"
ls $WORK

echo "Populating $WORK with libvmmapi and linking machine"
svn co http://svn.freebsd.org/base/projects/bhyve/lib/libvmmapi@$BHYVEVER $WORK/lib/libvmmapi/
ls $WORK/lib/libvmmapi/ 

svn export http://svn.freebsd.org/base/projects/bhyve/sys/amd64/include/vmm.h@$BHYVEVER $WORK/sys/amd64/include/
svn export http://svn.freebsd.org/base/projects/bhyve/sys/amd64/include/vmm_dev.h@$BHYVEVER $WORK/sys/amd64/include/

#echo "modifying lib/libvmmapi/Makefile and linking machine"
#echo 'CFLAGS+= -I. -I../../lib/libvmmapi' >> $WORK/usr.sbin/libvmmapi/Makefile
#echo 'LDFLAGS+= -L../../lib/libvmmapi' >> $WORK/usr.sbin/libvmmapi/Makefile
ln -s ../../sys/amd64/include $WORK/lib/libvmmapi/machine

echo "Populating $WORK with bhyve and patching it"
svn co http://svn.freebsd.org/base/projects/bhyve/usr.sbin/bhyve@$BHYVEVER $WORK/usr.sbin/bhyve/

echo "modifying the usr.sbin/bhyve/Makefile and linking machine"
echo 'CFLAGS+= -I. -I../../lib/libvmmapi' >> $WORK/usr.sbin/bhyve/Makefile
echo 'LDFLAGS+= -L../../lib/libvmmapi' >> $WORK/usr.sbin/bhyve/Makefile
ln -s ../../sys/amd64/include $WORK/usr.sbin/bhyve/machine

echo "Populating $WORK with bhyveload and patching it"
svn co http://svn.freebsd.org/base/projects/bhyve/usr.sbin/bhyveload@$BHYVEVER $WORK/usr.sbin/bhyveload/

echo "modifying usr.sbin/bhyveload/Makefile and linking machine"
echo 'CFLAGS+= -I. -I../../lib/libvmmapi' >> $WORK/usr.sbin/bhyveload/Makefile
echo 'LDFLAGS+= -L../../lib/libvmmapi' >> $WORK/usr.sbin/bhyveload/Makefile
ln -s ../../sys/amd64/include $WORK/usr.sbin/bhyveload/machine

echo "Populating $WORK with vmmctl and patching it"
svn co http://svn.freebsd.org/base/projects/bhyve/usr.sbin/vmmctl@$BHYVEVER $WORK/usr.sbin/vmmctl/

echo "modifying the usr.sbin/vmmctl/Makefile and patching it"
echo 'CFLAGS+= -I. -I../../lib/libvmmapi' >> $WORK/usr.sbin/vmmctl/Makefile
echo 'LDFLAGS+= -L../../lib/libvmmapi' >> $WORK/usr.sbin/vmmctl/Makefile
ln -s ../../sys/amd64/include $WORK/usr.sbin/vmmctl/machine

echo "Populating $WORK with vmm and misc BHyVe files"
# The vmm.ko module
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/modules/vmm@$BHYVEVER $WORK/sys/modules/vmm/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/modules/Makefile@$BHYVEVER $WORK/sys/modules/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/vmm@$BHYVEVER $WORK/sys/amd64/vmm/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/amd64/apic_vector.S@$BHYVEVER $WORK/sys/amd64/amd64/
# Fix for busy-wait spin loops in the shutdown code
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/amd64/minidump_machdep.c@$BHYVEVER $WORK/sys/amd64/amd64/
# The modified mp_machdep.c to allow CPUs without 'unrestricted guest'
# support to bypass the real-mode bootstrap when running under BHyVe
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/amd64/mp_machdep.c@$BHYVEVER $WORK/sys/amd64/amd64/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/amd64/vm_machdep.c@$BHYVEVER $WORK/sys/amd64/amd64/
# The MSR definitions for the x2apic are defined here.
# Using MSR registers to access the local APIC is a lot more efficient
# than having to do instruction decode/emulation for the standard
# memory-mapped mode of access.
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/include/specialreg.h@$BHYVEVER $WORK/sys/amd64/include/
# Files for the bvm console and debugport
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/conf/files.amd64@$BHYVEVER $WORK/sys/conf/
# Support for booting with large ramdisks (e.g. ~400MB)
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/conf/options.amd64@$BHYVEVER $WORK/sys/conf/
# Bug fix to support the pass-through stub driver when it is unloaded
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/kern/subr_bus.c@$BHYVEVER $WORK/sys/kern/

# ~/sys/kern/subr_bus.c
# psignal -> kern_psignal
#psignal was changed to kern_psignal in http://svnweb.freebsd.org/base?view=revision&revision=225617
#This has a space as not to break the correct one
sed -i '' 's/psignal/kern_psignal/g' $WORK/sys/kern/subr_bus.c

#Emulating the string versions of i/o instructions (e.g. insb/w/l, outsb/w/l) turns out to be a little complicated. When Neel was playing around with the ATA driver which uses these instructions via the bus_space_{read|write}_multi_* accessors, he instead changed the accessors to loop on single-byte i/o instructions instead.
# This doesn't help us with running older binaries, or non-FreeBSD guests, so we need to fix this sometime.
# And, if ATA isn't being used, this diff isn't required.
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/x86/include/bus.h@$BHYVEVER $WORK/sys/x86/include/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/x86/x86/local_apic.c@$BHYVEVER $WORK/sys/x86/x86/
echo "Creating package tree prior to build"

echo "Populating guest kernel configuration files, blackhole and bvm"
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/conf/BHYVE $WORK/sys/amd64/conf/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/amd64/conf/GENERIC $WORK/sys/amd64/conf/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/dev/bvm@$BHYVEVER $WORK/sys/dev/bvm/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/dev/blackhole@$BHYVEVER $WORK/sys/dev/blackhole/
svn export --force http://svn.freebsd.org/base/projects/bhyve/sys/modules/blackhole@$BHYVEVER $WORK/sys/modules/blackhole/

# Populating $WORK with virtio files
svn co http://svn.freebsd.org/base/projects/virtio/sys/dev/virtio $WORK/sys/dev/virtio/
svn co http://svn.freebsd.org/base/projects/virtio/sys/modules/virtio $WORK/sys/modules/virtio/

mkdir $WORK/package
mkdir -p $WORK/package/boot/kernel
mkdir -p $WORK/package/usr/sbin
mkdir -p $WORK/package/usr/include
mkdir -p $WORK/package/usr/lib

echo "BHyVe kernel module, library and utilities" > $WORK/package/+COMMENT
echo "The BHyVe BSD HyperVisor is a Type 2 hypervisor for FreeBSD" > $WORK/package/+DESC

cat > $WORK/package/+CONTENTS << EOF
@comment PKG_FORMAT_REVISION:1.1
@name $PACKAGENAME
@comment ORIGIN:sysutils/bhyve
@cwd /
boot/kernel/vmm.ko
usr/sbin/bhyve
usr/sbin/bhyveload
usr/sbin/vmmctl
usr/include/vmmapi.h
usr/lib/libvmmapi.a
usr/lib/libvmmapi.so
usr/lib/libvmmapi.so.5
usr/lib/libvmmapi_p.a
EOF

echo "Listing the package directory contents"
ls $WORK/package
echo ""
echo "Completed: 3. Retrieve BHyVe Sources and Set-up the Working Directory"
echo "Next: 4. Build BHyVe Host Components and Package"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

build_bhyve(){

# Build libvmmapi first to satisfy utility dependencies
cd $WORK/lib/libvmmapi/
make
make install DESTDIR=$WORK/package/

cd $WORK/usr.sbin/bhyve/
make
make install DESTDIR=$WORK/package/

cd $WORK/usr.sbin/bhyveload/
make
make install DESTDIR=$WORK/package/

# Requires /src/sys/amd64/vmm/intel/vmcs.h from vmm
cd $WORK/usr.sbin/vmmctl
make
make install DESTDIR=$WORK/package/

cd $WORK/sys/modules/vmm/
make
make install DESTDIR=$WORK/package/

cd $WORK/package/
tar -cf $WORK/$PACKAGENAME.tar *

echo "$WORK/$PACKAGENAME.tar package created"
echo "ls $WORK/$PACKAGENAME.tar"
#touch $WORK/package/+MTREE_DIRS
echo ""
echo "Completed: 4. Build BHyVe Host Components and Package"
echo "Next: 5. Add the BHyVe Package (pkg_add $WORK/$PACKAGENAME.tar"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

add_pkg(){

echo "Running pkg_add $WORK/$PACKAGENAME.tar"
pkg_add $WORK/$PACKAGENAME.tar
pkg_info | grep BHyVe
echo ""
echo "Completed:: 5. Add the BHyVe Package (pkg_add $WORK/$PACKAGENAME.tar"
echo "Next: Build BhyVe Guest"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

build_guest(){

rm -rf $GUESTDIR
mkdir -p $GUESTDIR/boot/kernel

echo "Building the guest kernel"
echo "Deleting /usr/obj/*"
rm -rf /usr/obj/*
cd $WORK
make buildkernel -DNO_MODULES KERNCONF=BHYVE \
WITHOUT_CDDL="YES" WITHOUT_ZFS="YES" WITHOUT_USB="YES" WITHOUT_WIRELESS="YES" WITHOUT_WIRELESS_SUPPORT="YES" WITHOUT_WPA_SUPPLICANT_EAPOL="YES"
# Recall /usr/src/tools/build/options

make installkernel -DNO_MODULES KERNCONF=BHYVE DESTDIR=$GUESTDIR
# Note that this resides in /usr/obj/$GUESTDIR/sys/BHYVE/
ls $GUESTDIR/boot/kernel

echo "Building the virtio modules"
cd $WORK/sys/modules/virtio/
make
make install DESTDIR=$GUESTDIR
ls $GUESTDIR/boot/kernel/

echo "Copying /boot components from the host"
cp /boot/*.4th $GUESTDIR/boot/
cp -a /boot/defaults $GUESTDIR/boot/
cp /boot/loader.help $GUESTDIR/boot/
cp /boot/loader.rc $GUESTDIR/boot/
cp /boot/menu.rc $GUESTDIR/boot/
ls $GUESTDIR/boot
 
echo "Building /src/sys/userboot/"
cd $WORK/sys/boot/userboot
make
make install DESTDIR=$GUESTDIR
cp $GUESTDIR/boot/userboot.so $GUESTDIR/

cat > $GUESTDIR/boot/loader.conf << EOF
kernel="/kernel"

virtio_load="YES"
if_vtnet_load="YES"
virtio_pci_load="YES"
virtio_blk_load="YES"

kern.hz="100"
hw.pci.enable_msix="0"
hw.pci.honor_msi_blacklist="0"
bootverbose="1"

# For use with r/w root partition
vfs.root.mountfrom="ufs:vtbd0"

# For use with MDROOT
#mfsroot_load="YES"
#mfsroot_type="mfs_root"
#mfsroot_name="mdroot"
EOF

echo "Creating and populating the guest disk device"
echo "umount: and mdconfig: errors are good"
umount /mnt
mdconfig -d -u md0
dd if=/dev/zero of=$GUESTDIR/diskdev bs=1k count=$ROOTSIZE
mdconfig -a -f $GUESTDIR/diskdev -u 0
bsdlabel -w md0 auto
newfs md0
mount /dev/md0 /mnt

echo "Fetching base.txz and extracting it into /mnt"
fetch -o - $FTPURL | tar --unlink -xpJf - -C /mnt/

echo "Creating guest's fstab"
cat > /mnt/etc/fstab << EOF
# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/vtbd0              /               ufs     rw              1       1
EOF

echo "Creating guest's rc.conf"
cat > /mnt/etc/rc.conf << EOF
hostname="bhyve-tap0"

ifconfig_vtnet0="inet $GUESTIP netmask $GUESTNETMASK"
defaultrouter="$GUESTGW"
sshd_enable="YES"

sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
EOF

echo "Creating guest's resolv.conf"
cat > /mnt/etc/resolv.conf << EOF
search $GUESTSEARCHDOMAIN
nameserver $GUESTDNS
EOF

echo "Creating guest's ttys"
cp /mnt/etc/ttys /mnt/etc/ttys.orig
cat > /mnt/etc/ttys << EOF
console "/usr/libexec/getty std.9600"   vt100   on      secure
#
ttyv0   "/usr/libexec/getty Pc"         cons25  off  secure
# Virtual terminals
ttyv1   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv2   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv3   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv4   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv5   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv6   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv7   "/usr/libexec/getty Pc"         cons25  off  secure
ttyv8   "/usr/local/bin/xdm -nodaemon"  xterm   off secure
# Serial terminals
# The 'dialup' keyword identifies dialin lines to login, fingerd etc.
ttyu0   "/usr/libexec/getty std.9600"   dialup  off secure
ttyu1   "/usr/libexec/getty std.9600"   dialup  off secure
ttyu2   "/usr/libexec/getty std.9600"   dialup  off secure
ttyu3   "/usr/libexec/getty std.9600"   dialup  off secure
# Dumb console
dcons   "/usr/libexec/getty std.9600"   vt100   off secure
# Pseudo terminals
ttyp0   none                    network
ttyp1   none                    network
ttyp2   none                    network
ttyp3   none                    network
ttyp4   none                    network
ttyp5   none                    network
ttyp6   none                    network
ttyp7   none                    network
ttyp8   none                    network
EOF

echo "Permitting root login in sshd"
echo "PermitRootLogin yes" >> /mnt/etc/ssh/sshd_config
EOF

echo "Displaying the contents of /mnt/etc/"
ls /mnt/etc/
echo "Unmounting /mnt"
umount /mnt

echo "Creating $GUESTDIR/vmprep.sh"
cat > $GUESTDIR/vmprep.sh << EOF
#!/bin/sh
kldload vmm
kldload if_tap
ifconfig tap0 create
kldload bridgestp
kldload if_bridge
ifconfig bridge0 create
ifconfig bridge0 addm tap0 addm $HOSTNIC up
EOF

echo "Creating $GUESTDIR/vmrun.sh"
cat > $GUESTDIR/vmrun.sh << EOF
echo "Wait until 20 seconds after boot for networking to work"
/usr/sbin/vmmctl --vm=$GUESTNAME --destroy; /usr/sbin/bhyveload -m 768 -M 2048 -h $GUESTDIR $GUESTNAME && /usr/sbin/bhyve -c 2 -m 768 -M 2048 -g 0 -P -H -s 1,virtio-net,tap0 -s 2,virtio-blk,diskdev $GUESTNAME && sleep 20 && ifconfig tap0 up
EOF
ls $GUESTDIR/
echo ""
ls $GUESTDIR/boot/
echo ""
ls $GUESTDIR/boot/kernel/
echo ""
echo "Run 'sh vmprep.sh' in #GUESTDIR followed by 'sh vmrun.sh' to boot the guest."
echo "Run 'reboot' to terminate it" 
echo ""
echo "Guest has no root password by default. Create one to allow SSH access."
echo ""
echo "Completed: 6. Build BhyVe Guest"
echo "Optional: 7. Clean Up /mnt/ /dev/md0 /usr/obj/ and /usr/src-bhyve"
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

clean_up(){

echo "Unmounting /mnt"
umount /mnt
echo "Destroying md0"
mdconfig -d -u md0
echo "Deleting /usr/obj/*"
rm -rf /usr/obj/*
echo "Unmounting $WORK"
umount $WORK
#echo "Deleting $WORK"
#rm -rf $WORK
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

delete_pkg(){

echo "Running pkg_delete $PACKAGENAME"
pkg_delete $PACKAGENAME
pkg_info | grep BHyVe
echo ""
echo "Press the key of your choice to continue"
read "$*"
}

# The Menu

while :
do
clear
echo "Select an option. These steps must be performed sequentially:"
echo ""
echo "1. Add the subversion and binutils Packages"
echo "2. Configure the Host's /boot/loader.conf (reboot required)"
echo "3. Retrieve BHyVe Sources and Set-up the Working Directory"
echo "4. Build BHyVe Host Components and Package"
echo "5. Add the BHyVe Package (pkg_add $WORK/$PACKAGENAME.tar"
echo "6. Build BhyVe Guest"
echo "7. Clean Up /mnt/ /dev/md0 /usr/obj/ and $WORK"
echo "8. Delete the BHyVe Package (pkg_delete $PACKAGENAME)"
echo "9. Exit"
echo ""
echo "Once steps 1 through 6 complete successfully, you can boot the guest with:"
echo ""
echo "cd $GUESTDIR"
echo "sh vmprep.sh                      # Once per session"
echo "sh vmrun.sh                       # Each time you boot the guest"
echo "reboot                            # To shutdown the guest"
echo ""
echo -n "Please enter option : "
read option
case $option in
1) required_packages;;
2) host_loader;;
3) retrieve_and_setup;;
4) build_bhyve;;
5) add_pkg;;
6) build_guest;;
7) clean_up;;
8) delete_pkg;;
9) exit 1;;
*) echo "Invalid option";;
esac
done
