From 1e2387474a449452b78520b9ad96a8b4b5e99722 Mon Sep 17 00:00:00 2001 From: Harald Pfeiffer Date: Wed, 17 Apr 2019 19:07:19 +0200 Subject: initial commit of source fetch --- .../check_raid/Makefile | 4 + .../check_raid/check_raid | 6664 ++++++++++++++++++++ .../check_raid/control | 30 + .../check_raid/copyright | 9 + 4 files changed, 6707 insertions(+) create mode 100644 nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile create mode 100644 nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid create mode 100644 nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control create mode 100644 nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright (limited to 'nagios-plugins-contrib-24.20190301~bpo9+1/check_raid') 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) + { + # 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}"; + + # 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 () { + 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 is of format: + # [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ] + # [array all|] + # [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#] + # [logicaldrive all|#] + # [enclosure all|#:#|serialnumber=#|chassisname=#] + # [licensekey all|] + + # 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 () { # / 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 () { + 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 () { # 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=;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(){/^\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=){$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 +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 + +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". + + -- cgit v1.2.3