#!/usr/bin/sh
# @(#) $ Revision: 4.8 Author: Bill Hassell Date: Nov 2008

#      ######  ######  ####### #     # #######  #####   #####
#      #     # #     # #       ##   ## #       #     # #     #
#      #     # #     # #       # # # # #       #       #
#      ######  #     # #####   #  #  # #####   #  ####  #####
#      #     # #     # #       #     # #       #     #       #
#      #     # #     # #       #     # #       #     # #     #
#      ######  ######  #       #     # #######  #####   #####
#      

VERSION=4.8_Nov2008
# Ver 4.8 - Added -c <column> for sorting
#           Added -V <VGname> to select a VG name
#           Fixed overflow when looking at terabyte filesystems 
# Ver 4.7 - Deleted an extra blank line for -q displays
# Ver 4.6 - Fixed display enhancements for unknown $TERM values
# Ver 4.5 - Fixed FSQTY to assign right number of elements in array
# Ver 4.5 - Replaced cat $BDFLIST | while read... with stdin redirect
#           Replaced awk & grep pipes with awk only
# Ver 4.4 - Added version numnber to Usage. Added -u option which 
#           returns Usage on stdout and zero return code. Otherwise,
#           Usage goes to stderr and return code = 1. 
#           Extended sum line to match the source column
# Ver 4.3 - Added -P ## to show only filesystems over percentage
#           Added bdf error messages to end of list
#           -q also turns off enhancements for script/batch usage
#           Added -s for summation of displayed filesystem space
#           Added autofs and DevFS to filesystem support
#           Add diagnostic msg for unmounted sources
#           Fixed cifs problems with \\names from Windows
# Ver 4.2 - Added highlights to error messages. Added -p ##
#           to show percent usage higher than ##. Default to
#           gigabytes (-g) if script is called bdfgigs
# Ver 4.1 - Added -t to match bdf. 
# Ver 4.0 - Handle errors when looking at source devices w/out 
#           a filesystem or unmounted such as swap and raw devices.
# Ver 3.9 - Added -s for sums (total, used, avail)
# Ver 3.8 - Cleaned up help text with a Usage function.
#           Added -q to suppress the header line for data collection
# Ver 3.7 - Added vxfs filesystem version to -v. Requires read 
#           permission on character dev file
# Ver 3.6 - Changed to add -g for gigabytes
#           Added dir(s) or file(s) to match bdf options
#           Removed blk/frg and vxfs version and obtain 
#             largefiles flag from df -g so anyone can use -v
# Ver 3.5 - Changed to use getopts for options
# Ver 3.4 - Changed to ignore autofs entries in mount -p
# Ver 3.3 - Changed formats to handle terabyte lvols 
# Ver 3.2 - Simplify code with local $PATH
# Ver 3.1 - Trap fsadm errors and change return code.  Allow -vl
#             or -lv combinations.
# Ver 3.0 - Rewrote using temp files to speed up code about 10:1
#           Added -l option and ? or -? help
# Ver 2.1 - Added vxfs filesystem layout verrsion (2,3,4,etc)
#           Added non-root check for -v option fsadm needs root
#             permission for fsadm to read lvols directly.
# Ver 2.0 - Change Blks to blk/frag size and add largefile status
#           Made: FSTYPE blk/frag and largefile optional with -v
#             to reduce the width of the line
# Ver 1.2 - Expanded filesystem type to handle autofs
# Ver 1.1 - Added blocksize column for filesystem blocks
# Ver 1.0 - Original
# -----------------------------------------------------------------------

# Show bdf in megs or gigs for easy reading
#
# Usage:  bdfmegs [ -vlgqstuv ] [ -p | -P ## ] [ -c # ] [ -V <VG> ] [ file(s) or dir(s) ]
#   -c # = Sort on column #
#   -g = show gigabytes, otherwise megabytes
#   -l = local (no NFS)
#   -p ## = highlight % -ge ##
#   -P ## = show only % -ge ##
#   -q = suppress header line and no char enhancements
#   -s = summarize total, used and available
#   -t = filesystem (one only such as hfs cdfs nfs vxfs cifs etc)
#   -u = usage (return code=0, Usage to stdout)
#   -v = verbose (type, version, largefiles) 
#        (version info read permission for mountpoint)
#   -V <VGname> = select by volume group

