#!/usr/bin/perl -w 
############################## check_linux_vol ####################
# Version : 0.20
# Date : Jan 08 2006
# Author  : Rainer Brunold (rainer.brunold@brunold.at)
#
# This script contains parts from the check_snmp_mem.pl from
# Patrick Proy (patrick at proy.org)
#
# This programs queries the host ressource mib of the destination server
# and builds a list of all partitions and filesystems. Then it tries to
# determine all mounted filesystems and checks if any filesystem is 
# above the warning or critical value. Filesystems that are mounted via
# NFS as well as the shared memory /dev/shm are ignored.
# As a result you get a short list of the checked filesystems and the
# exit code reagrding your settings. If you activate the verbose mode
# you get much more information ....
#
# The new thing is that you don't have to provide a list of filesystems.
# There are some other check modules where you can provide a list of
# filesystems but I don't want to do that, to much work. I want to check all.
#
# Another important thing is that this check program works only with snmp v3 !
# I don't want to use the older one any more.
#
# The program was tested with
# Novell Open Enterprise Server 9
# Novell SuSE Linux Enterprise Server 9
#
# Currectly no test with lvm/evms have been done.
# I'll do this very quickly .... 6.1.2006
#
# Licence : GPL - http://www.fsf.org/licenses/gpl.txt
# 
# changelog 
# 0.20 - 8.1.2006:  nss tests done, some minor changes
#                   - nss admin volume /_admin will be ignored,
#                   - nss pools /opt/novell/nss/mnt/.pools will be ignored
#################################################################
#
# Help : ./check_linux_vol.pl -h
#
use strict;
use Net::SNMP;
use Getopt::Long;

# Nagios specific
use lib "/usr/local/nagios/libexec";
use utils qw(%ERRORS $TIMEOUT);

# SNMP Datas
my $oid_hrpart_size_base  = "1.3.6.1.2.1.25.3.7.1.4"; # eq hrPartitionSize
my $oid_hrpart_index      = "1.3.6.1.2.1.25.3.7.1.5"; # eq hrPartitionFSIndex
my $oid_mountpoint        = "1.3.6.1.2.1.25.3.8.1.2"; # eq hrFSMountPoint
my $oid_stor_index        = "1.3.6.1.2.1.25.2.3.1.1"; # eq hrStorageIndex
my $oid_stor_type         = "1.3.6.1.2.1.25.2.3.1.2"; # eq hrStorageType
my $oid_stor_desc         = "1.3.6.1.2.1.25.2.3.1.3"; # eq hrStorageDescr
my $oid_stor_units        = "1.3.6.1.2.1.25.2.3.1.4"; # eq hrStorageAllocationUnits
my $oid_stor_size         = "1.3.6.1.2.1.25.2.3.1.5"; # eq hrStorageSize
my $oid_stor_used         = "1.3.6.1.2.1.25.2.3.1.6"; # eq hrStorageUsed
my $oid_fs_mountpoint     = "1.3.6.1.2.1.25.3.8.1.2"; # eq hrFSMountPoint
my $oid_fs_remmountpoint  = "1.3.6.1.2.1.25.3.8.1.3"; # eq hrFSRemoteMountPoint
my $oid_fs_type           = "1.3.6.1.2.1.25.3.8.1.4"; # eq hrFSType
my $oid_fs_access         = "1.3.6.1.2.1.25.3.8.1.5"; # eq hrFSAccess
my $oid_hrpart_next;
my $oid_hrpart_sub;
my $exitcode = 0;
# conversion table from the host ressource mib
# didn't work with autom. conversion from the names to the ois, so here a list
my %stor_types        = ( "1.3.6.1.2.1.25.2.1.1" => "hrStorageOther",
                          "1.3.6.1.2.1.25.2.1.2" => "hrStorageRam",
                          "1.3.6.1.2.1.25.2.1.3" => "hrStorageVirtualMemory",
                          "1.3.6.1.2.1.25.2.1.4" => "hrStorageFixedDisk",
                          "1.3.6.1.2.1.25.2.1.5" => "hrStorageRemovableDisk",
                          "1.3.6.1.2.1.25.2.1.6" => "hrStorageFloppyDisk",
                          "1.3.6.1.2.1.25.2.1.7" => "hrStorageCompactDisc",
                          "1.3.6.1.2.1.25.2.1.8" => "hrStorageRamDisk" );
