#!/usr/bin/perl
# nagios: -epn

# This chunk of stuff was generated by App::FatPacker. To find the original
# file's code, look for the end of this BEGIN block or the string 'FATPACK'
BEGIN {
my %fatpacked;

$fatpacked{"App/Monitoring/Plugin/CheckRaid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID';
  package App::Monitoring::Plugin::CheckRaid;
  
  use Carp qw(croak);
  use Module::Pluggable 5.1 instantiate => 'new', sub_name => '_plugins';
  use strict;
  use warnings;
  
  # constructor
  sub new {
  	my $class = shift;
  
  	croak 'Odd number of elements in argument hash' if @_ % 2;
  
  	my $self = {
  		@_,
  	};
  
  	my $obj = bless $self, $class;
  
  	# setup search path for Module::Pluggable
  	$self->search_path(add => __PACKAGE__ . '::Plugins');
  
  	# setup only certain plugins
  	if ($self->{enable_plugins}) {
  		my @plugins = map {
  			__PACKAGE__ . '::Plugins::' . $_
  		} @{$self->{enable_plugins}};
  		$self->only(\@plugins);
  	}
  
  	return $obj;
  }
  
  # create list of plugins
  sub plugins {
  	my ($this) = @_;
  
  	# call this once
  	if (!defined $this->{plugins}) {
  		my @plugins = $this->_plugins(%$this);
  		$this->{plugins} = \@plugins;
  	}
  
  	wantarray ? @{$this->{plugins}} : $this->{plugins};
  }
  
  # get plugin by name
  sub plugin {
  	my ($this, $name) = @_;
  
  	if (!defined $this->{plugin_names}) {
  		my %names;
  		foreach my $plugin ($this->plugins) {
  			my $name = $plugin->{name};
  			$names{$name} = $plugin;
  		}
  		$this->{plugin_names} = \%names;
  	}
  
  	croak "Plugin '$name' Can not be created" unless exists $this->{plugin_names}{$name};
  
  	$this->{plugin_names}{$name};
  }
  
  # Get active plugins.
  # Returns the plugin objects
  sub active_plugins {
  	my $this = shift;
  	# whether the query is for sudo rules
  	my $sudo = shift || 0;
  
  	my @plugins = ();
  
  	# go over all registered plugins
  	foreach my $plugin ($this->plugins) {
  		# skip if no check method (not standalone checker)
  		next unless $plugin->can('check');
  
  		# skip inactive plugins (disabled or no tools available)
  		next unless $plugin->active($sudo);
  
  		push(@plugins, $plugin);
  	}
  
  	return wantarray ? @plugins : \@plugins;
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN';
  package App::Monitoring::Plugin::CheckRaid::Plugin;
  
  use Carp qw(croak);
  use App::Monitoring::Plugin::CheckRaid::Utils;
  use strict;
  use warnings;
  
  # Nagios standard error codes
  my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3);
  
  # default plugin options
  our %options = (
  	# status to set when RAID is in resync state
  	resync_status => $ERRORS{WARNING},
  
  	# Status code to use when no raid volumes were detected
  	noraid_status => $ERRORS{UNKNOWN},
  
  	# status to set when RAID is in check state
  	check_status => $ERRORS{OK},
  
  	# status to set when PD is spare
  	spare_status => $ERRORS{OK},
  
  	# status to set when BBU is in learning cycle.
  	bbulearn_status => $ERRORS{WARNING},
  
  	# status to set when Write Cache has failed.
  	cache_fail_status => $ERRORS{WARNING},
  
  	# check status of BBU
  	bbu_monitoring => 0,
  );
  
  # return list of programs this plugin needs
  # @internal
  sub program_names {
  }
  
  # return hash of canonical commands that plugin can use
  # @internal
  sub commands {
  	{}
  }
  
  # return sudo rules if program needs it
  # may be SCALAR or LIST of scalars
  # @internal
  sub sudo {
  	();
  }
  
  # constructor for plugins
  sub new {
  	my $class = shift;
  
  	croak 'Odd number of elements in argument hash' if @_ % 2;
  	croak 'Class is already a reference' if ref $class;
  
  	# convert to hash
  	my %args = @_;
  
  	# merge 'options' from param and class defaults
  	my %opts = %options;
  	%opts = (%options, %{$args{options}}) if $args{options};
  	delete $args{options};
  
  	# merge commands
  	my %commands = %{$class->commands};
  	%commands = (%commands, %{$args{commands}}) if $args{commands};
  	delete $args{commands};
  
  	my $self = {
  		commands => \%commands,
  		sudo => $class->sudo ? find_sudo() : '',
  		options => \%opts,
  		%args,
  
  		# name of the plugin, without package namespace
  		name => ($class =~ /.*::([^:]+)$/),
  
  		status => undef,
  		message => undef,
  		perfdata => undef,
  		longoutput => undef,
  	};
  
  	my $this = bless $self, $class;
  
  	# lookup program, if not defined by params
  	if (!$self->{program}) {
  		$self->{program} = which($this->program_names);
  	}
  
  	return $this;
  }
  
  # see if plugin is active (disabled or no tools available)
  sub active {
  	my $this = shift;
  
  	# no tool found, return false
  	return 0 unless $this->{program};
  
  	# program file must exist, don't check for execute bit. #104
  	-f $this->{program};
  }
  
  # set status code for plugin result
  # does not overwrite status with lower value
  # returns the current status code
  sub status {
  	my ($this, $status) = @_;
  
  	if (defined $status) {
  		$this->{status} = $status unless defined($this->{status}) and $status < $this->{status};
  	}
  	$this->{status};
  }
  
  sub set_critical_as_warning {
  	$ERRORS{CRITICAL} = $ERRORS{WARNING};
  }
  
  # helper to set status to WARNING
  # returns $this to allow fluent api
  sub warning {
  	my ($this) = @_;
  	$this->status($ERRORS{WARNING});
  	return $this;
  }
  
  # helper to set status to CRITICAL
  # returns $this to allow fluent api
  sub critical {
  	my ($this) = @_;
  	$this->status($ERRORS{CRITICAL});
  	return $this;
  }
  
  # helper to set status to UNKNOWN
  # returns $this to allow fluent api
  sub unknown {
  	my ($this) = @_;
  	$this->status($ERRORS{UNKNOWN});
  	return $this;
  }
  
  # helper to set status to OK
  sub ok {
  	my ($this) = @_;
  	$this->status($ERRORS{OK});
  	return $this;
  }
  
  # helper to set status for resync
  # returns $this to allow fluent api
  sub resync {
  	my ($this) = @_;
  	$this->status($this->{options}{resync_status});
  	return $this;
  }
  
  # helper to set status for check
  # returns $this to allow fluent api
  sub check_status {
  	my ($this) = @_;
  	$this->status($this->{options}{check_status});
  	return $this;
  }
  
  # helper to set status for no raid condition
  # returns $this to allow fluent api
  sub noraid {
  	my ($this) = @_;
  	$this->status($this->{options}{noraid_status});
  	return $this;
  }
  
  # helper to set status for spare
  # returns $this to allow fluent api
  sub spare {
  	my ($this) = @_;
  	$this->status($this->{options}{spare_status});
  	return $this;
  }
  
  # helper to set status for BBU learning cycle
  # returns $this to allow fluent api
  sub bbulearn {
  	my ($this) = @_;
  	$this->status($this->{options}{bbulearn_status});
  	return $this;
  }
  
  # helper to set status when Write Cache fails
  # returns $this to allow fluent api
  sub cache_fail {
  	my ($this) = @_;
  	$this->status($this->{options}{cache_fail_status});
  	return $this;
  }
  
  # helper to get/set bbu monitoring
  sub bbu_monitoring {
  	my ($this, $val) = @_;
  
  	if (defined $val) {
  		$this->{options}{bbu_monitoring} = $val;
  	}
  	$this->{options}{bbu_monitoring};
  }
  
  # setup status message text
  sub message {
  	my ($this, $message) = @_;
  	if (defined $message) {
  		# TODO: append if already something there
  		$this->{message} = $message;
  	}
  	$this->{message};
  }
  
  # Set performance data output.
  sub perfdata {
  	my ($this, $perfdata) = @_;
  	if (defined $perfdata) {
  		# TODO: append if already something there
  		$this->{perfdata} = $perfdata;
  	}
  	$this->{perfdata};
  }
  
  # Set plugin long output.
  sub longoutput {
  	my ($this, $longoutput) = @_;
  	if (defined $longoutput) {
  		# TODO: append if already something there
  		$this->{longoutput} = $longoutput;
  	}
  	$this->{longoutput};
  }
  
  # a helper to join similar statuses for items
  # instead of printing
  #  0: OK, 1: OK, 2: OK, 3: NOK, 4: OK
  # it would print
  #  0-2,4: OK, 3: NOK
  # takes as input list:
  #  { status => @items }
  sub join_status {
  	my $this = shift;
  	my %status = %{$_[0]};
  
  	my @status;
  	for my $status (sort {$a cmp $b} keys %status) {
  		my $disks = $status{$status};
  		my @s;
  		foreach my $disk (@$disks) {
  			push(@s, $disk);
  		}
  		push(@status, join(',', @s).'='.$status);
  	}
  
  	return join ' ', @status;
  }
  
  # return true if parameter is not in ignore list
  sub valid {
  	my $this = shift;
  	my ($v) = lc $_[0];
  
  	foreach (@utils::ignore) {
  		return 0 if lc $_ eq $v;
  	}
  	return 1;
  }
  
  use constant K => 1024;
  use constant M => K * 1024;
  use constant G => M * 1024;
  use constant T => G * 1024;
  
  sub parse_bytes {
  	my ($this, $size) = @_;
  
  	if ($size =~ s/\sT//) {
  		return int($size) * T;
  	}
  	if ($size =~ s/\sG//) {
  		return int($size) * G;
  	}
  	if ($size =~ s/\sM//) {
  		return int($size) * M;
  	}
  	if ($size =~ s/\sK//) {
  		return int($size) * K;
  	}
  
  	return int($size);
  }
  
  sub format_bytes {
  	my $this = shift;
  
  	my ($bytes) = @_;
  	if ($bytes > T) {
  		return sprintf("%.2f TiB", $bytes / T);
  	}
  	if ($bytes > G) {
  		return sprintf("%.2f GiB", $bytes / G);
  	}
  	if ($bytes > M) {
  		return sprintf("%.2f MiB", $bytes / M);
  	}
  	if ($bytes > K) {
  		return sprintf("%.2f KiB", $bytes / K);
  	}
  	return "$bytes B";
  }
  
  # disable sudo temporarily
  sub nosudo_cmd {
  	my ($this, $command, $cb) = @_;
  
  	my ($res, @res);
  
  	my $sudo = $this->{sudo};
  	$this->{sudo} = 0;
  
  	if (wantarray) {
  		@res = $this->cmd($command, $cb);
  	} else {
  		$res = $this->cmd($command, $cb);
  	}
  
  	$this->{sudo} = $sudo;
  
  	return wantarray ? @res : $res;
  }
  
  # build up command for $command
  # returns open filehandle to process output
  # if command fails, program is exited (caller needs not to worry)
  sub cmd {
  	my ($this, $command, $cb) = @_;
  
  	my $debug = $App::Monitoring::Plugin::CheckRaid::Utils::debug;
  
  	# build up command
  	my @CMD = $this->{program};
  
  	# add sudo if program needs
  	unshift(@CMD, @{$this->{sudo}}) if $> and $this->{sudo};
  
  	my $args = $this->{commands}{$command} or croak "command '$command' not defined";
  
  	# callback to replace args in command
  	my $cb_ = sub {
  		my $param = shift;
  		if ($cb) {
  			if (ref $cb eq 'HASH' and exists $cb->{$param}) {
  				return wantarray ? @{$cb->{$param}} : $cb->{$param};
  			}
  			return &$cb($param) if ref $cb eq 'CODE';
  		}
  
  		if ($param eq '@CMD') {
  			# command wanted, but not found
  			croak "Command for $this->{name} not found" unless defined $this->{program};
  			return @CMD;
  		}
  		return $param;
  	};
  
  	# add command arguments
  	my @cmd;
  	for my $arg (@$args) {
  		local $_ = $arg;
  		# can't do arrays with s///
  		# this limits that @arg must be single argument
  		if (/@/) {
  			push(@cmd, $cb_->($_));
  		} else {
  			s/([\$]\w+)/$cb_->($1)/ge;
  			push(@cmd, $_);
  		}
  	}
  
  	my $op = shift @cmd;
  	my $fh;
  	if ($op eq '=' and ref $cb eq 'SCALAR') {
  		# Special: use open2
  		use IPC::Open2;
  		warn "DEBUG EXEC: $op @cmd" if $debug;
  		my $pid = open2($fh, $$cb, @cmd) or croak "open2 failed: @cmd: $!";
  	} elsif ($op eq '>&2') {
  		# Special: same as '|-' but reads both STDERR and STDOUT
  		use IPC::Open3;
  		warn "DEBUG EXEC: $op @cmd" if $debug;
  		my $pid = open3(undef, $fh, $cb, @cmd);
  
  	} else {
  		warn "DEBUG EXEC: @cmd" if $debug;
  		open($fh, $op, @cmd) or croak "open failed: @cmd: $!";
  	}
  
  	# for dir handles, reopen as opendir
  	if (-d $fh) {
  		undef($fh);
  		warn "DEBUG OPENDIR: $cmd[0]" if $debug;
  		opendir($fh, $cmd[0]) or croak "opendir failed: @cmd: $!";
  	}
  
  	return $fh;
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/aaccli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::aaccli;
  
  # Adaptec ServeRAID
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'container list' => ['=', '@CMD'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd container list /full"
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $write = "";
  	$write .= "open aac0\n";
  	$write .= "container list /full\n";
  	$write .= "exit\n";
  	my $read = $this->cmd('container list', \$write);
  
  #File foo receiving all output.
  #
  #AAC0>
  #COMMAND: container list /full=TRUE
  #Executing: container list /full=TRUE
  #Num          Total  Oth Stripe          Scsi   Partition                                       Creation
  #Label Type   Size   Ctr Size   Usage   C:ID:L Offset:Size   State   RO Lk Task    Done%  Ent Date   Time
  #----- ------ ------ --- ------ ------- ------ ------------- ------- -- -- ------- ------ --- ------ --------
  # 0    Mirror 74.5GB            Open    0:02:0 64.0KB:74.5GB Normal                        0  051006 13:48:54
  # /dev/sda             Auth             0:03:0 64.0KB:74.5GB Normal                        1  051006 13:48:54
  #
  #
  #AAC0>
  #COMMAND: logfile end
  #Executing: logfile end
  	while (<$read>) {
  		if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\S+\s+(\S+)/) {
  			next unless $this->valid($dsk);
  			$dsk =~ s#:#/#g;
  			next unless $this->valid($dsk);
  
  			push(@status, "$dsk:$stat");
  
  			$this->critical if ($stat eq "Broken");
  			$this->warning if ($stat eq "Rebuild");
  			$this->warning if ($stat eq "Bld/Vfy");
  			$this->critical if ($stat eq "Missing");
  			if ($stat eq "Verify") {
  				$this->resync;
  			}
  			$this->warning if ($stat eq "VfyRepl");
  		}
  	}
  	close $read;
  
  	return unless @status;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/afacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::afacli;
  
  # Adaptec AACRAID
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'container list' => ['=', '@CMD'],
  	}
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $write = "";
  	$write .= "open afa0\n";
  	$write .= "container list /full\n";
  	$write .= "exit\n";
  
  	my $read = $this->cmd('container list', \$write);
  	while (<$read>) {
  		# 0    Mirror  465GB            Valid   0:00:0 64.0KB: 465GB Normal                        0  032511 17:55:06
  		# /dev/sda             root             0:01:0 64.0KB: 465GB Normal                        1  032511 17:55:06
  		if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\s?\S+\s+(\S+)/) {
  			next unless $this->valid($dsk);
  			$dsk =~ s#:#/#g;
  			next unless $this->valid($dsk);
  			push(@status, "$dsk:$stat");
  
  			$this->critical if ($stat eq "Broken");
  			$this->warning if ($stat eq "Rebuild");
  			$this->warning if ($stat eq "Bld/Vfy");
  			$this->critical if ($stat eq "Missing");
  			if ($stat eq "Verify") {
  				$this->resync;
  			}
  			$this->warning if ($stat eq "VfyRepl");
  		}
  	}
  	close $read;
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/arcconf.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF';
  package App::Monitoring::Plugin::CheckRaid::Plugins::arcconf;
  
  # Adaptec AAC-RAID
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'getstatus' => ['-|', '@CMD', 'GETSTATUS', '1'],
  		# 'nologs' does not exist in arcconf 6.50. #118
  		'getconfig' => ['-|', '@CMD', 'GETCONFIG', '$ctrl', 'AL'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETSTATUS 1",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETCONFIG * AL",
  	);
  }
  
  sub parse_error {
  	my ($this, $message) = @_;
  	warn "arcconf: parse error: $message";
  	$this->unknown->message("Parse Error: $message");
  }
  
  # parse GETSTATUS command
  # parses
  # - number of controllers
  # - logical device tasks (if any running)
  sub parse_status {
  	my ($this) = @_;
  
  	my $count = 0;
  	my $ok = 0;
  	my $fh = $this->cmd('getstatus');
  	my %s;
  	# controller task
  	my %task;
  	while (<$fh>) {
  		chomp;
  		# empty line or comment
  		next if /^$/ or /^#/;
  
  		# termination
  		if (/^Command completed successfully/) {
  			$ok = 1;
  			last;
  		}
  
  		if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
  			$count = int($c);
  			next;
  		}
  
  		if (/^(\S.+) Task:$/) {
  			$task{type} = $1;
  			next;
  		}
  
  		if (/^\s+Logical device\s+: (\d+)/) {
  			$task{device} = $1;
  		} elsif (/^\s+Task ID\s+: (\d+)/) {
  			$task{id} = $1;
  		} elsif (/^\s+Current operation\s+: (.+)/) {
  			$task{operation} = $1;
  		} elsif (/^\s+Status\s+: (.+)/) {
  			$task{status} = $1;
  		} elsif (/^\s+Priority\s+: (.+)/) {
  			$task{priority} = $1;
  		} elsif (/^\s+Percentage complete\s+: (\d+)/) {
  			$task{percent} = $1;
  		} elsif (/^Invalid controller number/) {
  			;
  		} else {
  			warn "Unknown line: [$_]";
  			# FIXME: ->message() gets overwritten later on
  			$this->unknown->message("Unknown line: [$_]");
  		}
  	}
  	close($fh);
  
  	# Tasks seem to be Controller specific, but as we don't support over one controller, let it be global
  	$s{tasks} = { %task } if %task;
  
  	if ($count == 0) {
  		# if command completed, but no controllers,
  		# assume no hardware present
  		if (!$ok) {
  			$this->unknown->message("No controllers found!");
  		}
  		return undef;
  	}
  
  	$s{ctrl_count} = $count;
  
  	return \%s;
  }
  
  # parse GETCONFIG for all controllers
  sub parse_config {
  	my ($this, $status) = @_;
  
  	my %c;
  	for (my $i = 1; $i <= $status->{ctrl_count}; $i++) {
  		$c{$i} = $this->parse_ctrl_config($i, $status->{ctrl_count});
  	}
  
  	return { controllers => \%c };
  }
  
  # parse GETCONFIG command for specific controller
  sub parse_ctrl_config {
  	my ($this, $ctrl, $ctrl_count) = @_;
  
  	# Controller information, Logical/Physical device info
  	my ($ld, $ch, $pd);
  
  	my $res = { controller => {}, logical => [], physical => [] };
  
  	my $fh = $this->cmd('getconfig', { '$ctrl' => $ctrl });
  	my ($section, $subsection, $ok);
  	my %sectiondata = ();
  
  	# called when data for section needs to be processed
  	my $flush = sub {
  		my $method = 'process_' . lc($section);
  		$method =~ s/[.\s]+/_/g;
  		$this->$method($res, \%sectiondata);
  		%sectiondata = ();
  	};
  	my $subsection_reset = sub {
  		$ch = 0;
  		undef($ld);
  		undef($pd);
  		undef($subsection);
  	};
  	while (<$fh>) {
  		chomp;
  
  		# empty line or comment
  		if (/^$/ or /^#/) {
  			&$subsection_reset;
  			next;
  		}
  
  		if (/^Command completed successfully/) {
  			$ok = 1;
  			last;
  		}
  
  		if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
  			if ($c != $ctrl_count) {
  				# internal error?!
  				$this->unknown->message("Controller count mismatch");
  			}
  			next;
  		}
  
  		# section start
  		if (/^---+/) {
  			if (my($s) = <$fh> =~ /^(\w.+)$/) {
  				# flush the lines
  				if (defined($section)) {
  					&$flush();
  				}
  
  				$section = $s;
  				unless (<$fh> =~ /^---+/) {
  					$this->parse_error($_);
  				}
  				&$subsection_reset;
  				next;
  			}
  			$this->parse_error($_);
  		}
  
  		# sub section start
  		# there are also sections in subsections, but currently section names
  		# are unique enough
  		if (/^\s+---+/) {
  			if (my($s) = <$fh> =~ /^\s+(\S.+?)\s*?$/) {
  				$subsection = $s;
  				unless (<$fh> =~ /^\s+---+/) {
  					$this->parse_error($_);
  				}
  				next;
  			}
  			$this->parse_error($_);
  		}
  
  		warn("SKIP without section: [$_]\n"),next unless defined $section;
  
  		# regex notes:
  		# - value portion may be missing
  		# - value may be empty
  		# - value may be truncated (t/data/arcconf/issue47/getconfig)
  		my ($key, $value) = /^\s*(.+?)(?:\s+:\s*(.*?))?$/;
  
  		if ($section =~ /Controller [Ii]nformation/) {
  			if (not defined $subsection) {
  				$sectiondata{$key} = $value;
  			} else {
  				$sectiondata{$subsection}{$key} = $value;
  			}
  
  		} elsif ($section =~ /Physical Device [Ii]nformation/) {
  			if (my($c) = /Channel #(\d+)/) {
  				$ch = int($c);
  				undef($pd);
  				next;
  
  			} elsif (my($n) = /^\s+Device #(\d+)/) {
  				$pd = int($n);
  				next;
  
  			} else {
  				if (not defined $pd) {
  					$sectiondata{$ch}{$key} = $value;
  				} elsif (not defined $subsection) {
  					$sectiondata{$ch}{'pd'}{$pd}{$key} = $value;
  				} else {
  					$sectiondata{$ch}{'pd'}{$pd}{$subsection}{$key} = $value;
  				}
  			}
  
  		} elsif ($section =~ /Logical ([Dd]evice|drive) [Ii]nformation/) {
  			if (my($n) = /Logical (?:[Dd]evice|drive) [Nn]umber (\d+)/) {
  				$ld = int($n);
  			} else {
  				# skip lone line: issue87/getconfig
  				if (/No logical devices configured/) {
  					next;
  				}
  				if (not defined $ld) {
  					warn "LD undefined:[$_]\n";
  					next;
  				}
  				if (not defined $subsection) {
  					$sectiondata{$ld}{$key} = $value;
  				} else {
  					$sectiondata{$ld}{$subsection}{$key} = $value;
  				}
  			}
  
  		} elsif ($section eq 'MaxCache 3.0 information') {
  			# not parsed yet
  		} elsif ($section eq 'Connector information') {
  			# not parsed yet
  		} else {
  			warn "NOT PARSED: [$section] [$_]";
  		}
  	}
  	close $fh;
  	&$flush() if $section;
  
  	$this->unknown->message("Command did not succeed") unless defined $ok;
  
  	return $res;
  }
  
  # Process Controller Information section
  sub process_controller_information {
  	my ($this, $res, $data) = @_;
  	my $c = {};
  	my $s;
  
  	# current section
  	my $cs = $data;
  
  	$c->{status} = $cs->{'Controller Status'};
  
  	if (exists $cs->{$s = 'Defunct Disk Drive Count'} || exists $cs->{$s = 'Defunct disk drive count'}) {
  		$c->{defunct_count} = int($cs->{$s});
  	}
  
  	if ($s = $cs->{'Logical devices/Failed/Degraded'}) {
  		my($td, $fd, $dd) = $s =~ m{(\d+)/(\d+)/(\d+)};
  		$c->{logical_count} = int($td);
  		$c->{logical_failed} = int($fd);
  		$c->{logical_degraded} = int($dd);
  	}
  	# ARCCONF 9.30: Logical drives/Offline/Critical
  	if ($s = $cs->{'Logical drives/Offline/Critical'}) {
  		my($td2, $fd2, $dd2) = $s =~ m{(\d+)/(\d+)/(\d+)};
  		$c->{logical_count} = int($td2);
  		$c->{logical_offline} = int($fd2);
  		$c->{logical_critical} = int($dd2);
  	}
  
  	$cs = $data->{'Controller Battery Information'};
  	$c->{battery_status} = $cs->{Status} if exists $cs->{Status};
  	$c->{battery_overtemp} = $cs->{'Over temperature'} if exists $cs->{'Over temperature'};
  
  	if ($s = $cs->{'Capacity remaining'}) {
  		my ($bc) = $s =~ m{(\d+)\s*percent.*$};
  		$c->{battery_capacity} = int($bc);
  	}
  
  	if ($s = $cs->{'Time remaining (at current draw)'}) {
  		my($d, $h, $m) = $s =~ /(\d+) days, (\d+) hours, (\d+) minutes/;
  		$c->{battery_time} = int($d) * 1440 + int($h) * 60 + int($m);
  		$c->{battery_time_full} = "${d}d${h}h${m}m";
  	}
  
  
  	$cs = $data->{'Controller ZMM Information'};
  	$c->{zmm_status} = $cs->{Status} if exists $cs->{'Status'};
  
  	$res->{controller} = $c;
  }
  
  sub process_logical_device_information {
  	my ($this, $res, $data) = @_;
  	my $s;
  
  	my @ld;
  	while (my($ld, $cs) = each %$data) {
  
  		$ld[$ld]{id} = $ld;
  		if (exists $cs->{$s = 'RAID Level'} || exists $cs->{$s = 'RAID level'}) {
  			$ld[$ld]{raid} = $cs->{$s};
  		}
  		$ld[$ld]{size} = $cs->{'Size'};
  		$ld[$ld]{failed_stripes} = $cs->{'Failed stripes'} if exists $cs->{'Failed stripes'};
  		$ld[$ld]{defunct_segments} = $cs->{'Defunct segments'} if exists $cs->{'Defunct segments'};
  
  		if ($s = $cs->{'Status of Logical Device'} || $cs->{'Status of logical device'} || $cs->{'Status of logical drive'}) {
  			$ld[$ld]{status} = $s;
  		}
  		if ($s = $cs->{'Logical Device name'} || $cs->{'Logical device name'} || $cs->{'Logical drive name'}) {
  			$ld[$ld]{name} = $s;
  		}
  
  		#   Write-cache mode                         : Not supported]
  		#   Partitioned                              : Yes]
  		#   Number of segments                       : 2]
  		#   Drive(s) (Channel,Device)                : 0,0 0,1]
  		#   Defunct segments                         : No]
  	}
  
  	$res->{logical} = \@ld;
  }
  
  sub process_physical_device_information {
  	my ($this, $res, $data) = @_;
  
  	# Keys with no values:
  	# "Device #0"
  	# "Device is a Hard drive"
  	#
  	# ignored:
  	# /Transfer Speed\s+:\s+(.+)/
  	# /Initiator at SCSI ID/
  	# /No physical drives attached/
  
  	my (@pd, $cs, $s);
  	while (my($ch, $channel_data) = each %$data) {
  		while (my($pd, $cs) = each %{$channel_data->{pd}}) {
  			$pd[$ch][$pd]{device_id} = $pd;
  			$pd[$ch][$pd]{power_state} = $cs->{'Power State'} if exists $cs->{'Power State'};
  			$pd[$ch][$pd]{status} = $cs->{'State'} if exists $cs->{'State'};
  			$pd[$ch][$pd]{supported} = $cs->{'Supported'} if exists $cs->{'Supported'};
  			$pd[$ch][$pd]{spare} = $cs->{'Dedicated Spare for'} if exists $cs->{'Dedicated Spare for'};
  			$pd[$ch][$pd]{model} = $cs->{'Model'};
  			$pd[$ch][$pd]{serial} = $cs->{'Serial number'} if exists $cs->{'Serial number'};
  			$pd[$ch][$pd]{wwn} = $cs->{'World-wide name'} if exists $cs->{'World-wide name'};
  			$pd[$ch][$pd]{write_cache} = $cs->{'Write Cache'} if exists $cs->{'Write Cache'};
  			$pd[$ch][$pd]{ssd} = $cs->{'SSD'} if exists $cs->{'SSD'};
  			$pd[$ch][$pd]{fru} = $cs->{'FRU'} if exists $cs->{'FRU'};
  			$pd[$ch][$pd]{ncq} = $cs->{'NCQ status'} if exists $cs->{'NCQ status'};
  			$pd[$ch][$pd]{pfa} = $cs->{'PFA'} if exists $cs->{'PFA'};
  			$pd[$ch][$pd]{enclosure} = $cs->{'Enclosure ID'} if exists $cs->{'Enclosure ID'};
  			$pd[$ch][$pd]{type} = $cs->{'Type'} if exists $cs->{'Type'};
  			$pd[$ch][$pd]{smart} = $cs->{'S.M.A.R.T.'} if exists $cs->{'S.M.A.R.T.'};
  			$pd[$ch][$pd]{smart_warn} = $cs->{'S.M.A.R.T. warnings'} if exists $cs->{'S.M.A.R.T. warnings'};
  			$pd[$ch][$pd]{speed} = $cs->{'Transfer Speed'} if $cs->{'Transfer Speed'};
  			$pd[$ch][$pd]{power_states} = $cs->{'Supported Power States'} if exists $cs->{'Supported Power States'};
  			$pd[$ch][$pd]{fail_ldev_segs} = $cs->{'Failed logical device segments'} if exists $cs->{'Failed logical device segments'};
  
  			# allow edits, i.e removed 'Vendor'/'Firmware' value from test data
  			$pd[$ch][$pd]{vendor} = $cs->{'Vendor'} || '';
  			$pd[$ch][$pd]{firmware} = $cs->{'Firmware'} if exists $cs->{'Firmware'};
  
  			# previous parser was not exact line match
  			if ($s = $cs->{'Size'} || $cs->{'Total Size'}) {
  				$pd[$ch][$pd]{size} = $s;
  			}
  
  			$s = $cs->{'Reported ESD'} || $cs->{'Reported ESD(T:L)'};
  			$pd[$ch][$pd]{esd} = $s if $s;
  
  			if ($s = $cs->{'Reported Location'}) {
  				my($e, $s) = $s =~ /(?:Enclosure|Connector) (\d+), (?:Slot|Device) (\d+)/;
  				$pd[$ch][$pd]{location} = "$e:$s";
  			}
  
  			if ($s = $cs->{'Reported Channel,Device'} || $cs->{'Reported Channel,Device(T:L)'}) {
  				$pd[$ch][$pd]{cd} = $s;
  			}
  
  			if (exists $cs->{$s = 'Device is a Hard drive'}
  				|| exists $cs->{$s = 'Device is an Enclosure'}
  				|| exists $cs->{$s = 'Device is an Enclosure services device'}
  				|| exists $cs->{$s = 'Device is an Enclosure Services Device'}
  			) {
  				($pd[$ch][$pd]{devtype}) = $s =~ /Device is an?\s+(.+)/;
  			}
  
  			# TODO: normalize and other formats:
  			# Current Temperature                : 27 deg C
  			# Life-time Temperature Recorded
  			# Temperature                              : 51 C/ 123 F (Normal)
  			# Temperature                     : Normal
  			# Temperature                        : Not Supported
  			# Temperature Sensor Status 1     : 21 C/ 69 F (Normal)
  			# Temperature Sensor Status 1     : 23 C/ 73 F (Normal)
  			# Temperature Sensor Status 1     : 27 C/ 80 F (Normal)
  			# Temperature Sensor Status 1     : 46 C/ 114 F (Abnormal)
  			# Temperature status              : Normal
  			# Threshold Temperature              : 51 deg C
  			# FIXME: previous code used last line with /Temperature/ match
  			if ($s = $cs->{'Temperature'} || $cs->{'Temperature Sensor Status 1'} || $cs->{'Temperature status'}) {
  				$pd[$ch][$pd]{temperature} = $s;
  			}
  
  			# ignored:
  			# Status of Enclosure
  			# (Fan \d+|Speaker) status/
  			# /Expander ID\s+:/
  			# /Enclosure Logical Identifier\s+:/
  			# /Expander SAS Address\s+:/
  			# /[Mm]axCache (Capable|Assigned)\s+:\s+(.+)/
  			# /Power supply \d+ status/
  		}
  	}
  
  	$res->{physical} = \@pd;
  }
  
  sub process_logical_drive_information {
  	shift->process_logical_device_information(@_);
  }
  
  sub process_maxcache_3_0_information {
  }
  
  # TODO: issue152/arc2_getconfig.txt
  sub process_connector_information {
  }
  
  # NB: side effect: ARCCONF changes current directory to /var/log
  sub parse {
  	my ($this) = @_;
  
  	# we chdir to /var/log, as tool is creating 'UcliEvt.log'
  	# this can be disabled with 'nologs' parameter, but not sure do all versions support it
  	chdir('/var/log') || chdir('/');
  
  	my ($status, $config);
  	$status = $this->parse_status or return;
  	$config = $this->parse_config($status) or return;
  
  	return { %$status, %$config };
  }
  
  # check for controller status
  sub check_controller {
  	my ($this, $c) = @_;
  
  	my @status;
  
  	$this->critical if $c->{status} !~ /Optimal|Okay|OK/;
  	push(@status, "Controller:$c->{status}");
  
  	if ($c->{defunct_count} > 0) {
  		$this->critical;
  		push(@status, "Defunct drives:$c->{defunct_count}");
  	}
  
  	if (defined $c->{logical_failed} && $c->{logical_failed} > 0) {
  		$this->critical;
  		push(@status, "Failed drives:$c->{logical_failed}");
  	}
  
  	if (defined $c->{logical_degraded} && $c->{logical_degraded} > 0) {
  		$this->critical;
  		push(@status, "Degraded drives:$c->{logical_degraded}");
  	}
  
  	if (defined $c->{logical_offline} && $c->{logical_offline} > 0) {
  		$this->critical;
  		push(@status, "Offline drives:$c->{logical_offline}");
  	}
  
  	if (defined $c->{logical_critical} && $c->{logical_critical} > 0) {
  		$this->critical;
  		push(@status, "Critical drives:$c->{logical_critical}");
  	}
  
  	# ZMM (Zero-Maintenance Module) status
  	if (defined($c->{zmm_status})) {
  		push(@status, "ZMM Status: $c->{zmm_status}");
  	}
  
  	# Battery status
  	if ($this->bbu_monitoring) {
  		my @s = $this->battery_status($c);
  		push(@status, @s) if @s;
  	}
  
  	return @status;
  }
  
  # check for physical devices
  sub check_physical {
  	my ($this, $p) = @_;
  
  	my %pd;
  	$this->{pd_resync} = 0;
  	for my $ch (@$p) {
  		for my $pd (@{$ch}) {
  			# skip not disks
  			next if not defined $pd;
  			next if $pd->{devtype} =~ m/Enclosure/;
  
  			if ($pd->{status} eq 'Rebuilding') {
  				$this->resync;
  				$this->{pd_resync}++;
  
  			} elsif ($pd->{status} eq 'Dedicated Hot-Spare') {
  				$this->spare;
  				$pd->{status} = "$pd->{status} for $pd->{spare}";
  
  			} elsif ($pd->{status} !~ /^Online|Hot[- ]Spare|Ready/) {
  				$this->critical;
  			}
  
  			my $id = $pd->{serial} || $pd->{wwn} || $pd->{location} || $pd->{cd};
  			push(@{$pd{$pd->{status}}}, $id);
  		}
  	}
  
  	return \%pd;
  }
  
  # check for logical devices
  sub check_logical {
  	my ($this, $l) = @_;
  
  	my @status;
  	for my $ld (@$l) {
  		next unless $ld; # FIXME: fix that script assumes controllers start from '0'
  
  		if ($ld->{status} eq 'Degraded' && $this->{pd_resync}) {
  			$this->warning;
  		} elsif ($ld->{status} !~ /Optimal|Okay/) {
  			$this->critical;
  		}
  
  		my $id = $ld->{id};
  		if ($ld->{name}) {
  			$id = "$id($ld->{name})";
  		}
  		push(@status, "Logical Device $id:$ld->{status}");
  
  		if (defined $ld->{failed_stripes} && $ld->{failed_stripes} ne 'No') {
  			push(@status, "Failed stripes: $ld->{failed_stripes}");
  		}
  		if (defined $ld->{defunct_segments} && $ld->{defunct_segments} ne 'No') {
  			push(@status, "Defunct segments: $ld->{defunct_segments}");
  		}
  	}
  
  	return @status;
  }
  
  sub check {
  	my $this = shift;
  
  	my $data = $this->parse;
  	$this->unknown,return unless $data;
  
  	my @status;
  
  	for my $i (sort {$a cmp $b} keys %{$data->{controllers}}) {
  		my $c = $data->{controllers}->{$i};
  
  		push(@status, $this->check_controller($c->{controller}));
  
  		# current (logical device) tasks
  		if ($data->{tasks}->{operation} ne 'None') {
  			# just print it. no status change
  			my $task = $data->{tasks};
  			push(@status, "$task->{type} #$task->{device}: $task->{operation}: $task->{status} $task->{percent}%");
  		}
  
  		# check physical first, as it setups pd_resync flag
  		my $pd = $this->check_physical($c->{physical});
  
  		push(@status, $this->check_logical($c->{logical}));
  
  		# but report after logical devices
  		push(@status, "Drives: ".$this->join_status($pd)) if $pd;
  	}
  
  	$this->ok->message(join(', ', @status));
  }
  
  # check battery status in $c
  sub battery_status {
  	my ($this, $c) = @_;
  
  	my @status;
  
  	if (!defined($c->{battery_status}) || $c->{battery_status} eq 'Not Installed') {
  		return;
  	}
  
  	push(@status, "Battery Status: $c->{battery_status}");
  
  	# if battery status is 'Failed', none of the details below are available. #105
  	if ($c->{battery_status} eq 'Failed') {
  		$this->critical;
  		return @status;
  	}
  
  	# detailed battery checks
  	if ($c->{battery_overtemp} ne 'No') {
  		$this->critical;
  		push(@status, "Battery Overtemp: $c->{battery_overtemp}");
  	}
  
  	push(@status, "Battery Capacity Remaining: $c->{battery_capacity}%");
  	if ($c->{battery_capacity} < 50) {
  		$this->critical;
  	}
  	if ($c->{battery_capacity} < 25) {
  		$this->warning;
  	}
  
  	if ($c->{battery_time} < 1440) {
  		$this->warning;
  	}
  	if ($c->{battery_time} < 720) {
  		$this->critical;
  	}
  
  	if ($c->{battery_time} < 60) {
  		push(@status, "Battery Time: $c->{battery_time}m");
  	} else {
  		push(@status, "Battery Time: $c->{battery_time_full}");
  	}
  
  	return @status;
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/areca.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA';
  package App::Monitoring::Plugin::CheckRaid::Plugins::areca;
  
  ## Areca SATA RAID Support
  ## requires cli64 or cli32 binaries
  ## For links to manuals and binaries, see this issue:
  ## https://github.com/glensc/nagios-plugin-check_raid/issues/10
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	qw(areca-cli areca_cli64 areca_cli32 cli64 cli32);
  }
  
  sub commands {
  	{
  		'rsf info' => ['-|', '@CMD', 'rsf', 'info'],
  		'disk info' => ['-|', '@CMD', 'disk', 'info'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd rsf info",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd disk info",
  	);
  }
  
  # plugin check
  # can store its exit code in $this->status
  # can output its message in $this->message
  sub check {
  	my $this = shift;
  
  	## Check Array Status
  	my (@status, %arrays);
  	my $fh = $this->cmd('rsf info');
  	while (<$fh>) {
  =cut
   #  Name             Disks TotalCap  FreeCap MinDiskCap         State
   #  Name             Disks TotalCap  FreeCap DiskChannels       State
  ===============================================================================
   1  Raid Set # 000      23 34500.0GB    0.0GB   1500.0GB         Normal
   1  Raid Set # 00       15 15000.0GB    0.0GB 123G567C9AB48EF    Normal
   1  data                15 11250.0GB    0.0GB 123456789ABCDEF    Normal
   1  data                15 11250.0GB    0.0GB 123456789ABCDEF    Initializing
  ===============================================================================
  =cut
  		next unless (my($id, $n, $s) = m{^
  			\s*(\d+)    # Id
  			\s+(.+)     # Name
  			\s+\d+      # Disks
  			\s+\S+      # TotalCap
  			\s+\S+      # FreeCap
  			\s+\S+      # MinDiskCap/DiskChannels
  			\s+(\S+)\s* # State
  		$}x);
  
  		# trim trailing spaces from name
  		$n =~ s/\s+$//;
  
  		if ($s =~ /[Rr]e[Bb]uild/) {
  			$this->warning;
  		} elsif ($s !~ /[Nn]ormal|[Rr]e[Bb]uild|Checking|Initializing/) {
  			$this->critical;
  		}
  
  		push(@status, "Array#$id($n): $s");
  
  		$arrays{$n} = [ $id, $s ];
  	}
  	close $fh;
  
  	## Check Drive Status
  	$fh = $this->cmd('disk info');
  	my %drivestatus;
  	while (<$fh>) {
  		chomp;
  =cut
    # Enc# Slot#   ModelName                        Capacity  Usage
  ===============================================================================
    1  01  Slot#1  N.A.                                0.0GB  N.A.
    8  01  Slot#8  N.A.                                0.0GB  N.A.
    9  02  SLOT 01 ST31500341AS                     1500.3GB  Raid Set # 000
   11  02  SLOT 03 ST31500341AS                     1500.3GB  Raid Set # 000
  
    # Ch# ModelName                       Capacity  Usage
  ===============================================================================
    1  1  ST31000340NS                    1000.2GB  Raid Set # 00
    6  6  ST31000340NS                    1000.2GB  Raid Set # 00
    3  3  WDC WD7500AYYS-01RCA0            750.2GB  data
    4  4  WDC WD7500AYYS-01RCA0            750.2GB  data
   16 16  WDC WD7500AYYS-01RCA0            750.2GB  HotSpare[Global]
  =cut
  		next unless my($id, $model, $usage) = m{^
  			\s*(\d+)      # Id
  			\s+\d+        # Channel/Enclosure (not reliable, tests 1,2,12 differ)
  			\s+(.+)       # ModelName
  			\s+\d+.\d\S+  # Capacity
  			\s+(.+)       # Usage (Raid Name)
  		}x;
  
  		# trim trailing spaces from name
  		$usage =~ s/\s+$//;
  
  		# Asssume model N.A. means the slot not in use
  		# we could also check for Capacity being zero, but this seems more
  		# reliable.
  		next if $usage eq 'N.A.';
  
  		# use array id in output: shorter
  		my $array_id = defined($arrays{$usage}) ? ($arrays{$usage})->[0] : undef;
  		my $array_name = defined $array_id ? "Array#$array_id" : $usage;
  
  		# assume critical if Usage is not one of:
  		# - existing Array name
  		# - HotSpare
  		# - Rebuild
  		if (defined($arrays{$usage})) {
  			# Disk in Array named $usage
  			push(@{$drivestatus{$array_name}}, $id);
  		} elsif ($usage =~ /[Rr]e[Bb]uild/) {
  			# rebuild marks warning
  			push(@{$drivestatus{$array_name}}, $id);
  			$this->warning;
  		} elsif ($usage =~ /HotSpare/) {
  			# hotspare is OK
  			push(@{$drivestatus{$array_name}}, $id);
  		} elsif ($usage =~ /Pass Through/) {
  			# Pass Through is OK
  			push(@{$drivestatus{$array_name}}, $id);
  		} else {
  			push(@{$drivestatus{$array_name}}, $id);
  			$this->critical;
  		}
  	}
  	close $fh;
  
  	push(@status, "Drive Assignment: ".$this->join_status(\%drivestatus)) if %drivestatus;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cciss.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS';
  package App::Monitoring::Plugin::CheckRaid::Plugins::cciss;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
  use App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
  use strict;
  use warnings;
  
  sub program_names {
  	'cciss_vol_status';
  }
  
  sub commands {
  	{
  		'controller status' => ['-|', '@CMD', '@devs'],
  		'controller status verbose' => ['-|', '@CMD', '-V', '@devs'],
  		'cciss_vol_status version' => ['>&2', '@CMD', '-v'],
  
  		'detect hpsa' => ['<', '/sys/module/hpsa/refcnt'],
  		'detect cciss' => ['<', '/proc/driver/cciss'],
  		'cciss proc' => ['<', '/proc/driver/cciss/$controller'],
  
  		# for lsscsi, issue #109
  		'lsscsi list' => ['-|', '@CMD', '-g'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  
  	my $v1_10 = $this->cciss_vol_status_version >= 1.10;
  
  	my @sudo;
  	my @cciss_devs = $this->detect;
  	if (@cciss_devs) {
  		my $c = join(' ', @cciss_devs);
  		if ($v1_10) {
  			push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -V $c");
  		} else {
  			push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd $c");
  		}
  	}
  
  	my @cciss_disks = $this->detect_disks(@cciss_devs);
  	if (!$v1_10 && @cciss_disks) {
  		my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new();
  
  		if ($smartctl->active) {
  			my $cmd = $smartctl->{program};
  			foreach my $ref (@cciss_disks) {
  				my ($dev, $diskopt, $disk) = @$ref;
  				# escape comma for sudo
  				$diskopt =~ s/,/\\$&/g;
  				push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -H $dev $diskopt$disk");
  			}
  		}
  	}
  
  	return @sudo;
  }
  
  # detects if hpsa (formerly cciss) is present in system
  sub detect {
  	my $this = shift;
  
  	my ($fh, @devs);
  
  	# try lsscsi first if enabled and allowed
  	my $lsscsi = App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi->new('commands' => $this->{commands});
  	my $use_lsscsi = defined($this->{use_lsscsi}) ? $this->{use_lsscsi} : $lsscsi->active;
  	if ($use_lsscsi) {
  		# for cciss_vol_status < 1.10 we need /dev/sgX nodes, columns which are type storage
  		@devs = $lsscsi->list_sg;
  
  		# cciss_vol_status 1.10 can process disk nodes too even if sg is not present
  		my $v1_10 = $this->cciss_vol_status_version >= 1.10;
  		if (!@devs && $v1_10) {
  			@devs = $lsscsi->list_dd;
  		}
  
  		return wantarray ? @devs : \@devs if @devs;
  	}
  
  	# check hpsa devs
  	eval { $fh = $this->cmd('detect hpsa'); };
  	if ($fh) {
  		my $refcnt = <$fh>;
  		close $fh;
  
  		if ($refcnt) {
  			# TODO: how to figure which sgX is actually in use?
  			# for now we collect all, and expect cciss_vol_status to ignore unknowns
  			# refcnt seems to match number of sg devs: /sys/class/scsi_generic/sg*
  			for (my $i = 0; $i < $refcnt; $i++) {
  				my $dev = "/dev/sg$i";
  				# filter via valid() so could exclude devs
  				push(@devs, $dev) if $this->valid($dev);
  			}
  		}
  	}
  	undef($fh);
  
  	# check legacy cciss devs
  	eval { $fh = $this->cmd('detect cciss'); };
  	if ($fh) {
  		my @c = grep { !/^\./ } readdir($fh);
  		close($fh);
  
  		# find controllers
  		#	cciss0: HP Smart Array P400i Controller
  		#	Board ID: 0x3235103c
  		#	Firmware Version: 4.06
  		#	IRQ: 98
  		#	Logical drives: 1
  		#	Current Q depth: 0
  		#	Current # commands on controller: 0
  		#	Max Q depth since init: 249
  		#	Max # commands on controller since init: 275
  		#	Max SG entries since init: 31
  		#	Sequential access devices: 0
  		#
  		#	cciss/c0d0:      220.12GB       RAID 1(1+0)
  		for my $c (@c) {
  			my $fh = $this->cmd('cciss proc', { '$controller' => $c });
  			while (<$fh>) {
  				# check "c*d0" - iterate over each controller
  				next unless (my($dev) = m{^(cciss/c\d+d0):});
  				$dev = "/dev/$dev";
  				# filter via valid() so could exclude devs
  				push(@devs, $dev) if $this->valid($dev);
  			}
  			close $fh;
  		}
  	}
  	undef($fh);
  
  	return wantarray ? @devs : \@devs;
  }
  
  # build list of cciss disks
  # used by smartctl check
  # just return all disks (0..15) for each cciss dev found
  sub detect_disks {
  	my $this = shift;
  
  	my @devs;
  	# build devices list for smartctl
  	foreach my $scsi_dev (@_) {
  		foreach my $disk (0..15) {
  			push(@devs, [ $scsi_dev, '-dcciss,', $disk ]);
  		}
  	}
  	return wantarray ? @devs : \@devs;
  }
  
  # parse version out of "cciss_vol_status version 1.09"
  # NOTE: it prints the output to stderr, but may print to stdout in the future
  sub cciss_vol_status_version {
  	my $this = shift;
  
  	# cache inside single run
  	return $this->{cciss_vol_status_version} if defined $this->{cciss_vol_status_version};
  
  	my $version = sub {
  		my $fh = $this->nosudo_cmd('cciss_vol_status version');
  		my ($line) = <$fh>;
  		close $fh;
  		return 0 unless $line;
  
  		if (my($v) = $line =~ /^cciss_vol_status version ([\d.]+)$/) {
  			return 0 + $v;
  		}
  		return 0;
  	};
  
  	return $this->{cciss_vol_status_version} = &$version();
  }
  
  sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
  
  # we process until we find end of sentence (dot at the end of the line)
  sub consume_diagnostic {
  	my ($this, $fh) = @_;
  
  	my $diagnostic = '';
  	while (1) {
  		my $s = <$fh>;
  		last unless $s;
  		chomp;
  		$diagnostic .= ' '. trim($s);
  		last if $s =~ /\.$/;
  	}
  	return trim($diagnostic);
  }
  
  # process to skip lines with physical location:
  # "         connector 1I box 1 bay 4 ..."
  sub consume_disk_map {
  	my ($this, $fh) = @_;
  
  	while (my $s = <$fh>) {
  		chomp $s;
  		# connector 1I box 1 bay 4
  		last unless $s =~ /^\s+connector\s/;
  	}
  }
  
  sub parse {
  	my $this = shift;
  	my @devs = @_;
  
  	my (%c, $cdev);
  
  	# cciss_vol_status 1.10 has -V option to print more info about controller and disks.
  	my $v1_10 = $this->cciss_vol_status_version >= 1.10;
  
  	# add all devs at once to commandline, cciss_vol_status can do that
  	my $fh = $this->cmd($v1_10 ? 'controller status verbose' : 'controller status', { '@devs' => \@devs });
  	while (<$fh>) {
  		chomp;
  
  		# skip empty lines and artificial comments (added by this project)
  		next if /^$/ or /^#/;
  
  		if (/Controller:/) {
  			# this is first item when new controller is found
  			# reset previous state
  			undef $cdev;
  			next;
  		}
  
  		# catch enclosures, print_bus_status()
  		# /dev/cciss/c1d0: (Smart Array P800) Enclosure MSA70 (S/N: SGA651004J) on Bus 2, Physical Port 1E status: OK.
  		# /dev/cciss/c0d0: (Smart Array 6i) Enclosure PROLIANT 6L2I (S/N: ) on Bus 0, Physical Port J1 status: OK.
  		if (my($file, $board_name, $name, $sn, $bus, $port1, $port2, $status) = m{
  			^(/dev/[^:]+):\s        # File
  			\(([^)]+)\)\s           # Board Name
  			Enclosure\s(.*?)\s      # Enclosure Name
  			\(S/N:\s(\S*)\)\s       # Enclosure SN
  			on\sBus\s(\d+),\s       # Bus Number
  			Physical\sPort\s(.)     # physical_port1
  			(.)\s                   # physical_port2
  			status:\s(.*?)\.        # status (without a dot)
  		}x) {
  			$c{$file}{enclosures}{$bus} = {
  				board_name => $board_name,
  				name => $name,
  				sn => $sn,
  				bus => int($bus),
  				phys1 => $port1,
  				phys2 => $port2,
  				status => $status,
  			};
  			next;
  		}
  
  		# volume status, print_volume_status()
  		# /dev/cciss/c0d0: (Smart Array P400i) RAID 1 Volume 0 status: OK
  		# /dev/sda: (Smart Array P410i) RAID 1 Volume 0 status: OK.
  		# /dev/sda: (Smart Array P410i) RAID 5 Volume 0 status: OK.   At least one spare drive designated.  At least one spare drive has failed.
  		if (my($file, $board_name, $raid_level, $volume_number, $certain, $status, $spare_drive_status) = m{
  			^(/dev/[^:]+):\s        # File
  			\(([^)]+)\)\s           # Board Name
  			(RAID\s\d+|\([^)]+\))\s # RAID level
  			Volume\s(\d+)           # Volume number
  			(\(\?\))?\s             # certain?
  			status:\s(.*?)\.        # status (without a dot)
  			(.*)?                   # spare drive status messages
  		}x) {
  			$cdev = $file;
  			$c{$file}{volumes}{$volume_number} = {
  				board_name => $board_name,
  				raid_level => $raid_level,
  				volume_number => $volume_number,
  				certain => int(not defined $certain),
  				status => $status,
  				spare_drive_status => trim($spare_drive_status),
  			};
  
  			$c{$file}{board_name} = $board_name;
  			next;
  		}
  
  		next unless $cdev;
  
  		if (my ($count) = /Physical drives: (\d+)/) {
  			$c{$cdev}{'pd count'} = $count;
  			next;
  		}
  
  		# check_physical_drives(file, fd);
  		# NOTE: check for physical drives is enabled with -V or -s option (-V enables -s)
  		# cciss_vol_status.c format_phys_drive_location()
  		if (my ($phys1, $phys2, $box, $bay, $model, $serial_no, $fw_rev, $status) = m{
  			\sconnector\s(.)(.)\s # Phys connector 1&2
  			box\s(\d+)\s          # phys_box_on_bus
  			bay\s(\d+)\s          # phys_bay_in_box
  			(.{40})\s             # model
  			(.{40})\s             # serial no
  			(.{8})\s              # fw rev
  			(.+)                  # status
  		$}x) {
  			my $slot = "$phys1$phys2-$box-$bay";
  			$c{$cdev}{drives}{$slot} = {
  				'slot' => $slot,
  				'phys1' => $phys1,
  				'phys2' => $phys2,
  				'box' => int($box),
  				'bay' => int($bay),
  
  				'model' => trim($model),
  				'serial' => trim($serial_no),
  				'fw' => trim($fw_rev),
  				'status' => $status,
  			};
  			next;
  		}
  
  		# TODO
  		# check_fan_power_temp(file, ctlrtype, fd, num_controllers);
  
  		# check_nonvolatile_cache_status(file, ctlrtype, fd, num_controllers);
  		# /dev/cciss/c0d0(Smart Array P400i:0): Non-Volatile Cache status:
  		if (my($file, $board_name, $instance) = m{^(/dev/[^(]+)\((.+):(\d+)\): Non-Volatile Cache status}) {
  			# $file and $dev may differ, so store it
  			$c{$cdev}{cache} = {
  				'file' => $file,
  				'board' => $board_name,
  				'instance' => int($instance),
  			};
  			next;
  		}
  
  		if (defined($c{$cdev}{cache})) {
  			my $cache = $c{$cdev}{cache};
  			my %map = (
  				configured => qr/Cache configured: (.+)/,
  				read_cache_memory => qr/Read cache memory: (.+)/,
  				write_cache_memory => qr/Write cache memory: (.+)/,
  				write_cache_enabled => qr/Write cache enabled: (.+)/,
  				flash_cache => qr/Flash backed cache present/,
  				disabled_temporarily => qr/Write cache temporarily disabled/,
  				disabled_permanently => qr/Write Cache permanently disabled/,
  			);
  			my $got;
  			while (my($k, $r) = each %map) {
  				next unless (my($v) = $_ =~ $r);
  				$cache->{$k} = $v;
  				$got = 1;
  
  				# consume extended diagnostic
  				if ($k =~ /disabled_(temporari|permanentl)ly/) {
  					$cache->{"$k diagnostic"} = $this->consume_diagnostic($fh);
  				}
  			}
  
  			next if $got;
  		}
  
  		# show_disk_map("  Failed drives:", file, fd, id, controller_lun, ctlrtype,
  		# show_disk_map("  'Replacement' drives:", file, fd, id, controller_lun, ctlrtype,
  		# show_disk_map("  Drives currently substituted for by spares:", file, fd, id, controller_lun, ctlrtype,
  		if (/^  Failed drives:/ ||
  			/^  'Replacement' drives:/ ||
  			/^  Drives currently substituted for by spares:/
  		) {
  			# could store this somewhere, ignore for now
  			$this->consume_disk_map($fh);
  			next;
  		}
  
  		if (my($total_failed) = /Total of (\d+) failed physical drives detected on this logical drive\./) {
  			$c{$cdev}{phys_failed} = $total_failed;
  			next;
  		}
  
  		warn "Unparsed[$_]";
  	}
  	close($fh);
  
  	return \%c;
  }
  
  sub check {
  	my $this = shift;
  	my @devs = $this->detect;
  
  	unless (@devs) {
  		$this->warning;
  		$this->message("No Smart Array Adapters were found on this machine");
  		return;
  	}
  
  	# status messages pushed here
  	my @status;
  
  	my $res = $this->parse(@devs);
  	for my $dev (sort {$a cmp $b} keys %$res) {
  		my $c = $res->{$dev};
  		my @bstatus;
  
  		# check volumes
  		my @vstatus;
  		for my $vn (sort {$a cmp $b} keys %{$c->{volumes}}) {
  			my $v = $c->{volumes}->{$vn};
  			if ($v->{status} !~ '^OK') {
  				$this->critical;
  			}
  			push(@vstatus, "Volume $v->{volume_number} ($v->{raid_level}): $v->{status}");
  		}
  
  		push(@bstatus, @vstatus) if @vstatus;
  
  		# check physical devices
  		if ($c->{'pd count'}) {
  			my %pd;
  			for my $ps (sort {$a cmp $b} keys %{$c->{drives}}) {
  				my $pd = $c->{drives}{$ps};
  				if ($pd->{status} !~ '^OK') {
  					$this->critical;
  					$ps .= "($pd->{serial})";
  				}
  				push(@{$pd{$pd->{status}}}, $ps);
  			}
  			push(@bstatus, "Drives($c->{'pd count'}): ". $this->join_status(\%pd));
  		}
  
  		# check enclosures
  		if ($c->{enclosures}) {
  			my @e;
  			for my $i (sort {$a cmp $b} keys %{$c->{enclosures}}) {
  				my $e = $c->{enclosures}{$i};
  
  				# enclosure name may be missing, identify by connection
  				my $s = $e->{name} || "$e->{bus}-$e->{phys1}$e->{phys2}";
  				# enclosure S/N may be missing
  				$s .= "($e->{sn})" if $e->{sn};
  				$s .= ": $e->{status}";
  				if ($e->{status} !~ '^OK') {
  					$this->critical;
  				}
  				push(@e, $s);
  			}
  			push(@bstatus, "Enclosures: ". join(', ', @e));
  		}
  
  		# check cache
  		if ($c->{cache} && $c->{cache}->{configured} eq 'Yes') {
  			my $cache = $c->{cache};
  			my @cstatus = 'Cache:';
  
  			if ($cache->{write_cache_enabled} eq 'Yes') {
  				push(@cstatus, "WriteCache");
  
  			} elsif ($cache->{disabled_temporarily} || $cache->{disabled_permanently}) {
  				# disabled diagnostic is available, but it's too long to print here
  				push(@cstatus, "WriteCache:DISABLED");
  				$this->cache_fail;
  			}
  
  			push(@cstatus, "FlashCache") if $cache->{flash_cache};
  			push(@cstatus, "ReadMem:$cache->{read_cache_memory}") if $cache->{read_cache_memory};
  			push(@cstatus, "WriteMem:$cache->{write_cache_memory}") if $cache->{write_cache_memory};
  
  			push(@bstatus, join(' ', @cstatus));
  		}
  
  		push(@status, "$dev($c->{board_name}): ". join(', ', @bstatus));
  	}
  
  	unless (@status) {
  		return;
  	}
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join(', ', @status));
  
  	# cciss_vol_status 1.10 with -V (or -s) checks individual disk health anyway
  	my $v1_10 = $this->cciss_vol_status_version >= 1.10;
  
  	# no_smartctl: allow skip from tests
  	if (!$v1_10 && !$this->{no_smartctl}) {
  		# check also individual disk health
  		my @disks = $this->detect_disks(@devs);
  		if (@disks) {
  			# inherit smartctl command from our commands (testing)
  			my %params = ();
  			$params{commands}{smartctl} = $this->{commands}{smartctl} if $this->{commands}{smartctl};
  
  			my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new(%params);
  			# do not perform check if smartctl is missing
  			if ($smartctl->active) {
  				$smartctl->check_devices(@disks);
  
  				# XXX this is hack, as we have no proper subcommand check support
  				$this->message($this->message . " " .$smartctl->message);
  				if ($smartctl->status > 0) {
  					$this->critical;
  				}
  			}
  		}
  	}
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cmdtool2.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2';
  package App::Monitoring::Plugin::CheckRaid::Plugins::cmdtool2;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	'CmdTool2';
  }
  
  sub commands {
  	{
  		'adapter list' => ['-|', '@CMD', , '-AdpAllInfo', '-aALL', '-nolog'],
  		'adapter config' => ['-|', '@CMD', '-CfgDsply', '-a$adapter', '-nolog'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpAllInfo -aALL -nolog",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -CfgDsply -a* -nolog",
  	);
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	# get adapters
  	my $fh = $this->cmd('adapter list');
  	my @c;
  	while (<$fh>) {
  		if (my($c) = /^Adapter #(\d+)/) {
  			push(@c, $c);
  		}
  	}
  	close $fh;
  
  	unless (@c) {
  		$this->warning;
  		$this->message("No LSI adapters were found on this machine");
  		return;
  	}
  
  	foreach my $c (@c) {
  		my $fh = $this->cmd('adapter config', { '$adapter' => $c });
  		my ($d);
  		while (<$fh>) {
  			# DISK GROUPS: 0
  			if (my($s) = /^DISK GROUPS: (\d+)/) {
  				$d = int($s);
  				next;
  			}
  
  			# State: Optimal
  			if (my($s) = /^State: (\S+)$/) {
  				if ($s ne 'Optimal') {
  					$this->critical;
  				}
  				push(@status, "Logical Drive $c,$d: $s");
  			}
  		}
  	}
  
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dm.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM';
  package App::Monitoring::Plugin::CheckRaid::Plugins::dm;
  
  # Package to check Linux Device Mapper
  
  # Linux LVM Mirrors
  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/mirror_create.html
  #
  # Linux LVM RAID
  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/raid_volumes.html
  #
  # Low-level:
  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#dmraid-map
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	qw(dmsetup);
  }
  
  sub active {
  	my ($this, $sudo) = @_;
  
  	# return if parent said NO
  	my $res = $this->SUPER::active(@_);
  	return $res unless $res;
  
  	# check if there really are any devices
  	my $c = $this->parse;
  	return !!@$c;
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd status --noflush",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd status",
  	);
  }
  
  sub commands {
  	{
  		'dmsetup'         => [ '-|', '@CMD', 'status' ],
  		'dmsetup noflush' => [ '-|', '@CMD', 'status', '--noflush' ],
  	}
  }
  
  # https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt
  sub parse_raid {
  	local $_ = shift;
  
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1377
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1423
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1425-L1435
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1437-L1442
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1444-L1452
  	my @cols = qw(
  		raid_type raid_disks
  		status_chars
  		sync_ratio
  		sync_action
  		mismatch_cnt
  	);
  
  	my %h;
  	@h{@cols} = split;
  
  	\%h;
  }
  
  # https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
  sub parse_mirror {
  	local $_ = shift;
  
  	my %h;
  
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1355
  	my @parts = split;
  
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1365
  	$h{nr_mirrors} = shift @parts;
  
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1366-L1369
  	my @devs;
  	for (my $i = 0; $i < $h{nr_mirrors}; $i++) {
  		push(@devs, shift @parts);
  	}
  	$h{devices} = \@devs;
  
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1372-L1374
  	# some ratio?
  	$h{ratio} = shift @parts;
  	# param count? always '1'
  	shift @parts;
  	# the 'buffer' filled with status chars
  	$h{status_chars} = shift @parts;
  
  	# log device information
  	# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-log.c#L807-L810
  	# log params, always '3'
  	shift @parts;
  	my %l;
  	$l{type} = shift @parts;
  	$l{device} = shift @parts;
  	# status: F->D->A
  	$l{status_char} = shift @parts;
  	$h{log} = { %l };
  
  	# for debugging. fill only if something remains not parsed
  	$h{_remaining} = join ' ', @parts if @parts;
  
  	\%h;
  }
  
  sub parse_target {
  	my ($target, $data) = @_;
  
  	return parse_raid($data) if $target eq 'raid';
  	return parse_mirror($data) if $target eq 'mirror';
  	undef;
  }
  
  sub get_fh {
  	my $this = shift;
  
  	# use dmsetup --noflush, requires LVM >= 2.02.97
  	# if that fails, fall back to just dmsetup
  	# https://github.com/glensc/nagios-plugin-check_raid/issues/130#issuecomment-194476070
  	my $fh = $this->cmd('dmsetup noflush');
  	$fh = $this->cmd('dmsetup') if eof $fh;
  
  	return $fh;
  }
  
  sub parse {
  	my $this = shift;
  
  	# cache for single run
  	if (!defined($this->{parsed})) {
  		$this->{parsed} = $this->_parse;
  	}
  
  	return $this->{parsed};
  }
  
  sub _parse {
  	my $this = shift;
  
  	my @devices;
  	my $fh = $this->get_fh();
  	while (<$fh>) {
  		# skip comments.
  		# not present in dmsetup output, but our test files may have.
  		next if /^#/;
  
  		last if /No devices found/;
  
  		if (my ($dmname, $s, $l, $target, $rest) = m{^
  			(\S+):\s+       # dmname
  			(\d+)\s+        # start
  			(\d+)\s+        # length
  			(\S+)           # target
  			(?:\s+(.+))?    # rest of the data
  			\s?             # there may be trailing space
  			$}x) {
  			my $h = parse_target($target, $rest);
  
  			# skip target type not handled
  			next unless $h;
  
  			my %h = (
  				'dmname' => $dmname,
  				's'      => $s,
  				'l'      => $l,
  				'target' => $target,
  				%$h,
  			);
  			push @devices, \%h;
  			next;
  		}
  
  		warn "Unhandled:[$_]";
  		$this->unknown;
  	}
  	close $fh;
  	return \@devices;
  }
  
  sub check {
  	my $this = shift;
  
  	my $c = $this->parse;
  
  	if (!@$c) {
  		$this->noraid->message("No devices to check");
  		return;
  	}
  
  	my @status;
  	foreach my $dm (@$c)
  	{
  		# <status_chars>  One char for each device, indicating:
  		# 'A' = alive and in-sync (mirror, raid1, raid)
  		# 'a' = alive but not in-sync (mirror, raid1)
  		# 'D' = dead/failed (mirror, raid1, raid)
  		# 'S' = Sync (mirror, raid1)
  		# 'mirror'/'raid1': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1330-L1342
  		# 'raid': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1414
  		$this->critical if ($dm->{status_chars} =~ /D/);
  		$this->warning if ($dm->{status_chars} =~ /[aS]/);
  
  		my @s = "$dm->{dmname}:$dm->{status_chars}";
  
  		# <sync_action>   One of the following possible states:
  		# idle    - No synchronization action is being performed.
  		# frozen  - The current action has been halted.
  		# resync  - Array is undergoing its initial synchronization or...
  		# recover - A device in the array is being rebuilt or...
  		# check   - A user-initiated full check of the array is...
  		# repair  - The same as "check", but discrepancies are...
  		# reshape - The array is undergoing a reshape.
  		if ($dm->{sync_action}) {
  			push(@s, $dm->{sync_action});
  			if ($dm->{sync_action} =~ /^(check|repair|init)$/) {
  				$this->warning;
  			}
  		}
  		push(@status, join(' ', @s));
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dmraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID';
  package App::Monitoring::Plugin::CheckRaid::Plugins::dmraid;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'dmraid' => ['-|', '@CMD', '-r'],
  	}
  }
  
  sub active {
  	my ($this) = @_;
  
  	# allow --plugin-option=dmraid-enabled to force this plugin to be enabled
  	return 1 if exists $this->{options}{'dmraid-enabled'};
  
  	# return if parent said NO
  	my $res = $this->SUPER::active(@_);
  	return $res unless $res;
  
  	# check if dmraid is empty
  	return keys %{$this->parse} > 0;
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -r";
  }
  
  # parse arrays, return data indexed by array name
  sub parse {
  	my $this = shift;
  
  	my (%arrays);
  	my $fh = $this->cmd('dmraid');
  	while (<$fh>) {
  		chomp;
  		next unless (my($device, $format, $name, $type, $status, $sectors) = m{^
  			# /dev/sda: jmicron, "jmicron_JRAID", mirror, ok, 781385728 sectors, data@ 0
  			# /dev/sdb: ddf1, ".ddf1_disks", GROUP, ok, 1953253376 sectors, data@ 0
  			(/dev/\S+):\s # device
  			(\S+),\s # format
  			"([^"]+)",\s # name
  			(mirror|stripe[d]?|GROUP),\s # type
  			(\w+),\s # status
  			(\d+)\ssectors,.* # sectors
  		$}x);
  		next unless $this->valid($device);
  
  		# trim trailing spaces from name
  		$name =~ s/\s+$//;
  
  		my $member = {
  			'device' => $device,
  			'format' => $format,
  			'type' => $type,
  			'status' => $status,
  			'size' => $sectors,
  		};
  
  		push(@{$arrays{$name}}, $member);
  	}
  	close $fh;
  
  	return \%arrays;
  }
  
  
  # plugin check
  # can store its exit code in $this->status
  # can output its message in $this->message
  sub check {
  	my $this = shift;
  	my (@status);
  
  	## Check Array and Drive Status
  	my $arrays = $this->parse;
  	while (my($name, $array) = each(%$arrays)) {
  		my @s;
  		foreach my $dev (@$array) {
  			if ($dev->{status} =~ m/sync|rebuild/i) {
  				$this->warning;
  			} elsif ($dev->{status} !~ m/ok/i) {
  				$this->critical;
  			}
  			my $size = $this->format_bytes($dev->{size});
  			push(@s, "$dev->{device}($dev->{type}, $size): $dev->{status}");
  		}
  		push(@status, "$name: " . join(', ', @s));
  	}
  
  	return unless @status;
  
  	# denote that this plugin as ran ok, not died unexpectedly
  	$this->ok->message(join(' ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dpt_i2o.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O';
  package App::Monitoring::Plugin::CheckRaid::Plugins::dpt_i2o;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub commands {
  	{
  		'proc' => ['<', '/proc/scsi/dpt_i2o'],
  		'proc entry' => ['<', '/proc/scsi/dpt_i2o/$controller'],
  	}
  }
  
  sub active {
  	my ($this) = @_;
  	return -d $this->{commands}{proc}[1];
  }
  
  sub check {
  	my $this = shift;
  	# status messages pushed here
  	my @status;
  
  	my $fh = $this->cmd('proc');
  	my @c = grep { !/^\./ } readdir($fh);
  	close($fh);
  
  	# TODO: check for failed disks!
  	for my $c (@c) {
  		my $fh = $this->cmd('proc entry', { '$controller' => $c });
  
  		while (<$fh>) {
  			if (my ($c, $t, $l, $s) = m/TID=\d+,\s+\(Channel=(\d+),\s+Target=(\d+),\s+Lun=(\d+)\)\s+\((\S+)\)/) {
  				if ($s ne "online") {
  					$this->critical;
  				}
  				push(@status, "$c,$t,$l:$s");
  			}
  		}
  		close($fh);
  	}
  
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/gdth.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH';
  package App::Monitoring::Plugin::CheckRaid::Plugins::gdth;
  
  # Linux gdth RAID
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub commands {
  	{
  		'proc' => ['<', '/proc/scsi/gdth'],
  		'proc entry' => ['<', '/proc/scsi/gdth/$controller'],
  	}
  }
  
  sub active {
  	my ($this) = @_;
  	return -d $this->{commands}{proc}[1];
  }
  
  sub parse {
  	my $this = shift;
  
  	my $fh = $this->cmd('proc');
  	my @c = grep { !/^\./ } readdir($fh);
  	close($fh);
  
  	my %c;
  	for my $c (@c) {
  		my (%ld, %ad, %pd, %l, %a, %p, $section);
  
  		my $fh = $this->cmd('proc entry', { '$controller' => $c });
  		while (<$fh>) {
  			chomp;
  
  			# new section start
  			if (my($s) = /^(\w.+):$/) {
  				$section = $s;
  				%a = %l = %p = ();
  				next;
  			}
  
  			# skip unknown sections
  			next unless /^\s/ or /^$/;
  
  			# process each section
  			if ($section eq 'Driver Parameters') {
  				# nothing useful
  			} elsif ($section eq 'Disk Array Controller Information') {
  				# nothing useful
  			} elsif ($section eq 'Physical Devices') {
  				# Chn/ID/LUN:        B/05/0          Name:           FUJITSU MAX3147NC       0104
  				# Capacity [MB]:     140239          To Log. Drive:  5
  				# Retries:           1               Reassigns:      0
  				# Grown Defects:     1
  
  				if (my($id, $n, $rv) = m{^\s+Chn/ID/LUN:\s+(\S+)\s+Name:\s+(.+)(.{4})$}) {
  					$n =~ s/\s+$//;
  					$p{id} = $id;
  					$p{name} = $n;
  					$p{revision} = $rv;
  				} elsif (my($unit, $c, $d) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+To Log\. Drive:\s+(\d+|--)/) {
  					$p{capacity} = int($c);
  					$p{capacity_unit} = $unit;
  					$p{drive} = $d;
  				} elsif (my($r, $ra) = m/^\s+Retries:\s+(\d+)\s+Reassigns:\s+(\d+)/) {
  					$p{retries} = int($r);
  					$p{reassigns} = int($ra);
  				} elsif (my($gd) = m/^\s+Grown Defects:\s+(\d+)/) {
  					$p{defects} = int($gd);
  				} elsif (/^$/) {
  					if ($p{capacity} == 0 and $p{name} =~ /SCA HSBP/) {
  						# HSBP is not a disk, so do not consider this an error
  						# http://support.gateway.com/s/Servers/COMPO/MOTHERBD/4000832/4000832si69.shtml
  						# Raid Hot Swap Backplane driver (recognized as "ESG-SHV SCA HSBP M16 SCSI Processor Device")
  						# Chn/ID/LUN:    B/06/0          Name:           ESG-SHV SCA HSBP M16    0.05
  						# Capacity [MB]: 0               To Log. Drive:  --
  						next;
  					}
  
  					$pd{$p{id}} = { %p };
  				} else {
  					warn "[$section] [$_]";
  					$this->unknown;
  				}
  
  			} elsif ($section eq 'Logical Drives') {
  				# Number:              3               Status:         ok
  				# Slave Number:        15              Status:         ok (older kernels)
  				# Capacity [MB]:       69974           Type:           Disk
  				if (my($num, $s) = m/^\s+(?:Slave )?Number:\s+(\d+)\s+Status:\s+(\S+)/) {
  					$l{number} = int($num);
  					$l{status} = $s;
  				} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
  					$l{capacity} = "$c $unit";
  					$l{type} = $t;
  				} elsif (my($md, $id) = m/^\s+Missing Drv\.:\s+(\d+)\s+Invalid Drv\.:\s+(\d+|--)/) {
  					$l{missing} = int($md);
  					$l{invalid} = int($id);
  				} elsif (my($n) = m/^\s+To Array Drv\.:\s+(\d+|--)/) {
  					$l{array} = $n;
  				} elsif (/^$/) {
  					$ld{$l{number}} = { %l };
  				} else {
  					warn "[$section] [$_]";
  					$this->unknown;
  				}
  
  			} elsif ($section eq 'Array Drives') {
  				# Number:        0               Status:         fail
  				# Capacity [MB]: 349872          Type:           RAID-5
  				if (my($num, $s) = m/^\s+Number:\s+(\d+)\s+Status:\s+(\S+)/) {
  					$a{number} = int($num);
  					$a{status} = $s;
  				} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
  					$a{capacity} = "$c $unit";
  					$a{type} = $t;
  				} elsif (/^(?: --)?$/) {
  					if (%a) {
  						$ad{$a{number}} = { %a };
  					}
  				} else {
  					warn "[$section] [$_]";
  					$this->unknown;
  				}
  
  			} elsif ($section eq 'Host Drives') {
  				# nothing useful
  			} elsif ($section eq 'Controller Events') {
  				# nothing useful
  			}
  		}
  		close($fh);
  
  		$c{$c} = { id => $c, array => { %ad }, logical => { %ld }, physical => { %pd } };
  	}
  
  	return \%c;
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $controllers = $this->parse;
  
  	# process each controller separately
  	for my $c (values %$controllers) {
  		# array status
  		my @ad;
  		for my $n (sort {$a cmp $b} keys %{$c->{array}}) {
  			my $ad = $c->{array}->{$n};
  			if ($ad->{status} ne "ready") {
  				$this->critical;
  			}
  			push(@ad, "Array $ad->{number}($ad->{type}) $ad->{status}");
  		}
  
  		# older raids have no Array drives, Look into Logical Drives for type!=Disk
  		unless (@ad) {
  			for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
  				my $ld = $c->{logical}->{$n};
  				if ($ld->{type} eq "Disk") {
  					next;
  				}
  
  				# emulate Array Drive
  				my $s = "Array($ld->{type}) $ld->{status}";
  				# check for missing drives
  				if ($ld->{missing} > 0) {
  					$this->warning;
  					$s .= " ($ld->{missing} missing drives)";
  				}
  
  				push(@ad, $s);
  			}
  		}
  
  		# logical drive status
  		my %ld;
  		for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
  			my $ld = $c->{logical}->{$n};
  			if ($ld->{status} ne "ok") {
  				$this->critical;
  			}
  			push(@{$ld{$ld->{status}}}, $ld->{number});
  		}
  
  		# physical drive status
  		my @pd;
  		for my $n (sort {$a cmp $b} keys %{$c->{physical}}) {
  			my $pd = $c->{physical}->{$n};
  
  			my @ds;
  			# TODO: make tresholds configurable
  			if ($pd->{defects} > 300) {
  				$this->critical;
  				push(@ds, "grown defects critical: $pd->{defects}");
  			} elsif ($pd->{defects} > 30) {
  				$this->warning;
  				push(@ds, "grown defects warning: $pd->{defects}");
  			}
  
  			# report disk being not assigned
  			if ($pd->{drive} eq '--') {
  				push(@ds, "not assigned");
  			}
  
  			if (@ds) {
  				push(@pd, "Disk $pd->{id}($pd->{name}) ". join(', ', @ds));
  			}
  		}
  
  		my @cd;
  		push(@cd, @ad) if @ad;
  		push(@cd, "Logical Drives: ". $this->join_status(\%ld));
  		push(@cd, @pd) if @pd;
  		push(@status, "Controller $c->{id}: ". join('; ', @cd));
  	}
  
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join('; ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hp_msa.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA';
  package App::Monitoring::Plugin::CheckRaid::Plugins::hp_msa;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use App::Monitoring::Plugin::CheckRaid::SerialLine;
  use strict;
  use warnings;
  
  sub active {
  	my $this = shift;
  	return $this->detect;
  }
  
  # check from /sys if there are any MSA VOLUME's present.
  sub detect {
  	my $this = shift;
  
  	# allow --plugin-option=hp_msa-enabled to force this plugin to be enabled
  	return 1 if exists $this->{options}{'hp_msa-enabled'};
  
  	for my $file (</sys/block/*/device/model>) {
  		open my $fh, '<', $file or next;
  		my $model = <$fh>;
  		close($fh);
  		return 1 if ($model =~ /^MSA.+VOLUME/);
  	}
  	return 0;
  }
  
  sub check {
  	my $this = shift;
  
  	#  allow --plugin-option=hp_msa-serial=/dev/ttyS2 to specify serial line
  	my $ctldevice = $this->{options}{'hp_msa-serial'} || '/dev/ttyS0';
  
  	# status messages pushed here
  	my @status;
  
  	my %opts = ();
  	$opts{lockdir} = $this->{lockdir} if $this->{lockdir};
  
  	my $modem = App::Monitoring::Plugin::CheckRaid::SerialLine->new($ctldevice, %opts);
  	my $fh = $modem->open();
  	unless ($fh) {
  		$this->warning;
  		$this->message("Can't open $ctldevice");
  		return;
  	}
  
  	# check first controller
  	print $fh "\r";
  	print $fh "show globals\r";
  	print $fh "show this_controller\r";
  	print $fh "show other_controller\r";
  	# this will issue termination match, ie. invalid command
  	print $fh "exit\r";
  
  	my ($c, %c, %t);
  	while (<$fh>) {
  		chomp;
  		s/[\n\r]$//;
  		last if /Invalid CLI command/;
  
  		# Temperature:
  		# EMU: 23 Celsius,  73 Fahrenheit
  		# PS1: 22 Celsius,  71 Fahrenheit
  		# PS2: 22 Celsius,  71 Fahrenheit
  		if (my($s, $c) = /(\S+): (\d+) Celsius,\s+\d+ Fahrenheit/) {
  			$t{$s} = int($c);
  			next;
  		}
  
  		# Controller 1 (right controller):
  		if (my($s) = /^(Controller \d+)/) {
  			$c = $s;
  			$c{$c} = [];
  			next;
  		}
  		# Surface Scan: Running, LUN 10 (68% Complete)
  		if (my($s, $m) = /Surface Scan:\s+(\S+)[,.]\s*(.*)/) {
  			if ($s eq 'Running') {
  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
  				push(@{$c{$c}}, "Surface: $l ($p%)");
  				$this->warning;
  			} elsif ($s ne 'Complete') {
  				push(@{$c{$c}}, "Surface: $s, $m");
  				$this->warning;
  			}
  			next;
  		}
  		# Rebuild Status: Running, LUN 0 (67% Complete)
  		if (my($s, $m) = /Rebuild Status:\s+(\S+)[,.]\s*(.*)/) {
  			if ($s eq 'Running') {
  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
  				push(@{$c{$c}}, "Rebuild: $l ($p%)");
  				$this->warning;
  			} elsif ($s ne 'Complete') {
  				push(@{$c{$c}}, "Rebuild: $s, $m");
  				$this->warning;
  			}
  			next;
  		}
  		# Expansion: Complete.
  		if (my($s, $m) = /Expansion:\s+(\S+)[.,]\s*(.*)/) {
  			if ($s eq 'Running') {
  				my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
  				push(@{$c{$c}}, "Expansion: $l ($p%)");
  				$this->warning;
  			} elsif ($s ne 'Complete') {
  				push(@{$c{$c}}, "Expansion: $s, $m");
  				$this->warning;
  			}
  			next;
  		}
  	}
  	$modem->close();
  
  	foreach $c (sort { $a cmp $b } keys %c) {
  		my $s = $c{$c};
  		$s = join(', ', @$s);
  		$s = 'OK' unless $s;
  		push(@status, "$c: $s");
  	}
  
  	# check that no temp is over the treshold
  	my $warn = 28;
  	my $crit = 33;
  	while (my($t, $c) = each %t) {
  		if ($c > $crit) {
  			push(@status, "$t: ${c}C");
  			$this->critical;
  		} elsif ($c > $warn) {
  			push(@status, "$t: ${c}C");
  			$this->warning;
  		}
  	}
  
  	return unless @status;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpacucli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli;
  
  ## hpacucli/hpssacli/ssacli support
  #
  # driver developers recommend to use cciss_vol_status for monitoring,
  # hpacucli/hpssacli shouldn't be used for monitoring due they obtaining global
  # kernel lock while cciss_vol_status does not. cciss_vol_status is designed for
  # monitoring
  # https://github.com/glensc/nagios-plugin-check_raid/issues/114#issuecomment-138866801
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  use constant E_NO_LOGICAL_DEVS => 'The specified device does not have any logical drives';
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'controller status' => ['-|', '@CMD', 'controller', 'all', 'show', 'status'],
  		'logicaldrive status' => ['-|', '@CMD', 'controller', '$target', 'logicaldrive', 'all', 'show'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller all show status",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller * logicaldrive all show",
  	);
  }
  
  # if --plugin-option=hpacucli-target=slot=0 is specified
  # filter only allowed values
  sub filter_targets {
  	my ($this, $targets) = @_;
  
  	my $cli_opts = $this->{options}{'hpacucli-target'};
  	if (!$cli_opts) {
  		return $targets;
  	}
  
  	my %res;
  	my @filters = split(/,/, $cli_opts);
  	for my $filter (@filters) {
  		if (exists $targets->{$filter}) {
  			$res{$filter} = $targets->{$filter};
  		} else {
  			$this->critical->message("Controller $filter not found");
  		}
  	}
  
  	return \%res;
  }
  
  # split:
  # '(Embedded) (RAID Mode)'
  # to:
  # [ 'Embedded', 'RAID Mode' ]
  sub split_controller_modes {
  	my ($modes) = @_;
  	my @parts;
  	push @parts, $1 while $modes =~ /\((.*?)\)/g;
  	return \@parts;
  }
  
  sub scan_targets {
  	my $this = shift;
  
  	# TODO: allow target customize:
  	# hpacucli <target> is of format:
  	#  [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ]
  	#  [array all|<id>]
  	#  [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#]
  	#  [logicaldrive all|#]
  	#  [enclosure all|#:#|serialnumber=#|chassisname=#]
  	#  [licensekey all|<key>]
  
  	# Scan controllers
  	my (%targets, $target);
  	my $fh = $this->cmd('controller status');
  	while (<$fh>) {
  		chomp;
  		# skip empty lines and artificial comments (added by this project)
  		next if /^$/ or /^#/;
  
  		# skip known noise
  		if (
  			/FIRMWARE UPGRADE REQUIRED: /
  			|| /^\s{27}/
  		) {
  			next;
  		}
  
  		# Numeric slot
  		if (my($controller, $slot, $modes) = /
  				^(\S.+)\sin\sSlot
  				\s(\S+?) # slot number
  				(?:     # optional mode
  					\s(\(.+\))
  				)?$
  			/x) {
  
  			$target = "slot=$slot";
  			$targets{$target} = {
  				target => $target,
  				controller => $controller,
  				slot => $slot,
  				modes => split_controller_modes($modes || ''),
  			};
  			$this->unknown if $slot !~ /^\d+/;
  			next;
  		}
  
  		# Named Entry
  		if (my($controller, $cn) = /^(\S.+) in (.+)/) {
  			$target = "chassisname=$cn";
  			$targets{$target} = {
  				target => $target,
  				controller => $controller,
  				chassisname => $cn,
  			};
  			next;
  		}
  
  		# Other statuses, try "key: value" pairs
  		if (my ($key, $value) = /^\s*(.+?):\s+(.+?)$/) {
  			$targets{$target}{$key} = $value;
  			next;
  		}
  
  		warn "Unparsed: [$_]\n";
  	}
  	close $fh;
  
  	return $this->filter_targets(\%targets);
  }
  
  # Scan logical drives
  sub scan_luns {
  	my ($this, $targets) = @_;
  
  	my @luns;
  	# sort by target to ensure consistent results
  	for my $target (sort {$a->{target} cmp $b->{target}} values(%$targets)) {
  		# check each controller
  		my $fh = $this->cmd('logicaldrive status', { '$target' => $target->{target} });
  
  		my $index = -1;
  		my @array;
  		my %array;
  		while (<$fh>) {
  			chomp;
  			# skip empty lines and artificial comments (added by this project)
  			next if /^$/ or /^#/;
  
  			# Error: The controller identified by "slot=attr_value_slot_unknown" was not detected.
  			if (/^Error:\s/) {
  				# store it somewhere. should it be appended?
  				($target->{'error'}) = /^Error:\s+(.+?)\.?\s*$/;
  				$this->unknown;
  				next;
  			}
  
  			# "array A"
  			# "array A (Failed)"
  			# "array B (Failed)"
  			if (my($a, $s) = /^\s+array (\S+)(?:\s*\((\S+)\))?$/i) {
  				$index++;
  				# Offset 0 is Array own status
  				# XXX: I don't like this one: undef could be false positive
  				$target->{'array'}[$index]{status} = $s || 'OK';
  				$target->{'array'}[$index]{name} = $a;
  				next;
  			}
  
  			# logicaldrive 1 (68.3 GB, RAID 1, OK)
  			# capture only status
  			if (my($drive, $size, $raid, $status) = /^\s+logicaldrive (\d+) \(([\d.]+ .B), ([^,]+), ([^\)]+)\)$/) {
  				warn "Index out of bounds" if $index < 0; # XXX should not happen
  
  				# Offset 1 is each logical drive status
  				my $ld = {
  					'id' => $drive,
  					'status' => $status,
  					'size' => $size,
  					'raid' => $raid,
  				};
  				push(@{$target->{'array'}[$index]{logicaldrives}}, $ld);
  				next;
  			}
  
  			# skip known noise
  			if (
  				/\s+Type "help" for more details/
  				# Controller name: exact match
  				|| /^\Q$target->{controller}\E\s/
  				# loose match, some test data seems malformed
  				|| / in Slot \d/
  				|| /^FIRMWARE UPGRADE REQUIRED:/
  				|| /^\s{27}/
  			) {
  				next;
  			}
  
  			warn "Unhandled: [$_]\n";
  		}
  		$this->unknown unless close $fh;
  
  		push(@luns, $target);
  	}
  
  	return \@luns;
  }
  
  # parse hpacucli output into logical structure
  sub parse {
  	my $this = shift;
  
  	my $targets = $this->scan_targets;
  	if (!$targets) {
  		return $targets;
  	}
  
  	return $this->scan_luns($targets);
  }
  
  # format lun (logicaldevice) status
  # update check status if problems found
  sub lstatus {
  	my ($this, $ld) = @_;
  
  	my $s = $ld->{status};
  
  	if ($s eq 'OK' or $s eq 'Disabled') {
  	} elsif ($s eq 'Failed' or $s eq 'Interim Recovery Mode') {
  		$this->critical;
  	} elsif ($s eq 'Rebuild' or $s eq 'Recover') {
  		$this->warning;
  	}
  
  	return "LUN$ld->{id}:$s";
  }
  
  # format array status
  # update check status if problems found
  sub astatus {
  	my ($this, $array) = @_;
  
  	if ($array->{status} ne 'OK') {
  		$this->critical;
  	}
  
  	return "Array $array->{name}($array->{status})";
  }
  
  # format controller status
  # updates check status if problems found
  sub cstatus {
  	my ($this, $c) = @_;
  	my (@s, $s);
  
  	# always include controller status
  	push(@s, $c->{'Controller Status'} || 'ERROR');
  	if ($c->{'Controller Status'} ne 'OK') {
  		$this->critical;
  	}
  
  	if ($c->{error}) {
  		if ($c->{error} eq E_NO_LOGICAL_DEVS) {
  			$this->noraid;
  			push(@s, 'Not configured');
  		} else {
  			$this->unknown;
  			push(@s, $c->{error});
  		}
  	}
  
  	# print those only if not ok and configured
  	if (($s = $c->{'Cache Status'}) && $s !~ /^(OK|Not Configured)/) {
  		push(@s, "Cache: $s");
  		$this->critical;
  	}
  	if (($s = $c->{'Battery/Capacitor Status'}) && $s !~ /^(OK|Not Configured)/) {
  		push(@s, "Battery: $s");
  		$this->critical;
  	}
  
  	# start with identifyier
  	my $name = $c->{chassisname} || $c->{controller};
  
  	return $name . '[' . join(', ', @s) . ']';
  }
  
  sub check {
  	my $this = shift;
  
  	my $ctrls = $this->parse;
  	unless ($ctrls) {
  		$this->warning->message("No Controllers were found on this machine");
  		return;
  	}
  
  	my @status;
  	foreach my $ctrl (@$ctrls) {
  		my @astatus;
  		foreach my $array (@{$ctrl->{array}}) {
  			my @lstatus;
  			foreach my $ld (@{$array->{logicaldrives}}) {
  				push(@lstatus, $this->lstatus($ld));
  			}
  			push(@astatus, $this->astatus($array). '['. join(',', @lstatus). ']');
  		}
  		my $cstatus = $this->cstatus($ctrl);
  		$cstatus .= ': '. join(', ', @astatus) if @astatus;
  		push(@status, $cstatus);
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::hpssacli;
  
  # This plugin extends hpacucli plugin,
  # with the only difference that different program name will be used.
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli';
  use strict;
  use warnings;
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ips.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS';
  package App::Monitoring::Plugin::CheckRaid::Plugins::ips;
  
  # Serveraid IPS
  # Tested on IBM xSeries 346 servers with Adaptec ServeRAID 7k controllers.
  # The ipssend version was v7.12.14.
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	qw(ipssend);
  }
  
  sub commands {
  	{
  		'list logical drive' => ['-|', '@CMD', 'GETCONFIG', '1', 'LD'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd getconfig 1 LD"
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $n;
  	my $fh = $this->cmd('list logical drive');
  	while (<$fh>) {
  		if (/drive number (\d+)/i){
  			$n = $1;
  			next;
  		}
  
  		next unless $n;
  		next unless $this->valid($n);
  		next unless (my($s, $c) = /Status .*: (\S+)\s+(\S+)/);
  
  		if ($c =~ /SYN|RBL/i) { # resynching
  			$this->resync;
  		} elsif ($c !~ /OKY/i) { # not OK
  			$this->critical;
  		}
  
  		push(@status, "$n:$s");
  	}
  	close $fh;
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID';
  package App::Monitoring::Plugin::CheckRaid::Plugins::lsraid;
  
  # Linux, software RAID
  # Broken: missing test data
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'list' => ['-|', '@CMD', '-A', '-p'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -A -p"
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $fh = $this->cmd('list');
  	while (<$fh>) {
  		next unless (my($n, $s) = m{/dev/(\S+) \S+ (\S+)});
  		next unless $this->valid($n);
  		if ($s =~ /good|online/) {
  			# no worries
  		} elsif ($s =~ /sync/) {
  			$this->warning;
  		} else {
  			$this->critical;
  		}
  		push(@status, "$n:$s");
  	}
  	close $fh;
  
  	return unless @status;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsscsi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'lsscsi list' => ['-|', '@CMD', '-g'],
  	}
  }
  
  # lists contoller devices (type=storage)
  # this will fail (return empty list) if sg module is not present
  # return /dev/sgX nodes
  sub list_sg {
  	my $this = shift;
  
  	my @scan = $this->scan;
  
  	my @devs = map { $_->{sgnode} } grep { $_->{type} eq 'storage' && $_->{sgnode} ne '-' } @scan;
  	return wantarray ? @devs : \@devs;
  }
  
  # list disk nodes one for each controller
  # return /dev/sdX nodes
  sub list_dd {
  	my $this = shift;
  
  	my @scan = $this->scan;
  	my @devs = map { $_->{devnode} } grep { $_->{type} eq 'disk' && $_->{devnode} ne '-' && $_->{sgnode} } @scan;
  	return wantarray ? @devs : \@devs;
  }
  
  # scan lsscsi output
  sub scan {
  	my $this = shift;
  
  	# cache inside single run
  	return wantarray ? @{$this->{sdevs}} : $this->{sdevs} if $this->{sdevs};
  
  	# Scan such output:
  	# [0:0:0:0]    disk    HP       LOGICAL VOLUME   3.00  /dev/sda   /dev/sg0
  	# [0:3:0:0]    storage HP       P410i            3.00  -          /dev/sg1
  	# or without sg driver:
  	# [0:0:0:0]    disk    HP       LOGICAL VOLUME   3.00  /dev/sda   -
  	# [0:3:0:0]    storage HP       P410i            3.00  -          -
  
  	my $fh = $this->cmd('lsscsi list');
  	my @sdevs;
  	while (<$fh>) {
  		chop;
  		if (my($hctl, $type, $vendor, $model, $rev, $devnode, $sgnode) = m{^
  			\[([\d:]+)\] # SCSI Controller, SCSI bus, SCSI target, and SCSI LUN
  			\s+(\S+) # type
  			\s+(\S+) # vendor
  			\s+(.*?) # model, match everything as it may contain spaces
  			\s+(\S+) # revision
  			\s+((?:/dev/\S+|-)) # /dev node
  			\s+((?:/dev/\S+|-)) # /dev/sg node
  		}x) {
  			push(@sdevs, {
  				'hctl' => $hctl,
  				'type' => $type,
  				'vendor' => $vendor,
  				'model' => $model,
  				'rev' => $rev,
  				'devnode' => $devnode,
  				'sgnode' => $sgnode,
  			});
  		}
  	}
  	close $fh;
  
  	$this->{sdevs} = \@sdevs;
  	return wantarray ? @sdevs : \@sdevs;
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsvg.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG';
  package App::Monitoring::Plugin::CheckRaid::Plugins::lsvg;
  
  # AIX LVM
  # Status: broken (no test data)
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'lsvg' => ['-|', '@CMD'],
  		'lsvg list' => ['-|', '@CMD', '-l', '$vg'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -l *",
  	)
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my @vg;
  	my $fh = $this->cmd('lsvg');
  	while (<$fh>) {
  		chomp;
  		push @vg, $_;
  	}
  	close $fh;
  
  	foreach my $vg (@vg) {
  		next unless $this->valid($vg); # skip entire VG
  
  		my $fh = $this->cmd('lsvg list', { '$vg' => $vg });
  
  		while (<$fh>) {
  			my @f = split /\s/;
  			my ($n, $s) = ($f[0], $f[5]);
  			next if (!$this->valid($n) or !$s);
  			next if ($f[3] eq $f[2]); # not a mirrored LV
  
  			if ($s =~ m#open/(\S+)#i) {
  				$s = $1;
  				if ($s ne 'syncd') {
  					$this->critical;
  				}
  				push(@status, "lvm:$n:$s");
  			}
  		}
  		close $fh;
  	}
  
  	return unless @status;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mdstat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT';
  package App::Monitoring::Plugin::CheckRaid::Plugins::mdstat;
  
  # Linux Multi-Device (md)
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub commands {
  	{
  		'mdstat' => ['<', '/proc/mdstat'],
  	}
  }
  
  sub active {
  	my ($this) = @_;
  	# easy way out. no /proc/mdstat
  	return 0 unless -e $this->{commands}{mdstat}[1];
  
  	# extra check if mdstat is empty
  	my @md = $this->parse;
  	return $#md >= 0;
  }
  
  sub parse {
  	my $this = shift;
  
  	my (@md, %md);
  	my $fh = $this->cmd('mdstat');
  	my $arr_checking = 0;
  	while (<$fh>) {
  		chomp;
  
  		# skip first line
  		next if (/^Personalities : /);
  
  		# kernel-3.0.101/drivers/md/md.c, md_seq_show
  		# md1 : active raid1 sdb2[0] sda2[1]
  		if (my($dev, $active, $ro, $rest) = m{^
  			(\S+)\s+:\s+ # mdname
  			(\S+)\s+     # active: "inactive", "active"
  			(\((?:auto-)?read-only\)\s+)? # readonly
  			(.+)         # personality name + disks
  		}x) {
  			my @parts = split /\s/, $rest;
  			my $re = qr{^
  				(\S+)           # devname
  				(?:\[(\d+)\])   # desc_nr
  				(?:\((.)\))?    # flags: (W|F|S) - WriteMostly, Faulty, Spare
  			$}x;
  			my @disks = ();
  			my $personality;
  			while (my($disk) = pop @parts) {
  				last if !$disk;
  				if ($disk !~ $re) {
  					$personality = $disk;
  					last;
  				}
  				my($dev, $number, $flags) = $disk =~ $re;
  				push(@disks, {
  					'dev' => $dev,
  					'number' => int($number),
  					'flags' => $flags || '',
  				});
  			}
  
  			die "Unexpected parse" if @parts;
  
  			# first line resets %md
  			%md = (dev => $dev, personality => $personality, readonly => $ro, active => $active, disks => [ @disks ]);
  
  			next;
  		}
  
  		# variations:
  		#"      8008320 blocks [2/2] [UU]"
  		#"      58291648 blocks 64k rounding" - linear
  		#"      5288 blocks super external:imsm"
  		#"      20969472 blocks super 1.2 512k chunks"
  		#
  		# Metadata version:
  		# This is one of
  		# - 'none' for arrays with no metadata (good luck...)
  		# - 'external' for arrays with externally managed metadata,
  		# - or N.M for internally known formats
  		#
  		if (my($b, $mdv, $status) = m{^
  			\s+(\d+)\sblocks\s+ # blocks
  			# metadata version
  			(super\s(?:
  				(?:\d+\.\d+) | # N.M
  				(?:external:\S+) |
  				(?:non-persistent)
  			))?\s*
  			(.+) # mddev->pers->status (raid specific)
  		$}x) {
  			# linux-2.6.33/drivers/md/dm-raid1.c, device_status_char
  			# A => Alive - No failures
  			# D => Dead - A write failure occurred leaving mirror out-of-sync
  			# S => Sync - A sychronization failure occurred, mirror out-of-sync
  			# R => Read - A read failure occurred, mirror data unaffected
  			# U => for the rest
  			my ($s) = $status =~ /\s+\[([ADSRU_]+)\]/;
  
  			$md{status} = $s || '';
  			$md{blocks} = int($b);
  			$md{md_version} = $mdv;
  
  			# if external try to parse dev
  			if ($mdv) {
  				($md{md_external}) = $mdv =~ m{external:(\S+)};
  			}
  			next;
  		}
  
  		# linux-2.6.33/drivers/md/md.c, md_seq_show
  		if (my($action) = m{(resync=(?:PENDING|DELAYED))}) {
  			$md{resync_status} = $action;
  			next;
  		}
  		# linux-2.6.33/drivers/md/md.c, status_resync
  		# [==>..................]  resync = 13.0% (95900032/732515712) finish=175.4min speed=60459K/sec
  		# [=>...................]  check =  8.8% (34390144/390443648) finish=194.2min speed=30550K/sec
  		if (my($action, $perc, $eta, $speed) = m{(resync|recovery|reshape)\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
  			$md{resync_status} = "$action:$perc $speed ETA: $eta";
  			next;
  		} elsif (($perc, $eta, $speed) = m{check\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
  			$md{check_status} = "check:$perc $speed ETA: $eta";
  			$arr_checking = 1;
  			next;
  		}
  
  		# we need empty line denoting end of one md
  		next unless /^\s*$/;
  
  		next unless $this->valid($md{dev});
  
  		push(@md, { %md } ) if %md;
  	}
  	close $fh;
  
  	# One of the arrays is in checking state, which could be because there is a scheduled sync of all MD arrays
  	# In such a case, all of the arrays are scheduled to by checked, but only one of them is actually running the check
  	# while the others are in "resync=DELAYED" state.
  	# We don't want to receive notifications in such case, so we check for this particular case here
  	if ($arr_checking && scalar(@md) >= 2) {
  		foreach my $dev (@md) {
  			if ($dev->{resync_status} && $dev->{resync_status} eq "resync=DELAYED") {
  				delete $dev->{resync_status};
  				$dev->{check_status} = "check=DELAYED";
  			}
  		}
  	}
  
  	return wantarray ? @md : \@md;
  }
  
  sub check {
  	my $this = shift;
  
  	my (@status);
  	my @md = $this->parse;
  
  	my @spare_options = ();
  
  	@spare_options = split(/\,/, $this->{options}{mdstat_spare_count})
  		if (exists $this->{options}{mdstat_spare_count});
  
  	foreach (@md) {
  		my %md = %$_;
  
  		# common status
  		my $size = $this->format_bytes($md{blocks} * 1024);
  		my $personality = $md{personality} ? " $md{personality}" : "";
  		my $s = "$md{dev}($size$personality):";
  
  		# failed disks
  		my @fd = map { $_->{dev} } grep { $_->{flags} =~ /F/ } @{$md{disks}};
  		# spare disks
  		my @sd = map { $_->{dev} } grep { $_->{flags} =~ /S/ } @{$md{disks}};
  
  		my $spare_count = 0;
  		OPTION_LOOP:
  		{
  			foreach my $i (0 .. $#spare_options)
  			{
  				my ($disk, $value) = split(/:/, $spare_options[$i]);
  				for(@md)
  				{
  					if ($md{dev} eq $disk)
  					{
  						$spare_count = $value;
  						splice(@spare_options, $i, 1);
  						last OPTION_LOOP;
  					}
  				}
  			}
  		}
  
  		# raid0 is just there or its not. raid0 can't degrade.
  		# same for linear, no $md_status available
  		if ($personality =~ /linear|raid0/) {
  			$s .= "OK";
  
  		} elsif ($md{resync_status}) {
  			$this->resync;
  			$s .= "$md{status} ($md{resync_status})";
  
  		} elsif ($md{check_status}) {
  			$this->check_status;
  			$s .= "$md{status} ($md{check_status})";
  
  		} elsif ($md{status} =~ /_/) {
  			$this->critical;
  			my $fd = join(',', @fd);
  			$s .= "F:$fd:$md{status}";
  
  		} elsif (@fd > 0) {
  			# FIXME: this is same as above?
  			$this->warning;
  			$s .= "hot-spare failure:". join(",", @fd) .":$md{status}";
  		} elsif (@sd < $spare_count)
  		{
  			$this->warning;
  			$s .= "Array ".$md{dev}." should have ".$spare_count." spares, but has only ".(0+@sd)." spares";
  		} else {
  			$s .= "$md{status}";
  		}
  		push(@status, $s);
  	}
  
  	if (scalar @spare_options > 0)
  	{
  		$this->critical;
  		foreach (@spare_options)
  		{
  			my ($disk, $value) = split(/:/, $_);
  			my $s = "$disk is defined in spare_count option but could not be found!";
  			push(@status, $s);
  		}
  	}
  
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::megacli;
  
  # MegaRAID SAS 8xxx controllers
  # based on info from here:
  # http://www.bxtra.net/Articles/2008-09-16/Dell-Perc6i-RAID-Monitoring-Script-using-MegaCli-LSI-CentOS-52-64-bits
  # TODO: http://www.techno-obscura.com/~delgado/code/check_megaraid_sas
  # TODO: process several adapters
  # TODO: process drive temperatures
  # TODO: check error counts
  # TODO: hostspare information
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	qw(MegaCli64 MegaCli megacli);
  }
  
  sub commands {
  	{
  		'pdlist' => ['-|', '@CMD', '-PDList', '-aALL', '-NoLog'],
  		'ldinfo' => ['-|', '@CMD', '-LdInfo', '-Lall', '-aALL', '-NoLog'],
  		'battery' => ['-|', '@CMD', '-AdpBbuCmd', '-GetBbuStatus', '-aALL', '-NoLog'],
  	}
  }
  
  # TODO: process from COMMANDS
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -PDList -aALL -NoLog",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -LdInfo -Lall -aALL -NoLog",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpBbuCmd -GetBbuStatus -aALL -NoLog",
  	);
  }
  
  # parse physical devices
  sub parse_pd {
  	my $this = shift;
  
  	my (@pd, %pd);
  	my $rc = -1;
  	my $fh = $this->cmd('pdlist');
  	while (<$fh>) {
  		if (my($s) = /Device Id: (\S+)/) {
  			push(@pd, { %pd }) if %pd;
  			%pd = ( dev => $s, state => undef, name => undef, predictive => undef );
  			next;
  		}
  
  		if (my($s) = /Firmware state: (.+)/) {
  			# strip the extra state:
  			# 'Hotspare, Spun Up'
  			# 'Hotspare, Spun down'
  			# 'Online, Spun Up'
  			# 'Online, Spun Up'
  			# 'Online, Spun down'
  			# 'Unconfigured(bad)'
  			# 'Unconfigured(good), Spun Up'
  			# 'Unconfigured(good), Spun down'
  			$s =~ s/,.+//;
  			$pd{state} = $s;
  
  			if (defined($pd{predictive})) {
  				$pd{state} = $pd{predictive};
  			}
  			next;
  		}
  
  		if (my($s) = /Predictive Failure Count: (\d+)/) {
  			if ($s > 0) {
  				$pd{predictive} = 'Predictive';
  			}
  			next;
  		}
  
  		if (my($s) = /Inquiry Data: (.+)/) {
  			# trim some spaces
  			$s =~ s/\s+/ /g; $s =~ s/^\s+|\s+$//g;
  			$pd{name} = $s;
  			next;
  		}
  
  		if (my($s) = /Exit Code: (\d+x\d+)/) {
  			$rc = hex($s);
  		}
  		else {
  			$rc = 0;
  		}
  	}
  	push(@pd, { %pd }) if %pd;
  
  	$this->critical unless close $fh;
  	$this->critical if $rc;
  
  	return \@pd;
  }
  
  sub parse_ld {
  	my $this = shift;
  
  	my (@ld, %ld);
  	my $rc = -1;
  	my $fh = $this->cmd('ldinfo');
  	while (<$fh>) {
  		if (my($drive_id, $target_id) = /Virtual (?:Disk|Drive)\s*:\s*(\d+)\s*\(Target Id:\s*(\d+)\)/i) {
  			push(@ld, { %ld }) if %ld;
  			# Default to DriveID:TargetID in case no Name is given ...
  			%ld = ( name => "DISK$drive_id.$target_id", state => undef );
  			next;
  		}
  
  		if (my($name) = /Name\s*:\s*(\S+)/) {
  			# Add a symbolic name, if given
  			$ld{name} = $name;
  			next;
  		}
  
  		if (my($s) = /Virtual Drive Type\s*:\s*(\S+)/) {
  			$ld{type} = $s;
  			next;
  		}
  
  		if (my($s) = /State\s*:\s*(\S+)/) {
  			$ld{state} = $s;
  			next;
  		}
  
  		if (my($s) = /Default Cache Policy\s*:\s*(.+)/) {
  			$ld{default_cache} = [split /,\s*/, $s];
  			next;
  		}
  
  		if (my($s) = /Current Cache Policy\s*:\s*(.+)/) {
  			$ld{current_cache} = [split /,\s*/, $s];
  			next;
  		}
  
  		if (my($s) = /Exit Code: (\d+x\d+)/) {
  			$rc = hex($s);
  		} else {
  			$rc = 0;
  		}
  	}
  	push(@ld, { %ld }) if %ld;
  
  	$this->critical unless close $fh;
  	$this->critical if $rc;
  
  	return \@ld;
  }
  
  # check battery
  sub parse_bbu {
  	my $this = shift;
  
  	return undef unless $this->bbu_monitoring;
  
  	my %default_bbu = (
  		name => undef, state => '???', charging_status => '???', missing => undef,
  		learn_requested => undef, replacement_required => undef,
  		learn_cycle_requested => undef, learn_cycle_active => '???',
  		pack_will_fail => undef, temperature => undef, temperature_state => undef,
  		voltage => undef, voltage_state => undef
  	);
  
  	my (@bbu, %bbu);
  	my $fh = $this->cmd('battery');
  	while (<$fh>) {
  		# handle when bbu status get gives an error. see issue #32
  		if (my($s) = /Get BBU Status Failed/) {
  			last;
  		}
  
  		if (my($s) = /BBU status for Adapter: (.+)/) {
  			push(@bbu, { %bbu }) if %bbu;
  			%bbu = %default_bbu;
  			$bbu{name} = $s;
  			next;
  		}
  #=cut
  # according to current sample data, Battery State never has value
  		if (my($s) = /Battery State\s*: ?(.*)/i) {
  			if (!$s) { $s = 'Faulty'; };
  			$bbu{state} = $s;
  			next;
  		}
  #=cut
  		if (my($s) = /Charging Status\s*: (\w*)/) {
  			$bbu{charging_status} = $s;
  			next;
  		}
  		if (my($s) = /Battery Pack Missing\s*: (\w*)/) {
  			$bbu{missing} = $s;
  			next;
  		}
  		if (my($s) = /Battery Replacement required\s*: (\w*)/) {
  			$bbu{replacement_required} = $s;
  			next;
  		}
  		if (my($s) = /Learn Cycle Requested\s*: (\w*)/) {
  			$bbu{learn_cycle_requested} = $s;
  			next;
  		}
  		if (my($s) = /Learn Cycle Active\s*: (\w*)/) {
  			$bbu{learn_cycle_active} = $s;
  			next;
  		}
  		if (my($s) = /Pack is about to fail & should be replaced\s*: (\w*)/) {
  			$bbu{pack_will_fail} = $s;
  			next;
  		}
  		# Temperature: 18 C
  		if (my($s) = /Temperature: (\d+) C/) {
  			$bbu{temperature} = $s;
  			next;
  		}
  		# Temperature : OK
  		if (my($s) = /  Temperature\s*: (\w*)/) {
  			$bbu{temperature_state} = $s;
  			next;
  		}
  		# Voltage: 4074 mV
  		if (my($s) = /Voltage: (\d+) mV/) {
  			$bbu{voltage} = $s;
  			next;
  		}
  		# Voltage : OK
  		if (my($s) = /Voltage\s*: (\w*)/) {
  			$bbu{voltage_state} = $s;
  			next;
  		}
  
  	}
  	$this->critical unless close $fh;
  
  	push(@bbu, { %bbu }) if %bbu;
  
  	return \@bbu;
  }
  
  sub parse {
  	my $this = shift;
  
  	my $pd = $this->parse_pd;
  	my $ld = $this->parse_ld;
  	my $bbu = $this->parse_bbu;
  
  	my @devs = @$pd if $pd;
  	my @vols = @$ld if $ld;
  	my @bats = @$bbu if $bbu;
  
  	return {
  		logical => $ld,
  		physical => $pd,
  		battery => $bbu,
  	};
  }
  
  sub check {
  	my $this = shift;
  
  	my $c = $this->parse;
  
  	my @vstatus;
  	foreach my $vol (@{$c->{logical}}) {
  		# skip CacheCade for now. #91
  		if ($vol->{type} && $vol->{type} eq 'CacheCade') {
  			next;
  		}
  
  		push(@vstatus, sprintf "%s:%s", $vol->{name}, $vol->{state});
  		if ($vol->{state} ne 'Optimal') {
  			$this->critical;
  		}
  
  		# check cache policy, #65
  		my @wt = grep { /WriteThrough/ } @{$vol->{current_cache}};
  		if (@wt) {
  			my @default = grep { /WriteThrough/ } @{$vol->{default_cache}};
  			# alert if WriteThrough is configured in default
  			$this->cache_fail unless @default;
  			push(@vstatus, "WriteCache:DISABLED");
  		}
  	}
  
  	my %dstatus;
  	foreach my $dev (@{$c->{physical}}) {
  		if ($dev->{state} eq 'Online' || $dev->{state} eq 'Hotspare' || $dev->{state} eq 'Unconfigured(good)' || $dev->{state} eq 'JBOD') {
  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d", $dev->{dev});
  
  		} elsif ($dev->{state} eq 'Predictive') {
  			$this->warning;
  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
  		} else {
  			$this->critical;
  			# TODO: process other statuses
  			push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
  		}
  	}
  
  	my (%bstatus, @bpdata, @blongout);
  	foreach my $bat (@{$c->{battery}}) {
  		if ($bat->{state} !~ /^(Operational|Optimal)$/) {
  			# BBU learn cycle in progress.
  			if ($bat->{charging_status} =~ /^(Charging|Discharging)$/ && $bat->{learn_cycle_active} eq 'Yes') {
  				$this->bbulearn;
  			} else {
  				$this->critical;
  			}
  		}
  		if ($bat->{missing} ne 'No') {
  			$this->critical;
  		}
  		if ($bat->{replacement_required} ne 'No') {
  			$this->critical;
  		}
  		if (defined($bat->{pack_will_fail}) && $bat->{pack_will_fail} ne 'No') {
  			$this->critical;
  		}
  		if ($bat->{temperature_state} ne 'OK') {
  			$this->critical;
  		}
  		if ($bat->{voltage_state} ne 'OK') {
  			$this->critical;
  		}
  
  		# Short output.
  		#
  		# CRITICAL: megacli:[Volumes(1): NoName:Optimal; Devices(2): 06,07=Online; Batteries(1): 0=Non Operational]
  		push(@{$bstatus{$bat->{state}}}, sprintf "%d", $bat->{name});
  		# Performance data.
  		# Return current battery temparature & voltage.
  		#
  		# Battery0=18;4074
  		push(@bpdata, sprintf "Battery%s_T=%s;;;; Battery%s_V=%s;;;;", $bat->{name}, $bat->{temperature}, $bat->{name}, $bat->{voltage});
  
  		# Long output.
  		# Detailed plugin output.
  		#
  		# Battery0:
  		#  - State: Non Operational
  		#  - Missing: No
  		#  - Replacement required: Yes
  		#  - About to fail: No
  		#  - Temperature: OK (18 °C)
  		#  - Voltage: OK (4015 mV)
  		push(@blongout, join("\n", grep {/./}
  			"Battery$bat->{name}:",
  			" - State: $bat->{state}",
  			" - Charging status: $bat->{charging_status}",
  			" - Learn cycle requested: $bat->{learn_cycle_requested}",
  			" - Learn cycle active: $bat->{learn_cycle_active}",
  			" - Missing: $bat->{missing}",
  			" - Replacement required: $bat->{replacement_required}",
  			defined($bat->{pack_will_fail}) ? " - About to fail: $bat->{pack_will_fail}" : "",
  			" - Temperature: $bat->{temperature_state} ($bat->{temperature} C)",
  			" - Voltage: $bat->{voltage_state} ($bat->{voltage} mV)",
  		));
  	}
  
  	my @cstatus;
  	push(@cstatus, 'Volumes(' . ($#{$c->{logical}} + 1) . '): ' . join(',', @vstatus));
  	push(@cstatus, 'Devices(' . ($#{$c->{physical}} + 1) . '): ' . $this->join_status(\%dstatus));
  	push(@cstatus, 'Batteries(' . ($#{$c->{battery}} + 1) . '): ' . $this->join_status(\%bstatus)) if @{$c->{battery}};
  	my @status = join('; ', @cstatus);
  
  	my @pdata;
  	push(@pdata,
  		join('\n', @bpdata)
  	);
  	my @longout;
  	push(@longout,
  		join('\n', @blongout)
  	);
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join(' ', @status));
  	$this->perfdata(join(' ', @pdata));
  	$this->longoutput(join(' ', @longout));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaide.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE';
  package App::Monitoring::Plugin::CheckRaid::Plugins::megaide;
  
  # MegaIDE RAID controller
  # Status: BROKEN: no test data
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub sudo {
  	my ($this) = @_;
  	my $cat = $this->which('cat');
  
  	"CHECK_RAID ALL=(root) NOPASSWD: $cat /proc/megaide/0/status";
  }
  
  sub check {
  	my $this = shift;
  	my $fh;
  
  	# status messages pushed here
  	my @status;
  
  	foreach my $f (</proc/megaide/*/status>) { # / silly comment to fix vim syntax hilighting
  		if (-r $f) {
  			open $fh, '<', $f or next;
  =cut
  		} else {
  			my @CMD = ($cat, $f);
  			unshift(@CMD, $sudo) if $> and $sudo;
  			open($fh , '-|', @CMD) or next;
  =cut
  		}
  		while (<$fh>) {
  			next unless (my($s, $n) = /Status\s*:\s*(\S+).*Logical Drive.*:\s*(\d+)/i);
  			next unless $this->valid($n);
  			if ($s ne 'ONLINE') {
  				$this->critical;
  				push(@status, "$n:$s");
  			} else {
  				push(@status, "$n:$s");
  			}
  			last;
  		}
  		close $fh;
  	}
  
  	return unless @status;
  
  	$this->message(join(' ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID';
  package App::Monitoring::Plugin::CheckRaid::Plugins::megaraid;
  
  # MegaRAID
  # Status: BROKEN: no test data
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub sudo {
  	my ($this) = @_;
  	my $cat = $this->which('cat');
  
  	my @sudo;
  	foreach my $mr (</proc/mega*/*/raiddrives*>) {
  		push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cat $mr") if -d $mr;
  	}
  
  	@sudo;
  }
  
  sub check {
  	my $this = shift;
  	# status messages pushed here
  	my @status;
  
  	foreach my $f (</proc/megaraid/*/raiddrives*>) { # vim/
  		my $fh;
  		if (-r $f) {
  			open $fh, '<', $f or next;
  =cut
  		} else {
  			my @CMD = ($cat, $f);
  			unshift(@CMD, $sudo) if $> and $sudo;
  			open($fh , '-|', @CMD) or next;
  =cut
  		}
  		my ($n) = $f =~ m{/proc/megaraid/([^/]+)};
  		while (<$fh>) {
  			if (my($s) = /logical drive\s*:\s*\d+.*, state\s*:\s*(\S+)/i) {
  				if ($s ne 'optimal') {
  					$this->critical;
  				}
  				push(@status, "$n: $s");
  				last;
  			}
  		}
  		close $fh;
  	}
  
  	return unless @status;
  
  	$this->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megarc.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC';
  package App::Monitoring::Plugin::CheckRaid::Plugins::megarc;
  
  # LSI MegaRaid or Dell Perc arrays
  # Check the status of all arrays on all Lsi MegaRaid controllers on the local
  # machine. Uses the megarc program written by Lsi to get the status of all
  # arrays on all local Lsi MegaRaid controllers.
  #
  # check designed from check_lsi_megaraid:
  # http://www.monitoringexchange.org/cgi-bin/page.cgi?g=Detailed/2416.html;d=1
  # Perl port (check_raid) by Elan Ruusamäe.
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'controller list' => ['-|', '@CMD', '-AllAdpInfo', '-nolog'],
  		'controller config' => ['-|', '@CMD', '-dispCfg', '-a$controller', '-nolog'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AllAdpInfo -nolog",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd -dispCfg -a* -nolog",
  	);
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	# get controllers
  	my $fh = $this->cmd('controller list');
  	my @lines = <$fh>;
  	close $fh;
  
  	if ($lines[11] =~ /No Adapters Found/) {
  		$this->warning;
  		$this->message("No LSI adapters were found on this machine");
  		return;
  	}
  
  	my @c;
  	foreach (@lines[12..$#lines]) {
  		if (my ($id) = /^\s*(\d+)/) {
  			push(@c, int($id));
  		}
  	}
  	unless (@c) {
  		$this->warning;
  		$this->message("No LSI adapters were found on this machine");
  		return;
  	}
  
  	foreach my $c (@c) {
  		my $fh = $this->cmd('controller config', { '$controller' => $c });
  		my (%d, %s, $ld);
  		while (<$fh>) {
  			# Logical Drive : 0( Adapter: 0 ):  Status: OPTIMAL
  			if (my($d, $s) = /Logical Drive\s+:\s+(\d+).+Status:\s+(\S+)/) {
  				$ld = $d;
  				$s{$ld} = $s;
  				next;
  			}
  			# SpanDepth :01     RaidLevel: 5  RdAhead : Adaptive  Cache: DirectIo
  			if (my($s) = /RaidLevel:\s+(\S+)/) {
  				$d{$ld} = $s if defined $ld;
  				next;
  			}
  		}
  		close $fh;
  
  		# now process the details
  		unless (keys %d) {
  			$this->message("No arrays found on controller $c");
  			$this->warning;
  			return;
  		}
  
  		while (my($d, $s) = each %s) {
  			if ($s ne 'OPTIMAL') {
  				# The Array number here is incremented by one because of the
  				# inconsistent way that the LSI tools count arrays.
  				# This brings it back in line with the view in the bios
  				# and from megamgr.bin where the array counting starts at
  				# 1 instead of 0
  				push(@status, "Array ".(int($d) + 1)." status is ".$s{$d}." (Raid-$s on adapter $c)");
  				$this->critical;
  				next;
  			}
  
  			push(@status, "Logical Drive $d: $s");
  		}
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/metastat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT';
  package App::Monitoring::Plugin::CheckRaid::Plugins::metastat;
  
  # Solaris, software RAID
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'metastat' => ['>&2', '@CMD'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd"
  }
  
  sub active {
  	my ($this) = @_;
  
  	# return if parent said NO
  	my $res = $this->SUPER::active(@_);
  	return $res unless $res;
  
  	my $output = $this->get_metastat;
  	return !!@$output;
  }
  
  sub get_metastat {
  	my $this = shift;
  
  	# cache inside single run
  	return $this->{output} if defined $this->{output};
  
  	my $fh = $this->cmd('metastat');
  	my @data;
  	while (<$fh>) {
  		chomp;
  		last if /there are no existing databases/;
  		push(@data, $_);
  	}
  
  	return $this->{output} = \@data;
  }
  
  sub check {
  	my $this = shift;
  
  	my ($d, $sd);
  
  	# status messages pushed here
  	my @status;
  	my $output = $this->get_metastat;
  
  	foreach (@$output) {
  		if (/^(\S+):/) { $d = $1; $sd = ''; next; }
  		if (/Submirror \d+:\s+(\S+)/) { $sd = $1; next; }
  		if (/Device:\s+(\S+)/) { $sd = $1; next; }
  		if (my($s) = /State: (\S.+\w)/) {
  			if ($sd and $this->valid($sd) and $this->valid($d)) {
  				if ($s =~ /Okay/i) {
  					# no worries...
  				} elsif ($s =~ /Resync/i) {
  					$this->resync;
  				} else {
  					$this->critical;
  				}
  				push(@status, "$d:$sd:$s");
  			}
  		}
  
  		if (defined $d && $d =~ /hsp/) {
  			if (/(c[0-9]+t[0-9]+d[0-9]+s[0-9]+)\s+(\w+)/) {
  				$sd = $1;
  				my $s = $2;
  				$this->warning if ($s !~ /Available/);
  				push(@status, "$d:$sd:$s");
  			}
  		}
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mpt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT';
  package App::Monitoring::Plugin::CheckRaid::Plugins::mpt;
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	qw(mpt-status);
  }
  
  sub commands {
  	{
  		'get_controller_no' => ['-|', '@CMD', '-p'],
  		'status' => ['-|', '@CMD', '-i', '$id'],
  		'sync status' => ['-|', '@CMD', '-n'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [0-9]",
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [1-9][0-9]",
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -n",
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd -p",
  	);
  }
  
  sub active {
  	my ($this) = @_;
  
  	# return if parent said NO
  	my $res = $this->SUPER::active(@_);
  	return $res unless $res;
  
  	# there should be a controller. #95
  	my $id = $this->get_controller;
  	return defined($id);
  }
  
  sub get_controller {
  	my $this = shift;
  	
  	# controller ID may be given on the command line
  	my $id = $this->{options}{'mpt-id'};
  	if (!$id) {
  
  		# get controller from mpt-status -p
  		my $fh = $this->cmd('get_controller_no');
  		while (<$fh>) {
  			chomp;
  			if (/^Found.*id=(\d{1,2}),.*/) {
  				$id = $1;
  				last;
  			}
  		}
  		close $fh;
  	}
  
  	return $id;
  }
  
  sub parse {
  	my ($this, $id) = @_;
  
  	my (%ld, %pd);
  
  	my $fh = $this->cmd('status', { '$id' => $id });
  
  	my %VolumeTypesHuman = (
  		IS => 'RAID-0',
  		IME => 'RAID-1E',
  		IM => 'RAID-1',
  	);
  
  	while (<$fh>) {
  		chomp;
  		# mpt-status.c __print_volume_classic
  		# ioc0 vol_id 0 type IM, 2 phy, 136 GB, state OPTIMAL, flags ENABLED
  		if (my($vioc, $vol_id, $type, $disks, $vol_size, $vol_state, $vol_flags) =
  			/^ioc(\d+)\s+ vol_id\s(\d+)\s type\s(\S+),\s (\d+)\sphy,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
  			$ld{$vol_id} = {
  				ioc => int($vioc),
  				vol_id => int($vol_id),
  				# one of: IS, IME, IM
  				vol_type => $type,
  				raid_level => $VolumeTypesHuman{$type},
  				phy_disks => int($disks),
  				size => int($vol_size),
  				# one of: OPTIMAL, DEGRADED, FAILED, UNKNOWN
  				status => $vol_state,
  				# array of: ENABLED, QUIESCED, RESYNC_IN_PROGRESS, VOLUME_INACTIVE or NONE
  				flags => [ split ' ', $vol_flags ],
  			};
  		}
  
  		# ./include/lsi/mpi_cnfg.h
  		# typedef struct _RAID_PHYS_DISK_INQUIRY_DATA
  		# {
  		#   U8 VendorID[8];            /* 00h */
  		#   U8 ProductID[16];          /* 08h */
  		#   U8 ProductRevLevel[4];     /* 18h */
  		#   U8 Info[32];               /* 1Ch */
  		# }
  		# mpt-status.c __print_physdisk_classic
  		# ioc0 phy 0 scsi_id 0 IBM-ESXS PYH146C3-ETS10FN RXQN, 136 GB, state ONLINE, flags NONE
  		# ioc0 phy 0 scsi_id 1 ATA      ST3808110AS      J   , 74 GB, state ONLINE, flags NONE
  		# ioc0 phy 0 scsi_id 1 ATA      Hitachi HUA72101 AJ0A, 931 GB, state ONLINE, flags NONE
  		elsif (my($pioc, $num, $phy_id, $vendor, $prod_id, $rev, $size, $state, $flags) =
  			/^ioc(\d+)\s+ phy\s(\d+)\s scsi_id\s(\d+)\s (.{8})\s+(.{16})\s+(.{4})\s*,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
  			$pd{$num} = {
  				ioc => int($pioc),
  				num => int($num),
  				phy_id => int($phy_id),
  				vendor => $vendor,
  				prod_id => $prod_id,
  				rev => $rev,
  				size => int($size),
  				# one of: ONLINE, MISSING, NOT_COMPATIBLE, FAILED, INITIALIZING, OFFLINE_REQUESTED, FAILED_REQUESTED, OTHER_OFFLINE, UNKNOWN
  				status => $state,
  				# array of: OUT_OF_SYNC, QUIESCED or NONE
  				flags => [ split ' ', $flags ],
  			};
  		} else {
  			warn "mpt unparsed: [$_]";
  			$this->unknown;
  		}
  	}
  	close $fh;
  
  	# extra parse, if mpt-status has -n flag, can process also resync state
  	# TODO: if -n becames default can do this all in one run
  	my $resyncing = grep {/RESYNC_IN_PROGRESS/} map { @{$_->{flags}} } values %ld;
  	if ($resyncing) {
  		my $fh = $this->cmd('sync status');
  		while (<$fh>) {
  			if (/^ioc:\d+/) {
  				# ignore
  			}
  			# mpt-status.c GetResyncPercentage
  			# scsi_id:0 70%
  			elsif (my($scsi_id, $percent) = /^scsi_id:(\d+) (\d+)%/) {
  				$pd{$scsi_id}{resync} = int($percent);
  			} else {
  				warn "mpt unparsed: [$_]";
  				$this->unknown;
  			}
  		}
  		close $fh;
  	}
  
  	return {
  		'logical' => { %ld },
  		'physical' => { %pd },
  	};
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $id = $this->get_controller;
  	my $status = $this->parse($id);
  
  	# process logical units
  	while (my($d, $u) = each %{$status->{logical}}) {
  		next unless $this->valid($d);
  
  		my $s = $u->{status};
  		if ($s =~ /INITIAL|INACTIVE/) {
  			$this->warning;
  		} elsif ($s =~ /RESYNC/) {
  			$this->resync;
  		} elsif ($s =~ /DEGRADED|FAILED/) {
  			$this->critical;
  		} elsif ($s !~ /ONLINE|OPTIMAL/) {
  			$this->unknown;
  		}
  
  		# FIXME: this resync_in_progress is separate state of same as value in status?
  		if (grep { /RESYNC_IN_PROGRESS/ } @{$u->{flags}}) {
  			# find matching disks
  			my @disks = grep {$_->{ioc} eq $u->{ioc} } values %{$status->{physical}};
  			# collect percent for each disk
  			my @percent = map { $_->{resync}.'%'} @disks;
  			$s .= ' RESYNCING: '.join('/', @percent);
  		}
  		push(@status, "Volume $d ($u->{raid_level}, $u->{phy_disks} disks, $u->{size} GiB): $s");
  	}
  
  	# process physical units
  	while (my($d, $u) = each %{$status->{physical}}) {
  		my $s = $u->{status};
  		# remove uninteresting flags
  		my @flags = grep {!/NONE/} @{$u->{flags}};
  
  		# skip print if nothing in flags and disk is ONLINE
  		next unless @flags and $s eq 'ONLINE';
  
  		$s .= ' ' . join(' ', @flags);
  		push(@status, "Disk $d ($u->{size} GiB):$s");
  		$this->critical;
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mvcli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::mvcli;
  
  # Status: BROKEN: not finished
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'mvcli blk' => ['-|', '@CMD', 'info', '-o', 'blk'],
  		'mvcli vd' => ['-|', '@CMD', 'info', '-o', 'vd'],
  		'mvcli smart' => ['-|', '@CMD', 'smart', '-p', '0'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	"CHECK_RAID ALL=(root) NOPASSWD: $cmd"
  }
  
  sub parse_blk {
  	my $this = shift;
  
  	my (@blk, %blk);
  
  	my $fh = $this->cmd('mvcli blk');
  	while (<$fh>) {
  		chomp;
  
  		if (my ($blk_id) = /Block id:\s+(\d+)/) {
  			# block id is first item, so push previous item to list
  			if (%blk) {
  				push(@blk, { %blk });
  				%blk = ();
  			}
  			$blk{blk_id} = int($blk_id);
  		} elsif (my($pd_id) = /PD id:\s+(\d+)/) {
  			$blk{pd_id} = int($pd_id);
  		} elsif (my($vd_id) = /VD id:\s+(\d+)/) {
  			$blk{vd_id} = int($vd_id);
  		} elsif (my($bstatus) = /Block status:\s+(.+)/) {
  			$blk{block_status} = $bstatus;
  		} elsif (my($size) = /Size:\s+(\d+) K/) {
  			$blk{size} = int($size);
  		} elsif (my($offset) = /Starting offset:\s+(\d+) K/) {
  			$blk{offset} = int($offset);
  		} else {
  #			warn "[$_]\n";
  		}
  	}
  	close $fh;
  
  	if (%blk) {
  		push(@blk, { %blk });
  	}
  
  	return wantarray ? @blk : \@blk;
  }
  
  sub parse_vd {
  	my $this = shift;
  
  	my (@vd, %vd);
  	my ($name, $value);
  
  	my $fh = $this->cmd('mvcli vd');
  	while (<$fh>) {
  		chomp;
  
  		if (/^$/
  				|| /----+/
  				|| /SG driver version/
  				|| /Virtual Disk Information/
  			) {
  			next;
  		}
  
  		unless (($name, $value) = /^(.+):\s+(.+)$/) {
  			warn "UNPARSED: [$_]";
  			next;
  		}
  
  		if ($name eq 'id') {
  			# id is first item, so push previous item to list
  			if (%vd) {
  				push(@vd, { %vd });
  				%vd = ();
  			}
  		}
  
  		$vd{$name} = $value;
  	}
  	close $fh;
  
  	if (%vd) {
  		push(@vd, { %vd });
  	}
  
  	return wantarray ? @vd : \@vd;
  }
  
  sub parse_smart {
  	my ($this, $blk) = @_;
  
  	# collect pd numbers
  	my @pd = map { $_->{pd_id} } @$blk;
  
  	my %smart;
  	foreach my $pd (@pd) {
  		my $fh = $this->cmd('mvcli smart', { '$pd' => $pd });
  		my %attrs = ();
  		while (<$fh>) {
  			chomp;
  
  			if (my($id, $name, $current, $worst, $treshold, $raw, $status) = /
  				([\dA-F]{2})\s+ # id
  				(.*?)\s+        # name
  				(\d+)\s+        # current
  				(\d+)\s+        # worst
  				(\d+)\s+        # treshold
  				([\dA-F]{12})   # raw
  				(?:\s+(\w+))?   # status
  			/x) {
  				my %attr = ();
  				$attr{id} = $id;
  				$attr{name} = $name;
  				$attr{current} = int($current);
  				$attr{worst} = int($worst);
  				$attr{treshold} = int($treshold);
  				$attr{raw} = $raw;
  				$attr{status} = $status || undef;
  				$attrs{$id} = { %attr };
  			} else {
  #				warn "[$_]\n";
  			}
  		}
  
  		$smart{$pd} = { %attrs };
  	}
  
  	return \%smart;
  }
  
  sub parse {
  	my $this = shift;
  
  	my $blk = $this->parse_blk;
  	my $vd = $this->parse_vd;
  	my $smart = $this->parse_smart($blk);
  
  	return {
  		blk => $blk,
  		vd => $vd,
  		smart => $smart,
  	};
  }
  
  sub check {
  	my $this = shift;
  
  	my @status;
  	my $c = $this->parse;
  
  	foreach my $vd (@{$c->{vd}}) {
  		my $size = $this->format_bytes($this->parse_bytes($vd->{size}));
  		if ($vd->{status} ne 'functional') {
  			$this->critical;
  		}
  		push(@status, "VD($vd->{name} $vd->{'RAID mode'} $size): $vd->{status}");
  	}
  
  	return unless @status;
  
  	# denote this plugin as ran ok
  	$this->ok;
  
  	$this->message(join('; ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/sas2ircu.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU';
  package App::Monitoring::Plugin::CheckRaid::Plugins::sas2ircu;
  
  # LSI SAS-2 controllers using the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU)
  # Based on the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU) User Guide
  # http://www.lsi.com/downloads/Public/Host%20Bus%20Adapters/Host%20Bus%20Adapters%20Common%20Files/SAS_SATA_6G_P12/SAS2IRCU_User_Guide.pdf
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'controller list' => ['-|', '@CMD', 'LIST'],
  		'controller status' => ['-|', '@CMD', '$controller', 'STATUS'],
  		'device status' => ['-|', '@CMD', '$controller', 'DISPLAY'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd LIST",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * STATUS",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * DISPLAY",
  	);
  }
  
  # detect controllers for sas2ircu
  sub detect {
  	my $this = shift;
  
  	my @ctrls;
  	my $fh = $this->cmd('controller list');
  
  	my $success = 0;
  	my $state="";
  	my $noctrlstate="No Controllers";
  	while (<$fh>) {
  		chomp;
  
  		#         Adapter     Vendor  Device                        SubSys  SubSys
  		# Index    Type          ID      ID    Pci Address          Ven ID  Dev ID
  		# -----  ------------  ------  ------  -----------------    ------  ------
  		#   0     SAS2008     1000h    72h     00h:03h:00h:00h      1028h   1f1eh
  		if (my($c) = /^\s*(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/) {
  			push(@ctrls, $c);
  		}
  		$success = 1 if /SAS2IRCU: Utility Completed Successfully/;
  
  		# handle the case where there's no hardware present.
  		# when there is no controller, we get
  		# root@i41:/tmp$ /usr/sbin/sas2ircudsr LIST
  		# LSI Corporation SAS2 IR Configuration Utility.
  		# Version 18.00.00.00 (2013.11.18)
  		# Copyright (c) 2009-2013 LSI Corporation. All rights reserved.
  
  		# SAS2IRCU: MPTLib2 Error 1
  		# root@i41:/tmp$ echo $?
  		# 1
  
  		if (/SAS2IRCU: MPTLib2 Error 1/) {
  			$state = $noctrlstate;
  			$success = 1 ;
  		}
  
  	}
  
  	unless (close $fh) {
  		#sas2ircu exits 1 (but close exits 256) when we close fh if we have no controller, so handle that, too
  		if ($? != 256 && $state eq $noctrlstate) {
  			$this->critical;
  		}
  	}
  	unless ($success) {
  		$this->critical;
  	}
  
  	return wantarray ? @ctrls : \@ctrls;
  }
  
  sub  trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
  sub ltrim { my $s = shift; $s =~ s/^\s+//;       return $s };
  sub rtrim { my $s = shift; $s =~ s/\s+$//;       return $s };
  
  sub check {
  	my $this = shift;
  
  	my @ctrls = $this->detect;
  
  	my @status;
  	my $numvols=0;
  	# determine the RAID states of each controller
  	foreach my $c (@ctrls) {
  		my $fh = $this->cmd('controller status', { '$controller' => $c });
  
  		my $novolsstate="No Volumes";
  		my $state;
  		my $success = 0;
  		while (<$fh>) {
  			chomp;
  
  			# match adapter lines
  			if (my($s) = /^\s*Volume state\s*:\s*(\w+)\s*$/) {
  				$state = $s;
  				$numvols++;
  				if ($state ne "Optimal") {
  					$this->critical;
  				}
  			}
  			$success = 1 if /SAS2IRCU: Utility Completed Successfully/;
  
  			##handle the case where there are no volumes configured
  			#
  			# SAS2IRCU: there are no IR volumes on the controller!
  			# SAS2IRCU: Error executing command STATUS.
  
  			if (/SAS2IRCU: there are no IR volumes on the controller/
  				or /The STATUS command is not supported by the firmware currently loaded on controller/
  			) {
  				# even though this isn't the last line, go ahead and set success.
  				$success = 1;
  				$state = $novolsstate;
  			}
  
  		}
  
  		unless (close $fh) {
  			#sas2ircu exits 256 when we close fh if we have no volumes, so handle that, too
  			if ($? != 256 && $state eq $novolsstate) {
  				$this->critical;
  				$state = $!;
  			}
  		}
  
  		unless ($success) {
  			$this->critical;
  			$state = "SAS2IRCU Unknown exit";
  		}
  
  		unless ($state) {
  			$state = "Unknown Error";
  		}
  
  		my $finalvolstate=$state;
  		#push(@status, "ctrl #$c: $numvols Vols: $state");
  
  
  		#####  now look at the devices.
  		# Device is a Hard disk
  		#   Enclosure #                             : 2
  		#   Slot #                                  : 0
  		#   SAS Address                             : 500065b-3-6789-abe0
  		#   State                                   : Ready (RDY)
  		#   Size (in MB)/(in sectors)               : 3815447/7814037167
  		#   Manufacturer                            : ATA
  		#   Model Number                            : ST4000DM000-1F21
  		#   Firmware Revision                       : CC52
  		#   Serial No                               : S30086G4
  		#   GUID                                    : 5000c5006d27b344
  		#   Protocol                                : SATA
  		#   Drive Type                              : SATA_HDD
  
  		$fh = $this->cmd('device status', { '$controller' => $c });
  		$state="";
  		$success = 0;
  		my $enc="";
  		my $slot="";
  		my @data;
  		my $device="";
  		my $numslots=0;
  		my $finalstate;
  		my $finalerrors="";
  
  		while (my $line = <$fh>) {
  			chomp $line;
  			# Device is a Hard disk
  			# Device is a Hard disk
  			# Device is a Enclosure services device
  			#
  			#lets make sure we're only checking disks.  we dont support other devices right now
  			if ("$line" eq 'Device is a Hard disk') {
  				$device='disk';
  			} elsif ($line =~ /^Device/) {
  				$device='other';
  			}
  
  			if ("$device" eq 'disk') {
  				if ($line =~ /Enclosure #|Slot #|State /) {
  					#find our enclosure #
  					if ($line =~ /^  Enclosure # /) {
  						@data = split /:/, $line;
  						$enc=trim($data[1]);
  						#every time we hit a new enclosure line, reset our state and slot
  						undef $state;
  						undef $slot;
  					}
  					#find our slot #
  					if ($line =~ /^  Slot # /) {
  						@data = split /:/, $line;
  						$slot=trim($data[1]);
  						$numslots++
  					}
  					#find our state
  					if ($line =~ /^  State /) {
  						@data = split /:/, $line;
  						$state=ltrim($data[1]);
  
  						#for test
  						#if ($numslots == 10 ) { $state='FREDFISH';}
  
  						#when we get a state, test on it and report it..
  						if ($state =~ /Optimal|Ready/) {
  							#do nothing at the moment.
  						} else {
  							$this->critical;
  							$finalstate=$state;
  							$finalerrors="$finalerrors ERROR:Ctrl$c:Enc$enc:Slot$slot:$state";
  						}
  					}
  				}
  			}
  
  			if ($line =~ /SAS2IRCU: Utility Completed Successfully/) {
  				$success = 1;
  			}
  
  		} #end while
  
  
  		unless (close $fh) {
  			$this->critical;
  			$state = $!;
  		}
  
  		unless ($success) {
  			$this->critical;
  			$state = "SAS2IRCU Unknown exit";
  		}
  
  		unless ($state) {
  			$state = "Unknown Error";
  		}
  
  		unless($finalstate) {
  			$finalstate=$state;
  		}
  
  		#per controller overall report
  		#push(@status, ":$numslots Drives:$finalstate:$finalerrors");
  		push(@status, "ctrl #$c: $numvols Vols: $finalvolstate: $numslots Drives: $finalstate:$finalerrors:");
  
  	}
  
  	##if we didn't get a status out of the controllers and an empty ctrls array, we must not have any.
  	unless (@status && @ctrls) {
  		push(@status, "No Controllers");
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/smartctl.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL';
  package App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
  
  # NOTE: not standalone plugin
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  use strict;
  use warnings;
  
  sub program_names {
  	shift->{name};
  }
  
  sub commands {
  	{
  		'smartctl' => ['-|', '@CMD', '-H', '$dev', '$diskopt$disk'],
  	}
  }
  
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	# nothing, as not standalone plugin yet
  }
  
  # check for -H parameter for physical disks
  # this is currently called out from cciss plugin
  # @param device list
  # device list being an array of:
  # - device to check (/dev/cciss/c0d0)
  # - disk options (-dcciss)
  # - disk number (0..15)
  sub check_devices {
  	my $this = shift;
  	my @devs = @_;
  
  	unless (@devs) {
  		$this->warning;
  		$this->message("No devices to check");
  		return;
  	}
  
  	# status message for devs, latter just joined for shorter messages
  	my %status;
  
  	foreach my $ref (@devs) {
  		my ($dev, $diskopt, $disk) = @$ref;
  
  		my $fh = $this->cmd('smartctl', { '$dev' => $dev, '$diskopt' => $diskopt => '$disk' => $disk });
  		while (<$fh>) {
  			chomp;
  
  			# SMART Health Status: HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE [asc=5d, ascq=10]
  			if (my($s, $sc) = /SMART Health Status: (.*?)(\s*\[asc=\w+, ascq=\w+\])?$/) {
  				# use shorter output, message that hpacucli would use
  				if ($s eq 'HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE') {
  					$s = 'Predictive Failure';
  				}
  
  				if ($s eq 'Predictive Failure') {
  					$this->warning;
  				} elsif ($s !~ '^OK') {
  					$this->critical;
  				}
  				push(@{$status{$s}}, $dev.'#'.$disk);
  			}
  		}
  		close($fh);
  	}
  
  	return unless %status;
  
  	$this->ok->message($this->join_status(\%status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::ssacli;
  
  # This plugin extends hpacucli plugin,
  # with the only difference that different program name will be used.
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli';
  use strict;
  use warnings;
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/tw_cli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI';
  package App::Monitoring::Plugin::CheckRaid::Plugins::tw_cli;
  
  # tw_cli(8) is a Command Line Interface Storage Management Software for
  # AMCC/3ware ATA RAID Controller(s).
  # Owned by LSI currently: https://en.wikipedia.org/wiki/3ware
  #
  # http://www.cyberciti.biz/files/tw_cli.8.html
  
  use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
  # not yet, see:
  # https://github.com/glensc/nagios-plugin-check_raid/pull/131#issuecomment-189957806
  #use Date::Parse qw(strptime);
  #use DateTime;
  use strict;
  use warnings;
  
  sub program_names {
  	qw(tw_cli-9xxx tw_cli tw-cli);
  }
  
  sub commands {
  	{
  		'show' => ['-|', '@CMD', 'show'], # This is 'info' output AND enclosure summary
  		'unitstatus' => ['-|', '@CMD', 'info', '$controller', 'unitstatus'],
  		'drivestatus' => ['-|', '@CMD', 'info', '$controller', 'drivestatus'],
  		'bbustatus' => ['-|', '@CMD', 'info', '$controller', 'bbustatus'],
  		'enc_show_all' => ['-|', '@CMD', '$encid', 'show all'],
  	}
  }
  
  sub sudo {
  	my ($this, $deep) = @_;
  	# quick check when running check
  	return 1 unless $deep;
  
  	my $cmd = $this->{program};
  	(
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd info",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd info *",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd show",
  		"CHECK_RAID ALL=(root) NOPASSWD: $cmd * show all",
  	);
  }
  
  sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
  
  sub to_i {
  	my $i = shift;
  	return $i if $i !~ /^\d+$/;
  	return int($i);
  }
  
  sub parse {
  	my $this = shift;
  
  	my (%c);
  	# scan controllers
  	my ($sect_ctl, $sect_enc) = 0;
  	my $fh = $this->cmd('show');
  	while (<$fh>) {
  		# Section break
  		if(/^\s*$/) { ($sect_ctl,$sect_enc) = (0,0); next; };
  		# header line
  		if(/^-+$/) { next; };
  		# section headers: Controller
  		# Ctl   Model  Ports     Drives  Units   NotOpt  RRate VRate  BBU
  		# Ctl   Model  (V)Ports  Drives  Units   NotOpt  RRate VRate  BBU
  		if (/^Ctl.*Model.*Rate/) { $sect_ctl = 1; next; };
  		# section headers: Enclosure
  		#  Encl    Slots   Drives  Fans   TSUnits   Ctls
  		#  Encl    Slots   Drives  Fans   TSUnits  PSUnits
  		#  Enclosure     Slots  Drives  Fans  TSUnits  PSUnits  Alarms
  		if (/^Encl.*Drive/) { $sect_enc = 1; next; };
  
  		# controller section
  		if ($sect_ctl and my($ctl, $model, $ports, $drives, $units, $notopt, $rrate, $vrate, $bbu) = m{^
  			(c\d+)\s+   # Controller
  			(\S+)\s+    # Model
  			(\d+)\s+    # (V)Ports
  			(\d+)\s+    # Drives
  			(\d+)\s+    # Units
  			(\d+)\s+    # NotOpt: Not Optional
  						# Not Optimal refers to any state except OK and VERIFYING.
  						# Other states include INITIALIZING, INIT-PAUSED,
  						# REBUILDING, REBUILD-PAUSED, DEGRADED, MIGRATING,
  						# MIGRATE-PAUSED, RECOVERY, INOPERABLE, and UNKNOWN.
  			(\d+)\s+    # RRate: Rebuild Rate
  			(\d+|-)\s+  # VRate: Verify Rate
  			(\S+|-)?    # BBU
  		}x) {
  			$c{$ctl} = {
  				model => $model,
  				ports => int($ports),
  				drives => int($drives),
  				units => int($units),
  				optimal => int(!$notopt),
  				rrate => int($rrate),
  				vrate => to_i($vrate),
  				bbu => $bbu,
  			};
  		}
  		# enclosure section
  		if ($sect_enc and my($enc, $slots, $drives, $fans, $tsunits, $psunits, $alarms) = m{^
  			((?:/c\d+)?/e\d+)\s+   # Controller, Enclosure
  			  # 9650SE reports enclosures as /eX
  			  # 9690SA+ report enclosures as /cX/eX
  			(\d+)\s+    # Slots
  			(\d+)\s+    # Drives
  			(\d+)\s+    # Fans
  			(\d+)\s+    # TSUnits - Temp Sensor
  			(\d+)?\s+    # PSUnits - Power Supply, not always present!
  			(\d+)?\s+    # Controller OR Alarms, not always present!
  		}x) {
  			# This will be filled in later by the enclosure pass
  			$c{$enc} = {};
  		}
  	}
  	close $fh;
  
  	# no controllers? skip early
  	return unless %c;
  
  	for my $c (grep /^\/?c\d+$/, keys %c) {
  		# get each unit on controllers
  		$fh = $this->cmd('unitstatus', { '$controller' => $c });
  		while (<$fh>) {
  			if (my($u, $type, $status, $p_rebuild, $p_vim, $strip, $size, $cache, $avrify) = m{^
  				(u\d+)\s+ # Unit
  				(\S+)\s+  # UnitType
  				(\S+)\s+  # Status
  				(\S+)\s+  # %RCmpl: The %RCompl reports the percent completion
  						  # of the unit's Rebuild, if this task is in progress.
  				(\S+)\s+  # %V/I/M: The %V/I/M reports the percent completion
  						  # of the unit's Verify, Initialize, or Migrate,
  						  # if one of these are in progress.
  				(\S+)\s+  # Strip
  				(\S+)\s+  # Size(GB)
  				(\S+)\s+  # Cache
  				(\S+)     # AVrify
  			}x) {
  				$c{$c}{unitstatus}{$u} = {
  					type => $type,
  					status => $status,
  					rebuild_percent => $p_rebuild,
  					vim_percent => $p_vim,
  					strip => $strip,
  					size => $size,
  					cache => $cache,
  					avrify => $avrify,
  				};
  				next;
  			}
  
  			if (m{^u\d+}) {
  				$this->unknown;
  				warn "unparsed: [$_]";
  			}
  		}
  		close $fh;
  
  		# get individual disk status
  		$fh = $this->cmd('drivestatus', { '$controller' => $c });
  		# common regexp
  		my $r = qr{^
  			(p\d+)\s+       # Port
  			(\S+)\s+        # Status
  			(\S+)\s+        # Unit
  			([\d.]+\s[TG]B|-)\s+ # Size
  		}x;
  
  		while (<$fh>) {
  			# skip empty line
  			next if /^$/;
  
  			# Detect version
  			if (/^Port/) {
  				# <=9.5.1: Blocks Serial
  				$r .= qr{
  					(\S+)\s+  # Blocks
  					(.+)      # Serial
  				}x;
  				next;
  			} elsif (/^VPort/) {
  				# >=9.5.2: Type Phy Encl-Slot Model
  				$r .= qr{
  					(\S+)\s+ # Type
  					(\S+)\s+ # Phy
  					(\S+)\s+ # Encl-Slot
  					(.+)     # Model
  				}x;
  				next;
  			}
  
  			if (my($port, $status, $unit, $size, @rest) = ($_ =~ $r)) {
  				# do not report disks not present
  				# tw_cli 9.5.2 and above do not list these at all
  				next if $status eq 'NOT-PRESENT';
  				my %p;
  
  				if (@rest <= 2) {
  					my ($blocks, $serial) = @rest;
  					%p = (
  						blocks => to_i($blocks),
  						serial => trim($serial),
  					);
  				} else {
  					my ($type, $phy, $encl, $model) = @rest;
  					%p = (
  						type => $type,
  						phy => to_i($phy),
  						encl => $encl,
  						model => $model,
  					);
  				}
  
  				$c{$c}{drivestatus}{$port} = {
  					status => $status,
  					unit => $unit,
  					size => $size,
  					%p,
  				};
  
  				next;
  			}
  
  			if (m{^p\d+}) {
  				$this->unknown;
  				warn "unparsed: [$_]";
  			}
  		}
  		close $fh;
  
  		# get BBU status
  		$fh = $this->cmd('bbustatus', { '$controller' => $c });
  		while (<$fh>) {
  			next if /^$/;
  			next if /^-{10,}$/;
  			if (my($bbu, $onlinestate, $bbuready, $status, $volt, $temp, $hours, $lastcaptest) = m{^
  				(bbu\d*)\s+     # BBU, possibly numbered (RARE)
  				(\S+)\s+        # OnlineState
  				(\S+)\s+        # BBUReady
  				(\S+)\s+        # Status
  				(\S+)\s+        # Volt
  				(\S+)\s+        # Temp
  				(\d+)\s+        # Hours
  				(\S+)\s+        # LastCapTest
  			}x) {
  				$c{$c}{bbustatus}{$bbu} = {
  					OnlineState => $onlinestate,
  					BBUReady => $bbuready,
  					Status => $status,
  					Volt => $volt,
  					Temp => $temp,
  					Hours => $hours,
  					LastCapTest => $lastcaptest,
  				};
  				next;
  			}
  			if (m{^\S+\+}) {
  				$this->unknown;
  				warn "unparsed: [$_]";
  			}
  		}
  		close $fh;
  	}
  
  	# Do enclosures now, which might NOT be attached the controllers
  	# WARNING: This data section has not always been consistent over versions of tw_cli.
  	# You should try to use the newest version of the driver, as it deliberately uses the newer style of output
  	# rather than the output for the tw_cli versions released with 9550SX/9590SE/9650SE
  	for my $encid (grep /\/e\d+$/, keys %c) {
  		$fh = $this->cmd('enc_show_all', { '$encid' => $encid });
  		# Variable names chose to be 'sect_XXX' explicitly.
  		# This says what section we are in right now
  		my ($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
  		# This says what section we have seen, it gets reset at the start of each enclosure block;
  		my ($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
  		while (<$fh>) {
  			# Skip the header break lines
  			next if /^-+$/;
  			# and the partial indented header that is ABOVE the fan header
  			next if /^\s+-+Speed-+\s*$/;
  			# If the line is blank, reset our section headers
  			if(/^\s*$/){
  				($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
  				# If we have SEEN all of the sections, also reset the seen markers
  				# This is needed when the output contains multiple enclosures
  				if($sect_enc and $sect_fan and $sect_tmp and $sect_psu and $sect_slt and $sect_alm) {
  					($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
  				}
  				next;
  			}
  			if (/^Encl.*Status/)        { $seen_enc = $sect_enc = 1; next; }
  			if (/^Fan.*Status/)         { $seen_fan = $sect_fan = 1; next; }
  			if (/^TempSensor.*Status/)  { $seen_tmp = $sect_tmp = 1; next; }
  			if (/^PowerSupply.*Status/) { $seen_psu = $sect_psu = 1; next; }
  			if (/^Slot.*Status/)        { $seen_slt = $sect_slt = 1; next; }
  			if (/^Alarm.*Status/)       { $seen_alm = $sect_alm = 1; next; }
  			# ------ Start of new enclosure
  			if ($sect_enc and my($encl, $encl_status) = m{^
  				((?:/c\d+)?/e\d+)\s+   # Controller, Enclosure
  				(\S+)\s+   # Status
  			}x) {
  				# This is a special case for the test environment, as it is
  				# hard to feed MULTI command inputs into the mock.
  				if($ENV{'HARNESS_ACTIVE'} and $encl ne $encid) {
  					$encid = $encl;
  				}
  				$c{$encid} = {
  					encl   => $encl, # Dupe of $encid to verify
  					status => $encl_status,
  					# This is the top-level enclosure object
  					fans => {},
  					tempsensor => {},
  					powersupply => {},
  					slot => {},
  					alarm => {},
  				};
  			}
  			# ------ Fans
  			elsif ($sect_fan and my($fan, $fan_status, $fan_state, $fan_step, $fan_rpm, $fan_identify) = m{^
  				(fan\S+)\s+   # Fan
  				(\S+)\s+   # Status
  				(\S+)\s+   # State
  				(\S+)\s+   # Step
  				(\d+|N/A)\s+   # RPM
  				(\S+)\s+   # Identify
  			}x) {
  				$c{$encid}{fans}{$fan} = {
  					status => $fan_status,
  					state => $fan_state,
  					step => $fan_step,
  					rpm => $fan_rpm,
  					identify => $fan_identify,
  				};
  				next;
  			}
  			# ------ TempSensor
  			elsif ($sect_tmp and my($tmp, $tmp_status, $tmp_temperature, $tmp_identify) = m{^
  				(temp\S+)\s+   # TempSensor
  				(\S+)\s+   # Status
  				(\S+)\s+   # Temperature
  				(\S+)\s+   # Identify
  			}x) {
  				$c{$encid}{tempsensor}{$tmp} = {
  					status => $tmp_status,
  					temperature => $tmp_temperature,
  					identify => $tmp_identify,
  				};
  				next;
  			}
  			# ------ PowerSupply
  			elsif ($sect_psu and my($psu, $psu_status, $psu_state, $psu_voltage, $psu_current, $psu_identify) = m{^
  				((?:pw|psu)\S+)\s+   # PowerSupply
  				(\S+)\s+   # Status
  				(\S+)\s+   # State
  				(\S+)\s+   # Voltage
  				(\S+)\s+   # Current
  				(\S+)\s+   # Identify
  			}x) {
  				$c{$encid}{powersupply}{$psu} = {
  					status => $psu_status,
  					state => $psu_state,
  					voltage => $psu_voltage,
  					current => $psu_current,
  					identify => $psu_identify,
  				};
  				next;
  			}
  			# ------ Slot
  			elsif ($sect_slt and my($slt, $slt_status, $slt_vport, $slt_identify) = m{^
  				(slo?t\S+)\s+   # Slot
  				(\S+)\s+   # Status
  				(\S+)\s+   # (V)Port
  				(\S+)\s+   # Identify
  			}x) {
  				$c{$encid}{slot}{$slt} = {
  					status => $slt_status,
  					vport => $slt_vport,
  					identify => $slt_identify,
  				};
  				next;
  			}
  			# ------ Alarm
  			elsif ($sect_alm and my($alm, $alm_status, $alm_state, $alm_audibility) = m{^
  				(alm\S+)\s+   # Alarm
  				(\S+)\s+   # Status
  				(\S+)\s+   # State
  				(\S+)\s+   # Audibility
  			}x) {
  				$c{$encid}{alarm}{$alm} = {
  					status => $alm_status,
  					state => $alm_state,
  					audibility => $alm_audibility,
  				};
  				next;
  			}
  			# ---- End of known data
  			elsif (m{^\S+\+}) {
  				$this->unknown;
  				warn "unparsed: [$_]";
  			}
  
  		}
  		close $fh;
  	}
  
  	return \%c;
  }
  
  sub check {
  	my $this = shift;
  
  	# status messages pushed here
  	my @status;
  
  	my $c = $this->parse;
  	if (!$c) {
  		$this->unknown;
  		$this->message("No Adapters were found on this machine");
  	}
  
  	# process each controller
  	for my $cid (sort grep !/e\d+/, keys %$c) {
  		my $c = $c->{$cid};
  		my @cstatus;
  
  		for my $uid (sort keys %{$c->{unitstatus}}) {
  			my $u = $c->{unitstatus}->{$uid};
  			my $s = $u->{status};
  
  			if ($s =~ /INITIALIZING|MIGRATING/) {
  				$this->warning;
  				$s .= " $u->{vim_percent}";
  
  			} elsif ($s eq 'VERIFYING') {
  				$this->check_status;
  				$s .= " $u->{vim_percent}";
  
  			} elsif ($s eq 'REBUILDING') {
  				$this->resync;
  				$s .= " $u->{rebuild_percent}";
  
  			} elsif ($s eq 'DEGRADED') {
  				$this->critical;
  
  			} elsif ($s ne 'OK') {
  				$this->critical;
  
  			}
  
  			my @ustatus = $s;
  
  			# report cache, no checking
  			if ($u->{cache} && $u->{cache} ne '-') {
  				push(@ustatus, "Cache:$u->{cache}");
  			}
  
  			push(@status, "$cid($c->{model}): $uid($u->{type}): ".join(', ', @ustatus));
  		}
  
  		# check individual disk status
  		my %ds;
  		foreach my $p (sort { $a cmp $b } keys %{$c->{drivestatus}}) {
  			my $d = $c->{drivestatus}->{$p};
  			my $ds = $d->{status};
  			if ($ds eq 'VERIFYING') {
  				$this->check_status;
  			} elsif ($ds ne 'OK') {
  				$this->critical;
  			}
  
  			if ($d->{unit} eq '-') {
  				$ds = 'SPARE';
  			}
  
  			push(@{$ds{$ds}}, $p);
  		}
  		push(@status, "Drives($c->{drives}): ".$this->join_status(\%ds)) if %ds;
  
  		# check BBU, but be prepared that BBU status might not report anything
  		if ($this->{options}{bbu_monitoring} && $c->{bbu} && $c->{bbu} ne '-') {
  			# On old controllers, bbustatus did not exist; and the only BBU status
  			# you got was on the controller listing.
  			if(scalar(keys %{$c->{bbustatus}}) < 1) {
  				$this->critical if $c->{bbu} ne 'OK';
  				push(@status, "BBU: $c->{bbu}");
  			} else {
  				foreach my $bbuid (sort { $a cmp $b } keys %{$c->{bbustatus}}) {
  					my $bat = $c->{bbustatus}->{$bbuid};
  					my $bs = $bat->{Status}; # We might override this later
  					my @batmsg;
  					if($bs eq 'Testing' or $bs eq 'Charging') {
  						$this->bbulearn;
  					} elsif($bs eq 'WeakBat') {
  						# Time to replace your battery
  						$this->warning;
  					} elsif($bs ne 'OK') {
  						$this->critical;
  					}
  					# We do NOT check BBUReady, as it doesn't private granular
  					# info.
  					# Check OnlineState flag as well
  					# A battery can be GOOD, but disabled; this is only reflected in OnlineState.
  					if($bat->{OnlineState} ne 'On') {
  						push @batmsg, 'OnlineStatus='.$bat->{OnlineState};
  						$this->critical;
  					}
  					# Check voltage & temps
  					push @batmsg, 'Volt='.$bat->{Volt};
  					push @batmsg, 'Temp='.$bat->{Temp};
  					if ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
  						$this->critical;
  					} elsif ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
  						$this->warning;
  					}
  					if ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
  						$this->critical;
  					} elsif ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
  						$this->warning;
  					}
  					# Check runtime estimate
  					# Warn if too low
  					my $bbulearn = '';
  					if ($bat->{Hours} ne '-' and int($bat->{Hours}) <= 1) {
  						# TODO: make this configurable before going live
  						#$this->warning;
  						$this->bbulearn;
  						$bbulearn = '/LEARN';
  					}
  					push @batmsg, 'Hours='.$bat->{Hours};
  
  					# Check date of last capacity test
  					if ($bat->{LastCapTest} eq 'xx-xxx-xxxx') {
  						$this->bbulearn;
  						$bbulearn = '/LEARN';
  					} elsif ($bat->{LastCapTest} ne '-') {
  						# TODO: is the short name of month localized by tw_cli?
  						#my ($mday, $mon, $year) = (strptime($bat->{LastCapTest}, '%d-%b-%Y'))[3,4,5];
  						#my $lastcaptest_epoch = DateTime->new(year => $year, month => $mon, day => $mday, hour => 0, minute => 0, second => 0);
  						#my $present_time = time;
  						## TODO: this value should be configurable before going live, also need to mock system date for testing
  						#if (($present_time-$lastcaptest_epoch) > 86400*365) {
  						#	$this->bbulearn;
  						#}
  					}
  					push @batmsg, 'LastCapTest='.$bat->{LastCapTest};
  					my $msg = join(',', @batmsg);
  					my $bbustatus = $bs.$bbulearn;
  					$bbustatus = "$bbuid=$bs" if $bbuid ne 'bbu'; # If we have multiple BBU, specify which one
  					push(@status, "BBU: $bbustatus($msg)");
  				}
  			}
  		}
  	}
  	# process each enclosure
  	for my $eid (sort grep /\/e\d+/, keys %$c) {
  		my $e = $c->{$eid};
  		# If the enclosure command returned nothing, we have no status to
  		# report.
  		next unless defined($e->{status});
  
  		# Something is wrong, but we are not sure what yet.
  		$this->warning unless $e->{status} eq 'OK';
  		my @estatus;
  		for my $fan_id (sort keys %{$e->{fans}}) {
  			my $f = $e->{fans}->{$fan_id};
  			my $s = $f->{status};
  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
  			$this->warning if $s ne 'OK';
  			push(@estatus, "$fan_id=$s($f->{rpm})");
  		}
  		for my $tmp_id (sort keys %{$e->{tempsensor}}) {
  			my $t = $e->{tempsensor}->{$tmp_id};
  			my $s = $t->{status};
  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
  			$this->warning if $s ne 'OK';
  			$t->{temperature} =~ s/\(\d+F\)//; # get rid of extra units
  			push(@estatus, "$tmp_id=$s($t->{temperature})");
  		}
  		for my $psu_id (sort keys %{$e->{powersupply}}) {
  			my $t = $e->{powersupply}->{$psu_id};
  			my $s = $t->{status};
  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
  			$this->warning if $s ne 'OK';
  			push(@estatus, "$psu_id=$s(status=$t->{state},voltage=$t->{voltage},current=$t->{current})");
  		}
  		for my $slot_id (sort keys %{$e->{slot}}) {
  			my $t = $e->{slot}->{$slot_id};
  			my $s = $t->{status};
  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
  			$this->warning if $s ne 'OK';
  			push(@estatus, "$slot_id=$s");
  		}
  		for my $alarm_id (sort keys %{$e->{alarm}}) {
  			my $t = $e->{alarm}->{$alarm_id};
  			my $s = $t->{status};
  			next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
  			$this->warning if $s ne 'OK';
  			push(@estatus, "$alarm_id=$s(State=$t->{state},Audibility=$t->{audibility})");
  		}
  		#warn join("\n", @estatus);
  		push(@status, "Enclosure: $eid(".join(',', @estatus).")");
  	}
  
  	return unless @status;
  
  	$this->ok->message(join(', ', @status));
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI

$fatpacked{"App/Monitoring/Plugin/CheckRaid/SerialLine.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE';
  package App::Monitoring::Plugin::CheckRaid::SerialLine;
  
  # Package dealing with connecting to serial line and handling UUCP style locks.
  
  use Carp;
  use strict;
  use warnings;
  
  sub new {
  	my $self = shift;
  	my $class = ref($self) || $self;
  	my $device = shift;
  
  	my $this = {
  		lockdir => "/var/lock",
  
  		@_,
  
  		lockfile => undef,
  		device => $device,
  		fh => undef,
  	};
  
  	bless($this, $class);
  }
  
  sub lock {
  	my $self = shift;
  	# create lock in style: /var/lock/LCK..ttyS0
  	my $device = shift;
  	my ($lockfile) = $self->{device} =~ m#/dev/(.+)#;
  	$lockfile = "$self->{lockdir}/LCK..$lockfile";
  	if (-e $lockfile) {
  		return 0;
  	}
  	open(my $fh, '>', $lockfile) || croak "Can't create lock: $lockfile\n";
  	print $fh $$;
  	close($fh);
  
  	$self->{lockfile} = $lockfile;
  }
  
  sub open {
  	my $self = shift;
  
  	$self->lock or return;
  
  	# open the device
  	open(my $fh, '+>', $self->{device}) || croak "Couldn't open $self->{device}, $!\n";
  
  	$self->{fh} = $fh;
  }
  
  sub close {
  	my $self = shift;
  	if ($self->{fh}) {
  		close($self->{fh});
  		undef($self->{fh});
  		unlink $self->{lockfile} or carp $!;
  	}
  }
  
  sub DESTROY {
  	my $self = shift;
  	$self->close();
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Sudoers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS';
  package App::Monitoring::Plugin::CheckRaid::Sudoers;
  
  use App::Monitoring::Plugin::CheckRaid::Utils;
  use warnings;
  use strict;
  
  use Exporter 'import';
  
  our @EXPORT = qw(sudoers);
  our @EXPORT_OK = @EXPORT;
  
  # update sudoers file
  #
  # if sudoers config has "#includedir" directive, add file to that dir
  # otherwise update main sudoers file
  # @returns true if file was updated
  sub sudoers {
  	my $dry_run = shift;
  	my @plugins = @_;
  
  	# build values to be added
  	# go over all active plugins
  	my @sudo;
  	foreach my $plugin (@plugins) {
  		# collect sudo rules
  		my @rules = $plugin->sudo(1) or next;
  
  		push(@sudo, @rules);
  	}
  
  	unless (@sudo) {
  		warn "Your configuration does not need to use sudo, sudoers not updated\n";
  		return 0;
  	}
  
  	my @rules = join "\n", (
  		"",
  		# setup alias, so we could easily remove these later by matching lines with 'CHECK_RAID'
  		# also this avoids installing ourselves twice.
  		"# Lines matching CHECK_RAID added by $0 -S on ". scalar localtime,
  		"User_Alias CHECK_RAID=nagios, icinga, sensu",
  		"Defaults:CHECK_RAID !requiretty",
  
  		# actual rules from plugins
  		join("\n", @sudo),
  		"",
  	);
  
  	if ($dry_run) {
  		warn "Content to be inserted to sudo rules:\n";
  		warn "--- sudoers ---\n";
  		print @rules;
  		warn "--- sudoers ---\n";
  		return 0;
  	}
  
  	my $sudoers = find_file('/usr/local/etc/sudoers', '/etc/sudoers');
  	my $visudo = which('visudo');
  
  	die "Unable to find sudoers file.\n" unless -f $sudoers;
  	die "Unable to write to sudoers file '$sudoers'.\n" unless -w $sudoers;
  	die "visudo program not found\n" unless -x $visudo;
  
  	# parse sudoers file for "#includedir" directive
  	my $sudodir = parse_sudoers_includedir($sudoers);
  	if ($sudodir) {
  		# sudo will read each file in /etc/sudoers.d, skipping file names that
  		# end in ~ or contain a . character to avoid causing problems with
  		# package manager or editor temporary/backup files
  		$sudoers = "$sudodir/check_raid";
  	}
  
  	warn "Updating file $sudoers\n";
  
  	# NOTE: secure as visudo itself: /etc is root owned
  	my $new = $sudoers.".new.".$$;
  
  	# setup to have sane perm for new sudoers file
  	umask(0227);
  
  	open my $fh, '>', $new or die $!;
  
  	# insert old sudoers
  	if (!$sudodir) {
  		open my $old, '<', $sudoers or die $!;
  		while (<$old>) {
  			print $fh $_;
  		}
  		close $old or die $!;
  	}
  
  	# insert the rules
  	print $fh @rules;
  	close $fh;
  
  	# validate sudoers
  	system($visudo, '-c', '-f', $new) == 0 or unlink($new),exit $? >> 8;
  
  	# check if they differ
  	if (filediff($sudoers, $new)) {
  		# use the new file
  		rename($new, $sudoers) or die $!;
  		warn "$sudoers file updated.\n";
  		return 1;
  	}
  
  	warn "$sudoers file not changed.\n";
  	unlink($new);
  	return 0;
  }
  
  # return first "#includedir" directive from $sudoers file
  sub parse_sudoers_includedir {
  	my ($sudoers) = @_;
  
  	open my $fh, '<', $sudoers or die "Can't open: $sudoers: $!";
  	while (<$fh>) {
  		if (my ($dir) = /^#includedir\s+(.+)$/) {
  			return $dir;
  		}
  	}
  	close $fh or die $!;
  
  	return undef;
  }
  
  # return FALSE if files are identical
  # return TRUE if files are different
  # return TRUE if any of the files is missing
  sub filediff {
  	my ($file1, $file2) = @_;
  
  	# return TRUE if neither of them exist
  	return 1 unless -f $file1;
  	return 1 unless -f $file2;
  
  	my $f1 = cat($file1);
  	my $f2 = cat($file2);
  
  	# wipe comments
  	$f1 =~ s/^#.+$//m;
  	$f2 =~ s/^#.+$//m;
  
  	# return TRUE if they differ
  	return $f1 ne $f2;
  }
  
  # get contents of a file
  sub cat {
  	my ($file) = @_;
  	open(my $fh, '<', $file) or die "Can't open $file: $!";
  	local $/ = undef;
  	local $_ = <$fh>;
  	close($fh) or die $!;
  
  	return $_;
  }
  
  # find first existing file from list of file paths
  sub find_file {
  	for my $file (@_) {
  		return $file if -f $file;
  	}
  	return undef;
  }
APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS

$fatpacked{"App/Monitoring/Plugin/CheckRaid/Utils.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_UTILS';
  package App::Monitoring::Plugin::CheckRaid::Utils;
  
  use warnings;
  use strict;
  use Exporter 'import';
  
  our @EXPORT = qw(which find_sudo);
  our @EXPORT_OK = @EXPORT;
  
  # registered plugins
  our @plugins;
  
  # devices to ignore
  our @ignore;
  
  # debug level
  our $debug = 0;
  
  # paths for which()
  our @paths = split /:/, $ENV{'PATH'};
  unshift(@paths, qw(/usr/local/nrpe /usr/local/bin /sbin /usr/sbin /bin /usr/sbin /opt/bin /opt/MegaRAID/MegaCli /usr/StorMan));
  
  # lookup program from list of possible filenames
  # search is performed from $PATH plus additional hardcoded @paths
  # NOTE: we do not check for execute bit as it may fail for non-root. #104
  sub which {
  	for my $prog (@_) {
  		for my $path (@paths) {
  			return "$path/$prog" if -f "$path/$prog";
  		}
  	}
  	return undef;
  }
  
  our @sudo;
  sub find_sudo {
  	# no sudo needed if already root
  	return [] unless $>;
  
  	# detect once
  	return \@sudo if @sudo;
  
  	my $sudo = which('sudo') or die "Can't find sudo";
  	push(@sudo, $sudo);
  
  	# detect if sudo supports -A, issue #88
  	use IPC::Open3;
  	my $fh;
  	my @cmd = ($sudo, '-h');
  	my $pid = open3(undef, $fh, undef, @cmd) or die "Can't run 'sudo -h': $!";
  	local $/ = undef;
  	local $_ = <$fh>;
  	close($fh) or die $!;
  	# prefer -n to skip password prompt
  	push(@sudo, '-n') if /-n/;
  	# ..if not supported, add -A as well
  	push(@sudo, '-A') if /-A/;
  
  	return \@sudo;
  }
  
  1;
APP_MONITORING_PLUGIN_CHECKRAID_UTILS

$fatpacked{"Class/Accessor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR';
  package Class::Accessor;require 5.00502;use strict;$Class::Accessor::VERSION='0.51';sub new {return bless defined $_[1]? {%{$_[1]}}: {},ref $_[0]|| $_[0]}sub mk_accessors {my($self,@fields)=@_;$self->_mk_accessors('rw',@fields)}if (eval {require Sub::Name}){Sub::Name->import}{no strict 'refs';sub import {my ($class,@what)=@_;my$caller=caller;for (@what){if (/^(?:antlers|moose-?like)$/i){*{"${caller}::has"}=sub {my ($f,%args)=@_;$caller->_mk_accessors(($args{is}||"rw"),$f)};*{"${caller}::extends"}=sub {@{"${caller}::ISA"}=@_;unless (grep $_->can("_mk_accessors"),@_){push @{"${caller}::ISA"},$class}};&{"${caller}::extends"}(@{"${caller}::ISA"})}}}sub follow_best_practice {my($self)=@_;my$class=ref$self || $self;*{"${class}::accessor_name_for"}=\&best_practice_accessor_name_for;*{"${class}::mutator_name_for"}=\&best_practice_mutator_name_for}sub _mk_accessors {my($self,$access,@fields)=@_;my$class=ref$self || $self;my$ra=$access eq 'rw' || $access eq 'ro';my$wa=$access eq 'rw' || $access eq 'wo';for my$field (@fields){my$accessor_name=$self->accessor_name_for($field);my$mutator_name=$self->mutator_name_for($field);if($accessor_name eq 'DESTROY' or $mutator_name eq 'DESTROY'){$self->_carp("Having a data accessor named DESTROY  in '$class' is unwise.")}if ($accessor_name eq $mutator_name){my$accessor;if ($ra && $wa){$accessor=$self->make_accessor($field)}elsif ($ra){$accessor=$self->make_ro_accessor($field)}else {$accessor=$self->make_wo_accessor($field)}my$fullname="${class}::$accessor_name";my$subnamed=0;unless (defined &{$fullname}){subname($fullname,$accessor)if defined&subname;$subnamed=1;*{$fullname}=$accessor}if ($accessor_name eq $field){my$alias="${class}::_${field}_accessor";subname($alias,$accessor)if defined&subname and not $subnamed;*{$alias}=$accessor unless defined &{$alias}}}else {my$fullaccname="${class}::$accessor_name";my$fullmutname="${class}::$mutator_name";if ($ra and not defined &{$fullaccname}){my$accessor=$self->make_ro_accessor($field);subname($fullaccname,$accessor)if defined&subname;*{$fullaccname}=$accessor}if ($wa and not defined &{$fullmutname}){my$mutator=$self->make_wo_accessor($field);subname($fullmutname,$mutator)if defined&subname;*{$fullmutname}=$mutator}}}}}sub mk_ro_accessors {my($self,@fields)=@_;$self->_mk_accessors('ro',@fields)}sub mk_wo_accessors {my($self,@fields)=@_;$self->_mk_accessors('wo',@fields)}sub best_practice_accessor_name_for {my ($class,$field)=@_;return "get_$field"}sub best_practice_mutator_name_for {my ($class,$field)=@_;return "set_$field"}sub accessor_name_for {my ($class,$field)=@_;return$field}sub mutator_name_for {my ($class,$field)=@_;return$field}sub set {my($self,$key)=splice(@_,0,2);if(@_==1){$self->{$key}=$_[0]}elsif(@_ > 1){$self->{$key}=[@_]}else {$self->_croak("Wrong number of arguments received")}}sub get {my$self=shift;if(@_==1){return$self->{$_[0]}}elsif(@_ > 1){return @{$self}{@_}}else {$self->_croak("Wrong number of arguments received")}}sub make_accessor {my ($class,$field)=@_;return sub {my$self=shift;if(@_){return$self->set($field,@_)}else {return$self->get($field)}}}sub make_ro_accessor {my($class,$field)=@_;return sub {my$self=shift;if (@_){my$caller=caller;$self->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}else {return$self->get($field)}}}sub make_wo_accessor {my($class,$field)=@_;return sub {my$self=shift;unless (@_){my$caller=caller;$self->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return$self->set($field,@_)}}}use Carp ();sub _carp {my ($self,$msg)=@_;Carp::carp($msg || $self);return}sub _croak {my ($self,$msg)=@_;Carp::croak($msg || $self);return}1;
CLASS_ACCESSOR

$fatpacked{"Class/Accessor/Fast.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FAST';
  package Class::Accessor::Fast;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Fast::VERSION='0.51';sub make_accessor {my ($class,$field)=@_;eval sprintf q{
          sub {
              return $_[0]{%s} if scalar(@_) == 1;
              return $_[0]{%s}  = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]];
          }
      },map {perlstring($_)}$field,$field}sub make_ro_accessor {my($class,$field)=@_;eval sprintf q{
          sub {
              return $_[0]{%s} if @_ == 1;
              my $caller = caller;
              $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s);
          }
      },map {perlstring($_)}$field,$field,$class}sub make_wo_accessor {my($class,$field)=@_;eval sprintf q{
          sub {
              if (@_ == 1) {
                  my $caller = caller;
                  $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s);
              }
              else {
                  return $_[0]{%s} = $_[1] if @_ == 2;
                  return (shift)->{%s} = \@_;
              }
          }
      },map {perlstring($_)}$field,$class,$field,$field}1;
CLASS_ACCESSOR_FAST

$fatpacked{"Class/Accessor/Faster.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FASTER';
  package Class::Accessor::Faster;use base 'Class::Accessor';use strict;use B 'perlstring';$Class::Accessor::Faster::VERSION='0.51';my%slot;sub _slot {my($class,$field)=@_;my$n=$slot{$class}->{$field};return$n if defined$n;$n=keys %{$slot{$class}};$slot{$class}->{$field}=$n;return$n}sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;my$self=bless [],$class;$fields={}unless defined$fields;for my$k (keys %$fields){my$n=$class->_slot($k);$self->[$n]=$fields->{$k}}return$self}sub make_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
          sub {
              return $_[0][%d] if scalar(@_) == 1;
              return $_[0][%d]  = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]];
          }
      },$n,$n}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
          sub {
              return $_[0][%d] if @_ == 1;
              my $caller = caller;
              $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s);
          }
      },$n,map(perlstring($_),$field,$class)}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{
          sub {
              if (@_ == 1) {
                  my $caller = caller;
                  $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s);
              }
              else {
                  return $_[0][%d] = $_[1] if @_ == 2;
                  return (shift)->[%d] = \@_;
              }
          }
      },map(perlstring($_),$field,$class),$n,$n}1;
CLASS_ACCESSOR_FASTER

$fatpacked{"Config/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CONFIG_TINY';
  package Config::Tiny;use strict;our$VERSION='2.23';BEGIN {require 5.008001;$Config::Tiny::errstr=''}sub new {return bless {},shift}sub read {my($class)=ref $_[0]? ref shift : shift;my($file,$encoding)=@_;return$class -> _error('No file name provided')if (!defined$file || ($file eq ''));$encoding=$encoding ? "<:$encoding" : '<';local $/=undef;open(CFG,$encoding,$file)or return$class -> _error("Failed to open file '$file' for reading: $!");my$contents=<CFG>;close(CFG);return$class -> _error("Reading from '$file' returned undef")if (!defined$contents);return$class -> read_string($contents)}sub read_string {my($class)=ref $_[0]? ref shift : shift;my($self)=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;s/\s\;\s.+$//g;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){$self->{$ns}->{$1}=$2;next}return$self -> _error("Syntax error at line $counter: '$_'")}return$self}sub write {my($self)=shift;my($file,$encoding)=@_;return$self -> _error('No file name provided')if (!defined$file or ($file eq ''));$encoding=$encoding ? ">:$encoding" : '>';my($string)=$self->write_string;return undef unless defined$string;open(CFG,$encoding,$file)or return$self->_error("Failed to open file '$file' for writing: $!");print CFG$string;close CFG;return 1}sub write_string {my($self)=shift;my($contents)='';for my$section (sort {(($b eq '_')<=> ($a eq '_'))|| ($a cmp $b)}keys %$self){return$self->_error("Illegal whitespace in section name '$section'")if$section =~ /(?:^\s|\n|\s$)/s;my$block=$self->{$section};$contents .= "\n" if length$contents;$contents .= "[$section]\n" unless$section eq '_';for my$property (sort keys %$block){return$self->_error("Illegal newlines in property '$section.$property'")if$block->{$property}=~ /(?:\012|\015)/s;$contents .= "$property=$block->{$property}\n"}}return$contents}sub errstr {$Config::Tiny::errstr}sub _error {$Config::Tiny::errstr=$_[1];undef}1;
CONFIG_TINY

$fatpacked{"Devel/InnerPackage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DEVEL_INNERPACKAGE';
  package Devel::InnerPackage;use strict;use Exporter 5.57 'import';use vars qw($VERSION @EXPORT_OK);use if $] > 5.017,'deprecate';$VERSION='0.4';@EXPORT_OK=qw(list_packages);sub list_packages {my$pack=shift;$pack .= "::" unless$pack =~ m!::$!;no strict 'refs';my@packs;my@stuff=grep!/^(main|)::$/,keys %{$pack};for my$cand (grep /::$/,@stuff){$cand =~ s!::$!!;my@children=list_packages($pack.$cand);push@packs,"$pack$cand" unless$cand =~ /^::/ || !__PACKAGE__->_loaded($pack.$cand);push@packs,@children}return grep {$_ !~ /::(::ISA::CACHE|SUPER)/}@packs}sub _loaded {my ($class,$name)=@_;no strict 'refs';return 1 if defined ${"${name}::VERSION"};return 1 if @{"${name}::ISA"};for (keys %{"${name}::"}){next if substr($_,-2,2)eq '::';return 1 if defined &{"${name}::$_"}}my$filename=join('/',split /(?:'|::)/,$name).'.pm';return 1 if defined$INC{$filename};''}1;
DEVEL_INNERPACKAGE

$fatpacked{"Math/Calc/Units.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS';
  package Math::Calc::Units;use Math::Calc::Units::Compute qw(compute);use Math::Calc::Units::Rank qw(render render_unit choose_juicy_ones);use Math::Calc::Units::Convert;use base 'Exporter';use vars qw($VERSION @EXPORT_OK);BEGIN {$VERSION='1.07';@EXPORT_OK=qw(calc readable convert equal exact)}use strict;sub calc ($;$) {my ($expr,$exact)=@_;my$v=compute($expr);return$exact ? ($v->[0],render_unit($v->[1])): render($v)}sub readable {my$expr=shift;my%options;if (@_==1){$options{verbose}=shift}else {%options=@_}my$v=compute($expr);return map {render($_,\%options)}choose_juicy_ones($v,\%options)}sub convert ($$;$) {my ($expr,$units,$exact)=@_;my$v=compute($expr);my$u=compute("# $units");my$c=Math::Calc::Units::Convert::convert($v,$u->[1]);return$exact ? ($c->[0],render_unit($c->[1])): render($c)}use constant EPSILON=>1e-12;sub equal {my ($u,$v)=@_;$u=compute($u);$v=compute($v);$v=Math::Calc::Units::Convert::convert($v,$u->[1]);$u=$u->[0];$v=$v->[0];return 1 if ($u==0)&& abs($v)< EPSILON;return abs(($u-$v)/$u)< EPSILON}if (!(caller)){my$verbose;my%options;if ($ARGV[0]eq '-v'){shift;$options{verbose}=1}if ($ARGV[0]eq '-a'){shift;$options{abbreviate}=1}print "$_\n" foreach readable($ARGV[0],%options)}1;
MATH_CALC_UNITS

$fatpacked{"Math/Calc/Units/Compute.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_COMPUTE';
  package Math::Calc::Units::Compute;use base 'Exporter';use vars qw(@EXPORT_OK);@EXPORT_OK=qw(compute plus minus mult divide power unit_mult unit_divide unit_power construct);use strict;use Math::Calc::Units::Convert qw(reduce);use Math::Calc::Units::Rank qw(render_unit);use Math::Calc::Units::Convert::Base;require Math::Calc::Units::Grammar;sub equivalent {my ($u,$v)=@_;return Math::Calc::Units::Convert::Base->same($u,$v)}sub is_unit {my ($x,$unit)=@_;return equivalent($x,{$unit=>1 })}sub plus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (equivalent($u->[1],$v->[1])){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'sec')&& is_unit($v->[1],'timestamp')){return [$u->[0]+ $v->[0],$v->[1]]}die "Unable to add incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub minus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'timestamp')){return [$u->[0]- $v->[0],{sec=>1 }]}elsif (equivalent($u->[1],$v->[1])){return [$u->[0]- $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]- $v->[0],$u->[1]]}die "Unable to subtract incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub mult {my ($u,$v)=@_;return [$u->[0]* $v->[0],unit_mult($u->[1],$v->[1])]}sub divide {my ($u,$v)=@_;return [$u->[0]/ $v->[0],unit_divide($u->[1],$v->[1])]}sub power {my ($u,$v)=@_;die "Can only raise to unit-less powers" if keys %{$v->[1]};$u=reduce($u);if (keys %{$u->[1]}!=0){my$power=$v->[0];die "Can only raise a value with units to an integral power" if abs($power - int($power))> 1e-20;return [$u->[0]** $power,unit_power($u->[1],$power)]}return [$u->[0]** $v->[0],{}]}sub unit_mult {my ($u,$v,$mult)=@_;$mult ||= 1;while (my ($unit,$vp)=each %$v){$u->{$unit}+= $vp * $mult;delete$u->{$unit}if$u->{$unit}==0}return$u}sub unit_divide {my ($u,$v)=@_;return unit_mult($u,$v,-1)}sub unit_power {my ($u,$power)=@_;return {}if$power==0;$u->{$_}*= $power foreach (keys %$u);return$u}sub construct {my$s=shift;my ($constructor,$args)=$s =~ /^(\w+)\((.*)\)/;return Math::Calc::Units::Convert::construct($constructor,$args)}package Math::Calc::Units::Compute;sub tokenize {my$data=shift;my@tokens=$data =~ m{\s*
                             (
                               \w+\([^\(\)]*\) # constructed (eg date(2001...))
                              |[\d.]+       # Numbers
                              |\w+          # Words
                              |\*\*         # Exponentiation (**)
                              |[-+*/()@]    # Operators
                             )}xg;my@types=map {/\w\(/ ? 'CONSTRUCT' :(/\d/ ? 'NUMBER' :(/\w/ ? 'WORD' :($_)))}@tokens;return \@tokens,\@types}sub compute {my$expr=shift;my$canonicalize=$expr !~ /^\#/;my ($vals,$types)=tokenize($expr);my$lexer=sub {return shift(@$types),shift(@$vals)if (@$types);return ('',undef)};my$parser=new Math::Calc::Units::Grammar;my$v=$parser->YYParse(yylex=>$lexer,yyerror=>sub {my$parser=shift;die "Error: expected ".join(" ",$parser->YYExpect)." got `".$parser->YYCurtok."', rest=".join(" ",@$types)."\nfrom ".join(" ",@$vals)."\n"},yydebug=>0);return$canonicalize ? reduce($v): $v};1;
MATH_CALC_UNITS_COMPUTE

$fatpacked{"Math/Calc/Units/Convert.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT';
  package Math::Calc::Units::Convert;use base 'Exporter';use strict;use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(convert reduce canonical find_top construct)};use Math::Calc::Units::Convert::Multi qw(to_canonical);sub convert {my ($from,$unit)=@_;my$to=[1,$unit ];my$canon_from=canonical($from);my$canon_to=canonical($to);die "conversion between incompatible units" if not same_units($canon_from->[1],$canon_to->[1]);return [$canon_from->[0]/ $canon_to->[0],$unit ]}sub same_units {my ($u1,$u2)=@_;return if keys %$u1!=keys %$u2;while (my ($bu1,$bp1)=each %$u1){return if!exists$u2->{$bu1};return if$bp1!=$u2->{$bu1}}return 1}sub canonical {my ($v)=@_;my$c=to_canonical($v->[1]);my$w=[$v->[0]* $c->[0],$c->[1]];return$w}sub reduce {my ($v)=@_;return canonical($v,'reduce, please')}sub construct {my ($constructor,$args)=@_;return Math::Calc::Units::Convert::Multi::construct($constructor,$args)}1;
MATH_CALC_UNITS_CONVERT

$fatpacked{"Math/Calc/Units/Convert/Base.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE';
  package Math::Calc::Units::Convert::Base;use strict;sub major_pref {return 0}sub major_variants {my ($self,$unit)=@_;return$unit}sub singular {my$self=shift;local $_=shift;return $_ unless /s$/;return $1 if /^(.*[^e])s$/;return $1 if /^(.*(ch|sh))es$/;return $1 if /^(.*[aeiou][^aeiou]e)s$/;chop;return $_}sub unit_map {return {}}sub variants {my ($self,$base)=@_;my$map=$self->unit_map();return ($base,keys %$map)}sub same {my ($self,$u,$v)=@_;return 0 if keys %$u!=keys %$v;while (my ($name,$power)=each %$u){return 0 if!exists$v->{$name};return 0 if$v->{$name}!=$power}return 1}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from eq $to;my$map=$self->unit_map();my$w=$map->{$from}|| $map->{lc($from)};if (!$w){$from=$self->singular($from);$w=$map->{$from}|| $map->{lc($from)}}return if!$w;if ($w->[1]ne $to){my$submult=$self->simple_convert($w->[1],$to);return if!defined$submult;return$w->[0]* $submult}else {return$w->[0]}}sub to_canonical {my ($self,$unitName)=@_;my$canon=$self->canonical_unit();if ($canon){my$mult=$self->simple_convert($unitName,$canon);return if!defined$mult;return ($mult,$canon)}else {return (1,$self->singular($unitName))}}sub canonical_unit {return}sub abbreviated_canonical_unit {my ($self)=@_;return$self->canonical_unit}my$THRESHOLD=0.01;sub spread {my ($self,$mag,$base,$start,$units)=@_;die if$mag < 0;return [0,$base ]if$mag==0;my$orig=$mag;my@desc;my$started=0;for my$unit (@$units){$started=1 if$unit eq $start;next unless$started;last if ($mag / $orig)< $THRESHOLD;my$mult=$self->simple_convert($unit,$base);my$n=int($mag / $mult);next if$n==0;$mag -= $n * $mult;push@desc,[$n,$unit ]}return@desc}sub range_score {my ($self,$val,$unitName)=@_;my$ranges=$self->get_ranges();my$range=$ranges->{$unitName}|| $ranges->{default};if ($val >= $range->[0]){if (!defined$range->[1]|| ($val <= $range->[1])){return 1}}$val=_sillylog($val);my$r0=_sillylog($range->[0]);my$r1;if (defined$range->[1]){$r1=_sillylog($range->[1])}else {$r1=4}my$width=$r1 - $r0;my$mean=($r0 + $r1)/ 2;my$stddev=$width / 2;my$n=($val - $mean)/ $stddev;our$mulconst;$mulconst ||= 0.999 * exp(1/8);return 0.001 + $mulconst * exp(-$n**2/2)}sub _sillylog {my$x=shift;return log($x)if$x;return log(1e-50)}sub pref_score {my ($self,$unitName)=@_;my$prefs=$self->get_prefs();my$specific=$prefs->{$unitName};return defined($specific)? $specific : $prefs->{default}}sub get_prefs {return {default=>0.1 }}sub get_ranges {return {default=>[1,undef ]}}sub render_unit {my ($self,$name,$power,$options)=@_;if ($power==1){return$name}else {return "$name**$power"}}sub render {my ($self,$val,$name,$power,$options)=@_;return sprintf("%.5g ",$val).$self->render_unit($name,$power,$options)}sub construct {return}1;
MATH_CALC_UNITS_CONVERT_BASE

$fatpacked{"Math/Calc/Units/Convert/Base2Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE2METRIC';
  package Math::Calc::Units::Convert::Base2Metric;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%metric_base2 %abbrev $metric_prefix_test %pref);%metric_base2=(kilo=>2**10,mega=>2**20,giga=>2**30,tera=>2**40,peta=>2**50,exa=>2**60,);%abbrev=(k=>'kilo',m=>'mega',g=>'giga',t=>'tera',p=>'peta',e=>'exa',);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,);sub get_metric {my ($self,$what)=@_;return$metric_base2{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}|| $abbrev{lc($what)}}$metric_prefix_test=qr/^(${\join("|",keys %metric_base2)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub get_prefixes {return keys%metric_base2}sub expand {my ($self,$char)=@_;return$self->get_abbrev($char)}1;
MATH_CALC_UNITS_CONVERT_BASE2METRIC

$fatpacked{"Math/Calc/Units/Convert/Byte.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BYTE';
  package Math::Calc::Units::Convert::Byte;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;my%units=(bit=>[1/8,'byte' ]);my%pref=(bit=>0.1,default=>1);my%ranges=(default=>[1,999 ]);my%total_unit_map;sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('byte')}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'byte'}sub abbreviated_canonical_unit {return 'B'}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^b(yte(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)b(yte(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."byte",$to)}}return}1;
MATH_CALC_UNITS_CONVERT_BYTE

$fatpacked{"Math/Calc/Units/Convert/Combo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_COMBO';
  package Math::Calc::Units::Convert::Combo;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;use vars qw(%units %metric_units %prefixable_metric_units %total_unit_map);use vars qw(%ranges %pref);%units=();%metric_units=();%prefixable_metric_units=(bps=>[1,{bit=>1,sec=>-1 }]);%ranges=(default=>[1,999 ]);%pref=(default=>1);sub canonical_unit {return}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units,%metric_units,%prefixable_metric_units)}return \%total_unit_map}sub singular {my ($self,$unit)=@_;return$self->SUPER::singular($unit)unless$unit =~ /bps$/;return$unit}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=lc($self->singular(substr($string,length($prefix))));if ($metric_units{$tail}){return ($self->get_metric($prefix),$tail)}}elsif (my$abbrev=$self->get_abbrev_prefix($string)){my$tail=lc($self->singular(substr($string,length($abbrev))));if ($prefixable_metric_units{$tail}){my$prefix=$self->get_abbrev($abbrev);return ($self->get_metric($prefix),$tail)}}return (1,$string)}sub to_canonical {return}sub lookup_compound {my ($self,$unitName)=@_;for (keys%units,keys%metric_units,keys%prefixable_metric_units){if (my$mult=$self->simple_convert($unitName,$_)){my$u=$units{$_}|| $metric_units{$_}|| $prefixable_metric_units{$_};return [$mult * $u->[0],$u->[1]]}}return}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}1;
MATH_CALC_UNITS_CONVERT_COMBO

$fatpacked{"Math/Calc/Units/Convert/Date.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DATE';
  package Math::Calc::Units::Convert::Date;use base 'Math::Calc::Units::Convert::Base';use Time::Local qw(timegm);use strict;use vars qw(%units %pref %ranges %total_unit_map);my$min_nice_time=timegm(0,0,0,1,0,1975-1900);my$max_nice_time=timegm(0,0,0,1,0,2030-1900);%units=();%pref=(default=>1);%ranges=(timestamp=>[$min_nice_time,$max_nice_time ]);sub major_pref {return 2}sub canonical_unit {return 'timestamp'}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}use vars qw(@MonthNames);BEGIN {@MonthNames=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)}sub construct {my ($self,$constructor,$args)=@_;if ($constructor eq 'timestamp'){$args=time if$args eq '';return [$args,{'timestamp'=>1 }]}return unless$constructor eq 'date';$args =~ s/\s+GMT\s+$//;my ($Mon,$d,$y,$h,$m,$s,$tz,$M);$tz='GMT';if ($args =~ /^((?:\w\w\w\s+)?)
                     (\w\w\w)\s*
                     (\d+)\s+
                     (\d+):(\d+)[:.](\d+)\s+
                     (\w+)?\s*
                     (\d\d\d\d)$/x){(undef,$Mon,$d,$h,$m,$s,$tz,$y)=($1,$2,$3,$4,$5,$6,$7,$8)}elsif ($args =~ /^(\w\w\w)[\s-]*
                          (\d+)[,\s-]+
                          (\d\d\d\d)$/x){($Mon,$d,$y)=($1,$2,$3)}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)\s+
                          (\d+):(\d+)[:.](\d+)$/x){($y,$M,$d,$h,$m,$s)=($1,$2,$3,$4,$5,$6);$M--}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)$/){($y,$M,$d)=($1,$2,$3);$M--}else {die "Unparseable date string '$args'"}$h ||= 0;$m ||= 0;$s ||= 0;if (defined$Mon){$M=0;for (@MonthNames){last if lc($_)eq lc($Mon);$M++}die "Unparseable month '$Mon'" if$M > 11}if (defined($tz)&& $tz ne 'GMT'){warn "Timezones not supported. Assuming GMT.\n"}my$timestamp=timegm($s,$m,$h,$d,$M,$y-1900);die "Date '$args' is out of range" if$timestamp==-1;return [$timestamp,{'timestamp'=>1 }]}sub render {my ($self,$mag,$name,$power)=@_;return "\@$mag" if$power!=1;return "\@$mag" if$mag < $min_nice_time;return "\@$mag" if$mag > $max_nice_time;return gmtime($mag)." (\@$mag)"}1;
MATH_CALC_UNITS_CONVERT_DATE

$fatpacked{"Math/Calc/Units/Convert/Distance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DISTANCE';
  package Math::Calc::Units::Convert::Distance;use base 'Math::Calc::Units::Convert::Metric';use strict;my%total_unit_map;my%ranges=(default=>[1,999 ]);my%distance_units=(inch=>[2.54,'centimeter' ],foot=>[12,'inch' ],yard=>[3,'foot' ],mile=>[5280,'foot' ],);my%distance_pref=(meter=>1.1,inch=>0.7,foot=>0.9,yard=>0,mile=>1.0,);my%aliases=('feet'=>'foot',);sub canonical_unit {return 'meter'}sub abbreviated_canonical_unit {return 'm'}sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('meter')}sub get_ranges {return \%ranges}sub get_prefs {return \%distance_pref}sub singular {my ($self,$unit)=@_;$unit=$self->SUPER::singular($unit);return$aliases{$unit}|| $unit}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%distance_units)}return \%total_unit_map}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^m(eter(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)m(eter(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."meter",$to)}}return}sub variants {my ($self,$base)=@_;my$canon=$self->canonical_unit();return ($base,keys %{$self->unit_map()},map {"$_$canon"}$self->get_prefixes())}1;
