<?PHP
/* Copyright 2005-2025, Lime Technology
 * Copyright 2012-2025, Bergware International.
 * Copyright 2015-2021, Derek Macias, Eric Schultz, Jon Panozzo.
 *
 * 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 ??= ($_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,
					'vcpus' => 2,
					'vcpu' => [0,1],
				],
				'media' => [
					'cdrombus' => 'sata',
					'driversbus' => 'sata' ,
				],			
				'disk' => [
					[
						'size' => '64G'
					]
				],
				'nic'=>[
					[
						'model' => 'e1000'
					]
				]
			]
		],

		'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'
					]
				]
			]
		],


		' 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'
		]
	];


	$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'
		]
	];}

	$arrDefaultClocks = [
		"windows" => [
			"hpet" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"hypervclock" => [
				"present" => "yes",
				"tickpolicy" => "delay"
			],
			"pit" => [
				"present" => "yes",
				"tickpolicy" => "delay"
			],
			"rtc" => [
				"present" => "yes",
				"tickpolicy" => "catchup"
			]
		],
		"hyperv" => [
			"hpet" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"hypervclock" => [
				"present" => "yes",
				"tickpolicy" => "delay"
			],
			"pit" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"rtc" => [
				"present" => "no",
				"tickpolicy" => "catchup"
			]
		] ,
		"other" => [
			"hpet" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"hypervclock" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"pit" => [
				"present" => "yes",
				"tickpolicy" => "delay"
			],
			"rtc" => [
				"present" => "yes",
				"tickpolicy" => "catchup"
			]
		]
	];

	#<model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>

	$arrDisplayOptions = [
		"H1.16M" => [
			"text" => "1 Display 16Mb Memory",
			"qxlxml" => "ram='65536' vram='16384' vgamem='16384' heads='1' primary='yes'",
		],
		"H1.32M" => [
			"text" => "1 Display 32Mb Memory",
			"qxlxml" => "ram='65536' vram='32768' vgamem='32768' heads='1' primary='yes'",
		],
		"H1.64M" => [
			"text" => "1 Display 64Mb Memory",
			"qxlxml" => "ram='65536' vram='65536' vram64='65535' vgamem='65536' heads='1' primary='yes'",
		],
		"H1.128M" => [
			"text" => "1 Display 128Mb Memory",
			"qxlxml"=> "ram='65536' vram='131072' vram64='131072' vgamem='65536' heads='1' primary='yes'",
		],
		"H1.256M" => [
			"text" => "1 Display 256Mb Memory",
			"qxlxml" => "ram='65536' vram='262144' vram64='262144' vgamem='65536' heads='1' primary='yes'",
		],
		"H2.64M" => [
			"text" => "2 Displays 64Mb Memory",
			"qxlxml" => "ram='65536' vram='65536' vram64='65535' vgamem='65536' heads='2' primary='yes'",
		],
		"H2.128M" => [
			"text" => "2 Displays 128Mb Memory",
			"qxlxml" => "ram='65536' vram='131072'vram64='131072' vgamem='65536' heads='2' primary='yes'",
		],
		"H2.256M" => [
			"text" => "2 Displays 256Mb Memory",
			"qxlxml" => "ram='65536' vram='262144'vram64='262144' vgamem='65536' heads='2' primary='yes'",
		],
		"H4.64M" => [
			"text" => "4 Displays 64Mb Memory",
			"qxlxml" => "ram='65536' vram='65536' vram64='65535' vgamem='65536' heads='4' primary='yes'",
		],
		"H4.128M" => [
			"text" => "4 Displays 128Mb Memory",
			"qxlxml" => "ram='65536' vram='131072'vram64='131072' vgamem='65536' heads='4' primary='yes'",
		],
		"H4.256M" => [
			"text" => "4 Displays 256Mb Memory",
			"qxlxml"=> "ram='65536' vram='262144' vram64='262144' vgamem='65536' heads='4' primary='yes'",
		],
		];

	// Read configuration file (guaranteed to exist)
	$domain_cfgfile = "/boot/config/domain.cfg";
	$domain_cfg = parse_ini_file($domain_cfgfile);

	if ( ($domain_cfg['DEBUG'] ?? false) != "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)/';

		# "System peripheral [0880]" "Global unichip corp. [1ac1]" "Coral Edge Tpu [089a]" -pff "Global unichip corp. [1ac1]" "Coral Edge Tpu [089a]"
		#                    typeid													productid
		# file is csv typeid:productid
		#
		if (is_file("/boot/config/VMPCIOverride.cfg")) {
			$arrWhiteListOverride = str_getcsv(file_get_contents("/boot/config/VMPCIOverride.cfg"));
		}
		$arrWhiteListOverride[] = "0880:089a";

		$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;
				}

				$overrideCheck = "{$arrMatch['typeid']}:{$arrMatch['productid']}";
				if (in_array($overrideCheck,$arrWhiteListOverride) ) $boolBlacklisted = false;

				$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);
					}
				}

				// Attempt to get the boot_vga driver for this pci device
				$strBootVGA = '';
				if (is_file('/sys/bus/pci/devices/0000:'.$arrMatch['id'].'/boot_vga') && $strClass == 'vga') {
					$strFileVal = file_get_contents('/sys/bus/pci/devices/0000:'.$arrMatch['id'].'/boot_vga');
					if (!empty($strFileVal)) {
						$strBootVGA = trim($strFileVal);
					}
				}

				// Clean up the vendor and product name
				$arrMatch['vendorname'] = sanitizeVendor($arrMatch['vendorname']);
				$arrMatch['productname'] = sanitizeProduct($arrMatch['productname']);

				$arrValidPCIDevices[$arrMatch['id']] = [
					'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,
					'bootvga' => $strBootVGA,
					'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 getValidSoundCards() {
		$arrValidSoundCards = [
			'ich6'		=> ['name' => 'ich6','id' => 'virtual::ich6'],
			'ich7' 		=> ['name' => 'ich7','id' => 'virtual::ich7'],
			'ich9' 		=> ['name' => 'ich9','id' => 'virtual::ich9'],
			'ac97' 		=> ['name' => 'ac97','id' => 'virtual::ac97'],
			'es1370' 	=> ['name' => 'es1370','id' => 'virtual::es1370'],
			'pcspk' 	=> ['name' => 'pcspk','id' => 'virtual::pcspk'],
			'sb16' 		=> ['name' => 'sb16','id' => 'virtual::sb16'],
			'usb' 		=> ['name' => 'usb','id' => 'virtual::usb'],
			'virtio' 	=> ['name' => 'virtio','id' => 'virtual::virtio'],
		];
		return $arrValidSoundCards;
	}

	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;
	}

	function getValidevDev() {
		$inputevdev = array_merge(glob("/dev/input/by-id/*event-kbd"),glob("/dev/input/by-id/*event-mouse"));
		return $inputevdev;
	}

	function getevDev($res) {
		global $lv;
		$xml = $lv->domain_get_xml($res);
		$xmldoc = new SimpleXMLElement($xml);
		$xmlpath = $xmldoc->xpath('//devices/input[@type="evdev"] ');
		$evdevs = [];
		foreach ($xmlpath as $i => $evDev) {
		$evdevs[$i] = [
			'dev' => $evDev->source->attributes()->dev,
			'grab' => $evDev->source->attributes()->grab,
			'repeat' => $evDev->source->attributes()->repeat,
			'grabToggle' => $evDev->source->attributes()->grabToggle
			];
		}
		if (empty($evdevs)) $evdevs[0] = ['dev'=>"",'grab'=>"",'repeat'=>"",'grabToggle'=>""];
		return $evdevs;
	}

	$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); #PHPS
					$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[$arrMatch['id']] = [
					'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 ValidateMachineType($machinetype) {
		$machinetypes=getValidMachineTypes();
		$type = substr($machinetype,0,strpos($machinetype,'-',3));
		foreach($machinetypes as $machinetypekey => $machinedetails) {
			$check_type = substr($machinetypekey,0,strlen($type));
			if ($check_type == $type) break;
		}
		return($machinetypekey);
	}

	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 getValidDiskDiscard() {
		$arrValidDiskDiscard = [
			'ignore' => 'Ignore(No Trim)',
			'unmap' => 'Unmap(Trim)',
		];
		return $arrValidDiskDiscard;
	}

	function getValidCdromBuses() {
		$arrValidCdromBuses = [
			'scsi' => 'SCSI',
			'sata' => 'SATA',
			'ide' => 'IDE',
			'usb' => 'USB'
		];
		return $arrValidCdromBuses;
	}

	function getValidVNCModels() {
		$arrValidVNCModels = [
			'cirrus' => 'Cirrus',
			'qxl' => 'QXL (best)',
			'virtio' => 'Virtio(2d)',
			'virtio3d' => 'Virtio(3d)',
			'vmvga' => 'vmvga'
		];
		return $arrValidVNCModels;
	}
	function getValidVMRCProtocols() {
		$arrValidProtocols = [
			'vnc' => 'VNC',
			'spice' => 'SPICE'
		];
		return $arrValidProtocols;
	}

	function getValidKeyMaps() {
		$arrValidKeyMaps = [
			'none' => 'No Keymap',
			'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,$libvirt_running;
		$arrValidNetworks = [];
		exec("ls --indicator-style=none /sys/class/net | grep -Po '^virbr[0-9]+'",$arrBridges);
		exec("ls --indicator-style=none /sys/class/net | grep -Po '^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges);
		// remove redundant references of bridge and bond interfaces
		$remove = [];
		foreach ($arrBridges as $name) {
			if (substr($name,0,4) == 'bond') {
				$remove = array_merge($remove, (array)@file("/sys/class/net/$name/bonding/slaves",FILE_IGNORE_NEW_LINES));
			} elseif (substr($name,0,2) == 'br') {
				$remove = array_merge($remove, array_map(function($n){return end(explode('/',$n));}, glob("/sys/class/net/$name/brif/*")));
			}
		}
		$arrValidNetworks['bridges'] = array_diff($arrBridges, $remove);

		// 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);
		$arrSoundCards = $lv->domain_get_sound_cards($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,
				'guest' => ['multi' => 'off' ],
				'render' => $lv->domain_get_vnc_render($res),
				'DisplayOptions' => $lv->domain_get_vnc_display_options($res),
			];
		}

		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'], 'guest' => $arrHostDev['guest']];
				continue;
			}

			$arrFoundAudioDevices = array_filter($arrValidAudioDevices, function($arrDev) use ($arrHostDev) {return ($arrDev['id'] == $arrHostDev['id']);});
			if (!empty($arrFoundAudioDevices)) {
				$arrAudioDevices[] = ['id' => $arrHostDev['id'], 'guest' => $arrHostDev['guest']];
				continue;
			}

			$arrFoundOtherDevices = array_filter($arrValidOtherDevices, function($arrDev) use ($arrHostDev) {return ($arrDev['id'] == $arrHostDev['id']);});
			if (!empty($arrFoundOtherDevices)) {
				$arrOtherDevices[] = ['id' => $arrHostDev['id'],'boot' => $arrHostDev['boot'], 'guest' => $arrHostDev['guest']];
				continue;
			}
		}
		if (empty($arrGPUDevices)) {
			$arrGPUDevices[] = [
				'id' => 'nogpu',
			];
		}

		if (!empty($arrSoundCards)) {
			foreach ($arrSoundCards as $sckey => $soundcard) $arrAudioDevices[] = ['id' => "virtual::".$soundcard['model']];
		}

		// 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' || basename($strPath) != 'vdisk'.($i+1).'.qcow2') {
				if (($disk['type'] == "qcow2" && (basename($strPath) == 'vdisk'.($i+1).'.qcow2')) || ($disk['type'] == "raw" && (basename($strPath) == 'vdisk'.($i+1).'.img'))) $default_option = "auto"; else $default_option = 'manual';
			}

			$arrDisks[] = [
				'new' => $strPath,
				'size' => '',
				'driver' => $disk['type'],
				'dev' => $disk['device'],
				'bus' => $disk['bus'],
				'discard' => $disk['discard'],
				'boot' => $disk['boot order'],
				'rotation' => $disk['rotation'],
				'serial' => $disk['serial'],
				'select' => $default_option
			];
		}
		if (empty($arrDisks)) {
			$arrDisks[] = [
				'new' => '',
				'size' => '',
				'driver' => 'raw',
				'dev' => 'hda',
				'select' => '',
				'bus' => 'virtio',
				'discard' => 'ignore',
				'rotation' => "0"
			];
		}

		// 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";
		$vmname = $lv->domain_get_name($res);
		$cmdline = null;
		$QEMUCmdline = getQEMUCmdLine($strDOMXML);
		$QEMUOverride = getQEMUOverride($strDOMXML);
		if (isset($QEMUCmdline)) $cmdline = $QEMUCmdline;
		if (isset($QEMUOverride) && isset($QEMUCmdline)) $cmdline .= "\n".$QEMUOverride;
		if (isset($QEMUOverride) && !isset($QEMUCmdline)) $cmdline = $QEMUOverride;
		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),
				'cpupmemlmt' => $lv->domain_get_cpu_pmem_limit($res),
				'cpumigrate' => $lv->domain_get_cpu_migrate($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),
			'evdev' => getevDev($res),
			'qemucmdline' => $cmdline,
			'clocks' => getClocks($strDOMXML),
			'xml' => [
				'machine' => $lv->domain_get_xml($vmname, "//domain/os/*"),
				'devices' => $lv->get_xpath($vmname, "//domain/os/*"),
			],
		];
	}

	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 + scsi controllers
		$devices = $old['devices']['controller'];
		foreach ($devices as $key => $controller) {
			if ($controller['@attributes']['type']=='usb') unset($old['devices']['controller'][$key]);
			if ($controller['@attributes']['type']=='scsi') unset($old['devices']['controller'][$key]);
		}
		// preserve existing disk driver settings
		foreach ($new['devices']['disk'] as $key => $disk) {
			$source = $disk['source']['@attributes']['file'];
			if (isset($disk['driver']['@attributes']['discard'])) $discard = $disk['driver']['@attributes']['discard']; else $discard = null;
			foreach ($old['devices']['disk'] as $k => $d)
				if ($source==$d['source']['@attributes']['file']) {
					if (isset($discard)) $d['driver']['@attributes']['discard'] = $discard;
					$new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes'];
				}
		}
		// settings not in the GUI, but maybe customized
		unset($old['clock']);
		unset($old['devices']['input']);
		// preserve vnc/spice port settings
		// unset($new['devices']['graphics']['@attributes']['port'],$new['devices']['graphics']['@attributes']['autoport']);
		unset($old['devices']['sound']);
		unset($old['devices']['graphics']);
		if (!isset($new['devices']['graphics']['@attributes']['keymap']) && isset($old['devices']['graphics']['@attributes']['keymap'])) unset($old['devices']['graphics']['@attributes']['keymap']);
		// 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'], $old['cpu']['maxphysaddr']);
		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 ?? null;
		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 getClocks($xml) {
		$clocks = new SimpleXMLElement($xml);
		$clocks = json_decode(json_encode($clocks->clock),true);
		$arrClocks = [
			"hpet" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"hypervclock" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"pit" => [
				"present" => "no",
				"tickpolicy" => "delay"
			],
			"rtc" => [
				"present" => "no",
				"tickpolicy" => "delay"
			]
		];
		foreach ($clocks['timer'] as $timer) {
			$name = $timer["@attributes"]["name"];
			$tickpolicy = $timer["@attributes"]["tickpolicy"];
			$present = $timer["@attributes"]["present"];
			if (isset($present)) $arrClocks[$name]['present'] = $present;
			if (isset($tickpolicy)) { $arrClocks[$name]['tickpolicy'] = $tickpolicy; $arrClocks[$name]['present'] = 'yes'; }
		}
		return json_encode($arrClocks);
	}

	function getQEMUCmdLine($xml) {
		$x = strpos($xml,"<qemu:commandline>", 0);
		if ($x === false) return null;
		$y = strpos($xml,"</qemu:commandline>", 0) ;
		$z=$y;
		while ($y!=false) {
			$y = strpos($xml,"<qemu:commandline>", $z +19) ;
			if ($y != false) $z =$y ;
		}
		return substr($xml,$x, ($z + 19) -$x);
	}

	function getQEMUOverride($xml) {
		$x = strpos($xml,"<qemu:override>", 0);
		if ($x === false) return null;
		$y = strpos($xml,"</qemu:override>", 0) ;
		$z=$y;
		while ($y!=false) {
			$y = strpos($xml,"<qemu:override>", $z +16) ;
			if ($y != false) $z =$y ;
		}
		return substr($xml,$x, ($z + 16) -$x);
	}

	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, $regenmac) {
		global $lv,$domain_cfg,$arrDisplayOptions;
		/*
			Clone.

			Stopped only.

			Get new VM Name
			Extract XML for VM to be cloned.
			Check if snapshots.
			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
		*/
		$snaps = getvmsnapshots($vm);
		if (is_array($snaps)) {
			if (count($snaps) ) {write("addLog\0".htmlspecialchars(_("Clone of VM not currently supported if it has snapshots"))); $arrResponse =  ['error' => _("Clone of VM not currently supported if it has snapshots")]; return false;}
		}
		$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);
		$storage =  $lv->_get_single_xpath_result($vm, '//domain/metadata/*[local-name()=\'vmtemplate\']/@storage');
		if (empty($storage)) $storage = "default";
		# 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);
			$capacity = $capacity + $disk["capacity"];
		}

		#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"));
		if (!empty($sourcedir)) $repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]); else $repdir = $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();
		if ($regenmac == "yes") {
			write("addLog\0".htmlspecialchars(_("Regenerate MACs")));
			foreach($config["nic"] as $index => $detail) {
				$config["nic"][$index]["mac"] = $lv->generate_random_mac_addr();
			}
		} else write("addLog\0".htmlspecialchars(_("Retain existing MACs")));
		$config["domain"]["type"] = "kvm";

		$usbs = getVMUSBs($vmxml);
		foreach($usbs as $i => $usb) {
			if ($usb["checked"] == "checked") continue;
			unset($usbs[$i]);
		}
		$config["usb"] = $usbs;

		$file_exists = false;
		$file_clone = array();
		if ($config['disk'][0]['new'] != "") {
		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;
			write("addLog\0".htmlspecialchars(_("Checking from file:").$file_clone[$diskid]["source"]));
			write("addLog\0".htmlspecialchars(_("Checking to file:").$config["disk"][$diskid]["new"]));
			write("addLog\0".htmlspecialchars(_("File exists value:"). ($file_exists ? "True" : "False")));
			$file_clone[$diskid]["target"] = $config["disk"][$diskid]["new"];
			}

		if ($storage == "default") $clonedir = $domain_cfg['DOMAINDIR'].$clone; else $clonedir = str_replace('/mnt/user/', "/mnt/$storage/", $domain_cfg['DOMAINDIR']).$clone;
		if (!is_dir($clonedir)) {
			my_mkdir($clonedir,0777,true);
		}
		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)  {
			$reptgt = $target = $disk['target'];
			$repsrc = $source = $disk['source'];
			if ($target == $source) { write("addLog\0".htmlspecialchars(_("New image file is same as old")));  return( false); }
			if ($storage == "default") $sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null")); else $sourcerealdisk = $storage;
			if (!empty($sourcerealdisk)) {
			$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
			$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
			}

			$refresult = getFilesystemAndReflinkMode($repsrc);
			$refcmdaction = $refresult['reflink_mode'];
			$refcmdfs = $refresult['filesystem'];
			$cmdstr = "cp --reflink=$refcmdaction '$repsrc' '$reptgt'";
			write("addLog\0".htmlspecialchars(_("Reflink Action")." ".$refcmdaction." "._("for filesystem")." ".$refcmdfs));
			if ($reflink == true) { $refcmd = $cmdstr; } else {$refcmd = false; }
			if ($refresult['filesystem'] == "zfs" && $refresult['reflink_mode'] == "skip") {
				write("addLog\0".htmlspecialchars(_("Reflink copy not supported on ZFS skipping")));
				$refcmd = false;
			}

			$cmdstr = "rsync -ahPIXS  --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$repsrc' '$reptgt'";
			$error = execCommand_nchan_clone($cmdstr,$target,$refcmd); #PHPS
			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));

		foreach($config['gpu'] as $ID => $arrGPU) {
			if ($arrGPU['id'] != 'virtual') continue;
			if ($arrGPU['model'] == 'qxl' && !empty($arrGPU['DisplayOptions'])) {
			  $config['gpu'][$ID]['DisplayOptions'] = $arrDisplayOptions[$arrGPU['DisplayOptions']]['qxlxml'];
			}
		}

		$xml = $lv->config_to_xml($config, true);
		$rtn = $lv->domain_define($xml);

		if (is_resource($rtn)) {
			$arrResponse  = ['success' => true];
			write("addLog\0".htmlspecialchars(_("Creating XML successful")));
		} else {
			$lastvmerror = $lv->get_last_error();
			$arrResponse = ['xml' => $xml,'error' => $lastvmerror];
			write("addLog\0".htmlspecialchars(_("Creating XML Error:$lastvmerror")));
			file_put_contents("/tmp/vmclonertn.debug", json_encode($arrResponse,JSON_PRETTY_PRINT));
		}

		return($rtn);

	}

	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/snapshotdb/$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,$state,$desc,$method="QEMU") {
		global $lv;
		$dbpath = "/etc/libvirt/qemu/snapshotdb/$vm";
		if (!is_dir($dbpath)) mkdir($dbpath);
		$noxml = "";
		$snaps_json = file_get_contents($dbpath."/snapshots.db");
		$snaps = json_decode($snaps_json,true);
		$snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name);
		if (!$snapshot_res) {
			 # Manual Snap no XML
		if ($state == "shutoff" && ($method == "ZFS" || $method == "BTRFS")) {
			# Create Snapshot info
			$vmsnap = $name;
			$snaps[$vmsnap]["name"]= $name;
			$snaps[$vmsnap]["parent"]= "None";
			$snaps[$vmsnap]["state"]= "shutoff";
			$snaps[$vmsnap]["desc"]= $desc;
			$snaps[$vmsnap]["memory"]= ['@attributes' => ['snapshot' => 'no']];
			$snaps[$vmsnap]["creationtime"]= date("U");
			$snaps[$vmsnap]["method"]= $method;
			$snaps[$vmsnap]['xml'] = $lv->domain_get_xml($vm);
			$noxml = "noxml";
		}
		} else {
		$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"];
		$snaps[$vmsnap]["method"]= $method;
		}

		$disks =$lv->get_disk_stats($vm);
			foreach($disks as $disk)   {
				$file = $disk["file"];
				if ($disk['device'] == "hdc" ) $primarypath = dirname(transpose_user_path($file));
				$output = array();
				exec("qemu-img info --backing-chain -U '$file'  | grep image:",$output); #PHPS
				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;
			}
			$snaps[$vmsnap]["primarypath"]= $primarypath;
			$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 (isset($b)) 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);
		return $noxml;
	}

	function refresh_snapshots_database($vm) {
		global $lv;
		$dbpath = "/etc/libvirt/qemu/snapshotdb/$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 = array();
				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/$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/snapshotdb/$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, $snapshotdescinput, $free = "yes", $method = "QEMU",  $memorysnap = "yes") {
		global $lv;
		$logging = true;
		#Get State
		$res = $lv->get_domain_by_name($vm);
		$dom = $lv->domain_get_info($res);
		$state = $lv->domain_state_translate($dom['state']);
		$storage =  $lv->_get_single_xpath_result($vm, '//domain/metadata/*[local-name()=\'vmtemplate\']/@storage');
		if (empty($storage)) $storage = "default";

		if ($method == "ZFS" && $state == "running" && $memorysnap == "no") {$arrResponse =  ['error' => _("ZFS snapshot without memory dump not supported at this time.") ];return $arrResponse;}

		#Get disks for --diskspec
		$disks =$lv->get_disk_stats($vm);
		$diskspec = "";
		$capacity = 0;
		if ($snapshotname == "--generate") $name= "S".date("YmdHis"); else $name=$snapshotname;
		if ($snapshotdescinput != "") $snapshotdesc = " --description '$snapshotdescinput'";

		foreach($disks as $disk)   {
			$file = $disk["file"];
			$pathinfo =  pathinfo($file);
			$dirpath = $pathinfo["dirname"];
			if ($storage == "default") {
				$dirpath = $pathinfo["dirname"];
			} else {
				$storagelocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($file)." 2>/dev/null"));
				$dirpath= str_replace('/mnt/user/', "/mnt/$storagelocation/", $dirpath);
			}
			$filenew = $dirpath.'/'.$pathinfo["filename"].'.'.$name.'qcow2';
			switch ($method) {
			case "QEMU" :
				$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'";
				break;
			case "ZFS":
			case "BTRFS":
				$diskspec .= " --diskspec '".$disk["device"]."',snapshot=manual ";
			}
			$capacity = $capacity + $disk["capacity"];
		}

		#get memory
		$mem = $lv->domain_get_memory_stats($vm);
		$memory = $mem[6];

		if ($memorysnap == "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external'; else $memspec = "";
		$cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc  --atomic";

		if ($state == "running" & $memorysnap == "yes") {
			$cmdstr .= " --live ".$memspec.$diskspec;
			$capacity = $capacity + $memory;

		} else {
			$cmdstr .= "  --disk-only ".$diskspec;
		}

		#Check free space.
		$dirfree = disk_free_space($dirpath);

		$capacity *=  1;

		if ($free == "yes" && $dirfree < $capacity) { $arrResponse =  ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse;}

		#Copy nvram
		if ($logging) qemu_log($vm,"Copy NVRAM");
		if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name);

		$xmlfile = $dirpath."/".$name.".running";
		if ($logging) qemu_log($vm,"Save XML if state is running current $state");
		if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml);

		$output= [];
		if ($logging) qemu_log($vm,"snap method $method");
		switch ($method) {
		case "ZFS":
			# Create ZFS Snapshot
			if ($state == "running") exec($cmdstr." 2>&1",$output,$return);
			$zfsdataset = trim(shell_exec("zfs list -H -o name -r $dirpath"));
			$fssnapcmd = " zfs snapshot $zfsdataset@$name";
			if ($logging) qemu_log($vm,"zfs snap: $fssnapcmd");
			shell_exec($fssnapcmd);
			# if running resume.
			if ($state == "running") $lv->domain_resume($vm);
			break;
		case "BTRFS":
			# Create BTRFS Snapshot
			break;
		default:
			# No Action
			if ($logging) qemu_log($vm,"Cmd: $cmdstr");
			exec($cmdstr." 2>&1",$output,$return);
		}

		if (strpos(" ".$output[0],"error") ) {
			$arrResponse =  ['error' => substr($output[0],6) ];
			if ($logging) qemu_log($vm,"Error");
		} else {
		$arrResponse = ['success' => true];
		if ($logging) qemu_log($vm,"Success write snap db");
		$ret = write_snapshots_database("$vm","$name",$state,$snapshotdescinput,$method);
		#remove meta data
		if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2);
		}
		return $arrResponse;

	}

		function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryrun = false) {
		global $lv;
		$logging = true;
		$snapslist= getvmsnapshots($vm);
		#$disks =$lv->get_disk_stats($vm);
		$snapstate = $snapslist[$snap]['state'];
		$method = $snapslist[$snap]['method'];

		#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 for method QEMU.
		if ($method == "QEMU") {
			$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'];
				}
				if ($snapstate != "running") {
					$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"];
						}
					}
				}
			}
		}

		# If Snapstate not running create new XML.
		if ($snapstate != "running") {
			if ($method == "ZFS") $xml = $snapslist[$snap]['xml']; else $xml = custom::createXML('domain',$xmlobj)->saveXML();
			if (!strpos($xml,'<vmtemplate xmlns="unraid"') && !strpos($xml,'<vmtemplate xmlns="http://unraid"') ) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="http://unraid"',$xml);
			if (!$dryrun) $new = $lv->domain_define($xml);
			if ($new) $arrResponse  = ['success' => true]; else $arrResponse = ['error' => $lv->get_last_error()];
			if ($logging) qemu_log($vm,"Create XML $new");
		}

		# remove snapshot meta data, images, memory, runxml and NVRAM. for all snapshots.

		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") if (!$dryrun)  unlink("$path");else echo "unlink $path\n";
			if ($logging) qemu_log($vm,"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") if (!$dryrun) unlink("$newpath"); else echo "unlink $newpath\n";
			if ($logging) qemu_log($vm,"unlink $newpath");
			$item++;
			}
		}

		# Remove later snapshots
		if (!is_null($snapslist)) uasort($snapslist,'compare_creationtimelt');

		foreach($snapslist as $s) {
			if ($s['name'] == $snap) break;
			$name = $s['name'];
			$oldmethod = $s['method'];
			if ($dryrun) echo "$name $oldmethod\n";
			if ($logging) qemu_log($vm,"$name $oldmethod");
			if (!isset($primarypath)) $primarypath = $s['primarypath'];
			$xmlfile = $primarypath."/$name.running";
			$memoryfile = $primarypath."/memory$name.mem";
			$olddisks = $snapslist[$name]['disks'];

			if ($oldmethod == "QEMU") {
				foreach ($olddisks as $olddisk) {
				$olddiskname = $olddisk["@attributes"]["name"];
				if ($olddiskname == "hda" || $olddiskname == "hdb") continue;
				$oldpath = $olddisk["source"]["@attributes"]["file"];
				if (is_file($oldpath) && $action == "yes") if (!$dryrun) unlink("$oldpath"); else echo "$oldpath\n";
				if ($logging) qemu_log($vm,"unlink $oldpath");
				}
			}
			if ($oldmethod == "ZFS") {
			# Rollback ZFS Snapshot
			$zfsdataset = trim(shell_exec("zfs list -H -o name -r ".transpose_user_path($primarypath)));
			$fssnapcmd = " zfs destroy $zfsdataset@$name";
			if (!$dryrun) shell_exec($fssnapcmd); else echo "old $fssnapcmd\n";
			if ($logging) qemu_log($vm,"old $fssnapcmd");
			}

			#Delete Metadata
			#if ($actionmeta == "yes") if (!$dryrun) $ret = delete_snapshots_database("$vm","$name");

			if (is_file($memoryfile) && $action == "yes") if (!$dryrun) unlink($memoryfile); else echo ("$memoryfile \n");
			if (is_file($xmlfile) && $action == "yes") if (!$dryrun) unlink($xmlfile); else echo ("$xmlfile \n");
			if ($logging) qemu_log($vm,"mem $memoryfile xml $xmlfile");
			# Delete NVRAM
			if (!empty($lv->domain_get_ovmf($res)) && $action == "yes")  if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name); else echo "Remove old NV\n";
			if ($actionmeta == "yes") {
				if (!$dryrun)  $ret = delete_snapshots_database("$vm","$name"); else echo "Old Delete snapshot meta\n";
				if ($logging) qemu_log($vm,"Old Delete snapshot meta");
			}
		}

		if ($method == "ZFS") {
			if (!isset($primarypath)) $primarypath = $snapslist[$snap]['primarypath'];

			$zfsdataset = trim(shell_exec("zfs list -H -o name -r ".transpose_user_path($primarypath)));
			if ($dryrun) {
				var_dump(transpose_user_path($primarypath));
			}
			$fssnapcmd = " zfs rollback $zfsdataset@$snap";
			if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
			if ($logging) qemu_log($vm,"$fssnapcmd");
			$fssnapcmd = " zfs destroy $zfsdataset@$snap";
			if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
			if ($logging) qemu_log($vm,"$fssnapcmd");
		}

		if ($snapslist[$snap]['state'] == "running" || $snapslist[$snap]['state'] == "disk-snapshot") {
			$xmlfile = $primarypath."/$snap.running";
			$memoryfile = $primarypath."/memory$snap.mem";
			# Set XML to saved XML
			$xml = file_get_contents($xmlfile);
			$xmlobj = custom::createArray('domain',$xml);
			$xml = custom::createXML('domain',$xmlobj)->saveXML();
			if (!strpos($xml,'<vmtemplate xmlns="unraid"') && !strpos($xml,'<vmtemplate xmlns="http://unraid"') ) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="http://unraid"',$xml);
			if (!$dryrun) $rtn = $lv->domain_define($xml);
			if ($logging) qemu_log($vm,"Define XML");

			# Restore Memory.
			if ($snapslist[$snap]['state'] == "running") {
				if (!$dryrun) $cmdrtn = exec("virsh restore --running ".escapeshellarg($memoryfile));
				if ($logging) qemu_log($vm,"Restore");
				if (!$dryrun && !$cmdrtn) unlink($xmlfile);
				if ($logging) qemu_log($vm,"Unlink XML");
				if (!$dryrun && !$cmdrtn) unlink($memoryfile);
				if ($logging) qemu_log($vm,"Unlink memoryfile");
			}
			if ($snapslist[$snap]['state'] == "disk-snapshot") if (!$dryrun) unlink($xmlfile);
		}

		#if VM was started restart.
		if ($state == 'running' && $snapslist[$snap]['state'] != "running") {
			if (!$dryrun) $arrResponse = $lv->domain_start($vm);
		}

		if ($actionmeta == "yes") {
			if (!$dryrun)  $ret = delete_snapshots_database("$vm","$snap"); else echo "Delete snapshot meta\n";
			if ($logging) qemu_log($vm,"Delete Snapshot DB entry");
		}

		if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$snap); else echo "Delete NV $vm,$snap\n";

		$arrResponse  = ['success' => true];
		if ($dryrun) var_dump($arrResponse);
		if ($logging) qemu_log($vm, "Success");
		return($arrResponse);
		}

	function vm_snapimages($vm, $snap, $only) {
		global $lv;
		$snapslist= getvmsnapshots($vm);
		$data = "<br><br>Images and metadata to remove if tickbox checked.<br>";
			$capacity = 0;
		$diskspec = "";
		$disks =$lv->get_disk_stats($vm);
		foreach($disks as $disk)   {
			$file = $disk["file"];
			$output = array();
			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);
			$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<br>";
			$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<br>";
			$item++;

			}
		}
		$data .= "<br>Snapshots metadata to remove.";
		if ($only == 0) {
			$data .= "<br>$snap";
		} else {
		uasort($snapslist,'compare_creationtimelt');
		foreach($snapslist as $s) {
			$name = $s['name'];
			$data .= "<br>$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 = array();
			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 <domain> <path> [--bandwidth <number>] [--base <string>] [--shallow] [--top <string>] [--active] [--delete] [--wait] [--verbose] [--timeout <number>] [--pivot] [--keep-overlay] [--async] [--keep-relative] [--bytes]

DESCRIPTION
	Commit changes from a snapshot down to its backing image.

OPTIONS
	[--domain] <string>  domain name, id or uuid
	[--path] <string>  fully-qualified path of disk
	--bandwidth <number>  bandwidth limit in MiB/s
	--base <string>  path of base file to commit into (default bottom of chain)
	--shallow        use backing file of top as base
	--top <string>   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 <number>  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
	*/
	#Get VM State. If shutdown start as paused.
	$res = $lv->get_domain_by_name($vm);
	$dom = $lv->domain_get_info($res);
	$state = $lv->domain_state_translate($dom['state']);
	if ($state == "shutoff") {
		$lv->domain_start($res);
		$lv->domain_suspend($res);
	}

		$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' => "Process Failed"];
				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);
		if ($state == "shutoff") {
		$lv->domain_destroy($res);
		}

		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 <domain> <path> [--bandwidth <number>] [--base <string>] [--wait] [--verbose] [--timeout <number>] [--async] [--keep-relative] [--bytes]

DESCRIPTION
	Populate a disk from its backing image.

OPTIONS
	[--domain] <string>  domain name, id or uuid
	[--path] <string>  fully-qualified path of disk
	--bandwidth <number>  bandwidth limit in MiB/s
	--base <string>  path of backing file in chain for a partial pull
	--wait           wait for job to finish
	--verbose        with --wait, display the progress
	--timeout <number>  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

	*/
	#Get VM State. If shutdown start as paused.
	$res = $lv->get_domain_by_name($vm);
	$dom = $lv->domain_get_info($res);
	$state = $lv->domain_state_translate($dom['state']);
	if ($state == "shutoff") {
	$lv->domain_start($res);
		$lv->domain_suspend($res);
	}

	$snapslist= getvmsnapshots($vm);
	$disks =$lv->get_disk_stats($vm);
	foreach($disks as $disk)   {
		$file = $disk["file"];
		$output = array();
		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' => "Process Failed" ];
		return($arrResponse);
	} else {
		# Remove nvram snapshot
		$arrResponse = ['success' => true];
	}

}

	if ($state == "shutoff") {
	$lv->domain_destroy($res);
	}

	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_blockcopy($vm,$path,$base,$top,$pivot,$action) {
	/*
NAME
	blockcopy - Start a block copy operation.

SYNOPSIS
	blockcopy <domain> <path> [--dest <string>] [--bandwidth <number>] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout <number>] [--pivot] [--finish] [--async] [--xml <string>] [--format <string>] [--granularity <number>] [--buf-size <number>] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml]

DESCRIPTION
	Copy a disk backing image chain to dest.

OPTIONS
	[--domain] <string>  domain name, id or uuid
	[--path] <string>  fully-qualified path of source disk
	--dest <string>  path of the copy to create
	--bandwidth <number>  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 <number>  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 <string>   filename containing XML description of the copy destination
	--format <string>  format of the destination file
	--granularity <number>  power-of-two granularity to use during the copy
	--buf-size <number>  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
	*/
}

