#!/bin/sh
# Copyright (C) 2016 Denis 'GNUtoo' Carikli <GNUtoo@no-log.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

usage()
{
  progname="$1"

  echo "Usage:"
  echo "${progname} smart              # Check SMART status"
  echo "${progname} space              # Check remaining free space"
  echo "${progname} state              # Prints power management settings"
  echo "${progname} rescan             # Rescans SCSI/SATA buses"
  echo "${progname} suspend_hdds       # Power down HDDs (but not SSDs)"
  echo "${progname} hdds_speed [1-255] # 1:Parks the head very often:"
  echo "                              # - consumes less power"
  echo "                              # - Shorter life span"
  echo "                              # 254: Parks the head if unused for long"
  echo "                              # - Fast enough"
  echo "                              # - Longer life span"
  echo "                              # 255: Doesn't park the head:"
  echo "                              #  - fastest"
}

hdparm()
{
  $(which hdparm) $@ 2>&1 | grep -v "^SG_IO: "
}

decode_smart_bit()
{
  hdd="$1"
  bit="$2"

  # The description has been taken from the smartctl manual
  case "${bit}" in
  "0")
      echo "    Bit 0: Command line did not parse."
      ;;
  "1")
      echo "    Bit 1: Device open failed, device did not return an IDENTIFY"
      echo "           DEVICE structure, or device is in a low-power mode"
      echo "           (see '-n' option in the smartctl manual)."
      ;;
  "2")
      echo "    Bit 2: Some  SMART or other ATA command to the disk failed,"
      echo "           or there was a checksum error in a SMART data structure"
      echo "           (see '-b' option in the smartctl manual)."
      ;;
  "3")
      echo "    Bit 3: SMART status check returned \"DISK FAILING\"."
      ;;
  "4")
      echo "    Bit 4: We found prefail Attributes <= threshold."
      ;;
  "5")
      echo "    Bit 5: SMART status check returned \"DISK OK\" but we found"
      echo "           that some (usage or prefail) Attributes have been <="
      echo "           threshold at some time in the past."
      ;;
  "6")
      echo "    Bit 6: The device error log contains records of errors."
      ;;
  "7")
      echo "    Bit 7: The device self-test log contains records of errors."
      echo "           [ATA only] Failed self-tests outdated by a newer"
      echo "           successful extended self-test are ignored."
      ;;
  esac
}

decode_smart_result()
{
  hdd="$1"
  result="$2"

  bit_nr=0
  while [ "${result}" != 0 ] ; do
    bit_value="$(expr ${result} % 2)"
    if [ "${bit_value}" = "1" ] ; then
      decode_smart_bit "${hdd}" "${bit_nr}"
    fi
    result="$(expr ${result} / 2)"
    bit_nr="$(expr ${bit_nr} + 1)"
  done
}

test_smart()
{
  result=0
  for hdd in ${hdds} ; do
      smartctl --all -q silent "${hdd}" ; result="$?"
      if [ "${result}" = "0" ] ; then
	  echo "${hdd}: ${result}: No error founds"
      else
	  echo "${hdd}: ${result}: Founds some errors:"
	  decode_smart_result "${hdd}" "${result}"
      fi
  done
}

get_speed()
{
  for hdd in ${hdds} ; do
    rotational="$(cat /sys/block/$(basename ${hdd})/queue/rotational)"
    if [ "${rotational}" = "1" ] ; then
      hdparm -B "${hdd}"
    fi
  done
}

set_speed()
{
  for hdd in ${hdds} ; do
    rotational="$(cat /sys/block/$(basename ${hdd})/queue/rotational)"
    if [ "${rotational}" = "1" ] ; then
      hdparm -B "$1" "${hdd}"
    fi
  done
}

get_hdds_state()
{
  for hdd in ${hdds} ; do
    state="$(hdparm -C ${hdd} | tail -n1 | awk '{print $4}')"
    model="$(hdparm -I ${hdd} | grep 'Model Number:' | sed 's#.*Model Number:       ##' | sed 's# *$##')"
    serial="$(hdparm -I ${hdd} | grep 'Serial Number:' | sed 's#.*Serial Number:      ##' | sed 's# *$##')"
    apm="$(hdparm -B ${hdd} | grep '^ APM_level' | sed 's#.*= ##')"
    type=""
    rotational="$(cat /sys/block/$(basename ${hdd})/queue/rotational)"

    if [ "${rotational}" = "1" ] ; then
      type=" HDD "
    elif [ "${rotational}" = "0" ] ; then
      type=" SSD "
    fi

    printf "%s%s %-25s %-20s %-15s %-10s\n" "$(basename ${hdd})" "${type}" "${model}" "${serial}" "${state}" "APM:${apm}"
  done
}

get_space()
{
  df -h | grep "^/" | while read line
  do
    volume=$(basename $(echo $line | awk '{print $1}'))
    gb=$(echo $line | awk '{print $4}')
    percent=$(echo $line | awk '{print $5}')
    mountpoint=$(echo $line | awk '{print $6}')

    printf "%-25s %-5s %-5s %s\n" "${volume}" "${gb}" "${percent}" "${mountpoint}"
  done
}

hdds="$(cat /proc/partitions  | awk '{print $4}' | grep '^sd' | grep -v '[[:digit:]]$' | awk '{print "/dev/" $1}' | sort -u)"
if [ "$1" = "state" -a $# -eq 1 ] ; then
  get_hdds_state
elif [ "$1" = "rescan" -a $# -eq 1 ] ; then
  busses="$(ls /sys/class/scsi_host/)"
  for bus in ${busses} ; do
    echo "- - -" > "/sys/class/scsi_host/${bus}/scan"
  done
elif [ "$1" = "suspend_hdds" -a $# -eq 1 ] ; then
  for hdd in ${hdds} ; do
    rotational="$(cat /sys/block/$(basename ${hdd})/queue/rotational)"
    if [ "${rotational}" = "1" ] ; then
      hdparm -Y ${hdd}
    fi
  done
elif [ "$1" = "hdds_speed" -a $# -eq 1 ] ; then
  get_speed
elif [ "$1" = "hdds_speed" -a $# -eq 2 ] ; then
  set_speed "$2"
elif [ "$1" = "smart" -a $# -eq 1 ] ; then
  test_smart
elif [ "$1" = "space" -a $# -eq 1 ] ; then
  get_space
else
  usage "$(basename $0)"
  exit 64 # EX_USAGE in sysexits.h
fi