MATH_CALC_UNITS_CONVERT_DISTANCE

$fatpacked{"Math/Calc/Units/Convert/Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_METRIC';
  package Math::Calc::Units::Convert::Metric;use base 'Math::Calc::Units::Convert::Base';use strict;use vars qw(%niceSmallMetric %metric %pref %abbrev %reverse_abbrev $metric_prefix_test);%niceSmallMetric=(milli=>1e-3,micro=>1e-6,nano=>1e-9,pico=>1e-12,femto=>1e-15,);%metric=(kilo=>1e3,mega=>1e6,giga=>1e9,tera=>1e12,peta=>1e15,exa=>1e18,centi=>1e-2,%niceSmallMetric,);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,centi=>0.1,milli=>0.8,micro=>0.8,nano=>0.6,pico=>0.4,femto=>0.3,);%abbrev=(k=>'kilo',M=>'mega',G=>'giga',T=>'tera',P=>'peta',E=>'exa',c=>'centi',m=>'milli',u=>'micro',n=>'nano',p=>'pico',f=>'femto',);%reverse_abbrev=reverse%abbrev;sub pref_score {my ($self,$unitName)=@_;my$prefix=$self->get_prefix($unitName);$unitName=substr($unitName,length($prefix || ""));my$prefix_pref=defined($prefix)? $self->prefix_pref($prefix): 1;return$prefix_pref * $self->SUPER::pref_score($unitName)}sub get_metric {my ($self,$what)=@_;return$metric{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}}$metric_prefix_test=qr/^(${\join("|",keys %metric)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub get_prefixes {my ($self,$options)=@_;if ($options->{small}){return grep {$metric{$_}< 1}keys%metric}else {return keys%metric}}sub get_abbrev_prefix {my ($self,$what)=@_;my$prefix=substr($what,0,1);if ($abbrev{$prefix}|| $abbrev{lc($prefix)}){return$prefix}else {return}}sub variants {my ($self,$base)=@_;my@main=$self->SUPER::variants($base);my@variants;for my$u (@main){push@variants,$u,map {"$_$u"}$self->get_prefixes()}return@variants}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$base=substr($string,length($prefix));return ($self->get_metric($prefix),$base)}else {return (1,$string)}}sub expand {my ($self,$char)=@_;my@expansions;my ($exact,$lower);if ($exact=$self->get_abbrev($char)){push@expansions,$exact}elsif (($char ne lc($char))&& ($lower=$self->get_abbrev(lc($char)))){push@expansions,$lower}return@expansions}sub simple_convert {my ($self,$from,$to)=@_;my ($mult_from,$base_from)=$self->demetric($from)or return;my ($mult_to,$base_to)=$self->demetric($to)or return;my$submult=$self->SUPER::simple_convert($base_from,$base_to);return if!defined$submult;return$submult * ($mult_from / $mult_to)}sub metric_abbreviation {my ($self,$prefix)=@_;return$reverse_abbrev{$prefix}|| $prefix}sub render {my ($self,$val,$name,$power,$options)=@_;if ($options->{abbreviate}){my$stem=$self->canonical_unit;if ($name =~ /(\w+)\Q$stem\E$/){my$prefix=$reverse_abbrev{$1};if (defined($prefix)){$name=$prefix .$self->abbreviated_canonical_unit}}}return$self->SUPER::render($val,$name,$power,$options)}1;
