package Monitor::fs;

require 5.002;

use strict;

@Monitor::fs::ISA = qw(Monitor::Monitor);

#
# 1. -F is SVR4 df, not just Solaris; this means that Irix 5.5 and later,
#    HPUX, UnixWare, etc. use it as well --- but don't necessarily use ufs
#    as the default filesystem type
#
# 2. FreeBSD uses -t but defaults to 512-byte blocks for POSIX compatibility
#
# 3. The whole -t vs. -F thing is stymied by installation of GNU df
#
# 4. If a filesystem name doesn't fit in its allotted field, GNU df skips
#    to the next line after printing it.
#
# 5. Rather than try to deduce the native filesystem type (a lost cause;
#    off the top of my head it could be ext2 ext3 reiserfs xfs jfs 4.2 ufs
#    advfs s51k efs and I'm certain there are others), use a heuristic to
#    reject non-local device filesystems if no fstype is specified.
#

sub new
{
    my ($this, $fstype, $fscomm) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};

    bless($self, $class);

    unless( $fscomm ) {
	my $fstype_opt = '-t';
	$fscomm = "df";
#	system( "$fscomm -k -F ufs > /dev/null 2>&1" );
#	if( $? ) {
#	    # BSD
#	    ($fstype = "4.2") unless( $fstype );
#	    $fscomm .= " -t $fstype";
#	 }
#	 else {
#	    # Solaris 2
#	    ($fstype = "ufs") unless( $fstype );
#	    $fscomm .= " -k -F $fstype";
#	 }
	open(_PROC, "$fscomm -t 2>/dev/null |");
	$_ = <_PROC>;
	if (!defined $_) {
	  # -t takes an argument, so probably BSD-style
	} else {
	  # SVR4-style
	  $fscomm .= ' -k';
	  $fstype_opt = '-F';
	}
	close(_PROC);
	open(_PROC, "$fscomm 2>/dev/null |");
	$_ = <_PROC>;
	if (!defined $_) {
	  # uh oh...
	  die "fs: no suitable df found, please set fscomm=\n";
	}
	elsif (/^\S+\s+\d/) {
	  # POSIX-ized df
	  $fscomm .= ' -k';
	}
	close(_PROC);
	if ($fstype) {
	  $fscomm .= " $fstype_opt $fstype";
	}
    }

    $self->{"fscomm"} = $fscomm;
    $self->{"fstype"} = $fstype;
    my( @bla)  = ();
    $self->{"htable"} = \@bla;

    return($self);
}


sub set_host {
    my( $self, $host ) = @_;

    $self->{"host"} = $host;
}


sub set_fscomm {
    my( $self, $fscomm ) = @_;

    $self->{"fscomm"} = $fscomm;
}


sub add_check {
    my( $self, $mp, $item, $yellowlevel, $redlevel ) = @_;
    my( $htable ) = $self->{"htable"};

    ($item = $uxmon::hostname.".disk") unless( $item );
    push( @$htable, join( "\x00", $mp, $item, $yellowlevel, $redlevel ) );
}


