git.lirion.de

Of git, get, and gud

summaryrefslogtreecommitdiffstats
path: root/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid
diff options
context:
space:
mode:
authorHarald Pfeiffer <coding _ lirion.de> 2019-04-17 19:07:19 +0200
committerHarald Pfeiffer <coding _ lirion.de> 2019-04-17 19:07:19 +0200
commit1e2387474a449452b78520b9ad96a8b4b5e99722 (patch)
tree836889471eec7d2aac177405068e2a8f1e2b1978 /nagios-plugins-contrib-24.20190301~bpo9+1/check_raid
downloadnagios-plugins-contrib-1e2387474a449452b78520b9ad96a8b4b5e99722.tar.bz2
initial commit of source fetch
Diffstat (limited to 'nagios-plugins-contrib-24.20190301~bpo9+1/check_raid')
-rw-r--r--nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile4
-rw-r--r--nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid6664
-rw-r--r--nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control30
-rw-r--r--nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright9
4 files changed, 6707 insertions, 0 deletions
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile
new file mode 100644
index 0000000..52de70c
--- /dev/null
+++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile
@@ -0,0 +1,4 @@
+#/usr/bin/make -f
+
+include ../common.mk
+
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid
new file mode 100644
index 0000000..2d115b1
--- /dev/null
+++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid
@@ -0,0 +1,6664 @@
+#!/usr/bin/perl
+
+# This chunk of stuff was generated by App::FatPacker. To find the original
+# file's code, look for the end of this BEGIN block or the string 'FATPACK'
+BEGIN {
+my %fatpacked;
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID';
+ package App::Monitoring::Plugin::CheckRaid;
+
+ use Carp qw(croak);
+ use Module::Pluggable 5.1 instantiate => 'new', sub_name => '_plugins';
+ use strict;
+ use warnings;
+
+ # constructor
+ sub new {
+ my $class = shift;
+
+ croak 'Odd number of elements in argument hash' if @_ % 2;
+
+ my $self = {
+ @_,
+ };
+
+ my $obj = bless $self, $class;
+
+ # setup search path for Module::Pluggable
+ $self->search_path(add => __PACKAGE__ . '::Plugins');
+
+ # setup only certain plugins
+ if ($self->{enable_plugins}) {
+ my @plugins = map {
+ __PACKAGE__ . '::Plugins::' . $_
+ } @{$self->{enable_plugins}};
+ $self->only(\@plugins);
+ }
+
+ return $obj;
+ }
+
+ # create list of plugins
+ sub plugins {
+ my ($this) = @_;
+
+ # call this once
+ if (!defined $this->{plugins}) {
+ my @plugins = $this->_plugins(%$this);
+ $this->{plugins} = \@plugins;
+ }
+
+ wantarray ? @{$this->{plugins}} : $this->{plugins};
+ }
+
+ # get plugin by name
+ sub plugin {
+ my ($this, $name) = @_;
+
+ if (!defined $this->{plugin_names}) {
+ my %names;
+ foreach my $plugin ($this->plugins) {
+ my $name = $plugin->{name};
+ $names{$name} = $plugin;
+ }
+ $this->{plugin_names} = \%names;
+ }
+
+ croak "Plugin '$name' Can not be created" unless exists $this->{plugin_names}{$name};
+
+ $this->{plugin_names}{$name};
+ }
+
+ # Get active plugins.
+ # Returns the plugin objects
+ sub active_plugins {
+ my $this = shift;
+ # whether the query is for sudo rules
+ my $sudo = shift || 0;
+
+ my @plugins = ();
+
+ # go over all registered plugins
+ foreach my $plugin ($this->plugins) {
+ # skip if no check method (not standalone checker)
+ next unless $plugin->can('check');
+
+ # skip inactive plugins (disabled or no tools available)
+ next unless $plugin->active($sudo);
+
+ push(@plugins, $plugin);
+ }
+
+ return wantarray ? @plugins : \@plugins;
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN';
+ package App::Monitoring::Plugin::CheckRaid::Plugin;
+
+ use Carp qw(croak);
+ use App::Monitoring::Plugin::CheckRaid::Utils;
+ use strict;
+ use warnings;
+
+ # Nagios standard error codes
+ my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3);
+
+ # default plugin options
+ our %options = (
+ # status to set when RAID is in resync state
+ resync_status => $ERRORS{WARNING},
+
+ # Status code to use when no raid volumes were detected
+ noraid_status => $ERRORS{UNKNOWN},
+
+ # status to set when RAID is in check state
+ check_status => $ERRORS{OK},
+
+ # status to set when PD is spare
+ spare_status => $ERRORS{OK},
+
+ # status to set when BBU is in learning cycle.
+ bbulearn_status => $ERRORS{WARNING},
+
+ # status to set when Write Cache has failed.
+ cache_fail_status => $ERRORS{WARNING},
+
+ # check status of BBU
+ bbu_monitoring => 0,
+ );
+
+ # return list of programs this plugin needs
+ # @internal
+ sub program_names {
+ }
+
+ # return hash of canonical commands that plugin can use
+ # @internal
+ sub commands {
+ {}
+ }
+
+ # return sudo rules if program needs it
+ # may be SCALAR or LIST of scalars
+ # @internal
+ sub sudo {
+ ();
+ }
+
+ # constructor for plugins
+ sub new {
+ my $class = shift;
+
+ croak 'Odd number of elements in argument hash' if @_ % 2;
+ croak 'Class is already a reference' if ref $class;
+
+ # convert to hash
+ my %args = @_;
+
+ # merge 'options' from param and class defaults
+ my %opts = %options;
+ %opts = (%options, %{$args{options}}) if $args{options};
+ delete $args{options};
+
+ # merge commands
+ my %commands = %{$class->commands};
+ %commands = (%commands, %{$args{commands}}) if $args{commands};
+ delete $args{commands};
+
+ my $self = {
+ commands => \%commands,
+ sudo => $class->sudo ? find_sudo() : '',
+ options => \%opts,
+ %args,
+
+ # name of the plugin, without package namespace
+ name => ($class =~ /.*::([^:]+)$/),
+
+ status => undef,
+ message => undef,
+ perfdata => undef,
+ longoutput => undef,
+ };
+
+ my $this = bless $self, $class;
+
+ # lookup program, if not defined by params
+ if (!$self->{program}) {
+ $self->{program} = which($this->program_names);
+ }
+
+ return $this;
+ }
+
+ # see if plugin is active (disabled or no tools available)
+ sub active {
+ my $this = shift;
+
+ # no tool found, return false
+ return 0 unless $this->{program};
+
+ # program file must exist, don't check for execute bit. #104
+ -f $this->{program};
+ }
+
+ # set status code for plugin result
+ # does not overwrite status with lower value
+ # returns the current status code
+ sub status {
+ my ($this, $status) = @_;
+
+ if (defined $status) {
+ $this->{status} = $status unless defined($this->{status}) and $status < $this->{status};
+ }
+ $this->{status};
+ }
+
+ sub set_critical_as_warning {
+ $ERRORS{CRITICAL} = $ERRORS{WARNING};
+ }
+
+ # helper to set status to WARNING
+ # returns $this to allow fluent api
+ sub warning {
+ my ($this) = @_;
+ $this->status($ERRORS{WARNING});
+ return $this;
+ }
+
+ # helper to set status to CRITICAL
+ # returns $this to allow fluent api
+ sub critical {
+ my ($this) = @_;
+ $this->status($ERRORS{CRITICAL});
+ return $this;
+ }
+
+ # helper to set status to UNKNOWN
+ # returns $this to allow fluent api
+ sub unknown {
+ my ($this) = @_;
+ $this->status($ERRORS{UNKNOWN});
+ return $this;
+ }
+
+ # helper to set status to OK
+ sub ok {
+ my ($this) = @_;
+ $this->status($ERRORS{OK});
+ return $this;
+ }
+
+ # helper to set status for resync
+ # returns $this to allow fluent api
+ sub resync {
+ my ($this) = @_;
+ $this->status($this->{options}{resync_status});
+ return $this;
+ }
+
+ # helper to set status for check
+ # returns $this to allow fluent api
+ sub check_status {
+ my ($this) = @_;
+ $this->status($this->{options}{check_status});
+ return $this;
+ }
+
+ # helper to set status for no raid condition
+ # returns $this to allow fluent api
+ sub noraid {
+ my ($this) = @_;
+ $this->status($this->{options}{noraid_status});
+ return $this;
+ }
+
+ # helper to set status for spare
+ # returns $this to allow fluent api
+ sub spare {
+ my ($this) = @_;
+ $this->status($this->{options}{spare_status});
+ return $this;
+ }
+
+ # helper to set status for BBU learning cycle
+ # returns $this to allow fluent api
+ sub bbulearn {
+ my ($this) = @_;
+ $this->status($this->{options}{bbulearn_status});
+ return $this;
+ }
+
+ # helper to set status when Write Cache fails
+ # returns $this to allow fluent api
+ sub cache_fail {
+ my ($this) = @_;
+ $this->status($this->{options}{cache_fail_status});
+ return $this;
+ }
+
+ # helper to get/set bbu monitoring
+ sub bbu_monitoring {
+ my ($this, $val) = @_;
+
+ if (defined $val) {
+ $this->{options}{bbu_monitoring} = $val;
+ }
+ $this->{options}{bbu_monitoring};
+ }
+
+ # setup status message text
+ sub message {
+ my ($this, $message) = @_;
+ if (defined $message) {
+ # TODO: append if already something there
+ $this->{message} = $message;
+ }
+ $this->{message};
+ }
+
+ # Set performance data output.
+ sub perfdata {
+ my ($this, $perfdata) = @_;
+ if (defined $perfdata) {
+ # TODO: append if already something there
+ $this->{perfdata} = $perfdata;
+ }
+ $this->{perfdata};
+ }
+
+ # Set plugin long output.
+ sub longoutput {
+ my ($this, $longoutput) = @_;
+ if (defined $longoutput) {
+ # TODO: append if already something there
+ $this->{longoutput} = $longoutput;
+ }
+ $this->{longoutput};
+ }
+
+ # a helper to join similar statuses for items
+ # instead of printing
+ # 0: OK, 1: OK, 2: OK, 3: NOK, 4: OK
+ # it would print
+ # 0-2,4: OK, 3: NOK
+ # takes as input list:
+ # { status => @items }
+ sub join_status {
+ my $this = shift;
+ my %status = %{$_[0]};
+
+ my @status;
+ for my $status (sort {$a cmp $b} keys %status) {
+ my $disks = $status{$status};
+ my @s;
+ foreach my $disk (@$disks) {
+ push(@s, $disk);
+ }
+ push(@status, join(',', @s).'='.$status);
+ }
+
+ return join ' ', @status;
+ }
+
+ # return true if parameter is not in ignore list
+ sub valid {
+ my $this = shift;
+ my ($v) = lc $_[0];
+
+ foreach (@utils::ignore) {
+ return 0 if lc $_ eq $v;
+ }
+ return 1;
+ }
+
+ use constant K => 1024;
+ use constant M => K * 1024;
+ use constant G => M * 1024;
+ use constant T => G * 1024;
+
+ sub parse_bytes {
+ my ($this, $size) = @_;
+
+ if ($size =~ s/\sT//) {
+ return int($size) * T;
+ }
+ if ($size =~ s/\sG//) {
+ return int($size) * G;
+ }
+ if ($size =~ s/\sM//) {
+ return int($size) * M;
+ }
+ if ($size =~ s/\sK//) {
+ return int($size) * K;
+ }
+
+ return int($size);
+ }
+
+ sub format_bytes {
+ my $this = shift;
+
+ my ($bytes) = @_;
+ if ($bytes > T) {
+ return sprintf("%.2f TiB", $bytes / T);
+ }
+ if ($bytes > G) {
+ return sprintf("%.2f GiB", $bytes / G);
+ }
+ if ($bytes > M) {
+ return sprintf("%.2f MiB", $bytes / M);
+ }
+ if ($bytes > K) {
+ return sprintf("%.2f KiB", $bytes / K);
+ }
+ return "$bytes B";
+ }
+
+ # disable sudo temporarily
+ sub nosudo_cmd {
+ my ($this, $command, $cb) = @_;
+
+ my ($res, @res);
+
+ my $sudo = $this->{sudo};
+ $this->{sudo} = 0;
+
+ if (wantarray) {
+ @res = $this->cmd($command, $cb);
+ } else {
+ $res = $this->cmd($command, $cb);
+ }
+
+ $this->{sudo} = $sudo;
+
+ return wantarray ? @res : $res;
+ }
+
+ # build up command for $command
+ # returns open filehandle to process output
+ # if command fails, program is exited (caller needs not to worry)
+ sub cmd {
+ my ($this, $command, $cb) = @_;
+
+ my $debug = $App::Monitoring::Plugin::CheckRaid::Utils::debug;
+
+ # build up command
+ my @CMD = $this->{program};
+
+ # add sudo if program needs
+ unshift(@CMD, @{$this->{sudo}}) if $> and $this->{sudo};
+
+ my $args = $this->{commands}{$command} or croak "command '$command' not defined";
+
+ # callback to replace args in command
+ my $cb_ = sub {
+ my $param = shift;
+ if ($cb) {
+ if (ref $cb eq 'HASH' and exists $cb->{$param}) {
+ return wantarray ? @{$cb->{$param}} : $cb->{$param};
+ }
+ return &$cb($param) if ref $cb eq 'CODE';
+ }
+
+ if ($param eq '@CMD') {
+ # command wanted, but not found
+ croak "Command for $this->{name} not found" unless defined $this->{program};
+ return @CMD;
+ }
+ return $param;
+ };
+
+ # add command arguments
+ my @cmd;
+ for my $arg (@$args) {
+ local $_ = $arg;
+ # can't do arrays with s///
+ # this limits that @arg must be single argument
+ if (/@/) {
+ push(@cmd, $cb_->($_));
+ } else {
+ s/([\$]\w+)/$cb_->($1)/ge;
+ push(@cmd, $_);
+ }
+ }
+
+ my $op = shift @cmd;
+ my $fh;
+ if ($op eq '=' and ref $cb eq 'SCALAR') {
+ # Special: use open2
+ use IPC::Open2;
+ warn "DEBUG EXEC: $op @cmd" if $debug;
+ my $pid = open2($fh, $$cb, @cmd) or croak "open2 failed: @cmd: $!";
+ } elsif ($op eq '>&2') {
+ # Special: same as '|-' but reads both STDERR and STDOUT
+ use IPC::Open3;
+ warn "DEBUG EXEC: $op @cmd" if $debug;
+ my $pid = open3(undef, $fh, $cb, @cmd);
+
+ } else {
+ warn "DEBUG EXEC: @cmd" if $debug;
+ open($fh, $op, @cmd) or croak "open failed: @cmd: $!";
+ }
+
+ # for dir handles, reopen as opendir
+ if (-d $fh) {
+ undef($fh);
+ warn "DEBUG OPENDIR: $cmd[0]" if $debug;
+ opendir($fh, $cmd[0]) or croak "opendir failed: @cmd: $!";
+ }
+
+ return $fh;
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/aaccli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::aaccli;
+
+ # Adaptec ServeRAID
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'container list' => ['=', '@CMD'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd container list /full"
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $write = "";
+ $write .= "open aac0\n";
+ $write .= "container list /full\n";
+ $write .= "exit\n";
+ my $read = $this->cmd('container list', \$write);
+
+ #File foo receiving all output.
+ #
+ #AAC0>
+ #COMMAND: container list /full=TRUE
+ #Executing: container list /full=TRUE
+ #Num Total Oth Stripe Scsi Partition Creation
+ #Label Type Size Ctr Size Usage C:ID:L Offset:Size State RO Lk Task Done% Ent Date Time
+ #----- ------ ------ --- ------ ------- ------ ------------- ------- -- -- ------- ------ --- ------ --------
+ # 0 Mirror 74.5GB Open 0:02:0 64.0KB:74.5GB Normal 0 051006 13:48:54
+ # /dev/sda Auth 0:03:0 64.0KB:74.5GB Normal 1 051006 13:48:54
+ #
+ #
+ #AAC0>
+ #COMMAND: logfile end
+ #Executing: logfile end
+ while (<$read>) {
+ if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\S+\s+(\S+)/) {
+ next unless $this->valid($dsk);
+ $dsk =~ s#:#/#g;
+ next unless $this->valid($dsk);
+
+ push(@status, "$dsk:$stat");
+
+ $this->critical if ($stat eq "Broken");
+ $this->warning if ($stat eq "Rebuild");
+ $this->warning if ($stat eq "Bld/Vfy");
+ $this->critical if ($stat eq "Missing");
+ if ($stat eq "Verify") {
+ $this->resync;
+ }
+ $this->warning if ($stat eq "VfyRepl");
+ }
+ }
+ close $read;
+
+ return unless @status;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/afacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::afacli;
+
+ # Adaptec AACRAID
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'container list' => ['=', '@CMD'],
+ }
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $write = "";
+ $write .= "open afa0\n";
+ $write .= "container list /full\n";
+ $write .= "exit\n";
+
+ my $read = $this->cmd('container list', \$write);
+ while (<$read>) {
+ # 0 Mirror 465GB Valid 0:00:0 64.0KB: 465GB Normal 0 032511 17:55:06
+ # /dev/sda root 0:01:0 64.0KB: 465GB Normal 1 032511 17:55:06
+ if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\s?\S+\s+(\S+)/) {
+ next unless $this->valid($dsk);
+ $dsk =~ s#:#/#g;
+ next unless $this->valid($dsk);
+ push(@status, "$dsk:$stat");
+
+ $this->critical if ($stat eq "Broken");
+ $this->warning if ($stat eq "Rebuild");
+ $this->warning if ($stat eq "Bld/Vfy");
+ $this->critical if ($stat eq "Missing");
+ if ($stat eq "Verify") {
+ $this->resync;
+ }
+ $this->warning if ($stat eq "VfyRepl");
+ }
+ }
+ close $read;
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/arcconf.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::arcconf;
+
+ # Adaptec AAC-RAID
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'getstatus' => ['-|', '@CMD', 'GETSTATUS', '1'],
+ # 'nologs' does not exist in arcconf 6.50. #118
+ 'getconfig' => ['-|', '@CMD', 'GETCONFIG', '$ctrl', 'AL'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd GETSTATUS 1",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd GETCONFIG * AL",
+ );
+ }
+
+ sub parse_error {
+ my ($this, $message) = @_;
+ warn "arcconf: parse error: $message";
+ $this->unknown->message("Parse Error: $message");
+ }
+
+ # parse GETSTATUS command
+ # parses
+ # - number of controllers
+ # - logical device tasks (if any running)
+ sub parse_status {
+ my ($this) = @_;
+
+ my $count = 0;
+ my $ok = 0;
+ my $fh = $this->cmd('getstatus');
+ my %s;
+ # controller task
+ my %task;
+ while (<$fh>) {
+ chomp;
+ # empty line or comment
+ next if /^$/ or /^#/;
+
+ # termination
+ if (/^Command completed successfully/) {
+ $ok = 1;
+ last;
+ }
+
+ if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
+ $count = int($c);
+ next;
+ }
+
+ if (/^(\S.+) Task:$/) {
+ $task{type} = $1;
+ next;
+ }
+
+ if (/^\s+Logical device\s+: (\d+)/) {
+ $task{device} = $1;
+ } elsif (/^\s+Task ID\s+: (\d+)/) {
+ $task{id} = $1;
+ } elsif (/^\s+Current operation\s+: (.+)/) {
+ $task{operation} = $1;
+ } elsif (/^\s+Status\s+: (.+)/) {
+ $task{status} = $1;
+ } elsif (/^\s+Priority\s+: (.+)/) {
+ $task{priority} = $1;
+ } elsif (/^\s+Percentage complete\s+: (\d+)/) {
+ $task{percent} = $1;
+ } elsif (/^Invalid controller number/) {
+ ;
+ } else {
+ warn "Unknown line: [$_]";
+ # FIXME: ->message() gets overwritten later on
+ $this->unknown->message("Unknown line: [$_]");
+ }
+ }
+ close($fh);
+
+ # Tasks seem to be Controller specific, but as we don't support over one controller, let it be global
+ $s{tasks} = { %task } if %task;
+
+ if ($count == 0) {
+ # if command completed, but no controllers,
+ # assume no hardware present
+ if (!$ok) {
+ $this->unknown->message("No controllers found!");
+ }
+ return undef;
+ }
+
+ $s{ctrl_count} = $count;
+
+ return \%s;
+ }
+
+ # parse GETCONFIG for all controllers
+ sub parse_config {
+ my ($this, $status) = @_;
+
+ my %c;
+ for (my $i = 1; $i <= $status->{ctrl_count}; $i++) {
+ $c{$i} = $this->parse_ctrl_config($i, $status->{ctrl_count});
+ }
+
+ return { controllers => \%c };
+ }
+
+ # parse GETCONFIG command for specific controller
+ sub parse_ctrl_config {
+ my ($this, $ctrl, $ctrl_count) = @_;
+
+ # Controller information, Logical/Physical device info
+ my ($ld, $ch, $pd);
+
+ my $res = { controller => {}, logical => [], physical => [] };
+
+ my $fh = $this->cmd('getconfig', { '$ctrl' => $ctrl });
+ my ($section, $subsection, $ok);
+ my %sectiondata = ();
+
+ # called when data for section needs to be processed
+ my $flush = sub {
+ my $method = 'process_' . lc($section);
+ $method =~ s/[.\s]+/_/g;
+ $this->$method($res, \%sectiondata);
+ %sectiondata = ();
+ };
+ my $subsection_reset = sub {
+ $ch = 0;
+ undef($ld);
+ undef($pd);
+ undef($subsection);
+ };
+ while (<$fh>) {
+ chomp;
+
+ # empty line or comment
+ if (/^$/ or /^#/) {
+ &$subsection_reset;
+ next;
+ }
+
+ if (/^Command completed successfully/) {
+ $ok = 1;
+ last;
+ }
+
+ if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
+ if ($c != $ctrl_count) {
+ # internal error?!
+ $this->unknown->message("Controller count mismatch");
+ }
+ next;
+ }
+
+ # section start
+ if (/^---+/) {
+ if (my($s) = <$fh> =~ /^(\w.+)$/) {
+ # flush the lines
+ if (defined($section)) {
+ &$flush();
+ }
+
+ $section = $s;
+ unless (<$fh> =~ /^---+/) {
+ $this->parse_error($_);
+ }
+ &$subsection_reset;
+ next;
+ }
+ $this->parse_error($_);
+ }
+
+ # sub section start
+ # there are also sections in subsections, but currently section names
+ # are unique enough
+ if (/^\s+---+/) {
+ if (my($s) = <$fh> =~ /^\s+(\S.+?)\s*?$/) {
+ $subsection = $s;
+ unless (<$fh> =~ /^\s+---+/) {
+ $this->parse_error($_);
+ }
+ next;
+ }
+ $this->parse_error($_);
+ }
+
+ warn("SKIP without section: [$_]\n"),next unless defined $section;
+
+ # regex notes:
+ # - value portion may be missing
+ # - value may be empty
+ # - value may be truncated (t/data/arcconf/issue47/getconfig)
+ my ($key, $value) = /^\s*(.+?)(?:\s+:\s*(.*?))?$/;
+
+ if ($section =~ /Controller [Ii]nformation/) {
+ if (not defined $subsection) {
+ $sectiondata{$key} = $value;
+ } else {
+ $sectiondata{$subsection}{$key} = $value;
+ }
+
+ } elsif ($section =~ /Physical Device [Ii]nformation/) {
+ if (my($c) = /Channel #(\d+)/) {
+ $ch = int($c);
+ undef($pd);
+ next;
+
+ } elsif (my($n) = /^\s+Device #(\d+)/) {
+ $pd = int($n);
+ next;
+
+ } else {
+ if (not defined $pd) {
+ $sectiondata{$ch}{$key} = $value;
+ } elsif (not defined $subsection) {
+ $sectiondata{$ch}{'pd'}{$pd}{$key} = $value;
+ } else {
+ $sectiondata{$ch}{'pd'}{$pd}{$subsection}{$key} = $value;
+ }
+ }
+
+ } elsif ($section =~ /Logical ([Dd]evice|drive) [Ii]nformation/) {
+ if (my($n) = /Logical (?:[Dd]evice|drive) [Nn]umber (\d+)/) {
+ $ld = int($n);
+ } else {
+ # skip lone line: issue87/getconfig
+ if (/No logical devices configured/) {
+ next;
+ }
+ if (not defined $ld) {
+ warn "LD undefined:[$_]\n";
+ next;
+ }
+ if (not defined $subsection) {
+ $sectiondata{$ld}{$key} = $value;
+ } else {
+ $sectiondata{$ld}{$subsection}{$key} = $value;
+ }
+ }
+
+ } elsif ($section eq 'MaxCache 3.0 information') {
+ # not parsed yet
+ } elsif ($section eq 'Connector information') {
+ # not parsed yet
+ } else {
+ warn "NOT PARSED: [$section] [$_]";
+ }
+ }
+ close $fh;
+ &$flush() if $section;
+
+ $this->unknown->message("Command did not succeed") unless defined $ok;
+
+ return $res;
+ }
+
+ # Process Controller Information section
+ sub process_controller_information {
+ my ($this, $res, $data) = @_;
+ my $c = {};
+ my $s;
+
+ # current section
+ my $cs = $data;
+
+ $c->{status} = $cs->{'Controller Status'};
+
+ if (exists $cs->{$s = 'Defunct Disk Drive Count'} || exists $cs->{$s = 'Defunct disk drive count'}) {
+ $c->{defunct_count} = int($cs->{$s});
+ }
+
+ if ($s = $cs->{'Logical devices/Failed/Degraded'}) {
+ my($td, $fd, $dd) = $s =~ m{(\d+)/(\d+)/(\d+)};
+ $c->{logical_count} = int($td);
+ $c->{logical_failed} = int($fd);
+ $c->{logical_degraded} = int($dd);
+ }
+ # ARCCONF 9.30: Logical drives/Offline/Critical
+ if ($s = $cs->{'Logical drives/Offline/Critical'}) {
+ my($td2, $fd2, $dd2) = $s =~ m{(\d+)/(\d+)/(\d+)};
+ $c->{logical_count} = int($td2);
+ $c->{logical_offline} = int($fd2);
+ $c->{logical_critical} = int($dd2);
+ }
+
+ $cs = $data->{'Controller Battery Information'};
+ $c->{battery_status} = $cs->{Status} if exists $cs->{Status};
+ $c->{battery_overtemp} = $cs->{'Over temperature'} if exists $cs->{'Over temperature'};
+
+ if ($s = $cs->{'Capacity remaining'}) {
+ my ($bc) = $s =~ m{(\d+)\s*percent.*$};
+ $c->{battery_capacity} = int($bc);
+ }
+
+ if ($s = $cs->{'Time remaining (at current draw)'}) {
+ my($d, $h, $m) = $s =~ /(\d+) days, (\d+) hours, (\d+) minutes/;
+ $c->{battery_time} = int($d) * 1440 + int($h) * 60 + int($m);
+ $c->{battery_time_full} = "${d}d${h}h${m}m";
+ }
+
+
+ $cs = $data->{'Controller ZMM Information'};
+ $c->{zmm_status} = $cs->{Status} if exists $cs->{'Status'};
+
+ $res->{controller} = $c;
+ }
+
+ sub process_logical_device_information {
+ my ($this, $res, $data) = @_;
+ my $s;
+
+ my @ld;
+ while (my($ld, $cs) = each %$data) {
+
+ $ld[$ld]{id} = $ld;
+ if (exists $cs->{$s = 'RAID Level'} || exists $cs->{$s = 'RAID level'}) {
+ $ld[$ld]{raid} = $cs->{$s};
+ }
+ $ld[$ld]{size} = $cs->{'Size'};
+ $ld[$ld]{failed_stripes} = $cs->{'Failed stripes'} if exists $cs->{'Failed stripes'};
+ $ld[$ld]{defunct_segments} = $cs->{'Defunct segments'} if exists $cs->{'Defunct segments'};
+
+ if ($s = $cs->{'Status of Logical Device'} || $cs->{'Status of logical device'} || $cs->{'Status of logical drive'}) {
+ $ld[$ld]{status} = $s;
+ }
+ if ($s = $cs->{'Logical Device name'} || $cs->{'Logical device name'} || $cs->{'Logical drive name'}) {
+ $ld[$ld]{name} = $s;
+ }
+
+ # Write-cache mode : Not supported]
+ # Partitioned : Yes]
+ # Number of segments : 2]
+ # Drive(s) (Channel,Device) : 0,0 0,1]
+ # Defunct segments : No]
+ }
+
+ $res->{logical} = \@ld;
+ }
+
+ sub process_physical_device_information {
+ my ($this, $res, $data) = @_;
+
+ # Keys with no values:
+ # "Device #0"
+ # "Device is a Hard drive"
+ #
+ # ignored:
+ # /Transfer Speed\s+:\s+(.+)/
+ # /Initiator at SCSI ID/
+ # /No physical drives attached/
+
+ my (@pd, $cs, $s);
+ while (my($ch, $channel_data) = each %$data) {
+ while (my($pd, $cs) = each %{$channel_data->{pd}}) {
+ $pd[$ch][$pd]{device_id} = $pd;
+ $pd[$ch][$pd]{power_state} = $cs->{'Power State'} if exists $cs->{'Power State'};
+ $pd[$ch][$pd]{status} = $cs->{'State'} if exists $cs->{'State'};
+ $pd[$ch][$pd]{supported} = $cs->{'Supported'} if exists $cs->{'Supported'};
+ $pd[$ch][$pd]{spare} = $cs->{'Dedicated Spare for'} if exists $cs->{'Dedicated Spare for'};
+ $pd[$ch][$pd]{model} = $cs->{'Model'};
+ $pd[$ch][$pd]{serial} = $cs->{'Serial number'} if exists $cs->{'Serial number'};
+ $pd[$ch][$pd]{wwn} = $cs->{'World-wide name'} if exists $cs->{'World-wide name'};
+ $pd[$ch][$pd]{write_cache} = $cs->{'Write Cache'} if exists $cs->{'Write Cache'};
+ $pd[$ch][$pd]{ssd} = $cs->{'SSD'} if exists $cs->{'SSD'};
+ $pd[$ch][$pd]{fru} = $cs->{'FRU'} if exists $cs->{'FRU'};
+ $pd[$ch][$pd]{ncq} = $cs->{'NCQ status'} if exists $cs->{'NCQ status'};
+ $pd[$ch][$pd]{pfa} = $cs->{'PFA'} if exists $cs->{'PFA'};
+ $pd[$ch][$pd]{enclosure} = $cs->{'Enclosure ID'} if exists $cs->{'Enclosure ID'};
+ $pd[$ch][$pd]{type} = $cs->{'Type'} if exists $cs->{'Type'};
+ $pd[$ch][$pd]{smart} = $cs->{'S.M.A.R.T.'} if exists $cs->{'S.M.A.R.T.'};
+ $pd[$ch][$pd]{smart_warn} = $cs->{'S.M.A.R.T. warnings'} if exists $cs->{'S.M.A.R.T. warnings'};
+ $pd[$ch][$pd]{speed} = $cs->{'Transfer Speed'} if $cs->{'Transfer Speed'};
+ $pd[$ch][$pd]{power_states} = $cs->{'Supported Power States'} if exists $cs->{'Supported Power States'};
+ $pd[$ch][$pd]{fail_ldev_segs} = $cs->{'Failed logical device segments'} if exists $cs->{'Failed logical device segments'};
+
+ # allow edits, i.e removed 'Vendor'/'Firmware' value from test data
+ $pd[$ch][$pd]{vendor} = $cs->{'Vendor'} || '';
+ $pd[$ch][$pd]{firmware} = $cs->{'Firmware'} if exists $cs->{'Firmware'};
+
+ # previous parser was not exact line match
+ if ($s = $cs->{'Size'} || $cs->{'Total Size'}) {
+ $pd[$ch][$pd]{size} = $s;
+ }
+
+ $s = $cs->{'Reported ESD'} || $cs->{'Reported ESD(T:L)'};
+ $pd[$ch][$pd]{esd} = $s if $s;
+
+ if ($s = $cs->{'Reported Location'}) {
+ my($e, $s) = $s =~ /(?:Enclosure|Connector) (\d+), (?:Slot|Device) (\d+)/;
+ $pd[$ch][$pd]{location} = "$e:$s";
+ }
+
+ if ($s = $cs->{'Reported Channel,Device'} || $cs->{'Reported Channel,Device(T:L)'}) {
+ $pd[$ch][$pd]{cd} = $s;
+ }
+
+ if (exists $cs->{$s = 'Device is a Hard drive'}
+ || exists $cs->{$s = 'Device is an Enclosure'}
+ || exists $cs->{$s = 'Device is an Enclosure services device'}
+ || exists $cs->{$s = 'Device is an Enclosure Services Device'}
+ ) {
+ ($pd[$ch][$pd]{devtype}) = $s =~ /Device is an?\s+(.+)/;
+ }
+
+ # TODO: normalize and other formats:
+ # Current Temperature : 27 deg C
+ # Life-time Temperature Recorded
+ # Temperature : 51 C/ 123 F (Normal)
+ # Temperature : Normal
+ # Temperature : Not Supported
+ # Temperature Sensor Status 1 : 21 C/ 69 F (Normal)
+ # Temperature Sensor Status 1 : 23 C/ 73 F (Normal)
+ # Temperature Sensor Status 1 : 27 C/ 80 F (Normal)
+ # Temperature Sensor Status 1 : 46 C/ 114 F (Abnormal)
+ # Temperature status : Normal
+ # Threshold Temperature : 51 deg C
+ # FIXME: previous code used last line with /Temperature/ match
+ if ($s = $cs->{'Temperature'} || $cs->{'Temperature Sensor Status 1'} || $cs->{'Temperature status'}) {
+ $pd[$ch][$pd]{temperature} = $s;
+ }
+
+ # ignored:
+ # Status of Enclosure
+ # (Fan \d+|Speaker) status/
+ # /Expander ID\s+:/
+ # /Enclosure Logical Identifier\s+:/
+ # /Expander SAS Address\s+:/
+ # /[Mm]axCache (Capable|Assigned)\s+:\s+(.+)/
+ # /Power supply \d+ status/
+ }
+ }
+
+ $res->{physical} = \@pd;
+ }
+
+ sub process_logical_drive_information {
+ shift->process_logical_device_information(@_);
+ }
+
+ sub process_maxcache_3_0_information {
+ }
+
+ # TODO: issue152/arc2_getconfig.txt
+ sub process_connector_information {
+ }
+
+ # NB: side effect: ARCCONF changes current directory to /var/log
+ sub parse {
+ my ($this) = @_;
+
+ # we chdir to /var/log, as tool is creating 'UcliEvt.log'
+ # this can be disabled with 'nologs' parameter, but not sure do all versions support it
+ chdir('/var/log') || chdir('/');
+
+ my ($status, $config);
+ $status = $this->parse_status or return;
+ $config = $this->parse_config($status) or return;
+
+ return { %$status, %$config };
+ }
+
+ # check for controller status
+ sub check_controller {
+ my ($this, $c) = @_;
+
+ my @status;
+
+ $this->critical if $c->{status} !~ /Optimal|Okay|OK/;
+ push(@status, "Controller:$c->{status}");
+
+ if ($c->{defunct_count} > 0) {
+ $this->critical;
+ push(@status, "Defunct drives:$c->{defunct_count}");
+ }
+
+ if (defined $c->{logical_failed} && $c->{logical_failed} > 0) {
+ $this->critical;
+ push(@status, "Failed drives:$c->{logical_failed}");
+ }
+
+ if (defined $c->{logical_degraded} && $c->{logical_degraded} > 0) {
+ $this->critical;
+ push(@status, "Degraded drives:$c->{logical_degraded}");
+ }
+
+ if (defined $c->{logical_offline} && $c->{logical_offline} > 0) {
+ $this->critical;
+ push(@status, "Offline drives:$c->{logical_offline}");
+ }
+
+ if (defined $c->{logical_critical} && $c->{logical_critical} > 0) {
+ $this->critical;
+ push(@status, "Critical drives:$c->{logical_critical}");
+ }
+
+ # ZMM (Zero-Maintenance Module) status
+ if (defined($c->{zmm_status})) {
+ push(@status, "ZMM Status: $c->{zmm_status}");
+ }
+
+ # Battery status
+ if ($this->bbu_monitoring) {
+ my @s = $this->battery_status($c);
+ push(@status, @s) if @s;
+ }
+
+ return @status;
+ }
+
+ # check for physical devices
+ sub check_physical {
+ my ($this, $p) = @_;
+
+ my %pd;
+ $this->{pd_resync} = 0;
+ for my $ch (@$p) {
+ for my $pd (@{$ch}) {
+ # skip not disks
+ next if not defined $pd;
+ next if $pd->{devtype} =~ m/Enclosure/;
+
+ if ($pd->{status} eq 'Rebuilding') {
+ $this->resync;
+ $this->{pd_resync}++;
+
+ } elsif ($pd->{status} eq 'Dedicated Hot-Spare') {
+ $this->spare;
+ $pd->{status} = "$pd->{status} for $pd->{spare}";
+
+ } elsif ($pd->{status} !~ /^Online|Hot[- ]Spare|Ready/) {
+ $this->critical;
+ }
+
+ my $id = $pd->{serial} || $pd->{wwn} || $pd->{location} || $pd->{cd};
+ push(@{$pd{$pd->{status}}}, $id);
+ }
+ }
+
+ return \%pd;
+ }
+
+ # check for logical devices
+ sub check_logical {
+ my ($this, $l) = @_;
+
+ my @status;
+ for my $ld (@$l) {
+ next unless $ld; # FIXME: fix that script assumes controllers start from '0'
+
+ if ($ld->{status} eq 'Degraded' && $this->{pd_resync}) {
+ $this->warning;
+ } elsif ($ld->{status} !~ /Optimal|Okay/) {
+ $this->critical;
+ }
+
+ my $id = $ld->{id};
+ if ($ld->{name}) {
+ $id = "$id($ld->{name})";
+ }
+ push(@status, "Logical Device $id:$ld->{status}");
+
+ if (defined $ld->{failed_stripes} && $ld->{failed_stripes} ne 'No') {
+ push(@status, "Failed stripes: $ld->{failed_stripes}");
+ }
+ if (defined $ld->{defunct_segments} && $ld->{defunct_segments} ne 'No') {
+ push(@status, "Defunct segments: $ld->{defunct_segments}");
+ }
+ }
+
+ return @status;
+ }
+
+ sub check {
+ my $this = shift;
+
+ my $data = $this->parse;
+ $this->unknown,return unless $data;
+
+ my @status;
+
+ for my $i (sort {$a cmp $b} keys %{$data->{controllers}}) {
+ my $c = $data->{controllers}->{$i};
+
+ push(@status, $this->check_controller($c->{controller}));
+
+ # current (logical device) tasks
+ if ($data->{tasks}->{operation} ne 'None') {
+ # just print it. no status change
+ my $task = $data->{tasks};
+ push(@status, "$task->{type} #$task->{device}: $task->{operation}: $task->{status} $task->{percent}%");
+ }
+
+ # check physical first, as it setups pd_resync flag
+ my $pd = $this->check_physical($c->{physical});
+
+ push(@status, $this->check_logical($c->{logical}));
+
+ # but report after logical devices
+ push(@status, "Drives: ".$this->join_status($pd)) if $pd;
+ }
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ # check battery status in $c
+ sub battery_status {
+ my ($this, $c) = @_;
+
+ my @status;
+
+ if (!defined($c->{battery_status}) || $c->{battery_status} eq 'Not Installed') {
+ return;
+ }
+
+ push(@status, "Battery Status: $c->{battery_status}");
+
+ # if battery status is 'Failed', none of the details below are available. #105
+ if ($c->{battery_status} eq 'Failed') {
+ $this->critical;
+ return @status;
+ }
+
+ # detailed battery checks
+ if ($c->{battery_overtemp} ne 'No') {
+ $this->critical;
+ push(@status, "Battery Overtemp: $c->{battery_overtemp}");
+ }
+
+ push(@status, "Battery Capacity Remaining: $c->{battery_capacity}%");
+ if ($c->{battery_capacity} < 50) {
+ $this->critical;
+ }
+ if ($c->{battery_capacity} < 25) {
+ $this->warning;
+ }
+
+ if ($c->{battery_time} < 1440) {
+ $this->warning;
+ }
+ if ($c->{battery_time} < 720) {
+ $this->critical;
+ }
+
+ if ($c->{battery_time} < 60) {
+ push(@status, "Battery Time: $c->{battery_time}m");
+ } else {
+ push(@status, "Battery Time: $c->{battery_time_full}");
+ }
+
+ return @status;
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/areca.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::areca;
+
+ ## Areca SATA RAID Support
+ ## requires cli64 or cli32 binaries
+ ## For links to manuals and binaries, see this issue:
+ ## https://github.com/glensc/nagios-plugin-check_raid/issues/10
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(areca-cli areca_cli64 areca_cli32 cli64 cli32);
+ }
+
+ sub commands {
+ {
+ 'rsf info' => ['-|', '@CMD', 'rsf', 'info'],
+ 'disk info' => ['-|', '@CMD', 'disk', 'info'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd rsf info",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd disk info",
+ );
+ }
+
+ # plugin check
+ # can store its exit code in $this->status
+ # can output its message in $this->message
+ sub check {
+ my $this = shift;
+
+ ## Check Array Status
+ my (@status, %arrays);
+ my $fh = $this->cmd('rsf info');
+ while (<$fh>) {
+ =cut
+ # Name Disks TotalCap FreeCap MinDiskCap State
+ # Name Disks TotalCap FreeCap DiskChannels State
+ ===============================================================================
+ 1 Raid Set # 000 23 34500.0GB 0.0GB 1500.0GB Normal
+ 1 Raid Set # 00 15 15000.0GB 0.0GB 123G567C9AB48EF Normal
+ 1 data 15 11250.0GB 0.0GB 123456789ABCDEF Normal
+ 1 data 15 11250.0GB 0.0GB 123456789ABCDEF Initializing
+ ===============================================================================
+ =cut
+ next unless (my($id, $n, $s) = m{^
+ \s*(\d+) # Id
+ \s+(.+) # Name
+ \s+\d+ # Disks
+ \s+\S+ # TotalCap
+ \s+\S+ # FreeCap
+ \s+\S+ # MinDiskCap/DiskChannels
+ \s+(\S+)\s* # State
+ $}x);
+
+ # trim trailing spaces from name
+ $n =~ s/\s+$//;
+
+ if ($s =~ /[Rr]e[Bb]uild/) {
+ $this->warning;
+ } elsif ($s !~ /[Nn]ormal|[Rr]e[Bb]uild|Checking|Initializing/) {
+ $this->critical;
+ }
+
+ push(@status, "Array#$id($n): $s");
+
+ $arrays{$n} = [ $id, $s ];
+ }
+ close $fh;
+
+ ## Check Drive Status
+ $fh = $this->cmd('disk info');
+ my %drivestatus;
+ while (<$fh>) {
+ chomp;
+ =cut
+ # Enc# Slot# ModelName Capacity Usage
+ ===============================================================================
+ 1 01 Slot#1 N.A. 0.0GB N.A.
+ 8 01 Slot#8 N.A. 0.0GB N.A.
+ 9 02 SLOT 01 ST31500341AS 1500.3GB Raid Set # 000
+ 11 02 SLOT 03 ST31500341AS 1500.3GB Raid Set # 000
+
+ # Ch# ModelName Capacity Usage
+ ===============================================================================
+ 1 1 ST31000340NS 1000.2GB Raid Set # 00
+ 6 6 ST31000340NS 1000.2GB Raid Set # 00
+ 3 3 WDC WD7500AYYS-01RCA0 750.2GB data
+ 4 4 WDC WD7500AYYS-01RCA0 750.2GB data
+ 16 16 WDC WD7500AYYS-01RCA0 750.2GB HotSpare[Global]
+ =cut
+ next unless my($id, $model, $usage) = m{^
+ \s*(\d+) # Id
+ \s+\d+ # Channel/Enclosure (not reliable, tests 1,2,12 differ)
+ \s+(.+) # ModelName
+ \s+\d+.\d\S+ # Capacity
+ \s+(.+) # Usage (Raid Name)
+ }x;
+
+ # trim trailing spaces from name
+ $usage =~ s/\s+$//;
+
+ # Asssume model N.A. means the slot not in use
+ # we could also check for Capacity being zero, but this seems more
+ # reliable.
+ next if $usage eq 'N.A.';
+
+ # use array id in output: shorter
+ my $array_id = defined($arrays{$usage}) ? ($arrays{$usage})->[0] : undef;
+ my $array_name = defined $array_id ? "Array#$array_id" : $usage;
+
+ # assume critical if Usage is not one of:
+ # - existing Array name
+ # - HotSpare
+ # - Rebuild
+ if (defined($arrays{$usage})) {
+ # Disk in Array named $usage
+ push(@{$drivestatus{$array_name}}, $id);
+ } elsif ($usage =~ /[Rr]e[Bb]uild/) {
+ # rebuild marks warning
+ push(@{$drivestatus{$array_name}}, $id);
+ $this->warning;
+ } elsif ($usage =~ /HotSpare/) {
+ # hotspare is OK
+ push(@{$drivestatus{$array_name}}, $id);
+ } elsif ($usage =~ /Pass Through/) {
+ # Pass Through is OK
+ push(@{$drivestatus{$array_name}}, $id);
+ } else {
+ push(@{$drivestatus{$array_name}}, $id);
+ $this->critical;
+ }
+ }
+ close $fh;
+
+ push(@status, "Drive Assignment: ".$this->join_status(\%drivestatus)) if %drivestatus;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cciss.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::cciss;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
+ use App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
+ use strict;
+ use warnings;
+
+ sub program_names {
+ 'cciss_vol_status';
+ }
+
+ sub commands {
+ {
+ 'controller status' => ['-|', '@CMD', '@devs'],
+ 'controller status verbose' => ['-|', '@CMD', '-V', '@devs'],
+ 'cciss_vol_status version' => ['>&2', '@CMD', '-v'],
+
+ 'detect hpsa' => ['<', '/sys/module/hpsa/refcnt'],
+ 'detect cciss' => ['<', '/proc/driver/cciss'],
+ 'cciss proc' => ['<', '/proc/driver/cciss/$controller'],
+
+ # for lsscsi, issue #109
+ 'lsscsi list' => ['-|', '@CMD', '-g'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+
+ my $v1_10 = $this->cciss_vol_status_version >= 1.10;
+
+ my @sudo;
+ my @cciss_devs = $this->detect;
+ if (@cciss_devs) {
+ my $c = join(' ', @cciss_devs);
+ if ($v1_10) {
+ push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -V $c");
+ } else {
+ push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd $c");
+ }
+ }
+
+ my @cciss_disks = $this->detect_disks(@cciss_devs);
+ if (!$v1_10 && @cciss_disks) {
+ my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new();
+
+ if ($smartctl->active) {
+ my $cmd = $smartctl->{program};
+ foreach my $ref (@cciss_disks) {
+ my ($dev, $diskopt, $disk) = @$ref;
+ # escape comma for sudo
+ $diskopt =~ s/,/\\$&/g;
+ push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -H $dev $diskopt$disk");
+ }
+ }
+ }
+
+ return @sudo;
+ }
+
+ # detects if hpsa (formerly cciss) is present in system
+ sub detect {
+ my $this = shift;
+
+ my ($fh, @devs);
+
+ # try lsscsi first if enabled and allowed
+ my $lsscsi = App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi->new('commands' => $this->{commands});
+ my $use_lsscsi = defined($this->{use_lsscsi}) ? $this->{use_lsscsi} : $lsscsi->active;
+ if ($use_lsscsi) {
+ # for cciss_vol_status < 1.10 we need /dev/sgX nodes, columns which are type storage
+ @devs = $lsscsi->list_sg;
+
+ # cciss_vol_status 1.10 can process disk nodes too even if sg is not present
+ my $v1_10 = $this->cciss_vol_status_version >= 1.10;
+ if (!@devs && $v1_10) {
+ @devs = $lsscsi->list_dd;
+ }
+
+ return wantarray ? @devs : \@devs if @devs;
+ }
+
+ # check hpsa devs
+ eval { $fh = $this->cmd('detect hpsa'); };
+ if ($fh) {
+ my $refcnt = <$fh>;
+ close $fh;
+
+ if ($refcnt) {
+ # TODO: how to figure which sgX is actually in use?
+ # for now we collect all, and expect cciss_vol_status to ignore unknowns
+ # refcnt seems to match number of sg devs: /sys/class/scsi_generic/sg*
+ for (my $i = 0; $i < $refcnt; $i++) {
+ my $dev = "/dev/sg$i";
+ # filter via valid() so could exclude devs
+ push(@devs, $dev) if $this->valid($dev);
+ }
+ }
+ }
+ undef($fh);
+
+ # check legacy cciss devs
+ eval { $fh = $this->cmd('detect cciss'); };
+ if ($fh) {
+ my @c = grep { !/^\./ } readdir($fh);
+ close($fh);
+
+ # find controllers
+ # cciss0: HP Smart Array P400i Controller
+ # Board ID: 0x3235103c
+ # Firmware Version: 4.06
+ # IRQ: 98
+ # Logical drives: 1
+ # Current Q depth: 0
+ # Current # commands on controller: 0
+ # Max Q depth since init: 249
+ # Max # commands on controller since init: 275
+ # Max SG entries since init: 31
+ # Sequential access devices: 0
+ #
+ # cciss/c0d0: 220.12GB RAID 1(1+0)
+ for my $c (@c) {
+ my $fh = $this->cmd('cciss proc', { '$controller' => $c });
+ while (<$fh>) {
+ # check "c*d0" - iterate over each controller
+ next unless (my($dev) = m{^(cciss/c\d+d0):});
+ $dev = "/dev/$dev";
+ # filter via valid() so could exclude devs
+ push(@devs, $dev) if $this->valid($dev);
+ }
+ close $fh;
+ }
+ }
+ undef($fh);
+
+ return wantarray ? @devs : \@devs;
+ }
+
+ # build list of cciss disks
+ # used by smartctl check
+ # just return all disks (0..15) for each cciss dev found
+ sub detect_disks {
+ my $this = shift;
+
+ my @devs;
+ # build devices list for smartctl
+ foreach my $scsi_dev (@_) {
+ foreach my $disk (0..15) {
+ push(@devs, [ $scsi_dev, '-dcciss,', $disk ]);
+ }
+ }
+ return wantarray ? @devs : \@devs;
+ }
+
+ # parse version out of "cciss_vol_status version 1.09"
+ # NOTE: it prints the output to stderr, but may print to stdout in the future
+ sub cciss_vol_status_version {
+ my $this = shift;
+
+ # cache inside single run
+ return $this->{cciss_vol_status_version} if defined $this->{cciss_vol_status_version};
+
+ my $version = sub {
+ my $fh = $this->nosudo_cmd('cciss_vol_status version');
+ my ($line) = <$fh>;
+ close $fh;
+ return 0 unless $line;
+
+ if (my($v) = $line =~ /^cciss_vol_status version ([\d.]+)$/) {
+ return 0 + $v;
+ }
+ return 0;
+ };
+
+ return $this->{cciss_vol_status_version} = &$version();
+ }
+
+ sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
+
+ # we process until we find end of sentence (dot at the end of the line)
+ sub consume_diagnostic {
+ my ($this, $fh) = @_;
+
+ my $diagnostic = '';
+ while (1) {
+ my $s = <$fh>;
+ last unless $s;
+ chomp;
+ $diagnostic .= ' '. trim($s);
+ last if $s =~ /\.$/;
+ }
+ return trim($diagnostic);
+ }
+
+ # process to skip lines with physical location:
+ # " connector 1I box 1 bay 4 ..."
+ sub consume_disk_map {
+ my ($this, $fh) = @_;
+
+ while (my $s = <$fh>) {
+ chomp $s;
+ # connector 1I box 1 bay 4
+ last unless $s =~ /^\s+connector\s/;
+ }
+ }
+
+ sub parse {
+ my $this = shift;
+ my @devs = @_;
+
+ my (%c, $cdev);
+
+ # cciss_vol_status 1.10 has -V option to print more info about controller and disks.
+ my $v1_10 = $this->cciss_vol_status_version >= 1.10;
+
+ # add all devs at once to commandline, cciss_vol_status can do that
+ my $fh = $this->cmd($v1_10 ? 'controller status verbose' : 'controller status', { '@devs' => \@devs });
+ while (<$fh>) {
+ chomp;
+
+ # skip empty lines and artificial comments (added by this project)
+ next if /^$/ or /^#/;
+
+ if (/Controller:/) {
+ # this is first item when new controller is found
+ # reset previous state
+ undef $cdev;
+ next;
+ }
+
+ # catch enclosures, print_bus_status()
+ # /dev/cciss/c1d0: (Smart Array P800) Enclosure MSA70 (S/N: SGA651004J) on Bus 2, Physical Port 1E status: OK.
+ # /dev/cciss/c0d0: (Smart Array 6i) Enclosure PROLIANT 6L2I (S/N: ) on Bus 0, Physical Port J1 status: OK.
+ if (my($file, $board_name, $name, $sn, $bus, $port1, $port2, $status) = m{
+ ^(/dev/[^:]+):\s # File
+ \(([^)]+)\)\s # Board Name
+ Enclosure\s(.*?)\s # Enclosure Name
+ \(S/N:\s(\S*)\)\s # Enclosure SN
+ on\sBus\s(\d+),\s # Bus Number
+ Physical\sPort\s(.) # physical_port1
+ (.)\s # physical_port2
+ status:\s(.*?)\. # status (without a dot)
+ }x) {
+ $c{$file}{enclosures}{$bus} = {
+ board_name => $board_name,
+ name => $name,
+ sn => $sn,
+ bus => int($bus),
+ phys1 => $port1,
+ phys2 => $port2,
+ status => $status,
+ };
+ next;
+ }
+
+ # volume status, print_volume_status()
+ # /dev/cciss/c0d0: (Smart Array P400i) RAID 1 Volume 0 status: OK
+ # /dev/sda: (Smart Array P410i) RAID 1 Volume 0 status: OK.
+ # /dev/sda: (Smart Array P410i) RAID 5 Volume 0 status: OK. At least one spare drive designated. At least one spare drive has failed.
+ if (my($file, $board_name, $raid_level, $volume_number, $certain, $status, $spare_drive_status) = m{
+ ^(/dev/[^:]+):\s # File
+ \(([^)]+)\)\s # Board Name
+ (RAID\s\d+|\([^)]+\))\s # RAID level
+ Volume\s(\d+) # Volume number
+ (\(\?\))?\s # certain?
+ status:\s(.*?)\. # status (without a dot)
+ (.*)? # spare drive status messages
+ }x) {
+ $cdev = $file;
+ $c{$file}{volumes}{$volume_number} = {
+ board_name => $board_name,
+ raid_level => $raid_level,
+ volume_number => $volume_number,
+ certain => int(not defined $certain),
+ status => $status,
+ spare_drive_status => trim($spare_drive_status),
+ };
+
+ $c{$file}{board_name} = $board_name;
+ next;
+ }
+
+ next unless $cdev;
+
+ if (my ($count) = /Physical drives: (\d+)/) {
+ $c{$cdev}{'pd count'} = $count;
+ next;
+ }
+
+ # check_physical_drives(file, fd);
+ # NOTE: check for physical drives is enabled with -V or -s option (-V enables -s)
+ # cciss_vol_status.c format_phys_drive_location()
+ if (my ($phys1, $phys2, $box, $bay, $model, $serial_no, $fw_rev, $status) = m{
+ \sconnector\s(.)(.)\s # Phys connector 1&2
+ box\s(\d+)\s # phys_box_on_bus
+ bay\s(\d+)\s # phys_bay_in_box
+ (.{40})\s # model
+ (.{40})\s # serial no
+ (.{8})\s # fw rev
+ (.+) # status
+ $}x) {
+ my $slot = "$phys1$phys2-$box-$bay";
+ $c{$cdev}{drives}{$slot} = {
+ 'slot' => $slot,
+ 'phys1' => $phys1,
+ 'phys2' => $phys2,
+ 'box' => int($box),
+ 'bay' => int($bay),
+
+ 'model' => trim($model),
+ 'serial' => trim($serial_no),
+ 'fw' => trim($fw_rev),
+ 'status' => $status,
+ };
+ next;
+ }
+
+ # TODO
+ # check_fan_power_temp(file, ctlrtype, fd, num_controllers);
+
+ # check_nonvolatile_cache_status(file, ctlrtype, fd, num_controllers);
+ # /dev/cciss/c0d0(Smart Array P400i:0): Non-Volatile Cache status:
+ if (my($file, $board_name, $instance) = m{^(/dev/[^(]+)\((.+):(\d+)\): Non-Volatile Cache status}) {
+ # $file and $dev may differ, so store it
+ $c{$cdev}{cache} = {
+ 'file' => $file,
+ 'board' => $board_name,
+ 'instance' => int($instance),
+ };
+ next;
+ }
+
+ if (defined($c{$cdev}{cache})) {
+ my $cache = $c{$cdev}{cache};
+ my %map = (
+ configured => qr/Cache configured: (.+)/,
+ read_cache_memory => qr/Read cache memory: (.+)/,
+ write_cache_memory => qr/Write cache memory: (.+)/,
+ write_cache_enabled => qr/Write cache enabled: (.+)/,
+ flash_cache => qr/Flash backed cache present/,
+ disabled_temporarily => qr/Write cache temporarily disabled/,
+ disabled_permanently => qr/Write Cache permanently disabled/,
+ );
+ my $got;
+ while (my($k, $r) = each %map) {
+ next unless (my($v) = $_ =~ $r);
+ $cache->{$k} = $v;
+ $got = 1;
+
+ # consume extended diagnostic
+ if ($k =~ /disabled_(temporari|permanentl)ly/) {
+ $cache->{"$k diagnostic"} = $this->consume_diagnostic($fh);
+ }
+ }
+
+ next if $got;
+ }
+
+ # show_disk_map(" Failed drives:", file, fd, id, controller_lun, ctlrtype,
+ # show_disk_map(" 'Replacement' drives:", file, fd, id, controller_lun, ctlrtype,
+ # show_disk_map(" Drives currently substituted for by spares:", file, fd, id, controller_lun, ctlrtype,
+ if (/^ Failed drives:/ ||
+ /^ 'Replacement' drives:/ ||
+ /^ Drives currently substituted for by spares:/
+ ) {
+ # could store this somewhere, ignore for now
+ $this->consume_disk_map($fh);
+ next;
+ }
+
+ if (my($total_failed) = /Total of (\d+) failed physical drives detected on this logical drive\./) {
+ $c{$cdev}{phys_failed} = $total_failed;
+ next;
+ }
+
+ warn "Unparsed[$_]";
+ }
+ close($fh);
+
+ return \%c;
+ }
+
+ sub check {
+ my $this = shift;
+ my @devs = $this->detect;
+
+ unless (@devs) {
+ $this->warning;
+ $this->message("No Smart Array Adapters were found on this machine");
+ return;
+ }
+
+ # status messages pushed here
+ my @status;
+
+ my $res = $this->parse(@devs);
+ for my $dev (sort {$a cmp $b} keys %$res) {
+ my $c = $res->{$dev};
+ my @bstatus;
+
+ # check volumes
+ my @vstatus;
+ for my $vn (sort {$a cmp $b} keys %{$c->{volumes}}) {
+ my $v = $c->{volumes}->{$vn};
+ if ($v->{status} !~ '^OK') {
+ $this->critical;
+ }
+ push(@vstatus, "Volume $v->{volume_number} ($v->{raid_level}): $v->{status}");
+ }
+
+ push(@bstatus, @vstatus) if @vstatus;
+
+ # check physical devices
+ if ($c->{'pd count'}) {
+ my %pd;
+ for my $ps (sort {$a cmp $b} keys %{$c->{drives}}) {
+ my $pd = $c->{drives}{$ps};
+ if ($pd->{status} !~ '^OK') {
+ $this->critical;
+ $ps .= "($pd->{serial})";
+ }
+ push(@{$pd{$pd->{status}}}, $ps);
+ }
+ push(@bstatus, "Drives($c->{'pd count'}): ". $this->join_status(\%pd));
+ }
+
+ # check enclosures
+ if ($c->{enclosures}) {
+ my @e;
+ for my $i (sort {$a cmp $b} keys %{$c->{enclosures}}) {
+ my $e = $c->{enclosures}{$i};
+
+ # enclosure name may be missing, identify by connection
+ my $s = $e->{name} || "$e->{bus}-$e->{phys1}$e->{phys2}";
+ # enclosure S/N may be missing
+ $s .= "($e->{sn})" if $e->{sn};
+ $s .= ": $e->{status}";
+ if ($e->{status} !~ '^OK') {
+ $this->critical;
+ }
+ push(@e, $s);
+ }
+ push(@bstatus, "Enclosures: ". join(', ', @e));
+ }
+
+ # check cache
+ if ($c->{cache} && $c->{cache}->{configured} eq 'Yes') {
+ my $cache = $c->{cache};
+ my @cstatus = 'Cache:';
+
+ if ($cache->{write_cache_enabled} eq 'Yes') {
+ push(@cstatus, "WriteCache");
+
+ } elsif ($cache->{disabled_temporarily} || $cache->{disabled_permanently}) {
+ # disabled diagnostic is available, but it's too long to print here
+ push(@cstatus, "WriteCache:DISABLED");
+ $this->cache_fail;
+ }
+
+ push(@cstatus, "FlashCache") if $cache->{flash_cache};
+ push(@cstatus, "ReadMem:$cache->{read_cache_memory}") if $cache->{read_cache_memory};
+ push(@cstatus, "WriteMem:$cache->{write_cache_memory}") if $cache->{write_cache_memory};
+
+ push(@bstatus, join(' ', @cstatus));
+ }
+
+ push(@status, "$dev($c->{board_name}): ". join(', ', @bstatus));
+ }
+
+ unless (@status) {
+ return;
+ }
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join(', ', @status));
+
+ # cciss_vol_status 1.10 with -V (or -s) checks individual disk health anyway
+ my $v1_10 = $this->cciss_vol_status_version >= 1.10;
+
+ # no_smartctl: allow skip from tests
+ if (!$v1_10 && !$this->{no_smartctl}) {
+ # check also individual disk health
+ my @disks = $this->detect_disks(@devs);
+ if (@disks) {
+ # inherit smartctl command from our commands (testing)
+ my %params = ();
+ $params{commands}{smartctl} = $this->{commands}{smartctl} if $this->{commands}{smartctl};
+
+ my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new(%params);
+ # do not perform check if smartctl is missing
+ if ($smartctl->active) {
+ $smartctl->check_devices(@disks);
+
+ # XXX this is hack, as we have no proper subcommand check support
+ $this->message($this->message . " " .$smartctl->message);
+ if ($smartctl->status > 0) {
+ $this->critical;
+ }
+ }
+ }
+ }
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cmdtool2.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::cmdtool2;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ 'CmdTool2';
+ }
+
+ sub commands {
+ {
+ 'adapter list' => ['-|', '@CMD', , '-AdpAllInfo', '-aALL', '-nolog'],
+ 'adapter config' => ['-|', '@CMD', '-CfgDsply', '-a$adapter', '-nolog'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpAllInfo -aALL -nolog",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -CfgDsply -a* -nolog",
+ );
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ # get adapters
+ my $fh = $this->cmd('adapter list');
+ my @c;
+ while (<$fh>) {
+ if (my($c) = /^Adapter #(\d+)/) {
+ push(@c, $c);
+ }
+ }
+ close $fh;
+
+ unless (@c) {
+ $this->warning;
+ $this->message("No LSI adapters were found on this machine");
+ return;
+ }
+
+ foreach my $c (@c) {
+ my $fh = $this->cmd('adapter config', { '$adapter' => $c });
+ my ($d);
+ while (<$fh>) {
+ # DISK GROUPS: 0
+ if (my($s) = /^DISK GROUPS: (\d+)/) {
+ $d = int($s);
+ next;
+ }
+
+ # State: Optimal
+ if (my($s) = /^State: (\S+)$/) {
+ if ($s ne 'Optimal') {
+ $this->critical;
+ }
+ push(@status, "Logical Drive $c,$d: $s");
+ }
+ }
+ }
+
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dm.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::dm;
+
+ # Package to check Linux Device Mapper
+
+ # Linux LVM Mirrors
+ # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/mirror_create.html
+ #
+ # Linux LVM RAID
+ # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/raid_volumes.html
+ #
+ # Low-level:
+ # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
+ # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#dmraid-map
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(dmsetup);
+ }
+
+ sub active {
+ my ($this, $sudo) = @_;
+
+ # return if parent said NO
+ my $res = $this->SUPER::active(@_);
+ return $res unless $res;
+
+ # check if there really are any devices
+ my $c = $this->parse;
+ return !!@$c;
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd status --noflush",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd status",
+ );
+ }
+
+ sub commands {
+ {
+ 'dmsetup' => [ '-|', '@CMD', 'status' ],
+ 'dmsetup noflush' => [ '-|', '@CMD', 'status', '--noflush' ],
+ }
+ }
+
+ # https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt
+ sub parse_raid {
+ local $_ = shift;
+
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1377
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1423
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1425-L1435
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1437-L1442
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1444-L1452
+ my @cols = qw(
+ raid_type raid_disks
+ status_chars
+ sync_ratio
+ sync_action
+ mismatch_cnt
+ );
+
+ my %h;
+ @h{@cols} = split;
+
+ \%h;
+ }
+
+ # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
+ sub parse_mirror {
+ local $_ = shift;
+
+ my %h;
+
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1355
+ my @parts = split;
+
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1365
+ $h{nr_mirrors} = shift @parts;
+
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1366-L1369
+ my @devs;
+ for (my $i = 0; $i < $h{nr_mirrors}; $i++) {
+ push(@devs, shift @parts);
+ }
+ $h{devices} = \@devs;
+
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1372-L1374
+ # some ratio?
+ $h{ratio} = shift @parts;
+ # param count? always '1'
+ shift @parts;
+ # the 'buffer' filled with status chars
+ $h{status_chars} = shift @parts;
+
+ # log device information
+ # https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-log.c#L807-L810
+ # log params, always '3'
+ shift @parts;
+ my %l;
+ $l{type} = shift @parts;
+ $l{device} = shift @parts;
+ # status: F->D->A
+ $l{status_char} = shift @parts;
+ $h{log} = { %l };
+
+ # for debugging. fill only if something remains not parsed
+ $h{_remaining} = join ' ', @parts if @parts;
+
+ \%h;
+ }
+
+ sub parse_target {
+ my ($target, $data) = @_;
+
+ return parse_raid($data) if $target eq 'raid';
+ return parse_mirror($data) if $target eq 'mirror';
+ undef;
+ }
+
+ sub get_fh {
+ my $this = shift;
+
+ # use dmsetup --noflush, requires LVM >= 2.02.97
+ # if that fails, fall back to just dmsetup
+ # https://github.com/glensc/nagios-plugin-check_raid/issues/130#issuecomment-194476070
+ my $fh = $this->cmd('dmsetup noflush');
+ $fh = $this->cmd('dmsetup') if eof $fh;
+
+ return $fh;
+ }
+
+ sub parse {
+ my $this = shift;
+
+ # cache for single run
+ if (!defined($this->{parsed})) {
+ $this->{parsed} = $this->_parse;
+ }
+
+ return $this->{parsed};
+ }
+
+ sub _parse {
+ my $this = shift;
+
+ my @devices;
+ my $fh = $this->get_fh();
+ while (<$fh>) {
+ # skip comments.
+ # not present in dmsetup output, but our test files may have.
+ next if /^#/;
+
+ last if /No devices found/;
+
+ if (my ($dmname, $s, $l, $target, $rest) = m{^
+ (\S+):\s+ # dmname
+ (\d+)\s+ # start
+ (\d+)\s+ # length
+ (\S+) # target
+ (?:\s+(.+))? # rest of the data
+ \s? # there may be trailing space
+ $}x) {
+ my $h = parse_target($target, $rest);
+
+ # skip target type not handled
+ next unless $h;
+
+ my %h = (
+ 'dmname' => $dmname,
+ 's' => $s,
+ 'l' => $l,
+ 'target' => $target,
+ %$h,
+ );
+ push @devices, \%h;
+ next;
+ }
+
+ warn "Unhandled:[$_]";
+ $this->unknown;
+ }
+ close $fh;
+ return \@devices;
+ }
+
+ sub check {
+ my $this = shift;
+
+ my $c = $this->parse;
+
+ if (!@$c) {
+ $this->noraid->message("No devices to check");
+ return;
+ }
+
+ my @status;
+ foreach my $dm (@$c)
+ {
+ # <status_chars> One char for each device, indicating:
+ # 'A' = alive and in-sync (mirror, raid1, raid)
+ # 'a' = alive but not in-sync (mirror, raid1)
+ # 'D' = dead/failed (mirror, raid1, raid)
+ # 'S' = Sync (mirror, raid1)
+ # 'mirror'/'raid1': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1330-L1342
+ # 'raid': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1414
+ $this->critical if ($dm->{status_chars} =~ /D/);
+ $this->warning if ($dm->{status_chars} =~ /[aS]/);
+
+ my @s = "$dm->{dmname}:$dm->{status_chars}";
+
+ # <sync_action> One of the following possible states:
+ # idle - No synchronization action is being performed.
+ # frozen - The current action has been halted.
+ # resync - Array is undergoing its initial synchronization or...
+ # recover - A device in the array is being rebuilt or...
+ # check - A user-initiated full check of the array is...
+ # repair - The same as "check", but discrepancies are...
+ # reshape - The array is undergoing a reshape.
+ if ($dm->{sync_action}) {
+ push(@s, $dm->{sync_action});
+ if ($dm->{sync_action} =~ /^(check|repair|init)$/) {
+ $this->warning;
+ }
+ }
+ push(@status, join(' ', @s));
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dmraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::dmraid;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'dmraid' => ['-|', '@CMD', '-r'],
+ }
+ }
+
+ sub active {
+ my ($this) = @_;
+
+ # allow --plugin-option=dmraid-enabled to force this plugin to be enabled
+ return 1 if exists $this->{options}{'dmraid-enabled'};
+
+ # return if parent said NO
+ my $res = $this->SUPER::active(@_);
+ return $res unless $res;
+
+ # check if dmraid is empty
+ return keys %{$this->parse} > 0;
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -r";
+ }
+
+ # parse arrays, return data indexed by array name
+ sub parse {
+ my $this = shift;
+
+ my (%arrays);
+ my $fh = $this->cmd('dmraid');
+ while (<$fh>) {
+ chomp;
+ next unless (my($device, $format, $name, $type, $status, $sectors) = m{^
+ # /dev/sda: jmicron, "jmicron_JRAID", mirror, ok, 781385728 sectors, data@ 0
+ # /dev/sdb: ddf1, ".ddf1_disks", GROUP, ok, 1953253376 sectors, data@ 0
+ (/dev/\S+):\s # device
+ (\S+),\s # format
+ "([^"]+)",\s # name
+ (mirror|stripe[d]?|GROUP),\s # type
+ (\w+),\s # status
+ (\d+)\ssectors,.* # sectors
+ $}x);
+ next unless $this->valid($device);
+
+ # trim trailing spaces from name
+ $name =~ s/\s+$//;
+
+ my $member = {
+ 'device' => $device,
+ 'format' => $format,
+ 'type' => $type,
+ 'status' => $status,
+ 'size' => $sectors,
+ };
+
+ push(@{$arrays{$name}}, $member);
+ }
+ close $fh;
+
+ return \%arrays;
+ }
+
+
+ # plugin check
+ # can store its exit code in $this->status
+ # can output its message in $this->message
+ sub check {
+ my $this = shift;
+ my (@status);
+
+ ## Check Array and Drive Status
+ my $arrays = $this->parse;
+ while (my($name, $array) = each(%$arrays)) {
+ my @s;
+ foreach my $dev (@$array) {
+ if ($dev->{status} =~ m/sync|rebuild/i) {
+ $this->warning;
+ } elsif ($dev->{status} !~ m/ok/i) {
+ $this->critical;
+ }
+ my $size = $this->format_bytes($dev->{size});
+ push(@s, "$dev->{device}($dev->{type}, $size): $dev->{status}");
+ }
+ push(@status, "$name: " . join(', ', @s));
+ }
+
+ return unless @status;
+
+ # denote that this plugin as ran ok, not died unexpectedly
+ $this->ok->message(join(' ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dpt_i2o.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::dpt_i2o;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub commands {
+ {
+ 'proc' => ['<', '/proc/scsi/dpt_i2o'],
+ 'proc entry' => ['<', '/proc/scsi/dpt_i2o/$controller'],
+ }
+ }
+
+ sub active {
+ my ($this) = @_;
+ return -d $this->{commands}{proc}[1];
+ }
+
+ sub check {
+ my $this = shift;
+ # status messages pushed here
+ my @status;
+
+ my $fh = $this->cmd('proc');
+ my @c = grep { !/^\./ } readdir($fh);
+ close($fh);
+
+ # TODO: check for failed disks!
+ for my $c (@c) {
+ my $fh = $this->cmd('proc entry', { '$controller' => $c });
+
+ while (<$fh>) {
+ if (my ($c, $t, $l, $s) = m/TID=\d+,\s+\(Channel=(\d+),\s+Target=(\d+),\s+Lun=(\d+)\)\s+\((\S+)\)/) {
+ if ($s ne "online") {
+ $this->critical;
+ }
+ push(@status, "$c,$t,$l:$s");
+ }
+ }
+ close($fh);
+ }
+
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/gdth.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::gdth;
+
+ # Linux gdth RAID
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub commands {
+ {
+ 'proc' => ['<', '/proc/scsi/gdth'],
+ 'proc entry' => ['<', '/proc/scsi/gdth/$controller'],
+ }
+ }
+
+ sub active {
+ my ($this) = @_;
+ return -d $this->{commands}{proc}[1];
+ }
+
+ sub parse {
+ my $this = shift;
+
+ my $fh = $this->cmd('proc');
+ my @c = grep { !/^\./ } readdir($fh);
+ close($fh);
+
+ my %c;
+ for my $c (@c) {
+ my (%ld, %ad, %pd, %l, %a, %p, $section);
+
+ my $fh = $this->cmd('proc entry', { '$controller' => $c });
+ while (<$fh>) {
+ chomp;
+
+ # new section start
+ if (my($s) = /^(\w.+):$/) {
+ $section = $s;
+ %a = %l = %p = ();
+ next;
+ }
+
+ # skip unknown sections
+ next unless /^\s/ or /^$/;
+
+ # process each section
+ if ($section eq 'Driver Parameters') {
+ # nothing useful
+ } elsif ($section eq 'Disk Array Controller Information') {
+ # nothing useful
+ } elsif ($section eq 'Physical Devices') {
+ # Chn/ID/LUN: B/05/0 Name: FUJITSU MAX3147NC 0104
+ # Capacity [MB]: 140239 To Log. Drive: 5
+ # Retries: 1 Reassigns: 0
+ # Grown Defects: 1
+
+ if (my($id, $n, $rv) = m{^\s+Chn/ID/LUN:\s+(\S+)\s+Name:\s+(.+)(.{4})$}) {
+ $n =~ s/\s+$//;
+ $p{id} = $id;
+ $p{name} = $n;
+ $p{revision} = $rv;
+ } elsif (my($unit, $c, $d) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+To Log\. Drive:\s+(\d+|--)/) {
+ $p{capacity} = int($c);
+ $p{capacity_unit} = $unit;
+ $p{drive} = $d;
+ } elsif (my($r, $ra) = m/^\s+Retries:\s+(\d+)\s+Reassigns:\s+(\d+)/) {
+ $p{retries} = int($r);
+ $p{reassigns} = int($ra);
+ } elsif (my($gd) = m/^\s+Grown Defects:\s+(\d+)/) {
+ $p{defects} = int($gd);
+ } elsif (/^$/) {
+ if ($p{capacity} == 0 and $p{name} =~ /SCA HSBP/) {
+ # HSBP is not a disk, so do not consider this an error
+ # http://support.gateway.com/s/Servers/COMPO/MOTHERBD/4000832/4000832si69.shtml
+ # Raid Hot Swap Backplane driver (recognized as "ESG-SHV SCA HSBP M16 SCSI Processor Device")
+ # Chn/ID/LUN: B/06/0 Name: ESG-SHV SCA HSBP M16 0.05
+ # Capacity [MB]: 0 To Log. Drive: --
+ next;
+ }
+
+ $pd{$p{id}} = { %p };
+ } else {
+ warn "[$section] [$_]";
+ $this->unknown;
+ }
+
+ } elsif ($section eq 'Logical Drives') {
+ # Number: 3 Status: ok
+ # Slave Number: 15 Status: ok (older kernels)
+ # Capacity [MB]: 69974 Type: Disk
+ if (my($num, $s) = m/^\s+(?:Slave )?Number:\s+(\d+)\s+Status:\s+(\S+)/) {
+ $l{number} = int($num);
+ $l{status} = $s;
+ } elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
+ $l{capacity} = "$c $unit";
+ $l{type} = $t;
+ } elsif (my($md, $id) = m/^\s+Missing Drv\.:\s+(\d+)\s+Invalid Drv\.:\s+(\d+|--)/) {
+ $l{missing} = int($md);
+ $l{invalid} = int($id);
+ } elsif (my($n) = m/^\s+To Array Drv\.:\s+(\d+|--)/) {
+ $l{array} = $n;
+ } elsif (/^$/) {
+ $ld{$l{number}} = { %l };
+ } else {
+ warn "[$section] [$_]";
+ $this->unknown;
+ }
+
+ } elsif ($section eq 'Array Drives') {
+ # Number: 0 Status: fail
+ # Capacity [MB]: 349872 Type: RAID-5
+ if (my($num, $s) = m/^\s+Number:\s+(\d+)\s+Status:\s+(\S+)/) {
+ $a{number} = int($num);
+ $a{status} = $s;
+ } elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
+ $a{capacity} = "$c $unit";
+ $a{type} = $t;
+ } elsif (/^(?: --)?$/) {
+ if (%a) {
+ $ad{$a{number}} = { %a };
+ }
+ } else {
+ warn "[$section] [$_]";
+ $this->unknown;
+ }
+
+ } elsif ($section eq 'Host Drives') {
+ # nothing useful
+ } elsif ($section eq 'Controller Events') {
+ # nothing useful
+ }
+ }
+ close($fh);
+
+ $c{$c} = { id => $c, array => { %ad }, logical => { %ld }, physical => { %pd } };
+ }
+
+ return \%c;
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $controllers = $this->parse;
+
+ # process each controller separately
+ for my $c (values %$controllers) {
+ # array status
+ my @ad;
+ for my $n (sort {$a cmp $b} keys %{$c->{array}}) {
+ my $ad = $c->{array}->{$n};
+ if ($ad->{status} ne "ready") {
+ $this->critical;
+ }
+ push(@ad, "Array $ad->{number}($ad->{type}) $ad->{status}");
+ }
+
+ # older raids have no Array drives, Look into Logical Drives for type!=Disk
+ unless (@ad) {
+ for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
+ my $ld = $c->{logical}->{$n};
+ if ($ld->{type} eq "Disk") {
+ next;
+ }
+
+ # emulate Array Drive
+ my $s = "Array($ld->{type}) $ld->{status}";
+ # check for missing drives
+ if ($ld->{missing} > 0) {
+ $this->warning;
+ $s .= " ($ld->{missing} missing drives)";
+ }
+
+ push(@ad, $s);
+ }
+ }
+
+ # logical drive status
+ my %ld;
+ for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
+ my $ld = $c->{logical}->{$n};
+ if ($ld->{status} ne "ok") {
+ $this->critical;
+ }
+ push(@{$ld{$ld->{status}}}, $ld->{number});
+ }
+
+ # physical drive status
+ my @pd;
+ for my $n (sort {$a cmp $b} keys %{$c->{physical}}) {
+ my $pd = $c->{physical}->{$n};
+
+ my @ds;
+ # TODO: make tresholds configurable
+ if ($pd->{defects} > 300) {
+ $this->critical;
+ push(@ds, "grown defects critical: $pd->{defects}");
+ } elsif ($pd->{defects} > 30) {
+ $this->warning;
+ push(@ds, "grown defects warning: $pd->{defects}");
+ }
+
+ # report disk being not assigned
+ if ($pd->{drive} eq '--') {
+ push(@ds, "not assigned");
+ }
+
+ if (@ds) {
+ push(@pd, "Disk $pd->{id}($pd->{name}) ". join(', ', @ds));
+ }
+ }
+
+ my @cd;
+ push(@cd, @ad) if @ad;
+ push(@cd, "Logical Drives: ". $this->join_status(\%ld));
+ push(@cd, @pd) if @pd;
+ push(@status, "Controller $c->{id}: ". join('; ', @cd));
+ }
+
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join('; ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hp_msa.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::hp_msa;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use App::Monitoring::Plugin::CheckRaid::SerialLine;
+ use strict;
+ use warnings;
+
+ sub active {
+ my $this = shift;
+ return $this->detect;
+ }
+
+ # check from /sys if there are any MSA VOLUME's present.
+ sub detect {
+ my $this = shift;
+
+ # allow --plugin-option=hp_msa-enabled to force this plugin to be enabled
+ return 1 if exists $this->{options}{'hp_msa-enabled'};
+
+ for my $file (</sys/block/*/device/model>) {
+ open my $fh, '<', $file or next;
+ my $model = <$fh>;
+ close($fh);
+ return 1 if ($model =~ /^MSA.+VOLUME/);
+ }
+ return 0;
+ }
+
+ sub check {
+ my $this = shift;
+
+ # allow --plugin-option=hp_msa-serial=/dev/ttyS2 to specify serial line
+ my $ctldevice = $this->{options}{'hp_msa-serial'} || '/dev/ttyS0';
+
+ # status messages pushed here
+ my @status;
+
+ my %opts = ();
+ $opts{lockdir} = $this->{lockdir} if $this->{lockdir};
+
+ my $modem = App::Monitoring::Plugin::CheckRaid::SerialLine->new($ctldevice, %opts);
+ my $fh = $modem->open();
+ unless ($fh) {
+ $this->warning;
+ $this->message("Can't open $ctldevice");
+ return;
+ }
+
+ # check first controller
+ print $fh "\r";
+ print $fh "show globals\r";
+ print $fh "show this_controller\r";
+ print $fh "show other_controller\r";
+ # this will issue termination match, ie. invalid command
+ print $fh "exit\r";
+
+ my ($c, %c, %t);
+ while (<$fh>) {
+ chomp;
+ s/[\n\r]$//;
+ last if /Invalid CLI command/;
+
+ # Temperature:
+ # EMU: 23 Celsius, 73 Fahrenheit
+ # PS1: 22 Celsius, 71 Fahrenheit
+ # PS2: 22 Celsius, 71 Fahrenheit
+ if (my($s, $c) = /(\S+): (\d+) Celsius,\s+\d+ Fahrenheit/) {
+ $t{$s} = int($c);
+ next;
+ }
+
+ # Controller 1 (right controller):
+ if (my($s) = /^(Controller \d+)/) {
+ $c = $s;
+ $c{$c} = [];
+ next;
+ }
+ # Surface Scan: Running, LUN 10 (68% Complete)
+ if (my($s, $m) = /Surface Scan:\s+(\S+)[,.]\s*(.*)/) {
+ if ($s eq 'Running') {
+ my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
+ push(@{$c{$c}}, "Surface: $l ($p%)");
+ $this->warning;
+ } elsif ($s ne 'Complete') {
+ push(@{$c{$c}}, "Surface: $s, $m");
+ $this->warning;
+ }
+ next;
+ }
+ # Rebuild Status: Running, LUN 0 (67% Complete)
+ if (my($s, $m) = /Rebuild Status:\s+(\S+)[,.]\s*(.*)/) {
+ if ($s eq 'Running') {
+ my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
+ push(@{$c{$c}}, "Rebuild: $l ($p%)");
+ $this->warning;
+ } elsif ($s ne 'Complete') {
+ push(@{$c{$c}}, "Rebuild: $s, $m");
+ $this->warning;
+ }
+ next;
+ }
+ # Expansion: Complete.
+ if (my($s, $m) = /Expansion:\s+(\S+)[.,]\s*(.*)/) {
+ if ($s eq 'Running') {
+ my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
+ push(@{$c{$c}}, "Expansion: $l ($p%)");
+ $this->warning;
+ } elsif ($s ne 'Complete') {
+ push(@{$c{$c}}, "Expansion: $s, $m");
+ $this->warning;
+ }
+ next;
+ }
+ }
+ $modem->close();
+
+ foreach $c (sort { $a cmp $b } keys %c) {
+ my $s = $c{$c};
+ $s = join(', ', @$s);
+ $s = 'OK' unless $s;
+ push(@status, "$c: $s");
+ }
+
+ # check that no temp is over the treshold
+ my $warn = 28;
+ my $crit = 33;
+ while (my($t, $c) = each %t) {
+ if ($c > $crit) {
+ push(@status, "$t: ${c}C");
+ $this->critical;
+ } elsif ($c > $warn) {
+ push(@status, "$t: ${c}C");
+ $this->warning;
+ }
+ }
+
+ return unless @status;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpacucli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli;
+
+ ## hpacucli/hpssacli/ssacli support
+ #
+ # driver developers recommend to use cciss_vol_status for monitoring,
+ # hpacucli/hpssacli shouldn't be used for monitoring due they obtaining global
+ # kernel lock while cciss_vol_status does not. cciss_vol_status is designed for
+ # monitoring
+ # https://github.com/glensc/nagios-plugin-check_raid/issues/114#issuecomment-138866801
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ use constant E_NO_LOGICAL_DEVS => 'The specified device does not have any logical drives';
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'controller status' => ['-|', '@CMD', 'controller', 'all', 'show', 'status'],
+ 'logicaldrive status' => ['-|', '@CMD', 'controller', '$target', 'logicaldrive', 'all', 'show'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd controller all show status",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd controller * logicaldrive all show",
+ );
+ }
+
+ # if --plugin-option=hpacucli-target=slot=0 is specified
+ # filter only allowed values
+ sub filter_targets {
+ my ($this, $targets) = @_;
+
+ my $cli_opts = $this->{options}{'hpacucli-target'};
+ if (!$cli_opts) {
+ return $targets;
+ }
+
+ my %res;
+ my @filters = split(/,/, $cli_opts);
+ for my $filter (@filters) {
+ if (exists $targets->{$filter}) {
+ $res{$filter} = $targets->{$filter};
+ } else {
+ $this->critical->message("Controller $filter not found");
+ }
+ }
+
+ return \%res;
+ }
+
+ # split:
+ # '(Embedded) (RAID Mode)'
+ # to:
+ # [ 'Embedded', 'RAID Mode' ]
+ sub split_controller_modes {
+ my ($modes) = @_;
+ my @parts;
+ push @parts, $1 while $modes =~ /\((.*?)\)/g;
+ return \@parts;
+ }
+
+ sub scan_targets {
+ my $this = shift;
+
+ # TODO: allow target customize:
+ # hpacucli <target> is of format:
+ # [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ]
+ # [array all|<id>]
+ # [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#]
+ # [logicaldrive all|#]
+ # [enclosure all|#:#|serialnumber=#|chassisname=#]
+ # [licensekey all|<key>]
+
+ # Scan controllers
+ my (%targets, $target);
+ my $fh = $this->cmd('controller status');
+ while (<$fh>) {
+ chomp;
+ # skip empty lines and artificial comments (added by this project)
+ next if /^$/ or /^#/;
+
+ # skip known noise
+ if (
+ /FIRMWARE UPGRADE REQUIRED: /
+ || /^\s{27}/
+ ) {
+ next;
+ }
+
+ # Numeric slot
+ if (my($controller, $slot, $modes) = /
+ ^(\S.+)\sin\sSlot
+ \s(\S+?) # slot number
+ (?: # optional mode
+ \s(\(.+\))
+ )?$
+ /x) {
+
+ $target = "slot=$slot";
+ $targets{$target} = {
+ target => $target,
+ controller => $controller,
+ slot => $slot,
+ modes => split_controller_modes($modes || ''),
+ };
+ $this->unknown if $slot !~ /^\d+/;
+ next;
+ }
+
+ # Named Entry
+ if (my($controller, $cn) = /^(\S.+) in (.+)/) {
+ $target = "chassisname=$cn";
+ $targets{$target} = {
+ target => $target,
+ controller => $controller,
+ chassisname => $cn,
+ };
+ next;
+ }
+
+ # Other statuses, try "key: value" pairs
+ if (my ($key, $value) = /^\s*(.+?):\s+(.+?)$/) {
+ $targets{$target}{$key} = $value;
+ next;
+ }
+
+ warn "Unparsed: [$_]\n";
+ }
+ close $fh;
+
+ return $this->filter_targets(\%targets);
+ }
+
+ # Scan logical drives
+ sub scan_luns {
+ my ($this, $targets) = @_;
+
+ my @luns;
+ # sort by target to ensure consistent results
+ for my $target (sort {$a->{target} cmp $b->{target}} values(%$targets)) {
+ # check each controller
+ my $fh = $this->cmd('logicaldrive status', { '$target' => $target->{target} });
+
+ my $index = -1;
+ my @array;
+ my %array;
+ while (<$fh>) {
+ chomp;
+ # skip empty lines and artificial comments (added by this project)
+ next if /^$/ or /^#/;
+
+ # Error: The controller identified by "slot=attr_value_slot_unknown" was not detected.
+ if (/^Error:\s/) {
+ # store it somewhere. should it be appended?
+ ($target->{'error'}) = /^Error:\s+(.+?)\.?\s*$/;
+ $this->unknown;
+ next;
+ }
+
+ # "array A"
+ # "array A (Failed)"
+ # "array B (Failed)"
+ if (my($a, $s) = /^\s+array (\S+)(?:\s*\((\S+)\))?$/i) {
+ $index++;
+ # Offset 0 is Array own status
+ # XXX: I don't like this one: undef could be false positive
+ $target->{'array'}[$index]{status} = $s || 'OK';
+ $target->{'array'}[$index]{name} = $a;
+ next;
+ }
+
+ # logicaldrive 1 (68.3 GB, RAID 1, OK)
+ # capture only status
+ if (my($drive, $size, $raid, $status) = /^\s+logicaldrive (\d+) \(([\d.]+ .B), ([^,]+), ([^\)]+)\)$/) {
+ warn "Index out of bounds" if $index < 0; # XXX should not happen
+
+ # Offset 1 is each logical drive status
+ my $ld = {
+ 'id' => $drive,
+ 'status' => $status,
+ 'size' => $size,
+ 'raid' => $raid,
+ };
+ push(@{$target->{'array'}[$index]{logicaldrives}}, $ld);
+ next;
+ }
+
+ # skip known noise
+ if (
+ /\s+Type "help" for more details/
+ # Controller name: exact match
+ || /^\Q$target->{controller}\E\s/
+ # loose match, some test data seems malformed
+ || / in Slot \d/
+ || /^FIRMWARE UPGRADE REQUIRED:/
+ || /^\s{27}/
+ ) {
+ next;
+ }
+
+ warn "Unhandled: [$_]\n";
+ }
+ $this->unknown unless close $fh;
+
+ push(@luns, $target);
+ }
+
+ return \@luns;
+ }
+
+ # parse hpacucli output into logical structure
+ sub parse {
+ my $this = shift;
+
+ my $targets = $this->scan_targets;
+ if (!$targets) {
+ return $targets;
+ }
+
+ return $this->scan_luns($targets);
+ }
+
+ # format lun (logicaldevice) status
+ # update check status if problems found
+ sub lstatus {
+ my ($this, $ld) = @_;
+
+ my $s = $ld->{status};
+
+ if ($s eq 'OK' or $s eq 'Disabled') {
+ } elsif ($s eq 'Failed' or $s eq 'Interim Recovery Mode') {
+ $this->critical;
+ } elsif ($s eq 'Rebuild' or $s eq 'Recover') {
+ $this->warning;
+ }
+
+ return "LUN$ld->{id}:$s";
+ }
+
+ # format array status
+ # update check status if problems found
+ sub astatus {
+ my ($this, $array) = @_;
+
+ if ($array->{status} ne 'OK') {
+ $this->critical;
+ }
+
+ return "Array $array->{name}($array->{status})";
+ }
+
+ # format controller status
+ # updates check status if problems found
+ sub cstatus {
+ my ($this, $c) = @_;
+ my (@s, $s);
+
+ # always include controller status
+ push(@s, $c->{'Controller Status'} || 'ERROR');
+ if ($c->{'Controller Status'} ne 'OK') {
+ $this->critical;
+ }
+
+ if ($c->{error}) {
+ if ($c->{error} eq E_NO_LOGICAL_DEVS) {
+ $this->noraid;
+ push(@s, 'Not configured');
+ } else {
+ $this->unknown;
+ push(@s, $c->{error});
+ }
+ }
+
+ # print those only if not ok and configured
+ if (($s = $c->{'Cache Status'}) && $s !~ /^(OK|Not Configured)/) {
+ push(@s, "Cache: $s");
+ $this->critical;
+ }
+ if (($s = $c->{'Battery/Capacitor Status'}) && $s !~ /^(OK|Not Configured)/) {
+ push(@s, "Battery: $s");
+ $this->critical;
+ }
+
+ # start with identifyier
+ my $name = $c->{chassisname} || $c->{controller};
+
+ return $name . '[' . join(', ', @s) . ']';
+ }
+
+ sub check {
+ my $this = shift;
+
+ my $ctrls = $this->parse;
+ unless ($ctrls) {
+ $this->warning->message("No Controllers were found on this machine");
+ return;
+ }
+
+ my @status;
+ foreach my $ctrl (@$ctrls) {
+ my @astatus;
+ foreach my $array (@{$ctrl->{array}}) {
+ my @lstatus;
+ foreach my $ld (@{$array->{logicaldrives}}) {
+ push(@lstatus, $this->lstatus($ld));
+ }
+ push(@astatus, $this->astatus($array). '['. join(',', @lstatus). ']');
+ }
+ my $cstatus = $this->cstatus($ctrl);
+ $cstatus .= ': '. join(', ', @astatus) if @astatus;
+ push(@status, $cstatus);
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::hpssacli;
+
+ # This plugin extends hpacucli plugin,
+ # with the only difference that different program name will be used.
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli';
+ use strict;
+ use warnings;
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ips.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::ips;
+
+ # Serveraid IPS
+ # Tested on IBM xSeries 346 servers with Adaptec ServeRAID 7k controllers.
+ # The ipssend version was v7.12.14.
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(ipssend);
+ }
+
+ sub commands {
+ {
+ 'list logical drive' => ['-|', '@CMD', 'GETCONFIG', '1', 'LD'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd getconfig 1 LD"
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $n;
+ my $fh = $this->cmd('list logical drive');
+ while (<$fh>) {
+ if (/drive number (\d+)/i){
+ $n = $1;
+ next;
+ }
+
+ next unless $n;
+ next unless $this->valid($n);
+ next unless (my($s, $c) = /Status .*: (\S+)\s+(\S+)/);
+
+ if ($c =~ /SYN|RBL/i) { # resynching
+ $this->resync;
+ } elsif ($c !~ /OKY/i) { # not OK
+ $this->critical;
+ }
+
+ push(@status, "$n:$s");
+ }
+ close $fh;
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::lsraid;
+
+ # Linux, software RAID
+ # Broken: missing test data
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'list' => ['-|', '@CMD', '-A', '-p'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -A -p"
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $fh = $this->cmd('list');
+ while (<$fh>) {
+ next unless (my($n, $s) = m{/dev/(\S+) \S+ (\S+)});
+ next unless $this->valid($n);
+ if ($s =~ /good|online/) {
+ # no worries
+ } elsif ($s =~ /sync/) {
+ $this->warning;
+ } else {
+ $this->critical;
+ }
+ push(@status, "$n:$s");
+ }
+ close $fh;
+
+ return unless @status;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsscsi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'lsscsi list' => ['-|', '@CMD', '-g'],
+ }
+ }
+
+ # lists contoller devices (type=storage)
+ # this will fail (return empty list) if sg module is not present
+ # return /dev/sgX nodes
+ sub list_sg {
+ my $this = shift;
+
+ my @scan = $this->scan;
+
+ my @devs = map { $_->{sgnode} } grep { $_->{type} eq 'storage' && $_->{sgnode} ne '-' } @scan;
+ return wantarray ? @devs : \@devs;
+ }
+
+ # list disk nodes one for each controller
+ # return /dev/sdX nodes
+ sub list_dd {
+ my $this = shift;
+
+ my @scan = $this->scan;
+ my @devs = map { $_->{devnode} } grep { $_->{type} eq 'disk' && $_->{devnode} ne '-' && $_->{sgnode} } @scan;
+ return wantarray ? @devs : \@devs;
+ }
+
+ # scan lsscsi output
+ sub scan {
+ my $this = shift;
+
+ # cache inside single run
+ return wantarray ? @{$this->{sdevs}} : $this->{sdevs} if $this->{sdevs};
+
+ # Scan such output:
+ # [0:0:0:0] disk HP LOGICAL VOLUME 3.00 /dev/sda /dev/sg0
+ # [0:3:0:0] storage HP P410i 3.00 - /dev/sg1
+ # or without sg driver:
+ # [0:0:0:0] disk HP LOGICAL VOLUME 3.00 /dev/sda -
+ # [0:3:0:0] storage HP P410i 3.00 - -
+
+ my $fh = $this->cmd('lsscsi list');
+ my @sdevs;
+ while (<$fh>) {
+ chop;
+ if (my($hctl, $type, $vendor, $model, $rev, $devnode, $sgnode) = m{^
+ \[([\d:]+)\] # SCSI Controller, SCSI bus, SCSI target, and SCSI LUN
+ \s+(\S+) # type
+ \s+(\S+) # vendor
+ \s+(.*?) # model, match everything as it may contain spaces
+ \s+(\S+) # revision
+ \s+((?:/dev/\S+|-)) # /dev node
+ \s+((?:/dev/\S+|-)) # /dev/sg node
+ }x) {
+ push(@sdevs, {
+ 'hctl' => $hctl,
+ 'type' => $type,
+ 'vendor' => $vendor,
+ 'model' => $model,
+ 'rev' => $rev,
+ 'devnode' => $devnode,
+ 'sgnode' => $sgnode,
+ });
+ }
+ }
+ close $fh;
+
+ $this->{sdevs} = \@sdevs;
+ return wantarray ? @sdevs : \@sdevs;
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsvg.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::lsvg;
+
+ # AIX LVM
+ # Status: broken (no test data)
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'lsvg' => ['-|', '@CMD'],
+ 'lsvg list' => ['-|', '@CMD', '-l', '$vg'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -l *",
+ )
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my @vg;
+ my $fh = $this->cmd('lsvg');
+ while (<$fh>) {
+ chomp;
+ push @vg, $_;
+ }
+ close $fh;
+
+ foreach my $vg (@vg) {
+ next unless $this->valid($vg); # skip entire VG
+
+ my $fh = $this->cmd('lsvg list', { '$vg' => $vg });
+
+ while (<$fh>) {
+ my @f = split /\s/;
+ my ($n, $s) = ($f[0], $f[5]);
+ next if (!$this->valid($n) or !$s);
+ next if ($f[3] eq $f[2]); # not a mirrored LV
+
+ if ($s =~ m#open/(\S+)#i) {
+ $s = $1;
+ if ($s ne 'syncd') {
+ $this->critical;
+ }
+ push(@status, "lvm:$n:$s");
+ }
+ }
+ close $fh;
+ }
+
+ return unless @status;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mdstat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::mdstat;
+
+ # Linux Multi-Device (md)
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub commands {
+ {
+ 'mdstat' => ['<', '/proc/mdstat'],
+ }
+ }
+
+ sub active {
+ my ($this) = @_;
+ # easy way out. no /proc/mdstat
+ return 0 unless -e $this->{commands}{mdstat}[1];
+
+ # extra check if mdstat is empty
+ my @md = $this->parse;
+ return $#md >= 0;
+ }
+
+ sub parse {
+ my $this = shift;
+
+ my (@md, %md);
+ my $fh = $this->cmd('mdstat');
+ my $arr_checking = 0;
+ while (<$fh>) {
+ chomp;
+
+ # skip first line
+ next if (/^Personalities : /);
+
+ # kernel-3.0.101/drivers/md/md.c, md_seq_show
+ # md1 : active raid1 sdb2[0] sda2[1]
+ if (my($dev, $active, $ro, $rest) = m{^
+ (\S+)\s+:\s+ # mdname
+ (\S+)\s+ # active: "inactive", "active"
+ (\((?:auto-)?read-only\)\s+)? # readonly
+ (.+) # personality name + disks
+ }x) {
+ my @parts = split /\s/, $rest;
+ my $re = qr{^
+ (\S+) # devname
+ (?:\[(\d+)\]) # desc_nr
+ (?:\((.)\))? # flags: (W|F|S) - WriteMostly, Faulty, Spare
+ $}x;
+ my @disks = ();
+ my $personality;
+ while (my($disk) = pop @parts) {
+ last if !$disk;
+ if ($disk !~ $re) {
+ $personality = $disk;
+ last;
+ }
+ my($dev, $number, $flags) = $disk =~ $re;
+ push(@disks, {
+ 'dev' => $dev,
+ 'number' => int($number),
+ 'flags' => $flags || '',
+ });
+ }
+
+ die "Unexpected parse" if @parts;
+
+ # first line resets %md
+ %md = (dev => $dev, personality => $personality, readonly => $ro, active => $active, disks => [ @disks ]);
+
+ next;
+ }
+
+ # variations:
+ #" 8008320 blocks [2/2] [UU]"
+ #" 58291648 blocks 64k rounding" - linear
+ #" 5288 blocks super external:imsm"
+ #" 20969472 blocks super 1.2 512k chunks"
+ #
+ # Metadata version:
+ # This is one of
+ # - 'none' for arrays with no metadata (good luck...)
+ # - 'external' for arrays with externally managed metadata,
+ # - or N.M for internally known formats
+ #
+ if (my($b, $mdv, $status) = m{^
+ \s+(\d+)\sblocks\s+ # blocks
+ # metadata version
+ (super\s(?:
+ (?:\d+\.\d+) | # N.M
+ (?:external:\S+) |
+ (?:non-persistent)
+ ))?\s*
+ (.+) # mddev->pers->status (raid specific)
+ $}x) {
+ # linux-2.6.33/drivers/md/dm-raid1.c, device_status_char
+ # A => Alive - No failures
+ # D => Dead - A write failure occurred leaving mirror out-of-sync
+ # S => Sync - A sychronization failure occurred, mirror out-of-sync
+ # R => Read - A read failure occurred, mirror data unaffected
+ # U => for the rest
+ my ($s) = $status =~ /\s+\[([ADSRU_]+)\]/;
+
+ $md{status} = $s || '';
+ $md{blocks} = int($b);
+ $md{md_version} = $mdv;
+
+ # if external try to parse dev
+ if ($mdv) {
+ ($md{md_external}) = $mdv =~ m{external:(\S+)};
+ }
+ next;
+ }
+
+ # linux-2.6.33/drivers/md/md.c, md_seq_show
+ if (my($action) = m{(resync=(?:PENDING|DELAYED))}) {
+ $md{resync_status} = $action;
+ next;
+ }
+ # linux-2.6.33/drivers/md/md.c, status_resync
+ # [==>..................] resync = 13.0% (95900032/732515712) finish=175.4min speed=60459K/sec
+ # [=>...................] check = 8.8% (34390144/390443648) finish=194.2min speed=30550K/sec
+ if (my($action, $perc, $eta, $speed) = m{(resync|recovery|reshape)\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
+ $md{resync_status} = "$action:$perc $speed ETA: $eta";
+ next;
+ } elsif (($perc, $eta, $speed) = m{check\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
+ $md{check_status} = "check:$perc $speed ETA: $eta";
+ $arr_checking = 1;
+ next;
+ }
+
+ # we need empty line denoting end of one md
+ next unless /^\s*$/;
+
+ next unless $this->valid($md{dev});
+
+ push(@md, { %md } ) if %md;
+ }
+ close $fh;
+
+ # One of the arrays is in checking state, which could be because there is a scheduled sync of all MD arrays
+ # In such a case, all of the arrays are scheduled to by checked, but only one of them is actually running the check
+ # while the others are in "resync=DELAYED" state.
+ # We don't want to receive notifications in such case, so we check for this particular case here
+ if ($arr_checking && scalar(@md) >= 2) {
+ foreach my $dev (@md) {
+ if ($dev->{resync_status} && $dev->{resync_status} eq "resync=DELAYED") {
+ delete $dev->{resync_status};
+ $dev->{check_status} = "check=DELAYED";
+ }
+ }
+ }
+
+ return wantarray ? @md : \@md;
+ }
+
+ sub check {
+ my $this = shift;
+
+ my (@status);
+ my @md = $this->parse;
+
+ my @spare_options = ();
+
+ @spare_options = split(/\,/, $this->{options}{mdstat_spare_count})
+ if (exists $this->{options}{mdstat_spare_count});
+
+ foreach (@md) {
+ my %md = %$_;
+
+ # common status
+ my $size = $this->format_bytes($md{blocks} * 1024);
+ my $personality = $md{personality} ? " $md{personality}" : "";
+ my $s = "$md{dev}($size$personality):";
+
+ # failed disks
+ my @fd = map { $_->{dev} } grep { $_->{flags} =~ /F/ } @{$md{disks}};
+ # spare disks
+ my @sd = map { $_->{dev} } grep { $_->{flags} =~ /S/ } @{$md{disks}};
+
+ my $spare_count = 0;
+ OPTION_LOOP:
+ {
+ foreach my $i (0 .. $#spare_options)
+ {
+ my ($disk, $value) = split(/:/, $spare_options[$i]);
+ for(@md)
+ {
+ if ($md{dev} eq $disk)
+ {
+ $spare_count = $value;
+ splice(@spare_options, $i, 1);
+ last OPTION_LOOP;
+ }
+ }
+ }
+ }
+
+ # raid0 is just there or its not. raid0 can't degrade.
+ # same for linear, no $md_status available
+ if ($personality =~ /linear|raid0/) {
+ $s .= "OK";
+
+ } elsif ($md{resync_status}) {
+ $this->resync;
+ $s .= "$md{status} ($md{resync_status})";
+
+ } elsif ($md{check_status}) {
+ $this->check_status;
+ $s .= "$md{status} ($md{check_status})";
+
+ } elsif ($md{status} =~ /_/) {
+ $this->critical;
+ my $fd = join(',', @fd);
+ $s .= "F:$fd:$md{status}";
+
+ } elsif (@fd > 0) {
+ # FIXME: this is same as above?
+ $this->warning;
+ $s .= "hot-spare failure:". join(",", @fd) .":$md{status}";
+ } elsif (@sd < $spare_count)
+ {
+ $this->warning;
+ $s .= "Array ".$md{dev}." should have ".$spare_count." spares, but has only ".(0+@sd)." spares";
+ } else {
+ $s .= "$md{status}";
+ }
+ push(@status, $s);
+ }
+
+ if (scalar @spare_options > 0)
+ {
+ $this->critical;
+ foreach (@spare_options)
+ {
+ my ($disk, $value) = split(/:/, $_);
+ my $s = "$disk is defined in spare_count option but could not be found!";
+ push(@status, $s);
+ }
+ }
+
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::megacli;
+
+ # MegaRAID SAS 8xxx controllers
+ # based on info from here:
+ # http://www.bxtra.net/Articles/2008-09-16/Dell-Perc6i-RAID-Monitoring-Script-using-MegaCli-LSI-CentOS-52-64-bits
+ # TODO: http://www.techno-obscura.com/~delgado/code/check_megaraid_sas
+ # TODO: process several adapters
+ # TODO: process drive temperatures
+ # TODO: check error counts
+ # TODO: hostspare information
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(MegaCli64 MegaCli megacli);
+ }
+
+ sub commands {
+ {
+ 'pdlist' => ['-|', '@CMD', '-PDList', '-aALL', '-NoLog'],
+ 'ldinfo' => ['-|', '@CMD', '-LdInfo', '-Lall', '-aALL', '-NoLog'],
+ 'battery' => ['-|', '@CMD', '-AdpBbuCmd', '-GetBbuStatus', '-aALL', '-NoLog'],
+ }
+ }
+
+ # TODO: process from COMMANDS
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -PDList -aALL -NoLog",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -LdInfo -Lall -aALL -NoLog",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpBbuCmd -GetBbuStatus -aALL -NoLog",
+ );
+ }
+
+ # parse physical devices
+ sub parse_pd {
+ my $this = shift;
+
+ my (@pd, %pd);
+ my $rc = -1;
+ my $fh = $this->cmd('pdlist');
+ while (<$fh>) {
+ if (my($s) = /Device Id: (\S+)/) {
+ push(@pd, { %pd }) if %pd;
+ %pd = ( dev => $s, state => undef, name => undef, predictive => undef );
+ next;
+ }
+
+ if (my($s) = /Firmware state: (.+)/) {
+ # strip the extra state:
+ # 'Hotspare, Spun Up'
+ # 'Hotspare, Spun down'
+ # 'Online, Spun Up'
+ # 'Online, Spun Up'
+ # 'Online, Spun down'
+ # 'Unconfigured(bad)'
+ # 'Unconfigured(good), Spun Up'
+ # 'Unconfigured(good), Spun down'
+ $s =~ s/,.+//;
+ $pd{state} = $s;
+
+ if (defined($pd{predictive})) {
+ $pd{state} = $pd{predictive};
+ }
+ next;
+ }
+
+ if (my($s) = /Predictive Failure Count: (\d+)/) {
+ if ($s > 0) {
+ $pd{predictive} = 'Predictive';
+ }
+ next;
+ }
+
+ if (my($s) = /Inquiry Data: (.+)/) {
+ # trim some spaces
+ $s =~ s/\s+/ /g; $s =~ s/^\s+|\s+$//g;
+ $pd{name} = $s;
+ next;
+ }
+
+ if (my($s) = /Exit Code: (\d+x\d+)/) {
+ $rc = hex($s);
+ }
+ else {
+ $rc = 0;
+ }
+ }
+ push(@pd, { %pd }) if %pd;
+
+ $this->critical unless close $fh;
+ $this->critical if $rc;
+
+ return \@pd;
+ }
+
+ sub parse_ld {
+ my $this = shift;
+
+ my (@ld, %ld);
+ my $rc = -1;
+ my $fh = $this->cmd('ldinfo');
+ while (<$fh>) {
+ if (my($drive_id, $target_id) = /Virtual (?:Disk|Drive)\s*:\s*(\d+)\s*\(Target Id:\s*(\d+)\)/i) {
+ push(@ld, { %ld }) if %ld;
+ # Default to DriveID:TargetID in case no Name is given ...
+ %ld = ( name => "DISK$drive_id.$target_id", state => undef );
+ next;
+ }
+
+ if (my($name) = /Name\s*:\s*(\S+)/) {
+ # Add a symbolic name, if given
+ $ld{name} = $name;
+ next;
+ }
+
+ if (my($s) = /Virtual Drive Type\s*:\s*(\S+)/) {
+ $ld{type} = $s;
+ next;
+ }
+
+ if (my($s) = /State\s*:\s*(\S+)/) {
+ $ld{state} = $s;
+ next;
+ }
+
+ if (my($s) = /Default Cache Policy\s*:\s*(.+)/) {
+ $ld{default_cache} = [split /,\s*/, $s];
+ next;
+ }
+
+ if (my($s) = /Current Cache Policy\s*:\s*(.+)/) {
+ $ld{current_cache} = [split /,\s*/, $s];
+ next;
+ }
+
+ if (my($s) = /Exit Code: (\d+x\d+)/) {
+ $rc = hex($s);
+ } else {
+ $rc = 0;
+ }
+ }
+ push(@ld, { %ld }) if %ld;
+
+ $this->critical unless close $fh;
+ $this->critical if $rc;
+
+ return \@ld;
+ }
+
+ # check battery
+ sub parse_bbu {
+ my $this = shift;
+
+ return undef unless $this->bbu_monitoring;
+
+ my %default_bbu = (
+ name => undef, state => '???', charging_status => '???', missing => undef,
+ learn_requested => undef, replacement_required => undef,
+ learn_cycle_requested => undef, learn_cycle_active => '???',
+ pack_will_fail => undef, temperature => undef, temperature_state => undef,
+ voltage => undef, voltage_state => undef
+ );
+
+ my (@bbu, %bbu);
+ my $fh = $this->cmd('battery');
+ while (<$fh>) {
+ # handle when bbu status get gives an error. see issue #32
+ if (my($s) = /Get BBU Status Failed/) {
+ last;
+ }
+
+ if (my($s) = /BBU status for Adapter: (.+)/) {
+ push(@bbu, { %bbu }) if %bbu;
+ %bbu = %default_bbu;
+ $bbu{name} = $s;
+ next;
+ }
+ #=cut
+ # according to current sample data, Battery State never has value
+ if (my($s) = /Battery State\s*: ?(.*)/i) {
+ if (!$s) { $s = 'Faulty'; };
+ $bbu{state} = $s;
+ next;
+ }
+ #=cut
+ if (my($s) = /Charging Status\s*: (\w*)/) {
+ $bbu{charging_status} = $s;
+ next;
+ }
+ if (my($s) = /Battery Pack Missing\s*: (\w*)/) {
+ $bbu{missing} = $s;
+ next;
+ }
+ if (my($s) = /Battery Replacement required\s*: (\w*)/) {
+ $bbu{replacement_required} = $s;
+ next;
+ }
+ if (my($s) = /Learn Cycle Requested\s*: (\w*)/) {
+ $bbu{learn_cycle_requested} = $s;
+ next;
+ }
+ if (my($s) = /Learn Cycle Active\s*: (\w*)/) {
+ $bbu{learn_cycle_active} = $s;
+ next;
+ }
+ if (my($s) = /Pack is about to fail & should be replaced\s*: (\w*)/) {
+ $bbu{pack_will_fail} = $s;
+ next;
+ }
+ # Temperature: 18 C
+ if (my($s) = /Temperature: (\d+) C/) {
+ $bbu{temperature} = $s;
+ next;
+ }
+ # Temperature : OK
+ if (my($s) = / Temperature\s*: (\w*)/) {
+ $bbu{temperature_state} = $s;
+ next;
+ }
+ # Voltage: 4074 mV
+ if (my($s) = /Voltage: (\d+) mV/) {
+ $bbu{voltage} = $s;
+ next;
+ }
+ # Voltage : OK
+ if (my($s) = /Voltage\s*: (\w*)/) {
+ $bbu{voltage_state} = $s;
+ next;
+ }
+
+ }
+ $this->critical unless close $fh;
+
+ push(@bbu, { %bbu }) if %bbu;
+
+ return \@bbu;
+ }
+
+ sub parse {
+ my $this = shift;
+
+ my $pd = $this->parse_pd;
+ my $ld = $this->parse_ld;
+ my $bbu = $this->parse_bbu;
+
+ my @devs = @$pd if $pd;
+ my @vols = @$ld if $ld;
+ my @bats = @$bbu if $bbu;
+
+ return {
+ logical => $ld,
+ physical => $pd,
+ battery => $bbu,
+ };
+ }
+
+ sub check {
+ my $this = shift;
+
+ my $c = $this->parse;
+
+ my @vstatus;
+ foreach my $vol (@{$c->{logical}}) {
+ # skip CacheCade for now. #91
+ if ($vol->{type} && $vol->{type} eq 'CacheCade') {
+ next;
+ }
+
+ push(@vstatus, sprintf "%s:%s", $vol->{name}, $vol->{state});
+ if ($vol->{state} ne 'Optimal') {
+ $this->critical;
+ }
+
+ # check cache policy, #65
+ my @wt = grep { /WriteThrough/ } @{$vol->{current_cache}};
+ if (@wt) {
+ my @default = grep { /WriteThrough/ } @{$vol->{default_cache}};
+ # alert if WriteThrough is configured in default
+ $this->cache_fail unless @default;
+ push(@vstatus, "WriteCache:DISABLED");
+ }
+ }
+
+ my %dstatus;
+ foreach my $dev (@{$c->{physical}}) {
+ if ($dev->{state} eq 'Online' || $dev->{state} eq 'Hotspare' || $dev->{state} eq 'Unconfigured(good)' || $dev->{state} eq 'JBOD') {
+ push(@{$dstatus{$dev->{state}}}, sprintf "%02d", $dev->{dev});
+
+ } elsif ($dev->{state} eq 'Predictive') {
+ $this->warning;
+ push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
+ } else {
+ $this->critical;
+ # TODO: process other statuses
+ push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
+ }
+ }
+
+ my (%bstatus, @bpdata, @blongout);
+ foreach my $bat (@{$c->{battery}}) {
+ if ($bat->{state} !~ /^(Operational|Optimal)$/) {
+ # BBU learn cycle in progress.
+ if ($bat->{charging_status} =~ /^(Charging|Discharging)$/ && $bat->{learn_cycle_active} eq 'Yes') {
+ $this->bbulearn;
+ } else {
+ $this->critical;
+ }
+ }
+ if ($bat->{missing} ne 'No') {
+ $this->critical;
+ }
+ if ($bat->{replacement_required} ne 'No') {
+ $this->critical;
+ }
+ if (defined($bat->{pack_will_fail}) && $bat->{pack_will_fail} ne 'No') {
+ $this->critical;
+ }
+ if ($bat->{temperature_state} ne 'OK') {
+ $this->critical;
+ }
+ if ($bat->{voltage_state} ne 'OK') {
+ $this->critical;
+ }
+
+ # Short output.
+ #
+ # CRITICAL: megacli:[Volumes(1): NoName:Optimal; Devices(2): 06,07=Online; Batteries(1): 0=Non Operational]
+ push(@{$bstatus{$bat->{state}}}, sprintf "%d", $bat->{name});
+ # Performance data.
+ # Return current battery temparature & voltage.
+ #
+ # Battery0=18;4074
+ push(@bpdata, sprintf "Battery%s_T=%s;;;; Battery%s_V=%s;;;;", $bat->{name}, $bat->{temperature}, $bat->{name}, $bat->{voltage});
+
+ # Long output.
+ # Detailed plugin output.
+ #
+ # Battery0:
+ # - State: Non Operational
+ # - Missing: No
+ # - Replacement required: Yes
+ # - About to fail: No
+ # - Temperature: OK (18 °C)
+ # - Voltage: OK (4015 mV)
+ push(@blongout, join("\n", grep {/./}
+ "Battery$bat->{name}:",
+ " - State: $bat->{state}",
+ " - Charging status: $bat->{charging_status}",
+ " - Learn cycle requested: $bat->{learn_cycle_requested}",
+ " - Learn cycle active: $bat->{learn_cycle_active}",
+ " - Missing: $bat->{missing}",
+ " - Replacement required: $bat->{replacement_required}",
+ defined($bat->{pack_will_fail}) ? " - About to fail: $bat->{pack_will_fail}" : "",
+ " - Temperature: $bat->{temperature_state} ($bat->{temperature} C)",
+ " - Voltage: $bat->{voltage_state} ($bat->{voltage} mV)",
+ ));
+ }
+
+ my @cstatus;
+ push(@cstatus, 'Volumes(' . ($#{$c->{logical}} + 1) . '): ' . join(',', @vstatus));
+ push(@cstatus, 'Devices(' . ($#{$c->{physical}} + 1) . '): ' . $this->join_status(\%dstatus));
+ push(@cstatus, 'Batteries(' . ($#{$c->{battery}} + 1) . '): ' . $this->join_status(\%bstatus)) if @{$c->{battery}};
+ my @status = join('; ', @cstatus);
+
+ my @pdata;
+ push(@pdata,
+ join('\n', @bpdata)
+ );
+ my @longout;
+ push(@longout,
+ join('\n', @blongout)
+ );
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join(' ', @status));
+ $this->perfdata(join(' ', @pdata));
+ $this->longoutput(join(' ', @longout));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaide.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::megaide;
+
+ # MegaIDE RAID controller
+ # Status: BROKEN: no test data
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub sudo {
+ my ($this) = @_;
+ my $cat = $this->which('cat');
+
+ "CHECK_RAID ALL=(root) NOPASSWD: $cat /proc/megaide/0/status";
+ }
+
+ sub check {
+ my $this = shift;
+ my $fh;
+
+ # status messages pushed here
+ my @status;
+
+ foreach my $f (</proc/megaide/*/status>) { # / silly comment to fix vim syntax hilighting
+ if (-r $f) {
+ open $fh, '<', $f or next;
+ =cut
+ } else {
+ my @CMD = ($cat, $f);
+ unshift(@CMD, $sudo) if $> and $sudo;
+ open($fh , '-|', @CMD) or next;
+ =cut
+ }
+ while (<$fh>) {
+ next unless (my($s, $n) = /Status\s*:\s*(\S+).*Logical Drive.*:\s*(\d+)/i);
+ next unless $this->valid($n);
+ if ($s ne 'ONLINE') {
+ $this->critical;
+ push(@status, "$n:$s");
+ } else {
+ push(@status, "$n:$s");
+ }
+ last;
+ }
+ close $fh;
+ }
+
+ return unless @status;
+
+ $this->message(join(' ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::megaraid;
+
+ # MegaRAID
+ # Status: BROKEN: no test data
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub sudo {
+ my ($this) = @_;
+ my $cat = $this->which('cat');
+
+ my @sudo;
+ foreach my $mr (</proc/mega*/*/raiddrives*>) {
+ push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cat $mr") if -d $mr;
+ }
+
+ @sudo;
+ }
+
+ sub check {
+ my $this = shift;
+ # status messages pushed here
+ my @status;
+
+ foreach my $f (</proc/megaraid/*/raiddrives*>) { # vim/
+ my $fh;
+ if (-r $f) {
+ open $fh, '<', $f or next;
+ =cut
+ } else {
+ my @CMD = ($cat, $f);
+ unshift(@CMD, $sudo) if $> and $sudo;
+ open($fh , '-|', @CMD) or next;
+ =cut
+ }
+ my ($n) = $f =~ m{/proc/megaraid/([^/]+)};
+ while (<$fh>) {
+ if (my($s) = /logical drive\s*:\s*\d+.*, state\s*:\s*(\S+)/i) {
+ if ($s ne 'optimal') {
+ $this->critical;
+ }
+ push(@status, "$n: $s");
+ last;
+ }
+ }
+ close $fh;
+ }
+
+ return unless @status;
+
+ $this->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megarc.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::megarc;
+
+ # LSI MegaRaid or Dell Perc arrays
+ # Check the status of all arrays on all Lsi MegaRaid controllers on the local
+ # machine. Uses the megarc program written by Lsi to get the status of all
+ # arrays on all local Lsi MegaRaid controllers.
+ #
+ # check designed from check_lsi_megaraid:
+ # http://www.monitoringexchange.org/cgi-bin/page.cgi?g=Detailed/2416.html;d=1
+ # Perl port (check_raid) by Elan Ruusamäe.
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'controller list' => ['-|', '@CMD', '-AllAdpInfo', '-nolog'],
+ 'controller config' => ['-|', '@CMD', '-dispCfg', '-a$controller', '-nolog'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -AllAdpInfo -nolog",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -dispCfg -a* -nolog",
+ );
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ # get controllers
+ my $fh = $this->cmd('controller list');
+ my @lines = <$fh>;
+ close $fh;
+
+ if ($lines[11] =~ /No Adapters Found/) {
+ $this->warning;
+ $this->message("No LSI adapters were found on this machine");
+ return;
+ }
+
+ my @c;
+ foreach (@lines[12..$#lines]) {
+ if (my ($id) = /^\s*(\d+)/) {
+ push(@c, int($id));
+ }
+ }
+ unless (@c) {
+ $this->warning;
+ $this->message("No LSI adapters were found on this machine");
+ return;
+ }
+
+ foreach my $c (@c) {
+ my $fh = $this->cmd('controller config', { '$controller' => $c });
+ my (%d, %s, $ld);
+ while (<$fh>) {
+ # Logical Drive : 0( Adapter: 0 ): Status: OPTIMAL
+ if (my($d, $s) = /Logical Drive\s+:\s+(\d+).+Status:\s+(\S+)/) {
+ $ld = $d;
+ $s{$ld} = $s;
+ next;
+ }
+ # SpanDepth :01 RaidLevel: 5 RdAhead : Adaptive Cache: DirectIo
+ if (my($s) = /RaidLevel:\s+(\S+)/) {
+ $d{$ld} = $s if defined $ld;
+ next;
+ }
+ }
+ close $fh;
+
+ # now process the details
+ unless (keys %d) {
+ $this->message("No arrays found on controller $c");
+ $this->warning;
+ return;
+ }
+
+ while (my($d, $s) = each %s) {
+ if ($s ne 'OPTIMAL') {
+ # The Array number here is incremented by one because of the
+ # inconsistent way that the LSI tools count arrays.
+ # This brings it back in line with the view in the bios
+ # and from megamgr.bin where the array counting starts at
+ # 1 instead of 0
+ push(@status, "Array ".(int($d) + 1)." status is ".$s{$d}." (Raid-$s on adapter $c)");
+ $this->critical;
+ next;
+ }
+
+ push(@status, "Logical Drive $d: $s");
+ }
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/metastat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::metastat;
+
+ # Solaris, software RAID
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'metastat' => ['>&2', '@CMD'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd"
+ }
+
+ sub active {
+ my ($this) = @_;
+
+ # return if parent said NO
+ my $res = $this->SUPER::active(@_);
+ return $res unless $res;
+
+ my $output = $this->get_metastat;
+ return !!@$output;
+ }
+
+ sub get_metastat {
+ my $this = shift;
+
+ # cache inside single run
+ return $this->{output} if defined $this->{output};
+
+ my $fh = $this->cmd('metastat');
+ my @data;
+ while (<$fh>) {
+ chomp;
+ last if /there are no existing databases/;
+ push(@data, $_);
+ }
+
+ return $this->{output} = \@data;
+ }
+
+ sub check {
+ my $this = shift;
+
+ my ($d, $sd);
+
+ # status messages pushed here
+ my @status;
+ my $output = $this->get_metastat;
+
+ foreach (@$output) {
+ if (/^(\S+):/) { $d = $1; $sd = ''; next; }
+ if (/Submirror \d+:\s+(\S+)/) { $sd = $1; next; }
+ if (/Device:\s+(\S+)/) { $sd = $1; next; }
+ if (my($s) = /State: (\S.+\w)/) {
+ if ($sd and $this->valid($sd) and $this->valid($d)) {
+ if ($s =~ /Okay/i) {
+ # no worries...
+ } elsif ($s =~ /Resync/i) {
+ $this->resync;
+ } else {
+ $this->critical;
+ }
+ push(@status, "$d:$sd:$s");
+ }
+ }
+
+ if (defined $d && $d =~ /hsp/) {
+ if (/(c[0-9]+t[0-9]+d[0-9]+s[0-9]+)\s+(\w+)/) {
+ $sd = $1;
+ my $s = $2;
+ $this->warning if ($s !~ /Available/);
+ push(@status, "$d:$sd:$s");
+ }
+ }
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mpt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::mpt;
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(mpt-status);
+ }
+
+ sub commands {
+ {
+ 'get_controller_no' => ['-|', '@CMD', '-p'],
+ 'status' => ['-|', '@CMD', '-i', '$id'],
+ 'sync status' => ['-|', '@CMD', '-n'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [0-9]",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [1-9][0-9]",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -n",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd -p",
+ );
+ }
+
+ sub active {
+ my ($this) = @_;
+
+ # return if parent said NO
+ my $res = $this->SUPER::active(@_);
+ return $res unless $res;
+
+ # there should be a controller. #95
+ my $id = $this->get_controller;
+ return defined($id);
+ }
+
+ sub get_controller {
+ my $this = shift;
+
+ # controller ID may be given on the command line
+ my $id = $this->{options}{'mpt-id'};
+ if (!$id) {
+
+ # get controller from mpt-status -p
+ my $fh = $this->cmd('get_controller_no');
+ while (<$fh>) {
+ chomp;
+ if (/^Found.*id=(\d{1,2}),.*/) {
+ $id = $1;
+ last;
+ }
+ }
+ close $fh;
+ }
+
+ return $id;
+ }
+
+ sub parse {
+ my ($this, $id) = @_;
+
+ my (%ld, %pd);
+
+ my $fh = $this->cmd('status', { '$id' => $id });
+
+ my %VolumeTypesHuman = (
+ IS => 'RAID-0',
+ IME => 'RAID-1E',
+ IM => 'RAID-1',
+ );
+
+ while (<$fh>) {
+ chomp;
+ # mpt-status.c __print_volume_classic
+ # ioc0 vol_id 0 type IM, 2 phy, 136 GB, state OPTIMAL, flags ENABLED
+ if (my($vioc, $vol_id, $type, $disks, $vol_size, $vol_state, $vol_flags) =
+ /^ioc(\d+)\s+ vol_id\s(\d+)\s type\s(\S+),\s (\d+)\sphy,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
+ $ld{$vol_id} = {
+ ioc => int($vioc),
+ vol_id => int($vol_id),
+ # one of: IS, IME, IM
+ vol_type => $type,
+ raid_level => $VolumeTypesHuman{$type},
+ phy_disks => int($disks),
+ size => int($vol_size),
+ # one of: OPTIMAL, DEGRADED, FAILED, UNKNOWN
+ status => $vol_state,
+ # array of: ENABLED, QUIESCED, RESYNC_IN_PROGRESS, VOLUME_INACTIVE or NONE
+ flags => [ split ' ', $vol_flags ],
+ };
+ }
+
+ # ./include/lsi/mpi_cnfg.h
+ # typedef struct _RAID_PHYS_DISK_INQUIRY_DATA
+ # {
+ # U8 VendorID[8]; /* 00h */
+ # U8 ProductID[16]; /* 08h */
+ # U8 ProductRevLevel[4]; /* 18h */
+ # U8 Info[32]; /* 1Ch */
+ # }
+ # mpt-status.c __print_physdisk_classic
+ # ioc0 phy 0 scsi_id 0 IBM-ESXS PYH146C3-ETS10FN RXQN, 136 GB, state ONLINE, flags NONE
+ # ioc0 phy 0 scsi_id 1 ATA ST3808110AS J , 74 GB, state ONLINE, flags NONE
+ # ioc0 phy 0 scsi_id 1 ATA Hitachi HUA72101 AJ0A, 931 GB, state ONLINE, flags NONE
+ elsif (my($pioc, $num, $phy_id, $vendor, $prod_id, $rev, $size, $state, $flags) =
+ /^ioc(\d+)\s+ phy\s(\d+)\s scsi_id\s(\d+)\s (.{8})\s+(.{16})\s+(.{4})\s*,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
+ $pd{$num} = {
+ ioc => int($pioc),
+ num => int($num),
+ phy_id => int($phy_id),
+ vendor => $vendor,
+ prod_id => $prod_id,
+ rev => $rev,
+ size => int($size),
+ # one of: ONLINE, MISSING, NOT_COMPATIBLE, FAILED, INITIALIZING, OFFLINE_REQUESTED, FAILED_REQUESTED, OTHER_OFFLINE, UNKNOWN
+ status => $state,
+ # array of: OUT_OF_SYNC, QUIESCED or NONE
+ flags => [ split ' ', $flags ],
+ };
+ } else {
+ warn "mpt unparsed: [$_]";
+ $this->unknown;
+ }
+ }
+ close $fh;
+
+ # extra parse, if mpt-status has -n flag, can process also resync state
+ # TODO: if -n becames default can do this all in one run
+ my $resyncing = grep {/RESYNC_IN_PROGRESS/} map { @{$_->{flags}} } values %ld;
+ if ($resyncing) {
+ my $fh = $this->cmd('sync status');
+ while (<$fh>) {
+ if (/^ioc:\d+/) {
+ # ignore
+ }
+ # mpt-status.c GetResyncPercentage
+ # scsi_id:0 70%
+ elsif (my($scsi_id, $percent) = /^scsi_id:(\d+) (\d+)%/) {
+ $pd{$scsi_id}{resync} = int($percent);
+ } else {
+ warn "mpt unparsed: [$_]";
+ $this->unknown;
+ }
+ }
+ close $fh;
+ }
+
+ return {
+ 'logical' => { %ld },
+ 'physical' => { %pd },
+ };
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $id = $this->get_controller;
+ my $status = $this->parse($id);
+
+ # process logical units
+ while (my($d, $u) = each %{$status->{logical}}) {
+ next unless $this->valid($d);
+
+ my $s = $u->{status};
+ if ($s =~ /INITIAL|INACTIVE/) {
+ $this->warning;
+ } elsif ($s =~ /RESYNC/) {
+ $this->resync;
+ } elsif ($s =~ /DEGRADED|FAILED/) {
+ $this->critical;
+ } elsif ($s !~ /ONLINE|OPTIMAL/) {
+ $this->unknown;
+ }
+
+ # FIXME: this resync_in_progress is separate state of same as value in status?
+ if (grep { /RESYNC_IN_PROGRESS/ } @{$u->{flags}}) {
+ # find matching disks
+ my @disks = grep {$_->{ioc} eq $u->{ioc} } values %{$status->{physical}};
+ # collect percent for each disk
+ my @percent = map { $_->{resync}.'%'} @disks;
+ $s .= ' RESYNCING: '.join('/', @percent);
+ }
+ push(@status, "Volume $d ($u->{raid_level}, $u->{phy_disks} disks, $u->{size} GiB): $s");
+ }
+
+ # process physical units
+ while (my($d, $u) = each %{$status->{physical}}) {
+ my $s = $u->{status};
+ # remove uninteresting flags
+ my @flags = grep {!/NONE/} @{$u->{flags}};
+
+ # skip print if nothing in flags and disk is ONLINE
+ next unless @flags and $s eq 'ONLINE';
+
+ $s .= ' ' . join(' ', @flags);
+ push(@status, "Disk $d ($u->{size} GiB):$s");
+ $this->critical;
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mvcli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::mvcli;
+
+ # Status: BROKEN: not finished
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'mvcli blk' => ['-|', '@CMD', 'info', '-o', 'blk'],
+ 'mvcli vd' => ['-|', '@CMD', 'info', '-o', 'vd'],
+ 'mvcli smart' => ['-|', '@CMD', 'smart', '-p', '0'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd"
+ }
+
+ sub parse_blk {
+ my $this = shift;
+
+ my (@blk, %blk);
+
+ my $fh = $this->cmd('mvcli blk');
+ while (<$fh>) {
+ chomp;
+
+ if (my ($blk_id) = /Block id:\s+(\d+)/) {
+ # block id is first item, so push previous item to list
+ if (%blk) {
+ push(@blk, { %blk });
+ %blk = ();
+ }
+ $blk{blk_id} = int($blk_id);
+ } elsif (my($pd_id) = /PD id:\s+(\d+)/) {
+ $blk{pd_id} = int($pd_id);
+ } elsif (my($vd_id) = /VD id:\s+(\d+)/) {
+ $blk{vd_id} = int($vd_id);
+ } elsif (my($bstatus) = /Block status:\s+(.+)/) {
+ $blk{block_status} = $bstatus;
+ } elsif (my($size) = /Size:\s+(\d+) K/) {
+ $blk{size} = int($size);
+ } elsif (my($offset) = /Starting offset:\s+(\d+) K/) {
+ $blk{offset} = int($offset);
+ } else {
+ # warn "[$_]\n";
+ }
+ }
+ close $fh;
+
+ if (%blk) {
+ push(@blk, { %blk });
+ }
+
+ return wantarray ? @blk : \@blk;
+ }
+
+ sub parse_vd {
+ my $this = shift;
+
+ my (@vd, %vd);
+ my ($name, $value);
+
+ my $fh = $this->cmd('mvcli vd');
+ while (<$fh>) {
+ chomp;
+
+ if (/^$/
+ || /----+/
+ || /SG driver version/
+ || /Virtual Disk Information/
+ ) {
+ next;
+ }
+
+ unless (($name, $value) = /^(.+):\s+(.+)$/) {
+ warn "UNPARSED: [$_]";
+ next;
+ }
+
+ if ($name eq 'id') {
+ # id is first item, so push previous item to list
+ if (%vd) {
+ push(@vd, { %vd });
+ %vd = ();
+ }
+ }
+
+ $vd{$name} = $value;
+ }
+ close $fh;
+
+ if (%vd) {
+ push(@vd, { %vd });
+ }
+
+ return wantarray ? @vd : \@vd;
+ }
+
+ sub parse_smart {
+ my ($this, $blk) = @_;
+
+ # collect pd numbers
+ my @pd = map { $_->{pd_id} } @$blk;
+
+ my %smart;
+ foreach my $pd (@pd) {
+ my $fh = $this->cmd('mvcli smart', { '$pd' => $pd });
+ my %attrs = ();
+ while (<$fh>) {
+ chomp;
+
+ if (my($id, $name, $current, $worst, $treshold, $raw, $status) = /
+ ([\dA-F]{2})\s+ # id
+ (.*?)\s+ # name
+ (\d+)\s+ # current
+ (\d+)\s+ # worst
+ (\d+)\s+ # treshold
+ ([\dA-F]{12}) # raw
+ (?:\s+(\w+))? # status
+ /x) {
+ my %attr = ();
+ $attr{id} = $id;
+ $attr{name} = $name;
+ $attr{current} = int($current);
+ $attr{worst} = int($worst);
+ $attr{treshold} = int($treshold);
+ $attr{raw} = $raw;
+ $attr{status} = $status || undef;
+ $attrs{$id} = { %attr };
+ } else {
+ # warn "[$_]\n";
+ }
+ }
+
+ $smart{$pd} = { %attrs };
+ }
+
+ return \%smart;
+ }
+
+ sub parse {
+ my $this = shift;
+
+ my $blk = $this->parse_blk;
+ my $vd = $this->parse_vd;
+ my $smart = $this->parse_smart($blk);
+
+ return {
+ blk => $blk,
+ vd => $vd,
+ smart => $smart,
+ };
+ }
+
+ sub check {
+ my $this = shift;
+
+ my @status;
+ my $c = $this->parse;
+
+ foreach my $vd (@{$c->{vd}}) {
+ my $size = $this->format_bytes($this->parse_bytes($vd->{size}));
+ if ($vd->{status} ne 'functional') {
+ $this->critical;
+ }
+ push(@status, "VD($vd->{name} $vd->{'RAID mode'} $size): $vd->{status}");
+ }
+
+ return unless @status;
+
+ # denote this plugin as ran ok
+ $this->ok;
+
+ $this->message(join('; ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/sas2ircu.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::sas2ircu;
+
+ # LSI SAS-2 controllers using the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU)
+ # Based on the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU) User Guide
+ # http://www.lsi.com/downloads/Public/Host%20Bus%20Adapters/Host%20Bus%20Adapters%20Common%20Files/SAS_SATA_6G_P12/SAS2IRCU_User_Guide.pdf
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'controller list' => ['-|', '@CMD', 'LIST'],
+ 'controller status' => ['-|', '@CMD', '$controller', 'STATUS'],
+ 'device status' => ['-|', '@CMD', '$controller', 'DISPLAY'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd LIST",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd * STATUS",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd * DISPLAY",
+ );
+ }
+
+ # detect controllers for sas2ircu
+ sub detect {
+ my $this = shift;
+
+ my @ctrls;
+ my $fh = $this->cmd('controller list');
+
+ my $success = 0;
+ my $state="";
+ my $noctrlstate="No Controllers";
+ while (<$fh>) {
+ chomp;
+
+ # Adapter Vendor Device SubSys SubSys
+ # Index Type ID ID Pci Address Ven ID Dev ID
+ # ----- ------------ ------ ------ ----------------- ------ ------
+ # 0 SAS2008 1000h 72h 00h:03h:00h:00h 1028h 1f1eh
+ if (my($c) = /^\s*(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/) {
+ push(@ctrls, $c);
+ }
+ $success = 1 if /SAS2IRCU: Utility Completed Successfully/;
+
+ # handle the case where there's no hardware present.
+ # when there is no controller, we get
+ # root@i41:/tmp$ /usr/sbin/sas2ircudsr LIST
+ # LSI Corporation SAS2 IR Configuration Utility.
+ # Version 18.00.00.00 (2013.11.18)
+ # Copyright (c) 2009-2013 LSI Corporation. All rights reserved.
+
+ # SAS2IRCU: MPTLib2 Error 1
+ # root@i41:/tmp$ echo $?
+ # 1
+
+ if (/SAS2IRCU: MPTLib2 Error 1/) {
+ $state = $noctrlstate;
+ $success = 1 ;
+ }
+
+ }
+
+ unless (close $fh) {
+ #sas2ircu exits 1 (but close exits 256) when we close fh if we have no controller, so handle that, too
+ if ($? != 256 && $state eq $noctrlstate) {
+ $this->critical;
+ }
+ }
+ unless ($success) {
+ $this->critical;
+ }
+
+ return wantarray ? @ctrls : \@ctrls;
+ }
+
+ sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
+ sub ltrim { my $s = shift; $s =~ s/^\s+//; return $s };
+ sub rtrim { my $s = shift; $s =~ s/\s+$//; return $s };
+
+ sub check {
+ my $this = shift;
+
+ my @ctrls = $this->detect;
+
+ my @status;
+ my $numvols=0;
+ # determine the RAID states of each controller
+ foreach my $c (@ctrls) {
+ my $fh = $this->cmd('controller status', { '$controller' => $c });
+
+ my $novolsstate="No Volumes";
+ my $state;
+ my $success = 0;
+ while (<$fh>) {
+ chomp;
+
+ # match adapter lines
+ if (my($s) = /^\s*Volume state\s*:\s*(\w+)\s*$/) {
+ $state = $s;
+ $numvols++;
+ if ($state ne "Optimal") {
+ $this->critical;
+ }
+ }
+ $success = 1 if /SAS2IRCU: Utility Completed Successfully/;
+
+ ##handle the case where there are no volumes configured
+ #
+ # SAS2IRCU: there are no IR volumes on the controller!
+ # SAS2IRCU: Error executing command STATUS.
+
+ if (/SAS2IRCU: there are no IR volumes on the controller/
+ or /The STATUS command is not supported by the firmware currently loaded on controller/
+ ) {
+ # even though this isn't the last line, go ahead and set success.
+ $success = 1;
+ $state = $novolsstate;
+ }
+
+ }
+
+ unless (close $fh) {
+ #sas2ircu exits 256 when we close fh if we have no volumes, so handle that, too
+ if ($? != 256 && $state eq $novolsstate) {
+ $this->critical;
+ $state = $!;
+ }
+ }
+
+ unless ($success) {
+ $this->critical;
+ $state = "SAS2IRCU Unknown exit";
+ }
+
+ unless ($state) {
+ $state = "Unknown Error";
+ }
+
+ my $finalvolstate=$state;
+ #push(@status, "ctrl #$c: $numvols Vols: $state");
+
+
+ ##### now look at the devices.
+ # Device is a Hard disk
+ # Enclosure # : 2
+ # Slot # : 0
+ # SAS Address : 500065b-3-6789-abe0
+ # State : Ready (RDY)
+ # Size (in MB)/(in sectors) : 3815447/7814037167
+ # Manufacturer : ATA
+ # Model Number : ST4000DM000-1F21
+ # Firmware Revision : CC52
+ # Serial No : S30086G4
+ # GUID : 5000c5006d27b344
+ # Protocol : SATA
+ # Drive Type : SATA_HDD
+
+ $fh = $this->cmd('device status', { '$controller' => $c });
+ $state="";
+ $success = 0;
+ my $enc="";
+ my $slot="";
+ my @data;
+ my $device="";
+ my $numslots=0;
+ my $finalstate;
+ my $finalerrors="";
+
+ while (my $line = <$fh>) {
+ chomp $line;
+ # Device is a Hard disk
+ # Device is a Hard disk
+ # Device is a Enclosure services device
+ #
+ #lets make sure we're only checking disks. we dont support other devices right now
+ if ("$line" eq 'Device is a Hard disk') {
+ $device='disk';
+ } elsif ($line =~ /^Device/) {
+ $device='other';
+ }
+
+ if ("$device" eq 'disk') {
+ if ($line =~ /Enclosure #|Slot #|State /) {
+ #find our enclosure #
+ if ($line =~ /^ Enclosure # /) {
+ @data = split /:/, $line;
+ $enc=trim($data[1]);
+ #every time we hit a new enclosure line, reset our state and slot
+ undef $state;
+ undef $slot;
+ }
+ #find our slot #
+ if ($line =~ /^ Slot # /) {
+ @data = split /:/, $line;
+ $slot=trim($data[1]);
+ $numslots++
+ }
+ #find our state
+ if ($line =~ /^ State /) {
+ @data = split /:/, $line;
+ $state=ltrim($data[1]);
+
+ #for test
+ #if ($numslots == 10 ) { $state='FREDFISH';}
+
+ #when we get a state, test on it and report it..
+ if ($state =~ /Optimal|Ready/) {
+ #do nothing at the moment.
+ } else {
+ $this->critical;
+ $finalstate=$state;
+ $finalerrors="$finalerrors ERROR:Ctrl$c:Enc$enc:Slot$slot:$state";
+ }
+ }
+ }
+ }
+
+ if ($line =~ /SAS2IRCU: Utility Completed Successfully/) {
+ $success = 1;
+ }
+
+ } #end while
+
+
+ unless (close $fh) {
+ $this->critical;
+ $state = $!;
+ }
+
+ unless ($success) {
+ $this->critical;
+ $state = "SAS2IRCU Unknown exit";
+ }
+
+ unless ($state) {
+ $state = "Unknown Error";
+ }
+
+ unless($finalstate) {
+ $finalstate=$state;
+ }
+
+ #per controller overall report
+ #push(@status, ":$numslots Drives:$finalstate:$finalerrors");
+ push(@status, "ctrl #$c: $numvols Vols: $finalvolstate: $numslots Drives: $finalstate:$finalerrors:");
+
+ }
+
+ ##if we didn't get a status out of the controllers and an empty ctrls array, we must not have any.
+ unless (@status && @ctrls) {
+ push(@status, "No Controllers");
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/smartctl.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
+
+ # NOTE: not standalone plugin
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ use strict;
+ use warnings;
+
+ sub program_names {
+ shift->{name};
+ }
+
+ sub commands {
+ {
+ 'smartctl' => ['-|', '@CMD', '-H', '$dev', '$diskopt$disk'],
+ }
+ }
+
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ # nothing, as not standalone plugin yet
+ }
+
+ # check for -H parameter for physical disks
+ # this is currently called out from cciss plugin
+ # @param device list
+ # device list being an array of:
+ # - device to check (/dev/cciss/c0d0)
+ # - disk options (-dcciss)
+ # - disk number (0..15)
+ sub check_devices {
+ my $this = shift;
+ my @devs = @_;
+
+ unless (@devs) {
+ $this->warning;
+ $this->message("No devices to check");
+ return;
+ }
+
+ # status message for devs, latter just joined for shorter messages
+ my %status;
+
+ foreach my $ref (@devs) {
+ my ($dev, $diskopt, $disk) = @$ref;
+
+ my $fh = $this->cmd('smartctl', { '$dev' => $dev, '$diskopt' => $diskopt => '$disk' => $disk });
+ while (<$fh>) {
+ chomp;
+
+ # SMART Health Status: HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE [asc=5d, ascq=10]
+ if (my($s, $sc) = /SMART Health Status: (.*?)(\s*\[asc=\w+, ascq=\w+\])?$/) {
+ # use shorter output, message that hpacucli would use
+ if ($s eq 'HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE') {
+ $s = 'Predictive Failure';
+ }
+
+ if ($s eq 'Predictive Failure') {
+ $this->warning;
+ } elsif ($s !~ '^OK') {
+ $this->critical;
+ }
+ push(@{$status{$s}}, $dev.'#'.$disk);
+ }
+ }
+ close($fh);
+ }
+
+ return unless %status;
+
+ $this->ok->message($this->join_status(\%status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::ssacli;
+
+ # This plugin extends hpacucli plugin,
+ # with the only difference that different program name will be used.
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli';
+ use strict;
+ use warnings;
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/tw_cli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI';
+ package App::Monitoring::Plugin::CheckRaid::Plugins::tw_cli;
+
+ # tw_cli(8) is a Command Line Interface Storage Management Software for
+ # AMCC/3ware ATA RAID Controller(s).
+ # Owned by LSI currently: https://en.wikipedia.org/wiki/3ware
+ #
+ # http://www.cyberciti.biz/files/tw_cli.8.html
+
+ use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
+ # not yet, see:
+ # https://github.com/glensc/nagios-plugin-check_raid/pull/131#issuecomment-189957806
+ #use Date::Parse qw(strptime);
+ #use DateTime;
+ use strict;
+ use warnings;
+
+ sub program_names {
+ qw(tw_cli-9xxx tw_cli tw-cli);
+ }
+
+ sub commands {
+ {
+ 'show' => ['-|', '@CMD', 'show'], # This is 'info' output AND enclosure summary
+ 'unitstatus' => ['-|', '@CMD', 'info', '$controller', 'unitstatus'],
+ 'drivestatus' => ['-|', '@CMD', 'info', '$controller', 'drivestatus'],
+ 'bbustatus' => ['-|', '@CMD', 'info', '$controller', 'bbustatus'],
+ 'enc_show_all' => ['-|', '@CMD', '$encid', 'show all'],
+ }
+ }
+
+ sub sudo {
+ my ($this, $deep) = @_;
+ # quick check when running check
+ return 1 unless $deep;
+
+ my $cmd = $this->{program};
+ (
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd info",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd info *",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd show",
+ "CHECK_RAID ALL=(root) NOPASSWD: $cmd * show all",
+ );
+ }
+
+ sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
+
+ sub to_i {
+ my $i = shift;
+ return $i if $i !~ /^\d+$/;
+ return int($i);
+ }
+
+ sub parse {
+ my $this = shift;
+
+ my (%c);
+ # scan controllers
+ my ($sect_ctl, $sect_enc) = 0;
+ my $fh = $this->cmd('show');
+ while (<$fh>) {
+ # Section break
+ if(/^\s*$/) { ($sect_ctl,$sect_enc) = (0,0); next; };
+ # header line
+ if(/^-+$/) { next; };
+ # section headers: Controller
+ # Ctl Model Ports Drives Units NotOpt RRate VRate BBU
+ # Ctl Model (V)Ports Drives Units NotOpt RRate VRate BBU
+ if (/^Ctl.*Model.*Rate/) { $sect_ctl = 1; next; };
+ # section headers: Enclosure
+ # Encl Slots Drives Fans TSUnits Ctls
+ # Encl Slots Drives Fans TSUnits PSUnits
+ # Enclosure Slots Drives Fans TSUnits PSUnits Alarms
+ if (/^Encl.*Drive/) { $sect_enc = 1; next; };
+
+ # controller section
+ if ($sect_ctl and my($ctl, $model, $ports, $drives, $units, $notopt, $rrate, $vrate, $bbu) = m{^
+ (c\d+)\s+ # Controller
+ (\S+)\s+ # Model
+ (\d+)\s+ # (V)Ports
+ (\d+)\s+ # Drives
+ (\d+)\s+ # Units
+ (\d+)\s+ # NotOpt: Not Optional
+ # Not Optimal refers to any state except OK and VERIFYING.
+ # Other states include INITIALIZING, INIT-PAUSED,
+ # REBUILDING, REBUILD-PAUSED, DEGRADED, MIGRATING,
+ # MIGRATE-PAUSED, RECOVERY, INOPERABLE, and UNKNOWN.
+ (\d+)\s+ # RRate: Rebuild Rate
+ (\d+|-)\s+ # VRate: Verify Rate
+ (\S+|-)? # BBU
+ }x) {
+ $c{$ctl} = {
+ model => $model,
+ ports => int($ports),
+ drives => int($drives),
+ units => int($units),
+ optimal => int(!$notopt),
+ rrate => int($rrate),
+ vrate => to_i($vrate),
+ bbu => $bbu,
+ };
+ }
+ # enclosure section
+ if ($sect_enc and my($enc, $slots, $drives, $fans, $tsunits, $psunits, $alarms) = m{^
+ ((?:/c\d+)?/e\d+)\s+ # Controller, Enclosure
+ # 9650SE reports enclosures as /eX
+ # 9690SA+ report enclosures as /cX/eX
+ (\d+)\s+ # Slots
+ (\d+)\s+ # Drives
+ (\d+)\s+ # Fans
+ (\d+)\s+ # TSUnits - Temp Sensor
+ (\d+)?\s+ # PSUnits - Power Supply, not always present!
+ (\d+)?\s+ # Controller OR Alarms, not always present!
+ }x) {
+ # This will be filled in later by the enclosure pass
+ $c{$enc} = {};
+ }
+ }
+ close $fh;
+
+ # no controllers? skip early
+ return unless %c;
+
+ for my $c (grep /^\/?c\d+$/, keys %c) {
+ # get each unit on controllers
+ $fh = $this->cmd('unitstatus', { '$controller' => $c });
+ while (<$fh>) {
+ if (my($u, $type, $status, $p_rebuild, $p_vim, $strip, $size, $cache, $avrify) = m{^
+ (u\d+)\s+ # Unit
+ (\S+)\s+ # UnitType
+ (\S+)\s+ # Status
+ (\S+)\s+ # %RCmpl: The %RCompl reports the percent completion
+ # of the unit's Rebuild, if this task is in progress.
+ (\S+)\s+ # %V/I/M: The %V/I/M reports the percent completion
+ # of the unit's Verify, Initialize, or Migrate,
+ # if one of these are in progress.
+ (\S+)\s+ # Strip
+ (\S+)\s+ # Size(GB)
+ (\S+)\s+ # Cache
+ (\S+) # AVrify
+ }x) {
+ $c{$c}{unitstatus}{$u} = {
+ type => $type,
+ status => $status,
+ rebuild_percent => $p_rebuild,
+ vim_percent => $p_vim,
+ strip => $strip,
+ size => $size,
+ cache => $cache,
+ avrify => $avrify,
+ };
+ next;
+ }
+
+ if (m{^u\d+}) {
+ $this->unknown;
+ warn "unparsed: [$_]";
+ }
+ }
+ close $fh;
+
+ # get individual disk status
+ $fh = $this->cmd('drivestatus', { '$controller' => $c });
+ # common regexp
+ my $r = qr{^
+ (p\d+)\s+ # Port
+ (\S+)\s+ # Status
+ (\S+)\s+ # Unit
+ ([\d.]+\s[TG]B|-)\s+ # Size
+ }x;
+
+ while (<$fh>) {
+ # skip empty line
+ next if /^$/;
+
+ # Detect version
+ if (/^Port/) {
+ # <=9.5.1: Blocks Serial
+ $r .= qr{
+ (\S+)\s+ # Blocks
+ (.+) # Serial
+ }x;
+ next;
+ } elsif (/^VPort/) {
+ # >=9.5.2: Type Phy Encl-Slot Model
+ $r .= qr{
+ (\S+)\s+ # Type
+ (\S+)\s+ # Phy
+ (\S+)\s+ # Encl-Slot
+ (.+) # Model
+ }x;
+ next;
+ }
+
+ if (my($port, $status, $unit, $size, @rest) = ($_ =~ $r)) {
+ # do not report disks not present
+ # tw_cli 9.5.2 and above do not list these at all
+ next if $status eq 'NOT-PRESENT';
+ my %p;
+
+ if (@rest <= 2) {
+ my ($blocks, $serial) = @rest;
+ %p = (
+ blocks => to_i($blocks),
+ serial => trim($serial),
+ );
+ } else {
+ my ($type, $phy, $encl, $model) = @rest;
+ %p = (
+ type => $type,
+ phy => to_i($phy),
+ encl => $encl,
+ model => $model,
+ );
+ }
+
+ $c{$c}{drivestatus}{$port} = {
+ status => $status,
+ unit => $unit,
+ size => $size,
+ %p,
+ };
+
+ next;
+ }
+
+ if (m{^p\d+}) {
+ $this->unknown;
+ warn "unparsed: [$_]";
+ }
+ }
+ close $fh;
+
+ # get BBU status
+ $fh = $this->cmd('bbustatus', { '$controller' => $c });
+ while (<$fh>) {
+ next if /^$/;
+ next if /^-{10,}$/;
+ if (my($bbu, $onlinestate, $bbuready, $status, $volt, $temp, $hours, $lastcaptest) = m{^
+ (bbu\d*)\s+ # BBU, possibly numbered (RARE)
+ (\S+)\s+ # OnlineState
+ (\S+)\s+ # BBUReady
+ (\S+)\s+ # Status
+ (\S+)\s+ # Volt
+ (\S+)\s+ # Temp
+ (\d+)\s+ # Hours
+ (\S+)\s+ # LastCapTest
+ }x) {
+ $c{$c}{bbustatus}{$bbu} = {
+ OnlineState => $onlinestate,
+ BBUReady => $bbuready,
+ Status => $status,
+ Volt => $volt,
+ Temp => $temp,
+ Hours => $hours,
+ LastCapTest => $lastcaptest,
+ };
+ next;
+ }
+ if (m{^\S+\+}) {
+ $this->unknown;
+ warn "unparsed: [$_]";
+ }
+ }
+ close $fh;
+ }
+
+ # Do enclosures now, which might NOT be attached the controllers
+ # WARNING: This data section has not always been consistent over versions of tw_cli.
+ # You should try to use the newest version of the driver, as it deliberately uses the newer style of output
+ # rather than the output for the tw_cli versions released with 9550SX/9590SE/9650SE
+ for my $encid (grep /\/e\d+$/, keys %c) {
+ $fh = $this->cmd('enc_show_all', { '$encid' => $encid });
+ # Variable names chose to be 'sect_XXX' explicitly.
+ # This says what section we are in right now
+ my ($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
+ # This says what section we have seen, it gets reset at the start of each enclosure block;
+ my ($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
+ while (<$fh>) {
+ # Skip the header break lines
+ next if /^-+$/;
+ # and the partial indented header that is ABOVE the fan header
+ next if /^\s+-+Speed-+\s*$/;
+ # If the line is blank, reset our section headers
+ if(/^\s*$/){
+ ($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
+ # If we have SEEN all of the sections, also reset the seen markers
+ # This is needed when the output contains multiple enclosures
+ if($sect_enc and $sect_fan and $sect_tmp and $sect_psu and $sect_slt and $sect_alm) {
+ ($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
+ }
+ next;
+ }
+ if (/^Encl.*Status/) { $seen_enc = $sect_enc = 1; next; }
+ if (/^Fan.*Status/) { $seen_fan = $sect_fan = 1; next; }
+ if (/^TempSensor.*Status/) { $seen_tmp = $sect_tmp = 1; next; }
+ if (/^PowerSupply.*Status/) { $seen_psu = $sect_psu = 1; next; }
+ if (/^Slot.*Status/) { $seen_slt = $sect_slt = 1; next; }
+ if (/^Alarm.*Status/) { $seen_alm = $sect_alm = 1; next; }
+ # ------ Start of new enclosure
+ if ($sect_enc and my($encl, $encl_status) = m{^
+ ((?:/c\d+)?/e\d+)\s+ # Controller, Enclosure
+ (\S+)\s+ # Status
+ }x) {
+ # This is a special case for the test environment, as it is
+ # hard to feed MULTI command inputs into the mock.
+ if($ENV{'HARNESS_ACTIVE'} and $encl ne $encid) {
+ $encid = $encl;
+ }
+ $c{$encid} = {
+ encl => $encl, # Dupe of $encid to verify
+ status => $encl_status,
+ # This is the top-level enclosure object
+ fans => {},
+ tempsensor => {},
+ powersupply => {},
+ slot => {},
+ alarm => {},
+ };
+ }
+ # ------ Fans
+ elsif ($sect_fan and my($fan, $fan_status, $fan_state, $fan_step, $fan_rpm, $fan_identify) = m{^
+ (fan\S+)\s+ # Fan
+ (\S+)\s+ # Status
+ (\S+)\s+ # State
+ (\S+)\s+ # Step
+ (\d+|N/A)\s+ # RPM
+ (\S+)\s+ # Identify
+ }x) {
+ $c{$encid}{fans}{$fan} = {
+ status => $fan_status,
+ state => $fan_state,
+ step => $fan_step,
+ rpm => $fan_rpm,
+ identify => $fan_identify,
+ };
+ next;
+ }
+ # ------ TempSensor
+ elsif ($sect_tmp and my($tmp, $tmp_status, $tmp_temperature, $tmp_identify) = m{^
+ (temp\S+)\s+ # TempSensor
+ (\S+)\s+ # Status
+ (\S+)\s+ # Temperature
+ (\S+)\s+ # Identify
+ }x) {
+ $c{$encid}{tempsensor}{$tmp} = {
+ status => $tmp_status,
+ temperature => $tmp_temperature,
+ identify => $tmp_identify,
+ };
+ next;
+ }
+ # ------ PowerSupply
+ elsif ($sect_psu and my($psu, $psu_status, $psu_state, $psu_voltage, $psu_current, $psu_identify) = m{^
+ ((?:pw|psu)\S+)\s+ # PowerSupply
+ (\S+)\s+ # Status
+ (\S+)\s+ # State
+ (\S+)\s+ # Voltage
+ (\S+)\s+ # Current
+ (\S+)\s+ # Identify
+ }x) {
+ $c{$encid}{powersupply}{$psu} = {
+ status => $psu_status,
+ state => $psu_state,
+ voltage => $psu_voltage,
+ current => $psu_current,
+ identify => $psu_identify,
+ };
+ next;
+ }
+ # ------ Slot
+ elsif ($sect_slt and my($slt, $slt_status, $slt_vport, $slt_identify) = m{^
+ (slo?t\S+)\s+ # Slot
+ (\S+)\s+ # Status
+ (\S+)\s+ # (V)Port
+ (\S+)\s+ # Identify
+ }x) {
+ $c{$encid}{slot}{$slt} = {
+ status => $slt_status,
+ vport => $slt_vport,
+ identify => $slt_identify,
+ };
+ next;
+ }
+ # ------ Alarm
+ elsif ($sect_alm and my($alm, $alm_status, $alm_state, $alm_audibility) = m{^
+ (alm\S+)\s+ # Alarm
+ (\S+)\s+ # Status
+ (\S+)\s+ # State
+ (\S+)\s+ # Audibility
+ }x) {
+ $c{$encid}{alarm}{$alm} = {
+ status => $alm_status,
+ state => $alm_state,
+ audibility => $alm_audibility,
+ };
+ next;
+ }
+ # ---- End of known data
+ elsif (m{^\S+\+}) {
+ $this->unknown;
+ warn "unparsed: [$_]";
+ }
+
+ }
+ close $fh;
+ }
+
+ return \%c;
+ }
+
+ sub check {
+ my $this = shift;
+
+ # status messages pushed here
+ my @status;
+
+ my $c = $this->parse;
+ if (!$c) {
+ $this->unknown;
+ $this->message("No Adapters were found on this machine");
+ }
+
+ # process each controller
+ for my $cid (sort grep !/e\d+/, keys %$c) {
+ my $c = $c->{$cid};
+ my @cstatus;
+
+ for my $uid (sort keys %{$c->{unitstatus}}) {
+ my $u = $c->{unitstatus}->{$uid};
+ my $s = $u->{status};
+
+ if ($s =~ /INITIALIZING|MIGRATING/) {
+ $this->warning;
+ $s .= " $u->{vim_percent}";
+
+ } elsif ($s eq 'VERIFYING') {
+ $this->check_status;
+ $s .= " $u->{vim_percent}";
+
+ } elsif ($s eq 'REBUILDING') {
+ $this->resync;
+ $s .= " $u->{rebuild_percent}";
+
+ } elsif ($s eq 'DEGRADED') {
+ $this->critical;
+
+ } elsif ($s ne 'OK') {
+ $this->critical;
+
+ }
+
+ my @ustatus = $s;
+
+ # report cache, no checking
+ if ($u->{cache} && $u->{cache} ne '-') {
+ push(@ustatus, "Cache:$u->{cache}");
+ }
+
+ push(@status, "$cid($c->{model}): $uid($u->{type}): ".join(', ', @ustatus));
+ }
+
+ # check individual disk status
+ my %ds;
+ foreach my $p (sort { $a cmp $b } keys %{$c->{drivestatus}}) {
+ my $d = $c->{drivestatus}->{$p};
+ my $ds = $d->{status};
+ if ($ds eq 'VERIFYING') {
+ $this->check_status;
+ } elsif ($ds ne 'OK') {
+ $this->critical;
+ }
+
+ if ($d->{unit} eq '-') {
+ $ds = 'SPARE';
+ }
+
+ push(@{$ds{$ds}}, $p);
+ }
+ push(@status, "Drives($c->{drives}): ".$this->join_status(\%ds)) if %ds;
+
+ # check BBU, but be prepared that BBU status might not report anything
+ if ($this->{options}{bbu_monitoring} && $c->{bbu} && $c->{bbu} ne '-') {
+ # On old controllers, bbustatus did not exist; and the only BBU status
+ # you got was on the controller listing.
+ if(scalar(keys %{$c->{bbustatus}}) < 1) {
+ $this->critical if $c->{bbu} ne 'OK';
+ push(@status, "BBU: $c->{bbu}");
+ } else {
+ foreach my $bbuid (sort { $a cmp $b } keys %{$c->{bbustatus}}) {
+ my $bat = $c->{bbustatus}->{$bbuid};
+ my $bs = $bat->{Status}; # We might override this later
+ my @batmsg;
+ if($bs eq 'Testing' or $bs eq 'Charging') {
+ $this->bbulearn;
+ } elsif($bs eq 'WeakBat') {
+ # Time to replace your battery
+ $this->warning;
+ } elsif($bs ne 'OK') {
+ $this->critical;
+ }
+ # We do NOT check BBUReady, as it doesn't private granular
+ # info.
+ # Check OnlineState flag as well
+ # A battery can be GOOD, but disabled; this is only reflected in OnlineState.
+ if($bat->{OnlineState} ne 'On') {
+ push @batmsg, 'OnlineStatus='.$bat->{OnlineState};
+ $this->critical;
+ }
+ # Check voltage & temps
+ push @batmsg, 'Volt='.$bat->{Volt};
+ push @batmsg, 'Temp='.$bat->{Temp};
+ if ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
+ $this->critical;
+ } elsif ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
+ $this->warning;
+ }
+ if ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
+ $this->critical;
+ } elsif ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
+ $this->warning;
+ }
+ # Check runtime estimate
+ # Warn if too low
+ my $bbulearn = '';
+ if ($bat->{Hours} ne '-' and int($bat->{Hours}) <= 1) {
+ # TODO: make this configurable before going live
+ #$this->warning;
+ $this->bbulearn;
+ $bbulearn = '/LEARN';
+ }
+ push @batmsg, 'Hours='.$bat->{Hours};
+
+ # Check date of last capacity test
+ if ($bat->{LastCapTest} eq 'xx-xxx-xxxx') {
+ $this->bbulearn;
+ $bbulearn = '/LEARN';
+ } elsif ($bat->{LastCapTest} ne '-') {
+ # TODO: is the short name of month localized by tw_cli?
+ #my ($mday, $mon, $year) = (strptime($bat->{LastCapTest}, '%d-%b-%Y'))[3,4,5];
+ #my $lastcaptest_epoch = DateTime->new(year => $year, month => $mon, day => $mday, hour => 0, minute => 0, second => 0);
+ #my $present_time = time;
+ ## TODO: this value should be configurable before going live, also need to mock system date for testing
+ #if (($present_time-$lastcaptest_epoch) > 86400*365) {
+ # $this->bbulearn;
+ #}
+ }
+ push @batmsg, 'LastCapTest='.$bat->{LastCapTest};
+ my $msg = join(',', @batmsg);
+ my $bbustatus = $bs.$bbulearn;
+ $bbustatus = "$bbuid=$bs" if $bbuid ne 'bbu'; # If we have multiple BBU, specify which one
+ push(@status, "BBU: $bbustatus($msg)");
+ }
+ }
+ }
+ }
+ # process each enclosure
+ for my $eid (sort grep /\/e\d+/, keys %$c) {
+ my $e = $c->{$eid};
+ # If the enclosure command returned nothing, we have no status to
+ # report.
+ next unless defined($e->{status});
+
+ # Something is wrong, but we are not sure what yet.
+ $this->warning unless $e->{status} eq 'OK';
+ my @estatus;
+ for my $fan_id (sort keys %{$e->{fans}}) {
+ my $f = $e->{fans}->{$fan_id};
+ my $s = $f->{status};
+ next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
+ $this->warning if $s ne 'OK';
+ push(@estatus, "$fan_id=$s($f->{rpm})");
+ }
+ for my $tmp_id (sort keys %{$e->{tempsensor}}) {
+ my $t = $e->{tempsensor}->{$tmp_id};
+ my $s = $t->{status};
+ next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
+ $this->warning if $s ne 'OK';
+ $t->{temperature} =~ s/\(\d+F\)//; # get rid of extra units
+ push(@estatus, "$tmp_id=$s($t->{temperature})");
+ }
+ for my $psu_id (sort keys %{$e->{powersupply}}) {
+ my $t = $e->{powersupply}->{$psu_id};
+ my $s = $t->{status};
+ next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
+ $this->warning if $s ne 'OK';
+ push(@estatus, "$psu_id=$s(status=$t->{state},voltage=$t->{voltage},current=$t->{current})");
+ }
+ for my $slot_id (sort keys %{$e->{slot}}) {
+ my $t = $e->{slot}->{$slot_id};
+ my $s = $t->{status};
+ next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
+ $this->warning if $s ne 'OK';
+ push(@estatus, "$slot_id=$s");
+ }
+ for my $alarm_id (sort keys %{$e->{alarm}}) {
+ my $t = $e->{alarm}->{$alarm_id};
+ my $s = $t->{status};
+ next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
+ $this->warning if $s ne 'OK';
+ push(@estatus, "$alarm_id=$s(State=$t->{state},Audibility=$t->{audibility})");
+ }
+ #warn join("\n", @estatus);
+ push(@status, "Enclosure: $eid(".join(',', @estatus).")");
+ }
+
+ return unless @status;
+
+ $this->ok->message(join(', ', @status));
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/SerialLine.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE';
+ package App::Monitoring::Plugin::CheckRaid::SerialLine;
+
+ # Package dealing with connecting to serial line and handling UUCP style locks.
+
+ use Carp;
+ use strict;
+ use warnings;
+
+ sub new {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ my $device = shift;
+
+ my $this = {
+ lockdir => "/var/lock",
+
+ @_,
+
+ lockfile => undef,
+ device => $device,
+ fh => undef,
+ };
+
+ bless($this, $class);
+ }
+
+ sub lock {
+ my $self = shift;
+ # create lock in style: /var/lock/LCK..ttyS0
+ my $device = shift;
+ my ($lockfile) = $self->{device} =~ m#/dev/(.+)#;
+ $lockfile = "$self->{lockdir}/LCK..$lockfile";
+ if (-e $lockfile) {
+ return 0;
+ }
+ open(my $fh, '>', $lockfile) || croak "Can't create lock: $lockfile\n";
+ print $fh $$;
+ close($fh);
+
+ $self->{lockfile} = $lockfile;
+ }
+
+ sub open {
+ my $self = shift;
+
+ $self->lock or return;
+
+ # open the device
+ open(my $fh, '+>', $self->{device}) || croak "Couldn't open $self->{device}, $!\n";
+
+ $self->{fh} = $fh;
+ }
+
+ sub close {
+ my $self = shift;
+ if ($self->{fh}) {
+ close($self->{fh});
+ undef($self->{fh});
+ unlink $self->{lockfile} or carp $!;
+ }
+ }
+
+ sub DESTROY {
+ my $self = shift;
+ $self->close();
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Sudoers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS';
+ package App::Monitoring::Plugin::CheckRaid::Sudoers;
+
+ use App::Monitoring::Plugin::CheckRaid::Utils;
+ use warnings;
+ use strict;
+
+ use Exporter 'import';
+
+ our @EXPORT = qw(sudoers);
+ our @EXPORT_OK = @EXPORT;
+
+ # update sudoers file
+ #
+ # if sudoers config has "#includedir" directive, add file to that dir
+ # otherwise update main sudoers file
+ # @returns true if file was updated
+ sub sudoers {
+ my $dry_run = shift;
+ my @plugins = @_;
+
+ # build values to be added
+ # go over all active plugins
+ my @sudo;
+ foreach my $plugin (@plugins) {
+ # collect sudo rules
+ my @rules = $plugin->sudo(1) or next;
+
+ push(@sudo, @rules);
+ }
+
+ unless (@sudo) {
+ warn "Your configuration does not need to use sudo, sudoers not updated\n";
+ return 0;
+ }
+
+ my @rules = join "\n", (
+ "",
+ # setup alias, so we could easily remove these later by matching lines with 'CHECK_RAID'
+ # also this avoids installing ourselves twice.
+ "# Lines matching CHECK_RAID added by $0 -S on ". scalar localtime,
+ "User_Alias CHECK_RAID=nagios, icinga, sensu",
+ "Defaults:CHECK_RAID !requiretty",
+
+ # actual rules from plugins
+ join("\n", @sudo),
+ "",
+ );
+
+ if ($dry_run) {
+ warn "Content to be inserted to sudo rules:\n";
+ warn "--- sudoers ---\n";
+ print @rules;
+ warn "--- sudoers ---\n";
+ return 0;
+ }
+
+ my $sudoers = find_file('/usr/local/etc/sudoers', '/etc/sudoers');
+ my $visudo = which('visudo');
+
+ die "Unable to find sudoers file.\n" unless -f $sudoers;
+ die "Unable to write to sudoers file '$sudoers'.\n" unless -w $sudoers;
+ die "visudo program not found\n" unless -x $visudo;
+
+ # parse sudoers file for "#includedir" directive
+ my $sudodir = parse_sudoers_includedir($sudoers);
+ if ($sudodir) {
+ # sudo will read each file in /etc/sudoers.d, skipping file names that
+ # end in ~ or contain a . character to avoid causing problems with
+ # package manager or editor temporary/backup files
+ $sudoers = "$sudodir/check_raid";
+ }
+
+ warn "Updating file $sudoers\n";
+
+ # NOTE: secure as visudo itself: /etc is root owned
+ my $new = $sudoers.".new.".$$;
+
+ # setup to have sane perm for new sudoers file
+ umask(0227);
+
+ open my $fh, '>', $new or die $!;
+
+ # insert old sudoers
+ if (!$sudodir) {
+ open my $old, '<', $sudoers or die $!;
+ while (<$old>) {
+ print $fh $_;
+ }
+ close $old or die $!;
+ }
+
+ # insert the rules
+ print $fh @rules;
+ close $fh;
+
+ # validate sudoers
+ system($visudo, '-c', '-f', $new) == 0 or unlink($new),exit $? >> 8;
+
+ # check if they differ
+ if (filediff($sudoers, $new)) {
+ # use the new file
+ rename($new, $sudoers) or die $!;
+ warn "$sudoers file updated.\n";
+ return 1;
+ }
+
+ warn "$sudoers file not changed.\n";
+ unlink($new);
+ return 0;
+ }
+
+ # return first "#includedir" directive from $sudoers file
+ sub parse_sudoers_includedir {
+ my ($sudoers) = @_;
+
+ open my $fh, '<', $sudoers or die "Can't open: $sudoers: $!";
+ while (<$fh>) {
+ if (my ($dir) = /^#includedir\s+(.+)$/) {
+ return $dir;
+ }
+ }
+ close $fh or die $!;
+
+ return undef;
+ }
+
+ # return FALSE if files are identical
+ # return TRUE if files are different
+ # return TRUE if any of the files is missing
+ sub filediff {
+ my ($file1, $file2) = @_;
+
+ # return TRUE if neither of them exist
+ return 1 unless -f $file1;
+ return 1 unless -f $file2;
+
+ my $f1 = cat($file1);
+ my $f2 = cat($file2);
+
+ # wipe comments
+ $f1 =~ s/^#.+$//m;
+ $f2 =~ s/^#.+$//m;
+
+ # return TRUE if they differ
+ return $f1 ne $f2;
+ }
+
+ # get contents of a file
+ sub cat {
+ my ($file) = @_;
+ open(my $fh, '<', $file) or die "Can't open $file: $!";
+ local $/ = undef;
+ local $_ = <$fh>;
+ close($fh) or die $!;
+
+ return $_;
+ }
+
+ # find first existing file from list of file paths
+ sub find_file {
+ for my $file (@_) {
+ return $file if -f $file;
+ }
+ return undef;
+ }
+APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS
+
+$fatpacked{"App/Monitoring/Plugin/CheckRaid/Utils.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_UTILS';
+ package App::Monitoring::Plugin::CheckRaid::Utils;
+
+ use warnings;
+ use strict;
+ use Exporter 'import';
+
+ our @EXPORT = qw(which find_sudo);
+ our @EXPORT_OK = @EXPORT;
+
+ # registered plugins
+ our @plugins;
+
+ # devices to ignore
+ our @ignore;
+
+ # debug level
+ our $debug = 0;
+
+ # paths for which()
+ our @paths = split /:/, $ENV{'PATH'};
+ unshift(@paths, qw(/usr/local/nrpe /usr/local/bin /sbin /usr/sbin /bin /usr/sbin /opt/bin /opt/MegaRAID/MegaCli /usr/StorMan));
+
+ # lookup program from list of possible filenames
+ # search is performed from $PATH plus additional hardcoded @paths
+ # NOTE: we do not check for execute bit as it may fail for non-root. #104
+ sub which {
+ for my $prog (@_) {
+ for my $path (@paths) {
+ return "$path/$prog" if -f "$path/$prog";
+ }
+ }
+ return undef;
+ }
+
+ our @sudo;
+ sub find_sudo {
+ # no sudo needed if already root
+ return [] unless $>;
+
+ # detect once
+ return \@sudo if @sudo;
+
+ my $sudo = which('sudo') or die "Can't find sudo";
+ push(@sudo, $sudo);
+
+ # detect if sudo supports -A, issue #88
+ use IPC::Open3;
+ my $fh;
+ my @cmd = ($sudo, '-h');
+ my $pid = open3(undef, $fh, undef, @cmd) or die "Can't run 'sudo -h': $!";
+ local $/ = undef;
+ local $_ = <$fh>;
+ close($fh) or die $!;
+ # prefer -n to skip password prompt
+ push(@sudo, '-n') if /-n/;
+ # ..if not supported, add -A as well
+ push(@sudo, '-A') if /-A/;
+
+ return \@sudo;
+ }
+
+ 1;
+APP_MONITORING_PLUGIN_CHECKRAID_UTILS
+
+$fatpacked{"Class/Accessor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR';
+ package Class::Accessor;require 5.00502;use strict;$Class::Accessor::VERSION='0.51';sub new {return bless defined $_[1]? {%{$_[1]}}: {},ref $_[0]|| $_[0]}sub mk_accessors {my($self,@fields)=@_;$self->_mk_accessors('rw',@fields)}if (eval {require Sub::Name}){Sub::Name->import}{no strict 'refs';sub import {my ($class,@what)=@_;my$caller=caller;for (@what){if (/^(?:antlers|moose-?like)$/i){*{"${caller}::has"}=sub {my ($f,%args)=@_;$caller->_mk_accessors(($args{is}||"rw"),$f)};*{"${caller}::extends"}=sub {@{"${caller}::ISA"}=@_;unless (grep $_->can("_mk_accessors"),@_){push @{"${caller}::ISA"},$class}};&{"${caller}::extends"}(@{"${caller}::ISA"})}}}sub follow_best_practice {my($self)=@_;my$class=ref$self || $self;*{"${class}::accessor_name_for"}=\&best_practice_accessor_name_for;*{"${class}::mutator_name_for"}=\&best_practice_mutator_name_for}sub _mk_accessors {my($self,$access,@fields)=@_;my$class=ref$self || $self;my$ra=$access eq 'rw' || $access eq 'ro';my$wa=$access eq 'rw' || $access eq 'wo';for my$field (@fields){my$accessor_name=$self->accessor_name_for($field);my$mutator_name=$self->mutator_name_for($field);if($accessor_name eq 'DESTROY' or $mutator_name eq 'DESTROY'){$self->_carp("Having a data accessor named DESTROY in '$class' is unwise.")}if ($accessor_name eq $mutator_name){my$accessor;if ($ra && $wa){$accessor=$self->make_accessor($field)}elsif ($ra){$accessor=$self->make_ro_accessor($field)}else {$accessor=$self->make_wo_accessor($field)}my$fullname="${class}::$accessor_name";my$subnamed=0;unless (defined &{$fullname}){subname($fullname,$accessor)if defined&subname;$subnamed=1;*{$fullname}=$accessor}if ($accessor_name eq $field){my$alias="${class}::_${field}_accessor";subname($alias,$accessor)if defined&subname and not $subnamed;*{$alias}=$accessor unless defined &{$alias}}}else {my$fullaccname="${class}::$accessor_name";my$fullmutname="${class}::$mutator_name";if ($ra and not defined &{$fullaccname}){my$accessor=$self->make_ro_accessor($field);subname($fullaccname,$accessor)if defined&subname;*{$fullaccname}=$accessor}if ($wa and not defined &{$fullmutname}){my$mutator=$self->make_wo_accessor($field);subname($fullmutname,$mutator)if defined&subname;*{$fullmutname}=$mutator}}}}}sub mk_ro_accessors {my($self,@fields)=@_;$self->_mk_accessors('ro',@fields)}sub mk_wo_accessors {my($self,@fields)=@_;$self->_mk_accessors('wo',@fields)}sub best_practice_accessor_name_for {my ($class,$field)=@_;return "get_$field"}sub best_practice_mutator_name_for {my ($class,$field)=@_;return "set_$field"}sub accessor_name_for {my ($class,$field)=@_;return$field}sub mutator_name_for {my ($class,$field)=@_;return$field}sub set {my($self,$key)=splice(@_,0,2);if(@_==1){$self->{$key}=$_[0]}elsif(@_ > 1){$self->{$key}=[@_]}else {$self->_croak("Wrong number of arguments received")}}sub get {my$self=shift;if(@_==1){return$self->{$_[0]}}elsif(@_ > 1){return @{$self}{@_}}else {$self->_croak("Wrong number of arguments received")}}sub make_accessor {my ($class,$field)=@_;return sub {my$self=shift;if(@_){return$self->set($field,@_)}else {return$self->get($field)}}}sub make_ro_accessor {my($class,$field)=@_;return sub {my$self=shift;if (@_){my$caller=caller;$self->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}else {return$self->get($field)}}}sub make_wo_accessor {my($class,$field)=@_;return sub {my$self=shift;unless (@_){my$caller=caller;$self->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return$self->set($field,@_)}}}use Carp ();sub _carp {my ($self,$msg)=@_;Carp::carp($msg || $self);return}sub _croak {my ($self,$msg)=@_;Carp::croak($msg || $self);return}1;
+CLASS_ACCESSOR
+
+$fatpacked{"Class/Accessor/Fast.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FAST';
+ package Class::Accessor::Fast;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Fast::VERSION='0.51';sub make_accessor {my ($class,$field)=@_;eval sprintf q{
+ sub {
+ return $_[0]{%s} if scalar(@_) == 1;
+ return $_[0]{%s} = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]];
+ }
+ },map {perlstring($_)}$field,$field}sub make_ro_accessor {my($class,$field)=@_;eval sprintf q{
+ sub {
+ return $_[0]{%s} if @_ == 1;
+ my $caller = caller;
+ $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s);
+ }
+ },map {perlstring($_)}$field,$field,$class}sub make_wo_accessor {my($class,$field)=@_;eval sprintf q{
+ sub {
+ if (@_ == 1) {
+ my $caller = caller;
+ $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s);
+ }
+ else {
+ return $_[0]{%s} = $_[1] if @_ == 2;
+ return (shift)->{%s} = \@_;
+ }
+ }
+ },map {perlstring($_)}$field,$class,$field,$field}1;
+CLASS_ACCESSOR_FAST
+
+$fatpacked{"Class/Accessor/Faster.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FASTER';
+ package Class::Accessor::Faster;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Faster::VERSION='0.51';my%slot;sub _slot {my($class,$field)=@_;my$n=$slot{$class}->{$field};return$n if defined$n;$n=keys %{$slot{$class}};$slot{$class}->{$field}=$n;return$n}sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;my$self=bless [],$class;$fields={}unless defined$fields;for my$k (keys %$fields){my$n=$class->_slot($k);$self->[$n]=$fields->{$k}}return$self}sub make_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
+ sub {
+ return $_[0][%d] if scalar(@_) == 1;
+ return $_[0][%d] = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]];
+ }
+ },$n,$n}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
+ sub {
+ return $_[0][%d] if @_ == 1;
+ my $caller = caller;
+ $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s);
+ }
+ },$n,map(perlstring($_),$field,$class)}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
+ sub {
+ if (@_ == 1) {
+ my $caller = caller;
+ $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s);
+ }
+ else {
+ return $_[0][%d] = $_[1] if @_ == 2;
+ return (shift)->[%d] = \@_;
+ }
+ }
+ },map(perlstring($_),$field,$class),$n,$n}1;
+CLASS_ACCESSOR_FASTER
+
+$fatpacked{"Config/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CONFIG_TINY';
+ package Config::Tiny;use strict;our$VERSION='2.23';BEGIN {require 5.008001;$Config::Tiny::errstr=''}sub new {return bless {},shift}sub read {my($class)=ref $_[0]? ref shift : shift;my($file,$encoding)=@_;return$class -> _error('No file name provided')if (!defined$file || ($file eq ''));$encoding=$encoding ? "<:$encoding" : '<';local $/=undef;open(CFG,$encoding,$file)or return$class -> _error("Failed to open file '$file' for reading: $!");my$contents=<CFG>;close(CFG);return$class -> _error("Reading from '$file' returned undef")if (!defined$contents);return$class -> read_string($contents)}sub read_string {my($class)=ref $_[0]? ref shift : shift;my($self)=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;s/\s\;\s.+$//g;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){$self->{$ns}->{$1}=$2;next}return$self -> _error("Syntax error at line $counter: '$_'")}return$self}sub write {my($self)=shift;my($file,$encoding)=@_;return$self -> _error('No file name provided')if (!defined$file or ($file eq ''));$encoding=$encoding ? ">:$encoding" : '>';my($string)=$self->write_string;return undef unless defined$string;open(CFG,$encoding,$file)or return$self->_error("Failed to open file '$file' for writing: $!");print CFG$string;close CFG;return 1}sub write_string {my($self)=shift;my($contents)='';for my$section (sort {(($b eq '_')<=> ($a eq '_'))|| ($a cmp $b)}keys %$self){return$self->_error("Illegal whitespace in section name '$section'")if$section =~ /(?:^\s|\n|\s$)/s;my$block=$self->{$section};$contents .= "\n" if length$contents;$contents .= "[$section]\n" unless$section eq '_';for my$property (sort keys %$block){return$self->_error("Illegal newlines in property '$section.$property'")if$block->{$property}=~ /(?:\012|\015)/s;$contents .= "$property=$block->{$property}\n"}}return$contents}sub errstr {$Config::Tiny::errstr}sub _error {$Config::Tiny::errstr=$_[1];undef}1;
+CONFIG_TINY
+
+$fatpacked{"Devel/InnerPackage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DEVEL_INNERPACKAGE';
+ package Devel::InnerPackage;use strict;use Exporter 5.57 'import';use vars qw($VERSION @EXPORT_OK);use if $] > 5.017,'deprecate';$VERSION='0.4';@EXPORT_OK=qw(list_packages);sub list_packages {my$pack=shift;$pack .= "::" unless$pack =~ m!::$!;no strict 'refs';my@packs;my@stuff=grep!/^(main|)::$/,keys %{$pack};for my$cand (grep /::$/,@stuff){$cand =~ s!::$!!;my@children=list_packages($pack.$cand);push@packs,"$pack$cand" unless$cand =~ /^::/ || !__PACKAGE__->_loaded($pack.$cand);push@packs,@children}return grep {$_ !~ /::(::ISA::CACHE|SUPER)/}@packs}sub _loaded {my ($class,$name)=@_;no strict 'refs';return 1 if defined ${"${name}::VERSION"};return 1 if @{"${name}::ISA"};for (keys %{"${name}::"}){next if substr($_,-2,2)eq '::';return 1 if defined &{"${name}::$_"}}my$filename=join('/',split /(?:'|::)/,$name).'.pm';return 1 if defined$INC{$filename};''}1;
+DEVEL_INNERPACKAGE
+
+$fatpacked{"Math/Calc/Units.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS';
+ package Math::Calc::Units;use Math::Calc::Units::Compute qw(compute);use Math::Calc::Units::Rank qw(render render_unit choose_juicy_ones);use Math::Calc::Units::Convert;use base 'Exporter';use vars qw($VERSION @EXPORT_OK);BEGIN {$VERSION='1.07';@EXPORT_OK=qw(calc readable convert equal exact)}use strict;sub calc ($;$) {my ($expr,$exact)=@_;my$v=compute($expr);return$exact ? ($v->[0],render_unit($v->[1])): render($v)}sub readable {my$expr=shift;my%options;if (@_==1){$options{verbose}=shift}else {%options=@_}my$v=compute($expr);return map {render($_,\%options)}choose_juicy_ones($v,\%options)}sub convert ($$;$) {my ($expr,$units,$exact)=@_;my$v=compute($expr);my$u=compute("# $units");my$c=Math::Calc::Units::Convert::convert($v,$u->[1]);return$exact ? ($c->[0],render_unit($c->[1])): render($c)}use constant EPSILON=>1e-12;sub equal {my ($u,$v)=@_;$u=compute($u);$v=compute($v);$v=Math::Calc::Units::Convert::convert($v,$u->[1]);$u=$u->[0];$v=$v->[0];return 1 if ($u==0)&& abs($v)< EPSILON;return abs(($u-$v)/$u)< EPSILON}if (!(caller)){my$verbose;my%options;if ($ARGV[0]eq '-v'){shift;$options{verbose}=1}if ($ARGV[0]eq '-a'){shift;$options{abbreviate}=1}print "$_\n" foreach readable($ARGV[0],%options)}1;
+MATH_CALC_UNITS
+
+$fatpacked{"Math/Calc/Units/Compute.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_COMPUTE';
+ package Math::Calc::Units::Compute;use base 'Exporter';use vars qw(@EXPORT_OK);@EXPORT_OK=qw(compute plus minus mult divide power unit_mult unit_divide unit_power construct);use strict;use Math::Calc::Units::Convert qw(reduce);use Math::Calc::Units::Rank qw(render_unit);use Math::Calc::Units::Convert::Base;require Math::Calc::Units::Grammar;sub equivalent {my ($u,$v)=@_;return Math::Calc::Units::Convert::Base->same($u,$v)}sub is_unit {my ($x,$unit)=@_;return equivalent($x,{$unit=>1 })}sub plus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (equivalent($u->[1],$v->[1])){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'sec')&& is_unit($v->[1],'timestamp')){return [$u->[0]+ $v->[0],$v->[1]]}die "Unable to add incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub minus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'timestamp')){return [$u->[0]- $v->[0],{sec=>1 }]}elsif (equivalent($u->[1],$v->[1])){return [$u->[0]- $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]- $v->[0],$u->[1]]}die "Unable to subtract incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub mult {my ($u,$v)=@_;return [$u->[0]* $v->[0],unit_mult($u->[1],$v->[1])]}sub divide {my ($u,$v)=@_;return [$u->[0]/ $v->[0],unit_divide($u->[1],$v->[1])]}sub power {my ($u,$v)=@_;die "Can only raise to unit-less powers" if keys %{$v->[1]};$u=reduce($u);if (keys %{$u->[1]}!=0){my$power=$v->[0];die "Can only raise a value with units to an integral power" if abs($power - int($power))> 1e-20;return [$u->[0]** $power,unit_power($u->[1],$power)]}return [$u->[0]** $v->[0],{}]}sub unit_mult {my ($u,$v,$mult)=@_;$mult ||= 1;while (my ($unit,$vp)=each %$v){$u->{$unit}+= $vp * $mult;delete$u->{$unit}if$u->{$unit}==0}return$u}sub unit_divide {my ($u,$v)=@_;return unit_mult($u,$v,-1)}sub unit_power {my ($u,$power)=@_;return {}if$power==0;$u->{$_}*= $power foreach (keys %$u);return$u}sub construct {my$s=shift;my ($constructor,$args)=$s =~ /^(\w+)\((.*)\)/;return Math::Calc::Units::Convert::construct($constructor,$args)}package Math::Calc::Units::Compute;sub tokenize {my$data=shift;my@tokens=$data =~ m{\s*
+ (
+ \w+\([^\(\)]*\) # constructed (eg date(2001...))
+ |[\d.]+ # Numbers
+ |\w+ # Words
+ |\*\* # Exponentiation (**)
+ |[-+*/()@] # Operators
+ )}xg;my@types=map {/\w\(/ ? 'CONSTRUCT' :(/\d/ ? 'NUMBER' :(/\w/ ? 'WORD' :($_)))}@tokens;return \@tokens,\@types}sub compute {my$expr=shift;my$canonicalize=$expr !~ /^\#/;my ($vals,$types)=tokenize($expr);my$lexer=sub {return shift(@$types),shift(@$vals)if (@$types);return ('',undef)};my$parser=new Math::Calc::Units::Grammar;my$v=$parser->YYParse(yylex=>$lexer,yyerror=>sub {my$parser=shift;die "Error: expected ".join(" ",$parser->YYExpect)." got `".$parser->YYCurtok."', rest=".join(" ",@$types)."\nfrom ".join(" ",@$vals)."\n"},yydebug=>0);return$canonicalize ? reduce($v): $v};1;
+MATH_CALC_UNITS_COMPUTE
+
+$fatpacked{"Math/Calc/Units/Convert.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT';
+ package Math::Calc::Units::Convert;use base 'Exporter';use strict;use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(convert reduce canonical find_top construct)};use Math::Calc::Units::Convert::Multi qw(to_canonical);sub convert {my ($from,$unit)=@_;my$to=[1,$unit ];my$canon_from=canonical($from);my$canon_to=canonical($to);die "conversion between incompatible units" if not same_units($canon_from->[1],$canon_to->[1]);return [$canon_from->[0]/ $canon_to->[0],$unit ]}sub same_units {my ($u1,$u2)=@_;return if keys %$u1!=keys %$u2;while (my ($bu1,$bp1)=each %$u1){return if!exists$u2->{$bu1};return if$bp1!=$u2->{$bu1}}return 1}sub canonical {my ($v)=@_;my$c=to_canonical($v->[1]);my$w=[$v->[0]* $c->[0],$c->[1]];return$w}sub reduce {my ($v)=@_;return canonical($v,'reduce, please')}sub construct {my ($constructor,$args)=@_;return Math::Calc::Units::Convert::Multi::construct($constructor,$args)}1;
+MATH_CALC_UNITS_CONVERT
+
+$fatpacked{"Math/Calc/Units/Convert/Base.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE';
+ package Math::Calc::Units::Convert::Base;use strict;sub major_pref {return 0}sub major_variants {my ($self,$unit)=@_;return$unit}sub singular {my$self=shift;local $_=shift;return $_ unless /s$/;return $1 if /^(.*[^e])s$/;return $1 if /^(.*(ch|sh))es$/;return $1 if /^(.*[aeiou][^aeiou]e)s$/;chop;return $_}sub unit_map {return {}}sub variants {my ($self,$base)=@_;my$map=$self->unit_map();return ($base,keys %$map)}sub same {my ($self,$u,$v)=@_;return 0 if keys %$u!=keys %$v;while (my ($name,$power)=each %$u){return 0 if!exists$v->{$name};return 0 if$v->{$name}!=$power}return 1}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from eq $to;my$map=$self->unit_map();my$w=$map->{$from}|| $map->{lc($from)};if (!$w){$from=$self->singular($from);$w=$map->{$from}|| $map->{lc($from)}}return if!$w;if ($w->[1]ne $to){my$submult=$self->simple_convert($w->[1],$to);return if!defined$submult;return$w->[0]* $submult}else {return$w->[0]}}sub to_canonical {my ($self,$unitName)=@_;my$canon=$self->canonical_unit();if ($canon){my$mult=$self->simple_convert($unitName,$canon);return if!defined$mult;return ($mult,$canon)}else {return (1,$self->singular($unitName))}}sub canonical_unit {return}sub abbreviated_canonical_unit {my ($self)=@_;return$self->canonical_unit}my$THRESHOLD=0.01;sub spread {my ($self,$mag,$base,$start,$units)=@_;die if$mag < 0;return [0,$base ]if$mag==0;my$orig=$mag;my@desc;my$started=0;for my$unit (@$units){$started=1 if$unit eq $start;next unless$started;last if ($mag / $orig)< $THRESHOLD;my$mult=$self->simple_convert($unit,$base);my$n=int($mag / $mult);next if$n==0;$mag -= $n * $mult;push@desc,[$n,$unit ]}return@desc}sub range_score {my ($self,$val,$unitName)=@_;my$ranges=$self->get_ranges();my$range=$ranges->{$unitName}|| $ranges->{default};if ($val >= $range->[0]){if (!defined$range->[1]|| ($val <= $range->[1])){return 1}}$val=_sillylog($val);my$r0=_sillylog($range->[0]);my$r1;if (defined$range->[1]){$r1=_sillylog($range->[1])}else {$r1=4}my$width=$r1 - $r0;my$mean=($r0 + $r1)/ 2;my$stddev=$width / 2;my$n=($val - $mean)/ $stddev;our$mulconst;$mulconst ||= 0.999 * exp(1/8);return 0.001 + $mulconst * exp(-$n**2/2)}sub _sillylog {my$x=shift;return log($x)if$x;return log(1e-50)}sub pref_score {my ($self,$unitName)=@_;my$prefs=$self->get_prefs();my$specific=$prefs->{$unitName};return defined($specific)? $specific : $prefs->{default}}sub get_prefs {return {default=>0.1 }}sub get_ranges {return {default=>[1,undef ]}}sub render_unit {my ($self,$name,$power,$options)=@_;if ($power==1){return$name}else {return "$name**$power"}}sub render {my ($self,$val,$name,$power,$options)=@_;return sprintf("%.5g ",$val).$self->render_unit($name,$power,$options)}sub construct {return}1;
+MATH_CALC_UNITS_CONVERT_BASE
+
+$fatpacked{"Math/Calc/Units/Convert/Base2Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE2METRIC';
+ package Math::Calc::Units::Convert::Base2Metric;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%metric_base2 %abbrev $metric_prefix_test %pref);%metric_base2=(kilo=>2**10,mega=>2**20,giga=>2**30,tera=>2**40,peta=>2**50,exa=>2**60,);%abbrev=(k=>'kilo',m=>'mega',g=>'giga',t=>'tera',p=>'peta',e=>'exa',);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,);sub get_metric {my ($self,$what)=@_;return$metric_base2{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}|| $abbrev{lc($what)}}$metric_prefix_test=qr/^(${\join("|",keys %metric_base2)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub get_prefixes {return keys%metric_base2}sub expand {my ($self,$char)=@_;return$self->get_abbrev($char)}1;
+MATH_CALC_UNITS_CONVERT_BASE2METRIC
+
+$fatpacked{"Math/Calc/Units/Convert/Byte.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BYTE';
+ package Math::Calc::Units::Convert::Byte;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;my%units=(bit=>[1/8,'byte' ]);my%pref=(bit=>0.1,default=>1);my%ranges=(default=>[1,999 ]);my%total_unit_map;sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('byte')}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'byte'}sub abbreviated_canonical_unit {return 'B'}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^b(yte(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)b(yte(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."byte",$to)}}return}1;
+MATH_CALC_UNITS_CONVERT_BYTE
+
+$fatpacked{"Math/Calc/Units/Convert/Combo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_COMBO';
+ package Math::Calc::Units::Convert::Combo;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;use vars qw(%units %metric_units %prefixable_metric_units %total_unit_map);use vars qw(%ranges %pref);%units=();%metric_units=();%prefixable_metric_units=(bps=>[1,{bit=>1,sec=>-1 }]);%ranges=(default=>[1,999 ]);%pref=(default=>1);sub canonical_unit {return}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units,%metric_units,%prefixable_metric_units)}return \%total_unit_map}sub singular {my ($self,$unit)=@_;return$self->SUPER::singular($unit)unless$unit =~ /bps$/;return$unit}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=lc($self->singular(substr($string,length($prefix))));if ($metric_units{$tail}){return ($self->get_metric($prefix),$tail)}}elsif (my$abbrev=$self->get_abbrev_prefix($string)){my$tail=lc($self->singular(substr($string,length($abbrev))));if ($prefixable_metric_units{$tail}){my$prefix=$self->get_abbrev($abbrev);return ($self->get_metric($prefix),$tail)}}return (1,$string)}sub to_canonical {return}sub lookup_compound {my ($self,$unitName)=@_;for (keys%units,keys%metric_units,keys%prefixable_metric_units){if (my$mult=$self->simple_convert($unitName,$_)){my$u=$units{$_}|| $metric_units{$_}|| $prefixable_metric_units{$_};return [$mult * $u->[0],$u->[1]]}}return}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}1;
+MATH_CALC_UNITS_CONVERT_COMBO
+
+$fatpacked{"Math/Calc/Units/Convert/Date.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DATE';
+ package Math::Calc::Units::Convert::Date;use base 'Math::Calc::Units::Convert::Base';use Time::Local qw(timegm);use strict;use vars qw(%units %pref %ranges %total_unit_map);my$min_nice_time=timegm(0,0,0,1,0,1975-1900);my$max_nice_time=timegm(0,0,0,1,0,2030-1900);%units=();%pref=(default=>1);%ranges=(timestamp=>[$min_nice_time,$max_nice_time ]);sub major_pref {return 2}sub canonical_unit {return 'timestamp'}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}use vars qw(@MonthNames);BEGIN {@MonthNames=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)}sub construct {my ($self,$constructor,$args)=@_;if ($constructor eq 'timestamp'){$args=time if$args eq '';return [$args,{'timestamp'=>1 }]}return unless$constructor eq 'date';$args =~ s/\s+GMT\s+$//;my ($Mon,$d,$y,$h,$m,$s,$tz,$M);$tz='GMT';if ($args =~ /^((?:\w\w\w\s+)?)
+ (\w\w\w)\s*
+ (\d+)\s+
+ (\d+):(\d+)[:.](\d+)\s+
+ (\w+)?\s*
+ (\d\d\d\d)$/x){(undef,$Mon,$d,$h,$m,$s,$tz,$y)=($1,$2,$3,$4,$5,$6,$7,$8)}elsif ($args =~ /^(\w\w\w)[\s-]*
+ (\d+)[,\s-]+
+ (\d\d\d\d)$/x){($Mon,$d,$y)=($1,$2,$3)}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)\s+
+ (\d+):(\d+)[:.](\d+)$/x){($y,$M,$d,$h,$m,$s)=($1,$2,$3,$4,$5,$6);$M--}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)$/){($y,$M,$d)=($1,$2,$3);$M--}else {die "Unparseable date string '$args'"}$h ||= 0;$m ||= 0;$s ||= 0;if (defined$Mon){$M=0;for (@MonthNames){last if lc($_)eq lc($Mon);$M++}die "Unparseable month '$Mon'" if$M > 11}if (defined($tz)&& $tz ne 'GMT'){warn "Timezones not supported. Assuming GMT.\n"}my$timestamp=timegm($s,$m,$h,$d,$M,$y-1900);die "Date '$args' is out of range" if$timestamp==-1;return [$timestamp,{'timestamp'=>1 }]}sub render {my ($self,$mag,$name,$power)=@_;return "\@$mag" if$power!=1;return "\@$mag" if$mag < $min_nice_time;return "\@$mag" if$mag > $max_nice_time;return gmtime($mag)." (\@$mag)"}1;
+MATH_CALC_UNITS_CONVERT_DATE
+
+$fatpacked{"Math/Calc/Units/Convert/Distance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DISTANCE';
+ package Math::Calc::Units::Convert::Distance;use base 'Math::Calc::Units::Convert::Metric';use strict;my%total_unit_map;my%ranges=(default=>[1,999 ]);my%distance_units=(inch=>[2.54,'centimeter' ],foot=>[12,'inch' ],yard=>[3,'foot' ],mile=>[5280,'foot' ],);my%distance_pref=(meter=>1.1,inch=>0.7,foot=>0.9,yard=>0,mile=>1.0,);my%aliases=('feet'=>'foot',);sub canonical_unit {return 'meter'}sub abbreviated_canonical_unit {return 'm'}sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('meter')}sub get_ranges {return \%ranges}sub get_prefs {return \%distance_pref}sub singular {my ($self,$unit)=@_;$unit=$self->SUPER::singular($unit);return$aliases{$unit}|| $unit}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%distance_units)}return \%total_unit_map}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^m(eter(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)m(eter(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."meter",$to)}}return}sub variants {my ($self,$base)=@_;my$canon=$self->canonical_unit();return ($base,keys %{$self->unit_map()},map {"$_$canon"}$self->get_prefixes())}1;
+MATH_CALC_UNITS_CONVERT_DISTANCE
+
+$fatpacked{"Math/Calc/Units/Convert/Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_METRIC';
+ package Math::Calc::Units::Convert::Metric;use base 'Math::Calc::Units::Convert::Base';use strict;use vars qw(%niceSmallMetric %metric %pref %abbrev %reverse_abbrev $metric_prefix_test);%niceSmallMetric=(milli=>1e-3,micro=>1e-6,nano=>1e-9,pico=>1e-12,femto=>1e-15,);%metric=(kilo=>1e3,mega=>1e6,giga=>1e9,tera=>1e12,peta=>1e15,exa=>1e18,centi=>1e-2,%niceSmallMetric,);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,centi=>0.1,milli=>0.8,micro=>0.8,nano=>0.6,pico=>0.4,femto=>0.3,);%abbrev=(k=>'kilo',M=>'mega',G=>'giga',T=>'tera',P=>'peta',E=>'exa',c=>'centi',m=>'milli',u=>'micro',n=>'nano',p=>'pico',f=>'femto',);%reverse_abbrev=reverse%abbrev;sub pref_score {my ($self,$unitName)=@_;my$prefix=$self->get_prefix($unitName);$unitName=substr($unitName,length($prefix || ""));my$prefix_pref=defined($prefix)? $self->prefix_pref($prefix): 1;return$prefix_pref * $self->SUPER::pref_score($unitName)}sub get_metric {my ($self,$what)=@_;return$metric{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}}$metric_prefix_test=qr/^(${\join("|",keys %metric)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub get_prefixes {my ($self,$options)=@_;if ($options->{small}){return grep {$metric{$_}< 1}keys%metric}else {return keys%metric}}sub get_abbrev_prefix {my ($self,$what)=@_;my$prefix=substr($what,0,1);if ($abbrev{$prefix}|| $abbrev{lc($prefix)}){return$prefix}else {return}}sub variants {my ($self,$base)=@_;my@main=$self->SUPER::variants($base);my@variants;for my$u (@main){push@variants,$u,map {"$_$u"}$self->get_prefixes()}return@variants}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$base=substr($string,length($prefix));return ($self->get_metric($prefix),$base)}else {return (1,$string)}}sub expand {my ($self,$char)=@_;my@expansions;my ($exact,$lower);if ($exact=$self->get_abbrev($char)){push@expansions,$exact}elsif (($char ne lc($char))&& ($lower=$self->get_abbrev(lc($char)))){push@expansions,$lower}return@expansions}sub simple_convert {my ($self,$from,$to)=@_;my ($mult_from,$base_from)=$self->demetric($from)or return;my ($mult_to,$base_to)=$self->demetric($to)or return;my$submult=$self->SUPER::simple_convert($base_from,$base_to);return if!defined$submult;return$submult * ($mult_from / $mult_to)}sub metric_abbreviation {my ($self,$prefix)=@_;return$reverse_abbrev{$prefix}|| $prefix}sub render {my ($self,$val,$name,$power,$options)=@_;if ($options->{abbreviate}){my$stem=$self->canonical_unit;if ($name =~ /(\w+)\Q$stem\E$/){my$prefix=$reverse_abbrev{$1};if (defined($prefix)){$name=$prefix .$self->abbreviated_canonical_unit}}}return$self->SUPER::render($val,$name,$power,$options)}1;
+MATH_CALC_UNITS_CONVERT_METRIC
+
+$fatpacked{"Math/Calc/Units/Convert/Multi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_MULTI';
+ package Math::Calc::Units::Convert::Multi;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(to_canonical simple_convert singular variants major_variants major_pref range_score pref_score get_class construct)};require Math::Calc::Units::Convert::Time;require Math::Calc::Units::Convert::Byte;require Math::Calc::Units::Convert::Date;require Math::Calc::Units::Convert::Distance;require Math::Calc::Units::Convert::Combo;use strict;use vars qw(@UnitClasses);@UnitClasses=qw(Math::Calc::Units::Convert::Time Math::Calc::Units::Convert::Byte Math::Calc::Units::Convert::Date Math::Calc::Units::Convert::Distance Math::Calc::Units::Convert::Combo);sub to_canonical {my ($unit)=@_;my$val=1;my%newUnit;while (my ($unitName,$power)=each %$unit){my ($mult,$canon)=name_to_canonical($unitName);$val *= $mult ** $power;if (ref$canon){my$c=to_canonical($canon);$val *= $c->[0]** $power;while (my ($name,$subPower)=each %{$c->[1]}){if (($newUnit{$name}+= $subPower * $power)==0){delete$newUnit{$name}}}}else {if (($newUnit{$canon}+= $power)==0){delete$newUnit{$canon}}}}return [$val,\%newUnit ]}my%CANON_CACHE;sub name_to_canonical {my$unitName=shift;$CANON_CACHE{$unitName}||= [_name_to_canonical($unitName)];return @{$CANON_CACHE{$unitName}}}sub _name_to_canonical {my ($unitName)=@_;if (my$v=Math::Calc::Units::Convert::Combo->lookup_compound($unitName)){return @$v}for my$uclass (@UnitClasses){if (my ($val,$base)=$uclass->to_canonical($unitName)){return ($val,$base)}}return Math::Calc::Units::Convert::Base->to_canonical($unitName)}sub get_class {my ($unitName)=@_;my (undef,$canon)=name_to_canonical($unitName);for my$uclass (@UnitClasses){my$canon_unit=$uclass->canonical_unit();next if!defined$canon_unit;return$uclass if$canon_unit eq $canon}return 'Math::Calc::Units::Convert::Base'}sub simple_convert {my ($u,$v)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->simple_convert($u,$v)}return}sub singular {my ($unitName)=@_;return get_class($unitName)->singular($unitName)}sub variants {my ($base)=@_;return get_class($base)->variants($base)}sub major_variants {my ($base)=@_;return get_class($base)->major_variants($base)}sub major_pref {my ($base)=@_;return get_class($base)->major_pref($base)}sub range_score {my ($val,$unitName)=@_;die if ref$unitName;return get_class($unitName)->range_score($val,$unitName)}sub pref_score {my ($unitName)=@_;die if ref$unitName;return get_class($unitName)->pref_score($unitName)}sub construct {my ($constructor,$args)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->construct($constructor,$args)}return}1;
+MATH_CALC_UNITS_CONVERT_MULTI
+
+$fatpacked{"Math/Calc/Units/Convert/Time.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_TIME';
+ package Math::Calc::Units::Convert::Time;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%units %pref %ranges %total_unit_map);%units=(minute=>[60,'sec' ],hour=>[60,'minute' ],day=>[24,'hour' ],week=>[7,'day' ],year=>[365,'day' ],);%pref=(default=>1,hour=>0.8,day=>0.8,week=>0.4,minute=>0.9,year=>0.9,);%ranges=(default=>[1,300 ],millisec=>[1,999 ],sec=>[1,200 ],minute=>[2,100 ],hour=>[1,80 ],day=>[1,500 ],week=>[1,4 ],year=>[1,undef ],);sub major_pref {return 2}sub major_variants {my ($self)=@_;return grep {($_ ne 'default')&& ($_ ne 'week')}keys%ranges}sub variants {my ($self,$base)=@_;return 'sec',(keys%units),map {"${_}sec"}$self->get_prefixes({small=>1 })}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'sec'}sub abbreviated_canonical_unit {return 's'}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=substr($string,length($prefix));if ($tail =~ /^sec(ond)?s?$/){return ($self->get_metric($prefix),"sec")}return}else {return (1,$string)}}sub simple_convert {my ($self,$from,$to)=@_;$from="sec" if$from =~ /^sec(ond)?s?$/i;$from="minute" if$from =~ /^min(ute)?s?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)s$/){my ($expansion)=$self->expand($1);return$self->simple_convert($expansion ."sec",$to)}return}sub preference {my ($self,$v)=@_;my ($val,$unit)=@$v;my$base=lc(($self->demetric($unit))[1]);my$pref=$pref{$base}|| $pref{default};return$pref * $self->prefix_pref(substr($unit,0,-length($base)))}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}my@BREAKDOWN=qw(year week day hour minute sec ms us ns ps);sub render {my ($self,$val,$name,$power,$options)=@_;my$full_name=$name;if ($options->{abbreviate}){if ($name =~ /(\w+)sec/){my$prefix=$1;my$mabbrev=$self->metric_abbreviation($prefix);$name=$mabbrev ."s" unless$mabbrev eq $prefix}}my$basic=$self->SUPER::render($val,$name,$power,$options);return$basic if$power!=1;$val *= $self->simple_convert($full_name,'sec');my@spread=$self->spread($val,'sec',$name,\@BREAKDOWN);my$spread=join(" ",map {"$_->[0] $_->[1]"}@spread);return "($basic = $spread)" if@spread > 1;return$basic}1;
+MATH_CALC_UNITS_CONVERT_TIME
+
+$fatpacked{"Math/Calc/Units/Grammar.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_GRAMMAR';
+ package Math::Calc::Units::Grammar;use vars qw ( @ISA);use strict;@ISA=qw ( Parse::Yapp::Driver);{package Parse::Yapp::Driver;require 5.004;use strict;use vars qw ( $VERSION $COMPATIBLE $FILENAME);$VERSION='1.04';$COMPATIBLE='0.07';$FILENAME=__FILE__;use Carp;my(%params)=(YYLEX=>'CODE','YYERROR'=>'CODE',YYVERSION=>'',YYRULES=>'ARRAY',YYSTATES=>'ARRAY',YYDEBUG=>'');my(@params)=('LEX','RULES','STATES');sub new {my($class)=shift;my($errst,$nberr,$token,$value,$check,$dotpos);my($self)={ERROR=>\&_Error,ERRST=>\$errst,NBERR=>\$nberr,TOKEN=>\$token,VALUE=>\$value,DOTPOS=>\$dotpos,STACK=>[],DEBUG=>0,CHECK=>\$check };_CheckParams([],\%params,\@_,$self);exists($$self{VERSION})and $$self{VERSION}< $COMPATIBLE and croak "Yapp driver version $VERSION "."incompatible with version $$self{VERSION}:\n"."Please recompile parser module.";ref($class)and $class=ref($class);bless($self,$class)}sub YYParse {my($self)=shift;my($retval);_CheckParams(\@params,\%params,\@_,$self);if($$self{DEBUG}){_DBLoad();$retval=eval '$self->_DBParse()';$@ and die $@}else {$retval=$self->_Parse()}$retval}sub YYData {my($self)=shift;exists($$self{USER})or $$self{USER}={};$$self{USER}}sub YYErrok {my($self)=shift;${$$self{ERRST}}=0;undef}sub YYNberr {my($self)=shift;${$$self{NBERR}}}sub YYRecovering {my($self)=shift;${$$self{ERRST}}!=0}sub YYAbort {my($self)=shift;${$$self{CHECK}}='ABORT';undef}sub YYAccept {my($self)=shift;${$$self{CHECK}}='ACCEPT';undef}sub YYError {my($self)=shift;${$$self{CHECK}}='ERROR';undef}sub YYSemval {my($self)=shift;my($index)=$_[0]- ${$$self{DOTPOS}}- 1;$index < 0 and -$index <= @{$$self{STACK}}and return $$self{STACK}[$index][1];undef}sub YYCurtok {my($self)=shift;@_ and ${$$self{TOKEN}}=$_[0];${$$self{TOKEN}}}sub YYCurval {my($self)=shift;@_ and ${$$self{VALUE}}=$_[0];${$$self{VALUE}}}sub YYExpect {my($self)=shift;keys %{$self->{STATES}[$self->{STACK}[-1][0]]{ACTIONS}}}sub YYLexer {my($self)=shift;$$self{LEX}}sub _CheckParams {my($mandatory,$checklist,$inarray,$outhash)=@_;my($prm,$value);my($prmlst)={};while(($prm,$value)=splice(@$inarray,0,2)){$prm=uc($prm);exists($$checklist{$prm})or croak("Unknow parameter '$prm'");ref($value)eq $$checklist{$prm}or croak("Invalid value for parameter '$prm'");$prm=unpack('@2A*',$prm);$$outhash{$prm}=$value}for (@$mandatory){exists($$outhash{$_})or croak("Missing mandatory parameter '".lc($_)."'")}}sub _Error {print "Parse error.\n"}sub _DBLoad {{no strict 'refs';exists(${__PACKAGE__.'::'}{_DBParse})and return}my($fname)=__FILE__;my(@drv);open(DRV,"<$fname")or die "Report this as a BUG: Cannot open $fname";while(<DRV>){/^\s*sub\s+_Parse\s*{\s*$/ .. /^\s*}\s*#\s*_Parse\s*$/ and do {s/^#DBG>//;push(@drv,$_)}}close(DRV);$drv[0]=~s/_P/_DBP/;eval join('',@drv)}sub _Parse {my($self)=shift;my($rules,$states,$lex,$error)=@$self{'RULES','STATES','LEX','ERROR' };my($errstatus,$nberror,$token,$value,$stack,$check,$dotpos)=@$self{'ERRST','NBERR','TOKEN','VALUE','STACK','CHECK','DOTPOS' };$$errstatus=0;$$nberror=0;($$token,$$value)=(undef,undef);@$stack=([0,undef ]);$$check='';while(1){my($actions,$act,$stateno);$stateno=$$stack[-1][0];$actions=$$states[$stateno];if (exists($$actions{ACTIONS})){defined($$token)or do {($$token,$$value)=&$lex($self)};$act=exists($$actions{ACTIONS}{$$token})? $$actions{ACTIONS}{$$token}: exists($$actions{DEFAULT})? $$actions{DEFAULT}: undef}else {$act=$$actions{DEFAULT}}defined($act)and do {$act > 0 and do {$$errstatus and do {--$$errstatus};push(@$stack,[$act,$$value ]);$$token ne '' and $$token=$$value=undef;next};my($lhs,$len,$code,@sempar,$semval);($lhs,$len,$code)=@{$$rules[-$act]};$act or $self->YYAccept();$$dotpos=$len;unpack('A1',$lhs)eq '@' and do {$lhs =~ /^\@[0-9]+\-([0-9]+)$/ or die "In line rule name '$lhs' ill formed: "."report it as a BUG.\n";$$dotpos=$1};@sempar=$$dotpos ? map {$$_[1]}@$stack[-$$dotpos .. -1 ]: ();$semval=$code ? &$code($self,@sempar): @sempar ? $sempar[0]: undef;splice(@$stack,-$len,$len);$$check eq 'ACCEPT' and do {return($semval)};$$check eq 'ABORT' and do {return(undef)};$$check eq 'ERROR' or do {push(@$stack,[$$states[$$stack[-1][0]]{GOTOS}{$lhs},$semval ]);$$check='';next};$$check=''};$$errstatus or do {$$errstatus=1;&$error($self);$$errstatus or next;++$$nberror};$$errstatus==3 and do {$$token eq '' and do {return(undef)};$$token=$$value=undef};$$errstatus=3;while(@$stack and (not exists($$states[$$stack[-1][0]]{ACTIONS})or not exists($$states[$$stack[-1][0]]{ACTIONS}{error})or $$states[$$stack[-1][0]]{ACTIONS}{error}<= 0)){pop(@$stack)}@$stack or do {return(undef)};push(@$stack,[$$states[$$stack[-1][0]]{ACTIONS}{error},undef ])}croak("Error in driver logic. Please, report it as a BUG")}1}use Math::Calc::Units::Compute qw(plus minus mult divide power construct);sub new {my($class)=shift;ref($class)and $class=ref($class);my($self)=$class->SUPER::new(yyversion=>'1.04',yystates=>[{ACTIONS=>{'NUMBER'=>5,"#"=>2,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'START'=>10,'expr'=>11 }},{DEFAULT=>-12 },{ACTIONS=>{'WORD'=>7 },GOTOS=>{'unit'=>12 }},{DEFAULT=>-16 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>13 }},{ACTIONS=>{'WORD'=>7 },DEFAULT=>-13,GOTOS=>{'unit'=>14 }},{ACTIONS=>{'NUMBER'=>15 }},{DEFAULT=>-17 },{DEFAULT=>-9 },{ACTIONS=>{'NUMBER'=>17 }},{ACTIONS=>{''=>18 }},{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24 },DEFAULT=>-1,GOTOS=>{'unit'=>19 }},{DEFAULT=>-2 },{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24,")"=>25 },GOTOS=>{'unit'=>19 }},{DEFAULT=>-11 },{DEFAULT=>-14 },{DEFAULT=>-18 },{DEFAULT=>-15 },{DEFAULT=>-0 },{DEFAULT=>-10 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>26 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>27 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>28 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>29 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>30 }},{DEFAULT=>-8 },{DEFAULT=>-7,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-5,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-3,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-4,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-6,GOTOS=>{'unit'=>19 }}],yyrules=>[['$start',2,undef ],['START',1,undef ],['START',2,undef ],['expr',3,sub {return plus($_[1],$_[3])}],['expr',3,sub {return minus($_[1],$_[3])}],['expr',3,sub {return mult($_[1],$_[3])}],['expr',3,sub {return divide($_[1],$_[3])}],['expr',3,sub {return power($_[1],$_[3])}],['expr',3,sub {return $_[2]}],['expr',1,sub {return $_[1]}],['expr',2,sub {return mult($_[1],[1,$_[2]])}],['value',2,sub {return [$_[1]=>$_[2]]}],['value',1,sub {return [1=>$_[1]]}],['value',1,sub {return [$_[1]=>{}]}],['value',2,sub {return [-$_[2]=>{}]}],['value',2,sub {return [$_[2]=>{'timestamp'=>1 }]}],['value',1,sub {return construct($_[1])}],['unit',1,sub {return {$_[1]=>1 }}],['unit',2,sub {my$u={};$u->{$_[1]}++;$u->{$_[2]}++;return$u}]],@_);bless($self,$class)}1;
+MATH_CALC_UNITS_GRAMMAR
+
+$fatpacked{"Math/Calc/Units/Rank.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_RANK';
+ package Math::Calc::Units::Rank;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(choose_juicy_ones render render_unit)}use Math::Calc::Units::Convert qw(convert canonical);use Math::Calc::Units::Convert::Multi qw(variants major_variants major_pref pref_score range_score get_class);use strict;sub choose_juicy_ones {my ($v,$options)=@_;my@variants=rank_variants($v,$options);my%variants;for my$variant (@variants){my$id=join(";;",values %{$variant->[0]});$variants{$id}=$variant}my@options;for my$variant (values%variants){my ($map,$score)=@$variant;my%copy;my ($magnitude,$units)=@$v;while (my ($unit,$count)=each %$units){$copy{$map->{$unit}}=$count}push@options,[$score,convert($v,\%copy)]}my@juicy;my$first;my$prev;for (sort {$b->[0]<=> $a->[0]}@options){my ($score,$val)=@$_;last if (defined$prev && ($prev / $score)> 8);last if (defined$first && ($first / $score)> 25);push@juicy,$val;$first=$score unless defined$first;$prev=$score;last if@juicy==5}return@juicy}sub rank_variants {my ($v,$options)=@_;$v=canonical($v);my ($mag,$count)=@$v;my@rangeable=grep {$count->{$_}> 0}keys %$count;if (@rangeable==0){@rangeable=keys %$count}return rank_power_variants($mag,\@rangeable,$count,$options)}sub choose_major {my (@possibilities)=@_;my@majors=map {[major_pref($_),$_ ]}@possibilities;return (sort {$a->[0]<=> $b->[0]}@majors)[-1]->[1]}sub rank_power_variants {my ($mag,$top,$power,$options)=@_;if (keys %$power > 1){my$major=choose_major(keys %$power);my$majorClass=get_class($major);my%powerless=%$power;delete$powerless{$major};my@ranked;for my$variant (major_variants($major,$options)){my$mult=$majorClass->simple_convert($variant,$major);my$cval=$mag / $mult ** $power->{$major};print "\n --- for $variant ---\n" if$options->{verbose};my@r=rank_power_variants($cval,$top,\%powerless,$options);next if@r==0;my$best=$r[0];$best->[0]->{$major}=$variant;$best->[1]=pref_score($variant);push@ranked,$best}return@ranked}if (keys %$power==0){return [{},1 ]}my$unit=(keys %$power)[0];$power=$power->{$unit};my$class=get_class($unit);my (undef,$canon)=$class->to_canonical($unit);my$mult=$class->simple_convert($unit,$canon);$mag *= $mult ** $power;my@choices;my@subtop=grep {$_ ne $canon}@$top;my$add_variant=(@subtop==@$top);for my$variant (variants($canon)){my$mult=$class->simple_convert($variant,$canon);my$minimag=$mag / $mult ** $power;my@vtop=@subtop;push@vtop,$variant if$add_variant;my$score=score($minimag,$variant,\@vtop);printf "($mag $unit) score %.6f:\t $minimag $variant\n",$score if$options->{verbose};push@choices,[$score,$variant ]}@choices=sort {$b->[0]<=> $a->[0]}@choices;return ()if@choices==0;return map {[{$unit=>$_->[1]},$_->[0]]}@choices}sub render_unit {my ($units,$options)=@_;my$str='';while (my ($name,$power)=each %$units){if ($power > 0){$str .= get_class($name)->render_unit($name,$power,$options);$str .= " "}}chop($str);my$botstr='';while (my ($name,$power)=each %$units){if ($power < 0){$botstr .= get_class($name)->render_unit($name,-$power,$options);$botstr .= " "}}chop($botstr);if ($botstr eq ''){return$str}elsif ($botstr =~ /\s/){return "$str / ($botstr)"}else {return "$str / $botstr"}}sub render {my ($v,$options)=@_;my ($mag,$units)=@$v;if (keys %$units==0){my$str=sprintf("%.4g",$mag);if (($mag < 1)&& ($mag >= 0.01)){if ($options->{abbreviate}){$str .= sprintf(" = %.4g percent",100 * $mag)}else {$str .= sprintf(" = %.4g%%",100 * $mag)}}return$str}my@top;my@bottom;while (my ($name,$power)=each %$units){if ($power > 0){push@top,$name}else {push@bottom,$name}}my$str;if (@top==1){my ($name)=@top;$str=get_class($name)->render($mag,$name,$units->{$name},$options);$str .= " "}else {$str=sprintf("%.4g ",$mag);for my$name (@top){$str .= get_class($name)->render_unit($name,$units->{$name},$options);$str .= " "}}if (@bottom > 0){my$botstr;for my$name (@bottom){$botstr .= get_class($name)->render_unit($name,-$units->{$name},$options);$botstr .= " "}chop($botstr);if (@bottom > 1){$str .= "/ ($botstr) "}else {$str .= "/ $botstr "}}chop($str);return$str}sub max_range_score {my ($mag,$units)=@_;my$score=0;for my$name (@$units){my$uscore=range_score($mag,$name);$score=$uscore if$score < $uscore}return$score}sub score {my ($mag,$unit,$top)=@_;my@rangeable=@$top ? @$top : ($unit);my$pref=pref_score($unit);my$range_score=max_range_score($mag,\@rangeable);return$pref * $range_score}1;
+MATH_CALC_UNITS_RANK
+
+$fatpacked{"Module/Implementation.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_IMPLEMENTATION';
+ package Module::Implementation;$Module::Implementation::VERSION='0.09';use strict;use warnings;use Module::Runtime 0.012 qw(require_module);use Try::Tiny;unless (exists$Module::Implementation::{VERSION}&& ${$Module::Implementation::{VERSION}}){$Module::Implementation::{VERSION}=\42}my%Implementation;sub build_loader_sub {my$caller=caller();return _build_loader($caller,@_)}sub _build_loader {my$package=shift;my%args=@_;my@implementations=@{$args{implementations}};my@symbols=@{$args{symbols}|| []};my$implementation;my$env_var=uc$package;$env_var =~ s/::/_/g;$env_var .= '_IMPLEMENTATION';return sub {my ($implementation,$loaded)=_load_implementation($package,$ENV{$env_var},\@implementations,);$Implementation{$package}=$implementation;_copy_symbols($loaded,$package,\@symbols);return$loaded}}sub implementation_for {my$package=shift;return$Implementation{$package}}sub _load_implementation {my$package=shift;my$env_value=shift;my$implementations=shift;if ($env_value){die "$env_value is not a valid implementation for $package" unless grep {$_ eq $env_value}@{$implementations};my$requested="${package}::$env_value";($requested)=$requested =~ /^(.+)$/;try {require_module($requested)}catch {require Carp;Carp::croak("Could not load $requested: $_")};return ($env_value,$requested)}else {my$err;for my$possible (@{$implementations}){my$try="${package}::$possible";my$ok;try {require_module($try);$ok=1}catch {$err .= $_ if defined $_};return ($possible,$try)if$ok}require Carp;if (defined$err && length$err){Carp::croak("Could not find a suitable $package implementation: $err")}else {Carp::croak('Module::Runtime failed to load a module but did not throw a real error. This should never happen. Something is very broken')}}}sub _copy_symbols {my$from_package=shift;my$to_package=shift;my$symbols=shift;for my$sym (@{$symbols}){my$type=$sym =~ s/^([\$\@\%\&\*])// ? $1 : '&';my$from="${from_package}::$sym";my$to="${to_package}::$sym";{no strict 'refs';no warnings 'once';*{$to}=$type eq '&' ? \&{$from}: $type eq '$' ? \${$from}: $type eq '@' ? \@{$from}: $type eq '%' ? \%{$from}: $type eq '*' ? *{$from}: die "Can't copy symbol from $from_package to $to_package: $type$sym"}}}1;
+MODULE_IMPLEMENTATION
+
+$fatpacked{"Module/Pluggable.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE';
+ package Module::Pluggable;use strict;use vars qw($VERSION $FORCE_SEARCH_ALL_PATHS);use Module::Pluggable::Object;use if $] > 5.017,'deprecate';$VERSION='5.2';$FORCE_SEARCH_ALL_PATHS=0;sub import {my$class=shift;my%opts=@_;my ($pkg,$file)=caller;my$sub=$opts{'sub_name'}|| 'plugins';my ($package)=$opts{'package'}|| $pkg;$opts{filename}=$file;$opts{package}=$package;$opts{force_search_all_paths}=$FORCE_SEARCH_ALL_PATHS unless exists$opts{force_search_all_paths};my$finder=Module::Pluggable::Object->new(%opts);my$subroutine=sub {my$self=shift;return$finder->plugins(@_)};my$searchsub=sub {my$self=shift;my ($action,@paths)=@_;$finder->{'search_path'}=["${package}::Plugin"]if ($action eq 'add' and not $finder->{'search_path'});push @{$finder->{'search_path'}},@paths if ($action eq 'add');$finder->{'search_path'}=\@paths if ($action eq 'new');return$finder->{'search_path'}};my$onlysub=sub {my ($self,$only)=@_;if (defined$only){$finder->{'only'}=$only};return$finder->{'only'}};my$exceptsub=sub {my ($self,$except)=@_;if (defined$except){$finder->{'except'}=$except};return$finder->{'except'}};no strict 'refs';no warnings qw(redefine prototype);*{"$package\::$sub"}=$subroutine;*{"$package\::search_path"}=$searchsub;*{"$package\::only"}=$onlysub;*{"$package\::except"}=$exceptsub}1;
+MODULE_PLUGGABLE
+
+$fatpacked{"Module/Pluggable/Object.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE_OBJECT';
+ package Module::Pluggable::Object;use strict;use File::Find ();use File::Basename;use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);use Carp qw(croak carp confess);use Devel::InnerPackage;use vars qw($VERSION $MR);use if $] > 5.017,'deprecate';$VERSION='5.2';BEGIN {eval {require Module::Runtime};unless ($@){Module::Runtime->import('require_module')}else {*require_module=sub {my$module=shift;my$path=$module .".pm";$path =~ s{::}{/}g;require$path}}}sub new {my$class=shift;my%opts=@_;return bless \%opts,$class}sub plugins {my$self=shift;my@args=@_;$self->{'require'}=1 if$self->{'inner'};my$filename=$self->{'filename'};my$pkg=$self->{'package'};$self->_setup_exceptions;for (qw(search_path search_dirs)){$self->{$_}=[$self->{$_}]if exists$self->{$_}&&!ref($self->{$_})}$self->{'search_path'}||= ["${pkg}::Plugin"];$self->{'on_require_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't require $plugin : $err";return 0};$self->{'on_instantiate_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't instantiate $plugin: $err";return 0};$self->{'follow_symlinks'}=1 unless exists$self->{'follow_symlinks'};my@SEARCHDIR=exists$INC{"blib.pm"}&& defined$filename && $filename =~ m!(^|/)blib/! &&!$self->{'force_search_all_paths'}? grep {/blib/}@INC : @INC;unshift@SEARCHDIR,@{$self->{'search_dirs'}}if defined$self->{'search_dirs'};my@tmp=@INC;unshift@tmp,@{$self->{'search_dirs'}|| []};local@INC=@tmp if defined$self->{'search_dirs'};my@plugins=$self->search_directories(@SEARCHDIR);push(@plugins,$self->handle_inc_hooks($_,@SEARCHDIR))for @{$self->{'search_path'}};push(@plugins,$self->handle_innerpackages($_))for @{$self->{'search_path'}};return ()unless@plugins;my%plugins;for(@plugins){next unless$self->_is_legit($_);$plugins{$_}=1}if (defined$self->{'instantiate'}){my$method=$self->{'instantiate'};my@objs=();for my$package (sort keys%plugins){next unless$package->can($method);my$obj=eval {$package->$method(@_)};$self->{'on_instantiate_error'}->($package,$@)if $@;push@objs,$obj if$obj}return@objs}else {my@objs=sort keys%plugins;return@objs}}sub _setup_exceptions {my$self=shift;my%only;my%except;my$only;my$except;if (defined$self->{'only'}){if (ref($self->{'only'})eq 'ARRAY'){%only=map {$_=>1}@{$self->{'only'}}}elsif (ref($self->{'only'})eq 'Regexp'){$only=$self->{'only'}}elsif (ref($self->{'only'})eq ''){$only{$self->{'only'}}=1}}if (defined$self->{'except'}){if (ref($self->{'except'})eq 'ARRAY'){%except=map {$_=>1}@{$self->{'except'}}}elsif (ref($self->{'except'})eq 'Regexp'){$except=$self->{'except'}}elsif (ref($self->{'except'})eq ''){$except{$self->{'except'}}=1}}$self->{_exceptions}->{only_hash}=\%only;$self->{_exceptions}->{only}=$only;$self->{_exceptions}->{except_hash}=\%except;$self->{_exceptions}->{except}=$except}sub _is_legit {my$self=shift;my$plugin=shift;my%only=%{$self->{_exceptions}->{only_hash}||{}};my%except=%{$self->{_exceptions}->{except_hash}||{}};my$only=$self->{_exceptions}->{only};my$except=$self->{_exceptions}->{except};my$depth=()=split '::',$plugin,-1;return 0 if (keys%only &&!$only{$plugin});return 0 unless (!defined$only || $plugin =~ m!$only!);return 0 if (keys%except && $except{$plugin});return 0 if (defined$except && $plugin =~ m!$except!);return 0 if defined$self->{max_depth}&& $depth>$self->{max_depth};return 0 if defined$self->{min_depth}&& $depth<$self->{min_depth};return 1}sub search_directories {my$self=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){push@plugins,$self->search_paths($dir)}return@plugins}sub search_paths {my$self=shift;my$dir=shift;my@plugins;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;for my$searchpath (@{$self->{'search_path'}}){my$sp=catdir($dir,(split /::/,$searchpath));next unless (-e $sp && -d _);my@files=$self->find_files($sp);for my$file (@files){next unless ($file)=($file =~ /(.*$file_regex)$/);my ($name,$directory,$suffix)=fileparse($file,$file_regex);next if (!$self->{include_editor_junk}&& $self->_is_editor_junk($name));$directory=abs2rel($directory,$sp);my@pkg_dirs=();if ($name eq lc($name)|| $name eq uc($name)){my$pkg_file=catfile($sp,$directory,"$name$suffix");open PKGFILE,"<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";my$in_pod=0;while (my$line=<PKGFILE>){$in_pod=1 if$line =~ m/^=\w/;$in_pod=0 if$line =~ /^=cut/;next if ($in_pod || $line =~ /^=cut/);next if$line =~ /^\s*#/;if ($line =~ m/^\s*package\s+(.*::)?($name)\s*;/i){@pkg_dirs=split /::/,$1 if defined $1;;$name=$2;last}}close PKGFILE}$directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/);my@dirs=();if ($directory){($directory)=($directory =~ /(.*)/);@dirs=grep(length($_),splitdir($directory))unless$directory eq curdir();for my$d (reverse@dirs){my$pkg_dir=pop@pkg_dirs;last unless defined$pkg_dir;$d =~ s/\Q$pkg_dir\E/$pkg_dir/i}}else {$directory=""}my$plugin=join '::',$searchpath,@dirs,$name;next unless$plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;$self->handle_finding_plugin($plugin,\@plugins)}push@plugins,$self->handle_innerpackages($searchpath)}return@plugins}sub _is_editor_junk {my$self=shift;my$name=shift;return 1 if$name =~ /~$/;return 1 if$name =~ /^\.#/;return 1 if$name =~ /\.sw[po]$/;return 0}sub handle_finding_plugin {my$self=shift;my$plugin=shift;my$plugins=shift;my$no_req=shift || 0;return unless$self->_is_legit($plugin);unless (defined$self->{'instantiate'}|| $self->{'require'}){push @$plugins,$plugin;return}$self->{before_require}->($plugin)|| return if defined$self->{before_require};unless ($no_req){my$tmp=$@;my$res=eval {require_module($plugin)};my$err=$@;$@=$tmp;if ($err){if (defined$self->{on_require_error}){$self->{on_require_error}->($plugin,$err)|| return}else {return}}}$self->{after_require}->($plugin)|| return if defined$self->{after_require};push @$plugins,$plugin}sub find_files {my$self=shift;my$search_path=shift;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;my@files=();{local $_;File::Find::find({no_chdir=>1,follow=>$self->{'follow_symlinks'},wanted=>sub {return unless$File::Find::name =~ /$file_regex/;(my$path=$File::Find::name)=~ s#^\\./##;push@files,$path}},$search_path)}return@files}sub handle_inc_hooks {my$self=shift;my$path=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){next unless ref$dir && eval {$dir->can('files')};for my$plugin ($dir->files){$plugin =~ s/\.pm$//;$plugin =~ s{/}{::}g;next unless$plugin =~ m!^${path}::!;$self->handle_finding_plugin($plugin,\@plugins)}}return@plugins}sub handle_innerpackages {my$self=shift;return ()if (exists$self->{inner}&&!$self->{inner});my$path=shift;my@plugins;for my$plugin (Devel::InnerPackage::list_packages($path)){$self->handle_finding_plugin($plugin,\@plugins,1)}return@plugins}1;
+MODULE_PLUGGABLE_OBJECT
+
+$fatpacked{"Module/Runtime.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_RUNTIME';
+ package Module::Runtime;BEGIN {require 5.006}BEGIN {${^WARNING_BITS}=""}our$VERSION="0.016";our@EXPORT_OK=qw($module_name_rx is_module_name is_valid_module_name check_module_name module_notional_filename require_module use_module use_package_optimistically $top_module_spec_rx $sub_module_spec_rx is_module_spec is_valid_module_spec check_module_spec compose_module_name);my%export_ok=map {($_=>undef)}@EXPORT_OK;sub import {my$me=shift;my$callpkg=caller(0);my$errs="";for(@_){if(exists$export_ok{$_}){if(/\A\$(.*)\z/s){*{$callpkg."::".$1}=\$$1}else {*{$callpkg."::".$_}=\&$_}}else {$errs .= "\"$_\" is not exported by the $me module\n"}}if($errs ne ""){die "${errs}Can't continue after import errors "."at @{[(caller(0))[1]]} line @{[(caller(0))[2]]}.\n"}}sub _is_string($) {my($arg)=@_;return defined($arg)&& ref(\$arg)eq "SCALAR"}our$module_name_rx=qr/[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*/;my$qual_module_spec_rx=qr#(?:/|::)[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;my$unqual_top_module_spec_rx=qr#[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$top_module_spec_rx=qr/$qual_module_spec_rx|$unqual_top_module_spec_rx/o;my$unqual_sub_module_spec_rx=qr#[0-9A-Z_a-z]+(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$sub_module_spec_rx=qr/$qual_module_spec_rx|$unqual_sub_module_spec_rx/o;sub is_module_name($) {_is_string($_[0])&& $_[0]=~ /\A$module_name_rx\z/o}*is_valid_module_name=\&is_module_name;sub check_module_name($) {unless(&is_module_name){die +(_is_string($_[0])? "`$_[0]'" : "argument")." is not a module name\n"}}sub module_notional_filename($) {&check_module_name;my($name)=@_;$name =~ s!::!/!g;return$name.".pm"}BEGIN {*_WORK_AROUND_HINT_LEAKAGE="$]" < 5.011 &&!("$]" >= 5.009004 && "$]" < 5.010001)? sub(){1}: sub(){0};*_WORK_AROUND_BROKEN_MODULE_STATE="$]" < 5.009 ? sub(){1}: sub(){0}}BEGIN {if(_WORK_AROUND_BROKEN_MODULE_STATE){eval q{
+ sub Module::Runtime::__GUARD__::DESTROY {
+ delete $INC{$_[0]->[0]} if @{$_[0]};
+ }
+ 1;
+ };die $@ if $@ ne ""}}sub require_module($) {local %^H if _WORK_AROUND_HINT_LEAKAGE;if(_WORK_AROUND_BROKEN_MODULE_STATE){my$notional_filename=&module_notional_filename;my$guard=bless([$notional_filename ],"Module::Runtime::__GUARD__");my$result=CORE::require($notional_filename);pop @$guard;return$result}else {return scalar(CORE::require(&module_notional_filename))}}sub use_module($;$) {my($name,$version)=@_;require_module($name);$name->VERSION($version)if @_ >= 2;return$name}sub use_package_optimistically($;$) {my($name,$version)=@_;my$fn=module_notional_filename($name);eval {local$SIG{__DIE__};require_module($name)};die $@ if $@ ne "" && ($@ !~ /\ACan't locate \Q$fn\E .+ at \Q@{[__FILE__]}\E line/s || $@ =~ /^Compilation\ failed\ in\ require
+ \ at\ \Q@{[__FILE__]}\E\ line/xm);$name->VERSION($version)if @_ >= 2;return$name}sub is_module_spec($$) {my($prefix,$spec)=@_;return _is_string($spec)&& $spec =~ ($prefix ? qr/\A$sub_module_spec_rx\z/o : qr/\A$top_module_spec_rx\z/o)}*is_valid_module_spec=\&is_module_spec;sub check_module_spec($$) {unless(&is_module_spec){die +(_is_string($_[1])? "`$_[1]'" : "argument")." is not a module specification\n"}}sub compose_module_name($$) {my($prefix,$spec)=@_;check_module_name($prefix)if defined$prefix;&check_module_spec;if($spec =~ s#\A(?:/|::)##){}else {$spec=$prefix."::".$spec if defined$prefix}$spec =~ s#/#::#g;return$spec}1;
+MODULE_RUNTIME
+
+$fatpacked{"Monitoring/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN';
+ package Monitoring::Plugin;use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT @STATUS_CODES);use Params::Validate qw(:all);use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);Monitoring::Plugin->mk_accessors(qw(shortname perfdata messages opts threshold));use Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES);our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT);our$VERSION="0.40";sub new {my$class=shift;my%args=validate(@_,{shortname=>0,usage=>0,version=>0,url=>0,plugin=>0,blurb=>0,extra=>0,license=>0,timeout=>0 },);my$shortname=Monitoring::Plugin::Functions::get_shortname(\%args);delete$args{shortname}if (exists$args{shortname});my$self={shortname=>$shortname,perfdata=>[],messages=>{warning=>[],critical=>[],ok=>[]},opts=>undef,threshold=>undef,};bless$self,$class;if (exists$args{usage}){require Monitoring::Plugin::Getopt;$self->opts(new Monitoring::Plugin::Getopt(%args))}return$self}sub add_perfdata {my ($self,%args)=@_;require Monitoring::Plugin::Performance;my$perf=Monitoring::Plugin::Performance->new(%args);push @{$self->perfdata},$perf}sub all_perfoutput {my$self=shift;return join(" ",map {$_->perfoutput}(@{$self->perfdata}))}sub set_thresholds {my$self=shift;require Monitoring::Plugin::Threshold;return$self->threshold(Monitoring::Plugin::Threshold->set_thresholds(@_))}sub plugin_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub plugin_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub nagios_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub nagios_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub max_state {Monitoring::Plugin::Functions::max_state(@_)}sub max_state_alt {Monitoring::Plugin::Functions::max_state_alt(@_)}sub check_threshold {my$self=shift;my%args;if ($#_==0 && (!ref $_[0]|| ref $_[0]eq "ARRAY")){%args=(check=>shift)}else {%args=validate (@_,{check=>1,warning=>0,critical=>0,})}if (exists$args{warning}|| exists$args{critical}){$self->set_thresholds(warning=>$args{warning},critical=>$args{critical},)}elsif (defined$self->threshold){}elsif (defined$self->opts){$self->set_thresholds(warning=>$self->opts->warning,critical=>$self->opts->critical,)}else {return UNKNOWN}return$self->threshold->get_status($args{check})}sub add_arg {my$self=shift;$self->opts->arg(@_)if$self->_check_for_opts}sub getopts {my$self=shift;$self->opts->getopts(@_)if$self->_check_for_opts}sub _check_for_opts {my$self=shift;croak "You have to supply a 'usage' param to Monitoring::Plugin::new() if you want to use Getopts from your Monitoring::Plugin object." unless ref$self->opts()eq 'Monitoring::Plugin::Getopt';return$self}sub add_message {my$self=shift;my ($code,@messages)=@_;croak "Invalid error code '$code'" unless defined($ERRORS{uc$code})|| defined($STATUS_TEXT{$code});$code=$STATUS_TEXT{$code}if$STATUS_TEXT{$code};$code=lc$code;croak "Error code '$code' not supported by add_message" if$code eq 'unknown' || $code eq 'dependent';$self->messages($code,[])unless$self->messages->{$code};push @{$self->messages->{$code}},@messages}sub check_messages {my$self=shift;my%args=@_;for my$code (qw(critical warning ok)){my$messages=$self->messages->{$code}|| [];if ($args{$code}){unless (ref$args{$code}eq 'ARRAY'){if ($code eq 'ok'){$args{$code}=[$args{$code}]}else {croak "Invalid argument '$code'"}}push @{$args{$code}},@$messages}else {$args{$code}=$messages}}Monitoring::Plugin::Functions::check_messages(%args)}1;
+MONITORING_PLUGIN
+
+$fatpacked{"Monitoring/Plugin/Config.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_CONFIG';
+ package Monitoring::Plugin::Config;use 5.006;use strict;use warnings;use Carp;use File::Spec;use base qw(Config::Tiny);my$FILENAME1='plugins.ini';my$FILENAME2='nagios-plugins.ini';my$FILENAME3='monitoring-plugins.ini';my$CURRENT_FILE=undef;my@MONITORING_CONFIG_PATH=qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios);my@CONFIG_PATH=qw(/etc /usr/local/etc /etc/opt);sub read {my$class=shift;unless ($_[0]){SEARCH: {if ($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH}){for (split /:/,($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH})){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}for (@MONITORING_CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file}for (@CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0]}$CURRENT_FILE=$_[0];$class->SUPER::read(@_)}sub read_string {my$class=ref $_[0]? ref shift : shift;my$self=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){push @{$self->{$ns}->{$1}},$2;next}return$self->_error("Syntax error at line $counter: '$_'")}$self}sub write {croak "Write access not permitted"}sub mp_getfile {return$CURRENT_FILE}1;
+MONITORING_PLUGIN_CONFIG
+
+$fatpacked{"Monitoring/Plugin/ExitResult.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_EXITRESULT';
+ package Monitoring::Plugin::ExitResult;use 5.006;use strict;use warnings;use overload '""'=>sub {shift->{message}};sub new {my$class=shift;return bless {return_code=>$_[0],message=>$_[1]},$class}sub message {shift->{message}}sub return_code {shift->{return_code}}sub code {shift->{return_code}}1;
+MONITORING_PLUGIN_EXITRESULT
+
+$fatpacked{"Monitoring/Plugin/Functions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_FUNCTIONS';
+ package Monitoring::Plugin::Functions;use 5.006;use strict;use warnings;use File::Basename;use Params::Validate qw(:types validate);use Math::Calc::Units;our$VERSION="0.40";our@STATUS_CODES=qw(OK WARNING CRITICAL UNKNOWN DEPENDENT);require Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES,qw(plugin_exit plugin_die check_messages));our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re);our%EXPORT_TAGS=(all=>[@EXPORT,@EXPORT_OK ],codes=>[@STATUS_CODES ],functions=>[qw(plugin_exit plugin_die check_messages max_state max_state_alt convert) ],);use constant OK=>0;use constant WARNING=>1;use constant CRITICAL=>2;use constant UNKNOWN=>3;use constant DEPENDENT=>4;our%ERRORS=('OK'=>OK,'WARNING'=>WARNING,'CRITICAL'=>CRITICAL,'UNKNOWN'=>UNKNOWN,'DEPENDENT'=>DEPENDENT,);our%STATUS_TEXT=reverse%ERRORS;my$value=qr/[-+]?[\d\.]+/;our$value_re=qr/$value(?:e$value)?/;my$_fake_exit=0;sub _fake_exit {@_ ? $_fake_exit=shift : $_fake_exit};my$_use_die=0;sub _use_die {@_ ? $_use_die=shift : $_use_die};sub get_shortname {my$arg=shift;my$shortname=undef;return$arg->{shortname}if (defined($arg->{shortname}));$shortname=$arg->{plugin}if (defined($arg->{plugin}));$shortname=uc basename($shortname || $ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);$shortname =~ s/^CHECK_(?:BY_)?//;$shortname =~ s/\..*$//;return$shortname}sub max_state {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return OK if grep {$_==OK}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return UNKNOWN}sub max_state_alt {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return OK if grep {$_==OK}@_;return UNKNOWN}sub plugin_exit {my ($code,$message,$arg)=@_;if (defined$code && ($code eq 'return_code' || $code eq 'message')){if (int(@_ / 2)!=@_ / 2 && ref $_[$#_]){$arg=pop @_}else {undef$arg}my%arg=@_;$code=$arg{return_code};$message=$arg{message}}$arg ||= {};$code=$ERRORS{$code}if defined$code && exists$ERRORS{$code};$code=UNKNOWN unless defined$code && exists$STATUS_TEXT{$code};$message='' unless defined$message;if (ref$message && ref$message eq 'ARRAY'){$message=join(' ',map {chomp;$_}@$message)}else {chomp$message}my$output="$STATUS_TEXT{$code}";if (defined$message && $message ne ''){$output .= " - " unless$message =~ /^\s*\n/mxs;$output .= $message}my$shortname=($arg->{plugin}? $arg->{plugin}->shortname : undef);$shortname ||= get_shortname();$output="$shortname $output" if$shortname;if ($arg->{plugin}){my$plugin=$arg->{plugin};$output .= " | ".$plugin->all_perfoutput if$plugin->perfdata && $plugin->all_perfoutput}$output .= "\n";if ($_fake_exit){require Monitoring::Plugin::ExitResult;return Monitoring::Plugin::ExitResult->new($code,$output)}_plugin_exit($code,$output)}sub _plugin_exit {my ($code,$output)=@_;if ($_use_die){for (my$i=0;;$i++){@_=caller($i);last unless @_;if ($_[3]=~ m/die/){$!=$code;die($output)}}}print$output;exit$code}sub plugin_die {my ($arg1,$arg2,$rest)=@_;if (defined$arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')){return plugin_exit(@_)}elsif (defined$arg1 && (exists$ERRORS{$arg1}|| exists$STATUS_TEXT{$arg1})){return plugin_exit(@_)}elsif (defined$arg2 && (exists$ERRORS{$arg2}|| exists$STATUS_TEXT{$arg2})){return plugin_exit($arg2,$arg1,$rest)}else {return plugin_exit(UNKNOWN,$arg1,$arg2)}}sub die {plugin_die(@_)}sub convert {my ($value,$from,$to)=@_;my ($newval)=Math::Calc::Units::convert("$value $from",$to,'exact');return$newval}sub check_messages {my%arg=validate(@_,{critical=>{type=>ARRAYREF },warning=>{type=>ARRAYREF },ok=>{type=>ARRAYREF | SCALAR,optional=>1 },'join'=>{default=>' ' },join_all=>0,});$arg{join}=' ' unless defined$arg{join};my$code=OK;$code ||= CRITICAL if @{$arg{critical}};$code ||= WARNING if @{$arg{warning}};return$code unless wantarray;my$message='';if ($arg{join_all}){$message=join($arg{join_all},map {@$_ ? join($arg{'join'},@$_): ()}$arg{critical},$arg{warning},$arg{ok}? (ref$arg{ok}? $arg{ok}: [$arg{ok}]): [])}else {$message ||= join($arg{'join'},@{$arg{critical}})if$code==CRITICAL;$message ||= join($arg{'join'},@{$arg{warning}})if$code==WARNING;$message ||= ref$arg{ok}? join($arg{'join'},@{$arg{ok}}): $arg{ok}if$arg{ok}}return ($code,$message)}1;
+MONITORING_PLUGIN_FUNCTIONS
+
+$fatpacked{"Monitoring/Plugin/Getopt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_GETOPT';
+ package Monitoring::Plugin::Getopt;use 5.006;use strict;use warnings;use File::Basename;use Getopt::Long qw(:config no_ignore_case bundling);use Carp;use Params::Validate qw(:all);use base qw(Class::Accessor);use Monitoring::Plugin::Functions;use Monitoring::Plugin::Config;use vars qw($VERSION);$VERSION=$Monitoring::Plugin::Functions::VERSION;my%DEFAULT=(timeout=>15,verbose=>0,license=>"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
+ It may be used, redistributed and/or modified under the terms of the GNU
+ General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",);my@ARGS=({spec=>'usage|?',help=>"-?, --usage\n Print usage information",},{spec=>'help|h',help=>"-h, --help\n Print detailed help screen",},{spec=>'version|V',help=>"-V, --version\n Print version information",},{spec=>'extra-opts:s@',help=>"--extra-opts=[section][\@file]\n Read options from an ini file. See https://www.monitoring-plugins.org/doc/extra-opts.html\n for usage and examples.",},{spec=>'timeout|t=i',help=>"-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)",default=>$DEFAULT{timeout},},{spec=>'verbose|v+',help=>"-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)",default=>$DEFAULT{verbose},},);my%DEFER_ARGS=map {$_=>1}qw(timeout verbose);sub _die {my$self=shift;my ($msg)=@_;$msg .= "\n" unless substr($msg,-1)eq "\n";Monitoring::Plugin::Functions::_plugin_exit(3,$msg)}sub _attr {my$self=shift;my ($item,$extra)=@_;$extra='' unless defined$extra;return '' unless$self->{_attr}->{$item};$self->{_attr}->{$item}."\n" .$extra}sub _spec_to_help {my ($self,$spec,$label)=@_;my ($opts,$type)=split /=|:|!/,$spec,2;my$optional=($spec =~ m/:/);my$boolean=($spec =~ m/!/);my (@short,@long);for (split /\|/,$opts){if (length $_==1){push@short,"-$_"}else {push@long,$boolean ? "--[no-]$_" : "--$_"}}my$help=join(', ',@short,@long);if ($type){if (!$label){if ($type eq 'i' || $type eq '+' || $type =~ /\d+/){$label='INTEGER'}else {$label='STRING'}}if ($optional){$help .= '[=' .$label .']'}else {$help .= '=' .$label}}elsif ($label){carp "Label specified, but there's no type in spec '$spec'"}$help .= "\n ";return$help}sub _options {my$self=shift;my@args=();my@defer=();for (@{$self->{_args}}){if (exists$DEFER_ARGS{$_->{name}}){push@defer,$_}else {push@args,$_}}my@options=();for my$arg (@args,@defer){my$help_array=ref$arg->{help}&& ref$arg->{help}eq 'ARRAY' ? $arg->{help}: [$arg->{help}];my$label_array=$arg->{label}&& ref$arg->{label}&& ref$arg->{label}eq 'ARRAY' ? $arg->{label}: [$arg->{label}];my$help_string='';for (my$i=0;$i <= $#$help_array;$i++){my$help=$help_array->[$i];if ($help =~ m/^\s*-/){$help_string .= $help}else {$help_string .= $self->_spec_to_help($arg->{spec},$label_array->[$i]).$help;$help_string .= "\n " if$i < $#$help_array}}if ($help_string =~ m/%s/){my$default=defined$arg->{default}? $arg->{default}: '';my$replaced=$help_string;$replaced =~ s|%s|$default|gmx;push@options,$replaced}else {push@options,$help_string}}return ' ' .join("\n ",@options)}sub _usage {my$self=shift;my$usage=$self->_attr('usage');$usage =~ s|%s|$self->{_attr}->{plugin}|gmx;return($usage)}sub _revision {my$self=shift;my$revision=sprintf "%s %s",$self->{_attr}->{plugin},$self->{_attr}->{version};$revision .= sprintf " [%s]",$self->{_attr}->{url}if$self->{_attr}->{url};$revision .= "\n";$revision}sub _help {my$self=shift;my$help='';$help .= $self->_revision ."\n";$help .= $self->_attr('license',"\n");$help .= $self->_attr('blurb',"\n");$help .= $self->_usage ? $self->_usage ."\n" : '';$help .= $self->_options ? $self->_options ."\n" : '';$help .= $self->_attr('extra',"\n");return$help}sub _process_specs_getopt_long {my$self=shift;my@opts=();for my$arg (@{$self->{_args}}){push@opts,$arg->{spec};my$spec=$arg->{spec};$spec =~ s/[=:!].*$//;my$name=(split /\s*\|\s*/,$spec)[0];$arg->{name}=$name;if (defined$self->{$name}){$arg->{default}=$self->{$name}}else {$self->{$name}=$arg->{default}}}return@opts}sub _check_required_opts {my$self=shift;my@missing=();for my$arg (@{$self->{_args}}){if ($arg->{required}&&!defined$self->{$arg->{name}}){push@missing,$arg->{name}}}if (@missing){$self->_die($self->_usage ."\n" .join("\n",map {sprintf "Missing argument: %s",$_}@missing)."\n")}}sub _process_opts {my$self=shift;$self->_die($self->_usage)if$self->{usage};$self->_die($self->_revision)if$self->{version};$self->_die($self->_help)if$self->{help}}sub _load_config_section {my$self=shift;my ($section,$file,$flags)=@_;$section ||= $self->{_attr}->{plugin};my$Config;eval {$Config=Monitoring::Plugin::Config->read($file)};$self->_die($@)if ($@);defined$Config or $self->_die(Monitoring::Plugin::Config->errstr);$file ||= $Config->mp_getfile();$self->_die("Invalid section '$section' in config file '$file'")unless exists$Config->{$section};return$Config->{$section}}sub _setup_spec_index {my$self=shift;return if defined$self->{_spec};$self->{_spec}={map {$_->{name}=>$_->{spec}}@{$self->{_args}}}}sub _cmdline_value {my$self=shift;local $_=shift;if (m/\s/ && (m/^[^"']/ || m/[^"']$/)){return qq("$_")}elsif ($_ eq ''){return q("")}else {return $_}}sub _cmdline {my$self=shift;my ($hash)=@_;$hash ||= $self;$self->_setup_spec_index;my@args=();for my$key (sort keys %$hash){next if$key =~ m/^_/;next if exists$DEFAULT{$key}&& $hash->{$key}eq $DEFAULT{$key};next if grep {$key eq $_}qw(help usage version extra-opts);next unless defined$hash->{$key};my$spec=$self->{_spec}->{$key}|| '';if ($spec =~ m/[=:].+$/){for my$value (ref$hash->{$key}eq 'ARRAY' ? @{$hash->{$key}}: ($hash->{$key})){$value=$self->_cmdline_value($value);if (length($key)> 1){push@args,sprintf "--%s=%s",$key,$value}else {push@args,"-$key",$value}}}else {push@args,(length($key)> 1 ? '--' : '-').$key}}return wantarray ? @args : join(' ',@args)}sub _process_extra_opts {my$self=shift;my ($args)=@_;my$extopts_list=$args->{'extra-opts'};my@sargs=();for my$extopts (@$extopts_list){$extopts ||= $self->{_attr}->{plugin};my$section=$extopts;my$file='';if ($extopts =~ m/^([^@]*)@(.*?)\s*$/){$section=$1;$file=$2}my$shash=$self->_load_config_section($section,$file);push@sargs,$self->_cmdline($shash)}@ARGV=(@sargs,@{$self->{_attr}->{argv}});printf "[extra-opts] %s %s\n",$self->{_attr}->{plugin},join(' ',@ARGV)if$args->{verbose}&& $args->{verbose}>= 3}sub arg {my$self=shift;my%args;my%params=(spec=>1,help=>1,default=>0,required=>0,label=>0,);if (exists$params{$_[0]}&& @_ % 2==0){%args=validate(@_,\%params)}else {my@order=qw(spec help default required label);@args{@order}=validate_pos(@_,@params{@order})}push @{$self->{_args}},\%args}sub getopts {my$self=shift;my@opt_array=$self->_process_specs_getopt_long;$self->{_attr}->{argv}=[@ARGV ];my$args1={};my$ok=GetOptions($args1,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_extra_opts($args1);$ok=GetOptions($self,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_opts;$self->_check_required_opts;$self->mk_ro_accessors(grep!/^_/,keys %$self);$SIG{ALRM}=sub {my$plugin=uc$self->{_attr}->{plugin};$plugin =~ s/^CHECK[-_]//i;$self->_die(sprintf("%s UNKNOWN - plugin timed out (timeout %ss)",$plugin,$self->timeout))}}sub _init {my$self=shift;my$plugin=basename($ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);my%attr=validate(@_,{usage=>1,version=>0,url=>0,plugin=>{default=>$plugin },blurb=>0,extra=>0,'extra-opts'=>0,license=>{default=>$DEFAULT{license}},timeout=>{default=>$DEFAULT{timeout}},});$self->{timeout}=delete$attr{timeout};$self->{_attr}={%attr };chomp foreach values %{$self->{_attr}};$self->{_args}=[@ARGS ];$self}sub new {my$class=shift;my$self=bless {},$class;$self->_init(@_)}1;
+MONITORING_PLUGIN_GETOPT
+
+$fatpacked{"Monitoring/Plugin/Performance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_PERFORMANCE';
+ package Monitoring::Plugin::Performance;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_ro_accessors(qw(label value uom warning critical min max));use Monitoring::Plugin::Functions;use Monitoring::Plugin::Threshold;use Monitoring::Plugin::Range;our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub import {my ($class,%attr)=@_;$_=$attr{use_die}|| 0;Monitoring::Plugin::Functions::_use_die($_)}my$value=qr/[-+]?[\d\.,]+/;my$value_re=qr/$value(?:e$value)?/;my$value_with_negative_infinity=qr/$value_re|~/;sub _parse {my$class=shift;my$string=shift;$string =~ /^'?([^'=]+)'?=($value_re)([\w%]*);?($value_with_negative_infinity\:?$value_re?)?;?($value_with_negative_infinity\:?$value_re?)?;?($value_re)?;?($value_re)?/o;return undef unless ((defined $1 && $1 ne "")&& (defined $2 && $2 ne ""));my@info=($1,$2,$3,$4,$5,$6,$7);map {defined$info[$_]&& $info[$_]=~ s/,/./go}(1,3,4,5,6);my$performance_value;{my$not_value;local$SIG{__WARN__}=sub {$not_value++};$performance_value=$info[1]+0;return undef if$not_value}my$p=$class->new(label=>$info[0],value=>$performance_value,uom=>$info[2],warning=>$info[3],critical=>$info[4],min=>$info[5],max=>$info[6]);return$p}sub _nvl {my ($self,$value)=@_;defined$value ? $value : ''}sub perfoutput {my$self=shift;my$label=$self->label;if ($label =~ / /){$label="'$label'"}my$value=$self->value;if ($value eq ''){$value='U'}my$out=sprintf "%s=%s%s;%s;%s;%s;%s",$label,$value,$self->_nvl($self->uom),$self->_nvl($self->warning),$self->_nvl($self->critical),$self->_nvl($self->min),$self->_nvl($self->max);$out =~ s/;;$//;return$out}sub parse_perfstring {my ($class,$perfstring)=@_;my@perfs=();my$obj;while ($perfstring){$perfstring =~ s/^\s*//;if (@{[$perfstring =~ /=/g]}> 1){$perfstring =~ s/^(.*?=.*?)\s//;if (defined $1){$obj=$class->_parse($1)}else {$perfstring="";$obj=$class->_parse($perfstring)}}else {$obj=$class->_parse($perfstring);$perfstring=""}push@perfs,$obj if$obj}return@perfs}sub rrdlabel {my$self=shift;my$name=$self->clean_label;return substr($name,0,19)}sub clean_label {my$self=shift;my$name=$self->label;if ($name eq "/"){$name="root"}elsif ($name =~ s/^\///){$name =~ s/\//_/g}$name =~ s/\W/_/g;return$name}sub threshold {my$self=shift;return Monitoring::Plugin::Threshold->set_thresholds(warning=>$self->warning,critical=>$self->critical)}sub new {my$class=shift;my%arg=@_;if (my$threshold=delete$arg{threshold}){$arg{warning}||= $threshold->warning ."";$arg{critical}||= $threshold->critical .""}$class->SUPER::new(\%arg)}1;
+MONITORING_PLUGIN_PERFORMANCE
+
+$fatpacked{"Monitoring/Plugin/Range.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_RANGE';
+ package Monitoring::Plugin::Range;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(start end start_infinity end_infinity alert_on));use Monitoring::Plugin::Functions qw(:DEFAULT $value_re);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;use overload 'eq'=>sub {shift->_stringify},'""'=>sub {shift->_stringify};use constant OUTSIDE=>0;use constant INSIDE=>1;sub _stringify {my$self=shift;return "" unless$self->is_set;return (($self->alert_on)? "@" : "").(($self->start_infinity==1)? "~:" : (($self->start==0)?"":$self->start.":")).(($self->end_infinity==1)? "" : $self->end)}sub is_set {my$self=shift;(!defined$self->alert_on)? 0 : 1}sub _set_range_start {my ($self,$value)=@_;$self->start($value+0);$self->start_infinity(0)}sub _set_range_end {my ($self,$value)=@_;$self->end($value+0);$self->end_infinity(0)}sub parse_range_string {my ($class,$string)=@_;my$valid=0;my$range=$class->new(start=>0,start_infinity=>0,end=>0,end_infinity=>1,alert_on=>OUTSIDE);$string =~ s/\s//g;unless ($string =~ /[\d~]/ && $string =~ m/^\@?($value_re|~)?(:($value_re)?)?$/){carp "invalid range definition '$string'";return undef}if ($string =~ s/^\@//){$range->alert_on(INSIDE)}if ($string =~ s/^~//){$range->start_infinity(1)}if ($string =~ m/^($value_re)?:/){my$start=$1;$range->_set_range_start($start)if defined$start;$range->end_infinity(1);$string =~ s/^($value_re)?://;$valid++}if ($string =~ /^($value_re)$/){$range->_set_range_end($string);$valid++}if ($valid && ($range->start_infinity==1 || $range->end_infinity==1 || $range->start <= $range->end)){return$range}return undef}sub check_range {my ($self,$value)=@_;my$false=0;my$true=1;if ($self->alert_on==INSIDE){$false=1;$true=0}if ($self->end_infinity==0 && $self->start_infinity==0){if ($self->start <= $value && $value <= $self->end){return$false}else {return$true}}elsif ($self->start_infinity==0 && $self->end_infinity==1){if ($value >= $self->start){return$false}else {return$true}}elsif ($self->start_infinity==1 && $self->end_infinity==0){if ($value <= $self->end){return$false}else {return$true}}else {return$false}}sub new {shift->SUPER::new({@_})}1;
+MONITORING_PLUGIN_RANGE
+
+$fatpacked{"Monitoring/Plugin/Threshold.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_THRESHOLD';
+ package Monitoring::Plugin::Threshold;use 5.006;use strict;use warnings;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(warning critical));use Monitoring::Plugin::Range;use Monitoring::Plugin::Functions qw(:codes plugin_die);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub get_status {my ($self,$value)=@_;$value=[$value ]if (ref$value eq "");for my$v (@$value){if ($self->critical->is_set){return CRITICAL if$self->critical->check_range($v)}}for my$v (@$value){if ($self->warning->is_set){return WARNING if$self->warning->check_range($v)}}return OK}sub _inflate {my ($self,$value,$key)=@_;return Monitoring::Plugin::Range->new if!defined$value;if (ref$value){plugin_die("Invalid $key object: type " .ref$value)unless$value->isa("Monitoring::Plugin::Range");return$value}return Monitoring::Plugin::Range->new if$value eq "";my$range=Monitoring::Plugin::Range->parse_range_string($value);plugin_die("Cannot parse $key range: '$value'")unless(defined($range));return$range}sub set_thresholds {my ($self,%arg)=@_;return$self->new(%arg)unless ref$self;$self->set($_,$arg{$_})foreach qw(warning critical)}sub set {my$self=shift;my ($key,$value)=@_;$self->SUPER::set($key,$self->_inflate($value,$key))}sub new {my ($self,%arg)=@_;$self->SUPER::new({map {$_=>$self->_inflate($arg{$_},$_)}qw(warning critical)})}1;
+MONITORING_PLUGIN_THRESHOLD
+
+$fatpacked{"Params/Validate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE';
+ package Params::Validate;use 5.008001;use strict;use warnings;our$VERSION='1.29';use Exporter;use Module::Implementation;use Params::Validate::Constants;use vars qw($NO_VALIDATION %OPTIONS $options);our@ISA='Exporter';my@types=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT);our%EXPORT_TAGS=('all'=>[qw(validate validate_pos validation_options validate_with),@types ],types=>\@types,);our@EXPORT_OK=(@{$EXPORT_TAGS{all}},'set_options');our@EXPORT=qw(validate validate_pos);$NO_VALIDATION=$ENV{PERL_NO_VALIDATION};{my$loader=Module::Implementation::build_loader_sub(implementations=>['XS','PP' ],symbols=>[qw(validate validate_pos validate_with validation_options set_options),],);$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP' if$ENV{PV_TEST_PERL};$loader->()}1;
+PARAMS_VALIDATE
+
+$fatpacked{"Params/Validate/Constants.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_CONSTANTS';
+ package Params::Validate::Constants;use strict;use warnings;our$VERSION='1.29';our@ISA='Exporter';our@EXPORT=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT UNKNOWN);sub SCALAR () {1}sub ARRAYREF () {2}sub HASHREF () {4}sub CODEREF () {8}sub GLOB () {16}sub GLOBREF () {32}sub SCALARREF () {64}sub UNKNOWN () {128}sub UNDEF () {256}sub OBJECT () {512}sub HANDLE () {16 | 32}sub BOOLEAN () {1 | 256}1;
+PARAMS_VALIDATE_CONSTANTS
+
+$fatpacked{"Params/Validate/PP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_PP';
+ package Params::Validate::PP;use strict;use warnings;our$VERSION='1.29';use Params::Validate::Constants;use Scalar::Util 1.10 ();our$options;sub validate_pos (\@@) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=shift;my@specs=@_;my@p=@$p;if ($Params::Validate::NO_VALIDATION){for (my$x=$#p + 1;$x <= $#specs;$x++ ){$p[$x]=$specs[$x]->{default}if ref$specs[$x]&& exists$specs[$x]->{default}}return wantarray ? @p : \@p}local$options ||= _get_options((caller(0))[0])unless defined$options;my$min=0;while (1){last unless (ref$specs[$min]?!(exists$specs[$min]->{default}|| $specs[$min]->{optional}): $specs[$min]);$min++}my$max=scalar@specs;my$actual=scalar@p;unless ($actual >= $min && ($options->{allow_extra}|| $actual <= $max)){my$minmax=($options->{allow_extra}? "at least $min" : ($min!=$max ? "$min - $max" : $max));my$val=$options->{allow_extra}? $min : $max;$minmax .= $val!=1 ? ' were' : ' was';my$called=_get_called();$options->{on_fail}->("$actual parameter" .($actual!=1 ? 's' : '')." " .($actual!=1 ? 'were' : 'was')." passed to $called but $minmax expected\n")}my$bigger=$#p > $#specs ? $#p : $#specs;for (0 .. $bigger){my$spec=$specs[$_];next unless ref$spec;if ($_ <= $#p){_validate_one_param($p[$_],\@p,$spec,'Parameter #' .($_ + 1).' (%s)')}$p[$_]=$spec->{default}if $_ > $#p && exists$spec->{default}}_validate_pos_depends(\@p,\@specs);for (grep {defined$p[$_]&&!ref$p[$_]&& ref$specs[$_]&& $specs[$_]{untaint}}0 .. $bigger){($p[$_])=$p[$_]=~ /(.+)/}return wantarray ? @p : \@p}sub _validate_pos_depends {my ($p,$specs)=@_;for my$p_idx (0 .. $#$p){my$spec=$specs->[$p_idx];next unless$spec && UNIVERSAL::isa($spec,'HASH')&& exists$spec->{depends};my$depends=$spec->{depends};if (ref$depends){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' for validate_pos() must be a scalar")}my$p_size=scalar @$p;if ($p_size < $depends - 1){my$error =("Parameter #" .($p_idx + 1)." depends on parameter #" .$depends .", which was not given");$options->{on_fail}->($error)}}return 1}sub _validate_named_depends {my ($p,$specs)=@_;for my$pname (keys %$p){my$spec=$specs->{$pname};next unless$spec && UNIVERSAL::isa($spec,'HASH')&& $spec->{depends};unless (UNIVERSAL::isa($spec->{depends},'ARRAY')||!ref$spec->{depends}){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' must be a scalar or arrayref")}for my$depends_name (ref$spec->{depends}? @{$spec->{depends}}: $spec->{depends}){unless (exists$p->{$depends_name}){my$error =("Parameter '$pname' depends on parameter '" .$depends_name ."', which was not given");$options->{on_fail}->($error)}}}}sub validate (\@$) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=$_[0];my$specs=$_[1];local$options=_get_options((caller(0))[0])unless defined$options;if (ref$p eq 'ARRAY'){if (ref$p->[0]){$p={%{$p->[0]}}}elsif (@$p % 2){my$called=_get_called();$options->{on_fail}->("Odd number of parameters in call to $called " ."when named parameters were expected\n")}else {$p={@$p}}}if ($options->{normalize_keys}){$specs=_normalize_callback($specs,$options->{normalize_keys});$p=_normalize_callback($p,$options->{normalize_keys})}elsif ($options->{ignore_case}|| $options->{strip_leading}){$specs=_normalize_named($specs);$p=_normalize_named($p)}if ($Params::Validate::NO_VALIDATION){return (wantarray ? ((map {$_=>$specs->{$_}->{default}}grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs),(ref$p eq 'ARRAY' ? (ref$p->[0]? %{$p->[0]}: @$p): %$p)): do {my$ref=(ref$p eq 'ARRAY' ? (ref$p->[0]? $p->[0]: {@$p}): $p);for (grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs){$ref->{$_}=$specs->{$_}->{default}unless exists$ref->{$_}}return$ref})}_validate_named_depends($p,$specs);unless ($options->{allow_extra}){if (my@unmentioned=grep {!exists$specs->{$_}}keys %$p){my$called=_get_called();$options->{on_fail}->("The following parameter" .(@unmentioned > 1 ? 's were' : ' was')." passed in the call to $called but " .(@unmentioned > 1 ? 'were' : 'was')." not listed in the validation options: @unmentioned\n")}}my@missing;keys %$specs;OUTER: while (my ($key,$spec)=each %$specs){if (!exists$p->{$key}&& (ref$spec ?!(do {if (exists$spec->{default}){$p->{$key}=$spec->{default};next OUTER}}|| do {next OUTER if$spec->{optional}}): $spec)){push@missing,$key}elsif (ref$spec){my$value=defined$p->{$key}? qq|"$p->{$key}"| : 'undef';_validate_one_param($p->{$key},$p,$spec,qq{The '$key' parameter (%s)})}}if (@missing){my$called=_get_called();my$missing=join ', ',map {"'$_'"}sort@missing;$options->{on_fail}->("Mandatory parameter" .(@missing > 1 ? 's' : '')." $missing missing in call to $called\n")}for my$key (grep {defined$p->{$_}&&!ref$p->{$_}&& ref$specs->{$_}&& $specs->{$_}{untaint}}keys %$p){($p->{$key})=$p->{$key}=~ /(.+)/}return wantarray ? %$p : $p}sub validate_with {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my%p=@_;local$options=_get_options((caller(0))[0],%p);unless ($Params::Validate::NO_VALIDATION){unless (exists$options->{called}){$options->{called}=(caller($options->{stack_skip}))[3]}}if (UNIVERSAL::isa($p{spec},'ARRAY')){return validate_pos(@{$p{params}},@{$p{spec}})}else {return&validate($p{params},$p{spec})}}sub _normalize_callback {my ($p,$func)=@_;my%new;for my$key (keys %$p){my$new_key=$func->($key);unless (defined$new_key){die "The normalize_keys callback did not return a defined value when normalizing the key '$key'"}if (exists$new{$new_key}){die "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'"}$new{$new_key}=$p->{$key}}return \%new}sub _normalize_named {my%h=(ref $_[0])=~ /ARRAY/ ? @{$_[0]}: %{$_[0]};if ($options->{ignore_case}){$h{lc $_ }=delete$h{$_}for keys%h}if ($options->{strip_leading}){for my$key (keys%h){my$new;($new=$key)=~ s/^\Q$options->{strip_leading}\E//;$h{$new}=delete$h{$key}}}return \%h}my%Valid=map {$_=>1}qw(callbacks can default depends isa optional regex type untaint);sub _validate_one_param {my ($value,$params,$spec,$id)=@_;if (exists$spec->{type}){unless (defined$spec->{type}&& Scalar::Util::looks_like_number($spec->{type})&& $spec->{type}> 0){my$msg ="$id has a type specification which is not a number. It is ";if (defined$spec->{type}){$msg .= "a string - $spec->{type}"}else {$msg .= "undef"}$msg .= ".\n Use the constants exported by Params::Validate to declare types.";$options->{on_fail}->(sprintf($msg,_stringify($value)))}unless (_get_type($value)& $spec->{type}){my$type=_get_type($value);my@is=_typemask_to_strings($type);my@allowed=_typemask_to_strings($spec->{type});my$article=$is[0]=~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was $article '@is', which " ."is not one of the allowed types: @allowed\n",_stringify($value)))}}return unless ($spec->{isa}|| $spec->{can}|| $spec->{callbacks}|| $spec->{regex});if (exists$spec->{isa}){for (ref$spec->{isa}? @{$spec->{isa}}: $spec->{isa}){unless (do {local $@=q{};eval {$value->isa($_)}}){my$is=ref$value ? ref$value : 'plain scalar';my$article1=$_ =~ /^[aeiou]/i ? 'an' : 'a';my$article2=$is =~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was not $article1 '$_' " ."(it is $article2 $is)\n",_stringify($value)))}}}if (exists$spec->{can}){for (ref$spec->{can}? @{$spec->{can}}: $spec->{can}){unless (do {local $@=q{};eval {$value->can($_)}}){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called does not have the method: '$_'\n",_stringify($value)))}}}if ($spec->{callbacks}){unless (UNIVERSAL::isa($spec->{callbacks},'HASH')){my$called=_get_called(1);$options->{on_fail}->("'callbacks' validation parameter for $called must be a hash reference\n")}for (keys %{$spec->{callbacks}}){unless (UNIVERSAL::isa($spec->{callbacks}{$_},'CODE')){my$called=_get_called(1);$options->{on_fail}->("callback '$_' for $called is not a subroutine reference\n")}my$ok;my$e=do {local $@=q{};local$SIG{__DIE__};$ok=eval {$spec->{callbacks}{$_}->($value,$params)};$@};if (!$ok){my$called=_get_called(1);if (ref$e){$options->{on_fail}->($e)}else {my$msg="$id to $called did not pass the '$_' callback";$msg .= ": $e" if length$e;$msg .= "\n";$options->{on_fail}->(sprintf($msg,_stringify($value)))}}}}if (exists$spec->{regex}){unless ((defined$value ? $value : '')=~ /$spec->{regex}/){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called did not pass regex check\n",_stringify($value)))}}}{my%isas=('ARRAY'=>ARRAYREF,'HASH'=>HASHREF,'CODE'=>CODEREF,'GLOB'=>GLOBREF,'SCALAR'=>SCALARREF,'REGEXP'=>SCALARREF,);my%simple_refs=map {$_=>1}keys%isas;sub _get_type {return UNDEF unless defined $_[0];my$ref=ref $_[0];unless ($ref){return GLOB if UNIVERSAL::isa(\$_[0],'GLOB');return SCALAR}return$isas{$ref}if$simple_refs{$ref};for (keys%isas){return$isas{$_}| OBJECT if UNIVERSAL::isa($_[0],$_)}return UNKNOWN}}{my%type_to_string=(SCALAR()=>'scalar',ARRAYREF()=>'arrayref',HASHREF()=>'hashref',CODEREF()=>'coderef',GLOB()=>'glob',GLOBREF()=>'globref',SCALARREF()=>'scalarref',UNDEF()=>'undef',OBJECT()=>'object',UNKNOWN()=>'unknown',);sub _typemask_to_strings {my$mask=shift;my@types;for (SCALAR,ARRAYREF,HASHREF,CODEREF,GLOB,GLOBREF,SCALARREF,UNDEF,OBJECT,UNKNOWN){push@types,$type_to_string{$_}if$mask & $_}return@types ? @types : ('unknown')}}{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>sub {require Carp;Carp::croak($_[0])},stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}sub _get_options {my$caller=shift;if (@_){return ($Params::Validate::OPTIONS{$caller}? {%{$Params::Validate::OPTIONS{$caller}},@_ }: {%defaults,@_ })}else {return (exists$Params::Validate::OPTIONS{$caller}? $Params::Validate::OPTIONS{$caller}: \%defaults)}}}sub _get_called {my$extra_skip=$_[0]|| 0;$extra_skip++;my$called=(exists$options->{called}? $options->{called}: (caller($options->{stack_skip}+ $extra_skip))[3]);$called='(unknown)' unless defined$called;return$called}sub _stringify {return defined $_[0]? qq{"$_[0]"} : 'undef'}1;
+PARAMS_VALIDATE_PP
+
+$fatpacked{"Params/Validate/XS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_XS';
+ package Params::Validate::XS;use strict;use warnings;our$VERSION='1.29';use Carp;my$default_fail=sub {Carp::confess($_[0])};{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>$default_fail,stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}use XSLoader;XSLoader::load(__PACKAGE__,exists$Params::Validate::XS::{VERSION}? ${$Params::Validate::XS::{VERSION}}: (),)}sub _check_regex_from_xs {return (defined $_[0]? $_[0]: '')=~ /$_[1]/ ? 1 : 0}1;
+PARAMS_VALIDATE_XS
+
+$fatpacked{"Params/ValidatePP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEPP';
+ package Params::Validate;our$VERSION='1.29';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP'}use Params::Validate;1;
+PARAMS_VALIDATEPP
+
+$fatpacked{"Params/ValidateXS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEXS';
+ package Params::Validate;our$VERSION='1.29';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='XS'}use Params::Validate;1;
+PARAMS_VALIDATEXS
+
+$fatpacked{"Try/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TRY_TINY';
+ package Try::Tiny;use 5.006;our$VERSION='0.30';use strict;use warnings;use Exporter 5.57 'import';our@EXPORT=our@EXPORT_OK=qw(try catch finally);use Carp;$Carp::Internal{+__PACKAGE__}++;BEGIN {my$su=$INC{'Sub/Util.pm'}&& defined&Sub::Util::set_subname;my$sn=$INC{'Sub/Name.pm'}&& eval {Sub::Name->VERSION(0.08)};unless ($su || $sn){$su=eval {require Sub::Util}&& defined&Sub::Util::set_subname;unless ($su){$sn=eval {require Sub::Name;Sub::Name->VERSION(0.08)}}}*_subname=$su ? \&Sub::Util::set_subname : $sn ? \&Sub::Name::subname : sub {$_[1]};*_HAS_SUBNAME=($su || $sn)? sub(){1}: sub(){0}}my%_finally_guards;sub try (&;@) {my ($try,@code_refs)=@_;my$wantarray=wantarray;my ($catch,@finally)=();for my$code_ref (@code_refs){if (ref($code_ref)eq 'Try::Tiny::Catch'){croak 'A try() may not be followed by multiple catch() blocks' if$catch;$catch=${$code_ref}}elsif (ref($code_ref)eq 'Try::Tiny::Finally'){push@finally,${$code_ref}}else {croak('try() encountered an unexpected argument (' .(defined$code_ref ? $code_ref : 'undef').') - perhaps a missing semi-colon before or')}}_subname(caller().'::try {...} '=>$try)if _HAS_SUBNAME;local$_finally_guards{guards}=[map {Try::Tiny::ScopeGuard->_new($_)}@finally ];my$prev_error=$@;my (@ret,$error);my$failed=not eval {$@=$prev_error;if ($wantarray){@ret=$try->()}elsif (defined$wantarray){$ret[0]=$try->()}else {$try->()};return 1};$error=$@;$@=$prev_error;if ($failed){push @$_,$error for @{$_finally_guards{guards}};if ($catch){for ($error){return$catch->($error)}}return}else {return$wantarray ? @ret : $ret[0]}}sub catch (&;@) {my ($block,@rest)=@_;croak 'Useless bare catch()' unless wantarray;_subname(caller().'::catch {...} '=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Catch'),@rest,)}sub finally (&;@) {my ($block,@rest)=@_;croak 'Useless bare finally()' unless wantarray;_subname(caller().'::finally {...} '=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Finally'),@rest,)}{package Try::Tiny::ScopeGuard;use constant UNSTABLE_DOLLARAT=>("$]" < '5.013002')? 1 : 0;sub _new {shift;bless [@_ ]}sub DESTROY {my ($code,@args)=@{$_[0]};local $@ if UNSTABLE_DOLLARAT;eval {$code->(@args);1}or do {warn "Execution of finally() block $code resulted in an exception, which " .'*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. ' .'Your program will continue as if this event never took place. ' ."Original exception text follows:\n\n" .(defined $@ ? $@ : '$@ left undefined...')."\n" }}}__PACKAGE__
+TRY_TINY
+
+s/^ //mg for values %fatpacked;
+
+my $class = 'FatPacked::'.(0+\%fatpacked);
+no strict 'refs';
+*{"${class}::files"} = sub { keys %{$_[0]} };
+
+if ($] < 5.008) {
+ *{"${class}::INC"} = sub {
+ if (my $fat = $_[0]{$_[1]}) {
+ my $pos = 0;
+ my $last = length $fat;
+ return (sub {
+ return 0 if $pos == $last;
+ my $next = (1 + index $fat, "\n", $pos) || $last;
+ $_ .= substr $fat, $pos, $next - $pos;
+ $pos = $next;
+ return 1;
+ });
+ }
+ };
+}
+
+else {
+ *{"${class}::INC"} = sub {
+ if (my $fat = $_[0]{$_[1]}) {
+ open my $fh, '<', \$fat
+ or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
+ return $fh;
+ }
+ return;
+ };
+}
+
+unshift @INC, bless \%fatpacked, $class;
+ } # END OF FATPACK CODE
+
+#
+# This is development version of the check_raid.pl plugin
+# If you are running this file directly, you are doing it wrong
+#
+# See installation notes:
+# https://github.com/glensc/nagios-plugin-check_raid#installing
+#
+use Monitoring::Plugin 0.37;
+use App::Monitoring::Plugin::CheckRaid;
+use App::Monitoring::Plugin::CheckRaid::Sudoers;
+use App::Monitoring::Plugin::CheckRaid::Plugin;
+use App::Monitoring::Plugin::CheckRaid::Utils;
+use warnings;
+use strict;
+
+my $PROGNAME = 'check_raid';
+my $VERSION = q/4.0.9/;
+my $URL = 'https://github.com/glensc/nagios-plugin-check_raid';
+my $BUGS_URL = 'https://github.com/glensc/nagios-plugin-check_raid#reporting-bugs';
+
+my $mp = Monitoring::Plugin->new(
+ usage =>
+ "Usage: %s [-h] [-V] [-S] [list of devices to ignore]",
+
+ version => $VERSION,
+ blurb => join($/,
+ "This plugin checks all RAID volumes (hardware and software) that can be identified",
+ "",
+ "Homepage: $URL",
+ "Reporting Bugs: $BUGS_URL",
+ ),
+
+ plugin => $PROGNAME,
+ shortname => $PROGNAME,
+);
+
+$mp->add_arg(
+ spec => 'sudoers|S',
+ help => 'Setup sudo rules',
+);
+$mp->add_arg(
+ spec => 'warnonly|W',
+ help => 'Treat CRITICAL errors as WARNING',
+);
+$mp->add_arg(
+ spec => 'debug|d',
+ help => 'debug mode, or dry-run for sudoers',
+);
+$mp->add_arg(
+ spec => 'list_plugins|list-plugins|l',
+ help => 'Lists active plugins',
+);
+$mp->add_arg(
+ spec => 'plugin|p=s@',
+ help => 'Force the use of selected plugins, comma separated',
+);
+$mp->add_arg(
+ spec => 'plugin-option=s@',
+ help => "Specify extra option for specific plugin.\n" .
+'
+Plugin options (key=>value pairs) passed as "options" key to each plugin constructor.
+The options are global, not plugin specific, but it\'s recommended to prefix option with plugin name.
+The convention is to have PLUGIN_NAME-OPTION_NAME=OPTION_VALUE syntax to namespace each plugin option.
+
+For example "--plugin-option=hp_msa-serial=/dev/ttyS2"
+would define option "serial" for "hp_msa" plugin with value "/dev/ttyS2".
+'
+);
+$mp->add_arg(
+ spec => 'noraid=s',
+ help => 'Return STATE if no RAID volumes are found. Defaults to UNKNOWN',
+);
+$mp->add_arg(
+ spec => 'resync=s',
+ help => 'Return STATE if RAID is in resync state. Defaults to WARNING',
+);
+$mp->add_arg(
+ spec => 'check=s',
+ help => 'Return STATE if RAID is in check state. Defaults to OK',
+);
+$mp->add_arg(
+ spec => 'cache-fail=s',
+ help => 'Set status as STATE if Write Cache is present but disabled. Defaults to WARNING',
+);
+$mp->add_arg(
+ spec => 'bbulearn=s',
+ help => 'Return STATE if Backup Battery Unit (BBU) learning cycle is in progress. Defaults to WARNING',
+);
+$mp->add_arg(
+ spec => 'bbu-monitoring',
+ help => 'Enable experimental monitoring of the BBU status',
+);
+
+$mp->getopts;
+
+if (@ARGV) {
+ @App::Monitoring::Plugin::CheckRaid::Utils::ignore = @ARGV;
+}
+
+my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3);
+
+my %plugin_options;
+
+if ($mp->opts->warnonly) {
+ App::Monitoring::Plugin::CheckRaid::Plugin->set_critical_as_warning;
+}
+if ($mp->opts->get('bbu-monitoring')) {
+ $plugin_options{options}{bbu_monitoring} = 1;
+}
+
+# setup state flags
+my %state_flags = (
+ 'resync' => 'resync_status',
+ 'check' => 'check_status',
+ 'noraid' => 'noraid_status',
+ 'bbulearn' => 'bbulearn_status',
+ 'cache-fail' => 'cache_fail_status',
+);
+while (my($opt, $key) = each %state_flags) {
+ if (my $value = $mp->opts->get($opt)) {
+ unless (exists $ERRORS{$value}) {
+ print "Invalid value: '$value' for --$opt\n";
+ exit $ERRORS{UNKNOWN};
+ }
+ $plugin_options{options}{$key} = $ERRORS{$value};
+ }
+}
+
+# enable only specified plugins
+if (my $plugins = $mp->opts->plugin) {
+ # split, as each value can contain commas
+ $plugin_options{enable_plugins} = [ map { split(/,/, $_) } @$plugins ];
+}
+
+if (my $opts = $mp->opts->get('plugin-option')) {
+ foreach my $o (@$opts) {
+ my($k, $v) = split(/=/, $o, 2);
+ $plugin_options{options}{$k} = $v;
+ }
+}
+
+my $mc = App::Monitoring::Plugin::CheckRaid->new(%plugin_options);
+
+$App::Monitoring::Plugin::CheckRaid::Utils::debug = $mp->opts->debug;
+
+if ($mp->opts->debug) {
+ print "$PROGNAME $VERSION\n";
+ print "Visit <$BUGS_URL> how to report bugs\n";
+ print "Please include output of **ALL** commands in bugreport\n\n";
+}
+
+if ($mp->opts->sudoers) {
+ my $res = sudoers($mp->opts->debug, $mc->active_plugins(1));
+ $mp->plugin_exit(OK, $res ? "sudoers updated" : "sudoers not updated");
+}
+
+my @plugins = $mc->active_plugins;
+if (!@plugins) {
+ $mp->plugin_exit($plugin_options{options}{noraid_status}, "No active plugins (No RAID found)");
+}
+
+# print active plugins
+if ($mp->opts->list_plugins) {
+ foreach my $p (@plugins) {
+ print $p->{name}, "\n";
+ }
+ my $count = @plugins;
+ warn "$count active plugins\n";
+ exit $ERRORS{OK};
+}
+
+my $message = '';
+my $status = $ERRORS{OK};
+
+# perform check of each active plugin
+foreach my $plugin (@plugins) {
+ # skip if no check method (not standalone checker)
+ next unless $plugin->can('check');
+
+ # perform the check
+ $plugin->check;
+ my $pn = $plugin->{name};
+
+ # collect results
+ unless (defined $plugin->status) {
+ $status = $ERRORS{UNKNOWN} if $ERRORS{UNKNOWN} > $status;
+ $message .= '; ' if $message;
+ $message .= "$pn:[Plugin error]";
+ next;
+ }
+ if ($plugin->message or $plugin->{options}{noraid_status} == $ERRORS{UNKNOWN}) {
+ $status = $plugin->status if $plugin->status > $status;
+ } else {
+ $status = $plugin->{options}{noraid_status} if $plugin->{options}{noraid_status} > $status;
+ }
+ $message .= '; ' if $message;
+ $message .= "$pn:[".$plugin->message."]";
+ $message .= ' | ' . $plugin->perfdata if $plugin->perfdata;
+ $message .= "\n" . $plugin->longoutput if $plugin->longoutput;
+}
+
+if ($message) {
+ if ($status == $ERRORS{OK}) {
+ print "OK: ";
+ } elsif ($status == $ERRORS{WARNING}) {
+ print "WARNING: ";
+ } elsif ($status == $ERRORS{CRITICAL}) {
+ print "CRITICAL: ";
+ } else {
+ print "UNKNOWN: ";
+ }
+ print "$message\n";
+} elsif ($plugin::options{noraid_status} != $ERRORS{UNKNOWN}) {
+ $status = $plugin::options{noraid_status};
+ print "No RAID configuration found\n";
+} else {
+ $status = $ERRORS{UNKNOWN};
+ print "No RAID configuration found (tried: ", join(', ', @plugins), ")\n";
+}
+exit $status;
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control
new file mode 100644
index 0000000..9ffb706
--- /dev/null
+++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control
@@ -0,0 +1,30 @@
+Homepage: https://github.com/glensc/nagios-plugin-check_raid
+Watch: https://github.com/glensc/nagios-plugin-check_raid "/glensc/nagios-plugin-check_raid/tree/([0-9.]+)"
+Suggests: cciss-vol-status (>= 1.10), mpt-status
+Version: 4.0.9
+Uploaders: Bernd Zeimetz <bzed@debian.org>
+Description: plugin to check sw/hw RAID status
+ The plugin looks for any known types of RAID configurations,
+ and checks them all.
+ Supports:
+ - Adaptec AAC RAID via aaccli or afacli or arcconf
+ - AIX software RAID via lsvg
+ - HP/Compaq Smart Array via cciss_vol_status (hpsa supported too)
+ - HP Smart Array Controllers and MSA Controllers via hpacucli
+ - HP Smart Array (MSA1500) via serial line
+ - Linux 3ware SATA RAID via tw_cli
+ - Linux Device Mapper RAID via dmraid
+ - Linux DPT/I2O hardware RAID controllers via /proc/scsi/dpt_i2o
+ - Linux GDTH hardware RAID controllers via /proc/scsi/gdth
+ - Linux LSI MegaRaid hardware RAID via CmdTool2
+ - Linux LSI MegaRaid hardware RAID via megarc
+ - Linux LSI MegaRaid hardware RAID via /proc/megaraid
+ - Linux MegaIDE hardware RAID controllers via /proc/megaide
+ - Linux MPT hardware RAID via mpt-status
+ - Linux software RAID (md) via /proc/mdstat
+ - LSI Logic MegaRAID SAS series via MegaCli
+ - LSI MegaRaid via lsraid
+ - Serveraid IPS via ipssend
+ - Solaris software RAID via metastat
+ - Areca SATA RAID Support via cli64/cli32
+ - Detecting SCSI devices or hosts with lsscsi
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright
new file mode 100644
index 0000000..ef9e592
--- /dev/null
+++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright
@@ -0,0 +1,9 @@
+2004-2006 Steve Shipway, university of auckland,
+2009-2013 Elan Ruusamäe <glen@pld-linux.org>
+
+License: GPL v2
+
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+