#!/usr/bin/perl
#
# Report on filesystem space usage ... sort of an improved 'df'

use strict;
use warnings;
use Number::Bytes::Human qw(format_bytes);
use Filesys::DfPortable;
use Text::Truncate;

our $debug = 0;
#   Enable debugging messages
our $width = `tput cols`;
#   Width of output; we hope to have something useful out of 'tput cols'
#   which seems to work for me.
our $siunits = 0;
#   Whether to use SI units for Number::Bytes::Human
our $blocksize = 1024;
#   Get a consistent block size
our @mountpoints = getmounts("/etc/mtab");
push @mountpoints, getmounts("/etc/mnttab");
#   And get the mountpoints. First is Linux and second is Solaris
our @headings = ("Filesystem", "Size", "%Used", "%fslots", "Avail", "Rsrved", "Used");
our @widths = (-35, 7, 7, 8, 7, 7, 7);
#   Headings for each column together with their nominal width. Note that
#   negative widths means left justfied and not right justified.
our %tput = ( "smso" => `tput smso`,
	      "rmso" => `tput rmso` );

if (($width + 0) < 1) {
  $width = 80;
  # Just a sensible default in case things aren't working. The addition of
  # zero makes sure the test works.
} else {
  $width = $width + 0;
  # Make numeric; gets rid of an extraneous \n
}

print "Number of mountpoints: $#mountpoints\n" if $debug;
print "Width = $width\n" if $debug;

outheadings($width);
foreach my $mount (@mountpoints) {
  my $df = dfportable($mount, $blocksize);
  if ((defined($df)) && ($df->{blocks} > 0)) {
    # Not interested in fake filesystems ... yet
    outfs($mount, $df, $width);
  } else {
    print "No information for $mount\n" if $debug;
  }
}

# getmounts
#
# Go through /etc/mnttab and extract mount points

sub getmounts {
  my $mnttab = pop(@_);
  my $in;
  my @mountpoints;

  open $in, "<$mnttab" or return ();
  # This deliberately doesn't die because we might want to try different
  # files. 
  while (<$in>) {
    my @words = split;
    push @mountpoints, $words[1];
  }
  return @mountpoints;
}

# outraw
#
# Output a series of strings according to their percentage share of 
# the width of the screen.
#
#sub outraw {
#  my $w = pop(@_);
#  my $strper;
#  while (@_) {
#    my $str = pop(@_);
#    my $width = int(pop(@_) * $w / 100);
#    #print "str=$str, width=$width\n" if $debug;
#    $str = truncstr($str, abs($width), "+");
#    printf "%${width}s", $str;
#  }
#  print "\n";
#}

# outraw
#
# Output a series of strings according to their minimum size; once the width
# is consumed, and remaining columns are silently discarded; the intention is
# that less useful columns are left until the end.

sub outraw {
  my $w = pop(@_);
  print "Initial width=$w\n" if $debug;
  while (@_) {
    my $str = pop(@_);
    my $width = pop(@_);
    if ($width < $w) {
      $str = truncstr($str, abs($width), "+");
      printf "%${width}s", $str;
    }
    $w = $w - abs($width);
  }
  print "\n";
  print "Remaining width=$w\n" if $debug;
}

# outfs
#
# Display the filesystem output in a neatly formatted manner

sub outfs {
  my $width = pop(@_);
  my $df = pop(@_);
  my $mount = pop(@_);

  my @words;

  # Remember: Last column first!
  push @words, ($widths[5], format_bytes($df->{bused} * $blocksize, si => $siunits));
  push @words, ($widths[4], format_bytes(($df->{bfree} - $df->{bavail}) * $blocksize, si => $siunits));
  push @words, ($widths[4], format_bytes($df->{bavail} * $blocksize, si => $siunits));
  if (defined($df->{fper})) {
    push @words, ($widths[3], "$df->{fper}%");
  } else {
    push @words, ($widths[3], "n/a");
  }
  push @words, ($widths[2], "$df->{per}%");
  push @words, ($widths[1], format_bytes($df->{blocks} * $blocksize, si => $siunits));
  push @words, ($widths[0], $mount);

  outraw(@words, $width);
}

# outheadings
#
# Display the headings for each column using outraw in much the same way as outfs.

sub outheadings {
  my $width = pop(@_);
  my @words;
  for (my $i = $#widths; $i > -1; $i--) {
    push @words, ($widths[$i], $headings[$i]);
  }
  print $tput{smso};
  outraw(@words, $width);
  print $tput{rmso};
}
