(floor($intDisplaySize) == $intDisplaySize) && isset($arrSizePrefix[$intShifts])) { $arrResponse['display-size'] = $intDisplaySize . $arrSizePrefix[$intShifts]; $intDisplaySize /= 1024; $intShifts++; } $arrResponse['virtual-size'] = $json_info['virtual-size']; $arrResponse['actual-size'] = $json_info['actual-size']; $arrResponse['format'] = $json_info['format']; $arrResponse['dirty-flag'] = $json_info['dirty-flag']; $arrResponse['resizable'] = true; } } else if (is_block($file)) { $strDevSize = trim(shell_exec("blockdev --getsize64 " . escapeshellarg($file))); if (!empty($strDevSize) && is_numeric($strDevSize)) { $arrResponse['actual-size'] = (int)$strDevSize; $arrResponse['format'] = 'raw'; $intDisplaySize = (int)$strDevSize; $intShifts = 0; while (!empty($intDisplaySize) && ($intDisplaySize >= 2) && isset($arrSizePrefix[$intShifts])) { $arrResponse['display-size'] = round($intDisplaySize, 0) . $arrSizePrefix[$intShifts]; $intDisplaySize /= 1000; // 1000 looks better than 1024 for block devs $intShifts++;
function config_to_xml($config) { $domain = $config['domain']; $media = $config['media']; $nics = $config['nic']; $disks = $config['disk']; $usb = $config['usb']; $shares = $config['shares']; $gpus = $config['gpu']; $audios = $config['audio']; $template = $config['template']; $type = $domain['type']; $name = $domain['name']; $mem = $domain['mem']; $maxmem = $mem; if (!empty($domain['maxmem'])) { $maxmem = $domain['maxmem']; } $uuid = (!empty($domain['uuid']) ? $domain['uuid'] : $this->domain_generate_uuid()); $machine = $domain['machine']; $machine_type = (stripos($machine, 'q35') !== false ? 'q35' : 'pc'); $os_type = ((empty($template['os']) || stripos($template['os'], 'windows') === false) ? 'other' : 'windows'); $emulator = $this->get_default_emulator(); $arch = $domain['arch']; $pae = ''; if ($arch == 'i686'){ $pae = '<pae/>'; } $loader = ''; if (!empty($domain['ovmf'])) { $loader = "<loader type='pflash'>/usr/share/qemu/ovmf-x64/OVMF-pure-efi.fd</loader>"; } $metadata = ''; if (!empty($template)) { $template_options = ''; foreach ($template as $key => $value) { $template_options .= $key . "='" . htmlspecialchars($value, ENT_QUOTES | ENT_XML1) . "' "; } $metadata = "<metadata><vmtemplate " . $template_options . "/></metadata>"; } $vcpus = 1; $vcpupinstr = ''; if (!empty($domain['vcpu']) && is_array($domain['vcpu'])) { $vcpus = count($domain['vcpu']); foreach($domain['vcpu'] as $i => $vcpu) { $vcpupinstr .= "<vcpupin vcpu='$i' cpuset='$vcpu'/>"; } } else if (!empty($domain['vcpus'])) { $vcpus = $domain['vcpus']; for ($i=0; $i < $vcpus; $i++) { $vcpupinstr .= "<vcpupin vcpu='$i' cpuset='$i'/>"; } } $cpumode = ''; if (!empty($domain['cpumode']) && $domain['cpumode'] == 'host-passthrough') { $cpumode .= "mode='host-passthrough'"; } $cpustr = "<cpu $cpumode> <topology sockets='1' cores='{$vcpus}' threads='1'/> </cpu> <vcpu placement='static'>{$vcpus}</vcpu> <cputune> $vcpupinstr </cputune>"; $bus = "ide"; $ctrl = ''; if ($machine_type == 'q35'){ $bus = "sata"; $ctrl = "<controller type='usb' index='0' model='ich9-ehci1'/> <controller type='usb' index='0' model='ich9-uhci1'/>"; } // OVMF needs the bus set to ide for cdroms if (!empty($domain['ovmf'])) { $bus = "ide"; } $clock = "<clock offset='" . $domain['clock'] . "'> <timer name='rtc' tickpolicy='catchup'/> <timer name='pit' tickpolicy='delay'/> <timer name='hpet' present='no'/> </clock>"; $hyperv = ''; if (!empty($domain['hyperv']) && $os_type == "windows") { $hyperv = "<hyperv> <relaxed state='on'/> <vapic state='on'/> <spinlocks state='on' retries='8191'/> </hyperv>"; $clock = "<clock offset='" . $domain['clock'] . "'> <timer name='hypervclock' present='yes'/> <timer name='hpet' present='no'/> </clock>"; } $usbstr = ''; if (!empty($usb)) { foreach($usb as $i => $v){ $usbx = explode(':', $v); $usbstr .= "<hostdev mode='subsystem' type='usb' managed='yes'> <source> <vendor id='0x".$usbx[0]."'/> <product id='0x".$usbx[1]."'/> </source> </hostdev>"; } } $arrAvailableDevs = []; foreach (range('a', 'z') as $letter) { $arrAvailableDevs['hd' . $letter] = 'hd' . $letter; } $arrUsedBootOrders = []; //media settings $mediastr = ''; if (!empty($media['cdrom'])) { unset($arrAvailableDevs['hda']); $arrUsedBootOrders[] = 2; $mediastr = "<disk type='file' device='cdrom'> <driver name='qemu'/> <source file='" . htmlspecialchars($media['cdrom'], ENT_QUOTES | ENT_XML1) . "'/> <target dev='hda' bus='$bus'/> <readonly/> <boot order='2'/> </disk>"; } $driverstr = ''; if (!empty($media['drivers']) && $os_type == "windows") { unset($arrAvailableDevs['hdb']); $driverstr = "<disk type='file' device='cdrom'> <driver name='qemu'/> <source file='" . htmlspecialchars($media['drivers'], ENT_QUOTES | ENT_XML1) . "'/> <target dev='hdb' bus='$bus'/> <readonly/> </disk>"; } //disk settings $diskstr = ''; $diskcount = 0; if (!empty($disks)) { foreach ($disks as $i => $disk) { if (!empty($disk['image']) | !empty($disk['new']) ) { //TODO: check if image/new is a block device $diskcount++; if (!empty($disk['new'])) { if (is_file($disk['new']) || is_block($disk['new'])) { $disk['image'] = $disk['new']; } } if (!empty($disk['image'])) { if (empty($disk['driver'])) { $disk['driver'] = 'raw'; if (is_file($disk['image'])) { $json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($disk['image'])), true); $disk['driver'] = $json_info['format']; } } } else { if (empty($disk['driver'])) { $disk['driver'] = 'raw'; } $strImgFolder = $disk['new']; $strImgPath = ''; $path_parts = pathinfo($strImgFolder); if (empty($path_parts['extension'])) { // 'new' is a folder if (substr($strImgFolder, -1) != '/') { $strImgFolder .= '/'; } if (is_dir($strImgFolder)) { // 'new' is a folder and already exists, append domain name as child folder $strImgFolder .= preg_replace('((^\.)|\/|(\.$))', '_', $domain['name']) . '/'; } $strExt = ($disk['driver'] == 'raw') ? 'img' : $disk['driver']; $strImgPath = $strImgFolder . 'vdisk' . $diskcount . '.' . $strExt; } else { // 'new' is a file $strImgPath = $strImgFolder; } if (is_file($strImgPath)) { $json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($strImgPath)), true); $disk['driver'] = $json_info['format']; } $arrReturn = [ 'image' => $strImgPath, 'driver' => $disk['driver'] ]; if (!empty($disk['dev'])) { $arrReturn['dev'] = $disk['dev']; } if (!empty($disk['bus'])) { $arrReturn['bus'] = $disk['bus']; } $disk = $arrReturn; } if (empty($disk['bus'])) { $disk['bus'] = 'virtio'; } if (empty($disk['dev']) || !in_array($disk['dev'], $arrAvailableDevs)) { $disk['dev'] = array_shift($arrAvailableDevs); } unset($arrAvailableDevs[$disk['dev']]); $bootorder = ''; if (!in_array(1, $arrUsedBootOrders)) { $bootorder = "<boot order='1'/>"; $arrUsedBootOrders[] = 1; } $readonly = ''; if (!empty($disk['readonly'])) { $readonly = '<readonly/>'; } $strDevType = @filetype(realpath($disk['image'])); if ($strDevType == 'file' || $strDevType == 'block') { $strSourceType = ($strDevType == 'file' ? 'file' : 'dev'); $diskstr .= "<disk type='" . $strDevType . "' device='disk'> <driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'/> <source " . $strSourceType . "='" . htmlspecialchars($disk['image'], ENT_QUOTES | ENT_XML1) . "'/> <target bus='" . $disk['bus'] . "' dev='" . $disk['dev'] . "'/> $bootorder $readonly </disk>"; } } } } $netstr = ''; if (!empty($nics)) { foreach ($nics as $i => $nic) { if (empty($nic['mac']) || empty($nic['network'])) { continue; } $netstr .= "<interface type='bridge'> <mac address='{$nic['mac']}'/> <source bridge='" . htmlspecialchars($nic['network'], ENT_QUOTES | ENT_XML1) . "'/> <model type='virtio'/> </interface>"; } } $sharestr = ''; if (!empty($shares) && $os_type != "windows") { foreach ($shares as $i => $share) { if (empty($share['source']) || empty($share['target'])) { continue; } $sharestr .= "<filesystem type='mount' accessmode='passthrough'> <source dir='" . htmlspecialchars($share['source'], ENT_QUOTES | ENT_XML1) . "'/> <target dir='" . htmlspecialchars($share['target'], ENT_QUOTES | ENT_XML1) . "'/> </filesystem>"; } } $passwdstr = ''; if (!empty($domain['password'])){ $passwdstr = "passwd='" . htmlspecialchars($domain['password'], ENT_QUOTES | ENT_XML1) . "'"; } $pcidevs=''; $gpuargs=''; $gpudevs=[]; $gpuargsdevs=[]; $gpuincr=0; $vnc=''; if (!empty($gpus)) { foreach ($gpus as $i => $gpu) { if (empty($gpu['id'])) { continue; } // Skip duplicate video devices if (in_array($gpu['id'], $gpudevs)) { continue; } if ($gpu['id'] == 'vnc') { $strKeyMap = ''; if (!empty($gpu['keymap'])) { $strKeyMap = "keymap='" . $gpu['keymap'] . "'"; } $vnc = "<input type='tablet' bus='usb'/> <input type='mouse' bus='ps2'/> <input type='keyboard' bus='ps2'/> <graphics type='vnc' port='-1' autoport='yes' websocket='-1' listen='0.0.0.0' $passwdstr $strKeyMap> <listen type='address' address='0.0.0.0'/> </graphics>"; if (!empty($domain['ovmf'])) { // OVMF doesn't work with vmvga $vnc .= "<video> <model type='cirrus'/> </video>"; } else { // SeaBIOS is cool with vmvga $vnc .= "<video> <model type='vmvga'/> </video>"; } continue; } list($gpu_bus, $gpu_slot, $gpu_function) = explode(":", str_replace('.', ':', $gpu['id'])); if (!empty($domain['ovmf'])) { // OVMF passthrough uses the normal hostdev and libvirt can fully manage the device $pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'> <driver name='vfio'/> <source> <address domain='0x0000' bus='0x" . $gpu_bus . "' slot='0x" . $gpu_slot . "' function='0x" . $gpu_function . "'/> </source> </hostdev>"; } else { // VGA BIOS passthrough uses qemu args and we have to manage the device (libvirt wont attach/detach the device driver to vfio-pci) switch ($machine_type) { case 'q35': $gpuargs .= "<qemu:arg value='-device'/> <qemu:arg value='vfio-pci,host={$gpu_bus}:{$gpu_slot}.{$gpu_function},bus=pcie.0,multifunction=on" . ((empty($gpuargs) && empty($vnc)) ? ',x-vga=on' : '') . "'/>"; $gpuargsdevs[$gpu['id']] = ""; // no address needed break; case 'pc': $gpuargs .= "<qemu:arg value='-device'/> <qemu:arg value='vfio-pci,host={$gpu_bus}:{$gpu_slot}.{$gpu_function},bus=root.1,addr=0{$gpuincr}.0,multifunction=on" . ((empty($gpuargs) && empty($vnc)) ? ',x-vga=on' : '') . "'/>"; $gpuargsdevs[$gpu['id']] = "0{$gpuincr}.0"; break; } } $gpudevs[] = $gpu['id']; $gpuincr++; } } $audioargs=''; $audiodevs=[]; if (!empty($audios)) { foreach ($audios as $i => $audio) { if (empty($audio['id'])) { continue; } // Skip duplicate audio devices if (in_array($audio['id'], $audiodevs)) { continue; } list($audio_bus, $audio_slot, $audio_function) = explode(":", str_replace('.', ':', $audio['id'])); if (!empty($domain['ovmf']) || empty($gpuargsdevs)) { $pcidevs .= "<hostdev mode='subsystem' type='pci' managed='yes'> <driver name='vfio'/> <source> <address domain='0x0000' bus='0x" . $audio_bus . "' slot='0x" . $audio_slot . "' function='0x" . $audio_function . "'/> </source> </hostdev>"; } else { // VGA BIOS passthrough uses qemu args and we have to manage the device (libvirt wont attach/detach the device driver to vfio-pci) switch ($machine_type) { case 'q35': $audioargs .= "<qemu:arg value='-device'/> <qemu:arg value='vfio-pci,host={$audio_bus}:{$audio_slot}.{$audio_function},bus=pcie.0'/>"; break; case 'pc': // Look for video device and see if this sound device is a function of that video card $addr = ''; if (isset($gpuargsdevs[$audio_bus . ':' . $audio_slot . '.0'])) { $addr = str_replace('.0', '.' . $audio_function, $gpuargsdevs[$audio_bus . ':' . $audio_slot . '.0']); } else { $addr = "0" . $gpuincr++ . ".0"; } $audioargs .= "<qemu:arg value='-device'/> <qemu:arg value='vfio-pci,host={$audio_bus}:{$audio_slot}.{$audio_function},bus=root.1,addr=" . $addr . "'/>"; break; } } $audiodevs[] = $audio['id']; } } $cmdargs=''; if (!empty($gpuargs) || !empty($audioargs)) { switch ($machine_type) { case 'q35': $cmdargs .= "<qemu:commandline> <qemu:arg value='-device'/> <qemu:arg value='ioh3420,bus=pcie.0,addr=1c.0,multifunction=on,port=2,chassis=1,id=root.1'/> $gpuargs $audioargs </qemu:commandline>"; break; case 'pc': $cmdargs .= "<qemu:commandline> <qemu:arg value='-device'/> <qemu:arg value='ioh3420,bus=pci.0,addr=1c.0,multifunction=on,port=2,chassis=1,id=root.1'/> $gpuargs $audioargs </qemu:commandline>"; break; } } return "<domain type='$type' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> <uuid>$uuid</uuid> <name>$name</name> <description>" . htmlspecialchars($domain['desc'], ENT_QUOTES | ENT_XML1) . "</description> $metadata <currentMemory>$mem</currentMemory> <memory>$maxmem</memory> <memoryBacking> <nosharepages/> <locked/> </memoryBacking> $cpustr <os> $loader <type arch='$arch' machine='$machine'>hvm</type> </os> <features> <acpi/> <apic/> $hyperv $pae </features> $clock <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>restart</on_crash> <devices> <emulator>$emulator</emulator> $diskstr $mediastr $driverstr $ctrl $sharestr $netstr $vnc <console type='pty'/> $pcidevs $usbstr <channel type='unix'> <target type='virtio' name='org.qemu.guest_agent.0'/> </channel> <memballoon model='virtio'> <alias name='balloon0'/> </memballoon> </devices> $cmdargs </domain>"; }