Files
webgui/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php

1586 lines
52 KiB
PHP

<?PHP
/* Copyright 2005-2021, Lime Technology
* Copyright 2015-2021, Derek Macias, Eric Schultz, Jon Panozzo.
* Copyright 2012-2021, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
/**
* Array2XML: A class to convert array in PHP to XML
* It also takes into account attributes names unlike SimpleXML in PHP
* It returns the XML in form of DOMDocument class for further manipulation.
* It throws exception if the tag name or attribute name has illegal chars.
*
* Author : Lalit Patel
* Website: http://www.lalit.org/lab/convert-php-array-to-xml-with-attributes
* License: Apache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0
* Version: 0.1 (10 July 2011)
* Version: 0.2 (16 August 2011)
* - replaced htmlentities() with htmlspecialchars() (Thanks to Liel Dulev)
* - fixed a edge case where root node has a false/null/0 value. (Thanks to Liel Dulev)
* Version: 0.3 (22 August 2011)
* - fixed tag sanitize regex which didn't allow tagnames with single character.
* Version: 0.4 (18 September 2011)
* - Added support for CDATA section using @cdata instead of @value.
* Version: 0.5 (07 December 2011)
* - Changed logic to check numeric array indices not starting from 0.
* Version: 0.6 (04 March 2012)
* - Code now doesn't @cdata to be placed in an empty array
* Version: 0.7 (24 March 2012)
* - Reverted to version 0.5
* Version: 0.8 (02 May 2012)
* - Removed htmlspecialchars() before adding to text node or attributes.
*
* Usage:
* $xml = Array2XML::createXML('root_node_name', $php_array);
* echo $xml->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";
// 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<gpuvendor>.+) \[.+\]/', $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<id>\S+) \"(?P<type>[^"]+) \[(?P<typeid>[a-f0-9]{4})\]\" \"(?P<vendorname>[^"]+) \[(?P<vendorid>[a-f0-9]{4})\]\" \"(?P<productname>[^"]+) \[(?P<productid>[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<gpuname>.+)\]/', $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<id>\S+)(?P<name>.*)$/', $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)"], ["","","&#169;","&#174;","&#8482;"], $cpu[0]) . '@', 1);
return trim($strCPUModel);
}
function getValidNetworks() {
global $lv;
$arrValidNetworks = [];
exec("ls --indicator-style=none /sys/class/net|grep -Po '^((vir)?br|vhost)[0-9]+(\.[0-9]+)?'",$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),
'password' => $lv->domain_get_vnc_password($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' => '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,"<channel", 0) ;
$y = strpos($xml,"</channel>", 0) ;
$z=$y ;
while ($y!=false) {
$y = strpos($xml,"</channel>", $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 vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID) {
global $lv,$domain_cfg ;
/*
Clone.
Stopped only.
Get new VM Name
Extract XML for VM to be cloned.
Check if directory exists.
Check for disk space
Stop VM Starting until clone is finished or fails.
Create new directory for Clone.
Update paths with new directory
Create new UUID
Create new MAC Address for NICs
Create VM Disks from source. Options full or Sparce. Method of copy?
release orginal VM to start.
If option to edit, show VMUpdate
*/
$uuid = $lv->domain_get_uuid($clone) ;
write("addLog\0".htmlspecialchars(_("Checking if clone exists")));
if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return false ;}
#VM must be shutdown.
$res = $lv->get_domain_by_name($vm);
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
$vmxml = $lv->domain_get_xml($res) ;
file_put_contents("/tmp/cloningxml" ,$vmxml) ;
# if VM running shutdown. Record was running.
if ($state != 'shutoff') {write("addLog\0".htmlspecialchars(_("Shuting down $vm current $state"))); $arrResponse = $lv->domain_destroy($vm) ; }
# Wait for shutdown?
$disks =$lv->get_disk_stats($vm) ;
$capacity = 0 ;
foreach($disks as $disk) {
$file = $disk["file"] ;
$pathinfo = pathinfo($file) ;
$filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
$capacity = $capacity + $disk["capacity"] ;
}
$dirpath = $pathinfo["dirname"] ;
#Check free space.
write("addLog\0".htmlspecialchars("Checking for free space"));
$dirfree = disk_free_space($pathinfo["dirname"]) ;
$sourcedir = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($pathinfo["dirname"])." 2>/dev/null"));
$repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]);
$repdirfree = disk_free_space($repdir) ;
$reflink = true ;
$capacity *= 1 ;
if ($free == "yes" && $repdirfree < $capacity) { $reflink = false ;}
if ($free == "yes" && $dirfree < $capacity) { write("addLog\0".htmlspecialchars(_("Insufficent storage for clone"))); return false ;}
#Clone XML
$uuid = $lv->domain_get_uuid($vm) ;
$config=domain_to_config($uuid) ;
$config["domain"]["name"] = $clone ;
$config["domain"]["uuid"] = $lv->domain_generate_uuid() ;
foreach($config["nic"] as $index => $detail) {
$config["nic"][$index]["mac"] = $lv->generate_random_mac_addr() ;
}
$config["domain"]["type"] = "kvm";
$usbs = getVMUSBs($vmxml) ;
foreach($usbs as $i => $usb) {
if ($usb["checked"] == "checked") continue ;
unset($usbs[$i]) ;
}
$config["usb"] = $usbs ;
$files_exist = false ;
$files_clone = array() ;
foreach ($config["disk"] as $diskid => $disk) {
$file_clone[$diskid]["source"] = $config["disk"][$diskid]["new"] ;
$config["disk"][$diskid]["new"] = str_replace($vm,$clone,$config["disk"][$diskid]["new"]) ;
$pi = pathinfo($config["disk"][$diskid]["new"]) ;
$isdir = is_dir($pi['dirname']) ;
if (is_file($config["disk"][$diskid]["new"])) $file_exists = true ;
$file_clone[$diskid]["target"] = $config["disk"][$diskid]["new"] ;
}
$clonedir = $domain_cfg['DOMAINDIR'].$clone ;
if (!is_dir($clonedir)) {
mkdir($clonedir,0777,true) ;
chown($clonedir, 'nobody');
chgrp($clonedir, 'users');
}
write("addLog\0".htmlspecialchars("Checking for image files"));
if ($file_exists && $overwrite != "yes") { write("addLog\0".htmlspecialchars(_("New image file names exist and Overwrite is not allowed"))); return( false) ; }
#Create duplicate files.
foreach($file_clone as $diskid => $disk) {
$target = $disk['target'] ;
$source = $disk['source'] ;
$sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null"));
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
#var_dump($repsrc,$reptgt) ;
$cmdstr = "cp --reflink=always '$repsrc' '$reptgt'" ;
if ($reflink == true) { $refcmd = $cmdstr ; } else {$refcmd = false; }
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ;
$error = execCommand_nchan($cmdstr,$path,$refcmd) ;
if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; }
}
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Completing Clone").": </legend><p class='logLine'></p><span id='wait-$waitID'></span></fieldset>");
write("addLog\0".htmlspecialchars("Creating new XML $clone"));
$xml = $lv->config_to_xml($config, true) ;
file_put_contents("/tmp/clonexml" ,$xml) ;
$rtn = $lv->domain_define($xml) ;
return($rtn) ;
}
?>