function addtemplatexml($post) {
	global $templateslocation,$lv,$config;
	$savedtemplates = json_decode(file_get_contents($templateslocation),true);
	if (isset($post['xmldesc'])) {
		$data = explode("\n",$post['xmldesc']);
		foreach ($data as $k => $line) {
			if (strpos($line,"uuid")) unset($data[$k]);
			if (strpos($line,"<nvram>")) unset($data[$k]);
			if (strpos($line,"<name>")) $data[$k] = "<name>#template123456</name>";
		}

		$data = implode("\n",$data);
		$new = $lv->domain_define($data);
		$dom = $lv->get_domain_by_name("#template123456");
		$uuid = $lv->domain_get_uuid("#template123456");
		$usertemplate = domain_to_config($uuid);
		$lv->domain_undefine($dom);
		$usertemplate['templatename'] = $post['templatename'];
		$usertemplate['template'] = $post['template'];
		unset($usertemplate['domain']['uuid']);
		unset($usertemplate['domain']['name']);

	} else {
		// form view

		$usertemplate = $post;
					// generate xml for this domain
		$strXML = $lv->config_to_xml($usertemplate);
		$qemucmdline = $config['qemucmdline'];
		$strXML = $lv->appendqemucmdline($strXML,$qemucmdline);
	}

	foreach($usertemplate['disk'] as $diskid => $diskdata) { unset($usertemplate['disk'][$diskid]['new']);}
	foreach($usertemplate['gpu'] as $gpuid => $gpudata) { $usertemplate['gpu'][$gpuid]['guest']['multi'] = $usertemplate['gpu'][$gpuid]['multi'];  unset($usertemplate['gpu'][$gpuid]['multi']);}
	unset($usertemplate['createvmtemplate']);
	unset($usertemplate['domain']['xmlstart']);
	unset($usertemplate['pci']);
	unset($usertemplate['usb']);
	unset($usertemplate['usbboot']);
	unset($usertemplate['nic']['mac']);

	$templatename=$usertemplate['templatename'];
	if ($templatename == "") $templatename=$usertemplate['template']['os'];
	unset($usertemplate['templatename']);
	if (strpos($templatename,"User-") === false) $templatename = "User-".$templatename;
	if (is_array($usertemplate['clock'])) $usertemplate['clocks'] = json_encode($usertemplate['clock']);
	unset($usertemplate['clock']);
	$savedtemplates[$templatename] = [
		'icon' => $usertemplate['template']['icon'],
		'form' => 'Custom.form.php',
		'os' => $usertemplate['template']['os'],
		'overrides' => $usertemplate
	];
	if (!is_dir(dirname($templateslocation))) mkdir(dirname($templateslocation));
	file_put_contents($templateslocation,json_encode($savedtemplates,JSON_PRETTY_PRINT));
	$reply = ['success' => true];
	return $reply;
}

