#!/usr/bin/perl

#
# Copyright 2003 VMware, Inc.  All rights reserved.
#

# Does the 'df' equivalent for VMFS file systems under /vmfs

# Handles an optional '-h' argument (human-readable output), an
# optional '-P' argument (no line wrapping) plus zero or
# one path arguments.

use Cwd;
use Getopt::Std;

# If human-readable output is requested.
my $human;
# Each entry should occupy only one line
my $nowrap;
# Locations of vmkfstools
my $fstools;
# Path argument
my $file;
my %opts;

sub title() {
  if ($human) {
    print("Filesystem            Size  Used Avail Use% Mounted on\n");
  } else {
    print("Filesystem           1k-blocks      Used Available Use% Mounted on\n");
  }
}

#
# Convert byte size to human-readable form.
#
sub mksize($) {
  my ($n) = @_;

   if ($n < 1000) {
      $res = sprintf("%4u", $n);
   } elsif ($n < 10 * 1024) {
      $res = sprintf("%3.1f", $n / 1024);
   } elsif ($n < 1024 * 1024) {
      $res = sprintf("%3uk", $n / 1024);
   } elsif ($n < 10 * 1024 * 1024) {
      $res = sprintf("%3.1fM", $n / (1024 * 1024));
   } elsif ($n < 1024 * 1024 * 1024) {
      $res = sprintf("%3uM", $n / (1024 * 1024));
   } elsif ($n < 10 * 1024 * 1024 * 1024) {
      $res = sprintf("%3.1fG", $n / (1024 * 1024 * 1024));
   } elsif ($n < 1024 * 1024 * 1024 * 1024) {
      $res = sprintf("%3uG", $n / (1024 * 1024 * 1024));
   } else {
      $res = sprintf("%3.1fT", $n / (1024 * 1024 * 1024 * 1024));
   }
   return ($res);
}

#
# Show info on a single VMFS
#
sub showfs($) {
   my ($fs) = @_;
   my $fsName = $fs;
   my $fmt;  # Used for format specifier in printf
   open("Y", "$fstools -P \"$fs\"|");
   while (<Y>) {
      #
      # Use volume labels under "Mounted on" column
      #
      if (/^File system label \(if any\):\s*(\S.*)\s*$/) {
         if ($fs !~ /\/vmfs\/devices.*/) {
            $fsName = "/vmfs/volumes/$1";
         }
      }
      if (/Capacity ([0-9]*) .*, ([0-9]*) .*avail/) {
         $cap = $1;
         $avail = $2;
         #
         # With UUID based volume names, after printing the fs
         # name we move to the next line to print Size/Avail
         # etc. Make sure we insert a new line only if we hit
         # the character limit for Filesystem length and 
         # 'nowrap' is turned off.
         #
         if ($human) {
            $fmt = "%-15s      %5s %5s %5s %3d%% %s\n";
            if (!$nowrap && (countfsname($fs) > 21)) {
               $fmt = "%-15s\n%26s %5s %5s %3d%% %s\n";
            }
	    if ($cap != 0) {
		printf "$fmt", 
			$fs, mksize($cap), mksize($cap-$avail), 
			mksize($avail), ($cap-$avail)*100/$cap, $fsName;
	    } else {
		printf "$fmt", 
			$fs, mksize($cap), mksize($cap-$avail), 
			mksize($avail), 0, $fsName;
	    }
         } else {
            $fmt = "%-15s%15.0f%15.0f%15.0f %3d%% %s\n"; 
            if (!$nowrap && countfsname($fs) > 15) {
               $fmt = "%-15s\n%30.0f%15.0f%15.0f %3d%% %s\n";
            }
	    if ($cap != 0) {
		printf "$fmt", 
			$fs, $cap / 1024, ($cap-$avail) / 1024, 
			$avail / 1024, ($cap-$avail)*100/$cap, $fsName;
	    } else {
		printf "$fmt", 
			$fs, $cap / 1024, ($cap-$avail) / 1024, 
			$avail / 1024, 0, $fsName;
	    }
         }
         break;
      }
   }
}

#
# Count the characters in filesystem name using
# transliteration
#
sub countfsname {
   my $fs = shift; # IN: fs name
   return ($fs =~ tr~/:a-zA-Z0-9_-~/:a-zA-Z0-9_-~);
}

#
# Show info on all VMFSes
#
sub showallfs {
   my $noDev = shift || 0;
   unless ($noDev) {
      showfs("/vmfs/devices");
   }
  open("X", "/bin/ls /vmfs/volumes|");
  while (<X>) {
     #
     # Bypass in case of a sym link 
     #
     chop;
     unless (-l "/vmfs/volumes/$_") {
       showfs("/vmfs/volumes/$_");
     }
  }
}

if ($ENV{"VMTREE"} && $ENV{"VMBLD"} && -x "$ENV{'VMTREE'}/build/$ENV{'VMBLD'}/apps/vmkfstools/vmkfstools") {
  $fstools = "$ENV{'VMTREE'}/build/$ENV{'VMBLD'}/apps/vmkfstools/vmkfstools";
} elsif (-x '/usr/sbin/vmkfstools') {
  $fstools = '/usr/sbin/vmkfstools';
} else {
  printf("Can't find vmkfstools executable\n");
  exit(1);
}

getopts('hP', \%opts);

$human = 0;
$nowrap = 0;
if ($opts{'h'}) {
  $human = 1;
}
if ($opts{'P'}) {
   $nowrap = 1;
}

my $args = "";
$args .= " -h" if ($human);
$args .= " -P" if ($nowrap);

if ($#ARGV >= 0) {
  my %volumeTable;

  for ($i=0; $i <= $#ARGV; $i++) {
   $file = $ARGV[$i];
   if (! -e $file) {
         printf "`%s': No such file or directory\n", $file;
         exit(1);
   }
   if (-f $file) {
      # realpath only works on directories, not files
      if ($file =~ m~/~) {
            $file =~ s~/[^/]*$~~;
      } else {
            $file = ".";
      }
   }
   $file = Cwd::realpath($file);
   if ($i == 0 && $file =~ m/^\/vmfs/) {
	title();
   }
   if (!defined $volumeTable{$file}) {
      $volumeTable{$file} = "1";
      if ($file eq '/vmfs') {
	    # Show all VMFSes if df on /vmfs
	    showallfs();
      } else {
	 if ($file eq '/vmfs/volumes') {
	    # Only show vmfs volumes
	    showallfs(1);
	 } elsif ($file =~ /(\/vmfs\/.*)/) {
	    showfs($file);
	 } else {
	    # Do a normal df if we're not under /vmfs
	    system("/bin/df -x vmfs $args @ARGV");
	 }
      }
   }
  }
} else {
  # With no args, do a normal df followed by info on all VMFSes
   
  system("/bin/df -x vmfs $args");
  if (-e "/vmfs") {
    showallfs();
  }
}
