set_logfile($debug); } if ($uri != false) { $this->enabled = $this->connect($uri, $login, $pwd); } } function __construct($uri=false, $login=false, $pwd=false, $debug=false) { if ($debug) { $this->set_logfile($debug); } if ($uri != false) { $this->enabled = $this->connect($uri, $login, $pwd); } } function _set_last_error() { $this->last_error = libvirt_get_last_error(); return false; } function enabled() { return $this->enabled; } function set_logfile($filename) { if (!libvirt_logfile_set($filename,10000)) return $this->_set_last_error(); return true; } function get_capabilities() { $tmp = libvirt_connect_get_capabilities($this->conn); return $tmp ?: $this->_set_last_error(); } function get_domain_capabilities($emulatorbin, $arch, $machine, $virttype, $xpath) { #@conn [resource]: resource for connection #@emulatorbin [string]: optional path to emulator #@arch [string]: optional domain architecture #@machine [string]: optional machine type #@virttype [string]: optional virtualization type #@flags [int] : extra flags; not used yet, so callers should always pass 0 #@xpath [string]: optional xPath query to be applied on the result #Returns: : domain capabilities XML from the connection or FALSE for error $tmp = libvirt_connect_get_domain_capabilities($this->conn, $emulatorbin, $arch, $machine, $virttype, 0, $xpath); return $tmp ?: $this->_set_last_error(); } function get_machine_types($arch='x86_64' /* or 'i686' */) { $tmp = libvirt_connect_get_machine_types($this->conn); if (!$tmp) return $this->_set_last_error(); if (empty($tmp[$arch])) return []; return $tmp[$arch]; } function get_default_emulator() { $tmp = libvirt_connect_get_capabilities($this->conn, '//capabilities/guest/arch/domain/emulator'); return $tmp ?: $this->_set_last_error(); } function set_folder_nodatacow($folder) { if (!is_dir($folder)) return false; $folder = transpose_user_path($folder); #@shell_exec("chattr +C -R ".escapeshellarg($folder)." &>/dev/null"); return true; } function create_disk_image($disk, $vmname='', $diskid=1) { $arrReturn = []; if (!empty($disk['size'])) { $disk['size'] = str_replace(["KB","MB","GB","TB","PB"], ["K","M","G","T","P"], strtoupper($disk['size'])); } if (empty($disk['driver'])) { $disk['driver'] = 'raw'; } // if new is a folder then // if existing then // create folder 'new/vmname' // create image file as new/vmname/vdisk[1-x].xxx // if doesn't exist then // create folder 'new' // create image file as new/vdisk[1-x].xxx // if new is a file then // if existing then // nothing to do // if doesn't exist then // create folder dirname('new') if needed // create image file as new --> if size is specified if (!empty($disk['new'])) { if (is_file($disk['new']) || is_block($disk['new'])) $disk['image'] = $disk['new']; } if (!empty($disk['image'])) { // Use existing disk image if (is_block($disk['image'])) { // Valid block device, return as-is return $disk; } if (is_file($disk['image'])) { $json_info = getDiskImageInfo($disk['image']); $disk['driver'] = $json_info['format']; if (!empty($disk['size'])) { //TODO: expand disk image if size param is larger } return $disk; } $disk['new'] = $disk['image']; } if (!empty($disk['new'])) { // Create new disk image $strImgFolder = $disk['new']; $strImgPath = ''; if (strpos($strImgFolder, '/dev/') === 0) { // ERROR invalid block device $arrReturn = [ 'error' => "Not a valid block device location '".$strImgFolder."'" ]; return $arrReturn; } if (empty($disk['size'])) { // ERROR invalid disk size $arrReturn = [ 'error' => "Please specify a disk size for '".$strImgFolder."'" ]; return $arrReturn; } $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 vmname folder $strImgFolder .= preg_replace('((^\.)|\/|(\.$))', '_', $vmname).'/'; } // create folder if needed if (!is_dir($strImgFolder)) { #mkdir($strImgFolder, 0777, true); my_mkdir($strImgFolder, 0777, true); #chown($strImgFolder, 'nobody'); #chgrp($strImgFolder, 'users'); } $this->set_folder_nodatacow($strImgFolder); $strExt = ($disk['driver'] == 'raw') ? 'img' : $disk['driver']; $strImgPath = $strImgFolder.'vdisk'.$diskid.'.'.$strExt; } else { // 'new' is a file // create parent folder if needed if (!is_dir($path_parts['dirname'])) { #mkdir($path_parts['dirname'], 0777, true); my_mkdir($path_parts['dirname'], 0777, true); #chown($path_parts['dirname'], 'nobody'); #chgrp($path_parts['dirname'], 'users'); } $this->set_folder_nodatacow($path_parts['dirname']); $strExt = ($disk['driver'] == 'raw') ? 'img' : $disk['driver']; $strImgPath = $path_parts['dirname'].'/vdisk'.$diskid.'.'.$strExt; } if (is_file($strImgPath)) { $json_info = getDiskImageInfo($strImgPath); $disk['driver'] = $json_info['format']; $return_value = 0; } else { $strImgRawLocationPath = $strImgPath; if (!empty($disk['storage']) && !empty($disk['select']) && $disk['select'] == 'auto' && $disk['storage'] != "default") $disk['select'] = $disk['storage']; if (!empty($disk['select']) && (!in_array($disk['select'], ['auto', 'manual'])) && (is_dir('/mnt/'.$disk['select']))) { // Force qemu disk creation to happen directly on either cache/disk1/disk2 ect based on dropdown selection $strImgRawLocationPath = str_replace('/mnt/user/', '/mnt/'.$disk['select'].'/', $strImgPath); // create folder if needed $strImgRawLocationParent = dirname($strImgRawLocationPath); if (!is_dir($strImgRawLocationParent)) { #mkdir($strImgRawLocationParent, 0777, true); my_mkdir($strImgRawLocationParent, 0777, true); #chown($strImgRawLocationParent, 'nobody'); #chgrp($strImgRawLocationParent, 'users'); } $this->set_folder_nodatacow($strImgRawLocationParent); } $strLastLine = exec("qemu-img create -q -f ".escapeshellarg($disk['driver'])." ".escapeshellarg($strImgRawLocationPath)." ".escapeshellarg($disk['size'])." 2>&1", $output, $return_value); if (is_file($strImgPath)) { chmod($strImgPath, 0777); chown($strImgPath, 'nobody'); chgrp($strImgPath, 'users'); } } if ($return_value != 0) { // ERROR during image creation, return message to user $arrReturn = [ 'error' => "Error creating disk image '".$strImgPath."': ".$strLastLine, 'error_output' => $output ]; } else { // Success! $arrReturn = [ 'image' => $strImgPath, 'driver' => $disk['driver'] ]; if (!empty($disk['dev'])) { $arrReturn['dev'] = $disk['dev']; } if (!empty($disk['bus'])) { $arrReturn['bus'] = $disk['bus']; } if (!empty($disk['boot'])) { $arrReturn['boot'] = $disk['boot']; } if (!empty($disk['rotation'])) { $arrReturn['rotation'] = $disk['rotation']; } if (!empty($disk['serial'])) { $arrReturn['serial'] = $disk['serial']; } if (!empty($disk['discard'])) { $arrReturn['discard'] = $disk['discard']; } } } return $arrReturn; } function config_to_xml($config, $vmclone=false) { $domain = $config['domain']; $media = $config['media']; $nics = $config['nic']; $disks = $config['disk']; $usb = $config['usb']; $usbopt = $config['usbopt']; $usbboot = $config['usbboot']; $shares = $config['shares']; $gpus = $config['gpu']; $pcis = $config['pci']; $pciboot = $config['pciboot']; $audios = $config['audio']; $template = $config['template']; $clocks = $config['clock']; $evdevs = $config['evdev']; $type = $domain['type']; $name = $domain['name']; $mem = $domain['mem']; $maxmem = (!empty($domain['maxmem'])) ? $domain['maxmem'] : $mem; $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(); $emulator = '/usr/local/sbin/qemu'; $arch = $domain['arch']; $pae = ($arch == 'i686') ? '' : ''; $loader = ''; $swtpm = ''; $osbootdev = ''; if (!empty($domain['ovmf'])) { if ($domain['ovmf'] == 1) { if (!is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) { // Create a new copy of OVMF VARS for this VM mkdir('/etc/libvirt/qemu/nvram/', 0777, true); copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd'); } if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) { // Delete OVMF-TPM VARS for this VM if found unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd'); } $loader = "/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd /etc/libvirt/qemu/nvram/".$uuid."_VARS-pure-efi.fd"; if ($domain['usbboot'] == 'Yes') $osbootdev = ""; } if ($domain['ovmf'] == 2) { if (!is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) { // Create a new copy of OVMF VARS for this VM mkdir('/etc/libvirt/qemu/nvram/', 0777, true); copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd'); } if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) { // Delete OVMF VARS for this VM if found unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd'); } $loader = "/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi-tpm.fd /etc/libvirt/qemu/nvram/".$uuid."_VARS-pure-efi-tpm.fd"; $swtpm = " "; if ($domain['usbboot'] == 'Yes') $osbootdev = ""; } } $metadata = ''; if (!empty($template)) { $metadata .= ""; $template_options = ''; foreach ($template as $key => $value) { $template_options .= $key."='".htmlspecialchars($value, ENT_QUOTES | ENT_XML1)."' "; } $metadata .= ""; $metadata .= ""; } $vcpus = $domain['vcpus']; $vcpupinstr = ''; if (!empty($domain['vcpu']) && is_array($domain['vcpu'])) { $vcpus = count($domain['vcpu']); foreach($domain['vcpu'] as $i => $vcpu) { $vcpupinstr .= ""; } } $intCores = $vcpus; $intThreads = 1; $intCPUThreadsPerCore = 1; $cpumode = ''; $cpucache = ''; $cpufeatures = ''; $cpumigrate = ''; $cpucheck = ''; $cpumatch = ''; $cpucustom = ''; $cpufallback = ''; if (!empty($domain['cpumode']) && $domain['cpumode'] == 'host-passthrough') { $cpumode .= "mode='host-passthrough'"; $cpucache = ""; // detect if the processor is hyperthreaded: $intCPUThreadsPerCore = max(intval(shell_exec('/usr/bin/lscpu | grep \'Thread(s) per core\' | awk \'{print $4}\'')), 1); // detect if the processor is AMD + multithreaded, and if so, enable topoext cpu feature if ($intCPUThreadsPerCore > 1) { $strCPUInfo = file_get_contents('/proc/cpuinfo'); if (strpos($strCPUInfo, 'AuthenticAMD') !== false) { $cpufeatures .= ""; } } // even amount of cores assigned and cpu is hyperthreaded: pass that info along to the cpu section below if ($intCPUThreadsPerCore > 1 && ($vcpus % $intCPUThreadsPerCore == 0)) { $intCores = $vcpus / $intCPUThreadsPerCore; $intThreads = $intCPUThreadsPerCore; } if (!empty($domain['cpumigrate'])) $cpumigrate = " migratable='".$domain['cpumigrate']."'"; } $cpupmemlmt =''; if ($domain['cpupmemlmt'] != "None") { $escaped_limit = htmlspecialchars($domain['cpupmemlmt'], ENT_QUOTES | ENT_XML1); if ($domain['cpumode'] == 'host-passthrough') $cpupmemlmt = ""; else $cpupmemlmt = ""; } # #Skylake-Client-noTSX-IBRS $cpustr = " $cpucache $cpupmemlmt $cpufeatures {$vcpus} $vcpupinstr "; $usbmode = 'usb3'; if (!empty($domain['usbmode'])) { $usbmode = $domain['usbmode']; } $ctrl = ''; switch ($usbmode) { case 'usb3': $ctrl = "
"; break; case 'usb3-qemu': $ctrl = "
"; break; case 'usb2': $ctrl = "
"; break; } /* if ($os_type == "windows") $hypervclock = ""; else $hypervclock = ""; $clock = " $hypervclock "; $hyperv = ''; if ($domain['hyperv'] == 1 && $os_type == "windows") { $hyperv = " "; $clock = " "; } */ $clock = ""; foreach ($clocks as $clockname => $clockvalues) { switch ($clockname){ case "rtc": if ($clockvalues['present'] == "yes") $clock .= ""; break; case "pit": if ($clockvalues['present'] == "yes") $clock .= ""; break; case "hpet": $clock .= ""; break; case "hypervclock": $clock .= ""; break; } } $hyperv = ""; if ($domain['hyperv'] == 1 && $os_type == "windows") { $hyperv = " "; if ($clocks['hypervclock']['present'] == "yes") { $hyperv .= ""; } $hyperv .=""; # $clock = " # # "; } $clock .= ""; $usbstr = ''; if (!empty($usb)) { foreach($usb as $i => $v){ if ($vmclone) $usbx = explode(':', $v['id']); else $usbx = explode(':', $v); $startupPolicy = ''; if (isset($usbopt[$v]) && !$vmclone ) { if (strpos($usbopt[$v], "#remove") == false) $startupPolicy = 'startupPolicy="optional"'; else $startupPolicy = ''; } if (isset($v["startupPolicy"]) && $vmclone ) { if ($v["startupPolicy"] == "optional" ) $startupPolicy = 'startupPolicy="optional"'; else $startupPolicy = ''; } $usbstr .= " "; if (!empty($usbboot[$v]) && !$vmclone ) { $usbstr .= ""; } if (isset($v["usbboot"]) && $vmclone ) { if ($v["usbboot"] != NULL) $usbstr .= ""; } $usbstr .= ""; } } $arrAvailableDevs = []; foreach (range('a', 'z') as $letter) { $arrAvailableDevs['hd'.$letter] = 'hd'.$letter; } $needSCSIController = false; //media settings $bus = "ide"; if ($machine_type == 'q35'){ $bus = "sata"; } $mediastr = ''; if (!empty($media['cdrom'])) { unset($arrAvailableDevs['hda']); $cdromboot = $media['cdromboot']; $media['cdrombus'] = $media['cdrombus'] ?: $bus; if ($media['cdrombus'] == 'scsi') { $needSCSIController = true; } if ($cdromboot > 0) { $mediaboot = ""; } $mediastr = " $mediaboot "; } $driverstr = ''; if (!empty($media['drivers']) && $os_type == "windows") { unset($arrAvailableDevs['hdb']); $media['driversbus'] = $media['driversbus'] ?: $bus; if ($media['driversbus'] == 'scsi') { $needSCSIController = true; } $driverstr = " "; } //disk settings $diskstr = ''; $diskcount = 0; if (!empty($disks)) { // force any hard drives to start with hdc, hdd, hde, etc unset($arrAvailableDevs['hda']); unset($arrAvailableDevs['hdb']); 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 = getDiskImageInfo($disk['image']); $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 = getDiskImageInfo($strImgPath); $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; } $disk['bus'] = $disk['bus'] ?: 'virtio'; if ($disk['bus'] == 'scsi') { $needSCSIController = true; } if (empty($disk['dev']) || !in_array($disk['dev'], $arrAvailableDevs)) { $disk['dev'] = array_shift($arrAvailableDevs); } unset($arrAvailableDevs[$disk['dev']]); $boot = $disk['boot']; $bootorder = ''; if ($boot > 0) { $bootorder = ""; } $readonly = ''; if (!empty($disk['readonly'])) { $readonly = ''; } $strDevType = @filetype(realpath($disk['image'])); if ($disk["serial"] != "") $serial = "".$disk["serial"].""; else $serial = ""; $rotation_rate = ""; if ($disk['bus'] == "scsi" || $disk['bus'] == "sata" || $disk['bus'] == "ide" ) { if ($disk['rotation']) $rotation_rate = " rotation_rate='1' "; } if ($strDevType == 'file' || $strDevType == 'block') { $strSourceType = ($strDevType == 'file' ? 'file' : 'dev'); if (isset($disk['discard'])) $strDevUnmap = " discard=\"{$disk['discard']}\" "; else $strDevUnmap = " discard=\"ignore\" "; $diskstr .= " $bootorder $readonly $serial "; } } } } $scsicontroller = ''; if ($needSCSIController) { $scsicontroller = ""; } $netstr = ''; if (!empty($nics)) { foreach ($nics as $i => $nic) { if (empty($nic['mac']) || empty($nic['network'])) continue; $netmodel = $nic['model'] ?: 'virtio-net'; $net_res = $this->libvirt_get_net_res($this->conn, $nic['network']); exec("ls --indicator-style=none /sys/class/net | grep -Po '^((vir)?br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'", $host); $nicboot = $nic["boot"] != null ? "" : ""; if ($net_res) { $netstr .= " $nicboot "; } elseif (in_array($nic['network'], $host)) { if (preg_match('/^(vir)?br/', $nic['network'])) { $netstr .= " $nicboot "; } elseif ($nic['network'] == 'wlan0') { $mac = file_get_contents('/sys/class/net/wlan0/address'); $netstr .= " $nicboot "; } else { $netstr .= " $nicboot "; } } else { continue; } } } $sharestr = ''; $memorybacking = json_decode($domain['memoryBacking'],true); if (!empty($shares)) { foreach ($shares as $i => $share) { if (empty($share['source']) || empty($share['target']) || ($os_type == "windows" && $share["mode"] == "9p")) { continue; } if ($share['mode'] == "virtiofs") { if (!isset($memorybacking['source'])) $memorybacking['source']["@attributes"]["type"] = "memfd"; if (!isset($memorybacking['access'])) $memorybacking['access']["@attributes"]["mode"] = "shared"; $sharestr .= " "; } else { $sharestr .= " "; } } } $pcidevs=''; $gpudevs_used=[]; $multidevices = []; #Load? $vmrc=''; $channelscopypaste = ''; if (!empty($gpus)) { foreach ($gpus as $i => $gpu) { // Skip duplicate video devices if (empty($gpu['id']) || in_array($gpu['id'], $gpudevs_used)) { continue; } if ($gpu['id'] == 'nogpu') break; if ($gpu['id'] == 'virtual') { $strKeyMap = ''; if (!empty($gpu['keymap'])) { if ($gpu['keymap'] != "none") $strKeyMap = "keymap='".$gpu['keymap']."'"; } $passwdstr = ''; if (!empty($domain['password'])){ $passwdstr = "passwd='".htmlspecialchars($domain['password'], ENT_QUOTES | ENT_XML1)."'"; } $strModelType = 'qxl'; if (!empty($gpu['model'])) { $strModelType = $gpu['model']; if (!empty($domain['ovmf']) && $strModelType == 'vmvga') { // OVMF doesn't work with vmvga $strModelType = 'qxl'; } } if (!empty($gpu['autoport'])) { $strAutoport = $gpu['autoport']; } else $strAutoport = "yes"; if (!empty($gpu['protocol'])) { $strProtocol = $gpu['protocol']; } else $strProtocol = "vnc"; if (!empty($gpu['wsport'])) { $strWSport = $gpu['wsport']; } else $strWSport = "-1"; if (!empty($gpu['port'])) { $strPort = $gpu['port']; } else $strPort = "-1"; if ($strAutoport == "yes") $strPort = $strWSport = "-1"; if (($gpu['copypaste'] == "yes") && ($strProtocol == "spice")) $vmrcmousemode = ""; else $vmrcmousemode = "" ; if ($strProtocol == "spice") $virtualaudio = "spice"; else $virtualaudio = "none"; $strEGLHeadless = ""; $strAccel3d =""; $additionalqxlheads = ""; $qxlheads=1; if ($strModelType == "virtio3d") { $strModelType = "virtio"; if (!isset($gpu['render'])) $gpu['render'] = "auto"; if ($gpu['render'] == "auto") { $strEGLHeadless = ''; $strAccel3d = ""; } else { $strEGLHeadless = ''; $strAccel3d =""; }} $strDisplayOptions = ""; if ($strModelType == "qxl") { if (empty($gpu['DisplayOptions'])) $gpu['DisplayOptions'] ="ram='65536' vram='16384' vgamem='16384' heads='1' primary='yes'"; $strDisplayOptions = $gpu['DisplayOptions']; preg_match_all("/(\w+)='([^']+)'/", $strDisplayOptions, $headmatches, PREG_SET_ORDER); $headparams = []; foreach ($headmatches as $headmatch) { $headparams[$headmatch[1]] = $headmatch[2]; } // Default heads to 1 if not found $qxlheads = isset($headparams['heads']) ? (int)$headparams['heads'] : 1; $additionalqxlheads= ''; if ($os_type == "windows") { for ($i = 0; $i < $qxlheads - 1; $i++) { $function = '0x' . dechex($i + 1); $qxlvideo = <<
XML; $additionalqxlheads .= $qxlvideo; } } } $vmrc = " $vmrcmousemode $strEGLHeadless