Merge pull request #2068 from SimonFair/general-protection-fix

7.x Save PCI Data.
This commit is contained in:
tom mortensen
2025-05-06 10:35:00 -07:00
committed by GitHub
12 changed files with 409 additions and 21 deletions
@@ -39,6 +39,7 @@ $i = 0;
$kvm = ['var kvm=[];'];
$show = explode(',',unscript(_var($_GET,'show')));
$path = _var($domain_cfg,'MEDIADIR');
$pci_device_changes = comparePCIData();
foreach ($vms as $vm) {
$res = $lv->get_domain_by_name($vm);
@@ -52,6 +53,11 @@ foreach ($vms as $vm) {
$image = substr($icon,-4)=='.png' ? "<img src='$icon' class='img'>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
$arrConfig = domain_to_config($uuid);
$snapshots = getvmsnapshots($vm) ;
$vmpciids = $lv->domain_get_vm_pciids($vm);
$pcierror = false;
foreach($vmpciids as $pciid => $pcidetail) {
if (isset($pci_device_changes["0000:".$pciid])) $pcierror = true;
}
$cdroms = $lv->get_cdrom_stats($res,true,true) ;
if ($state == 'running') {
$mem = $dom['memory']/1024;
@@ -117,7 +123,7 @@ foreach ($vms as $vm) {
unset($dom);
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no" ; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"] ;
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', '%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)));
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', '%s', %s)\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)),$pcierror);
$kvm[] = "kvm.push({id:'$uuid',state:'$state'});";
switch ($state) {
case 'running':
@@ -196,7 +202,10 @@ foreach ($vms as $vm) {
$title = _('Select ISO image');
$cdstr = $cdromcount." / 2<a class='hand' title='$title' href='#' onclick='$changemedia'><i class='fa fa-dot-circle-o'></i></a>";
echo "<tr parent-id='$i' class='sortable'><td class='vm-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
echo "<span class='outer'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'><a href='#' onclick='return toggle_id(\"name-$i\")' title='click for more VM info'>$vm</a><br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)." </span></span></span></td>";
echo "<span class='outer'><span id='vm-$uuid' $menu class='hand'>$image</span>";
echo "<span class='inner'><a href='#' onclick='return toggle_id(\"name-$i\")' title='click for more VM info'>$vm</a>";
if ($pcierror) echo "<i class=\"fa fa-warning fa-fw orange-text\" title=\""._('PCI Changed')."\n"._('Start disabled')."\"></i>";
echo "<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)." </span></span></span></td>";
echo "<td>$desc</td>";
echo "<td><a class='vcpu-$uuid' style='cursor:pointer'>$vcpu</a></td>";
echo "<td>$mem</td>";
@@ -2257,6 +2257,18 @@ class Libvirt {
return ['pci' => $devs_pci, 'usb' => $devs_usb];
}
function domain_get_vm_pciids($domain) {
$hostdevs=$this->domain_get_host_devices_pci($domain);
$vmpcidevs=[];
foreach($hostdevs as $key => $dev) {
$vmpcidevs[$dev['id']] = [
'vendor_id' => ltrim($dev['vendor_id'] ?? "", '0x'),
'device_id' => ltrim($dev['product_id'] ?? "", '0x'),
];
}
return $vmpcidevs;
}
function get_nic_info($domain) {
$macs = $this->get_xpath($domain, "//domain/devices/interface/mac/@address", false);
if (!$macs) return $this->_set_last_error();
@@ -83,7 +83,7 @@ function ajaxVMDispatchWebUI(params, spin){
}
},'json');
}
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",consolein="web;no",usage=false,webui=""){
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",consolein="web;no",usage=false,webui="",pcierror=false){
var opts = [];
var path = location.pathname;
var x = path.indexOf("?");
@@ -172,22 +172,28 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
ajaxVMDispatch({action:"domain-destroy", uuid:uuid}, "loadlist");
}});
} else {
opts.push({text:_("Start"), icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatch({action:"domain-start", uuid:uuid}, "loadlist");
}});
if (vmrcprotocol == "VNC" || vmrcprotocol == "SPICE") {
if (console == "web" || console == "both") {
opts.push({text:_("Start with console")+ " (" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatchconsole({action:"domain-start-console", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
}});}
if (console == "remote" || console == "both") {
opts.push({text:_("Start with remote-viewer")+ " (" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatchconsoleRV({action:"domain-start-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
}});
if (!pcierror) {
opts.push({text:_("Start"), icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatch({action:"domain-start", uuid:uuid}, "loadlist");
}});
if (vmrcprotocol == "VNC" || vmrcprotocol == "SPICE") {
if (console == "web" || console == "both") {
opts.push({text:_("Start with console")+ " (" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatchconsole({action:"domain-start-console", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
}});}
if (console == "remote" || console == "both") {
opts.push({text:_("Start with remote-viewer")+ " (" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
e.preventDefault();
ajaxVMDispatchconsoleRV({action:"domain-start-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
}});
}
}
} else {
opts.push({text:_("Start disabled due to PCI Changes"), icon:"fa fa-minus-circle orb red-orb", action:function(e) {
e.preventDefault();
}});
}
}
opts.push({divider:true});
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/php
<?php
/* Copyright 2005-2024, Lime Technology
* Copyright 2024, Simon Fairweather
*
* 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.
*/
# Command for bash script /usr/libexec/virtiofsd
# eval exec /usr/bin/virtiofsd $(/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/virtiofsd.php "$@")
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
$pci_device_changes = comparePCIData();
$pcierror = false;
$pci_addresses = [];
foreach ($argv as $arg) {
if (preg_match('/"host"\s*:\s*"([^"]+)"/', $arg, $matches)) {
$pci_addresses[] = $matches[1];
}
}
foreach($pci_addresses as $pciid) {
if (isset($pci_device_changes[$pciid])) {
$pcierror = true;
}
}
echo $pcierror == true ? "yes" : "no";
?>
+52
View File
@@ -46,6 +46,58 @@ function applyCfg() {
$("#applycfg").attr("disabled",true);
});
}
function formatFullInput(input) {
return input
.split(';')
.filter(Boolean) // remove empty trailing entry
.map(entry => {
let [pci, status] = entry.split(',');
status = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
return `${pci} _(${status})_`;
})
.join('<br>');
}
function formatVMS(input) {
return input
.split(';')
.filter(Boolean) // remove empty trailing entry
.join('<br>');
}
function ackPCI(pcidevice, action) {
$.post('/webGui/include/PCIUpdate.php', { action: "getvm", pciid: pcidevice }).done(function(vms) {
let swaltext = "";
switch(action) {
case 'removed':
swaltext = "_(Acknowledge removal of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
case 'changed':
swaltext = "_(Acknowledge update of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>"+ formatVMS(vms);
break;
case 'added':
swaltext = "_(Acknowledge addition of PCI Address)_: " + pcidevice + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
case 'all':
swaltext = "_(Acknowledge all PCI Address modifications)_: <br>" + formatFullInput(pcidevice) + "<br>_(VMs where PCI device used)_<br>" + formatVMS(vms);
break;
}
swal({
title: "Are you sure?",
text: swaltext,
type: "warning",
html: true,
showCancelButton: true
}, function() {
$.post('/webGui/include/PCIUpdate.php', { action: action, pciid: pcidevice }).done(function(d) {
if (d === "OK") {
$('#t1').load('/webGui/include/SysDevs.php', { table: 't1' });
}
});
});
});
}
</script>
<?exec('ls /sys/kernel/iommu_groups/',$groups); ?>
@@ -115,6 +115,7 @@ if ($_POST['vms']) {
}
echo "<tr title='' class='updated'><td>";
$running = 0;
$pci_device_changes = comparePCIData();
foreach ($vms as $vm) {
$res = $lv->get_domain_by_name($vm);
$uuid = libvirt_domain_get_uuid_string($res);
@@ -155,8 +156,15 @@ if ($_POST['vms']) {
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web"; else $vmrcconsole = $domain_cfg["CONSOLE"];
if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no"; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"];
$WebUI = html_entity_decode($arrConfig["template"]["webui"]);
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)));
$WebUI = html_entity_decode($arrConfig["template"]["webui"]??"");
$vmpciids = $lv->domain_get_vm_pciids($vm);
$pcierror = false;
foreach($vmpciids as $pciid => $pcidetail) {
if (isset($pci_device_changes["0000:".$pciid])) {
$pcierror = true;
}
}
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)),$pcierror);
$icon = $lv->domain_get_icon_url($res);
switch ($state) {
case 'running':
@@ -178,7 +186,9 @@ if ($_POST['vms']) {
break;
}
$image = substr($icon,-4)=='.png' ? "<img src='$icon' class='img'>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
echo "<span class='outer solid vms $status'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'>$vm<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
echo "<span class='outer solid vms $status'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'>$vm";
if ($pcierror) echo "<i class=\"fa fa-warning fa-fw orange-text\" title=\""._('PCI Changed')."\n"._('Start disabled')."\"></i>";
echo "<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
if ($state == "running") {
#Build VM Usage array.
$menuusage = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,true,addslashes(str_replace('"',"'",$WebUI)));
@@ -416,4 +416,99 @@ function device_exists($name)
global $disks,$devs;
return (array_key_exists($name, $disks) && !str_contains(_var($disks[$name],'status'),'_NP')) || (array_key_exists($name, $devs));
}
// Load saved PCI data
function loadSavedData($filename) {
if (file_exists($filename)) {
$saveddata = file_get_contents($filename);
} else $saveddata = "";
return json_decode($saveddata, true);
}
// Run lspci -Dmn to get the current devices
function loadCurrentPCIData() {
$output = shell_exec('lspci -Dmn');
$devices = [];
if (file_exists("/boot/config/current.json")){
$devices = loadSavedData("/boot/config/current.json");
} else {
foreach (explode("\n", trim($output)) as $line) {
$parts = explode(" ", $line);
if (count($parts) < 6) continue; // Skip malformed lines
$description_str = shell_exec(("lspci -s ".$parts[0]));
$description = preg_replace('/^\S+\s+/', '', $description_str);
$device = [
'class' => trim($parts[1], '"'),
'vendor_id' => trim($parts[2], '"'),
'device_id' => trim($parts[3], '"'),
'description' => trim($description,'"'),
];
$devices[$parts[0]] = $device;
}
}
return $devices;
}
// Compare the saved and current data
function comparePCIData() {
$changes = [];
$saved = loadSavedData("/boot/config/savedpcidata.json");
if (!$saved) return [];
$current = loadCurrentPCIData();
// Compare saved devices with current devices
foreach ($saved as $pci_id => $saved_device) {
if (!isset($current[$pci_id])) {
// Device has been removed
$changes[$pci_id] = [
'status' => 'removed',
'device' => $saved_device
];
} else {
// Device exists in both, check for modifications
$current_device = $current[$pci_id];
$differences = [];
// Compare fields
foreach (['vendor_id', 'device_id', 'class'] as $field) {
if (isset($saved_device[$field]) && isset($current_device[$field]) && $saved_device[$field] !== $current_device[$field]) {
$differences[$field] = [
'old' => $saved_device[$field],
'new' => $current_device[$field]
];
}
}
if (!empty($differences)) {
$changes[$pci_id] = [
'status' => 'changed',
'device' => $current_device,
'differences' => $differences
];
}
}
}
// Check for added devices
foreach ($current as $pci_id => $current_device) {
if (!isset($saved[$pci_id])) {
// Device has been added
$changes[$pci_id] = [
'status' => 'added',
'device' => $current_device
];
}
}
return $changes;
}
?>
@@ -0,0 +1,96 @@
<?PHP
/* Copyright 2005-2025, Lime Technology
* Copyright 2012-2025, Bergware International.
* Copyright 2012-2025, Simon Fairweather.
*
* 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.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
function process_action($pciaddr,$action)
{
global $saved,$current;
switch ($action) {
case 'removed':
unset($saved[$pciaddr]);
break;
case 'changed':
case 'added':
$saved[$pciaddr] = $current[$pciaddr];
break;
}
}
function build_pci_vm_map() {
global $lv;
$pci_device_changes = comparePCIData();
$vms = $lv->get_domains();
foreach ($vms as $vm) {
$vmpciids = $lv->domain_get_vm_pciids($vm);
foreach($vmpciids as $pciid => $pcidetail) {
if (isset($pci_device_changes["0000:".$pciid])) {
$pcitovm["0000:".$pciid][$vm] = $vm;
}
}
}
return $pcitovm;
}
$savedfile = "/boot/config/savedpcidata.json";
$saved = loadSavedData($savedfile);
if (!$saved) {echo "ERROR"; return;};
$current = loadCurrentPCIData();
$pciaddr = $_POST['pciid'];
$action = $_POST['action']??'';
switch($action) {
case "all":
$pciaddrs = explode(";", $pciaddr);
foreach ($pciaddrs as $pciaddraction){
if ($pciaddraction == "") continue;
$values = explode(',',$pciaddraction);
process_action($values[0],$values[1]);
}
file_put_contents($savedfile,json_encode($saved,JSON_PRETTY_PRINT));
break;
case "removed":
case "added":
case "changed":
process_action($pciaddr,$action);
file_put_contents($savedfile,json_encode($saved,JSON_PRETTY_PRINT));
break;
case "getvm":
$pcimap = build_pci_vm_map();
$pciaddrs = explode(";", $pciaddr);
$vmact =[];
foreach ($pciaddrs as $pcidev) {
if ($pcidev == "") continue;
if (strpos($pcidev,",")) {
$values = explode(',',$pcidev);
$pcidev = $values[0];
}
foreach ($pcimap[$pcidev] as $key => $vmname) {
$vmact[$vmname]= $vmname;
}
}
$ret = implode(";",$vmact);
echo $ret;
exit;
}
file_put_contents($savedfile,json_encode($saved,JSON_PRETTY_PRINT));
echo "OK";
?>
@@ -18,6 +18,8 @@ require_once "$docroot/webGui/include/Helpers.php";
$_SERVER['REQUEST_URI'] = 'tools';
require_once "$docroot/webGui/include/Translations.php";
$pci_device_diffs = comparePCIData();
function usb_physical_port($usbbusdev) {
if (preg_match('/^Bus (?P<bus>\S+) Device (?P<dev>\S+): ID (?P<id>\S+)(?P<name>.*)$/', $usbbusdev, $usbMatch)) {
//udevadm info -a --name=/dev/bus/usb/003/002 | grep KERNEL==
@@ -115,6 +117,15 @@ case 't1':
$iommuinuse[] = (strpos($string,'/')) ? strstr($string, '/', true) : $string;
}
exec('lsscsi -s',$lsscsi);
// Filter for 'removed' devices
$removedArr = array_filter($pci_device_diffs, function($entry) {
return isset($entry['status']) && $entry['status'] === 'removed';
});
foreach ($removedArr as $removedpci => $removeddata) {
$groups[] = "IOMMU "._("Removed");
$groups[] = "\tR[{$removeddata['device']['vendor_id']}:{$removeddata['device']['device_id']}] ".str_replace("0000:","",$removedpci)." ".trim($removeddata['device']['description'],"\n");
}
$ackparm = "";
foreach ($groups as $line) {
if (!$line) continue;
if ($line[0]=='I') {
@@ -126,6 +137,8 @@ case 't1':
$line = preg_replace("/^\t/","",$line);
$vd = trim(explode(" ", $line)[0], "[]");
$pciaddress = explode(" ", $line)[1];
$removed = $line[0]=='R' ? true : false;
if ($removed) $line=preg_replace('/R/', '', $line, 1);
if (preg_match($BDF_REGEX, $pciaddress)) {
// By default lspci does not output the <Domain> when the only domain in the system is 0000. Add it back.
$pciaddress = "0000:".$pciaddress;
@@ -140,13 +153,32 @@ case 't1':
if ((strpos($line, 'Host bridge') === false) && (strpos($line, 'PCI bridge') === false)) {
if (file_exists('/sys/kernel/iommu_groups/'.$iommu.'/devices/'.$pciaddress.'/reset')) echo "<i class=\"fa fa-retweet grey-orb middle\" title=\"",_('Function Level Reset (FLR) supported'),".\"></i>";
echo "</td><td>";
if (!$removed) {
echo in_array($iommu, $iommuinuse) ? '<input type="checkbox" value="" title="'._('In use by Unraid').'" disabled ' : '<input type="checkbox" class="iommu'.$iommu.'" value="'.$pciaddress."|".$vd.'" ';
// check config file for two formats: <Domain:Bus:Device.Function>|<Vendor:Device> or just <Domain:Bus:Device.Function>
echo (in_array($pciaddress."|".$vd, $vfio_cfg_devices) || in_array($pciaddress, $vfio_cfg_devices)) ? " checked>" : ">";
}
} else { echo "</td><td>"; }
echo '</td><td title="';
foreach ($outputvfio as $line2) echo htmlentities($line2,ENT_QUOTES)."&#10;";
echo '">',$line,'</td></tr>';
if (array_key_exists($pciaddress,$pci_device_diffs)) {
echo "<tr><td></td><td><td></td><td></td><td>";
echo "<i class=\"fa fa-warning fa-fw orange-text\" title=\""._('PCI Change')."\n"._('Click to acknowledge').".\" onclick=\"ackPCI('".htmlentities($pciaddress)."','".htmlentities($pci_device_diffs[$pciaddress]['status'])."')\"></i>";
echo _("PCI Device change");
echo " "._("Action").":".ucfirst(_($pci_device_diffs[$pciaddress]['status']))." ";
$ackparm .= $pciaddress.",".$pci_device_diffs[$pciaddress]['status'].";";
if ($pci_device_diffs[$pciaddress]['status']!="removed") echo $pci_device_diffs[$pciaddress]['device']['description'];
echo "</td></tr>";
if ($pci_device_diffs[$pciaddress]['status']=="changed") {
echo "<tr><td></td><td><td></td><td></td><td>";
echo _("Differences");
foreach($pci_device_diffs[$pciaddress]['differences'] as $key => $changes){
echo " $key "._("before").":{$changes['old']} "._("after").":{$changes['new']} ";
}
echo "</td></tr>";
}
}
unset($outputvfio);
switch (true) {
case (strpos($line, 'USB controller') !== false):
@@ -194,8 +226,10 @@ case 't1':
if (file_exists("/var/log/vfio-pci") && filesize("/var/log/vfio-pci")) {
echo '<input id="viewlog" type="button" value="'._('View VFIO-PCI Log').'" onclick="openTerminal(\'log\',\'vfio-pci\',\'vfio-pci\')">';
}
if ($ackparm == "") $ackdisable =" disabled "; else $ackdisable = "";
echo '<input id="applycfg" type="submit" disabled value="'._('Bind selected to VFIO at Boot').'" onclick="applyCfg();" '.(isset($noiommu) ? "style=\"display:none\"" : "").'>';
echo '<span id="warning"></span>';
echo '<input id="applypci" type="submit"'.$ackdisable.' value="'._('Acknowledge all PCI changes').'" onclick="ackPCI(\''.htmlentities($ackparm).'\',\'all\')" >';
echo '</td></tr>';
echo <<<EOT
<script>
+4
View File
@@ -76,6 +76,10 @@ if [[ -x /sbin/hwclock ]]; then
fi
fi
# Save PCI data to validate system changes at system start.
log "Save PCI Configuration."
/usr/local/sbin/savepcidata
# Run any local shutdown scripts:
if [[ -x /etc/rc.d/rc.local_shutdown ]]; then
/etc/rc.d/rc.local_shutdown stop
+7
View File
@@ -6,4 +6,11 @@ if [ $DISABLE == "yes" ]
printf '\n%s\n' "Start/autostart is disabled in VM settings." >&2 ## Send message to stderr.
exit 1 ;
fi
PCI="no"
PCI=$(/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/pcicheck.php "$@");
if [ $PCI == "yes" ]
then
printf '\n%s\n' "Start/autostart is disabled PCI Change detected." >&2 ## Send message to stderr.
exit 1 ;
fi
eval exec /usr/bin/qemu-system-x86_64 $(/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/qemu.php "$@")
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/php
<?php
$output = shell_exec('lspci -Dmn');
$devices = [];
foreach (explode("\n", trim($output)) as $line) {
$parts = explode(" ", $line);
if (count($parts) < 6) continue; // Skip malformed lines
$description_str = shell_exec(("lspci -s ".$parts[0]));
$description = preg_replace('/^\S+\s+/', '', $description_str);
$device = [
'class' => trim($parts[1], '"'),
'vendor_id' => trim($parts[2], '"'),
'device_id' => trim($parts[3], '"'),
'description' => trim($description,'"'),
];
$devices[$parts[0]] = $device;
}
file_put_contents("/boot/config/savedpcidata.json",json_encode($devices,JSON_PRETTY_PRINT));
?>