#!perl
#
# This plugin takes a mountpoint and checks that a filesystem is mounted
# thereupon, once and once only.
#
# Author: Tom Ryder <tom@sanctum.geek.nz>
# License: MIT
#
package main;
# Force me to write this properly
use strict;
use warnings;
use utf8;
# Require at least this Perl version
use 5.010_001;
# Import required modules
use English qw(-no_match_vars);
use Exception::Class ( PluginException => { alias => 'throw' } );
use Monitoring::Plugin qw(%ERRORS);
use Path::Tiny;
use Quota;
use Try::Tiny;
# Decree package version
our $VERSION = '1.01';
# Add description and license package variables
our $DESCRIPTION = <<'EOF';
This plugin takes a mountpoint and checks that a filesystem is mounted
thereupon, once and once only.
EOF
our $LICENSE = <<'EOF';
MIT License <https://opensource.org/licenses/MIT>
EOF
# Custom plugin options
our @OPTS = (
{
spec => 'mountpoint|m=s',
help => 'Path to mountpoint',
label => 'PATH',
required => 1,
},
);
# Build Monitoring::Plugin object
my $mp = Monitoring::Plugin->new(
usage => 'Usage: %s --mountpoint|-m PATH',
version => $VERSION,
blurb => $DESCRIPTION,
license => $LICENSE,
);
# Anything that dies in here raises ->plugin_die()
try {
# Add and read custom options
for my $opt (@OPTS) {
$mp->add_arg( %{$opt} );
}
$mp->getopts();
# Start counting down to timeout
alarm $mp->opts->timeout();
# Get a cleaned-up and absolute path for the mountpoint
length $mp->opts->mountpoint
or throw 'Empty mountpoint path';
my $point = path( $mp->opts->mountpoint )->realpath();
$point->exists()
or throw "$point does not exist";
$point->is_dir()
or throw "$point is not a directory";
# Read the mount table row by row and collect any entries that correspond
# to our mountpoint
my @mounts = grep { $_->{path} eq $point } mounts();
# Make a string for the message that describes the device and type for each
# of the matching mounts, comma-separated
my $devs = join q(, ), map { "$_->{dev} ($_->{type})" } @mounts;
# One mount is ideal; exit OK, describing the mounted devices and the
# filesytem types
if ( @mounts == 1 ) {
$mp->add_message( $ERRORS{OK}, "Mounted on $point: $devs\n" );
}
# More than one mount is not ideal; exit WARNING, describing the mounted
# devices and the filesystem types
elsif ( @mounts > 1 ) {
$mp->add_message( $ERRORS{WARNING}, "Multi mounts on $point: $devs\n" );
}
# No matching mount at all is bad
else {
$mp->add_message( $ERRORS{CRITICAL}, "No mounts found on $point\n" );
}
# Exit with the selected code and message
$mp->plugin_exit( $mp->check_messages );
}
catch {
$mp->plugin_die($_);
};
# Return a listref of hashrefs describing every mount table entry
sub mounts {
# Open mount table for reading, throw on error (return great than zero)
Quota::setmntent() == 0
or throw Quota::strerr();
# Iterate through each mount table entry to collect a list. The
# getmntent() function here returns an empty list when it's read the whole
# table, and undef on error in place of what would normally be the device,
# so this may seem a bit awkward on first reading.
#
my @mounts;
while ( my @mnt = Quota::getmntent() ) {
# Check the first element; if it's undefined, there was an error, and
# we should bail out
defined $mnt[0]
or throw Quota::strerr();
# Otherwise, this really is a mountpoint entry; extract the device,
# mountpoint, and filesystem type
my ( $dev, $path, $type ) = @mnt;
length $path or next;
# Build a hash structure including canonicalising and resolving the
# path, and add it as a reference to the mounts collection
my %mount = (
dev => $dev || 'unknown',
path => path($path)->realpath(),
type => $type || 'unknown',
);
push @mounts, \%mount;
}
# Close mount table
Quota::endmntent();
# Return collected mounts
return @mounts;
}
1;