#!/usr/bin/perl

#
# ldstate - tool to act as a front end for subordinate tools to monitor storage arrays.
# Copyright (c) 2012 Dell Inc.  All rights reserved.
#
# michael_stumpf@dell.com 
# curtis_quintal@dell.com
#

my $BUILD_DATE = "2013-11-23 08:32:54";

use strict; 
use warnings;

##no warnings 'portable'; #64-bit int support required

my $SAS2IRCU_CMD 	= 	"/usr/lib64/nagios/plugins/sas2ircu";
my $SAS2IRCU_ADPLIST= "";

my $SAS2FLASH_CMD = "/usr/lib64/nagios/plugins/sas2flash";
my @sas2flashcpids; #used to fork sas2flash process
my $sas2flashfn; #used to store sas2flash output
my $GETFULLNAME = 0; #set to 1 once sas2flash is used

my $MEGACLI_CMD = "/opt/MegaRAID/MegaCli/MegaCli64";		# Default path
my $MEGARAID_ADPLIST = "";

my $ADAPTEC_CMD = "./arcconf";

my $SMARTMONTOOLS_CMD = "./smartctl";
my $MDADM_CMD = "./mdadm";

my $ADPCOUNT = 0;		# How many adapters do we have?

my $SMARTCTL_VERSION;

my $TOOL_USES = 0;
my $TOOL_CALLS = "";

my $starttime = time();
my $endtime;
#
# Nagios constants
#
my $OK = 0;		
my $WARNING = 1;
my $CRITICAL = 2;
my $UNKNOWN = 3;

#
#Forking Variables
#
my $SMARTCTL_FORKING = 1;
my $USB_FORKING = 1;
my $MDADM_FORKING = 0;
my $SAS2FLASH_FORKING = 1;
#my $ADAPTEC_FORKING = 1;
#my $LSI_FORKING = 1;
#my $MEGARAID_FORKING = 1;

#
#Info Dashboard custom output variables
#
my $INFO_ADP_TEMPLATE = "%pf%AF %FN  %FWDV %BBU_STRING";
my $INFO_LD_TEMPLATE =  "%pf%AF ld%ld %wa%hl%wa %dc drv %sz %rt %ip [%dl]";
my $INFO_PD_TEMPLATE =  "%pf%AF %ES %st %tc %sz %pc %ls %mt %os %sd %iq";


#
# GLOBALS
#
my @ADP = ();		# Adapters
my @LD = ();		# Logical Drives

		# ldstate := { optimal, degraded, failed }	
		# ldinprogress := { background init, synchronize } 

my @PD = ();		# Physical Drives
	
		# These are the codes the SAS2008 chip uses.
		# Optimal 		OPT == good, in array
		# Ready 		RDY == available, not included in array
		# Missing		MIS
		# Rebuilding	RBLD	---- shortened to "rebuild"


my @BADPD = (); #PDs to reference to state checking

my $HOSTOS;

my @ARGLIST = ();
my @ARGFLAGS = ();
my @ARGLONGFLAGS = ();

my $MODE_CSV = 0;
my $MODE_NONAG = 0;
my $MODE_HRCSV = 0;
my $MODE_SHOWCALLS = 0;
my $SHOW_ADP = 1;
my $SHOW_LD =  1;
my $SHOW_PD =  1;

# Emulate command line equivalents
# Simple, does not support much or check for many errors
sub sgrep {
    my @arr;
    my $i;
    my $output = "";
    my ($flags, $tgt_string, $input) = @_;

    @arr = split(/\n/, $input);

    for $i (0 .. $#arr) {
        if ($flags eq "") {
            if ($arr[$i] =~ m/$tgt_string/) {
                $output .= $arr[$i] . "\n";
            }
        }
        if ($flags eq "i") {
            # ignore case
            if ($arr[$i] =~ m/$tgt_string/i) {
                $output .= $arr[$i] . "\n";
            }
        }
        if ($flags eq "v") {
            # invert match; if matches, do NOT include in output
            if (! ($arr[$i] =~ m/$tgt_string/i)) {
                $output .= $arr[$i] . "\n";
            }
        }
    }

    return $output;
}

# Only works for single field
sub cut {
#    my ($delim, $fld, $input) = @_;
	my $delim = shift || "";
	my $fld = shift || 1;
	my $input = shift || "";

    my $rc;
    my @arr;

    @arr = split($delim, $input);
    $rc = $arr[$fld-1];

    return $rc;
}


# Condense all extra spaces in a line
sub condense_spaces {
    my $x = shift;
    if (defined $x) {
        $x =~ s/\s+/ /g;
    }
    return $x;
}


# Given a string and a delimeter, get everything before the delimeter
# assumption is that we use the first instance of the delimeter
sub grabbefore {
	my $d1 = shift || "";
	my $inp = shift || "";

	my @a = split($d1,$inp);
	return $a[0];
}

# Given a string and a delimeter, get everything after the delimeter
# assumption is that we use the first instance of the delimeter
sub grabafter {
	my $d1 = shift || "";
	my $inp = shift || "";

	my @a = split($d1,$inp);
	shift @a;
	my $rc = join($d1, @a);		# Incase there are multiple cases of the delimeter, reconstruct & return
	return $rc;
}

# Grab everything between d1 and d2 delimeters.
sub grabbetween {
	my $d1 = shift || "";
	my $d2 = shift || "";
	my $inp = shift || "";

	my @a = split($d1,$inp);
	shift @a;
	my $tmp = $a[0] || "";

	my @b = split($d2,$tmp);
	$tmp = $b[0] || "";

	return $tmp;
}

# Grab line # from the input text
# line_num==1 is the first line
sub grabline {
	my $line_num = shift;
	my $inp = shift || "";

	$line_num = $line_num - 1;
	my @a = split(/\n/,$inp);
	return $a[$line_num];
}

# Grab a line range from the input text.
# line_num_finish == 0  means until end of array
# line_num_finish < 0   refers to end of array; so -2 would mean that we want to exclude the last 2 lines
sub grablines {
	my $line_num_start = shift;
	my $line_num_finish = shift;
	my $inp = shift || "";

	$line_num_start -= 1;
	if ($line_num_finish > 0) { $line_num_finish -= 1; }

	my @a = split(/\n/, $inp);
	my $numlines = $#a;

	# Zero or negative --> refers to end of list
	if ($line_num_finish <= 0) { $line_num_finish += $numlines; } 

	# Don't exceed end of array
	if ($line_num_finish > $numlines) { $line_num_finish = $numlines; }

	my @b = @a[$line_num_start..$line_num_finish];

	my $rc = join("\n",@b);
	return $rc;
}

# Nicer approach to the common problem of finding, then extracting a value from 
# a delimeted key-value pair
sub keyval_get_val {
	my $key = shift;
	my $inp = shift;
	my $delim = shift || ":";
	my @lines;
	my $rc = "error";

	my $tmp = sgrep("",$key,$inp);
	@lines = split(/\n/, $tmp);
	foreach my $line (@lines) {
		# We're going to return the first valid result we see.
		my @a = split($delim,$line);
		if (index($a[0],$key) != -1) { 
			# The left side contains the delimeter.  
			$rc = $a[1];
		} else {
			$rc = $rc;  # nop	
#			printf "skipping invalid line:: key:$key  line:$line\n";
		}
	}
	return trim($rc);
}

# Trim all whitespace
#
sub trim {
    my $x = shift;
    if (! defined $x) {
        # Explicitly go in and make undefined variables null strings.
        $x = "";
    } else {
        $x =~ s/^\s+//;
        $x =~ s/\s+$//;
    }

    return $x;
}


# Take whatever size in mb, kb, pb, tb, etc and streamline it into something common (megabytes)
# Megabytes is our lingua franca here.
sub streamline_size {
	my $inp = shift || "";
	my $rc = "error";

	$inp = lc($inp);
	$inp = trim($inp);
	if($inp =~ /byte/) {
		$inp = trim(cut("b",1,$inp));
		$rc = $inp / (1042 * 1024);
	}

	if ($inp =~ /kb/ || $inp =~ /kilobyte/) {
		$inp = trim(cut("k",1,$inp));
		$rc = $inp / 1024;
	}

	if ($inp =~ /mb/ || $inp =~ /megabyte/) {
		# This is target output form
		$inp = trim(cut("m",1,$inp));
		$rc = $inp;
	}

	if ($inp =~ /gb/ || $inp =~ /gigabyte/) {
		$inp = trim(cut("g",1,$inp));
		$rc = $inp * 1024;
	}

	if ($inp =~ /tb/ || $inp =~ /terabyte/) {
		$inp = trim(cut("t",1,$inp));
		$rc = $inp * 1024 * 1024;
	}

	if ($inp =~ /pb/ || $inp =~ /petabyte/) {
		$inp = trim(cut("p",1,$inp));
		$rc = $inp * 1024 * 1024 * 1024;
	}

	if ($inp =~ /eb/ || $inp =~ /exabyte/) {
		$inp = trim(cut("e",1,$inp));
		$rc = $inp * 1024 * 1024 * 1024 * 1024;
	}

	# The next programmer can address, if this limitation becomes a problem :-)
	return $rc;
}


sub isnumeric {
        my $inp = shift;
        no warnings 'numeric';
        if ($inp eq $inp+0) { return 1; }
        # non-numeric
        return 0;
}

# Convert [from MiB] to GB.
sub togb {
        my $inp = shift || 0;
        my $rc;

        if (! isnumeric($inp)) { $inp = 0; }
        # From MB to bytes
        $inp = $inp * 1024 * 1024;
        # From bytes to GiB
        $inp = $inp / 1000 / 1000 / 1000; #this is actually converting to GB
        $rc = sprintf "%d",$inp;
        if($rc eq"0") { $rc = sprintf "%.2f", $inp; }

        return $rc;
}


# Convert [from MiB] to GiB. 
sub togib {
	my $inp = shift || 0;
	my $rc;

	$inp = $inp / 1024; #this actually converts to GiB
	$rc = sprintf "%d",$inp; 

	return $rc;
}

# Debugging shim
sub read_from_file {
	my $fname = shift || "";
	my $rc = "";
	open MYFILE, $fname or die "\nError: could not open file $fname\n\n";
	while (<MYFILE>) {
		$rc .= $_;	
	}
	return $rc;	
}

# Given a default, and some env variables (comma delimeted strings); figure out which
# tool we're going to use. 
# Require that we find SOMETHING.
sub test_tool_exists {
	my $toolname = shift;
	my $default_loc = shift;
	my $env_var_list = shift;
	my $tool;

	# Try env variables first.  They take precidence over the default.
	if (defined $env_var_list) {
		my @evars = split(",",$env_var_list);
		foreach my $cur (@evars) {
			$tool = $ENV{$cur};
			if ((defined $tool) && (-e $tool)) {
#				printf "Found and using tool: $tool  (from env var $cur)\n";
				return ($tool,0);
			}
		}

	}

	# Ok, try the default locations.
	my @deflocs = split(",",$default_loc);
	foreach my $loc (@deflocs) {
		$tool = $default_loc;
		if (-e $tool) {
	#		printf "Found and using tool: $tool  (from default location list)\n";
			return ($tool,0);
		}
	}

###	$tool = $default_loc;
###	if (-e $tool) {
####		printf "Found and using tool: $tool  (from default)\n";
###		return $tool;
###	}

	# Try the default install directory
	$tool = "/opt/dell/pec/" . $default_loc;
	if (-e $tool) {
		return ($tool,0);
	}

	#try /usr/sbin/
	$tool = "/usr/sbin/" . 	lc($toolname);
	if(-e $tool){
	
		return ($tool,0);
	}	

	return ("error",1);
}

sub require_tool_exists {
	my $toolname = shift;
	my $default_loc = shift;
	my $env_var_list = shift;

	# Found nothing.  Error.
	printf "\n";
	printf "Error:  \"$toolname\" is required, but could not be found.\n";
	printf "  I looked for it in:  $default_loc\n";
	if (defined $env_var_list) {
		printf "  I also searched env variables in this list: $env_var_list\n";
	}
	printf "  Halting; could not find it.\n\n";
	exit(1);
}

sub shell_command{
	my $cmd = shift;
	my $data;
	
	$data = `$cmd`;

	$TOOL_USES++;
	$TOOL_CALLS .= "$cmd\n";
	return $data;
}

sub get_adaptec_cmd {
	my $cmd = shift;
	my $adp = shift;
	my $arg3 = shift || "";
	my $cmdline;
	my $rc;
	my $ec;

	$cmdline = $ADAPTEC_CMD . " " . $cmd . " " . $adp . " " . $arg3;
	#$rc = `$cmdline`; #normally just `$cmdline`
	$rc = shell_command($cmdline);
	$ec = $?;
	
	return $rc;
}

sub get_madm_cmd {
	my $arg1 = shift || "";
	my $arg2 = shift || "";
	my $arg3 = shift || "";
	my $cmdline;
	my $rc;
	my $ec;

	$cmdline = $MDADM_CMD . " $arg1" . " $arg2" . " $arg3";
	#$rc = `$cmdline`;
	$rc = shell_command($cmdline);
	$ec = $?;
	return $rc;
}

sub get_smartmontools_cmd{
	my $arg1 = shift || "";
	my $arg2 = shift || "";
	my $cmdline;
	my $rc;
	my $ec;

	$cmdline = $SMARTMONTOOLS_CMD . " $arg1" . " $arg2" . " 2>&1";
	#$rc = `$cmdline`;
	$rc = shell_command($cmdline);
	$ec = $?;

	return $rc;
}