sub check
{
    my ($self) = @_;
    my( $htable ) = $self->{"htable"};
    my( $item, $yellowlevel, $redlevel );
    my( %mp_item, %mp_yellowlevel, %mp_redlevel, $default_item );
    my( $i, $n, $yn , $rn, $ystatus, $rstatus, $gstatus ); 
    my( %fstable, $type, $title, $cmd, $mpcnt, $prefix1, $prefix2 );
    my( %df, %res );
    my( $dev, $total, $used, $free, $usage, $mp, $stat, $txt, $ntxt, $nstat );
    $default_item = $uxmon::hostname.".disk";
    my $fstype=$self->{"fstype"};
    $mpcnt=-1;
    for( $i=0; $i<=$#$htable; $i++ ) {
        undef $redlevel;
        undef $yellowlevel;
	($mp, $item, $yellowlevel, $redlevel) = split( "\x00", $$htable[$i] );
        $mp_item{$mp}=$item;
        $mp_yellowlevel{$mp}=$yellowlevel;
        $mp_redlevel{$mp}=$redlevel;
        $txt = "($redlevel-$yellowlevel)";
        $txt = "" unless ($redlevel || $yellowlevel) ;
	# (Tom) I do not like this
	# $self->report( $item, 100, "fs=$mp$txt" );
        if ( $mp =~ /^some-(.*)/ || $mp =~ /^all-(.*)/ ) {
            $fstable{$1}=1;
            $prefix1="fstype ";
        } 
        next if /^all/ || /^some/;
        $mpcnt++;
    }
    $fstype ||= 'xxx';
    #
    # Backward compatibility - if only real mount points specified
    # (no all or some parameters) then just do those 
    # Always do the default fstype unless told otherwise
    #
    $mp_item{"all-$fstype"}=$default_item
        unless $fstable{$fstype} || 
               $mpcnt == $#$htable || 
               $mp_item{"some"};
    $fstable{$fstype}=1;    
    my $comment="";
    my $contline = '';
    foreach $type (keys %fstable) {
       $cmd =  $self->{'fscomm'};
       $cmd =~ s/\Q$fstype/$type/g;
       $prefix2 = sprintf "%-6.6s ",$type if $prefix1;
       open( _PROC,  "$cmd|" );
       while( <_PROC> ) {
           if( /^File/ ) {
              $title="$prefix1" . "$_"; 
              next;
           }
	   # if it is one word, we need a continuation line
	   if (!/[ \t]/) {	# NOT \s!  trailing newline, which we want
	     $contline = $_;
	     next;
	   }
	   elsif ($contline ne '') {
	     $_ = $contline . $_;
	     $contline = '';
	   }
           chomp;
	   ( $dev, $total, $used, $free, $usage, $mp ) = split( /[\s\t]+/ );
	   # reject non-devices.
	   # I will mercilessly mock anyone who complains about this losing
	   # on Cygwin...
	   print "fs: dev <$dev> ex <@{[-e $dev]}> f <@{[-f $dev]}> d <@{[-d $dev]}>\n" if $BigSister::common::dl > 3;
	   # If users want to monitor non-device filesystems (e.g. remote filesystems) let them
	   # do it
	   #next unless $dev =~ m!^/! && -e $dev && !(-f $dev || -d $dev);
	   $usage =~ s/%//g;
	   print "fs: dev $dev tot $total used $used free $free u% $usage\n" if $BigSister::common::dl > 2;
	   $self->set_variable( "", "disk.fs.$dev.total", $total );
	   $self->set_variable( "", "disk.fs.$dev.used", $used );
	   $self->set_variable( "", "disk.fs.$dev.free", $free );
	   $self->set_variable( "", "disk.fs.$dev.usage", $usage );
	   $self->set_variable( "", "disk.fs.$dev.mount", $mp );
	   $self->set_variable( "", "disk.fs.$dev.device", $dev );
           #
           # continue if this mount point is specifically selected; or 
           # all of this file system type is selected; or 
           # all filesystems but not some of this filesystem type; 
           #
           next unless 
		 $type eq '' ||
                 $mp_item{$mp} ||
                 $mp_item{"all-$type"} ||
                 (! $mp_item{"some-$type"} && $mp_item{"all"});

           $yellowlevel=$mp_yellowlevel{$mp} ||
                        $mp_yellowlevel{"all-$type"} ||
                        $mp_yellowlevel{"some-$type"} ||
                        $mp_yellowlevel{"all"} || 
                        $mp_yellowlevel{"some"} || 
                        10000;
           $redlevel=   $mp_redlevel{$mp} ||
                        $mp_redlevel{"all-$type"} ||
                        $mp_redlevel{"some-$type"} ||
                        $mp_redlevel{"all"} || 
                        $mp_redlevel{"some"} || 
                        5000;
           # item should always be hostname.disk - but just in case
           $item=       $mp_item{$mp} ||
                        $mp_item{"all-$type"} ||
                        $mp_item{"some-$type"} ||
                        $mp_item{"all"} || 
                        $mp_item{"some"} || 
                        $default_item;
           
           $yn=$rn=$free;
           $yellowlevel =~ s/[kK]$//g;   # strip off k(ilobytes)
           $redlevel =~ s/[kK]$//g;
           $ystatus=$free . "k (< ${yellowlevel}k) ";
           $rstatus=$free . "k (<< ${redlevel}k) ";
           $gstatus=$free . "k";
           #
           # if redlevel is a percentage convert free space to a percentage.
           # and express the red status message in percentages
           #
           if ( $redlevel =~ /(.*)%/ ) {
               $redlevel=$1;
               $rn = $free == 0 ? 0 : $free*100/($free+$used);
               $rstatus = sprintf "%.1f%% (<< %s%%)",$rn,$redlevel;
               #
               #  If either redlevel or yellow level is a percentage ...
               #
               $gstatus = sprintf "%.1f%%", $rn;
           }
           if ( $yellowlevel =~ /(.*)%/ ) {
               $yellowlevel=$1;
               $yn = $free == 0 ? 0 : $free*100/($free+$used);
               $ystatus = sprintf "%.1f%% (< %s%%)",$yn,$yellowlevel;
               #
               #  ... Then express green status as a percentage.
               #
               $gstatus = sprintf "%.1f%%", $yn;

           }
           if( $rn<=$redlevel ) {
	       $nstat = $BigSister::common::status_codes{"red"};
	       $ntxt = "$mp $rstatus free";
	   }
	   elsif( $yn<=$yellowlevel ) {
	       $nstat = $BigSister::common::status_codes{"yellow"};
	       $ntxt = "$mp $ystatus free";
	   }
	   else {
	       $nstat = $BigSister::common::status_codes{"green"};
	       $ntxt = "$mp $gstatus free";
	   }
	   $self->report( $item, $nstat, $ntxt );
           $comment .= "$prefix2$_\n";
       }
       close _PROC;
   }
   $self->report( $item, 101, "&html<HR><PRE><strong>$title</strong>".$comment."</PRE>" );
}

1;