function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$collectnicstats = true, $collectmemstats = true) {
	global $lv, $vmusagestats;

	$hostcpus = $lv->host_get_node_info();
	$timestamp = time();
	$allstats=$lv->domain_get_all_domain_stats();

	foreach ($allstats as $vm => $data) {
		$rd=$wr=$tx=$rx=null;
		$state = $data["state.state"];
		# CPU Metrics
		$cpuTime = 0;
		$cpuHostPercent = 0;
		$cpuGuestPercent = 0;
		$cpuTimeAbs = $data["cpu.time"];
		if ($state == 1 && $collectcpustats == true) {
			$guestcpus = $data["vcpu.current"];
			$cpuTime = $cpuTimeAbs - $vmusagestats[$vm]["cpuTimeAbs"];
			$pcentbase = ((($cpuTime) * 100.0) / ((($timestamp) - $vmusagestats[$vm]["timestamp"] ) * 1000.0 * 1000.0 * 1000.0));
			$cpuHostPercent = round($pcentbase / $hostcpus['cpus'],1);
			$cpuGuestPercent = round($pcentbase / $guestcpus, 1);
			$cpuHostPercent = max(0.0, min(100.0, $cpuHostPercent));
			$cpuGuestPercent = max(0.0, min(100.0, $cpuGuestPercent));
		}

		# Memory Metrics
		if ($state == 1 && $collectmemstats) {
		$currentmem = $data["balloon.current"];
		$maximummem = $data["balloon.maximum"];
		$meminuse = min($data["balloon.rss"],$data["balloon.current"]);
		} else $maximummem = $currentmem = $meminuse = 0;

		# Disk
		if ($state == 1 && $collectdiskstats) {
			$disknum = $data["block.count"];
			$rd=$wr=$i=0;
			for ($i = 0; $i < $disknum; $i++) {
				if ($data["block.$i.name"] == "hda" || $data["block.$i.name"] == "hdb") continue;
				$rd +=  $data["block.$i.rd.bytes"];
				$wr +=  $data["block.$i.wr.bytes"];
			}
			$rdrate = ($rd - $vmusagestats[$vm]['rdp']);
			$wrrate = ($wr - $vmusagestats[$vm]['wrp']);
		} else $rdrate=$wrrate=0;

		# Net
		if ($state == 1 && $collectnicstats) {
			$nicnum = $data["net.count"];
			$rx=$tx=$i=0;
			for ($i = 0; $i < $nicnum; $i++) {
				$rx +=  $data["net.$i.rx.bytes"];
				$tx +=  $data["net.$i.tx.bytes"];
			}
			$rxrate = round(($rx - $vmusagestats[$vm]['rxp']),0);
			$txrate = round(($tx - $vmusagestats[$vm]['txp']),0);
		} else {
			$rxrate=$txrate=0;
		}
		$vmusagestats[$vm] = [
			"cpuTime" => $cpuTime,
			"cpuTimeAbs" => $cpuTimeAbs,
			"cpuhost" => $cpuHostPercent,
			"cpuguest" => $cpuGuestPercent,
			"timestamp" => $timestamp,
			"mem" => $meminuse,
			"curmem" => $currentmem,
			"maxmem" => $maximummem,
			"rxrate" => $rxrate,
			"rxp" => $rx,
			"txrate" => $txrate,
			"txp" => $tx,
			"rdp" => $rd,
			"rdrate" => $rdrate,
			"wrp" => $wr,
			"wrrate" => $wrrate,
			"state" => $state,
		];
	}
}