# After the options, path(s) or file(s) may be added to limit 
#   the filesystems to be listed.

# PERFORMANCE NOTE:
#   bdf has historically performed a filesystem sync as it looks
#       at every mountpoint. This can be very costly in time and
#       disk I/O on a busy system. If gdf has been installed, it
#       will use gdf in place of bdf.

# The width of the source filesystem will be adjusted to the
# longest path (ie, NFS or other long device filename).  All
# fields are in megs (defined as Kbytes/1024) or gigs.  Field 
# widths for sizes are 7 digits for multi-terabyte capability.

set -u
PATH=/usr/bin:/usr/sbin
TRACEME=${TRACEME:-false}               # TRACEME non-null = trace on
[ $TRACEME != false ]  && set -x && PS4='[$LINENO]: '

MYNAME=${0##*/} 
HPUXVER=$(uname -r | cut -d. -f2-3 | tr -d .) 
if [ "$MYNAME" = "bdfgigs" ]
then
   let MAGNITUDE=1048576                # 1024*1024
   GIGABYTES=true
else
   MAGNITUDE=1024
   GIGABYTES=false
fi

TEMPDIR=/var/tmp/$MYNAME.$$             # tempfile setup and cleanup
mkdir $TEMPDIR
chmod 700 $TEMPDIR
BDFLIST=$TEMPDIR/bdflist
ERRLIST=$TEMPDIR/bdflist.error
trap "rm -rf $TEMPDIR;exit" 0 1 2 3 15

# speedup df for 11.23 and up
[ $HPUXVER -ge 1123 ] && FASTDF="-s" || FASTDF=""

# Initial values

typeset -R5  PERCENT
typeset -R6  FSTYPE
typeset -R2  FSVER
typeset -i   SUMTOT=0
typeset -i   SUMUSED=0
typeset -i   SUMAVAIL=0

# filesystems

set -A FSID hfs vxfs nfs cdfs cifs autofs DevFS  # filesystem types allowed
FSQTY=${#FSID[@]}               # qty of known filesystems
FSONLY=""                       # used if -t <fs-type> specified
NODEVREAD=false                 # flag in case -v can't read devfile ver
PCTLIMIT=0                      # 0=no highlight
PCTLIMITONLY=false              # true=suppress lines under pct limit

# terminal enhancements - error messages and -p option
# set char enhancments only if interactive

if tty -s 
then 
# set char enhancments only if interactive (ie, TERM is set)
#  If TERM is not set or the TERM value is not found in terminfo
#  files, then enhancements are null.
  export HB=$(tput dim   2>/dev/null) # dim text
  export HV=$(tput smso  2>/dev/null) # 1/2 bright inverse
  export IV=$(tput bold  2>/dev/null) # inverse
  export UL=$(tput smul  2>/dev/null) # underline
  export BL=$(tput blink 2>/dev/null) # blink
  export EL=$(tput el    2>/dev/null) # clear to end of line 
  export ED=$(tput ed    2>/dev/null) # clear to end of display 
  export EE=$(tput sgr0  2>/dev/null) # end all enhancements
else
  HB="" HB="" IV="" UL="" BL="" EE=""
fi



#########################
#   F U N C T I O N S   #
#########################

function Usage
{

#########################################################################
# Usage "error message"
#
# If "error message" is null, 
#     send Usage-msg to stdout and return code = 0
#     Otherwise, send Usage to stderr and set return code to 1.

  TRACEME=${TRACEME:-false}               # TRACEME non-null = trace on
  [ $TRACEME != false ]  \
    && set -x && PS4='[$LINENO]: '
  MYNAME=${MYNAME:-${0##*/}}  

# Find a working enhancement - blink preferred
  ZZ=$(tput blink 2>/dev/null) 
  [ "$ZZ" = "" ] && UL=$(tput smul 2>/dev/null) 
  [ "$ZZ" = "" ] && ZZ=$(tput bold 2>/dev/null)
  EE=$(tput sgr0 2>/dev/null)

# Show the error message
  
  [ $# -gt 0 ] && print "\n$ZZ$@$EE\n"

  TEXT="
Usage:  $MYNAME [ -clgPpqstuVv ] file(s) or dir(s) ]
  where: 
    -c # = Sort on column #
    -g = show gigabytes, otherwise megabytes
    -l = local (no NFS)
    -p ## = highlight % -ge ##
    -P ## = show only % -ge ##
    -q = suppress header line and no char enhancements
    -s = summarize total, used and available
    -t <fs> = specifc filesystem: $IV(${FSID[@]})$EE
    -u = usage (return code=0, Usage to stdout)
    -v = verbose (type, version, largefiles)
         (version info read permission for mountpoint)
    -V <VGname> = select a volume group

File(s) or dirpath(s) may be specified to limit 
  the output of $MYNAME as in:

       $MYNAME -vlg /usr/contrib/bin /var/tmp
     
If $MYNAME is run as bdfgigs (ie, a link), then -g is default.
                                     ($MYNAME ver $VERSION)
"
# send help to stdout or stderr depending on whether there was an error

  if [ $# -eq 0 ]
  then
    print -u1 "$TEXT"           # stdout
  else
    print -u2 "$TEXT"           # stderr
    exit 1
  fi
  exit 0
}

function TGconv
{

# Usage: TGconv number magnitude sum
#   number = size in Kbytes
#   magnitude = 1024 or 1024*1024 (megs or gigs for results)
#   sum = 0 (normal) or 1 (sum has been normalized to megs or gigs)

# Routine to take a number (in KB) make it fit in 7 characters 
#    with G or T suffix if needed. Will calculate megs or gigs
#    based on magnitude. Because sumtotals can exceed 32 bit
#    integers, the sums are taken in the current units of
#    measure (megs or gigs). So sum=1 bypasses the divisor.

  TRACEME=${TRACEME:-false}               # TRACEME non-null = trace on
  [ $TRACEME != false ]  \
    && set -x && PS4='[$LINENO]: '

# Check the number after magnitude has been handled
#   Convert to GB or TB if still too big for a 7-char field
#   Prints 7 char number field and sets MAG to " " or g, t, p

  DIV=$2 
  if [ $DIV -le 1024 ] 
  then
     MAG1=g		# Gigabyte suffix for > 9999
     MAG2=t		# Terabyte suffix for > 9999999
  else
     MAG1=t		# Terabyte suffix for > 9999
     MAG2=p		# Petabyte suffix for > 9999999
  fi

  SUMFLAG=$3
  [ $SUMFLAG -eq 0 ] && NUM=$(($1/$DIV)) || NUM=$1
  NUMLEN=${#NUM}
  MAG=" "

  # reformat based on the magnitude of the value
  if [ $NUMLEN -le 4 ]
  then
     VAL="$NUM"
  elif [ $NUMLEN -le 7 ]
  then
     VAL="$(print -n "$NUM" | awk '{printf "%7.1f",$1/1024}')"
     MAG="$MAG1"
  else
     VAL="$(print -n "$NUM" | awk '{printf "%7.1f",$1/1024/1024}')"
     MAG="$MAG2"
  fi
  printf "%8s" "$VAL$MAG"
}


###############################
#   M A I N   P R O G R A M   #
###############################

# Construct a multiple -e list of filesystem types based on
#    the size of the FSID array. EOPT=" -e hfs -e nfs -e vxfs"ID

EOPT=""
for MYFS in ${FSID[@]}
do
   EOPT="$EOPT -e $MYFS"
done

# Process all options

SUMS="false"
NOSYNC=""
VGONLY=""
LOCALFS=""
VERBOSE="false"
SORTCOL=""
NOHEADER="false"

while getopts ":c:lgqip:P:st:uvV:" OPTCHAR
do
  case $OPTCHAR in
       l) LOCALFS="-l" 
	  ;;
       q) NOHEADER="true"       # also turn off header and char enhancements
          HB="" HB="" IV="" UL="" BL="" EE=""
          ;;
       g) let MAGNITUDE=$MAGNITUDE*1024 
          GIGABYTES=true
          ;;
       s) SUMS="true" 
	  SUMTOT=0
	  SUMUSED=0
	  SUMAVAIL=0
	  ;;
       c) SORTCOL="$OPTARG"   	# sort by field (1-6 or 1-9 -v)
	  if [ "$(print -n "$SORTCOL" | tr -d '[:digit:]')" = "" ]
	  then
	     [ $SORTCOL -lt 1 -o $SORTCOL -gt 9 ] \
	       && Usage "sort field ($SORTCOL) must be 1-9"
	     [ $SORTCOL -gt 1 -a $SORTCOL -le 5 ] && NUM="n" || NUM=""
	  else
	     Usage "Sort field ($SORTCOL) is not numeric"
	  fi
	  ;;
       t) FSONLY="$OPTARG"              # validate filesystem type
          if [ $(echo "$FSONLY" | grep -c $EOPT) -eq 0 ]
          then
             echo "\n${IV}Invalid -t option: $EE$BL$FSONLY$EE"
             Usage "  ${IV}- must be one (only) of: ${FSID[@]} $EE"
          else
             FSONLY="-t $FSONLY"
          fi
          ;;
   P | p) PCTLIMIT="$OPTARG"            # highlight percent limit
          [ ! -z "$(echo $PCTLIMIT | tr -d '[:digit:]')" ] \
            && Usage "\n${BL}-p $PCTLIMIT$EE$IV not a number 1-100$EE" 
          [ $PCTLIMIT -gt 100 -o $PCTLIMIT -lt 1 ] \
            && Usage "${BL}-p $PCTLIMIT$EE$IV not in range 1-100$EE" 
          [ $OPTCHAR = P ] && PCTLIMITONLY=true
          ;;
       u) Usage 
	  ;;
       v) VERBOSE=true 
	  ;;
       V) VGONLY="$OPTARG" 
	  [ $(print "$VGONLY" | grep -c "/dev/") -eq 0 ] \
	    && VGONLY="/dev/$VGONLY/" \
	    || VGONLY="$VGONLY/"
	  ;;
       *) eval "ERROPT=\$$(($OPTIND-1))"
	  Usage "\n${IV}Invalid option(s) ${EE}${BL} ${ERROPT} ${EE}"
          ;;
  esac