#
# Fetch a response from the LSI "sas2ircu" tool, and make sure that it's valid.
#
sub get_sas2ircu_cmd {
	my $adp = shift;
	my $cmd = shift;
	my $cmdline;
	my $rc;
	my $ec;
	my @arr;
	my $tmp;
	my $iter = 0;

	while (1) {	
		$iter++;
		$cmdline = "$SAS2IRCU_CMD $adp $cmd";
		#$rc = `$cmdline`;
		$rc = shell_command($cmdline);
		$ec = $?;
		
		# Need to check the exit code.  This sometimes fails.
		if ($ec == 0) {
			# Should also check that the last line looks correct ("SAS2IRCU: Utility Completed Successfully.")
			$rc = trim($rc);
			@arr = split("\n", $rc);
			$tmp = lc($arr[$#arr]);
			# Generic success
			if ($tmp =~ /utility completed successfully/) { last; }

		}

		# Below this line, all exit codes indicate an error.  But some are actually ok/expected.
		# Asked for adapters on a system that has none:
		if ($cmd eq "list") {
			if ($rc =~ /MPTLib2 Error/i) { last; }
		}

		# No LDs configured--this is the error message
		if ($cmd eq "status") {
#			printf "dbg:\n---\n$rc\n---\n";
			if ($rc =~ /are no IR volumes on the/) {
				$rc = "";
				last;
			}
		}


		if ($iter >= 3) {
			printf "Error: could not successfully fetch this command:\n";
			printf "   cmdline: $cmdline\n";
			exit(1);
		}
		sleep (1+$iter);
	}

	return $rc;
}

#
# Go run megacli and grab the output.
#
sub get_megaraid_cmd {
	my $adp = shift;
	my $cmd = shift;
	my $noprompt = shift || "yes";
	my $cmdline;
	my $rc;
	my $ec;

	if($noprompt eq "no") { $cmdline = $MEGACLI_CMD . " " . $cmd . " a$adp 2>&1"; }
	else{ $cmdline = $MEGACLI_CMD . " " . $cmd . " a$adp nolog"; }
	#$rc = `$cmdline`;	
	$rc = shell_command($cmdline);
	$ec = $?;

	return $rc;
}


#
# sub-worker for parse_sas2ircu.  This finds and returns a particular drive entry from the status output
# from sas2ircu.
#
sub fetch_status_drive_entry {
	my $ldnum = shift;
	my $input_st = shift;
	my $thisld;

	# Numbering is off for "IR Volume"
	my $seek_irvol = $ldnum + 1;
	my $irvol;

	my @a = split("IR Volume", $input_st);
	shift @a;

	foreach (@a) {
		$thisld = $_;
		$irvol = trim(grabline(1,$thisld));	
		$irvol = int($irvol);	
		
		if ($irvol == $seek_irvol) {
			# This is the one we want.  
			return $thisld;
		} 
		
		# Did not match; take a look at the next listed IR Volume.
	}


	# Failed..
	printf "ERROR:  DID NOT FIND status entry for drive $ldnum\n";
	return "";
}

#
# Take the output of "sas2ircu list" and return a list of present adapters.
#
sub parse_sas2ircu_adplist {
	my $inp = shift;
	my $adplist = "";
	my $adplines = "";

	$inp = grabafter("All rights reserved",$inp);
	$inp = grablines(2,0,$inp);

	# mjs >>> this approach works, but is more fragile.
#	$inp = sgrep("v","Utility Completed Successfully",$inp);
#	$inp = sgrep("v","SubSys",$inp);
#	$inp = sgrep("v","Pci Address",$inp);
#	$inp = sgrep("v","------",$inp);

	# Instead just support per-chip.
	#
	# mjs >>> TODO >>> this is fundamentally the wrong approach.  This is fragile.  The underlying
	# tools support all kinds of chips, the breadth of which we cannot imagine.
	#
	#
	$adplines .= sgrep("","SAS2004",$inp);
	$adplines .= sgrep("","SAS2008",$inp);
	$adplines .= sgrep("","SAS2108",$inp);
	$adplines .= sgrep("","SAS2116",$inp);
	$adplines .= sgrep("","SAS2208",$inp);
	$adplines .= sgrep("","SAS2308",$inp);
	$adplines .= sgrep("","SAS3108",$inp);

	$adplines = trim($adplines);

#	printf"debug:\n---\n$inp\n---\n";

	my @lines = split("\n",$adplines);
	my $notfirst = 0;
	my $line;
	foreach (@lines) {
		$line = trim($_);
		if ($notfirst) { $adplist .= ","; }
		$adplist .= cut(" ",1,$line);
		$notfirst = 1;
	}
	return $adplist;
}

sub parse_sas2ircu {
	my $adpref = shift;		# Reference to adapter object
	my $input_st = shift;
	my $input_dsp = shift;
	my @a;
	my @b;
	my $tmp;
	my $thisld;
	my $thispd;
	my $tmppd;
	my $sde;

	my $toolname;
	my $toolver;
	my $adptype;
	my $adpnum;
	my $adpfwver;
	my $adpbiosver;

	my $ldnum;
	my $ldtype;
	my $ldsize;
	my $ldmembers;
	my $ldpdlist;
	my $ldstate;
	my $ldinprogress;
	my $ldinprogresspct;
	my $ldvolumeid;

	my $pdenc;
	my $pdslot;
	my $pdsz;
	my $pdmodel;
	my $pdfw;
	my $pdserial;
	my $pdsasaddr;
	my $pdstate;
	my $pdguid;
	my $pdmediatype = "";
	my $pdprotocol;
	my $pdosmapping= "";
	my $pdtemp = "";
	my $pdstateraw = ""; #raw output from tool
	my $pdsmrtstateraw = ""; #raw output from smartctl
	my $serial = ""; #used in comparison to BADPD measures from smartctl
	my $pdppid = "";
	my $pdformfactor = "";
	my $pdrotation = "";
	my $tmppdlist;
	my @pdarr;

	# Version of sas2ircu tool
	$toolname = $adpref->{'toolname'};
	$toolver = $adpref->{'toolver'};

##	$toolver = grablines(1,3,$input_dsp);
##	$toolver = trim(sgrep("","Version",$toolver));
##	$toolver = cut(" ",2,$toolver);
###	keyval_get_val("Version ", $input_st);
	
	@a = split(/IR Volume/,$input_st);
	shift @a;
	# @a now contains the data from "status" for each logical drive 
	# we mostly need this for LD rebuild status.  "display" is much more useful overall

	$adptype = keyval_get_val("Controller type", $input_dsp);
	$adptype = lc($adptype);

	$adpnum = sgrep("","Read configuration has been initiated for controller", $input_dsp);
	$adpnum = trim(cut("controller",2,$adpnum));

	$adpbiosver = keyval_get_val("BIOS version",$input_dsp);
	$adpfwver 	= keyval_get_val("Firmware version",$input_dsp);

	#
	# Look at each physical drive
	#
	$tmp = grabbetween("Physical device information","Enclosure information",$input_dsp);
	$tmp = grablines(4,0,$tmp);
	$tmp = trim($tmp);
	@b = split("Device is a",$tmp);
	shift @b; 	# first one is not real

	my $pdtype;
	foreach (@b) {
		$thispd = trim($_);
		$pdtype = lc(grabline(1,$thispd));
		if (!($pdtype =~ /hard disk/)) { 
			# Not a hard disk.  NEXT.
			next;  
		}
		$pdenc = keyval_get_val("Enclosure ",$thispd);
		$pdslot = keyval_get_val("Slot ",$thispd);
		$pdsz = keyval_get_val("Size ",$thispd) || 0;
		if ($pdsz =~ "/") {
			$pdsz = cut("/",1,$pdsz);
			$pdsz = streamline_size($pdsz . " mb");
		}
		$pdmodel = keyval_get_val("Model Number",$thispd);
		$pdfw = keyval_get_val("Firmware Revision",$thispd);
		$pdserial = keyval_get_val("Serial No",$thispd);
		$pdserial =~ s/\s//g; #remove whitespace
		$pdserial =~ s/-//g; #and dashes for matching
		$pdsasaddr = keyval_get_val("SAS Address",$thispd);
		$pdguid = keyval_get_val("GUID",$thispd);
		$pdguid =~ s/\s//g; #remove whitespace
		$pdguid =~ s/-//g; #remove dashes
		$pdstate = keyval_get_val("State",$thispd);
		$pdstateraw = $pdstate;
		$pdstate = lc(cut(" ",1,$pdstate));
		#treat "degraded" state as a failed state
		if($pdstate =~ /degraded/i) { $pdstate = "failed"; }
		if($pdstate eq "rebuilding") { $pdstate = "rebuild"; }
		if($pdstate =~ /available/i) { $pdstate = "failed"; } #Available-drive is not suitable for use in a volume or a hot spare.
		if($pdstate =~ /out of sync/i) { $pdstate = "out of sync"; } #in LD but not in sync with other drives in LD
		#check smartctl to see if failed
		$pdrotation = $pdosmapping = $pdtemp = $pdsmrtstateraw = $serial = $pdppid = $pdformfactor ="";
		
		foreach(@BADPD){
			$serial = $_->{"pdserial"};
			#print "guid/pdguid $guid/$pdguid\n";
			if($serial eq $pdserial){
				if($_->{"pdstate"} eq "failed") { $pdstate = $_->{"pdstate"}; }
				$pdosmapping = $_->{"pdname"};
				$pdtemp = $_->{"pdtemp"};
				$pdsmrtstateraw = $_->{"pdstateraw"};
				$pdppid = $_->{"pdppid"};
				$pdformfactor = $_->{"pdformfactor"};
				$pdrotation = $_->{"pdrotation"};
				last;
			}
		}
		$pdprotocol = keyval_get_val("Protocol",$thispd) || "";

		my $pdoverheat = "";
	
#		printf "%3s:%-3s %s %s  %s (%s) [%s] $pdprotocol $pdsasaddr $pdguid\n",$pdenc,$pdslot,$pdstate,togb($pdsz) . "gb",$pdmodel,$pdfw,$pdserial;
		my %h = ();

		$h{'toolname'} = $toolname;
		$h{'toolver'} = $toolver;

		$h{'adpref'} = $adpref;

		$h{'adptype'} = $adptype;	# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
		$h{'adpbiosver'} = $adpbiosver;
		$h{'adpfwver'} = $adpfwver;
		
		$h{'pdrpm'} = $pdrotation;
		$h{'pdenc'} = $pdenc;
		$h{'pdslot'} = $pdslot;
		$h{'pdsz'} = $pdsz;
		$h{'pdmodel'} = $pdmodel;
		$h{'pdfw'} = $pdfw;
		$h{'pdserial'} = $pdserial;
		$h{'pdsasaddr'} = $pdsasaddr;
		$h{'pdstate'} = $pdstate;
		$h{'pdguid'} = $pdguid;
		$h{'pdprotocol'} = $pdprotocol;
		$h{'pdosmapping'} = $pdosmapping;
		$h{'pdtemp'} = $pdtemp;
		$h{'pdoverheat'} = $pdoverheat;
		$h{'pdstateraw'} = $pdstateraw;
		$h{'pdsmartstateraw'} = $pdsmrtstateraw;
		$h{"pdppid"} = $pdppid;
		$h{'pdformfactor'} = $pdformfactor;	
		push @PD, \%h;	

	}

	#
	# Now walk over each logical drive in "display"
	#
	$tmp = grabbetween("IR Volume information", "Physical device information", $input_dsp);
	@b = split("IR volume", $tmp);
	shift @b;	# first one is cruft

	# For each LD
	foreach (@b) {
		$thisld = $_;
		
		$ldnum = trim(grabline(1,$thisld));	
		$ldnum = int($ldnum) - 1;

		$ldtype = keyval_get_val("RAID level", $thisld);
		$ldsize = keyval_get_val("Size ", $thisld);
		$ldsize = streamline_size($ldsize . " mb");
		# ldstripesz isn't available with this tool

		# Now grab the physical drive list
		$tmppdlist = trim(sgrep("","PHY",$thisld));
		@pdarr = split(/\n/,$tmppdlist);
		$ldmembers = $#pdarr + 1;
		# For each physical drive listed as a member of the LD
		my $notfirst = 0;
		$ldpdlist = "";
		foreach (@pdarr) {
			$thispd = $_;
			my @tmpa = split(":",$thispd);
			shift @tmpa;
			$tmppd = trim(join(":",@tmpa));
			if ($notfirst) { $ldpdlist .= ","; }
			$ldpdlist .= $tmppd;
			$notfirst = 1;	
		}

		$ldvolumeid = keyval_get_val("Volume ID", $thisld);

		# Now get LD state.  This is tricky.  Using the status info from the tool, locate the blurb we care about
		$sde = fetch_status_drive_entry($ldnum, $input_st);
	
		$ldstate = lc(keyval_get_val("Volume state", $sde));
		$ldinprogress = lc(keyval_get_val("Current operation", $sde));
		$ldinprogresspct = keyval_get_val("Percentage complete", $sde);

		if ($ldinprogress eq "none") { $ldinprogress = ""; } 
		

		#
		# That's all for the LD.  
		# Now, construct a logical drive data structure and add it to our list
		#

		my %h = ();


		$h{'toolname'} = $toolname;
		$h{'toolver'} = $toolver;

		$h{'adpref'} = $adpref;

		$h{'adptype'} = $adptype;			# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
		$h{'adpbiosver'} = $adpbiosver;
		$h{'adpfwver'} = $adpfwver;

	
		$h{'ldvolumeid'} = $ldvolumeid; #used for deleting ld
		$h{'ldinprogress'} = $ldinprogress;
		$h{'ldstate'} = $ldstate;
		$h{'ldpdlist'} = $ldpdlist;
		$h{'ldsize'} = $ldsize;
		$h{'ldmembers'} = $ldmembers;
		$h{'ldnum'} = $ldnum;
		$h{'ldtype'} = $ldtype;

		push @LD, \%h;	
	}

}

sub get_onboard_pds {
	my @pd;
	my $goodref;
	my $badref;
	my $unknownref;
	my $pds;
	my $checkversion = 0;
	my $rc;
	#
	# Get a list of all onboard PDs
	# 
	# Start with a list of PDs from /dev/disk/by-path
	# We could have PDs that are attached to known onboard controllers ("good")
	# or, PDs attached to known RAID/HBA adapters ("bad").  (These are covered by 
	# the other parsing functions.)
	# or, the "unknown" don't fit either of these categories.
	#
	($goodref, $unknownref, $badref) = filter_dev_disk_bypath();
	@pd = @$goodref;
	$pds = @pd;
	
	#smartctl needed for onboard drives
	($SMARTMONTOOLS_CMD,$rc) = test_tool_exists("smartctl",$SMARTMONTOOLS_CMD,"SMARTMONTOOLS,SMARTCTL");
	if ($rc) {
		print "smartctl not found.  Please install the latest version of smartctl.\n";
		print "Some drive characteristic data will not be available.  Also, onboard PDs\n";
		print "and USB devices will not be listed.\n";
		return;
	}

	$SMARTCTL_VERSION = get_smartctl_vrsn();

###	if($SMARTCTL_VERSION == -1){
###		print "smartctl not found. Please install the latest version of smartctl.\n";
###		print "No onboard PDs or USB devices will be listed because smartctl isn't present\n";
###		return;
###	}
	
	if($pds) { 
		@pd = get_pd_info_from_smartmontools(\@pd,"yes"); 
		$checkversion = 1;
	}
	push @PD, @pd;

	@pd = @$unknownref;
	$pds = @pd;
	if($pds) { 
		@pd = get_pd_info_from_smartmontools(\@pd, "unknown");
		$checkversion = 1;
	}
	push @PD, @pd;

	#$endtime = time() - $starttime;
	#print "before smartctl of bads $endtime\n";	
	#Get some info on "bad" PDs to check smartctl state in other parsing functions	
	@pd = @$badref;
	$pds = @pd;
	if($pds) { @pd = get_pd_state_info_smartmontools(\@pd, "bad"); }
	push @BADPD, @pd;
	
	#if onboard PDs were found print warning smartctl version was below 6.0
	#Taken out for now, this warning only allows for one more keyval
	if($checkversion && $SMARTCTL_VERSION =~ /^[12345]./) { 
		if($MODE_NONAG) { }
		#else{ print "!smartctl is version $SMARTCTL_VERSION. Update to version 6.0 or later reccomended!\n"; }
	}
	

}

#get info on lvm LVs, VGs and PVs associated with them
sub parse_lvm {
	my @ld;
	my @pvs;
	my @a;	
	my @b;
	my $tmp;
	my $thisld;

	my $toolname;
	my $toolver;
	my $adptype;
	my $adpnum;

	my $ldnum;
	my $ldtype;
	my $ldsize;
	my $ldmembers;
	my $ldpdlist;
	my $ldstate;
	my $ldinprogress;
	my $ldinprogresspct;
	my $ldname;

	my $ldpdlistlong; #this has drives plus PE offset
	my $ldpath;
	my $ldvg;
	my $lvsegments;
	my $lvmirrors;
	my $vgpvlist;
	my $ldvgformat;
	my $ldvgmembers;
	my $ldvgsize;
	my $ldstripes;
	my $ldsegtype;
	my $lvmirrorlog = "no mirrors";

	my $i = 0;
	#as at this point we've only detected md Raid LDs this is sufficient for numbering
	foreach(@LD){
		if($_->{"ldnum"} >= $i) { $i = $_->{"ldnum"}+1; }
	}

	#look for any lvm LVs with lvm tool
	$tmp = shell_command("lvm lvscan 2>&1");
	if(!defined($tmp) || $tmp =~ /no volume groups found/i){
		return;	#not LVs detected by lvm
	}

	$toolver = shell_command("lvm version");
	$toolver = keyval_get_val("LVM version", $toolver);
	
	$tmp = shell_command("lvm lvdisplay 2>&1");

	@ld = split("Logical volume",$tmp);
	shift @ld; #first result is before actual data

	foreach(@ld){
		
		#grep and grabafter as " " will not work as delimeter in keyval_get_val
		$ldpath = sgrep("","LV Name", $_);
		$ldpath = grabafter("LV Name",$ldpath);
		$ldpath = trim($ldpath);
		$ldtype = "lvmLV"; #default until more specific type is identified by stripes/mirrors
		$ldsize = sgrep("","LV Size", $_);
		$ldsize = grabafter("LV Size",$ldsize);
		$ldsize = trim($ldsize);
		$ldsize = streamline_size(lc($ldsize));
		$ldstate = sgrep("","LV Status", $_);
		$ldstate = grabafter("LV Status",$ldstate);
		$ldstate = trim($ldstate);
		$ldvg = sgrep("","VG Name", $_);
		$ldvg = grabafter("VG Name",$ldvg);
		$ldvg = trim($ldvg);
		#segments are NOT the same as stipes
		$lvsegments = sgrep("","Segments", $_);
		$lvsegments = grabafter("Segments",$lvsegments);
		$lvsegments = trim($lvsegments);
		$lvmirrors = sgrep("","Mirrored volumes", $_);
		$lvmirrors = grabafter("Mirrored volumes",$lvmirrors);
		$lvmirrors = trim($lvmirrors);
		if(!defined($lvmirrors) || $lvmirrors eq "") { $lvmirrors = 0; } #if field is blank/absent=0 mirrors

		$ldname = grabafter("$ldvg/",$ldpath);

		$tmp = shell_command("lvs -a --o lv_name,vg_name,segtype,stripes,devices 2>&1");
		$thisld = sgrep("","$ldname",$tmp);
		$thisld = sgrep("","$ldvg ", $thisld);
		
		#if a device is on more than one line different scraping method required as below
		my $lines = $thisld =~ tr/\n//;	
		@a = split(" ", $thisld);
		$ldsegtype = trim($a[2]);
		$ldstripes = trim($a[3]);
		$ldpdlist = trim($a[4]);
		@a = split(",",$ldpdlist);
		$ldpdlist = "";
		$ldpdlistlong = "";
		$ldmembers = 0;
		foreach(@a){
			if($_ =~ /read failed/i) { next; } #error, missing pd most likely
			if($_ =~ /unknown/i) { $ldpdlist .= "UNK,"; $ldpdlistlong .= "UNK,"; next; }
			if($_ =~ /mimage/i) { next; } #deal with mirrors after
			$ldpdlist .= grabbetween("/dev/","\Q(\E",$_) . ",";
			$ldpdlistlong .= grabafter("/dev/",$_) . ",";
			$ldmembers++;
		}
		#linear device on multiple drives appear on multiple lines
		if($lines > 1 && $ldsegtype ne "mirror"){
			@a = split("\n",$thisld);
			shift @a;
			foreach(@a){
				if($_ =~ /read failed/i) { next; } #error, missing pd most likely
				@b = split(" ",$_);
				@b = split(",",trim($b[4]));
				foreach(@b) { 
					if($_ =~ /unknown/i) { $ldpdlist .= "UNK,"; $ldpdlistlong .= "UNK,"; next; }
					my $thispd = grabbetween("/dev/","\Q(\E",$_);
					$ldpdlistlong .= grabafter("/dev/",$_) . ","; 
					if($ldpdlist =~ /$thispd/) { next; }
					else { $ldpdlist .= $thispd . ","; $ldmembers++; }
				}
			}
		}
		
		$lvmirrorlog = "No mirrors";
		#these devices are a bit different
		if($ldsegtype eq "mirror"){
			$ldmembers = 0;
			$ldpdlist = "";
			$tmp = sgrep("","\Q[\E"."$ldname"."_mimage",$thisld);
			@a = split("\n",$tmp);
			foreach(@a){
				if($_ =~ /read failed/i) { next; } #error, missing pd most likely
				@b = split(" ",$_);
				@b = split(",",trim($b[4]));
				foreach(@b) { 
					if($_ =~ /unknown/i) { $ldpdlist .= "UNK,"; $ldpdlistlong .= "UNK,"; next; }
					my $thispd = grabbetween("/dev/","\Q(\E",$_);
					$ldpdlistlong .= grabafter("/dev/",$_) . ","; 
					if($ldpdlist =~ /$thispd/) { next; }
					else { $ldpdlist .= $thispd . ","; $ldmembers++; }
				}
			}
			$tmp = sgrep("","$ldname"."_mlog",$thisld);
			@a = split(" ",$tmp);
			$lvmirrorlog = trim($a[4]);
			if(!defined($lvmirrorlog) || $lvmirrorlog eq "") { $lvmirrorlog = "Mirror log stored in memory"; }
		}
		$ldpdlist =~ s/,$//;
		$ldpdlistlong =~ s/,$//;

		#get segment info
		$tmp = shell_command("lvm vgdisplay $ldvg 2>&1");
		$ldvgsize = sgrep("","VG Size", $tmp);
		$ldvgsize = grabafter("VG Size",$ldvgsize);
		$ldvgsize = trim($ldvgsize);
		$ldvgsize = streamline_size(lc($ldvgsize));
		$ldvgmembers = sgrep("","Cur PV", $tmp);
		$ldvgmembers = grabafter("Cur PV",$ldvgmembers);
		$ldvgmembers = trim($ldvgmembers);
		$ldvgformat = sgrep("","Format", $tmp); 
		$ldvgformat = grabafter("Format",$ldvgformat);
		$ldvgformat = trim($ldvgformat);

		#get PVs that are part of VG
		$tmp = shell_command("lvm pvdisplay 2>&1");
		@pvs = split("Physical volume",$tmp);
		shift @pvs; #first result contains no real data
		$vgpvlist = ""; #clear before loop
		foreach (@pvs) {
			my $pvvg = sgrep("","VG Name",$_);
			$pvvg = grabafter("VG Name",$pvvg);
			$pvvg = trim($pvvg);
			if($pvvg ne $ldvg) { next; } #only consider PDs in the VG that out LV belongs to
			my $pvname = sgrep("","PV Name", $_);
			$pvname = grabafter("PV Name",$pvname);
			$pvname = trim($pvname);
			if($pvname =~ /\/dev\//) { $pvname = grabafter("/dev/",$pvname); }
			$vgpvlist .= $pvname . ",";
		}
		$vgpvlist =~ s/,$//;
	
		#determine actualy lvm type of LV
		if   ($lvmirrors >  0) 				{ $ldtype = "lvmMirror"; }
		elsif($lvmirrors == 0 && $ldstripes > 1)	{ $ldtype = "lvmStripe"; }
		elsif($lvmirrors == 0 && $ldstripes == 1)	{ $ldtype = "lvmLinear"; }
			
		if($ldstate =~ /NOT available/i) { 
			$ldstate = "failed"; #Not available on non mirror most likely means data is missing
			if($ldtype eq "lvmMirror") { $ldstate = "degraded"; } #mirrored data can still be restored
		}
		if($ldstate =~ /available/i) { $ldstate = "optimal"; }
	
		my %h = ();

                $h{'toolver'} = $toolver;
		$h{'toolname'} = "lvm";

		$h{'adpnum'} = $adpnum = "0";
		$h{'adptype'} = $adptype = "onboard";
		
                $h{'ldname'} = $ldname;
		$h{'ldnum'} = $i;
             	$h{'ldpath'} = $ldpath;
	   	$h{'ldtype'} = $ldtype;
                $h{'ldsize'} = $ldsize;
		$h{'ldmembers'} = $ldmembers;
                $h{'ldpdlist'} = $ldpdlist;
                $h{'ldpdlistlong'} = $ldpdlistlong;
		$h{'lvsegtype'} = $ldsegtype;
		$h{'ldstate'} = $ldstate;
		$h{'lvsegments'} = $lvsegments;
		$h{'lvmirrors'} = $lvmirrors;
		$h{'lvstripes'} = $ldstripes;
		$h{'vgpvlist'} = $vgpvlist;
		$h{'vg'} = $ldvg;
		$h{'vgsize'} = $ldvgsize;
		$h{'lvmirrorlog'} = $lvmirrorlog;
		$h{'vgmembers'} = $ldvgmembers;
		$h{'vgformat'} = $ldvgformat;
		$h{'ldinprogress'} = ""; #cannot find right now
                push @LD, \%h;
		$i++;
	}





}

#get info on md devices from linux software raid
sub parse_lswr {
	my @ld;
	my @a;	
	my $tmp;
	my $thisld;

	my $toolname;
	my $toolver;
	my $adptype;
	my $adpnum;
	my $adpbiosver;
	my $adpfwver;

	my $ldnum;
	my $ldtype;
	my $ldsize;
	my $ldmembers;
	my $ldpdlist;
	my $ldstate;
	my $ldinprogress;
	my $ldinprogresspct;
	my $ldname;
	my $ldstripesz;
	my $ldparity;
	my $ldbootable;
	my $ldhs;
	my $ldhspresent;

	my $pid;
	my @pids;
	my $filename;
	my $file;

	#look for any md devices in car proc/partitions
	#These are linux software raid devices
	$tmp = shell_command("cat /proc/partitions");
	$tmp = sgrep("i","md",$tmp);
	@a = split("\n",$tmp);
	foreach(@a){
		$tmp = grabafter("md",$_);
		$tmp = trim($tmp);
		push @ld, $tmp;
	}

	#run all mdadm calls in parellel
	if($MDADM_FORKING){
		$filename = get_temp_filename();
		#this will get mdadm output into a tmp file
		#uses fork so children can run mdadm in parallel
		foreach (@ld){
			my $number = $_;
			$file = $filename . $number;
			$pid = fork;
			die "Failed to fork: $!\n" if !defined $pid;
			if($pid == 0) { 
				#in child process
				system("mdadm --misc --detail /dev/md$number > $file");
				exit;
			}
			else { 
				#in parent process
				push @pids, $pid;
				next; 
			}
		}
	
		#wait until each child process has ended
		foreach my $cpid (@pids) {
			waitpid $cpid, 0;	
		}
	}
	
	foreach(@ld){
		#for each md device use mdadm to get LD info
		#hash ld info and pass into @LD

		#print"ldnum:$_\n";
		$ldnum = $_;                
                if($MDADM_FORKING) {
			$file = $filename . $ldnum;
			$thisld = shell_command("cat $file");
		}
		else { $thisld = get_madm_cmd("--misc","--detail","/dev/md$ldnum"); }
                $ldname = "md" . $ldnum;
                $ldtype = sgrep("i","RAID Level",$thisld);
                $ldtype = cut(": ",2,$ldtype);
                $ldtype =~ s/[^0-9]//g;
                $ldtype = trim($ldtype);
                if($ldtype =~ m/[0-9]*?/) { $ldtype = "RAID" . $ldtype; }
                $ldsize = sgrep("i","Array Size",$thisld);
		$ldsize = cut(": ",2,$ldsize);
		if ($ldsize =~ /([0-9]+)/) { $ldsize = $1; }
		$ldsize = streamline_size($ldsize . "kb"); 
		#grab one of the three numbers left in ldsize
		$ldmembers = sgrep("i","Raid Devices",$thisld);
		$ldmembers = cut(": ",2,$ldmembers);
		$ldmembers = trim($ldmembers);
		$ldstate = sgrep("i","State",$thisld);
		$ldstate = grabline(1,$ldstate);
		$ldstate = cut(": ",2,$ldstate);
		$ldstate = trim($ldstate);
		$ldpdlist = grabafter("State",$thisld);
		$ldpdlist = grabafter("State",$ldpdlist);
		my @pdlist = split("\n",$ldpdlist);
		$ldpdlist = "";
		shift @pdlist;
		$ldhs = "";
		$ldhspresent = "no";
		foreach(@pdlist){
			if($_ ne "") {
				if($_ =~ /active sync/i) { 	
					if($ldpdlist ne "") { $ldpdlist .= ","; }
					$ldpdlist .= grabafter("/dev/",$_); 
				}
				elsif($_ =~ /spare rebuilding/i) { 	
					if($ldpdlist ne "") { $ldpdlist .= ","; }
					$ldpdlist .= grabafter("/dev/",$_); 
				}
				elsif($_ =~ /spare/i) {
					$ldhspresent = "yes";
					$ldhs = grabafter("/dev/",$_);
				}
			}
		}

		
		$ldinprogresspct = sgrep("i","Rebuild Status",$thisld);
		if($ldinprogresspct eq "") { $ldinprogress = ""; }
		else{
			if($ldstate =~ /recovering/i) { $ldinprogress = "recovery"; }
			elsif($ldstate =~ /resyncing/i) { $ldinprogress = "resync"; }
			else { $ldinprogress = "yes"; }
			$ldinprogresspct = cut(": ",2,$ldinprogresspct);
			$ldinprogresspct =~ /([0-9]+)/;
			$ldinprogresspct = $1;
		}
	
	##GET ldinprogress(pct) from cat /proc/mdstat, not preffered method
	#	$tmp = `cat /proc/mdstat`;
	#	my @ldlist = split("md",$tmp);
	#	
	#	foreach(@ldlist){
	#		$tmp = grabbefore(":",$_);
	#		$tmp =~ s/[^0-9]//g;
	#		if($tmp eq $ldnum){
	#			if($_ =~ /recovery/i) { 
	#				$ldinprogress = "rcv"; 
	#				$ldinprogresspct = sgrep("i","recovery",$_);
	#				$ldinprogresspct =~ /([0-9]+)/;
	#				$ldinprogresspct = $1;
	#			}
        #                        elsif($_ =~ /resync/i) {
        #                                $ldinprogress = "rsy";
        #                                $ldinprogresspct = sgrep("i","resync",$_);
        #                                $ldinprogresspct =~ /([0-9]+)/;
        #                                $ldinprogresspct = $1;    
        #                }
	#			else{
	#				$ldinprogress = "";
	#				$ldinprogresspct = "";
	#			}
	#		}
	#	}
		
		if($ldstate =~ /degraded/i) { $ldstate = "degraded"; }
		elsif($ldstate =~ /failed/i) { $ldstate = "failed"; }
		elsif($ldstate =~ /active/i) { $ldstate = "optimal"; }
		elsif($ldstate =~ /clean/i) { $ldstate = "optimal"; }

		if($MDADM_FORKING) { system("rm $file"); }

		my %h = ();

                #$h{'toolver'} = $toolver;
                #$h{'adpref'} = $adpref;

		$h{'adpnum'} = $adpnum = "0";
		$h{'adptype'} = $adptype = "onboard";
		
                $h{'ldname'} = $ldname;
		$h{'ldnum'} = $ldnum;
                $h{'ldtype'} = uc($ldtype);
                $h{'ldsize'} = $ldsize;
                $h{'ldmembers'} = $ldmembers;
                $h{'ldpdlist'} = $ldpdlist;
                $h{'ldstate'} = $ldstate;
                $h{'ldinprogress'} = $ldinprogress;
                $h{'ldinprogresspct'} = $ldinprogresspct;
		
		$h{'ldhs'} = $ldhs;
		$h{'ldhspresent'} = $ldhspresent;

                push @LD, \%h;

	}

}


#
#this parses all info in physical and logical
#drives from adaptec controller call to getconfig
#
sub parse_adaptec {
	my $adpref = shift;
	my $input_getconfig = shift;
	my @pd;
	my @ld;	
	my @a;
	my $tmp;
	my $thispd;
	my $thisld;

	my $toolname;
	my $toolver;
	my $adptype;
	my $adpnum;
	my $adpbiosver;
	my $adpfwver;

	my $ldnum;
	my $ldtype;
	my $ldsize;
	my $ldmembers;
	my $ldpdlist;
	my $ldstate;
	my $ldinprogress;
	my $ldinprogresspct;
	my $ldname;
	my $ldstripesz;
	my $ldparity;
	my $ldbootable;

	my $pdtype;
	my $pdenc;
	my $pdslot;
	my $pdsz;
	my $pdmodel;
	my $pdfw;
	my $pdserial;
	my $pdsasaddr;
	my $pdstate;
	my $pdguid;
	my $pdprotocol;
	my $pdosmapping;
	my $pdstateraw = ""; #raw output from tool
	my $pdsmrtstateraw = ""; #raw state output from smartctl
	my $pdppid = "";
	my $pdformfactor = "";
	my $pdrotation = "";

	my $pddevspeed;
	my $pdlinkspeed;
	my $pdspundown;
	my $pdmediatype;
	my $pdtemp;

	my $tmppdlist;
	my @pdarr;


	#adaptec arcconf tool info
	$toolname = $adpref->{'toolname'};
	$toolver = $adpref->{'toolver'};
	$adptype =  $adpref->{'adptype'};
	$adpnum = $adpref->{'adpnum'};
	$adpbiosver = $adpref->{'adpbios'};
	$adpfwver = $adpref->{'adpfw'};

	#look at the physical drives
	$tmp = grabbetween("Physical Device information", "Command completed successfully", $input_getconfig);
	@pd = split("Device #",$tmp);
	#First result not really a drive, shift
	shift @pd;
	
	foreach(@pd){
		# TODO
		#find pddevspeed in arcconf output and parse/hash
		#find more possible values for pdstate, match to abbreviate_pdstate
		#drive temperature needs to be found in arcconf
		#pdsassadr needs to be found in arcconf
		#confirm pdslot, pdenc coming from correct field - done for fw 7.xx
		
#TESTS
#tested mulitple drives in series 6/7 cards
###some cards have no guid, no "world-wide name" field
#Reported Channel device, Reported Location handeled for both series 6/7
###Reported Location saved as pdphysenc,pdphysslot are not the actual enclosure and slot
###but it is what the LD output holds so we track it to cross-reference and get actual enc/slot
#

		my $pdphysenc;
		my$pdphysslot;

		$thispd = trim($_);
		$pdtype = lc(grabline(2,$thispd));
		if (!($pdtype =~ /hard drive/)) {
			#not a hard disk, parse next pd
			next;
		}
		
		#pdphysenc,pdphysslot used as reference for logical drives to get pdenc,pdslot
		$pdphysenc = sgrep("i","Reported Location",$thispd);
		$pdphysenc = cut(": ",2,$pdphysenc);
		my @phys = split(",", $pdphysenc);
		$pdphysenc = shift @phys; #Reported Enclosure/Connector
		$pdphysslot = shift @phys; #Reported Slot/Device
		$pdphysenc =~ s/[^0-9]//g;
		$pdphysslot =~ s/[^0-9]//g;
		#pdenc and pdslot are actualy enclosure and slot for this pd

		$tmp = keyval_get_val("Reported Channel,Device",$thispd, "  :");	# spaces REQUIRED in delimeter
		$tmp = grabbefore("\Q(\E",$tmp); 
		$pdslot = cut(",",2,$tmp);
		$pdenc = cut(",",1,$tmp);

		$pdsz = sgrep("i","Total Size",$thispd);
		$pdsz = cut(": ",2,$pdsz);
		$pdsz = trim($pdsz);
		$pdsz = streamline_size($pdsz); #
		$pdmodel = sgrep("i","Model",$thispd);
		$pdmodel = cut(": ",2,$pdmodel);
		$pdmodel = trim($pdmodel);
		$pdfw = sgrep("i","Firmware",$thispd);
		$pdfw = cut(": ",2,$pdfw);
		$pdfw = trim($pdfw);
		$pdserial = sgrep("i","Serial number",$thispd);
		$pdserial = cut(": ",2,$pdserial);
		$pdserial = trim($pdserial);
		$pdserial =~ s/\s//g; #remove whitespace
		$pdserial =~ s/-//g; #and remove dashes for matching later
		$pdsasaddr = ""; #still needs to be found
		$pdstate = sgrep("i","State",$thispd);
		$pdstate = grabline(1,$pdstate);
		$pdstate = cut(": ",2,$pdstate);
		$pdstate = trim($pdstate); #more parsing pdstate later, before hash
		$pdstateraw = $pdstate;
		$pdguid = sgrep("i","World-wide name",$thispd);
		if($pdguid eq "") { $pdguid =  ": "; } #some drives lack WWN field
		$pdguid = cut(": ",2,$pdguid);
		$pdguid = trim($pdguid);
	

		$pdosmapping = "";
		$pdtemp = "";	
		$pdsmrtstateraw = "";
		$pdppid = "";
		$pdformfactor = "";
		$pdrotation = "";
		#check against os drives to get osmapping/state/temperature
		#will only work for jbod drives as only they are seen by os
		foreach(@BADPD){
			my $serial = $_->{"pdserial"};
			#pdguid detected by adaptec is 1 higher than smartctl
			if($serial eq $pdserial){
				if($_->{"pdstate"} eq "failed") { $pdstate = "failed"; }
				$pdosmapping = $_->{"pdname"};
				$pdtemp = $_->{"pdtemp"};
				$pdsmrtstateraw = $_->{"pdstateraw"};
				$pdppid = $_->{"pdppid"};
				$pdformfactor = $_->{"pdformfactor"};
				$pdrotation = $_->{"pdrotation"};
				last;
			}
		}


		$pdprotocol = sgrep("i","Transfer Speed",$thispd);
			
		if($pdprotocol ne ""){
			$pdprotocol = cut(":",2,$pdprotocol);
			$pdlinkspeed = $pdprotocol;
			$pdprotocol = grabbetween(" "," ",$pdprotocol);#get first word SAS/SATA
			$pdprotocol = trim($pdprotocol);
			if ($pdlinkspeed =~ /unknown/i) { $pdlinkspeed = ""; }
        		elsif ($pdlinkspeed =~ /1.5 Gb/i) { $pdlinkspeed = "1.5G"; }
        		elsif ($pdlinkspeed =~ /3.0 Gb/i) { $pdlinkspeed = "3G"; }
       		 	elsif ($pdlinkspeed =~ /6.0 Gb/i) { $pdlinkspeed = "6G"; }
        		elsif ($pdlinkspeed =~ /12.0 Gb/i) { $pdlinkspeed = "12G"; }
			else { $pdlinkspeed = ""; }
			#$pddevspeed = ""; #cannot find in arcconf output
		}
		else {
			$pdlinkspeed = $pdprotocol = "" #device disconnected
		}
		
#		$pdmediatype = sgrep("i", "SSD", $thispd);
#		$pdmediatype = cut(": ",2,$pdmediatype);
#		$pdmediatype = trim($pdmediatype);
		$pdmediatype = keyval_get_val("SSD",$thispd);
		if($pdmediatype =~ /No/i) { $pdmediatype = "hd"; }
		if($pdmediatype =~ /Yes/i) { $pdmediatype = "ssd"; }
		$pdspundown = sgrep("i","Power State", $thispd);
		if($pdspundown eq "") { $pdspundown = "yes"; } #disconnected
		else{
			$pdspundown = cut(": ",2,$pdspundown);
			$pdspundown = trim($pdspundown);
			if($pdspundown =~ /Full rpm/i) { $pdspundown = "no"; }
			if($pdspundown =~ /Powered off/i) { $pdspundown = "yes"; }
			if($pdspundown =~ /Reduced rpm/i) { $pdspundown = "no"; }
		}
		#$pdtemp = ""; #needs to be added for sata drives in arcconf output
		
		#translate pdstate
		if ($pdstate =~ /ready/i) { $pdstate = "ready"; }
		elsif ($pdstate =~ /jbod/i) { $pdstate = "jbod"; }
		elsif ($pdstate =~ /raw/i) { $pdstate = "jbod"; }
		elsif ($pdstate =~ /online/i) { $pdstate = "optimal"; }
		elsif ($pdstate =~ /Failed/i) { $pdstate = "failed"; }		
		elsif ($pdstate =~ /Hot-Spare/i) { $pdstate = "hot spare"; }
		elsif ($pdstate =~ /Rebuilding/i) { $pdstate = "rebuild"; }
	
		my $pdoverheat = "";
		my %h = ();

		$h{'toolname'} = $toolname;
		$h{'toolver'} = $toolver;

		$h{'adpref'} = $adpref;

		$h{'adptype'} = $adptype;	# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
		#$h{'adpbiosver'} = $adpbiosver;
		#$h{'adpfwver'} = $adpfwver;
		$h{'pdrpm'} = $pdrotation;
		$h{'pdformfactor'} = $pdformfactor;
		$h{'pdpppid'} = $pdppid;
		$h{'pdenc'} = $pdenc;
		$h{'pdslot'} = $pdslot;
		$h{'pdsz'} = $pdsz;
		$h{'pdmodel'} = $pdmodel;
		$h{'pdfw'} = $pdfw;
		$h{'pdserial'} = $pdserial;
		$h{'pdsasaddr'} = $pdsasaddr;
		$h{'pdstate'} = $pdstate;
		$h{'pdguid'} = $pdguid;
		$h{'pdprotocol'} = $pdprotocol;
		$h{'pdosmapping'} = $pdosmapping;
		$h{'pdlinkspeed'} = $pdlinkspeed;
		$h{'pdspundown'} = $pdspundown;
		$h{'pdmediatype'} = $pdmediatype;
		$h{'pdtemp'} = $pdtemp;
		$h{'pdoverheat'} = $pdoverheat;
		$h{'pdstateraw'} = $pdstateraw;
		$h{'pdsmartstateraw'} = $pdsmrtstateraw;

		#as a reference for LDs	
		$h{'pdphysenc'} = $pdphysenc;
		$h{'pdphysslot'} = $pdphysslot; 

		#$h{'pddevspeed'} = $pddevspeed;

		if(!validate_pd(\%h)) {print "Invalid PD input on adpnum: $adpnum\n";}
		push @PD, \%h;	
	
	}

	#look at the logical drives
	$tmp = grabbetween("Logical device information","Physical Device information",$input_getconfig);
	@ld = split("Logical device number",$tmp);
	shift @ld;
	
	foreach(@ld){
#Raid 0,1,1E,10,5,6 all tested  on series 6/7 cards
#Multiple tests of raid 5/6 with different pdlists
#Raid 5EE, 50 and 60 still need to be tested
#Raid 6 is type "6 Reed-Solomon", set to trim type to just number
#Possible other adaptec-specific form of RAID6 on other cards
#All tested RAID drives had MAX possible space (should make no difference)
#
		my $pdenc_match = "";
		my $pdslot_match = "";
		my $tmp;
			
		$ldinprogress = "";
		
		$thisld = trim($_);	
		$ldname = sgrep("i","Logical device name",$thisld);
		$ldname = cut(": ",2,$ldname);
		$ldname = trim($ldname);
		$ldnum = grabline(1, $thisld);
		$ldnum = trim($ldnum);
		$ldtype = sgrep("i","RAID level",$thisld);
		$ldtype = cut(": ",2,$ldtype);
		if($ldtype =~ /1E/i) { $ldtype = "1E"; }
		else{ $ldtype =~ s/[^0-9]//g; }
		$ldtype = trim($ldtype);
		if($ldtype =~ m/[0-9]*?/) { $ldtype = "RAID" . $ldtype; }
		$ldsize = sgrep("i","Size",$thisld);
		$ldsize = cut(": ",2,$ldsize);
		$ldsize = trim($ldsize);
		$ldsize = streamline_size($ldsize, "mb");
		$ldpdlist = sgrep("i","Segment",$thisld);
		#still need to check for resyncing as possability or other unseen modes
		if($ldpdlist =~ /Rebuilding/i) { $ldinprogress = "rebuild"; }
		@a = split("\n",$ldpdlist);
		shift @a; #first line is only header
		$ldpdlist = "";
		foreach(@a){
			my $inf = cut(": ",2,$_);
			my @loc = split(",",$inf);
			shift @loc;
			$pdenc_match = shift @loc || ""; 
        		$pdslot_match = shift @loc || "";
			#print "pdencm:$pdenc_match pdsm:$pdslot_match\n";
        		if($pdenc_match eq "" || $pdslot_match eq "") { 
			if($ldpdlist ne "") { $ldpdlist .= ","; }
			$ldpdlist .= "m:m";
			next; 
			}
			$pdenc_match =~ s/[^0-9]//g;
        		$pdslot_match =~ /([0-9]+)/; #matches numbers
			$pdslot_match = $1; #first number, to avoid serial
			#print "1pdencm:$pdenc_match 1pdsm:$pdslot_match\n";
			foreach(@PD){
            			my $physenc = $_->{"pdphysenc"} || "";
            			my $physslot = $_->{"pdphysslot"} || "";
            			my $enc = $_->{"pdenc"};
            			my $slot = $_->{"pdslot"};
            			if($pdenc_match eq $physenc && $pdslot_match eq $physslot){
                			if($ldpdlist ne "") { $ldpdlist .= ","; } 
					$ldpdlist .= "$enc:$slot";
            			}

        		}
		}
		$ldmembers = $#a + 1;

		$ldstripesz = sgrep("i","Stripe-unit size",$thisld);
		$ldstripesz = cut(": ",2,$ldstripesz);
		$ldstripesz = trim($ldstripesz);
		$ldbootable = sgrep("i","Bootable",$thisld);
		$ldbootable = cut(": ",2,$ldbootable);
		if($ldbootable =~/Yes/i) { $ldbootable = "yes" };
		if($ldbootable =~/No/i) { $ldbootable = "no" };
		$ldstate = sgrep("i","Status of logical device",$thisld);
		$ldstate = cut(": ",2,$ldstate);
		$ldstate = trim($ldstate);
		my $ldhspresent = sgrep("i","Protected by Hot-Spare",$thisld);
		$ldhspresent = cut(": ",2,$ldhspresent);
		$ldhspresent = trim($ldhspresent);
		$ldhspresent = lc($ldhspresent);
		my $ldhs = sgrep ("i","Dedicated Hot-Spare",$thisld);
		$ldhs = cut(": ",2,$ldhs);
		$ldhs = trim($ldhs);
		$ldhs =~ s/,/:/;
		if($ldinprogress eq "rebuild"){
			$tmp = get_adaptec_cmd("getstatus","$adpnum");
			my @lds = split("Logical device Task:",$tmp);
			shift @lds;
			foreach(@lds){ 
				
                                my $ldnumber = sgrep("i","Logical device",$_);
                                $ldnumber = cut(": ",2,$ldnumber);
                                $ldnumber = trim($ldnumber);
				if ($ldnumber eq $ldnum){
					$ldinprogresspct = sgrep("i","Percentage complete",$_);
					$ldinprogresspct = cut(": ",2,$ldinprogresspct);
					$ldinprogresspct = trim($ldinprogresspct);
				}
			}
		}
		else { $ldinprogresspct = ""; }
		if($ldstate =~ /Optimal/i) { $ldstate = "optimal"; }
        	if($ldstate =~ /Impacted/i) { $ldstate = "impacted"; }
		if($ldstate =~ /Failed/i) { $ldstate = "failed"; }
		if($ldstate =~ /Degraded/i) { $ldstate = "degraded", }
		my %h =();
		
		$h{'toolname'} = $toolname;
		$h{'toolver'} = $toolver;
	
		$h{'adpref'} = $adpref;
	
		$h{'adptype'} = $adptype;			# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
		#$h{'adpbiosver'} = $adpbiosver;
		#$h{'adpfwver'} = $adpfwver;
	
		$h{'ldnum'} = $ldnum;
		$h{'ldtype'} = $ldtype;
		$h{'ldsize'} = $ldsize;
		#$h{'ldstripesz'} = $ldstripesz;
		$h{'ldmembers'} = $ldmembers;
		$h{'ldpdlist'} = $ldpdlist;
		$h{'ldstate'} = $ldstate;
		#$h{'ldname'} = $ldname;
		$h{'ldinprogress'} = $ldinprogress;
		$h{'ldinprogresspct'} = $ldinprogresspct;
		$h{'ldbootable'} = $ldbootable;
	
		$h{'ldhspresent'} = $ldhspresent;
		$h{'ldhs'} = $ldhs;
			
		if(!validate_ld(\%h)) {print "Invalid LD input on adpnum: $adpnum\n";}
		push @LD, \%h;

	}

}

#
# This walks over the output from a single megaraid adapter
#	
sub parse_megaraid {
	my $adpref = shift;		# Reference to adapter object
###	my $inp_ver = shift;
	my $adptype;
	my $adpnum;
	my $inp_ldpdinfo = shift;
	my $inp_pdlist = shift;

	my $datasrc; 
	my $datasrc_ver = "";
	
	my $CHECK_FOREIGN = 0; #set to 1 if foreign state detected on any drives

	my @a;
	my $thisld;
	my $thispd;
	my $tmp;
	my $tmp2;
	my $tmp_pri;
	my $tmp_sec;
	my @tmparr;
	my @pdarr;
	my $notfirst;

	my $pdenc;
	my $pdslot;
	my $pdsz;
	my $pdinquiry="";
	my $pdmodel;
	my $pdfw;
	my $pdserial;
	my $pdsasaddr;
	my $pdstate;
	my $pdspundown="";
	my $pdguid;
	my $pdprotocol;
	my $pdtemp;
	my $pdmediatype="";
	my $pddevspeed="";
	my $pdlinkspeed="";
	my $pdstateraw = "";
	my $pdforeign = ""; #drive may have foreign config from LD on another system

	my $ldnum;
	my $ldtype;
	my $ldsize;
	my $ldmembers;
	my $ldpdlist;
	my $ldstate;
	my $ldinprogress;
	my $ldinprogresspct="";
	my $ldspans = "";

	my $toolname;
	my $toolver;

	# Grab these from the Adapter object
	$toolname = $adpref->{"toolname"};
	$toolver = $adpref->{"toolver"};
	$adptype = $adpref->{"adptype"};
	$adptype = lc($adptype);
	$adpnum = $adpref->{"adpnum"};

	#
	# Walk over each physical drive
	#
	@a = split("Enclosure Device ID",$inp_pdlist);
	shift @a;
	foreach (@a) {
		$thispd = $_;

		$pdenc = grabline(1,$thispd);
		$pdenc = trim(cut(":",2,$pdenc));
		$pdslot = keyval_get_val("Slot Number",$thispd);
	
		$tmp = trim(keyval_get_val("Raw Size",$thispd));
		$pdsz = cut(" ",1,$tmp);	# 931.512
		$pdsz .= " ";
		$pdsz .= cut(" ",2,$tmp);	# GB
		$pdsz = streamline_size($pdsz);

		$pdinquiry = keyval_get_val("Inquiry Data", $thispd);
		$pdinquiry = condense_spaces($pdinquiry);
		#MCLI is bad sometimes, so we do this part
		if($pdinquiry =~ /Hotspare Information/) {
			$pdinquiry = grabbefore("Hotspare Info", $pdinquiry);
			$pdinquiry = trim($pdinquiry);
		}
		# Eventually try to fill out $pdserial, $pdmodel, $pdfw .. and manufacturer

		$tmp = keyval_get_val("Media Type", $thispd);
		$pdmediatype = "";
		if ($tmp =~ /Solid State/) { $pdmediatype = "ssd"; }
		if ($tmp =~ /Hard Disk/) { $pdmediatype = "hd"; }

		$pdsasaddr = keyval_get_val('SAS Address\(0\)', $thispd);
		$pdprotocol = keyval_get_val("PD Type", $thispd);

		#MCLI is bad and made a mistake so now we need to do this
		if($pdprotocol =~ /SASHotspare information/i) {
			$pdprotocol = "SAS";
		}
		
		# Device speed & Link speed (6G,3G, etc)
		$pddevspeed = keyval_get_val('Device Speed', $thispd);
		$pdlinkspeed = keyval_get_val('Link Speed', $thispd);
		if ($pddevspeed =~ /unknown/i) { $pddevspeed = ""; }
		if ($pddevspeed =~ /1.5Gb/) { $pddevspeed = "1.5G"; }
		if ($pddevspeed =~ /3.0Gb/) { $pddevspeed = "3G"; }
		if ($pddevspeed =~ /6.0Gb/) { $pddevspeed = "6G"; }
		if ($pddevspeed =~ /12.0Gb/) { $pddevspeed = "12G"; }
		if ($pdlinkspeed =~ /unknown/i) { $pdlinkspeed = ""; }
		if ($pdlinkspeed =~ /1.5Gb/) { $pdlinkspeed = "1.5G"; }
		if ($pdlinkspeed =~ /3.0Gb/) { $pdlinkspeed = "3G"; }
		if ($pdlinkspeed =~ /6.0Gb/) { $pdlinkspeed = "6G"; }
		if ($pdlinkspeed =~ /12.0Gb/) { $pdlinkspeed = "12G"; }

		$pdtemp = "";
		$tmp = lc(keyval_get_val("Drive Temperature",$thispd));
		$pdtemp = grabbefore("c",$tmp);
		$pdtemp .= "C";
		if($pdtemp eq "n/aC") { $pdtemp = ""; }
		$pdforeign = keyval_get_val("Foreign State",$thispd);	
		########################### mjs

		# These are the possible values:
		#
		# optimal 			OPT 	working drives that are in an array
		# ready 			RDY 	also Unconfigured(good)... available, but not included in array
		# missing			MIS
		# rebuild			RBLD
		# uncbad            BAD 	also Unconfigured(bad).... Typical of failed drives in MegaRAID
		# failed            FAIL	Failed drive in MegaRAID
		# offline 			OFFL	"offline" in MegaRAID; drives in the array, but user must explicitly add drive 
		# jbod				jbod	default for PERC H310 Falcon_MegaRAID and used on adaptec
		
		$pdspundown = "";
		$pdstate = lc(keyval_get_val("Firmware state", $thispd));
		
		$pdstateraw = $pdstate;	

		if ($pdstate =~ /spun down/) { $pdspundown = "yes"; }
		if ($pdstate =~ /spun up/) { $pdspundown = "no"; }

		if ($pdstate =~ /^online/) { $pdstate = "optimal"; }
		if ($pdstate =~ /^offline/) { $pdstate = "offline"; }

		if ($pdstate =~ /rebuild/) { $pdstate = "rebuild"; }
		
		if ($pdstate =~ /^unconfigured/) {
			$tmp = grabbetween('\(','\)',$pdstate);
			if ($tmp eq "bad") { $pdstate = "uncbad" }
			if ($tmp eq "good") { $pdstate = "ready" }
		}	
		
		if ($pdstate =~ /failed/) { $pdstate = "failed"; }
		if ($pdstate =~ /hotspare/i) { $pdstate = "hot spare"; }

		if($pdforeign =~ /foreign/i) { 
			$CHECK_FOREIGN = 1; 
		#	$pdstate = "foreign";
		}

		my $pdoverheat = "";

# This is now handled in process_pds()
#
#		if($pdtemp ne ""){
#			my $thresh = 60;
#			my $drivetemp = $pdtemp;
#			$drivetemp =~ s/(\D)+//g;
#			if($pdmediatype eq "HDD") { $thresh = 55; }
#
#			if($drivetemp > $thresh) { $pdoverheat = "yes" }
#			else { $pdoverheat = "no" }
#		}

#		printf "%3s:%-3s %s %s  %s (%s) [%s] $pdprotocol $pdsasaddr $pdguid\n",$pdenc,$pdslot,$pdstate,togb($pdsz) . "gb",$pdmodel,$pdfw,$pdserial;
		my %h = ();

		$h{'toolname'} = $toolname;			# Where we are getting the data from
		$h{'toolver'} = $toolver;

		$h{'adpref'} = $adpref;

		# TODO
		$h{'adptype'} = $adptype;			# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
#		$h{'adpbiosver'} = $adpbiosver;
#		$h{'adpfwver'} = $adpfwver;

		$h{'pdenc'} = $pdenc;
		$h{'pdslot'} = $pdslot;
		$h{'pdsz'} = $pdsz;
		$h{'pdinquiry'} = $pdinquiry;
#		$h{'pdmodel'} = $pdmodel;
#		$h{'pdfw'} = $pdfw;
#		$h{'pdserial'} = $pdserial;
		$h{'pdsasaddr'} = $pdsasaddr;
		$h{'pdstate'} = $pdstate;
		$h{'pdstateraw'} = $pdstateraw;
		$h{'pdspundown'} = $pdspundown;
#		$h{'pdguid'} = $pdguid;				# not present in megaraid
		$h{'pdprotocol'} = $pdprotocol;
		$h{'pdtemp'} = $pdtemp;
		$h{'pdoverheat'} = $pdoverheat;
		$h{'pdmediatype'} = $pdmediatype;
		$h{'pddevspeed'} = $pddevspeed;
		$h{'pdlinkspeed'} = $pdlinkspeed;
		$h{'pdosmapping'} = ""; #for info screen, there is no osmapping for megaraid drives
		$h{'pdforeign'} = $pdforeign;
		push @PD, \%h;


###############
############### support correctly
###############		inquiry data vs model/fw/serial
###############		uncbad state

	}
	
	#grab info on any foreign LD configs left on any PDs
	if($CHECK_FOREIGN){
		my $fdadpnum = "";
		my $fdtype = "";
		my $fdsize = "";
		my $fdstate = "";
		my $fdmembers = "";
		my $fdid = "";
		my $fdnum = "";	
		my $fdpdlist = "";

		my @foreigninfo;
		my $data = get_megaraid_cmd("All 2>&1", "-CfgForeign dsply");
		if($data =~ /Foreign configuration/) { @foreigninfo = split("Foreign configuration ",$data); }
		shift @foreigninfo;

		foreach (@foreigninfo){
			my $thisfd = $_;
			if($thisfd =~ /Foreign drives cannot be imported, as they do not form a complete configuration/) {
				#leave most fields empty-indicate its an incomplete foreign config
				$fdadpnum = grabline(1,$thisfd);
				my @fdnums = $fdadpnum =~ /([0-9]+)/g;
				$fdadpnum = $fdnums[1];
				$fdnum = $fdnums[0];
				$fdstate = "incomplete";	
			}
			else {		
				$fdadpnum = grabline(1,$thisfd);
				my @fdnums = $fdadpnum =~ /([0-9]+)/g;
				$fdadpnum = $fdnums[1];
				$fdnum = $fdnums[0];
				#$fdadpnum = grabbetween("on controller ",":",$thisfd);
				$fdtype = keyval_get_val("RAID Level",$thisfd);
				$fdtype = grabbetween("Primary-",",",$fdtype);
				$fdtype = "RAID". $fdtype;
				$fdsize = keyval_get_val("Size",$thisfd);
				$fdsize = grabbefore(" GB",$fdsize);
				$fdsize = streamline_size($fdsize . "gb");		
				$fdstate = keyval_get_val("State",$thisfd);
				$fdstate = grabline(1,$fdstate);
				$fdmembers = keyval_get_val("Number Of Drives",$thisfd);
				$fdid = sgrep("","Target Id",$thisfd);
				$fdid = grabafter("\Q(\E",$fdid);
				$fdid =~ s/\D//g;
				#$fdnum = $fdid;	
				$data = grabafter("Physical Disk Information:",$thisfd);
				my @fdpdinfo = split("\n\n\n",$data);
				$fdpdlist = "";
				foreach(@fdpdinfo){
					if($_ =~ /This Physical Disk is Missing/i) { $fdpdlist .= "MIS,"; next; }
					my $thisenc = keyval_get_val("Enclosure Device ID",$_);
					my $thisslot = keyval_get_val("Slot Number", $_); 
					$fdpdlist .= $thisenc . ":" . $thisslot . ",";
				}
				$fdpdlist =~ s/,$//;
			}

			my %h = ();
			
			$h{'adptype'} = "megaraid";
			$h{'adpnum'} = $fdadpnum;
			$h{'ldtype'} = $fdtype;
			$h{'ldnum'} = $fdnum;
			$h{'ldfdid'} = $fdid;
			$h{'ldstate'} = "foreign";
			$h{'ldfrgnstate'} = $fdstate;
			$h{'ldforeignstate'} = $fdstate;
			$h{'ldmembers'} = $fdmembers;
			$h{'ldsize'} = $fdsize;
			$h{'ldforeign'} = "foreign";
			$h{'ldpdlist'} = $fdpdlist; 	
			push @LD, \%h;

		}
	}
	

	#
	# Walk over each virtual drive
	#
	@a = split("Virtual Drive\:", $inp_ldpdinfo);	
	shift @a;

	foreach (@a) {
		$thisld = $_;	
	
		$tmp = trim(grabline(1,$thisld));
		$ldnum = cut(" ",1,$tmp); 

		$tmp = trim(keyval_get_val("RAID Level", $thisld));
		$tmp_pri = grabbetween("Primary-",",",$tmp);	
		$tmp_sec = grabbetween("Secondary-",",",$tmp);	
		$tmp2 = keyval_get_val("Span Depth", $thisld);
		# TODO:  This is deficient.  I'm not sure precisely how the card works here.
		# I do know that a span depth > 1 indicates RAID of RAID.
		$ldtype = "RAID" . $tmp_pri;
		if ($tmp2 > 1) { $ldtype .= "0"; }		# For RAID 10, 50

		$ldsize = sgrep("","Size",$thisld);
		$ldsize = sgrep("v","Coerced Size",$thisld);
		$ldsize = sgrep("v","Raw Size",$thisld);
		$ldsize = sgrep("v","Parity Size",$thisld);
		$ldsize = sgrep("v","Strip Size",$thisld);
		$ldsize = keyval_get_val("Size",$ldsize);
		$ldsize = streamline_size($ldsize);

		$ldmembers = lc(sgrep("i","number of drives",$thisld));
		$ldmembers = keyval_get_val("number of drives",$ldmembers);
		$ldspans = keyval_get_val("Number of Spans", $thisld);
		$ldmembers = $ldmembers * $ldspans;		


		$ldstate = lc(sgrep("i","state",$thisld));
		$ldstate = sgrep("v","firmware state", $ldstate);
		$ldstate = sgrep("v","foreign state", $ldstate);
		$ldstate = sgrep("v","media type", $ldstate);		# Can show up as "Solid State" ;-)
		$ldstate = keyval_get_val("state",$ldstate);

		# Look for PDs.  This is a bit complicated.  ldpdinfo will show us all drives 
		# that are associated with LDs, but for a comprehensive list of all drives, we
		# have to look at "pdlist"
		
		@pdarr = split("PD:",$thisld);
		shift(@pdarr);	# beginning is garbage;
		$ldpdlist = "";
		$ldinprogress = "";
		$notfirst = 0;
		# Walk each LD's associated PDs
		foreach (@pdarr) {
			$thispd = $_;
		
			# First, note the enclosure:slot identification of this PD	
			$pdenc = keyval_get_val("Enclosure Device ID",$thispd);	
			$pdslot = keyval_get_val("Slot Number",$thispd);
			if ($notfirst) { $ldpdlist .= ","; }
			$ldpdlist .= "$pdenc:$pdslot";

			# Now, see if any constituent drive is rebuilding or initializing
			$tmp = lc(keyval_get_val("Firmware state",$thispd));
				# Online, Spun Up
				# Rebuild
			if ($tmp =~ /rebuild/) {
				# I don't know all the states, so I'm doing this by inclusion rather than exclusion
				$ldinprogress = "synchronize";

				# At this point, we have to make another call to megacli to fetch information about
				# the particular physical drive that is rebuilding.
				$tmp2 = get_megaraid_cmd($adpnum, "pdrbld showprog physdrv [$pdenc:$pdslot]");			
				$tmp2 = lc(sgrep("i","rebuild progress",$tmp2));
				$tmp2 = trim(grabbetween("completed","in",$tmp2));
				$ldinprogresspct = $tmp2;
			}

			$notfirst = 1;
		}

		
		#
		# Create an LD data structure; add to list
		#
		my %h = ();

		$h{'toolname'} = $toolname;
		$h{'toolver'} = $toolver;

		$h{'adpref'} = $adpref;

		# TODO
		$h{'adptype'} = $adptype;			# Info regarding the adapter
		$h{'adpnum'} = $adpnum;
#		$h{'adpbiosver'} = $adpbiosver;
#		$h{'adpfwver'} = $adpfwver;

		$h{'ldnum'} = $ldnum;
		$h{'ldtype'} = uc($ldtype);
		$h{'ldsize'} = $ldsize;
		$h{'ldmembers'} = $ldmembers;
		$h{'ldpdlist'} = $ldpdlist;
		$h{'ldstate'} = $ldstate;
		$h{'ldinprogress'} = $ldinprogress;
		$h{'ldinprogresspct'} = $ldinprogresspct;


		push @LD, \%h;

	}



}

#
#this will check certain fields of adp
#to ensure they are of a set of valid values
#
sub validate_adp {
	
	my $adpref = shift;
	
	my $toolname 	   = $adpref->{"toolname"}   || "";
#	my $toolver    	   = $adpref->{"toolver "}   || "";
	my $adptype  	   = $adpref->{"adptype"}   || "";
#	my $adpname 	   = $adpref->{"adpname"}   || "";
#	my $adpfullname    = $adpref->{"adpfullname"}   || "";
	my $adpnum 	 	   = $adpref->{"adpnum"}   || "";
#	my $adpbios   	   = $adpref->{"adpbios"}   || "";
#	my $adpfw 	 	   = $adpref->{"adpfw"}   || "";
#	my $adpdriver      = $adpref->{"adpdriver"}   || "";
	my $bbu_present    = $adpref->{"bbu_present"}   || "";
	my $adp_can_use_bbu= $adpref->{"adp_can_use_bbu"}   || "";


	if($adp_can_use_bbu ne "yes" && $adp_can_use_bbu ne "no") {
		print "adp_can_use_bbu is $adp_can_use_bbu. Expected {yes, no}\n";
		return 0; 
	}
	
	if($bbu_present ne "yes" && $bbu_present ne "no") {
		print "bbu_present is $bbu_present. Expected {yes, no}\n";
		return 0; 
	}
	
	if($toolname ne "arcconf" && $toolname ne "sas2ircu" && $toolname ne "megacli") {
		print "toolname is $toolname. Expected {arcconf, sas2ircu, megacli}\n";
		return 0; 
	}

	if($adptype ne "adaptec" && $adptype ne "sas2008" && $adptype ne "megaraid") {
		print "adptype is $adptype. Expected {adaptec, sas2008, megaraid}\n";
		return 0; 
	}
	
	if(!is_numeric($adpnum)) {
		print "adpnum is $adpnum. Expected a numeric value\n";
		return 0; 
	}	
		
	return 1;
}

#
#this will check certain fields of pd
#to ensure they are of a set of valid values
#
sub validate_pd {

	my $pdref = shift;
	
#	my $adptype     = lc($pdref->{"adptype"})   || "";
#	my $adpnum      = $pdref->{"adpnum"};
	my $pdenc       = lc($pdref->{"pdenc"});
	my $pdslot      = $pdref->{"pdslot"};
	my $pdstate     = $pdref->{"pdstate"};
#	my $pdsz        = $pdref->{"pdsz"}      || 0;
#?	my $pdinquiry   = $pdref->{"pdinquiry"} || "";
	my $pdspundown  = $pdref->{"pdspundown"}|| "";
#	my $pdmodel     = $pdref->{"pdmodel"}   || "";
#	my $pdfw        = $pdref->{"pdfw"}      || "";
#	my $pdserial    = $pdref->{"pdserial"}  || "";
	my $pdprotocol  = $pdref->{"pdprotocol"}|| "";
	my $pdsasaddr   = $pdref->{"pdsasaddr"} || "";
	my $pdguid      = $pdref->{"pdguid"}    || "";
#	my $pdtemp      = $pdref->{"pdtemp"};
	my $pdmediatype = $pdref->{"pdmediatype"};
	my $pdlinkspeed = $pdref->{"pdlinkspeed"} || "";
		
	if(!is_numeric($pdenc)) { 
		print "pdenc is $pdenc. Expected a numeric value\n";
		return 0; 
	}
	if(!is_numeric($pdslot)) { 
		print "pdslot is $pdslot. Expected a numeric value\n";
		return 0;
	}
	if($pdstate ne "optimal" && $pdstate ne "ready" && $pdstate ne "rebuild" && 
	$pdstate ne "missing" && $pdstate ne "uncbad" && $pdstate ne "failed" && 
	$pdstate ne "offline" && $pdstate ne "jbod" && $pdstate ne "hot spare") { 
		print "pdstate is $pdstate. Expected {optimal, ready, rebuild, ";
		print "missing, uncbad, failed, offline, jbod}\n";
		return 0; 
	}
	if($pdspundown ne "yes" && $pdspundown ne "no") {
		print "pdsundown is $pdspundown. Expected {yes, no}\n";
		return 0; 
	}
	if($pdprotocol ne "SAS" && $pdprotocol ne "SATA" && $pdprotocol ne "") { 
		print "pdrotocol is $pdprotocol. Expected {SAS, SATA}\n";
		return 0; 
	}
	#if($pdsasaddr isnt number?) { return 0; }
	if($pdmediatype ne "hd" && $pdmediatype ne "ssd") {
		print "pdmediatype is $pdmediatype. Expected {hd, ssd}\n";
		return 0; 
	}
	if($pdlinkspeed ne "1.5G" && $pdlinkspeed ne "3G" && $pdlinkspeed ne "6G" 
	&& $pdlinkspeed ne "12G" && $pdlinkspeed ne "") { 
		print "pdlinkspeed is $pdlinkspeed. Expected {1.5G, 3G, 6G, 12G, }\n";
		return 0; 
	}
	if(!is_pdguid($pdguid) && $pdguid ne "") { 
		print "pdguid is $pdguid. Expected a hex value (possibly seperated by colons) ";
		print "or ''.\n";
		return 0; 
	}
		
	return 1;
}


sub validate_ld {

	my $ldref = shift;
	
	my $ldnum       = $ldref->{"ldnum"};
	my $ldtype      = $ldref->{"ldtype"};
	my $ldmembers   = $ldref->{"ldmembers"};
	my $ldbootable  = $ldref->{"ldbootable"};
	my $ldsize      = $ldref->{"ldsize"};
	my $ldstate     = $ldref->{"ldstate"};
	
	if(!is_numeric($ldnum)) { 
		print "ldnum is $ldnum. Expected a numeric value\n";
		return 0; 
	}
	if($ldtype ne "RAID0"  && $ldtype ne "RAID1" && $ldtype ne "RAID1E"
	&& $ldtype ne "RAID10" && $ldtype ne "RAID5" && $ldtype ne "RAID5EE"
	&& $ldtype ne "RAID50" && $ldtype ne "RAID6" && $ldtype ne "RAID60" ) { 
		print "ldtype is $ldtype. Expected {RAID 0,1,1E,10,5,5EE,50,6,60}\n";
		return 0; 
	}
	if(!is_numeric($ldmembers)) {
		print "ldmembers is $ldmembers. Expected a numeric value\n";
		return 0;
	}
	if($ldbootable ne "no" && $ldbootable ne "yes" && $ldbootable ne ""){
		print "ldbootable is $ldbootable. Expected {yes, no}\n";
		return 0;
	}
	if(!is_numeric($ldsize)) {
		print "ldsize is $ldsize. Expected numeric value\n";
		return 0;	
	}
	if(!is_numeric($ldnum)) {
		print "ldnum is $ldnum. Expected a numeric value\n";
		return 0;
	}
	if($ldstate ne "optimal" && $ldstate ne "degraded"&& $ldstate ne "failed"
	&& $ldstate ne "offline" && $ldstate ne "impacted") {
		print "ldstate is $ldstate. Expected {optimal, degraded, failed, offline, impacted}\n";
		return 0;
	}

	return 1;

}

sub is_numeric {
	my $string = shift;
	if($string =~ m/^(\d)+?$/) {return 1; }
	return 0;	
}

sub is_hex {
	my $string = shift;	
	if($string =~ m/^[0-9a-fA-F]+?$/) {return 1; }
	return 0;	
}

sub is_pdguid {
	my $string = shift;
	if($string =~ m/^[0-9a-fA-F:]+?$/) {return 1; }
	return 0;	
}


sub abbreviate_ldstate {
	my $inp = shift || "";
	my $rc;

	$inp = lc($inp);
	if ($inp eq "optimal") 		{ $rc = "ok"; }
	elsif ($inp eq "degraded") 	{ $rc = "degr"; }
	elsif ($inp eq "failed") 	{ $rc = "FAIL"; }
	elsif ($inp eq "offline") 	{ $rc = "OFFL"; }		# MegaRAID
	elsif ($inp eq "foreign")	{ $rc = "FRGN"; }
	elsif ($inp eq "impacted") 	{ $rc = "IMPC"; } #adaptec
	else {
		# some state we don't know about.  fail gracefully.
		$rc = $inp;
	}
	return $rc;
}

sub abbreviate_pdstate {
	my $inp = shift || "";
	my $rc;

	if ($inp eq "optimal") 		{ $rc = "ok"; }
	elsif ($inp eq "ready") 	{ $rc = "rdy"; }
	elsif ($inp eq "rebuild") 	{ $rc = "rbld"; }
	elsif ($inp eq "missing") 	{ $rc = "MIS"; }
	elsif ($inp eq "uncbad") 	{ $rc = "BAD"; }
	elsif ($inp eq "failed") 	{ $rc = "FAIL"; }
	elsif ($inp eq "offline")	{ $rc = "OFFL"; }
	elsif ($inp eq "hot spare")	{ $rc = "htsp"; }
	elsif ($inp eq "jbod")		{ $rc = "jbod"; }		# default for PERC H310 Mini Falcon+MegaRAID
	elsif ($inp eq "foreign")	{ $rc = "FRGN"; }
	elsif ($inp eq "degraded")	{ $rc = "degr"; }
	elsif ($inp eq "available") 	{ $rc = "AVL"; } #this is seen sometimes in sas2008 pds, we don't know what it means
	elsif ($inp eq "out of sync")	{ $rc = "OSY";	}
	else {
		# some state we don't know about.  fail gracefully, but add the question mark as an indicator to user
		$rc = $inp . "?";
	}
	return $rc;
}

sub info_abbreviate_inprogress {
	my $inp = shift || "";
	my $rc;

	if ($inp eq "resync") 		{$rc = "resync in progress"; }
	elsif($inp eq "recovery")	{$rc = "recovery in progress"; }
	elsif($inp eq "rebuild")	{$rc = "rebuild in progress"; }
	elsif ($inp eq "background init") {$rc = "init in progress"; }
	else {
		# some state we don't know about.  fail gracefully.
		$rc = $inp;
	}
	return $rc;
}

sub nagios_abbreviate_inprogress {
	my $inp = shift || "";
	my $rc;

	if ($inp eq "resync") 		{$rc = "resync"; }
	elsif ($inp eq "background init") {$rc = "bg_init"; }
	else {
		# some state we don't know about.  fail gracefully.
		$rc = $inp;
	}
	return $rc;
}

#
# Return exit code and message in a ASM-ish way
#
sub asm_exit {
	my $level = shift;
	my $msg = shift || "";

	if ($level == $OK) {
		printf "OK\n";
	} elsif ($level == $WARNING) {
		printf "Degraded: $msg\n";
	} elsif ($level == $CRITICAL) {
		printf "Error: $msg\n";
	} elsif ($level == $UNKNOWN) {
		# Internal fault
		printf "Unknown: $msg\n";
	}
	exit ($level);
}

#
# Return exit code and message in a Nagios-ish way
#
sub nagios_exit {
	my $level = shift;
	my $msg = shift || "";

	if ($level == $OK) {
		printf "NORMAL: $msg\n";
	} elsif ($level == $WARNING) {
		printf "WARNING: $msg\n";
	} elsif ($level == $CRITICAL) {
		printf "CRITICAL: $msg\n";
	} elsif ($level == $UNKNOWN) {
		# Internal fault
		printf "UNKNOWN: $msg\n";
	}
	exit ($level);
}

# For testing.
sub inject_bbu_error {
	my $adpref = shift;
	my $which = shift;

	if 	  ($which eq "temp") 		{ $adpref->{'bbu_temp_ok'} = "no"; }
	elsif ($which eq "volt")		{ $adpref->{'bbu_volt_ok'} = "no"; }
	elsif ($which eq "learncycle")	{ $adpref->{'bbu_learning_now'} = "yes"; }
	elsif ($which eq "missing")		{ $adpref->{'bbu_missing'} = "yes"; }
	elsif ($which eq "charging")	{ $adpref->{'bbu_charge_status'} = "charging"; }
	elsif ($which eq "discharging")	{ $adpref->{'bbu_charge_status'} = "discharging"; }
	elsif ($which eq "caplow")		{ $adpref->{'bbu_cap_low'} = "yes"; }
	elsif ($which eq "replreqd")	{ $adpref->{'bbu_repl_reqd'} = "yes"; }
	
}

# For testing.
sub inject_pd_error {
	my $pdref = shift;
	my $which = shift;

	if	($which eq "overheat") 		{ $pdref->{'pdoverheat'} = "yes"; }
	elsif 	($which eq "foreign")		{ $pdref->{'pdforeign'} = "foreign";}
	elsif 	($which eq "failed")		{ $pdref->{'pdstate'} = "failed"; }
	elsif 	($which eq "offline")		{ $pdref->{'pdstate'} = "offline"; }


}


# Can the adapter support a BBU?
sub can_use_bbu {
	my $adpref = shift;
	my $adp_can_use_bbu = $adpref->{'adp_can_use_bbu'};
	my $rc;

	if (! defined $adp_can_use_bbu) {
		print "Error:  Adapter should specify if it can use a battery.  This is a bug.\n";
		exit 1;
	}
	if ($adp_can_use_bbu eq "yes") {
		return 1;
	}

	# Assume no
	return 0;
}

# Does the adapter have a BBU connected?
sub has_bbu {
	my $adpref = shift;
	my $bbu_present = $adpref->{'bbu_present'};
	my $rc;

	if (! defined $bbu_present) {
		print "Error:  Adapter should specify if it has a battery.  This is a bug.\n";
		exit 1;
	}
	if ($bbu_present eq "yes") {
		return 1;
	}

	# Assume no
	return 0;
}

# (Assumes a BBU is present)
sub is_bbu_healthy {
	my $adpref = shift;
	my $healthy = 1;
	my $str = "";

	if (! has_bbu($adpref)) {
		print "Error: bad call to is_bbu_healthy() -- there is no bbu on this adapter.\n";
		exit 1;
	}

	my $bbu_temp_ok = $adpref->{'bbu_temp_ok'} || "";
	my $bbu_volt_ok = $adpref->{'bbu_volt_ok'} || "";
	my $bbu_missing = $adpref->{'bbu_missing'} || "";
	my $bbu_repl_reqd = $adpref->{'bbu_repl_reqd'} || "";
	my $bbu_cap_low = $adpref->{'bbu_cap_low'} || "";
	my $bbu_learning_now = $adpref->{'bbu_learning_now'} || "";
	my $bbu_charge_status = $adpref->{'bbu_charge_status'} || "";

	# Other conditions
	if ($bbu_learning_now eq "yes") { $str .= "learnCycleActive "; }
	if ($bbu_charge_status eq "none") {
		0;	# NOP
	} elsif ($bbu_charge_status eq "charging") {
		$str .= "charging ";	
	} elsif ($bbu_charge_status eq "discharging") {
		$str .= "discharging ";	
	} else {
		# Don't know this state...
		$str .= "?" . $bbu_charge_status . "?";
	}

	# Error conditions
	if ($bbu_temp_ok eq "no") { $healthy = 0; $str .= "!battTempHigh! "; }
	if ($bbu_volt_ok eq "no") { $healthy = 0; $str .= "!battVoltLow! "; }
	if ($bbu_missing eq "yes") { $healthy = 0; $str .= "!battMissing! "; }	
	if ($bbu_repl_reqd eq "yes") { $healthy = 0; $str .= "!battReplReqd! "; }	
	if ($bbu_cap_low eq "yes") { $healthy = 0; $str .= "!battCapacityLow! "; }	


	# Done; clean up and return	
	$str = trim($str);	
	return $healthy,$str;
}

sub write_bbuinfo {
	my $adpref = shift;
	my $rc = "";
	my $bbu_healthy_str;

	my ($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);

	# Dress up the response a bit
	if ($bbu_healthy_flag) {
		$bbu_healthy_str = "healthy";
	} else {
		$bbu_healthy_str = "!ALERT!";
	}

	if ($bbu_problem_str ne "") {
		$bbu_problem_str = $bbu_problem_str . " ";
	}

	# Grab other parameters for the BBU
	my $bbu_tech = $adpref->{'bbu_tech'};
	my $bbu_temp = $adpref->{'bbu_temp'};
	my $bbu_charge_mah = $adpref->{'bbu_charge_mah'};
	my $bbu_charge_max_mah = $adpref->{'bbu_charge_max_mah'};
	my $bbu_charge_design_mah = $adpref->{'bbu_charge_design_mah'};
	my $bbu_charge_status = $adpref->{'bbu_charge_status'};
	my $bbu_charge_relative = $adpref->{'bbu_charge_relative'};
	my $bbu_charge_absolute = $adpref->{'bbu_charge_absolute'};
	my $bbu_charge_cycles = $adpref->{'bbu_charge_cycles'};


	# Clean these up
	$bbu_charge_relative =~ s/ //g;

	if($bbu_tech eq "battery"){
		if ($MODE_CSV) {
			# Clean up some exclamation points, etc
			$bbu_healthy_str =~ s/!//g;
			$bbu_problem_str =~ s/!//g;
			$bbu_problem_str = trim($bbu_problem_str);
	
			$rc .= sprintf "%s,%s,%s,%s,%s,%s,%s,%s", 
						$bbu_tech,$bbu_healthy_str,$bbu_problem_str,$bbu_charge_relative,$bbu_charge_mah,$bbu_charge_max_mah,$bbu_charge_design_mah,
						$bbu_charge_cycles;
		} 
		elsif ($MODE_HRCSV) {
			# Clean up some exclamation points, etc
			$bbu_healthy_str =~ s/!//g;
			$bbu_problem_str =~ s/!//g;
			$bbu_problem_str = trim($bbu_problem_str);
	
			$rc .= sprintf "%8s,%10s,%11s,%8s,%8s,%11s,%11s,%11s", 
						$bbu_tech,$bbu_healthy_str,$bbu_problem_str,$bbu_charge_relative,$bbu_charge_mah,$bbu_charge_max_mah,$bbu_charge_design_mah,
						$bbu_charge_cycles;
		} else {
			$rc .= sprintf "%s, %s%s chg, %s / %s | %s, %s cycles", 
						$bbu_healthy_str,$bbu_problem_str,$bbu_charge_relative,$bbu_charge_mah,$bbu_charge_max_mah,$bbu_charge_design_mah,
						$bbu_charge_cycles;
		}
	}
	elsif($bbu_tech eq "supercap"){
                if ($MODE_CSV) {
                        # Clean up some exclamation points, etc
                        $bbu_healthy_str =~ s/!//g;
                        $bbu_problem_str =~ s/!//g;
                        $bbu_problem_str = trim($bbu_problem_str);
			#fix this output later
                        $rc .= sprintf "%s,%s,%s,%s,%s,%s,%s,%s",
                                                $bbu_tech,$bbu_healthy_str,$bbu_problem_str,$bbu_charge_relative,$bbu_charge_mah,$bbu_charge_max_mah,$bbu_charge_design_mah,
                                                $bbu_charge_cycles;
                } 
		elsif ($MODE_HRCSV) {
                        # Clean up some exclamation points, etc
                        $bbu_healthy_str =~ s/!//g;
                        $bbu_problem_str =~ s/!//g;
                        $bbu_problem_str = trim($bbu_problem_str);
			#fix this output later
                        $rc .= sprintf "%8s,%10s,%11s,%8s,%8s,%11s,%11s,%11s",
                                                $bbu_tech,$bbu_healthy_str,$bbu_problem_str,$bbu_charge_relative,$bbu_charge_mah,$bbu_charge_max_mah,$bbu_charge_design_mah,
                                                $bbu_charge_cycles;
                } else {
                        $rc .= sprintf "%s, %s %s",
                                                $bbu_healthy_str,$bbu_problem_str,$bbu_charge_absolute,;
                }

	}

	return $rc;
}

#generate all the data to be printed for
#ADPs when in hrcsv mode
sub get_hrcsv_str_adp {
	my $rc;
	my $adptype;
	my $adpfullname;
	my $adpnum;
	my $adpfw;
	my $adpdriver;
	
	my $bbu_present = 0;
	my $bbu_charge_cycles;
	my $bbu_charge_relative;
	my $bbu_charge_design_mah;
	my $bbu_charge_max_mah;
	my $bbu_charge_mah;
	my $bbu_tech;
	my $bbu_health_str;
	my $bbu_healthy_flag;
	my $bbu_problem_str;

	#define minimum length of each field
	my $adptype_ = 8;
	my $adpfullname_ = 11;
	my $adpnum_ = 6;
	my $adpfw_ = 5;
	my $adpdriver_ = 9;
	my $bbu_tech_ = 8;
	my $bbu_health_str_ = 10;
	my $bbu_problem_str_ = 9;
	my $bbu_charge_relative_ = 8;
	my $bbu_charge_mah_ = 8;
	my $bbu_charge_max_mah_ = 11;
	my $bbu_charge_design_mah_ = 11;
	my $bbu_charge_cycles_ = 11;
	
	#look at all ADP to see longest field lengths
	foreach my $adpref (@ADP) {
		$adptype 	= length($adpref->{'adptype'});
		$adpfullname	= length($adpref->{'adpfullname'});
		$adpnum 	= length($adpref->{'adpnum'});
		$adpfw 		= $adpref->{'adpfw'} || "";
		$adpdriver	= $adpref->{'adpdriver'} || "";
		$adpfw = length($adpfw);
		$adpdriver = length($adpdriver);
		
		if($adpnum > $adpnum_)   { $adpnum_ = $adpnum; }
		if($adpfullname > $adpfullname_) { $adpfullname_ = $adpfullname; }
		if($adpfw > $adpfw_) { $adpfw_ = $adpfw; }
		if($adpdriver > $adpdriver_) { $adpdriver_ = $adpdriver; }

		#see if bbu printing is needed
		if(can_use_bbu($adpref)){
			if(has_bbu($adpref)){
				#we grab bbu info field lengths if need be
				$bbu_present = 1;
				($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);
				if (!defined($bbu_problem_str) ) { $bbu_problem_str = ""; }
				$bbu_problem_str = length($bbu_problem_str);
	
				# Grab other parameters for the BBU
				$bbu_tech 		= length($adpref->{'bbu_tech'});
				$bbu_charge_mah 	= length($adpref->{'bbu_charge_mah'});
				$bbu_charge_max_mah 	= length($adpref->{'bbu_charge_max_mah'});
				$bbu_charge_design_mah 	= length($adpref->{'bbu_charge_design_mah'});
				$bbu_charge_relative 	= length($adpref->{'bbu_charge_relative'});
				$bbu_charge_cycles 	= length($adpref->{'bbu_charge_cycles'});
	
				if($bbu_tech > $bbu_tech_) { $bbu_tech_ = $bbu_tech; }
				if($bbu_charge_mah > $bbu_charge_mah_) { $bbu_charge_mah_ = $bbu_charge_mah; }
				if($bbu_charge_max_mah > $bbu_charge_max_mah_) { $bbu_charge_max_mah_ = $bbu_charge_max_mah; }
				if($bbu_charge_design_mah > $bbu_charge_design_mah_) { $bbu_charge_design_mah_ = $bbu_charge_design_mah; }
				if($bbu_charge_relative > $bbu_charge_relative_) { $bbu_charge_relative_ = $bbu_charge_relative; }
				if($bbu_charge_cycles > $bbu_charge_cycles_) { $bbu_charge_cycles_ = $bbu_charge_cycles; }
				if($bbu_problem_str > $bbu_problem_str_) { $bbu_problem_str_ = $bbu_problem_str; }

			}
		}
	}
 
	
	#print commented header to label fields
	$rc .= "#adp,";
	$rc .= " " x ($adptype_ - length("adptype")) . "adptype" . ",";
	$rc .= " " x ($adpnum_ - length("adpnum")) . "adpnum" . ",";
	$rc .= " " x ($adpfullname_ - length("adpfullname")) . "adpfullname" . ",";
	$rc .= " " x ($adpfw_ - length("adpfw")) . "adpfw" . ",";
	$rc .= " " x ($adpdriver_ - length("adpdriver")) . "adpdriver";
	if($bbu_present){
		$rc .= ",";
		$rc .= " " x ($bbu_tech_ - length("bbu_tech")) . "bbu_tech" . "," ;
		$rc .= " " x ($bbu_health_str_ - length("bbu_health")) . "bbu_health" . ",";
		$rc .= " " x ($bbu_problem_str_ - length("bbu_problem")) . "bbu_problem" . ",";
		$rc .= " " x ($bbu_charge_relative_ - length("chrge_rel")) . "chrg_rel" . ",";
		$rc .= " " x ($bbu_charge_mah_ - length("chrg_mah")) . "chrg_mah" . ",";
		$rc .= " " x ($bbu_charge_max_mah_ - length("chrg_maxmah")) . "chrg_maxmah" . ",";
		$rc .= " " x ($bbu_charge_design_mah_ - length("chrg_design")) . "chrg_design" . ",";
		$rc .= " " x ($bbu_charge_cycles_ - length("chrg_cycles")) . "chrg_cycles";
	}	

	$rc .= "\n";

	#now print info for each adp
	foreach my $adpref (@ADP) {
		$adptype		= $adpref->{'adptype'};
		$adpfullname		= $adpref->{'adpfullname'};
		$adpnum			= $adpref->{'adpnum'};
		$adpfw			= $adpref->{'adpfw'};
		$adpdriver		= $adpref->{'adpdriver'};

		if(has_bbu($adpref)) {
			($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);
			if (!defined($bbu_problem_str) ) { $bbu_problem_str = ""; }

			$bbu_tech = $adpref->{'bbu_tech'};
			$bbu_charge_mah = $adpref->{'bbu_charge_mah'};
			$bbu_charge_max_mah = $adpref->{'bbu_charge_max_mah'};
			$bbu_charge_design_mah = $adpref->{'bbu_charge_design_mah'};
			$bbu_charge_relative = $adpref->{'bbu_charge_relative'};
			$bbu_charge_cycles = $adpref->{'bbu_charge_cycles'};

			if ($bbu_healthy_flag) {
				$bbu_health_str = "healthy";
			} else {
				$bbu_health_str = "ALERT";
			}
		}

		$rc .= "adp ,";
		$rc .= " " x ($adptype_ - length($adptype)) . $adptype . ",";
		$rc .= " " x ($adpnum_ - length($adpnum)) . $adpnum . ",";
		$rc .= " " x ($adpfullname_ - length($adpfullname)) . $adpfullname . ",";
		if($adptype ne "onboard") { $rc .= " " x ($adpfw_ - length($adpfw)) . $adpfw . ","; }
		else { $rc .= " " x ($adpfw_) . ","; }
		if($adptype ne "onboard") { $rc .= " " x ($adpdriver_ - length($adpdriver)) . $adpdriver; } 
		else { $rc .= " " x ($adpdriver_); }
	
		if(has_bbu($adpref)){
			$rc .= ",";
			$rc .= " " x ($bbu_tech_ - length($bbu_tech)) . $bbu_tech . "," ;
			$rc .= " " x ($bbu_health_str_ - length($bbu_health_str)) . $bbu_health_str . ",";
			$rc .= " " x ($bbu_problem_str_ - length($bbu_problem_str)) . $bbu_problem_str . ",";
			$rc .= " " x ($bbu_charge_relative_ - length($bbu_charge_relative)) . $bbu_charge_relative . ",";
			$rc .= " " x ($bbu_charge_mah_ - length($bbu_charge_mah)) . $bbu_charge_mah . ",";
			$rc .= " " x ($bbu_charge_max_mah_ - length($bbu_charge_max_mah)) . $bbu_charge_max_mah . ",";
			$rc .= " " x ($bbu_charge_design_mah_ - length($bbu_charge_design_mah)) . $bbu_charge_design_mah . ",";
			$rc .= " " x ($bbu_charge_cycles_ - length($bbu_charge_cycles)) . $bbu_charge_cycles;
		}

	
		$rc.= "\n";
	}

	return $rc;
}


sub write_adpinfo {
	my $prefix = shift || "";
	my $rc = "";
	my $rc1;
	
	if ($MODE_HRCSV) {
	#	$rc .= sprintf "\n#adp,%7s,%6s,%30s,%12s,%12s,%8s,%8s,%9s,%8s,%8s,%11s,%11s,%11s\n",
	#	"adptype","adpnum","adpfullname","adpfw","adpdriver",
	#	"bbu_tech","bbu_health","bbu_problem","chrg_rel","chrg_mah","chrg_maxmah","chrg_design","chrg_cycles";
	
		$rc .= get_hrcsv_str_adp();
		return "$rc\n";
	} 


	# Other fields I may eventually want:
	# 	sas2ircu_ver
	# 	adpbios
	#	adpdriver 

	foreach my $adpref (@ADP) {
		my $adptype 	= lc($adpref->{'adptype'}) || "";
		my $adpname 	= $adpref->{'adpname'} || "";
		my $adpfullname	= $adpref->{'adpfullname'} || "";
		my $adpnum 		= $adpref->{'adpnum'};
		my $adpfw 		= $adpref->{'adpfw'} || "";
		my $adpdriver	= $adpref->{'adpdriver'} || "";
		my $bbu_string = "";
		#bbu items
		my $bbu_tech = $adpref->{'bbu_tech'} || "";
		my $bbu_temp = $adpref->{'bbu_temp'} || "";
		my $bbu_charge_mah = $adpref->{'bbu_charge_mah'} || "";
		my $bbu_charge_max_mah = $adpref->{'bbu_charge_max_mah'} || "";
		my $bbu_charge_design_mah = $adpref->{'bbu_charge_design_mah'} || "";
		my $bbu_charge_status = $adpref->{'bbu_charge_status'} || "";
		my $bbu_charge_relative = $adpref->{'bbu_charge_relative'} || "";
		my $bbu_charge_absolute = $adpref->{'bbu_charge_absolute'} || "";
		my $bbu_charge_cycles = $adpref->{'bbu_charge_cycles'} || "";

		my $adpdriverstr = "";
		if ($adpdriver) {
			$adpdriverstr = "  driver: " . $adpdriver;
		}


		if (can_use_bbu($adpref)) {
			if (has_bbu($adpref)) {
				$bbu_string = write_bbuinfo($adpref);
			} else {
				$bbu_string = "none";
			}
		}
	
		if ($MODE_CSV) {
			$rc .= sprintf "adp,$adptype,$adpnum,$adpfullname,$adpfw,$adpdriver";
			if (has_bbu($adpref)) {
				$rc .= "," . $bbu_string . "\n"; 
			}
			else { $rc .= "\n"; }

		} 

		else {
			#format fields for regular printing
			my $adapterfull = sprintf "%8s.%-2s", $adptype, $adpnum;
			my $FWDVstr   = sprintf "(fw:%s | driver:%s)", $adpfw, $adpdriver;
			if($adpfw eq "" && $adpdriver eq "") { $FWDVstr = ""; }
			if($bbu_string ne "" && $bbu_string ne "none") { $bbu_string = "\n\t" . $bbu_string; }
			$adptype = sprintf "%8s", $adptype;
			$adpnum  = sprintf "%2s", $adpnum;
			#TODO add more bbu fields for template			
			

			$rc1 = $INFO_ADP_TEMPLATE;
			$rc1 =~ s/%%%/90183x123THis_1S_a_peCen7_SsssymBoL323215/g; #escape %%%
			$rc1 =~ s/%AN/$adptype/g; #adp name (short)
			$rc1 =~ s/%An/$adpnum/g; #adp num
			$rc1 =~ s/%AF/$adapterfull/g; #adptype.adpnum
			$rc1 =~ s/%FN/$adpfullname/g; #adp full name
			$rc1 =~ s/%FWDV/$FWDVstr/g; #formatted firmware and driver
			$rc1 =~ s/%fw/$adpfw/g; #firmware
			$rc1 =~ s/%pf/$prefix/g;
			$rc1 =~ s/%dr/$adpdriver/g; #adp driver
			$rc1 =~ s/%Bty/$bbu_tech/g; #BBU type
			$rc1 =~ s/%Btc/$bbu_temp/g; #BBU temperature C
			$rc1 =~ s/%Bcm/$bbu_charge_mah/g; #BBU charge mAh
			$rc1 =~ s/%Bmm/$bbu_charge_max_mah/g; #BBU max mAh
			$rc1 =~ s/%Bdm/$bbu_charge_design_mah/g; #BBU charge deisgn mAh
			$rc1 =~ s/%Bcs/$bbu_charge_status/g; #BBU charge status
			$rc1 =~ s/%Bcr/$bbu_charge_relative/g; #BBU charge relative
			$rc1 =~ s/%Bca/$bbu_charge_absolute/g; #BBU charge absolute
			$rc1 =~ s/%Bcc/$bbu_charge_cycles/g; #BBU charge cycles
			$rc1 =~ s/%BBU_STRING/$bbu_string/g; #not for user, for default template
			$rc1 =~ s/90183x123THis_1S_a_peCen7_SsssymBoL323215/%/g; #escape %%%
			$rc1 =~ s/\\n/\n/g; #newlines
			$rc1 =~ s/\\t/\t/g; #tabs
			$rc1 .= "\n";
			$rc .= $rc1;
		}

	}

	return $rc;
}

#generate all the data to be printed for
#LDs when in hrcsv mode
sub get_hrcsv_str_ld {
	my $rc;
	my $adptype;
	my $adpnum;
	my $ldnum;
	my $ldhealth;
	my $ldmembers;
	my $ldsize;
	my $ldtype;
	my $ldinprog;
	my $ldpdlist;

	#define minimum length of each field
	my $adptype_ = 8;
	my $adpnum_ = 6;
	my $ldnum_ = 5;
	my $ldhealth_ = 8;
	my $ldmembers_ = 9;
	my $ldsize_ = 6;
	my $ldtype_ = 6;
	my $ldinprog_ = 8;
	my $ldpdlist_ = 8;
	
	#look at all LD to see longest field lengths
	foreach my $ldref (@LD) {
		$adptype 	= length($ldref->{'adptype'});
		$adpnum 	= length($ldref->{'adpnum'});
		$ldnum	 	= length($ldref->{'ldnum'});
		$ldmembers 	= length($ldref->{'ldmembers'});
		$ldsize	 	= $ldref->{'ldsize'};
		$ldtype	 	= length($ldref->{'ldtype'});
		$ldinprog 	= length($ldref->{'ldinprogress'});
		$ldpdlist	= length($ldref->{'ldpdlist'});
		$ldhealth 	= abbreviate_ldstate($ldref->{"ldstate"});
		$ldinprog 	= info_abbreviate_inprogress($ldref->{"ldinprogress"});
		my $inprogpct 	= $ldref->{"ldinprogresspct"} || "";
		if ($ldinprog ne "") { $ldinprog = "(" . $ldinprog . ": " . $inprogpct . ")"; };
		$ldsize = togb($ldsize) . "gb";
		$ldinprog = length($ldinprog);
		$ldsize = length($ldsize);
		$ldhealth = length($ldhealth);
		#if determined length was longer set that to be new length
		if($adptype > $adptype_) { $adptype_ = $adptype; }
		if($adpnum > $adpnum_)   { $adpnum_ = $adpnum; }
		if($ldnum > $ldnum_) { $ldnum_ = $ldnum; }
		if($ldmembers > $ldmembers_) { $ldmembers_ = $ldmembers; }
		if($ldsize > $ldsize_) { $ldsize_ = $ldsize; }
		if($ldtype > $ldtype_) { $ldtype_ = $ldtype; }
		if($ldinprog > $ldinprog_) { $ldinprog_ = $ldinprog; }
		if($ldpdlist > $ldpdlist_) { $ldpdlist_ = $ldpdlist; }
		if($ldhealth > $ldhealth_) { $ldhealth_ = $ldhealth; }
	}

		
	#print commented header to label fields
	$rc .= "#ld,";
	$rc .= " " x ($adptype_ - length("adptype")) . "adptype" . ",";
	$rc .= " " x ($adpnum_ - length("adpnum")) . "adpnum" . ",";
	$rc .= " " x ($ldnum_ - length("ldnum")) . "ldnum" . ",";
	$rc .= " " x ($ldhealth_ - length("ldhealth")) . "ldhealth" . ",";
	$rc .= " " x ($ldmembers_ - length("ldmembers")) . "ldmembers" . ",";
	$rc .= " " x ($ldsize_ - length("ldsize")) . "ldsize" . ",";
	$rc .= " " x ($ldtype_ - length("ldtype")) . "ldtype" . ",";
	$rc .= " " x ($ldinprog_ - length("ldinprog")) . "ldinprog" . ",";
	$rc .= " " x ($ldpdlist_ - length("ldpdlist")) . "ldpdlist";
	$rc .= "\n";
	
	foreach my $ldref (@LD){
		$adptype	 	= $ldref->{"adptype"};
		$adpnum		= $ldref->{"adpnum"};
		$ldnum			= $ldref->{"ldnum"};
		$ldsize 			= $ldref->{"ldsize"};
		$ldmembers 		= $ldref->{"ldmembers"};
		$ldtype		= $ldref ->{"ldtype"};
		$ldpdlist		= $ldref ->{"ldpdlist"};
		$ldhealth 		= abbreviate_ldstate($ldref->{"ldstate"});
		$ldinprog 		= info_abbreviate_inprogress($ldref->{"ldinprogress"});
		my $inprogpct 	= $ldref->{"ldinprogresspct"} || "";
		if ($ldinprog ne "") { $ldinprog = "(" . $ldinprog . ": " . $inprogpct . ")"; };
		$ldsize = togb($ldsize) . "gb";

		$rc .= "ld ,";
		$rc .= " " x ($adptype_ - length($adptype)) . $adptype . ",";
		$rc .= " " x ($adpnum_ - length($adpnum)) . $adpnum . ",";
		$rc .= " " x ($ldnum_ - length($ldnum)) . $ldnum . ",";
		$rc .= " " x ($ldhealth_ - length($ldhealth)) . $ldhealth . ",";
		$rc .= " " x ($ldmembers_ - length($ldmembers)) . $ldmembers . ",";
		$rc .= " " x ($ldsize_ - length($ldsize)) . $ldsize . ",";
		$rc .= " " x ($ldtype_ - length($ldtype)) . $ldtype . ",";
		$rc .= " " x ($ldinprog_ - length($ldinprog)) . $ldinprog . ",";
		$rc .= " " x ($ldpdlist_ - length($ldpdlist)) . $ldpdlist;
		
		$rc .= "\n";
	}

	return $rc;
}


sub write_ldinfo {
	my $prefix = shift || "";
	my $rc = "";
	my $rc1;
	my $print_adapters = 1;		# Forcing this; I think this mode is preferrable.

	if ($ADPCOUNT >= 2) {
		$print_adapters = 1;
	}

	if ($MODE_HRCSV) {
			#$rc .= sprintf "\n#ld,%7s,%6s,%5s,%8s,%9s,%8s,%6s,%8s,%7s\n", "adptype", "adpnum", "ldnum",
			#"ldhealth","ldmembers","ldsize","ldtype","ldinprog","ldpdlist";
			$rc .= get_hrcsv_str_ld();
			return "$rc\n";
	} 

	foreach my $ldref (@LD) {
		my $adptype 	= lc($ldref->{"adptype"});
		my $adpnum		= $ldref->{"adpnum"};
		my $num			= $ldref->{"ldnum"};
		my $st 			= $ldref->{"ldstate"};
		my $sz 			= $ldref->{"ldsize"};
		my $mem 		= $ldref->{"ldmembers"};
		my $type 		= $ldref ->{"ldtype"};
		my $pdlist		= $ldref ->{"ldpdlist"};
		my $foreign		= $ldref->{"ldforeign"} || "";
		my $health 		= abbreviate_ldstate($ldref->{"ldstate"});
		my $inprog 		= info_abbreviate_inprogress($ldref->{"ldinprogress"});
		my $inprogpct 	= $ldref->{"ldinprogresspct"} || "";
	
		my $sep = " ";
		if ($health ne "ok") { $sep = "!"; }

		if ($inprog ne "") { $inprog = "(" . $inprog . ": " . $inprogpct . ")"; };

		# Make size show up as gb.
		$sz = togb($sz) . "gb";


		if ($MODE_CSV) {
			$rc .= sprintf "ld,$adptype,$adpnum,ld$num,$health,$mem,$sz,$type,$inprog,$pdlist\n";
		} 
		else {
			#format fields for regular printing
			my $adapterfull = sprintf "%8s.%-2s", $adptype, $adpnum;
			$adptype = sprintf "%8s", $adptype;
			$adpnum  = sprintf "%2s", $adpnum;
			$num   = sprintf "%-2d", $num;
			$health = sprintf "%4s", $health;
			$mem = sprintf "%5s", $mem;
			$sz = sprintf "%8s", $sz;
			$type = sprintf "%7s", $type;
	
			$rc1 = $INFO_LD_TEMPLATE;
			$rc1 =~ s/%%%/90183x123THis_1S_a_peCen7_SsssymBoL323215/g; #escape %%%
			$rc1 =~ s/%AN/$adptype/g; #adp name (short)
			$rc1 =~ s/%An/$adpnum/g; #adp num
			$rc1 =~ s/%AF/$adapterfull/g; #adptype.adpnum
			$rc1 =~ s/%ld/$num/g; #ldnum
			$rc1 =~ s/%st/$st/g; #state
			$rc1 =~ s/%dc/$mem/g; #drive count
			$rc1 =~ s/%sz/$sz/g; #size
			$rc1 =~ s/%rt/$type/g; #raid type
			$rc1 =~ s/%wa/$sep/g; #! if raid not ok !
			$rc1 =~ s/%ip/$inprog/g; #in progress
			$rc1 =~ s/%dl/$pdlist/g; #drive list
			$rc1 =~ s/%fr/$foreign/g; #foreign status
			$rc1 =~ s/%hl/$health/g; #ld health
			$rc1 =~ s/%pf/$prefix/g;
			$rc1 =~ s/90183x123THis_1S_a_peCen7_SsssymBoL323215/%/g; #escape %%%
			$rc1 =~ s/\\n/\n/g; #newlines
			$rc1 =~ s/\\t/\t/g; #tabs
			$rc1 .= "\n";
			$rc .= $rc1;
		}

		# Code below is deprecated.  I originally wanted to avoid printing the adapter type/number if not
		# required, but I think it is useful.
#		if ($print_adapters) {
#			$rc .= sprintf "%s%s.%s   ld%-2d  %s%4s%s %5s drv %8s %-6s %s\n", 
#				$prefix, $adptype, $adpnum, $num, $sep, $health, $sep, $mem, $sz, $type, $inprog ;
#		} else {
#			# Don't print adapter and adapter number if we don't REALLY have to.
#			$rc .= sprintf "%s  ld%-2d  %s%4s%s %5s drv %8s %-6s %s\n", 
#				$prefix, $num, $sep, $health, $sep, $mem, $sz, $type, $inprog ;
#		}
	}

	return $rc;
}

#generate all the data to be printed for
#LDs when in hrcsv mode
sub get_hrcsv_str_pd {
	my $rc;
	my $adptype;
	my $adpnum;
	my $pdenc;
	my $pdslot;
	my $pdstate;
	my $pdtemp;
	my $pdsize;
	my $pdprotocol;
	my $pdlinkspeed;
	my $pdmediatype;
	my $pdspundown;
	my $pdosmapping;
	my $inqdata;

	#define minimum length of each field
	my $adptype_ = 8;
	my $adpnum_ = 6;
	my $pdenc_ = 4;
	my $pdslot_ = 4;
	my $pdstate_ = 7;
	my $pdtemp_ = 6;
	my $pdsize_ = 6;
	my $pdprotocol_ = 8; #protocol (SAS/SATA)
	my $pdlinkspeed_ = 9;
	my $pdmediatype_ = 9;
	my $pdspundown_ = 8;
	my $pdosmapping_ = 9;
	my $inqdata_ = 8;
	
	#look at all LD to see longest field lengths
	foreach my $pdref (@PD) {	
		$adptype 	= length($pdref->{'adptype'});
		$adpnum 	= length($pdref->{'adpnum'});
		$pdenc		= $pdref->{"pdenc"} || "";
		$pdenc = length($pdenc);
		$pdslot		= $pdref->{"pdslot"} || "";
		$pdslot = length($pdslot);
		$pdstate		= abbreviate_pdstate($pdref->{"pdstate"});
		$pdstate = length($pdstate);
		$pdsize		= $pdref->{"pdsz"} 		|| 0;
		my $pdinquiry 	= $pdref->{"pdinquiry"}	|| "";
		$pdspundown	= $pdref->{"pdspundown"}|| "";
		$pdspundown = length($pdspundown);
		my $pdmodel		= $pdref->{"pdmodel"} 	|| "";
		my $pdfw		= $pdref->{"pdfw"} 		|| "";
		my $pdserial	= $pdref->{"pdserial"} 	|| "";
		$pdprotocol 	= $pdref->{"pdprotocol"}|| "";
		$pdprotocol = length($pdprotocol);
		$pdtemp		= $pdref->{"pdtemp"} || "";
		$pdtemp = length($pdtemp);
		$pdmediatype = $pdref->{"pdmediatype"} || "";
		$pdmediatype = length($pdmediatype);
		$pdlinkspeed	= $pdref->{"pdlinkspeed"} || "";
		if($adptype ne "onboard" && $adptype ne "usb") { $pdosmapping = $pdref->{"pdosmapping"} || ""; }
		else { $pdosmapping = $pdref->{"pdname"} || ""; }
		$pdosmapping = length($pdosmapping);

		#if($pdlinkspeed eq "smartmontools_upgrade_required") { $pdlinkspeed = "";}
		$pdlinkspeed = length($pdlinkspeed);
		$pdsize = togb($pdsize) . "gb";
		$pdsize = length($pdsize);
		# We may have granular data about manuf / model / serial # / fw.  But if we don't, fall back to the
		# generic "inquiry data" that megacli gives us
		if ($pdinquiry ne "") {
			# We have it.  
			$inqdata = "[" . $pdinquiry . "]";
		} else {
			# No inq data --> we have granular data
			if($pdref->{"adptype"} eq "usb"){
				my $vendorshort = $pdref->{"pdvendor"};
				my $pdproduct = $pdref->{"pdproduct"};
				if($vendorshort ne "") { $vendorshort = grabbefore(" ",$vendorshort); } #may be empty
				if($pdproduct ne "" && $vendorshort ne ""){ #have both fields
					$inqdata = sprintf "%s - %s", $vendorshort, $pdproduct;
				}
				elsif($pdproduct eq "" || $vendorshort eq ""){ #if one or both is empty print without' -'
					$inqdata = sprintf "%s%s ", $vendorshort, $pdproduct;
				}
				if($pdserial ne "") { #add serial in brackets if it is non-empty
					$inqdata .= sprintf "[%s]",$pdserial;
				}
			}
			else{
				$inqdata = sprintf "%s (%s) [%s]",$pdmodel,$pdfw,$pdserial;
			}
		}
		$inqdata = length($inqdata);

		if($adptype > $adptype_) { $adptype_ = $adptype; }
		if($adpnum > $adpnum_) { $adpnum_ = $adpnum; }
		if($pdenc > $pdenc_) { $pdenc_ = $pdenc; }
		if($pdslot > $pdslot_) { $pdslot_ = $pdslot; }
		if($pdstate > $pdstate_) { $pdstate_ = $pdstate; }
		if($pdtemp > $pdtemp_) { $pdtemp_ = $pdtemp; }
		if($pdsize > $pdsize_) { $pdsize_ = $pdsize; }
		if($pdprotocol > $pdprotocol_) { $pdprotocol_ = $pdprotocol; }
		if($pdlinkspeed > $pdlinkspeed_) { $pdlinkspeed_ = $pdlinkspeed; }
		if($pdmediatype > $pdmediatype_) { $pdmediatype_ = $pdmediatype; }
		if($pdspundown > $pdspundown_) { $pdspundown_ = $pdspundown; }
		if($pdosmapping > $pdosmapping_) { $pdosmapping_ = $pdosmapping; }
		if($inqdata > $inqdata_) { $inqdata_ = $inqdata; }
	}

		
	#print commented header to label fields
	$rc .= "#pd,";
	$rc .= " " x ($adptype_ - length("adptype")) . "adptype" . ",";
	$rc .= " " x ($adpnum_ - length("adpnum")) . "adpnum" . ",";
	$rc .= " " x ($pdenc_ - length("enc")) . "enc" . ",";
	$rc .= " " x ($pdslot_ - length("slot")) . "slot" . ",";
	$rc .= " " x ($pdstate_ - length("pdstate")) . "pdstate" . ",";
	$rc .= " " x ($pdtemp_ - length("pdtemp")) . "pdtemp" . ",";
	$rc .= " " x ($pdsize_ - length("pdsize")) . "pdsize" . ",";
	$rc .= " " x ($pdprotocol_ - length("protocol")) . "protocol" . ",";
	$rc .= " " x ($pdlinkspeed_ - length("linkspeed")) . "linkspeed" . ",";
	$rc .= " " x ($pdmediatype_ - length("mediatype")) . "mediatype" . ",";
	$rc .= " " x ($pdspundown_ - length("spundown")) . "spundown" . ",";
	$rc .= " " x ($pdosmapping_ - length("OSmapping")) . "OSmapping" . ",";
	$rc .= " " x ($inqdata_ - length("inqdata")) . "inqdata";
	$rc .= "\n";

	foreach my $pdref (@PD) {
		$adptype 	= $pdref->{'adptype'};
		$adpnum 	= $pdref->{'adpnum'};
		$pdenc		= $pdref->{"pdenc"} || "";
		if(!defined($pdenc)) { $pdenc = "" };
		$pdslot		= $pdref->{"pdslot"};
		if(!defined($pdslot)) { $pdslot = "" };
		$pdstate		= abbreviate_pdstate($pdref->{"pdstate"});
		$pdsize		= $pdref->{"pdsz"} 		|| 0;
		my $pdinquiry 	= $pdref->{"pdinquiry"}	|| "";
		$pdspundown	= $pdref->{"pdspundown"}|| "";
		my $pdmodel		= $pdref->{"pdmodel"} 	|| "";
		my $pdfw		= $pdref->{"pdfw"} 		|| "";
		my $pdserial	= $pdref->{"pdserial"} 	|| "";
		$pdprotocol 	= $pdref->{"pdprotocol"}|| "";
		$pdtemp		= $pdref->{"pdtemp"} || "";
		$pdmediatype 	= $pdref->{"pdmediatype"} || "";
		$pdmediatype 	= uc($pdmediatype);
		$pdlinkspeed	= $pdref->{"pdlinkspeed"} || "";
		if($adptype ne "onboard" && $adptype ne "usb") { $pdosmapping = $pdref->{"pdosmapping"} || ""; }
		else { $pdosmapping = $pdref->{"pdname"}; }

		#if($pdlinkspeed eq "smartmontools_upgrade_required") { $pdlinkspeed = "";}
		$pdsize = togb($pdsize) . "gb";
		# We may have granular data about manuf / model / serial # / fw.  But if we don't, fall back to the
		# generic "inquiry data" that megacli gives us
		if ($pdinquiry ne "") {
			# We have it.  
			$inqdata = "[" . $pdinquiry . "]";
		} else {
			# No inq data --> we have granular data
			if($adptype eq "usb"){
				my $vendorshort = $pdref->{"pdvendor"};
				my $pdproduct = $pdref->{"pdproduct"};
				if($vendorshort ne "") { $vendorshort = grabbefore(" ",$vendorshort); } #may be empty
				if($pdproduct ne "" && $vendorshort ne ""){ #have both fields
					$inqdata = sprintf "%s - %s", $vendorshort, $pdproduct;
				}
				elsif($pdproduct eq "" || $vendorshort eq ""){ #if one or both is empty print without' -'
					$inqdata = sprintf "%s%s ", $vendorshort, $pdproduct;
				}
				if($pdserial ne "") { #add serial in brackets if it is non-empty
					$inqdata .= sprintf "[%s]",$pdserial;
				}
			}
			else{
				$inqdata = sprintf "%s (%s) [%s]",$pdmodel,$pdfw,$pdserial;
			}
		}
	
	
		#print commented header to label fields
		$rc .= "pd ,";
		$rc .= " " x ($adptype_ - length($adptype)) . $adptype . ",";
		$rc .= " " x ($adpnum_ - length($adpnum)) . $adpnum . ",";
		$rc .= " " x ($pdenc_ - length($pdenc)) . $pdenc . ",";
		$rc .= " " x ($pdslot_ - length($pdslot)) . $pdslot . ",";
		$rc .= " " x ($pdstate_ - length($pdstate)) . $pdstate . ",";
		$rc .= " " x ($pdtemp_ - length($pdtemp)) . $pdtemp . ",";
		$rc .= " " x ($pdsize_ - length($pdsize)) . $pdsize . ",";
		$rc .= " " x ($pdprotocol_ - length($pdprotocol)) . $pdprotocol . ",";
		$rc .= " " x ($pdlinkspeed_ - length($pdlinkspeed)) . $pdlinkspeed . ",";
		$rc .= " " x ($pdmediatype_ - length($pdmediatype)) . $pdmediatype . ",";
		$rc .= " " x ($pdspundown_ - length($pdspundown)) . $pdspundown . ",";
		$rc .= " " x ($pdosmapping_ - length($pdosmapping)) . $pdosmapping . ",";
		$rc .= " " x ($inqdata_ - length($inqdata)) . $inqdata;
		$rc .= "\n";
	
	}

	return $rc;
}

sub write_pdinfo {
	my $prefix = shift || "";
	my $rc = "";
	my $rc1;
#	my $print_adapters = 1;		# Forcing this; I think this mode is preferrable.

#	if ($ADPCOUNT >= 2) {
#		$print_adapters = 1;
#	}

	if ($MODE_HRCSV) {
		$rc .= get_hrcsv_str_pd();
		return "$rc\n";
	}

	foreach my $pdref (@PD) {

		my $adptype 	= lc($pdref->{"adptype"}) 	|| "";
		my $adpnum 		= $pdref->{"adpnum"};
		my $pdenc		= $pdref->{"pdenc"};
		my $pdslot		= $pdref->{"pdslot"};
		my $pdstate		= abbreviate_pdstate($pdref->{"pdstate"});
		my $pdsz		= $pdref->{"pdsz"} 		|| 0;
		my $pdinquiry 	= $pdref->{"pdinquiry"}	|| "";
		my $pdspundown	= $pdref->{"pdspundown"}|| "";
		my $pdmodel		= $pdref->{"pdmodel"} 	|| "";
		my $pdfw		= $pdref->{"pdfw"} 		|| "";
		my $pdserial	= $pdref->{"pdserial"} 	|| "";
		my $pdprotocol 	= $pdref->{"pdprotocol"}|| "";
		my $pdsasaddr  	= $pdref->{"pdsasaddr"} || "";
		my $pdguid		= $pdref->{"pdguid"} 	|| "";
		my $pdtemp		= $pdref->{"pdtemp"};
		my $pdmediatype = $pdref->{"pdmediatype"} || "";
		$pdmediatype = uc($pdmediatype);
		my $pdlinkspeed	= $pdref->{"pdlinkspeed"} || "";
		my $pdname = $pdref->{"pdname"} || "";		
		my $pdforeign = $pdref->{"pdforeign"} || "";
		my $pdosmapping = $pdref->{"pdosmapping"} || "";
		my $pdformfactor = $pdref->{"pdformfactor"} || "";
		my $pdrpm = $pdref->{"pdrpm"} || "";
		my $pdppid = $pdref->{'pdppid'} || "";

		#shorten link speed for info
		if($pdlinkspeed eq "smartmontools_upgrade_required") { $pdlinkspeed = "";}
		#add foreign onto state for info
		if($pdforeign eq "foreign") { $pdstate = "FRGN-" . $pdstate; }
		#define as empty if undefined
		if(!defined($pdenc))  { $pdenc  = ""; }
		if(!defined($pdslot)) { $pdslot = ""; }
		# Have a spundown display
		my $pdspundownstr = "";
		if ($pdspundown eq "yes") { $pdspundownstr = " asleep "; }
		elsif($pdspundown eq "no") { $pdspundownstr = ""; }

		# Clean up Enclosure
		my $pdencstr = "";
		if (defined $pdenc) {
			$pdencstr = $pdenc;
			if ($pdencstr eq "") { $pdencstr = ""; }
		}
		
		# Clean up Drive Temperature
		my $pdtempstr = "";
		if (defined $pdtemp) { 
			$pdtempstr = $pdtemp;
			if ($pdtempstr =~ /n\/a/i) { $pdtempstr = ""; }
		}

		my $pdszstr = togb($pdsz) . "gb";
		# We may have granular data about manuf / model / serial # / fw.  But if we don't, fall back to the
		# generic "inquiry data" that megacli gives us
		my $inqdata;
		if ($pdinquiry ne "") {
			# We have it.  
			$inqdata = "[" . $pdinquiry . "]";
		} else {
			# No inq data --> we have granular data
			if($adptype eq "usb"){
				my $vendorshort = $pdref->{"pdvendor"};
				my $pdproduct = $pdref->{"pdproduct"};
				if($vendorshort ne "") { $vendorshort = grabbefore(" ",$vendorshort); } #may be empty
				if($pdproduct ne "" && $vendorshort ne ""){ #have both fields
					$inqdata = sprintf "%s - %s", $vendorshort, $pdproduct;
				}
				elsif($pdproduct eq "" || $vendorshort eq ""){ #if one or both is empty print without' -'
					$inqdata = sprintf "%s%s ", $vendorshort, $pdproduct;
				}
				if($pdserial ne "") { #add serial in brackets if it is non-empty
					$inqdata .= sprintf "[%s]",$pdserial;
				}
			}
			else{
				$inqdata = sprintf "%s (%s) [%s]",$pdmodel,$pdfw,$pdserial;
			}
		}
	
		if ($MODE_CSV) {
			$rc .= sprintf "pd,$adptype,$adpnum,$pdencstr,$pdslot,$pdstate,$pdtempstr,$pdszstr,$pdprotocol," . 
							"$pdlinkspeed,$pdmediatype,$pdspundown,$pdosmapping,$inqdata\n";
		} 
		else { 
			#format fields for regular printing
			my $pdencslot = sprintf "%3s:%-3s", $pdenc, $pdslot; 
			if($pdenc eq "" && $pdslot eq "") { $pdencslot = sprintf "%7s", ""; }
			my $adapterfull = sprintf "%8s.%-2s", $adptype, $adpnum;
			$adptype = sprintf "%8s", $adptype;
			$adpnum  = sprintf "%2s", $adpnum;
			$pdenc   = sprintf "%3s", $pdenc;
			$pdslot  = sprintf "%3s", $pdslot;
			$pdstate = sprintf "%4s", $pdstate;
			$pdtempstr = sprintf "%4s", $pdtempstr;
			$pdszstr = sprintf "%6s", $pdszstr;
			$pdprotocol = sprintf "%4s", $pdprotocol;
			$pdlinkspeed = sprintf "%3s", $pdlinkspeed;
			$pdmediatype = sprintf "%3s", $pdmediatype;
			$pdosmapping = sprintf "%4s", $pdosmapping;
				
			#use template to print info fields	
			$rc1 = $INFO_PD_TEMPLATE;
			$rc1 =~ s/%%%/90183x123THis_1S_a_peCen7_SsssymBoL323215/g; #escape %%% - placeholder string
			$rc1 =~ s/%AN/$adptype/g; #adp name (short)
			$rc1 =~ s/%An/$adpnum/g; #adp number
			$rc1 =~ s/%AF/$adapterfull/g; #adpname.adpnum
			$rc1 =~ s/%en/$pdencstr/g; #enclosure
			$rc1 =~ s/%sl/$pdslot/g; #slot
			$rc1 =~ s/%ES/$pdencslot/g; #enc:slot or empty for drives without enc:slot
			$rc1 =~ s/%st/$pdstate/g; #state
			$rc1 =~ s/%sz/$pdszstr/g; #size
			$rc1 =~ s/%tc/$pdtempstr/g; #temperature celsius
			$rc1 =~ s/%pc/$pdprotocol/g; #protocol
			$rc1 =~ s/%ls/$pdlinkspeed/g; #link speed
			$rc1 =~ s/%mt/$pdmediatype/g; #media type
			$rc1 =~ s/%os/$pdosmapping/g; #OS mapping to sd device
			$rc1 =~ s/%sd/$pdspundownstr/g; #spundown
			$rc1 =~ s/%iq/$inqdata/g; #inquery data
			$rc1 =~ s/%pf/$prefix/g; #prefix
			$rc1 =~ s/%id/$pdppid/g; #PPID
			$rc1 =~ s/%ff/$pdformfactor/g; #phsysical size - 2.5/3.5''
			$rc1 =~ s/%rv/$pdrpm/g; #rpm
			$rc1 =~ s/90183x123THis_1S_a_peCen7_SsssymBoL323215/%/g; #escape %%% - placeholder back to %
			$rc1 =~ s/\\n/\n/g; #newlines
			$rc1 =~ s/\\t/\t/g; #tabs
			$rc1 .= "\n";
			$rc .= $rc1;
		
		}
	
		# Deprecated
#		if ($print_adapters) {
#			$rc .= sprintf "%s%s.%s %3s:%-3s  %4s  %3s  %6s %4s %-3s %3s  %s%s\n",
#				$prefix,$adptype,$adpnum,$pdencstr,$pdslot,$pdstate,$pdtempstr,togb($pdsz) . "gb",
#				$pdprotocol, $pdlinkspeed, $pdmediatype, $pdspundownstr, $inqdata;
#		} else {
#			# Don't print adapter and adapter number if we don't REALLY have to.
#			$rc .= sprintf       "%s%3s:%-3s  %4s  %3s  %6s %4s %-3s %3s  %s%s\n",
#				$prefix,$pdencstr,$pdslot,$pdstate,$pdtempstr, togb($pdsz) . "gb",
#				$pdprotocol, $pdlinkspeed, $pdmediatype, $pdspundownstr, $inqdata;
#		}
	}
	
	return $rc;
}

#check if any pd at all has an osmapping
sub pdosmappings_present {

	foreach(@PD) {
		my $pdosmapping = $_->{"pdosmapping"} || "";
		my $pdname = $_->{"pdname"} || "";
		#if either field is populated we have osmappings
		if($pdosmapping ne "" || $pdname ne "") { return 1; }
	}
	
	return 0;
}

sub cmd_asm_health {
	my $num; my $st;
	my $warn = 0;
	my $crit = 0;
	my $inprog;
	my $rc = 0;
	my $msg = "";
	my $adpnum;

	# Iterate over every adapter (looking at the BBU)
	foreach my $adpref (@ADP) {
		$adpnum = $adpref->{'adpnum'};
		if (! can_use_bbu($adpref)) { next; }
		if (! has_bbu($adpref)) { 
			$msg .= sprintf " adp%dbatt:%s ",$adpnum,"none";
			next; 
		}
		
		my ($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);

		if ($bbu_healthy_flag) {
			next;
		} else {
			$msg .= sprintf " adp%dbatt:%s ",$adpnum,"NOT_OK";
			$warn = 1;
			next;
		}
	}

	# Iterate over every logical drive
	foreach my $ldref (@LD) {
		$num = $ldref->{'ldnum'};
		$st  = $ldref->{'ldstate'};
		$inprog = nagios_abbreviate_inprogress($ldref->{"ldinprogress"});
		if ($inprog ne "") { $inprog = "(" . $inprog . ")"; };
		
		if ($st ne "optimal") { 
			# Not optimal?  This is _at least_ a warning.
			$warn = 1; 

			if ($st eq "failed") {
				$crit = 1;
			}
			if ($st eq "offline") {
				$crit = 1;
			}

	
		}

                if ($warn == 1 || $crit == 1) {
		       $msg .= sprintf " ld%d:%s%s ", $num, abbreviate_ldstate($st), $inprog;
                }

	}

	#iterate over every PD
	foreach my $pdref (@PD) {
		$st = $pdref->{"pdstate"};
		my $slot = $pdref->{"pdslot"};
		my $enc = $pdref->{"pdenc"};
		my $adptype = $pdref->{"adptype"};
		my $overheat = $pdref->{'pdoverheat'} || "";
		my $pdforeign = $pdref->{'pdforeign'} || "";

		my $name;

		#get pdname to identify pd, enc:slot or sdX
		if($adptype eq "onboard" || $adptype eq "usb") { $name = $pdref->{"pdname"}; }
		else { $name = "$enc:$slot"; }

		if($st ne "optimal" && 
			$st ne "ready" &&
			$st ne "hot spare" &&
			$st ne "hot" &&
			$st ne "jbod" 
		) {
			#if its not optimal or ready at least a warning
			$warn = 1;

			#rebuild	warning - probably fine, but not fine yet
			#uncbad 	warning - not part of an array, but a bad drive

			# Check for critical errors
			if($st eq "failed")  { $crit = 1; } #failed drive is critical
			if($st eq "missing") { $crit = 1; } #missing drive is critical
			if($st eq "offline") { $crit = 1; } #offline drive is critical

		}

		#check temperature after everything else
		if($overheat eq "yes") { $crit = 1; $st .= "-overheated"; } #overheated drive is critical
		if($pdforeign eq "foreign") { $warn = 1; $st .= "-foreign"; } #foreign drive is a warning		

                if ($warn == 1 || $crit == 1) {
			$msg .= sprintf " pd-%s:%s" , $name, $st;
		}

	}

#	printf "warn:$warn  crit:$crit\n";
	if ($crit) { $rc = 2; }
		elsif ($warn) { $rc = 1; }

#	printf "rc:$rc  msg:$msg\n\n";
	asm_exit($rc, $msg);

}

sub cmd_nagios_health {
	my $num; my $st;
	my $warn = 0;
	my $crit = 0;
	my $inprog;
	my $rc = 0;
	my $msg = "";
	my $adpnum;

	# Iterate over every adapter (looking at the BBU)
	foreach my $adpref (@ADP) {
		$adpnum = $adpref->{'adpnum'};
		if (! can_use_bbu($adpref)) { next; }
		if (! has_bbu($adpref)) { 
			$msg .= sprintf " adp%dbatt:%s ",$adpnum,"none";
			next; 
		}
		
		my ($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);

		if ($bbu_healthy_flag) {
			$msg .= sprintf " adp%dbatt:%s ",$adpnum,"ok";
			next;
		} else {
			$msg .= sprintf " adp%dbatt:%s ",$adpnum,"NOT_OK";
			$warn = 1;
			next;
		}
	}

	# Iterate over every logical drive
	foreach my $ldref (@LD) {
		$num = $ldref->{'ldnum'};
		$st  = $ldref->{'ldstate'};
		$inprog = nagios_abbreviate_inprogress($ldref->{"ldinprogress"});
		if ($inprog ne "") { $inprog = "(" . $inprog . ")"; };
		
		if ($st ne "optimal") { 
			# Not optimal?  This is _at least_ a warning.
			$warn = 1; 

			if ($st eq "failed") {
				$crit = 1;
			}
			if ($st eq "offline") {
				$crit = 1;
			}

	
		}
		$msg .= sprintf " ld%d:%s%s ", $num, abbreviate_ldstate($st), $inprog;

	}

	#iterate over every PD
	foreach my $pdref (@PD) {
		$st = $pdref->{"pdstate"};
		my $slot = $pdref->{"pdslot"};
		my $enc = $pdref->{"pdenc"};
		my $adptype = $pdref->{"adptype"};
		my $overheat = $pdref->{'pdoverheat'} || "";
		my $pdforeign = $pdref->{'pdforeign'} || "";

		my $name;

		#get pdname to identify pd, enc:slot or sdX
		if($adptype eq "onboard" || $adptype eq "usb") { $name = $pdref->{"pdname"}; }
		else { $name = "$enc:$slot"; }

		if($st ne "optimal" && 
			$st ne "ready" &&
			$st ne "hot spare" &&
			$st ne "jbod" 
		) {
			#if its not optimal or ready at least a warning
			$warn = 1;

			#rebuild	warning - probably fine, but not fine yet
			#uncbad 	warning - not part of an array, but a bad drive

			# Check for critical errors
			if($st eq "failed")  { $crit = 1; } #failed drive is critical
			if($st eq "missing") { $crit = 1; } #missing drive is critical
			if($st eq "offline") { $crit = 1; } #offline drive is critical

		}

		#check temperature after everything else
		if($overheat eq "yes") { $crit = 1; $st .= "-overheated"; } #overheated drive is critical
		if($pdforeign eq "foreign") { $warn = 1; $st .= "-foreign"; } #foreign drive is a warning		
		$msg .= sprintf " pd-%s:%s" , $name, $st;

	}

#	printf "warn:$warn  crit:$crit\n";
	if ($crit) { $rc = 2; }
		elsif ($warn) { $rc = 1; }

#	printf "rc:$rc  msg:$msg\n\n";
	nagios_exit($rc, $msg);

}

# Give a summary of current state, in Dell PE-C SNMP trap terms, of all logical drives  
# as well as all physical drives and bbu/supercaps.
# This will produce output like:
#
# LSI2008,adp0,ld0,ldDegraded
# LSI2008,adp0,ld1,ldOptimal
#
sub cmd_pecagent_health {
	my $num; my $st;
	my $adptype; my $adpnum;
	my $inprog;
	my $state;
	my $pdsinlds = "";

	# Start by reporting battery health for every adapter
	foreach my $adpref (@ADP) {
		if (! can_use_bbu($adpref)) { next; }
		if (! has_bbu($adpref)) {	
			$state = "battNotConnected";
			goto done;
		}
		my ($bbu_healthy_flag,$bbu_problem_str) = is_bbu_healthy($adpref);
		if ($bbu_healthy_flag) {
			$state = "battOk";
		} else {
			$state = "battNeedsAttention";
		}

		done:
		$adptype = $adpref->{'adptype'};
		$adpnum = $adpref->{'adpnum'};
		print "$adptype,adp$adpnum,self,$state\n";
	}

	foreach my $ldref (@LD) {
		$num = $ldref->{'ldnum'};
		$st  = $ldref->{'ldstate'};
		$inprog = $ldref->{"ldinprogress"};
		$adptype = $ldref->{"adptype"};
		$adpnum = $ldref->{"adpnum"};
		my $pdlist = $ldref->{"ldpdlist"};

		$state = "ldUnknown";
##			# debug
##			printf "\nld num:    $num\n";
##			printf "ld state:  $st\n";
##			printf "ld inprog: $inprog\n\n";
##			#
		if ($st eq "optimal") {
			if ($inprog eq "") {
				$state = "ldOptimal";
			} else {
				if ($inprog eq "background init") { $state = "ldOptimalBGinit"; }
				elsif ($inprog eq "resync") { $state = "ldOptimalBGresync"; }
				# Any other optimal state, but unknown "in-progress" results as ldUnknown.
			}
		}

		if ($st eq "degraded") {
			if ($inprog eq "") {
				$state = "ldDegraded";
			} else {
				if ($inprog eq "resync") { $state = "ldDegradedRbld"; }
				if ($inprog eq "synchronize") { $state = "ldDegradedRbld"; }

				# Any other degraded state, but unknown "in-progress" results as ldUnknown.
			}

		}

		if ($st eq "failed") {
			$state = "ldFailed";
		}
		if ($st eq "offline") {
			$state = "ldOffline";
		}
		if ($st eq "foreign") {
			$state = "ldForeign";
		}
		$pdsinlds .= "$pdlist ";

		printf "$adptype,adp$adpnum,ld$num,$state\n";	

	}

	foreach my $pdref (@PD) {
		my $st = $pdref->{"pdstate"};
		my $adptype = $pdref->{"adptype"};
		my $adpnum = $pdref->{"adpnum"};
		my $slot = $pdref->{"pdslot"};
		my $enc = $pdref->{"pdenc"};
		my $overheat = $pdref->{'pdoverheat'} || "";
		my $pdforeign = $pdref->{'pdforeign'} || "";

		my $name;
		#get pdname to identify pd, enc:slot or sdX
		if($adptype eq "onboard" || $adptype eq "usb") { $name = $pdref->{"pdname"}; }
		else { $name = "$enc:$slot"; }
			
		# Exclude drives that are constituent components of LDs.  In hindsight this seems like a bad idea.
		# It's likely to confuse the user (as it did me).
		#if($pdsinlds =~ /$name/) { next; }

		# Drive health is probed via smartctl too, but if a failure is detected
		# pdstate will be overwritten with "failed", etc.
		
		$state = "pdUnknown";
		
		if ($pdforeign eq "foreign") { $state = "pdForeign"; }

		if    ($st eq "optimal") 	{ $state = "pdOptimal"; }
		elsif ($st eq "ready") 		{ $state = "pdOptimal"; }	# ok, but not included in an LD
		elsif ($st eq "rebuild") 	{ $state = "pdOptimal"; }
		elsif ($st eq "missing") 	{ $state = "pdOffline"; }
		elsif ($st eq "uncbad") 	{ $state = "pdFailed"; }
		elsif ($st eq "failed") 	{ $state = "pdFailed"; }
		elsif ($st eq "offline")	{ $state = "pdOffline"; }
		elsif ($st eq "hot spare")	{ $state = "pdOptimal"; }
		elsif ($st eq "jbod")		{ $state = "pdOptimal"; }
		#elsif ($st eq "foreign")	{ $state = "pdForeign"; } #possibly belongs to a foreign LD
		if ($overheat eq "yes")		{ $state = "pdOverheated"; }

		printf "$adptype,adp$adpnum,pd-$name,$state\n";
	}
}

#
# Detect all adapters on the system, and populate @ADP.
#
sub detect_storage_adapters {

	my $seek_sas2ircu = 0;
	my $seek_megaraid = 0;
	my $seek_adaptec = 0;
	my $seek_lswr = 0;
	my $toolname;
	my $toolver;

	my $adptype = "";
	my $adpname = "";
	my $adpfullname = "";
	my $adpnum = "";
	my $adpbios = "";
	my $adpfw = "";
	my $adpdriver = "";
	my $bbu_present = "";
	my $adp_can_use_bbu = "";

	my @a;
	my @adparr;
	my $tmp;
	my $data_st;
	my $data_dsp;
	my $data_megacliver;
	my $data_adpallinfo;
	my $thisadp;
	my $notfirst;
	my $rc;

	



	#
	# Start by figuring out what we should look for.  Method varies by OS.
	#
	if ($HOSTOS eq "win") {
		#$tmp = `wmic scsicontroller`;
		$tmp = shell_command("wmic scsicontroller");
		if ($tmp =~ /megasas/) { $seek_megaraid = 1; }			# PERC5
		if ($tmp =~ /LSI_SAS2/) { $seek_sas2ircu = 1; }			# PERC H200
		#### TODO: improve this detection.
		#### TODO: provide generic override mechanisms via env var.
	}

	if ($HOSTOS eq "linux") {
		#detect LSI cards
		#$tmp = `lspci`;
		$tmp = shell_command("lspci");
		$tmp = sgrep("i","lsi",$tmp);	
		$tmp = sgrep("i","logic",$tmp);	
		$tmp = lc($tmp);

		if ($tmp =~ /falcon/) { $seek_sas2ircu = 1; }			# all boards using SAS2008 chipset (there are many)
		if ($tmp =~ /liberator/) { $seek_megaraid = 1; }		# LSI9260, LSI9280
		if ($tmp =~ /thunderbolt/) { $seek_megaraid = 1; }		# LSI9265
        	if ($tmp =~ /megaraid/i) { $seek_megaraid = 1; }        # Generic MegaRAID of some other flavor
        	if ($tmp =~ /megasas/i) { $seek_megaraid = 1; }         # Generic MegaRAID of some other flavor
		if ($tmp =~ /sas2308/) { $seek_sas2ircu = 1; }			# SAS2308 chipset
		if ($tmp =~ /fusion-mpt/) { $seek_sas2ircu = 1; }		# all Fusion-MPT
		if ($tmp =~ /sas3108/) { $seek_sas2ircu = 1; }		# LSI IR/IT SAS3 - really uses sas3ircu.  INCOMPLETE


		#detect adaptec cards
		#$tmp = `lspci`;
		$tmp = shell_command("lspci");
		$tmp = sgrep("i","adaptec",$tmp);
		$tmp = lc($tmp);
		if ($tmp =~ /adaptec/) { $seek_adaptec = 1; }	#Generic Adaptec search
	
		$seek_lswr =1; #we always display onboard adapter
	}
	
	if($seek_lswr){ #this simply adds adapter "onboard" to @ADP
		my @partitions;
		
		my %h = ();
		
		$h{'adpname'} = "onboard";
		#$h{'toolname'} = "";
		$h{'adpfullname'} = "onboard";
		$h{'adptype'} = "onboard";
		$h{'adpnum'} = "0";
		#$h{'adpfw'} = "";
		$h{'adp_can_use_bbu'} = "no";
		$h{'bbu_present'} = "no";	
		push @ADP, \%h;
		
		
	}
	


	if($seek_adaptec){
		my $data;
		my $data1;
		my $data2;	
		my $fullname;
		my $channel;
		my $adp_total;
		my $i;
		my $adptemp;
	
		my $bbu_status;
		my $bbu_tech;
		my $bbu_temp;
		my $bbu_temp_thresh;
		my $bbu_temp_ok;
		my $bbu_charge_mah;
		my $bbu_charge_max_mah;


		($ADAPTEC_CMD,$rc) = test_tool_exists("arcconf", $ADAPTEC_CMD, "");
		if ($rc) { require_tool_exists("arcconf", $ADAPTEC_CMD, ""); }
		$data1 = get_adaptec_cmd(""," 2>&1");	
		#check if we can use arcconf
		if($data1 =~ /no such file or directory/i) {
			print "arcconf not found, cannot get information on adaptec devices.\n";
		}
		#if we can use it start getting adaptec ADP info
		else {
			$data2 = get_adaptec_cmd("getconfig","9999");
			$adp_total = sgrep("i","Controllers found",$data2);
			$adp_total = cut(": ",2,$adp_total);
			$adp_total = trim($adp_total);
			$toolname = "arcconf";
			$toolver = sgrep("i", "UCLI",$data1);
			$toolver = sgrep("i","Version",$toolver);
			$toolver = cut("Version ",2,$toolver);
			$toolver = trim($toolver);
			$adptype = "adaptec"; 
	
			for($i = 1; $i <= $adp_total; $i++){		
	
				$data = get_adaptec_cmd("getconfig","$i","ad");
		
				$adpname = sgrep("i", "Controller Model",$data);
				$fullname = cut(": ",2, $adpname);
				$fullname = trim($fullname);
				$adpname = cut("Adaptec ",2, $adpname);
				$adpname = trim($adpname);
				$channel = sgrep("i","Channel description",$data);
				$channel = cut(": ",2,$channel);
				$channel = trim($channel);
				$adpfullname = "$fullname ($channel)";
				$adpnum = $i;
				#gets serial number if ever neeeded
				#$adpserial = sgrep("i", "Controller Serial Number", $data);
				#$adpserial = cut(": ",2,$adpserial);
				#$adpserial = trim($adpserial;
				$adpbios = sgrep("i","BIOS", $data);
				$adpbios = cut(": ", 2, $adpbios);
				$adpbios = trim($adpbios);	
				$adpfw = sgrep("i","Firmware", $data);
				$adpfw = cut(": ",2, $adpfw);
				$adpfw = trim($adpfw);
				$adpdriver = sgrep("i","Driver",$data);
				$adpdriver = cut(": ",2, $adpdriver);
				$adpdriver = trim($adpdriver);
				$adptemp = sgrep("i","Temperature",$data);
				$adptemp = cut(": ",2,$adptemp);
				$adptemp = grabbefore("C",$adptemp); #only show degrees C
				$adptemp = trim($adptemp);
				$adptemp .= "C";
				$adp_can_use_bbu = "yes"; #assumed yes for all adaptec cards	
		        	$bbu_present = keyval_get_val("Overall Backup Unit Status",$data);
###		        	$bbu_present = sgrep("i","Overall Backup Unit Status",$data);
###			        $bbu_present = cut(": ",2,$bbu_present);
###    				$bbu_present = trim($bbu_present);
       	   			if ($bbu_present eq "Not Ready") { $bbu_present = "no"; } #it may actually be here and broken?
       	 			if ($bbu_present eq "Ready") { $bbu_present = "yes"; }
       	 			if ($bbu_present eq "Preparing") { $bbu_present = "yes"; }
				if( $bbu_present eq "" ) { #firmware 5.xx adapters
 			       		$bbu_present = sgrep("i","ZMM",$data);
       	     				$bbu_present = cut(": ",2,$bbu_present);
       	    				$bbu_present = trim($bbu_present);
       		    	 		if ($bbu_present eq "ZMM not installed") { $bbu_present = "no"; }
				}
				
				#get bbu info
				if($bbu_present eq "yes"){
					$data = grabafter("Controller Cache Backup Unit Information",$data);
					$bbu_status = keyval_get_val("Supercap Status",$data);
#					if ($bbu_status =~ /Fatal/i) { print "It's dead, Jim."; }
					if (($bbu_status =~ /Preparing/i) || 
					   ($bbu_status =~ /ready/i) )	{ 
						if($data =~ /supercap information/i) { $bbu_tech = "supercap"; }
						$bbu_temp = keyval_get_val("Current Temperature",$data);
						$bbu_temp = grabbefore(" ",$bbu_temp);
						$bbu_temp_thresh = keyval_get_val("Threshold Temperature",$data);
						$bbu_temp_thresh = grabbefore(" ",$bbu_temp_thresh);
						if($bbu_temp > $bbu_temp_thresh) { $bbu_temp_ok = "no"; }
						else { $bbu_temp_ok = "yes"; }
	
						$bbu_charge_mah = keyval_get_val("Current Draw",$data);
						$bbu_charge_max_mah = grabafter("/ ",$bbu_charge_mah);
						$bbu_charge_mah = grabbefore("/",$bbu_charge_mah);
						#more fields to get?	
					}
	
				}
				
				my %h = ();
				
				$h{'toolname'} = $toolname;
				$h{'toolver'} = $toolver;
				$h{'adptype'} = $adptype;
				$h{'adpname'} = $adpname;
				$h{'adpfullname'} = $adpfullname;
				$h{'adpnum'} = $adpnum;
				$h{'adpbios'} = $adpbios;
				$h{'adpfw'} = $adpfw;
				$h{'adpdriver'} = $adpdriver;
				$h{'bbu_present'} = $bbu_present;
				$h{'adptemp'} = $adptemp;
				$h{'adp_can_use_bbu'} = $adp_can_use_bbu;
				if ($bbu_present =~ /yes/) {
					$h{'bbu_tech'} = $bbu_tech;
					$h{'bbu_temp'} = $bbu_temp;
					#$h{'bbu_charge_max_err'} = $bbu_charge_max_err;
					$h{'bbu_charge_design_mah'} = "";
					# We don't really need mV.
		#			$h{'bbu_design_volt'} = $bbu_design_volt;
					#$h{'bbu_manuf_date'} = $bbu_manuf_date;
					#$h{'bbu_autolearn'} = $bbu_autolearn;
					#$h{'bbu_learn_time'} = $bbu_learn_time;
					#$h{'bbu_learn_next_time'} = $bbu_learn_next_time;
					#$h{'bbu_learning_now'} = $bbu_learning_now;
					$h{'bbu_temp_ok'} = $bbu_temp_ok;
					#$h{'bbu_temp_ok_raw'} = $bbu_temp_ok_raw;
					#$h{'bbu_volt_ok'} = $bbu_volt_ok;
					#$h{'bbu_volt_ok_raw'} = $bbu_volt_ok_raw;
					$h{'bbu_charge_status'} = "";
					#$h{'bbu_missing'} = $bbu_missing;
					#$h{'bbu_repl_reqd'} = $bbu_repl_reqd;
					#$h{'bbu_cap_low'} = $bbu_cap_low;
					$h{'bbu_charge_relative'} = "";
					$h{'bbu_charge_absolute'} = "";
					$h{'bbu_charge_mah'} = $bbu_charge_mah;
					$h{'bbu_charge_max_mah'} = $bbu_charge_max_mah;
					$h{'bbu_charge_cycles'} = "";
				}
				if(!validate_adp(\%h)){ print "Invalid ADP fields on adapter $i.\n"; }	
				push @ADP, \%h;
			}
		}

	}
		

	#
	# SAS2IRCU adapter/LD/PD   (for SAS2008 chipset, possibly others)
	#
	if ($seek_sas2ircu) {	
		if($SAS2FLASH_FORKING) { $sas2flashfn = get_temp_filename(); }#get a filename for checking later
		if ($HOSTOS eq "linux") { 
			$adpdriver = `cat /sys/module/mpt2sas/version 2>&1`;
			my $errorcode = $?;
			#print "ERR $errorcode \n";
			#if file now found
			if($errorcode == 256) { $adpdriver = " "; }
		}
		$adpdriver = trim($adpdriver);
		# TODO:  Not sure how to get this in windows.  It looked promising to pull from Win32_PnPSignedDriver,
		# But it does not have megasas (and probably mpt or whatever LSI2008 windows driver is)

		($SAS2IRCU_CMD,$rc) = test_tool_exists("sas2ircu",$SAS2IRCU_CMD,"SAS2IRCU");
		if ($rc) { require_tool_exists("sas2ircu",$SAS2IRCU_CMD,"SAS2IRCU"); }
		my $data_list = get_sas2ircu_cmd("","list"); 

		# Get a list of SAS2008 Adapter Numbers that we've found.  (example for 3 adpaters:  "0,1,2")
		$SAS2IRCU_ADPLIST = parse_sas2ircu_adplist($data_list);
###		printf "DBG >> SAS2IRCU adapters detected: $SAS2IRCU_ADPLIST\n";

		@adparr = split(",",$SAS2IRCU_ADPLIST);


		foreach (@adparr) {
			$thisadp = $_;
			# Get the data for this adapter
			$data_st =  get_sas2ircu_cmd($thisadp,"status"); 
			$data_dsp = get_sas2ircu_cmd($thisadp,"display"); 
			# TODO:  could cache this here, to avoid further reads

			# Get version of sas2ircu tool
			$toolname = "sas2ircu";
			$toolver = grablines(1,3,$data_dsp);
			$toolver = trim(sgrep("","Version",$toolver));
			$toolver = cut(" ",2,$toolver);
		
#			@a = split(/IR Volume/,$input_st);
#			shift @a;

			##### TODO: ERROR: MUSTFIX:  
			##### MUST TEST WITH MULTIPLE CONTROLLERS.
			$adptype = "sas2008";
			$adpname = keyval_get_val("Controller type", $data_dsp);

			$adpnum = sgrep("","Read configuration has been initiated for controller", $data_dsp);
			$adpnum = trim(cut("controller",2,$adpnum));
			if ($adpnum ne $thisadp) {
				printf "**** ERROR/Inconsistency:  thisadp: $thisadp   adpnum: $adpnum\n";
			}
			
			#sas2flash required to get aspfullname
			if($SAS2FLASH_FORKING) {
				my $pid = fork;
				if($pid == 0) { 
					#in child process
					$adpfullname = shell_command("$SAS2FLASH_CMD -list -c $adpnum 2>&1");
					if( (!defined($adpfullname)) || $adpfullname eq "no such file") { $SAS2FLASH_CMD = "./sas2flash"; }
					$adpfullname = shell_command("$SAS2FLASH_CMD -list -c $adpnum 2>&1");
					if( (!defined($adpfullname)) || $adpfullname eq "no such file") { $adpfullname = "LSI SAS2008 chipset adp"; }
					else{ $adpfullname = keyval_get_val("Board Name",$adpfullname); }
					if($adpfullname eq "" || (!defined($adpfullname)) ) { $adpfullname = "LSI SAS2008 chipset adapter"; }
					system("echo 'adp$adpnum: $adpfullname\n' >> $sas2flashfn");
					exit;
				}
				else { 
					#in parent process
					push @sas2flashcpids, $pid; 
				}
				$GETFULLNAME = 1; #we will check for fullnames later
			}

			else {
				$adpfullname = shell_command("$SAS2FLASH_CMD -list -c $adpnum 2>&1");
				if( (!defined($adpfullname)) || $adpfullname eq "no such file") { $SAS2FLASH_CMD = "./sas2flash"; }
				$adpfullname = shell_command("$SAS2FLASH_CMD -list -c $adpnum 2>&1");
				if( (!defined($adpfullname)) || $adpfullname eq "no such file") { $adpfullname = "LSI SAS2008 chipset adp"; }
				else{ $adpfullname = keyval_get_val("Board Name",$adpfullname); }
				if($adpfullname eq "" || (!defined($adpfullname)) ) { $adpfullname = "LSI SAS2008 chipset adapter"; }
			}

			$adpbios = keyval_get_val("BIOS version",$data_dsp);
			$adpfw 	= keyval_get_val("Firmware version",$data_dsp);

			# SAS2008 adapters do not have a battery
			$adp_can_use_bbu = "no";
			$bbu_present = "no";

			my %h = ();

			$h{'toolname'} = $toolname;
			$h{'toolver'} = $toolver;
			$h{'adptype'} = $adptype;
			$h{'adpname'} = $adpname;
			$h{'adpfullname'} = $adpfullname;
			$h{'adpnum'} = $adpnum;
			$h{'adpbios'} = $adpbios;
			$h{'adpfw'} = $adpfw;
			$h{'adpdriver'} = $adpdriver;
			$h{'bbu_present'} = $bbu_present;
			$h{'adp_can_use_bbu'} = $adp_can_use_bbu;

			push @ADP, \%h;
		}

	}

	my $data_bbuprop;
	my $data_bbudesign;
	my $data_bbucapinfo;
	my $data_bbustatus;

	my $data_bbu;

	my $bbu_temp = "";
	my $bbu_charge_design_mah = "";
	my $bbu_design_volt = "";
	my $bbu_manuf_date = "";
	my $bbu_autolearn = "";
	my $bbu_learn_time = "";
	my $bbu_learn_next_time = "";
	my $bbu_charge_max_err = "";
	# I don't know exactly what the possible values are for these.  So I'm including the raw for later debug.
	my $bbu_learning_now = "";			# yes | no	
	my $bbu_temp_ok = "";			# yes | no
	my $bbu_temp_ok_raw = "";
	my $bbu_volt_ok = "";			# yes | no
	my $bbu_volt_ok_raw = "";
	my $bbu_charge_status = "";	# none | charging | discharging	
	my $bbu_missing = "";			# yes | no
	my $bbu_repl_reqd = "";			# yes | no
	my $bbu_cap_low = "";			# yes | no
	my $bbu_charge_relative = "";
	my $bbu_charge_absolute = "";
	my $bbu_charge_mah = "";
	my $bbu_charge_max_mah = "";
	my $bbu_charge_cycles = "";
	my $bbu_tech = "";

	if ($seek_megaraid) {

		$adpdriver = "";
		if ($HOSTOS eq "linux") { $adpdriver = `cat /sys/module/megaraid_sas/version`; }
		$adpdriver = trim($adpdriver);

		($MEGACLI_CMD,$rc) = test_tool_exists("MegaCli",$MEGACLI_CMD,"MEGACLI,MCLI");
		if ($rc) { require_tool_exists("MegaCli",$MEGACLI_CMD,"MEGACLI,MCLI"); }

		$data_megacliver = get_megaraid_cmd("all", "-v");			
		$data_adpallinfo = get_megaraid_cmd("all", "adpallinfo");			
		# TODO:  could cache this here, to avoid further reads

		$toolname = "megacli";
		$toolver = sgrep("i","megacli",$data_megacliver);
		$toolver = trim(cut("Ver",2,$toolver));
		$toolver = cut(" ",1,$toolver);

		@adparr = split(/Adapter #/, $data_adpallinfo);			
		shift @adparr;
	
		$notfirst = 0;	
		foreach (@adparr) {
			$thisadp = $_;

			$adptype = "megaraid";
			$adpnum = trim(grabline(1,$thisadp));	
			$adpfullname = keyval_get_val("Product Name", $thisadp);
			$adpname = $adpfullname;
			
			# Abbreviate if known.
			if ($adpname =~ /9240/) { $adpname = "LSI9240"; }
			if ($adpname =~ /9260/) { $adpname = "LSI9260"; }
			if ($adpname =~ /9261/) { $adpname = "LSI9261"; }
			if ($adpname =~ /9265/) { $adpname = "LSI9265"; }
			if ($adpname =~ /9280/) { $adpname = "LSI9280"; }
			if ($adpname =~ /9285/) { $adpname = "LSI9285"; }
			if ($adpname =~ /PERC 5\/i/) { $adpname = "PERC5i"; }
			if ($adpname =~ /PERC 5\/e/) { $adpname = "PERC5e"; }

			$adpfw = keyval_get_val("FW Package Build",$thisadp);

			$adp_can_use_bbu = "yes";

			$tmp = grabbefore("Settings",$thisadp);
			$tmp = keyval_get_val("BBU",$tmp);
			$tmp = lc($tmp);
			if (($tmp eq "present") || ($tmp eq "yes")) {
				$bbu_present = "yes";
			} else {
				$bbu_present = "no";
			}

			if ($bbu_present =~/yes/) {
				# Need more data.
				$data_bbu = get_megaraid_cmd($adpnum,"adpbbucmd");

				# From bbustatus
				$bbu_temp = keyval_get_val("Temperature",$data_bbu);
				$bbu_temp = grabline(1,$bbu_temp);

				#regular bbu or SuperCaP
				$bbu_tech = keyval_get_val("BatteryType",$data_bbu);
				if($bbu_tech =~ /supercap/i) { $bbu_tech = "supercap"; }
				elsif($bbu_tech =~ /bbu/i) { $bbu_tech = "battery"; }
				else { $bbu_tech = "battery"; } #we assume battery unless supercap is stated			
				# From bbucapinfo
				$tmp = grabbefore("BBU Design Info for Adapter",$data_bbu);
				$tmp = grabafter("BBU Capacity Info for Adapter",$tmp);
				$data_bbucapinfo = $tmp;

				
				$tmp = keyval_get_val("Max Error",$data_bbu); #was using $bbu_capinfo, this was bad
				$bbu_charge_max_err = grabline(1,$tmp); #!!BAD ON SUPERCAP!!
				if($bbu_tech eq "supercap") { $bbu_charge_max_err = ""; }
				
				$tmp = keyval_get_val("Remaining Capacity",$data_bbucapinfo);
				$bbu_charge_mah = grabline(1,$tmp); #!! BAD ON SUPERCAP
				if($bbu_tech eq "supercap") { $bbu_charge_mah = ""; }
	
				$bbu_charge_max_mah = keyval_get_val("Full Charge Capacity",$data_bbucapinfo);
				$bbu_charge_cycles = keyval_get_val("Cycle Count",$data_bbucapinfo);
				if($bbu_tech eq "supercap") {
					$bbu_charge_max_mah = "";
					$bbu_charge_cycles = "";
				}				

				# From bbudesign	

				$bbu_charge_design_mah = keyval_get_val("Design Capacity",$data_bbu);	
#				$bbu_design_volt = keyval_get_val("Design Voltage",$data_bbu);	
				$bbu_manuf_date = keyval_get_val("Date of Manufacture",$data_bbu);	
			
				# From bbuprop	
				$bbu_autolearn = keyval_get_val("Auto-Learn Mode",$data_bbu);	
				$bbu_learn_time = lc(keyval_get_val("Auto Learn Period",$data_bbu));	
				$bbu_learn_next_time = lc(keyval_get_val("Next Learn time",$data_bbu));	

				# Carve off BBU FW status
				$data_bbustatus = grabafter("BBU Firmware Status",$data_bbu);	
				if($data_bbustatus =~ /Battery state/i) {
					$data_bbustatus = grabbefore("Battery state",$data_bbustatus); #not in SuperCap output
				}
				elsif($data_bbustatus =~ /GasGuageStatus/i){ #that's right gUAge, not gAUge
					$data_bbustatus = grabbefore("GasGuageStatus",$data_bbustatus); #for SuperCap output
				}
				elsif($data_bbustatus =~ /BBU GasGauge Status/i) {
					$data_bbustatus = grabbefore("BBU GasGauge Status",$data_bbustatus); #some batteries
				}
				#	
				# Approach is cautious here because I don't really know all of the possible values.	A
				# Comments show the ones I do know
				#	

				$tmp = keyval_get_val("Learn Cycle Active",$data_bbustatus); 	# yes | no
				$bbu_learning_now = lc($tmp);
				
				$tmp = keyval_get_val("Temperature",$data_bbustatus);  		# OK | High
				$bbu_temp_ok_raw = lc($tmp);
				$bbu_temp_ok = "no";
				if ($bbu_temp_ok_raw =~ /ok/) { $bbu_temp_ok = "yes"; }
				if ($bbu_temp_ok_raw =~ /high/) { $bbu_temp_ok = "no"; }

				$tmp = keyval_get_val("Voltage",$data_bbustatus);			# OK | Low
				$bbu_volt_ok_raw = lc($tmp);
				$bbu_volt_ok = "no";
				if ($bbu_volt_ok_raw =~ /ok/) { $bbu_volt_ok = "yes"; }
				if ($bbu_volt_ok_raw =~ /low/) { $bbu_volt_ok = "no"; }

				$tmp = keyval_get_val("Charging Status",$data_bbustatus);	# None | Discharging | Charging
				$bbu_charge_status = lc($tmp);

				$tmp = keyval_get_val("Battery Pack Missing",$data_bbustatus);
				$bbu_missing = lc($tmp);

				$tmp = keyval_get_val("Battery Replacement required",$data_bbustatus);
				$bbu_repl_reqd = lc($tmp);

				$tmp = keyval_get_val("Remaining Capacity Low",$data_bbustatus);
				$bbu_cap_low = lc($tmp);

				$tmp = keyval_get_val("Absolute State of charge",$data_bbu);
				$bbu_charge_absolute = grabline(1,$tmp); #!! BAD ON SUPERCAP
				if($bbu_tech eq "supercap") { 
					$bbu_charge_absolute = keyval_get_val("Fully Charged", $data_bbu);
					if($bbu_charge_absolute =~ /yes/i) { $bbu_charge_absolute = "fully charged"; }
					else { $bbu_charge_absolute = "not charged"; }
					#possibly get more information on charging status from other fields 
				}
				
				$tmp = keyval_get_val("Relative State of Charge",$data_bbu);
				$bbu_charge_relative = grabline(1,$tmp); #!! BAD ON SUPERCAP
				if($bbu_tech eq "supercap") { $bbu_charge_relative = ""; }
				

			}


			# Done collecting data about this adapter.  Push it into our array.

			my %h = ();

			$h{'toolname'} = $toolname;
			$h{'toolver'} = $toolver;
			$h{'adptype'} = $adptype;
			$h{'adpname'} = $adpname;
			$h{'adpfullname'} = $adpfullname;
			$h{'adpnum'} = $adpnum;
#			$h{'adpbios'} = $adpbios;
			$h{'adpfw'} = $adpfw;
			$h{'adpdriver'} = $adpdriver;
			$h{'adp_can_use_bbu'} = $adp_can_use_bbu;
			$h{'bbu_present'} = $bbu_present;
			if ($bbu_present =~ /yes/) {
				$h{'bbu_tech'} = $bbu_tech;
				$h{'bbu_temp'} = $bbu_temp;
				$h{'bbu_charge_max_err'} = $bbu_charge_max_err;
				$h{'bbu_charge_design_mah'} = $bbu_charge_design_mah;
				# We don't really need mV.
	#			$h{'bbu_design_volt'} = $bbu_design_volt;
				$h{'bbu_manuf_date'} = $bbu_manuf_date;
				$h{'bbu_autolearn'} = $bbu_autolearn;
				$h{'bbu_learn_time'} = $bbu_learn_time;
				$h{'bbu_learn_next_time'} = $bbu_learn_next_time;
				$h{'bbu_learning_now'} = $bbu_learning_now;
				$h{'bbu_temp_ok'} = $bbu_temp_ok;
				$h{'bbu_temp_ok_raw'} = $bbu_temp_ok_raw;
				$h{'bbu_volt_ok'} = $bbu_volt_ok;
				$h{'bbu_volt_ok_raw'} = $bbu_volt_ok_raw;
				$h{'bbu_charge_status'} = $bbu_charge_status;
				$h{'bbu_missing'} = $bbu_missing;
				$h{'bbu_repl_reqd'} = $bbu_repl_reqd;
				$h{'bbu_cap_low'} = $bbu_cap_low;
				$h{'bbu_charge_relative'} = $bbu_charge_relative;
				$h{'bbu_charge_absolute'} = $bbu_charge_absolute;
				$h{'bbu_charge_mah'} = $bbu_charge_mah;
				$h{'bbu_charge_max_mah'} = $bbu_charge_max_mah;
				$h{'bbu_charge_cycles'} = $bbu_charge_cycles;
			}

			push @ADP, \%h;	
		}

	}

	# For convenience.
	$ADPCOUNT = $#ADP + 1;

}



#finds all usb devices
#uses /dev/disk/by-path
#ensures devices exist by checking /proc/partitions
sub get_usb_devices {
	my $data;
	my @list;
	my $usbdevice;
	my @usbdevices;
	
	#check that partitions exist
        #$data = `cat /proc/partitions`;
      	$data = shell_command("cat /proc/partitions");
	if($data =~ /sd[a-z]/i){
                #$data = `ls -l /dev/disk/by-path/`;
		$data = shell_command("ls -l /dev/disk/by-path/");		
		$data = sgrep("i","usb",$data);
                @list = split("\n",$data);
        }
        else {
		#if none exist, return empty array
                return @usbdevices;
        }

	foreach(@list){
       		$usbdevice = grabafter("\Q../../\E",$_);
		$usbdevice = trim($usbdevice);
		if($usbdevice =~ /[0-9]/) { next; }
		push @usbdevices, $usbdevice;
	}

	return @usbdevices;
}


#this takes the usb list produced from get_usb_devices in the form of sdX
#and finds the bus:device number for every sdX drive
sub get_usb_bus_dev {

	my $listref = shift;
	my @usblist = @$listref;
	my $data;
	my $bus;
	my $dev;
	my $major;
	my $minor;
	my $idprod;
	my $idvend;
	my $lsusbinfo;
	my $usbdev;
	my @usbdevs;


	$lsusbinfo = shell_command("lsusb 2>&1");
	if(!defined($lsusbinfo) || $lsusbinfo =~ /no such/i || $lsusbinfo =~ /not found/i) {
		print "lsusb not found, unable to gather information on usb devices\n";
		return;
	}
		
	foreach (@usblist){
		$usbdev = $_;
		#$data = `ls -l /dev/$usbdev`; #get the major/minor number of the sdX device
		$data = shell_command("ls -l /dev/$usbdev");
		$data = grabafter("disk",$data);
		$data =~ /([0-9]+)/;
		$major = $1; #first number listed is major
		$data = grabafter(",",$data);
                $data =~ /([0-9]+)/;
		$minor = $1; #second is minor = grabafter("disk",$data);
		#$data = `ls -l /sys/dev/block/$major:$minor`;
		
		#Multiple methods here for different OS, some older linux versions may not have the
		#/sys/dev directory so we must use another method to gather bus:dev numbers
		
		$data = shell_command("ls /sys/");
		#if /sys/dev/ exists use this method
		if($data =~ /dev / || $data =~ /dev\n/) {
			
			$data = shell_command("ls -l /sys/dev/block/$major:$minor");
			$data = grabafter("-> ",$data); #this gets you a directory
			#print "maj/min $major/$minor path: $data\n";
			$data = grabbefore("/host",$data); #this trims directory to relevant part
			$data = grabafter("\Q../../\E",$data);
			#$bus = `cd /sys/$data/;cd ..;cat busnum`; #file contains only bus numberi
			$bus = shell_command("cd /sys/$data/;cd ..;cat busnum");
			$bus = trim($bus);
			#$dev = `cd /sys/$data/;cd ..;cat devnum`; #file contains only device number
			$dev = shell_command("cd /sys/$data/;cd ..;cat devnum");
			$dev = trim($dev);
		}
		#if there is no /sys/dev we go here
		else{
			$data = shell_command("ls -l /sys/block/$usbdev/device 2>&1");
			#in the case the other method doesnt work, skip device and notify user it failed
			if(!defined($data) || $data =~ /no such/i) { 
				print "Unable to locate bus:dev numbers for USB device $usbdev!\n";
				next;
			}
			$data = grabafter("-> ",$data); #this gets you a directory
			$data = grabbefore("/host",$data); #this trims directory to relevant part
			$data = grabafter("\Q../../\E",$data);

			#old method does not work in all scenarios
			#$bus = `cd /sys/$data/;cd ..;cat busnum`; #file contains only bus numberi
			#$bus = shell_command("cd /sys/$data/;cd ../../../;cat bNumConfigurations");
			#$bus = trim($bus);
			#$dev = `cd /sys/$data/;cd ..;cat devnum`; #file contains only device number
		
			#in this part of the directory we just grabbed we can get idProduct and idVendor
			#we can cross referrence these against those listed in lsusb to get bud:Dev numbers
			$idprod = shell_command("cd /sys/$data/;cd ..;cat idProduct 2>&1");
			if($idprod =~ /no such/i) { print "bus:dev numbs not found for $usbdev, skipping\n"; next;}
			$idprod = trim($idprod);
			$idvend = shell_command("cd /sys/$data/;cd ..;cat idVendor 2>&1");
			if($idvend =~ /no such/i) { print "bus:dev numbs not found for $usbdev, skipping\n"; next;}
			$idvend = trim($idvend);
			$data = sgrep("","$idvend:$idprod",$lsusbinfo);
			if($data eq "" || !defined($data) ) { print "bus:dev numbs not found for $usbdev, skipping\n"; next;}
			$bus = grabbetween("Bus "," Device",$data);
			$dev = grabbetween("Device ", ":", $data);
		}
		
		my %h = ();
	
		$h{'usbdevice'} = $usbdev;
		$h{'usbdevnum'} = $dev;
		$h{'usbbusnum'} = $bus;
		$h{'devmajor'} = $major;
		$h{'devminor'} = $minor;
		
		push @usbdevs, \%h;
	}

	return @usbdevs;
}


#takes two arrays references
#returns an array of all items that are only in first array argument
sub array_difference {
	my $aref= shift;
	my @a = @$aref; #all items
	my $bref = shift;
	my @b = @$bref; #items to remove
	
	my %h = map {$_ => 1} @b;
	my @diff = grep {not $h{$_}} @a; #get @a - @b

	return @diff;
}

#filter out partitions
#obtained from partitions_list
#so only possible pch partitions are found
#drives from known adaptec/megaraid/lsi adapters get
#filtered out
sub filter_dev_disk_bypath {
	my @list = get_partitions();
	my @partitions;
	my @good; #onboard for sure
	my @bad; #off board on controller for sure
	my @unknown; #unknown
	my @usb; #usb devices
	my $data;
	my $pciadr;
	my $partition;
	
	foreach my $ref (@list){
		$partition = $ref->{'partition'};
		$pciadr = $ref->{'pciadr'};
		#print "FILT:par/adr = $partition/$pciadr\n";
		#$data = `lspci | grep -i "$pciadr"`;
		$data = shell_command("lspci | grep -i '$pciadr'");
		#print "par/pci/lspci: $partition/$pciadr/$data\n"; #used for checking for new controllers on new systems
		if($data =~ /USB/i) { push @usb, $ref; next;} #we will deal with  these later
		if($data =~ /adaptec/i) { push @bad, $ref; next; }
		elsif($data =~ /lsi/i) { push @bad, $ref; next; }
		elsif($data =~ /Intel Corporation/) { push @good, $ref; next; }
		elsif($data =~ /MegaRAID/i) { push @bad, $ref; next; }
		else { push @unknown, $ref; }
		
	}

	return (\@good, \@unknown, \@bad, \@usb);


}

#takes array of usb devices with bud:dev nums
#that has been generated from get_usb_bus_dev
#gets information on these devices from lsusb -v
#also gets some information from linux dev files
sub get_usb_info {
	my $usbref = shift;
	my @usblist = @$usbref;
	my @usbinfolist;
	my $data;

	my $major;
	my $minor;
	my $vendor;
	my $product;
	my $serial;
	my $bcdusb;
	my $maxpower;
	my $bus;
	my $devnum;
	my $device;
	my $size;
	my $pdsg;	

	my $pid;
	my @pids;
	my $filename;
	my $file;

	#run all lsusb calls in parellel
	if($USB_FORKING){
		$filename = get_temp_filename();
		#this will get lsusb output into a tmp file
		#uses fork so children can run lsusb in parallel
		foreach my $ref (@usblist){
			my $bus = $ref->{'usbbusnum'};
			my $devnum = $ref->{'usbdevnum'};

			$file = $filename . $bus . $devnum;
			$pid = fork;
			die "Failed to fork: $!\n" if !defined $pid;
			if($pid == 0) { 
				#in child process
				system("lsusb -s $bus:$devnum -v > $file 2>&1");
				exit;
			}
			else { 
				#in parent process
				push @pids, $pid;
				next; 
			}
		}
	
		#wait until each child process has ended
		foreach my $cpid (@pids) {
			waitpid $cpid, 0;	
		}
	}
	
	foreach my $ref (@usblist){
		$major = $ref->{"devmajor"};
		$minor = $ref->{"devminor"};
		$devnum = $ref->{"usbdevnum"};
		$bus = $ref->{"usbbusnum"};
		$device = $ref->{"usbdevice"};
		
		#able to use bus:dev numbers to get info from lsusb	
		if($USB_FORKING) { 
			$file = $filename . $bus . $devnum;
			$data = shell_command("cat $file"); 
		}
		else {
			$data = "lsusb -s $bus:$devnum -v 2>&1"; #format= -s bus:device -v
			#this call may hang for a while on a faulty device
			$data = shell_command("$data");
		}
		$vendor = sgrep("i","idVendor",$data);
		$vendor = grabafter("idVendor",$vendor);
		$vendor = trim($vendor);
		$vendor = grabafter(" ",$vendor); #strip leading id
		$product = sgrep("i","idProduct",$data);
		$product = grabafter("idProduct",$product);
	        $product = trim($product);
		$product = grabafter(" ",$product); #strip leading id
     		$serial = sgrep("i","iSerial",$data);
        	$serial = grabafter("iSerial",$serial);
		$serial = trim($serial);
		$serial = grabafter(" ",$serial); #there is a random number before serial
        	#$serial = trim($serial);
	  	$bcdusb = sgrep("i","bcdUSB",$data);
        	$bcdusb = grabline(1,$bcdusb); #two instances of this variable
		$bcdusb = grabafter("bcdUSB",$bcdusb);
        	$bcdusb = grabbefore("\Q.\E",$bcdusb); #strip off everything after '.' eg 2.00->2
		$bcdusb = trim($bcdusb); 
        	$maxpower = sgrep("i","MaxPower",$data);
        	$maxpower = grabafter("MaxPower",$maxpower);
        	$maxpower = trim($maxpower);
		#size not listed in lsusb, instead go to this path and cat size file
		#$size = `cat /sys/dev/block/$major:$minor/size`; #gives size in blocks
		#$size = trim($size);;
		#$size = `fdisk -l /dev/$device`;
		$size = shell_command("fdisk -l /dev/$device");
		$size = grabbetween(": ","\Q,\E",$size);
		$size = streamline_size($size);
		$size = trim($size);

		#$pdsg = `ls -l /sys/block/$device/device/generic`; #generic scsi link located here
		$pdsg = shell_command("ls -l /sys/block/$device/device/generic");
		$pdsg = grabafter("->", $pdsg);
		$pdsg = grabafter("sg", $pdsg); #grab only sgX device, not path to it
		$pdsg = trim($pdsg);
		$pdsg = "sg" . $pdsg;
		my $pdpartitions = get_pd_partitions_from_fdisk($device);

		if($USB_FORKING) { system("rm $file"); }

		my %h = ();

		$h{'pdvendor'} = $vendor;
		$h{'pdproduct'} = $product;
		$h{'pdserial'} = $serial;
		$h{'usbtype'} = $bcdusb;
		$h{'usbmaxpower'} = $maxpower;
		$h{'pdsz'} = $size;

		$h{'pdosmapping'} = $device;
		$h{'pdname'} = $device;
		$h{'usbbusnum'} = $bus;
		$h{'usbdevnum'} = $devnum;
		$h{'pdmajor'} = $major;
		$h{'pdminor'} = $minor;
		$h{'pdsg'} = $pdsg;	
		$h{'pdpartitions'} = $pdpartitions;
		#these are standard for all usb
		$h{'adpnum'} = "0";
		$h{'adptype'} = "usb";
		$h{'pdstate'} = "optimal"; #not best solution, just assume if read it is optimal
		#$h{'pdslot'} = ""; not relevant nor needed anymore, was here for old info display
		#$h{'pdenc'} = ""; not relevant nor needed anymore, was here for old info display
		
		
		
		push @usbinfolist, \%h;		
	}	
	
	return @usbinfolist;
}

#get smartctl version number
sub get_smartctl_vrsn {
	my $data;
	my $version;

	$data = get_smartmontools_cmd("-V");
	if(!defined($data) || $data =~ /no such/i) { return -1; }
	$data = sgrep("i","release",$data);
	$data= grabbetween("release","dated",$data);
	$version = trim($data);

	return $version;
}

#Make/goto /tmp/ldstatetmpfiles and generate
#filename for base of each file
sub get_temp_filename {
	my $data;
	my $prefix = "tmpdatafile";
	my $filename;
	#make directory, if already made, thats fine
	$data = `mkdir -p /tmp/ldstatetmpfiles/ 2>&1`;
	if($data =~ /read-only/i) {
		print "Read-Only File system-Error generating temp file. Cannot fork.\n";
		exit (5);
	}
	#search for files
	$data = `ls /tmp/ldstatetmpfiles/`;
	while($data =~ /$prefix/) { $prefix .= "X"; }

	$filename = "/tmp/ldstatetmpfiles/$prefix";

	return $filename;
}


sub get_pd_state_info_smartmontools {
	my @partitions;
	my $listref = shift;
	my @list = @$listref;
	
	my $data;
	my $pdguid = "";
	my $pdstate = "";
	my $pdtemp = "";
	my $pdserial = "";
	my $pdstateraw = "";
	my $pdppid = "";
	my $pdformfactor = "";
	my $pdrotation = "";

	my $pid;
	my @pids;
	my $filename;
	my $file;

	#run all smartctl calls in parellel
	if($SMARTCTL_FORKING){
		$filename = get_temp_filename();
		#this will get smartctl output into a tmp file
		#uses fork so children can run smartctl in parallel
		foreach my $ref (@list){
			my $partition = $ref->{'partition'};
			if ($partition =~ /[0-9]/) { next; }
	
			$file = $filename . $partition;
			$pid = fork;
			die "Failed to fork: $!\n" if !defined $pid;
			if($pid == 0) { 
				#in child process, calling smartctl
				system("smartctl -a /dev/$partition > $file");
				exit;
			}
			else { 
				#in parent process, push child pid
				push @pids, $pid;
				next; 
			}
		}
	
		#wait until each child process has ended
		foreach my $cpid (@pids) {
			waitpid $cpid, 0;	
		}
	}
	
	foreach my $ref (@list){
		my $partition = $ref->{'partition'};
		if ($partition =~ /[0-9]/) { next; }
		if($SMARTCTL_FORKING) { 
			$file = $filename . $partition;
			$data = shell_command("cat $file"); 
		}
		else { $data = get_smartmontools_cmd("-a", "/dev/$partition"); }
		if($data =~ /INQUIRY failed/) { 
		#	$vendor = $productnum = $capacity = $serial = "smartctl inquiry failed";	
			print "smartctl -i /dev/$_ INQUIRY FAILED!\n";
			next; 
		}

#	This method for temperature grabbing does not work with non-PCH drives
#		$pdtemp = shell_command("smartctl -l scttempsts /dev/$partition");	
#		$pdtemp = keyval_get_val("Current Temperature",$pdtemp);
#		$pdtemp =~ s/(\D)+//g;
#		if($pdtemp ne "") { $pdtemp .= "C"; }

		$pdtemp = sgrep("i","Current Drive Temperature:",$data);
		$pdtemp = cut(": ",2,$pdtemp);
		if(defined($pdtemp)) { $pdtemp =~ s/\D//g; }
		else { 
			$pdtemp = sgrep("i","Temperature_Celsius",$data);
			$pdtemp = grabafter("Always",$pdtemp);
			$pdtemp =~ /([0-9]+)/;
			$pdtemp = $1; #first number is degrees C
		}
		$pdtemp .= "C";
		$pdguid = sgrep("i","LU WWN Device Id",$data);
		$pdguid = cut(": ",2,$pdguid);
		$pdguid = trim($pdguid);
		$pdguid =~ s/\s//g;
		$pdguid =~ s/-//g;
		if($pdguid eq ""){
		$pdguid = sgrep("i","Logical Unit Id",$data);
			$pdguid = grabafter("0x",$pdguid);
			$pdguid = trim($pdguid);
		}

		$pdserial = sgrep("i","Serial number",$data);
		$pdserial = cut(": ",2,$pdserial);
		$pdserial= trim($pdserial);
		#eliminate dashes and spaces for matching
		$pdserial =~ s/-//g;
		$pdserial =~ s/\s//g;
		$pdstate = sgrep("i","SMART overall-health self-assessment test result",$data);
		$pdstate = cut(": ",2,$pdstate);
		if(defined($pdstate)) { $pdstateraw = trim($pdstate); }
		#check other parameter
		if(!(defined $pdstate) || $pdstate eq ""){
			$pdstate = sgrep("i","SMART Health Status",$data);
			$pdstate = cut(": ",2,$pdstate);
			if(!defined($pdstate)) { $pdstate = "";  }
			$pdstateraw = trim($pdstate);
			if($pdstate =~ /OK/i) { $pdstate = "optimal"; }
			else { $pdstate = "failed"; }
		}
		if($pdstate =~ /PASSED/i || $pdstate =~ /optimal/i) { $pdstate = "ready"; } #optimal as far as SMART tests
		else { $pdstate = "failed"; } #any non pass state considered a fail
		if($SMARTCTL_FORKING) { system("rm $file &"); }

		#Get PPID from sg_vpd on Dell VPD page 0xDC
		$data = shell_command("sg_vpd -l -p 0xDC /dev/$partition 2>&1");
		if(!defined($data) || $data =~ /not found/ || $data =~ /no such/i) { $pdppid = "sg_vpd required"; }
		elsif($data =~ /fetching VPD page failed/) { $pdppid = "vpd page 0xdc not found"; }
		else{
			$pdformfactor = sgrep("","40  ",$data);
			if($pdformfactor =~ /3500/) { $pdformfactor = "3.5"; }
			elsif($pdformfactor =~ /2500/) { $pdformfactor = "2.5"; }
			else { $pdformfactor = ""; }
			$pdppid = sgrep("","100 ",$data); #grab line with bytes 100-10f
			my @p = split(" ", $pdppid);
			my $fields = @p;
			$pdppid = $p[$fields-1];
			$data .= sgrep("","110 ",$data); #also line with bytes 110-11f
			@p = split(" ", $data);
			$fields = @p;
			$pdppid .= $p[$fields-2];
			$pdppid = trim($pdppid);		
		}

		#get rotational rate from VPD page 0xb1
		$data = shell_command("sg_vpd -l -p 0xb1 /dev/$partition 2>&1");
		if(!defined($data) || $data =~ /not found/ || $data =~ /no such/i) { $pdppid = "sg_vpd required"; }
		elsif($data =~ /fetching VPD page failed/) { $pdppid = "vpd page 0xdc not found"; }
		else{
			$pdrotation = keyval_get_val("Nominal rotation rate",$data);
			if($pdrotation =~ /15000/) { $pdrotation = "15k"; }
			if($pdrotation =~ /10000/) { $pdrotation = "10k"; }
			if($pdrotation =~ /5400/) { $pdrotation = "5400"; }
			if($pdrotation =~ /7200/) { $pdrotation = "7200"; }
		}	
		my %h = ();
		
		$h{'pdguid'} = $pdguid;
		$h{'pdstate'} = $pdstate;
		$h{'pdname'} = $partition;
		$h{'pdtemp'} = $pdtemp;
		$h{'pdserial'}= $pdserial;
		$h{'pdstateraw'} = $pdstateraw;
		$h{'pdppid'} = $pdppid;
		$h{'pdformfactor'} = $pdformfactor;
		$h{'pdrotation'} = $pdrotation;
		push @partitions, \%h;
	}
	
	return @partitions;

}

#scrape fdisk for pdpartitions and type id
#used for any onboard pds
#form: sdX1:id sdX2:id sdX3:id ...
sub get_pd_partitions_from_fdisk {
	my $pdname = shift;
	my $pdpartitions = "";
	my $data;
	my $partition;
	my @partitions;
	my @info;
	my $id;
	my $system;

	$data = shell_command("fdisk -l /dev/$pdname 2>&1");
	if($data =~ /command not found/i || $data =~ /no such file/i) { return "fdisk_not_found"; }
	if($data =~ /doesn't contain a valid partition table/i) { return "No valid partition table"; }
	$data = grabafter("Device Boot",$data);
	@partitions = split("\n",$data);

	foreach(@partitions) {
		#we only care about partition lines
		if($_ =~ /\/dev\/$pdname/){
			$partition = grabbetween("/dev/"," ",$_);
			@info = split(" ",$_);
			if($info[1] eq "*") { $id = $info[5]; }
			else { $id = $info[4]; }
			$system = grabafter(" $id ",$_);
			$system = trim($system);
			$pdpartitions .= "$partition:$id:$system,";
		}	
	}
	
	$pdpartitions =~ s/,$//;
	return $pdpartitions;

}

#uses smartctl to get information about PDs
#tested mainly with version smartctl 5.43, 
#should still work on newer and older versions
#some paramaters are only available in version 6 or later
sub get_pd_info_from_smartmontools{
	my @partitions;
	my $listref = shift;
	my @list = @$listref;
	my $onboard = shift;

	my $data;
	
	my $version;
	my $vendor;
	my $productnum;
	my $capacity;
	my $serial;
	my $devicemodel;
	my $formfactor;
	my $rpm;
	my $firmware;
	my $pdstate;
	my $pdmediatype;
	my $pdtemp;
	my $pdsg;
	my $pdguid = "";
	my $pdlinkspeed = "";
	my $pdprotocol = ""; 
	my $pdpartitions = "";
	my $pdsmartstateraw = "";
	my $pdppid = "";
	my $pdformfactor = "";
	my $pdrotation = "";
	my $pid;
	my @pids;
	my $filename;
	my $file;

	$version = $SMARTCTL_VERSION;
	
	#run all smartctl calls in parellel	
	if($SMARTCTL_FORKING){
		$filename = get_temp_filename();
		#this will get smartctl output into a tmp file
		#uses fork so children can run smartctl in parallel
		foreach my $ref (@list){
			my $partition = $ref->{'partition'};
			if ($partition =~ /[0-9]/) { next; }
	
			$file = $filename . $partition;
			$pid = fork;
			die "Failed to fork: $!\n" if !defined $pid;
			if($pid == 0) { 
				#in child process to call smartctl
				system("smartctl -a /dev/$partition > $file");
				exit;
			}
			else { 
				#parent process, push child pid to @pids
				push @pids, $pid;
				next; 
			}
		}
	
		#wait until each child process has ended
		foreach my $cpid (@pids) {
			waitpid $cpid, 0;	
		}
	}
	
	# Now, pick up any drives that are hung off of onboard controllers

	#print "smartctl version: $version\n";
	foreach my $ref (@list){
		my $partition = $ref->{'partition'};
		if ($partition =~ /[0-9]/) { next; }
		if($SMARTCTL_FORKING) {
			$file = $filename . $partition;
			$data = shell_command("cat $file");
		}
		else { $data = get_smartmontools_cmd("-a", "/dev/$partition"); }
		if($data =~ /INQUIRY failed/) { 
		#	$vendor = $productnum = $capacity = $serial = "smartctl inquiry failed";	
			print "smartctl -a /dev/$_ INQUIRY FAILED!\n";
			next; 
		}
		$vendor = sgrep("i","Vendor",$data);
		$vendor = cut(":",2,$vendor);
		$vendor = trim($vendor);
               	$productnum = sgrep("i","Product",$data);
               	$productnum = cut(":",2,$productnum);
       	     	$productnum = trim($productnum);
		$capacity = sgrep("i","User Capacity",$data);
	 	$capacity = cut(":",2,$capacity);
       	        $capacity = trim($capacity);
                $capacity =~ s/\Q,\E//g;
                $capacity =~ /([0-9]+)/;
                $capacity = $1;
		$capacity = streamline_size($capacity . "bytes");
		$firmware = sgrep("i","Firmware Version",$data);
		$firmware = cut(": ",2,$firmware);
		$firmware = trim($firmware);
		$serial = sgrep("i","Serial number",$data);
	        $serial = cut(":",2,$serial);
       		$serial = trim($serial);
                $formfactor = sgrep("i","Form Factor",$data);
                $formfactor = cut(":",2,$formfactor);
                $formfactor = trim($formfactor);
                $rpm = sgrep("i","Rotational Rate",$data);
                $rpm = cut(":",2,$rpm);
                $rpm = trim($rpm);
                $devicemodel = sgrep("i","Device Model",$data);
                $devicemodel = cut(":",2,$devicemodel);
                $devicemodel = trim($devicemodel);
		#Old way of getting temperature, new method below
		#$pdtemp = sgrep("i","Temperature_Celsius", $data);
		#$pdtemp = grabafter("Always",$pdtemp);
		#$pdtemp =~ /([0-9]+)/;
		#$pdtemp = $1; #first number is degrees C
		#$pdtemp .= "C";
		$pdtemp = shell_command("smartctl -l scttempsts /dev/$partition");	
		$pdtemp = keyval_get_val("Current Temperature",$pdtemp);
		$pdtemp =~ s/(\D)+//g;
		if($pdtemp ne "") { $pdtemp .= "C"; }
		#get pdprotocol and pdlinkspeed if its versions 6 or later
		#pdprotocol MAY be obtainable in earlier versions but needs more testing
		if($version =~ /[6789]./) { #check for version 6 (newest now) or higher
			$pdprotocol = sgrep("i","SATA Version is",$data);
			if($pdprotocol ne "") {
				$pdlinkspeed = grabafter("\Q,\E",$pdprotocol);
				$pdprotocol = "SATA";
				#streamline pdlinkspeed
				if ($pdlinkspeed =~ /unknown/i) { $pdlinkspeed = ""; }
       	 			elsif ($pdlinkspeed =~ /1.5 Gb/i) { $pdlinkspeed = "1.5G"; }
       	 			elsif ($pdlinkspeed =~ /3.0 Gb/i) { $pdlinkspeed = "3G"; }
       			 	elsif ($pdlinkspeed =~ /6.0 Gb/i) { $pdlinkspeed = "6G"; }
       		 		elsif ($pdlinkspeed =~ /12.0 Gb/i) { $pdlinkspeed = "12G"; }
				else { $pdlinkspeed = ""; }
			}

		}
		else {
			if($data =~ /ATA/) { $pdprotocol = "SATA" }
			if($data =~ /SAS/) { $pdprotocol = "SAS" }
			$pdlinkspeed = "smartmontools_upgrade_required"; #Find someway to indicate smartctl needs to be updated
		}

		$pdsg = `ls -l /sys/block/$partition/device/generic`; #generic scsi link located here
		#$pdsg = shell_command"ls -l /sys/block/$partition/device/generic");
		$pdsg = grabafter("->", $pdsg);
		$pdsg = grabafter("sg", $pdsg); #grab only sgX device, not path to it
		$pdsg = trim($pdsg);
		$pdsg = "sg" . $pdsg;
		$pdguid = sgrep("i","LU WWN Device Id",$data);
		$pdguid = cut(": ",2,$pdguid);
		$pdguid = trim($pdguid);
		$pdguid =~ s/\s//g;
		$pdguid =~ s/-//g;
		#$pdmediatype = `cat /sys/block/$partition/queue/rotational`; #this file contains {1,0} for {HD,SSD}
		$pdmediatype = shell_command("ls /sys/block/$partition/queue/");
		if($pdmediatype =~ /rotational/) { $pdmediatype = shell_command("cat /sys/block/$partition/queue/rotational"); }
		else{ $pdmediatype = ""; }
		if($pdmediatype =~ /1/) { $pdmediatype = "HD"; }
		if($pdmediatype =~ /0/) { $pdmediatype = "SSD"; }	
		$pdstate = sgrep("i","SMART overall-health self-assessment test result",$data);
		$pdstate = cut(": ",2,$pdstate);
		$pdsmartstateraw = trim($pdstate);
		#check other thing
		if(!(defined $pdstate) || $pdstate eq ""){
			$pdstate = sgrep("i","SMART Health Status",$data);
			$pdstate = cut(": ",2,$pdstate);
			$pdsmartstateraw = trim($pdstate);
			if($pdstate =~ /OK/i) { $pdstate = "optimal"; }
			else { $pdstate = "failed"; }
		}
		if($pdstate =~ /PASSED/i || $pdstate =~ /optimal/i) { $pdstate = "ready"; } #optimal as far as SMART tests
		else { $pdstate = "failed"; } #any non pass state considered a fail
		#check to see if the drive is already part of an array
		foreach(@LD){
			if($_->{"adptype"} eq "onboard"){
				if($_->{"ldpdlist"} =~ /$partition/) { $pdstate = "optimal"; }
			}
		}


		my $pdoverheat = "";
		$pdpartitions = get_pd_partitions_from_fdisk($partition);

		if($SMARTCTL_FORKING) { system("rm $file &"); }
	
		#Get PPID from sg_vpd on Dell VPD page 0xDC
		$data = shell_command("sg_vpd -l -p 0xDC /dev/$partition 2>&1");
		if(!defined($data) || $data =~ /not found/ || $data =~ /no such/i) { $pdppid = "sg_vpd required"; }
		elsif($data =~ /fetching VPD page failed/) { $pdppid = "vpd page 0xdc not found"; }
		else{	
			$pdformfactor = sgrep("","40  ",$data);
			if($pdformfactor =~ /3500/) { $pdformfactor = "3.5"; }
			elsif($pdformfactor =~ /2500/) { $pdformfactor = "2.5"; }$pdppid = sgrep("","100 ",$data); #grab line with bytes 100-10f
			my @p = split(" ", $pdppid);
			my $fields = @p;
			$pdppid = $p[$fields-1];
			$data .= sgrep("","110 ",$data); #also line with bytes 110-11f
			@p = split(" ", $data);
			$fields = @p;
			$pdppid .= $p[$fields-2];
			$pdppid = trim($pdppid);
		}


		#get rotational rate from VPD page 0xb1
		$data = shell_command("sg_vpd -l -p 0xb1 /dev/$partition 2>&1");
		if(!defined($data) || $data =~ /not found/ || $data =~ /no such/i) { $pdppid = "sg_vpd required"; }
		elsif($data =~ /fetching VPD page failed/) { $pdppid = "vpd page 0xdc not found"; }
		else{
			$pdrotation = keyval_get_val("Nominal rotation rate",$data);
			if($pdrotation =~ /15000/) { $pdrotation = "15k"; }
			if($pdrotation =~ /10000/) { $pdrotation = "10k"; }
			if($pdrotation =~ /5400/) { $pdrotation = "5400"; }
			if($pdrotation =~ /7200/) { $pdrotation = "7200"; }
		}	

		my %h = ();
		
		$h{'adpnum'} = "0";
		$h{'adptype'} = "onboard";
		$h{'toolname'} = "smartctl";
		$h{'toolver'} = $version;		

		$h{"pdformfactor"} = $pdformfactor;
		$h{'pdguid'} = $pdguid;
		$h{'pdstate'} = $pdstate;
		$h{'pdsmartstateraw'} = $pdsmartstateraw;
		$h{'pdname'} = $partition;
		$h{'pdosmapping'} = $partition;
		$h{'pdvendor'} = $vendor;
		$h{'pdproductnum'} = $productnum;		
		$h{'pdsz'} = $capacity;
		$h{'pdfw'} = $firmware;
		$h{'pdserial'} = $serial;
		#$h{'pdrpm'} = $rpm;
		$h{'pdmodel'} = $devicemodel;
		$h{'pdform'} = $formfactor;		
		$h{'pdmediatype'} = $pdmediatype;
		$h{'pdtemp'} = $pdtemp;
		$h{'pdoverheat'} = $pdoverheat;
		$h{'pdsg'} = $pdsg;
		$h{'pdprotocol'} = $pdprotocol;
		$h{'pdlinkspeed'} = $pdlinkspeed;
		$h{'pdpartitions'} = $pdpartitions;
		$h{'pdppid'} = $pdppid;
		$h{'pdrpm'} = $pdrotation;
		#These are here for so info command doesn't display an error
		#$h{'pdslot'} = "";
		#$h{'pdenc'} = "";

		$h{'pdonboard'} = $onboard;
		
		push @partitions, \%h;
		#push @PD, \%h;

	}
	
	return @partitions;
}

#gets list of partitions from /dev/disk/by-path
#collects partition name and pciadress from each
#returns array with these key-value pairs
sub get_partitions {
	my $data;
	my @list;
	my @partitions;
	my $partition;
	my $pciadress;

	#$data = `cat /proc/partitions`;
	$data = shell_command("cat /proc/partitions");
        if($data =~ /sd[a-z]/i){
                #$data = `ls -l /dev/disk/by-path/`;
		$data = shell_command("ls -l /dev/disk/by-path/");
                $data = sgrep("i","pci",$data);
                @list = split("\n",$data);
        }
        else {
                return @partitions;
        }

	foreach(@list){
		$partition = grabafter("\Q../../\E",$_);
		if($partition =~ /[0-9]/) { next; }
		$partition = trim($partition);
		$pciadress = grabbetween("0000:","-",$_);

		my %h = ();
		$h{'partition'} = $partition;
		$h{'pciadr'} = $pciadress;
		#print "GET:par/adr - $partition/$pciadress\n";
		push @partitions, \%h;
	}
	
	return @partitions;

}

#unused for now, gets partitions from `cat /proc/partitions`
sub partitions_list {
        my $plist;
        my @partitions;
        my @a;
        my $tmp;
        my $partition;

        #$plist = `cat /proc/partitions`;
	$plist = shell_command("cat /proc/partitions");
	@a = split("\n",$plist);
        foreach(@a){
                if($_ =~ / sd/) {
                        $tmp = grabafter("sd",$_);
                        $partition = "sd" . $tmp;
                        push @partitions, $partition;
                }
                elsif($_ =~ / md/) {
       			$tmp = grabafter("md",$_);
                        $partition = "md" . $tmp;
                        push @partitions, $partition;
                }

        }

	return @partitions;

}

sub parse_arguments {
	my $args_str = shift;
	$args_str = trim($args_str);
	$args_str = condense_spaces($args_str);

	my @args = split(" ",$args_str);
	
	foreach my $arg (@args) {

		if ($arg =~ /^\-\-/) {
			$arg = substr ($arg, 2);
			push @ARGLONGFLAGS, $arg;
			next;
		}	

		if ($arg =~ /^\-/) {
			$arg = substr ($arg, 1);
			push @ARGFLAGS, $arg;
			next;
		}	

		# At this point, it's just an ordinary argument
		push @ARGLIST, $arg;
		next;
		
	}

}

# Is a given long flag set?
sub longflag_set {
	my $seek = shift;
	my $rc = 0;

	foreach (@ARGLONGFLAGS) {
		if ($_ =~ /$seek/i) { $rc = 1; last; }
	}

	return $rc;
}

#prints usage for make_raid command
sub make_raid_usage {

	print "ldstate make_raid usage:\n";
	print "\tldstate make_raid [RAID0|RAID1|RAID1E|RAID5|RAID10] [all|each|adapter.number] ";
	print "[all|enclosure:slot] <enclosure:slot>...\n";
	print "\tpossible adapters: adaptec, megaraid, sas2008, onboard.\n";
	print "\tusing 'all' in place of an adapter will work if there is only one usable adapter on the system\n";
	print "\t'all' will create the largest possible array of the entered type\n";
	print "\tusing 'each' in place of an adapter will create as many arrays of the minimum size as possible on each adapter\n";
	print "\tusing 'all' after listing an adapter will use the most available drives possible to make the array\n";
	print "\tUse ldstate info to find available adapters and drives with enclosure:slot\n\n";

}

#uses tool calls to create arrays
sub create_a_raid {
	my ($adptype, $adpnum, $raidtype, $drives) = @_;
	my $data;
	my $drives_formatted;

	#format drives list and call arcconf command
	if($adptype =~ /adaptec/){
		#cmd_make_raid formats drives in adaptec form already
		$drives_formatted = $drives;
		$data = get_adaptec_cmd("create", $adpnum,"logicaldrive max $raidtype $drives_formatted noprompt 2>&1");
		if($data =~ /aborted/i) { print "Command failed!\n"; }
		elsif($data =~ /success/i) { print "Command succeeded. RAID$raidtype array created.\n"; }
		else { print "Command error!\n"; }
	}
	#format drives list and call megaraid command
	if($adptype =~ /megaraid/){
		#put drives in form enc:slot,enc:slot,..
		my $drives_formatted2;
		my @numbers;
		my $i = 0;
		@numbers = $drives =~ /([0-9]+)/g;
		my $lastnumb = @numbers;
		if($raidtype eq "10") {
			foreach(@numbers){
				if(($i/2) % 2) {
					if($i % 2) { $drives_formatted .= "$_, "; }
					else { $drives_formatted .= "$_:"; }
				}
				else {
					if($i % 2) { $drives_formatted .= "$_, "; }
					else { $drives_formatted .= "$_:"; }	
				}	
				$i++; 
	
			}
			$drives_formatted  =~ s/,$//;
			$drives_formatted2 =~ s/,$//;
			$data = get_megaraid_cmd($adpnum,"-CfgSpanAdd -r10 -Array0[$drives_formatted] -Array1[$drives_formatted2]","no");
			if($data =~ /aborted/i) { print "Command failed!\n"; }
			elsif($data =~ /configured the adapter/i) { print "Command succeeded. RAID$raidtype array created.\n"; }
			else { print "Command error!\n"; }
		}
		else {
			foreach(@numbers){
				if($i == $lastnumb-1) { $drives_formatted .= "$_"; last; }
				if($i % 2) { $drives_formatted .= "$_, "; }
				else { $drives_formatted .= "$_:"; }
				$i++; 
			}
			$data = get_megaraid_cmd($adpnum,"-CfgLdAdd -r$raidtype [$drives_formatted]","no");
			if($data =~ /aborted/i) { print "Command failed!\n"; }
			elsif($data =~ /configured the adapter/i) { print "Command succeeded. RAID$raidtype array created.\n"; }
			else { print "Command error!\n"; }
		}
	}
	#format drives list and call sas2ircu command
	if($adptype =~ /sas2008/){
		#put drives in form enc:slot enc:slot..
		my @numbers;
		my $i = 0;
		@numbers = $drives =~ /([0-9]+)/g;
		foreach(@numbers){
			if($i % 2) { $drives_formatted .= "$_ "; }
			else { $drives_formatted .= "$_:"; }
			$i++; 
		}
		$data = get_sas2ircu_cmd($adpnum,"create RAID$raidtype max $drives_formatted noprompt 2>&1");
		if($data =~ /failed/i) { print "Command failed!\n"; }
		elsif($data =~ /success/i) { print "Command succeeded. RAID$raidtype array created.\n"; }
		else { print "Command error!\n"; }
	}
	#format drives and call mdadm command
	if($adptype =~ /onboard/){
		#cmd_make_raid formats drives in onboard form already
		print "Creating LDs with mdadm has several bugs, may not work\n";
		$drives_formatted = $drives;
		my @drivelist = split("/dev/",$drives);
		my $drivecount = @drivelist - 1;
		my $ldnum = 0;
		foreach(@LD){
			if($_->{"adptype"} eq "onboard"){
				my $ldname = $_->{"ldname"};
				$ldname = s/\D//g;
				if($ldname >= $ldnum) { $ldnum = $ldname + 1; }
			}
		}
		$data = `mdadm --create /dev/md$ldnum --level=$raidtype --raid-devices=$drivecount $drives_formatted --run`;
		if($data =~ /aborted/i) { print "Command failed!\n"; }
		if($data =~ /another array by this name/i) { print "Command failed!\n"; }
		elsif($data =~ /success/i) { print "Command succeeded. RAID$raidtype array md$ldnum created.\n"; }
		else { print "Command error!\n"; }
	}

	print "$data\n";	
}

#checks to see if the correct amount of
#physical drives is provided to make raid array
#and creates a string that is the list of physical drives
sub make_raid_drive_list{
	my($adptype, $adpnum, $type, @drives) = @_;
	my $drives;
	my $count;

	my $drivecount = @drives;

	#for RAID0  array
	if($type eq "0"){
		#ensure correct drive count
		if($drivecount < 1) {
			print "Not enough available drives for a RAID0 array. Requires minimum of 2 drives.\n";
			make_raid_usage();
			return;
		}
		#sas2008 AND adaptec wont allow 1 drive RAID0 arrays
		if($drivecount == 1){
			if($drives[0]->{"adptype"} eq "sas2008"){
				print "warning: sas2008 adapters don't allow raid0s of size 1!\n";
				return;
			}

		}
	
		if($adptype eq "onboard") { #must get /dev/sdX not enc:slot
			foreach(@drives) { $drives .= "/dev/" . $_->{"pdname"} . " "; }
		}

		else {
			foreach(@drives){
				#get drive enc:slot to pass to arcconf tool call
				$drives .= $_->{"pdenc"} . " " . $_->{"pdslot"} . " ";
			}
		}
		#create the raid
		create_a_raid($adptype, $adpnum, $type, $drives);
	}
	#for RAID1 array
	if($type eq "1"){
		#ensure correct drive count
		if($drivecount < 2) {
			print "Not enough available drives for a RAID1 array. Requires exactly of 2 drives.\n";
			make_raid_usage();
			return;
		}
		if($drivecount > 2){
			print "Choosing first 2 available drives, cannot use more than 2 drives in RAID1.\n";
		}
		$count = 0;
		
		if($adptype eq "onboard") { #must get /dev/sdX not enc:slot
			foreach(@drives) { 
				$drives .= "/dev/" . $_->{"pdname"} . " "; 
				$count++;
				if($count == 2) { last; }
			}
		}

		else {
			foreach(@drives){
				#get drive enc:slot to pass to arcconf tool call
				$drives .= $_->{"pdenc"} . " " . $_->{"pdslot"} . " ";
				$count++;
				if($count == 2) { last; }
			}
		}
		#create the array
		create_a_raid($adptype, $adpnum, $type, $drives);
	}
	#for RAID10 array
	if($type eq "10"){
		if($adptype eq "megaraid") { print "Creating megaraid RAID10 arrays has known problems, may not work.\n"; return;} 
		#ensure correct drive count
		if($drivecount < 4) {
			print "Not enough available drives for a RAID10 array. Requires minimum of 4 drives.\n";
			make_raid_usage();
			return;
		}
		if($drivecount % 2 != 0){
			print "RAID10 requires an even number of drives, selecting max even number available.\n";
		}
		$count = 0;	
		if($adptype eq "onboard") { #must get /dev/sdX not enc:slot
			foreach(@drives) { 
				$drives .= "/dev/" . $_->{"pdname"} . " "; 
				$count++;
				if($count == $drivecount -1 && $drivecount % 2 != 0) { last; }
			}
		}
		else {
			foreach(@drives){
				#get drive enc:slot to pass to arcconf tool call
				$drives .= $_->{"pdenc"} . " " . $_->{"pdslot"} . " ";
				$count++;
				if($count == $drivecount - 1 && $drivecount % 2 != 0) { last; }
			}
		}
		#create the array
		create_a_raid($adptype, $adpnum, $type, $drives);
	}
	#for RAID5 array
	if($type eq "5"){
		if($adptype eq "sas2008") { print "SAS2008 cards cannot make RAID5 arrays\n"; return; }
		#ensure correct drive count
		if($drivecount < 3) {
			print "Not enough available drives for a RAID5 array. Requires minimum of 3 drives.\n";
			make_raid_usage();
			return;
		}	
		if($adptype eq "onboard") { #must get /dev/sdX not enc:slot
			foreach(@drives) { 
				$drives .= "/dev/" . $_->{"pdname"} . " "; 
			}
		}
		else{ 
			foreach(@drives){
				#get drive enc:slot to pass to arcconf tool call
				$drives .= $_->{"pdenc"} . " " . $_->{"pdslot"} . " ";
			}
		}
		#create the array
		create_a_raid($adptype, $adpnum, $type, $drives);
	}
	#for RAID1E array
	if($type eq "1e"){
		if($adptype eq "onboard") { print "mdadm does not allow creation of RAID1E arrays.\n"; return;} 
		if($adptype eq "megaraid") { print "megacli does not allow creation of RAID1E arrays.\n"; return;} 
		#ensure correct drive count
		if($drivecount < 3) {
			print "Not enough available drives for a RAID1E array. Requires minimum of 3 drives.\n";
			make_raid_usage();
			return;
		}
		if($drivecount % 2 == 0){
			print "RAID1E requires an odd number of drives, selecting max odd number available.\n";
		}
		$count = 0;
		foreach(@drives){
			#get drive enc:slot to pass to arcconf tool call
			$drives .= $_->{"pdenc"} . " " . $_->{"pdslot"} . " ";
			$count++;
			if($count == $drivecount - 1 && $drivecount % 2 == 0) { last; }
		}
		#create the array
		create_a_raid($adptype, $adpnum, $type, $drives);
	}

}

#make a new RAID array
sub cmd_make_raid {
	my ($type, $adp, $drive, @moredrives) = @_;
	my @drives;
	my @drivestmp;
	my $adptype = $adp;
	my $adpnum = $adp;
	my $adaptecpds;
	my $megaraidpds;
	my $lsipds;
	my $onboardpds;
	my $lasttype;
	my $lastnum;
	my $thistype;
	my $thisnum;
	my $count = 0;
	my $suitable_adps = 0;
	my $required;


	if($adp =~ /sas2008/i) {
		$adptype = "sas2008";
		my @numbers = $adp =~ /([0-9]+)/g;
		$adpnum = $numbers[1];
	}
	else {
		$adptype =~ s/[^a-z]//g; #only type
		$adpnum  =~ s/\D//g; #only number
	}
	
	#get just number of raidtype
	if(!($type =~ /1E/i)) { $type =~ s/\D//g; }
	else{ $type = "1e"; }
	
	#if drives isnt "all" add to list of drives	
	if($drive ne "all") { unshift @moredrives, $drive; }

	if($type ne "0" && $type ne "1" && $type ne "5" && $type ne "10" && $type ne "1e") {
		print "Invalid raid type selection: $type\n";
		make_raid_usage();
		return;
	}
	
	#check adptype to make sure it is correct
	if(!($adptype =~ /adaptec/i) && !($adptype =~ /sas2008/i) && !($adptype =~ /megaraid/i) && 
	!($adptype =~ /onboard/i) && $adptype ne "all" && $adptype ne "each") {
		print "Invalid adapter selection: $adp\n";
		make_raid_usage();
		return;
	}


	#determine required amout of PDs
	if($type eq "0")  { 
		if($adptype =~ /megaraid/i) { $required = 1; }
		else { $required = 2; }
	}
	if($type eq "1")  { $required = 2; }
	if($type eq "1e") { $required = 3; }
	if($type eq "5")  { $required = 3; }
	if($type eq "10") { $required = 4; }
	
	if($adp eq "all") {
		#make sure there is only one type of drive/adapter combo
		#make sure raid type is correct
		foreach(@PD){
			my $pdstate = $_->{"pdstate"};
			if($pdstate eq "ready"){ push @drives, $_; }
		}
		
		$lasttype = $drives[0]->{"adptype"};
		$lastnum = $drives[0]->{"adpnum"};	

		#count the number of adapters that have enough drives to make the requested raid
		foreach(@drives){
			$thistype = $_->{"adptype"};
			$thisnum = $_->{"adpnum"};
			if($thistype eq $lasttype && $thisnum eq $lastnum){
				$count++;
				if ($count == $required){ $suitable_adps++; }
			}
			else {
				$lasttype = $thistype;
				$lastnum = $thisnum;
				$count = 1;
			}
		}

		if($suitable_adps == 0) { print "Not enough drives on any adapter to make RAID$type. $required drives needed"; return; }
		if($suitable_adps == 1) { 
			$adptype = $lasttype; $adpnum = $lastnum;
			#filter out remaining drives from other adapters
			foreach(@drives){
				if($_->{"adptype"} eq $adptype && $_->{"adpnum"} eq $adpnum) { push @drivestmp, $_; }
				else{ next; }
			}
			make_raid_drive_list($adptype,$adpnum,$type,@drivestmp);
			return;
		}
		if($suitable_adps >  1) { print "More than one adapter can make RAID$type. Must specify adapter type!\n"; return; }
	}

	#make a bunch a raids of minimum size
	#method here will be to make multiple calls to make_raid_drive_list() with the
	#minum sized arrays in @drives, this will also do the ideal pairing of drives
	elsif($adp eq "each"){	
		foreach(@PD){
			my $pdstate = $_->{"pdstate"};
			if($pdstate eq "ready"){ push @drives, $_; }
		}

		#We should sort @drives here, first by SIZE then by ADPTYPE then by ADPNUM (I think this will work)
		#If we do that sorting we get optimal pairings, that's for later though
	
		$lasttype = $drives[0]->{"adptype"};
		$lastnum = $drives[0]->{"adpnum"};	

		#count the number of adapters that have enough drives to make the requested raid
		foreach(@drives){
			if($type eq "0" && $lasttype eq "megaraid") { $required = 1; }
			elsif($type eq "0" && $lasttype ne "megaraid") { $required = 2; }
			push @drivestmp, $_;
			$thistype = $_->{"adptype"};
			$thisnum = $_->{"adpnum"};
			if($thistype eq $lasttype && $thisnum eq $lastnum){
				$count++;
				if ($count == $required){ 
					#we have enough drives on this adp, make an array
					make_raid_drive_list($thistype,$thisnum,$type,@drivestmp);
					@drivestmp = ();
					$count = 0; 
				}
			}
			else {
				@drivestmp = ();
				push @drivestmp, $_;
				$lasttype = $thistype;
				$lastnum = $thisnum;
				$count = 1;
			}
		}
		return;
	}
		
	#If no adpnum is listed, get one or find out there isnt any suitable one
	if(!defined($adpnum) || $adpnum eq "") { 
		#loop through PDs matching $adptype find out which one they meant		
		foreach(@PD){
			my $pdstate = $_->{"pdstate"}; my $pdadp = $_->{"adptype"};
			if($pdstate eq "ready" && $pdadp eq $adptype){ push @drives, $_; }
		}
		
		$lasttype = $drives[0]->{"adptype"};
		$lastnum = $drives[0]->{"adpnum"};		

		#count the number of adapters that have enough drives to make the requested raid
		foreach(@drives){
			$thistype = $_->{"adptype"};
			$thisnum = $_->{"adpnum"};
			if($thistype eq $lasttype && $thisnum eq $lastnum){
				$count++;
				if ($count == $required){ $suitable_adps++; }
			}
			else {
				$lasttype = $thistype;
				$lastnum = $thisnum;
				$count = 0;
			}
		}
		
		if($suitable_adps == 0) { print "Not enough drives on any $adptype adapter to make RAID$type. $required drives needed"; return; }
		if($suitable_adps == 1) { $adpnum = $lastnum; @drives = (); } #we know adpnum and clear drives for re-entry
		if($suitable_adps >  1) { print "More than one $adptype adapter can make RAID$type. Must specify adapter number!\n"; return; }
	}

	#if drive is "all" find the correct drives to use and make an array
	if($drive eq "all") {
		#match each PD on this adapter and make sure the drive is ready to be put into RAID array
		foreach(@PD){
			if($_->{"adptype"} eq $adptype && $_->{"adpnum"} eq $adpnum && $_->{"pdstate"} eq "ready") { 
				push @drives, $_; 
			}
		}
		
		#construct string of drives and create arrays
		make_raid_drive_list($adptype,$adpnum,$type,@drives);
		return;
	}

	#if drive is "each" make as many arrays as possible on the specified adapter
	elsif($drive eq "each") {
		$count = 0;
		#match each PD on this adapter and make sure the drive is ready to be put into RAID array
		foreach(@PD){
			if($_->{"adptype"} eq $adptype && $_->{"adpnum"} eq $adpnum && $_->{"pdstate"} eq "ready"){
				push @drivestmp, $_;
 				$count++;
				if ($count == $required){ 
					#we have enough drives on this adp, make an array
					make_raid_drive_list($adptype,$adpnum,$type,@drivestmp);
					@drivestmp = ();
					$count = 0; 
				}	
			}
		}
		return;
	}

	#if drives were manually listed rather than "all"
	else {
		#make sure @moredrives has no repeats
		my @sorted = @moredrives;
		#sort by enc:slot as one number to check for repeats
		foreach(@sorted) { $_ =~ s/\D//g; }
		@sorted = sort(@sorted);
		my $last = "lastdrive";
		foreach(@sorted){
			if($_ eq $last) {
				print "Cannot use the same drive twice!\n";
				make_raid_usage();
				return;
			}
			$last = $_;
		}
			
		#Get list of physical drives
		foreach(@moredrives){
			if(!defined($_) || $_ eq "") {next;}
			#Make sure drive arguments are in correct format
			if($_ =~ m/^(\d)+[\D](\d)+$/) {
				my @encslot = $_ =~ /(\d+)/g;
				my $matched = 0;
				#check to see if the drive is actually on the system
				foreach(@PD){
					my $enc = $_->{"pdenc"}; my $slot = $_->{"pdslot"};
					if($_->{"adptype"} eq $adptype && $_->{"adpnum"} eq $adpnum && 
					$_->{"pdenc"} eq $encslot[0] && $_->{"pdslot"} eq $encslot[1]) { 
						#if it is on the system check its state then push PD to @drives
						if($_->{"pdstate"} eq "ready") { 
							$matched = 1;
							push @drives, $_;
						}
						else { print "Drive $adptype.$adpnum $enc:$slot is not in ready state.\n"; return; }
					}
				}	
				#notify user that drive was not found
				if($matched == 0 ) { 
					my $enc = $_->{"pdenc"}; my $slot = $_->{"pdslot"};
					print "No matching drive $adptype.$adpnum $_ was found!\n" ; 
					make_raid_usage(); 
					return;
				}
	
			}
			else{
				print "Argument '$_' does not match enclosure:slot format\n";
				make_raid_usage();
				return;
			}
		}
		#now we have @drives list
		make_raid_drive_list($adptype,$adpnum,$type, @drives);
		return;	
	}
}

#return 1 if there are foreign LDs
sub foreign_lds_exist {
	my @lds = shift || @LD;

	foreach(@lds) {
		my $foreign = $_->{"ldforeign"} || "";

		if($foreign ne "") { return 1; }
	}

	return 0;
}

#assemble foreign LDs into actual (non-foreign) LDs
sub cmd_assemble_foreign_lds {
	my ($adpnum, $confignum) = @_;
	my $data; #we should check what the commands return

	# 'ldstate clear_foreign_config all' - clear everything
	if($adpnum eq "all" && ($confignum eq "" || $confignum eq "all")){
		$data = get_megaraid_cmd("ALL","-CfgForeign -Import");
		print "megacli output:\n$data\n";
		return;
	}

	elsif(is_numeric($adpnum) && $confignum eq "all"){
		$data = get_megaraid_cmd($adpnum,"-CfgForeign -Import");
		print "megacli output:\n$data\n";
		return;
	}
	
	elsif(is_numeric($adpnum) && is_numeric($confignum)){
		$data = get_megaraid_cmd($adpnum,"-CfgForeign -Import $confignum");
		print "megacli output:\n$data\n";
		return;
	}

	else {
		#give them the usage, something isnt right
		print "\nCorrect Usage: 'ldstate clear_foreign_configs [adpnum | all] [confignum | all]'\n\n";
		return
	}	
}

#wipe a physical drive of its data, or its partition table
#mehod used is: 'dd if=/dev/zero of=/dev/sdX'
sub cmd_wipedrive {
	my ($type, $device) = @_;
	my $pd;
	my $pdpart;
	my $data;
	my $continue;

	if($type ne "full" and $type ne "partitions") {
		print "type '$type' isn't allowed.\nUsage: 'ldstate wipedrive [full|partitions] [pdosmapping]'\n";
		return;
	}

	foreach(@PD) {
		$pd = $_->{'pdname'} || $_->{'pdosmapping'} || "";
		if($pd eq $device) {
			$pdpart = $_->{'pdpartitions'} || "";
			if($MODE_NONAG == 0) {
				print "About to wipe drive $pd\n$pd has partitions:$pdpart\nDo you wish to continue? (yes,no)\n";
				chomp($continue = <STDIN>);
			}
			if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
			if($type eq "full") { 
				print "Now attempting wipe all data on $pd. This may take a long time...\n";
				$data = shell_command("dd if=/dev/zero of=/dev/$pd bs=1M 2>&1"); 
			}
			elsif($type eq "partitions") { 
				print "Now attempting to wipe partition table of $pd.\n";
				$data = shell_command("dd if=/dev/zero of=/dev/$pd bs=512 count=1024 2>&1"); 
			}
			print "dd results:\n$data\n";
			return;
		}
	}
	
	print "No drive '$device' was found.\nUsage: 'ldstate wipedrive [full|partitions] [pdosmapping]'\npdosmapping = sdX\n";

}

#list all devices of type specified by the user
#where type is the adaptername
sub cmd_listdev {
	my ($type) = @_;
	my $adptype;
	my $devname;
	my $list = "";

	foreach(@PD){
		#print out device names
		$adptype = $_->{'adptype'};
		if($adptype eq lc($type)) {
			$devname = $_->{"pdname"};
			if($MODE_CSV) { $list .= "$devname,"; }
			else { $list .= "$devname\n"; }
		}
	}

	$list =~ s/,$//;	
	$list .= "\n";

	print "$list";

}

#list app partitions on device/device types specified
#by the user
sub cmd_listpartitions {
	my ($type) = @_;
	my $adptype;
	my $devname;
	my $partitions;
	my $list = "";

	foreach(@PD){
		#print out device partitions
		$adptype = $_->{'adptype'};
		$devname = $_->{"pdname"};
		if($adptype eq lc($type)) {
			$partitions = $_->{"pdpartitions"} || "";
			if($MODE_CSV) { $list .= "$partitions,"; }
			else { $list .= "$partitions\n"; }
		}
		if($devname eq $type){
			$partitions = $_->{"pdpartitions"} || "";
			if($MODE_CSV) { $list .= $partitions }
			else { $partitions =~ s/,/\n/g; $list .= $partitions; }
		}
	}

	$list =~ s/,$//;	
	$list .= "\n";

	print "$list";
}

#print list of usb devices and thier partitions
#second arg is delimter to seperate them, if no arg 
#then a new line is used
sub cmd_usb_partitions {
	my $cmd2 = shift || "";
	my $type;
	my $partitions;
	my $device;

	my $delim = "";
	
	if($cmd2 ne "") { $delim = $cmd2; }

	foreach(@PD){
		$type = $_->{'adptype'} || "";
		$partitions = $_->{'pdpartitions'} || "";
		$device = $_->{'pdname'} || "";
		if($type ne "usb") { next; }

		print "$device-$partitions";
		if($delim ne "") { print "$delim"; }
		else { print "\n"; }
	}

	print "\n";	
}

#destroys RAID arrays on the system
sub cmd_destroy_raid {
	my ($adp, $ldnum, $ldnum2, @morelds) = @_;
	my $adptype = $adp;
	my $adpnum = $adp;
	my @lds;
	my $lastnum;
	my $thisnum;
	my @ldlist;
	my $ldtotal;

	if(foreign_lds_exist()){
		print "Foreign LDs are present, if you wish to destroy these use clear_foreign_configs command\n"
	}
	
	if($adp =~ /sas2008/i) {
		$adptype = "sas2008";
		my @numbers = $adp =~ /([0-9]+)/g;
		$adpnum = $numbers[1];
	}
	else {
		$adptype =~ s/[^a-z]//g; #only type
		$adpnum  =~ s/\D//g; #only number
	}

	unshift @morelds, $ldnum2;
	unshift @morelds, $ldnum;
	
	#get only number for each drive
	foreach(@morelds) { 
		$_ =~ s/\D//g; 
	}
	
	#destroy every LD detected on system
	if($adp eq "all") {
		#destroy every ld
		destroy_lds(@LD);
		return;
	}	
	
	#if adapter is specified make sure it has a valid type
	if(!($adp =~ /adaptec/i) && !($adp =~ /sas2008/i) && !($adp =~ /megaraid/i) && !($adp =~ /onboard/i)) {
		print "Invalid adapter selection: $adp\n";
		destroy_raid_usage();
		return;
	}

	#If no adpnum is listed, get one or find out there isnt any suitable one
	if(!defined($adpnum) || $adpnum eq "") { 
		#loop through PDs matching $adptype find out which one they meant		
		foreach(@LD){
			my $ldadp = $_->{"adptype"};
			if($adptype eq $ldadp){ push @ldlist, $_; }
		}
		
		$lastnum = $ldlist[0]->{"adpnum"};		

		#count the number of adapters that have enough drives to make the requested raid
		foreach(@ldlist){
			$thisnum = $_->{"adpnum"};
			if($thisnum eq $lastnum){ }
			else {
				print "Multiple $adptype adapters with LDs found. Specify adapter number!\n";
				destroy_raid_usage();
				return;
			}
		}
		#all LD on the same adapter number
		$adpnum = $lastnum;
	}

	#if no LD numbers listed, nothing to destroy	
	if($ldnum eq "") {
		print "LD number must be listed!\n";
		destroy_raid_usage();
		return;
	}

	#find and destroy all lds listed in specified adapter
	if($ldnum eq "all"){
		#get all LDs for specified adapter
		foreach(@LD){
			my $ldadptype = $_->{"adptype"};
			my $ldadpnum = $_->{"adpnum"};
			if($adptype eq $ldadptype && $adpnum eq $ldadpnum) { push @lds, $_; }
		}
		$ldtotal = @lds;
		if($ldtotal == 0) { print "No LDs found on adp: $adptype.$adpnum\n"; }
		destroy_lds(@lds);
		return;
	}

	#make sure @morelds has no repeats
	my @sorted = sort(@morelds);
	my $last = "lastdrive";
	foreach(@sorted){
		$_ =~ s/\D//g;
		if($_ eq $last && $last ne "") {
			print "Cannot use the same drive twice!\n";
			destroy_raid_usage();
			return;
		}
		$last = $_;
	}
		
	#create list from entered LDs and destroy them
	foreach(@morelds) {
		my $number = $_;
		if($number eq "") {next;}
		#make sure LD on specified adapter exists
		my $matched = 0;
		foreach(@LD){
			my $ldadptype = $_->{"adptype"};
			my $ldadpnum = $_->{"adpnum"};
			my $ldnumber = $_->{"ldnum"};
			if($adptype eq $ldadptype && $adpnum eq $ldadpnum && $number eq $ldnumber) { 
				push @lds, $_; 
				$matched = 1;
			}	
		}
		if($matched == 0) { 
			print "ld$number on adapter $adptype.$adpnum not found!\n";
			#destroy_raid_usage();
			return;
		}
	}
	destroy_lds(@lds);
	return;
}

#takes a list of lds and uses
#appropriate tool to destroy them
sub destroy_lds {
	my (@ldlist) = @_;
	my $data;
	my $continue = "yes";
	foreach(@ldlist){
		my $adptype = $_->{"adptype"};
		my $adpnum = $_->{"adpnum"};
		my $ldnum = $_->{"ldnum"};
		my $ldforeign = $_->{"ldforeign"} || "";
		
		if($ldforeign ne "") { next; } #skip foreign LDs		

		if($adptype eq "adaptec"){
			#prompt user to make sure they know they are deleting this ld
			if($MODE_NONAG == 0) {
				print "About to delete $adptype.$adpnum ld$ldnum. Do you wish to continue? (yes,no)\n";
				chomp($continue = <STDIN>);
			}
			if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
			$data = get_adaptec_cmd("delete", $adpnum,"logicaldrive $ldnum noprompt 2>&1");
			if($data =~ /aborted/i) { print "Command failed!\n"; }
			elsif($data =~ /success/i) { print "Command succeeded. LD$ldnum destroyed.\n"; }
			else { print "Command error!\n"; }
		}

		if($adptype eq "megaraid"){
			#prompt user to make sure they know they are deleting this ld
			if($MODE_NONAG == 0) {
				print "About to delete LD$ldnum on $adptype.$adpnum. Do you wish to continue? (yes,no)\n";
				chomp($continue = <STDIN>);
			}
			if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
			$data = get_megaraid_cmd($adpnum,"-CfgLdDel -L$ldnum","no");
			if($data =~ /does not exist/i) { print "Command failed!\n"; }
			elsif($data =~ /deleted/i) { print "Command succeeded. LD$ldnum destroyed.\n"; }
			else { print "Command error!\n"; }
		}

		if($adptype eq "sas2008"){
			#prompt user to make sure they know they are deleting this ld
			if($MODE_NONAG == 0) {
				print "About to delete $adptype.$adpnum ld$ldnum. Do you wish to continue? (yes,no)\n";
				chomp($continue = <STDIN>);
			}
			if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
			my $volumeid = $_->{"ldvolumeid"};
			$data = get_sas2ircu_cmd($adpnum, "deletevolume $volumeid noprompt 2>&1");
			if($data =~ /failed/i) { print "Command failed!\n"; }
			elsif($data =~ /success/i) { print "Command succeeded. LD$ldnum destroyed.\n"; }
			else { print "Command error!\n"; }
		}

		if($adptype eq "onboard"){
			my $ldtype = $_->{"ldtype"};
			#If it is an lvm logical volume
			if($ldtype =~ /lvm/) {
				my $ldpath = $_->{"ldpath"};
				#prompt user to make sure they know they are deleting this ld
				if($MODE_NONAG == 0) {
					print "About to delete $adptype.$adpnum ld$ldnum (an LVM LV-$ldpath). Do you wish to continue? (yes,no)\n";
					chomp($continue = <STDIN>);
				}
				if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
				$data = `lvremove $ldpath -f 2>&1`;
				if($data =~ /failed/i) { print "Command failed!\n"; }
				elsif($data =~ /successfully/i) { print "Command succeeded. LD$ldnum destroyed.\n"; }
				elsif($data =~ /not found/i) { print "LV not found\n"; }
				else { print "Command error!\nlvm output-\n$data\n"; }
				return;
			}

			#not lvm raid then must be md raid with mdadm
			#prompt user to make sure they know they are deleting this ld
			if($MODE_NONAG == 0) {
				print "About to delete $adptype.$adpnum ld$ldnum. Do you wish to continue? (yes,no)\n";
				chomp($continue = <STDIN>);
			}
			if(lc($continue) ne "yes") { print "Stopping proccess.\n"; return; }
			my $ldname = $_->{"ldname"};
			my $pdlist = $_->{"ldpdlist"};
			my @pds = split(",",$pdlist);
			$data = `mdadm --manage /dev/$ldname --stop 2>&1`;
			if($data =~ /failed/i) { print "Command failed!\n"; }
			elsif($data =~ /stopped/i) { print "Command succeeded. LD$ldnum destroyed.\n"; }
			else { print "Command error!\nmdadm output-\n$data\n"; }
			#after array has been stopped we must --zero-superblock each pd in the array
			foreach(@pds) { system("mdadm --zero-superblock /dev/$_"); }
		}
	}

}

sub destroy_raid_usage {

	print "ldstate destroy_raid usage:\n";
	print "\tldstate destroy_raid [all | adapter.number] [all | ldnum] <ldnum>....\n";
	print "\tpossible adapters: adaptec, megaraid, sas2008, onboard.\n";
	print "\te.g. 'ldstate destroy_raid all' will destroy all detected logical drives\n";
	print "\te.g. 'ldstate destroy_raid adaptec.0 all' will destroy all LDs on adaptec adapter 0\n";
}

#execute command to activate led on set adapter/drive
sub cmd_led {
	my ($cmd, $adapter, $drives) = @_;
	my $adptype = $adapter;
	my $adpnum = $adapter;
	my $adpexists = 0;
	my $pdslot;
	my $pdenc;
	
	if($adapter =~ /sas2008/i) {
		$adptype = "sas2008";
		my @numbers = $adapter =~ /([0-9]+)/g;
		$adpnum = $numbers[1];
	}
	else {
		$adptype =~ s/[^a-z]//g; #only type
		$adpnum  =~ s/\D//g; #only number
	}


	#Make sure we have enough arguments for the command if its not an "all" command	
	if( $cmd eq "" || $adapter eq "" || ($drives eq "" && $drives ne "all") ){
		print "ERROR: Not enough arguments for alarm command.\n";
		print "alarm command usage: ";
		print "'ldstate led [on|off|blink] [adptype.adpnum] [all|ld-ldnum|pdnenc:pdslot]'\n";		
		exit 1;
	}
	if($cmd ne "on" && $cmd ne "off" && $cmd ne "blink"){
		print "ERROR: command must be [on|off|blink] you entered '$cmd'\n";
		return;
	}

	#ensure user=specified ADP exists
	foreach(@ADP) {
		my $num = $_->{"adpnum"};
		my $type = $_->{"adptype"};
		if($adptype eq $type && $adpnum eq $num) { $adpexists = 1; last; }
	}
	unless($adpexists) { print "Adapter $adapter not found. Enter adapter as adptype.adpnum\n"; return; }
	
	#if all then blink all drives on adapter
	if($drives eq "all") { 
		foreach(@PD){
			my $num = $_->{"adpnum"};
			my $type = $_->{"adptype"};
			if($adptype eq $type && $adpnum eq $num) {
				led_action($adptype, $adpnum, $_->{"pdenc"}, $_->{"pdslot"}, $cmd);
			}
	
		}
	}
	#if there is a colon then user wants a specific physical device	
	elsif($drives =~ /:/) { 
		$pdenc = grabbefore(":",$drives); 
		$pdslot = grabafter(":",$drives); 
		$pdenc =~ s/(\D)+//g;
		$pdslot =~ s/(\D)+//g;
		foreach(@PD){
			my $num = $_->{"adpnum"};
			my $type = $_->{"adptype"};
			my $slot =  $_->{"pdslot"};
			my $enc = $_->{"pdenc"};
			if($adptype eq $type && $adpnum eq $num && $pdenc eq $enc && $pdslot eq $slot) {
				led_action($adptype, $adpnum, $_->{"pdenc"}, $_->{"pdslot"}, $cmd);
				return;
			}
	
		}
		print "PD $drives on $adapter not found.\n";
	}
	#check if its an ld
	elsif($drives =~ /ld/i) {
		$drives =~ s/(\D)+//g;
		#look for matching LD
		foreach(@LD){
			my $num = $_->{"adpnum"};
			my $type = $_->{"adptype"};
			my $ldnum =  $_->{"ldnum"};
			my $ldpdlist = $_->{"ldpdlist"};
			if($adptype eq $type && $adpnum eq $num && $ldnum eq $drives) {
				my @pdlist = split(",",$ldpdlist);
				#in matching LD get list of PDs by enc:slot
				foreach(@pdlist){
					$pdenc = grabbefore(":",$_);
					$pdslot = grabafter(":",$_);
					led_action($adptype, $adpnum, $pdenc, $pdslot, $cmd);	
				}
				return;
			}
		}
		print "LD $drives on $adapter not found.\n";
	}
	else {
		print "Unable to tell which device to led-$cmd enter as [all|pdenc:pdslot|ld-ldnum]\n";
	}

	return;
}

#execute and ld command based on adapter and pd info
#gets arguments from cmd_led which parses user arguments
sub led_action {
	my ($adptype, $adpnum, $pdenc, $pdslot, $cmd) = @_;
	my $data;
	#notify user of drive command is being used on unless nonag flag was used
	unless($MODE_NONAG) { print "Attempting to execute:led-$cmd on $adptype.$adpnum-$pdenc:$pdslot\n"; }

	#execute led command based on adapter type/ different command with each tool
	if($adptype eq "adaptec") {

	}
	elsif($adptype eq "sas2008") {
		if($cmd eq "blink") { print "SAS2008 boards do no support blinking leds, try on or off\n"; return; }
		$data = get_sas2ircu_cmd($adpnum, "locate $pdenc:$pdslot $cmd 2>&1");
		unless($MODE_NONAG) { print "sas2ircu output:\n$data\n\n"; }
	}
	elsif($adptype eq "megaraid") {
		if($cmd eq "on") { print "MegaRaid boards only support blinking leds, cannot use led-on\n"; return; }
		elsif($cmd eq "off") { $cmd = "stop"; } #off->stop blinking
		elsif($cmd eq "blink") { $cmd = "start"; } #blink->start blinking
		$data = get_megaraid_cmd($adpnum, "-PdLocate -$cmd -physdrv [$pdenc:$pdslot]", "no"); #"no" arg for 2>&1 in command
		unless($MODE_NONAG) { print "megacli output:\n$data\n"; }
	}
	else { print "Adapters of type $adptype are not supported for led functions\n"; }

	return;
}


#executed alarm command for selected adapter
sub cmd_alarm {
	my ($cmd, $adp, $adpnum) = @_;
	my $data;
	my $output = "Results:\n";

	#Make sure we have enough arguments for the command if its not an "all" command	
	if($cmd eq "" || $adp eq "" || $adpnum eq "" && $adp ne "all"){
		print "ERROR: Not enough arguments for alarm command.\n";
		print "alarm command usage: ";
		print "'ldstate alarm [on|off|silence] [adaptec|megaraid|all] [adpnum]'\n";		
		exit 1;
	}
	
	if($cmd eq "silence"){
		if($adp eq "megaraid"){
			$data = get_megaraid_cmd($adpnum, "adpsetprop AlarmSilence");
			if($data =~ /success/i) { $output .= "megaraid.$adpnum silenced succefully\n"; }
			if($data =~ /abort/i) { $output .= "megaraid.$adpnum silence command aborted\n"; }
		}
		elsif($adp eq "adaptec"){
			$data = get_adaptec_cmd("setalarm","$adpnum","silence 2>&1");
			if($data =~ /success/i) { $output .= "adaptec.$adpnum silenced succefully\n"; }
			if($data =~ /abort/i) { $output .= "adaptec.$adpnum silence command aborted\n"; }
			if($data =~ /invalid controller number/i) { $output .= "adaptec.$adpnum is invalid controller\n"; }
		}
		elsif($adp eq "all"){
			foreach my $adpref (@ADP){
				my $adptype = $adpref->{'adptype'};
				my $thisadpnum = $adpref->{'adpnum'};
				if($adptype =~ /adaptec/i) {
					$data = get_adaptec_cmd("setalarm","$thisadpnum","silence 2>&1");
					if($data =~ /success/i) { $output .= "adaptec.$thisadpnum silenced succefully\n"; }
					if($data =~ /aborted/i) { $output .= "adaptec.$thisadpnum silence command aborted\n"; }
				}
				elsif($adptype =~ /megaraid/i) {
					$data = get_megaraid_cmd($thisadpnum,"adpsetprop AlarmSilence");
					if($data =~ /success/i) { $output .= "megaraid.$thisadpnum silenced succefully\n"; }
					if($data =~ /abort/i) { $output .= "megaraid.$thisadpnum silence command aborted\n"; }
				}
			}
		}
		else{
			print "arg[3] is $adp, expected {megaraid,adaptec,all}\n"
		#	exit 1;
		}
	}
	elsif($cmd eq "on") {
                if($adp eq "megaraid"){
                        $data = get_megaraid_cmd($adpnum, "adpsetprop AlarmEnbl");
                       	if($data =~ /success/i) { $output .= "megaraid.$adpnum set on succefully\n"; }
			if($data =~ /abort/i) { $output .= "megaraid.$adpnum set on command aborted\n"; }
                }
                elsif($adp eq "adaptec"){
                        $data = get_adaptec_cmd("setalarm","$adpnum","on 2>&1");
                       	if($data =~ /success/i) { $output .= "adaptec.$adpnum set on succefully\n"; }
			if($data =~ /abort/i) { $output .= "adaptec.$adpnum set on command aborted\n"; }
			if($data =~ /invalid controller number/i) { $output .= "adaptec.$adpnum is invalid controller\n"; }
                }
		elsif($adp eq "all"){
			foreach my $adpref (@ADP){
				my $adptype = $adpref->{'adptype'};
				my $thisadpnum = $adpref->{'adpnum'};
				if($adptype =~ /adaptec/i) {
					$data = get_adaptec_cmd("setalarm","$thisadpnum","on 2>&1");
					if($data =~ /success/i) { $output .= "adaptec.$thisadpnum set on succefully\n"; }
					if($data =~ /abort/i) { $output .= "adaptec.$thisadpnum set on command aborted\n"; }
				}
				elsif($adptype =~ /megaraid/i) {
					$data = get_megaraid_cmd($thisadpnum,"adpsetprop AlarmEnbl");
					if($data =~ /success/i) { $output .= "megaraid.$thisadpnum set on succefully\n"; }
					if($data =~ /abort/i) { $output .= "megaraid.$thisadpnum set on command aborted\n"; }
				}
			}
		}
                else{
                        print "arg[3] is $adp, expected {megaraid,adaptec,all}\n"
                #	exit 1;
		}
	
	}
	elsif($cmd eq "off") {
                if($adp eq "megaraid"){
                        $data = get_megaraid_cmd($adpnum, "adpsetprop AlarmDsbl");
                       	if($data =~ /success/i) { $output .= "megaraid.$adpnum disabled succefully\n"; }
			if($data =~ /abort/i) { $output .= "megaraid.$adpnum disable command aborted\n"; }
                }
                elsif($adp eq "adaptec"){
                        $data = get_adaptec_cmd("setalarm","$adpnum","off 2>&1");
                	if($data =~ /success/i) { $output .= "adaptec.$adpnum disabled succefully\n"; }
			if($data =~ /abort/i) { $output .= "adaptec.$adpnum disable command aborted\n"; }
			if($data =~ /invalid controller number/i) { $output .= "adaptec.$adpnum is invalid controller\n"; }
                }
		elsif($adp eq "all"){
			foreach my $adpref (@ADP){
				my $adptype = $adpref->{'adptype'};
				my $thisadpnum = $adpref->{'adpnum'};
				if($adptype =~ /adaptec/i) {
					$data = get_adaptec_cmd("setalarm","$thisadpnum","off 2>&1");
					if($data =~ /success/i) { $output .= "adaptec.$thisadpnum disabled succefully\n"; }
					if($data =~ /abort/i) { $output .= "adaptec.$thisadpnum disable command aborted\n"; }
				}
				elsif($adptype =~ /megaraid/i) {
					$data = get_megaraid_cmd($thisadpnum,"adpsetprop AlarmDsbl");
					if($data =~ /success/i) { $output .= "megaraid.$thisadpnum disabled succefully\n"; }
					if($data =~ /abort/i) { $output .= "megaraid.$thisadpnum disable command aborted\n"; }
				}
			}
		}
                else{
                        print "arg[3] is $adp, expected {megaraid,adaptec}\n"
                #	exit 1;
		}
                
	}
	else { 
		print "arg[2] = $cmd, exptected {on,off,silence}\n";
	#	exit 1;
	}

	print "$output\n";

}

#attempt to use megacli to clear foreign configs from any drives
#on megaraid controllers
sub cmd_clear_foreign_configs {
	my ($adpnum, $confignum) = @_;
	my $data; #we should check what the commands return

	# 'ldstate clear_foreign_config all' - clear everything
	if($adpnum eq "all" && ($confignum eq "" || $confignum eq "all")){
		$data = get_megaraid_cmd("ALL","-CfgForeign -Clear");
		print "megacli output:\n$data\n";
		return;
	}

	elsif(is_numeric($adpnum) && $confignum eq "all"){
		$data = get_megaraid_cmd($adpnum,"-CfgForeign -Clear");
		print "megacli output:\n$data\n";
		return;
	}
	
	elsif(is_numeric($adpnum) && is_numeric($confignum)){
		$data = get_megaraid_cmd($adpnum,"-CfgForeign -Clear $confignum");
		print "megacli output:\n$data\n";
		return;
	}

	else {
		#give them the usage, something isnt right
		print "\nCorrect Usage: 'ldstate clear_foreign_configs [adpnum | all] [confignum | all]'\n\n";
		return
	}
	
}


#make all magaraid unconf(bad) drives unconf(good)
#only usage is "ldstate pd_makegood all" right now
sub cmd_pd_makegood {
	my ($adp, $drives, $moredrives) = @_;
	my $data = "";

	if($adp eq "all") {
		foreach(@PD) {
			my $adptype = $_->{"adptype"};
			my $adpnum = $_->{"adpnum"};
			my $state = $_->{"pdstate"};
			if($adptype eq "megaraid") {
				my $enc = $_->{"pdenc"};
				my $slot = $_->{"pdslot"};
				if($state eq "uncbad") { 
					$data = get_megaraid_cmd($adpnum, "-PDmakeGood -physdrv[$enc:$slot]"); 
					print "megacli output:\n$data\n";
				}
			}
		}
	}

	return 0;
}


# Display brief info about the Adapters/LD/PD seen
sub cmd_info {
	my ($cmd,$cmd2) = @_;

	if ($MODE_CSV || $MODE_HRCSV) {
		if ($SHOW_ADP) {
			printf "%s", write_adpinfo("  ");
		}

		if ($SHOW_LD) {
			printf "%s", write_ldinfo("  ");
		}

		if ($SHOW_PD) {
			printf "%s", write_pdinfo("  ");
		}

	} else {
		if ($SHOW_ADP) {
			printf "\n";
			printf "Adapters:\n\n";
			printf "%s", write_adpinfo("  ");
		}

		if ($SHOW_LD) {
			printf "\n";
			printf "Logical drives:\n\n";
			printf "%s", write_ldinfo("  ");
		}

		if ($SHOW_PD) {
			printf "\n";
			printf "Physical drives:\n\n";
			printf "%s", write_pdinfo("  ");
		}
			
		printf "\n";
	}

}

sub cmd_keyval {
	my ($cmd,$cmd2) = @_;


	if ($SHOW_ADP) {
		printf "ADAPTERS:\n\n";
		foreach my $ref (@ADP) {
#			foreach my $key (keys %$ref) {
			foreach my $key (sort (keys %$ref)) {
				#if($ref->{$key} eq "") { next; } #dont print empty fields
				printf "%22s : %s\n", $key, $ref->{$key};
			}
			print "\n\n";
		}
	}

	if ($SHOW_LD) {
		printf "LOGICAL DRIVES:\n\n";
		foreach my $ref (@LD) {
			foreach my $key (sort (keys %$ref)) {
				if ($key eq "adpref") { next; }
				#if($ref->{$key} eq "") { next; } #dont print empty fields
				printf "%22s : %s\n", $key, $ref->{$key};
			}
			print "\n\n";
		}
	}

	if ($SHOW_PD) {
		printf "PHYSICAL DRIVES:\n\n";
		foreach my $ref (@PD) {
			foreach my $key (sort (keys %$ref)) {
				if ($key eq "adpref") { next; }
				#if($ref->{$key} eq "") { next; } #dont print empty fields
				printf "%22s : %s\n", $key, $ref->{$key};
			}
			print "\n\n";
		}
	}

	printf "\n";
}

#process all pds
#currently only checks if overheated
sub process_pds {
	my $data;
	my $temp;
	my $overheat;
	my $thresh ;
	my $mediatype;
	my $pdforeign;
	my $pdosmapping;

	foreach my $pdref (@PD) {
		$mediatype = $pdref->{'pdmediatype'} || "";
		$temp = $pdref->{'pdtemp'} || "";
		$pdforeign = $pdref->{'pdforeign'} || "";
		$pdosmapping = $pdref->{'pdosmapping'} || "";
		#overheat check
		$temp =~ s/(\D)+//g;
		$thresh = 60;
		
		if($mediatype eq "HDD") { $thresh = 55; }		

		if($temp eq "") { $overheat = ""; }
		elsif($temp > $thresh) { $overheat = "yes" }
		else { $overheat = "no"; }
		$pdref->{'pdoverheat'} = $overheat;

		#foreign state setting (old way of handling foreign)
		#if($pdforeign eq "foreign") { $pdref->{'pdstate'} = "foreign"; }
	
	#this code will identify any SD devices being used by lvm, not needed right now	
	#	$data = shell_command("lvmdiskscan 2>&1");
	#	if($pdosmapping ne "") { 
	#		$data = sgrep("","/dev/$pdosmapping ",$data); 
	#		if($data =~ /LVM physical volume/) { next; } #This means it is an lvm PV
	#	}
		
	}

	return;
}


#
#
#
sub usage {
	printf "\n";
	printf "Dell ldstate - Storage visualization & state tool | built: $BUILD_DATE\n";
	printf "\n";
	printf "Usage:\n";
	printf "\n";
	printf "    ldstate  command_name  [adp|ld|pd]  [--csv|--hrcsv|--nonag]\n";
	printf "\n";
	printf "Supported commands:\n";
	printf "\n";
	printf "    info             Show brief info on each logical drive (1-line)\n";
	printf "    keyval           Show all captured key-value pairs for each logical drive\n";
	printf "\n";
	printf "    nagios_health    Monitoring: summary of LD health (for Nagios)\n";
	printf "    asm_health       Monitoring: summary of LD health (for ASM)\n";
	printf "    pecagent_health  Monitoring: summary of LD state  (for Dell PowerEdge-C agent)\n";
	printf "\n";
	printf "    alarm            Set alarm {on,off,silence}\n";
	printf "             usage: 'ldstate alarm [on|off|silence] [adaptec|megaraid|all] [adpnum]'\n";
	printf "\n";
	printf "    make_raid[BETA]  Create a RAID array from PDs in 'ready' state\n";
	printf "             usage: 'ldstate make_raid [0|1|5|10|1E] [all|each|adptype.adpnum] [all|each|enc:slot] <enc:slot>..\n";
	printf "\n";
	printf "    destroy_raid[BETA] Destroy a RAID array from detected LDs on the system\n";
	printf "             usage: 'ldstate destroy_raid [all|adptype.adpnum] [ldnum] <ldnum>..\n";
	printf "\n";
	printf "    clear_foreign_configs (cfc) Clear foreign configurations on MegaRaid controllers\n";
	printf "             usage: 'ldstate clear_foreign_configs [adpnum|all] [confignum|all]'\n";
	printf "\n";
	printf "    assemble_foreign_lds (afld) Assemble foreign configurations on MegaRaid controllers\n";
	printf "             usage: 'ldstate assemble_foreign_lds [adpnum|all] [confignum|all]'\n";
	printf "\n";
	printf "    listdev          List all physical devices on a specific adapter type\n";
	printf "             usage: 'ldstate listdev [adptype] <--csv>\n";
	printf "\n";
	printf "    listpartitions   List all partitions of a physical device/all devices on an adapter\n";
	printf "             usage: 'ldstate listdev [adptype|pdosmapping] <--csv>\n";
	printf "\n";
	printf "    wipedrive        Wipe partition table or all data on a physical device\n";
	printf "             usage: 'ldstate wipedrive [full|partitions] [pdosmapping]\n";
	printf "\n";	
	printf "Supported modifiers:\n";
	printf "\n";
	printf "       adp|ld|pd     Restrict display to only adapters, logical drives, phys drives\n";
	printf "       --csv         Output in CSV format (for parsing)\n";
	printf "       --hrcsv       Output in easily readable CSV format with comment lines.\n";
	printf "       --nonag       Restricts all warnings\n";
	printf "       --nofork      Prevents forking of child processes to run tool commands in parallel\n";
	printf "       --showcalls   Shows the number of system calls and all system calls that were made\n";
	printf "\n";
	printf "Testing (error injection):\n";
	printf "\n";
	printf "       --inject-bbu-volt-low\n";
	printf "       --inject-bbu-temp-high\n";
	printf "       --inject-bbu-learn-cycle\n";
	printf "       --inject-bbu-missing\n";
	printf "       --inject-bbu-charging\n";
	printf "       --inject-bbu-discharging\n";
	printf "       --inject-bbu-cap-low\n";
	printf "       --inject-bbu-repl-reqd\n\n";
	printf "       --inject-pd-overheat\n";
	printf "       --inject-pd-foreign\n";
	printf "       --inject-pd-failed\n";
	printf "       --inject-pd-offline\n";
	printf "\n";
	printf "\n";
}

#populate @PD,@LD,@ADP arrays
#gets all info so it can be displayed to user
sub get_all_info {
	my ($cmd) = @_; 	
	my $tmp;
	my $adptype;
	my $adpnum;
	my $adpdriver;				# OS level driver

	my $data_list = "";			# SAS2008
	my $data_st = "";
	my $data_dsp = "";


	my $data_adpallinfo = ""; 	# MegaRAID
	my $data_ldpdinfo = "";
	my $data_pdlist = "";
	my $data_toolver;

	#arcconf data (adaptec)
	my $data_getconfig;



	#Detect Onboard adapter and present PDs/LDs
	# This may always be present.  So look for linux software raid devices.
	parse_lswr();

	#Grab info on any lvm LV devices (as well as associated VGs and PVs)
	# TODO:  mjs >>> come back later, test LVM further and fix bugs before reenabling
	#parse_lvm();

	get_onboard_pds();

	#now get USB devices
	my @usb = get_usb_devices();
	@usb = get_usb_bus_dev(\@usb);
	@usb = get_usb_info(\@usb);
#	foreach my $usbref (@usb){
#		foreach my $usbkey(sort(keys %$usbref)) {
#			printf "%22s : %s\n", $usbkey, $usbref->{$usbkey};
#		}
#		print"\n\n";
#	}
	push @PD, @usb;
	


	#now get raid adapters
	detect_storage_adapters();

	# Error injection (BBU)
	foreach my $adpref (@ADP) {
		if (longflag_set("inject-bbu-volt-low")) { inject_bbu_error($adpref,"volt"); }
		if (longflag_set("inject-bbu-temp-high")) { inject_bbu_error($adpref,"temp"); }
		
		if (longflag_set("inject-bbu-learn-cycle")) { inject_bbu_error($adpref,"learncycle"); }
		if (longflag_set("inject-bbu-missing")) { inject_bbu_error($adpref,"missing"); }
		if (longflag_set("inject-bbu-charging")) { inject_bbu_error($adpref,"charging"); }
		if (longflag_set("inject-bbu-discharging")) { inject_bbu_error($adpref,"discharging"); }
		if (longflag_set("inject-bbu-cap-low")) { inject_bbu_error($adpref,"caplow"); }
		if (longflag_set("inject-bbu-repl-reqd")) { inject_bbu_error($adpref,"replreqd"); }
	}

	foreach my $adpref (@ADP) {
		$adptype = $adpref->{"adptype"};

		if ($adptype =~ /adaptec/i) {
			$adpnum = $adpref->{'adpnum'};
			$data_getconfig = get_adaptec_cmd("getconfig",$adpnum);			
			parse_adaptec($adpref, $data_getconfig);			
		}

		if ($adptype =~ /sas2008/i) {
#			printf "sas2008 adp\n";
			$adpnum = $adpref->{'adpnum'};
			$data_st =  get_sas2ircu_cmd($adpnum,"status"); 
			$data_dsp = get_sas2ircu_cmd($adpnum,"display"); 
			parse_sas2ircu($adpref, $data_st, $data_dsp);

			### This is probably depricated and should be removed.
#			if ($cmd eq "viewraw" || $cmd eq "raw") {
#				# Show the user the raw data
#				print "SAS2008 adapter $adpnum 'status':\n$data_st\n";
#				print "SAS2008 adapter $adpnum 'display':\n$data_dsp\n";
#			}
		}

		if ($adptype =~ /megaraid/i) {
#			printf "megaraid adp\n";
			$adpnum = $adpref->{'adpnum'};

			#
			# For this adapter, go get information about its LD/PD.
			# I prefer this to be logically separated but this is faster at the moment.
			#
###			$data_toolver = get_megaraid_cmd("all", "-v");			
			$data_ldpdinfo = get_megaraid_cmd($adpnum, "ldpdinfo");			
			$data_pdlist = get_megaraid_cmd($adpnum, "pdlist");			
			parse_megaraid($adpref, $data_ldpdinfo, $data_pdlist);	

			### This is probably depricated and should be removed.
#			if ($cmd eq "viewraw" || $cmd eq "raw") {
#				# Show the user the raw data
#				print "megacli ldpdinfo a$adpnum:\n$data_ldpdinfo\n";
#				print "megacli pdlist a$adpnum:\n$data_pdlist\n";
#			}


		}
		#onboard always present
#		if ($adptype =~ /onboard/i) {
#			parse_lswr();
#		}

	}
	
	#if we are grabbing board names from sas2flash
	if($GETFULLNAME) {
		#wait for pids getting board fullname
		foreach my $cpid (@sas2flashcpids) {
			waitpid $cpid, 0;
		}
	
		my $data = shell_command("cat $sas2flashfn");
		my @fullnames = split("\n",$data);
		my $boardname = "";
	
		foreach(@ADP){
			if($_->{"adptype"} eq "sas2008"){
				my $thisadpnum = $_->{"adpnum"};
				foreach(@fullnames){
					if($_ =~ /adp$thisadpnum/) { $boardname = grabafter(": ",$_); last; }
				}
				$_->{"adpfullname"} = $boardname;
			} 
		}
		shell_command("rm $sas2flashfn");
	}

	
	process_pds();	

	# Error injection (PD)
	foreach my $pdref (@PD) {
		if (longflag_set("inject-pd-overheat")) { inject_pd_error($pdref,"overheat"); }
		if (longflag_set("inject-pd-foreign")) { inject_pd_error($pdref,"foreign"); }
		if (longflag_set("inject-pd-failed")) { inject_pd_error($pdref,"failed"); }
		if (longflag_set("inject-pd-offline")) { inject_pd_error($pdref,"offline"); }
	}

	return 0;
}

####
####
####
####
#### MAIN
####
####
####
####

#root check
if($> != 0) {
	print "This tool must be run as root.\n";
	exit(1);
}


my $x;
my $rc = 0;
my $argcount = $#ARGV + 1;
my $args = join (" ",@ARGV);

my $cmd = "";
my $cmd2 = "";
my $cmd3 = "";
my $cmd4 = "";
my @morecmds;

my $adpdriver;				# OS level driver

my $testmode = 0;

#my $cmd = shift || "";

my $tmp;
my $adptype;
my $adpnum;

	if ($argcount < 1) {
		usage();
		exit 1;
	}

	# Detect OS we're running on.  
	$HOSTOS = "linux";
	if ($^O =~ /MSWin/) { $HOSTOS = "win"; }
	if ($^O =~ /cygwin/) { $HOSTOS = "win"; }


	parse_arguments($args);

	$cmd  = shift @ARGLIST;
	$cmd2 = shift @ARGLIST;
	$cmd3 = shift @ARGLIST; #adptype for alarm command
	$cmd4 = shift @ARGLIST; #adpnum for alarm command
	@morecmds = @ARGLIST;

	
	#will switch null string to empty string
	if(!(defined $cmd2)) { $cmd2 = ""; }
	if(!(defined $cmd3)) { $cmd3 = ""; }
	if(!(defined $cmd4)) { $cmd4 = ""; }

#######

	#set MODE_XXXX variables based on linux environment variables
	my $setting;
	#$setting = `echo LDSTATE_NONAG`;
	$setting = shell_command("echo \$LDSTATE_NONAG");
	if($setting =~ /1/) { $MODE_NONAG = 1; }
	#$setting = `echo LDSTATE_CSV`;
	$setting = shell_command("echo \$LDSTATE_CSV");
	if($setting =~ /1/) { $MODE_CSV = 1; }

	$setting = shell_command("echo \$LDSTATE_INFO_ADP_TEMPLATE");
	if(defined($setting) && $setting ne "\n") { $INFO_ADP_TEMPLATE = trim($setting); }
	$setting = shell_command("echo \$LDSTATE_INFO_LD_TEMPLATE");
	if(defined($setting) && $setting ne "\n") { $INFO_LD_TEMPLATE = trim($setting); }
	$setting = shell_command("echo \$LDSTATE_INFO_PD_TEMPLATE");
	if(defined($setting) && $setting ne "\n") { $INFO_PD_TEMPLATE = trim($setting); }
#	printf "ARGLONGFLAGS\n";
	foreach (@ARGLONGFLAGS) {
#		printf "  $_\n"; 
		if ($_ =~ /csv/i) { $MODE_CSV = 1; }
		if ($_ =~ /hrcsv/i) { $MODE_HRCSV = 1; $MODE_CSV = 0; }
		if ($_ =~ /nonag/i) { $MODE_NONAG = 1; }
		if ($_ =~ /showcalls/i) { $MODE_SHOWCALLS = 1; }
		if ($_ =~ /nofork/i) {
			$SMARTCTL_FORKING = 0;
			$USB_FORKING = 0;
			$MDADM_FORKING = 0;
			$SAS2FLASH_FORKING = 0;
		}
	}

	# Did user request that we restrict what is shown?
	if (($cmd2 =~ /adp/i) || ($cmd2 =~ /^adap/i)) { 
		$SHOW_ADP = 1;
		$SHOW_LD  = 0;
		$SHOW_PD  = 0;
	}

	if (($cmd2 =~ /ld/i) || ($cmd2 =~ /^log/i)) { 
		$SHOW_ADP = 0;
		$SHOW_LD  = 1;
		$SHOW_PD  = 0;
	}

	if (($cmd2 =~ /pd/i) || ($cmd2 =~ /^phy/i)) { 
		$SHOW_ADP = 0;
		$SHOW_LD  = 0;
		$SHOW_PD  = 1;
	}

	#
	# Command handling
	#

	### This is probably depricated and should be removed.
#	if ($cmd eq "viewraw" || $cmd eq "raw") {
#		# We have already shown the user the raw data.  Just exit.
#		exit(0);
#	}

	$rc = 0;

	# Print a 1 line summary of the arrays & their state
	if ($cmd eq "info") {
		get_all_info($cmd);
		cmd_info($cmd,$cmd2);
		goto finished;
	}

	# Dump the contents of the LD array.  Basically, show everything we've collected.
	if ($cmd eq "keyval" || $cmd eq "keyvalue") {
		get_all_info($cmd);
		cmd_keyval($cmd,$cmd2);
		goto finished;
	}

	# Print a Nagios-like summary of the arrays' health
	if ($cmd eq "nagios_health") {
		get_all_info($cmd);
		cmd_nagios_health();
		# This command will have exited already.
		goto finished;
	}

	# Print a ASM-like summary of the arrays' health
	if ($cmd eq "asm_health") {
		get_all_info($cmd);
		cmd_asm_health();
		# This command will have exited already.
		goto finished;
	}

	if ($cmd eq "alarm") {
		get_all_info($cmd);
		cmd_alarm(lc($cmd2), lc($cmd3), $cmd4);
		goto finished;
	}
	
	if ($cmd eq "led") {
		get_all_info($cmd);
		cmd_led(lc($cmd2), lc($cmd3), $cmd4);
		goto finished;
	}

	if ($cmd eq "make_raid") {
		get_all_info($cmd);
		cmd_make_raid(lc($cmd2), lc($cmd3), $cmd4, @morecmds);
		goto finished;
	}

	if ($cmd eq "destroy_raid") {
		get_all_info($cmd);
		cmd_destroy_raid(lc($cmd2), lc($cmd3), $cmd4, @morecmds);
		goto finished;
	}


	# Give a PEC monitoring agent summary of current state (of all logical drives).  
	# This will produce output like:
	#
	# LSI2008,adp0,ld0,ldDegraded
	# LSI2008,adp0,ld1,ldOptimal
	#
	if ($cmd eq "state") {	# Deprecated command name 2012-09-01
		get_all_info($cmd);
		cmd_pecagent_health();
		exit 0;
	}
	if ($cmd eq "pecagent_health") {	# Deprecated command name 2012-09-01
		get_all_info($cmd);
		cmd_pecagent_health();
		goto finished;
	}

	#beta-needs more testing/options (only supports 'ldstate pd_makegood all')
	if ($cmd eq "pd_makegood") {
		get_all_info($cmd);
		cmd_pd_makegood($cmd2, $cmd3, $cmd4);
		goto finished;
	}

	if ($cmd eq "clear_foreign_configs" || $cmd eq "cfc") {
		get_all_info($cmd);
		cmd_clear_foreign_configs($cmd2, $cmd3, $cmd4);
		goto finished;
	}


	if ($cmd eq "assemble_foreign_lds" || $cmd eq "afld") {
		get_all_info($cmd);
		cmd_assemble_foreign_lds($cmd2, $cmd3, $cmd4);
		goto finished;
	}

	if ($cmd eq "listdev") {
		get_all_info($cmd);
		cmd_listdev($cmd2, $cmd3, $cmd4);
		goto finished;
	}


	if ($cmd eq "listpartitions") {
		get_all_info($cmd);
		cmd_listpartitions($cmd2, $cmd3, $cmd4);
		goto finished;
		exit 0;
	}

	if ($cmd eq "wipedrive") {
		get_all_info($cmd);
		cmd_wipedrive($cmd2, $cmd3, $cmd4);
		goto finished;
	}

	printf "ERROR:  invalid command: $cmd\n";
	usage();
	exit 1;

finished:

	if($MODE_SHOWCALLS) {
		#display the count of shell calls and the actual calls made
		print "SYSTEM CALLS MADE = $TOOL_USES\n\nSYSTEM CALLS LIST:\n$TOOL_CALLS\n";
	}

	exit $rc;


