saveXML(); */ class Array2XML { private static $xml = null; private static $encoding = 'UTF-8'; /** * Initialize the root XML node [optional] * @param $version * @param $encoding * @param $format_output */ public static function init($version = '1.0', $encoding = 'UTF-8', $format_output = true) { self::$xml = new DomDocument($version, $encoding); self::$xml->formatOutput = $format_output; self::$encoding = $encoding; } /** * Convert an Array to XML * @param string $node_name - name of the root node to be converted * @param array $arr - aray to be converterd * @return DomDocument */ public static function &createXML($node_name, $arr=array()) { $xml = self::getXMLRoot(); $xml->appendChild(self::convert($node_name, $arr)); self::$xml = null; // clear the xml node in the class for 2nd time use. return $xml; } /** * Convert an Array to XML * @param string $node_name - name of the root node to be converted * @param array $arr - aray to be converterd * @return DOMNode */ private static function &convert($node_name, $arr=array()) { //print_arr($node_name); $xml = self::getXMLRoot(); $node = $xml->createElement($node_name); if(is_array($arr)){ // get the attributes first.; if(isset($arr['@attributes'])) { foreach($arr['@attributes'] as $key => $value) { if(!self::isValidTagName($key)) { throw new Exception('[Array2XML] Illegal character in attribute name. attribute: '.$key.' in node: '.$node_name); } $node->setAttribute($key, self::bool2str($value)); } unset($arr['@attributes']); //remove the key from the array once done. } // check if it has a value stored in @value, if yes store the value and return // else check if its directly stored as string if(isset($arr['@value'])) { $node->appendChild($xml->createTextNode(self::bool2str($arr['@value']))); unset($arr['@value']); //remove the key from the array once done. //return from recursion, as a note with value cannot have child nodes. return $node; } else if(isset($arr['@cdata'])) { $node->appendChild($xml->createCDATASection(self::bool2str($arr['@cdata']))); unset($arr['@cdata']); //remove the key from the array once done. //return from recursion, as a note with cdata cannot have child nodes. return $node; } } //create subnodes using recursion if(is_array($arr)){ // recurse to get the node for that key foreach($arr as $key=>$value){ if(!self::isValidTagName($key)) { throw new Exception('[Array2XML] Illegal character in tag name. tag: '.$key.' in node: '.$node_name); } if(is_array($value) && is_numeric(key($value))) { // MORE THAN ONE NODE OF ITS KIND; // if the new array is numeric index, means it is array of nodes of the same kind // it should follow the parent key name foreach($value as $k=>$v){ $node->appendChild(self::convert($key, $v)); } } else { // ONLY ONE NODE OF ITS KIND $node->appendChild(self::convert($key, $value)); } unset($arr[$key]); //remove the key from the array once done. } } // after we are done with all the keys in the array (if it is one) // we check if it has any text value, if yes, append it. if(!is_array($arr)) { $node->appendChild($xml->createTextNode(self::bool2str($arr ?? ""))); } return $node; } /* * Get the root XML node, if there isn't one, create it. */ private static function getXMLRoot(){ if(empty(self::$xml)) { self::init(); } return self::$xml; } /* * Get string representation of boolean value */ private static function bool2str($v){ //convert boolean to text value. $v = $v === true ? 'true' : $v; $v = $v === false ? 'false' : $v; return $v; } /* * Check if the tag name or attribute name contains illegal characters * Ref: http://www.w3.org/TR/xml/#sec-common-syn */ private static function isValidTagName($tag){ $pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i'; return preg_match($pattern, $tag, $matches) && $matches[0] == $tag; } } $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp'; require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt.php"; require_once "$docroot/webGui/include/Custom.php"; // Load emhttp variables if needed. if (!isset($var)){ $var = @parse_ini_file("$docroot/state/var.ini"); $disks = @parse_ini_file("$docroot/state/disks.ini", true); extract(parse_plugin_cfg("dynamix",true)); } $ethX = 'eth0'; if (!isset($$ethX) && is_file("$docroot/state/network.ini")) { extract(parse_ini_file("$docroot/state/network.ini",true)); } // Check if program is running and $libvirt_running = trim(shell_exec( "[ -f /proc/`cat /var/run/libvirt/libvirtd.pid 2> /dev/null`/exe ] && echo 'yes' || echo 'no' 2> /dev/null" )); $arrAllTemplates = [ ' Windows ' => '', /* Windows Header */ 'Windows 11' => [ 'form' => 'Custom.form.php', 'icon' => 'windows11.png', 'os' => 'windowstpm', 'overrides' => [ 'domain' => [ 'ovmf' => 2, 'mem' => 4096 * 1024, 'maxmem' => 4096 * 1024 ], 'disk' => [ [ 'size' => '64G' ] ] ] ], 'Windows 10' => [ 'form' => 'Custom.form.php', 'icon' => 'windows.png', 'os' => 'windows10', 'overrides' => [ 'domain' => [ 'mem' => 2048 * 1024, 'maxmem' => 2048 * 1024 ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows 8.x' => [ 'form' => 'Custom.form.php', 'icon' => 'windows.png', 'os' => 'windows', 'overrides' => [ 'domain' => [ 'name' => 'Windows 8.1', 'mem' => 2048 * 1024, 'maxmem' => 2048 * 1024 ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows 7' => [ 'form' => 'Custom.form.php', 'icon' => 'windows7.png', 'os' => 'windows7', 'overrides' => [ 'domain' => [ 'mem' => 2048 * 1024, 'maxmem' => 2048 * 1024, 'ovmf' => 0, 'usbmode' => 'usb2' ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows XP' => [ 'form' => 'Custom.form.php', 'icon' => 'windowsxp.png', 'os' => 'windowsxp', 'overrides' => [ 'domain' => [ 'ovmf' => 0, 'usbmode' => 'usb2' ], 'disk' => [ [ 'size' => '15G' ] ] ] ], 'Windows Server 2016' => [ 'form' => 'Custom.form.php', 'icon' => 'windows.png', 'os' => 'windows2016', 'overrides' => [ 'domain' => [ 'mem' => 2048 * 1024, 'maxmem' => 2048 * 1024 ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows Server 2012' => [ 'form' => 'Custom.form.php', 'icon' => 'windows.png', 'os' => 'windows2012', 'overrides' => [ 'domain' => [ 'mem' => 2048 * 1024, 'maxmem' => 2048 * 1024 ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows Server 2008' => [ 'form' => 'Custom.form.php', 'icon' => 'windows7.png', 'os' => 'windows2008', 'overrides' => [ 'domain' => [ 'usbmode' => 'usb2' ], 'disk' => [ [ 'size' => '30G' ] ] ] ], 'Windows Server 2003' => [ 'form' => 'Custom.form.php', 'icon' => 'windowsxp.png', 'os' => 'windows2003', 'overrides' => [ 'domain' => [ 'usbmode' => 'usb2' ], 'disk' => [ [ 'size' => '15G' ] ] ] ], ' Pre-packaged ' => '', /* Pre-built Header */ 'LibreELEC' => [ 'form' => 'LibreELEC.form.php', 'icon' => 'libreelec.png' ], 'OpenELEC' => [ 'form' => 'OpenELEC.form.php', 'icon' => 'openelec.png' ], ' Linux ' => '', /* Linux Header */ 'Linux' => [ 'form' => 'Custom.form.php', 'icon' => 'linux.png', 'os' => 'linux' ], 'Arch' => [ 'form' => 'Custom.form.php', 'icon' => 'arch.png', 'os' => 'arch' ], 'CentOS' => [ 'form' => 'Custom.form.php', 'icon' => 'centos.png', 'os' => 'centos' ], 'ChromeOS' => [ 'form' => 'Custom.form.php', 'icon' => 'chromeos.png', 'os' => 'chromeos' ], 'CoreOS' => [ 'form' => 'Custom.form.php', 'icon' => 'coreos.png', 'os' => 'coreos' ], 'Debian' => [ 'form' => 'Custom.form.php', 'icon' => 'debian.png', 'os' => 'debian' ], 'Fedora' => [ 'form' => 'Custom.form.php', 'icon' => 'fedora.png', 'os' => 'fedora' ], 'FreeBSD' => [ 'form' => 'Custom.form.php', 'icon' => 'freebsd.png', 'os' => 'freebsd' ], 'OpenSUSE' => [ 'form' => 'Custom.form.php', 'icon' => 'opensuse.png', 'os' => 'opensuse' ], 'RedHat' => [ 'form' => 'Custom.form.php', 'icon' => 'redhat.png', 'os' => 'redhat' ], 'Scientific' => [ 'form' => 'Custom.form.php', 'icon' => 'scientific.png', 'os' => 'scientific' ], 'Slackware' => [ 'form' => 'Custom.form.php', 'icon' => 'slackware.png', 'os' => 'slackware' ], 'SteamOS' => [ 'form' => 'Custom.form.php', 'icon' => 'steamos.png', 'os' => 'steamos' ], 'Ubuntu' => [ 'form' => 'Custom.form.php', 'icon' => 'ubuntu.png', 'os' => 'ubuntu' ], 'VyOS' => [ 'form' => 'Custom.form.php', 'icon' => 'vyos.png', 'os' => 'vyos' ], ' ' => '', /* Custom / XML Expert Header */ 'Custom' => [ 'form' => 'XML_Expert.form.php', 'icon' => 'default.png' ] ]; $arrOpenELECVersions = [ '6.0.3_1' => [ 'name' => '6.0.3', 'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-6.0.3_1.tar.xz', 'size' => 178909136, 'md5' => 'c584312831d7cd93a40e61ac9f186d32', 'localpath' => '', 'valid' => '0' ], '6.0.0_1' => [ 'name' => '6.0.0', 'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-6.0.0_1.tar.xz', 'size' => 165658636, 'md5' => '66fb6c3f1b6db49c291753fb3ec7c15c', 'localpath' => '', 'valid' => '0' ], '5.95.3_1' => [ 'name' => '5.95.3 (6.0.0 Beta3)', 'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-5.95.3_1.tar.xz', 'size' => 153990180, 'md5' => '8936cda74c28ddcaa165cc49ff2a477a', 'localpath' => '', 'valid' => '0' ], '5.95.2_1' => [ 'name' => '5.95.2 (6.0.0 Beta2)', 'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/OpenELEC/OpenELEC-unRAID.x86_64-5.95.2_1.tar.xz', 'size' => 156250392, 'md5' => 'ac70048eecbda4772e386c6f271cb5e9', 'localpath' => '', 'valid' => '0' ] ]; $arrLibreELECVersions = [ '7.0.1_1' => [ 'name' => '7.0.1', 'url' => 'https://s3.amazonaws.com/dnld.lime-technology.com/images/LibreELEC/LibreELEC-unRAID.x86_64-7.0.1_1.tar.xz', 'size' => 209748564, 'md5' => 'c1e8def2ffb26a355e7cc598311697f6', 'localpath' => '', 'valid' => '0' ] ]; $fedora = '/var/tmp/fedora-virtio-isos'; // set variable to obtained information if (file_exists($fedora)) $virtio_isos = unserialize(file_get_contents($fedora)); else { // else initialize variable $virtio_isos = [ 'virtio-win-0.1.208-1' => [ 'name' => 'virtio-win-0.1.208-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.208-1/virtio-win-0.1.208.iso', 'size' => 556431360, 'md5' => '3bbc69bdcf1d46f4ee0ddaf35c2656f3' ], 'virtio-win-0.1.190-1' => [ 'name' => 'virtio-win-0.1.190-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.190-1/virtio-win-0.1.190.iso', 'size' => 501745664, 'md5' => '6e30288fa45ba99a1434740204b8e8e8' ], 'virtio-win-0.1.189-1' => [ 'name' => 'virtio-win-0.1.189-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.189-1/virtio-win-0.1.189.iso', 'size' => 500496384, 'md5' => '86c924cf591c275de81f0e64eefe69a3' ], 'virtio-win-0.1.173-2' => [ 'name' => 'virtio-win-0.1.173-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.173-2/virtio-win-0.1.173.iso', 'size' => 394303488, 'md5' => '88fcd398b7d54301b559d1762240aa67' ], 'virtio-win-0.1.160-1' => [ 'name' => 'virtio-win-0.1.160-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.160-1/virtio-win-0.1.160.iso', 'size' => 322842624, 'md5' => 'eec0b91dd72fb2b42774d5d0b39175c7' ], 'virtio-win-0.1.141-1' => [ 'name' => 'virtio-win-0.1.141-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.141-1/virtio-win-0.1.141.iso', 'size' => 316628992, 'md5' => '6327d722bdea72bcb1849ce99604bbe0' ], 'virtio-win-0.1.126-2' => [ 'name' => 'virtio-win-0.1.126-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.126-2/virtio-win-0.1.126.iso', 'size' => 155856896, 'md5' => 'b8379138ae5f8d0adecb839f9debf875' ], 'virtio-win-0.1.126-1' => [ 'name' => 'virtio-win-0.1.126-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.126-1/virtio-win-0.1.126.iso', 'size' => 155856896, 'md5' => '85637076191887d4cd425bf8d59f8dd9' ], 'virtio-win-0.1.118-2' => [ 'name' => 'virtio-win-0.1.118-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.118-2/virtio-win-0.1.118.iso', 'size' => 56967168, 'md5' => '9cb51bde60decfafdf8119ce01b7c1cf' ], 'virtio-win-0.1.118-1' => [ 'name' => 'virtio-win-0.1.118-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.118-1/virtio-win-0.1.118.iso', 'size' => 56967168, 'md5' => 'cc5771f2f0ea5097946d3d447f21cce8' ], 'virtio-win-0.1.117-1' => [ 'name' => 'virtio-win-0.1.117-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.117-1/virtio-win-0.1.117.iso', 'size' => 56999936, 'md5' => '2a79d6036ea4292f81c3370dd0a8b6d6' ], 'virtio-win-0.1.113-1' => [ 'name' => 'virtio-win-0.1.113-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.113-1/virtio-win-0.1.113.iso', 'size' => 56936448, 'md5' => '11ed773055e19eca75ed186ff12d354c' ], 'virtio-win-0.1.112-1' => [ 'name' => 'virtio-win-0.1.112-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.112-1/virtio-win-0.1.112.iso', 'size' => 56926208, 'md5' => '7db0211d7aec3e08fadd21c8eaaf35db' ], 'virtio-win-0.1.110-2' => [ 'name' => 'virtio-win-0.1.110-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.110-2/virtio-win-0.1.110.iso', 'size' => 56586240, 'md5' => '93357a5105f1255591f1c389748288a9' ], 'virtio-win-0.1.110-1' => [ 'name' => 'virtio-win-0.1.110-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.110-1/virtio-win-0.1.110.iso', 'size' => 56586240, 'md5' => '239e0eb442bb63c177deb4af39397731' ], 'virtio-win-0.1.109-2' => [ 'name' => 'virtio-win-0.1.109-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.109-2/virtio-win-0.1.109.iso', 'size' => 56606720, 'md5' => '2a9f78f648f03fe72decdadb38837db3' ], 'virtio-win-0.1.109-1' => [ 'name' => 'virtio-win-0.1.109-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.109-1/virtio-win-0.1.109.iso', 'size' => 56606720, 'md5' => '1b0da008d0ec79a6223d21be2fcce2ee' ], 'virtio-win-0.1.108-1' => [ 'name' => 'virtio-win-0.1.108-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.108-1/virtio-win-0.1.108.iso', 'size' => 56598528, 'md5' => '46deb991f8c382f2d9af0fb786792990' ], 'virtio-win-0.1.106-1' => [ 'name' => 'virtio-win-0.1.106-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.106-1/virtio-win-0.1.106.iso', 'size' => 56586240, 'md5' => '66228ea20fae1a28d7a1583b9a5a1b8b' ], 'virtio-win-0.1.105-1' => [ 'name' => 'virtio-win-0.1.105-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.105-1/virtio-win-0.1.105.iso', 'size' => 56584192, 'md5' => 'c3194fa62a4a1ccbecfe784a52feda66' ], 'virtio-win-0.1.104-1' => [ 'name' => 'virtio-win-0.1.104-1.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.104-1/virtio-win-0.1.104.iso', 'size' => 56584192, 'md5' => '9aa28b6f5b18770d796194aaaeeea31a' ], 'virtio-win-0.1.103-2' => [ 'name' => 'virtio-win-0.1.103-2.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.103-2/virtio-win-0.1.103.iso', 'size' => 56340480, 'md5' => '07c4356880f0b385d6908392e48d6e75' ], 'virtio-win-0.1.103' => [ 'name' => 'virtio-win-0.1.103.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.103/virtio-win-0.1.103.iso', 'size' => 49903616, 'md5' => 'd31069b620820b75730d2def7690c271' ], 'virtio-win-0.1.102' => [ 'name' => 'virtio-win-0.1.102.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.102/virtio-win-0.1.102.iso', 'size' => 160755712, 'md5' => '712561dd78ef532c54f8fee927c1ce2e' ], 'virtio-win-0.1.101' => [ 'name' => 'virtio-win-0.1.101.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.101/virtio-win-0.1.101.iso', 'size' => 160755712, 'md5' => 'cf73576efc03685907c1fa49180ea388' ], 'virtio-win-0.1.100' => [ 'name' => 'virtio-win-0.1.100.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.100/virtio-win-0.1.100.iso', 'size' => 160704512, 'md5' => '8b21136f988bef7981ee580e9101b6b4' ], 'virtio-win-0.1.96' => [ 'name' => 'virtio-win-0.1.96.iso', 'url' => 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.96/virtio-win-0.1.96.iso', 'size' => 160659456, 'md5' => 'd406bf6748b9ba4c872c5b5301ba7272' ] ];} // Read configuration file (guaranteed to exist) $domain_cfgfile = "/boot/config/domain.cfg"; $domain_cfg = parse_ini_file($domain_cfgfile); if ($domain_cfg['DEBUG'] != "yes") { error_reporting(0); } if (empty($domain_cfg['VMSTORAGEMODE'])) { $domain_cfg['VMSTORAGEMODE'] = "auto"; } if (!empty($domain_cfg['DOMAINDIR'])) { $domain_cfg['DOMAINDIR'] = rtrim($domain_cfg['DOMAINDIR'], '/') . '/'; } if (!empty($domain_cfg['MEDIADIR'])) { $domain_cfg['MEDIADIR'] = rtrim($domain_cfg['MEDIADIR'], '/') . '/'; } if (empty($domain_cfg['TIMEOUT'])) { $domain_cfg['TIMEOUT'] = 60; } $domain_bridge = (!($domain_cfg['BRNAME'])) ? 'virbr0' : $domain_cfg['BRNAME']; $msg = (empty($domain_bridge)) ? "Error: Setup Bridge in Settings/Network Settings" : false; $libvirt_service = isset($domain_cfg['SERVICE']) ? $domain_cfg['SERVICE'] : "disable"; if ($libvirt_running == "yes"){ $lv = new Libvirt('qemu:///system', null, null, false); $arrHostInfo = $lv->host_get_node_info(); $maxcpu = (int)$arrHostInfo['cpus']; $maxmem = number_format(($arrHostInfo['memory'] / 1048576), 1, '.', ''); } function mk_dropdown_options($arrOptions, $strSelected) { foreach ($arrOptions as $key => $label) { echo mk_option($strSelected, $key, $label); } } function appendOrdinalSuffix($number) { $ends = ['th','st','nd','rd','th','th','th','th','th','th']; if (($number % 100) >= 11 && ($number % 100) <= 13) { $abbreviation = $number . 'th'; } else { $abbreviation = $number . $ends[$number % 10]; } return $abbreviation; } function sanitizeVendor($strVendor) { // Specialized vendor name cleanup // e.g.: Advanced Micro Devices, Inc. [AMD/ATI] --> Advanced Micro Devices, Inc. if (preg_match('/(?P.+) \[.+\]/', $strVendor, $arrGPUMatch)) { $strVendor = $arrGPUMatch['gpuvendor']; } $strVendor = str_replace('Advanced Micro Devices', 'AMD', $strVendor); $strVendor = str_replace('Samsung Electronics Co.', 'Samsung', $strVendor); $strVendor = str_replace([' Corporation', ' Semiconductor ', ' Technology Group Ltd.', ' System, Inc.', ' Systems, Inc.'], '', $strVendor); $strVendor = str_replace([' Co., Ltd.', ', Ltd.', ', Ltd', ', Inc.'], '', $strVendor); return $strVendor; } function sanitizeProduct($strProduct) { $strProduct = str_replace(' PCI Express', ' PCIe', $strProduct); $strProduct = str_replace(' High Definition ', ' HD ', $strProduct); return $strProduct; } function getDiskImageInfo($strImgPath) { $arrJSON = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($strImgPath) . " 2>/dev/null"), true); return $arrJSON; } $cacheValidPCIDevices = null; function getValidPCIDevices() { global $cacheValidPCIDevices; global $disks; if (!is_null($cacheValidPCIDevices)) { return $cacheValidPCIDevices; } $strOSNetworkDevice = trim(exec("udevadm info -q path -p /sys/class/net/eth0 2>/dev/null | grep -Po '0000:\K\w{2}:\w{2}\.\w{1}'")); $arrOSDiskControllers = []; foreach ($disks as $strDisk => $arrDisk) { if (!empty($arrDisk['device']) && file_exists('/dev/'.$arrDisk['device'])) { $strOSDiskController = trim(exec("udevadm info -q path -n /dev/".$arrDisk['device']." | grep -Po '0000:\K\w{2}:\w{2}\.\w{1}'")); if (!empty($strOSDiskController)) { $arrOSDiskControllers[] = $strOSDiskController; } } } $arrOSDiskControllers = array_values(array_unique($arrOSDiskControllers)); $arrBlacklistIDs = $arrOSDiskControllers; if (!empty($strOSNetworkDevice)) { $arrBlacklistIDs[] = $strOSNetworkDevice; } $arrBlacklistClassIDregex = '/^(05|06|08|0a|0b|0c05)/'; // Got Class IDs at the bottom of /usr/share/hwdata/pci.ids $arrWhitelistGPUClassIDregex = '/^(0001|03)/'; $arrWhitelistAudioClassIDregex = '/^(0403)/'; $arrValidPCIDevices = []; exec("lspci -m -nn 2>/dev/null", $arrAllPCIDevices); foreach ($arrAllPCIDevices as $strPCIDevice) { // Example: 00:1f.0 "ISA bridge [0601]" "Intel Corporation [8086]" "Z77 Express Chipset LPC Controller [1e44]" -r04 "Micro-Star International Co., Ltd. [MSI] [1462]" "Device [7759]" if (preg_match('/^(?P\S+) \"(?P[^"]+) \[(?P[a-f0-9]{4})\]\" \"(?P[^"]+) \[(?P[a-f0-9]{4})\]\" \"(?P[^"]+) \[(?P[a-f0-9]{4})\]\"/', $strPCIDevice, $arrMatch)) { $boolBlacklisted = false; if (in_array($arrMatch['id'], $arrBlacklistIDs) || preg_match($arrBlacklistClassIDregex, $arrMatch['typeid'])) { // Device blacklisted, skip device $boolBlacklisted = true; } $strClass = 'other'; if (preg_match($arrWhitelistGPUClassIDregex, $arrMatch['typeid'])) { $strClass = 'vga'; // Specialized product name cleanup for GPU // GF116 [GeForce GTX 550 Ti] --> GeForce GTX 550 Ti if (preg_match('/.+\[(?P.+)\]/', $arrMatch['productname'], $arrGPUMatch)) { $arrMatch['productname'] = $arrGPUMatch['gpuname']; } } elseif (preg_match($arrWhitelistAudioClassIDregex, $arrMatch['typeid'])) { $strClass = 'audio'; } if (!file_exists('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/iommu_group/')) { // No IOMMU support for device, skip device continue; } // Attempt to get the current kernel-bound driver for this pci device $strDriver = ''; if (is_link('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/driver')) { $strLink = @readlink('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/driver'); if (!empty($strLink)) { $strDriver = basename($strLink); } } // Clean up the vendor and product name $arrMatch['vendorname'] = sanitizeVendor($arrMatch['vendorname']); $arrMatch['productname'] = sanitizeProduct($arrMatch['productname']); $arrValidPCIDevices[] = [ 'id' => $arrMatch['id'], 'type' => $arrMatch['type'], 'typeid' => $arrMatch['typeid'], 'vendorid' => $arrMatch['vendorid'], 'vendorname' => $arrMatch['vendorname'], 'productid' => $arrMatch['productid'], 'productname' => $arrMatch['productname'], 'class' => $strClass, 'driver' => $strDriver, 'name' => $arrMatch['vendorname'] . ' ' . $arrMatch['productname'], 'blacklisted' => $boolBlacklisted ]; } } $cacheValidPCIDevices = $arrValidPCIDevices; return $arrValidPCIDevices; } function getValidGPUDevices() { $arrValidPCIDevices = getValidPCIDevices(); $arrValidGPUDevices = array_filter($arrValidPCIDevices, function($arrDev) { return ($arrDev['class'] == 'vga' && !$arrDev['blacklisted']); }); return $arrValidGPUDevices; } function getValidAudioDevices() { $arrValidPCIDevices = getValidPCIDevices(); $arrValidAudioDevices = array_filter($arrValidPCIDevices, function($arrDev) { return ($arrDev['class'] == 'audio' && !$arrDev['blacklisted']); }); return $arrValidAudioDevices; } function getValidOtherDevices() { $arrValidPCIDevices = getValidPCIDevices(); $arrValidOtherDevices = array_filter($arrValidPCIDevices, function($arrDev) { return ($arrDev['class'] == 'other' && !$arrDev['blacklisted']); }); return $arrValidOtherDevices; } function getValidOtherStubbedDevices() { $arrValidPCIDevices = getValidPCIDevices(); $arrValidOtherStubbedDevices = array_filter($arrValidPCIDevices, function($arrDev) { return ($arrDev['class'] == 'other' && !$arrDev['blacklisted'] && in_array($arrDev['driver'], ['pci-stub', 'vfio-pci'])); }); return $arrValidOtherStubbedDevices; } $cacheValidUSBDevices = null; function getValidUSBDevices() { global $cacheValidUSBDevices; if (!is_null($cacheValidUSBDevices)) { return $cacheValidUSBDevices; } $arrValidUSBDevices = []; // Get a list of all usb hubs so we can blacklist them exec("cat /sys/bus/usb/drivers/hub/*/modalias | grep -Po 'usb:v\K\w{9}' | tr 'p' ':'", $arrAllUSBHubs); exec("lsusb 2>/dev/null", $arrAllUSBDevices); foreach ($arrAllUSBDevices as $strUSBDevice) { if (preg_match('/^.+: ID (?P\S+)(?P.*)$/', $strUSBDevice, $arrMatch)) { if (stripos($GLOBALS['var']['flashGUID'], str_replace(':', '-', $arrMatch['id'])) === 0) { // Device id matches the unraid boot device, skip device continue; } if (in_array(strtoupper($arrMatch['id']), $arrAllUSBHubs)) { // Device class is a Hub, skip device continue; } $arrMatch['name'] = trim($arrMatch['name']); if (empty($arrMatch['name'])) { // Device name is blank, attempt to lookup usb details exec("lsusb -d ".$arrMatch['id']." -v 2>/dev/null | grep -Po '^\s+(iManufacturer|iProduct)\s+[1-9]+ \K[^\\n]+'", $arrAltName); $arrMatch['name'] = trim(implode(' ', (array)$arrAltName)); if (empty($arrMatch['name'])) { // Still blank, replace using fallback default $arrMatch['name'] = '[unnamed device]'; } } // Clean up the name $arrMatch['name'] = sanitizeVendor($arrMatch['name']); $arrValidUSBDevices[] = [ 'id' => $arrMatch['id'], 'name' => $arrMatch['name'], ]; } } uasort($arrValidUSBDevices, function ($a, $b) { return strcasecmp($a['id'], $b['id']); }); $cacheValidUSBDevices = $arrValidUSBDevices; return $arrValidUSBDevices; } function getValidMachineTypes() { global $lv; $arrValidMachineTypes = []; $arrQEMUInfo = $lv->get_connect_information(); $arrMachineTypes = $lv->get_machine_types('x86_64'); $strQEMUVersion = $arrQEMUInfo['hypervisor_major'] . '.' . $arrQEMUInfo['hypervisor_minor']; foreach ($arrMachineTypes as $arrMachine) { if ($arrMachine['name'] == 'q35') { // Latest Q35 $arrValidMachineTypes['pc-q35-' . $strQEMUVersion] = 'Q35-' . $strQEMUVersion; } if (strpos($arrMachine['name'], 'q35-') !== false) { // Prior releases of Q35 $arrValidMachineTypes[$arrMachine['name']] = str_replace(['q35', 'pc-'], ['Q35', ''], $arrMachine['name']); } if ($arrMachine['name'] == 'pc') { // Latest i440fx $arrValidMachineTypes['pc-i440fx-' . $strQEMUVersion] = 'i440fx-' . $strQEMUVersion; } if (strpos($arrMachine['name'], 'i440fx-') !== false) { // Prior releases of i440fx $arrValidMachineTypes[$arrMachine['name']] = str_replace('pc-', '', $arrMachine['name']); } } uksort($arrValidMachineTypes, 'version_compare'); $arrValidMachineTypes = array_reverse($arrValidMachineTypes); return $arrValidMachineTypes; } function getLatestMachineType($strType = 'i440fx') { $arrMachineTypes = getValidMachineTypes(); foreach ($arrMachineTypes as $key => $value) { if (stripos($key, $strType) !== false) { return $key; } } return array_shift(array_keys($arrMachineTypes)); } function getValidDiskDrivers() { $arrValidDiskDrivers = [ 'raw' => 'raw', 'qcow2' => 'qcow2' ]; return $arrValidDiskDrivers; } function getValidDiskBuses() { $arrValidDiskBuses = [ 'virtio' => 'VirtIO', 'scsi' => 'SCSI', 'sata' => 'SATA', 'ide' => 'IDE', 'usb' => 'USB' ]; return $arrValidDiskBuses; } function getValidCdromBuses() { $arrValidCdromBuses = [ 'scsi' => 'SCSI', 'sata' => 'SATA', 'ide' => 'IDE', 'usb' => 'USB' ]; return $arrValidCdromBuses; } function getValidVNCModels() { $arrValidVNCModels = [ 'cirrus' => 'Cirrus', 'qxl' => 'QXL (best)', 'vmvga' => 'vmvga' ]; return $arrValidVNCModels; } function getValidVMRCProtocols() { $arrValidProtocols = [ 'vnc' => 'VNC', 'spice' => 'SPICE' ]; return $arrValidProtocols; } function getValidKeyMaps() { $arrValidKeyMaps = [ 'ar' => 'Arabic (ar)', 'hr' => 'Croatian (hr)', 'cz' => 'Czech (cz)', 'da' => 'Danish (da)', 'nl' => 'Dutch (nl)', 'en-gb' => 'English-United Kingdom (en-gb)', 'en-us' => 'English-United States (en-us)', 'es' => 'Español (es)', 'et' => 'Estonian (et)', 'fo' => 'Faroese (fo)', 'fi' => 'Finnish (fi)', 'fr' => 'French (fr)', 'bepo' => 'French-Bépo (bepo)', 'fr-be' => 'French-Belgium (fr-be)', 'fr-ca' => 'French-Canadian (fr-ca)', 'fr-ch' => 'French-Switzerland (fr-ch)', 'de-ch' => 'German-Switzerland (de-ch)', 'de' => 'German (de)', 'hu' => 'Hungarian (hu)', 'is' => 'Icelandic (is)', 'it' => 'Italian (it)', 'ja' => 'Japanese (ja)', 'lv' => 'Latvian (lv)', 'lt' => 'Lithuanian (lt)', 'mk' => 'Macedonian (mk)', 'no' => 'Norwegian (no)', 'pl' => 'Polish (pl)', 'pt' => 'Portuguese (pt)', 'pt-br' => 'Portuguese-Brazil (pt-br)', 'ru' => 'Russian (ru)', 'sl' => 'Slovene (sl)', 'sv' => 'Swedish (sv)', 'th' => 'Thailand (th)', 'tr' => 'Turkish (tr)' ]; return $arrValidKeyMaps; } function getHostCPUModel() { $cpu = explode('#', exec("dmidecode -q -t 4|awk -F: '{if(/Version:/) v=$2; else if(/Current Speed:/) s=$2} END{print v\"#\"s}'")); [$strCPUModel] = my_explode('@', str_replace(["Processor","CPU","(C)","(R)","(TM)"], ["","","©","®","™"], $cpu[0]) . '@', 1); return trim($strCPUModel); } function getValidNetworks() { global $lv; $arrValidNetworks = []; exec("brctl show|grep -Po '^(vir)?br\d\S*'", $arrBridges); if (!is_array($arrBridges)) { $arrBridges = []; } // Make sure the default libvirt bridge is first in the list if (($key = array_search('virbr0', $arrBridges)) !== false) { unset($arrBridges[$key]); } // We always list virbr0 because libvirt might not be started yet (thus the bridge doesn't exists) array_unshift($arrBridges, 'virbr0'); $arrValidNetworks['bridges'] = array_values($arrBridges); // This breaks VMSettings.page if libvirt is not running if ($libvirt_running == "yes") { $arrVirtual = $lv->libvirt_get_net_list($lv->get_connection()); if (($key = array_search('default', $arrVirtual)) !== false) { unset($arrVirtual[$key]); } array_unshift($arrVirtual, 'default'); $arrValidNetworks['libvirt'] = array_values($arrVirtual); } return $arrValidNetworks; } function domain_to_config($uuid) { global $lv; global $domain_cfg; $arrValidGPUDevices = getValidGPUDevices(); $arrValidAudioDevices = getValidAudioDevices(); $arrValidOtherDevices = getValidOtherDevices(); $arrValidUSBDevices = getValidUSBDevices(); $arrValidDiskDrivers = getValidDiskDrivers(); $res = $lv->domain_get_domain_by_uuid($uuid); $dom = $lv->domain_get_info($res); $medias = $lv->get_cdrom_stats($res); $disks = $lv->get_disk_stats($res, false); $arrNICs = $lv->get_nic_info($res); $arrHostDevs = $lv->domain_get_host_devices_pci($res); $arrUSBDevs = $lv->domain_get_host_devices_usb($res); $getcopypaste=getcopypaste($res) ; // Metadata Parsing // libvirt xpath parser sucks, use php's xpath parser instead $strDOMXML = $lv->domain_get_xml($res); $xmldoc = new DOMDocument(); $xmldoc->loadXML($strDOMXML); $xpath = new DOMXPath($xmldoc); $objNodes = $xpath->query('//domain/metadata/*[local-name()=\'vmtemplate\']/@*'); $arrTemplateValues = []; if ($objNodes->length > 0) { foreach ($objNodes as $objNode) { $arrTemplateValues[$objNode->nodeName] = $objNode->nodeValue; } } if (empty($arrTemplateValues['name'])) { $arrTemplateValues['name'] = 'Custom'; } $arrGPUDevices = []; $arrAudioDevices = []; $arrOtherDevices = []; // check for vnc/spice; add to arrGPUDevices $vmrcport = $lv->domain_get_vnc_port($res); $autoport = $lv->domain_get_vmrc_autoport($res); if (empty($vmrcport) && $autoport == "yes") $vmrcport = -1 ; if (!empty($vmrcport)) { $arrGPUDevices[] = [ 'id' => 'virtual', 'protocol' => $lv->domain_get_vmrc_protocol($res), 'model' => $lv->domain_get_vnc_model($res), 'keymap' => $lv->domain_get_vnc_keymap($res), 'port' => $vmrcport, 'wsport' => $lv->domain_get_ws_port($res), 'autoport' => $autoport, 'copypaste' => $getcopypaste, ]; } foreach ($arrHostDevs as $arrHostDev) { $arrFoundGPUDevices = array_filter($arrValidGPUDevices, function($arrDev) use ($arrHostDev) {return ($arrDev['id'] == $arrHostDev['id']);}); if (!empty($arrFoundGPUDevices)) { $arrGPUDevices[] = ['id' => $arrHostDev['id'], 'rom' => $arrHostDev['rom']]; continue; } $arrFoundAudioDevices = array_filter($arrValidAudioDevices, function($arrDev) use ($arrHostDev) {return ($arrDev['id'] == $arrHostDev['id']);}); if (!empty($arrFoundAudioDevices)) { $arrAudioDevices[] = ['id' => $arrHostDev['id']]; continue; } $arrFoundOtherDevices = array_filter($arrValidOtherDevices, function($arrDev) use ($arrHostDev) {return ($arrDev['id'] == $arrHostDev['id']);}); if (!empty($arrFoundOtherDevices)) { $arrOtherDevices[] = ['id' => $arrHostDev['id'],'boot' => $arrHostDev['boot']]; continue; } } // Add claimed USB devices by this VM to the available USB devices /* foreach($arrUSBDevs as $arrUSB) { $arrValidUSBDevices[] = [ 'id' => $arrUSB['id'], 'name' => $arrUSB['product'], ]; } */ $arrDisks = []; foreach ($disks as $i => $disk) { $strPath = (empty($disk['file']) ? $disk['partition'] : $disk['file']); $default_option = 'auto'; if (empty($domain_cfg['DOMAINDIR']) || !file_exists($domain_cfg['DOMAINDIR']) || !is_file($strPath) || strpos($domain_cfg['DOMAINDIR'], dirname(dirname($strPath))) === false || basename($strPath) != 'vdisk'.($i+1).'.img') { $default_option = 'manual'; } $arrDisks[] = [ 'new' => $strPath, 'size' => '', 'driver' => $disk['type'], 'driver' => 'raw', 'dev' => $disk['device'], 'bus' => $disk['bus'], 'boot' => $disk['boot order'], 'serial' => $disk['serial'], 'select' => $default_option ]; } if (empty($arrDisks)) { $arrDisks[] = [ 'new' => '', 'size' => '', 'driver' => 'raw', 'dev' => 'hda', 'select' => '', 'bus' => 'virtio' ]; } // HACK: If there's only 1 cdrom and the dev=hdb then it's most likely a VirtIO Driver ISO instead of the OS Install ISO if (!empty($medias) && count($medias) == 1 && array_key_exists('device', $medias[0]) && $medias[0]['device'] == 'hdb') { $medias[] = null; $medias = array_reverse($medias); } $strUSBMode = 'usb2'; if ($lv->_get_single_xpath_result($res, '//domain/devices/controller[@model=\'nec-xhci\']')) { $strUSBMode = 'usb3'; } else if ($lv->_get_single_xpath_result($res, '//domain/devices/controller[@model=\'qemu-xhci\']')) { $strUSBMode = 'usb3-qemu'; } $strOVMF = '0'; if (!empty($lv->domain_get_ovmf($res))) { $ovmfloader = $lv->domain_get_ovmf($res); if (strpos($ovmfloader, '_CODE-pure-efi.fd') !== false) { $strOVMF = '1'; } else if (strpos($ovmfloader, '_CODE-pure-efi-tpm.fd') !== false) { $strOVMF = '2'; } } if ($lv->domain_get_boot_devices($res)[0] == "fd") $osbootdev = "Yes" ; else $osbootdev = "No" ; return [ 'template' => $arrTemplateValues, 'domain' => [ 'name' => $lv->domain_get_name($res), 'desc' => $lv->domain_get_description($res), 'persistent' => 1, 'uuid' => $lv->domain_get_uuid($res), 'clock' => $lv->domain_get_clock_offset($res), 'arch' => $lv->domain_get_arch($res), 'machine' => $lv->domain_get_machine($res), 'mem' => $lv->domain_get_current_memory($res), 'maxmem' => $lv->domain_get_memory($res), 'password' => '', //TODO? 'cpumode' => $lv->domain_get_cpu_type($res), 'vcpus' => $dom['nrVirtCpu'], 'vcpu' => $lv->domain_get_vcpu_pins($res), 'hyperv' => ($lv->domain_get_feature($res, 'hyperv') ? 1 : 0), 'autostart' => ($lv->domain_get_autostart($res) ? 1 : 0), 'state' => $lv->domain_state_translate($dom['state']), 'ovmf' => $strOVMF, 'usbboot' => $osbootdev, 'usbmode' => $strUSBMode, 'memoryBacking' => getmemoryBacking($res) ], 'media' => [ 'cdrom' => (!empty($medias) && !empty($medias[0]) && array_key_exists('file', $medias[0])) ? $medias[0]['file'] : '', 'cdromboot' => (!empty($medias) && !empty($medias[0]) && array_key_exists('file', $medias[0])) ? $medias[0]['boot order'] : '', 'cdrombus' => (!empty($medias) && !empty($medias[0]) && array_key_exists('bus', $medias[0])) ? $medias[0]['bus'] : (stripos($lv->domain_get_machine($res), 'q35')!==false ? 'sata': 'ide'), 'drivers' => (!empty($medias) && !empty($medias[1]) && array_key_exists('file', $medias[1])) ? $medias[1]['file'] : '', 'driversbus' => (!empty($medias) && !empty($medias[1]) && array_key_exists('bus', $medias[1])) ? $medias[1]['bus'] : (stripos($lv->domain_get_machine($res), 'q35')!==false ? 'sata': 'ide') ], 'disk' => $arrDisks, 'gpu' => $arrGPUDevices, 'audio' => $arrAudioDevices, 'pci' => $arrOtherDevices, 'nic' => $arrNICs, 'usb' => $arrUSBDevs, 'shares' => $lv->domain_get_mount_filesystems($res) ]; } function create_vdisk(&$new) { global $lv; $index = 0; foreach ($new['disk'] as $i => $disk) { $index++; if ($disk['new']) { $disk = $lv->create_disk_image($disk, $new['domain']['name'], $index); if ($disk['error']) return $disk['error']; $new['disk'][$i] = $disk; } } return false; } function array_update_recursive(&$old, &$new) { $hostold = $old['devices']['hostdev']; // existing devices including custom settings $hostnew = $new['devices']['hostdev']; // GUI generated devices // update USB & PCI host devices foreach ($hostnew as $key => $device) { $auto = $device['tag']; $vendor = $device['source']['vendor']['@attributes']['id']; $remove_usb = $remove_pci = false; [$product,$remove_usb] = my_explode('#',$device['source']['product']['@attributes']['id']); $pci = $device['source']['address']['@attributes']; [$function,$remove_pci] = my_explode('#',$pci['function']); if ($remove_usb || $remove_pci) unset($new['devices']['hostdev'][$key]); foreach ($hostold as $k => $d) { $v = $d['source']['vendor']['@attributes']['id']; $p = $d['source']['product']['@attributes']['id']; $p2 = $d['source']['address']['@attributes']; if ($v && $p && $v==$vendor && $p==$product) unset($old['devices']['hostdev'][$k]); if ($p2['bus'] && $p2['slot'] && $p2['function'] && $p2['bus']==$pci['bus'] && $p2['slot']==$pci['slot'] && $p2['function']==$function) unset($old['devices']['hostdev'][$k]); } } // remove and rebuild usb controllers $devices = $old['devices']['controller']; foreach ($devices as $key => $controller) { if ($controller['@attributes']['type']=='usb') unset($old['devices']['controller'][$key]); } // preserve existing disk driver settings foreach ($new['devices']['disk'] as $key => $disk) { $source = $disk['source']['@attributes']['file']; foreach ($old['devices']['disk'] as $k => $d) if ($source==$d['source']['@attributes']['file']) $new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes']; } // settings not in the GUI, but maybe customized unset($new['clock']); // preserve vnc/spice port settings // unset($new['devices']['graphics']['@attributes']['port'],$new['devices']['graphics']['@attributes']['autoport']); if (!$new['devices']['graphics']) unset($old['devices']['graphics']); // update parent arrays if (!$old['devices']['hostdev']) unset($old['devices']['hostdev']); if (!$new['devices']['hostdev']) unset($new['devices']['hostdev']); // preserve tpm if (!$new['devices']['tpm']) unset($old['devices']['tpm']); // remove existing auto-generated settings unset($old['cputune']['vcpupin'],$old['devices']['video'],$old['devices']['disk'],$old['devices']['interface'],$old['devices']['filesystem'],$old['cpu']['@attributes'],$old['os']['boot'],$old['os']['loader'],$old['os']['nvram']); // Remove old CPU cache and features unset($old['cpu']['cache'], $old['cpu']['feature']) ; unset($old['features']['hyperv'],$old['devices']['channel']) ; // set namespace $new['metadata']['vmtemplate']['@attributes']['xmlns'] = 'unraid'; } function getVMUSBs($strXML){ $arrValidUSBDevices = getValidUSBDevices() ; foreach($arrValidUSBDevices as $key => $data) { $array[$key] = [ 'id' => $data['id'], 'name' => $data["name"], 'checked' => '', 'startupPolicy' => '', 'usbboot' => '' ]; } if ($strXML !="") { $VMxml = new SimpleXMLElement($strXML); $VMUSB=$VMxml->xpath('//devices/hostdev[@type="usb"]') ; foreach($VMUSB as $USB){ $vendor=$USB->source->vendor->attributes()->id ; $product=$USB->source->product->attributes()->id ; $startupPolicy=$USB->source->attributes()->startupPolicy ; $usbboot= $USB->boot->attributes()->order ; $id = str_replace('0x', '', $vendor . ':' . $product) ; $found = false ; foreach($arrValidUSBDevices as $key => $data) { if ($data['id'] == $id) { $array[$key]['checked'] = "checked" ; $array[$key]['startupPolicy'] = $startupPolicy ; $array[$key]['usbboot'] = $usbboot ; $found = true ; break ; } } if (!$found) { $array[] = [ 'id' => $id, 'name' => _("USB device is missing"), 'checked' => 'checked', 'startupPolicy' => $startupPolicy, 'usbboot' => $usbboot ]; } } } return $array ; } function sharesOnly($disk) { return strpos('Data,Cache',$disk['type'])!==false && $disk['exportable']=='yes'; } function getUnraidShares(){ $shares = parse_ini_file('state/shares.ini',true); uksort($shares,'strnatcasecmp'); $arrreturn[] = "Manual" ; foreach ($shares as $share) { $arrreturn[] = "User:".$share["name"] ; } $disks = parse_ini_file('state/disks.ini',true); $disks = array_filter($disks,'sharesOnly'); foreach ($disks as $name => $disk) { $arrreturn[] = "Disk:".$name ; } return $arrreturn ; } function getgastate($res) { global $lv ; $xml = new SimpleXMLElement($lv->domain_get_xml($res)) ; $data = $xml->xpath('//channel/target[@name="org.qemu.guest_agent.0"]/@state') ; $data = $data[0]->state ; return $data ; } function getmemoryBacking($res) { global $lv ; $xml = $lv->domain_get_xml($res) ; $memoryBacking = new SimpleXMLElement($xml); $memorybacking = $memoryBacking->memoryBacking ; return json_encode($memorybacking); ; } function getchannels($res) { global $lv ; $xml = $lv->domain_get_xml($res) ; $x = strpos($xml,"", 0) ; $z=$y ; while ($y!=false) { $y = strpos($xml,"", $z +10) ; if ($y != false) $z =$y ; } $channels = substr($xml,$x, ($z + 10) -$x) ; return $channels ; } function getcopypaste($res) { $channels = getchannels($res) ; $spicevmc = $qemuvdaagent = $copypaste = false ; if (strpos($channels,"spicevmc",0)) $spicevmc = true ; if (strpos($channels,"qemu-vdagent",0)) $qemuvdaagent = true ; if ($spicevmc || $qemuvdaagent) $copypaste = true ; else $copypaste = false ; return $copypaste ; } function compare_creationtime($a, $b) { return strnatcmp($a['creationtime'], $b['creationtime']); } function compare_creationtimelt($a, $b) { return $a['creationtime'] < $b['creationtime']; } function getvmsnapshots($vm) { $snaps=array() ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; return $snaps ; } function write_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; if (!is_dir($dbpath)) mkdir($dbpath) ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ; $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; $a = simplexml_load_string($snapshot_xml) ; $a = json_encode($a) ; $b= json_decode($a, TRUE); $vmsnap = $b["name"] ; $snaps[$vmsnap]["name"]= $b["name"]; $snaps[$vmsnap]["parent"]= $b["parent"] ; $snaps[$vmsnap]["state"]= $b["state"]; $snaps[$vmsnap]["desc"]= $b["description"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } $snaps[$vmsnap]['backing'][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vmsnap]['backing'][$rev] = $reversed ; } $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ; $parendfileinfo = pathinfo($parentfind[1]) ; $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"]; $snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ; if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ; if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; file_put_contents($dbpath."/snapshots.db",$value) ; } function refresh_snapshots_database($vm) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; if (!is_dir($dbpath)) mkdir($dbpath) ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; foreach($snaps as $vmsnap=>$snap) $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } $snaps[$vmsnap]['backing'][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vmsnap]['backing'][$rev] = $reversed ; } $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ; $parendfileinfo = pathinfo($parentfind[1]) ; $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"]; $snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ; if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; $res = $lv->get_domain_by_name($vm); if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; #Remove any NVRAMs that are no longer valid. # Get uuid $vmuuid = $lv->domain_get_uuid($vm) ; #Get list of files #$filepath = "/etc/libvirt/qemu/nvram/'.$uuid*" ; #$snapshotname" $filepath = "/etc/libvirt/qemu/nvram/$vmuuid*" ; #$snapshotname" $nvram_files=glob($filepath) ; foreach($nvram_files as $key => $nvram_file) { if ($nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi.fd" || $nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi-tpm.fd" ) unset($nvram_files[$key]) ; foreach ($snaps as $snapshotname => $snap) { $tpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi-tpm.fd" ; $nontpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi.fd" ; if ($nvram_file == $tpmfilename || $nvram_file == $nontpmfilename ) { unset($nvram_files[$key]) ;} } } foreach ($nvram_files as $nvram_file) unlink($nvram_file) ; file_put_contents($dbpath."/snapshots.db",$value) ; } function delete_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; unset($snaps[$name]) ; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; file_put_contents($dbpath."/snapshots.db",$value) ; return true ; } function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") { global $lv ; #Get State $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); $state = $lv->domain_state_translate($dom['state']); #Get disks for --diskspec $disks =$lv->get_disk_stats($vm) ; $diskspec = "" ; $capacity = 0 ; if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; if ($snapshotdesc != "") $snapshotdesc = " --description '$snapshotdesc'" ; foreach($disks as $disk) { $file = $disk["file"] ; $pathinfo = pathinfo($file) ; $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; $capacity = $capacity + $disk["capacity"] ; } $dirpath = $pathinfo["dirname"] ; #get memory $mem = $lv->domain_get_memory_stats($vm) ; $memory = $mem[6] ; if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory"'.$name.'".mem,snapshot=external' ; else $memspec = "" ; $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ; if ($state == "running") { $cmdstr .= " --live ".$memspec.$diskspec ; $capacity = $capacity + $memory ; } else { $cmdstr .= " --disk-only ".$diskspec ; } #Check free space. $dirfree = disk_free_space($pathinfo["dirname"]) ; $capacity *= 1 ; if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} #Copy nvram if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; $xmlfile = $pathinfo["dirname"].'/"'.$name.'".running' ; if ($state == "running") exec("virsh dumpxml '$vm' > $xmlfile",$outxml,$rtnxml) ; $test = false ; if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; if (strpos(" ".$output[0],"error") ) { $arrResponse = ['error' => substr($output[0],6) ] ; } else { # Remove nvram snapshot $arrResponse = ['success' => true] ; } write_snapshots_database("$vm","$name") ; #remove meta data $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; return $arrResponse ; } function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { global $lv ; $snapslist= getvmsnapshots($vm) ; $disks =$lv->get_disk_stats($vm) ; switch ($snapslist[$snap]['state']) { case "shutoff": case "running": #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); $state = $lv->domain_state_translate($dom['state']); # if VM running shutdown. Record was running. if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ; # Wait for shutdown? # GetXML $strXML= $lv->domain_get_xml($res) ; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks and update path. $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if ($diskname == "hdc") { $primarypathinfo = pathinfo($path) ; $primarypath = $primarypathinfo['dirname'] ; } $item = array_search($path,$snapslist[$snap]['backing'][$diskname]) ; $newpath = $snapslist[$snap]['backing'][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; foreach($xmlobj['devices']['disk'] as $ddk => $dd){ if ($dd['target']["@attributes"]['dev'] == $diskname) { $xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = "$newpath" ; $xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ; } } } $xml = custom::createXML('domain',$xmlobj)->saveXML(); $new = $lv->domain_define($xml); if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ; # remove snapshot meta data and images for all snpahots. foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path) && $action == "yes") unlink("$path") ; $item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ; $item++ ; while($item > 0) { if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ; $newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ; if (is_file($newpath) && $action == "yes") unlink("$newpath") ; $item++ ; } } uasort($snapslist,'compare_creationtimelt') ; foreach($snapslist as $s) { $name = $s['name'] ; $xmlfile = $primarypath."/$name.running" ; $memoryfile = $primarypath."/memory$name.mem" ; if ($snapslist[$snap]['state'] == "running") { # Set XML to saved XML $xml = file_get_contents($xmlfile) ; $xmlobj = custom::createArray('domain',$xml) ; $xml = custom::createXML('domain',$xmlobj)->saveXML(); $rtn = $lv->domain_define($xml) ; # Resotre Memeory. $makerun = true ; if ($makerun == true) exec("virsh restore $memoryfile") ; #exec("virsh restore $memoryfile") ; } #Delete Metadata only. if ($actionmeta == "yes") { $ret = delete_snapshots_database("$vm","$name") ; } if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ; if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ; if ($s['name'] == $snap) break ; } #if VM was started restart. if ($state == 'running' && $snapslist[$snap]['state'] != "running") { $arrResponse = $lv->domain_start($vm) ; } if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; break ; } $arrResponse = ['success' => true] ; return($arrResponse) ; } function vm_snapimages($vm, $snap, $only) { global $lv ; $snapslist= getvmsnapshots($vm) ; $data = "

Images and metadata to remove if tickbox checked.
" ; $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } $snaps[$vm][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; $pathinfo = pathinfo($file) ; $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; $capacity = $capacity + $disk["capacity"] ; } $snapdisks= $snapslist[$snap]['disks'] ; foreach ($snapdisks as $diskkey => $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $snapdisk["source"]["@attributes"]["file"] ; if (is_file($path)) $data .= "$path
" ; $item = array_search($path,$snaps[$vm]["r".$diskname]) ; $item++ ; if ($only == 0) $item = 0 ; while($item > 0) { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; if (is_file($path)) $data .= "$newpath
" ; $item++ ; } } $data .= "
Snapshots metadata to remove." ; if ($only == 0) { $data .= "
$snap"; } else { uasort($snapslist,'compare_creationtimelt') ; foreach($snapslist as $s) { $name = $s['name'] ; $data .= "
$name"; if ($s['name'] == $snap) break ; } } return($data) ; } function vm_snapremove($vm, $snap) { global $lv ; $snapslist= getvmsnapshots($vm) ; $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } $snaps[$vm][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; $pathinfo = pathinfo($file) ; } # GetXML $strXML= $lv->domain_get_xml($res) ; $xmlobj = custom::createArray('domain',$strXML) ; # Process disks. $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; $item = array_search($path,$snaps[$vm][$diskname]) ; if ($item!==false) { $data = ["error" => "Image currently active for this domain."] ; return ($data) ; } } $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path)) { if(!unlink("$path")) { $data = ["error" => "Unable to remove image file $path"] ; return ($data) ; } } } # Delete NVRAM if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ; $ret = delete_snapshots_database("$vm","$snap") ; if(!$ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else $data = ["success => 'true"] ; return($data) ; } function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { global $lv ; /* NAME blockcommit - Start a block commit operation. SYNOPSIS blockcommit [--bandwidth ] [--base ] [--shallow] [--top ] [--active] [--delete] [--wait] [--verbose] [--timeout ] [--pivot] [--keep-overlay] [--async] [--keep-relative] [--bytes] DESCRIPTION Commit changes from a snapshot down to its backing image. OPTIONS [--domain] domain name, id or uuid [--path] fully-qualified path of disk --bandwidth bandwidth limit in MiB/s --base path of base file to commit into (default bottom of chain) --shallow use backing file of top as base --top path of top file to commit from (default top of chain) --active trigger two-stage active commit of top file --delete delete files that were successfully committed --wait wait for job to complete (with --active, wait for job to sync) --verbose with --wait, display the progress --timeout implies --wait, abort if copy exceeds timeout (in seconds) --pivot implies --active --wait, pivot when commit is synced --keep-overlay implies --active --wait, quit when commit is synced --async with --wait, don't wait for cancel to finish --keep-relative keep the backing chain relatively referenced --bytes the bandwidth limit is in bytes/s rather than MiB/s blockcommit Debian --path /mnt/user/domains/Debian/vdisk1.S20230513120410qcow2 --verbose --pivot --delete */ # Error if VM Not running. $snapslist= getvmsnapshots($vm) ; $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $path = $disk['file'] ; $cmdstr = "virsh blockcommit '$vm' --path '$path' --verbose " ; if ($pivot == "yes") $cmdstr .= " --pivot " ; if ($action == "yes") $cmdstr .= " --delete " ; # Process disks and update path. $snapdisks=($snapslist[$snap]['disks']) ; if ($base != "--base" && $base != "") { #get file name from snapshot. $snapdisks=($snapslist[$base]['disks']) ; $basepath = "" ; foreach ($snapdisks as $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname != $disk['device']) continue ; $basepath = $snapdisk["source"]["@attributes"]["file"] ; } if ($basepath != "") $cmdstr .= " --base '$basepath' "; } if ($top != "--top" && $top !="") { #get file name from snapshot. $snapdisks=($snapslist[$top]['disks']) ; $toppath = "" ; foreach ($snapdisks as $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname != $disk['device']) continue ; $toppath = $snapdisk["source"]["@attributes"]["file"] ; } if ($toppath != "") $cmdstr .= " --top '$toppath' "; } $error = execCommand_nchan($cmdstr,$path) ; if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; return($arrResponse) ; } else { $arrResponse = ['success' => true] ; } } # Delete NVRAM #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ; refresh_snapshots_database($vm) ; $ret = $ret = delete_snapshots_database("$vm","$snap") ; ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else $data = ["success => 'true"] ; return $data ; } function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { global $lv ; /* NAME blockpull - Populate a disk from its backing image. SYNOPSIS blockpull [--bandwidth ] [--base ] [--wait] [--verbose] [--timeout ] [--async] [--keep-relative] [--bytes] DESCRIPTION Populate a disk from its backing image. OPTIONS [--domain] domain name, id or uuid [--path] fully-qualified path of disk --bandwidth bandwidth limit in MiB/s --base path of backing file in chain for a partial pull --wait wait for job to finish --verbose with --wait, display the progress --timeout with --wait, abort if pull exceeds timeout (in seconds) --async with --wait, don't wait for cancel to finish --keep-relative keep the backing chain relatively referenced --bytes the bandwidth limit is in bytes/s rather than MiB/s */ $snapslist= getvmsnapshots($vm) ; $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } $snaps[$vm][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; } $snaps_json=json_encode($snaps,JSON_PRETTY_PRINT) ; $pathinfo = pathinfo($file) ; $dirpath = $pathinfo["dirname"] ; file_put_contents("$dirpath/image.tracker",$snaps_json) ; foreach($disks as $disk) { $path = $disk['file'] ; $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ; $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait " ; # Process disks and update path. $snapdisks=($snapslist[$snap]['disks']) ; if ($base != "--base" && $base != "") { #get file name from snapshot. $snapdisks=($snapslist[$base]['disks']) ; $basepath = "" ; foreach ($snapdisks as $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname != $disk['device']) continue ; $basepath = $snapdisk["source"]["@attributes"]["file"] ; } if ($basepath != "") $cmdstr .= " --base '$basepath' "; } if ($action) $cmdstr .= " $action "; $error = execCommand_nchan($cmdstr,$path) ; if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; return($arrResponse) ; } else { # Remove nvram snapshot $arrResponse = ['success' => true] ; } } refresh_snapshots_database($vm) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else $data = ["success => 'true"] ; return $data ; } function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { /* NAME blockcopy - Start a block copy operation. SYNOPSIS blockcopy [--dest ] [--bandwidth ] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout ] [--pivot] [--finish] [--async] [--xml ] [--format ] [--granularity ] [--buf-size ] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml] DESCRIPTION Copy a disk backing image chain to dest. OPTIONS [--domain] domain name, id or uuid [--path] fully-qualified path of source disk --dest path of the copy to create --bandwidth bandwidth limit in MiB/s --shallow make the copy share a backing chain --reuse-external reuse existing destination --blockdev copy destination is block device instead of regular file --wait wait for job to reach mirroring phase --verbose with --wait, display the progress --timeout implies --wait, abort if copy exceeds timeout (in seconds) --pivot implies --wait, pivot when mirroring starts --finish implies --wait, quit when mirroring starts --async with --wait, don't wait for cancel to finish --xml filename containing XML description of the copy destination --format format of the destination file --granularity power-of-two granularity to use during the copy --buf-size maximum amount of in-flight data during the copy --bytes the bandwidth limit is in bytes/s rather than MiB/s --transient-job the copy job is not persisted if VM is turned off --synchronous-writes the copy job forces guest writes to be synchronously written to the destination --print-xml print the XML used to start the copy job instead of starting the job */ } ?>