MATH_CALC_UNITS_CONVERT_METRIC

$fatpacked{"Math/Calc/Units/Convert/Multi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_MULTI';
  package Math::Calc::Units::Convert::Multi;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(to_canonical simple_convert singular variants major_variants major_pref range_score pref_score get_class construct)};require Math::Calc::Units::Convert::Time;require Math::Calc::Units::Convert::Byte;require Math::Calc::Units::Convert::Date;require Math::Calc::Units::Convert::Distance;require Math::Calc::Units::Convert::Combo;use strict;use vars qw(@UnitClasses);@UnitClasses=qw(Math::Calc::Units::Convert::Time Math::Calc::Units::Convert::Byte Math::Calc::Units::Convert::Date Math::Calc::Units::Convert::Distance Math::Calc::Units::Convert::Combo);sub to_canonical {my ($unit)=@_;my$val=1;my%newUnit;while (my ($unitName,$power)=each %$unit){my ($mult,$canon)=name_to_canonical($unitName);$val *= $mult ** $power;if (ref$canon){my$c=to_canonical($canon);$val *= $c->[0]** $power;while (my ($name,$subPower)=each %{$c->[1]}){if (($newUnit{$name}+= $subPower * $power)==0){delete$newUnit{$name}}}}else {if (($newUnit{$canon}+= $power)==0){delete$newUnit{$canon}}}}return [$val,\%newUnit ]}my%CANON_CACHE;sub name_to_canonical {my$unitName=shift;$CANON_CACHE{$unitName}||= [_name_to_canonical($unitName)];return @{$CANON_CACHE{$unitName}}}sub _name_to_canonical {my ($unitName)=@_;if (my$v=Math::Calc::Units::Convert::Combo->lookup_compound($unitName)){return @$v}for my$uclass (@UnitClasses){if (my ($val,$base)=$uclass->to_canonical($unitName)){return ($val,$base)}}return Math::Calc::Units::Convert::Base->to_canonical($unitName)}sub get_class {my ($unitName)=@_;my (undef,$canon)=name_to_canonical($unitName);for my$uclass (@UnitClasses){my$canon_unit=$uclass->canonical_unit();next if!defined$canon_unit;return$uclass if$canon_unit eq $canon}return 'Math::Calc::Units::Convert::Base'}sub simple_convert {my ($u,$v)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->simple_convert($u,$v)}return}sub singular {my ($unitName)=@_;return get_class($unitName)->singular($unitName)}sub variants {my ($base)=@_;return get_class($base)->variants($base)}sub major_variants {my ($base)=@_;return get_class($base)->major_variants($base)}sub major_pref {my ($base)=@_;return get_class($base)->major_pref($base)}sub range_score {my ($val,$unitName)=@_;die if ref$unitName;return get_class($unitName)->range_score($val,$unitName)}sub pref_score {my ($unitName)=@_;die if ref$unitName;return get_class($unitName)->pref_score($unitName)}sub construct {my ($constructor,$args)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->construct($constructor,$args)}return}1;
