#!/bin/bash
#
# __copy1__
# __copy2__
#
CMD=$(basename $0)
CMDVER="1.21"
CMDSTR="$CMD v$CMDVER (2025-12-27)"

set -u

usage()
{
	echo "
== $CMDSTR == show human readable SMART disk status ==

usage: $CMD [options] {-a | device(s)...}

options:
  -l	extended (long) infos
  -t	terse (compact) infos
" >&2
	exit 1
}


getnumber()
{
	local num=$(head -1 | sed \
		-e 's/ (.*//' -e 's/.* //' -e 's/ .*//' \
		-e 's/()//g' -e 's/h.*//' -e 's#/.*##')

	# if the number is too high, can be a combination of 2 or more numbers
	# we try to convert in exadecimal and split

	case $num in
	  ????????????*)
		num=$(echo -e "obase=16\n$num" | bc)
		case $num in
		  ????????????)	# 6 ex digits, are 3 numbers combined? keep the last
			num=$(echo "$num" | sed -e 's/^........//') ;;
		esac
		num=$(echo -e "ibase=16\n$num" | bc)
		;;
	esac
	echo $num
}

get_norm_val()
{
	awk '{ print $5; }' | sed -e 's/^0*//'
}

check_errors()
{
	local all=false
	[ "X${1:-}" = "X--all" ] && all=true

	grep "Pre-fail" | while read id name flag value worst thresh type updated when raw
	do
	        [ "X$when" != "X-" ] && {
			echo "$id $name $value ($thresh) $when"
			$all || return 0
		}
	done
	return 0
}

search_lastck()
{
	local time=
	local last=0

	echo "$out" | grep '^# [0-9]' | sort -r | while :
	do
		read time || {
			echo "$last"
			break
		}
		# 1  Short offline       Completed without error       00%     13622         -
		time=$(echo "$time" | sed -e 's/.*%  *//' -e 's/ .*//')
		# 65535 overflow?
		[ $time -lt $last ] && time=$(($time+65535))
		last=$time
	done
}




# (MAIN)


[ $# == 0 ] && usage

devs=
f_all=false
out_type="normal"
last_check_warning="1440"	# hours, =60 days

while [ $# != 0 ]
do
  case $1 in
    -a|--all)	f_all=true ;;
    -l|--long)	out_type="long" ;;
    -t|--terse)	out_type="terse" ;;
    -*)		usage ;;
    *)		devs="$devs $1" ;;
  esac
  shift
done

smartctl=$(which smartctl 2>/dev/null) || :

[ "X$smartctl" = "X" ] && {
	echo "I refuse to do anything: 'smartctl' command is not installed" >&2
	exit 1
}
if $f_all
then
	[ "$devs" != "" ] && usage
	devs=$(ls /dev/sd? /dev/hd? 2>/dev/null)
else
	[ "$devs" == "" ] && usage
fi


fmt2="%-12s %5.5s %s (no data)\n"
fmt_xtra=" %s %-16s %s\n"
fmt_line="----------------------------------"

case $out_type in
  long)
	fmt="%-12s %5.5s %-36s %8s %8s %12s %6s %6s %6s %8s %8s\n"
	hfmt="%-12.12s %-5.5s %-36.36s %8.8s %8.8s %12.12s %6.6s %6.6s %6.6s %8.8s %8.8s\n"
	# (header will be printed for each entry)
	;;
  normal)
	fmt="%-12s %5.5s %-36.36s %6s %6s %8s %8s\n"
	hfmt="%-12.12s %-5.5s %-36.36s %6.6s %6.6s %8.8s %8.8s\n"
	printf "$hfmt" "DEV" "FAILS" "MODEL" "BADSECT" "TEMP" "HOURS" "LASTCK"
	;;
esac


Status=0

