#!/bin/bash
#
# showmydisks - report partitions, types, filesystems, and disk space usage
#
PRGVERSION="v0.09 * 2011-02-20 (c) Andreas Schamanek"

# Copyright 2004: Andreas Schamanek <andreas@schamanek.net>
# Homepage: http://wox.at/as/_/showmydisks
# License + History: see end of file

# Usage  : showmydisks [--debug] [-L] [-u] ["dev1 ... devN"]
# Example: showmydisks "hda sda"
# Example: showmydisks -u          # show also unallocated sectors

# Note   : Use this script carefully. It's been hacked quick and dirty
# by a bad programmer. It's been tested only on a few Debian systems.
# It currently depends on the existence of at least:
#   bash, df, sed, awk, file, /etc/fstab, /sys/block, /proc

# me (name of this script without path)
ME=$(basename $0)
PRGVERSION="$ME $PRGVERSION"

# some standard variables
TRUE=0 ; FALSE=1

# debug?
DEBUG=$FALSE
test "x$1" = "x--debug" && DEBUG=$TRUE && shift

# some useful functions
function debugmsg { test $DEBUG = $TRUE && echo -ne "$1" >&2 ; }
function warning { echo -e "\n$ME: warning: $1\n" >&2 ; }
function die { echo -e "\n$ME: error: $2. aborting.\n" >&2 ; exit $1 ; }

# if $1 is --help show usage and example
test "/$1/" = '/--help/' \
&& { echo "Usage: $ME [-L] [-u] [\"dev1 ... devN\"]" >&2 ;
     echo "Example: $ME \"hda sda\"" >&2 ; exit 0 ;}

# if $1 is -L only list devices of hard drives/block devices w/o RAIDs
LISTONLY=$FALSE
test "x$1" = "x-L" && LISTONLY=$TRUE && shift

# if $1 is -u show also unallocated sectors
SHOWUNALLOCATED=$FALSE
test "x$1" = "x-u" && SHOWUNALLOCATED=$TRUE && shift

debugmsg "$PRGVERSION\n"

# warn if script not run as root and expand PATH (mostly for fdisk)
test $UID -ne 0 -a $LISTONLY = $FALSE \
&& { warning "you probably want to be root" ; PATH="/sbin:$PATH" ; }

# list of disks to investigate, provided by $1 or /sys/block/{h,s}d?
DISKS=${1:-`cd /sys/block && "ls" -d hd? sd? 2>/dev/null`}
# Skip CD-Rom drives and construct $DISKS
for MYDEV in $DISKS ; do
  test -r /sys/block/$MYDEV/device/media \
  && if [ "/$(</sys/block/$MYDEV/device/media)" = "/cdrom" ] ; then
     debugmsg "$MYDEV is type cd-rom\n"
     # remove CD-Rom from $DISKS
     DISKS=${DISKS/$MYDEV/}
  fi
done
DISKS=$(echo $DISKS) # just to remove newlines and extra spaces

# bail out if $DISKS is empty
test "/$DISKS/" = '//' \
&& die 2 "list of disks to investigate is empty"

# add Software RAID to DISKS unless user requested a specific list
if [ "/$1/" = '//' -a -r /proc/mdstat ] ; then
   # testing for /proc/mdstat is not sufficient; maybe test -s is?
   MDDEVS=$(cd /sys/block && "ls" -d md[0-9] md[0-9][0-9] 2>/dev/null )
   MDDEVS=$(echo $MDDEVS) # remove newlines (just for the debugmsg)
   debugmsg "[MDDEVS=$MDDEVS]\n"
   test "/$MDDEVS/" != '//' && DISKS="$DISKS md"
fi

# Eventually we have compiled $DISKS
debugmsg "[DISKS=$DISKS]\n"

# list of valid disks and partitions (e.g. as detected by kernel)
# VALIDDISKS=$(</proc/partitions)      # kernel 2.4.x, has no cdrom, etc.
VALIDDISKS=$(</proc/diskstats)         # kernel 2.6.x
# VALIDDISKS=" hda hda1 hda2 hde hde5" # your custom list, mind the 1st space

# symbols for output
SYMDISK="> "      # symbol for disks
SYMPART="|-- "    # symbol for partitions
# SYMSEP="-----------\n" # separator between disks (include \n, ...)

# make sure we got what we need
[ ! -r /proc/partitions ] && { die 1 "/proc/partitions not readable" ; }
[ ! -r /proc/mounts ]     && { die 1 "/proc/mounts not readable" ; }

# collect system data for later use
DF="`df -klT`"
[ -r /proc/mdstat ] && MDSTAT=$(</proc/mdstat) || MDSTAT=