MATH_CALC_UNITS_CONVERT_MULTI

$fatpacked{"Math/Calc/Units/Convert/Time.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_TIME';
  package Math::Calc::Units::Convert::Time;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%units %pref %ranges %total_unit_map);%units=(minute=>[60,'sec' ],hour=>[60,'minute' ],day=>[24,'hour' ],week=>[7,'day' ],year=>[365,'day' ],);%pref=(default=>1,hour=>0.8,day=>0.8,week=>0.4,minute=>0.9,year=>0.9,);%ranges=(default=>[1,300 ],millisec=>[1,999 ],sec=>[1,200 ],minute=>[2,100 ],hour=>[1,80 ],day=>[1,500 ],week=>[1,4 ],year=>[1,undef ],);sub major_pref {return 2}sub major_variants {my ($self)=@_;return grep {($_ ne 'default')&& ($_ ne 'week')}keys%ranges}sub variants {my ($self,$base)=@_;return 'sec',(keys%units),map {"${_}sec"}$self->get_prefixes({small=>1 })}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'sec'}sub abbreviated_canonical_unit {return 's'}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=substr($string,length($prefix));if ($tail =~ /^sec(ond)?s?$/){return ($self->get_metric($prefix),"sec")}return}else {return (1,$string)}}sub simple_convert {my ($self,$from,$to)=@_;$from="sec" if$from =~ /^sec(ond)?s?$/i;$from="minute" if$from =~ /^min(ute)?s?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)s$/){my ($expansion)=$self->expand($1);return$self->simple_convert($expansion ."sec",$to)}return}sub preference {my ($self,$v)=@_;my ($val,$unit)=@$v;my$base=lc(($self->demetric($unit))[1]);my$pref=$pref{$base}|| $pref{default};return$pref * $self->prefix_pref(substr($unit,0,-length($base)))}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}my@BREAKDOWN=qw(year week day hour minute sec ms us ns ps);sub render {my ($self,$val,$name,$power,$options)=@_;my$full_name=$name;if ($options->{abbreviate}){if ($name =~ /(\w+)sec/){my$prefix=$1;my$mabbrev=$self->metric_abbreviation($prefix);$name=$mabbrev ."s" unless$mabbrev eq $prefix}}my$basic=$self->SUPER::render($val,$name,$power,$options);return$basic if$power!=1;$val *= $self->simple_convert($full_name,'sec');my@spread=$self->spread($val,'sec',$name,\@BREAKDOWN);my$spread=join(" ",map {"$_->[0] $_->[1]"}@spread);return "($basic = $spread)" if@spread > 1;return$basic}1;
