#!/usr/bin/perl -w

# nadisk module
# This module is not particularly efficient.

# Version: $Revision: 0.11 $
# Author: Benjamin Oshrin and Matt Selsky
# Date: $Date: 2007/02/11 02:08:03 $
#
# Copyright (c) 2002 - 2007
# The Trustees of Columbia University in the City of New York
# Academic Information Systems
# 
# License restrictions apply, see doc/license.html for details.

use strict;
use FindBin;
use lib "$FindBin::Bin/../common";
use Survivor::Check;

use SNMP;

my %argfmt = ('community' => 'optional string default(public)',
	      'examine' => 'flags any(q,s,v)',
	      'warn' => 'optional number between(0,101) default(101)',
              'prob' => 'optional number between(0,101) default(101)',
	      'diskcf' => 'optional file',
              'filesystem' => 'optional string list',
	      'snmpversion' => 'optional string one(1,2,2c,3) default(1)',
	      'snmptimeout' => 'optional number between(1,inf) default(5)');

my $sc = new Survivor::Check();

# Disable threading pending threadsafe SNMP.
$sc->UseThreads(0);

$sc->GetOpt(\%argfmt, \&NadiskValidate);

my $warn = $sc->Arg('warn');
my $prob = $sc->Arg('prob');

if ($warn == 101 && $prob == 101 && !$sc->Arg('filesystem')) {
    $sc->ExitError(MODEXEC_MISCONFIG, 0,
		   "Must specify either 'warn' or 'prob'");
}

my $opts = $sc->Arg('examine');

$opts =~ /[qv]/ or $sc->ExitError(MODEXEC_MISCONFIG, 0,
		     "Argument 'examine' must contain 'q' or 'v'");

# If we were given a control file, parse it

my $cf = $sc->Arg('diskcf');
my(%fswarn,%fsprob);

if(defined $cf) {
    open(CONTROL, $cf) or
	$sc->ExitError(MODEXEC_MISCONFIG, 0,
		       "Unable to read config file $cf");

    while(<CONTROL>) {
	chomp;          # Toss newline
	s/#.*$//;       # Toss comments
	s/^\s+//;       # Toss initial spaces
	s/\s+$//;       # Toss final spaces
	my($xfs,$xwarn,$xprob) = split(',', $_, 3);
	
	if(defined $xfs && $xfs ne '') {
	    $fswarn{$xfs} = $xwarn;
	    $fsprob{$xfs} = $xprob;
	}
    }
    
    close(CONTROL);
}

# Calculate timeout in microseconds

my $snmptimeout = $sc->Arg('snmptimeout') * 1000000;

my $r = $sc->Exec( \&NadiskCheck);

exit($r);

sub NadiskValidate {
    return(MODEXEC_OK, 0, "Nadisk OK");
}

