diff options
| author | mail_redacted_for_web | 2019-04-17 19:07:19 +0200 | 
|---|---|---|
| committer | mail_redacted_for_web | 2019-04-17 19:07:19 +0200 | 
| commit | 1e2387474a449452b78520b9ad96a8b4b5e99722 (patch) | |
| tree | 836889471eec7d2aac177405068e2a8f1e2b1978 /nagios-plugins-contrib-24.20190301~bpo9+1/check_raid | |
| download | nagios-plugins-contrib-1e2387474a449452b78520b9ad96a8b4b5e99722.tar.bz2 | |
initial commit of source fetch
Diffstat (limited to 'nagios-plugins-contrib-24.20190301~bpo9+1/check_raid')
4 files changed, 6707 insertions, 0 deletions
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile new file mode 100644 index 0000000..52de70c --- /dev/null +++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/Makefile @@ -0,0 +1,4 @@ +#/usr/bin/make -f + +include ../common.mk + diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid new file mode 100644 index 0000000..2d115b1 --- /dev/null +++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/check_raid @@ -0,0 +1,6664 @@ +#!/usr/bin/perl + +# This chunk of stuff was generated by App::FatPacker. To find the original +# file's code, look for the end of this BEGIN block or the string 'FATPACK' +BEGIN { +my %fatpacked; + +$fatpacked{"App/Monitoring/Plugin/CheckRaid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID'; +  package App::Monitoring::Plugin::CheckRaid; +   +  use Carp qw(croak); +  use Module::Pluggable 5.1 instantiate => 'new', sub_name => '_plugins'; +  use strict; +  use warnings; +   +  # constructor +  sub new { +  	my $class = shift; +   +  	croak 'Odd number of elements in argument hash' if @_ % 2; +   +  	my $self = { +  		@_, +  	}; +   +  	my $obj = bless $self, $class; +   +  	# setup search path for Module::Pluggable +  	$self->search_path(add => __PACKAGE__ . '::Plugins'); +   +  	# setup only certain plugins +  	if ($self->{enable_plugins}) { +  		my @plugins = map { +  			__PACKAGE__ . '::Plugins::' . $_ +  		} @{$self->{enable_plugins}}; +  		$self->only(\@plugins); +  	} +   +  	return $obj; +  } +   +  # create list of plugins +  sub plugins { +  	my ($this) = @_; +   +  	# call this once +  	if (!defined $this->{plugins}) { +  		my @plugins = $this->_plugins(%$this); +  		$this->{plugins} = \@plugins; +  	} +   +  	wantarray ? @{$this->{plugins}} : $this->{plugins}; +  } +   +  # get plugin by name +  sub plugin { +  	my ($this, $name) = @_; +   +  	if (!defined $this->{plugin_names}) { +  		my %names; +  		foreach my $plugin ($this->plugins) { +  			my $name = $plugin->{name}; +  			$names{$name} = $plugin; +  		} +  		$this->{plugin_names} = \%names; +  	} +   +  	croak "Plugin '$name' Can not be created" unless exists $this->{plugin_names}{$name}; +   +  	$this->{plugin_names}{$name}; +  } +   +  # Get active plugins. +  # Returns the plugin objects +  sub active_plugins { +  	my $this = shift; +  	# whether the query is for sudo rules +  	my $sudo = shift || 0; +   +  	my @plugins = (); +   +  	# go over all registered plugins +  	foreach my $plugin ($this->plugins) { +  		# skip if no check method (not standalone checker) +  		next unless $plugin->can('check'); +   +  		# skip inactive plugins (disabled or no tools available) +  		next unless $plugin->active($sudo); +   +  		push(@plugins, $plugin); +  	} +   +  	return wantarray ? @plugins : \@plugins; +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN'; +  package App::Monitoring::Plugin::CheckRaid::Plugin; +   +  use Carp qw(croak); +  use App::Monitoring::Plugin::CheckRaid::Utils; +  use strict; +  use warnings; +   +  # Nagios standard error codes +  my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3); +   +  # default plugin options +  our %options = ( +  	# status to set when RAID is in resync state +  	resync_status => $ERRORS{WARNING}, +   +  	# Status code to use when no raid volumes were detected +  	noraid_status => $ERRORS{UNKNOWN}, +   +  	# status to set when RAID is in check state +  	check_status => $ERRORS{OK}, +   +  	# status to set when PD is spare +  	spare_status => $ERRORS{OK}, +   +  	# status to set when BBU is in learning cycle. +  	bbulearn_status => $ERRORS{WARNING}, +   +  	# status to set when Write Cache has failed. +  	cache_fail_status => $ERRORS{WARNING}, +   +  	# check status of BBU +  	bbu_monitoring => 0, +  ); +   +  # return list of programs this plugin needs +  # @internal +  sub program_names { +  } +   +  # return hash of canonical commands that plugin can use +  # @internal +  sub commands { +  	{} +  } +   +  # return sudo rules if program needs it +  # may be SCALAR or LIST of scalars +  # @internal +  sub sudo { +  	(); +  } +   +  # constructor for plugins +  sub new { +  	my $class = shift; +   +  	croak 'Odd number of elements in argument hash' if @_ % 2; +  	croak 'Class is already a reference' if ref $class; +   +  	# convert to hash +  	my %args = @_; +   +  	# merge 'options' from param and class defaults +  	my %opts = %options; +  	%opts = (%options, %{$args{options}}) if $args{options}; +  	delete $args{options}; +   +  	# merge commands +  	my %commands = %{$class->commands}; +  	%commands = (%commands, %{$args{commands}}) if $args{commands}; +  	delete $args{commands}; +   +  	my $self = { +  		commands => \%commands, +  		sudo => $class->sudo ? find_sudo() : '', +  		options => \%opts, +  		%args, +   +  		# name of the plugin, without package namespace +  		name => ($class =~ /.*::([^:]+)$/), +   +  		status => undef, +  		message => undef, +  		perfdata => undef, +  		longoutput => undef, +  	}; +   +  	my $this = bless $self, $class; +   +  	# lookup program, if not defined by params +  	if (!$self->{program}) { +  		$self->{program} = which($this->program_names); +  	} +   +  	return $this; +  } +   +  # see if plugin is active (disabled or no tools available) +  sub active { +  	my $this = shift; +   +  	# no tool found, return false +  	return 0 unless $this->{program}; +   +  	# program file must exist, don't check for execute bit. #104 +  	-f $this->{program}; +  } +   +  # set status code for plugin result +  # does not overwrite status with lower value +  # returns the current status code +  sub status { +  	my ($this, $status) = @_; +   +  	if (defined $status) { +  		$this->{status} = $status unless defined($this->{status}) and $status < $this->{status}; +  	} +  	$this->{status}; +  } +   +  sub set_critical_as_warning { +  	$ERRORS{CRITICAL} = $ERRORS{WARNING}; +  } +   +  # helper to set status to WARNING +  # returns $this to allow fluent api +  sub warning { +  	my ($this) = @_; +  	$this->status($ERRORS{WARNING}); +  	return $this; +  } +   +  # helper to set status to CRITICAL +  # returns $this to allow fluent api +  sub critical { +  	my ($this) = @_; +  	$this->status($ERRORS{CRITICAL}); +  	return $this; +  } +   +  # helper to set status to UNKNOWN +  # returns $this to allow fluent api +  sub unknown { +  	my ($this) = @_; +  	$this->status($ERRORS{UNKNOWN}); +  	return $this; +  } +   +  # helper to set status to OK +  sub ok { +  	my ($this) = @_; +  	$this->status($ERRORS{OK}); +  	return $this; +  } +   +  # helper to set status for resync +  # returns $this to allow fluent api +  sub resync { +  	my ($this) = @_; +  	$this->status($this->{options}{resync_status}); +  	return $this; +  } +   +  # helper to set status for check +  # returns $this to allow fluent api +  sub check_status { +  	my ($this) = @_; +  	$this->status($this->{options}{check_status}); +  	return $this; +  } +   +  # helper to set status for no raid condition +  # returns $this to allow fluent api +  sub noraid { +  	my ($this) = @_; +  	$this->status($this->{options}{noraid_status}); +  	return $this; +  } +   +  # helper to set status for spare +  # returns $this to allow fluent api +  sub spare { +  	my ($this) = @_; +  	$this->status($this->{options}{spare_status}); +  	return $this; +  } +   +  # helper to set status for BBU learning cycle +  # returns $this to allow fluent api +  sub bbulearn { +  	my ($this) = @_; +  	$this->status($this->{options}{bbulearn_status}); +  	return $this; +  } +   +  # helper to set status when Write Cache fails +  # returns $this to allow fluent api +  sub cache_fail { +  	my ($this) = @_; +  	$this->status($this->{options}{cache_fail_status}); +  	return $this; +  } +   +  # helper to get/set bbu monitoring +  sub bbu_monitoring { +  	my ($this, $val) = @_; +   +  	if (defined $val) { +  		$this->{options}{bbu_monitoring} = $val; +  	} +  	$this->{options}{bbu_monitoring}; +  } +   +  # setup status message text +  sub message { +  	my ($this, $message) = @_; +  	if (defined $message) { +  		# TODO: append if already something there +  		$this->{message} = $message; +  	} +  	$this->{message}; +  } +   +  # Set performance data output. +  sub perfdata { +  	my ($this, $perfdata) = @_; +  	if (defined $perfdata) { +  		# TODO: append if already something there +  		$this->{perfdata} = $perfdata; +  	} +  	$this->{perfdata}; +  } +   +  # Set plugin long output. +  sub longoutput { +  	my ($this, $longoutput) = @_; +  	if (defined $longoutput) { +  		# TODO: append if already something there +  		$this->{longoutput} = $longoutput; +  	} +  	$this->{longoutput}; +  } +   +  # a helper to join similar statuses for items +  # instead of printing +  #  0: OK, 1: OK, 2: OK, 3: NOK, 4: OK +  # it would print +  #  0-2,4: OK, 3: NOK +  # takes as input list: +  #  { status => @items } +  sub join_status { +  	my $this = shift; +  	my %status = %{$_[0]}; +   +  	my @status; +  	for my $status (sort {$a cmp $b} keys %status) { +  		my $disks = $status{$status}; +  		my @s; +  		foreach my $disk (@$disks) { +  			push(@s, $disk); +  		} +  		push(@status, join(',', @s).'='.$status); +  	} +   +  	return join ' ', @status; +  } +   +  # return true if parameter is not in ignore list +  sub valid { +  	my $this = shift; +  	my ($v) = lc $_[0]; +   +  	foreach (@utils::ignore) { +  		return 0 if lc $_ eq $v; +  	} +  	return 1; +  } +   +  use constant K => 1024; +  use constant M => K * 1024; +  use constant G => M * 1024; +  use constant T => G * 1024; +   +  sub parse_bytes { +  	my ($this, $size) = @_; +   +  	if ($size =~ s/\sT//) { +  		return int($size) * T; +  	} +  	if ($size =~ s/\sG//) { +  		return int($size) * G; +  	} +  	if ($size =~ s/\sM//) { +  		return int($size) * M; +  	} +  	if ($size =~ s/\sK//) { +  		return int($size) * K; +  	} +   +  	return int($size); +  } +   +  sub format_bytes { +  	my $this = shift; +   +  	my ($bytes) = @_; +  	if ($bytes > T) { +  		return sprintf("%.2f TiB", $bytes / T); +  	} +  	if ($bytes > G) { +  		return sprintf("%.2f GiB", $bytes / G); +  	} +  	if ($bytes > M) { +  		return sprintf("%.2f MiB", $bytes / M); +  	} +  	if ($bytes > K) { +  		return sprintf("%.2f KiB", $bytes / K); +  	} +  	return "$bytes B"; +  } +   +  # disable sudo temporarily +  sub nosudo_cmd { +  	my ($this, $command, $cb) = @_; +   +  	my ($res, @res); +   +  	my $sudo = $this->{sudo}; +  	$this->{sudo} = 0; +   +  	if (wantarray) { +  		@res = $this->cmd($command, $cb); +  	} else { +  		$res = $this->cmd($command, $cb); +  	} +   +  	$this->{sudo} = $sudo; +   +  	return wantarray ? @res : $res; +  } +   +  # build up command for $command +  # returns open filehandle to process output +  # if command fails, program is exited (caller needs not to worry) +  sub cmd { +  	my ($this, $command, $cb) = @_; +   +  	my $debug = $App::Monitoring::Plugin::CheckRaid::Utils::debug; +   +  	# build up command +  	my @CMD = $this->{program}; +   +  	# add sudo if program needs +  	unshift(@CMD, @{$this->{sudo}}) if $> and $this->{sudo}; +   +  	my $args = $this->{commands}{$command} or croak "command '$command' not defined"; +   +  	# callback to replace args in command +  	my $cb_ = sub { +  		my $param = shift; +  		if ($cb) { +  			if (ref $cb eq 'HASH' and exists $cb->{$param}) { +  				return wantarray ? @{$cb->{$param}} : $cb->{$param}; +  			} +  			return &$cb($param) if ref $cb eq 'CODE'; +  		} +   +  		if ($param eq '@CMD') { +  			# command wanted, but not found +  			croak "Command for $this->{name} not found" unless defined $this->{program}; +  			return @CMD; +  		} +  		return $param; +  	}; +   +  	# add command arguments +  	my @cmd; +  	for my $arg (@$args) { +  		local $_ = $arg; +  		# can't do arrays with s/// +  		# this limits that @arg must be single argument +  		if (/@/) { +  			push(@cmd, $cb_->($_)); +  		} else { +  			s/([\$]\w+)/$cb_->($1)/ge; +  			push(@cmd, $_); +  		} +  	} +   +  	my $op = shift @cmd; +  	my $fh; +  	if ($op eq '=' and ref $cb eq 'SCALAR') { +  		# Special: use open2 +  		use IPC::Open2; +  		warn "DEBUG EXEC: $op @cmd" if $debug; +  		my $pid = open2($fh, $$cb, @cmd) or croak "open2 failed: @cmd: $!"; +  	} elsif ($op eq '>&2') { +  		# Special: same as '|-' but reads both STDERR and STDOUT +  		use IPC::Open3; +  		warn "DEBUG EXEC: $op @cmd" if $debug; +  		my $pid = open3(undef, $fh, $cb, @cmd); +   +  	} else { +  		warn "DEBUG EXEC: @cmd" if $debug; +  		open($fh, $op, @cmd) or croak "open failed: @cmd: $!"; +  	} +   +  	# for dir handles, reopen as opendir +  	if (-d $fh) { +  		undef($fh); +  		warn "DEBUG OPENDIR: $cmd[0]" if $debug; +  		opendir($fh, $cmd[0]) or croak "opendir failed: @cmd: $!"; +  	} +   +  	return $fh; +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/aaccli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::aaccli; +   +  # Adaptec ServeRAID +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'container list' => ['=', '@CMD'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +   +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd container list /full" +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $write = ""; +  	$write .= "open aac0\n"; +  	$write .= "container list /full\n"; +  	$write .= "exit\n"; +  	my $read = $this->cmd('container list', \$write); +   +  #File foo receiving all output. +  # +  #AAC0> +  #COMMAND: container list /full=TRUE +  #Executing: container list /full=TRUE +  #Num          Total  Oth Stripe          Scsi   Partition                                       Creation +  #Label Type   Size   Ctr Size   Usage   C:ID:L Offset:Size   State   RO Lk Task    Done%  Ent Date   Time +  #----- ------ ------ --- ------ ------- ------ ------------- ------- -- -- ------- ------ --- ------ -------- +  # 0    Mirror 74.5GB            Open    0:02:0 64.0KB:74.5GB Normal                        0  051006 13:48:54 +  # /dev/sda             Auth             0:03:0 64.0KB:74.5GB Normal                        1  051006 13:48:54 +  # +  # +  #AAC0> +  #COMMAND: logfile end +  #Executing: logfile end +  	while (<$read>) { +  		if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\S+\s+(\S+)/) { +  			next unless $this->valid($dsk); +  			$dsk =~ s#:#/#g; +  			next unless $this->valid($dsk); +   +  			push(@status, "$dsk:$stat"); +   +  			$this->critical if ($stat eq "Broken"); +  			$this->warning if ($stat eq "Rebuild"); +  			$this->warning if ($stat eq "Bld/Vfy"); +  			$this->critical if ($stat eq "Missing"); +  			if ($stat eq "Verify") { +  				$this->resync; +  			} +  			$this->warning if ($stat eq "VfyRepl"); +  		} +  	} +  	close $read; +   +  	return unless @status; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/afacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::afacli; +   +  # Adaptec AACRAID +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'container list' => ['=', '@CMD'], +  	} +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $write = ""; +  	$write .= "open afa0\n"; +  	$write .= "container list /full\n"; +  	$write .= "exit\n"; +   +  	my $read = $this->cmd('container list', \$write); +  	while (<$read>) { +  		# 0    Mirror  465GB            Valid   0:00:0 64.0KB: 465GB Normal                        0  032511 17:55:06 +  		# /dev/sda             root             0:01:0 64.0KB: 465GB Normal                        1  032511 17:55:06 +  		if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\s?\S+\s+(\S+)/) { +  			next unless $this->valid($dsk); +  			$dsk =~ s#:#/#g; +  			next unless $this->valid($dsk); +  			push(@status, "$dsk:$stat"); +   +  			$this->critical if ($stat eq "Broken"); +  			$this->warning if ($stat eq "Rebuild"); +  			$this->warning if ($stat eq "Bld/Vfy"); +  			$this->critical if ($stat eq "Missing"); +  			if ($stat eq "Verify") { +  				$this->resync; +  			} +  			$this->warning if ($stat eq "VfyRepl"); +  		} +  	} +  	close $read; +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/arcconf.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::arcconf; +   +  # Adaptec AAC-RAID +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'getstatus' => ['-|', '@CMD', 'GETSTATUS', '1'], +  		# 'nologs' does not exist in arcconf 6.50. #118 +  		'getconfig' => ['-|', '@CMD', 'GETCONFIG', '$ctrl', 'AL'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETSTATUS 1", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETCONFIG * AL", +  	); +  } +   +  sub parse_error { +  	my ($this, $message) = @_; +  	warn "arcconf: parse error: $message"; +  	$this->unknown->message("Parse Error: $message"); +  } +   +  # parse GETSTATUS command +  # parses +  # - number of controllers +  # - logical device tasks (if any running) +  sub parse_status { +  	my ($this) = @_; +   +  	my $count = 0; +  	my $ok = 0; +  	my $fh = $this->cmd('getstatus'); +  	my %s; +  	# controller task +  	my %task; +  	while (<$fh>) { +  		chomp; +  		# empty line or comment +  		next if /^$/ or /^#/; +   +  		# termination +  		if (/^Command completed successfully/) { +  			$ok = 1; +  			last; +  		} +   +  		if (my($c) = /^Controllers [Ff]ound: (\d+)/) { +  			$count = int($c); +  			next; +  		} +   +  		if (/^(\S.+) Task:$/) { +  			$task{type} = $1; +  			next; +  		} +   +  		if (/^\s+Logical device\s+: (\d+)/) { +  			$task{device} = $1; +  		} elsif (/^\s+Task ID\s+: (\d+)/) { +  			$task{id} = $1; +  		} elsif (/^\s+Current operation\s+: (.+)/) { +  			$task{operation} = $1; +  		} elsif (/^\s+Status\s+: (.+)/) { +  			$task{status} = $1; +  		} elsif (/^\s+Priority\s+: (.+)/) { +  			$task{priority} = $1; +  		} elsif (/^\s+Percentage complete\s+: (\d+)/) { +  			$task{percent} = $1; +  		} elsif (/^Invalid controller number/) { +  			; +  		} else { +  			warn "Unknown line: [$_]"; +  			# FIXME: ->message() gets overwritten later on +  			$this->unknown->message("Unknown line: [$_]"); +  		} +  	} +  	close($fh); +   +  	# Tasks seem to be Controller specific, but as we don't support over one controller, let it be global +  	$s{tasks} = { %task } if %task; +   +  	if ($count == 0) { +  		# if command completed, but no controllers, +  		# assume no hardware present +  		if (!$ok) { +  			$this->unknown->message("No controllers found!"); +  		} +  		return undef; +  	} +   +  	$s{ctrl_count} = $count; +   +  	return \%s; +  } +   +  # parse GETCONFIG for all controllers +  sub parse_config { +  	my ($this, $status) = @_; +   +  	my %c; +  	for (my $i = 1; $i <= $status->{ctrl_count}; $i++) { +  		$c{$i} = $this->parse_ctrl_config($i, $status->{ctrl_count}); +  	} +   +  	return { controllers => \%c }; +  } +   +  # parse GETCONFIG command for specific controller +  sub parse_ctrl_config { +  	my ($this, $ctrl, $ctrl_count) = @_; +   +  	# Controller information, Logical/Physical device info +  	my ($ld, $ch, $pd); +   +  	my $res = { controller => {}, logical => [], physical => [] }; +   +  	my $fh = $this->cmd('getconfig', { '$ctrl' => $ctrl }); +  	my ($section, $subsection, $ok); +  	my %sectiondata = (); +   +  	# called when data for section needs to be processed +  	my $flush = sub { +  		my $method = 'process_' . lc($section); +  		$method =~ s/[.\s]+/_/g; +  		$this->$method($res, \%sectiondata); +  		%sectiondata = (); +  	}; +  	my $subsection_reset = sub { +  		$ch = 0; +  		undef($ld); +  		undef($pd); +  		undef($subsection); +  	}; +  	while (<$fh>) { +  		chomp; +   +  		# empty line or comment +  		if (/^$/ or /^#/) { +  			&$subsection_reset; +  			next; +  		} +   +  		if (/^Command completed successfully/) { +  			$ok = 1; +  			last; +  		} +   +  		if (my($c) = /^Controllers [Ff]ound: (\d+)/) { +  			if ($c != $ctrl_count) { +  				# internal error?! +  				$this->unknown->message("Controller count mismatch"); +  			} +  			next; +  		} +   +  		# section start +  		if (/^---+/) { +  			if (my($s) = <$fh> =~ /^(\w.+)$/) { +  				# flush the lines +  				if (defined($section)) { +  					&$flush(); +  				} +   +  				$section = $s; +  				unless (<$fh> =~ /^---+/) { +  					$this->parse_error($_); +  				} +  				&$subsection_reset; +  				next; +  			} +  			$this->parse_error($_); +  		} +   +  		# sub section start +  		# there are also sections in subsections, but currently section names +  		# are unique enough +  		if (/^\s+---+/) { +  			if (my($s) = <$fh> =~ /^\s+(\S.+?)\s*?$/) { +  				$subsection = $s; +  				unless (<$fh> =~ /^\s+---+/) { +  					$this->parse_error($_); +  				} +  				next; +  			} +  			$this->parse_error($_); +  		} +   +  		warn("SKIP without section: [$_]\n"),next unless defined $section; +   +  		# regex notes: +  		# - value portion may be missing +  		# - value may be empty +  		# - value may be truncated (t/data/arcconf/issue47/getconfig) +  		my ($key, $value) = /^\s*(.+?)(?:\s+:\s*(.*?))?$/; +   +  		if ($section =~ /Controller [Ii]nformation/) { +  			if (not defined $subsection) { +  				$sectiondata{$key} = $value; +  			} else { +  				$sectiondata{$subsection}{$key} = $value; +  			} +   +  		} elsif ($section =~ /Physical Device [Ii]nformation/) { +  			if (my($c) = /Channel #(\d+)/) { +  				$ch = int($c); +  				undef($pd); +  				next; +   +  			} elsif (my($n) = /^\s+Device #(\d+)/) { +  				$pd = int($n); +  				next; +   +  			} else { +  				if (not defined $pd) { +  					$sectiondata{$ch}{$key} = $value; +  				} elsif (not defined $subsection) { +  					$sectiondata{$ch}{'pd'}{$pd}{$key} = $value; +  				} else { +  					$sectiondata{$ch}{'pd'}{$pd}{$subsection}{$key} = $value; +  				} +  			} +   +  		} elsif ($section =~ /Logical ([Dd]evice|drive) [Ii]nformation/) { +  			if (my($n) = /Logical (?:[Dd]evice|drive) [Nn]umber (\d+)/) { +  				$ld = int($n); +  			} else { +  				# skip lone line: issue87/getconfig +  				if (/No logical devices configured/) { +  					next; +  				} +  				if (not defined $ld) { +  					warn "LD undefined:[$_]\n"; +  					next; +  				} +  				if (not defined $subsection) { +  					$sectiondata{$ld}{$key} = $value; +  				} else { +  					$sectiondata{$ld}{$subsection}{$key} = $value; +  				} +  			} +   +  		} elsif ($section eq 'MaxCache 3.0 information') { +  			# not parsed yet +  		} elsif ($section eq 'Connector information') { +  			# not parsed yet +  		} else { +  			warn "NOT PARSED: [$section] [$_]"; +  		} +  	} +  	close $fh; +  	&$flush() if $section; +   +  	$this->unknown->message("Command did not succeed") unless defined $ok; +   +  	return $res; +  } +   +  # Process Controller Information section +  sub process_controller_information { +  	my ($this, $res, $data) = @_; +  	my $c = {}; +  	my $s; +   +  	# current section +  	my $cs = $data; +   +  	$c->{status} = $cs->{'Controller Status'}; +   +  	if (exists $cs->{$s = 'Defunct Disk Drive Count'} || exists $cs->{$s = 'Defunct disk drive count'}) { +  		$c->{defunct_count} = int($cs->{$s}); +  	} +   +  	if ($s = $cs->{'Logical devices/Failed/Degraded'}) { +  		my($td, $fd, $dd) = $s =~ m{(\d+)/(\d+)/(\d+)}; +  		$c->{logical_count} = int($td); +  		$c->{logical_failed} = int($fd); +  		$c->{logical_degraded} = int($dd); +  	} +  	# ARCCONF 9.30: Logical drives/Offline/Critical +  	if ($s = $cs->{'Logical drives/Offline/Critical'}) { +  		my($td2, $fd2, $dd2) = $s =~ m{(\d+)/(\d+)/(\d+)}; +  		$c->{logical_count} = int($td2); +  		$c->{logical_offline} = int($fd2); +  		$c->{logical_critical} = int($dd2); +  	} +   +  	$cs = $data->{'Controller Battery Information'}; +  	$c->{battery_status} = $cs->{Status} if exists $cs->{Status}; +  	$c->{battery_overtemp} = $cs->{'Over temperature'} if exists $cs->{'Over temperature'}; +   +  	if ($s = $cs->{'Capacity remaining'}) { +  		my ($bc) = $s =~ m{(\d+)\s*percent.*$}; +  		$c->{battery_capacity} = int($bc); +  	} +   +  	if ($s = $cs->{'Time remaining (at current draw)'}) { +  		my($d, $h, $m) = $s =~ /(\d+) days, (\d+) hours, (\d+) minutes/; +  		$c->{battery_time} = int($d) * 1440 + int($h) * 60 + int($m); +  		$c->{battery_time_full} = "${d}d${h}h${m}m"; +  	} +   +   +  	$cs = $data->{'Controller ZMM Information'}; +  	$c->{zmm_status} = $cs->{Status} if exists $cs->{'Status'}; +   +  	$res->{controller} = $c; +  } +   +  sub process_logical_device_information { +  	my ($this, $res, $data) = @_; +  	my $s; +   +  	my @ld; +  	while (my($ld, $cs) = each %$data) { +   +  		$ld[$ld]{id} = $ld; +  		if (exists $cs->{$s = 'RAID Level'} || exists $cs->{$s = 'RAID level'}) { +  			$ld[$ld]{raid} = $cs->{$s}; +  		} +  		$ld[$ld]{size} = $cs->{'Size'}; +  		$ld[$ld]{failed_stripes} = $cs->{'Failed stripes'} if exists $cs->{'Failed stripes'}; +  		$ld[$ld]{defunct_segments} = $cs->{'Defunct segments'} if exists $cs->{'Defunct segments'}; +   +  		if ($s = $cs->{'Status of Logical Device'} || $cs->{'Status of logical device'} || $cs->{'Status of logical drive'}) { +  			$ld[$ld]{status} = $s; +  		} +  		if ($s = $cs->{'Logical Device name'} || $cs->{'Logical device name'} || $cs->{'Logical drive name'}) { +  			$ld[$ld]{name} = $s; +  		} +   +  		#   Write-cache mode                         : Not supported] +  		#   Partitioned                              : Yes] +  		#   Number of segments                       : 2] +  		#   Drive(s) (Channel,Device)                : 0,0 0,1] +  		#   Defunct segments                         : No] +  	} +   +  	$res->{logical} = \@ld; +  } +   +  sub process_physical_device_information { +  	my ($this, $res, $data) = @_; +   +  	# Keys with no values: +  	# "Device #0" +  	# "Device is a Hard drive" +  	# +  	# ignored: +  	# /Transfer Speed\s+:\s+(.+)/ +  	# /Initiator at SCSI ID/ +  	# /No physical drives attached/ +   +  	my (@pd, $cs, $s); +  	while (my($ch, $channel_data) = each %$data) { +  		while (my($pd, $cs) = each %{$channel_data->{pd}}) { +  			$pd[$ch][$pd]{device_id} = $pd; +  			$pd[$ch][$pd]{power_state} = $cs->{'Power State'} if exists $cs->{'Power State'}; +  			$pd[$ch][$pd]{status} = $cs->{'State'} if exists $cs->{'State'}; +  			$pd[$ch][$pd]{supported} = $cs->{'Supported'} if exists $cs->{'Supported'}; +  			$pd[$ch][$pd]{spare} = $cs->{'Dedicated Spare for'} if exists $cs->{'Dedicated Spare for'}; +  			$pd[$ch][$pd]{model} = $cs->{'Model'}; +  			$pd[$ch][$pd]{serial} = $cs->{'Serial number'} if exists $cs->{'Serial number'}; +  			$pd[$ch][$pd]{wwn} = $cs->{'World-wide name'} if exists $cs->{'World-wide name'}; +  			$pd[$ch][$pd]{write_cache} = $cs->{'Write Cache'} if exists $cs->{'Write Cache'}; +  			$pd[$ch][$pd]{ssd} = $cs->{'SSD'} if exists $cs->{'SSD'}; +  			$pd[$ch][$pd]{fru} = $cs->{'FRU'} if exists $cs->{'FRU'}; +  			$pd[$ch][$pd]{ncq} = $cs->{'NCQ status'} if exists $cs->{'NCQ status'}; +  			$pd[$ch][$pd]{pfa} = $cs->{'PFA'} if exists $cs->{'PFA'}; +  			$pd[$ch][$pd]{enclosure} = $cs->{'Enclosure ID'} if exists $cs->{'Enclosure ID'}; +  			$pd[$ch][$pd]{type} = $cs->{'Type'} if exists $cs->{'Type'}; +  			$pd[$ch][$pd]{smart} = $cs->{'S.M.A.R.T.'} if exists $cs->{'S.M.A.R.T.'}; +  			$pd[$ch][$pd]{smart_warn} = $cs->{'S.M.A.R.T. warnings'} if exists $cs->{'S.M.A.R.T. warnings'}; +  			$pd[$ch][$pd]{speed} = $cs->{'Transfer Speed'} if $cs->{'Transfer Speed'}; +  			$pd[$ch][$pd]{power_states} = $cs->{'Supported Power States'} if exists $cs->{'Supported Power States'}; +  			$pd[$ch][$pd]{fail_ldev_segs} = $cs->{'Failed logical device segments'} if exists $cs->{'Failed logical device segments'}; +   +  			# allow edits, i.e removed 'Vendor'/'Firmware' value from test data +  			$pd[$ch][$pd]{vendor} = $cs->{'Vendor'} || ''; +  			$pd[$ch][$pd]{firmware} = $cs->{'Firmware'} if exists $cs->{'Firmware'}; +   +  			# previous parser was not exact line match +  			if ($s = $cs->{'Size'} || $cs->{'Total Size'}) { +  				$pd[$ch][$pd]{size} = $s; +  			} +   +  			$s = $cs->{'Reported ESD'} || $cs->{'Reported ESD(T:L)'}; +  			$pd[$ch][$pd]{esd} = $s if $s; +   +  			if ($s = $cs->{'Reported Location'}) { +  				my($e, $s) = $s =~ /(?:Enclosure|Connector) (\d+), (?:Slot|Device) (\d+)/; +  				$pd[$ch][$pd]{location} = "$e:$s"; +  			} +   +  			if ($s = $cs->{'Reported Channel,Device'} || $cs->{'Reported Channel,Device(T:L)'}) { +  				$pd[$ch][$pd]{cd} = $s; +  			} +   +  			if (exists $cs->{$s = 'Device is a Hard drive'} +  				|| exists $cs->{$s = 'Device is an Enclosure'} +  				|| exists $cs->{$s = 'Device is an Enclosure services device'} +  				|| exists $cs->{$s = 'Device is an Enclosure Services Device'} +  			) { +  				($pd[$ch][$pd]{devtype}) = $s =~ /Device is an?\s+(.+)/; +  			} +   +  			# TODO: normalize and other formats: +  			# Current Temperature                : 27 deg C +  			# Life-time Temperature Recorded +  			# Temperature                              : 51 C/ 123 F (Normal) +  			# Temperature                     : Normal +  			# Temperature                        : Not Supported +  			# Temperature Sensor Status 1     : 21 C/ 69 F (Normal) +  			# Temperature Sensor Status 1     : 23 C/ 73 F (Normal) +  			# Temperature Sensor Status 1     : 27 C/ 80 F (Normal) +  			# Temperature Sensor Status 1     : 46 C/ 114 F (Abnormal) +  			# Temperature status              : Normal +  			# Threshold Temperature              : 51 deg C +  			# FIXME: previous code used last line with /Temperature/ match +  			if ($s = $cs->{'Temperature'} || $cs->{'Temperature Sensor Status 1'} || $cs->{'Temperature status'}) { +  				$pd[$ch][$pd]{temperature} = $s; +  			} +   +  			# ignored: +  			# Status of Enclosure +  			# (Fan \d+|Speaker) status/ +  			# /Expander ID\s+:/ +  			# /Enclosure Logical Identifier\s+:/ +  			# /Expander SAS Address\s+:/ +  			# /[Mm]axCache (Capable|Assigned)\s+:\s+(.+)/ +  			# /Power supply \d+ status/ +  		} +  	} +   +  	$res->{physical} = \@pd; +  } +   +  sub process_logical_drive_information { +  	shift->process_logical_device_information(@_); +  } +   +  sub process_maxcache_3_0_information { +  } +   +  # TODO: issue152/arc2_getconfig.txt +  sub process_connector_information { +  } +   +  # NB: side effect: ARCCONF changes current directory to /var/log +  sub parse { +  	my ($this) = @_; +   +  	# we chdir to /var/log, as tool is creating 'UcliEvt.log' +  	# this can be disabled with 'nologs' parameter, but not sure do all versions support it +  	chdir('/var/log') || chdir('/'); +   +  	my ($status, $config); +  	$status = $this->parse_status or return; +  	$config = $this->parse_config($status) or return; +   +  	return { %$status, %$config }; +  } +   +  # check for controller status +  sub check_controller { +  	my ($this, $c) = @_; +   +  	my @status; +   +  	$this->critical if $c->{status} !~ /Optimal|Okay|OK/; +  	push(@status, "Controller:$c->{status}"); +   +  	if ($c->{defunct_count} > 0) { +  		$this->critical; +  		push(@status, "Defunct drives:$c->{defunct_count}"); +  	} +   +  	if (defined $c->{logical_failed} && $c->{logical_failed} > 0) { +  		$this->critical; +  		push(@status, "Failed drives:$c->{logical_failed}"); +  	} +   +  	if (defined $c->{logical_degraded} && $c->{logical_degraded} > 0) { +  		$this->critical; +  		push(@status, "Degraded drives:$c->{logical_degraded}"); +  	} +   +  	if (defined $c->{logical_offline} && $c->{logical_offline} > 0) { +  		$this->critical; +  		push(@status, "Offline drives:$c->{logical_offline}"); +  	} +   +  	if (defined $c->{logical_critical} && $c->{logical_critical} > 0) { +  		$this->critical; +  		push(@status, "Critical drives:$c->{logical_critical}"); +  	} +   +  	# ZMM (Zero-Maintenance Module) status +  	if (defined($c->{zmm_status})) { +  		push(@status, "ZMM Status: $c->{zmm_status}"); +  	} +   +  	# Battery status +  	if ($this->bbu_monitoring) { +  		my @s = $this->battery_status($c); +  		push(@status, @s) if @s; +  	} +   +  	return @status; +  } +   +  # check for physical devices +  sub check_physical { +  	my ($this, $p) = @_; +   +  	my %pd; +  	$this->{pd_resync} = 0; +  	for my $ch (@$p) { +  		for my $pd (@{$ch}) { +  			# skip not disks +  			next if not defined $pd; +  			next if $pd->{devtype} =~ m/Enclosure/; +   +  			if ($pd->{status} eq 'Rebuilding') { +  				$this->resync; +  				$this->{pd_resync}++; +   +  			} elsif ($pd->{status} eq 'Dedicated Hot-Spare') { +  				$this->spare; +  				$pd->{status} = "$pd->{status} for $pd->{spare}"; +   +  			} elsif ($pd->{status} !~ /^Online|Hot[- ]Spare|Ready/) { +  				$this->critical; +  			} +   +  			my $id = $pd->{serial} || $pd->{wwn} || $pd->{location} || $pd->{cd}; +  			push(@{$pd{$pd->{status}}}, $id); +  		} +  	} +   +  	return \%pd; +  } +   +  # check for logical devices +  sub check_logical { +  	my ($this, $l) = @_; +   +  	my @status; +  	for my $ld (@$l) { +  		next unless $ld; # FIXME: fix that script assumes controllers start from '0' +   +  		if ($ld->{status} eq 'Degraded' && $this->{pd_resync}) { +  			$this->warning; +  		} elsif ($ld->{status} !~ /Optimal|Okay/) { +  			$this->critical; +  		} +   +  		my $id = $ld->{id}; +  		if ($ld->{name}) { +  			$id = "$id($ld->{name})"; +  		} +  		push(@status, "Logical Device $id:$ld->{status}"); +   +  		if (defined $ld->{failed_stripes} && $ld->{failed_stripes} ne 'No') { +  			push(@status, "Failed stripes: $ld->{failed_stripes}"); +  		} +  		if (defined $ld->{defunct_segments} && $ld->{defunct_segments} ne 'No') { +  			push(@status, "Defunct segments: $ld->{defunct_segments}"); +  		} +  	} +   +  	return @status; +  } +   +  sub check { +  	my $this = shift; +   +  	my $data = $this->parse; +  	$this->unknown,return unless $data; +   +  	my @status; +   +  	for my $i (sort {$a cmp $b} keys %{$data->{controllers}}) { +  		my $c = $data->{controllers}->{$i}; +   +  		push(@status, $this->check_controller($c->{controller})); +   +  		# current (logical device) tasks +  		if ($data->{tasks}->{operation} ne 'None') { +  			# just print it. no status change +  			my $task = $data->{tasks}; +  			push(@status, "$task->{type} #$task->{device}: $task->{operation}: $task->{status} $task->{percent}%"); +  		} +   +  		# check physical first, as it setups pd_resync flag +  		my $pd = $this->check_physical($c->{physical}); +   +  		push(@status, $this->check_logical($c->{logical})); +   +  		# but report after logical devices +  		push(@status, "Drives: ".$this->join_status($pd)) if $pd; +  	} +   +  	$this->ok->message(join(', ', @status)); +  } +   +  # check battery status in $c +  sub battery_status { +  	my ($this, $c) = @_; +   +  	my @status; +   +  	if (!defined($c->{battery_status}) || $c->{battery_status} eq 'Not Installed') { +  		return; +  	} +   +  	push(@status, "Battery Status: $c->{battery_status}"); +   +  	# if battery status is 'Failed', none of the details below are available. #105 +  	if ($c->{battery_status} eq 'Failed') { +  		$this->critical; +  		return @status; +  	} +   +  	# detailed battery checks +  	if ($c->{battery_overtemp} ne 'No') { +  		$this->critical; +  		push(@status, "Battery Overtemp: $c->{battery_overtemp}"); +  	} +   +  	push(@status, "Battery Capacity Remaining: $c->{battery_capacity}%"); +  	if ($c->{battery_capacity} < 50) { +  		$this->critical; +  	} +  	if ($c->{battery_capacity} < 25) { +  		$this->warning; +  	} +   +  	if ($c->{battery_time} < 1440) { +  		$this->warning; +  	} +  	if ($c->{battery_time} < 720) { +  		$this->critical; +  	} +   +  	if ($c->{battery_time} < 60) { +  		push(@status, "Battery Time: $c->{battery_time}m"); +  	} else { +  		push(@status, "Battery Time: $c->{battery_time_full}"); +  	} +   +  	return @status; +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/areca.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::areca; +   +  ## Areca SATA RAID Support +  ## requires cli64 or cli32 binaries +  ## For links to manuals and binaries, see this issue: +  ## https://github.com/glensc/nagios-plugin-check_raid/issues/10 +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(areca-cli areca_cli64 areca_cli32 cli64 cli32); +  } +   +  sub commands { +  	{ +  		'rsf info' => ['-|', '@CMD', 'rsf', 'info'], +  		'disk info' => ['-|', '@CMD', 'disk', 'info'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd rsf info", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd disk info", +  	); +  } +   +  # plugin check +  # can store its exit code in $this->status +  # can output its message in $this->message +  sub check { +  	my $this = shift; +   +  	## Check Array Status +  	my (@status, %arrays); +  	my $fh = $this->cmd('rsf info'); +  	while (<$fh>) { +  =cut +   #  Name             Disks TotalCap  FreeCap MinDiskCap         State +   #  Name             Disks TotalCap  FreeCap DiskChannels       State +  =============================================================================== +   1  Raid Set # 000      23 34500.0GB    0.0GB   1500.0GB         Normal +   1  Raid Set # 00       15 15000.0GB    0.0GB 123G567C9AB48EF    Normal +   1  data                15 11250.0GB    0.0GB 123456789ABCDEF    Normal +   1  data                15 11250.0GB    0.0GB 123456789ABCDEF    Initializing +  =============================================================================== +  =cut +  		next unless (my($id, $n, $s) = m{^ +  			\s*(\d+)    # Id +  			\s+(.+)     # Name +  			\s+\d+      # Disks +  			\s+\S+      # TotalCap +  			\s+\S+      # FreeCap +  			\s+\S+      # MinDiskCap/DiskChannels +  			\s+(\S+)\s* # State +  		$}x); +   +  		# trim trailing spaces from name +  		$n =~ s/\s+$//; +   +  		if ($s =~ /[Rr]e[Bb]uild/) { +  			$this->warning; +  		} elsif ($s !~ /[Nn]ormal|[Rr]e[Bb]uild|Checking|Initializing/) { +  			$this->critical; +  		} +   +  		push(@status, "Array#$id($n): $s"); +   +  		$arrays{$n} = [ $id, $s ]; +  	} +  	close $fh; +   +  	## Check Drive Status +  	$fh = $this->cmd('disk info'); +  	my %drivestatus; +  	while (<$fh>) { +  		chomp; +  =cut +    # Enc# Slot#   ModelName                        Capacity  Usage +  =============================================================================== +    1  01  Slot#1  N.A.                                0.0GB  N.A. +    8  01  Slot#8  N.A.                                0.0GB  N.A. +    9  02  SLOT 01 ST31500341AS                     1500.3GB  Raid Set # 000 +   11  02  SLOT 03 ST31500341AS                     1500.3GB  Raid Set # 000 +   +    # Ch# ModelName                       Capacity  Usage +  =============================================================================== +    1  1  ST31000340NS                    1000.2GB  Raid Set # 00 +    6  6  ST31000340NS                    1000.2GB  Raid Set # 00 +    3  3  WDC WD7500AYYS-01RCA0            750.2GB  data +    4  4  WDC WD7500AYYS-01RCA0            750.2GB  data +   16 16  WDC WD7500AYYS-01RCA0            750.2GB  HotSpare[Global] +  =cut +  		next unless my($id, $model, $usage) = m{^ +  			\s*(\d+)      # Id +  			\s+\d+        # Channel/Enclosure (not reliable, tests 1,2,12 differ) +  			\s+(.+)       # ModelName +  			\s+\d+.\d\S+  # Capacity +  			\s+(.+)       # Usage (Raid Name) +  		}x; +   +  		# trim trailing spaces from name +  		$usage =~ s/\s+$//; +   +  		# Asssume model N.A. means the slot not in use +  		# we could also check for Capacity being zero, but this seems more +  		# reliable. +  		next if $usage eq 'N.A.'; +   +  		# use array id in output: shorter +  		my $array_id = defined($arrays{$usage}) ? ($arrays{$usage})->[0] : undef; +  		my $array_name = defined $array_id ? "Array#$array_id" : $usage; +   +  		# assume critical if Usage is not one of: +  		# - existing Array name +  		# - HotSpare +  		# - Rebuild +  		if (defined($arrays{$usage})) { +  			# Disk in Array named $usage +  			push(@{$drivestatus{$array_name}}, $id); +  		} elsif ($usage =~ /[Rr]e[Bb]uild/) { +  			# rebuild marks warning +  			push(@{$drivestatus{$array_name}}, $id); +  			$this->warning; +  		} elsif ($usage =~ /HotSpare/) { +  			# hotspare is OK +  			push(@{$drivestatus{$array_name}}, $id); +  		} elsif ($usage =~ /Pass Through/) { +  			# Pass Through is OK +  			push(@{$drivestatus{$array_name}}, $id); +  		} else { +  			push(@{$drivestatus{$array_name}}, $id); +  			$this->critical; +  		} +  	} +  	close $fh; +   +  	push(@status, "Drive Assignment: ".$this->join_status(\%drivestatus)) if %drivestatus; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cciss.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::cciss; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi; +  use App::Monitoring::Plugin::CheckRaid::Plugins::smartctl; +  use strict; +  use warnings; +   +  sub program_names { +  	'cciss_vol_status'; +  } +   +  sub commands { +  	{ +  		'controller status' => ['-|', '@CMD', '@devs'], +  		'controller status verbose' => ['-|', '@CMD', '-V', '@devs'], +  		'cciss_vol_status version' => ['>&2', '@CMD', '-v'], +   +  		'detect hpsa' => ['<', '/sys/module/hpsa/refcnt'], +  		'detect cciss' => ['<', '/proc/driver/cciss'], +  		'cciss proc' => ['<', '/proc/driver/cciss/$controller'], +   +  		# for lsscsi, issue #109 +  		'lsscsi list' => ['-|', '@CMD', '-g'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +   +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +   +  	my $v1_10 = $this->cciss_vol_status_version >= 1.10; +   +  	my @sudo; +  	my @cciss_devs = $this->detect; +  	if (@cciss_devs) { +  		my $c = join(' ', @cciss_devs); +  		if ($v1_10) { +  			push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -V $c"); +  		} else { +  			push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd $c"); +  		} +  	} +   +  	my @cciss_disks = $this->detect_disks(@cciss_devs); +  	if (!$v1_10 && @cciss_disks) { +  		my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new(); +   +  		if ($smartctl->active) { +  			my $cmd = $smartctl->{program}; +  			foreach my $ref (@cciss_disks) { +  				my ($dev, $diskopt, $disk) = @$ref; +  				# escape comma for sudo +  				$diskopt =~ s/,/\\$&/g; +  				push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -H $dev $diskopt$disk"); +  			} +  		} +  	} +   +  	return @sudo; +  } +   +  # detects if hpsa (formerly cciss) is present in system +  sub detect { +  	my $this = shift; +   +  	my ($fh, @devs); +   +  	# try lsscsi first if enabled and allowed +  	my $lsscsi = App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi->new('commands' => $this->{commands}); +  	my $use_lsscsi = defined($this->{use_lsscsi}) ? $this->{use_lsscsi} : $lsscsi->active; +  	if ($use_lsscsi) { +  		# for cciss_vol_status < 1.10 we need /dev/sgX nodes, columns which are type storage +  		@devs = $lsscsi->list_sg; +   +  		# cciss_vol_status 1.10 can process disk nodes too even if sg is not present +  		my $v1_10 = $this->cciss_vol_status_version >= 1.10; +  		if (!@devs && $v1_10) { +  			@devs = $lsscsi->list_dd; +  		} +   +  		return wantarray ? @devs : \@devs if @devs; +  	} +   +  	# check hpsa devs +  	eval { $fh = $this->cmd('detect hpsa'); }; +  	if ($fh) { +  		my $refcnt = <$fh>; +  		close $fh; +   +  		if ($refcnt) { +  			# TODO: how to figure which sgX is actually in use? +  			# for now we collect all, and expect cciss_vol_status to ignore unknowns +  			# refcnt seems to match number of sg devs: /sys/class/scsi_generic/sg* +  			for (my $i = 0; $i < $refcnt; $i++) { +  				my $dev = "/dev/sg$i"; +  				# filter via valid() so could exclude devs +  				push(@devs, $dev) if $this->valid($dev); +  			} +  		} +  	} +  	undef($fh); +   +  	# check legacy cciss devs +  	eval { $fh = $this->cmd('detect cciss'); }; +  	if ($fh) { +  		my @c = grep { !/^\./ } readdir($fh); +  		close($fh); +   +  		# find controllers +  		#	cciss0: HP Smart Array P400i Controller +  		#	Board ID: 0x3235103c +  		#	Firmware Version: 4.06 +  		#	IRQ: 98 +  		#	Logical drives: 1 +  		#	Current Q depth: 0 +  		#	Current # commands on controller: 0 +  		#	Max Q depth since init: 249 +  		#	Max # commands on controller since init: 275 +  		#	Max SG entries since init: 31 +  		#	Sequential access devices: 0 +  		# +  		#	cciss/c0d0:      220.12GB       RAID 1(1+0) +  		for my $c (@c) { +  			my $fh = $this->cmd('cciss proc', { '$controller' => $c }); +  			while (<$fh>) { +  				# check "c*d0" - iterate over each controller +  				next unless (my($dev) = m{^(cciss/c\d+d0):}); +  				$dev = "/dev/$dev"; +  				# filter via valid() so could exclude devs +  				push(@devs, $dev) if $this->valid($dev); +  			} +  			close $fh; +  		} +  	} +  	undef($fh); +   +  	return wantarray ? @devs : \@devs; +  } +   +  # build list of cciss disks +  # used by smartctl check +  # just return all disks (0..15) for each cciss dev found +  sub detect_disks { +  	my $this = shift; +   +  	my @devs; +  	# build devices list for smartctl +  	foreach my $scsi_dev (@_) { +  		foreach my $disk (0..15) { +  			push(@devs, [ $scsi_dev, '-dcciss,', $disk ]); +  		} +  	} +  	return wantarray ? @devs : \@devs; +  } +   +  # parse version out of "cciss_vol_status version 1.09" +  # NOTE: it prints the output to stderr, but may print to stdout in the future +  sub cciss_vol_status_version { +  	my $this = shift; +   +  	# cache inside single run +  	return $this->{cciss_vol_status_version} if defined $this->{cciss_vol_status_version}; +   +  	my $version = sub { +  		my $fh = $this->nosudo_cmd('cciss_vol_status version'); +  		my ($line) = <$fh>; +  		close $fh; +  		return 0 unless $line; +   +  		if (my($v) = $line =~ /^cciss_vol_status version ([\d.]+)$/) { +  			return 0 + $v; +  		} +  		return 0; +  	}; +   +  	return $this->{cciss_vol_status_version} = &$version(); +  } +   +  sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; +   +  # we process until we find end of sentence (dot at the end of the line) +  sub consume_diagnostic { +  	my ($this, $fh) = @_; +   +  	my $diagnostic = ''; +  	while (1) { +  		my $s = <$fh>; +  		last unless $s; +  		chomp; +  		$diagnostic .= ' '. trim($s); +  		last if $s =~ /\.$/; +  	} +  	return trim($diagnostic); +  } +   +  # process to skip lines with physical location: +  # "         connector 1I box 1 bay 4 ..." +  sub consume_disk_map { +  	my ($this, $fh) = @_; +   +  	while (my $s = <$fh>) { +  		chomp $s; +  		# connector 1I box 1 bay 4 +  		last unless $s =~ /^\s+connector\s/; +  	} +  } +   +  sub parse { +  	my $this = shift; +  	my @devs = @_; +   +  	my (%c, $cdev); +   +  	# cciss_vol_status 1.10 has -V option to print more info about controller and disks. +  	my $v1_10 = $this->cciss_vol_status_version >= 1.10; +   +  	# add all devs at once to commandline, cciss_vol_status can do that +  	my $fh = $this->cmd($v1_10 ? 'controller status verbose' : 'controller status', { '@devs' => \@devs }); +  	while (<$fh>) { +  		chomp; +   +  		# skip empty lines and artificial comments (added by this project) +  		next if /^$/ or /^#/; +   +  		if (/Controller:/) { +  			# this is first item when new controller is found +  			# reset previous state +  			undef $cdev; +  			next; +  		} +   +  		# catch enclosures, print_bus_status() +  		# /dev/cciss/c1d0: (Smart Array P800) Enclosure MSA70 (S/N: SGA651004J) on Bus 2, Physical Port 1E status: OK. +  		# /dev/cciss/c0d0: (Smart Array 6i) Enclosure PROLIANT 6L2I (S/N: ) on Bus 0, Physical Port J1 status: OK. +  		if (my($file, $board_name, $name, $sn, $bus, $port1, $port2, $status) = m{ +  			^(/dev/[^:]+):\s        # File +  			\(([^)]+)\)\s           # Board Name +  			Enclosure\s(.*?)\s      # Enclosure Name +  			\(S/N:\s(\S*)\)\s       # Enclosure SN +  			on\sBus\s(\d+),\s       # Bus Number +  			Physical\sPort\s(.)     # physical_port1 +  			(.)\s                   # physical_port2 +  			status:\s(.*?)\.        # status (without a dot) +  		}x) { +  			$c{$file}{enclosures}{$bus} = { +  				board_name => $board_name, +  				name => $name, +  				sn => $sn, +  				bus => int($bus), +  				phys1 => $port1, +  				phys2 => $port2, +  				status => $status, +  			}; +  			next; +  		} +   +  		# volume status, print_volume_status() +  		# /dev/cciss/c0d0: (Smart Array P400i) RAID 1 Volume 0 status: OK +  		# /dev/sda: (Smart Array P410i) RAID 1 Volume 0 status: OK. +  		# /dev/sda: (Smart Array P410i) RAID 5 Volume 0 status: OK.   At least one spare drive designated.  At least one spare drive has failed. +  		if (my($file, $board_name, $raid_level, $volume_number, $certain, $status, $spare_drive_status) = m{ +  			^(/dev/[^:]+):\s        # File +  			\(([^)]+)\)\s           # Board Name +  			(RAID\s\d+|\([^)]+\))\s # RAID level +  			Volume\s(\d+)           # Volume number +  			(\(\?\))?\s             # certain? +  			status:\s(.*?)\.        # status (without a dot) +  			(.*)?                   # spare drive status messages +  		}x) { +  			$cdev = $file; +  			$c{$file}{volumes}{$volume_number} = { +  				board_name => $board_name, +  				raid_level => $raid_level, +  				volume_number => $volume_number, +  				certain => int(not defined $certain), +  				status => $status, +  				spare_drive_status => trim($spare_drive_status), +  			}; +   +  			$c{$file}{board_name} = $board_name; +  			next; +  		} +   +  		next unless $cdev; +   +  		if (my ($count) = /Physical drives: (\d+)/) { +  			$c{$cdev}{'pd count'} = $count; +  			next; +  		} +   +  		# check_physical_drives(file, fd); +  		# NOTE: check for physical drives is enabled with -V or -s option (-V enables -s) +  		# cciss_vol_status.c format_phys_drive_location() +  		if (my ($phys1, $phys2, $box, $bay, $model, $serial_no, $fw_rev, $status) = m{ +  			\sconnector\s(.)(.)\s # Phys connector 1&2 +  			box\s(\d+)\s          # phys_box_on_bus +  			bay\s(\d+)\s          # phys_bay_in_box +  			(.{40})\s             # model +  			(.{40})\s             # serial no +  			(.{8})\s              # fw rev +  			(.+)                  # status +  		$}x) { +  			my $slot = "$phys1$phys2-$box-$bay"; +  			$c{$cdev}{drives}{$slot} = { +  				'slot' => $slot, +  				'phys1' => $phys1, +  				'phys2' => $phys2, +  				'box' => int($box), +  				'bay' => int($bay), +   +  				'model' => trim($model), +  				'serial' => trim($serial_no), +  				'fw' => trim($fw_rev), +  				'status' => $status, +  			}; +  			next; +  		} +   +  		# TODO +  		# check_fan_power_temp(file, ctlrtype, fd, num_controllers); +   +  		# check_nonvolatile_cache_status(file, ctlrtype, fd, num_controllers); +  		# /dev/cciss/c0d0(Smart Array P400i:0): Non-Volatile Cache status: +  		if (my($file, $board_name, $instance) = m{^(/dev/[^(]+)\((.+):(\d+)\): Non-Volatile Cache status}) { +  			# $file and $dev may differ, so store it +  			$c{$cdev}{cache} = { +  				'file' => $file, +  				'board' => $board_name, +  				'instance' => int($instance), +  			}; +  			next; +  		} +   +  		if (defined($c{$cdev}{cache})) { +  			my $cache = $c{$cdev}{cache}; +  			my %map = ( +  				configured => qr/Cache configured: (.+)/, +  				read_cache_memory => qr/Read cache memory: (.+)/, +  				write_cache_memory => qr/Write cache memory: (.+)/, +  				write_cache_enabled => qr/Write cache enabled: (.+)/, +  				flash_cache => qr/Flash backed cache present/, +  				disabled_temporarily => qr/Write cache temporarily disabled/, +  				disabled_permanently => qr/Write Cache permanently disabled/, +  			); +  			my $got; +  			while (my($k, $r) = each %map) { +  				next unless (my($v) = $_ =~ $r); +  				$cache->{$k} = $v; +  				$got = 1; +   +  				# consume extended diagnostic +  				if ($k =~ /disabled_(temporari|permanentl)ly/) { +  					$cache->{"$k diagnostic"} = $this->consume_diagnostic($fh); +  				} +  			} +   +  			next if $got; +  		} +   +  		# show_disk_map("  Failed drives:", file, fd, id, controller_lun, ctlrtype, +  		# show_disk_map("  'Replacement' drives:", file, fd, id, controller_lun, ctlrtype, +  		# show_disk_map("  Drives currently substituted for by spares:", file, fd, id, controller_lun, ctlrtype, +  		if (/^  Failed drives:/ || +  			/^  'Replacement' drives:/ || +  			/^  Drives currently substituted for by spares:/ +  		) { +  			# could store this somewhere, ignore for now +  			$this->consume_disk_map($fh); +  			next; +  		} +   +  		if (my($total_failed) = /Total of (\d+) failed physical drives detected on this logical drive\./) { +  			$c{$cdev}{phys_failed} = $total_failed; +  			next; +  		} +   +  		warn "Unparsed[$_]"; +  	} +  	close($fh); +   +  	return \%c; +  } +   +  sub check { +  	my $this = shift; +  	my @devs = $this->detect; +   +  	unless (@devs) { +  		$this->warning; +  		$this->message("No Smart Array Adapters were found on this machine"); +  		return; +  	} +   +  	# status messages pushed here +  	my @status; +   +  	my $res = $this->parse(@devs); +  	for my $dev (sort {$a cmp $b} keys %$res) { +  		my $c = $res->{$dev}; +  		my @bstatus; +   +  		# check volumes +  		my @vstatus; +  		for my $vn (sort {$a cmp $b} keys %{$c->{volumes}}) { +  			my $v = $c->{volumes}->{$vn}; +  			if ($v->{status} !~ '^OK') { +  				$this->critical; +  			} +  			push(@vstatus, "Volume $v->{volume_number} ($v->{raid_level}): $v->{status}"); +  		} +   +  		push(@bstatus, @vstatus) if @vstatus; +   +  		# check physical devices +  		if ($c->{'pd count'}) { +  			my %pd; +  			for my $ps (sort {$a cmp $b} keys %{$c->{drives}}) { +  				my $pd = $c->{drives}{$ps}; +  				if ($pd->{status} !~ '^OK') { +  					$this->critical; +  					$ps .= "($pd->{serial})"; +  				} +  				push(@{$pd{$pd->{status}}}, $ps); +  			} +  			push(@bstatus, "Drives($c->{'pd count'}): ". $this->join_status(\%pd)); +  		} +   +  		# check enclosures +  		if ($c->{enclosures}) { +  			my @e; +  			for my $i (sort {$a cmp $b} keys %{$c->{enclosures}}) { +  				my $e = $c->{enclosures}{$i}; +   +  				# enclosure name may be missing, identify by connection +  				my $s = $e->{name} || "$e->{bus}-$e->{phys1}$e->{phys2}"; +  				# enclosure S/N may be missing +  				$s .= "($e->{sn})" if $e->{sn}; +  				$s .= ": $e->{status}"; +  				if ($e->{status} !~ '^OK') { +  					$this->critical; +  				} +  				push(@e, $s); +  			} +  			push(@bstatus, "Enclosures: ". join(', ', @e)); +  		} +   +  		# check cache +  		if ($c->{cache} && $c->{cache}->{configured} eq 'Yes') { +  			my $cache = $c->{cache}; +  			my @cstatus = 'Cache:'; +   +  			if ($cache->{write_cache_enabled} eq 'Yes') { +  				push(@cstatus, "WriteCache"); +   +  			} elsif ($cache->{disabled_temporarily} || $cache->{disabled_permanently}) { +  				# disabled diagnostic is available, but it's too long to print here +  				push(@cstatus, "WriteCache:DISABLED"); +  				$this->cache_fail; +  			} +   +  			push(@cstatus, "FlashCache") if $cache->{flash_cache}; +  			push(@cstatus, "ReadMem:$cache->{read_cache_memory}") if $cache->{read_cache_memory}; +  			push(@cstatus, "WriteMem:$cache->{write_cache_memory}") if $cache->{write_cache_memory}; +   +  			push(@bstatus, join(' ', @cstatus)); +  		} +   +  		push(@status, "$dev($c->{board_name}): ". join(', ', @bstatus)); +  	} +   +  	unless (@status) { +  		return; +  	} +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join(', ', @status)); +   +  	# cciss_vol_status 1.10 with -V (or -s) checks individual disk health anyway +  	my $v1_10 = $this->cciss_vol_status_version >= 1.10; +   +  	# no_smartctl: allow skip from tests +  	if (!$v1_10 && !$this->{no_smartctl}) { +  		# check also individual disk health +  		my @disks = $this->detect_disks(@devs); +  		if (@disks) { +  			# inherit smartctl command from our commands (testing) +  			my %params = (); +  			$params{commands}{smartctl} = $this->{commands}{smartctl} if $this->{commands}{smartctl}; +   +  			my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new(%params); +  			# do not perform check if smartctl is missing +  			if ($smartctl->active) { +  				$smartctl->check_devices(@disks); +   +  				# XXX this is hack, as we have no proper subcommand check support +  				$this->message($this->message . " " .$smartctl->message); +  				if ($smartctl->status > 0) { +  					$this->critical; +  				} +  			} +  		} +  	} +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cmdtool2.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::cmdtool2; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	'CmdTool2'; +  } +   +  sub commands { +  	{ +  		'adapter list' => ['-|', '@CMD', , '-AdpAllInfo', '-aALL', '-nolog'], +  		'adapter config' => ['-|', '@CMD', '-CfgDsply', '-a$adapter', '-nolog'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpAllInfo -aALL -nolog", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -CfgDsply -a* -nolog", +  	); +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	# get adapters +  	my $fh = $this->cmd('adapter list'); +  	my @c; +  	while (<$fh>) { +  		if (my($c) = /^Adapter #(\d+)/) { +  			push(@c, $c); +  		} +  	} +  	close $fh; +   +  	unless (@c) { +  		$this->warning; +  		$this->message("No LSI adapters were found on this machine"); +  		return; +  	} +   +  	foreach my $c (@c) { +  		my $fh = $this->cmd('adapter config', { '$adapter' => $c }); +  		my ($d); +  		while (<$fh>) { +  			# DISK GROUPS: 0 +  			if (my($s) = /^DISK GROUPS: (\d+)/) { +  				$d = int($s); +  				next; +  			} +   +  			# State: Optimal +  			if (my($s) = /^State: (\S+)$/) { +  				if ($s ne 'Optimal') { +  					$this->critical; +  				} +  				push(@status, "Logical Drive $c,$d: $s"); +  			} +  		} +  	} +   +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2 + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dm.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::dm; +   +  # Package to check Linux Device Mapper +   +  # Linux LVM Mirrors +  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/mirror_create.html +  # +  # Linux LVM RAID +  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/raid_volumes.html +  # +  # Low-level: +  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map +  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#dmraid-map +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(dmsetup); +  } +   +  sub active { +  	my ($this, $sudo) = @_; +   +  	# return if parent said NO +  	my $res = $this->SUPER::active(@_); +  	return $res unless $res; +   +  	# check if there really are any devices +  	my $c = $this->parse; +  	return !!@$c; +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd status --noflush", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd status", +  	); +  } +   +  sub commands { +  	{ +  		'dmsetup'         => [ '-|', '@CMD', 'status' ], +  		'dmsetup noflush' => [ '-|', '@CMD', 'status', '--noflush' ], +  	} +  } +   +  # https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt +  sub parse_raid { +  	local $_ = shift; +   +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1377 +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1423 +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1425-L1435 +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1437-L1442 +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1444-L1452 +  	my @cols = qw( +  		raid_type raid_disks +  		status_chars +  		sync_ratio +  		sync_action +  		mismatch_cnt +  	); +   +  	my %h; +  	@h{@cols} = split; +   +  	\%h; +  } +   +  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map +  sub parse_mirror { +  	local $_ = shift; +   +  	my %h; +   +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1355 +  	my @parts = split; +   +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1365 +  	$h{nr_mirrors} = shift @parts; +   +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1366-L1369 +  	my @devs; +  	for (my $i = 0; $i < $h{nr_mirrors}; $i++) { +  		push(@devs, shift @parts); +  	} +  	$h{devices} = \@devs; +   +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1372-L1374 +  	# some ratio? +  	$h{ratio} = shift @parts; +  	# param count? always '1' +  	shift @parts; +  	# the 'buffer' filled with status chars +  	$h{status_chars} = shift @parts; +   +  	# log device information +  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-log.c#L807-L810 +  	# log params, always '3' +  	shift @parts; +  	my %l; +  	$l{type} = shift @parts; +  	$l{device} = shift @parts; +  	# status: F->D->A +  	$l{status_char} = shift @parts; +  	$h{log} = { %l }; +   +  	# for debugging. fill only if something remains not parsed +  	$h{_remaining} = join ' ', @parts if @parts; +   +  	\%h; +  } +   +  sub parse_target { +  	my ($target, $data) = @_; +   +  	return parse_raid($data) if $target eq 'raid'; +  	return parse_mirror($data) if $target eq 'mirror'; +  	undef; +  } +   +  sub get_fh { +  	my $this = shift; +   +  	# use dmsetup --noflush, requires LVM >= 2.02.97 +  	# if that fails, fall back to just dmsetup +  	# https://github.com/glensc/nagios-plugin-check_raid/issues/130#issuecomment-194476070 +  	my $fh = $this->cmd('dmsetup noflush'); +  	$fh = $this->cmd('dmsetup') if eof $fh; +   +  	return $fh; +  } +   +  sub parse { +  	my $this = shift; +   +  	# cache for single run +  	if (!defined($this->{parsed})) { +  		$this->{parsed} = $this->_parse; +  	} +   +  	return $this->{parsed}; +  } +   +  sub _parse { +  	my $this = shift; +   +  	my @devices; +  	my $fh = $this->get_fh(); +  	while (<$fh>) { +  		# skip comments. +  		# not present in dmsetup output, but our test files may have. +  		next if /^#/; +   +  		last if /No devices found/; +   +  		if (my ($dmname, $s, $l, $target, $rest) = m{^ +  			(\S+):\s+       # dmname +  			(\d+)\s+        # start +  			(\d+)\s+        # length +  			(\S+)           # target +  			(?:\s+(.+))?    # rest of the data +  			\s?             # there may be trailing space +  			$}x) { +  			my $h = parse_target($target, $rest); +   +  			# skip target type not handled +  			next unless $h; +   +  			my %h = ( +  				'dmname' => $dmname, +  				's'      => $s, +  				'l'      => $l, +  				'target' => $target, +  				%$h, +  			); +  			push @devices, \%h; +  			next; +  		} +   +  		warn "Unhandled:[$_]"; +  		$this->unknown; +  	} +  	close $fh; +  	return \@devices; +  } +   +  sub check { +  	my $this = shift; +   +  	my $c = $this->parse; +   +  	if (!@$c) { +  		$this->noraid->message("No devices to check"); +  		return; +  	} +   +  	my @status; +  	foreach my $dm (@$c) +  	{ +  		# <status_chars>  One char for each device, indicating: +  		# 'A' = alive and in-sync (mirror, raid1, raid) +  		# 'a' = alive but not in-sync (mirror, raid1) +  		# 'D' = dead/failed (mirror, raid1, raid) +  		# 'S' = Sync (mirror, raid1) +  		# 'mirror'/'raid1': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1330-L1342 +  		# 'raid': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1414 +  		$this->critical if ($dm->{status_chars} =~ /D/); +  		$this->warning if ($dm->{status_chars} =~ /[aS]/); +   +  		my @s = "$dm->{dmname}:$dm->{status_chars}"; +   +  		# <sync_action>   One of the following possible states: +  		# idle    - No synchronization action is being performed. +  		# frozen  - The current action has been halted. +  		# resync  - Array is undergoing its initial synchronization or... +  		# recover - A device in the array is being rebuilt or... +  		# check   - A user-initiated full check of the array is... +  		# repair  - The same as "check", but discrepancies are... +  		# reshape - The array is undergoing a reshape. +  		if ($dm->{sync_action}) { +  			push(@s, $dm->{sync_action}); +  			if ($dm->{sync_action} =~ /^(check|repair|init)$/) { +  				$this->warning; +  			} +  		} +  		push(@status, join(' ', @s)); +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dmraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::dmraid; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'dmraid' => ['-|', '@CMD', '-r'], +  	} +  } +   +  sub active { +  	my ($this) = @_; +   +  	# allow --plugin-option=dmraid-enabled to force this plugin to be enabled +  	return 1 if exists $this->{options}{'dmraid-enabled'}; +   +  	# return if parent said NO +  	my $res = $this->SUPER::active(@_); +  	return $res unless $res; +   +  	# check if dmraid is empty +  	return keys %{$this->parse} > 0; +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -r"; +  } +   +  # parse arrays, return data indexed by array name +  sub parse { +  	my $this = shift; +   +  	my (%arrays); +  	my $fh = $this->cmd('dmraid'); +  	while (<$fh>) { +  		chomp; +  		next unless (my($device, $format, $name, $type, $status, $sectors) = m{^ +  			# /dev/sda: jmicron, "jmicron_JRAID", mirror, ok, 781385728 sectors, data@ 0 +  			# /dev/sdb: ddf1, ".ddf1_disks", GROUP, ok, 1953253376 sectors, data@ 0 +  			(/dev/\S+):\s # device +  			(\S+),\s # format +  			"([^"]+)",\s # name +  			(mirror|stripe[d]?|GROUP),\s # type +  			(\w+),\s # status +  			(\d+)\ssectors,.* # sectors +  		$}x); +  		next unless $this->valid($device); +   +  		# trim trailing spaces from name +  		$name =~ s/\s+$//; +   +  		my $member = { +  			'device' => $device, +  			'format' => $format, +  			'type' => $type, +  			'status' => $status, +  			'size' => $sectors, +  		}; +   +  		push(@{$arrays{$name}}, $member); +  	} +  	close $fh; +   +  	return \%arrays; +  } +   +   +  # plugin check +  # can store its exit code in $this->status +  # can output its message in $this->message +  sub check { +  	my $this = shift; +  	my (@status); +   +  	## Check Array and Drive Status +  	my $arrays = $this->parse; +  	while (my($name, $array) = each(%$arrays)) { +  		my @s; +  		foreach my $dev (@$array) { +  			if ($dev->{status} =~ m/sync|rebuild/i) { +  				$this->warning; +  			} elsif ($dev->{status} !~ m/ok/i) { +  				$this->critical; +  			} +  			my $size = $this->format_bytes($dev->{size}); +  			push(@s, "$dev->{device}($dev->{type}, $size): $dev->{status}"); +  		} +  		push(@status, "$name: " . join(', ', @s)); +  	} +   +  	return unless @status; +   +  	# denote that this plugin as ran ok, not died unexpectedly +  	$this->ok->message(join(' ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dpt_i2o.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::dpt_i2o; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub commands { +  	{ +  		'proc' => ['<', '/proc/scsi/dpt_i2o'], +  		'proc entry' => ['<', '/proc/scsi/dpt_i2o/$controller'], +  	} +  } +   +  sub active { +  	my ($this) = @_; +  	return -d $this->{commands}{proc}[1]; +  } +   +  sub check { +  	my $this = shift; +  	# status messages pushed here +  	my @status; +   +  	my $fh = $this->cmd('proc'); +  	my @c = grep { !/^\./ } readdir($fh); +  	close($fh); +   +  	# TODO: check for failed disks! +  	for my $c (@c) { +  		my $fh = $this->cmd('proc entry', { '$controller' => $c }); +   +  		while (<$fh>) { +  			if (my ($c, $t, $l, $s) = m/TID=\d+,\s+\(Channel=(\d+),\s+Target=(\d+),\s+Lun=(\d+)\)\s+\((\S+)\)/) { +  				if ($s ne "online") { +  					$this->critical; +  				} +  				push(@status, "$c,$t,$l:$s"); +  			} +  		} +  		close($fh); +  	} +   +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/gdth.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::gdth; +   +  # Linux gdth RAID +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub commands { +  	{ +  		'proc' => ['<', '/proc/scsi/gdth'], +  		'proc entry' => ['<', '/proc/scsi/gdth/$controller'], +  	} +  } +   +  sub active { +  	my ($this) = @_; +  	return -d $this->{commands}{proc}[1]; +  } +   +  sub parse { +  	my $this = shift; +   +  	my $fh = $this->cmd('proc'); +  	my @c = grep { !/^\./ } readdir($fh); +  	close($fh); +   +  	my %c; +  	for my $c (@c) { +  		my (%ld, %ad, %pd, %l, %a, %p, $section); +   +  		my $fh = $this->cmd('proc entry', { '$controller' => $c }); +  		while (<$fh>) { +  			chomp; +   +  			# new section start +  			if (my($s) = /^(\w.+):$/) { +  				$section = $s; +  				%a = %l = %p = (); +  				next; +  			} +   +  			# skip unknown sections +  			next unless /^\s/ or /^$/; +   +  			# process each section +  			if ($section eq 'Driver Parameters') { +  				# nothing useful +  			} elsif ($section eq 'Disk Array Controller Information') { +  				# nothing useful +  			} elsif ($section eq 'Physical Devices') { +  				# Chn/ID/LUN:        B/05/0          Name:           FUJITSU MAX3147NC       0104 +  				# Capacity [MB]:     140239          To Log. Drive:  5 +  				# Retries:           1               Reassigns:      0 +  				# Grown Defects:     1 +   +  				if (my($id, $n, $rv) = m{^\s+Chn/ID/LUN:\s+(\S+)\s+Name:\s+(.+)(.{4})$}) { +  					$n =~ s/\s+$//; +  					$p{id} = $id; +  					$p{name} = $n; +  					$p{revision} = $rv; +  				} elsif (my($unit, $c, $d) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+To Log\. Drive:\s+(\d+|--)/) { +  					$p{capacity} = int($c); +  					$p{capacity_unit} = $unit; +  					$p{drive} = $d; +  				} elsif (my($r, $ra) = m/^\s+Retries:\s+(\d+)\s+Reassigns:\s+(\d+)/) { +  					$p{retries} = int($r); +  					$p{reassigns} = int($ra); +  				} elsif (my($gd) = m/^\s+Grown Defects:\s+(\d+)/) { +  					$p{defects} = int($gd); +  				} elsif (/^$/) { +  					if ($p{capacity} == 0 and $p{name} =~ /SCA HSBP/) { +  						# HSBP is not a disk, so do not consider this an error +  						# http://support.gateway.com/s/Servers/COMPO/MOTHERBD/4000832/4000832si69.shtml +  						# Raid Hot Swap Backplane driver (recognized as "ESG-SHV SCA HSBP M16 SCSI Processor Device") +  						# Chn/ID/LUN:    B/06/0          Name:           ESG-SHV SCA HSBP M16    0.05 +  						# Capacity [MB]: 0               To Log. Drive:  -- +  						next; +  					} +   +  					$pd{$p{id}} = { %p }; +  				} else { +  					warn "[$section] [$_]"; +  					$this->unknown; +  				} +   +  			} elsif ($section eq 'Logical Drives') { +  				# Number:              3               Status:         ok +  				# Slave Number:        15              Status:         ok (older kernels) +  				# Capacity [MB]:       69974           Type:           Disk +  				if (my($num, $s) = m/^\s+(?:Slave )?Number:\s+(\d+)\s+Status:\s+(\S+)/) { +  					$l{number} = int($num); +  					$l{status} = $s; +  				} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) { +  					$l{capacity} = "$c $unit"; +  					$l{type} = $t; +  				} elsif (my($md, $id) = m/^\s+Missing Drv\.:\s+(\d+)\s+Invalid Drv\.:\s+(\d+|--)/) { +  					$l{missing} = int($md); +  					$l{invalid} = int($id); +  				} elsif (my($n) = m/^\s+To Array Drv\.:\s+(\d+|--)/) { +  					$l{array} = $n; +  				} elsif (/^$/) { +  					$ld{$l{number}} = { %l }; +  				} else { +  					warn "[$section] [$_]"; +  					$this->unknown; +  				} +   +  			} elsif ($section eq 'Array Drives') { +  				# Number:        0               Status:         fail +  				# Capacity [MB]: 349872          Type:           RAID-5 +  				if (my($num, $s) = m/^\s+Number:\s+(\d+)\s+Status:\s+(\S+)/) { +  					$a{number} = int($num); +  					$a{status} = $s; +  				} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) { +  					$a{capacity} = "$c $unit"; +  					$a{type} = $t; +  				} elsif (/^(?: --)?$/) { +  					if (%a) { +  						$ad{$a{number}} = { %a }; +  					} +  				} else { +  					warn "[$section] [$_]"; +  					$this->unknown; +  				} +   +  			} elsif ($section eq 'Host Drives') { +  				# nothing useful +  			} elsif ($section eq 'Controller Events') { +  				# nothing useful +  			} +  		} +  		close($fh); +   +  		$c{$c} = { id => $c, array => { %ad }, logical => { %ld }, physical => { %pd } }; +  	} +   +  	return \%c; +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $controllers = $this->parse; +   +  	# process each controller separately +  	for my $c (values %$controllers) { +  		# array status +  		my @ad; +  		for my $n (sort {$a cmp $b} keys %{$c->{array}}) { +  			my $ad = $c->{array}->{$n}; +  			if ($ad->{status} ne "ready") { +  				$this->critical; +  			} +  			push(@ad, "Array $ad->{number}($ad->{type}) $ad->{status}"); +  		} +   +  		# older raids have no Array drives, Look into Logical Drives for type!=Disk +  		unless (@ad) { +  			for my $n (sort {$a cmp $b} keys %{$c->{logical}}) { +  				my $ld = $c->{logical}->{$n}; +  				if ($ld->{type} eq "Disk") { +  					next; +  				} +   +  				# emulate Array Drive +  				my $s = "Array($ld->{type}) $ld->{status}"; +  				# check for missing drives +  				if ($ld->{missing} > 0) { +  					$this->warning; +  					$s .= " ($ld->{missing} missing drives)"; +  				} +   +  				push(@ad, $s); +  			} +  		} +   +  		# logical drive status +  		my %ld; +  		for my $n (sort {$a cmp $b} keys %{$c->{logical}}) { +  			my $ld = $c->{logical}->{$n}; +  			if ($ld->{status} ne "ok") { +  				$this->critical; +  			} +  			push(@{$ld{$ld->{status}}}, $ld->{number}); +  		} +   +  		# physical drive status +  		my @pd; +  		for my $n (sort {$a cmp $b} keys %{$c->{physical}}) { +  			my $pd = $c->{physical}->{$n}; +   +  			my @ds; +  			# TODO: make tresholds configurable +  			if ($pd->{defects} > 300) { +  				$this->critical; +  				push(@ds, "grown defects critical: $pd->{defects}"); +  			} elsif ($pd->{defects} > 30) { +  				$this->warning; +  				push(@ds, "grown defects warning: $pd->{defects}"); +  			} +   +  			# report disk being not assigned +  			if ($pd->{drive} eq '--') { +  				push(@ds, "not assigned"); +  			} +   +  			if (@ds) { +  				push(@pd, "Disk $pd->{id}($pd->{name}) ". join(', ', @ds)); +  			} +  		} +   +  		my @cd; +  		push(@cd, @ad) if @ad; +  		push(@cd, "Logical Drives: ". $this->join_status(\%ld)); +  		push(@cd, @pd) if @pd; +  		push(@status, "Controller $c->{id}: ". join('; ', @cd)); +  	} +   +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join('; ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hp_msa.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::hp_msa; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use App::Monitoring::Plugin::CheckRaid::SerialLine; +  use strict; +  use warnings; +   +  sub active { +  	my $this = shift; +  	return $this->detect; +  } +   +  # check from /sys if there are any MSA VOLUME's present. +  sub detect { +  	my $this = shift; +   +  	# allow --plugin-option=hp_msa-enabled to force this plugin to be enabled +  	return 1 if exists $this->{options}{'hp_msa-enabled'}; +   +  	for my $file (</sys/block/*/device/model>) { +  		open my $fh, '<', $file or next; +  		my $model = <$fh>; +  		close($fh); +  		return 1 if ($model =~ /^MSA.+VOLUME/); +  	} +  	return 0; +  } +   +  sub check { +  	my $this = shift; +   +  	#  allow --plugin-option=hp_msa-serial=/dev/ttyS2 to specify serial line +  	my $ctldevice = $this->{options}{'hp_msa-serial'} || '/dev/ttyS0'; +   +  	# status messages pushed here +  	my @status; +   +  	my %opts = (); +  	$opts{lockdir} = $this->{lockdir} if $this->{lockdir}; +   +  	my $modem = App::Monitoring::Plugin::CheckRaid::SerialLine->new($ctldevice, %opts); +  	my $fh = $modem->open(); +  	unless ($fh) { +  		$this->warning; +  		$this->message("Can't open $ctldevice"); +  		return; +  	} +   +  	# check first controller +  	print $fh "\r"; +  	print $fh "show globals\r"; +  	print $fh "show this_controller\r"; +  	print $fh "show other_controller\r"; +  	# this will issue termination match, ie. invalid command +  	print $fh "exit\r"; +   +  	my ($c, %c, %t); +  	while (<$fh>) { +  		chomp; +  		s/[\n\r]$//; +  		last if /Invalid CLI command/; +   +  		# Temperature: +  		# EMU: 23 Celsius,  73 Fahrenheit +  		# PS1: 22 Celsius,  71 Fahrenheit +  		# PS2: 22 Celsius,  71 Fahrenheit +  		if (my($s, $c) = /(\S+): (\d+) Celsius,\s+\d+ Fahrenheit/) { +  			$t{$s} = int($c); +  			next; +  		} +   +  		# Controller 1 (right controller): +  		if (my($s) = /^(Controller \d+)/) { +  			$c = $s; +  			$c{$c} = []; +  			next; +  		} +  		# Surface Scan: Running, LUN 10 (68% Complete) +  		if (my($s, $m) = /Surface Scan:\s+(\S+)[,.]\s*(.*)/) { +  			if ($s eq 'Running') { +  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)}; +  				push(@{$c{$c}}, "Surface: $l ($p%)"); +  				$this->warning; +  			} elsif ($s ne 'Complete') { +  				push(@{$c{$c}}, "Surface: $s, $m"); +  				$this->warning; +  			} +  			next; +  		} +  		# Rebuild Status: Running, LUN 0 (67% Complete) +  		if (my($s, $m) = /Rebuild Status:\s+(\S+)[,.]\s*(.*)/) { +  			if ($s eq 'Running') { +  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)}; +  				push(@{$c{$c}}, "Rebuild: $l ($p%)"); +  				$this->warning; +  			} elsif ($s ne 'Complete') { +  				push(@{$c{$c}}, "Rebuild: $s, $m"); +  				$this->warning; +  			} +  			next; +  		} +  		# Expansion: Complete. +  		if (my($s, $m) = /Expansion:\s+(\S+)[.,]\s*(.*)/) { +  			if ($s eq 'Running') { +  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)}; +  				push(@{$c{$c}}, "Expansion: $l ($p%)"); +  				$this->warning; +  			} elsif ($s ne 'Complete') { +  				push(@{$c{$c}}, "Expansion: $s, $m"); +  				$this->warning; +  			} +  			next; +  		} +  	} +  	$modem->close(); +   +  	foreach $c (sort { $a cmp $b } keys %c) { +  		my $s = $c{$c}; +  		$s = join(', ', @$s); +  		$s = 'OK' unless $s; +  		push(@status, "$c: $s"); +  	} +   +  	# check that no temp is over the treshold +  	my $warn = 28; +  	my $crit = 33; +  	while (my($t, $c) = each %t) { +  		if ($c > $crit) { +  			push(@status, "$t: ${c}C"); +  			$this->critical; +  		} elsif ($c > $warn) { +  			push(@status, "$t: ${c}C"); +  			$this->warning; +  		} +  	} +   +  	return unless @status; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpacucli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli; +   +  ## hpacucli/hpssacli/ssacli support +  # +  # driver developers recommend to use cciss_vol_status for monitoring, +  # hpacucli/hpssacli shouldn't be used for monitoring due they obtaining global +  # kernel lock while cciss_vol_status does not. cciss_vol_status is designed for +  # monitoring +  # https://github.com/glensc/nagios-plugin-check_raid/issues/114#issuecomment-138866801 +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  use constant E_NO_LOGICAL_DEVS => 'The specified device does not have any logical drives'; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'controller status' => ['-|', '@CMD', 'controller', 'all', 'show', 'status'], +  		'logicaldrive status' => ['-|', '@CMD', 'controller', '$target', 'logicaldrive', 'all', 'show'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller all show status", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller * logicaldrive all show", +  	); +  } +   +  # if --plugin-option=hpacucli-target=slot=0 is specified +  # filter only allowed values +  sub filter_targets { +  	my ($this, $targets) = @_; +   +  	my $cli_opts = $this->{options}{'hpacucli-target'}; +  	if (!$cli_opts) { +  		return $targets; +  	} +   +  	my %res; +  	my @filters = split(/,/, $cli_opts); +  	for my $filter (@filters) { +  		if (exists $targets->{$filter}) { +  			$res{$filter} = $targets->{$filter}; +  		} else { +  			$this->critical->message("Controller $filter not found"); +  		} +  	} +   +  	return \%res; +  } +   +  # split: +  # '(Embedded) (RAID Mode)' +  # to: +  # [ 'Embedded', 'RAID Mode' ] +  sub split_controller_modes { +  	my ($modes) = @_; +  	my @parts; +  	push @parts, $1 while $modes =~ /\((.*?)\)/g; +  	return \@parts; +  } +   +  sub scan_targets { +  	my $this = shift; +   +  	# TODO: allow target customize: +  	# hpacucli <target> is of format: +  	#  [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ] +  	#  [array all|<id>] +  	#  [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#] +  	#  [logicaldrive all|#] +  	#  [enclosure all|#:#|serialnumber=#|chassisname=#] +  	#  [licensekey all|<key>] +   +  	# Scan controllers +  	my (%targets, $target); +  	my $fh = $this->cmd('controller status'); +  	while (<$fh>) { +  		chomp; +  		# skip empty lines and artificial comments (added by this project) +  		next if /^$/ or /^#/; +   +  		# skip known noise +  		if ( +  			/FIRMWARE UPGRADE REQUIRED: / +  			|| /^\s{27}/ +  		) { +  			next; +  		} +   +  		# Numeric slot +  		if (my($controller, $slot, $modes) = / +  				^(\S.+)\sin\sSlot +  				\s(\S+?) # slot number +  				(?:     # optional mode +  					\s(\(.+\)) +  				)?$ +  			/x) { +   +  			$target = "slot=$slot"; +  			$targets{$target} = { +  				target => $target, +  				controller => $controller, +  				slot => $slot, +  				modes => split_controller_modes($modes || ''), +  			}; +  			$this->unknown if $slot !~ /^\d+/; +  			next; +  		} +   +  		# Named Entry +  		if (my($controller, $cn) = /^(\S.+) in (.+)/) { +  			$target = "chassisname=$cn"; +  			$targets{$target} = { +  				target => $target, +  				controller => $controller, +  				chassisname => $cn, +  			}; +  			next; +  		} +   +  		# Other statuses, try "key: value" pairs +  		if (my ($key, $value) = /^\s*(.+?):\s+(.+?)$/) { +  			$targets{$target}{$key} = $value; +  			next; +  		} +   +  		warn "Unparsed: [$_]\n"; +  	} +  	close $fh; +   +  	return $this->filter_targets(\%targets); +  } +   +  # Scan logical drives +  sub scan_luns { +  	my ($this, $targets) = @_; +   +  	my @luns; +  	# sort by target to ensure consistent results +  	for my $target (sort {$a->{target} cmp $b->{target}} values(%$targets)) { +  		# check each controller +  		my $fh = $this->cmd('logicaldrive status', { '$target' => $target->{target} }); +   +  		my $index = -1; +  		my @array; +  		my %array; +  		while (<$fh>) { +  			chomp; +  			# skip empty lines and artificial comments (added by this project) +  			next if /^$/ or /^#/; +   +  			# Error: The controller identified by "slot=attr_value_slot_unknown" was not detected. +  			if (/^Error:\s/) { +  				# store it somewhere. should it be appended? +  				($target->{'error'}) = /^Error:\s+(.+?)\.?\s*$/; +  				$this->unknown; +  				next; +  			} +   +  			# "array A" +  			# "array A (Failed)" +  			# "array B (Failed)" +  			if (my($a, $s) = /^\s+array (\S+)(?:\s*\((\S+)\))?$/i) { +  				$index++; +  				# Offset 0 is Array own status +  				# XXX: I don't like this one: undef could be false positive +  				$target->{'array'}[$index]{status} = $s || 'OK'; +  				$target->{'array'}[$index]{name} = $a; +  				next; +  			} +   +  			# logicaldrive 1 (68.3 GB, RAID 1, OK) +  			# capture only status +  			if (my($drive, $size, $raid, $status) = /^\s+logicaldrive (\d+) \(([\d.]+ .B), ([^,]+), ([^\)]+)\)$/) { +  				warn "Index out of bounds" if $index < 0; # XXX should not happen +   +  				# Offset 1 is each logical drive status +  				my $ld = { +  					'id' => $drive, +  					'status' => $status, +  					'size' => $size, +  					'raid' => $raid, +  				}; +  				push(@{$target->{'array'}[$index]{logicaldrives}}, $ld); +  				next; +  			} +   +  			# skip known noise +  			if ( +  				/\s+Type "help" for more details/ +  				# Controller name: exact match +  				|| /^\Q$target->{controller}\E\s/ +  				# loose match, some test data seems malformed +  				|| / in Slot \d/ +  				|| /^FIRMWARE UPGRADE REQUIRED:/ +  				|| /^\s{27}/ +  			) { +  				next; +  			} +   +  			warn "Unhandled: [$_]\n"; +  		} +  		$this->unknown unless close $fh; +   +  		push(@luns, $target); +  	} +   +  	return \@luns; +  } +   +  # parse hpacucli output into logical structure +  sub parse { +  	my $this = shift; +   +  	my $targets = $this->scan_targets; +  	if (!$targets) { +  		return $targets; +  	} +   +  	return $this->scan_luns($targets); +  } +   +  # format lun (logicaldevice) status +  # update check status if problems found +  sub lstatus { +  	my ($this, $ld) = @_; +   +  	my $s = $ld->{status}; +   +  	if ($s eq 'OK' or $s eq 'Disabled') { +  	} elsif ($s eq 'Failed' or $s eq 'Interim Recovery Mode') { +  		$this->critical; +  	} elsif ($s eq 'Rebuild' or $s eq 'Recover') { +  		$this->warning; +  	} +   +  	return "LUN$ld->{id}:$s"; +  } +   +  # format array status +  # update check status if problems found +  sub astatus { +  	my ($this, $array) = @_; +   +  	if ($array->{status} ne 'OK') { +  		$this->critical; +  	} +   +  	return "Array $array->{name}($array->{status})"; +  } +   +  # format controller status +  # updates check status if problems found +  sub cstatus { +  	my ($this, $c) = @_; +  	my (@s, $s); +   +  	# always include controller status +  	push(@s, $c->{'Controller Status'} || 'ERROR'); +  	if ($c->{'Controller Status'} ne 'OK') { +  		$this->critical; +  	} +   +  	if ($c->{error}) { +  		if ($c->{error} eq E_NO_LOGICAL_DEVS) { +  			$this->noraid; +  			push(@s, 'Not configured'); +  		} else { +  			$this->unknown; +  			push(@s, $c->{error}); +  		} +  	} +   +  	# print those only if not ok and configured +  	if (($s = $c->{'Cache Status'}) && $s !~ /^(OK|Not Configured)/) { +  		push(@s, "Cache: $s"); +  		$this->critical; +  	} +  	if (($s = $c->{'Battery/Capacitor Status'}) && $s !~ /^(OK|Not Configured)/) { +  		push(@s, "Battery: $s"); +  		$this->critical; +  	} +   +  	# start with identifyier +  	my $name = $c->{chassisname} || $c->{controller}; +   +  	return $name . '[' . join(', ', @s) . ']'; +  } +   +  sub check { +  	my $this = shift; +   +  	my $ctrls = $this->parse; +  	unless ($ctrls) { +  		$this->warning->message("No Controllers were found on this machine"); +  		return; +  	} +   +  	my @status; +  	foreach my $ctrl (@$ctrls) { +  		my @astatus; +  		foreach my $array (@{$ctrl->{array}}) { +  			my @lstatus; +  			foreach my $ld (@{$array->{logicaldrives}}) { +  				push(@lstatus, $this->lstatus($ld)); +  			} +  			push(@astatus, $this->astatus($array). '['. join(',', @lstatus). ']'); +  		} +  		my $cstatus = $this->cstatus($ctrl); +  		$cstatus .= ': '. join(', ', @astatus) if @astatus; +  		push(@status, $cstatus); +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::hpssacli; +   +  # This plugin extends hpacucli plugin, +  # with the only difference that different program name will be used. +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli'; +  use strict; +  use warnings; +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ips.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::ips; +   +  # Serveraid IPS +  # Tested on IBM xSeries 346 servers with Adaptec ServeRAID 7k controllers. +  # The ipssend version was v7.12.14. +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(ipssend); +  } +   +  sub commands { +  	{ +  		'list logical drive' => ['-|', '@CMD', 'GETCONFIG', '1', 'LD'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd getconfig 1 LD" +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $n; +  	my $fh = $this->cmd('list logical drive'); +  	while (<$fh>) { +  		if (/drive number (\d+)/i){ +  			$n = $1; +  			next; +  		} +   +  		next unless $n; +  		next unless $this->valid($n); +  		next unless (my($s, $c) = /Status .*: (\S+)\s+(\S+)/); +   +  		if ($c =~ /SYN|RBL/i) { # resynching +  			$this->resync; +  		} elsif ($c !~ /OKY/i) { # not OK +  			$this->critical; +  		} +   +  		push(@status, "$n:$s"); +  	} +  	close $fh; +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::lsraid; +   +  # Linux, software RAID +  # Broken: missing test data +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'list' => ['-|', '@CMD', '-A', '-p'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -A -p" +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $fh = $this->cmd('list'); +  	while (<$fh>) { +  		next unless (my($n, $s) = m{/dev/(\S+) \S+ (\S+)}); +  		next unless $this->valid($n); +  		if ($s =~ /good|online/) { +  			# no worries +  		} elsif ($s =~ /sync/) { +  			$this->warning; +  		} else { +  			$this->critical; +  		} +  		push(@status, "$n:$s"); +  	} +  	close $fh; +   +  	return unless @status; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsscsi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'lsscsi list' => ['-|', '@CMD', '-g'], +  	} +  } +   +  # lists contoller devices (type=storage) +  # this will fail (return empty list) if sg module is not present +  # return /dev/sgX nodes +  sub list_sg { +  	my $this = shift; +   +  	my @scan = $this->scan; +   +  	my @devs = map { $_->{sgnode} } grep { $_->{type} eq 'storage' && $_->{sgnode} ne '-' } @scan; +  	return wantarray ? @devs : \@devs; +  } +   +  # list disk nodes one for each controller +  # return /dev/sdX nodes +  sub list_dd { +  	my $this = shift; +   +  	my @scan = $this->scan; +  	my @devs = map { $_->{devnode} } grep { $_->{type} eq 'disk' && $_->{devnode} ne '-' && $_->{sgnode} } @scan; +  	return wantarray ? @devs : \@devs; +  } +   +  # scan lsscsi output +  sub scan { +  	my $this = shift; +   +  	# cache inside single run +  	return wantarray ? @{$this->{sdevs}} : $this->{sdevs} if $this->{sdevs}; +   +  	# Scan such output: +  	# [0:0:0:0]    disk    HP       LOGICAL VOLUME   3.00  /dev/sda   /dev/sg0 +  	# [0:3:0:0]    storage HP       P410i            3.00  -          /dev/sg1 +  	# or without sg driver: +  	# [0:0:0:0]    disk    HP       LOGICAL VOLUME   3.00  /dev/sda   - +  	# [0:3:0:0]    storage HP       P410i            3.00  -          - +   +  	my $fh = $this->cmd('lsscsi list'); +  	my @sdevs; +  	while (<$fh>) { +  		chop; +  		if (my($hctl, $type, $vendor, $model, $rev, $devnode, $sgnode) = m{^ +  			\[([\d:]+)\] # SCSI Controller, SCSI bus, SCSI target, and SCSI LUN +  			\s+(\S+) # type +  			\s+(\S+) # vendor +  			\s+(.*?) # model, match everything as it may contain spaces +  			\s+(\S+) # revision +  			\s+((?:/dev/\S+|-)) # /dev node +  			\s+((?:/dev/\S+|-)) # /dev/sg node +  		}x) { +  			push(@sdevs, { +  				'hctl' => $hctl, +  				'type' => $type, +  				'vendor' => $vendor, +  				'model' => $model, +  				'rev' => $rev, +  				'devnode' => $devnode, +  				'sgnode' => $sgnode, +  			}); +  		} +  	} +  	close $fh; +   +  	$this->{sdevs} = \@sdevs; +  	return wantarray ? @sdevs : \@sdevs; +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsvg.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::lsvg; +   +  # AIX LVM +  # Status: broken (no test data) +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'lsvg' => ['-|', '@CMD'], +  		'lsvg list' => ['-|', '@CMD', '-l', '$vg'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -l *", +  	) +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my @vg; +  	my $fh = $this->cmd('lsvg'); +  	while (<$fh>) { +  		chomp; +  		push @vg, $_; +  	} +  	close $fh; +   +  	foreach my $vg (@vg) { +  		next unless $this->valid($vg); # skip entire VG +   +  		my $fh = $this->cmd('lsvg list', { '$vg' => $vg }); +   +  		while (<$fh>) { +  			my @f = split /\s/; +  			my ($n, $s) = ($f[0], $f[5]); +  			next if (!$this->valid($n) or !$s); +  			next if ($f[3] eq $f[2]); # not a mirrored LV +   +  			if ($s =~ m#open/(\S+)#i) { +  				$s = $1; +  				if ($s ne 'syncd') { +  					$this->critical; +  				} +  				push(@status, "lvm:$n:$s"); +  			} +  		} +  		close $fh; +  	} +   +  	return unless @status; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mdstat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::mdstat; +   +  # Linux Multi-Device (md) +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub commands { +  	{ +  		'mdstat' => ['<', '/proc/mdstat'], +  	} +  } +   +  sub active { +  	my ($this) = @_; +  	# easy way out. no /proc/mdstat +  	return 0 unless -e $this->{commands}{mdstat}[1]; +   +  	# extra check if mdstat is empty +  	my @md = $this->parse; +  	return $#md >= 0; +  } +   +  sub parse { +  	my $this = shift; +   +  	my (@md, %md); +  	my $fh = $this->cmd('mdstat'); +  	my $arr_checking = 0; +  	while (<$fh>) { +  		chomp; +   +  		# skip first line +  		next if (/^Personalities : /); +   +  		# kernel-3.0.101/drivers/md/md.c, md_seq_show +  		# md1 : active raid1 sdb2[0] sda2[1] +  		if (my($dev, $active, $ro, $rest) = m{^ +  			(\S+)\s+:\s+ # mdname +  			(\S+)\s+     # active: "inactive", "active" +  			(\((?:auto-)?read-only\)\s+)? # readonly +  			(.+)         # personality name + disks +  		}x) { +  			my @parts = split /\s/, $rest; +  			my $re = qr{^ +  				(\S+)           # devname +  				(?:\[(\d+)\])   # desc_nr +  				(?:\((.)\))?    # flags: (W|F|S) - WriteMostly, Faulty, Spare +  			$}x; +  			my @disks = (); +  			my $personality; +  			while (my($disk) = pop @parts) { +  				last if !$disk; +  				if ($disk !~ $re) { +  					$personality = $disk; +  					last; +  				} +  				my($dev, $number, $flags) = $disk =~ $re; +  				push(@disks, { +  					'dev' => $dev, +  					'number' => int($number), +  					'flags' => $flags || '', +  				}); +  			} +   +  			die "Unexpected parse" if @parts; +   +  			# first line resets %md +  			%md = (dev => $dev, personality => $personality, readonly => $ro, active => $active, disks => [ @disks ]); +   +  			next; +  		} +   +  		# variations: +  		#"      8008320 blocks [2/2] [UU]" +  		#"      58291648 blocks 64k rounding" - linear +  		#"      5288 blocks super external:imsm" +  		#"      20969472 blocks super 1.2 512k chunks" +  		# +  		# Metadata version: +  		# This is one of +  		# - 'none' for arrays with no metadata (good luck...) +  		# - 'external' for arrays with externally managed metadata, +  		# - or N.M for internally known formats +  		# +  		if (my($b, $mdv, $status) = m{^ +  			\s+(\d+)\sblocks\s+ # blocks +  			# metadata version +  			(super\s(?: +  				(?:\d+\.\d+) | # N.M +  				(?:external:\S+) | +  				(?:non-persistent) +  			))?\s* +  			(.+) # mddev->pers->status (raid specific) +  		$}x) { +  			# linux-2.6.33/drivers/md/dm-raid1.c, device_status_char +  			# A => Alive - No failures +  			# D => Dead - A write failure occurred leaving mirror out-of-sync +  			# S => Sync - A sychronization failure occurred, mirror out-of-sync +  			# R => Read - A read failure occurred, mirror data unaffected +  			# U => for the rest +  			my ($s) = $status =~ /\s+\[([ADSRU_]+)\]/; +   +  			$md{status} = $s || ''; +  			$md{blocks} = int($b); +  			$md{md_version} = $mdv; +   +  			# if external try to parse dev +  			if ($mdv) { +  				($md{md_external}) = $mdv =~ m{external:(\S+)}; +  			} +  			next; +  		} +   +  		# linux-2.6.33/drivers/md/md.c, md_seq_show +  		if (my($action) = m{(resync=(?:PENDING|DELAYED))}) { +  			$md{resync_status} = $action; +  			next; +  		} +  		# linux-2.6.33/drivers/md/md.c, status_resync +  		# [==>..................]  resync = 13.0% (95900032/732515712) finish=175.4min speed=60459K/sec +  		# [=>...................]  check =  8.8% (34390144/390443648) finish=194.2min speed=30550K/sec +  		if (my($action, $perc, $eta, $speed) = m{(resync|recovery|reshape)\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) { +  			$md{resync_status} = "$action:$perc $speed ETA: $eta"; +  			next; +  		} elsif (($perc, $eta, $speed) = m{check\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) { +  			$md{check_status} = "check:$perc $speed ETA: $eta"; +  			$arr_checking = 1; +  			next; +  		} +   +  		# we need empty line denoting end of one md +  		next unless /^\s*$/; +   +  		next unless $this->valid($md{dev}); +   +  		push(@md, { %md } ) if %md; +  	} +  	close $fh; +   +  	# One of the arrays is in checking state, which could be because there is a scheduled sync of all MD arrays +  	# In such a case, all of the arrays are scheduled to by checked, but only one of them is actually running the check +  	# while the others are in "resync=DELAYED" state. +  	# We don't want to receive notifications in such case, so we check for this particular case here +  	if ($arr_checking && scalar(@md) >= 2) { +  		foreach my $dev (@md) { +  			if ($dev->{resync_status} && $dev->{resync_status} eq "resync=DELAYED") { +  				delete $dev->{resync_status}; +  				$dev->{check_status} = "check=DELAYED"; +  			} +  		} +  	} +   +  	return wantarray ? @md : \@md; +  } +   +  sub check { +  	my $this = shift; +   +  	my (@status); +  	my @md = $this->parse; +   +  	my @spare_options = (); +   +  	@spare_options = split(/\,/, $this->{options}{mdstat_spare_count}) +  		if (exists $this->{options}{mdstat_spare_count}); +   +  	foreach (@md) { +  		my %md = %$_; +   +  		# common status +  		my $size = $this->format_bytes($md{blocks} * 1024); +  		my $personality = $md{personality} ? " $md{personality}" : ""; +  		my $s = "$md{dev}($size$personality):"; +   +  		# failed disks +  		my @fd = map { $_->{dev} } grep { $_->{flags} =~ /F/ } @{$md{disks}}; +  		# spare disks +  		my @sd = map { $_->{dev} } grep { $_->{flags} =~ /S/ } @{$md{disks}}; +   +  		my $spare_count = 0; +  		OPTION_LOOP: +  		{ +  			foreach my $i (0 .. $#spare_options) +  			{ +  				my ($disk, $value) = split(/:/, $spare_options[$i]); +  				for(@md) +  				{ +  					if ($md{dev} eq $disk) +  					{ +  						$spare_count = $value; +  						splice(@spare_options, $i, 1); +  						last OPTION_LOOP; +  					} +  				} +  			} +  		} +   +  		# raid0 is just there or its not. raid0 can't degrade. +  		# same for linear, no $md_status available +  		if ($personality =~ /linear|raid0/) { +  			$s .= "OK"; +   +  		} elsif ($md{resync_status}) { +  			$this->resync; +  			$s .= "$md{status} ($md{resync_status})"; +   +  		} elsif ($md{check_status}) { +  			$this->check_status; +  			$s .= "$md{status} ($md{check_status})"; +   +  		} elsif ($md{status} =~ /_/) { +  			$this->critical; +  			my $fd = join(',', @fd); +  			$s .= "F:$fd:$md{status}"; +   +  		} elsif (@fd > 0) { +  			# FIXME: this is same as above? +  			$this->warning; +  			$s .= "hot-spare failure:". join(",", @fd) .":$md{status}"; +  		} elsif (@sd < $spare_count) +  		{ +  			$this->warning; +  			$s .= "Array ".$md{dev}." should have ".$spare_count." spares, but has only ".(0+@sd)." spares"; +  		} else { +  			$s .= "$md{status}"; +  		} +  		push(@status, $s); +  	} +   +  	if (scalar @spare_options > 0) +  	{ +  		$this->critical; +  		foreach (@spare_options) +  		{ +  			my ($disk, $value) = split(/:/, $_); +  			my $s = "$disk is defined in spare_count option but could not be found!"; +  			push(@status, $s); +  		} +  	} +   +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::megacli; +   +  # MegaRAID SAS 8xxx controllers +  # based on info from here: +  # http://www.bxtra.net/Articles/2008-09-16/Dell-Perc6i-RAID-Monitoring-Script-using-MegaCli-LSI-CentOS-52-64-bits +  # TODO: http://www.techno-obscura.com/~delgado/code/check_megaraid_sas +  # TODO: process several adapters +  # TODO: process drive temperatures +  # TODO: check error counts +  # TODO: hostspare information +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(MegaCli64 MegaCli megacli); +  } +   +  sub commands { +  	{ +  		'pdlist' => ['-|', '@CMD', '-PDList', '-aALL', '-NoLog'], +  		'ldinfo' => ['-|', '@CMD', '-LdInfo', '-Lall', '-aALL', '-NoLog'], +  		'battery' => ['-|', '@CMD', '-AdpBbuCmd', '-GetBbuStatus', '-aALL', '-NoLog'], +  	} +  } +   +  # TODO: process from COMMANDS +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +   +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -PDList -aALL -NoLog", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -LdInfo -Lall -aALL -NoLog", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpBbuCmd -GetBbuStatus -aALL -NoLog", +  	); +  } +   +  # parse physical devices +  sub parse_pd { +  	my $this = shift; +   +  	my (@pd, %pd); +  	my $rc = -1; +  	my $fh = $this->cmd('pdlist'); +  	while (<$fh>) { +  		if (my($s) = /Device Id: (\S+)/) { +  			push(@pd, { %pd }) if %pd; +  			%pd = ( dev => $s, state => undef, name => undef, predictive => undef ); +  			next; +  		} +   +  		if (my($s) = /Firmware state: (.+)/) { +  			# strip the extra state: +  			# 'Hotspare, Spun Up' +  			# 'Hotspare, Spun down' +  			# 'Online, Spun Up' +  			# 'Online, Spun Up' +  			# 'Online, Spun down' +  			# 'Unconfigured(bad)' +  			# 'Unconfigured(good), Spun Up' +  			# 'Unconfigured(good), Spun down' +  			$s =~ s/,.+//; +  			$pd{state} = $s; +   +  			if (defined($pd{predictive})) { +  				$pd{state} = $pd{predictive}; +  			} +  			next; +  		} +   +  		if (my($s) = /Predictive Failure Count: (\d+)/) { +  			if ($s > 0) { +  				$pd{predictive} = 'Predictive'; +  			} +  			next; +  		} +   +  		if (my($s) = /Inquiry Data: (.+)/) { +  			# trim some spaces +  			$s =~ s/\s+/ /g; $s =~ s/^\s+|\s+$//g; +  			$pd{name} = $s; +  			next; +  		} +   +  		if (my($s) = /Exit Code: (\d+x\d+)/) { +  			$rc = hex($s); +  		} +  		else { +  			$rc = 0; +  		} +  	} +  	push(@pd, { %pd }) if %pd; +   +  	$this->critical unless close $fh; +  	$this->critical if $rc; +   +  	return \@pd; +  } +   +  sub parse_ld { +  	my $this = shift; +   +  	my (@ld, %ld); +  	my $rc = -1; +  	my $fh = $this->cmd('ldinfo'); +  	while (<$fh>) { +  		if (my($drive_id, $target_id) = /Virtual (?:Disk|Drive)\s*:\s*(\d+)\s*\(Target Id:\s*(\d+)\)/i) { +  			push(@ld, { %ld }) if %ld; +  			# Default to DriveID:TargetID in case no Name is given ... +  			%ld = ( name => "DISK$drive_id.$target_id", state => undef ); +  			next; +  		} +   +  		if (my($name) = /Name\s*:\s*(\S+)/) { +  			# Add a symbolic name, if given +  			$ld{name} = $name; +  			next; +  		} +   +  		if (my($s) = /Virtual Drive Type\s*:\s*(\S+)/) { +  			$ld{type} = $s; +  			next; +  		} +   +  		if (my($s) = /State\s*:\s*(\S+)/) { +  			$ld{state} = $s; +  			next; +  		} +   +  		if (my($s) = /Default Cache Policy\s*:\s*(.+)/) { +  			$ld{default_cache} = [split /,\s*/, $s]; +  			next; +  		} +   +  		if (my($s) = /Current Cache Policy\s*:\s*(.+)/) { +  			$ld{current_cache} = [split /,\s*/, $s]; +  			next; +  		} +   +  		if (my($s) = /Exit Code: (\d+x\d+)/) { +  			$rc = hex($s); +  		} else { +  			$rc = 0; +  		} +  	} +  	push(@ld, { %ld }) if %ld; +   +  	$this->critical unless close $fh; +  	$this->critical if $rc; +   +  	return \@ld; +  } +   +  # check battery +  sub parse_bbu { +  	my $this = shift; +   +  	return undef unless $this->bbu_monitoring; +   +  	my %default_bbu = ( +  		name => undef, state => '???', charging_status => '???', missing => undef, +  		learn_requested => undef, replacement_required => undef, +  		learn_cycle_requested => undef, learn_cycle_active => '???', +  		pack_will_fail => undef, temperature => undef, temperature_state => undef, +  		voltage => undef, voltage_state => undef +  	); +   +  	my (@bbu, %bbu); +  	my $fh = $this->cmd('battery'); +  	while (<$fh>) { +  		# handle when bbu status get gives an error. see issue #32 +  		if (my($s) = /Get BBU Status Failed/) { +  			last; +  		} +   +  		if (my($s) = /BBU status for Adapter: (.+)/) { +  			push(@bbu, { %bbu }) if %bbu; +  			%bbu = %default_bbu; +  			$bbu{name} = $s; +  			next; +  		} +  #=cut +  # according to current sample data, Battery State never has value +  		if (my($s) = /Battery State\s*: ?(.*)/i) { +  			if (!$s) { $s = 'Faulty'; }; +  			$bbu{state} = $s; +  			next; +  		} +  #=cut +  		if (my($s) = /Charging Status\s*: (\w*)/) { +  			$bbu{charging_status} = $s; +  			next; +  		} +  		if (my($s) = /Battery Pack Missing\s*: (\w*)/) { +  			$bbu{missing} = $s; +  			next; +  		} +  		if (my($s) = /Battery Replacement required\s*: (\w*)/) { +  			$bbu{replacement_required} = $s; +  			next; +  		} +  		if (my($s) = /Learn Cycle Requested\s*: (\w*)/) { +  			$bbu{learn_cycle_requested} = $s; +  			next; +  		} +  		if (my($s) = /Learn Cycle Active\s*: (\w*)/) { +  			$bbu{learn_cycle_active} = $s; +  			next; +  		} +  		if (my($s) = /Pack is about to fail & should be replaced\s*: (\w*)/) { +  			$bbu{pack_will_fail} = $s; +  			next; +  		} +  		# Temperature: 18 C +  		if (my($s) = /Temperature: (\d+) C/) { +  			$bbu{temperature} = $s; +  			next; +  		} +  		# Temperature : OK +  		if (my($s) = /  Temperature\s*: (\w*)/) { +  			$bbu{temperature_state} = $s; +  			next; +  		} +  		# Voltage: 4074 mV +  		if (my($s) = /Voltage: (\d+) mV/) { +  			$bbu{voltage} = $s; +  			next; +  		} +  		# Voltage : OK +  		if (my($s) = /Voltage\s*: (\w*)/) { +  			$bbu{voltage_state} = $s; +  			next; +  		} +   +  	} +  	$this->critical unless close $fh; +   +  	push(@bbu, { %bbu }) if %bbu; +   +  	return \@bbu; +  } +   +  sub parse { +  	my $this = shift; +   +  	my $pd = $this->parse_pd; +  	my $ld = $this->parse_ld; +  	my $bbu = $this->parse_bbu; +   +  	my @devs = @$pd if $pd; +  	my @vols = @$ld if $ld; +  	my @bats = @$bbu if $bbu; +   +  	return { +  		logical => $ld, +  		physical => $pd, +  		battery => $bbu, +  	}; +  } +   +  sub check { +  	my $this = shift; +   +  	my $c = $this->parse; +   +  	my @vstatus; +  	foreach my $vol (@{$c->{logical}}) { +  		# skip CacheCade for now. #91 +  		if ($vol->{type} && $vol->{type} eq 'CacheCade') { +  			next; +  		} +   +  		push(@vstatus, sprintf "%s:%s", $vol->{name}, $vol->{state}); +  		if ($vol->{state} ne 'Optimal') { +  			$this->critical; +  		} +   +  		# check cache policy, #65 +  		my @wt = grep { /WriteThrough/ } @{$vol->{current_cache}}; +  		if (@wt) { +  			my @default = grep { /WriteThrough/ } @{$vol->{default_cache}}; +  			# alert if WriteThrough is configured in default +  			$this->cache_fail unless @default; +  			push(@vstatus, "WriteCache:DISABLED"); +  		} +  	} +   +  	my %dstatus; +  	foreach my $dev (@{$c->{physical}}) { +  		if ($dev->{state} eq 'Online' || $dev->{state} eq 'Hotspare' || $dev->{state} eq 'Unconfigured(good)' || $dev->{state} eq 'JBOD') { +  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d", $dev->{dev}); +   +  		} elsif ($dev->{state} eq 'Predictive') { +  			$this->warning; +  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name}); +  		} else { +  			$this->critical; +  			# TODO: process other statuses +  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name}); +  		} +  	} +   +  	my (%bstatus, @bpdata, @blongout); +  	foreach my $bat (@{$c->{battery}}) { +  		if ($bat->{state} !~ /^(Operational|Optimal)$/) { +  			# BBU learn cycle in progress. +  			if ($bat->{charging_status} =~ /^(Charging|Discharging)$/ && $bat->{learn_cycle_active} eq 'Yes') { +  				$this->bbulearn; +  			} else { +  				$this->critical; +  			} +  		} +  		if ($bat->{missing} ne 'No') { +  			$this->critical; +  		} +  		if ($bat->{replacement_required} ne 'No') { +  			$this->critical; +  		} +  		if (defined($bat->{pack_will_fail}) && $bat->{pack_will_fail} ne 'No') { +  			$this->critical; +  		} +  		if ($bat->{temperature_state} ne 'OK') { +  			$this->critical; +  		} +  		if ($bat->{voltage_state} ne 'OK') { +  			$this->critical; +  		} +   +  		# Short output. +  		# +  		# CRITICAL: megacli:[Volumes(1): NoName:Optimal; Devices(2): 06,07=Online; Batteries(1): 0=Non Operational] +  		push(@{$bstatus{$bat->{state}}}, sprintf "%d", $bat->{name}); +  		# Performance data. +  		# Return current battery temparature & voltage. +  		# +  		# Battery0=18;4074 +  		push(@bpdata, sprintf "Battery%s_T=%s;;;; Battery%s_V=%s;;;;", $bat->{name}, $bat->{temperature}, $bat->{name}, $bat->{voltage}); +   +  		# Long output. +  		# Detailed plugin output. +  		# +  		# Battery0: +  		#  - State: Non Operational +  		#  - Missing: No +  		#  - Replacement required: Yes +  		#  - About to fail: No +  		#  - Temperature: OK (18 °C) +  		#  - Voltage: OK (4015 mV) +  		push(@blongout, join("\n", grep {/./} +  			"Battery$bat->{name}:", +  			" - State: $bat->{state}", +  			" - Charging status: $bat->{charging_status}", +  			" - Learn cycle requested: $bat->{learn_cycle_requested}", +  			" - Learn cycle active: $bat->{learn_cycle_active}", +  			" - Missing: $bat->{missing}", +  			" - Replacement required: $bat->{replacement_required}", +  			defined($bat->{pack_will_fail}) ? " - About to fail: $bat->{pack_will_fail}" : "", +  			" - Temperature: $bat->{temperature_state} ($bat->{temperature} C)", +  			" - Voltage: $bat->{voltage_state} ($bat->{voltage} mV)", +  		)); +  	} +   +  	my @cstatus; +  	push(@cstatus, 'Volumes(' . ($#{$c->{logical}} + 1) . '): ' . join(',', @vstatus)); +  	push(@cstatus, 'Devices(' . ($#{$c->{physical}} + 1) . '): ' . $this->join_status(\%dstatus)); +  	push(@cstatus, 'Batteries(' . ($#{$c->{battery}} + 1) . '): ' . $this->join_status(\%bstatus)) if @{$c->{battery}}; +  	my @status = join('; ', @cstatus); +   +  	my @pdata; +  	push(@pdata, +  		join('\n', @bpdata) +  	); +  	my @longout; +  	push(@longout, +  		join('\n', @blongout) +  	); +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join(' ', @status)); +  	$this->perfdata(join(' ', @pdata)); +  	$this->longoutput(join(' ', @longout)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaide.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::megaide; +   +  # MegaIDE RAID controller +  # Status: BROKEN: no test data +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub sudo { +  	my ($this) = @_; +  	my $cat = $this->which('cat'); +   +  	"CHECK_RAID ALL=(root) NOPASSWD: $cat /proc/megaide/0/status"; +  } +   +  sub check { +  	my $this = shift; +  	my $fh; +   +  	# status messages pushed here +  	my @status; +   +  	foreach my $f (</proc/megaide/*/status>) { # / silly comment to fix vim syntax hilighting +  		if (-r $f) { +  			open $fh, '<', $f or next; +  =cut +  		} else { +  			my @CMD = ($cat, $f); +  			unshift(@CMD, $sudo) if $> and $sudo; +  			open($fh , '-|', @CMD) or next; +  =cut +  		} +  		while (<$fh>) { +  			next unless (my($s, $n) = /Status\s*:\s*(\S+).*Logical Drive.*:\s*(\d+)/i); +  			next unless $this->valid($n); +  			if ($s ne 'ONLINE') { +  				$this->critical; +  				push(@status, "$n:$s"); +  			} else { +  				push(@status, "$n:$s"); +  			} +  			last; +  		} +  		close $fh; +  	} +   +  	return unless @status; +   +  	$this->message(join(' ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::megaraid; +   +  # MegaRAID +  # Status: BROKEN: no test data +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub sudo { +  	my ($this) = @_; +  	my $cat = $this->which('cat'); +   +  	my @sudo; +  	foreach my $mr (</proc/mega*/*/raiddrives*>) { +  		push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cat $mr") if -d $mr; +  	} +   +  	@sudo; +  } +   +  sub check { +  	my $this = shift; +  	# status messages pushed here +  	my @status; +   +  	foreach my $f (</proc/megaraid/*/raiddrives*>) { # vim/ +  		my $fh; +  		if (-r $f) { +  			open $fh, '<', $f or next; +  =cut +  		} else { +  			my @CMD = ($cat, $f); +  			unshift(@CMD, $sudo) if $> and $sudo; +  			open($fh , '-|', @CMD) or next; +  =cut +  		} +  		my ($n) = $f =~ m{/proc/megaraid/([^/]+)}; +  		while (<$fh>) { +  			if (my($s) = /logical drive\s*:\s*\d+.*, state\s*:\s*(\S+)/i) { +  				if ($s ne 'optimal') { +  					$this->critical; +  				} +  				push(@status, "$n: $s"); +  				last; +  			} +  		} +  		close $fh; +  	} +   +  	return unless @status; +   +  	$this->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megarc.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::megarc; +   +  # LSI MegaRaid or Dell Perc arrays +  # Check the status of all arrays on all Lsi MegaRaid controllers on the local +  # machine. Uses the megarc program written by Lsi to get the status of all +  # arrays on all local Lsi MegaRaid controllers. +  # +  # check designed from check_lsi_megaraid: +  # http://www.monitoringexchange.org/cgi-bin/page.cgi?g=Detailed/2416.html;d=1 +  # Perl port (check_raid) by Elan Ruusamäe. +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'controller list' => ['-|', '@CMD', '-AllAdpInfo', '-nolog'], +  		'controller config' => ['-|', '@CMD', '-dispCfg', '-a$controller', '-nolog'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AllAdpInfo -nolog", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -dispCfg -a* -nolog", +  	); +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	# get controllers +  	my $fh = $this->cmd('controller list'); +  	my @lines = <$fh>; +  	close $fh; +   +  	if ($lines[11] =~ /No Adapters Found/) { +  		$this->warning; +  		$this->message("No LSI adapters were found on this machine"); +  		return; +  	} +   +  	my @c; +  	foreach (@lines[12..$#lines]) { +  		if (my ($id) = /^\s*(\d+)/) { +  			push(@c, int($id)); +  		} +  	} +  	unless (@c) { +  		$this->warning; +  		$this->message("No LSI adapters were found on this machine"); +  		return; +  	} +   +  	foreach my $c (@c) { +  		my $fh = $this->cmd('controller config', { '$controller' => $c }); +  		my (%d, %s, $ld); +  		while (<$fh>) { +  			# Logical Drive : 0( Adapter: 0 ):  Status: OPTIMAL +  			if (my($d, $s) = /Logical Drive\s+:\s+(\d+).+Status:\s+(\S+)/) { +  				$ld = $d; +  				$s{$ld} = $s; +  				next; +  			} +  			# SpanDepth :01     RaidLevel: 5  RdAhead : Adaptive  Cache: DirectIo +  			if (my($s) = /RaidLevel:\s+(\S+)/) { +  				$d{$ld} = $s if defined $ld; +  				next; +  			} +  		} +  		close $fh; +   +  		# now process the details +  		unless (keys %d) { +  			$this->message("No arrays found on controller $c"); +  			$this->warning; +  			return; +  		} +   +  		while (my($d, $s) = each %s) { +  			if ($s ne 'OPTIMAL') { +  				# The Array number here is incremented by one because of the +  				# inconsistent way that the LSI tools count arrays. +  				# This brings it back in line with the view in the bios +  				# and from megamgr.bin where the array counting starts at +  				# 1 instead of 0 +  				push(@status, "Array ".(int($d) + 1)." status is ".$s{$d}." (Raid-$s on adapter $c)"); +  				$this->critical; +  				next; +  			} +   +  			push(@status, "Logical Drive $d: $s"); +  		} +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/metastat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::metastat; +   +  # Solaris, software RAID +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'metastat' => ['>&2', '@CMD'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd" +  } +   +  sub active { +  	my ($this) = @_; +   +  	# return if parent said NO +  	my $res = $this->SUPER::active(@_); +  	return $res unless $res; +   +  	my $output = $this->get_metastat; +  	return !!@$output; +  } +   +  sub get_metastat { +  	my $this = shift; +   +  	# cache inside single run +  	return $this->{output} if defined $this->{output}; +   +  	my $fh = $this->cmd('metastat'); +  	my @data; +  	while (<$fh>) { +  		chomp; +  		last if /there are no existing databases/; +  		push(@data, $_); +  	} +   +  	return $this->{output} = \@data; +  } +   +  sub check { +  	my $this = shift; +   +  	my ($d, $sd); +   +  	# status messages pushed here +  	my @status; +  	my $output = $this->get_metastat; +   +  	foreach (@$output) { +  		if (/^(\S+):/) { $d = $1; $sd = ''; next; } +  		if (/Submirror \d+:\s+(\S+)/) { $sd = $1; next; } +  		if (/Device:\s+(\S+)/) { $sd = $1; next; } +  		if (my($s) = /State: (\S.+\w)/) { +  			if ($sd and $this->valid($sd) and $this->valid($d)) { +  				if ($s =~ /Okay/i) { +  					# no worries... +  				} elsif ($s =~ /Resync/i) { +  					$this->resync; +  				} else { +  					$this->critical; +  				} +  				push(@status, "$d:$sd:$s"); +  			} +  		} +   +  		if (defined $d && $d =~ /hsp/) { +  			if (/(c[0-9]+t[0-9]+d[0-9]+s[0-9]+)\s+(\w+)/) { +  				$sd = $1; +  				my $s = $2; +  				$this->warning if ($s !~ /Available/); +  				push(@status, "$d:$sd:$s"); +  			} +  		} +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mpt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::mpt; +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(mpt-status); +  } +   +  sub commands { +  	{ +  		'get_controller_no' => ['-|', '@CMD', '-p'], +  		'status' => ['-|', '@CMD', '-i', '$id'], +  		'sync status' => ['-|', '@CMD', '-n'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [0-9]", +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [1-9][0-9]", +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -n", +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -p", +  	); +  } +   +  sub active { +  	my ($this) = @_; +   +  	# return if parent said NO +  	my $res = $this->SUPER::active(@_); +  	return $res unless $res; +   +  	# there should be a controller. #95 +  	my $id = $this->get_controller; +  	return defined($id); +  } +   +  sub get_controller { +  	my $this = shift; +  	 +  	# controller ID may be given on the command line +  	my $id = $this->{options}{'mpt-id'}; +  	if (!$id) { +   +  		# get controller from mpt-status -p +  		my $fh = $this->cmd('get_controller_no'); +  		while (<$fh>) { +  			chomp; +  			if (/^Found.*id=(\d{1,2}),.*/) { +  				$id = $1; +  				last; +  			} +  		} +  		close $fh; +  	} +   +  	return $id; +  } +   +  sub parse { +  	my ($this, $id) = @_; +   +  	my (%ld, %pd); +   +  	my $fh = $this->cmd('status', { '$id' => $id }); +   +  	my %VolumeTypesHuman = ( +  		IS => 'RAID-0', +  		IME => 'RAID-1E', +  		IM => 'RAID-1', +  	); +   +  	while (<$fh>) { +  		chomp; +  		# mpt-status.c __print_volume_classic +  		# ioc0 vol_id 0 type IM, 2 phy, 136 GB, state OPTIMAL, flags ENABLED +  		if (my($vioc, $vol_id, $type, $disks, $vol_size, $vol_state, $vol_flags) = +  			/^ioc(\d+)\s+ vol_id\s(\d+)\s type\s(\S+),\s (\d+)\sphy,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) { +  			$ld{$vol_id} = { +  				ioc => int($vioc), +  				vol_id => int($vol_id), +  				# one of: IS, IME, IM +  				vol_type => $type, +  				raid_level => $VolumeTypesHuman{$type}, +  				phy_disks => int($disks), +  				size => int($vol_size), +  				# one of: OPTIMAL, DEGRADED, FAILED, UNKNOWN +  				status => $vol_state, +  				# array of: ENABLED, QUIESCED, RESYNC_IN_PROGRESS, VOLUME_INACTIVE or NONE +  				flags => [ split ' ', $vol_flags ], +  			}; +  		} +   +  		# ./include/lsi/mpi_cnfg.h +  		# typedef struct _RAID_PHYS_DISK_INQUIRY_DATA +  		# { +  		#   U8 VendorID[8];            /* 00h */ +  		#   U8 ProductID[16];          /* 08h */ +  		#   U8 ProductRevLevel[4];     /* 18h */ +  		#   U8 Info[32];               /* 1Ch */ +  		# } +  		# mpt-status.c __print_physdisk_classic +  		# ioc0 phy 0 scsi_id 0 IBM-ESXS PYH146C3-ETS10FN RXQN, 136 GB, state ONLINE, flags NONE +  		# ioc0 phy 0 scsi_id 1 ATA      ST3808110AS      J   , 74 GB, state ONLINE, flags NONE +  		# ioc0 phy 0 scsi_id 1 ATA      Hitachi HUA72101 AJ0A, 931 GB, state ONLINE, flags NONE +  		elsif (my($pioc, $num, $phy_id, $vendor, $prod_id, $rev, $size, $state, $flags) = +  			/^ioc(\d+)\s+ phy\s(\d+)\s scsi_id\s(\d+)\s (.{8})\s+(.{16})\s+(.{4})\s*,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) { +  			$pd{$num} = { +  				ioc => int($pioc), +  				num => int($num), +  				phy_id => int($phy_id), +  				vendor => $vendor, +  				prod_id => $prod_id, +  				rev => $rev, +  				size => int($size), +  				# one of: ONLINE, MISSING, NOT_COMPATIBLE, FAILED, INITIALIZING, OFFLINE_REQUESTED, FAILED_REQUESTED, OTHER_OFFLINE, UNKNOWN +  				status => $state, +  				# array of: OUT_OF_SYNC, QUIESCED or NONE +  				flags => [ split ' ', $flags ], +  			}; +  		} else { +  			warn "mpt unparsed: [$_]"; +  			$this->unknown; +  		} +  	} +  	close $fh; +   +  	# extra parse, if mpt-status has -n flag, can process also resync state +  	# TODO: if -n becames default can do this all in one run +  	my $resyncing = grep {/RESYNC_IN_PROGRESS/} map { @{$_->{flags}} } values %ld; +  	if ($resyncing) { +  		my $fh = $this->cmd('sync status'); +  		while (<$fh>) { +  			if (/^ioc:\d+/) { +  				# ignore +  			} +  			# mpt-status.c GetResyncPercentage +  			# scsi_id:0 70% +  			elsif (my($scsi_id, $percent) = /^scsi_id:(\d+) (\d+)%/) { +  				$pd{$scsi_id}{resync} = int($percent); +  			} else { +  				warn "mpt unparsed: [$_]"; +  				$this->unknown; +  			} +  		} +  		close $fh; +  	} +   +  	return { +  		'logical' => { %ld }, +  		'physical' => { %pd }, +  	}; +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $id = $this->get_controller; +  	my $status = $this->parse($id); +   +  	# process logical units +  	while (my($d, $u) = each %{$status->{logical}}) { +  		next unless $this->valid($d); +   +  		my $s = $u->{status}; +  		if ($s =~ /INITIAL|INACTIVE/) { +  			$this->warning; +  		} elsif ($s =~ /RESYNC/) { +  			$this->resync; +  		} elsif ($s =~ /DEGRADED|FAILED/) { +  			$this->critical; +  		} elsif ($s !~ /ONLINE|OPTIMAL/) { +  			$this->unknown; +  		} +   +  		# FIXME: this resync_in_progress is separate state of same as value in status? +  		if (grep { /RESYNC_IN_PROGRESS/ } @{$u->{flags}}) { +  			# find matching disks +  			my @disks = grep {$_->{ioc} eq $u->{ioc} } values %{$status->{physical}}; +  			# collect percent for each disk +  			my @percent = map { $_->{resync}.'%'} @disks; +  			$s .= ' RESYNCING: '.join('/', @percent); +  		} +  		push(@status, "Volume $d ($u->{raid_level}, $u->{phy_disks} disks, $u->{size} GiB): $s"); +  	} +   +  	# process physical units +  	while (my($d, $u) = each %{$status->{physical}}) { +  		my $s = $u->{status}; +  		# remove uninteresting flags +  		my @flags = grep {!/NONE/} @{$u->{flags}}; +   +  		# skip print if nothing in flags and disk is ONLINE +  		next unless @flags and $s eq 'ONLINE'; +   +  		$s .= ' ' . join(' ', @flags); +  		push(@status, "Disk $d ($u->{size} GiB):$s"); +  		$this->critical; +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mvcli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::mvcli; +   +  # Status: BROKEN: not finished +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'mvcli blk' => ['-|', '@CMD', 'info', '-o', 'blk'], +  		'mvcli vd' => ['-|', '@CMD', 'info', '-o', 'vd'], +  		'mvcli smart' => ['-|', '@CMD', 'smart', '-p', '0'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd" +  } +   +  sub parse_blk { +  	my $this = shift; +   +  	my (@blk, %blk); +   +  	my $fh = $this->cmd('mvcli blk'); +  	while (<$fh>) { +  		chomp; +   +  		if (my ($blk_id) = /Block id:\s+(\d+)/) { +  			# block id is first item, so push previous item to list +  			if (%blk) { +  				push(@blk, { %blk }); +  				%blk = (); +  			} +  			$blk{blk_id} = int($blk_id); +  		} elsif (my($pd_id) = /PD id:\s+(\d+)/) { +  			$blk{pd_id} = int($pd_id); +  		} elsif (my($vd_id) = /VD id:\s+(\d+)/) { +  			$blk{vd_id} = int($vd_id); +  		} elsif (my($bstatus) = /Block status:\s+(.+)/) { +  			$blk{block_status} = $bstatus; +  		} elsif (my($size) = /Size:\s+(\d+) K/) { +  			$blk{size} = int($size); +  		} elsif (my($offset) = /Starting offset:\s+(\d+) K/) { +  			$blk{offset} = int($offset); +  		} else { +  #			warn "[$_]\n"; +  		} +  	} +  	close $fh; +   +  	if (%blk) { +  		push(@blk, { %blk }); +  	} +   +  	return wantarray ? @blk : \@blk; +  } +   +  sub parse_vd { +  	my $this = shift; +   +  	my (@vd, %vd); +  	my ($name, $value); +   +  	my $fh = $this->cmd('mvcli vd'); +  	while (<$fh>) { +  		chomp; +   +  		if (/^$/ +  				|| /----+/ +  				|| /SG driver version/ +  				|| /Virtual Disk Information/ +  			) { +  			next; +  		} +   +  		unless (($name, $value) = /^(.+):\s+(.+)$/) { +  			warn "UNPARSED: [$_]"; +  			next; +  		} +   +  		if ($name eq 'id') { +  			# id is first item, so push previous item to list +  			if (%vd) { +  				push(@vd, { %vd }); +  				%vd = (); +  			} +  		} +   +  		$vd{$name} = $value; +  	} +  	close $fh; +   +  	if (%vd) { +  		push(@vd, { %vd }); +  	} +   +  	return wantarray ? @vd : \@vd; +  } +   +  sub parse_smart { +  	my ($this, $blk) = @_; +   +  	# collect pd numbers +  	my @pd = map { $_->{pd_id} } @$blk; +   +  	my %smart; +  	foreach my $pd (@pd) { +  		my $fh = $this->cmd('mvcli smart', { '$pd' => $pd }); +  		my %attrs = (); +  		while (<$fh>) { +  			chomp; +   +  			if (my($id, $name, $current, $worst, $treshold, $raw, $status) = / +  				([\dA-F]{2})\s+ # id +  				(.*?)\s+        # name +  				(\d+)\s+        # current +  				(\d+)\s+        # worst +  				(\d+)\s+        # treshold +  				([\dA-F]{12})   # raw +  				(?:\s+(\w+))?   # status +  			/x) { +  				my %attr = (); +  				$attr{id} = $id; +  				$attr{name} = $name; +  				$attr{current} = int($current); +  				$attr{worst} = int($worst); +  				$attr{treshold} = int($treshold); +  				$attr{raw} = $raw; +  				$attr{status} = $status || undef; +  				$attrs{$id} = { %attr }; +  			} else { +  #				warn "[$_]\n"; +  			} +  		} +   +  		$smart{$pd} = { %attrs }; +  	} +   +  	return \%smart; +  } +   +  sub parse { +  	my $this = shift; +   +  	my $blk = $this->parse_blk; +  	my $vd = $this->parse_vd; +  	my $smart = $this->parse_smart($blk); +   +  	return { +  		blk => $blk, +  		vd => $vd, +  		smart => $smart, +  	}; +  } +   +  sub check { +  	my $this = shift; +   +  	my @status; +  	my $c = $this->parse; +   +  	foreach my $vd (@{$c->{vd}}) { +  		my $size = $this->format_bytes($this->parse_bytes($vd->{size})); +  		if ($vd->{status} ne 'functional') { +  			$this->critical; +  		} +  		push(@status, "VD($vd->{name} $vd->{'RAID mode'} $size): $vd->{status}"); +  	} +   +  	return unless @status; +   +  	# denote this plugin as ran ok +  	$this->ok; +   +  	$this->message(join('; ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/sas2ircu.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::sas2ircu; +   +  # LSI SAS-2 controllers using the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU) +  # Based on the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU) User Guide +  # http://www.lsi.com/downloads/Public/Host%20Bus%20Adapters/Host%20Bus%20Adapters%20Common%20Files/SAS_SATA_6G_P12/SAS2IRCU_User_Guide.pdf +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'controller list' => ['-|', '@CMD', 'LIST'], +  		'controller status' => ['-|', '@CMD', '$controller', 'STATUS'], +  		'device status' => ['-|', '@CMD', '$controller', 'DISPLAY'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd LIST", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * STATUS", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * DISPLAY", +  	); +  } +   +  # detect controllers for sas2ircu +  sub detect { +  	my $this = shift; +   +  	my @ctrls; +  	my $fh = $this->cmd('controller list'); +   +  	my $success = 0; +  	my $state=""; +  	my $noctrlstate="No Controllers"; +  	while (<$fh>) { +  		chomp; +   +  		#         Adapter     Vendor  Device                        SubSys  SubSys +  		# Index    Type          ID      ID    Pci Address          Ven ID  Dev ID +  		# -----  ------------  ------  ------  -----------------    ------  ------ +  		#   0     SAS2008     1000h    72h     00h:03h:00h:00h      1028h   1f1eh +  		if (my($c) = /^\s*(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/) { +  			push(@ctrls, $c); +  		} +  		$success = 1 if /SAS2IRCU: Utility Completed Successfully/; +   +  		# handle the case where there's no hardware present. +  		# when there is no controller, we get +  		# root@i41:/tmp$ /usr/sbin/sas2ircudsr LIST +  		# LSI Corporation SAS2 IR Configuration Utility. +  		# Version 18.00.00.00 (2013.11.18) +  		# Copyright (c) 2009-2013 LSI Corporation. All rights reserved. +   +  		# SAS2IRCU: MPTLib2 Error 1 +  		# root@i41:/tmp$ echo $? +  		# 1 +   +  		if (/SAS2IRCU: MPTLib2 Error 1/) { +  			$state = $noctrlstate; +  			$success = 1 ; +  		} +   +  	} +   +  	unless (close $fh) { +  		#sas2ircu exits 1 (but close exits 256) when we close fh if we have no controller, so handle that, too +  		if ($? != 256 && $state eq $noctrlstate) { +  			$this->critical; +  		} +  	} +  	unless ($success) { +  		$this->critical; +  	} +   +  	return wantarray ? @ctrls : \@ctrls; +  } +   +  sub  trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; +  sub ltrim { my $s = shift; $s =~ s/^\s+//;       return $s }; +  sub rtrim { my $s = shift; $s =~ s/\s+$//;       return $s }; +   +  sub check { +  	my $this = shift; +   +  	my @ctrls = $this->detect; +   +  	my @status; +  	my $numvols=0; +  	# determine the RAID states of each controller +  	foreach my $c (@ctrls) { +  		my $fh = $this->cmd('controller status', { '$controller' => $c }); +   +  		my $novolsstate="No Volumes"; +  		my $state; +  		my $success = 0; +  		while (<$fh>) { +  			chomp; +   +  			# match adapter lines +  			if (my($s) = /^\s*Volume state\s*:\s*(\w+)\s*$/) { +  				$state = $s; +  				$numvols++; +  				if ($state ne "Optimal") { +  					$this->critical; +  				} +  			} +  			$success = 1 if /SAS2IRCU: Utility Completed Successfully/; +   +  			##handle the case where there are no volumes configured +  			# +  			# SAS2IRCU: there are no IR volumes on the controller! +  			# SAS2IRCU: Error executing command STATUS. +   +  			if (/SAS2IRCU: there are no IR volumes on the controller/ +  				or /The STATUS command is not supported by the firmware currently loaded on controller/ +  			) { +  				# even though this isn't the last line, go ahead and set success. +  				$success = 1; +  				$state = $novolsstate; +  			} +   +  		} +   +  		unless (close $fh) { +  			#sas2ircu exits 256 when we close fh if we have no volumes, so handle that, too +  			if ($? != 256 && $state eq $novolsstate) { +  				$this->critical; +  				$state = $!; +  			} +  		} +   +  		unless ($success) { +  			$this->critical; +  			$state = "SAS2IRCU Unknown exit"; +  		} +   +  		unless ($state) { +  			$state = "Unknown Error"; +  		} +   +  		my $finalvolstate=$state; +  		#push(@status, "ctrl #$c: $numvols Vols: $state"); +   +   +  		#####  now look at the devices. +  		# Device is a Hard disk +  		#   Enclosure #                             : 2 +  		#   Slot #                                  : 0 +  		#   SAS Address                             : 500065b-3-6789-abe0 +  		#   State                                   : Ready (RDY) +  		#   Size (in MB)/(in sectors)               : 3815447/7814037167 +  		#   Manufacturer                            : ATA +  		#   Model Number                            : ST4000DM000-1F21 +  		#   Firmware Revision                       : CC52 +  		#   Serial No                               : S30086G4 +  		#   GUID                                    : 5000c5006d27b344 +  		#   Protocol                                : SATA +  		#   Drive Type                              : SATA_HDD +   +  		$fh = $this->cmd('device status', { '$controller' => $c }); +  		$state=""; +  		$success = 0; +  		my $enc=""; +  		my $slot=""; +  		my @data; +  		my $device=""; +  		my $numslots=0; +  		my $finalstate; +  		my $finalerrors=""; +   +  		while (my $line = <$fh>) { +  			chomp $line; +  			# Device is a Hard disk +  			# Device is a Hard disk +  			# Device is a Enclosure services device +  			# +  			#lets make sure we're only checking disks.  we dont support other devices right now +  			if ("$line" eq 'Device is a Hard disk') { +  				$device='disk'; +  			} elsif ($line =~ /^Device/) { +  				$device='other'; +  			} +   +  			if ("$device" eq 'disk') { +  				if ($line =~ /Enclosure #|Slot #|State /) { +  					#find our enclosure # +  					if ($line =~ /^  Enclosure # /) { +  						@data = split /:/, $line; +  						$enc=trim($data[1]); +  						#every time we hit a new enclosure line, reset our state and slot +  						undef $state; +  						undef $slot; +  					} +  					#find our slot # +  					if ($line =~ /^  Slot # /) { +  						@data = split /:/, $line; +  						$slot=trim($data[1]); +  						$numslots++ +  					} +  					#find our state +  					if ($line =~ /^  State /) { +  						@data = split /:/, $line; +  						$state=ltrim($data[1]); +   +  						#for test +  						#if ($numslots == 10 ) { $state='FREDFISH';} +   +  						#when we get a state, test on it and report it.. +  						if ($state =~ /Optimal|Ready/) { +  							#do nothing at the moment. +  						} else { +  							$this->critical; +  							$finalstate=$state; +  							$finalerrors="$finalerrors ERROR:Ctrl$c:Enc$enc:Slot$slot:$state"; +  						} +  					} +  				} +  			} +   +  			if ($line =~ /SAS2IRCU: Utility Completed Successfully/) { +  				$success = 1; +  			} +   +  		} #end while +   +   +  		unless (close $fh) { +  			$this->critical; +  			$state = $!; +  		} +   +  		unless ($success) { +  			$this->critical; +  			$state = "SAS2IRCU Unknown exit"; +  		} +   +  		unless ($state) { +  			$state = "Unknown Error"; +  		} +   +  		unless($finalstate) { +  			$finalstate=$state; +  		} +   +  		#per controller overall report +  		#push(@status, ":$numslots Drives:$finalstate:$finalerrors"); +  		push(@status, "ctrl #$c: $numvols Vols: $finalvolstate: $numslots Drives: $finalstate:$finalerrors:"); +   +  	} +   +  	##if we didn't get a status out of the controllers and an empty ctrls array, we must not have any. +  	unless (@status && @ctrls) { +  		push(@status, "No Controllers"); +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/smartctl.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::smartctl; +   +  # NOTE: not standalone plugin +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  use strict; +  use warnings; +   +  sub program_names { +  	shift->{name}; +  } +   +  sub commands { +  	{ +  		'smartctl' => ['-|', '@CMD', '-H', '$dev', '$diskopt$disk'], +  	} +  } +   +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	# nothing, as not standalone plugin yet +  } +   +  # check for -H parameter for physical disks +  # this is currently called out from cciss plugin +  # @param device list +  # device list being an array of: +  # - device to check (/dev/cciss/c0d0) +  # - disk options (-dcciss) +  # - disk number (0..15) +  sub check_devices { +  	my $this = shift; +  	my @devs = @_; +   +  	unless (@devs) { +  		$this->warning; +  		$this->message("No devices to check"); +  		return; +  	} +   +  	# status message for devs, latter just joined for shorter messages +  	my %status; +   +  	foreach my $ref (@devs) { +  		my ($dev, $diskopt, $disk) = @$ref; +   +  		my $fh = $this->cmd('smartctl', { '$dev' => $dev, '$diskopt' => $diskopt => '$disk' => $disk }); +  		while (<$fh>) { +  			chomp; +   +  			# SMART Health Status: HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE [asc=5d, ascq=10] +  			if (my($s, $sc) = /SMART Health Status: (.*?)(\s*\[asc=\w+, ascq=\w+\])?$/) { +  				# use shorter output, message that hpacucli would use +  				if ($s eq 'HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE') { +  					$s = 'Predictive Failure'; +  				} +   +  				if ($s eq 'Predictive Failure') { +  					$this->warning; +  				} elsif ($s !~ '^OK') { +  					$this->critical; +  				} +  				push(@{$status{$s}}, $dev.'#'.$disk); +  			} +  		} +  		close($fh); +  	} +   +  	return unless %status; +   +  	$this->ok->message($this->join_status(\%status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::ssacli; +   +  # This plugin extends hpacucli plugin, +  # with the only difference that different program name will be used. +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli'; +  use strict; +  use warnings; +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/tw_cli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI'; +  package App::Monitoring::Plugin::CheckRaid::Plugins::tw_cli; +   +  # tw_cli(8) is a Command Line Interface Storage Management Software for +  # AMCC/3ware ATA RAID Controller(s). +  # Owned by LSI currently: https://en.wikipedia.org/wiki/3ware +  # +  # http://www.cyberciti.biz/files/tw_cli.8.html +   +  use base 'App::Monitoring::Plugin::CheckRaid::Plugin'; +  # not yet, see: +  # https://github.com/glensc/nagios-plugin-check_raid/pull/131#issuecomment-189957806 +  #use Date::Parse qw(strptime); +  #use DateTime; +  use strict; +  use warnings; +   +  sub program_names { +  	qw(tw_cli-9xxx tw_cli tw-cli); +  } +   +  sub commands { +  	{ +  		'show' => ['-|', '@CMD', 'show'], # This is 'info' output AND enclosure summary +  		'unitstatus' => ['-|', '@CMD', 'info', '$controller', 'unitstatus'], +  		'drivestatus' => ['-|', '@CMD', 'info', '$controller', 'drivestatus'], +  		'bbustatus' => ['-|', '@CMD', 'info', '$controller', 'bbustatus'], +  		'enc_show_all' => ['-|', '@CMD', '$encid', 'show all'], +  	} +  } +   +  sub sudo { +  	my ($this, $deep) = @_; +  	# quick check when running check +  	return 1 unless $deep; +   +  	my $cmd = $this->{program}; +  	( +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd info", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd info *", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd show", +  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * show all", +  	); +  } +   +  sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; +   +  sub to_i { +  	my $i = shift; +  	return $i if $i !~ /^\d+$/; +  	return int($i); +  } +   +  sub parse { +  	my $this = shift; +   +  	my (%c); +  	# scan controllers +  	my ($sect_ctl, $sect_enc) = 0; +  	my $fh = $this->cmd('show'); +  	while (<$fh>) { +  		# Section break +  		if(/^\s*$/) { ($sect_ctl,$sect_enc) = (0,0); next; }; +  		# header line +  		if(/^-+$/) { next; }; +  		# section headers: Controller +  		# Ctl   Model  Ports     Drives  Units   NotOpt  RRate VRate  BBU +  		# Ctl   Model  (V)Ports  Drives  Units   NotOpt  RRate VRate  BBU +  		if (/^Ctl.*Model.*Rate/) { $sect_ctl = 1; next; }; +  		# section headers: Enclosure +  		#  Encl    Slots   Drives  Fans   TSUnits   Ctls +  		#  Encl    Slots   Drives  Fans   TSUnits  PSUnits +  		#  Enclosure     Slots  Drives  Fans  TSUnits  PSUnits  Alarms +  		if (/^Encl.*Drive/) { $sect_enc = 1; next; }; +   +  		# controller section +  		if ($sect_ctl and my($ctl, $model, $ports, $drives, $units, $notopt, $rrate, $vrate, $bbu) = m{^ +  			(c\d+)\s+   # Controller +  			(\S+)\s+    # Model +  			(\d+)\s+    # (V)Ports +  			(\d+)\s+    # Drives +  			(\d+)\s+    # Units +  			(\d+)\s+    # NotOpt: Not Optional +  						# Not Optimal refers to any state except OK and VERIFYING. +  						# Other states include INITIALIZING, INIT-PAUSED, +  						# REBUILDING, REBUILD-PAUSED, DEGRADED, MIGRATING, +  						# MIGRATE-PAUSED, RECOVERY, INOPERABLE, and UNKNOWN. +  			(\d+)\s+    # RRate: Rebuild Rate +  			(\d+|-)\s+  # VRate: Verify Rate +  			(\S+|-)?    # BBU +  		}x) { +  			$c{$ctl} = { +  				model => $model, +  				ports => int($ports), +  				drives => int($drives), +  				units => int($units), +  				optimal => int(!$notopt), +  				rrate => int($rrate), +  				vrate => to_i($vrate), +  				bbu => $bbu, +  			}; +  		} +  		# enclosure section +  		if ($sect_enc and my($enc, $slots, $drives, $fans, $tsunits, $psunits, $alarms) = m{^ +  			((?:/c\d+)?/e\d+)\s+   # Controller, Enclosure +  			  # 9650SE reports enclosures as /eX +  			  # 9690SA+ report enclosures as /cX/eX +  			(\d+)\s+    # Slots +  			(\d+)\s+    # Drives +  			(\d+)\s+    # Fans +  			(\d+)\s+    # TSUnits - Temp Sensor +  			(\d+)?\s+    # PSUnits - Power Supply, not always present! +  			(\d+)?\s+    # Controller OR Alarms, not always present! +  		}x) { +  			# This will be filled in later by the enclosure pass +  			$c{$enc} = {}; +  		} +  	} +  	close $fh; +   +  	# no controllers? skip early +  	return unless %c; +   +  	for my $c (grep /^\/?c\d+$/, keys %c) { +  		# get each unit on controllers +  		$fh = $this->cmd('unitstatus', { '$controller' => $c }); +  		while (<$fh>) { +  			if (my($u, $type, $status, $p_rebuild, $p_vim, $strip, $size, $cache, $avrify) = m{^ +  				(u\d+)\s+ # Unit +  				(\S+)\s+  # UnitType +  				(\S+)\s+  # Status +  				(\S+)\s+  # %RCmpl: The %RCompl reports the percent completion +  						  # of the unit's Rebuild, if this task is in progress. +  				(\S+)\s+  # %V/I/M: The %V/I/M reports the percent completion +  						  # of the unit's Verify, Initialize, or Migrate, +  						  # if one of these are in progress. +  				(\S+)\s+  # Strip +  				(\S+)\s+  # Size(GB) +  				(\S+)\s+  # Cache +  				(\S+)     # AVrify +  			}x) { +  				$c{$c}{unitstatus}{$u} = { +  					type => $type, +  					status => $status, +  					rebuild_percent => $p_rebuild, +  					vim_percent => $p_vim, +  					strip => $strip, +  					size => $size, +  					cache => $cache, +  					avrify => $avrify, +  				}; +  				next; +  			} +   +  			if (m{^u\d+}) { +  				$this->unknown; +  				warn "unparsed: [$_]"; +  			} +  		} +  		close $fh; +   +  		# get individual disk status +  		$fh = $this->cmd('drivestatus', { '$controller' => $c }); +  		# common regexp +  		my $r = qr{^ +  			(p\d+)\s+       # Port +  			(\S+)\s+        # Status +  			(\S+)\s+        # Unit +  			([\d.]+\s[TG]B|-)\s+ # Size +  		}x; +   +  		while (<$fh>) { +  			# skip empty line +  			next if /^$/; +   +  			# Detect version +  			if (/^Port/) { +  				# <=9.5.1: Blocks Serial +  				$r .= qr{ +  					(\S+)\s+  # Blocks +  					(.+)      # Serial +  				}x; +  				next; +  			} elsif (/^VPort/) { +  				# >=9.5.2: Type Phy Encl-Slot Model +  				$r .= qr{ +  					(\S+)\s+ # Type +  					(\S+)\s+ # Phy +  					(\S+)\s+ # Encl-Slot +  					(.+)     # Model +  				}x; +  				next; +  			} +   +  			if (my($port, $status, $unit, $size, @rest) = ($_ =~ $r)) { +  				# do not report disks not present +  				# tw_cli 9.5.2 and above do not list these at all +  				next if $status eq 'NOT-PRESENT'; +  				my %p; +   +  				if (@rest <= 2) { +  					my ($blocks, $serial) = @rest; +  					%p = ( +  						blocks => to_i($blocks), +  						serial => trim($serial), +  					); +  				} else { +  					my ($type, $phy, $encl, $model) = @rest; +  					%p = ( +  						type => $type, +  						phy => to_i($phy), +  						encl => $encl, +  						model => $model, +  					); +  				} +   +  				$c{$c}{drivestatus}{$port} = { +  					status => $status, +  					unit => $unit, +  					size => $size, +  					%p, +  				}; +   +  				next; +  			} +   +  			if (m{^p\d+}) { +  				$this->unknown; +  				warn "unparsed: [$_]"; +  			} +  		} +  		close $fh; +   +  		# get BBU status +  		$fh = $this->cmd('bbustatus', { '$controller' => $c }); +  		while (<$fh>) { +  			next if /^$/; +  			next if /^-{10,}$/; +  			if (my($bbu, $onlinestate, $bbuready, $status, $volt, $temp, $hours, $lastcaptest) = m{^ +  				(bbu\d*)\s+     # BBU, possibly numbered (RARE) +  				(\S+)\s+        # OnlineState +  				(\S+)\s+        # BBUReady +  				(\S+)\s+        # Status +  				(\S+)\s+        # Volt +  				(\S+)\s+        # Temp +  				(\d+)\s+        # Hours +  				(\S+)\s+        # LastCapTest +  			}x) { +  				$c{$c}{bbustatus}{$bbu} = { +  					OnlineState => $onlinestate, +  					BBUReady => $bbuready, +  					Status => $status, +  					Volt => $volt, +  					Temp => $temp, +  					Hours => $hours, +  					LastCapTest => $lastcaptest, +  				}; +  				next; +  			} +  			if (m{^\S+\+}) { +  				$this->unknown; +  				warn "unparsed: [$_]"; +  			} +  		} +  		close $fh; +  	} +   +  	# Do enclosures now, which might NOT be attached the controllers +  	# WARNING: This data section has not always been consistent over versions of tw_cli. +  	# You should try to use the newest version of the driver, as it deliberately uses the newer style of output +  	# rather than the output for the tw_cli versions released with 9550SX/9590SE/9650SE +  	for my $encid (grep /\/e\d+$/, keys %c) { +  		$fh = $this->cmd('enc_show_all', { '$encid' => $encid }); +  		# Variable names chose to be 'sect_XXX' explicitly. +  		# This says what section we are in right now +  		my ($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0); +  		# This says what section we have seen, it gets reset at the start of each enclosure block; +  		my ($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0); +  		while (<$fh>) { +  			# Skip the header break lines +  			next if /^-+$/; +  			# and the partial indented header that is ABOVE the fan header +  			next if /^\s+-+Speed-+\s*$/; +  			# If the line is blank, reset our section headers +  			if(/^\s*$/){ +  				($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0); +  				# If we have SEEN all of the sections, also reset the seen markers +  				# This is needed when the output contains multiple enclosures +  				if($sect_enc and $sect_fan and $sect_tmp and $sect_psu and $sect_slt and $sect_alm) { +  					($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0); +  				} +  				next; +  			} +  			if (/^Encl.*Status/)        { $seen_enc = $sect_enc = 1; next; } +  			if (/^Fan.*Status/)         { $seen_fan = $sect_fan = 1; next; } +  			if (/^TempSensor.*Status/)  { $seen_tmp = $sect_tmp = 1; next; } +  			if (/^PowerSupply.*Status/) { $seen_psu = $sect_psu = 1; next; } +  			if (/^Slot.*Status/)        { $seen_slt = $sect_slt = 1; next; } +  			if (/^Alarm.*Status/)       { $seen_alm = $sect_alm = 1; next; } +  			# ------ Start of new enclosure +  			if ($sect_enc and my($encl, $encl_status) = m{^ +  				((?:/c\d+)?/e\d+)\s+   # Controller, Enclosure +  				(\S+)\s+   # Status +  			}x) { +  				# This is a special case for the test environment, as it is +  				# hard to feed MULTI command inputs into the mock. +  				if($ENV{'HARNESS_ACTIVE'} and $encl ne $encid) { +  					$encid = $encl; +  				} +  				$c{$encid} = { +  					encl   => $encl, # Dupe of $encid to verify +  					status => $encl_status, +  					# This is the top-level enclosure object +  					fans => {}, +  					tempsensor => {}, +  					powersupply => {}, +  					slot => {}, +  					alarm => {}, +  				}; +  			} +  			# ------ Fans +  			elsif ($sect_fan and my($fan, $fan_status, $fan_state, $fan_step, $fan_rpm, $fan_identify) = m{^ +  				(fan\S+)\s+   # Fan +  				(\S+)\s+   # Status +  				(\S+)\s+   # State +  				(\S+)\s+   # Step +  				(\d+|N/A)\s+   # RPM +  				(\S+)\s+   # Identify +  			}x) { +  				$c{$encid}{fans}{$fan} = { +  					status => $fan_status, +  					state => $fan_state, +  					step => $fan_step, +  					rpm => $fan_rpm, +  					identify => $fan_identify, +  				}; +  				next; +  			} +  			# ------ TempSensor +  			elsif ($sect_tmp and my($tmp, $tmp_status, $tmp_temperature, $tmp_identify) = m{^ +  				(temp\S+)\s+   # TempSensor +  				(\S+)\s+   # Status +  				(\S+)\s+   # Temperature +  				(\S+)\s+   # Identify +  			}x) { +  				$c{$encid}{tempsensor}{$tmp} = { +  					status => $tmp_status, +  					temperature => $tmp_temperature, +  					identify => $tmp_identify, +  				}; +  				next; +  			} +  			# ------ PowerSupply +  			elsif ($sect_psu and my($psu, $psu_status, $psu_state, $psu_voltage, $psu_current, $psu_identify) = m{^ +  				((?:pw|psu)\S+)\s+   # PowerSupply +  				(\S+)\s+   # Status +  				(\S+)\s+   # State +  				(\S+)\s+   # Voltage +  				(\S+)\s+   # Current +  				(\S+)\s+   # Identify +  			}x) { +  				$c{$encid}{powersupply}{$psu} = { +  					status => $psu_status, +  					state => $psu_state, +  					voltage => $psu_voltage, +  					current => $psu_current, +  					identify => $psu_identify, +  				}; +  				next; +  			} +  			# ------ Slot +  			elsif ($sect_slt and my($slt, $slt_status, $slt_vport, $slt_identify) = m{^ +  				(slo?t\S+)\s+   # Slot +  				(\S+)\s+   # Status +  				(\S+)\s+   # (V)Port +  				(\S+)\s+   # Identify +  			}x) { +  				$c{$encid}{slot}{$slt} = { +  					status => $slt_status, +  					vport => $slt_vport, +  					identify => $slt_identify, +  				}; +  				next; +  			} +  			# ------ Alarm +  			elsif ($sect_alm and my($alm, $alm_status, $alm_state, $alm_audibility) = m{^ +  				(alm\S+)\s+   # Alarm +  				(\S+)\s+   # Status +  				(\S+)\s+   # State +  				(\S+)\s+   # Audibility +  			}x) { +  				$c{$encid}{alarm}{$alm} = { +  					status => $alm_status, +  					state => $alm_state, +  					audibility => $alm_audibility, +  				}; +  				next; +  			} +  			# ---- End of known data +  			elsif (m{^\S+\+}) { +  				$this->unknown; +  				warn "unparsed: [$_]"; +  			} +   +  		} +  		close $fh; +  	} +   +  	return \%c; +  } +   +  sub check { +  	my $this = shift; +   +  	# status messages pushed here +  	my @status; +   +  	my $c = $this->parse; +  	if (!$c) { +  		$this->unknown; +  		$this->message("No Adapters were found on this machine"); +  	} +   +  	# process each controller +  	for my $cid (sort grep !/e\d+/, keys %$c) { +  		my $c = $c->{$cid}; +  		my @cstatus; +   +  		for my $uid (sort keys %{$c->{unitstatus}}) { +  			my $u = $c->{unitstatus}->{$uid}; +  			my $s = $u->{status}; +   +  			if ($s =~ /INITIALIZING|MIGRATING/) { +  				$this->warning; +  				$s .= " $u->{vim_percent}"; +   +  			} elsif ($s eq 'VERIFYING') { +  				$this->check_status; +  				$s .= " $u->{vim_percent}"; +   +  			} elsif ($s eq 'REBUILDING') { +  				$this->resync; +  				$s .= " $u->{rebuild_percent}"; +   +  			} elsif ($s eq 'DEGRADED') { +  				$this->critical; +   +  			} elsif ($s ne 'OK') { +  				$this->critical; +   +  			} +   +  			my @ustatus = $s; +   +  			# report cache, no checking +  			if ($u->{cache} && $u->{cache} ne '-') { +  				push(@ustatus, "Cache:$u->{cache}"); +  			} +   +  			push(@status, "$cid($c->{model}): $uid($u->{type}): ".join(', ', @ustatus)); +  		} +   +  		# check individual disk status +  		my %ds; +  		foreach my $p (sort { $a cmp $b } keys %{$c->{drivestatus}}) { +  			my $d = $c->{drivestatus}->{$p}; +  			my $ds = $d->{status}; +  			if ($ds eq 'VERIFYING') { +  				$this->check_status; +  			} elsif ($ds ne 'OK') { +  				$this->critical; +  			} +   +  			if ($d->{unit} eq '-') { +  				$ds = 'SPARE'; +  			} +   +  			push(@{$ds{$ds}}, $p); +  		} +  		push(@status, "Drives($c->{drives}): ".$this->join_status(\%ds)) if %ds; +   +  		# check BBU, but be prepared that BBU status might not report anything +  		if ($this->{options}{bbu_monitoring} && $c->{bbu} && $c->{bbu} ne '-') { +  			# On old controllers, bbustatus did not exist; and the only BBU status +  			# you got was on the controller listing. +  			if(scalar(keys %{$c->{bbustatus}}) < 1) { +  				$this->critical if $c->{bbu} ne 'OK'; +  				push(@status, "BBU: $c->{bbu}"); +  			} else { +  				foreach my $bbuid (sort { $a cmp $b } keys %{$c->{bbustatus}}) { +  					my $bat = $c->{bbustatus}->{$bbuid}; +  					my $bs = $bat->{Status}; # We might override this later +  					my @batmsg; +  					if($bs eq 'Testing' or $bs eq 'Charging') { +  						$this->bbulearn; +  					} elsif($bs eq 'WeakBat') { +  						# Time to replace your battery +  						$this->warning; +  					} elsif($bs ne 'OK') { +  						$this->critical; +  					} +  					# We do NOT check BBUReady, as it doesn't private granular +  					# info. +  					# Check OnlineState flag as well +  					# A battery can be GOOD, but disabled; this is only reflected in OnlineState. +  					if($bat->{OnlineState} ne 'On') { +  						push @batmsg, 'OnlineStatus='.$bat->{OnlineState}; +  						$this->critical; +  					} +  					# Check voltage & temps +  					push @batmsg, 'Volt='.$bat->{Volt}; +  					push @batmsg, 'Temp='.$bat->{Temp}; +  					if ($bat->{Volt} =~ /^(LOW|HIGH)$/) { +  						$this->critical; +  					} elsif ($bat->{Volt} =~ /^(LOW|HIGH)$/) { +  						$this->warning; +  					} +  					if ($bat->{Temp} =~ /^(LOW|HIGH)$/) { +  						$this->critical; +  					} elsif ($bat->{Temp} =~ /^(LOW|HIGH)$/) { +  						$this->warning; +  					} +  					# Check runtime estimate +  					# Warn if too low +  					my $bbulearn = ''; +  					if ($bat->{Hours} ne '-' and int($bat->{Hours}) <= 1) { +  						# TODO: make this configurable before going live +  						#$this->warning; +  						$this->bbulearn; +  						$bbulearn = '/LEARN'; +  					} +  					push @batmsg, 'Hours='.$bat->{Hours}; +   +  					# Check date of last capacity test +  					if ($bat->{LastCapTest} eq 'xx-xxx-xxxx') { +  						$this->bbulearn; +  						$bbulearn = '/LEARN'; +  					} elsif ($bat->{LastCapTest} ne '-') { +  						# TODO: is the short name of month localized by tw_cli? +  						#my ($mday, $mon, $year) = (strptime($bat->{LastCapTest}, '%d-%b-%Y'))[3,4,5]; +  						#my $lastcaptest_epoch = DateTime->new(year => $year, month => $mon, day => $mday, hour => 0, minute => 0, second => 0); +  						#my $present_time = time; +  						## TODO: this value should be configurable before going live, also need to mock system date for testing +  						#if (($present_time-$lastcaptest_epoch) > 86400*365) { +  						#	$this->bbulearn; +  						#} +  					} +  					push @batmsg, 'LastCapTest='.$bat->{LastCapTest}; +  					my $msg = join(',', @batmsg); +  					my $bbustatus = $bs.$bbulearn; +  					$bbustatus = "$bbuid=$bs" if $bbuid ne 'bbu'; # If we have multiple BBU, specify which one +  					push(@status, "BBU: $bbustatus($msg)"); +  				} +  			} +  		} +  	} +  	# process each enclosure +  	for my $eid (sort grep /\/e\d+/, keys %$c) { +  		my $e = $c->{$eid}; +  		# If the enclosure command returned nothing, we have no status to +  		# report. +  		next unless defined($e->{status}); +   +  		# Something is wrong, but we are not sure what yet. +  		$this->warning unless $e->{status} eq 'OK'; +  		my @estatus; +  		for my $fan_id (sort keys %{$e->{fans}}) { +  			my $f = $e->{fans}->{$fan_id}; +  			my $s = $f->{status}; +  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE'; +  			$this->warning if $s ne 'OK'; +  			push(@estatus, "$fan_id=$s($f->{rpm})"); +  		} +  		for my $tmp_id (sort keys %{$e->{tempsensor}}) { +  			my $t = $e->{tempsensor}->{$tmp_id}; +  			my $s = $t->{status}; +  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE'; +  			$this->warning if $s ne 'OK'; +  			$t->{temperature} =~ s/\(\d+F\)//; # get rid of extra units +  			push(@estatus, "$tmp_id=$s($t->{temperature})"); +  		} +  		for my $psu_id (sort keys %{$e->{powersupply}}) { +  			my $t = $e->{powersupply}->{$psu_id}; +  			my $s = $t->{status}; +  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE'; +  			$this->warning if $s ne 'OK'; +  			push(@estatus, "$psu_id=$s(status=$t->{state},voltage=$t->{voltage},current=$t->{current})"); +  		} +  		for my $slot_id (sort keys %{$e->{slot}}) { +  			my $t = $e->{slot}->{$slot_id}; +  			my $s = $t->{status}; +  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE'; +  			$this->warning if $s ne 'OK'; +  			push(@estatus, "$slot_id=$s"); +  		} +  		for my $alarm_id (sort keys %{$e->{alarm}}) { +  			my $t = $e->{alarm}->{$alarm_id}; +  			my $s = $t->{status}; +  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE'; +  			$this->warning if $s ne 'OK'; +  			push(@estatus, "$alarm_id=$s(State=$t->{state},Audibility=$t->{audibility})"); +  		} +  		#warn join("\n", @estatus); +  		push(@status, "Enclosure: $eid(".join(',', @estatus).")"); +  	} +   +  	return unless @status; +   +  	$this->ok->message(join(', ', @status)); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/SerialLine.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE'; +  package App::Monitoring::Plugin::CheckRaid::SerialLine; +   +  # Package dealing with connecting to serial line and handling UUCP style locks. +   +  use Carp; +  use strict; +  use warnings; +   +  sub new { +  	my $self = shift; +  	my $class = ref($self) || $self; +  	my $device = shift; +   +  	my $this = { +  		lockdir => "/var/lock", +   +  		@_, +   +  		lockfile => undef, +  		device => $device, +  		fh => undef, +  	}; +   +  	bless($this, $class); +  } +   +  sub lock { +  	my $self = shift; +  	# create lock in style: /var/lock/LCK..ttyS0 +  	my $device = shift; +  	my ($lockfile) = $self->{device} =~ m#/dev/(.+)#; +  	$lockfile = "$self->{lockdir}/LCK..$lockfile"; +  	if (-e $lockfile) { +  		return 0; +  	} +  	open(my $fh, '>', $lockfile) || croak "Can't create lock: $lockfile\n"; +  	print $fh $$; +  	close($fh); +   +  	$self->{lockfile} = $lockfile; +  } +   +  sub open { +  	my $self = shift; +   +  	$self->lock or return; +   +  	# open the device +  	open(my $fh, '+>', $self->{device}) || croak "Couldn't open $self->{device}, $!\n"; +   +  	$self->{fh} = $fh; +  } +   +  sub close { +  	my $self = shift; +  	if ($self->{fh}) { +  		close($self->{fh}); +  		undef($self->{fh}); +  		unlink $self->{lockfile} or carp $!; +  	} +  } +   +  sub DESTROY { +  	my $self = shift; +  	$self->close(); +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Sudoers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS'; +  package App::Monitoring::Plugin::CheckRaid::Sudoers; +   +  use App::Monitoring::Plugin::CheckRaid::Utils; +  use warnings; +  use strict; +   +  use Exporter 'import'; +   +  our @EXPORT = qw(sudoers); +  our @EXPORT_OK = @EXPORT; +   +  # update sudoers file +  # +  # if sudoers config has "#includedir" directive, add file to that dir +  # otherwise update main sudoers file +  # @returns true if file was updated +  sub sudoers { +  	my $dry_run = shift; +  	my @plugins = @_; +   +  	# build values to be added +  	# go over all active plugins +  	my @sudo; +  	foreach my $plugin (@plugins) { +  		# collect sudo rules +  		my @rules = $plugin->sudo(1) or next; +   +  		push(@sudo, @rules); +  	} +   +  	unless (@sudo) { +  		warn "Your configuration does not need to use sudo, sudoers not updated\n"; +  		return 0; +  	} +   +  	my @rules = join "\n", ( +  		"", +  		# setup alias, so we could easily remove these later by matching lines with 'CHECK_RAID' +  		# also this avoids installing ourselves twice. +  		"# Lines matching CHECK_RAID added by $0 -S on ". scalar localtime, +  		"User_Alias CHECK_RAID=nagios, icinga, sensu", +  		"Defaults:CHECK_RAID !requiretty", +   +  		# actual rules from plugins +  		join("\n", @sudo), +  		"", +  	); +   +  	if ($dry_run) { +  		warn "Content to be inserted to sudo rules:\n"; +  		warn "--- sudoers ---\n"; +  		print @rules; +  		warn "--- sudoers ---\n"; +  		return 0; +  	} +   +  	my $sudoers = find_file('/usr/local/etc/sudoers', '/etc/sudoers'); +  	my $visudo = which('visudo'); +   +  	die "Unable to find sudoers file.\n" unless -f $sudoers; +  	die "Unable to write to sudoers file '$sudoers'.\n" unless -w $sudoers; +  	die "visudo program not found\n" unless -x $visudo; +   +  	# parse sudoers file for "#includedir" directive +  	my $sudodir = parse_sudoers_includedir($sudoers); +  	if ($sudodir) { +  		# sudo will read each file in /etc/sudoers.d, skipping file names that +  		# end in ~ or contain a . character to avoid causing problems with +  		# package manager or editor temporary/backup files +  		$sudoers = "$sudodir/check_raid"; +  	} +   +  	warn "Updating file $sudoers\n"; +   +  	# NOTE: secure as visudo itself: /etc is root owned +  	my $new = $sudoers.".new.".$$; +   +  	# setup to have sane perm for new sudoers file +  	umask(0227); +   +  	open my $fh, '>', $new or die $!; +   +  	# insert old sudoers +  	if (!$sudodir) { +  		open my $old, '<', $sudoers or die $!; +  		while (<$old>) { +  			print $fh $_; +  		} +  		close $old or die $!; +  	} +   +  	# insert the rules +  	print $fh @rules; +  	close $fh; +   +  	# validate sudoers +  	system($visudo, '-c', '-f', $new) == 0 or unlink($new),exit $? >> 8; +   +  	# check if they differ +  	if (filediff($sudoers, $new)) { +  		# use the new file +  		rename($new, $sudoers) or die $!; +  		warn "$sudoers file updated.\n"; +  		return 1; +  	} +   +  	warn "$sudoers file not changed.\n"; +  	unlink($new); +  	return 0; +  } +   +  # return first "#includedir" directive from $sudoers file +  sub parse_sudoers_includedir { +  	my ($sudoers) = @_; +   +  	open my $fh, '<', $sudoers or die "Can't open: $sudoers: $!"; +  	while (<$fh>) { +  		if (my ($dir) = /^#includedir\s+(.+)$/) { +  			return $dir; +  		} +  	} +  	close $fh or die $!; +   +  	return undef; +  } +   +  # return FALSE if files are identical +  # return TRUE if files are different +  # return TRUE if any of the files is missing +  sub filediff { +  	my ($file1, $file2) = @_; +   +  	# return TRUE if neither of them exist +  	return 1 unless -f $file1; +  	return 1 unless -f $file2; +   +  	my $f1 = cat($file1); +  	my $f2 = cat($file2); +   +  	# wipe comments +  	$f1 =~ s/^#.+$//m; +  	$f2 =~ s/^#.+$//m; +   +  	# return TRUE if they differ +  	return $f1 ne $f2; +  } +   +  # get contents of a file +  sub cat { +  	my ($file) = @_; +  	open(my $fh, '<', $file) or die "Can't open $file: $!"; +  	local $/ = undef; +  	local $_ = <$fh>; +  	close($fh) or die $!; +   +  	return $_; +  } +   +  # find first existing file from list of file paths +  sub find_file { +  	for my $file (@_) { +  		return $file if -f $file; +  	} +  	return undef; +  } +APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS + +$fatpacked{"App/Monitoring/Plugin/CheckRaid/Utils.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_UTILS'; +  package App::Monitoring::Plugin::CheckRaid::Utils; +   +  use warnings; +  use strict; +  use Exporter 'import'; +   +  our @EXPORT = qw(which find_sudo); +  our @EXPORT_OK = @EXPORT; +   +  # registered plugins +  our @plugins; +   +  # devices to ignore +  our @ignore; +   +  # debug level +  our $debug = 0; +   +  # paths for which() +  our @paths = split /:/, $ENV{'PATH'}; +  unshift(@paths, qw(/usr/local/nrpe /usr/local/bin /sbin /usr/sbin /bin /usr/sbin /opt/bin /opt/MegaRAID/MegaCli /usr/StorMan)); +   +  # lookup program from list of possible filenames +  # search is performed from $PATH plus additional hardcoded @paths +  # NOTE: we do not check for execute bit as it may fail for non-root. #104 +  sub which { +  	for my $prog (@_) { +  		for my $path (@paths) { +  			return "$path/$prog" if -f "$path/$prog"; +  		} +  	} +  	return undef; +  } +   +  our @sudo; +  sub find_sudo { +  	# no sudo needed if already root +  	return [] unless $>; +   +  	# detect once +  	return \@sudo if @sudo; +   +  	my $sudo = which('sudo') or die "Can't find sudo"; +  	push(@sudo, $sudo); +   +  	# detect if sudo supports -A, issue #88 +  	use IPC::Open3; +  	my $fh; +  	my @cmd = ($sudo, '-h'); +  	my $pid = open3(undef, $fh, undef, @cmd) or die "Can't run 'sudo -h': $!"; +  	local $/ = undef; +  	local $_ = <$fh>; +  	close($fh) or die $!; +  	# prefer -n to skip password prompt +  	push(@sudo, '-n') if /-n/; +  	# ..if not supported, add -A as well +  	push(@sudo, '-A') if /-A/; +   +  	return \@sudo; +  } +   +  1; +APP_MONITORING_PLUGIN_CHECKRAID_UTILS + +$fatpacked{"Class/Accessor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR'; +  package Class::Accessor;require 5.00502;use strict;$Class::Accessor::VERSION='0.51';sub new {return bless defined $_[1]? {%{$_[1]}}: {},ref $_[0]|| $_[0]}sub mk_accessors {my($self,@fields)=@_;$self->_mk_accessors('rw',@fields)}if (eval {require Sub::Name}){Sub::Name->import}{no strict 'refs';sub import {my ($class,@what)=@_;my$caller=caller;for (@what){if (/^(?:antlers|moose-?like)$/i){*{"${caller}::has"}=sub {my ($f,%args)=@_;$caller->_mk_accessors(($args{is}||"rw"),$f)};*{"${caller}::extends"}=sub {@{"${caller}::ISA"}=@_;unless (grep $_->can("_mk_accessors"),@_){push @{"${caller}::ISA"},$class}};&{"${caller}::extends"}(@{"${caller}::ISA"})}}}sub follow_best_practice {my($self)=@_;my$class=ref$self || $self;*{"${class}::accessor_name_for"}=\&best_practice_accessor_name_for;*{"${class}::mutator_name_for"}=\&best_practice_mutator_name_for}sub _mk_accessors {my($self,$access,@fields)=@_;my$class=ref$self || $self;my$ra=$access eq 'rw' || $access eq 'ro';my$wa=$access eq 'rw' || $access eq 'wo';for my$field (@fields){my$accessor_name=$self->accessor_name_for($field);my$mutator_name=$self->mutator_name_for($field);if($accessor_name eq 'DESTROY' or $mutator_name eq 'DESTROY'){$self->_carp("Having a data accessor named DESTROY  in '$class' is unwise.")}if ($accessor_name eq $mutator_name){my$accessor;if ($ra && $wa){$accessor=$self->make_accessor($field)}elsif ($ra){$accessor=$self->make_ro_accessor($field)}else {$accessor=$self->make_wo_accessor($field)}my$fullname="${class}::$accessor_name";my$subnamed=0;unless (defined &{$fullname}){subname($fullname,$accessor)if defined&subname;$subnamed=1;*{$fullname}=$accessor}if ($accessor_name eq $field){my$alias="${class}::_${field}_accessor";subname($alias,$accessor)if defined&subname and not $subnamed;*{$alias}=$accessor unless defined &{$alias}}}else {my$fullaccname="${class}::$accessor_name";my$fullmutname="${class}::$mutator_name";if ($ra and not defined &{$fullaccname}){my$accessor=$self->make_ro_accessor($field);subname($fullaccname,$accessor)if defined&subname;*{$fullaccname}=$accessor}if ($wa and not defined &{$fullmutname}){my$mutator=$self->make_wo_accessor($field);subname($fullmutname,$mutator)if defined&subname;*{$fullmutname}=$mutator}}}}}sub mk_ro_accessors {my($self,@fields)=@_;$self->_mk_accessors('ro',@fields)}sub mk_wo_accessors {my($self,@fields)=@_;$self->_mk_accessors('wo',@fields)}sub best_practice_accessor_name_for {my ($class,$field)=@_;return "get_$field"}sub best_practice_mutator_name_for {my ($class,$field)=@_;return "set_$field"}sub accessor_name_for {my ($class,$field)=@_;return$field}sub mutator_name_for {my ($class,$field)=@_;return$field}sub set {my($self,$key)=splice(@_,0,2);if(@_==1){$self->{$key}=$_[0]}elsif(@_ > 1){$self->{$key}=[@_]}else {$self->_croak("Wrong number of arguments received")}}sub get {my$self=shift;if(@_==1){return$self->{$_[0]}}elsif(@_ > 1){return @{$self}{@_}}else {$self->_croak("Wrong number of arguments received")}}sub make_accessor {my ($class,$field)=@_;return sub {my$self=shift;if(@_){return$self->set($field,@_)}else {return$self->get($field)}}}sub make_ro_accessor {my($class,$field)=@_;return sub {my$self=shift;if (@_){my$caller=caller;$self->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}else {return$self->get($field)}}}sub make_wo_accessor {my($class,$field)=@_;return sub {my$self=shift;unless (@_){my$caller=caller;$self->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return$self->set($field,@_)}}}use Carp ();sub _carp {my ($self,$msg)=@_;Carp::carp($msg || $self);return}sub _croak {my ($self,$msg)=@_;Carp::croak($msg || $self);return}1; +CLASS_ACCESSOR + +$fatpacked{"Class/Accessor/Fast.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FAST'; +  package Class::Accessor::Fast;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Fast::VERSION='0.51';sub make_accessor {my ($class,$field)=@_;eval sprintf q{ +          sub { +              return $_[0]{%s} if scalar(@_) == 1; +              return $_[0]{%s}  = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]]; +          } +      },map {perlstring($_)}$field,$field}sub make_ro_accessor {my($class,$field)=@_;eval sprintf q{ +          sub { +              return $_[0]{%s} if @_ == 1; +              my $caller = caller; +              $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s); +          } +      },map {perlstring($_)}$field,$field,$class}sub make_wo_accessor {my($class,$field)=@_;eval sprintf q{ +          sub { +              if (@_ == 1) { +                  my $caller = caller; +                  $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s); +              } +              else { +                  return $_[0]{%s} = $_[1] if @_ == 2; +                  return (shift)->{%s} = \@_; +              } +          } +      },map {perlstring($_)}$field,$class,$field,$field}1; +CLASS_ACCESSOR_FAST + +$fatpacked{"Class/Accessor/Faster.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FASTER'; +  package Class::Accessor::Faster;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Faster::VERSION='0.51';my%slot;sub _slot {my($class,$field)=@_;my$n=$slot{$class}->{$field};return$n if defined$n;$n=keys %{$slot{$class}};$slot{$class}->{$field}=$n;return$n}sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;my$self=bless [],$class;$fields={}unless defined$fields;for my$k (keys %$fields){my$n=$class->_slot($k);$self->[$n]=$fields->{$k}}return$self}sub make_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{ +          sub { +              return $_[0][%d] if scalar(@_) == 1; +              return $_[0][%d]  = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]]; +          } +      },$n,$n}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{ +          sub { +              return $_[0][%d] if @_ == 1; +              my $caller = caller; +              $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s); +          } +      },$n,map(perlstring($_),$field,$class)}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{ +          sub { +              if (@_ == 1) { +                  my $caller = caller; +                  $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s); +              } +              else { +                  return $_[0][%d] = $_[1] if @_ == 2; +                  return (shift)->[%d] = \@_; +              } +          } +      },map(perlstring($_),$field,$class),$n,$n}1; +CLASS_ACCESSOR_FASTER + +$fatpacked{"Config/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CONFIG_TINY'; +  package Config::Tiny;use strict;our$VERSION='2.23';BEGIN {require 5.008001;$Config::Tiny::errstr=''}sub new {return bless {},shift}sub read {my($class)=ref $_[0]? ref shift : shift;my($file,$encoding)=@_;return$class -> _error('No file name provided')if (!defined$file || ($file eq ''));$encoding=$encoding ? "<:$encoding" : '<';local $/=undef;open(CFG,$encoding,$file)or return$class -> _error("Failed to open file '$file' for reading: $!");my$contents=<CFG>;close(CFG);return$class -> _error("Reading from '$file' returned undef")if (!defined$contents);return$class -> read_string($contents)}sub read_string {my($class)=ref $_[0]? ref shift : shift;my($self)=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;s/\s\;\s.+$//g;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){$self->{$ns}->{$1}=$2;next}return$self -> _error("Syntax error at line $counter: '$_'")}return$self}sub write {my($self)=shift;my($file,$encoding)=@_;return$self -> _error('No file name provided')if (!defined$file or ($file eq ''));$encoding=$encoding ? ">:$encoding" : '>';my($string)=$self->write_string;return undef unless defined$string;open(CFG,$encoding,$file)or return$self->_error("Failed to open file '$file' for writing: $!");print CFG$string;close CFG;return 1}sub write_string {my($self)=shift;my($contents)='';for my$section (sort {(($b eq '_')<=> ($a eq '_'))|| ($a cmp $b)}keys %$self){return$self->_error("Illegal whitespace in section name '$section'")if$section =~ /(?:^\s|\n|\s$)/s;my$block=$self->{$section};$contents .= "\n" if length$contents;$contents .= "[$section]\n" unless$section eq '_';for my$property (sort keys %$block){return$self->_error("Illegal newlines in property '$section.$property'")if$block->{$property}=~ /(?:\012|\015)/s;$contents .= "$property=$block->{$property}\n"}}return$contents}sub errstr {$Config::Tiny::errstr}sub _error {$Config::Tiny::errstr=$_[1];undef}1; +CONFIG_TINY + +$fatpacked{"Devel/InnerPackage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DEVEL_INNERPACKAGE'; +  package Devel::InnerPackage;use strict;use Exporter 5.57 'import';use vars qw($VERSION @EXPORT_OK);use if $] > 5.017,'deprecate';$VERSION='0.4';@EXPORT_OK=qw(list_packages);sub list_packages {my$pack=shift;$pack .= "::" unless$pack =~ m!::$!;no strict 'refs';my@packs;my@stuff=grep!/^(main|)::$/,keys %{$pack};for my$cand (grep /::$/,@stuff){$cand =~ s!::$!!;my@children=list_packages($pack.$cand);push@packs,"$pack$cand" unless$cand =~ /^::/ || !__PACKAGE__->_loaded($pack.$cand);push@packs,@children}return grep {$_ !~ /::(::ISA::CACHE|SUPER)/}@packs}sub _loaded {my ($class,$name)=@_;no strict 'refs';return 1 if defined ${"${name}::VERSION"};return 1 if @{"${name}::ISA"};for (keys %{"${name}::"}){next if substr($_,-2,2)eq '::';return 1 if defined &{"${name}::$_"}}my$filename=join('/',split /(?:'|::)/,$name).'.pm';return 1 if defined$INC{$filename};''}1; +DEVEL_INNERPACKAGE + +$fatpacked{"Math/Calc/Units.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS'; +  package Math::Calc::Units;use Math::Calc::Units::Compute qw(compute);use Math::Calc::Units::Rank qw(render render_unit choose_juicy_ones);use Math::Calc::Units::Convert;use base 'Exporter';use vars qw($VERSION @EXPORT_OK);BEGIN {$VERSION='1.07';@EXPORT_OK=qw(calc readable convert equal exact)}use strict;sub calc ($;$) {my ($expr,$exact)=@_;my$v=compute($expr);return$exact ? ($v->[0],render_unit($v->[1])): render($v)}sub readable {my$expr=shift;my%options;if (@_==1){$options{verbose}=shift}else {%options=@_}my$v=compute($expr);return map {render($_,\%options)}choose_juicy_ones($v,\%options)}sub convert ($$;$) {my ($expr,$units,$exact)=@_;my$v=compute($expr);my$u=compute("# $units");my$c=Math::Calc::Units::Convert::convert($v,$u->[1]);return$exact ? ($c->[0],render_unit($c->[1])): render($c)}use constant EPSILON=>1e-12;sub equal {my ($u,$v)=@_;$u=compute($u);$v=compute($v);$v=Math::Calc::Units::Convert::convert($v,$u->[1]);$u=$u->[0];$v=$v->[0];return 1 if ($u==0)&& abs($v)< EPSILON;return abs(($u-$v)/$u)< EPSILON}if (!(caller)){my$verbose;my%options;if ($ARGV[0]eq '-v'){shift;$options{verbose}=1}if ($ARGV[0]eq '-a'){shift;$options{abbreviate}=1}print "$_\n" foreach readable($ARGV[0],%options)}1; +MATH_CALC_UNITS + +$fatpacked{"Math/Calc/Units/Compute.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_COMPUTE'; +  package Math::Calc::Units::Compute;use base 'Exporter';use vars qw(@EXPORT_OK);@EXPORT_OK=qw(compute plus minus mult divide power unit_mult unit_divide unit_power construct);use strict;use Math::Calc::Units::Convert qw(reduce);use Math::Calc::Units::Rank qw(render_unit);use Math::Calc::Units::Convert::Base;require Math::Calc::Units::Grammar;sub equivalent {my ($u,$v)=@_;return Math::Calc::Units::Convert::Base->same($u,$v)}sub is_unit {my ($x,$unit)=@_;return equivalent($x,{$unit=>1 })}sub plus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (equivalent($u->[1],$v->[1])){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'sec')&& is_unit($v->[1],'timestamp')){return [$u->[0]+ $v->[0],$v->[1]]}die "Unable to add incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub minus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'timestamp')){return [$u->[0]- $v->[0],{sec=>1 }]}elsif (equivalent($u->[1],$v->[1])){return [$u->[0]- $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]- $v->[0],$u->[1]]}die "Unable to subtract incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub mult {my ($u,$v)=@_;return [$u->[0]* $v->[0],unit_mult($u->[1],$v->[1])]}sub divide {my ($u,$v)=@_;return [$u->[0]/ $v->[0],unit_divide($u->[1],$v->[1])]}sub power {my ($u,$v)=@_;die "Can only raise to unit-less powers" if keys %{$v->[1]};$u=reduce($u);if (keys %{$u->[1]}!=0){my$power=$v->[0];die "Can only raise a value with units to an integral power" if abs($power - int($power))> 1e-20;return [$u->[0]** $power,unit_power($u->[1],$power)]}return [$u->[0]** $v->[0],{}]}sub unit_mult {my ($u,$v,$mult)=@_;$mult ||= 1;while (my ($unit,$vp)=each %$v){$u->{$unit}+= $vp * $mult;delete$u->{$unit}if$u->{$unit}==0}return$u}sub unit_divide {my ($u,$v)=@_;return unit_mult($u,$v,-1)}sub unit_power {my ($u,$power)=@_;return {}if$power==0;$u->{$_}*= $power foreach (keys %$u);return$u}sub construct {my$s=shift;my ($constructor,$args)=$s =~ /^(\w+)\((.*)\)/;return Math::Calc::Units::Convert::construct($constructor,$args)}package Math::Calc::Units::Compute;sub tokenize {my$data=shift;my@tokens=$data =~ m{\s* +                             ( +                               \w+\([^\(\)]*\) # constructed (eg date(2001...)) +                              |[\d.]+       # Numbers +                              |\w+          # Words +                              |\*\*         # Exponentiation (**) +                              |[-+*/()@]    # Operators +                             )}xg;my@types=map {/\w\(/ ? 'CONSTRUCT' :(/\d/ ? 'NUMBER' :(/\w/ ? 'WORD' :($_)))}@tokens;return \@tokens,\@types}sub compute {my$expr=shift;my$canonicalize=$expr !~ /^\#/;my ($vals,$types)=tokenize($expr);my$lexer=sub {return shift(@$types),shift(@$vals)if (@$types);return ('',undef)};my$parser=new Math::Calc::Units::Grammar;my$v=$parser->YYParse(yylex=>$lexer,yyerror=>sub {my$parser=shift;die "Error: expected ".join(" ",$parser->YYExpect)." got `".$parser->YYCurtok."', rest=".join(" ",@$types)."\nfrom ".join(" ",@$vals)."\n"},yydebug=>0);return$canonicalize ? reduce($v): $v};1; +MATH_CALC_UNITS_COMPUTE + +$fatpacked{"Math/Calc/Units/Convert.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT'; +  package Math::Calc::Units::Convert;use base 'Exporter';use strict;use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(convert reduce canonical find_top construct)};use Math::Calc::Units::Convert::Multi qw(to_canonical);sub convert {my ($from,$unit)=@_;my$to=[1,$unit ];my$canon_from=canonical($from);my$canon_to=canonical($to);die "conversion between incompatible units" if not same_units($canon_from->[1],$canon_to->[1]);return [$canon_from->[0]/ $canon_to->[0],$unit ]}sub same_units {my ($u1,$u2)=@_;return if keys %$u1!=keys %$u2;while (my ($bu1,$bp1)=each %$u1){return if!exists$u2->{$bu1};return if$bp1!=$u2->{$bu1}}return 1}sub canonical {my ($v)=@_;my$c=to_canonical($v->[1]);my$w=[$v->[0]* $c->[0],$c->[1]];return$w}sub reduce {my ($v)=@_;return canonical($v,'reduce, please')}sub construct {my ($constructor,$args)=@_;return Math::Calc::Units::Convert::Multi::construct($constructor,$args)}1; +MATH_CALC_UNITS_CONVERT + +$fatpacked{"Math/Calc/Units/Convert/Base.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE'; +  package Math::Calc::Units::Convert::Base;use strict;sub major_pref {return 0}sub major_variants {my ($self,$unit)=@_;return$unit}sub singular {my$self=shift;local $_=shift;return $_ unless /s$/;return $1 if /^(.*[^e])s$/;return $1 if /^(.*(ch|sh))es$/;return $1 if /^(.*[aeiou][^aeiou]e)s$/;chop;return $_}sub unit_map {return {}}sub variants {my ($self,$base)=@_;my$map=$self->unit_map();return ($base,keys %$map)}sub same {my ($self,$u,$v)=@_;return 0 if keys %$u!=keys %$v;while (my ($name,$power)=each %$u){return 0 if!exists$v->{$name};return 0 if$v->{$name}!=$power}return 1}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from eq $to;my$map=$self->unit_map();my$w=$map->{$from}|| $map->{lc($from)};if (!$w){$from=$self->singular($from);$w=$map->{$from}|| $map->{lc($from)}}return if!$w;if ($w->[1]ne $to){my$submult=$self->simple_convert($w->[1],$to);return if!defined$submult;return$w->[0]* $submult}else {return$w->[0]}}sub to_canonical {my ($self,$unitName)=@_;my$canon=$self->canonical_unit();if ($canon){my$mult=$self->simple_convert($unitName,$canon);return if!defined$mult;return ($mult,$canon)}else {return (1,$self->singular($unitName))}}sub canonical_unit {return}sub abbreviated_canonical_unit {my ($self)=@_;return$self->canonical_unit}my$THRESHOLD=0.01;sub spread {my ($self,$mag,$base,$start,$units)=@_;die if$mag < 0;return [0,$base ]if$mag==0;my$orig=$mag;my@desc;my$started=0;for my$unit (@$units){$started=1 if$unit eq $start;next unless$started;last if ($mag / $orig)< $THRESHOLD;my$mult=$self->simple_convert($unit,$base);my$n=int($mag / $mult);next if$n==0;$mag -= $n * $mult;push@desc,[$n,$unit ]}return@desc}sub range_score {my ($self,$val,$unitName)=@_;my$ranges=$self->get_ranges();my$range=$ranges->{$unitName}|| $ranges->{default};if ($val >= $range->[0]){if (!defined$range->[1]|| ($val <= $range->[1])){return 1}}$val=_sillylog($val);my$r0=_sillylog($range->[0]);my$r1;if (defined$range->[1]){$r1=_sillylog($range->[1])}else {$r1=4}my$width=$r1 - $r0;my$mean=($r0 + $r1)/ 2;my$stddev=$width / 2;my$n=($val - $mean)/ $stddev;our$mulconst;$mulconst ||= 0.999 * exp(1/8);return 0.001 + $mulconst * exp(-$n**2/2)}sub _sillylog {my$x=shift;return log($x)if$x;return log(1e-50)}sub pref_score {my ($self,$unitName)=@_;my$prefs=$self->get_prefs();my$specific=$prefs->{$unitName};return defined($specific)? $specific : $prefs->{default}}sub get_prefs {return {default=>0.1 }}sub get_ranges {return {default=>[1,undef ]}}sub render_unit {my ($self,$name,$power,$options)=@_;if ($power==1){return$name}else {return "$name**$power"}}sub render {my ($self,$val,$name,$power,$options)=@_;return sprintf("%.5g ",$val).$self->render_unit($name,$power,$options)}sub construct {return}1; +MATH_CALC_UNITS_CONVERT_BASE + +$fatpacked{"Math/Calc/Units/Convert/Base2Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE2METRIC'; +  package Math::Calc::Units::Convert::Base2Metric;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%metric_base2 %abbrev $metric_prefix_test %pref);%metric_base2=(kilo=>2**10,mega=>2**20,giga=>2**30,tera=>2**40,peta=>2**50,exa=>2**60,);%abbrev=(k=>'kilo',m=>'mega',g=>'giga',t=>'tera',p=>'peta',e=>'exa',);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,);sub get_metric {my ($self,$what)=@_;return$metric_base2{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}|| $abbrev{lc($what)}}$metric_prefix_test=qr/^(${\join("|",keys %metric_base2)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub get_prefixes {return keys%metric_base2}sub expand {my ($self,$char)=@_;return$self->get_abbrev($char)}1; +MATH_CALC_UNITS_CONVERT_BASE2METRIC + +$fatpacked{"Math/Calc/Units/Convert/Byte.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BYTE'; +  package Math::Calc::Units::Convert::Byte;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;my%units=(bit=>[1/8,'byte' ]);my%pref=(bit=>0.1,default=>1);my%ranges=(default=>[1,999 ]);my%total_unit_map;sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('byte')}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'byte'}sub abbreviated_canonical_unit {return 'B'}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^b(yte(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)b(yte(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."byte",$to)}}return}1; +MATH_CALC_UNITS_CONVERT_BYTE + +$fatpacked{"Math/Calc/Units/Convert/Combo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_COMBO'; +  package Math::Calc::Units::Convert::Combo;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;use vars qw(%units %metric_units %prefixable_metric_units %total_unit_map);use vars qw(%ranges %pref);%units=();%metric_units=();%prefixable_metric_units=(bps=>[1,{bit=>1,sec=>-1 }]);%ranges=(default=>[1,999 ]);%pref=(default=>1);sub canonical_unit {return}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units,%metric_units,%prefixable_metric_units)}return \%total_unit_map}sub singular {my ($self,$unit)=@_;return$self->SUPER::singular($unit)unless$unit =~ /bps$/;return$unit}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=lc($self->singular(substr($string,length($prefix))));if ($metric_units{$tail}){return ($self->get_metric($prefix),$tail)}}elsif (my$abbrev=$self->get_abbrev_prefix($string)){my$tail=lc($self->singular(substr($string,length($abbrev))));if ($prefixable_metric_units{$tail}){my$prefix=$self->get_abbrev($abbrev);return ($self->get_metric($prefix),$tail)}}return (1,$string)}sub to_canonical {return}sub lookup_compound {my ($self,$unitName)=@_;for (keys%units,keys%metric_units,keys%prefixable_metric_units){if (my$mult=$self->simple_convert($unitName,$_)){my$u=$units{$_}|| $metric_units{$_}|| $prefixable_metric_units{$_};return [$mult * $u->[0],$u->[1]]}}return}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}1; +MATH_CALC_UNITS_CONVERT_COMBO + +$fatpacked{"Math/Calc/Units/Convert/Date.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DATE'; +  package Math::Calc::Units::Convert::Date;use base 'Math::Calc::Units::Convert::Base';use Time::Local qw(timegm);use strict;use vars qw(%units %pref %ranges %total_unit_map);my$min_nice_time=timegm(0,0,0,1,0,1975-1900);my$max_nice_time=timegm(0,0,0,1,0,2030-1900);%units=();%pref=(default=>1);%ranges=(timestamp=>[$min_nice_time,$max_nice_time ]);sub major_pref {return 2}sub canonical_unit {return 'timestamp'}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}use vars qw(@MonthNames);BEGIN {@MonthNames=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)}sub construct {my ($self,$constructor,$args)=@_;if ($constructor eq 'timestamp'){$args=time if$args eq '';return [$args,{'timestamp'=>1 }]}return unless$constructor eq 'date';$args =~ s/\s+GMT\s+$//;my ($Mon,$d,$y,$h,$m,$s,$tz,$M);$tz='GMT';if ($args =~ /^((?:\w\w\w\s+)?) +                     (\w\w\w)\s* +                     (\d+)\s+ +                     (\d+):(\d+)[:.](\d+)\s+ +                     (\w+)?\s* +                     (\d\d\d\d)$/x){(undef,$Mon,$d,$h,$m,$s,$tz,$y)=($1,$2,$3,$4,$5,$6,$7,$8)}elsif ($args =~ /^(\w\w\w)[\s-]* +                          (\d+)[,\s-]+ +                          (\d\d\d\d)$/x){($Mon,$d,$y)=($1,$2,$3)}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)\s+ +                          (\d+):(\d+)[:.](\d+)$/x){($y,$M,$d,$h,$m,$s)=($1,$2,$3,$4,$5,$6);$M--}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)$/){($y,$M,$d)=($1,$2,$3);$M--}else {die "Unparseable date string '$args'"}$h ||= 0;$m ||= 0;$s ||= 0;if (defined$Mon){$M=0;for (@MonthNames){last if lc($_)eq lc($Mon);$M++}die "Unparseable month '$Mon'" if$M > 11}if (defined($tz)&& $tz ne 'GMT'){warn "Timezones not supported. Assuming GMT.\n"}my$timestamp=timegm($s,$m,$h,$d,$M,$y-1900);die "Date '$args' is out of range" if$timestamp==-1;return [$timestamp,{'timestamp'=>1 }]}sub render {my ($self,$mag,$name,$power)=@_;return "\@$mag" if$power!=1;return "\@$mag" if$mag < $min_nice_time;return "\@$mag" if$mag > $max_nice_time;return gmtime($mag)." (\@$mag)"}1; +MATH_CALC_UNITS_CONVERT_DATE + +$fatpacked{"Math/Calc/Units/Convert/Distance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DISTANCE'; +  package Math::Calc::Units::Convert::Distance;use base 'Math::Calc::Units::Convert::Metric';use strict;my%total_unit_map;my%ranges=(default=>[1,999 ]);my%distance_units=(inch=>[2.54,'centimeter' ],foot=>[12,'inch' ],yard=>[3,'foot' ],mile=>[5280,'foot' ],);my%distance_pref=(meter=>1.1,inch=>0.7,foot=>0.9,yard=>0,mile=>1.0,);my%aliases=('feet'=>'foot',);sub canonical_unit {return 'meter'}sub abbreviated_canonical_unit {return 'm'}sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('meter')}sub get_ranges {return \%ranges}sub get_prefs {return \%distance_pref}sub singular {my ($self,$unit)=@_;$unit=$self->SUPER::singular($unit);return$aliases{$unit}|| $unit}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%distance_units)}return \%total_unit_map}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^m(eter(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)m(eter(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."meter",$to)}}return}sub variants {my ($self,$base)=@_;my$canon=$self->canonical_unit();return ($base,keys %{$self->unit_map()},map {"$_$canon"}$self->get_prefixes())}1; +MATH_CALC_UNITS_CONVERT_DISTANCE + +$fatpacked{"Math/Calc/Units/Convert/Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_METRIC'; +  package Math::Calc::Units::Convert::Metric;use base 'Math::Calc::Units::Convert::Base';use strict;use vars qw(%niceSmallMetric %metric %pref %abbrev %reverse_abbrev $metric_prefix_test);%niceSmallMetric=(milli=>1e-3,micro=>1e-6,nano=>1e-9,pico=>1e-12,femto=>1e-15,);%metric=(kilo=>1e3,mega=>1e6,giga=>1e9,tera=>1e12,peta=>1e15,exa=>1e18,centi=>1e-2,%niceSmallMetric,);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,centi=>0.1,milli=>0.8,micro=>0.8,nano=>0.6,pico=>0.4,femto=>0.3,);%abbrev=(k=>'kilo',M=>'mega',G=>'giga',T=>'tera',P=>'peta',E=>'exa',c=>'centi',m=>'milli',u=>'micro',n=>'nano',p=>'pico',f=>'femto',);%reverse_abbrev=reverse%abbrev;sub pref_score {my ($self,$unitName)=@_;my$prefix=$self->get_prefix($unitName);$unitName=substr($unitName,length($prefix || ""));my$prefix_pref=defined($prefix)? $self->prefix_pref($prefix): 1;return$prefix_pref * $self->SUPER::pref_score($unitName)}sub get_metric {my ($self,$what)=@_;return$metric{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}}$metric_prefix_test=qr/^(${\join("|",keys %metric)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub get_prefixes {my ($self,$options)=@_;if ($options->{small}){return grep {$metric{$_}< 1}keys%metric}else {return keys%metric}}sub get_abbrev_prefix {my ($self,$what)=@_;my$prefix=substr($what,0,1);if ($abbrev{$prefix}|| $abbrev{lc($prefix)}){return$prefix}else {return}}sub variants {my ($self,$base)=@_;my@main=$self->SUPER::variants($base);my@variants;for my$u (@main){push@variants,$u,map {"$_$u"}$self->get_prefixes()}return@variants}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$base=substr($string,length($prefix));return ($self->get_metric($prefix),$base)}else {return (1,$string)}}sub expand {my ($self,$char)=@_;my@expansions;my ($exact,$lower);if ($exact=$self->get_abbrev($char)){push@expansions,$exact}elsif (($char ne lc($char))&& ($lower=$self->get_abbrev(lc($char)))){push@expansions,$lower}return@expansions}sub simple_convert {my ($self,$from,$to)=@_;my ($mult_from,$base_from)=$self->demetric($from)or return;my ($mult_to,$base_to)=$self->demetric($to)or return;my$submult=$self->SUPER::simple_convert($base_from,$base_to);return if!defined$submult;return$submult * ($mult_from / $mult_to)}sub metric_abbreviation {my ($self,$prefix)=@_;return$reverse_abbrev{$prefix}|| $prefix}sub render {my ($self,$val,$name,$power,$options)=@_;if ($options->{abbreviate}){my$stem=$self->canonical_unit;if ($name =~ /(\w+)\Q$stem\E$/){my$prefix=$reverse_abbrev{$1};if (defined($prefix)){$name=$prefix .$self->abbreviated_canonical_unit}}}return$self->SUPER::render($val,$name,$power,$options)}1; +MATH_CALC_UNITS_CONVERT_METRIC + +$fatpacked{"Math/Calc/Units/Convert/Multi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_MULTI'; +  package Math::Calc::Units::Convert::Multi;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(to_canonical simple_convert singular variants major_variants major_pref range_score pref_score get_class construct)};require Math::Calc::Units::Convert::Time;require Math::Calc::Units::Convert::Byte;require Math::Calc::Units::Convert::Date;require Math::Calc::Units::Convert::Distance;require Math::Calc::Units::Convert::Combo;use strict;use vars qw(@UnitClasses);@UnitClasses=qw(Math::Calc::Units::Convert::Time Math::Calc::Units::Convert::Byte Math::Calc::Units::Convert::Date Math::Calc::Units::Convert::Distance Math::Calc::Units::Convert::Combo);sub to_canonical {my ($unit)=@_;my$val=1;my%newUnit;while (my ($unitName,$power)=each %$unit){my ($mult,$canon)=name_to_canonical($unitName);$val *= $mult ** $power;if (ref$canon){my$c=to_canonical($canon);$val *= $c->[0]** $power;while (my ($name,$subPower)=each %{$c->[1]}){if (($newUnit{$name}+= $subPower * $power)==0){delete$newUnit{$name}}}}else {if (($newUnit{$canon}+= $power)==0){delete$newUnit{$canon}}}}return [$val,\%newUnit ]}my%CANON_CACHE;sub name_to_canonical {my$unitName=shift;$CANON_CACHE{$unitName}||= [_name_to_canonical($unitName)];return @{$CANON_CACHE{$unitName}}}sub _name_to_canonical {my ($unitName)=@_;if (my$v=Math::Calc::Units::Convert::Combo->lookup_compound($unitName)){return @$v}for my$uclass (@UnitClasses){if (my ($val,$base)=$uclass->to_canonical($unitName)){return ($val,$base)}}return Math::Calc::Units::Convert::Base->to_canonical($unitName)}sub get_class {my ($unitName)=@_;my (undef,$canon)=name_to_canonical($unitName);for my$uclass (@UnitClasses){my$canon_unit=$uclass->canonical_unit();next if!defined$canon_unit;return$uclass if$canon_unit eq $canon}return 'Math::Calc::Units::Convert::Base'}sub simple_convert {my ($u,$v)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->simple_convert($u,$v)}return}sub singular {my ($unitName)=@_;return get_class($unitName)->singular($unitName)}sub variants {my ($base)=@_;return get_class($base)->variants($base)}sub major_variants {my ($base)=@_;return get_class($base)->major_variants($base)}sub major_pref {my ($base)=@_;return get_class($base)->major_pref($base)}sub range_score {my ($val,$unitName)=@_;die if ref$unitName;return get_class($unitName)->range_score($val,$unitName)}sub pref_score {my ($unitName)=@_;die if ref$unitName;return get_class($unitName)->pref_score($unitName)}sub construct {my ($constructor,$args)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->construct($constructor,$args)}return}1; +MATH_CALC_UNITS_CONVERT_MULTI + +$fatpacked{"Math/Calc/Units/Convert/Time.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_TIME'; +  package Math::Calc::Units::Convert::Time;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%units %pref %ranges %total_unit_map);%units=(minute=>[60,'sec' ],hour=>[60,'minute' ],day=>[24,'hour' ],week=>[7,'day' ],year=>[365,'day' ],);%pref=(default=>1,hour=>0.8,day=>0.8,week=>0.4,minute=>0.9,year=>0.9,);%ranges=(default=>[1,300 ],millisec=>[1,999 ],sec=>[1,200 ],minute=>[2,100 ],hour=>[1,80 ],day=>[1,500 ],week=>[1,4 ],year=>[1,undef ],);sub major_pref {return 2}sub major_variants {my ($self)=@_;return grep {($_ ne 'default')&& ($_ ne 'week')}keys%ranges}sub variants {my ($self,$base)=@_;return 'sec',(keys%units),map {"${_}sec"}$self->get_prefixes({small=>1 })}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'sec'}sub abbreviated_canonical_unit {return 's'}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=substr($string,length($prefix));if ($tail =~ /^sec(ond)?s?$/){return ($self->get_metric($prefix),"sec")}return}else {return (1,$string)}}sub simple_convert {my ($self,$from,$to)=@_;$from="sec" if$from =~ /^sec(ond)?s?$/i;$from="minute" if$from =~ /^min(ute)?s?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)s$/){my ($expansion)=$self->expand($1);return$self->simple_convert($expansion ."sec",$to)}return}sub preference {my ($self,$v)=@_;my ($val,$unit)=@$v;my$base=lc(($self->demetric($unit))[1]);my$pref=$pref{$base}|| $pref{default};return$pref * $self->prefix_pref(substr($unit,0,-length($base)))}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}my@BREAKDOWN=qw(year week day hour minute sec ms us ns ps);sub render {my ($self,$val,$name,$power,$options)=@_;my$full_name=$name;if ($options->{abbreviate}){if ($name =~ /(\w+)sec/){my$prefix=$1;my$mabbrev=$self->metric_abbreviation($prefix);$name=$mabbrev ."s" unless$mabbrev eq $prefix}}my$basic=$self->SUPER::render($val,$name,$power,$options);return$basic if$power!=1;$val *= $self->simple_convert($full_name,'sec');my@spread=$self->spread($val,'sec',$name,\@BREAKDOWN);my$spread=join(" ",map {"$_->[0] $_->[1]"}@spread);return "($basic = $spread)" if@spread > 1;return$basic}1; +MATH_CALC_UNITS_CONVERT_TIME + +$fatpacked{"Math/Calc/Units/Grammar.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_GRAMMAR'; +  package Math::Calc::Units::Grammar;use vars qw ( @ISA);use strict;@ISA=qw ( Parse::Yapp::Driver);{package Parse::Yapp::Driver;require 5.004;use strict;use vars qw ( $VERSION $COMPATIBLE $FILENAME);$VERSION='1.04';$COMPATIBLE='0.07';$FILENAME=__FILE__;use Carp;my(%params)=(YYLEX=>'CODE','YYERROR'=>'CODE',YYVERSION=>'',YYRULES=>'ARRAY',YYSTATES=>'ARRAY',YYDEBUG=>'');my(@params)=('LEX','RULES','STATES');sub new {my($class)=shift;my($errst,$nberr,$token,$value,$check,$dotpos);my($self)={ERROR=>\&_Error,ERRST=>\$errst,NBERR=>\$nberr,TOKEN=>\$token,VALUE=>\$value,DOTPOS=>\$dotpos,STACK=>[],DEBUG=>0,CHECK=>\$check };_CheckParams([],\%params,\@_,$self);exists($$self{VERSION})and $$self{VERSION}< $COMPATIBLE and croak "Yapp driver version $VERSION "."incompatible with version $$self{VERSION}:\n"."Please recompile parser module.";ref($class)and $class=ref($class);bless($self,$class)}sub YYParse {my($self)=shift;my($retval);_CheckParams(\@params,\%params,\@_,$self);if($$self{DEBUG}){_DBLoad();$retval=eval '$self->_DBParse()';$@ and die $@}else {$retval=$self->_Parse()}$retval}sub YYData {my($self)=shift;exists($$self{USER})or $$self{USER}={};$$self{USER}}sub YYErrok {my($self)=shift;${$$self{ERRST}}=0;undef}sub YYNberr {my($self)=shift;${$$self{NBERR}}}sub YYRecovering {my($self)=shift;${$$self{ERRST}}!=0}sub YYAbort {my($self)=shift;${$$self{CHECK}}='ABORT';undef}sub YYAccept {my($self)=shift;${$$self{CHECK}}='ACCEPT';undef}sub YYError {my($self)=shift;${$$self{CHECK}}='ERROR';undef}sub YYSemval {my($self)=shift;my($index)=$_[0]- ${$$self{DOTPOS}}- 1;$index < 0 and -$index <= @{$$self{STACK}}and return $$self{STACK}[$index][1];undef}sub YYCurtok {my($self)=shift;@_ and ${$$self{TOKEN}}=$_[0];${$$self{TOKEN}}}sub YYCurval {my($self)=shift;@_ and ${$$self{VALUE}}=$_[0];${$$self{VALUE}}}sub YYExpect {my($self)=shift;keys %{$self->{STATES}[$self->{STACK}[-1][0]]{ACTIONS}}}sub YYLexer {my($self)=shift;$$self{LEX}}sub _CheckParams {my($mandatory,$checklist,$inarray,$outhash)=@_;my($prm,$value);my($prmlst)={};while(($prm,$value)=splice(@$inarray,0,2)){$prm=uc($prm);exists($$checklist{$prm})or croak("Unknow parameter '$prm'");ref($value)eq $$checklist{$prm}or croak("Invalid value for parameter '$prm'");$prm=unpack('@2A*',$prm);$$outhash{$prm}=$value}for (@$mandatory){exists($$outhash{$_})or croak("Missing mandatory parameter '".lc($_)."'")}}sub _Error {print "Parse error.\n"}sub _DBLoad {{no strict 'refs';exists(${__PACKAGE__.'::'}{_DBParse})and return}my($fname)=__FILE__;my(@drv);open(DRV,"<$fname")or die "Report this as a BUG: Cannot open $fname";while(<DRV>){/^\s*sub\s+_Parse\s*{\s*$/ .. /^\s*}\s*#\s*_Parse\s*$/ and do {s/^#DBG>//;push(@drv,$_)}}close(DRV);$drv[0]=~s/_P/_DBP/;eval join('',@drv)}sub _Parse {my($self)=shift;my($rules,$states,$lex,$error)=@$self{'RULES','STATES','LEX','ERROR' };my($errstatus,$nberror,$token,$value,$stack,$check,$dotpos)=@$self{'ERRST','NBERR','TOKEN','VALUE','STACK','CHECK','DOTPOS' };$$errstatus=0;$$nberror=0;($$token,$$value)=(undef,undef);@$stack=([0,undef ]);$$check='';while(1){my($actions,$act,$stateno);$stateno=$$stack[-1][0];$actions=$$states[$stateno];if (exists($$actions{ACTIONS})){defined($$token)or do {($$token,$$value)=&$lex($self)};$act=exists($$actions{ACTIONS}{$$token})? $$actions{ACTIONS}{$$token}: exists($$actions{DEFAULT})? $$actions{DEFAULT}: undef}else {$act=$$actions{DEFAULT}}defined($act)and do {$act > 0 and do {$$errstatus and do {--$$errstatus};push(@$stack,[$act,$$value ]);$$token ne '' and $$token=$$value=undef;next};my($lhs,$len,$code,@sempar,$semval);($lhs,$len,$code)=@{$$rules[-$act]};$act or $self->YYAccept();$$dotpos=$len;unpack('A1',$lhs)eq '@' and do {$lhs =~ /^\@[0-9]+\-([0-9]+)$/ or die "In line rule name '$lhs' ill formed: "."report it as a BUG.\n";$$dotpos=$1};@sempar=$$dotpos ? map {$$_[1]}@$stack[-$$dotpos .. -1 ]: ();$semval=$code ? &$code($self,@sempar): @sempar ? $sempar[0]: undef;splice(@$stack,-$len,$len);$$check eq 'ACCEPT' and do {return($semval)};$$check eq 'ABORT' and do {return(undef)};$$check eq 'ERROR' or do {push(@$stack,[$$states[$$stack[-1][0]]{GOTOS}{$lhs},$semval ]);$$check='';next};$$check=''};$$errstatus or do {$$errstatus=1;&$error($self);$$errstatus or next;++$$nberror};$$errstatus==3 and do {$$token eq '' and do {return(undef)};$$token=$$value=undef};$$errstatus=3;while(@$stack and (not exists($$states[$$stack[-1][0]]{ACTIONS})or not exists($$states[$$stack[-1][0]]{ACTIONS}{error})or $$states[$$stack[-1][0]]{ACTIONS}{error}<= 0)){pop(@$stack)}@$stack or do {return(undef)};push(@$stack,[$$states[$$stack[-1][0]]{ACTIONS}{error},undef ])}croak("Error in driver logic. Please, report it as a BUG")}1}use Math::Calc::Units::Compute qw(plus minus mult divide power construct);sub new {my($class)=shift;ref($class)and $class=ref($class);my($self)=$class->SUPER::new(yyversion=>'1.04',yystates=>[{ACTIONS=>{'NUMBER'=>5,"#"=>2,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'START'=>10,'expr'=>11 }},{DEFAULT=>-12 },{ACTIONS=>{'WORD'=>7 },GOTOS=>{'unit'=>12 }},{DEFAULT=>-16 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>13 }},{ACTIONS=>{'WORD'=>7 },DEFAULT=>-13,GOTOS=>{'unit'=>14 }},{ACTIONS=>{'NUMBER'=>15 }},{DEFAULT=>-17 },{DEFAULT=>-9 },{ACTIONS=>{'NUMBER'=>17 }},{ACTIONS=>{''=>18 }},{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24 },DEFAULT=>-1,GOTOS=>{'unit'=>19 }},{DEFAULT=>-2 },{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24,")"=>25 },GOTOS=>{'unit'=>19 }},{DEFAULT=>-11 },{DEFAULT=>-14 },{DEFAULT=>-18 },{DEFAULT=>-15 },{DEFAULT=>-0 },{DEFAULT=>-10 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>26 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>27 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>28 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>29 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>30 }},{DEFAULT=>-8 },{DEFAULT=>-7,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-5,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-3,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-4,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-6,GOTOS=>{'unit'=>19 }}],yyrules=>[['$start',2,undef ],['START',1,undef ],['START',2,undef ],['expr',3,sub {return plus($_[1],$_[3])}],['expr',3,sub {return minus($_[1],$_[3])}],['expr',3,sub {return mult($_[1],$_[3])}],['expr',3,sub {return divide($_[1],$_[3])}],['expr',3,sub {return power($_[1],$_[3])}],['expr',3,sub {return $_[2]}],['expr',1,sub {return $_[1]}],['expr',2,sub {return mult($_[1],[1,$_[2]])}],['value',2,sub {return [$_[1]=>$_[2]]}],['value',1,sub {return [1=>$_[1]]}],['value',1,sub {return [$_[1]=>{}]}],['value',2,sub {return [-$_[2]=>{}]}],['value',2,sub {return [$_[2]=>{'timestamp'=>1 }]}],['value',1,sub {return construct($_[1])}],['unit',1,sub {return {$_[1]=>1 }}],['unit',2,sub {my$u={};$u->{$_[1]}++;$u->{$_[2]}++;return$u}]],@_);bless($self,$class)}1; +MATH_CALC_UNITS_GRAMMAR + +$fatpacked{"Math/Calc/Units/Rank.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_RANK'; +  package Math::Calc::Units::Rank;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(choose_juicy_ones render render_unit)}use Math::Calc::Units::Convert qw(convert canonical);use Math::Calc::Units::Convert::Multi qw(variants major_variants major_pref pref_score range_score get_class);use strict;sub choose_juicy_ones {my ($v,$options)=@_;my@variants=rank_variants($v,$options);my%variants;for my$variant (@variants){my$id=join(";;",values %{$variant->[0]});$variants{$id}=$variant}my@options;for my$variant (values%variants){my ($map,$score)=@$variant;my%copy;my ($magnitude,$units)=@$v;while (my ($unit,$count)=each %$units){$copy{$map->{$unit}}=$count}push@options,[$score,convert($v,\%copy)]}my@juicy;my$first;my$prev;for (sort {$b->[0]<=> $a->[0]}@options){my ($score,$val)=@$_;last if (defined$prev && ($prev / $score)> 8);last if (defined$first && ($first / $score)> 25);push@juicy,$val;$first=$score unless defined$first;$prev=$score;last if@juicy==5}return@juicy}sub rank_variants {my ($v,$options)=@_;$v=canonical($v);my ($mag,$count)=@$v;my@rangeable=grep {$count->{$_}> 0}keys %$count;if (@rangeable==0){@rangeable=keys %$count}return rank_power_variants($mag,\@rangeable,$count,$options)}sub choose_major {my (@possibilities)=@_;my@majors=map {[major_pref($_),$_ ]}@possibilities;return (sort {$a->[0]<=> $b->[0]}@majors)[-1]->[1]}sub rank_power_variants {my ($mag,$top,$power,$options)=@_;if (keys %$power > 1){my$major=choose_major(keys %$power);my$majorClass=get_class($major);my%powerless=%$power;delete$powerless{$major};my@ranked;for my$variant (major_variants($major,$options)){my$mult=$majorClass->simple_convert($variant,$major);my$cval=$mag / $mult ** $power->{$major};print "\n --- for $variant ---\n" if$options->{verbose};my@r=rank_power_variants($cval,$top,\%powerless,$options);next if@r==0;my$best=$r[0];$best->[0]->{$major}=$variant;$best->[1]=pref_score($variant);push@ranked,$best}return@ranked}if (keys %$power==0){return [{},1 ]}my$unit=(keys %$power)[0];$power=$power->{$unit};my$class=get_class($unit);my (undef,$canon)=$class->to_canonical($unit);my$mult=$class->simple_convert($unit,$canon);$mag *= $mult ** $power;my@choices;my@subtop=grep {$_ ne $canon}@$top;my$add_variant=(@subtop==@$top);for my$variant (variants($canon)){my$mult=$class->simple_convert($variant,$canon);my$minimag=$mag / $mult ** $power;my@vtop=@subtop;push@vtop,$variant if$add_variant;my$score=score($minimag,$variant,\@vtop);printf "($mag $unit) score %.6f:\t $minimag $variant\n",$score if$options->{verbose};push@choices,[$score,$variant ]}@choices=sort {$b->[0]<=> $a->[0]}@choices;return ()if@choices==0;return map {[{$unit=>$_->[1]},$_->[0]]}@choices}sub render_unit {my ($units,$options)=@_;my$str='';while (my ($name,$power)=each %$units){if ($power > 0){$str .= get_class($name)->render_unit($name,$power,$options);$str .= " "}}chop($str);my$botstr='';while (my ($name,$power)=each %$units){if ($power < 0){$botstr .= get_class($name)->render_unit($name,-$power,$options);$botstr .= " "}}chop($botstr);if ($botstr eq ''){return$str}elsif ($botstr =~ /\s/){return "$str / ($botstr)"}else {return "$str / $botstr"}}sub render {my ($v,$options)=@_;my ($mag,$units)=@$v;if (keys %$units==0){my$str=sprintf("%.4g",$mag);if (($mag < 1)&& ($mag >= 0.01)){if ($options->{abbreviate}){$str .= sprintf(" = %.4g percent",100 * $mag)}else {$str .= sprintf(" = %.4g%%",100 * $mag)}}return$str}my@top;my@bottom;while (my ($name,$power)=each %$units){if ($power > 0){push@top,$name}else {push@bottom,$name}}my$str;if (@top==1){my ($name)=@top;$str=get_class($name)->render($mag,$name,$units->{$name},$options);$str .= " "}else {$str=sprintf("%.4g ",$mag);for my$name (@top){$str .= get_class($name)->render_unit($name,$units->{$name},$options);$str .= " "}}if (@bottom > 0){my$botstr;for my$name (@bottom){$botstr .= get_class($name)->render_unit($name,-$units->{$name},$options);$botstr .= " "}chop($botstr);if (@bottom > 1){$str .= "/ ($botstr) "}else {$str .= "/ $botstr "}}chop($str);return$str}sub max_range_score {my ($mag,$units)=@_;my$score=0;for my$name (@$units){my$uscore=range_score($mag,$name);$score=$uscore if$score < $uscore}return$score}sub score {my ($mag,$unit,$top)=@_;my@rangeable=@$top ? @$top : ($unit);my$pref=pref_score($unit);my$range_score=max_range_score($mag,\@rangeable);return$pref * $range_score}1; +MATH_CALC_UNITS_RANK + +$fatpacked{"Module/Implementation.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_IMPLEMENTATION'; +  package Module::Implementation;$Module::Implementation::VERSION='0.09';use strict;use warnings;use Module::Runtime 0.012 qw(require_module);use Try::Tiny;unless (exists$Module::Implementation::{VERSION}&& ${$Module::Implementation::{VERSION}}){$Module::Implementation::{VERSION}=\42}my%Implementation;sub build_loader_sub {my$caller=caller();return _build_loader($caller,@_)}sub _build_loader {my$package=shift;my%args=@_;my@implementations=@{$args{implementations}};my@symbols=@{$args{symbols}|| []};my$implementation;my$env_var=uc$package;$env_var =~ s/::/_/g;$env_var .= '_IMPLEMENTATION';return sub {my ($implementation,$loaded)=_load_implementation($package,$ENV{$env_var},\@implementations,);$Implementation{$package}=$implementation;_copy_symbols($loaded,$package,\@symbols);return$loaded}}sub implementation_for {my$package=shift;return$Implementation{$package}}sub _load_implementation {my$package=shift;my$env_value=shift;my$implementations=shift;if ($env_value){die "$env_value is not a valid implementation for $package" unless grep {$_ eq $env_value}@{$implementations};my$requested="${package}::$env_value";($requested)=$requested =~ /^(.+)$/;try {require_module($requested)}catch {require Carp;Carp::croak("Could not load $requested: $_")};return ($env_value,$requested)}else {my$err;for my$possible (@{$implementations}){my$try="${package}::$possible";my$ok;try {require_module($try);$ok=1}catch {$err .= $_ if defined $_};return ($possible,$try)if$ok}require Carp;if (defined$err && length$err){Carp::croak("Could not find a suitable $package implementation: $err")}else {Carp::croak('Module::Runtime failed to load a module but did not throw a real error. This should never happen. Something is very broken')}}}sub _copy_symbols {my$from_package=shift;my$to_package=shift;my$symbols=shift;for my$sym (@{$symbols}){my$type=$sym =~ s/^([\$\@\%\&\*])// ? $1 : '&';my$from="${from_package}::$sym";my$to="${to_package}::$sym";{no strict 'refs';no warnings 'once';*{$to}=$type eq '&' ? \&{$from}: $type eq '$' ? \${$from}: $type eq '@' ? \@{$from}: $type eq '%' ? \%{$from}: $type eq '*' ? *{$from}: die "Can't copy symbol from $from_package to $to_package: $type$sym"}}}1; +MODULE_IMPLEMENTATION + +$fatpacked{"Module/Pluggable.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE'; +  package Module::Pluggable;use strict;use vars qw($VERSION $FORCE_SEARCH_ALL_PATHS);use Module::Pluggable::Object;use if $] > 5.017,'deprecate';$VERSION='5.2';$FORCE_SEARCH_ALL_PATHS=0;sub import {my$class=shift;my%opts=@_;my ($pkg,$file)=caller;my$sub=$opts{'sub_name'}|| 'plugins';my ($package)=$opts{'package'}|| $pkg;$opts{filename}=$file;$opts{package}=$package;$opts{force_search_all_paths}=$FORCE_SEARCH_ALL_PATHS unless exists$opts{force_search_all_paths};my$finder=Module::Pluggable::Object->new(%opts);my$subroutine=sub {my$self=shift;return$finder->plugins(@_)};my$searchsub=sub {my$self=shift;my ($action,@paths)=@_;$finder->{'search_path'}=["${package}::Plugin"]if ($action eq 'add' and not $finder->{'search_path'});push @{$finder->{'search_path'}},@paths if ($action eq 'add');$finder->{'search_path'}=\@paths if ($action eq 'new');return$finder->{'search_path'}};my$onlysub=sub {my ($self,$only)=@_;if (defined$only){$finder->{'only'}=$only};return$finder->{'only'}};my$exceptsub=sub {my ($self,$except)=@_;if (defined$except){$finder->{'except'}=$except};return$finder->{'except'}};no strict 'refs';no warnings qw(redefine prototype);*{"$package\::$sub"}=$subroutine;*{"$package\::search_path"}=$searchsub;*{"$package\::only"}=$onlysub;*{"$package\::except"}=$exceptsub}1; +MODULE_PLUGGABLE + +$fatpacked{"Module/Pluggable/Object.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE_OBJECT'; +  package Module::Pluggable::Object;use strict;use File::Find ();use File::Basename;use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);use Carp qw(croak carp confess);use Devel::InnerPackage;use vars qw($VERSION $MR);use if $] > 5.017,'deprecate';$VERSION='5.2';BEGIN {eval {require Module::Runtime};unless ($@){Module::Runtime->import('require_module')}else {*require_module=sub {my$module=shift;my$path=$module .".pm";$path =~ s{::}{/}g;require$path}}}sub new {my$class=shift;my%opts=@_;return bless \%opts,$class}sub plugins {my$self=shift;my@args=@_;$self->{'require'}=1 if$self->{'inner'};my$filename=$self->{'filename'};my$pkg=$self->{'package'};$self->_setup_exceptions;for (qw(search_path search_dirs)){$self->{$_}=[$self->{$_}]if exists$self->{$_}&&!ref($self->{$_})}$self->{'search_path'}||= ["${pkg}::Plugin"];$self->{'on_require_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't require $plugin : $err";return 0};$self->{'on_instantiate_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't instantiate $plugin: $err";return 0};$self->{'follow_symlinks'}=1 unless exists$self->{'follow_symlinks'};my@SEARCHDIR=exists$INC{"blib.pm"}&& defined$filename && $filename =~ m!(^|/)blib/! &&!$self->{'force_search_all_paths'}? grep {/blib/}@INC : @INC;unshift@SEARCHDIR,@{$self->{'search_dirs'}}if defined$self->{'search_dirs'};my@tmp=@INC;unshift@tmp,@{$self->{'search_dirs'}|| []};local@INC=@tmp if defined$self->{'search_dirs'};my@plugins=$self->search_directories(@SEARCHDIR);push(@plugins,$self->handle_inc_hooks($_,@SEARCHDIR))for @{$self->{'search_path'}};push(@plugins,$self->handle_innerpackages($_))for @{$self->{'search_path'}};return ()unless@plugins;my%plugins;for(@plugins){next unless$self->_is_legit($_);$plugins{$_}=1}if (defined$self->{'instantiate'}){my$method=$self->{'instantiate'};my@objs=();for my$package (sort keys%plugins){next unless$package->can($method);my$obj=eval {$package->$method(@_)};$self->{'on_instantiate_error'}->($package,$@)if $@;push@objs,$obj if$obj}return@objs}else {my@objs=sort keys%plugins;return@objs}}sub _setup_exceptions {my$self=shift;my%only;my%except;my$only;my$except;if (defined$self->{'only'}){if (ref($self->{'only'})eq 'ARRAY'){%only=map {$_=>1}@{$self->{'only'}}}elsif (ref($self->{'only'})eq 'Regexp'){$only=$self->{'only'}}elsif (ref($self->{'only'})eq ''){$only{$self->{'only'}}=1}}if (defined$self->{'except'}){if (ref($self->{'except'})eq 'ARRAY'){%except=map {$_=>1}@{$self->{'except'}}}elsif (ref($self->{'except'})eq 'Regexp'){$except=$self->{'except'}}elsif (ref($self->{'except'})eq ''){$except{$self->{'except'}}=1}}$self->{_exceptions}->{only_hash}=\%only;$self->{_exceptions}->{only}=$only;$self->{_exceptions}->{except_hash}=\%except;$self->{_exceptions}->{except}=$except}sub _is_legit {my$self=shift;my$plugin=shift;my%only=%{$self->{_exceptions}->{only_hash}||{}};my%except=%{$self->{_exceptions}->{except_hash}||{}};my$only=$self->{_exceptions}->{only};my$except=$self->{_exceptions}->{except};my$depth=()=split '::',$plugin,-1;return 0 if (keys%only &&!$only{$plugin});return 0 unless (!defined$only || $plugin =~ m!$only!);return 0 if (keys%except && $except{$plugin});return 0 if (defined$except && $plugin =~ m!$except!);return 0 if defined$self->{max_depth}&& $depth>$self->{max_depth};return 0 if defined$self->{min_depth}&& $depth<$self->{min_depth};return 1}sub search_directories {my$self=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){push@plugins,$self->search_paths($dir)}return@plugins}sub search_paths {my$self=shift;my$dir=shift;my@plugins;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;for my$searchpath (@{$self->{'search_path'}}){my$sp=catdir($dir,(split /::/,$searchpath));next unless (-e $sp && -d _);my@files=$self->find_files($sp);for my$file (@files){next unless ($file)=($file =~ /(.*$file_regex)$/);my ($name,$directory,$suffix)=fileparse($file,$file_regex);next if (!$self->{include_editor_junk}&& $self->_is_editor_junk($name));$directory=abs2rel($directory,$sp);my@pkg_dirs=();if ($name eq lc($name)|| $name eq uc($name)){my$pkg_file=catfile($sp,$directory,"$name$suffix");open PKGFILE,"<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";my$in_pod=0;while (my$line=<PKGFILE>){$in_pod=1 if$line =~ m/^=\w/;$in_pod=0 if$line =~ /^=cut/;next if ($in_pod || $line =~ /^=cut/);next if$line =~ /^\s*#/;if ($line =~ m/^\s*package\s+(.*::)?($name)\s*;/i){@pkg_dirs=split /::/,$1 if defined $1;;$name=$2;last}}close PKGFILE}$directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/);my@dirs=();if ($directory){($directory)=($directory =~ /(.*)/);@dirs=grep(length($_),splitdir($directory))unless$directory eq curdir();for my$d (reverse@dirs){my$pkg_dir=pop@pkg_dirs;last unless defined$pkg_dir;$d =~ s/\Q$pkg_dir\E/$pkg_dir/i}}else {$directory=""}my$plugin=join '::',$searchpath,@dirs,$name;next unless$plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;$self->handle_finding_plugin($plugin,\@plugins)}push@plugins,$self->handle_innerpackages($searchpath)}return@plugins}sub _is_editor_junk {my$self=shift;my$name=shift;return 1 if$name =~ /~$/;return 1 if$name =~ /^\.#/;return 1 if$name =~ /\.sw[po]$/;return 0}sub handle_finding_plugin {my$self=shift;my$plugin=shift;my$plugins=shift;my$no_req=shift || 0;return unless$self->_is_legit($plugin);unless (defined$self->{'instantiate'}|| $self->{'require'}){push @$plugins,$plugin;return}$self->{before_require}->($plugin)|| return if defined$self->{before_require};unless ($no_req){my$tmp=$@;my$res=eval {require_module($plugin)};my$err=$@;$@=$tmp;if ($err){if (defined$self->{on_require_error}){$self->{on_require_error}->($plugin,$err)|| return}else {return}}}$self->{after_require}->($plugin)|| return if defined$self->{after_require};push @$plugins,$plugin}sub find_files {my$self=shift;my$search_path=shift;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;my@files=();{local $_;File::Find::find({no_chdir=>1,follow=>$self->{'follow_symlinks'},wanted=>sub {return unless$File::Find::name =~ /$file_regex/;(my$path=$File::Find::name)=~ s#^\\./##;push@files,$path}},$search_path)}return@files}sub handle_inc_hooks {my$self=shift;my$path=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){next unless ref$dir && eval {$dir->can('files')};for my$plugin ($dir->files){$plugin =~ s/\.pm$//;$plugin =~ s{/}{::}g;next unless$plugin =~ m!^${path}::!;$self->handle_finding_plugin($plugin,\@plugins)}}return@plugins}sub handle_innerpackages {my$self=shift;return ()if (exists$self->{inner}&&!$self->{inner});my$path=shift;my@plugins;for my$plugin (Devel::InnerPackage::list_packages($path)){$self->handle_finding_plugin($plugin,\@plugins,1)}return@plugins}1; +MODULE_PLUGGABLE_OBJECT + +$fatpacked{"Module/Runtime.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_RUNTIME'; +  package Module::Runtime;BEGIN {require 5.006}BEGIN {${^WARNING_BITS}=""}our$VERSION="0.016";our@EXPORT_OK=qw($module_name_rx is_module_name is_valid_module_name check_module_name module_notional_filename require_module use_module use_package_optimistically $top_module_spec_rx $sub_module_spec_rx is_module_spec is_valid_module_spec check_module_spec compose_module_name);my%export_ok=map {($_=>undef)}@EXPORT_OK;sub import {my$me=shift;my$callpkg=caller(0);my$errs="";for(@_){if(exists$export_ok{$_}){if(/\A\$(.*)\z/s){*{$callpkg."::".$1}=\$$1}else {*{$callpkg."::".$_}=\&$_}}else {$errs .= "\"$_\" is not exported by the $me module\n"}}if($errs ne ""){die "${errs}Can't continue after import errors "."at @{[(caller(0))[1]]} line @{[(caller(0))[2]]}.\n"}}sub _is_string($) {my($arg)=@_;return defined($arg)&& ref(\$arg)eq "SCALAR"}our$module_name_rx=qr/[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*/;my$qual_module_spec_rx=qr#(?:/|::)[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;my$unqual_top_module_spec_rx=qr#[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$top_module_spec_rx=qr/$qual_module_spec_rx|$unqual_top_module_spec_rx/o;my$unqual_sub_module_spec_rx=qr#[0-9A-Z_a-z]+(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$sub_module_spec_rx=qr/$qual_module_spec_rx|$unqual_sub_module_spec_rx/o;sub is_module_name($) {_is_string($_[0])&& $_[0]=~ /\A$module_name_rx\z/o}*is_valid_module_name=\&is_module_name;sub check_module_name($) {unless(&is_module_name){die +(_is_string($_[0])? "`$_[0]'" : "argument")." is not a module name\n"}}sub module_notional_filename($) {&check_module_name;my($name)=@_;$name =~ s!::!/!g;return$name.".pm"}BEGIN {*_WORK_AROUND_HINT_LEAKAGE="$]" < 5.011 &&!("$]" >= 5.009004 && "$]" < 5.010001)? sub(){1}: sub(){0};*_WORK_AROUND_BROKEN_MODULE_STATE="$]" < 5.009 ? sub(){1}: sub(){0}}BEGIN {if(_WORK_AROUND_BROKEN_MODULE_STATE){eval q{ +  	sub Module::Runtime::__GUARD__::DESTROY { +  		delete $INC{$_[0]->[0]} if @{$_[0]}; +  	} +  	1; +  };die $@ if $@ ne ""}}sub require_module($) {local %^H if _WORK_AROUND_HINT_LEAKAGE;if(_WORK_AROUND_BROKEN_MODULE_STATE){my$notional_filename=&module_notional_filename;my$guard=bless([$notional_filename ],"Module::Runtime::__GUARD__");my$result=CORE::require($notional_filename);pop @$guard;return$result}else {return scalar(CORE::require(&module_notional_filename))}}sub use_module($;$) {my($name,$version)=@_;require_module($name);$name->VERSION($version)if @_ >= 2;return$name}sub use_package_optimistically($;$) {my($name,$version)=@_;my$fn=module_notional_filename($name);eval {local$SIG{__DIE__};require_module($name)};die $@ if $@ ne "" && ($@ !~ /\ACan't locate \Q$fn\E .+ at \Q@{[__FILE__]}\E line/s || $@ =~ /^Compilation\ failed\ in\ require +  			 \ at\ \Q@{[__FILE__]}\E\ line/xm);$name->VERSION($version)if @_ >= 2;return$name}sub is_module_spec($$) {my($prefix,$spec)=@_;return _is_string($spec)&& $spec =~ ($prefix ? qr/\A$sub_module_spec_rx\z/o : qr/\A$top_module_spec_rx\z/o)}*is_valid_module_spec=\&is_module_spec;sub check_module_spec($$) {unless(&is_module_spec){die +(_is_string($_[1])? "`$_[1]'" : "argument")." is not a module specification\n"}}sub compose_module_name($$) {my($prefix,$spec)=@_;check_module_name($prefix)if defined$prefix;&check_module_spec;if($spec =~ s#\A(?:/|::)##){}else {$spec=$prefix."::".$spec if defined$prefix}$spec =~ s#/#::#g;return$spec}1; +MODULE_RUNTIME + +$fatpacked{"Monitoring/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN'; +  package Monitoring::Plugin;use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT @STATUS_CODES);use Params::Validate qw(:all);use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);Monitoring::Plugin->mk_accessors(qw(shortname perfdata messages opts threshold));use Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES);our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT);our$VERSION="0.40";sub new {my$class=shift;my%args=validate(@_,{shortname=>0,usage=>0,version=>0,url=>0,plugin=>0,blurb=>0,extra=>0,license=>0,timeout=>0 },);my$shortname=Monitoring::Plugin::Functions::get_shortname(\%args);delete$args{shortname}if (exists$args{shortname});my$self={shortname=>$shortname,perfdata=>[],messages=>{warning=>[],critical=>[],ok=>[]},opts=>undef,threshold=>undef,};bless$self,$class;if (exists$args{usage}){require Monitoring::Plugin::Getopt;$self->opts(new Monitoring::Plugin::Getopt(%args))}return$self}sub add_perfdata {my ($self,%args)=@_;require Monitoring::Plugin::Performance;my$perf=Monitoring::Plugin::Performance->new(%args);push @{$self->perfdata},$perf}sub all_perfoutput {my$self=shift;return join(" ",map {$_->perfoutput}(@{$self->perfdata}))}sub set_thresholds {my$self=shift;require Monitoring::Plugin::Threshold;return$self->threshold(Monitoring::Plugin::Threshold->set_thresholds(@_))}sub plugin_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub plugin_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub nagios_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub nagios_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub max_state {Monitoring::Plugin::Functions::max_state(@_)}sub max_state_alt {Monitoring::Plugin::Functions::max_state_alt(@_)}sub check_threshold {my$self=shift;my%args;if ($#_==0 && (!ref $_[0]|| ref $_[0]eq "ARRAY")){%args=(check=>shift)}else {%args=validate (@_,{check=>1,warning=>0,critical=>0,})}if (exists$args{warning}|| exists$args{critical}){$self->set_thresholds(warning=>$args{warning},critical=>$args{critical},)}elsif (defined$self->threshold){}elsif (defined$self->opts){$self->set_thresholds(warning=>$self->opts->warning,critical=>$self->opts->critical,)}else {return UNKNOWN}return$self->threshold->get_status($args{check})}sub add_arg {my$self=shift;$self->opts->arg(@_)if$self->_check_for_opts}sub getopts {my$self=shift;$self->opts->getopts(@_)if$self->_check_for_opts}sub _check_for_opts {my$self=shift;croak "You have to supply a 'usage' param to Monitoring::Plugin::new() if you want to use Getopts from your Monitoring::Plugin object." unless ref$self->opts()eq 'Monitoring::Plugin::Getopt';return$self}sub add_message {my$self=shift;my ($code,@messages)=@_;croak "Invalid error code '$code'" unless defined($ERRORS{uc$code})|| defined($STATUS_TEXT{$code});$code=$STATUS_TEXT{$code}if$STATUS_TEXT{$code};$code=lc$code;croak "Error code '$code' not supported by add_message" if$code eq 'unknown' || $code eq 'dependent';$self->messages($code,[])unless$self->messages->{$code};push @{$self->messages->{$code}},@messages}sub check_messages {my$self=shift;my%args=@_;for my$code (qw(critical warning ok)){my$messages=$self->messages->{$code}|| [];if ($args{$code}){unless (ref$args{$code}eq 'ARRAY'){if ($code eq 'ok'){$args{$code}=[$args{$code}]}else {croak "Invalid argument '$code'"}}push @{$args{$code}},@$messages}else {$args{$code}=$messages}}Monitoring::Plugin::Functions::check_messages(%args)}1; +MONITORING_PLUGIN + +$fatpacked{"Monitoring/Plugin/Config.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_CONFIG'; +  package Monitoring::Plugin::Config;use 5.006;use strict;use warnings;use Carp;use File::Spec;use base qw(Config::Tiny);my$FILENAME1='plugins.ini';my$FILENAME2='nagios-plugins.ini';my$FILENAME3='monitoring-plugins.ini';my$CURRENT_FILE=undef;my@MONITORING_CONFIG_PATH=qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios);my@CONFIG_PATH=qw(/etc /usr/local/etc /etc/opt);sub read {my$class=shift;unless ($_[0]){SEARCH: {if ($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH}){for (split /:/,($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH})){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}for (@MONITORING_CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file}for (@CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0]}$CURRENT_FILE=$_[0];$class->SUPER::read(@_)}sub read_string {my$class=ref $_[0]? ref shift : shift;my$self=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){push @{$self->{$ns}->{$1}},$2;next}return$self->_error("Syntax error at line $counter: '$_'")}$self}sub write {croak "Write access not permitted"}sub mp_getfile {return$CURRENT_FILE}1; +MONITORING_PLUGIN_CONFIG + +$fatpacked{"Monitoring/Plugin/ExitResult.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_EXITRESULT'; +  package Monitoring::Plugin::ExitResult;use 5.006;use strict;use warnings;use overload '""'=>sub {shift->{message}};sub new {my$class=shift;return bless {return_code=>$_[0],message=>$_[1]},$class}sub message {shift->{message}}sub return_code {shift->{return_code}}sub code {shift->{return_code}}1; +MONITORING_PLUGIN_EXITRESULT + +$fatpacked{"Monitoring/Plugin/Functions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_FUNCTIONS'; +  package Monitoring::Plugin::Functions;use 5.006;use strict;use warnings;use File::Basename;use Params::Validate qw(:types validate);use Math::Calc::Units;our$VERSION="0.40";our@STATUS_CODES=qw(OK WARNING CRITICAL UNKNOWN DEPENDENT);require Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES,qw(plugin_exit plugin_die check_messages));our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re);our%EXPORT_TAGS=(all=>[@EXPORT,@EXPORT_OK ],codes=>[@STATUS_CODES ],functions=>[qw(plugin_exit plugin_die check_messages max_state max_state_alt convert) ],);use constant OK=>0;use constant WARNING=>1;use constant CRITICAL=>2;use constant UNKNOWN=>3;use constant DEPENDENT=>4;our%ERRORS=('OK'=>OK,'WARNING'=>WARNING,'CRITICAL'=>CRITICAL,'UNKNOWN'=>UNKNOWN,'DEPENDENT'=>DEPENDENT,);our%STATUS_TEXT=reverse%ERRORS;my$value=qr/[-+]?[\d\.]+/;our$value_re=qr/$value(?:e$value)?/;my$_fake_exit=0;sub _fake_exit {@_ ? $_fake_exit=shift : $_fake_exit};my$_use_die=0;sub _use_die {@_ ? $_use_die=shift : $_use_die};sub get_shortname {my$arg=shift;my$shortname=undef;return$arg->{shortname}if (defined($arg->{shortname}));$shortname=$arg->{plugin}if (defined($arg->{plugin}));$shortname=uc basename($shortname || $ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);$shortname =~ s/^CHECK_(?:BY_)?//;$shortname =~ s/\..*$//;return$shortname}sub max_state {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return OK if grep {$_==OK}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return UNKNOWN}sub max_state_alt {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return OK if grep {$_==OK}@_;return UNKNOWN}sub plugin_exit {my ($code,$message,$arg)=@_;if (defined$code && ($code eq 'return_code' || $code eq 'message')){if (int(@_ / 2)!=@_ / 2 && ref $_[$#_]){$arg=pop @_}else {undef$arg}my%arg=@_;$code=$arg{return_code};$message=$arg{message}}$arg ||= {};$code=$ERRORS{$code}if defined$code && exists$ERRORS{$code};$code=UNKNOWN unless defined$code && exists$STATUS_TEXT{$code};$message='' unless defined$message;if (ref$message && ref$message eq 'ARRAY'){$message=join(' ',map {chomp;$_}@$message)}else {chomp$message}my$output="$STATUS_TEXT{$code}";if (defined$message && $message ne ''){$output .= " - " unless$message =~ /^\s*\n/mxs;$output .= $message}my$shortname=($arg->{plugin}? $arg->{plugin}->shortname : undef);$shortname ||= get_shortname();$output="$shortname $output" if$shortname;if ($arg->{plugin}){my$plugin=$arg->{plugin};$output .= " | ".$plugin->all_perfoutput if$plugin->perfdata && $plugin->all_perfoutput}$output .= "\n";if ($_fake_exit){require Monitoring::Plugin::ExitResult;return Monitoring::Plugin::ExitResult->new($code,$output)}_plugin_exit($code,$output)}sub _plugin_exit {my ($code,$output)=@_;if ($_use_die){for (my$i=0;;$i++){@_=caller($i);last unless @_;if ($_[3]=~ m/die/){$!=$code;die($output)}}}print$output;exit$code}sub plugin_die {my ($arg1,$arg2,$rest)=@_;if (defined$arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')){return plugin_exit(@_)}elsif (defined$arg1 && (exists$ERRORS{$arg1}|| exists$STATUS_TEXT{$arg1})){return plugin_exit(@_)}elsif (defined$arg2 && (exists$ERRORS{$arg2}|| exists$STATUS_TEXT{$arg2})){return plugin_exit($arg2,$arg1,$rest)}else {return plugin_exit(UNKNOWN,$arg1,$arg2)}}sub die {plugin_die(@_)}sub convert {my ($value,$from,$to)=@_;my ($newval)=Math::Calc::Units::convert("$value $from",$to,'exact');return$newval}sub check_messages {my%arg=validate(@_,{critical=>{type=>ARRAYREF },warning=>{type=>ARRAYREF },ok=>{type=>ARRAYREF | SCALAR,optional=>1 },'join'=>{default=>' ' },join_all=>0,});$arg{join}=' ' unless defined$arg{join};my$code=OK;$code ||= CRITICAL if @{$arg{critical}};$code ||= WARNING if @{$arg{warning}};return$code unless wantarray;my$message='';if ($arg{join_all}){$message=join($arg{join_all},map {@$_ ? join($arg{'join'},@$_): ()}$arg{critical},$arg{warning},$arg{ok}? (ref$arg{ok}? $arg{ok}: [$arg{ok}]): [])}else {$message ||= join($arg{'join'},@{$arg{critical}})if$code==CRITICAL;$message ||= join($arg{'join'},@{$arg{warning}})if$code==WARNING;$message ||= ref$arg{ok}? join($arg{'join'},@{$arg{ok}}): $arg{ok}if$arg{ok}}return ($code,$message)}1; +MONITORING_PLUGIN_FUNCTIONS + +$fatpacked{"Monitoring/Plugin/Getopt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_GETOPT'; +  package Monitoring::Plugin::Getopt;use 5.006;use strict;use warnings;use File::Basename;use Getopt::Long qw(:config no_ignore_case bundling);use Carp;use Params::Validate qw(:all);use base qw(Class::Accessor);use Monitoring::Plugin::Functions;use Monitoring::Plugin::Config;use vars qw($VERSION);$VERSION=$Monitoring::Plugin::Functions::VERSION;my%DEFAULT=(timeout=>15,verbose=>0,license=>"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. +  It may be used, redistributed and/or modified under the terms of the GNU +  General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",);my@ARGS=({spec=>'usage|?',help=>"-?, --usage\n   Print usage information",},{spec=>'help|h',help=>"-h, --help\n   Print detailed help screen",},{spec=>'version|V',help=>"-V, --version\n   Print version information",},{spec=>'extra-opts:s@',help=>"--extra-opts=[section][\@file]\n   Read options from an ini file. See https://www.monitoring-plugins.org/doc/extra-opts.html\n   for usage and examples.",},{spec=>'timeout|t=i',help=>"-t, --timeout=INTEGER\n   Seconds before plugin times out (default: %s)",default=>$DEFAULT{timeout},},{spec=>'verbose|v+',help=>"-v, --verbose\n   Show details for command-line debugging (can repeat up to 3 times)",default=>$DEFAULT{verbose},},);my%DEFER_ARGS=map {$_=>1}qw(timeout verbose);sub _die {my$self=shift;my ($msg)=@_;$msg .= "\n" unless substr($msg,-1)eq "\n";Monitoring::Plugin::Functions::_plugin_exit(3,$msg)}sub _attr {my$self=shift;my ($item,$extra)=@_;$extra='' unless defined$extra;return '' unless$self->{_attr}->{$item};$self->{_attr}->{$item}."\n" .$extra}sub _spec_to_help {my ($self,$spec,$label)=@_;my ($opts,$type)=split /=|:|!/,$spec,2;my$optional=($spec =~ m/:/);my$boolean=($spec =~ m/!/);my (@short,@long);for (split /\|/,$opts){if (length $_==1){push@short,"-$_"}else {push@long,$boolean ? "--[no-]$_" : "--$_"}}my$help=join(', ',@short,@long);if ($type){if (!$label){if ($type eq 'i' || $type eq '+' || $type =~ /\d+/){$label='INTEGER'}else {$label='STRING'}}if ($optional){$help .= '[=' .$label .']'}else {$help .= '=' .$label}}elsif ($label){carp "Label specified, but there's no type in spec '$spec'"}$help .= "\n   ";return$help}sub _options {my$self=shift;my@args=();my@defer=();for (@{$self->{_args}}){if (exists$DEFER_ARGS{$_->{name}}){push@defer,$_}else {push@args,$_}}my@options=();for my$arg (@args,@defer){my$help_array=ref$arg->{help}&& ref$arg->{help}eq 'ARRAY' ? $arg->{help}: [$arg->{help}];my$label_array=$arg->{label}&& ref$arg->{label}&& ref$arg->{label}eq 'ARRAY' ? $arg->{label}: [$arg->{label}];my$help_string='';for (my$i=0;$i <= $#$help_array;$i++){my$help=$help_array->[$i];if ($help =~ m/^\s*-/){$help_string .= $help}else {$help_string .= $self->_spec_to_help($arg->{spec},$label_array->[$i]).$help;$help_string .= "\n " if$i < $#$help_array}}if ($help_string =~ m/%s/){my$default=defined$arg->{default}? $arg->{default}: '';my$replaced=$help_string;$replaced =~ s|%s|$default|gmx;push@options,$replaced}else {push@options,$help_string}}return ' ' .join("\n ",@options)}sub _usage {my$self=shift;my$usage=$self->_attr('usage');$usage =~ s|%s|$self->{_attr}->{plugin}|gmx;return($usage)}sub _revision {my$self=shift;my$revision=sprintf "%s %s",$self->{_attr}->{plugin},$self->{_attr}->{version};$revision .= sprintf " [%s]",$self->{_attr}->{url}if$self->{_attr}->{url};$revision .= "\n";$revision}sub _help {my$self=shift;my$help='';$help .= $self->_revision ."\n";$help .= $self->_attr('license',"\n");$help .= $self->_attr('blurb',"\n");$help .= $self->_usage ? $self->_usage ."\n" : '';$help .= $self->_options ? $self->_options ."\n" : '';$help .= $self->_attr('extra',"\n");return$help}sub _process_specs_getopt_long {my$self=shift;my@opts=();for my$arg (@{$self->{_args}}){push@opts,$arg->{spec};my$spec=$arg->{spec};$spec =~ s/[=:!].*$//;my$name=(split /\s*\|\s*/,$spec)[0];$arg->{name}=$name;if (defined$self->{$name}){$arg->{default}=$self->{$name}}else {$self->{$name}=$arg->{default}}}return@opts}sub _check_required_opts {my$self=shift;my@missing=();for my$arg (@{$self->{_args}}){if ($arg->{required}&&!defined$self->{$arg->{name}}){push@missing,$arg->{name}}}if (@missing){$self->_die($self->_usage ."\n" .join("\n",map {sprintf "Missing argument: %s",$_}@missing)."\n")}}sub _process_opts {my$self=shift;$self->_die($self->_usage)if$self->{usage};$self->_die($self->_revision)if$self->{version};$self->_die($self->_help)if$self->{help}}sub _load_config_section {my$self=shift;my ($section,$file,$flags)=@_;$section ||= $self->{_attr}->{plugin};my$Config;eval {$Config=Monitoring::Plugin::Config->read($file)};$self->_die($@)if ($@);defined$Config or $self->_die(Monitoring::Plugin::Config->errstr);$file ||= $Config->mp_getfile();$self->_die("Invalid section '$section' in config file '$file'")unless exists$Config->{$section};return$Config->{$section}}sub _setup_spec_index {my$self=shift;return if defined$self->{_spec};$self->{_spec}={map {$_->{name}=>$_->{spec}}@{$self->{_args}}}}sub _cmdline_value {my$self=shift;local $_=shift;if (m/\s/ && (m/^[^"']/ || m/[^"']$/)){return qq("$_")}elsif ($_ eq ''){return q("")}else {return $_}}sub _cmdline {my$self=shift;my ($hash)=@_;$hash ||= $self;$self->_setup_spec_index;my@args=();for my$key (sort keys %$hash){next if$key =~ m/^_/;next if exists$DEFAULT{$key}&& $hash->{$key}eq $DEFAULT{$key};next if grep {$key eq $_}qw(help usage version extra-opts);next unless defined$hash->{$key};my$spec=$self->{_spec}->{$key}|| '';if ($spec =~ m/[=:].+$/){for my$value (ref$hash->{$key}eq 'ARRAY' ? @{$hash->{$key}}: ($hash->{$key})){$value=$self->_cmdline_value($value);if (length($key)> 1){push@args,sprintf "--%s=%s",$key,$value}else {push@args,"-$key",$value}}}else {push@args,(length($key)> 1 ? '--' : '-').$key}}return wantarray ? @args : join(' ',@args)}sub _process_extra_opts {my$self=shift;my ($args)=@_;my$extopts_list=$args->{'extra-opts'};my@sargs=();for my$extopts (@$extopts_list){$extopts ||= $self->{_attr}->{plugin};my$section=$extopts;my$file='';if ($extopts =~ m/^([^@]*)@(.*?)\s*$/){$section=$1;$file=$2}my$shash=$self->_load_config_section($section,$file);push@sargs,$self->_cmdline($shash)}@ARGV=(@sargs,@{$self->{_attr}->{argv}});printf "[extra-opts] %s %s\n",$self->{_attr}->{plugin},join(' ',@ARGV)if$args->{verbose}&& $args->{verbose}>= 3}sub arg {my$self=shift;my%args;my%params=(spec=>1,help=>1,default=>0,required=>0,label=>0,);if (exists$params{$_[0]}&& @_ % 2==0){%args=validate(@_,\%params)}else {my@order=qw(spec help default required label);@args{@order}=validate_pos(@_,@params{@order})}push @{$self->{_args}},\%args}sub getopts {my$self=shift;my@opt_array=$self->_process_specs_getopt_long;$self->{_attr}->{argv}=[@ARGV ];my$args1={};my$ok=GetOptions($args1,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_extra_opts($args1);$ok=GetOptions($self,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_opts;$self->_check_required_opts;$self->mk_ro_accessors(grep!/^_/,keys %$self);$SIG{ALRM}=sub {my$plugin=uc$self->{_attr}->{plugin};$plugin =~ s/^CHECK[-_]//i;$self->_die(sprintf("%s UNKNOWN - plugin timed out (timeout %ss)",$plugin,$self->timeout))}}sub _init {my$self=shift;my$plugin=basename($ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);my%attr=validate(@_,{usage=>1,version=>0,url=>0,plugin=>{default=>$plugin },blurb=>0,extra=>0,'extra-opts'=>0,license=>{default=>$DEFAULT{license}},timeout=>{default=>$DEFAULT{timeout}},});$self->{timeout}=delete$attr{timeout};$self->{_attr}={%attr };chomp foreach values %{$self->{_attr}};$self->{_args}=[@ARGS ];$self}sub new {my$class=shift;my$self=bless {},$class;$self->_init(@_)}1; +MONITORING_PLUGIN_GETOPT + +$fatpacked{"Monitoring/Plugin/Performance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_PERFORMANCE'; +  package Monitoring::Plugin::Performance;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_ro_accessors(qw(label value uom warning critical min max));use Monitoring::Plugin::Functions;use Monitoring::Plugin::Threshold;use Monitoring::Plugin::Range;our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub import {my ($class,%attr)=@_;$_=$attr{use_die}|| 0;Monitoring::Plugin::Functions::_use_die($_)}my$value=qr/[-+]?[\d\.,]+/;my$value_re=qr/$value(?:e$value)?/;my$value_with_negative_infinity=qr/$value_re|~/;sub _parse {my$class=shift;my$string=shift;$string =~ /^'?([^'=]+)'?=($value_re)([\w%]*);?($value_with_negative_infinity\:?$value_re?)?;?($value_with_negative_infinity\:?$value_re?)?;?($value_re)?;?($value_re)?/o;return undef unless ((defined $1 && $1 ne "")&& (defined $2 && $2 ne ""));my@info=($1,$2,$3,$4,$5,$6,$7);map {defined$info[$_]&& $info[$_]=~ s/,/./go}(1,3,4,5,6);my$performance_value;{my$not_value;local$SIG{__WARN__}=sub {$not_value++};$performance_value=$info[1]+0;return undef if$not_value}my$p=$class->new(label=>$info[0],value=>$performance_value,uom=>$info[2],warning=>$info[3],critical=>$info[4],min=>$info[5],max=>$info[6]);return$p}sub _nvl {my ($self,$value)=@_;defined$value ? $value : ''}sub perfoutput {my$self=shift;my$label=$self->label;if ($label =~ / /){$label="'$label'"}my$value=$self->value;if ($value eq ''){$value='U'}my$out=sprintf "%s=%s%s;%s;%s;%s;%s",$label,$value,$self->_nvl($self->uom),$self->_nvl($self->warning),$self->_nvl($self->critical),$self->_nvl($self->min),$self->_nvl($self->max);$out =~ s/;;$//;return$out}sub parse_perfstring {my ($class,$perfstring)=@_;my@perfs=();my$obj;while ($perfstring){$perfstring =~ s/^\s*//;if (@{[$perfstring =~ /=/g]}> 1){$perfstring =~ s/^(.*?=.*?)\s//;if (defined $1){$obj=$class->_parse($1)}else {$perfstring="";$obj=$class->_parse($perfstring)}}else {$obj=$class->_parse($perfstring);$perfstring=""}push@perfs,$obj if$obj}return@perfs}sub rrdlabel {my$self=shift;my$name=$self->clean_label;return substr($name,0,19)}sub clean_label {my$self=shift;my$name=$self->label;if ($name eq "/"){$name="root"}elsif ($name =~ s/^\///){$name =~ s/\//_/g}$name =~ s/\W/_/g;return$name}sub threshold {my$self=shift;return Monitoring::Plugin::Threshold->set_thresholds(warning=>$self->warning,critical=>$self->critical)}sub new {my$class=shift;my%arg=@_;if (my$threshold=delete$arg{threshold}){$arg{warning}||= $threshold->warning ."";$arg{critical}||= $threshold->critical .""}$class->SUPER::new(\%arg)}1; +MONITORING_PLUGIN_PERFORMANCE + +$fatpacked{"Monitoring/Plugin/Range.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_RANGE'; +  package Monitoring::Plugin::Range;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(start end start_infinity end_infinity alert_on));use Monitoring::Plugin::Functions qw(:DEFAULT $value_re);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;use overload 'eq'=>sub {shift->_stringify},'""'=>sub {shift->_stringify};use constant OUTSIDE=>0;use constant INSIDE=>1;sub _stringify {my$self=shift;return "" unless$self->is_set;return (($self->alert_on)? "@" : "").(($self->start_infinity==1)? "~:" : (($self->start==0)?"":$self->start.":")).(($self->end_infinity==1)? "" : $self->end)}sub is_set {my$self=shift;(!defined$self->alert_on)? 0 : 1}sub _set_range_start {my ($self,$value)=@_;$self->start($value+0);$self->start_infinity(0)}sub _set_range_end {my ($self,$value)=@_;$self->end($value+0);$self->end_infinity(0)}sub parse_range_string {my ($class,$string)=@_;my$valid=0;my$range=$class->new(start=>0,start_infinity=>0,end=>0,end_infinity=>1,alert_on=>OUTSIDE);$string =~ s/\s//g;unless ($string =~ /[\d~]/ && $string =~ m/^\@?($value_re|~)?(:($value_re)?)?$/){carp "invalid range definition '$string'";return undef}if ($string =~ s/^\@//){$range->alert_on(INSIDE)}if ($string =~ s/^~//){$range->start_infinity(1)}if ($string =~ m/^($value_re)?:/){my$start=$1;$range->_set_range_start($start)if defined$start;$range->end_infinity(1);$string =~ s/^($value_re)?://;$valid++}if ($string =~ /^($value_re)$/){$range->_set_range_end($string);$valid++}if ($valid && ($range->start_infinity==1 || $range->end_infinity==1 || $range->start <= $range->end)){return$range}return undef}sub check_range {my ($self,$value)=@_;my$false=0;my$true=1;if ($self->alert_on==INSIDE){$false=1;$true=0}if ($self->end_infinity==0 && $self->start_infinity==0){if ($self->start <= $value && $value <= $self->end){return$false}else {return$true}}elsif ($self->start_infinity==0 && $self->end_infinity==1){if ($value >= $self->start){return$false}else {return$true}}elsif ($self->start_infinity==1 && $self->end_infinity==0){if ($value <= $self->end){return$false}else {return$true}}else {return$false}}sub new {shift->SUPER::new({@_})}1; +MONITORING_PLUGIN_RANGE + +$fatpacked{"Monitoring/Plugin/Threshold.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_THRESHOLD'; +  package Monitoring::Plugin::Threshold;use 5.006;use strict;use warnings;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(warning critical));use Monitoring::Plugin::Range;use Monitoring::Plugin::Functions qw(:codes plugin_die);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub get_status {my ($self,$value)=@_;$value=[$value ]if (ref$value eq "");for my$v (@$value){if ($self->critical->is_set){return CRITICAL if$self->critical->check_range($v)}}for my$v (@$value){if ($self->warning->is_set){return WARNING if$self->warning->check_range($v)}}return OK}sub _inflate {my ($self,$value,$key)=@_;return Monitoring::Plugin::Range->new if!defined$value;if (ref$value){plugin_die("Invalid $key object: type " .ref$value)unless$value->isa("Monitoring::Plugin::Range");return$value}return Monitoring::Plugin::Range->new if$value eq "";my$range=Monitoring::Plugin::Range->parse_range_string($value);plugin_die("Cannot parse $key range: '$value'")unless(defined($range));return$range}sub set_thresholds {my ($self,%arg)=@_;return$self->new(%arg)unless ref$self;$self->set($_,$arg{$_})foreach qw(warning critical)}sub set {my$self=shift;my ($key,$value)=@_;$self->SUPER::set($key,$self->_inflate($value,$key))}sub new {my ($self,%arg)=@_;$self->SUPER::new({map {$_=>$self->_inflate($arg{$_},$_)}qw(warning critical)})}1; +MONITORING_PLUGIN_THRESHOLD + +$fatpacked{"Params/Validate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE'; +  package Params::Validate;use 5.008001;use strict;use warnings;our$VERSION='1.29';use Exporter;use Module::Implementation;use Params::Validate::Constants;use vars qw($NO_VALIDATION %OPTIONS $options);our@ISA='Exporter';my@types=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT);our%EXPORT_TAGS=('all'=>[qw(validate validate_pos validation_options validate_with),@types ],types=>\@types,);our@EXPORT_OK=(@{$EXPORT_TAGS{all}},'set_options');our@EXPORT=qw(validate validate_pos);$NO_VALIDATION=$ENV{PERL_NO_VALIDATION};{my$loader=Module::Implementation::build_loader_sub(implementations=>['XS','PP' ],symbols=>[qw(validate validate_pos validate_with validation_options set_options),],);$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP' if$ENV{PV_TEST_PERL};$loader->()}1; +PARAMS_VALIDATE + +$fatpacked{"Params/Validate/Constants.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_CONSTANTS'; +  package Params::Validate::Constants;use strict;use warnings;our$VERSION='1.29';our@ISA='Exporter';our@EXPORT=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT UNKNOWN);sub SCALAR () {1}sub ARRAYREF () {2}sub HASHREF () {4}sub CODEREF () {8}sub GLOB () {16}sub GLOBREF () {32}sub SCALARREF () {64}sub UNKNOWN () {128}sub UNDEF () {256}sub OBJECT () {512}sub HANDLE () {16 | 32}sub BOOLEAN () {1 | 256}1; +PARAMS_VALIDATE_CONSTANTS + +$fatpacked{"Params/Validate/PP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_PP'; +  package Params::Validate::PP;use strict;use warnings;our$VERSION='1.29';use Params::Validate::Constants;use Scalar::Util 1.10 ();our$options;sub validate_pos (\@@) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=shift;my@specs=@_;my@p=@$p;if ($Params::Validate::NO_VALIDATION){for (my$x=$#p + 1;$x <= $#specs;$x++ ){$p[$x]=$specs[$x]->{default}if ref$specs[$x]&& exists$specs[$x]->{default}}return wantarray ? @p : \@p}local$options ||= _get_options((caller(0))[0])unless defined$options;my$min=0;while (1){last unless (ref$specs[$min]?!(exists$specs[$min]->{default}|| $specs[$min]->{optional}): $specs[$min]);$min++}my$max=scalar@specs;my$actual=scalar@p;unless ($actual >= $min && ($options->{allow_extra}|| $actual <= $max)){my$minmax=($options->{allow_extra}? "at least $min" : ($min!=$max ? "$min - $max" : $max));my$val=$options->{allow_extra}? $min : $max;$minmax .= $val!=1 ? ' were' : ' was';my$called=_get_called();$options->{on_fail}->("$actual parameter" .($actual!=1 ? 's' : '')." " .($actual!=1 ? 'were' : 'was')." passed to $called but $minmax expected\n")}my$bigger=$#p > $#specs ? $#p : $#specs;for (0 .. $bigger){my$spec=$specs[$_];next unless ref$spec;if ($_ <= $#p){_validate_one_param($p[$_],\@p,$spec,'Parameter #' .($_ + 1).' (%s)')}$p[$_]=$spec->{default}if $_ > $#p && exists$spec->{default}}_validate_pos_depends(\@p,\@specs);for (grep {defined$p[$_]&&!ref$p[$_]&& ref$specs[$_]&& $specs[$_]{untaint}}0 .. $bigger){($p[$_])=$p[$_]=~ /(.+)/}return wantarray ? @p : \@p}sub _validate_pos_depends {my ($p,$specs)=@_;for my$p_idx (0 .. $#$p){my$spec=$specs->[$p_idx];next unless$spec && UNIVERSAL::isa($spec,'HASH')&& exists$spec->{depends};my$depends=$spec->{depends};if (ref$depends){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' for validate_pos() must be a scalar")}my$p_size=scalar @$p;if ($p_size < $depends - 1){my$error =("Parameter #" .($p_idx + 1)." depends on parameter #" .$depends .", which was not given");$options->{on_fail}->($error)}}return 1}sub _validate_named_depends {my ($p,$specs)=@_;for my$pname (keys %$p){my$spec=$specs->{$pname};next unless$spec && UNIVERSAL::isa($spec,'HASH')&& $spec->{depends};unless (UNIVERSAL::isa($spec->{depends},'ARRAY')||!ref$spec->{depends}){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' must be a scalar or arrayref")}for my$depends_name (ref$spec->{depends}? @{$spec->{depends}}: $spec->{depends}){unless (exists$p->{$depends_name}){my$error =("Parameter '$pname' depends on parameter '" .$depends_name ."', which was not given");$options->{on_fail}->($error)}}}}sub validate (\@$) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=$_[0];my$specs=$_[1];local$options=_get_options((caller(0))[0])unless defined$options;if (ref$p eq 'ARRAY'){if (ref$p->[0]){$p={%{$p->[0]}}}elsif (@$p % 2){my$called=_get_called();$options->{on_fail}->("Odd number of parameters in call to $called " ."when named parameters were expected\n")}else {$p={@$p}}}if ($options->{normalize_keys}){$specs=_normalize_callback($specs,$options->{normalize_keys});$p=_normalize_callback($p,$options->{normalize_keys})}elsif ($options->{ignore_case}|| $options->{strip_leading}){$specs=_normalize_named($specs);$p=_normalize_named($p)}if ($Params::Validate::NO_VALIDATION){return (wantarray ? ((map {$_=>$specs->{$_}->{default}}grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs),(ref$p eq 'ARRAY' ? (ref$p->[0]? %{$p->[0]}: @$p): %$p)): do {my$ref=(ref$p eq 'ARRAY' ? (ref$p->[0]? $p->[0]: {@$p}): $p);for (grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs){$ref->{$_}=$specs->{$_}->{default}unless exists$ref->{$_}}return$ref})}_validate_named_depends($p,$specs);unless ($options->{allow_extra}){if (my@unmentioned=grep {!exists$specs->{$_}}keys %$p){my$called=_get_called();$options->{on_fail}->("The following parameter" .(@unmentioned > 1 ? 's were' : ' was')." passed in the call to $called but " .(@unmentioned > 1 ? 'were' : 'was')." not listed in the validation options: @unmentioned\n")}}my@missing;keys %$specs;OUTER: while (my ($key,$spec)=each %$specs){if (!exists$p->{$key}&& (ref$spec ?!(do {if (exists$spec->{default}){$p->{$key}=$spec->{default};next OUTER}}|| do {next OUTER if$spec->{optional}}): $spec)){push@missing,$key}elsif (ref$spec){my$value=defined$p->{$key}? qq|"$p->{$key}"| : 'undef';_validate_one_param($p->{$key},$p,$spec,qq{The '$key' parameter (%s)})}}if (@missing){my$called=_get_called();my$missing=join ', ',map {"'$_'"}sort@missing;$options->{on_fail}->("Mandatory parameter" .(@missing > 1 ? 's' : '')." $missing missing in call to $called\n")}for my$key (grep {defined$p->{$_}&&!ref$p->{$_}&& ref$specs->{$_}&& $specs->{$_}{untaint}}keys %$p){($p->{$key})=$p->{$key}=~ /(.+)/}return wantarray ? %$p : $p}sub validate_with {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my%p=@_;local$options=_get_options((caller(0))[0],%p);unless ($Params::Validate::NO_VALIDATION){unless (exists$options->{called}){$options->{called}=(caller($options->{stack_skip}))[3]}}if (UNIVERSAL::isa($p{spec},'ARRAY')){return validate_pos(@{$p{params}},@{$p{spec}})}else {return&validate($p{params},$p{spec})}}sub _normalize_callback {my ($p,$func)=@_;my%new;for my$key (keys %$p){my$new_key=$func->($key);unless (defined$new_key){die "The normalize_keys callback did not return a defined value when normalizing the key '$key'"}if (exists$new{$new_key}){die "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'"}$new{$new_key}=$p->{$key}}return \%new}sub _normalize_named {my%h=(ref $_[0])=~ /ARRAY/ ? @{$_[0]}: %{$_[0]};if ($options->{ignore_case}){$h{lc $_ }=delete$h{$_}for keys%h}if ($options->{strip_leading}){for my$key (keys%h){my$new;($new=$key)=~ s/^\Q$options->{strip_leading}\E//;$h{$new}=delete$h{$key}}}return \%h}my%Valid=map {$_=>1}qw(callbacks can default depends isa optional regex type untaint);sub _validate_one_param {my ($value,$params,$spec,$id)=@_;if (exists$spec->{type}){unless (defined$spec->{type}&& Scalar::Util::looks_like_number($spec->{type})&& $spec->{type}> 0){my$msg ="$id has a type specification which is not a number. It is ";if (defined$spec->{type}){$msg .= "a string - $spec->{type}"}else {$msg .= "undef"}$msg .= ".\n Use the constants exported by Params::Validate to declare types.";$options->{on_fail}->(sprintf($msg,_stringify($value)))}unless (_get_type($value)& $spec->{type}){my$type=_get_type($value);my@is=_typemask_to_strings($type);my@allowed=_typemask_to_strings($spec->{type});my$article=$is[0]=~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was $article '@is', which " ."is not one of the allowed types: @allowed\n",_stringify($value)))}}return unless ($spec->{isa}|| $spec->{can}|| $spec->{callbacks}|| $spec->{regex});if (exists$spec->{isa}){for (ref$spec->{isa}? @{$spec->{isa}}: $spec->{isa}){unless (do {local $@=q{};eval {$value->isa($_)}}){my$is=ref$value ? ref$value : 'plain scalar';my$article1=$_ =~ /^[aeiou]/i ? 'an' : 'a';my$article2=$is =~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was not $article1 '$_' " ."(it is $article2 $is)\n",_stringify($value)))}}}if (exists$spec->{can}){for (ref$spec->{can}? @{$spec->{can}}: $spec->{can}){unless (do {local $@=q{};eval {$value->can($_)}}){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called does not have the method: '$_'\n",_stringify($value)))}}}if ($spec->{callbacks}){unless (UNIVERSAL::isa($spec->{callbacks},'HASH')){my$called=_get_called(1);$options->{on_fail}->("'callbacks' validation parameter for $called must be a hash reference\n")}for (keys %{$spec->{callbacks}}){unless (UNIVERSAL::isa($spec->{callbacks}{$_},'CODE')){my$called=_get_called(1);$options->{on_fail}->("callback '$_' for $called is not a subroutine reference\n")}my$ok;my$e=do {local $@=q{};local$SIG{__DIE__};$ok=eval {$spec->{callbacks}{$_}->($value,$params)};$@};if (!$ok){my$called=_get_called(1);if (ref$e){$options->{on_fail}->($e)}else {my$msg="$id to $called did not pass the '$_' callback";$msg .= ": $e" if length$e;$msg .= "\n";$options->{on_fail}->(sprintf($msg,_stringify($value)))}}}}if (exists$spec->{regex}){unless ((defined$value ? $value : '')=~ /$spec->{regex}/){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called did not pass regex check\n",_stringify($value)))}}}{my%isas=('ARRAY'=>ARRAYREF,'HASH'=>HASHREF,'CODE'=>CODEREF,'GLOB'=>GLOBREF,'SCALAR'=>SCALARREF,'REGEXP'=>SCALARREF,);my%simple_refs=map {$_=>1}keys%isas;sub _get_type {return UNDEF unless defined $_[0];my$ref=ref $_[0];unless ($ref){return GLOB if UNIVERSAL::isa(\$_[0],'GLOB');return SCALAR}return$isas{$ref}if$simple_refs{$ref};for (keys%isas){return$isas{$_}| OBJECT if UNIVERSAL::isa($_[0],$_)}return UNKNOWN}}{my%type_to_string=(SCALAR()=>'scalar',ARRAYREF()=>'arrayref',HASHREF()=>'hashref',CODEREF()=>'coderef',GLOB()=>'glob',GLOBREF()=>'globref',SCALARREF()=>'scalarref',UNDEF()=>'undef',OBJECT()=>'object',UNKNOWN()=>'unknown',);sub _typemask_to_strings {my$mask=shift;my@types;for (SCALAR,ARRAYREF,HASHREF,CODEREF,GLOB,GLOBREF,SCALARREF,UNDEF,OBJECT,UNKNOWN){push@types,$type_to_string{$_}if$mask & $_}return@types ? @types : ('unknown')}}{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>sub {require Carp;Carp::croak($_[0])},stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}sub _get_options {my$caller=shift;if (@_){return ($Params::Validate::OPTIONS{$caller}? {%{$Params::Validate::OPTIONS{$caller}},@_ }: {%defaults,@_ })}else {return (exists$Params::Validate::OPTIONS{$caller}? $Params::Validate::OPTIONS{$caller}: \%defaults)}}}sub _get_called {my$extra_skip=$_[0]|| 0;$extra_skip++;my$called=(exists$options->{called}? $options->{called}: (caller($options->{stack_skip}+ $extra_skip))[3]);$called='(unknown)' unless defined$called;return$called}sub _stringify {return defined $_[0]? qq{"$_[0]"} : 'undef'}1; +PARAMS_VALIDATE_PP + +$fatpacked{"Params/Validate/XS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_XS'; +  package Params::Validate::XS;use strict;use warnings;our$VERSION='1.29';use Carp;my$default_fail=sub {Carp::confess($_[0])};{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>$default_fail,stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}use XSLoader;XSLoader::load(__PACKAGE__,exists$Params::Validate::XS::{VERSION}? ${$Params::Validate::XS::{VERSION}}: (),)}sub _check_regex_from_xs {return (defined $_[0]? $_[0]: '')=~ /$_[1]/ ? 1 : 0}1; +PARAMS_VALIDATE_XS + +$fatpacked{"Params/ValidatePP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEPP'; +  package Params::Validate;our$VERSION='1.29';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP'}use Params::Validate;1; +PARAMS_VALIDATEPP + +$fatpacked{"Params/ValidateXS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEXS'; +  package Params::Validate;our$VERSION='1.29';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='XS'}use Params::Validate;1; +PARAMS_VALIDATEXS + +$fatpacked{"Try/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TRY_TINY'; +  package Try::Tiny;use 5.006;our$VERSION='0.30';use strict;use warnings;use Exporter 5.57 'import';our@EXPORT=our@EXPORT_OK=qw(try catch finally);use Carp;$Carp::Internal{+__PACKAGE__}++;BEGIN {my$su=$INC{'Sub/Util.pm'}&& defined&Sub::Util::set_subname;my$sn=$INC{'Sub/Name.pm'}&& eval {Sub::Name->VERSION(0.08)};unless ($su || $sn){$su=eval {require Sub::Util}&& defined&Sub::Util::set_subname;unless ($su){$sn=eval {require Sub::Name;Sub::Name->VERSION(0.08)}}}*_subname=$su ? \&Sub::Util::set_subname : $sn ? \&Sub::Name::subname : sub {$_[1]};*_HAS_SUBNAME=($su || $sn)? sub(){1}: sub(){0}}my%_finally_guards;sub try (&;@) {my ($try,@code_refs)=@_;my$wantarray=wantarray;my ($catch,@finally)=();for my$code_ref (@code_refs){if (ref($code_ref)eq 'Try::Tiny::Catch'){croak 'A try() may not be followed by multiple catch() blocks' if$catch;$catch=${$code_ref}}elsif (ref($code_ref)eq 'Try::Tiny::Finally'){push@finally,${$code_ref}}else {croak('try() encountered an unexpected argument (' .(defined$code_ref ? $code_ref : 'undef').') - perhaps a missing semi-colon before or')}}_subname(caller().'::try {...} '=>$try)if _HAS_SUBNAME;local$_finally_guards{guards}=[map {Try::Tiny::ScopeGuard->_new($_)}@finally ];my$prev_error=$@;my (@ret,$error);my$failed=not eval {$@=$prev_error;if ($wantarray){@ret=$try->()}elsif (defined$wantarray){$ret[0]=$try->()}else {$try->()};return 1};$error=$@;$@=$prev_error;if ($failed){push @$_,$error for @{$_finally_guards{guards}};if ($catch){for ($error){return$catch->($error)}}return}else {return$wantarray ? @ret : $ret[0]}}sub catch (&;@) {my ($block,@rest)=@_;croak 'Useless bare catch()' unless wantarray;_subname(caller().'::catch {...} '=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Catch'),@rest,)}sub finally (&;@) {my ($block,@rest)=@_;croak 'Useless bare finally()' unless wantarray;_subname(caller().'::finally {...} '=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Finally'),@rest,)}{package Try::Tiny::ScopeGuard;use constant UNSTABLE_DOLLARAT=>("$]" < '5.013002')? 1 : 0;sub _new {shift;bless [@_ ]}sub DESTROY {my ($code,@args)=@{$_[0]};local $@ if UNSTABLE_DOLLARAT;eval {$code->(@args);1}or do {warn "Execution of finally() block $code resulted in an exception, which " .'*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. ' .'Your program will continue as if this event never took place. ' ."Original exception text follows:\n\n" .(defined $@ ? $@ : '$@ left undefined...')."\n" }}}__PACKAGE__  +TRY_TINY + +s/^  //mg for values %fatpacked; + +my $class = 'FatPacked::'.(0+\%fatpacked); +no strict 'refs'; +*{"${class}::files"} = sub { keys %{$_[0]} }; + +if ($] < 5.008) { +  *{"${class}::INC"} = sub { +    if (my $fat = $_[0]{$_[1]}) { +      my $pos = 0; +      my $last = length $fat; +      return (sub { +        return 0 if $pos == $last; +        my $next = (1 + index $fat, "\n", $pos) || $last; +        $_ .= substr $fat, $pos, $next - $pos; +        $pos = $next; +        return 1; +      }); +    } +  }; +} + +else { +  *{"${class}::INC"} = sub { +    if (my $fat = $_[0]{$_[1]}) { +      open my $fh, '<', \$fat +        or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; +      return $fh; +    } +    return; +  }; +} + +unshift @INC, bless \%fatpacked, $class; +  } # END OF FATPACK CODE + +# +# This is development version of the check_raid.pl plugin +# If you are running this file directly, you are doing it wrong +# +# See installation notes: +# https://github.com/glensc/nagios-plugin-check_raid#installing +# +use Monitoring::Plugin 0.37; +use App::Monitoring::Plugin::CheckRaid; +use App::Monitoring::Plugin::CheckRaid::Sudoers; +use App::Monitoring::Plugin::CheckRaid::Plugin; +use App::Monitoring::Plugin::CheckRaid::Utils; +use warnings; +use strict; + +my $PROGNAME = 'check_raid'; +my $VERSION = q/4.0.9/; +my $URL = 'https://github.com/glensc/nagios-plugin-check_raid'; +my $BUGS_URL = 'https://github.com/glensc/nagios-plugin-check_raid#reporting-bugs'; + +my $mp = Monitoring::Plugin->new( +	usage => +		"Usage: %s [-h] [-V] [-S] [list of devices to ignore]", + +	version => $VERSION, +	blurb => join($/, +		"This plugin checks all RAID volumes (hardware and software) that can be identified", +		"", +		"Homepage: $URL", +		"Reporting Bugs: $BUGS_URL", +	), + +	plugin  => $PROGNAME, +	shortname => $PROGNAME, +); + +$mp->add_arg( +	spec => 'sudoers|S', +	help => 'Setup sudo rules', +); +$mp->add_arg( +	spec => 'warnonly|W', +	help => 'Treat CRITICAL errors as WARNING', +); +$mp->add_arg( +	spec => 'debug|d', +	help => 'debug mode, or dry-run for sudoers', +); +$mp->add_arg( +	spec => 'list_plugins|list-plugins|l', +	help => 'Lists active plugins', +); +$mp->add_arg( +	spec => 'plugin|p=s@', +	help => 'Force the use of selected plugins, comma separated', +); +$mp->add_arg( +	spec => 'plugin-option=s@', +	help => "Specify extra option for specific plugin.\n" . +' +Plugin options (key=>value pairs) passed as "options" key to each plugin constructor. +The options are global, not plugin specific, but it\'s recommended to prefix option with plugin name. +The convention is to have PLUGIN_NAME-OPTION_NAME=OPTION_VALUE syntax to namespace each plugin option. + +For example "--plugin-option=hp_msa-serial=/dev/ttyS2" +would define option "serial" for "hp_msa" plugin with value "/dev/ttyS2". +' +); +$mp->add_arg( +	spec => 'noraid=s', +	help => 'Return STATE if no RAID volumes are found. Defaults to UNKNOWN', +); +$mp->add_arg( +	spec => 'resync=s', +	help => 'Return STATE if RAID is in resync state. Defaults to WARNING', +); +$mp->add_arg( +	spec => 'check=s', +	help => 'Return STATE if RAID is in check state. Defaults to OK', +); +$mp->add_arg( +	spec => 'cache-fail=s', +	help => 'Set status as STATE if Write Cache is present but disabled. Defaults to WARNING', +); +$mp->add_arg( +	spec => 'bbulearn=s', +	help => 'Return STATE if Backup Battery Unit (BBU) learning cycle is in progress. Defaults to WARNING', +); +$mp->add_arg( +	spec => 'bbu-monitoring', +	help => 'Enable experimental monitoring of the BBU status', +); + +$mp->getopts; + +if (@ARGV) { +	@App::Monitoring::Plugin::CheckRaid::Utils::ignore = @ARGV; +} + +my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3); + +my %plugin_options; + +if ($mp->opts->warnonly) { +	App::Monitoring::Plugin::CheckRaid::Plugin->set_critical_as_warning; +} +if ($mp->opts->get('bbu-monitoring')) { +	$plugin_options{options}{bbu_monitoring} = 1; +} + +# setup state flags +my %state_flags = ( +	'resync' => 'resync_status', +	'check' => 'check_status', +	'noraid' => 'noraid_status', +	'bbulearn' => 'bbulearn_status', +	'cache-fail' => 'cache_fail_status', +); +while (my($opt, $key) = each %state_flags) { +	if (my $value = $mp->opts->get($opt)) { +		unless (exists $ERRORS{$value}) { +			print "Invalid value: '$value' for --$opt\n"; +			exit $ERRORS{UNKNOWN}; +		} +		$plugin_options{options}{$key} = $ERRORS{$value}; +	} +} + +# enable only specified plugins +if (my $plugins = $mp->opts->plugin) { +	# split, as each value can contain commas +	$plugin_options{enable_plugins} = [ map { split(/,/, $_) } @$plugins ]; +} + +if (my $opts = $mp->opts->get('plugin-option')) { +	foreach my $o (@$opts) { +		my($k, $v) = split(/=/, $o, 2); +		$plugin_options{options}{$k} = $v; +	} +} + +my $mc = App::Monitoring::Plugin::CheckRaid->new(%plugin_options); + +$App::Monitoring::Plugin::CheckRaid::Utils::debug = $mp->opts->debug; + +if ($mp->opts->debug) { +	print "$PROGNAME $VERSION\n"; +	print "Visit <$BUGS_URL> how to report bugs\n"; +	print "Please include output of **ALL** commands in bugreport\n\n"; +} + +if ($mp->opts->sudoers) { +	my $res = sudoers($mp->opts->debug, $mc->active_plugins(1)); +	$mp->plugin_exit(OK, $res ? "sudoers updated" : "sudoers not updated"); +} + +my @plugins = $mc->active_plugins; +if (!@plugins) { +	$mp->plugin_exit($plugin_options{options}{noraid_status}, "No active plugins (No RAID found)"); +} + +# print active plugins +if ($mp->opts->list_plugins) { +	foreach my $p (@plugins) { +		print $p->{name}, "\n"; +	} +	my $count = @plugins; +	warn "$count active plugins\n"; +	exit $ERRORS{OK}; +} + +my $message = ''; +my $status = $ERRORS{OK}; + +# perform check of each active plugin +foreach my $plugin (@plugins) { +	# skip if no check method (not standalone checker) +	next unless $plugin->can('check'); + +	# perform the check +	$plugin->check; +	my $pn = $plugin->{name}; + +	# collect results +	unless (defined $plugin->status) { +		$status = $ERRORS{UNKNOWN} if $ERRORS{UNKNOWN} > $status; +		$message .= '; ' if $message; +		$message .= "$pn:[Plugin error]"; +		next; +	} +	if ($plugin->message or $plugin->{options}{noraid_status} == $ERRORS{UNKNOWN}) { +		$status = $plugin->status if $plugin->status > $status; +	} else { +		$status = $plugin->{options}{noraid_status} if $plugin->{options}{noraid_status} > $status; +	} +	$message .= '; ' if $message; +	$message .= "$pn:[".$plugin->message."]"; +	$message .= ' | ' . $plugin->perfdata if $plugin->perfdata; +	$message .= "\n" . $plugin->longoutput if $plugin->longoutput; +} + +if ($message) { +	if ($status == $ERRORS{OK}) { +		print "OK: "; +	} elsif ($status == $ERRORS{WARNING}) { +		print "WARNING: "; +	} elsif ($status == $ERRORS{CRITICAL}) { +		print "CRITICAL: "; +	} else { +		print "UNKNOWN: "; +	} +	print "$message\n"; +} elsif ($plugin::options{noraid_status} != $ERRORS{UNKNOWN}) { +	$status = $plugin::options{noraid_status}; +	print "No RAID configuration found\n"; +} else { +	$status = $ERRORS{UNKNOWN}; +	print "No RAID configuration found (tried: ", join(', ', @plugins), ")\n"; +} +exit $status; diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control new file mode 100644 index 0000000..9ffb706 --- /dev/null +++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/control @@ -0,0 +1,30 @@ +Homepage: https://github.com/glensc/nagios-plugin-check_raid +Watch: https://github.com/glensc/nagios-plugin-check_raid "/glensc/nagios-plugin-check_raid/tree/([0-9.]+)" +Suggests: cciss-vol-status (>= 1.10), mpt-status +Version: 4.0.9 +Uploaders: Bernd Zeimetz <bzed@debian.org> +Description: plugin to check sw/hw RAID status + The plugin looks for any known types of RAID configurations, + and checks them all. + Supports: + - Adaptec AAC RAID via aaccli or afacli or arcconf + - AIX software RAID via lsvg + - HP/Compaq Smart Array via cciss_vol_status (hpsa supported too) + - HP Smart Array Controllers and MSA Controllers via hpacucli + - HP Smart Array (MSA1500) via serial line + - Linux 3ware SATA RAID via tw_cli + - Linux Device Mapper RAID via dmraid + - Linux DPT/I2O hardware RAID controllers via /proc/scsi/dpt_i2o + - Linux GDTH hardware RAID controllers via /proc/scsi/gdth + - Linux LSI MegaRaid hardware RAID via CmdTool2 + - Linux LSI MegaRaid hardware RAID via megarc + - Linux LSI MegaRaid hardware RAID via /proc/megaraid + - Linux MegaIDE hardware RAID controllers via /proc/megaide + - Linux MPT hardware RAID via mpt-status + - Linux software RAID (md) via /proc/mdstat + - LSI Logic MegaRAID SAS series via MegaCli + - LSI MegaRaid via lsraid + - Serveraid IPS via ipssend + - Solaris software RAID via metastat + - Areca SATA RAID Support via cli64/cli32 + - Detecting SCSI devices or hosts with lsscsi diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright new file mode 100644 index 0000000..ef9e592 --- /dev/null +++ b/nagios-plugins-contrib-24.20190301~bpo9+1/check_raid/copyright @@ -0,0 +1,9 @@ +2004-2006 Steve Shipway, university of auckland, +2009-2013 Elan Ruusamäe <glen@pld-linux.org> + +License: GPL v2 + +  On Debian systems, the complete text of the GNU General +  Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + +  | 