# some more useful functions
function getparm { MYPOS=$1 ; shift ; echo ${@:$MYPOS:1} ; }

function prettyprintsize {
  MYVAL="$1"
  for UNIT in KB MB GB TB ; do 
    MYRES="$MYVAL $UNIT"
    NEW=$((MYVAL / 1024))
    test $NEW -lt 2 && break 
    MYVAL=$NEW
  done
  echo -n $MYRES
}

function getcapacity {
  MYCAPAC=$(awk -v partition=$1 '$4 == partition {print $3}' </proc/partitions)
  debugmsg " [MYCAPAC=$MYCAPAC]"
  [ $MYCAPAC ] && prettyprintsize $MYCAPAC
}

function isrootfs {
  # check if $1 is the rootfs, if it is not in /proc/mounts
  # get type from /etc/fstab
  if echo "$DF" | grep -sq "^/dev/$1 .* /$" ; then
     grep -sq "^/dev/$1" /proc/mounts \
     || { echo -n "on /" ;
          awk "/^\/dev\/$1/"'{ printf " as %s", $3}' < /etc/fstab ; }
  fi
}

function mountedas {
  # note: mountedas returns $FALSE if $1 _is_ mounted, $TRUE if _not_
  awk "/^\/dev\/$1 /"'{ printf "on %s as %s", $2, $3 }' < /proc/mounts
  # if mounted get free capacity
  MYDF=`echo "$DF" | grep "^/dev/$1 "`
  if [ -n "$MYDF" ] ; then
     CAPFREE=$(prettyprintsize $(getparm 5 $MYDF))
     echo -n " ($CAPFREE free)"
     return $FALSE
  fi
}

function gettype {
  # get type of filesystem if partition $1 is not mounted by use of _file -s_
  # idea from _scanpartitions_ of KNOPPIX 3.7 (c) Klaus Knopper Nov 2002
  FILE=$(LC_ALL=C file -Ls "/dev/$1")
  [ "$?" = "0" ] || return 2
  case "$FILE" in
  *[Ee][Xx][Tt]2*)    echo -n "ext2"; return 0;;
  *[Ee][Xx][Tt]3*)    echo -n "ext3"; return 0;;
  *[Ee][Xx][Tt]4*)    echo -n "ext4"; return 0;;
  *[Rr][Ee][Ii][Ss][Ee][Rr]*)  echo -n "reiserfs"; return 0;;
  *[Ff][Aa][Tt]*|*[Xx]86*) echo -n "vfat|msdos"; return 0;;
  *[Xx][Ff][Ss]*)     echo -n "xfs"; return 0;;
  *[Nn][Tt][Ff][Ss]*) echo -n "ntfs"; return 0;;
  *[Ss][Ww][Aa][Pp]*) echo -n "swap"; return 0;;
  *LUKS\ encrypted*)  echo -n "LUKS encrypted"; return 0;;
  *\ data*)           echo -n "garbage?"; return 0;; 
  *) echo -n "fs unknown"; return 0;;
  esac
}

function isinraid {
  # check if $1 is part of a Software RAID set (checking $MDSTAT)
  RAID=$(awk -F: "/ $1\[/ { print \$1 }" <<< "$MDSTAT")
  [ $RAID ] && echo -n ", used in" $RAID
}

# start of actions ------------------------------------------------------

# print name of system
# echo "# $(uname -n)  (kernel $(uname -r), disks as of $(date +%Y-%m-%d))"