function build_xml_templates($strXML) {
	global $arrValidPCIDevices,$arrValidUSBDevices;

	$xmldevsections = $xmlsections = [];
	$xml = new SimpleXMLElement($strXML);
	$x = $xml->children();
	foreach($x as $key=>$y) {
		$xmlsections[] = $key;
	}

	$ns= $xml->getNamespaces(true);
	foreach($ns as $namekey=>$namespace) foreach($xml->children($namespace) as $key=>$y)	$xmlsections[] = "$namekey:$key";

	$v = $xml->devices->children();
	$keys = [];
	foreach($v as $key=>$y) $keys[] = $key;
	foreach(array_count_values($keys) as $key=>$number) $xmldevsections[]= $key;

	$endpos = 0;
	foreach($xmlsections as $xmlsection) {
		$strpos = strpos($strXML,"<$xmlsection",$endpos);
		if ($strpos === false) continue ;
		$endcheck = "</$xmlsection>";
		$endpos = strpos($strXML,$endcheck,$strpos);
		$xml2[$xmlsection] = trim(substr($strXML,$strpos,$endpos-$strpos+strlen($endcheck)),'/0');
	}

	$xml = $xml2['devices'];
	$endpos = 0;
	foreach($xmldevsections as $xmlsection ) {
		 $strpos = $count = 0;
		while (true) {
			$strpos = strpos($xml,"<$xmlsection",$endpos);
			if ($strpos === false) continue  2;
			$endcheck = "</$xmlsection>";
			$endpos = strpos($xml,$endcheck,$strpos);
			#echo $xmlsection." ".$strpos." ".$endpos."\n";
			if ($endpos === false) {
				$endcheck = "/>";
				$endpos = strpos($xml,$endcheck,$strpos);
			}
			# echo substr($xml,$strpos,$endpos-$strpos+strlen($endcheck));
			if ($xmlsection == "disk") {
				$disk = substr($xml,$strpos,$endpos-$strpos+strlen($endcheck));
				$xmldiskdoc = new SimpleXMLElement($disk);
				$devxml[$xmlsection][$xmldiskdoc->target->attributes()->dev->__toString()] = $disk;
			} else {
				$devxml[$xmlsection][$count] = substr($xml,$strpos,$endpos-$strpos+strlen($endcheck));
			}
			$count++;
		}
	}
	$xml2["devices"] = $devxml;
	$xml2["devices"]["allusb"] = "";
	if(isset($xml2['devices']["hostdev"])) {
		foreach ($xml2['devices']["hostdev"] as $xmlhostdev) {
			$xmlhostdevdoc = new SimpleXMLElement($xmlhostdev);
			switch ($xmlhostdevdoc->attributes()->type) {
			case 'pci' :
				$pciaddr = $xmlhostdevdoc->source->address->attributes()->bus.":".$xmlhostdevdoc->source->address->attributes()->slot.".".$xmlhostdevdoc->source->address->attributes()->function;
				$pciaddr = str_replace("0x","",$pciaddr);
				$xml2["devices"][$arrValidPCIDevices[$pciaddr]["class"]][$pciaddr] = $xmlhostdev;
				break;
			case "usb":
				$usbaddr = $xmlhostdevdoc->source->vendor->attributes()->id.":".$xmlhostdevdoc->source->product->attributes()->id;
				$usbaddr = str_replace("0x","",$usbaddr);
				$xml2["devices"]["usb"][$usbaddr] = $xmlhostdev;
				$xml2["devices"]["allusb"] .= $xmlhostdev;
				break;
			}
		}
	}
	foreach($xml2["devices"]["input"] as $input) $xml2["devices"]["allinput"] .= "$input\n";
	return $xml2;
}