my %filesys_types     = ( "1.3.6.1.2.1.25.3.9.1" => "hrFSOther",
                          "1.3.6.1.2.1.25.3.9.2" => "hrFSUnknown",
                          "1.3.6.1.2.1.25.3.9.3" => "hrFSBerkeleyFFS",
                          "1.3.6.1.2.1.25.3.9.4" => "hrFSSys5FS",
                          "1.3.6.1.2.1.25.3.9.5" => "hrFSFat",
                          "1.3.6.1.2.1.25.3.9.6" => "hrFSHPFS",
                          "1.3.6.1.2.1.25.3.9.7" => "hrFSHFS",
                          "1.3.6.1.2.1.25.3.9.8" => "hrFSMFS",
                          "1.3.6.1.2.1.25.3.9.9" => "hrFSNTFS",
                          "1.3.6.1.2.1.25.3.9.10" => "hrFSVNode",
                          "1.3.6.1.2.1.25.3.9.11" => "hrFSJournaled",
                          "1.3.6.1.2.1.25.3.9.12" => "hrFSiso9660",
                          "1.3.6.1.2.1.25.3.9.13" => "hrFSRockRidge",
                          "1.3.6.1.2.1.25.3.9.14" => "hrFSNFS",
                          "1.3.6.1.2.1.25.3.9.15" => "hrFSNetware",
                          "1.3.6.1.2.1.25.3.9.16" => "hrFSAFS",
                          "1.3.6.1.2.1.25.3.9.17" => "hrFSDFS",
                          "1.3.6.1.2.1.25.3.9.18" => "hrFSAppleshare",
                          "1.3.6.1.2.1.25.3.9.19" => "hrFSRFS",
                          "1.3.6.1.2.1.25.3.9.20" => "hrFSDGCFS",
                          "1.3.6.1.2.1.25.3.9.21" => "hrFSBFS" );
my $Version=	'0.20';
my $o_host= 	undef; 		# hostname
my $o_help=	undef; 		# wan't some help ?
my $o_version=	undef;		# print version
my $o_warn=	undef;		# warning level option
my $o_crit=	undef;		# Critical level option
my $o_timeout=  5;             	# Default 5s Timeout
my $o_login=	undef;		# Login for snmpv3
my $o_passwd=	undef;		# Pass for snmpv3
my $o_verb=     undef;          # Verbose Mode

# functions

sub p_version { print "check_linux_vol.pl   version : $Version\n"; }

sub print_usage {
    print "Usage: $0 -H <host> -l login -x passwd -w <warn level> -c <crit level> [-t <timeout>] [-V] [-v]\n";
}

sub isnnum { # Return true if arg is not a number
  my $num = shift;
  if ( $num =~ /^(\d+\.?\d*)|(^\.\d+)$/ ) { return 0 ;}
  return 1;
}

sub round ($$) {
    sprintf "%.$_[1]f", $_[0];
}

sub help {
   print "\nSNMP Volume Monitor for SuSE / Novell Linux - version ",$Version,"\n\n";
   print "\nThis program is licensed under GPL - http://www.fsf.org/licenses/gpl.txt\n\n";
   print_usage();
   print <<EOT;
-h, --help
   print this help message
-H, --hostname=HOST
   name or IP address of host to check
-l, --login=LOGIN
   Login for snmpv3 authentication 
-x, --passwd=PASSWD
   Password for snmpv3 authentication
-w, --warn=INTEGER
   warning level for volume used in percent 
-c, --crit=INTEGER 
   critical level for volume used in percent 
-t, --timeout=INTEGER
   timeout for SNMP in seconds (Default: 5)
-V, --version
   prints version number
-v, --verbose
   verbose output, displays additional informataion
EOT
}

# Get the alarm signal (just in case snmp timout screws up)
$SIG{'ALRM'} = sub {
     print ("ERROR: Alarm signal (Nagios time-out)\n");
     exit $ERRORS{"UNKNOWN"};
};

