#! /usr/bin/perl -T
# anders@aftenposten.no
# Check if network mounts work: if they have any files, have any space used,
# and. Script will time out if there is a hang.
#
# 2008-12-15: Fix for Linux: use POSIX output from df.
# 2005-08-15: Initial version.
#
# This script can also be used to check if local filesystems are mounted and has content.

package CKNetMount;
use strict;
use POSIX;

# config:
$CKNetMount::filereqnonempty = 2;
$CKNetMount::kbreqnonempty = 1;
$CKNetMount::okchars = '^([0-9a-zA-ZæøåÆØÅ\-_/\.]+)$';
# end, config

$ENV{PATH} = "/sbin:/bin:/usr/sbin:/usr/bin";
$CKNetMount::os = (POSIX::uname)[0];
use Getopt::Std;
getopts('d:f:k:X');

use vars qw { $opt_d $opt_f $opt_k $opt_X };

if ($CKNetMount::os eq "Linux") {
	$CKNetMount::dfcmd = "df -k -P";
} else {
	$CKNetMount::dfcmd = "df -k";
}

if ($opt_f) {
	$CKNetMount::filereq = $opt_f;
	$CKNetMount::filetxt = "too few files";
} else {
	$CKNetMount::filereq = $CKNetMount::filereqnonempty;
	$CKNetMount::filetxt = "no files";
}
if ($opt_k) {
	$CKNetMount::kbreq = $opt_k;
	$CKNetMount::kbtxt = "too little space used";
} else {
	$CKNetMount::kbreq = $CKNetMount::kbreqnonempty;
	$CKNetMount::kbtxt = "no space used";
}

sub dfkbused {
	# $_[0]: filesys
	my $ckfs = $_[0];
	my $fs;
	my $blocks;
	my $used;
	my $avail;
	my $capacity;
	my $mount;

	open(DF, "$CKNetMount::dfcmd $ckfs 2>/dev/null |");
	while (<DF>) {
		chomp;
		($fs,$blocks,$used,$avail,$capacity,$mount) = split(/\s+/);
		next if ($fs eq "Filesystem");

		if ($opt_X) {
			print "ckfs=$ckfs fs=$fs mount=$mount used=$used\n";
		}

		if ($ckfs eq $fs || $ckfs eq $mount || ($ckfs =~ /^$mount\//)) {
			return $used;
		} elsif ($opt_X) {
			print "dfkbused failed df check.\n";
		}
	}
	return 0;
	close(DF);
}

sub finish {
	# $0: text
	print "$_[0]\n";
	exit(3);
}

sub usage {
	print "check_netmount is used to check availability of a network filesystem, for\n";
	print "example a NFS or Samba mount. Usage:\n\n";

	print "check_netmount -d \"<space separated filesystem/dir pairs>\"\n\n";

	print "A filesystem/dir pair can either be the path to the filesystem/mount, or the\n";
	print "same, a colon, and a directory name. The directory name is an optional\n";
	print "directory to scan for files instead of the filesystem itself (which can take a\n";
	print "long time if there are many files there).\n\n";

	print "An example of usage:\n\n";
	print "check_netmount -d \"/data/www /data/bigmount:/data/bigmount/smalldir\"\n\n";

	print "This Nagios plugin checks two things:\n\n";
	print "1. If there are any files in the filesystem/directory.\n";
	print "2. If there is any space used (contrary to check if there is any free space),\n";
	print "any functional network filesystem must have some data.\n\n";

	print "Check the top of the script for configuration settings.\n";
	print "For a little debug info, use -X.\n";
	exit(3);
}

sub dirok {
	# $_[0]: dirpair $_[1]: part $_[2]: dir
	my $ckdirpair = $_[0];
	my $ckpart = $_[1];
	my $ckdir = $_[2];

	if ($ckdir) {
		opendir(DIR, $ckdir);
	} else {
		opendir(DIR, $ckpart);
	}
	my @files = readdir(DIR);
	closedir(DIR);
	my $kb = dfkbused($ckpart);

	if ($#files < $CKNetMount::filereq && $kb < $CKNetMount::kbreq) {
		$CKNetMount::baddirs .= " $ckdirpair ($CKNetMount::filetxt, $CKNetMount::kbtxt)";
	} elsif ($#files < $CKNetMount::filereq) {
		$CKNetMount::baddirs .= " $ckdirpair ($CKNetMount::filetxt)";
	} elsif ($kb < $CKNetMount::kbreq) {
		$CKNetMount::baddirs .= " $ckdirpair ($CKNetMount::kbtxt)";
	} else {
		$CKNetMount::okdirs .= " $ckdirpair ($#files files, $kb KB used)";
		return(1);
	}
	return(0);
}

usage unless ($opt_d);
#finish("ERROR: Must specify directory/directories using -d") unless ($opt_d);

$CKNetMount::baddirs = "";
my $part;
my $dir;
my $dirpair;

foreach $dirpair (split / /, $opt_d) {
	if ($dirpair =~ /:/) {
		($part,$dir) = split(/:/);
		if ($dir =~ /$CKNetMount::okchars/) {
			$dir = $1;
		} else {
			finish("ERROR: $dir is tainted with non-acceptable characters");
		}
	} else {
		$part = $dirpair;
		$dir = undef;
	}
	if ($part =~ /$CKNetMount::okchars/) {
		$part = $1;
	} else {
		finish("ERROR: $part is tainted with non-acceptable characters");
	}

	finish("ERROR: $part is not a filesystem") unless (-d $part);
	finish("ERROR: $dir is not a directory") unless ((!$dir) || -d $dir);

	dirok($dirpair,$part,$dir);
}

if ($CKNetMount::baddirs) {
	$CKNetMount::baddirs =~ s@^\s+@@;
	print "ERROR: $CKNetMount::baddirs\n";
	exit(2);
} elsif ($CKNetMount::okdirs) {
	$CKNetMount::okdirs =~ s@^\s+@@;
	print "OK: $CKNetMount::okdirs\n";
	exit(0);
} else {
	print "UNKNOWN: no results?\n";
	exit(3);
}