function qemu_log($vm,$m) {
	$m = print_r($m,true);
	$m = date("YmdHis")." ".$m;
	$m = str_replace("\n", " ", $m);
	$m = str_replace('"', "'", $m);
	file_put_contents("/var/log/libvirt/qemu/$vm.log",$m."\n",FILE_APPEND);
}

function get_vm_ip($dom) {
	global $lv;
	$myIP=null;
	$gastate = getgastate($dom);
	if ($gastate == "connected") {
		$ip  = $lv->domain_interface_addresses($dom, 1);
		$gastate = getgastate($dom);
		if ($gastate == "connected") {
			$ip  = $lv->domain_interface_addresses($dom, 1);
			if ($ip != false) {
				foreach ($ip as $arrIP) {
					$ipname = $arrIP["name"];
					if (preg_match('/^(lo|Loopback)/',$ipname)) continue; // omit loopback interface
					$iplist = $arrIP["addrs"];
						foreach ($iplist as $arraddr) {
							$myIP= $arraddr["addr"];
							if (preg_match('/^f[c-f]/',$myIP)) continue; // omit ipv6 private addresses
						break 2;
					}
				}
			}
		}
	}
	return $myIP;
}

function check_zfs_name($zfsname, $storage="default") {
	global $lv,$domain_cfg;
	if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
	$storage=transpose_user_path($storage);
	$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
	#Check if ZFS.
	$allowed_chars = "/^[A-Za-z0-9][A-Za-z0-9\-_.: ]*$/";
	if ($fstype == "zfs" && !preg_match($allowed_chars, $zfsname)) {
		return false;
	} else {
		return true;
	}
}

