#!perl # # This plugin takes a mountpoint and checks that a filesystem is mounted # thereupon, once and once only. # # Author: Tom Ryder # 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 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;