".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:"; } 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; } ?>