123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- # QEMU support lib
- die()
- {
- echo "$*" >&2
- exit 1
- }
- # $1=text
- tolower()
- {
- local str="$1"
- echo -n "$str" | tr '[:upper:]' '[:lower:]'
- }
- # $1=value
- parse_bool()
- {
- local str="$1"
- str="$(tolower "$str")"
- ! [ "$str" = "0" -o \
- "$str" = "off" -o \
- "$str" = "false" -o \
- "$str" = "no" ]
- }
- # $1=value
- bool_to_1_0()
- {
- parse_bool "$1" && echo -n 1 || echo -n 0
- }
- # $1=value
- bool_to_on_off()
- {
- parse_bool "$1" && echo -n on || echo -n off
- }
- get_ports_db_file()
- {
- local dbfile="/tmp/qemu-lib-ports.db"
- touch "$dbfile"
- chmod 666 "$dbfile"
- echo "$dbfile"
- }
- random_port()
- {
- local dbfile="$(get_ports_db_file)"
- local port=
- while true; do
- port="$(expr "$(hexdump -n2 -e'/2 "%u"' /dev/urandom)" '%' 16384 '+' 1024)"
- grep -qEe "^${port}\$" "$dbfile" || break
- done
- echo "$port" >> "$dbfile"
- echo "$port"
- }
- # $1=portnumber
- release_port()
- {
- local port="$1"
- local dbfile="$(get_ports_db_file)"
- sed -ie '/^'"$port"'$/d' "$dbfile"
- }
- run_qemu()
- {
- local bin="$qemu_binary"
- echo "Running QEMU..."
- "$bin" --version
- echo ""
- echo "$bin $*"
- [ $opt_dryrun -eq 0 ] || return
- if [ $opt_spice -eq 0 ]; then
- exec "$bin" "$@"
- else
- "$bin" "$@" &
- qemu_pid=$!
- echo "Forked qemu (pid ${qemu_pid})"
- fi
- }
- run_spice_client()
- {
- [ $opt_spice -eq 0 ] && return
- echo "Running spice client on ${spice_host}:${spice_port}..."
- [ $opt_dryrun -eq 0 ] && {
- sleep 1
- spicy -h "$spice_host" -p "$spice_port"
- echo "Killing qemu..."
- kill "$qemu_pid"
- wait
- }
- release_port "$spice_port"
- }
- share_init()
- {
- sharedir="$basedir/share"
- mkdir -p "$sharedir" || die "Failed to create $sharedir"
- }
- serial_init()
- {
- local serialdir="$basedir/serial"
- local from=0
- local to=0
- mkdir -p "$serialdir" || die "Failed to create $serialdir"
- for i in $(seq $from $to); do
- [ -p "$serialdir/$i" ] || {
- mkfifo "$serialdir/$i" || die "Failed to create fifo $i"
- }
- serial_opt="$serial_opt -serial pipe:$serialdir/0"
- done
- }
- kvm_init()
- {
- modprobe kvm >/dev/null 2>&1
- modprobe kvm-amd >/dev/null 2>&1
- modprobe kvm-intel >/dev/null 2>&1
- if [ -w /dev/kvm ]; then
- kvm_opt="-enable-kvm"
- else
- echo "===> WARNING: /dev/kvm not writable"
- fi
- }
- # Convert vendor:device to bus:dev
- usb_vendor_to_dev()
- {
- local ids="$1"
- local lsusb_string="$(lsusb | grep -e "$ids" | head -n1)"
- if [ -n "$lsusb_string" ]; then
- local busnr="$(echo "$lsusb_string" | awk '{print $2;}')"
- local devnr="$(echo "$lsusb_string" | awk '{print $4;}' | cut -d':' -f1)"
- printf '%s:%s' "$busnr" "$devnr"
- fi
- }
- # $1="vendor:device"
- host_usb_id_prepare()
- {
- local ids="$1"
- local bus_dev="$(usb_vendor_to_dev "$ids")"
- [ -n "$bus_dev" ] || die "USB device $ids not found"
- local busnr="$(printf '%s' "$bus_dev" | cut -f1 -d:)"
- local devnr="$(printf '%s' "$bus_dev" | cut -f2 -d:)"
- echo "Found USB device $ids on $busnr:$devnr"
- echo "Changing device permissions..."
- sudo chmod o+w "/dev/bus/usb/$busnr/$devnr" ||\
- die "Failed to set usb device permissions"
- }
- # $1="vendor:device"
- host_pci_find_by_ids()
- {
- local ids="$1"
- lspci -vn | grep -e "$ids" | awk '{print $1;}'
- }
- # $1="00:00.0"
- host_pci_prepare()
- {
- local dev="$1"
- echo "Assigning PCI device $dev ..."
- # Find the vendor and device IDs
- local lspci_string="$(lspci -mmn | grep -e "^$dev" | head -n1)"
- [ -n "$lspci_string" ] || die "PCI device $dev not found"
- dev="0000:$dev"
- local vendorid="$(echo "$lspci_string" | cut -d' ' -f 3 | tr -d \")"
- local deviceid="$(echo "$lspci_string" | cut -d' ' -f 4 | tr -d \")"
- echo "Found PCI device $dev with IDs $vendorid:$deviceid"
- # Find out which driver currently runs the device.
- local orig_drvdir="$(find /sys/bus/pci/drivers -type l -name "$dev")"
- orig_drvdir="$(dirname "$orig_drvdir")"
- if [ -n "$orig_drvdir" -a "$orig_drvdir" != "." ]; then
- echo "Original driver for $dev is '$orig_drvdir'"
- else
- echo "WARNING: Did not find attached kernel driver for PCI device $dev"
- orig_drvdir=
- fi
- # Register the device to VFIO
- modprobe vfio-pci || die "Failed to load 'vfio-pci' kernel module"
- echo "$vendorid $deviceid" > /sys/bus/pci/drivers/vfio-pci/new_id ||\
- die "Failed to register PCI-id to vfio-pci driver"
- if [ -n "$orig_drvdir" ]; then
- echo "$dev" > "$orig_drvdir/unbind" ||\
- die "Failed to unbind PCI kernel driver"
- fi
- echo "$dev" > /sys/bus/pci/drivers/vfio-pci/bind ||\
- die "Failed to bind vfio-pci kernel driver"
- echo "$vendorid $deviceid" > /sys/bus/pci/drivers/vfio-pci/remove_id ||\
- die "Failed to remove PCI-id from vfio-pci driver"
- # Remember the pci dev for cleanup
- if [ -n "$orig_drvdir" ]; then
- assigned_pci_devs="$assigned_pci_devs $dev/$orig_drvdir"
- fi
- }
- host_pci_restore_all()
- {
- for assigned_dev in $assigned_pci_devs; do
- local dev="$(echo "$assigned_dev" | cut -d'/' -f1)"
- local orig_drvdir="/$(echo "$assigned_dev" | cut -d '/' -f2-)"
- echo "Unbinding PCI device $dev from VFIO..."
- echo "$dev" > /sys/bus/pci/drivers/vfio-pci/unbind
- if [ -e "$orig_drvdir"/bind ]; then
- echo "Rebinding PCI device $dev to original driver '$orig_drvdir'..."
- echo "$dev" > "$orig_drvdir"/bind
- fi
- done
- }
- usage()
- {
- echo "qemu-script.sh [OPTIONS] [--] [QEMU-OPTIONS]"
- echo
- echo "Options:"
- echo " --dry-run Do not run qemu/spice. (But do (de)allocate ressources)"
- echo " -m|--ram RAM Amount of RAM. Default: 1024M"
- echo " -n|--net-restrict 1|0 Turn net restrict on/off. Default: 1"
- echo " -s|--spice 1|0 Use spice client. Default: 1"
- echo " -M|--mouse MOUSETYPE Select the mouse type:"
- echo " -M not specified: usbtablet"
- echo " default: Use qemu default"
- echo " usbmouse: Use USB mouse"
- echo " usbtablet: Use USB tablet"
- echo " -u|--usb-id ABCD:1234 Use host USB device with ID ABCD:1234"
- echo " -p|--pci-id ABCD:1234 Forward PCI device with ID ABCD:1234"
- echo " -P|--pci-device 00:00.0 Forward PCI device at 00:00.0"
- echo " -T|--tap Set up a tap to the default host bridge"
- echo " -S|--screens 1|2 Number of screens. Default: 1"
- echo " -j|--cores 1 Number of CPU cores. Default: 1"
- echo " -H|--host-cpu Host CPU feature passthrough (for nested virt)."
- echo " -F|--vvfat DIR Enable virtual VFAT drive."
- }
- # Global variables:
- # basedir, image, qemu_opts, rtc, qemu_binary, spice_host, spice_port,
- # opt_ram, opt_netrestrict, opt_...
- run()
- {
- [ -n "$basedir" ] || die "No basedir specified"
- [ -n "$image" ] || die "No image specified"
- # Canonicalize paths
- basedir="$(readlink -m "$basedir")"
- image="$(readlink -m "$image")"
- # Set variable-defaults
- [ -n "$image_format" ] || image_format="raw"
- [ -n "$qemu_binary" ] || qemu_binary="qemu-system-i386"
- [ -n "$spice_host" ] || spice_host="127.0.0.1"
- [ -n "$spice_port" ] || spice_port="$(random_port)"
- [ -n "$rtc" ] || rtc="-rtc base=localtime,clock=host"
- # Set option-defaults
- [ -n "$opt_nokvm" ] || opt_nokvm=0
- [ -n "$opt_ram" ] || opt_ram="1024M"
- [ -n "$opt_vga" ] || opt_vga="qxl"
- [ -n "$opt_netrestrict" ] || opt_netrestrict=1
- [ -n "$opt_netdevice" ] || opt_netdevice=rtl8139
- [ -n "$opt_dryrun" ] || opt_dryrun=0
- [ -n "$opt_spice" ] || opt_spice=1
- [ -n "$opt_mouse" ] || opt_mouse=usbtablet
- [ -n "$opt_usetap" ] || opt_usetap=0
- [ -n "$opt_screens" ] || opt_screens=1
- [ -n "$opt_cores" ] || opt_cores=1
- [ -n "$opt_hostcpu" ] || opt_hostcpu=0
- [ -n "$opt_vvfat" ] || opt_vvfat=
- # Variable defaults
- local spice_opt=
- local usbdevice_opt=
- local pcidevice_opt=
- local net0_conf=
- local net1_conf=
- local screen_opt=
- kvm_opt=
- serial_opt=
- # Basic initialization
- share_init
- # serial_init
- # Parse command line options
- local end=0
- while [ $# -gt 0 -a $end -eq 0 ]; do
- case "$1" in
- -h|--help)
- usage
- exit 0
- ;;
- -V|--vga)
- shift
- opt_vga="$1"
- ;;
- -K|--nokvm)
- opt_nokvm=1
- ;;
- -m|--ram)
- shift
- opt_ram="$1"
- ;;
- -n|--net-restrict)
- shift
- opt_netrestrict="$(bool_to_on_off "$1")"
- ;;
- -N|--net-device)
- shift
- opt_netdevice="$1"
- ;;
- --dry-run)
- opt_dryrun=1
- ;;
- -s|--spice)
- shift
- opt_spice="$(bool_to_1_0 "$1")"
- ;;
- -M|--mouse)
- shift
- opt_mouse="$1"
- ;;
- -u|--usb-id)
- shift
- local ids="$1"
- host_usb_id_prepare "$ids"
- local bus_dev="$(usb_vendor_to_dev "$ids")"
- [ -n "$bus_dev" ] || die "USB device $ids not found"
- local busnr="$(printf '%s' "$bus_dev" | cut -f1 -d: | sed 's/^[0]*//')"
- local devnr="$(printf '%s' "$bus_dev" | cut -f2 -d: | sed 's/^[0]*//')"
- usbdevice_opt="$usbdevice_opt -device usb-host,hostbus=$busnr,hostaddr=$devnr"
- ;;
- -p|--pci-id)
- shift
- local ids="$1"
- local dev="$(host_pci_find_by_ids "$ids")"
- [ -n "$dev" ] || die "Did not find PCI device with IDs '$ids'"
- host_pci_prepare "$dev"
- pcidevice_opt="$pcidevice_opt -device vfio-pci,host=$dev"
- ;;
- -P|--pci-device)
- shift
- local dev="$1"
- host_pci_prepare "$dev"
- pcidevice_opt="$pcidevice_opt -device vfio-pci,host=$dev"
- ;;
- -T|--tap)
- opt_usetap=1
- ;;
- -S|--screens)
- shift
- opt_screens="$1"
- ;;
- -j|--cores)
- shift
- opt_cores="$1"
- ;;
- -H|--host-cpu)
- opt_hostcpu=1
- ;;
- -F|--vvfat)
- shift
- opt_vvfat="$1"
- ;;
- --)
- end=1
- ;;
- *)
- die "Unknown option: $1"
- ;;
- esac
- shift
- done
- [ $opt_nokvm -eq 0 ] && kvm_init
- [ $opt_spice -ne 0 ] && {
- spice_opt="-spice addr=${spice_host},port=${spice_port},"
- spice_opt="${spice_opt}disable-ticketing=on,"
- spice_opt="${spice_opt}agent-mouse=off,"
- spice_opt="${spice_opt}disable-copy-paste=on,"
- spice_opt="${spice_opt}seamless-migration=on,"
- spice_opt="${spice_opt}plaintext-channel=main,plaintext-channel=display,"
- spice_opt="${spice_opt}plaintext-channel=cursor,plaintext-channel=inputs,"
- spice_opt="${spice_opt}plaintext-channel=record,plaintext-channel=playback"
- }
- if [ "$opt_mouse" = "default" ]; then
- true # do nothing
- elif [ "$opt_mouse" = "usbtablet" ]; then
- usbdevice_opt="$usbdevice_opt -device usb-tablet"
- elif [ "$opt_mouse" = "usbmouse" ]; then
- usbdevice_opt="$usbdevice_opt -device usb-mouse"
- else
- die "Invalid mouse selection"
- fi
- net0_conf="-netdev user,id=net0,restrict=$(bool_to_on_off "$opt_netrestrict"),net=192.168.5.1/24,smb=${sharedir},smbserver=192.168.5.4"
- net0_conf="$net0_conf -device $opt_netdevice,netdev=net0,mac=00:11:22:AA:BB:CC"
- if [ "$opt_usetap" -ne 0 ]; then
- net1_conf="-netdev tap,id=net1"
- net1_conf="$net1_conf -device $opt_netdevice,netdev=net1,mac=00:11:22:AA:BB:CD"
- fi
- local vga_opt="-vga $opt_vga"
- if [ "$opt_screens" = "1" ]; then
- local screen_opt=
- elif [ "$opt_screens" = "2" ]; then
- local screen_opt="-device qxl"
- else
- die "Invalid screen selection"
- fi
- if [ "$opt_hostcpu" = "1" ]; then
- local cpu_opt="-cpu host"
- else
- local cpu_opt=
- fi
- if [ "$opt_cores" = "1" ]; then
- local smp_opt=
- else
- local smp_opt="-smp cores=$opt_cores"
- fi
- if [ -n "$opt_vvfat" ]; then
- local vvfat_opt="-drive file=fat:rw:$opt_vvfat,format=raw"
- else
- local vvfat_opt=
- fi
- run_qemu \
- -name "$(basename "$image")" \
- $kvm_opt \
- $cpu_opt \
- $spice_opt \
- -m "$opt_ram" \
- -drive file="$image",index=0,format="$image_format",discard=on,media=disk \
- -boot c \
- $net0_conf \
- $net1_conf \
- $vvfat_opt \
- -usb \
- $usbdevice_opt \
- $serial_opt \
- $pcidevice_opt \
- $vga_opt \
- $screen_opt \
- $rtc \
- $smp_opt \
- $qemu_opts \
- "$@"
- run_spice_client
- host_pci_restore_all
- }
|