mirror of
https://github.com/unraid/webgui.git
synced 2025-12-31 06:30:10 -06:00
- Updated the deprecation message for XFS v4 to specify the migration deadline as 2030, improving user awareness and understanding of the timeline for necessary filesystem upgrades.
861 lines
30 KiB
PHP
861 lines
30 KiB
PHP
<?PHP
|
|
/* Copyright 2005-2025, Lime Technology
|
|
* Copyright 2012-2025, Bergware International.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version 2,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*/
|
|
?>
|
|
<?
|
|
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
|
require_once "$docroot/webGui/include/Wrappers.php";
|
|
require_once "$docroot/webGui/include/Secure.php";
|
|
|
|
// Helper functions
|
|
function my_scale($value, &$unit, $decimals=NULL, $scale=NULL, $kilo=1000) {
|
|
global $display, $language;
|
|
$scale = $scale ?? $display['scale'];
|
|
$number = _var($display,'number','.,');
|
|
$units = explode(' ', ' '.($kilo==1000 ? ($language['prefix_SI'] ?? 'K M G T P E Z Y') : ($language['prefix_IEC'] ?? 'Ki Mi Gi Ti Pi Ei Zi Yi')));
|
|
$size = count($units);
|
|
if ($scale == 0 && ($decimals === NULL || $decimals < 0)) {
|
|
$decimals = 0;
|
|
$unit = '';
|
|
} else {
|
|
$base = $value ? intval(floor(log($value, $kilo))) : 0;
|
|
if ($scale > 0 && $base > $scale) $base = $scale;
|
|
if ($base > $size) $base = $size - 1;
|
|
$value /= pow($kilo, $base);
|
|
if ($decimals === NULL) $decimals = $value >= 100 ? 0 : ($value >= 10 ? 1 : (round($value*100)%100 === 0 ? 0 : 2));
|
|
elseif ($decimals < 0) $decimals = $value >= 100 || round($value*10)%10 === 0 ? 0 : abs($decimals);
|
|
if ($scale < 0 && round($value,-1) == 1000) {$value = 1; $base++;}
|
|
$unit = $units[$base]._('B');
|
|
}
|
|
return number_format($value, $decimals, $number[0], $value > 9999 ? $number[1] : '');
|
|
}
|
|
|
|
function my_number($value) {
|
|
global $display;
|
|
$number = _var($display,'number','.,');
|
|
return number_format($value, 0, $number[0], ($value >= 10000 ? $number[1] : ''));
|
|
}
|
|
|
|
function my_time($time, $fmt=NULL) {
|
|
global $display;
|
|
if (!$fmt) $fmt = _var($display,'date').(_var($display,'date')!='%c' ? ", "._var($display,'time') : "");
|
|
return $time ? my_date($fmt, $time) : _('unknown');
|
|
}
|
|
|
|
function my_temp($value) {
|
|
global $display;
|
|
$unit = _var($display,'unit','C');
|
|
$number = _var($display,'number','.,');
|
|
return is_numeric($value) ? (($unit == 'F' ? fahrenheit($value) : str_replace('.', $number[0], $value)).' °'.$unit) : $value;
|
|
}
|
|
|
|
function my_disk($name, $raw=false) {
|
|
global $display;
|
|
return _var($display,'raw') || $raw ? $name : ucfirst(preg_replace('/(\d+)$/',' $1',$name));
|
|
}
|
|
|
|
function my_disks($disk) {
|
|
return strpos(_var($disk,'status'),'_NP') === false;
|
|
}
|
|
|
|
function my_hyperlink($text, $link) {
|
|
return str_replace(['[',']'],["<a href=\"$link\">","</a>"],$text);
|
|
}
|
|
|
|
function main_only($disk) {
|
|
return _var($disk,'type') == 'Parity' || _var($disk,'type') == 'Data';
|
|
}
|
|
|
|
function parity_only($disk) {
|
|
return _var($disk,'type') == 'Parity';
|
|
}
|
|
|
|
function data_only($disk) {
|
|
return _var($disk,'type') == 'Data';
|
|
}
|
|
|
|
function cache_only($disk) {
|
|
return _var($disk,'type') == 'Cache';
|
|
}
|
|
|
|
function luks_only($disk) {
|
|
return _var($disk,'type') == 'Data' || _var($disk,'type') == 'Cache';
|
|
}
|
|
|
|
function main_filter($disks) {
|
|
return array_filter($disks, 'main_only');
|
|
}
|
|
|
|
function parity_filter($disks) {
|
|
return array_filter($disks, 'parity_only');
|
|
}
|
|
|
|
function data_filter($disks) {
|
|
return array_filter($disks, 'data_only');
|
|
}
|
|
|
|
function cache_filter($disks) {
|
|
return array_filter($disks, 'cache_only');
|
|
}
|
|
|
|
function luks_filter($disks) {
|
|
return array_filter($disks, 'luks_only');
|
|
}
|
|
|
|
function pools_filter($disks) {
|
|
return array_unique(array_map('prefix', array_keys(cache_filter($disks))));
|
|
}
|
|
|
|
function my_id($id) {
|
|
global $display;
|
|
$len = strlen($id);
|
|
$wwn = substr($id,-18);
|
|
return (_var($display,'wwn') || substr($wwn,0,2) != '_3' || preg_match('/.[_-]/',$wwn)) ? $id : substr($id,0,$len-18);
|
|
}
|
|
|
|
function my_word($num) {
|
|
$words = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen','seventeen','eighteen','nineteen','twenty','twenty-one','twenty-two','twenty-three','twenty-four','twenty-five','twenty-six','twenty-seven','twenty-eight','twenty-nine','thirty'];
|
|
return $num < count($words) ? _($words[$num],1) : $num;
|
|
}
|
|
|
|
function my_usage() {
|
|
global $disks, $var, $display;
|
|
$arraysize = 0;
|
|
$arrayfree = 0;
|
|
foreach ($disks as $disk) {
|
|
if (strpos(_var($disk,'name'),'disk') !== false) {
|
|
$arraysize += _var($disk,'sizeSb',0);
|
|
$arrayfree += _var($disk,'fsFree',0);
|
|
}
|
|
}
|
|
if (_var($var,'fsNumMounted',0) > 0) {
|
|
$used = $arraysize ? 100-round(100*$arrayfree/$arraysize) : 0;
|
|
echo "<div class='usage-bar'><span style='width:{$used}%' class='".usage_color($display,$used,false)."'>{$used}%</span></div>";
|
|
} else {
|
|
echo "<div class='usage-bar'><span style='text-align:center'>".($var['fsState']=='Started'?'Maintenance':'off-line')."</span></div>";
|
|
}
|
|
}
|
|
|
|
function usage_color(&$disk, $limit, $free) {
|
|
global $display;
|
|
if (_var($display,'text',0) == 1 || intval(_var($display,'text',0)/10) == 1) return '';
|
|
$critical = _var($disk,'critical') >= 0 ? $disk['critical'] : (_var($display,'critical') >= 0 ? $display['critical'] : 0);
|
|
$warning = _var($disk,'warning') >= 0 ? $disk['warning'] : (_var($display,'warning') >= 0 ? $display['warning'] : 0);
|
|
if (!$free) {
|
|
if ($critical > 0 && $limit >= $critical) return 'redbar';
|
|
if ($warning > 0 && $limit >= $warning) return 'orangebar';
|
|
return 'greenbar';
|
|
} else {
|
|
if ($critical > 0 && $limit <= 100-$critical) return 'redbar';
|
|
if ($warning > 0 && $limit <= 100-$warning) return 'orangebar';
|
|
return 'greenbar';
|
|
}
|
|
}
|
|
|
|
function my_check($time, $speed) {
|
|
if (!$time) return _('unavailable (no parity-check entries logged)');
|
|
$days = floor($time/86400);
|
|
$hmss = $time-$days*86400;
|
|
$hour = floor($hmss/3600);
|
|
$mins = floor($hmss/60)%60;
|
|
$secs = $hmss%60;
|
|
return plus($days,'day',($hour|$mins|$secs) == 0).plus($hour,'hour',($mins|$secs) == 0).plus($mins,'minute',$secs == 0).plus($secs,'second',true).". "._('Average speed').": ".(is_numeric($speed) ? my_scale($speed,$unit,1)." $unit/s" : $speed);
|
|
}
|
|
|
|
function my_error($code) {
|
|
switch ($code) {
|
|
case -4:
|
|
return "<em>"._('aborted')."</em>";
|
|
default:
|
|
return "<strong>$code</strong>";
|
|
}
|
|
}
|
|
|
|
function mk_option($select, $value, $text, $extra="") {
|
|
$value = htmlspecialchars($value);
|
|
$text = htmlspecialchars($text);
|
|
return "<option value='$value'".($value == $select ? " selected" : "").(strlen($extra) ? " $extra" : "").">$text</option>";
|
|
}
|
|
|
|
function mk_option_check($name, $value, $text="") {
|
|
if ($text) {
|
|
$checked = in_array($value,explode(',',$name)) ? " selected" : "";
|
|
return "<option value='$value'$checked>$text</option>";
|
|
}
|
|
if (strpos($name,'disk') !== false) {
|
|
$checked = in_array($name,explode(',',$value)) ? " selected" : "";
|
|
return "<option value='$name'$checked>".my_disk($name)."</option>";
|
|
}
|
|
}
|
|
|
|
function mk_option_luks($name, $value, $luks) {
|
|
if (strpos($name,'disk') !== false) {
|
|
$checked = in_array($name,explode(',',$value)) ? " selected" : "";
|
|
return "<option luks='$luks' value='$name'$checked>".my_disk($name)."</option>";
|
|
}
|
|
}
|
|
|
|
function day_count($time) {
|
|
global $var;
|
|
if (!$time) return;
|
|
$datetz = new DateTimeZone($var['timeZone']);
|
|
$date = new DateTime("now", $datetz);
|
|
$offset = $datetz->getOffset($date);
|
|
$now = new DateTime("@".intval((time()+$offset)/86400)*86400);
|
|
$last = new DateTime("@".intval(($time+$offset)/86400)*86400);
|
|
$days = date_diff($last,$now)->format('%a');
|
|
switch (true) {
|
|
case ($days < 0):
|
|
return;
|
|
case ($days == 0):
|
|
return " <span class='green-text'>("._('today').")</span>";
|
|
case ($days == 1):
|
|
return " <span class='green-text'>("._('yesterday').")</span>";
|
|
case ($days <= 31):
|
|
return " <span class='green-text'>(".sprintf(_('%s days ago'),my_word($days)).")</span>";
|
|
case ($days <= 61):
|
|
return " <span class='orange-text'>(".sprintf(_('%s days ago'),$days).")</span>";
|
|
case ($days > 61):
|
|
return " <span class='red-text'>(".sprintf(_('%s days ago'),$days).")</span>";
|
|
}
|
|
}
|
|
|
|
function plus($val, $word, $last) {
|
|
return $val > 0 ? (($val || $last) ? ($val.' '._($word.($val != 1 ? 's' : '')).($last ? '' : ', ')) : '') : '';
|
|
}
|
|
|
|
function compress($name, $size=18, $end=6) {
|
|
return mb_strlen($name) <= $size ? $name : mb_substr($name, 0, $size-($end ? $end+3 : 0)).'...'.($end ? mb_substr($name,-$end) : '');
|
|
}
|
|
|
|
function escapestring($name) {
|
|
return "\"$name\"";
|
|
}
|
|
|
|
function tail($file, $rows=1) {
|
|
$file = new SplFileObject($file);
|
|
$file->seek(PHP_INT_MAX);
|
|
$file->seek($file->key()-$rows);
|
|
$echo = [];
|
|
while (!$file->eof()) {
|
|
$echo[] = $file->current();
|
|
$file->next();
|
|
}
|
|
return implode($echo);
|
|
}
|
|
|
|
/* Get the last parity check from the parity history. */
|
|
function last_parity_log() {
|
|
$log = '/boot/config/parity-checks.log';
|
|
if (file_exists($log)) {
|
|
[$date, $duration, $speed, $status, $error, $action, $size] = my_explode('|', tail($log), 7);
|
|
} else {
|
|
[$date, $duration, $speed, $status, $error, $action, $size] = array_fill(0, 7, 0);
|
|
}
|
|
if ($date) {
|
|
[$y, $m, $d, $t] = my_preg_split('/ +/', $date, 4);
|
|
$date = strtotime("$d-$m-$y $t");
|
|
}
|
|
return [$date, $duration, $speed, $status, $error, $action, $size];
|
|
}
|
|
|
|
|
|
/* Get the last parity check from Unraid. */
|
|
function last_parity_check() {
|
|
global $var;
|
|
/* Files for the latest parity check. */
|
|
$stamps = '/var/tmp/stamps.ini';
|
|
$resync = '/var/tmp/resync.ini';
|
|
/* Get the latest parity information from Unraid. */
|
|
$synced = file_exists($stamps) ? explode(',',file_get_contents($stamps)) : [];
|
|
$sbSynced = array_shift($synced) ?: _var($var,'sbSynced',0);
|
|
$idle = [];
|
|
while (count($synced) > 1) {
|
|
$idle[] = array_pop($synced) - array_pop($synced);
|
|
}
|
|
$action = _var($var, 'mdResyncAction');
|
|
$size = _var($var, 'mdResyncSize', 0);
|
|
if (file_exists($resync)) {
|
|
list($action, $size) = my_explode(',', file_get_contents($resync));
|
|
}
|
|
$duration = $var['sbSynced2']-$sbSynced-array_sum($idle);
|
|
$status = _var($var,'sbSyncExit');
|
|
$speed = $status==0 ? round($size*1024/$duration) : 0;
|
|
$error = _var($var,'sbSyncErrs',0);
|
|
return [$duration, $speed, $status, $error, $action, $size];
|
|
}
|
|
|
|
function urlencode_path($path) {
|
|
return str_replace("%2F", "/", urlencode($path));
|
|
}
|
|
|
|
function check_deprecated_filesystem($disk) {
|
|
$fsType = _var($disk, 'fsType', '');
|
|
$name = _var($disk, 'name', '');
|
|
$warnings = [];
|
|
|
|
// Check for ReiserFS
|
|
if (stripos($fsType, 'reiserfs') !== false) {
|
|
$warnings[] = [
|
|
'type' => 'reiserfs',
|
|
'severity' => 'critical',
|
|
'message' => _('ReiserFS is deprecated and will not be supported in future Unraid releases')
|
|
];
|
|
}
|
|
|
|
// Check for XFS v4 (lacks CRC checksums)
|
|
if (stripos($fsType, 'xfs') !== false) {
|
|
// Check if disk is mounted to determine XFS version
|
|
$mountPoint = "/mnt/$name";
|
|
if (is_dir($mountPoint) && exec("mountpoint -q " . escapeshellarg($mountPoint) . " 2>/dev/null", $output, $ret) && $ret == 0) {
|
|
// Check for crc=0 which indicates XFS v4
|
|
$xfsInfo = shell_exec("xfs_info " . escapeshellarg($mountPoint) . " 2>/dev/null");
|
|
if ($xfsInfo && strpos($xfsInfo, 'crc=0') !== false) {
|
|
$warnings[] = [
|
|
'type' => 'xfs_v4',
|
|
'severity' => 'critical',
|
|
'message' => _('XFS v4 is deprecated and will not be supported in future Unraid releases. Please migrate to XFS v5 immediately')
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $warnings;
|
|
}
|
|
|
|
function get_filesystem_warning_icon($warnings) {
|
|
if (empty($warnings)) return '';
|
|
|
|
$hasCritical = false;
|
|
$messages = [];
|
|
|
|
foreach ($warnings as $warning) {
|
|
if ($warning['severity'] == 'critical') {
|
|
$hasCritical = true;
|
|
}
|
|
$messages[] = $warning['message'];
|
|
}
|
|
|
|
$icon = $hasCritical ? 'exclamation-triangle' : 'exclamation-circle';
|
|
$color = $hasCritical ? 'red-text' : 'orange-text';
|
|
$tooltip = implode('. ', $messages);
|
|
|
|
return " <i class='fa fa-$icon $color' title='$tooltip'></i>";
|
|
}
|
|
|
|
function pgrep($process_name, $escape_arg=true) {
|
|
$pid = exec('pgrep --ns $$ '.($escape_arg ? escapeshellarg($process_name) : $process_name), $output, $retval);
|
|
return $retval == 0 ? $pid : false;
|
|
}
|
|
|
|
function is_block($path) {
|
|
return (@filetype(realpath($path)) == 'block');
|
|
}
|
|
|
|
function autov($file, $ret=false) {
|
|
global $docroot;
|
|
$path = $docroot.$file;
|
|
clearstatcache(true, $path);
|
|
$time = file_exists($path) ? filemtime($path) : 'autov_fileDoesntExist';
|
|
$newFile = "$file?v=".$time;
|
|
if ($ret)
|
|
return $newFile;
|
|
else
|
|
echo $newFile;
|
|
}
|
|
|
|
function transpose_user_path($path) {
|
|
if (strpos($path,'/mnt/user/') === 0 && file_exists($path)) {
|
|
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null"));
|
|
if (!empty($realdisk))
|
|
$path = str_replace('/mnt/user/', "/mnt/$realdisk/", $path);
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
function cpu_list() {
|
|
exec('cat /sys/devices/system/cpu/*/topology/thread_siblings_list|sort -nu', $cpus);
|
|
return $cpus;
|
|
}
|
|
|
|
function my_explode($split, $text, $count=2) {
|
|
return array_pad(explode($split, $text, $count), $count, '');
|
|
}
|
|
|
|
function my_preg_split($split, $text, $count=2) {
|
|
return array_pad(preg_split($split, $text, $count), $count, '');
|
|
}
|
|
|
|
function delete_file(...$file) {
|
|
array_map('unlink', array_filter($file,'file_exists'));
|
|
}
|
|
|
|
function my_mkdir($dirname, $permissions=0777, $recursive=false, $own="nobody", $grp="users") {
|
|
write_logging("Check if dir exists\n");
|
|
if (is_dir($dirname)) {write_logging("Dir exists\n"); return(false);}
|
|
write_logging("Dir does not exist\n");
|
|
$parent = $dirname;
|
|
write_logging("Getting $parent\n");
|
|
while (!is_dir($parent)){
|
|
if (!is_dir($parent)) write_logging("Not parent $parent\n"); else write_logging("Parent $parent is\n");
|
|
if (!$recursive) return(false);
|
|
$pathinfo2 = pathinfo($parent);
|
|
$parent = $pathinfo2["dirname"];
|
|
}
|
|
write_logging("Parent $parent\n");
|
|
if (strpos($dirname,'/mnt/user/') === 0) {
|
|
write_logging("Getting real disks\n");
|
|
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($parent)." 2>/dev/null"));
|
|
if (!empty($realdisk)) {
|
|
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname);
|
|
$parent = str_replace('/mnt/user/', "/mnt/$realdisk/", $parent);
|
|
}
|
|
}
|
|
$fstype = trim(shell_exec(" stat -f -c '%T' $parent"));
|
|
$rtncode = false;
|
|
write_logging("fstype:$fstype parent $parent dir name $dirname\n");
|
|
switch ($fstype) {
|
|
case "zfs":
|
|
if (is_dir($parent.'/.zfs')) {
|
|
write_logging("ZFS Volume\n");
|
|
$zfsdataset = trim(shell_exec("zfs list -H -o name $parent"));
|
|
write_logging("Shell $zfsdataset\n");
|
|
$zfsdataset .= str_replace($parent,"",$dirname);
|
|
write_logging("Dataset $zfsdataset\n");
|
|
$zfsoutput = array();
|
|
if ($recursive) exec("zfs create -p \"$zfsdataset\"",$zfsoutput,$rtncode);else exec("zfs create \"$zfsdataset\"", $zfsoutput, $rtncode);
|
|
write_logging("Output: {$zfsoutput[0]} $rtncode");
|
|
if ($rtncode == 0) write_logging( " ZFS Command OK\n"); else write_logging( "ZFS Command Fail\n");
|
|
} else {write_logging("Not ZFS dataset\n");$rtncode = 1;}
|
|
if ($rtncode > 0) { mkdir($dirname, $permissions, $recursive); write_logging( "created dir:$dirname\n");} else chmod($zfsdataset, $permissions);
|
|
break;
|
|
case "btrfs":
|
|
$btrfsoutput = array();
|
|
if ($recursive) exec("btrfs subvolume create --parents \"$dirname\"",$btrfsoutput,$rtncode); else exec("btrfs subvolume create \"$dirname\"", $btrfsoutput, $rtncode);
|
|
if ($rtncode > 0) mkdir($dirname, $permissions, $recursive); else chmod($dirname, $permissions);
|
|
break;
|
|
default:
|
|
mkdir($dirname, $permissions, $recursive);
|
|
break;
|
|
}
|
|
chown($dirname, $own);
|
|
chgrp($dirname, $grp);
|
|
return($rtncode);
|
|
}
|
|
|
|
function my_rmdir($dirname) {
|
|
if (!is_dir("$dirname")) {
|
|
$return = [
|
|
'rtncode' => "false",
|
|
'type' => "NoDir",
|
|
];
|
|
return($return);
|
|
}
|
|
if (strpos($dirname,'/mnt/user/') === 0) {
|
|
$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");
|
|
}
|
|
}
|
|
$fstype = trim(shell_exec(" stat -f -c '%T' ".escapeshellarg($dirname)));
|
|
$rtncode = false;
|
|
switch ($fstype) {
|
|
case "zfs":
|
|
$zfsoutput = array();
|
|
$zfsdataset = trim(shell_exec("zfs list -H -o name ".escapeshellarg($dirname))) ;
|
|
$cmdstr = "zfs destroy \"$zfsdataset\" 2>&1 ";
|
|
$error = exec($cmdstr,$zfsoutput,$rtncode);
|
|
$return = [
|
|
'rtncode' => $rtncode,
|
|
'output' => $zfsoutput,
|
|
'dataset' => $zfsdataset,
|
|
'type' => $fstype,
|
|
'cmd' => $cmdstr,
|
|
'error' => $error,
|
|
];
|
|
break;
|
|
case "btrfs":
|
|
default:
|
|
$rtncode = rmdir($dirname);
|
|
$return = [
|
|
'rtncode' => $rtncode,
|
|
'type' => $fstype,
|
|
];
|
|
break;
|
|
}
|
|
return($return);
|
|
}
|
|
|
|
function get_realvolume($path) {
|
|
if (strpos($path,"/mnt/user/",0) === 0)
|
|
$reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null"));
|
|
else {
|
|
$realexplode = explode("/",str_replace("/mnt/","",$path));
|
|
$reallocation = $realexplode[0];
|
|
}
|
|
return $reallocation;
|
|
}
|
|
|
|
function write_logging($value) {
|
|
$debug = is_file("/tmp/my_mkdir_debug");
|
|
if (!$debug) return;
|
|
file_put_contents('/tmp/my_mkdir_output', $value, FILE_APPEND);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
# Check for process Core Types.
|
|
function parse_cpu_ranges($file) {
|
|
if (!is_file($file)) return null;
|
|
$ranges = file_get_contents($file);
|
|
$ranges = trim($ranges);
|
|
if ($ranges === '') return null;
|
|
$cores = [];
|
|
foreach (explode(',', $ranges) as $range) {
|
|
if (strpos($range, '-') !== false) {
|
|
list($start, $end) = explode('-', $range);
|
|
$cores = array_merge($cores, range((int)$start, (int)$end));
|
|
} else {
|
|
$cores[] = (int)$range;
|
|
}
|
|
}
|
|
return $cores;
|
|
}
|
|
|
|
function get_intel_core_types() {
|
|
$core_types = array();
|
|
$cpu_core_file = "/sys/devices/cpu_core/cpus";
|
|
$cpu_atom_file = "/sys/devices/cpu_atom/cpus";
|
|
$p_cores = parse_cpu_ranges($cpu_core_file);
|
|
$e_cores = parse_cpu_ranges($cpu_atom_file);
|
|
if ($p_cores) {
|
|
foreach ($p_cores as $core) {
|
|
$core_types[$core] = _("P-Core");
|
|
}
|
|
}
|
|
if ($e_cores) {
|
|
foreach ($e_cores as $core) {
|
|
$core_types[$core] = _("E-Core");
|
|
}
|
|
}
|
|
return $core_types;
|
|
}
|
|
|
|
function dmidecode($key, $n, $all=true) {
|
|
$entries = array_filter(explode($key, shell_exec("dmidecode -qt$n")??""));
|
|
$properties = [];
|
|
foreach ($entries as $entry) {
|
|
$property = [];
|
|
foreach (explode("\n",$entry) as $line) if (strpos($line,': ') !== false) {
|
|
[$key, $value] = my_explode(': ',trim($line));
|
|
$property[$key] = $value;
|
|
}
|
|
$properties[] = $property;
|
|
}
|
|
return $all ? $properties : $properties[0] ?? null;
|
|
}
|
|
|
|
function is_intel_cpu() {
|
|
$cpu = dmidecode('Processor Information','4',0);
|
|
$cpu_vendor = $cpu['Manufacturer'] ?? "";
|
|
$is_intel_cpu = stripos($cpu_vendor, "intel") !== false ? true : false;
|
|
return $is_intel_cpu;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
function clone_list($disk) {
|
|
global $pools;
|
|
return strpos($disk['status'],'_NP') === false && ($disk['type'] == 'Data' || in_array($disk['name'], $pools));
|
|
}
|
|
|
|
// Deprecated filesystem detection and display functions
|
|
|
|
// Core function to check a single disk for deprecated filesystems
|
|
function check_disk_for_deprecated_fs($disk) {
|
|
$deprecated = [];
|
|
$fsType = strtolower(_var($disk, 'fsType', ''));
|
|
|
|
// Check for ReiserFS
|
|
if (strpos($fsType, 'reiserfs') !== false) {
|
|
$deprecated[] = [
|
|
'name' => _var($disk, 'name'),
|
|
'fsType' => 'ReiserFS',
|
|
'severity' => 'critical',
|
|
'message' => 'ReiserFS is deprecated and will not be supported in future Unraid releases'
|
|
];
|
|
}
|
|
|
|
// Check for XFS v4 (lacks CRC checksums)
|
|
if (strpos($fsType, 'xfs') !== false) {
|
|
$name = _var($disk, 'name');
|
|
$mountPoint = "/mnt/$name";
|
|
|
|
// Check if disk is mounted
|
|
if (is_dir($mountPoint)) {
|
|
exec("mountpoint -q " . escapeshellarg($mountPoint) . " 2>/dev/null", $output, $ret);
|
|
if ($ret == 0) {
|
|
// Get XFS info to check for crc=0 which indicates XFS v4
|
|
$xfsInfo = shell_exec("xfs_info " . escapeshellarg($mountPoint) . " 2>/dev/null");
|
|
if ($xfsInfo && strpos($xfsInfo, 'crc=0') !== false) {
|
|
$deprecated[] = [
|
|
'name' => $name,
|
|
'fsType' => 'XFS v4',
|
|
'severity' => 'notice',
|
|
'message' => 'XFS v4 is deprecated and will not be supported in future Unraid releases. You have until 2030 to migrate to XFS v5.'
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $deprecated;
|
|
}
|
|
|
|
// Generate inline warning HTML for a single disk
|
|
function get_inline_fs_warnings($disk) {
|
|
$warnings = check_disk_for_deprecated_fs($disk);
|
|
$html = '';
|
|
|
|
foreach ($warnings as $warning) {
|
|
if ($warning['severity'] === 'critical') {
|
|
// ReiserFS - critical warning
|
|
$html .= '<span id="reiserfs" class="warning"><i class="fa fa-exclamation-triangle"></i> ' .
|
|
htmlspecialchars(_($warning['message'])) . '</span>';
|
|
} else {
|
|
// XFS v4 - notice
|
|
$html .= '<span id="xfsv4" class="notice" style="color:#0066cc;"><i class="fa fa-info-circle"></i> ' .
|
|
htmlspecialchars(_($warning['message'])) . '</span>';
|
|
}
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
// Check array of disks for deprecated filesystems (used by Main page)
|
|
function check_deprecated_filesystems_array($disks, $filter_function) {
|
|
$deprecated = [];
|
|
|
|
foreach ($filter_function($disks) as $disk) {
|
|
if (substr($disk['status'],0,7) != 'DISK_NP') {
|
|
$disk_warnings = check_disk_for_deprecated_fs($disk);
|
|
$deprecated = array_merge($deprecated, $disk_warnings);
|
|
}
|
|
}
|
|
|
|
return $deprecated;
|
|
}
|
|
|
|
function display_deprecated_filesystem_warning($deprecated_disks, $type = 'array') {
|
|
if (empty($deprecated_disks)) return '';
|
|
|
|
// Separate warnings by severity
|
|
$critical_disks = [];
|
|
$notice_disks = [];
|
|
|
|
foreach ($deprecated_disks as $disk) {
|
|
if (_var($disk, 'severity', 'critical') === 'critical') {
|
|
$critical_disks[] = $disk;
|
|
} else {
|
|
$notice_disks[] = $disk;
|
|
}
|
|
}
|
|
|
|
$html = '';
|
|
|
|
// Critical warnings (ReiserFS) - severe styling, reappears on every page load
|
|
if (!empty($critical_disks)) {
|
|
$id = $type === 'array' ? 'array-critical-warning' : 'pool-critical-warning';
|
|
$title = htmlspecialchars($type === 'array' ? 'Critical: Deprecated Filesystem' : 'Critical: Pool Deprecated Filesystem');
|
|
$description = htmlspecialchars($type === 'array' ?
|
|
'The following array devices are using deprecated filesystems:' :
|
|
'The following pool devices are using deprecated filesystems:');
|
|
|
|
$diskList = '';
|
|
foreach ($critical_disks as $disk) {
|
|
$name = htmlspecialchars($disk['name']);
|
|
$fsType = htmlspecialchars($disk['fsType']);
|
|
$message = htmlspecialchars($disk['message']);
|
|
$diskList .= "<li><strong>{$name}:</strong> {$fsType} - {$message}</li>\n";
|
|
}
|
|
|
|
$html .= <<<HTML
|
|
<div id="{$id}" style="margin: 20px 0;">
|
|
<div style="background: #feefb3; border: 1px solid #ff8c2f; border-radius: 4px; padding: 15px; position: relative;">
|
|
<button onclick="$('#{$id}').fadeOut();"
|
|
style="position: absolute; right: 10px; top: 10px; background: transparent; border: none; color: #ff8c2f; cursor: pointer; font-size: 1.2em;">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
<div style="display: flex; align-items: start;">
|
|
<i class="fa fa-exclamation-triangle" style="color: #ff8c2f; margin-right: 10px; font-size: 1.2em;"></i>
|
|
<div style="flex: 1; color: #000;">
|
|
<div style="font-weight: bold; margin-bottom: 10px; color: #ff8c2f;">
|
|
{$title}
|
|
</div>
|
|
<div style="margin-bottom: 10px;">
|
|
{$description}
|
|
</div>
|
|
<ul style="margin: 10px 0 10px 20px;">
|
|
{$diskList}
|
|
</ul>
|
|
<div style="margin-top: 10px;">
|
|
<strong>Action Required:</strong> Migrate to a supported filesystem (XFS v5, BTRFS, or ZFS).
|
|
<a href="https://docs.unraid.net/go/convert-reiser-and-xfs"
|
|
target="_blank" style="color: #ff8c2f;">View migration guide →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
HTML;
|
|
}
|
|
|
|
// Notice warnings (XFS v4) - less severe styling, dismissible until reboot via sessionStorage
|
|
if (!empty($notice_disks)) {
|
|
$id = $type === 'array' ? 'array-notice-warning' : 'pool-notice-warning';
|
|
$title = htmlspecialchars($type === 'array' ? 'Notice: Filesystem Update Available' : 'Notice: Pool Filesystem Update Available');
|
|
$description = htmlspecialchars($type === 'array' ?
|
|
'The following array devices are using older filesystem versions:' :
|
|
'The following pool devices are using older filesystem versions:');
|
|
|
|
$diskList = '';
|
|
foreach ($notice_disks as $disk) {
|
|
$name = htmlspecialchars($disk['name']);
|
|
$fsType = htmlspecialchars($disk['fsType']);
|
|
$message = htmlspecialchars($disk['message']);
|
|
$diskList .= "<li><strong>{$name}:</strong> {$fsType} - {$message}</li>\n";
|
|
}
|
|
|
|
$html .= <<<HTML
|
|
<script>
|
|
// Check if XFS warning was dismissed this session
|
|
if (!sessionStorage.getItem('xfs-{$id}-dismissed')) {
|
|
document.write(`
|
|
<div id="{$id}" style="margin: 20px 0;">
|
|
<div style="background: #e7f3ff; border: 1px solid #0066cc; border-radius: 4px; padding: 15px; position: relative;">
|
|
<button onclick="sessionStorage.setItem('xfs-{$id}-dismissed', 'true'); $('#{$id}').fadeOut();"
|
|
style="position: absolute; right: 10px; top: 10px; background: transparent; border: none; color: #0066cc; cursor: pointer; font-size: 1.2em;"
|
|
title="Dismiss until reboot">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
<div style="display: flex; align-items: start;">
|
|
<i class="fa fa-info-circle" style="color: #0066cc; margin-right: 10px; font-size: 1.2em;"></i>
|
|
<div style="flex: 1; color: #000;">
|
|
<div style="font-weight: bold; margin-bottom: 10px; color: #0066cc;">
|
|
{$title}
|
|
</div>
|
|
<div style="margin-bottom: 10px;">
|
|
{$description}
|
|
</div>
|
|
<ul style="margin: 10px 0 10px 20px;">
|
|
{$diskList}
|
|
</ul>
|
|
<div style="margin-top: 10px;">
|
|
<strong>Recommendation:</strong> Plan to migrate to XFS v5, BTRFS, or ZFS within the next 5 years.
|
|
<a href="https://docs.unraid.net/go/convert-reiser-and-xfs"
|
|
target="_blank" style="color: #0066cc;">View migration guide →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
</script>
|
|
HTML;
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
?>
|