for disk in $devs
do
	LABEL= UUID= TYPE=
	partinfo=$(blkid ${disk}1 2>/dev/null) && {
		eval $(echo "$partinfo" | sed -e "s}^${disk}1:  *}}")
		case $TYPE in
		  linux_raid_member)	TYPE="Lraid" ;;
		  LVM2_member)		TYPE="LVM2" ;;
		  crypto_LUKS)		TYPE="LUKS" ;;
		esac
	}

	model= realloc= temp= hours= serial= model= lastck= health= usb= size= ssdleft= badsect=
	supported=true

	out=$($smartctl -a $disk 2>/dev/null) || {
		if $(echo "$out" | grep -iq 'permission denied')
		then
			echo -e "\nerror, you don't have permissions to run this command\n"
			exit 1
		fi
	}

	echo "$out" | grep -qi "un.* usb" && {
		# unknown usb, try to force SAT interface
		out=$($smartctl -d sat -a $disk 2>/dev/null || :)
		usb=" (usb)"
	}

	echo "$out" | grep -qi "does not support smart" && {
		if echo "$out" | grep -qi " usb "
		then
			# usb, try to force SAT interface
			out=$($smartctl -d sat -a $disk 2>/dev/null || :)
			usb=" (usb)"
		else
			# unsupported at all, fallback to hdparm infos
			supported=false
		fi
	}

	echo "$out" | grep -qi "no medium present" && {
		# smart supported, but device not present, skip
		continue
	}


	echo "$out" | grep -qi "smart support is: unavailable" && {
		# unsupported at all, fallback to hdparm infos
		supported=false
		out=
	}
	echo "$out" | grep -qi "A mandatory SMART command failed" && {
		# unsupported at all, fallback to hdparm infos
		supported=false
		out=
	}


	[ "X$out" = "X" ] && {
		model=$(hdparm -i $disk 2>/dev/null) && {
			model=$(echo "$model" | fgrep " Model=" | sed -e 's/.*Model=//')
			printf "$fmt2" "$disk" "$model"
			continue
		}
		out=
	}

	if [ "X$out" = "X" ]
	then
		printf "$fmt2" "$disk" "--" "unknown device"
		Status=1

	elif $(echo "$out" | grep -q "unknown device")
	then
		printf "$fmt2" "$disk" "--" "unknown device"
		Status=1

	else
		if $(echo "$out" | grep -q 'Model:')
		then
			model=$(echo "$out"	| grep 'Model:' | sed -e 's/.*Model: *//')
			realloc=$(echo "$out"	| grep ' Reallocated_.* [0-9]' | getnumber)		# 5 or 197
			temp=$(echo "$out"	| grep 'Temperature.* [0-9]'| getnumber)		# 194
			hours=$(echo "$out"	| grep ' Power_On_Hours.* [0-9]'| getnumber)		# 9
			badsect=$(echo "$out"	| grep ' Current_Pending_Sect.* [0-9]' | getnumber)	# 198
			badsect=$((badsect+$realloc))
		else
			model=$(echo "$out" | grep 'Device:' | sed \
				-e 's/.*Device: *//' -e 's/  */ /g' -e 's/  *$//')
		fi

		size=$(echo "$out"	| grep -i 'user capacity:' | sed -e 's/.*\[//' -e 's/\].*//')
		serial=$(echo "$out"	| grep -i 'serial number:' | sed -e 's/.*umber: *//')
		model="$model$usb"
		# 2025-12-27
		lastck=$(search_lastck)

		# SSD? lifespan?
		#
					   ssdleft=$(echo "$out" | grep '^233 ' | get_norm_val)	# Media_Wearout_Indicator
		[ "X$ssdleft" = "X" ] 	&& ssdleft=$(echo "$out" | grep '^177 ' | get_norm_val)	# Wear_Leveling_Count
		[ "X$ssdleft" != "X" ] && {
			model="$model (SSD ${ssdleft}%)"
		}

		if [ "X$lastck" = "X" ]
		then
			lastck="(none)"
		else
			# 2025-12-27
			[ $lastck -gt $hours ] && hours=$lastck
		fi

		if $supported
		then
			health="??"

			echo "$out" | grep -qi 'SMART Health Status: OK'	&& health="none"
			echo "$out" | grep -q '# 1 .* Completed without error'	&& health="none"
			echo "$out" | grep -q 'Completed: read failure'		&& health="ERR"
			echo "$out" | grep -q 'Error Count: ' && \
				health=$(echo "$out" | grep 'Error Count: ' | sed -e 's/.*Count: //' -e 's/ .*//')

			[ "$realloc" != "0" ] && health="WRN"
			[ "$badsect" != "0" ] && health=$badsect

			errors=$(echo "$out" | check_errors)
			[ "X$errors" != "X" ] && health="ERR"
		else
			health="(n/a)"
		fi

		case $out_type in
		  long)
			printf "$hfmt" "DEV" "FAILS" "MODEL" "SIZE" "1p/TYPE" "LABEL" "REALLOC" "BADSECT" "TEMP" "HOURS" "LASTCK"
			printf "$hfmt" "$fmt_line" "$fmt_line" "$fmt_line" "$fmt_line" "$fmt_line" "$fmt_line" "$fmt_line" \
				"$fmt_line" "$fmt_line" "$fmt_line" "$fmt_line"
			printf "$fmt" "$disk" "$health" "$model" \
				"$size" "$TYPE" "$LABEL" \
				"$realloc" "$badsect" "$temp" "$hours" "$lastck" \

			echo

			ck_from=
			ck_warn=
			ck_hours=
			run_hours=

			[ "$lastck" != "(none)" ] && {
				ck_from=$(($hours - $lastck))
				ck_warn="   ** WARNING, RUNNING >$last_check_warning HOURS FROM LAST CHECK **"
				[ $ck_from -le $last_check_warning ] && ck_warn=

				run_hours="${hours}h"
				ck_hours="${ck_from}h"
			}

			[ "X$(which age_real)" != "X" ] && {
				run_hours=$(age_real ${hours}h)
				ck_hours="${ck_from}h = $(age_real ${ck_from}h)$ck_warn"
			}
			[ "X$hours" != "X" ]		&& printf "$fmt_xtra" $disk "runtime:" "$run_hours"
			[ "X$lastck" != "X(none)" ]	&& printf "$fmt_xtra" $disk "w/out check:" "$ck_hours"

			[ "X$serial" != "X" ]		&& printf "$fmt_xtra" $disk "SN:" "$serial"
			echo

			[ "X$health" = "XERR" ] && {
				echo "REPORTED ERRORS:"
				echo "$out" | check_errors --all
				echo
			}
			;;
		  normal)
			printf "$fmt" "$disk" "$health" "$model" \
				"$badsect" "$temp" "$hours" "$lastck"
			;;
		  terse)
			model=$(echo "$model" | tr ',' ' ')
		  	echo "$disk,$health,$model,badesct=$badsect,realloc=$realloc,temp=$temp,hours=$hours,checked=$lastck"
			;;
		esac

	fi
done

# if all devices requested, then ignore any error on single devices
#
$f_all && Status=0
exit $Status