sub NadiskCheck {
    my $self = shift;
    my $host = shift;

    my $community = $self->Arg('community');
    my @fs = $self->Arg('filesystem');

    my $rc = MODEXEC_OK;

    my(%qentries, %qtreeavail, %qtreenames, %qtreeused, %volnames, %volvalues,
       $proberr, $warnerr, $okerr);

    # If exactly one fs was matched, the scalar returned is fullness
    # of that fs, otherwise # of "full" filesystems
    my $full = 0;
    my $count = 0;
    my $last = 0;  # This is only used if count is 1

    if($opts =~ /q/) {
	my $maxentry = 0;

	my($oid,$vb,$hasResults);
	my $sess = new SNMP::Session( DestHost => $host,
				      Community => $community,
				      UseLongNames => 1,
				      UseNumeric => 1,
				      Version => $sc->Arg("snmpversion"),
				      Timeout => $snmptimeout );

	if(!defined $sess) {
	    return(MODEXEC_PROBLEM, 0, 'Unable to resolve hostname');
	}

	# Assemble quota tree names

	$oid = '.1.3.6.1.4.1.789.1.4.5.1.2';
	$vb = new SNMP::Varbind([$oid]);
    
	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);
	    
	    if($vb->val() == 3) {
		# This is a quota tree
		$qentries{$vb->iid()} = 1;
		$maxentry = $vb->iid();
	    } else {
		# Stop after not-quota tree, else we'll be here for days
		# parsing everyone's quota
		last;
	    }
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 0, 'No response while getting quota tree names');
	}

	# Next, read in the path names

	$oid = '.1.3.6.1.4.1.789.1.4.5.1.8';
	$vb = new SNMP::Varbind([$oid]);
    
	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);

	    if($vb->iid() <= $maxentry) {
		if(exists $qentries{$vb->iid()}) {
		    $qtreenames{$vb->iid()} = $vb->val();
		}
	    } else {
	        # else we'll be here all week
		last;
	    }
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 'No response while getting path names');
	}

	# Next, read in the space used

	$oid = '.1.3.6.1.4.1.789.1.4.5.1.4';
	$vb = new SNMP::Varbind([$oid]);

	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);

	    if($vb->iid() <= $maxentry) {
		if(exists $qentries{$vb->iid()}) {
		    $qtreeused{$vb->iid()} = $vb->val();
		}
	    } else {
	        # else we'll be here all week
		last;
	    }
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 0, 'No response while getting space used');
	}

	# And finally, the space available

	$oid = '.1.3.6.1.4.1.789.1.4.5.1.5';
	$vb = new SNMP::Varbind([$oid]);

	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);
	    
	    if($vb->iid() <= $maxentry) {
		if(exists $qentries{$vb->iid()}) {
		    $qtreeavail{$vb->iid()} = $vb->val();
		}
	    } else {
	        # else we'll be here all week
		last;
	    }
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 0, 'No response while getting available space');
	}
    }
    
    if($opts =~ /v/) {
	my($oid,$vb,$hasResults);
	my $sess = new SNMP::Session( DestHost => $host,
				      Community => $community,
				      UseLongNames => 1,
				      UseNumeric => 1,
				      Version => $sc->Arg("snmpversion") );

	if(!defined $sess) {
	    return(MODEXEC_PROBLEM, 0, 'Unable to resolve hostname');
	}

	# Assemble volume names

	$oid = '.1.3.6.1.4.1.789.1.5.4.1.2';
	$vb = new SNMP::Varbind([$oid]);
    
	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);
	    
	    $volnames{$vb->iid()} = $vb->val();
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 0, 'No response while getting volume names');
	}

	# Assemble volume capacities

	$oid = '.1.3.6.1.4.1.789.1.5.4.1.6';
	$vb = new SNMP::Varbind([$oid]);
    
	$hasResults = 0;
	while(defined $sess->getnext($vb)) {
	    $hasResults++;
	    last if ($vb->name() !~ /^$oid/);
	    
	    $volvalues{$vb->iid()} = $vb->val();
	}
	
	if($hasResults == 0) {
	    return(MODEXEC_PROBLEM, 0, 'No response while getting volume capacities');
	}
    }

    # Look at the data
    
    foreach my $k (keys %volnames) {
	# Make sure this is a filesystem to examine
	my $match = 0;

	if(scalar(@fs)==0) {
	    $match++;
	} else {
	    foreach my $f (@fs) {
		if($volnames{$k} =~ /$f/) {
		    $match++;
		}
	    }
	}
	
	if($match && (!($volnames{$k} =~ /\.snapshot$/) || ($opts =~ /s/))) {
	    my($lwarn, $lprob) = &findthresholds($volnames{$k});
	    
	    if($lprob ne '' && $volvalues{$k} >= $lprob) {
		$rc = MODEXEC_PROBLEM;
		$proberr .= 'volume '.$volnames{$k}.' at '.$volvalues{$k}.'%,';
		$full++;
	    } elsif($lwarn ne '' && $volvalues{$k} >= $lwarn) {
		if($rc != MODEXEC_PROBLEM) {
		    $rc = MODEXEC_WARNING;
		}
		$warnerr .= 'volume '.$volnames{$k}.' at '.$volvalues{$k}.'%,';
		$full++;
	    } else {
		$okerr .= 'volume '.$volnames{$k}.' at '.$volvalues{$k}.'%,';
	    }

	    $count++;
	    $last = $volvalues{$k};
	}
    }

    foreach my $k (keys %qtreenames) {
	# Make sure this is a filesystem to examine
	my $match = 0;

	if(scalar(@fs)==0) {
	    $match++;
	} else {
	    foreach my $f (@fs) {
		if($qtreenames{$k} =~ /$f/) {
		    $match++;
		}
	    }
	}

	if($match) {
	    my($lwarn, $lprob) = &findthresholds($qtreenames{$k});

	    if(!defined($qtreeused{$k}) || !defined($qtreeavail{$k}))
	    {
		$rc = MODEXEC_PROBLEM;

		$proberr .=
		    'quota tree '.$qtreenames{$k}.' returned undefined result,';
	    }
	    else
	    {
		my $qfull = sprintf("%d",
				    ($qtreeused{$k} * 100)/$qtreeavail{$k});
	    
		if($lprob ne '' && $qfull >= $lprob) {
		    $rc = MODEXEC_PROBLEM;
		    $proberr .=
			'quota tree '.$qtreenames{$k}.' at '.$qfull.'%,';
		    $full++;
		} elsif($lwarn ne '' && $qfull >= $lwarn) {
		    if($rc != MODEXEC_PROBLEM) {
			$rc = MODEXEC_WARNING;
		    }
		    $warnerr .=
			'quota tree '.$qtreenames{$k}.' at '.$qfull.'%,';
		    $full++;
		} else {
		    $okerr .= 'quota tree '.$qtreenames{$k}.' at '.$qfull.'%,';
		}

		$count++;
		$last = $qfull;
	    }
	}	
    }
    
    # Create a result, leaving the commas at the end of the strings

    my $err = '';
    
    if(defined $proberr && length $proberr) {
	$err = "PROBLEM: $proberr";
    }
    if(defined $warnerr && length $warnerr) {
	$err .= "WARNING: $warnerr";
    }
    if(defined $okerr && length $okerr) {
	$err .= "OK: $okerr";
    }

    chop($err);

    if($count == 1) {
	$full = $last;
    }
    
    return($rc, $full, $err);
}

sub findthresholds {
    my $xmount = shift;

    my $xw = -1;
    my $xp = -1;

    foreach my $xkey (keys %fswarn) {
	if($xmount =~ /$xkey/) {
	    if($xw == -1) {
		$xw = $fswarn{$xkey};
		$xp = $fsprob{$xkey};
	    }
	}
    }

    if($xw == -1) {
	$xw = $warn;
	$xp = $prob;
    }

    return($xw, $xp);
}
