Import script from MikePeraltaScripts, I guess
This commit is contained in:
		
							
								
								
									
										293
									
								
								smart-disk-status.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								smart-disk-status.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,293 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	Change directory to here
 | 
				
			||||||
 | 
					$thisDir=DirName(__FILE__);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	Make sure we're root
 | 
				
			||||||
 | 
					$whoami=Trim(Shell_Exec("whoami"));
 | 
				
			||||||
 | 
					if($whoami!='root'){
 | 
				
			||||||
 | 
						die("Please run as root (Currently: $whoami)");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	First parameter should be path to config file
 | 
				
			||||||
 | 
					$config_path=$argv[1];
 | 
				
			||||||
 | 
					if(!File_Exists($config_path)){
 | 
				
			||||||
 | 
						die("\nInvalid config path: {$config_path}; Current directory: ".GetCWD()."\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					if(!Is_File($config_path)){
 | 
				
			||||||
 | 
						die("\nConfig file ... isn't a file?: {$config_path}\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					$config_path=RealPath($config_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	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]}");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	Load config
 | 
				
			||||||
 | 
					$config=LoadConfig($config_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//	Show information about each drive
 | 
				
			||||||
 | 
					foreach($config['devices'] as $name=>$device){
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						$match=true;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						print "\n".Str_Repeat('*',30);
 | 
				
			||||||
 | 
						print "\n*** Entry: {$name}";
 | 
				
			||||||
 | 
						print "\nPath={$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 "\nPath 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}";
 | 
				
			||||||
 | 
						$output_lines=false;
 | 
				
			||||||
 | 
						Exec($command,$output_lines,$smart_return_code);
 | 
				
			||||||
 | 
						$lines=Implode("\n",$output_lines);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//	Output any messages indicated by smartctl
 | 
				
			||||||
 | 
						$messages=CheckNamedBits($smart_return_code);
 | 
				
			||||||
 | 
						foreach($messages as $message){
 | 
				
			||||||
 | 
							print "\nMESSAGE: {$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 "\nNo 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(
 | 
				
			||||||
 | 
							'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."
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						foreach($map as $bitIndex=>$message){
 | 
				
			||||||
 | 
							if(CheckBitPosition($return_code,$bitIndex)){
 | 
				
			||||||
 | 
								$messages[]=$message;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return $messages;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					function CheckBitPosition($toCheck,$index)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						$value=Pow(2,$index);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						$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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user