for MYDEV in $DISKS ; do

  # cleanup + check MYDEV is a block device (unless it is md)
  MYDEV=$(basename -- "$MYDEV")
  if [ $MYDEV != 'md' -a ! -b /dev/"$MYDEV" ] ; then
     warning "$MYDEV is not a block special device"
  fi

  # start output (unless $LISTONLY)
  [ $LISTONLY = $FALSE ] && echo -en "${SYMDISK}$MYDEV:"

  # check if it's there
  if ! echo "$VALIDDISKS" | grep -sq " $MYDEV" ; then
     die 1 "$MYDEV seems to be no valid disk or partition"
  fi

  # LISTONLY: Only list devices of block devices (quick and dirty)
  if [ $LISTONLY = $TRUE ] ; then
     [ "/$MYDEV/" != '/md/' ] && echo "$MYDEV"
     continue
  fi

  # we have to distinguish normal block devs and Software RAID
  case /$MYDEV/ in
    /md/ ) # Software RAID
           MYMODEL="Software RAID"
           echo -n \ ${MYMODEL}
           # total capacity; we sum up all md "partitions"
           echo -n " ("
           prettyprintsize $(awk '$4 ~ /^md[0-9]/ { sum+=$3 } 
              END { print sum }' </proc/partitions)
           echo ")"
           MYFDISK=""
           ;;

     *   ) # block devices (no Software RAID)

           # print brand/model
           MYMODEL="unknown brand"
           test -f /sys/block/$MYDEV/device/vendor &&
           MYMODEL=$(</sys/block/$MYDEV/device/vendor)
           test -f /sys/block/$MYDEV/device/model &&
           MYMODEL="$MYMODEL $(</sys/block/$MYDEV/device/model)"
           test -f /proc/ide/$MYDEV/model &&
           MYMODEL=$(head -1 /proc/ide/$MYDEV/model)
           echo -n \ ${MYMODEL}

           # print total capacity
           echo " ($(getcapacity $MYDEV))"

           # save _fdisk -l_ for later use
           if [ $UID -ne 0 ] ; then
              MYFDISK=`fdisk -l /dev/$MYDEV 2>/dev/null \
                 | sed -ne "/^\/dev\/$MYDEV/{s/\*/ /;p}"`
           else 
           # if script is run as root do not suppress error messages
              MYFDISK=`fdisk -l /dev/$MYDEV \
                 | sed -ne "/^\/dev\/$MYDEV/{s/\*/ /;p}"`
           fi
           ;;
  esac # /$MYDEV/

  # get list of partitions
  PARTITIONS="$(echo `awk -v pat=$MYDEV[0-9]+ '$4 ~ pat {print $4}' < /proc/partitions`)"

  for PARTITION in $PARTITIONS ; do
  
    echo -n "${SYMPART}${PARTITION/$MYDEV}:"

    # get type ID of partition
    PARTTYPE=`echo "$MYFDISK" \
      | awk "/^\/dev\/$PARTITION /"'{ print $5}'`
      debugmsg " [PARTTYPE=$PARTTYPE]"

    case /$PARTTYPE/ in
      //   ) if [ $MYDEV = 'md' ] ; then
                echo -n " $(</sys/block/$PARTITION/md/level) "
             else
                echo -n " type unknown `getcapacity $PARTITION` " 
             fi
             isrootfs $PARTITION
             mountedas $PARTITION && gettype $PARTITION
             ;;
      /5/  ) echo -n " extended partition" ;;
      /82/ ) echo -n " Linux swap partition" 
             echo -n ", " ; getcapacity $PARTITION ;;
      /83/ ) echo -n " " ; getcapacity $PARTITION ; echo -n " " 
             isrootfs $PARTITION
             # mountedas returns $TRUE if $PARTITION is not mounted
             mountedas $PARTITION && gettype $PARTITION
             ;;
      /7/  ) echo -n " type 7 (NTFS?) "
             getcapacity $PARTITION ; echo -n " "
             mountedas $PARTITION && gettype $PARTITION
             ;;
      /fd/ ) echo -n " " ; getcapacity $PARTITION
             echo -n ", partition type fd"
             [ "$MDSTAT" ] && isinraid $PARTITION
             ;;
      *    ) echo -n " partition type $PARTTYPE" ;;
    esac
    
    echo
    
  done # all partitions

  # print number of unallocated sectors (if requested)
  if [ $SHOWUNALLOCATED = $TRUE -a $MYDEV != 'md' ] ; then
     UNALLOCATED=""
     UNALLOCATED=$(\
       (echo -e "v\nq" | fdisk /dev/$MYDEV) 2>/dev/null \
       | sed -ne '/unallocated sectors/s/^.* \([0-9]\+\) unallo.*$/\1/p' )
     test "/$UNALLOCATED/" = '//' || echo "($UNALLOCATED unallocated sectors)"
  fi

  # print separator line/symbol between disk
  echo -en "$SYMSEP"

done # all disks

exit 0

# License
#
# This script 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 2 of the License, or
# (at your option) any later version.
#
# This script 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.
#
# You should have received a copy of the GNU General Public License
# along with this script; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA

# History
#
# 0.02 * 2004-12-23  first buggy "punish me" alpha release
# 0.03 * 2005-01-19  added code to read /sys/block/sd?/device/...
# 0.04 * 2009-09-13  cosmetic changes and testing if it still works
# 0.05 * 2009-09-14  print warning if not /root but go on; added --help;
# 0.05 * 2009-09-14  removed sfdisk requirement; some cosmetics
# 0.06 * 2009-09-15  added -u (unallocated sectors); removed bc requirement
# 0.07 * 2009-09-15  added support for /dev/md (Software RAID)
# 0.08 * 2010-03-07  added support for -L (only list devices)
# 0.09 * 2011-02-20  added ext4 + LUKS, more info about fd partitions (mdstat)

