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) ? $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) ? $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']; } } } 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']; $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 = 1; $vcpupinstr = ''; if (!empty($domain['vcpu']) && is_array($domain['vcpu'])) { $vcpus = count($domain['vcpu']); foreach($domain['vcpu'] as $i => $vcpu) { $vcpupinstr .= ""; } } elseif (!empty($domain['vcpus'])) { $vcpus = $domain['vcpus']; for ($i=0; $i < $vcpus; $i++) { $vcpupinstr .= ""; } } $intCores = $vcpus; $intThreads = 1; $intCPUThreadsPerCore = 1; $cpumode = ''; $cpucache = ''; $cpufeatures = ''; $cpumigrate = ''; 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']."'" ; } $cpustr = " $cpucache $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'); $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|vhost)[0-9]+(\.[0-9]+)?'",$br); if ($nic["boot"] != NULL) $nicboot = "" ; else $nicboot = "" ; if ($net_res) { $netstr .= " $nicboot "; } elseif (in_array($nic['network'], $br)) { if (preg_match('/^(vir)?br/',$nic['network'])) { $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'] == '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" ; $vmrc = " $vmrcmousemode