MATH_CALC_UNITS_CONVERT_TIME

$fatpacked{"Math/Calc/Units/Grammar.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_GRAMMAR';
  package Math::Calc::Units::Grammar;use vars qw ( @ISA);use strict;@ISA=qw ( Parse::Yapp::Driver);{package Parse::Yapp::Driver;require 5.004;use strict;use vars qw ( $VERSION $COMPATIBLE $FILENAME);$VERSION='1.04';$COMPATIBLE='0.07';$FILENAME=__FILE__;use Carp;my(%params)=(YYLEX=>'CODE','YYERROR'=>'CODE',YYVERSION=>'',YYRULES=>'ARRAY',YYSTATES=>'ARRAY',YYDEBUG=>'');my(@params)=('LEX','RULES','STATES');sub new {my($class)=shift;my($errst,$nberr,$token,$value,$check,$dotpos);my($self)={ERROR=>\&_Error,ERRST=>\$errst,NBERR=>\$nberr,TOKEN=>\$token,VALUE=>\$value,DOTPOS=>\$dotpos,STACK=>[],DEBUG=>0,CHECK=>\$check };_CheckParams([],\%params,\@_,$self);exists($$self{VERSION})and $$self{VERSION}< $COMPATIBLE and croak "Yapp driver version $VERSION "."incompatible with version $$self{VERSION}:\n"."Please recompile parser module.";ref($class)and $class=ref($class);bless($self,$class)}sub YYParse {my($self)=shift;my($retval);_CheckParams(\@params,\%params,\@_,$self);if($$self{DEBUG}){_DBLoad();$retval=eval '$self->_DBParse()';$@ and die $@}else {$retval=$self->_Parse()}$retval}sub YYData {my($self)=shift;exists($$self{USER})or $$self{USER}={};$$self{USER}}sub YYErrok {my($self)=shift;${$$self{ERRST}}=0;undef}sub YYNberr {my($self)=shift;${$$self{NBERR}}}sub YYRecovering {my($self)=shift;${$$self{ERRST}}!=0}sub YYAbort {my($self)=shift;${$$self{CHECK}}='ABORT';undef}sub YYAccept {my($self)=shift;${$$self{CHECK}}='ACCEPT';undef}sub YYError {my($self)=shift;${$$self{CHECK}}='ERROR';undef}sub YYSemval {my($self)=shift;my($index)=$_[0]- ${$$self{DOTPOS}}- 1;$index < 0 and -$index <= @{$$self{STACK}}and return $$self{STACK}[$index][1];undef}sub YYCurtok {my($self)=shift;@_ and ${$$self{TOKEN}}=$_[0];${$$self{TOKEN}}}sub YYCurval {my($self)=shift;@_ and ${$$self{VALUE}}=$_[0];${$$self{VALUE}}}sub YYExpect {my($self)=shift;keys %{$self->{STATES}[$self->{STACK}[-1][0]]{ACTIONS}}}sub YYLexer {my($self)=shift;$$self{LEX}}sub _CheckParams {my($mandatory,$checklist,$inarray,$outhash)=@_;my($prm,$value);my($prmlst)={};while(($prm,$value)=splice(@$inarray,0,2)){$prm=uc($prm);exists($$checklist{$prm})or croak("Unknow parameter '$prm'");ref($value)eq $$checklist{$prm}or croak("Invalid value for parameter '$prm'");$prm=unpack('@2A*',$prm);$$outhash{$prm}=$value}for (@$mandatory){exists($$outhash{$_})or croak("Missing mandatory parameter '".lc($_)."'")}}sub _Error {print "Parse error.\n"}sub _DBLoad {{no strict 'refs';exists(${__PACKAGE__.'::'}{_DBParse})and return}my($fname)=__FILE__;my(@drv);open(DRV,"<$fname")or die "Report this as a BUG: Cannot open $fname";while(<DRV>){/^\s*sub\s+_Parse\s*{\s*$/ .. /^\s*}\s*#\s*_Parse\s*$/ and do {s/^#DBG>//;push(@drv,$_)}}close(DRV);$drv[0]=~s/_P/_DBP/;eval join('',@drv)}sub _Parse {my($self)=shift;my($rules,$states,$lex,$error)=@$self{'RULES','STATES','LEX','ERROR' };my($errstatus,$nberror,$token,$value,$stack,$check,$dotpos)=@$self{'ERRST','NBERR','TOKEN','VALUE','STACK','CHECK','DOTPOS' };$$errstatus=0;$$nberror=0;($$token,$$value)=(undef,undef);@$stack=([0,undef ]);$$check='';while(1){my($actions,$act,$stateno);$stateno=$$stack[-1][0];$actions=$$states[$stateno];if (exists($$actions{ACTIONS})){defined($$token)or do {($$token,$$value)=&$lex($self)};$act=exists($$actions{ACTIONS}{$$token})? $$actions{ACTIONS}{$$token}: exists($$actions{DEFAULT})? $$actions{DEFAULT}: undef}else {$act=$$actions{DEFAULT}}defined($act)and do {$act > 0 and do {$$errstatus and do {--$$errstatus};push(@$stack,[$act,$$value ]);$$token ne '' and $$token=$$value=undef;next};my($lhs,$len,$code,@sempar,$semval);($lhs,$len,$code)=@{$$rules[-$act]};$act or $self->YYAccept();$$dotpos=$len;unpack('A1',$lhs)eq '@' and do {$lhs =~ /^\@[0-9]+\-([0-9]+)$/ or die "In line rule name '$lhs' ill formed: "."report it as a BUG.\n";$$dotpos=$1};@sempar=$$dotpos ? map {$$_[1]}@$stack[-$$dotpos .. -1 ]: ();$semval=$code ? &$code($self,@sempar): @sempar ? $sempar[0]: undef;splice(@$stack,-$len,$len);$$check eq 'ACCEPT' and do {return($semval)};$$check eq 'ABORT' and do {return(undef)};$$check eq 'ERROR' or do {push(@$stack,[$$states[$$stack[-1][0]]{GOTOS}{$lhs},$semval ]);$$check='';next};$$check=''};$$errstatus or do {$$errstatus=1;&$error($self);$$errstatus or next;++$$nberror};$$errstatus==3 and do {$$token eq '' and do {return(undef)};$$token=$$value=undef};$$errstatus=3;while(@$stack and (not exists($$states[$$stack[-1][0]]{ACTIONS})or not exists($$states[$$stack[-1][0]]{ACTIONS}{error})or $$states[$$stack[-1][0]]{ACTIONS}{error}<= 0)){pop(@$stack)}@$stack or do {return(undef)};push(@$stack,[$$states[$$stack[-1][0]]{ACTIONS}{error},undef ])}croak("Error in driver logic. Please, report it as a BUG")}1}use Math::Calc::Units::Compute qw(plus minus mult divide power construct);sub new {my($class)=shift;ref($class)and $class=ref($class);my($self)=$class->SUPER::new(yyversion=>'1.04',yystates=>[{ACTIONS=>{'NUMBER'=>5,"#"=>2,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'START'=>10,'expr'=>11 }},{DEFAULT=>-12 },{ACTIONS=>{'WORD'=>7 },GOTOS=>{'unit'=>12 }},{DEFAULT=>-16 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>13 }},{ACTIONS=>{'WORD'=>7 },DEFAULT=>-13,GOTOS=>{'unit'=>14 }},{ACTIONS=>{'NUMBER'=>15 }},{DEFAULT=>-17 },{DEFAULT=>-9 },{ACTIONS=>{'NUMBER'=>17 }},{ACTIONS=>{''=>18 }},{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24 },DEFAULT=>-1,GOTOS=>{'unit'=>19 }},{DEFAULT=>-2 },{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24,")"=>25 },GOTOS=>{'unit'=>19 }},{DEFAULT=>-11 },{DEFAULT=>-14 },{DEFAULT=>-18 },{DEFAULT=>-15 },{DEFAULT=>-0 },{DEFAULT=>-10 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>26 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>27 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>28 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>29 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>30 }},{DEFAULT=>-8 },{DEFAULT=>-7,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-5,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-3,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-4,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-6,GOTOS=>{'unit'=>19 }}],yyrules=>[['$start',2,undef ],['START',1,undef ],['START',2,undef ],['expr',3,sub {return plus($_[1],$_[3])}],['expr',3,sub {return minus($_[1],$_[3])}],['expr',3,sub {return mult($_[1],$_[3])}],['expr',3,sub {return divide($_[1],$_[3])}],['expr',3,sub {return power($_[1],$_[3])}],['expr',3,sub {return $_[2]}],['expr',1,sub {return $_[1]}],['expr',2,sub {return mult($_[1],[1,$_[2]])}],['value',2,sub {return [$_[1]=>$_[2]]}],['value',1,sub {return [1=>$_[1]]}],['value',1,sub {return [$_[1]=>{}]}],['value',2,sub {return [-$_[2]=>{}]}],['value',2,sub {return [$_[2]=>{'timestamp'=>1 }]}],['value',1,sub {return construct($_[1])}],['unit',1,sub {return {$_[1]=>1 }}],['unit',2,sub {my$u={};$u->{$_[1]}++;$u->{$_[2]}++;return$u}]],@_);bless($self,$class)}1;
MATH_CALC_UNITS_GRAMMAR

$fatpacked{"Math/Calc/Units/Rank.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_RANK';
  package Math::Calc::Units::Rank;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(choose_juicy_ones render render_unit)}use Math::Calc::Units::Convert qw(convert canonical);use Math::Calc::Units::Convert::Multi qw(variants major_variants major_pref pref_score range_score get_class);use strict;sub choose_juicy_ones {my ($v,$options)=@_;my@variants=rank_variants($v,$options);my%variants;for my$variant (@variants){my$id=join(";;",values %{$variant->[0]});$variants{$id}=$variant}my@options;for my$variant (values%variants){my ($map,$score)=@$variant;my%copy;my ($magnitude,$units)=@$v;while (my ($unit,$count)=each %$units){$copy{$map->{$unit}}=$count}push@options,[$score,convert($v,\%copy)]}my@juicy;my$first;my$prev;for (sort {$b->[0]<=> $a->[0]}@options){my ($score,$val)=@$_;last if (defined$prev && ($prev / $score)> 8);last if (defined$first && ($first / $score)> 25);push@juicy,$val;$first=$score unless defined$first;$prev=$score;last if@juicy==5}return@juicy}sub rank_variants {my ($v,$options)=@_;$v=canonical($v);my ($mag,$count)=@$v;my@rangeable=grep {$count->{$_}> 0}keys %$count;if (@rangeable==0){@rangeable=keys %$count}return rank_power_variants($mag,\@rangeable,$count,$options)}sub choose_major {my (@possibilities)=@_;my@majors=map {[major_pref($_),$_ ]}@possibilities;return (sort {$a->[0]<=> $b->[0]}@majors)[-1]->[1]}sub rank_power_variants {my ($mag,$top,$power,$options)=@_;if (keys %$power > 1){my$major=choose_major(keys %$power);my$majorClass=get_class($major);my%powerless=%$power;delete$powerless{$major};my@ranked;for my$variant (major_variants($major,$options)){my$mult=$majorClass->simple_convert($variant,$major);my$cval=$mag / $mult ** $power->{$major};print "\n --- for $variant ---\n" if$options->{verbose};my@r=rank_power_variants($cval,$top,\%powerless,$options);next if@r==0;my$best=$r[0];$best->[0]->{$major}=$variant;$best->[1]=pref_score($variant);push@ranked,$best}return@ranked}if (keys %$power==0){return [{},1 ]}my$unit=(keys %$power)[0];$power=$power->{$unit};my$class=get_class($unit);my (undef,$canon)=$class->to_canonical($unit);my$mult=$class->simple_convert($unit,$canon);$mag *= $mult ** $power;my@choices;my@subtop=grep {$_ ne $canon}@$top;my$add_variant=(@subtop==@$top);for my$variant (variants($canon)){my$mult=$class->simple_convert($variant,$canon);my$minimag=$mag / $mult ** $power;my@vtop=@subtop;push@vtop,$variant if$add_variant;my$score=score($minimag,$variant,\@vtop);printf "($mag $unit) score %.6f:\t $minimag $variant\n",$score if$options->{verbose};push@choices,[$score,$variant ]}@choices=sort {$b->[0]<=> $a->[0]}@choices;return ()if@choices==0;return map {[{$unit=>$_->[1]},$_->[0]]}@choices}sub render_unit {my ($units,$options)=@_;my$str='';while (my ($name,$power)=each %$units){if ($power > 0){$str .= get_class($name)->render_unit($name,$power,$options);$str .= " "}}chop($str);my$botstr='';while (my ($name,$power)=each %$units){if ($power < 0){$botstr .= get_class($name)->render_unit($name,-$power,$options);$botstr .= " "}}chop($botstr);if ($botstr eq ''){return$str}elsif ($botstr =~ /\s/){return "$str / ($botstr)"}else {return "$str / $botstr"}}sub render {my ($v,$options)=@_;my ($mag,$units)=@$v;if (keys %$units==0){my$str=sprintf("%.4g",$mag);if (($mag < 1)&& ($mag >= 0.01)){if ($options->{abbreviate}){$str .= sprintf(" = %.4g percent",100 * $mag)}else {$str .= sprintf(" = %.4g%%",100 * $mag)}}return$str}my@top;my@bottom;while (my ($name,$power)=each %$units){if ($power > 0){push@top,$name}else {push@bottom,$name}}my$str;if (@top==1){my ($name)=@top;$str=get_class($name)->render($mag,$name,$units->{$name},$options);$str .= " "}else {$str=sprintf("%.4g ",$mag);for my$name (@top){$str .= get_class($name)->render_unit($name,$units->{$name},$options);$str .= " "}}if (@bottom > 0){my$botstr;for my$name (@bottom){$botstr .= get_class($name)->render_unit($name,-$units->{$name},$options);$botstr .= " "}chop($botstr);if (@bottom > 1){$str .= "/ ($botstr) "}else {$str .= "/ $botstr "}}chop($str);return$str}sub max_range_score {my ($mag,$units)=@_;my$score=0;for my$name (@$units){my$uscore=range_score($mag,$name);$score=$uscore if$score < $uscore}return$score}sub score {my ($mag,$unit,$top)=@_;my@rangeable=@$top ? @$top : ($unit);my$pref=pref_score($unit);my$range_score=max_range_score($mag,\@rangeable);return$pref * $range_score}1;
MATH_CALC_UNITS_RANK

$fatpacked{"Module/Implementation.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_IMPLEMENTATION';
  package Module::Implementation;$Module::Implementation::VERSION='0.09';use strict;use warnings;use Module::Runtime 0.012 qw(require_module);use Try::Tiny;unless (exists$Module::Implementation::{VERSION}&& ${$Module::Implementation::{VERSION}}){$Module::Implementation::{VERSION}=\42}my%Implementation;sub build_loader_sub {my$caller=caller();return _build_loader($caller,@_)}sub _build_loader {my$package=shift;my%args=@_;my@implementations=@{$args{implementations}};my@symbols=@{$args{symbols}|| []};my$implementation;my$env_var=uc$package;$env_var =~ s/::/_/g;$env_var .= '_IMPLEMENTATION';return sub {my ($implementation,$loaded)=_load_implementation($package,$ENV{$env_var},\@implementations,);$Implementation{$package}=$implementation;_copy_symbols($loaded,$package,\@symbols);return$loaded}}sub implementation_for {my$package=shift;return$Implementation{$package}}sub _load_implementation {my$package=shift;my$env_value=shift;my$implementations=shift;if ($env_value){die "$env_value is not a valid implementation for $package" unless grep {$_ eq $env_value}@{$implementations};my$requested="${package}::$env_value";($requested)=$requested =~ /^(.+)$/;try {require_module($requested)}catch {require Carp;Carp::croak("Could not load $requested: $_")};return ($env_value,$requested)}else {my$err;for my$possible (@{$implementations}){my$try="${package}::$possible";my$ok;try {require_module($try);$ok=1}catch {$err .= $_ if defined $_};return ($possible,$try)if$ok}require Carp;if (defined$err && length$err){Carp::croak("Could not find a suitable $package implementation: $err")}else {Carp::croak('Module::Runtime failed to load a module but did not throw a real error. This should never happen. Something is very broken')}}}sub _copy_symbols {my$from_package=shift;my$to_package=shift;my$symbols=shift;for my$sym (@{$symbols}){my$type=$sym =~ s/^([\$\@\%\&\*])// ? $1 : '&';my$from="${from_package}::$sym";my$to="${to_package}::$sym";{no strict 'refs';no warnings 'once';*{$to}=$type eq '&' ? \&{$from}: $type eq '$' ? \${$from}: $type eq '@' ? \@{$from}: $type eq '%' ? \%{$from}: $type eq '*' ? *{$from}: die "Can't copy symbol from $from_package to $to_package: $type$sym"}}}1;
MODULE_IMPLEMENTATION

$fatpacked{"Module/Pluggable.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE';
  package Module::Pluggable;use strict;use vars qw($VERSION $FORCE_SEARCH_ALL_PATHS);use Module::Pluggable::Object;use if $] > 5.017,'deprecate';$VERSION='5.2';$FORCE_SEARCH_ALL_PATHS=0;sub import {my$class=shift;my%opts=@_;my ($pkg,$file)=caller;my$sub=$opts{'sub_name'}|| 'plugins';my ($package)=$opts{'package'}|| $pkg;$opts{filename}=$file;$opts{package}=$package;$opts{force_search_all_paths}=$FORCE_SEARCH_ALL_PATHS unless exists$opts{force_search_all_paths};my$finder=Module::Pluggable::Object->new(%opts);my$subroutine=sub {my$self=shift;return$finder->plugins(@_)};my$searchsub=sub {my$self=shift;my ($action,@paths)=@_;$finder->{'search_path'}=["${package}::Plugin"]if ($action eq 'add' and not $finder->{'search_path'});push @{$finder->{'search_path'}},@paths if ($action eq 'add');$finder->{'search_path'}=\@paths if ($action eq 'new');return$finder->{'search_path'}};my$onlysub=sub {my ($self,$only)=@_;if (defined$only){$finder->{'only'}=$only};return$finder->{'only'}};my$exceptsub=sub {my ($self,$except)=@_;if (defined$except){$finder->{'except'}=$except};return$finder->{'except'}};no strict 'refs';no warnings qw(redefine prototype);*{"$package\::$sub"}=$subroutine;*{"$package\::search_path"}=$searchsub;*{"$package\::only"}=$onlysub;*{"$package\::except"}=$exceptsub}1;
MODULE_PLUGGABLE

$fatpacked{"Module/Pluggable/Object.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE_OBJECT';
  package Module::Pluggable::Object;use strict;use File::Find ();use File::Basename;use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);use Carp qw(croak carp confess);use Devel::InnerPackage;use vars qw($VERSION $MR);use if $] > 5.017,'deprecate';$VERSION='5.2';BEGIN {eval {require Module::Runtime};unless ($@){Module::Runtime->import('require_module')}else {*require_module=sub {my$module=shift;my$path=$module .".pm";$path =~ s{::}{/}g;require$path}}}sub new {my$class=shift;my%opts=@_;return bless \%opts,$class}sub plugins {my$self=shift;my@args=@_;$self->{'require'}=1 if$self->{'inner'};my$filename=$self->{'filename'};my$pkg=$self->{'package'};$self->_setup_exceptions;for (qw(search_path search_dirs)){$self->{$_}=[$self->{$_}]if exists$self->{$_}&&!ref($self->{$_})}$self->{'search_path'}||= ["${pkg}::Plugin"];$self->{'on_require_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't require $plugin : $err";return 0};$self->{'on_instantiate_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't instantiate $plugin: $err";return 0};$self->{'follow_symlinks'}=1 unless exists$self->{'follow_symlinks'};my@SEARCHDIR=exists$INC{"blib.pm"}&& defined$filename && $filename =~ m!(^|/)blib/! &&!$self->{'force_search_all_paths'}? grep {/blib/}@INC : @INC;unshift@SEARCHDIR,@{$self->{'search_dirs'}}if defined$self->{'search_dirs'};my@tmp=@INC;unshift@tmp,@{$self->{'search_dirs'}|| []};local@INC=@tmp if defined$self->{'search_dirs'};my@plugins=$self->search_directories(@SEARCHDIR);push(@plugins,$self->handle_inc_hooks($_,@SEARCHDIR))for @{$self->{'search_path'}};push(@plugins,$self->handle_innerpackages($_))for @{$self->{'search_path'}};return ()unless@plugins;my%plugins;for(@plugins){next unless$self->_is_legit($_);$plugins{$_}=1}if (defined$self->{'instantiate'}){my$method=$self->{'instantiate'};my@objs=();for my$package (sort keys%plugins){next unless$package->can($method);my$obj=eval {$package->$method(@_)};$self->{'on_instantiate_error'}->($package,$@)if $@;push@objs,$obj if$obj}return@objs}else {my@objs=sort keys%plugins;return@objs}}sub _setup_exceptions {my$self=shift;my%only;my%except;my$only;my$except;if (defined$self->{'only'}){if (ref($self->{'only'})eq 'ARRAY'){%only=map {$_=>1}@{$self->{'only'}}}elsif (ref($self->{'only'})eq 'Regexp'){$only=$self->{'only'}}elsif (ref($self->{'only'})eq ''){$only{$self->{'only'}}=1}}if (defined$self->{'except'}){if (ref($self->{'except'})eq 'ARRAY'){%except=map {$_=>1}@{$self->{'except'}}}elsif (ref($self->{'except'})eq 'Regexp'){$except=$self->{'except'}}elsif (ref($self->{'except'})eq ''){$except{$self->{'except'}}=1}}$self->{_exceptions}->{only_hash}=\%only;$self->{_exceptions}->{only}=$only;$self->{_exceptions}->{except_hash}=\%except;$self->{_exceptions}->{except}=$except}sub _is_legit {my$self=shift;my$plugin=shift;my%only=%{$self->{_exceptions}->{only_hash}||{}};my%except=%{$self->{_exceptions}->{except_hash}||{}};my$only=$self->{_exceptions}->{only};my$except=$self->{_exceptions}->{except};my$depth=()=split '::',$plugin,-1;return 0 if (keys%only &&!$only{$plugin});return 0 unless (!defined$only || $plugin =~ m!$only!);return 0 if (keys%except && $except{$plugin});return 0 if (defined$except && $plugin =~ m!$except!);return 0 if defined$self->{max_depth}&& $depth>$self->{max_depth};return 0 if defined$self->{min_depth}&& $depth<$self->{min_depth};return 1}sub search_directories {my$self=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){push@plugins,$self->search_paths($dir)}return@plugins}sub search_paths {my$self=shift;my$dir=shift;my@plugins;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;for my$searchpath (@{$self->{'search_path'}}){my$sp=catdir($dir,(split /::/,$searchpath));next unless (-e $sp && -d _);my@files=$self->find_files($sp);for my$file (@files){next unless ($file)=($file =~ /(.*$file_regex)$/);my ($name,$directory,$suffix)=fileparse($file,$file_regex);next if (!$self->{include_editor_junk}&& $self->_is_editor_junk($name));$directory=abs2rel($directory,$sp);my@pkg_dirs=();if ($name eq lc($name)|| $name eq uc($name)){my$pkg_file=catfile($sp,$directory,"$name$suffix");open PKGFILE,"<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";my$in_pod=0;while (my$line=<PKGFILE>){$in_pod=1 if$line =~ m/^=\w/;$in_pod=0 if$line =~ /^=cut/;next if ($in_pod || $line =~ /^=cut/);next if$line =~ /^\s*#/;if ($line =~ m/^\s*package\s+(.*::)?($name)\s*;/i){@pkg_dirs=split /::/,$1 if defined $1;;$name=$2;last}}close PKGFILE}$directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/);my@dirs=();if ($directory){($directory)=($directory =~ /(.*)/);@dirs=grep(length($_),splitdir($directory))unless$directory eq curdir();for my$d (reverse@dirs){my$pkg_dir=pop@pkg_dirs;last unless defined$pkg_dir;$d =~ s/\Q$pkg_dir\E/$pkg_dir/i}}else {$directory=""}my$plugin=join '::',$searchpath,@dirs,$name;next unless$plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;$self->handle_finding_plugin($plugin,\@plugins)}push@plugins,$self->handle_innerpackages($searchpath)}return@plugins}sub _is_editor_junk {my$self=shift;my$name=shift;return 1 if$name =~ /~$/;return 1 if$name =~ /^\.#/;return 1 if$name =~ /\.sw[po]$/;return 0}sub handle_finding_plugin {my$self=shift;my$plugin=shift;my$plugins=shift;my$no_req=shift || 0;return unless$self->_is_legit($plugin);unless (defined$self->{'instantiate'}|| $self->{'require'}){push @$plugins,$plugin;return}$self->{before_require}->($plugin)|| return if defined$self->{before_require};unless ($no_req){my$tmp=$@;my$res=eval {require_module($plugin)};my$err=$@;$@=$tmp;if ($err){if (defined$self->{on_require_error}){$self->{on_require_error}->($plugin,$err)|| return}else {return}}}$self->{after_require}->($plugin)|| return if defined$self->{after_require};push @$plugins,$plugin}sub find_files {my$self=shift;my$search_path=shift;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;my@files=();{local $_;File::Find::find({no_chdir=>1,follow=>$self->{'follow_symlinks'},wanted=>sub {return unless$File::Find::name =~ /$file_regex/;(my$path=$File::Find::name)=~ s#^\\./##;push@files,$path}},$search_path)}return@files}sub handle_inc_hooks {my$self=shift;my$path=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){next unless ref$dir && eval {$dir->can('files')};for my$plugin ($dir->files){$plugin =~ s/\.pm$//;$plugin =~ s{/}{::}g;next unless$plugin =~ m!^${path}::!;$self->handle_finding_plugin($plugin,\@plugins)}}return@plugins}sub handle_innerpackages {my$self=shift;return ()if (exists$self->{inner}&&!$self->{inner});my$path=shift;my@plugins;for my$plugin (Devel::InnerPackage::list_packages($path)){$self->handle_finding_plugin($plugin,\@plugins,1)}return@plugins}1;
MODULE_PLUGGABLE_OBJECT

$fatpacked{"Module/Runtime.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_RUNTIME';
  package Module::Runtime;BEGIN {require 5.006}BEGIN {${^WARNING_BITS}=""}our$VERSION="0.016";our@EXPORT_OK=qw($module_name_rx is_module_name is_valid_module_name check_module_name module_notional_filename require_module use_module use_package_optimistically $top_module_spec_rx $sub_module_spec_rx is_module_spec is_valid_module_spec check_module_spec compose_module_name);my%export_ok=map {($_=>undef)}@EXPORT_OK;sub import {my$me=shift;my$callpkg=caller(0);my$errs="";for(@_){if(exists$export_ok{$_}){if(/\A\$(.*)\z/s){*{$callpkg."::".$1}=\$$1}else {*{$callpkg."::".$_}=\&$_}}else {$errs .= "\"$_\" is not exported by the $me module\n"}}if($errs ne ""){die "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     