2020-01-02 22:14:03 -08:00
< ? php
// Change directory to here
$thisDir = DirName ( __FILE__ );
2020-01-02 22:53:48 -08:00
2020-01-02 22:14:03 -08:00
// Make sure we're root
$whoami = Trim ( Shell_Exec ( " whoami " ));
if ( $whoami != 'root' ){
die ( " Please run as root (Currently: $whoami ) " );
}
2020-01-02 22:53:48 -08:00
2020-01-02 22:14:03 -08:00
// First parameter should be path to config file
$config_path = $argv [ 1 ];
if ( ! File_Exists ( $config_path )){
die ( " \n Invalid config path: { $config_path } ; Current directory: " . GetCWD () . " \n " );
}
if ( ! Is_File ( $config_path )){
die ( " \n Config file ... isn't a file?: { $config_path } \n " );
}
2020-01-02 22:53:48 -08:00
$config_path = RealPath ( $config_path );
2020-01-02 22:14:03 -08:00
// Second could be a test type (optional)
$bStartTests_short = false ;
$bStartTests_long = false ;
if ( IsSet ( $argv [ 2 ])){
if ( $argv [ 2 ] == 'start-tests-short' ){
$bStartTests_short = true ;
}
else if ( $argv [ 2 ] == 'start-tests-long' ){
$bStartTests_long = true ;
}
else {
DoError ( " Unknown test type: { $argv [ 2 ] } " );
}
}
2020-01-02 22:53:48 -08:00
2020-01-02 22:14:03 -08:00
// Load config
2020-01-02 22:53:48 -08:00
$config = LoadConfig ( $config_path );
2020-01-02 22:14:03 -08:00
// Show information about each drive
2020-01-02 22:53:48 -08:00
foreach ( $config [ 'devices' ] as $name => $device ) {
2020-01-02 22:14:03 -08:00
//
$match = true ;
//
print " \n " . Str_Repeat ( '*' , 30 );
print " \n *** Entry: { $name } " ;
print " \n Path= { $device [ 'path' ] } ; Type= { $device [ 'device' ] } " ;
//
$device_safe = EscapeShellArg ( $device [ 'device' ]);
$path_safe = EscapeShellArg ( $device [ 'path' ]);
// If the path doesn't exist, possibly fail
Exec ( " [ -e { $path_safe } ] " , $output_lines , $return_code );
if ( $return_code != 0 ){
print " \n Path does not exist! " ;
if ( $device [ 'bRequired' ]){
DoError ( " Path doesn't exist but was required: { $device [ 'path' ] } " );
}
print " \n " ;
continue ;
}
// Is this even a block device?
Exec ( " [ -b { $path_safe } ] " , $output_lines , $return_code );
if ( $return_code != 0 ){
DoError ( " Path doesn't seem to point to a block device? " );
continue ;
}
// Ask smartctl for status
$command = " smartctl --all --device { $device_safe } { $path_safe } " ;
2020-01-02 22:53:48 -08:00
print " \n COMMAND: { $command } " ;
2020-01-02 22:14:03 -08:00
$output_lines = false ;
Exec ( $command , $output_lines , $smart_return_code );
$lines = Implode ( " \n " , $output_lines );
// Output any messages indicated by smartctl
2020-01-02 22:53:48 -08:00
$messages = CheckNamedBits ( $smart_return_code );
foreach ( $messages as $message ) {
2020-01-02 22:14:03 -08:00
print " \n MESSAGE: { $message } " ;
}
// Test Status:
if ( Preg_Match ( '@^Self-test execution status.+?(?P<remainder>[0-9]+\% of test remaining)\.@smi' , $lines , $match )){
print " \n { $match [ 'remainder' ] } " ;
}
else if ( Preg_Match ( '@^Self-test execution status.+?(completed\s+without\s+error)@smi' , $lines , $match )){
//
print " \n No test seems to be running " ;
// Start short test?
if ( $bStartTests_short ){
print " ; Will start short test now! \n " ;
Exec ( " smartctl --test=short --device { $device_safe } { $path_safe } " );
continue ;
}
// Start long test?
else if ( $bStartTests_long ){
print " ; Will start long test now! \n " ;
Exec ( " smartctl --test=long --device { $device_safe } { $path_safe } " );
continue ;
}
}
else {
DoError ( " Could not locate execution status information " );
}
// Check for terrible states
if ( CheckBitPosition ( $smart_return_code , 3 )){
$error_message = Str_Repeat ( '!' , 10 ) . " " . Str_Repeat ( '*' , 10 ) . " CATASTROPHY WARNING!!! DISK IS FAILING " . Str_Repeat ( '*' , 10 ) . " " . Str_Repeat ( '!' , 10 );
DoError ( $error_message );
}
if ( CheckBitPosition ( $smart_return_code , 4 )){
$error_message = Str_Repeat ( '!' , 10 ) . " " . Str_Repeat ( '*' , 10 ) . " DISK IS IN PREFAIL STATE " . Str_Repeat ( '*' , 10 ) . " " . Str_Repeat ( '!' , 10 );
DoError ( $error_message );
}
//
print " \n " ;
}
//
function LoadConfig ( $config_path )
{
//
$config = Array (
'global' => Array (),
'devices' => Array ()
);
// Load config
$ini = Parse_Ini_File ( $config_path , true );
if ( ! $ini ){
DoError ( " Unable to parse config file: { $config_path } " );
}
// Booleans
foreach ( $ini as $section_name =>& $section ){
foreach ( $section as $key =>& $value ){
//
$v = StrToLower ( $value );
switch ( $v )
{
//
case 'true' :
case '1' :
case 'on' :
case 'yes' :
$value = true ;
break ;
//
case 'false' :
case '0' :
case 'off' :
case 'no' :
$value = false ;
break ;
}
} Unset ( $value );
} Unset ( $section );
// Pull out global config
if ( IsSet ( $ini [ 'global' ])){
$config [ 'global' ] = $ini [ 'global' ];
Unset ( $ini [ 'global' ]);
}
// The remainder are devices
$config [ 'devices' ] = $ini ;
return $config ;
}
//
function CheckNamedBits ( $return_code )
{
//
$messages = Array ();
//
$map = Array (
2020-01-02 22:53:48 -08:00
'0' => " Command line did not parse. " ,
'1' => " Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode " ,
'2' => " Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure " ,
'3' => " SMART status check returned \" DISK FAILING \" . " ,
'4' => " We found prefail Attributes <= threshold. " ,
'5' => " SMART status check returned \" DISK OK \" but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past. " ,
'6' => " The device error log contains records of errors. " ,
'7' => " The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored. "
2020-01-02 22:14:03 -08:00
);
//
foreach ( $map as $bitIndex => $message ){
if ( CheckBitPosition ( $return_code , $bitIndex )){
$messages [] = $message ;
}
}
return $messages ;
}
//
2020-01-02 22:53:48 -08:00
function CheckBitPosition ( $toCheck , $index )
2020-01-02 22:14:03 -08:00
{
//
2020-01-02 22:53:48 -08:00
$value = Pow ( 2 , $index );
2020-01-02 22:14:03 -08:00
//
$bIsSet = false ;
if ( $toCheck == ( $toCheck | $value )){
$bIsSet = true ;
}
//
//print "\n toCheck=$toCheck, index=$index, value=$value, IsSet=".($bIsSet?'true':'false');
return $bIsSet ;
}
//
function DoError ( $message )
{
//print "\nError: {$message}";
LogError ( $message );
MailError ( $message );
}
//
function LogError ( $message )
{
//
$prefix = " [ " . ( BaseName ( __FILE__ )) . " ] " ;
//
$stderr = fOpen ( 'php://stderr' , 'w' );
fWrite ( $stderr , " \n { $message } " );
fClose ( $stderr );
//
$message_safe = EscapeShellArg ( " { $prefix } { $message } " );
Shell_Exec ( " logger { $message_safe } " );
}
//
function MailError ( $message )
{
//
global $config ;
//
$hostname = Shell_Exec ( " hostname " );
//
if ( ! IsSet ( $config [ 'global' ][ 'mailtos' ])){
return false ;
}
$mailtos = Explode ( " , " , $config [ 'global' ][ 'mailtos' ]);
//
foreach ( $mailtos as $mailto ){
//
$mailto = Trim ( $mailto );
//
if ( Filter_Var ( $mailto , FILTER_VALIDATE_EMAIL ) === FALSE ){
LogError ( " This doesn't seem to be a valid email address: { $mailto } " );
continue ;
}
if ( ! mail (
$mailto ,
" { $hostname } - smartctl error " ,
$message
))
{
LogError ( " PHP failed to send email to { $mailto } , for some reason " );
}
}
}
?>
Done