done
shift $(($OPTIND -1))

# Optional files or directories following options

[ $# -gt 0 ] && SPECIFY="$@" || SPECIFY=""


#####################################
# Get a list of mountpoints 
# For bdf, process the list and paste the 2-line data together
#    into a file to speed things up
# 
# The mechanism is to read all the params from each line.  If the
#    line has been split, only the source is on line 1 so the rest
#    of the params are null when read, so if $TOT is blank, read
#    the next line for the missing params.

# Use gdf rather than bdf when available
#   gdf does not try to sync which is much faster (but less up to date)
#   Look for G_df as well as gdf, choose file > 500k in case gdf is
#   a wrapper script.

if [ -x /usr/local/bin/gdf ]
then
   [ $(cat /usr/local/bin/gdf | wc -c) -gt 500000 ] \
      && alias bdf="/usr/local/bin/gdf -k --no-sync"
   if [ -x /usr/local/bin/G_df ]
   then
      [ $(cat /usr/local/bin/G_df | wc -c) -gt 500000 ] \
         && alias bdf="/usr/local/bin/G_df -k --no-sync"
   fi
fi

###################################################
# Run bdf listing into a file
# Get error messages into a separate file for later

bdf $LOCALFS $FSONLY $SPECIFY 2>$ERRLIST | \
  while read FS TOT USED AVAIL PERCENT MNT
  do
    if [ $FS != "Filesystem" ]
    then
       if [ "$TOT" = "" ]
       then
         read TOT USED AVAIL PERCENT MNT
       fi
       echo  $FS $TOT $USED $AVAIL $PERCENT $MNT
    fi
  done > $BDFLIST

##################################################
# Check if we are selecting and/or sorting

if [ "$VGONLY" != "" ]
then
   cp $BDFLIST $BDFLIST.tmp
   grep ^$VGONLY $BDFLIST.tmp > $BDFLIST
fi

if [ "$SORTCOL" != "" ]
then
   cp $BDFLIST $BDFLIST.tmp
   sort  -${NUM}k$SORTCOL $BDFLIST.tmp > $BDFLIST
fi
  

# Find the longest source string with a sweep through $BDFLIST
# Minimum length is 12 so typeset will pad on the right when
# needed.


MAXLEN=12
while read SOURCE DUMMY
do
   [ ${#SOURCE} -gt $MAXLEN ] && MAXLEN=${#SOURCE}
done < $BDFLIST

# Variable width typesets here
#
#  By using eval, a variable typeset instruction can be created
#  and then executed as part of the inline script.

#  First is for the filesystem source string
#  Second is to pad the title. Note that PAD must be typeset to
#  a value greater than 0, so subtract 11 for the evaluation.
#  (the minimum width for a source directory is 12 chars)

eval "typeset -L$MAXLEN FS"
eval "typeset -L$(( MAXLEN - 11 )) PAD=' '"

# Print the title line. $PAD is used to prorvide proper spacing for
#   short to long source filesystem names.  This must match the
#   evaluated typeset value for $FS above.  We'll split the line
#   at %Used in order to have plain and verbose versions.

[ $GIGABYTES = "true" ] && BYTES=Gbytes || BYTES=Mbytes
[ $NOHEADER = "true" ] || \
    echo "File-System $PAD $BYTES    Used   Avail %Used \c"

if [ $NOHEADER = "false" ]
then
   if $VERBOSE
   then
      echo "  Type Vr Lrg Mounted on"
   else
      echo "Mounted on"
   fi
fi

###################################################################
# Walk through each mountpoint gathering info
# To track down the filesystem type, use mount -p in a
#   variable so it can be searched faster in memory

MOUNTP=$(mount -p)

while read FS TOT USED AVAIL PERCENT MNT
do

#  Compute megs or gigs with $MAGNITUDE (1024 or 1024*1024)

  MTOT="$(TGconv $TOT $MAGNITUDE 0)"
  MUSED="$(TGconv $USED $MAGNITUDE 0)"
  MAVAIL="$(TGconv $AVAIL $MAGNITUDE 0)"

  # Setup for highlighting pct limit fields

  I=""
  E=""
  if [ $PCTLIMIT -gt 0 ]
  then
     PCT=$(echo $PERCENT | tr -d %)
     if [ $PCT -ge $PCTLIMIT ] 
     then
        I=$IV
        E=$EE
     fi
# show only over pct lines?

     [ $PCT -lt $PCTLIMIT -a $PCTLIMITONLY = "true" ] && continue
  fi

# Sums can easily overflow - change to megs before computing

  if [ $SUMS = "true" ]
  then
     MEGTOT=$((TOT/1024))
     MEGUSED=$((USED/1024))
     MEGAVAIL=$((AVAIL/1024))
     MEGGIG=$((MAGNITUDE/1024))
     let SUMTOT=$SUMTOT+$(echo $MEGTOT \
         | awk '{printf "%d", int($1/'$MEGGIG'+.5)}')
     let SUMUSED=$SUMUSED+$(echo $MEGUSED \
         | awk '{printf "%d", int($1/'$MEGGIG'+.5)}')
     let SUMAVAIL=$SUMAVAIL+$(echo $MEGAVAIL \
         | awk '{printf "%d", int($1/'$MEGGIG'+.5)}')
  fi

  if [ "$MNT" != "" ]                                   # skip unmounted
  then
    echo "$FS $MTOT$MUSED$MAVAIL$I$PERCENT$E \c"     # show data
    if $VERBOSE
    then

# VxFS filesystem version is found with fstyp but requires the source
#    device file be readable by current user. Otherwise, print na and 
#    add a note.  Also, handle raw devices such as swap and raw
#    database volumes in case bdf returns something for them.

# Find VxFS version - use mount -p to ID the filesystem type
# NOTE: search by mountpoint as cifs may have \\ names from WindowsJunk

       FSTYPE=$(echo "${MOUNTP}" \
              | awk -v MNT=${MNT} '{if ($2==MNT) {print $3}}')
       if [ "$FSTYPE" = "  vxfs" ]
       then
          if [ -r $FS ]                 # can we read the source devicefile?
          then
             FSVER=$(fstyp -v $FS | awk '/version:/ {print $2}')
          else
             FSVER="na"
             NODEVREAD=true
          fi
       else
          FSVER="-"
       fi

# SLOW CODE warning: df is invasive to get the right data 
#      so bdfmegs -v will be slow.

# filesystem features (from /usr/include/sys/statvfs.h)
# df -g will return these flags

#       ST_RDONLY      0x01    /* read only vfs */
#       ST_NOSUID      0x02    /* someone is waiting for lock */
#       ST_EXPORTED    0x04    /* file system is exported (NFS) */
#       ST_QUOTA       0x08    /* filesystem with quotas */
#       ST_LARGEFILES  0x10    /* filesystem supports largefiles */
#
# For now, just test that the features flag is greater
#   than 0x10 or 16. Use bc to convert the flag into
#   a decimal number. printf has a problem with %.0f
#   prior to 11.23. bc requires uppercase chars so
#   use typeset -u


       typeset -u HEX
       HEX=$(df -g $FASTDF $MNT \
            | tr -d x | awk '/ flags / {print $1}')
       DECFLG=$(echo "ibase=16\n $HEX" | bc) 
       [ $DECFLG -ge 16 ] && LG="yes" || LG=" no" 
  
       echo "$FSTYPE $FSVER $LG $MNT"
    else
       echo "$MNT"
    fi
  else

# this source is not mounted

    echo "$FS is not mounted or has no filesystem" >> $ERRLIST
  fi
done < $BDFLIST

# Summary of displayed lvol sizes
# BYTES=Mbytes or Gbytes (title line)
#   echo "File-System $PAD $BYTES    Used   Avail %Used \c"
# currently 8-char fields for total, used, avail. trailing char
# will be g,t,p if number is more than a 4 or 7 digit value

# Sum totals displayed here - Sums are in the current unit 
#    of measure (megs, gigs) so call TGconv with 1 for no conversion.

if [ $SUMS = "true" ]
then
   FSUMTOT=$(TGconv $SUMTOT $MAGNITUDE 1)
   FSUMUSED=$(TGconv $SUMUSED $MAGNITUDE 1)
   FSUMAVAIL=$(TGconv $SUMAVAIL $MAGNITUDE 1)
   eval "typeset -L$MAXLEN DASHES"
   DASHES="-- sum totals ------------------------------------------------"
   echo "$DASHES $FSUMTOT$FSUMUSED$FSUMAVAIL   --- sum totals ---"

fi

# In case -v was specified and one or more device files were not
#   readable by the current user, add a note

if $NODEVREAD
then
  echo "\n   ${IV}Note:${EE} ${BL}na$EE ${IV}means devicefile not readable by user $(id -un)$EE"
fi

######################
#   Error messages   #
######################

# Indent and highlight the error messages for readability

if [ -s $ERRLIST ]
then
   echo
   echo "Error messages:"
   cat $ERRLIST | while read
   do
      echo "   ${IV}$REPLY$EE"
   done
fi

# trailing blank line unless -q (no header line)
[ "$NOHEADER" = "false" ] && echo 