sub check_options {
    Getopt::Long::Configure ("bundling");
    GetOptions(
        'h'     => \$o_help,    	'help'        	=> \$o_help,
        'H:s'   => \$o_host,		'hostname:s'	=> \$o_host,
	'l:s'	=> \$o_login,		'login:s'	=> \$o_login,
	'x:s'	=> \$o_passwd,		'passwd:s'	=> \$o_passwd,
        't:i'   => \$o_timeout,       	'timeout:i'     => \$o_timeout,
        'w:s'   => \$o_warn,            'warn:s'        => \$o_warn,
        'c:s'   => \$o_crit,            'crit:s'        => \$o_crit,
        'v'     => \$o_verb,            'verbose'       => \$o_verb,
    );
    if (defined ($o_help) ) { help(); exit $ERRORS{"UNKNOWN"}};
    if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"}};
    if ( ! defined($o_host) ) # check host and filter 
	{ print "No host defined!\n";print_usage(); exit $ERRORS{"UNKNOWN"}}
    # check snmp information
    if ( !defined($o_login) || !defined($o_passwd) )
	{ print "Put snmp login info!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
    #Check Warning and crit are present
    if ( ! defined($o_warn) || ! defined($o_crit))
 	{ print "Put warning and critical values!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
    # Get rid of % sign
    $o_warn =~ s/\%//g; 
    $o_crit =~ s/\%//g;
    if ( isnnum($o_warn) || isnnum($o_crit)) 
	{ print "Numeric value for warning or critical !\n";print_usage(); exit $ERRORS{"UNKNOWN"} }
    if ( ($o_crit != 0) && ($o_warn > $o_crit) ) 
        { print "warning <= critical ! \n";print_usage(); exit $ERRORS{"UNKNOWN"}}
}

########## MAIN #######

check_options();

# Check gobal timeout if snmp screws up
if (defined($TIMEOUT)) {
  alarm($TIMEOUT);
} else {
  alarm ($o_timeout+10);
}

# Connect to host
my ($session,$error);
($session, $error) = Net::SNMP->session(
    -hostname   	=> $o_host,
    -version		=> '3',
    -username		=> $o_login,
    -authpassword	=> $o_passwd,
    -authprotocol	=> 'md5',
    -timeout            => $o_timeout
);

if (!defined($session)) {
   printf("ERROR opening session: %s.\n", $error);
   exit $ERRORS{"UNKNOWN"};
}

###################################################
# Build a hash with all available filesystem ntries
my $i = 0;
my $result = "";
my %filesyslist;
my @storlist;
my $netsnmp=undef;
my $entry = 0;
my $numberofentries;
my $hr_part_size;
my $hr_part_size_unit;
my $usage;

if (defined ($o_verb)){
  printf "\n\nVerbos Mode:\n\n";
  printf "Querying all available filesystems and building a hash table:\n\n";
}

# repeat the snmp/get_request till we get "noSuchInstance"
# that's the sign that there are no further entries below that oid
while ( "$result" !~ m/noSuchInstance/ ) {
# use the var i to count and increment the oids
  $i += 1;
  my $oid_fs_mountpoint_new = $oid_fs_mountpoint . "." . $i;
  my $oid_fs_remmountpoint_new = $oid_fs_remmountpoint . "." . $i;
  my $oid_fs_type_new = $oid_fs_type . "." . $i;
  my $oid_fs_access_new = $oid_fs_access . "." . $i;
  my @oid_fs = ($oid_fs_mountpoint_new,$oid_fs_remmountpoint_new,$oid_fs_type_new,$oid_fs_access_new);
# do the snmp get_request
  $netsnmp = $session->get_request(@oid_fs);
# if the result is not a "noSuchInstance" put the response into a array inside a hash
  if ( "$$netsnmp{$oid_fs_mountpoint_new}" !~ m/noSuchInstance/ ) {
    $filesyslist{$$netsnmp{$oid_fs_mountpoint_new}} = [$$netsnmp{$oid_fs_remmountpoint_new},$filesys_types{$$netsnmp{$oid_fs_type_new}},$$netsnmp{$oid_fs_access_new}];

    if (defined ($o_verb)){
      printf "==================\n";
      printf "Mountpoint ....... " . $$netsnmp{$oid_fs_mountpoint_new} . "\n";
      printf "Filesys Type ..... " . $filesyslist{$$netsnmp{$oid_fs_mountpoint_new}}[1] . "\n";
      printf "Filesys Access ... " . $filesyslist{$$netsnmp{$oid_fs_mountpoint_new}}[2] . "\n";
    }

  }
# set the result var to break the while loop if we got a "noSuchInstance"
  $result = $$netsnmp{$oid_fs_mountpoint_new};
}

############################################################
# Build a array of arrays with all available storage entries

$i = 1;
$result = "";
$entry = 0;

if (defined ($o_verb)){
  printf "\n\nQuerying all available partitions and building a array of arrays:\n\n";
}

# repeat the snmp/get_request till we get "noSuchInstance"
# that's the sign that there are no further entries below that oid
while ( "$result" !~ m/noSuchInstance/ ) {
# use the var i to count and increment the oids
  $i += 1;
  my $oid_stor_index_new = $oid_stor_index . "." . $i;
  my $oid_stor_type_new  = $oid_stor_type . "." . $i;
  my $oid_stor_desc_new  = $oid_stor_desc . "." . $i;
  my $oid_stor_units_new = $oid_stor_units . "." . $i;
  my $oid_stor_size_new  = $oid_stor_size . "." . $i;
  my $oid_stor_used_new  = $oid_stor_used . "." . $i;
  my @oid_stor = ($oid_stor_index_new,$oid_stor_type_new,$oid_stor_desc_new,$oid_stor_units_new,$oid_stor_size_new,$oid_stor_used_new);
# do the snmp get_request
  $netsnmp = $session->get_request(@oid_stor);
# if the result is not a "noSuchInstance" put the response into a array of arrays
  if ( "$$netsnmp{$oid_stor_desc_new}" !~ m/noSuchInstance/ ) {
    $storlist[$entry] = [$$netsnmp{$oid_stor_desc_new},$stor_types{$$netsnmp{$oid_stor_type_new}},$$netsnmp{$oid_stor_units_new}, $$netsnmp{$oid_stor_size_new}, $$netsnmp{$oid_stor_used_new}];

  if (defined ($o_verb)){
    printf "=====================\n";
    printf "Entry number ........ " . $entry . "\n";
    printf "Mount Point ......... " . $storlist[$entry][0] . "\n";
    printf "Partition Type ...... " . $storlist[$entry][1] . "\n";
    printf "Partition Unit Size . " . $storlist[$entry][2] . "\n";
    printf "Partition Size ...... " . $storlist[$entry][3] . "\n";
    printf "Partition Used ...... " . $storlist[$entry][4] . "\n";
  }

  $entry += 1;
  }
# set the result var to break the while loop if we got a "noSuchInstance"
  $result = $$netsnmp{$oid_stor_index_new};
}
# set the number of partitions we got
$numberofentries = $i - 2;

#######################################
# check the hash table for real volumes

if (defined ($o_verb)){
  printf "\n\nSummary of monitored filesystems:\n\n";
}

$i = 0;
# check every partition we got
while ( "$i" < "$numberofentries" ) {
# ignore all volumes which do no match hrStorageFixedDisk
# ignore all volumes with the size of 0,
# ignore all volumes from type NFS, that are mounted from a nfs server
# ignore all volumes named /dev/shm = shared memory
# ignore all volumes /_admin = Novell OES nss specific
# ignore all nss pools starting with /opt/novell/nss/mnt/.pools = nss specific

  if ( ($storlist[$i][1] =~ m/^hrStorageFixedDisk/) && ($storlist[$i][3] != "0") && ($filesyslist{"$storlist[$i][0]"}[1] !~ m/^hrFSNFS/) && ($storlist[$i][0] !~ m/^\/dev\/shm/) && ($storlist[$i][0] !~ m/^\/_admin/) && ($storlist[$i][0] !~ m/^\/opt\/novell\/nss\/mnt\/.pools/) ) {

    if (defined ($o_verb)){
      printf "=======================\n";
      printf "Entry ................. " . $i . "\n";
      printf "Mountpoint ............ " . $storlist[$i][0] . "\n";
      printf "Partition Type ........ " . $storlist[$i][1] . "\n";
      printf "Partition Unit Size ... " . $storlist[$i][2] . "\n";
      printf "Partition Size ........ " . $storlist[$i][3] . "\n";
      printf "Partition Used ........ " . $storlist[$i][4] . "\n";
      printf "Filesys Type .......... " . $filesyslist{"$storlist[$i][0]"}[1] . "\n";
    }
# calculate the partition size
# the size has to be multiplied by the unit size
    $hr_part_size_unit = " kB";
    $hr_part_size = $storlist[$i][3] * $storlist[$i][2] / 1024;
    if ( "$hr_part_size" >= "1024" ){
       $hr_part_size =  round($hr_part_size/1024,0);
       $hr_part_size_unit = " MB";
       if ( "$hr_part_size" >= "1024" ){
          $hr_part_size =  round($hr_part_size/1024,0);
          $hr_part_size_unit = " GB";
       }
    }
# calculate the usage percent of the partition
    $usage = round($storlist[$i][4]/$storlist[$i][3]*100,0);
    print "FS: " . $storlist[$i][0] . " -> Size: " . $hr_part_size . $hr_part_size_unit . " -> " . $usage . "\% used, ";
# set error level for program exit
    if ( "$exitcode" != "2" ){
      if ( "$usage" >= "$o_warn" ){
        $exitcode=1;
      }
      if ( "$usage" >= "$o_crit" ){
        $exitcode=2;
      }
    }


  }
  $i += 1;
}
# close snmp session and exit
$session->close;
exit $exitcode;

