mirror of
https://github.com/churchers/vm-bhyve.git
synced 2025-12-11 09:20:17 +01:00
1093 lines
34 KiB
Bash
1093 lines
34 KiB
Bash
#!/bin/sh
|
|
#-------------------------------------------------------------------------+
|
|
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
|
|
# All rights reserved
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted providing that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. 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 AUTHOR ``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 AUTHOR 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.
|
|
|
|
# 'vm _run'
|
|
# run a virtual machine
|
|
# this is the background process that does all the work
|
|
# in most cases this should not be run directly
|
|
#
|
|
# @param string _name the name of the guest to run
|
|
# @param optional string _iso the iso file for an install
|
|
#
|
|
vm::run(){
|
|
local _name _iso _iso_dev
|
|
local _cpu _memory _bootdisk _bootdisk_dev _guest _wiredmem
|
|
local _guest_support _uefi _uuid _debug _hostbridge _loader
|
|
local _opts _devices _slot _install_slot _func=0 _taplist _exit _passdev
|
|
local _com _comports _comstring _logpath="/dev/null" _run=1
|
|
local _bhyve_options _action
|
|
|
|
cmd::parse_args "$@"
|
|
shift $?
|
|
_name="$1"
|
|
_iso="$2"
|
|
|
|
# try to load datstore details
|
|
datastore::get_guest "${_name}" || exit 5
|
|
|
|
# bail out immediately if guest running
|
|
vm::confirm_stopped "${_name}" "1" || exit 10
|
|
|
|
config::load "${VM_DS_PATH}/${_name}/${_name}.conf"
|
|
config::get "_memory" "memory"
|
|
config::get "_loader" "loader"
|
|
config::get "_bootdisk" "disk0_name"
|
|
config::get "_bootdisk_dev" "disk0_dev" "file"
|
|
config::get "_hostbridge" "hostbridge" "standard"
|
|
config::get "_comports" "comports" "com1"
|
|
config::get "_uuid" "uuid"
|
|
config::get "_debug" "debug" "no"
|
|
config::get "_bhyve_options" "bhyve_options"
|
|
config::get "_slot" "start_slot" "4"
|
|
config::get "_install_slot" "install_slot" "3"
|
|
|
|
# generate a uuid if we don't have one already
|
|
if [ -z "${_uuid}" ]; then
|
|
_uuid=$(uuidgen)
|
|
config::set "${_name}" "uuid" "${_uuid}"
|
|
fi
|
|
|
|
# get cpu topology
|
|
vm::__cpu "_cpu"
|
|
|
|
util::log_rotate "guest" "${_name}"
|
|
util::log "guest" "${_name}" \
|
|
"initialising" \
|
|
" [loader: ${_loader}]" \
|
|
" [cpu: ${_cpu}]" \
|
|
" [memory: ${_memory}]" \
|
|
" [hostbridge: ${_hostbridge}]" \
|
|
" [com ports: ${_comports}]" \
|
|
" [uuid: ${_uuid}]" \
|
|
" [debug mode: ${_debug}]" \
|
|
" [primary disk: ${_bootdisk}]" \
|
|
" [primary disk dev: ${_bootdisk_dev}]"
|
|
|
|
# check basic settings
|
|
if [ -z "${_loader}" -o -z "${_cpu}" -o -z "${_memory}" ]; then
|
|
util::log "guest" "${_name}" "fatal; unable to start - missing required configuration"
|
|
exit 15
|
|
fi
|
|
|
|
# check ug
|
|
if [ -n "${VM_NO_UG}" ]; then
|
|
|
|
# only FreeBSD guests. these start direct in 64bit mode and don't need UG
|
|
if [ "${_loader}" != "bhyveload" ]; then
|
|
util::log "guest" "${_name}" "fatal; unable to start - no unrestricted guest support"
|
|
exit 15
|
|
fi
|
|
|
|
# only 1 vcpu
|
|
if [ "${_cpu}" != "1" ]; then
|
|
_cpu=1
|
|
util::log "guest" "${_name}" "warning; no unrestricted guest support. reducing vcpu count to 1"
|
|
fi
|
|
fi
|
|
|
|
# default bhyve options
|
|
_opts="-AHP"
|
|
|
|
# ignore access to unimplemented Model Specific Registers?
|
|
config::yesno "ignore_msr" && _opts="${_opts}w"
|
|
|
|
# if uefi, make sure we have bootrom, then update options for uefi support
|
|
if [ "${_loader%-*}" = "uefi" ]; then
|
|
vm::uefi
|
|
fi
|
|
|
|
# add any custom bhyve options
|
|
[ -n "${_bhyve_options}" ] && _opts="${_opts} ${_bhyve_options}"
|
|
|
|
# if we have passthru, check vt-d or amdvi support now and exit
|
|
config::get "_passdev" "passthru0"
|
|
|
|
if [ -n "${_passdev}" ] && ! util::check_bhyve_iommu; then
|
|
util::log "guest" "${_name}" "fatal; pci passthrough not supported on this system (no VT-d or amdvi)"
|
|
exit 15
|
|
fi
|
|
|
|
# wired memory?
|
|
if config::yesno "wired_memory"; then
|
|
_wiredmem="1"
|
|
_opts="${_opts} -S"
|
|
fi
|
|
|
|
# set cpu/memory and uuid in opts
|
|
_opts="-c ${_cpu} -m ${_memory} ${_opts}"
|
|
[ -n "${_uuid}" ] && _opts="${_opts} -U ${_uuid}"
|
|
|
|
# set utc time in opts if requested
|
|
if config::yesno "utctime"; then
|
|
if [ ${VERSION_BSD} -ge 1002000 ]; then
|
|
_opts="${_opts} -u"
|
|
else
|
|
util::log "guest" "${_name}" "warning; utc time requested but not available pre FreeBSD 10.2"
|
|
fi
|
|
fi
|
|
|
|
# send bhyve output to bhyve.log if debug=yes
|
|
util::yesno "${_debug}" && _logpath="${VM_DS_PATH}/${_name}/bhyve.log"
|
|
|
|
# complete the boot disk path
|
|
[ -n "${_bootdisk}" ] && vm::get_disk_path "_bootdisk" "${_name}" "${_bootdisk}" "${_bootdisk_dev}"
|
|
|
|
# build bhyve device string
|
|
vm::bhyve_device_comports
|
|
vm::bhyve_device_basic
|
|
vm::bhyve_device_disks
|
|
vm::bhyve_device_networking
|
|
vm::bhyve_device_rand
|
|
vm::bhyve_device_passthru
|
|
vm::bhyve_device_fbuf
|
|
vm::bhyve_device_mouse
|
|
vm::bhyve_device_sound
|
|
vm::bhyve_device_console
|
|
|
|
vm::lock
|
|
util::log "guest" "${_name}" "booting"
|
|
cd /
|
|
|
|
while true; do
|
|
|
|
# destroy existing vmm
|
|
# freebsd seems happy to run a bhyveload/bhyve loop
|
|
# grub-bhyve doesn't seem to like it for a lot of users
|
|
# Peter says don't destroy in Windows instructions, so don't if in UEFI mode
|
|
if [ -e "/dev/vmm/${_name}" -a -z "${_uefi}" ]; then
|
|
bhyvectl --vm="${_name}" --destroy >/dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
util::log "guest" "${_name}" "fatal; failed to destroy existing vmm device"
|
|
_exit=15
|
|
break
|
|
fi
|
|
sleep 1
|
|
fi
|
|
|
|
# run any prestart script while guest is fully down
|
|
vm::prestart
|
|
|
|
# add install iso or disk image
|
|
if [ -n "${_iso}" ]; then
|
|
if echo "${_iso}" | grep -iqs '.iso$'; then
|
|
_iso_dev="-s ${_install_slot}:0,ahci-cd,${_iso},ro"
|
|
else
|
|
_iso_dev="-s ${_install_slot}:0,ahci-hd,${_iso},ro"
|
|
fi
|
|
fi
|
|
|
|
# use null.iso if not an install and uefi firmware
|
|
# old instructions but some windows versions apparently needed this present
|
|
[ -z "${_iso}" -a "${_loader}" = "uefi" ] && config::yesno "nulliso_fix" && \
|
|
_iso_dev="-s ${_install_slot}:0,ahci-cd,${vm_dir}/.config/null.iso"
|
|
|
|
# reasonably ugly hack to remove wait option after first run
|
|
[ "${_run}" -eq "2" ] && vm::bhyve_device_fbuf_clear_wait
|
|
|
|
# load guest
|
|
if [ -z "${_uefi}" ]; then
|
|
|
|
guest::load "${_iso}"
|
|
_exit=$?
|
|
|
|
# check no errors
|
|
if [ ${_exit} -ne 0 ]; then
|
|
util::log "guest" "${_name}" "fatal; loader returned error ${_exit}"
|
|
break
|
|
fi
|
|
fi
|
|
|
|
util::log "guest" "${_name}" " [bhyve options: ${_opts}]" \
|
|
" [bhyve devices: ${_devices}]" \
|
|
" [bhyve console: ${_comstring}]"
|
|
[ -n "${_iso_dev}" ] && util::log "guest" "${_name}" " [bhyve iso device: ${_iso_dev}]"
|
|
util::log "guest" "${_name}" "starting bhyve (run ${_run})"
|
|
|
|
# call rctl now as next line will block until bhyve exits
|
|
rctl::set &
|
|
|
|
# actually run bhyve!
|
|
# we're already in the background so we just wait for it to exit
|
|
bhyve ${_opts} \
|
|
${_devices} \
|
|
${_iso_dev} \
|
|
${_comstring} \
|
|
${_name} 2> "${_logpath}"
|
|
|
|
# get bhyve exit code
|
|
_exit=$?
|
|
util::log "guest" "${_name}" "bhyve exited with status ${_exit}"
|
|
|
|
# remove any console sockets
|
|
rm ${VM_DS_PATH}/${_name}/vtcon.* >/dev/null 2>&1
|
|
|
|
# decide what to do with exit code
|
|
vm::handle_exit "_action" ${_exit}
|
|
[ "${_action}" != "restart" ] && break
|
|
|
|
util::log "guest" "${_name}" "restarting"
|
|
|
|
# remove install iso so guest reboots from disk
|
|
# after install non-uefi guests will still get install cd until a full shutdown+restart
|
|
# as we don't reset _iso_dev
|
|
_iso=""
|
|
_run=$((_run + 1))
|
|
done
|
|
|
|
# destroy taps
|
|
for _devices in ${_taplist}; do
|
|
util::log "guest" "${_name}" "destroying network device ${_devices}"
|
|
ifconfig "${_devices}" destroy
|
|
done
|
|
|
|
util::log "guest" "${_name}" "stopped"
|
|
[ -e "/dev/vmm/${_name}" ] && bhyvectl --destroy --vm=${_name} >/dev/null 2>&1
|
|
|
|
vm::unlock
|
|
exit ${_exit}
|
|
}
|
|
|
|
# creates options to use a uefi bootrom
|
|
#
|
|
# @modifies _opts _uefi
|
|
#
|
|
vm::uefi(){
|
|
local _bootrom
|
|
|
|
if [ ${VERSION_BSD} -lt 1002509 ]; then
|
|
util::log "guest" "${_name}" "fatal; uefi guests can only be run on FreeBSD 10.3 or newer"
|
|
exit 15
|
|
fi
|
|
|
|
case "${_loader}" in
|
|
uefi-devel)
|
|
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI_CODE-devel.fd"
|
|
;;
|
|
uefi-csm)
|
|
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI_CSM.fd"
|
|
;;
|
|
uefi-custom)
|
|
_bootrom="${VM_DS_PATH}/.config/BHYVE_UEFI.fd"
|
|
;;
|
|
*)
|
|
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI.fd"
|
|
;;
|
|
esac
|
|
|
|
if [ ! -e "${_bootrom}" ]; then
|
|
util::log "guest" "${_name}" "fatal; unable to locate firmware ${_bootrom}"
|
|
exit 15
|
|
fi
|
|
|
|
# should we store uefi vars?
|
|
if config::yesno "uefi_vars"; then
|
|
|
|
# do we already have a storage file for this guest?
|
|
if [ -e "${VM_DS_PATH}/${_name}/uefi-vars.fd" ]; then
|
|
:
|
|
elif [ -e "/usr/local/share/uefi-firmware/BHYVE_UEFI_VARS.fd" ]; then
|
|
# create a copy and use
|
|
cp "/usr/local/share/uefi-firmware/BHYVE_UEFI_VARS.fd" "${VM_DS_PATH}/${_name}/uefi-vars.fd"
|
|
else
|
|
util::log "guest" "${_name}" "fatal; unable to locate UEFI vars database or template"
|
|
exit 15
|
|
fi
|
|
|
|
_bootrom="${_bootrom},${VM_DS_PATH}/${_name}/uefi-vars.fd"
|
|
fi
|
|
|
|
_opts="-Hwl bootrom,${_bootrom}"
|
|
_uefi="yes"
|
|
}
|
|
|
|
# decide how to handle bhyve exit code
|
|
#
|
|
# @param string _var variable to put action into
|
|
# @param int _code bhyve exit code
|
|
#
|
|
vm::handle_exit(){
|
|
local _var="$1"
|
|
local _code="$2"
|
|
|
|
# check exit code
|
|
# get relevant action from config, defaulting to the behaviour
|
|
# we'd normally expect. we don't currently allow overriding shutdown
|
|
# as it makes it impossible to actually stop a guest cleanly
|
|
#
|
|
case "${_code}" in
|
|
0) config::get "${_var}" "on_restart" "restart" ;;
|
|
1) ;&
|
|
2) if [ -e "${VM_DS_PATH}/${_name}/restart" ]; then
|
|
setvar "${_var}" "restart"
|
|
unlink "${VM_DS_PATH}/${_name}/restart" >/dev/null 2>&1
|
|
else
|
|
setvar "${_var}" "shutdown"
|
|
fi
|
|
;;
|
|
*) config::get "${_var}" "on_fault" "shutdown" ;;
|
|
esac
|
|
}
|
|
|
|
# lock a vm
|
|
# stop another instance being started on this or another host
|
|
# we write hostname so vm-bhyve can inform user which host locked a vm
|
|
#
|
|
# @param string - the name of the guest to lock
|
|
#
|
|
vm::lock(){
|
|
hostname > "${VM_DS_PATH}/${_name}/run.lock"
|
|
}
|
|
|
|
# unlock a vm
|
|
#
|
|
# @param string - the name of the guest to unlock
|
|
#
|
|
vm::unlock(){
|
|
unlink "${VM_DS_PATH}/${_name}/run.lock" >/dev/null 2>&1
|
|
unlink "${VM_DS_PATH}/${_name}/console" >/dev/null 2>&1
|
|
}
|
|
|
|
# create string for guest com ports
|
|
# this builds the '-l comX' part of the bhyve command into _comstring
|
|
# _com is used by bhyveload|grub_bhyve so we set that to the first
|
|
# com port we come across.
|
|
# The nmdm devices are written to $vm_dir/{guest}/console so we can
|
|
# read them back later for the 'vm console' command
|
|
#
|
|
# @modifies _com _comstring
|
|
#
|
|
vm::bhyve_device_comports(){
|
|
local _port _num=1 _tmux_name
|
|
local _port_name
|
|
|
|
unlink "${VM_DS_PATH}/${_name}/console" >/dev/null 2>&1
|
|
|
|
for _port in ${_comports}; do
|
|
if [ ${_num} -eq 1 ]; then
|
|
# if tmux mode, log this to the console data
|
|
if [ -n "${VM_OPT_TMUX}" ]; then
|
|
_tmux_name=$(echo "${_name}" | tr "." "~")
|
|
echo "${_port}=tmux/${_tmux_name}" >> "${VM_DS_PATH}/${_name}/console"
|
|
fi
|
|
|
|
# if foreground mode, we don't configure a serial port
|
|
if [ -n "${VM_OPT_FOREGROUND}" ]; then
|
|
_comstring="-l ${_port},stdio"
|
|
_num=$((_num + 1))
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# generate a port name unique to this vm and port number
|
|
_port_name="/dev/nmdm-${_name}.${_num}"
|
|
|
|
# use first com port for the loader
|
|
[ ${_num} -eq 1 ] && _com="${_port_name}A"
|
|
|
|
echo "${_port}=${_port_name}B" >> "${VM_DS_PATH}/${_name}/console"
|
|
_comstring="${_comstring}${_comstring:+ }-l ${_port},${_port_name}A"
|
|
_num=$((_num + 1))
|
|
done
|
|
}
|
|
|
|
# get bhyve device string for basic devices
|
|
# hostbridge & lpc on their own slots
|
|
# windows requires slot 0 & 31, nothing else cares fortunately
|
|
#
|
|
# @modifies _devices
|
|
#
|
|
vm::bhyve_device_basic(){
|
|
|
|
# add hostbridge
|
|
case "$_hostbridge" in
|
|
no*) ;;
|
|
amd) _devices="-s 0,amd_hostbridge" ;;
|
|
*) _devices="-s 0,hostbridge" ;;
|
|
esac
|
|
|
|
# lpc
|
|
_devices="${_devices}${_devices:+ }-s 31,lpc"
|
|
}
|
|
|
|
# get bhyve device string for disk devices
|
|
# read all disks starting at 0 and add to the _devices string
|
|
# this is done first so disks will start at slot 4. For uefi,
|
|
# we move through slots and stop at slot 6. For non-uefi we
|
|
# step through all functions and just keep going
|
|
#
|
|
# since r302459 the ahci controller supports
|
|
# up to 32 devices per controller. by default we set
|
|
# the device limit to 1 and use the original syntax, but
|
|
# this can be overridden by setting the ahci_device_limit
|
|
# guest option to an integer between 2 and 32.
|
|
#
|
|
# @modifies _devices _slot
|
|
#
|
|
vm::bhyve_device_disks(){
|
|
local _disk _type _dev _path _opts _ahci _atype
|
|
local _ahci_num=0 _num=0 _add _ahci_multi
|
|
local _ahci_limit=1
|
|
|
|
# check if user has set a per-controller device limit
|
|
config::get "_ahci_multi" "ahci_device_limit"
|
|
|
|
if [ ${VERSION_BSD} -ge 1200000 ] || \
|
|
[ ${VERSION_BSD} -ge 1101000 ] || \
|
|
[ ${VERSION_BSD} -ge 1004000 ]; then
|
|
|
|
if [ -n "${_ahci_multi}" ]; then
|
|
|
|
# see if it's numeric
|
|
echo "${_ahci_multi}" | egrep -iqs '^[0-9]+$'
|
|
|
|
[ $? -eq 0 -a ${_ahci_multi} -gt 1 -a ${_ahci_multi} -le 32 ] && \
|
|
_ahci_limit="${_ahci_multi}"
|
|
fi
|
|
fi
|
|
|
|
# get all disks
|
|
while true; do
|
|
config::get "_disk" "disk${_num}_name"
|
|
config::get "_type" "disk${_num}_type"
|
|
[ -z "${_disk}" -o -z "${_type}" ] && break
|
|
|
|
config::get "_dev" "disk${_num}_dev"
|
|
config::get "_opts" "disk${_num}_opts"
|
|
|
|
if [ ${_func} -ge 8 ]; then
|
|
_func=0
|
|
_slot=$((_slot + 1))
|
|
fi
|
|
|
|
vm::get_disk_path "_path" "${_name}" "${_disk}" "${_dev}"
|
|
|
|
# ahci device and multi mode?
|
|
if [ ${_ahci_limit} -gt 1 -a "${_type%%-*}" = "ahci" ]; then
|
|
|
|
# check device type
|
|
case "${_type}" in
|
|
ahci-cd)
|
|
_atype="cd"
|
|
;&
|
|
ahci-hd)
|
|
[ -z "${_atype}" ] && _atype="hd"
|
|
_ahci="${_ahci},${_atype}:${_path}"
|
|
[ -n "${_opts}" ] && _ahci="${_ahci},${_opts}"
|
|
_ahci_num=$((_ahci_num + 1))
|
|
|
|
# we need to move to another controller if we get to the limit
|
|
if [ ${_ahci_num} -ge ${_ahci_limit} ]; then
|
|
_devices="${_devices} -s ${_slot}:${_func},ahci${_ahci}"
|
|
_ahci=""
|
|
_ahci_num=0
|
|
_add=1
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
_atype=""
|
|
else
|
|
|
|
_devices="${_devices} -s ${_slot}:${_func},${_type},${_path}"
|
|
[ -n "${_opts}" ] && _devices="${_devices},${_opts}"
|
|
_add=1
|
|
fi
|
|
|
|
# have we just added a device?
|
|
if [ -n "${_add}" ]; then
|
|
if [ -n "${_uefi}" ]; then
|
|
_slot=$((_slot + 1))
|
|
|
|
else
|
|
_func=$((_func + 1))
|
|
fi
|
|
|
|
_add=""
|
|
fi
|
|
|
|
_num=$((_num + 1))
|
|
done
|
|
|
|
# have ahci devices left?
|
|
if [ -n "${_ahci}" ]; then
|
|
_devices="${_devices} -s ${_slot}:${_func},ahci${_ahci}"
|
|
[ -n "${_uefi}" ] && _slot=$((_slot + 1))
|
|
fi
|
|
|
|
# move to next slot if we have devices
|
|
# unless uefi as we already inc slot in uefi mode
|
|
if [ ${_num} -ge 1 -a -z "${_uefi}" ]; then
|
|
_slot=$((_slot + 1))
|
|
_func=0
|
|
fi
|
|
}
|
|
|
|
# get bhyve device string for networking
|
|
# we dynamically create a new tap device for each interface
|
|
# if we can find the correct bridge, we then add the tap as a member
|
|
# we add each tap to __taplist from vm::run which it will
|
|
# use to desstroy them all on shutdown
|
|
#
|
|
# @modifies _devices _slot _taplist
|
|
#
|
|
vm::bhyve_device_networking(){
|
|
local _type _switch _mac _custom_tap _tap _sid _mtu
|
|
local _num=0 _member_type _sw_type _iname
|
|
|
|
while true; do
|
|
config::get "_type" "network${_num}_type"
|
|
[ -z "${_type}" ] && break
|
|
|
|
config::get "_switch" "network${_num}_switch"
|
|
config::get "_mac" "network${_num}_mac"
|
|
config::get "_custom_tap" "network${_num}_device"
|
|
config::get "_iname" "network${_num}_name"
|
|
|
|
# set a static mac if we don't have one
|
|
[ -z "${_mac}" ] && vm::generate_static_mac
|
|
|
|
# move slot if we've hit function 8
|
|
if [ ${_func} -ge 8 ]; then
|
|
_func=0
|
|
_slot=$((_slot + 1))
|
|
fi
|
|
|
|
# get type of switch
|
|
switch::type "_sw_type" "${_switch}"
|
|
|
|
if [ "${_sw_type}" = "vale" ]; then
|
|
# create a vale port id
|
|
switch::vale::id "_tap" "${_switch}" "${_mac}"
|
|
|
|
util::log "guest" "${_name}" "adding vale interface ${_tap} (${_switch})"
|
|
_devices="${_devices} -s ${_slot}:${_func},${_type},${_tap}"
|
|
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
|
|
|
|
_func=$((_func + 1))
|
|
else
|
|
# create interface
|
|
if [ -n "${_custom_tap}" ]; then
|
|
_tap="${_custom_tap}"
|
|
elif [ -n "${_iname}" ]; then
|
|
_tap=$(ifconfig tap create name "${_iname}")
|
|
else
|
|
_tap=$(ifconfig tap create)
|
|
fi
|
|
|
|
# should this be a span member?
|
|
_member_type="addm"
|
|
config::yesno "network${_num}_span" && _member_type="span"
|
|
|
|
if [ -n "${_tap}" ]; then
|
|
util::log "guest" "${_name}" "initialising network device ${_tap}"
|
|
ifconfig "${_tap}" descr "vmnet/${_name}/${_num}/${_switch:-custom}" group vm-port >/dev/null 2>&1
|
|
|
|
if [ -n "${_switch}" ]; then
|
|
switch::id "_sid" "${_switch}"
|
|
|
|
if [ -n "${_sid}" ]; then
|
|
_mtu=$(ifconfig "${_sid}" | head -n1 | awk '{print $NF}')
|
|
|
|
if [ "${_mtu}" != "1500" ]; then
|
|
util::log "guest" "${_name}" "setting mtu of ${_tap} to ${_mtu}"
|
|
ifconfig "${_tap}" mtu "${_mtu}" >/dev/null 2>&1
|
|
fi
|
|
|
|
util::log "guest" "${_name}" "adding ${_tap} -> ${_sid} (${_switch} ${_member_type})"
|
|
ifconfig "${_sid}" "${_member_type}" "${_tap}" >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to add ${_tap} to ${_sid}"
|
|
|
|
util::log "guest" "${_name}" "bring up ${_tap} -> ${_sid} (${_switch} ${_member_type})"
|
|
ifconfig "${_tap}" up >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to bring up ${_tap} in ${_sid}"
|
|
|
|
# set private if configured
|
|
switch::is_private "${_switch}" && ifconfig "${_sid}" "private" "${_tap}" >/dev/null 2>&1
|
|
else
|
|
util::log "guest" "${_name}" "failed to find virtual switch '${_switch}'"
|
|
fi
|
|
fi
|
|
|
|
_devices="${_devices} -s ${_slot}:${_func},${_type},${_tap}"
|
|
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
|
|
|
|
_func=$((_func + 1))
|
|
[ -z "${_custom_tap}" ] && _taplist="${_taplist}${_taplist:+ }${_tap}"
|
|
fi
|
|
fi
|
|
|
|
_num=$((_num + 1))
|
|
done
|
|
|
|
if [ ${_num} -ge 1 ]; then
|
|
_slot=$((_slot + 1))
|
|
_func=0
|
|
fi
|
|
}
|
|
|
|
# check if user wants a virtio-rand device
|
|
#
|
|
# @modifies _devices _slot
|
|
#
|
|
vm::bhyve_device_rand(){
|
|
|
|
if config::yesno "virt_random"; then
|
|
_devices="${_devices} -s ${_slot}:0,virtio-rnd"
|
|
_slot=$((_slot + 1))
|
|
fi
|
|
}
|
|
|
|
# add frame buffer output
|
|
#
|
|
vm::bhyve_device_fbuf(){
|
|
local _port _listen _res _pass _vga _wait
|
|
local _fbuf_conf
|
|
|
|
# only works in uefi mode
|
|
[ -z "${_uefi}" ] && return 0
|
|
|
|
# check graphics enabled
|
|
! config::yesno "graphics" && return 0
|
|
|
|
# only available in 11+
|
|
if [ ${VERSION_BSD} -lt 1100000 ]; then
|
|
util::log "guest" "${_name}" "warning; UEFI graphics is only available in FreeBSD 11 and newer"
|
|
return 1
|
|
fi
|
|
|
|
config::get "_port" "graphics_port"
|
|
config::get "_listen" "graphics_listen"
|
|
config::get "_res" "graphics_res"
|
|
config::get "_vga" "graphics_vga"
|
|
config::get "_pass" "vnc_password"
|
|
config::get "_wait" "graphics_wait" "auto"
|
|
|
|
# check if graphics_wait is auto
|
|
# auto will count as yes so we need to unset it if we're not in an install.
|
|
[ "${_wait}" = "auto" -a -z "${_iso}" ] && _wait="no"
|
|
|
|
# try to get port
|
|
# return if we can't
|
|
if [ -z "${_port}" ]; then
|
|
vm::find_available_net_port "_port" "5900"
|
|
|
|
if [ -z "${_port}" ]; then
|
|
util::log "guest" "${_name}" "warning; unable to allocate a network port for graphics/vnc"
|
|
return 1
|
|
fi
|
|
|
|
util::log "guest" "${_name}" "dynamically allocated port ${_port} for vnc connections"
|
|
fi
|
|
|
|
# add ip, port, resolution, wait
|
|
_fbuf_conf="tcp=${_listen:-0.0.0.0}:${_port}"
|
|
[ -n "${_res}" ] && _fbuf_conf="${_fbuf_conf},w=${_res%%x*},h=${_res##*x}"
|
|
[ -n "${_vga}" ] && _fbuf_conf="${_fbuf_conf},vga=${_vga}"
|
|
[ -n "${_pass}" ] && _fbuf_conf="${_fbuf_conf},password=${_pass}"
|
|
util::yesno "${_wait}" && _fbuf_conf="${_fbuf_conf},wait"
|
|
|
|
# write vnc port to console data
|
|
echo "vnc=${_listen:-0.0.0.0}:${_port}" >> "${VM_DS_PATH}/${_name}/console"
|
|
|
|
# add device
|
|
_devices="${_devices} -s ${_slot}:0,fbuf,${_fbuf_conf}"
|
|
_slot=$((_slot + 1))
|
|
}
|
|
|
|
# remove wait option if we're in auto mode
|
|
# fairly ugly code that uses sed to look for ",wait{word boundary}"
|
|
# and removes it. Hopefully this will never match anything else in
|
|
# the device string...
|
|
#
|
|
# @modifies _devices
|
|
#
|
|
vm::bhyve_device_fbuf_clear_wait(){
|
|
local _wait
|
|
|
|
[ -z "${_uefi}" ] && return
|
|
|
|
config::get "_wait" "graphics_wait" "auto"
|
|
[ "${_wait}" = "auto" ] && _devices=$(echo "${_devices}" | sed 's@,wait[[:>:]]@@')
|
|
}
|
|
|
|
# add a xhci mouse device
|
|
#
|
|
vm::bhyve_device_mouse(){
|
|
|
|
[ ${VERSION_BSD} -lt 1100000 ] && return 0
|
|
|
|
# add a tablet device if enabled
|
|
if config::yesno "xhci_mouse"; then
|
|
_devices="${_devices} -s ${_slot}:0,xhci,tablet"
|
|
_slot=$((_slot + 1))
|
|
fi
|
|
}
|
|
|
|
vm::bhyve_device_sound(){
|
|
local _play
|
|
config::get "_play" "sound_play" "/dev/dsp0"
|
|
|
|
if config::yesno "sound"; then
|
|
_devices="${_devices} -s ${_slot}:0,hda,play=${_play}"
|
|
_slot=$((_slot + 1))
|
|
fi
|
|
}
|
|
|
|
# add virtio_console devices to the guest
|
|
#
|
|
# @modifies _devices _slot
|
|
#
|
|
vm::bhyve_device_console(){
|
|
local _console _curr=0
|
|
local _dev_str
|
|
|
|
[ ${VERSION_BSD} -lt 1102000 ] && return 0
|
|
config::get "_console" "virt_console0"
|
|
[ -z "${_console}" ] && return 0
|
|
|
|
# add ports
|
|
while [ -n "${_console}" -a ${_curr} -lt 16 ]; do
|
|
# if set to "yes/on/1", just use the console number as port name
|
|
case "${_console}" in
|
|
[Yy][Ee][Ss]|[Oo][Nn]|1) _console="${_curr}" ;;
|
|
esac
|
|
|
|
_dev_str="${_dev_str},${_console}=${VM_DS_PATH}/${_name}/vtcon.${_console}"
|
|
|
|
_curr=$((_curr + 1))
|
|
config::get "_console" "virt_console${_curr}"
|
|
done
|
|
|
|
_devices="${_devices} -s ${_slot}:0,virtio-console${_dev_str}"
|
|
_slot=$((_slot + 1))
|
|
}
|
|
|
|
# get any pci passthrough devices
|
|
# FreeBSD 11 needs wired memory so update _opts in that case if
|
|
# we have any pass through devices
|
|
#
|
|
# @modifies _devices _slot _opts _wiredmem
|
|
#
|
|
vm::bhyve_device_passthru(){
|
|
local _dev _orig_slot _func=0
|
|
local _last_orig_slot
|
|
local _num=0
|
|
|
|
while true; do
|
|
config::get "_dev" "passthru${_num}"
|
|
[ -z "${_dev}" ] && break
|
|
|
|
# see if there's an = sign
|
|
# we allow A/B/C=D:E to force D:E as the guest SLOT:FUNC
|
|
if echo "${_dev}" | grep -qs "="; then
|
|
_devices="${_devices} -s ${_dev##*=},passthru,${_dev%%=*}"
|
|
else
|
|
_orig_slot=${_dev%%/*}
|
|
|
|
# only move to new slot if the original device is on a different slot to the last one.
|
|
# if user wants to passthru a device that has multiple functions which must stay together
|
|
# on one slot, they should be together in configuration file
|
|
if [ -n "${_last_orig_slot}" -a "${_last_orig_slot}" != "${_orig_slot}" ]; then
|
|
_slot=$((_slot + 1))
|
|
_func=0
|
|
fi
|
|
|
|
_devices="${_devices} -s ${_slot}:${_func},passthru,${_dev}"
|
|
_last_orig_slot=${_orig_slot}
|
|
_func=$((_func + 1))
|
|
fi
|
|
|
|
_num=$((_num + 1))
|
|
done
|
|
|
|
if [ ${_num} -ge 1 ]; then
|
|
_slot=$((_slot + 1))
|
|
|
|
# add wired memory for 10.3+
|
|
[ ${VERSION_BSD} -ge 1003000 ] && _opts="${_opts} -S" && _wiredmem="1"
|
|
fi
|
|
}
|
|
|
|
# get the path to a disk image depending on type.
|
|
# the disk name in configuration is usually the name of the file
|
|
# or zvol, directly under the guest directory/dataset.
|
|
# if a user wants the disk image anywhere else, they can specify
|
|
# the following
|
|
# diskX_dev="custom"
|
|
# diskX_name="/full/path/to/disk/device/or/file"
|
|
#
|
|
# @param string _var variable to put disk path into
|
|
# @param string _name the name of the guest
|
|
# @param string _disk the name of the disk
|
|
# @param string _disk_dev=file|zvol|sparse-zvol|custom type of device
|
|
#
|
|
vm::get_disk_path(){
|
|
local _var="$1"
|
|
local _name="$2"
|
|
local _disk="$3"
|
|
local _disk_dev="$4"
|
|
|
|
case "${_disk_dev}" in
|
|
zvol) ;&
|
|
sparse-zvol) setvar "${_var}" "/dev/zvol/${VM_DS_ZFS_DATASET}/${_name}/${_disk}" ;;
|
|
custom) setvar "${_var}" "${_disk}" ;;
|
|
iscsi) info::__find_iscsi "${_var}" "${_disk}" ;;
|
|
*) setvar "${_var}" "${VM_DS_PATH}/${_name}/${_disk}" ;;
|
|
esac
|
|
}
|
|
|
|
# looks for a prestart setting and runs it if possible
|
|
# this will execute before each start/reboot
|
|
#
|
|
# script is given the guest name and full path as arguments
|
|
#
|
|
vm::prestart(){
|
|
local _script
|
|
|
|
config::get "_script" "prestart" || return 0
|
|
echo "${_script}" | grep -qs '^/'
|
|
[ $? -ne 0 ] && _script="${VM_DS_PATH}/${_name}/${_script}"
|
|
[ ! -x "${_script}" ] && return 1
|
|
|
|
util::log "guest" "${_name}" "running prestart script ${_script} ${_name} ${VM_DS_ZFS_DATASET}/${_name}"
|
|
|
|
(
|
|
cd "${VM_DS_PATH}/${_name}"
|
|
exec "${_script}" "${_name}" "${VM_DS_ZFS_DATASET}/${_name}"
|
|
)
|
|
}
|
|
|
|
# get a list of all running virtual machines
|
|
# this loads a list of all running guests into global variables
|
|
# both variables are in the following format with one guest per line
|
|
# "pid guest"
|
|
#
|
|
# VM_RUN_BHYVE = all running bhyve instances
|
|
# VM_RUN_LOAD = all loading guests (bhyveload|grub-bhyve)
|
|
#
|
|
# @modifies VM_RUN_BHYVE VM_RUN_LOAD
|
|
#
|
|
vm::running_load(){
|
|
VM_RUN_BHYVE=$(pgrep -fl "bhyve:" | awk '{print $1,$NF}')
|
|
VM_RUN_LOAD=$(pgrep -fl "grub-bhyve|bhyveload" | awk '{print $1,$NF}')
|
|
}
|
|
|
|
# see if a specific virtual machine is running
|
|
# we search the VM_RUN_BHYVE and VM_RUN_LOAD variables looking for the
|
|
# specified guest. If found we output "Running" or "Bootloader", along with
|
|
# the PID. If not running we output "Stopped"
|
|
#
|
|
# @param string _var variable to put result into
|
|
# @param string _name name of the guest to look for
|
|
# @return success if running
|
|
#
|
|
vm::running_check(){
|
|
local _var="$1"
|
|
local _var2="$2"
|
|
local _name="$3"
|
|
local IFS=$'\n'
|
|
local _entry
|
|
|
|
for _entry in ${VM_RUN_BHYVE}; do
|
|
if [ "${_entry##* }" = "${_name}" ]; then
|
|
setvar "${_var}" "Running (${_entry%% *})"
|
|
setvar "${_var2}" "${_entry%% *}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
for _entry in ${VM_RUN_LOAD}; do
|
|
if [ "${_entry##* }" = "${_name}" ]; then
|
|
setvar "${_var}" "Bootloader (${_entry%% *})"
|
|
setvar "${_var2}" "${_entry%% *}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
setvar "${_var}" "Stopped"
|
|
return 1
|
|
}
|
|
|
|
# make sure a virtual machine is not running
|
|
# display warning if it is. up to caller to decide whether to continue.
|
|
# on unclean shutdown, lock files may not be cleared up by vm-bhyve,
|
|
# we now provide option to skip the lock file if it references this host,
|
|
# and there is no /dev/vmm/{_name}.
|
|
#
|
|
# @param string _name the guest name
|
|
# @param int _skip_lock skip local lock file if no /dev/vmm found
|
|
# @return success if not running
|
|
#
|
|
vm::confirm_stopped(){
|
|
local _name="$1"
|
|
local _skip_lock="$2"
|
|
local _host _our_host
|
|
|
|
_our_host=$(hostname)
|
|
|
|
# check vm-bhyve lock
|
|
# this will err even if guest is running on another node
|
|
if [ -e "${VM_DS_PATH}/${_name}/run.lock" ]; then
|
|
_host=$(head -n 1 "${VM_DS_PATH}/${_name}/run.lock")
|
|
|
|
if [ "${_host}" != "${_our_host}" -o "${_skip_lock}" != "1" ]; then
|
|
util::warn "${_name} appears to be running on ${_host} (locked)"
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
# check local machine, just in case guest run manually
|
|
if [ -e "/dev/vmm/${_name}" ]; then
|
|
util::warn "${_name} appears to be running locally (vmm exists)"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# generate a static mac address for a guest.
|
|
# we now make sure all interfaces have a static mac so
|
|
# there's no worry of guest problems due to it changing.
|
|
# bhyve is allocated 58:9c:fc:0x:xx:xx, so we try and
|
|
# randomise the last 20 bits (5 hex digits).
|
|
# using guest name, interface number and time as seed,
|
|
# with an incrementing integer just in case we happen to
|
|
# get 5 zeros.
|
|
#
|
|
vm::generate_static_mac(){
|
|
local _time=$(date +%s)
|
|
local _key _part="0:00:00"
|
|
local _base _int=0
|
|
|
|
_base="${_name}:${_num}:${_time}"
|
|
|
|
# generate the last 5 digits
|
|
# make sure we don't get 0:00:00 (see sys/net/ieee_oui.h)
|
|
while [ "${_part}" = "0:00:00" ]; do
|
|
_key="${_base}:${_int}"
|
|
_part=$(md5 -qs "${_key}" | awk 'BEGIN {FS="";OFS=":"}; {print $1,$2$3,$4$5}')
|
|
_int=$((_int + 1))
|
|
done
|
|
|
|
# add base bhyve OUI
|
|
_mac="58:9c:fc:0${_part}"
|
|
|
|
util::log "guest" "${_name}" "generated static mac ${_mac} (based on '${_key}')"
|
|
config::set "${_name}" "network${_num}_mac" "${_mac}"
|
|
}
|
|
|
|
# try to find an available network port to listen on
|
|
# this isn't clever enough to diffeential ports on unique ip's yet
|
|
#
|
|
# @param string _var variable to put port into
|
|
# @param int _curr the port to start searching from
|
|
# @return true if we found a port
|
|
#
|
|
vm::find_available_net_port(){
|
|
local _var="$1"
|
|
local _curr="$2"
|
|
local _open _line _max _used
|
|
local IFS=$'\n'
|
|
|
|
# stop searching after 200 ports
|
|
_max=$((_curr + 200))
|
|
|
|
# get list of open ports
|
|
_open=$(netstat -an | grep LISTEN | awk '{print $4}' | awk -F. '{print $NF}')
|
|
|
|
while [ "${_curr}" -lt "${_max}" ]; do
|
|
|
|
# reset used flag
|
|
_used=""
|
|
|
|
# see if current port is used
|
|
for _line in ${_open}; do
|
|
[ "${_line}" = "${_curr}" ] && _used="1"
|
|
done
|
|
|
|
# this port available?
|
|
if [ -z "${_used}" ]; then
|
|
setvar "${_var}" "${_curr}"
|
|
return 0
|
|
fi
|
|
|
|
_curr=$((_curr + 1))
|
|
done
|
|
|
|
# not found a port
|
|
setvar "${_var}" ""
|
|
return 1
|
|
}
|
|
|
|
# remove any values from configuration that should be unique
|
|
# such as uuid and mac addresses. these should be generated
|
|
# automatically at runtime
|
|
#
|
|
# used for cloning or imaging existing guests
|
|
# should this remove devices like passthru that cannot be shared?
|
|
#
|
|
# @param string _name name of guest to generalise
|
|
#
|
|
vm::generalise(){
|
|
local _name="$1"
|
|
local _entry _num=0
|
|
|
|
config::load "${VM_DS_PATH}/${_name}/${_name}.conf"
|
|
config::remove "${_name}" "uuid"
|
|
|
|
while true; do
|
|
config::get "_entry" "network${_num}_mac"
|
|
[ -z "${_entry}" ] && break
|
|
|
|
config::remove "${_name}" "network${_num}_mac"
|
|
_num=$((_num + 1))
|
|
done
|
|
}
|
|
|
|
# get number of cpus from the configuration file, and also
|
|
# look for sockets/cores/threads options.
|
|
# returns the bhyve cpu config string
|
|
#
|
|
# @param string _var variable to put cpu configuration into
|
|
#
|
|
vm::__cpu(){
|
|
local _var="$1"
|
|
local _c_cpu _c_socket _c_core _c_thread
|
|
local _config
|
|
|
|
config::get "_c_cpu" "cpu"
|
|
[ -n "${_c_cpu}" ] || util::err "fatal; unable to determine cpu count"
|
|
_config="${_c_cpu}"
|
|
|
|
config::get "_c_socket" "cpu_sockets"
|
|
config::get "_c_core" "cpu_cores"
|
|
config::get "_c_thread" "cpu_threads"
|
|
|
|
[ -n "${_c_socket}" ] && _config="${_config},sockets=${_c_socket}"
|
|
[ -n "${_c_core}" ] && _config="${_config},cores=${_c_core}"
|
|
[ -n "${_c_thread}" ] && _config="${_config},threads=${_c_thread}"
|
|
|
|
setvar "${_var}" "${_config}"
|
|
}
|