qemu-lib.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. # QEMU support lib
  2. die()
  3. {
  4. echo "$*" >&2
  5. exit 1
  6. }
  7. # $1=text
  8. tolower()
  9. {
  10. local str="$1"
  11. echo -n "$str" | tr '[:upper:]' '[:lower:]'
  12. }
  13. # $1=value
  14. parse_bool()
  15. {
  16. local str="$1"
  17. str="$(tolower "$str")"
  18. ! [ "$str" = "0" -o \
  19. "$str" = "off" -o \
  20. "$str" = "false" -o \
  21. "$str" = "no" ]
  22. }
  23. # $1=value
  24. bool_to_1_0()
  25. {
  26. parse_bool "$1" && echo -n 1 || echo -n 0
  27. }
  28. # $1=value
  29. bool_to_on_off()
  30. {
  31. parse_bool "$1" && echo -n on || echo -n off
  32. }
  33. get_ports_db_file()
  34. {
  35. local dbfile="/tmp/qemu-lib-ports.db"
  36. touch "$dbfile"
  37. chmod 666 "$dbfile"
  38. echo "$dbfile"
  39. }
  40. random_port()
  41. {
  42. local dbfile="$(get_ports_db_file)"
  43. local port=
  44. while true; do
  45. port="$(expr "$(hexdump -n2 -e'/2 "%u"' /dev/urandom)" '%' 16384 '+' 1024)"
  46. grep -qEe "^${port}\$" "$dbfile" || break
  47. done
  48. echo "$port" >> "$dbfile"
  49. echo "$port"
  50. }
  51. # $1=portnumber
  52. release_port()
  53. {
  54. local port="$1"
  55. local dbfile="$(get_ports_db_file)"
  56. sed -ie '/^'"$port"'$/d' "$dbfile"
  57. }
  58. run_qemu()
  59. {
  60. local bin="$qemu_binary"
  61. echo "Running QEMU..."
  62. "$bin" --version
  63. echo ""
  64. echo "$bin $*"
  65. [ $opt_dryrun -eq 0 ] || return
  66. if [ $opt_spice -eq 0 ]; then
  67. exec "$bin" "$@"
  68. else
  69. "$bin" "$@" &
  70. qemu_pid=$!
  71. echo "Forked qemu (pid ${qemu_pid})"
  72. fi
  73. }
  74. run_spice_client()
  75. {
  76. [ $opt_spice -eq 0 ] && return
  77. echo "Running spice client on ${spice_host}:${spice_port}..."
  78. [ $opt_dryrun -eq 0 ] && {
  79. sleep 1
  80. spicy -h "$spice_host" -p "$spice_port"
  81. echo "Killing qemu..."
  82. kill "$qemu_pid"
  83. wait
  84. }
  85. release_port "$spice_port"
  86. }
  87. share_init()
  88. {
  89. sharedir="$basedir/share"
  90. mkdir -p "$sharedir" || die "Failed to create $sharedir"
  91. }
  92. serial_init()
  93. {
  94. local serialdir="$basedir/serial"
  95. local from=0
  96. local to=0
  97. mkdir -p "$serialdir" || die "Failed to create $serialdir"
  98. for i in $(seq $from $to); do
  99. [ -p "$serialdir/$i" ] || {
  100. mkfifo "$serialdir/$i" || die "Failed to create fifo $i"
  101. }
  102. serial_opt="$serial_opt -serial pipe:$serialdir/0"
  103. done
  104. }
  105. kvm_init()
  106. {
  107. modprobe kvm >/dev/null 2>&1
  108. modprobe kvm-amd >/dev/null 2>&1
  109. modprobe kvm-intel >/dev/null 2>&1
  110. if [ -w /dev/kvm ]; then
  111. kvm_opt="-enable-kvm"
  112. else
  113. echo "===> WARNING: /dev/kvm not writable"
  114. fi
  115. }
  116. # Convert vendor:device to bus:dev
  117. usb_vendor_to_dev()
  118. {
  119. local ids="$1"
  120. local lsusb_string="$(lsusb | grep -e "$ids" | head -n1)"
  121. if [ -n "$lsusb_string" ]; then
  122. local busnr="$(echo "$lsusb_string" | awk '{print $2;}')"
  123. local devnr="$(echo "$lsusb_string" | awk '{print $4;}' | cut -d':' -f1)"
  124. printf '%s:%s' "$busnr" "$devnr"
  125. fi
  126. }
  127. # $1="vendor:device"
  128. host_usb_id_prepare()
  129. {
  130. local ids="$1"
  131. local bus_dev="$(usb_vendor_to_dev "$ids")"
  132. [ -n "$bus_dev" ] || die "USB device $ids not found"
  133. local busnr="$(printf '%s' "$bus_dev" | cut -f1 -d:)"
  134. local devnr="$(printf '%s' "$bus_dev" | cut -f2 -d:)"
  135. echo "Found USB device $ids on $busnr:$devnr"
  136. echo "Changing device permissions..."
  137. sudo chmod o+w "/dev/bus/usb/$busnr/$devnr" ||\
  138. die "Failed to set usb device permissions"
  139. }
  140. # $1="vendor:device"
  141. host_pci_find_by_ids()
  142. {
  143. local ids="$1"
  144. lspci -vn | grep -e "$ids" | awk '{print $1;}'
  145. }
  146. # $1="00:00.0"
  147. host_pci_prepare()
  148. {
  149. local dev="$1"
  150. echo "Assigning PCI device $dev ..."
  151. # Find the vendor and device IDs
  152. local lspci_string="$(lspci -mmn | grep -e "^$dev" | head -n1)"
  153. [ -n "$lspci_string" ] || die "PCI device $dev not found"
  154. dev="0000:$dev"
  155. local vendorid="$(echo "$lspci_string" | cut -d' ' -f 3 | tr -d \")"
  156. local deviceid="$(echo "$lspci_string" | cut -d' ' -f 4 | tr -d \")"
  157. echo "Found PCI device $dev with IDs $vendorid:$deviceid"
  158. # Find out which driver currently runs the device.
  159. local orig_drvdir="$(find /sys/bus/pci/drivers -type l -name "$dev")"
  160. orig_drvdir="$(dirname "$orig_drvdir")"
  161. if [ -n "$orig_drvdir" -a "$orig_drvdir" != "." ]; then
  162. echo "Original driver for $dev is '$orig_drvdir'"
  163. else
  164. echo "WARNING: Did not find attached kernel driver for PCI device $dev"
  165. orig_drvdir=
  166. fi
  167. # Register the device to VFIO
  168. modprobe vfio-pci || die "Failed to load 'vfio-pci' kernel module"
  169. echo "$vendorid $deviceid" > /sys/bus/pci/drivers/vfio-pci/new_id ||\
  170. die "Failed to register PCI-id to vfio-pci driver"
  171. if [ -n "$orig_drvdir" ]; then
  172. echo "$dev" > "$orig_drvdir/unbind" ||\
  173. die "Failed to unbind PCI kernel driver"
  174. fi
  175. echo "$dev" > /sys/bus/pci/drivers/vfio-pci/bind ||\
  176. die "Failed to bind vfio-pci kernel driver"
  177. echo "$vendorid $deviceid" > /sys/bus/pci/drivers/vfio-pci/remove_id ||\
  178. die "Failed to remove PCI-id from vfio-pci driver"
  179. # Remember the pci dev for cleanup
  180. if [ -n "$orig_drvdir" ]; then
  181. assigned_pci_devs="$assigned_pci_devs $dev/$orig_drvdir"
  182. fi
  183. }
  184. host_pci_restore_all()
  185. {
  186. for assigned_dev in $assigned_pci_devs; do
  187. local dev="$(echo "$assigned_dev" | cut -d'/' -f1)"
  188. local orig_drvdir="/$(echo "$assigned_dev" | cut -d '/' -f2-)"
  189. echo "Unbinding PCI device $dev from VFIO..."
  190. echo "$dev" > /sys/bus/pci/drivers/vfio-pci/unbind
  191. if [ -e "$orig_drvdir"/bind ]; then
  192. echo "Rebinding PCI device $dev to original driver '$orig_drvdir'..."
  193. echo "$dev" > "$orig_drvdir"/bind
  194. fi
  195. done
  196. }
  197. usage()
  198. {
  199. echo "qemu-script.sh [OPTIONS] [--] [QEMU-OPTIONS]"
  200. echo
  201. echo "Options:"
  202. echo " --dry-run Do not run qemu/spice. (But do (de)allocate ressources)"
  203. echo " -m|--ram RAM Amount of RAM. Default: 1024M"
  204. echo " -n|--net-restrict 1|0 Turn net restrict on/off. Default: 1"
  205. echo " -s|--spice 1|0 Use spice client. Default: 1"
  206. echo " -M|--mouse MOUSETYPE Select the mouse type:"
  207. echo " -M not specified: usbtablet"
  208. echo " default: Use qemu default"
  209. echo " usbmouse: Use USB mouse"
  210. echo " usbtablet: Use USB tablet"
  211. echo " -u|--usb-id ABCD:1234 Use host USB device with ID ABCD:1234"
  212. echo " -p|--pci-id ABCD:1234 Forward PCI device with ID ABCD:1234"
  213. echo " -P|--pci-device 00:00.0 Forward PCI device at 00:00.0"
  214. echo " -T|--tap Set up a tap to the default host bridge"
  215. echo " -S|--screens 1|2 Number of screens. Default: 1"
  216. echo " -j|--cores 1 Number of CPU cores. Default: 1"
  217. echo " -H|--host-cpu Host CPU feature passthrough (for nested virt)."
  218. echo " -F|--vvfat DIR Enable virtual VFAT drive."
  219. }
  220. # Global variables:
  221. # basedir, image, qemu_opts, rtc, qemu_binary, spice_host, spice_port,
  222. # opt_ram, opt_netrestrict, opt_...
  223. run()
  224. {
  225. [ -n "$basedir" ] || die "No basedir specified"
  226. [ -n "$image" ] || die "No image specified"
  227. # Canonicalize paths
  228. basedir="$(readlink -m "$basedir")"
  229. image="$(readlink -m "$image")"
  230. # Set variable-defaults
  231. [ -n "$image_format" ] || image_format="raw"
  232. [ -n "$qemu_binary" ] || qemu_binary="qemu-system-i386"
  233. [ -n "$spice_host" ] || spice_host="127.0.0.1"
  234. [ -n "$spice_port" ] || spice_port="$(random_port)"
  235. [ -n "$rtc" ] || rtc="-rtc base=localtime,clock=host"
  236. # Set option-defaults
  237. [ -n "$opt_nokvm" ] || opt_nokvm=0
  238. [ -n "$opt_ram" ] || opt_ram="1024M"
  239. [ -n "$opt_vga" ] || opt_vga="qxl"
  240. [ -n "$opt_netrestrict" ] || opt_netrestrict=1
  241. [ -n "$opt_netdevice" ] || opt_netdevice=rtl8139
  242. [ -n "$opt_dryrun" ] || opt_dryrun=0
  243. [ -n "$opt_spice" ] || opt_spice=1
  244. [ -n "$opt_mouse" ] || opt_mouse=usbtablet
  245. [ -n "$opt_usetap" ] || opt_usetap=0
  246. [ -n "$opt_screens" ] || opt_screens=1
  247. [ -n "$opt_cores" ] || opt_cores=1
  248. [ -n "$opt_hostcpu" ] || opt_hostcpu=0
  249. [ -n "$opt_vvfat" ] || opt_vvfat=
  250. # Variable defaults
  251. local spice_opt=
  252. local usbdevice_opt=
  253. local pcidevice_opt=
  254. local net0_conf=
  255. local net1_conf=
  256. local screen_opt=
  257. kvm_opt=
  258. serial_opt=
  259. # Basic initialization
  260. share_init
  261. # serial_init
  262. # Parse command line options
  263. local end=0
  264. while [ $# -gt 0 -a $end -eq 0 ]; do
  265. case "$1" in
  266. -h|--help)
  267. usage
  268. exit 0
  269. ;;
  270. -V|--vga)
  271. shift
  272. opt_vga="$1"
  273. ;;
  274. -K|--nokvm)
  275. opt_nokvm=1
  276. ;;
  277. -m|--ram)
  278. shift
  279. opt_ram="$1"
  280. ;;
  281. -n|--net-restrict)
  282. shift
  283. opt_netrestrict="$(bool_to_on_off "$1")"
  284. ;;
  285. -N|--net-device)
  286. shift
  287. opt_netdevice="$1"
  288. ;;
  289. --dry-run)
  290. opt_dryrun=1
  291. ;;
  292. -s|--spice)
  293. shift
  294. opt_spice="$(bool_to_1_0 "$1")"
  295. ;;
  296. -M|--mouse)
  297. shift
  298. opt_mouse="$1"
  299. ;;
  300. -u|--usb-id)
  301. shift
  302. local ids="$1"
  303. host_usb_id_prepare "$ids"
  304. local bus_dev="$(usb_vendor_to_dev "$ids")"
  305. [ -n "$bus_dev" ] || die "USB device $ids not found"
  306. local busnr="$(printf '%s' "$bus_dev" | cut -f1 -d: | sed 's/^[0]*//')"
  307. local devnr="$(printf '%s' "$bus_dev" | cut -f2 -d: | sed 's/^[0]*//')"
  308. usbdevice_opt="$usbdevice_opt -device usb-host,hostbus=$busnr,hostaddr=$devnr"
  309. ;;
  310. -p|--pci-id)
  311. shift
  312. local ids="$1"
  313. local dev="$(host_pci_find_by_ids "$ids")"
  314. [ -n "$dev" ] || die "Did not find PCI device with IDs '$ids'"
  315. host_pci_prepare "$dev"
  316. pcidevice_opt="$pcidevice_opt -device vfio-pci,host=$dev"
  317. ;;
  318. -P|--pci-device)
  319. shift
  320. local dev="$1"
  321. host_pci_prepare "$dev"
  322. pcidevice_opt="$pcidevice_opt -device vfio-pci,host=$dev"
  323. ;;
  324. -T|--tap)
  325. opt_usetap=1
  326. ;;
  327. -S|--screens)
  328. shift
  329. opt_screens="$1"
  330. ;;
  331. -j|--cores)
  332. shift
  333. opt_cores="$1"
  334. ;;
  335. -H|--host-cpu)
  336. opt_hostcpu=1
  337. ;;
  338. -F|--vvfat)
  339. shift
  340. opt_vvfat="$1"
  341. ;;
  342. --)
  343. end=1
  344. ;;
  345. *)
  346. die "Unknown option: $1"
  347. ;;
  348. esac
  349. shift
  350. done
  351. [ $opt_nokvm -eq 0 ] && kvm_init
  352. [ $opt_spice -ne 0 ] && {
  353. spice_opt="-spice addr=${spice_host},port=${spice_port},"
  354. spice_opt="${spice_opt}disable-ticketing=on,"
  355. spice_opt="${spice_opt}agent-mouse=off,"
  356. spice_opt="${spice_opt}disable-copy-paste=on,"
  357. spice_opt="${spice_opt}seamless-migration=on,"
  358. spice_opt="${spice_opt}plaintext-channel=main,plaintext-channel=display,"
  359. spice_opt="${spice_opt}plaintext-channel=cursor,plaintext-channel=inputs,"
  360. spice_opt="${spice_opt}plaintext-channel=record,plaintext-channel=playback"
  361. }
  362. if [ "$opt_mouse" = "default" ]; then
  363. true # do nothing
  364. elif [ "$opt_mouse" = "usbtablet" ]; then
  365. usbdevice_opt="$usbdevice_opt -device usb-tablet"
  366. elif [ "$opt_mouse" = "usbmouse" ]; then
  367. usbdevice_opt="$usbdevice_opt -device usb-mouse"
  368. else
  369. die "Invalid mouse selection"
  370. fi
  371. 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"
  372. net0_conf="$net0_conf -device $opt_netdevice,netdev=net0,mac=00:11:22:AA:BB:CC"
  373. if [ "$opt_usetap" -ne 0 ]; then
  374. net1_conf="-netdev tap,id=net1"
  375. net1_conf="$net1_conf -device $opt_netdevice,netdev=net1,mac=00:11:22:AA:BB:CD"
  376. fi
  377. local vga_opt="-vga $opt_vga"
  378. if [ "$opt_screens" = "1" ]; then
  379. local screen_opt=
  380. elif [ "$opt_screens" = "2" ]; then
  381. local screen_opt="-device qxl"
  382. else
  383. die "Invalid screen selection"
  384. fi
  385. if [ "$opt_hostcpu" = "1" ]; then
  386. local cpu_opt="-cpu host"
  387. else
  388. local cpu_opt=
  389. fi
  390. if [ "$opt_cores" = "1" ]; then
  391. local smp_opt=
  392. else
  393. local smp_opt="-smp cores=$opt_cores"
  394. fi
  395. if [ -n "$opt_vvfat" ]; then
  396. local vvfat_opt="-drive file=fat:rw:$opt_vvfat,format=raw"
  397. else
  398. local vvfat_opt=
  399. fi
  400. run_qemu \
  401. -name "$(basename "$image")" \
  402. $kvm_opt \
  403. $cpu_opt \
  404. $spice_opt \
  405. -m "$opt_ram" \
  406. -drive file="$image",index=0,format="$image_format",discard=on,media=disk \
  407. -boot c \
  408. $net0_conf \
  409. $net1_conf \
  410. $vvfat_opt \
  411. -usb \
  412. $usbdevice_opt \
  413. $serial_opt \
  414. $pcidevice_opt \
  415. $vga_opt \
  416. $screen_opt \
  417. $rtc \
  418. $smp_opt \
  419. $qemu_opts \
  420. "$@"
  421. run_spice_client
  422. host_pci_restore_all
  423. }