#!/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;
  
  	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;
  
  		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 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 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;
  
  	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 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+)\))?$/) {
  				$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;
  
  	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}};
  
  		# 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(",", @{$md{failed_disks}}) .":$md{status}";
  
  		} else {
  			$s .= "$md{status}";
  		}
  		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);
  }
  
  # get controller from mpt-status -p
  # FIXME: could there be multiple controllers?
  sub get_controller {
  	my $this = shift;
  
  	my $fh = $this->cmd('get_controller_no');
  	my $id;
  	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'],
  		'mvcli smart' => ['-|', '@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 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_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) = /
  				([\dA-F]{2})\s+ # attr
  				(.*?)\s+        # name
  				(\d+)\s+        # current
  				(\d+)\s+        # worst
  				(\d+)\s+        # treshold
  				([\dA-F]+)      # raw
  			/x) {
  				my %attr = ();
  				$attr{id} = $id;
  				$attr{name} = $name;
  				$attr{current} = int($current);
  				$attr{worst} = int($worst);
  				$attr{treshold} = int($treshold);
  				$attr{raw} = $raw;
  				$attrs{$id} = { %attr };
  			} else {
  #				warn "[$_]\n";
  			}
  		}
  
  		$smart{$pd} = { %attrs };
  	}
  
  	return \%smart;
  }
  
  sub parse {
  	my $this = shift;
  
  	my $blk = $this->parse_blk;
  	my $smart = $this->parse_smart($blk);
  
  	return {
  		blk => $blk,
  		smart => $smart,
  	};
  }
  
  sub check {
  	my $this = shift;
  
  	my (@status);
  	my @d = $this->parse;
  
  	# not implemented yet
  	$this->unknown;
  
  	$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/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
  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;
  	}
  
  	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",
  		"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;
  	}
  
  	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";
  	} else {
  		warn "$sudoers file not changed.\n";
  		unlink($new);
  	}
  }
  
  # 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 $!;
  	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.34';sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;$fields={}unless defined$fields;bless {%$fields},$class}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;$Class::Accessor::Fast::VERSION='0.34';sub make_accessor {my($class,$field)=@_;return sub {return $_[0]->{$field}if scalar(@_)==1;return $_[0]->{$field}=scalar(@_)==2 ? $_[1]: [@_[1..$#_]]}}sub make_ro_accessor {my($class,$field)=@_;return sub {return $_[0]->{$field}if @_==1;my$caller=caller;$_[0]->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}}sub make_wo_accessor {my($class,$field)=@_;return sub {if (@_==1){my$caller=caller;$_[0]->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return $_[0]->{$field}=$_[1]if @_==2;return (shift)->{$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;$Class::Accessor::Faster::VERSION='0.34';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);return sub {return $_[0]->[$n]if scalar(@_)==1;return $_[0]->[$n]=scalar(@_)==2 ? $_[1]: [@_[1..$#_]]}}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);return sub {return $_[0]->[$n]if @_==1;my$caller=caller;$_[0]->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);return sub {if (@_==1){my$caller=caller;$_[0]->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return $_[0]->[$n]=$_[1]if @_==2;return (shift)->[$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.014";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.39";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.39";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}";$output .= " - $message" if defined$message && $message ne '';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 (@short,@long);for (split /\|/,$opts){if (length $_==1){push@short,"-$_"}else {push@long,"--$_"}}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 ($@);$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;if ($_[0]=~ m/^(spec|help|required|default)$/ && scalar(@_)% 2==0){%args=validate(@_,{spec=>1,help=>1,default=>0,required=>0,label=>0,})}else {my@args=validate_pos(@_,1,1,0,0,0);%args=(spec=>$args[0],help=>$args[1],default=>$args[2],required=>$args[3],label=>$args[4],)}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_//;$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$out=sprintf "%s=%s%s;%s;%s;%s;%s",$label,$self->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.26';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.26';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.26';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 {"'$_'"}@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.26';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.26';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.26';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.27';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')}}my$caller=caller;_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;my$caller=caller;_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;my$caller=caller;_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.5/;
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->add_arg(
	spec => 'warnonly|W',
	help => 'Treat CRITICAL errors as WARNING',
);

$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\n",
}

my @plugins = $mc->active_plugins;
if (!@plugins) {
	$mp->plugin_exit($plugin_options{options}{noraid_status}, "No active plugins (No RAID found)");
}

if ($mp->opts->sudoers) {
	sudoers($mp->opts->debug, @plugins);
	$mp->plugin_exit(OK, "sudoers updated");
}

# 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;
