$disks = []; $var = [];
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/CustomMerge.php";
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/webGui/include/Preselect.php";
function normalize($text, $glue='_') {
$words = explode($glue,$text);
foreach ($words as &$word) $word = $word==strtoupper($word) ? $word : preg_replace(['/^(ct|cnt)$/','/^blk$/'],['count','block'],strtolower($word));
return "
".ucfirst(implode(' ',$words))." ";
}
function duration(&$hrs) {
$time = ceil(time()/3600)*3600;
$now = new DateTime("@$time");
$poh = new DateTime("@".($time-$hrs*3600));
$age = date_diff($poh,$now);
$hrs = "$hrs (".($age->y?"{$age->y}y, ":"").($age->m?"{$age->m}m, ":"").($age->d?"{$age->d}d, ":"")."{$age->h}h)";
}
function spindownDelay($port) {
$disks = parse_ini_file('state/disks.ini',true);
foreach ($disks as $disk) {
$name = substr($disk['device'],-2)!='n1' ? $disk['device'] : substr($disk['device'],0,-2);
if ($name==$port) {file_put_contents("/var/tmp/diskSpindownDelay.{$disk['idx']}", $disk['spindownDelay']); break;}
}
}
function append(&$ref, &$info) {
if ($info) $ref .= ($ref ? " " : "").$info;
}
$name = $_POST['name'] ?? '';
$port = $_POST['port'] ?? '';
if ($name) {
$disk = &$disks[$name];
$type = get_value($disk,'smType','');
get_ctlr_options($type, $disk);
} else {
$disk = [];
$type = '';
}
$port = port_name($disk['smDevice'] ?? $port);
switch ($_POST['cmd']) {
case "attributes":
$select = get_value($disk,'smSelect',0);
$level = get_value($disk,'smLevel',1);
$events = explode('|',get_value($disk,'smEvents',$numbers));
$unraid = parse_plugin_cfg('dynamix',true);
$max = $disk['maxTemp'] ?? $unraid['display']['max'];
$hot = $disk['hotTemp'] ?? $unraid['display']['hot'];
$top = $_POST['top'] ?? 120;
exec("smartctl -A $type ".escapeshellarg("/dev/$port")."|awk 'NR>4'",$output);
if (strpos($output[0], 'SMART Attributes Data Structure')===0) {
$output = array_slice($output, 3);
$empty = true;
foreach ($output as $line) {
if (!$line) continue;
$info = explode(' ', trim(preg_replace('/\s+/',' ',$line)), 10);
$color = "";
$highlight = strpos($info[8],'FAILING_NOW')!==false || ($select ? $info[5]>0 && $info[3]<=$info[5]*$level : $info[9]>0);
if (in_array($info[0], $events) && $highlight) $color = " class='warn'";
elseif (in_array($info[0], [190,194])) {
if (exceed($info[9],$max,$top)) $color = " class='alert'"; elseif (exceed($info[9],$hot,$top)) $color = " class='warn'";
}
if ($info[8]=='-') $info[8] = 'Never';
if ($info[0]==9 && is_numeric($info[9])) duration($info[9]);
echo "".implode('',array_map('normalize', $info))." ";
$empty = false;
}
if ($empty) echo "Can not read attributes ";
} else {
// probably a NMVe or SAS device that smartmontools doesn't know how to parse in to a SMART Attributes Data Structure
foreach ($output as $line) {
if (strpos($line,':')===false) continue;
list($name,$value) = explode(':', $line);
$name = ucfirst(strtolower($name));
$value = trim($value);
$color = '';
switch ($name) {
case 'Temperature':
$temp = strtok($value,' ');
if (exceed($temp,$max)) $color = " class='alert'"; elseif (exceed($temp,$hot)) $color = " class='warn'";
break;
case 'Power on hours':
if (is_numeric($value)) duration($value);
break;
}
echo "- $name $value ";
}
}
break;
case "capabilities":
exec("smartctl -c $type ".escapeshellarg("/dev/$port")."|awk 'NR>5'",$output);
$row = ['','',''];
$empty = true;
foreach ($output as $line) {
if (!$line) continue;
$line = preg_replace('/^_/','__',preg_replace(['/__+/','/_ +_/'],'_',str_replace([chr(9),')','('],'_',$line)));
$info = array_map('trim', explode('_', preg_replace('/_( +)_ /','__',$line), 3));
append($row[0],$info[0]);
append($row[1],$info[1]);
append($row[2],$info[2]);
if (substr($row[2],-1)=='.') {
echo "${row[0]} ${row[1]} ${row[2]} ";
$row = ['','',''];
$empty = false;
}
}
if ($empty) echo "Can not read capabilities ";
break;
case "identify":
$passed = ['PASSED','OK'];
$failed = ['FAILED','NOK'];
exec("smartctl -i $type ".escapeshellarg("/dev/$port")."|awk 'NR>4'",$output);
exec("smartctl -H $type ".escapeshellarg("/dev/$port")."|grep -Pom1 '^SMART.*: [A-Z]+'|sed 's:self-assessment test result::'",$output);
$empty = true;
foreach ($output as $line) {
if (!$line) continue;
if (strpos($line,'VALID ARGUMENTS')!==false) break;
list($title,$info) = array_map('trim', explode(':', $line, 2));
if (in_array($info,$passed)) $info = "Passed ";
if (in_array($info,$failed)) $info = "Failed ";
echo "".normalize(preg_replace('/ is:$/',':',"$title:"),' ')."$info ";
$empty = false;
}
if ($empty) {
echo "Can not read identification ";
} else {
$file = '/boot/config/disk.log';
$disks = parse_ini_file('state/disks.ini',true);
$extra = file_exists($file) ? parse_ini_file($file,true) : [];
$disk = $disks[$name]['id'];
$info = &$extra[$disk];
$periods = ['6','12','18','24','36','48','60'];
echo "Manufacturing date: ";
echo "Date of purchase: ";
echo "Warranty period: unknown ";
foreach ($periods as $period) echo "$period months ";
echo " ";
}
break;
case "save":
exec("smartctl -x $type ".escapeshellarg("/dev/$port")." >".escapeshellarg("$docroot/{$_POST['file']}"));
break;
case "delete":
if (strpos(realpath("/var/tmp/{$_POST['file']}"), "/var/tmp/") === 0) {
@unlink("/var/tmp/{$_POST['file']}");
}
break;
case "short":
spindownDelay($port);
exec("smartctl -t short $type ".escapeshellarg("/dev/$port"));
break;
case "long":
spindownDelay($port);
exec("smartctl -t long $type ".escapeshellarg("/dev/$port"));
break;
case "stop":
exec("smartctl -X $type ".escapeshellarg("/dev/$port"));
break;
case "update":
if (!exec("hdparm -C ".escapeshellarg("/dev/$port")."|grep -Pom1 'active|unknown'")) {
$cmd = $_POST['type']=='New' ? "cmd=/webGui/scripts/hd_parm&arg1=up&arg2=$name" : "cmdSpinup=$name";
echo "Unavailable - disk must be spun up ";
break;
}
$progress = exec("smartctl -c $type ".escapeshellarg("/dev/$port")."|grep -Pom1 '\d+%'");
if ($progress) {
echo " self-test in progress, ".(100-substr($progress,0,-1))."% complete ";
break;
}
$result = trim(exec("smartctl -l selftest $type ".escapeshellarg("/dev/$port")."|grep -m1 '^# 1'|cut -c26-55"));
if (!$result) {
echo "No self-tests logged on this disk ";
break;
}
if (strpos($result, "Completed without error")!==false) {
echo "$result ";
break;
}
if (strpos($result, "Aborted")!==false or strpos($result, "Interrupted")!==false) {
echo "$result ";
break;
}
echo "Errors occurred - Check SMART report ";
break;
case "selftest":
echo shell_exec("smartctl -l selftest $type ".escapeshellarg("/dev/$port")."|awk 'NR>5'");
break;
case "errorlog":
echo shell_exec("smartctl -l error $type ".escapeshellarg("/dev/$port")."|awk 'NR>5'");
break;
}
?>