Files
webgui/emhttp/plugins/dynamix/include/Helpers.php
Eli Bosley 9603eaa00a fix: Clarify XFS v4 deprecation timeline in warning message
- 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.
2025-09-12 15:28:30 -04:00

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)).'&#8201;&#176;'.$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>&nbsp;' .
htmlspecialchars(_($warning['message'])) . '</span>';
} else {
// XFS v4 - notice
$html .= '<span id="xfsv4" class="notice" style="color:#0066cc;"><i class="fa fa-info-circle"></i>&nbsp;' .
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;
}
?>