function get_storage_fstype($storage="default") {
	global $domain_cfg;
	if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
	$storage=transpose_user_path($storage);
	$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
	return $fstype;
}

function getFilesystemAndReflinkMode(string $filePath): array {
    // Default return values
    $result = [
        'filesystem' => 'unknown',
        'reflink_mode' => 'always' // safest fallback if unsupported
    ];

    $realPath = realpath($filePath);
    if ($realPath === false || !file_exists($realPath)) {
        return $result;
    }

    $dirname = dirname($realPath);

    // Handle Unraid-style virtual paths
    if (strpos($dirname, '/mnt/user/') === 0) {
        error_log("Getting real disks\n");
        $realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION " . escapeshellarg($dirname) . " 2>/dev/null"));
        if (!empty($realdisk)) {
            $dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname);
        }
    }

    // Get filesystem type
    $fstype = trim(shell_exec("stat -f -c '%T' " . escapeshellarg($dirname)));
    $result['filesystem'] = $fstype;

    // If not ZFS, return early
    if ($fstype !== 'zfs') {
       return $result;
    }
	
	$result['reflink_mode'] = 'skip';

    // Get dataset from the file
    $dataset = trim(shell_exec("zfs list -H -o name " . escapeshellarg($dirname) . " 2>/dev/null"));
    if (empty($dataset)) {
      return $result;
    }

    // Get pool from dataset
    $parts = explode('/', $dataset);
    $pool = $parts[0] ?? '';
    if (empty($pool)) {
        return $result;
    }

    // Check ZFS block_cloning feature
    $output = [];
    $resultCode = 0;
    exec("zpool get -H -o value feature@block_cloning " . escapeshellarg($pool) . " 2>/dev/null", $output, $resultCode);
    if ($resultCode !== 0 || empty($output) || (trim($output[0]) !== 'enabled' && trim($output[0]) !== 'active')) {
        return $result;
    }

    // Check if bclone is enabled at runtime
    $bcloneValue = @file_get_contents("/sys/module/zfs/parameters/zfs_bclone_enabled");
    if ($bcloneValue === false || trim($bcloneValue) !== '1') {
        return $result;
    }

    // If both checks passed, block cloning is supported
    $result['reflink_mode'] = 'auto';
    return $result;
}

?>
