#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#egan@us.ibm.com
#modified by jbjohnso@us.ibm.com
#(C)IBM Corp

package xCAT_plugin::ipmi;
BEGIN
{
  $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings "all";
use xCAT::GlobalDef;
use xCAT_monitoring::monitorctrl;
use xCAT::SPD qw/decode_spd/;
use xCAT::IPMI;
use xCAT::PasswordUtils;
my %needbladeinv;

use POSIX qw(ceil floor);
use Storable qw(nstore_fd retrieve_fd thaw freeze);
use xCAT::Utils;
use xCAT::TableUtils;
use xCAT::IMMUtils;
use xCAT::ServiceNodeUtils;
use xCAT::SvrUtils;
use xCAT::Usage;
use Thread qw(yield);
use LWP 5.64;
use HTTP::Request::Common;
my $iem_support;
my $vpdhash;
my %allerrornodes=();
my $global_sessdata;

eval {
    require IBM::EnergyManager;
    $iem_support=1;
};

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
	ipmicmd
);

sub handled_commands {
  return {
    rpower => 'nodehm:power,mgt', #done
    renergy => 'nodehm:power,mgt',
    getipmicons => 'ipmi', #done
    rspconfig => 'nodehm:mgt', #done
    rspreset => 'nodehm:mgt', #done
    rvitals => 'nodehm:mgt', #done
    rinv => 'nodehm:mgt', #done
    rsetboot => 'nodehm:mgt', #done
    rbeacon => 'nodehm:mgt', #done
    reventlog => 'nodehm:mgt',
    ripmi => 'ipmi',
#    rfrurewrite => 'nodehm:mgt', #deferred, doesn't even work on several models, no one asks about it, keeping it commented for future requests
    getrvidparms => 'nodehm:mgt', #done
    rscan => 'nodehm:mgt', # used to scan the mic cards installed on the target node
  }
}

    
#use Data::Dumper;
use POSIX "WNOHANG";
use IO::Handle;
use IO::Socket;
use IO::Select;
use Class::Struct;
use Digest::MD5 qw(md5);
use POSIX qw(WNOHANG mkfifo strftime);
use Fcntl qw(:flock);


#local to module
my $callback;
my $ipmi_bmcipaddr;
my $timeout;
my $port;
my $debug;
my $ndebug = 0;
my $sock;
my $noclose;
my %sessiondata; #hold per session variables, in preparation for single-process strategy
my %pendingtransactions; #list of peers with callbacks, callback arguments, and timer expiry data
my $ipmiv2=0;
my $authoffset=0;
my $enable_cache="yes";
my $cache_dir = "/var/cache/xcat";
#my $ibmledtab = $ENV{XCATROOT}."/lib/GUMI/ibmleds.tab";
use xCAT::data::ibmleds;
use xCAT::data::ipmigenericevents;
use xCAT::data::ipmisensorevents;
my $cache_version = 4;
my %sdr_caches; #store sdr cachecs in memory indexed such that identical nodes do not hit the disk multiple times

#my $status_noop="XXXno-opXXX";

my %idpxthermprofiles = (
    '0z' => [0x37,0x41,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e],
    '1a' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
    '2b' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
    '3c' => [0x30,0x3c,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
    '4d' => [0x37,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
    '5e' => [0x37,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
    '6f' => [0x35,0x44,0,0,0,0,5,0xa,0x3c,0xa,0xa,0x1e], 
);
my %codes = (
	0x00 => "Command Completed Normal",
	0xC0 => "Node busy, command could not be processed",
	0xC1 => "Invalid or unsupported command",
	0xC2 => "Command invalid for given LUN",
	0xC3 => "Timeout while processing command, response unavailable",
	0xC4 => "Out of space, could not execute command",
	0xC5 => "Reservation canceled or invalid reservation ID",
	0xC6 => "Request data truncated",
	0xC7 => "Request data length invalid",
	0xC8 => "Request data field length limit exceeded",
	0xC9 => "Parameter out of range",
	0xCA => "Cannot return number of requested data bytes",
	0xCB => "Requested Sensor, data, or record not present",
	0xCB => "Not present",
	0xCC => "Invalid data field in Request",
	0xCD => "Command illegal for specified sensor or record type",
	0xCE => "Command response could not be provided",
	0xCF => "Cannot execute duplicated request",
	0xD0 => "Command reqponse could not be provided. SDR Repository in update mode",
	0xD1 => "Command response could not be provided. Device in firmware update mode",
	0xD2 => "Command response could not be provided. BMC initialization or initialization agent in progress",
	0xD3 => "Destination unavailable",
	0xD4 => "Insufficient privilege level",
	0xD5 => "Command or request parameter(s) not supported in present state",
	0xFF => "Unspecified error",
);

#Payload types:
#  0 => IPMI  (format 1 0)
#  1 => SOL 1 0
#  0x10 => rmcp+ open req 1 0
#  0x11 => rmcp+ response 1 0
#  0x12 => rakp1 (all 1 0)
#  0x13 => rakp2
#  0x14 => rakp3
#  0x15 => rakp4
  
my %units = (
	0 => "", #"unspecified",
	1 => "C",
	2 => "F",
	3 => "K",
	4 => "Volts",
	5 => "Amps",
	6 => "Watts",
	7 => "Joules",
	8 => "Coulombs",
	9 => "VA",
	10 => "Nits",
	11 => "lumen",
	12 => "lux",
	13 => "Candela",
	14 => "kPa",
	15 => "PSI",
	16 => "Newton",
	17 => "CFM",
	18 => "RPM",
	19 => "Hz",
	20 => "microsecond",
	21 => "millisecond",
	22 => "second",
	23 => "minute",
	24 => "hour",
	25 => "day",
	26 => "week",
	27 => "mil",
	28 => "inches",
	29 => "feet",
	30 => "cu in",
	31 => "cu feet",
	32 => "mm",
	33 => "cm",
	34 => "m",
	35 => "cu cm",
	36 => "cu m",
	37 => "liters",
	38 => "fluid ounce",
	39 => "radians",
	40 => "steradians",
	41 => "revolutions",
	42 => "cycles",
	43 => "gravities",
	44 => "ounce",
	45 => "pound",
	46 => "ft-lb",
	47 => "oz-in",
	48 => "gauss",
	49 => "gilberts",
	50 => "henry",
	51 => "millihenry",
	52 => "farad",
	53 => "microfarad",
	54 => "ohms",
	55 => "siemens",
	56 => "mole",
	57 => "becquerel",
	58 => "PPM",
	59 => "reserved",
	60 => "Decibels",
	61 => "DbA",
	62 => "DbC",
	63 => "gray",
	64 => "sievert",
	65 => "color temp deg K",
	66 => "bit",
	67 => "kilobit",
	68 => "megabit",
	69 => "gigabit",
	70 => "byte",
	71 => "kilobyte",
	72 => "megabyte",
	73 => "gigabyte",
	74 => "word",
	75 => "dword",
	76 => "qword",
	77 => "line",
	78 => "hit",
	79 => "miss",
	80 => "retry",
	81 => "reset",
	82 => "overflow",
	83 => "underrun",
	84 => "collision",
	85 => "packets",
	86 => "messages",
	87 => "characters",
	88 => "error",
	89 => "correctable error",
	90 => "uncorrectable error",
);

my %chassis_types = (
	0 => "Unspecified",
	1 => "Other",
	2 => "Unknown",
	3 => "Desktop",
	4 => "Low Profile Desktop",
	5 => "Pizza Box",
	6 => "Mini Tower",
	7 => "Tower",
	8 => "Portable",
	9 => "LapTop",
	10 => "Notebook",
	11 => "Hand Held",
	12 => "Docking Station",
	13 => "All in One",
	14 => "Sub Notebook",
	15 => "Space-saving",
	16 => "Lunch Box",
	17 => "Main Server Chassis",
	18 => "Expansion Chassis",
	19 => "SubChassis",
	20 => "Bus Expansion Chassis",
	21 => "Peripheral Chassis",
	22 => "RAID Chassis",
	23 => "Rack Mount Chassis",
);

my %MFG_ID = (
	2 => "IBM",
	343 => "Intel",
	20301 => "IBM",
);

my %PROD_ID = (
	"2:34869" => "e325",
	"2:3" => "x346",
	"2:4" => "x336",
	"343:258" => "Tiger 2",
	"343:256" => "Tiger 4",
);

my $localtrys = 3;
my $localdebug = 0;

struct SDR => {
	rec_type			=> '$',
	sensor_owner_id		=> '$',
	sensor_owner_lun	=> '$',
	sensor_number		=> '$',
	entity_id			=> '$',
	entity_instance		=> '$',
	sensor_init			=> '$',
	sensor_cap			=> '$',
	sensor_type			=> '$',
	event_type_code		=> '$',
	ass_event_mask		=> '@',
	deass_event_mask	=> '@',
	dis_read_mask		=> '@',
	sensor_units_1		=> '$',
	sensor_units_2		=> '$',
	sensor_units_3		=> '$',
	linearization		=> '$',
	M					=> '$',
	tolerance			=> '$',
	B					=> '$',
	accuracy			=> '$',
	accuracy_exp		=> '$',
	R_exp				=> '$',
	B_exp				=> '$',
	analog_char_flag	=> '$',
	nominal_reading		=> '$',
	normal_max			=> '$',
	normal_min			=> '$',
	sensor_max_read		=> '$',
	sensor_min_read		=> '$',
	upper_nr_threshold	=> '$',
	upper_crit_thres	=> '$',
	upper_ncrit_thres	=> '$',
	lower_nr_threshold	=> '$',
	lower_crit_thres	=> '$',
	lower_ncrit_thres	=> '$',
	pos_threshold		=> '$',
	neg_threshold		=> '$',
	id_string_type		=> '$',
	id_string		=> '$',
	#LED id
	led_id		=> '$',
    fru_type  => '$',
    fru_subtype  => '$',
    fru_oem  => '$',
};

struct FRU => {
	rec_type			=> '$',
	desc				=> '$',
	value				=> '$',
};

sub decode_fru_locator { #Handle fru locator records
    my @locator = @_;
	my $sdr = SDR->new();
	$sdr->rec_type(0x11);
    $sdr->sensor_owner_id("FRU");
    $sdr->sensor_owner_lun("FRU");
    $sdr->sensor_number($locator[7]);
    unless ($locator[8] & 0x80 and ($locator[8] & 0x1f) == 0 and $locator[9] == 0) {
        #only logical devices at lun 0 supported for now
        return undef;
    }
    unless (($locator[16] & 0xc0) == 0xc0) { #Only unpacked ASCII for now, no unicode or BCD plus yet
        return undef;
    }
    my $idlen = $locator[16] & 0x3f;
    unless ($idlen > 1) { return undef; }
    $sdr->id_string(pack("C*",@locator[17..17+$idlen-1]));
    $sdr->fru_type($locator[11]);
    $sdr->fru_subtype($locator[12]);
    $sdr->fru_oem($locator[15]);

    return $sdr;
}

sub waitforack {
    my $sock = shift;
    my $select = new IO::Select;
    $select->add($sock);
    my $str;
    if ($select->can_read(60)) { # Continue after 60 seconds, even if not acked...
        if ($str = <$sock>) {
        } else {
           $select->remove($sock); #Block until parent acks data
        }
    }
}
sub translate_sensor {
   my $reading = shift;
   my $sdr = shift;
   my $unitdesc;
   my $value;
   my $lformat;
   my $per;
   $unitdesc = $units{$sdr->sensor_units_2};
   if ($sdr->rec_type == 1) {
    $value = (($sdr->M * $reading) + ($sdr->B * (10**$sdr->B_exp))) * (10**$sdr->R_exp);
   } else {
    $value = $reading;
   }
   if($sdr->rec_type !=1 or $sdr->linearization == 0) {
      $reading = $value;
      if($value == int($value)) {
         $lformat = "%-30s%8d%-20s";
      } else {
         $lformat = "%-30s%8.3f%-20s";
      }
   } elsif($sdr->linearization == 7) {
      if($value > 0) {
         $reading = 1/$value;
      } else {
         $reading = 0;
      }
      $lformat = "%-30s%8d %-20s";
   } else {
      $reading = "RAW($sdr->linearization) $reading";
   }
   if($sdr->sensor_units_1 & 1) {
      $per = "% ";
   } else {
      $per = " ";
   }
   my $numformat = ($sdr->sensor_units_1 & 0b11000000) >> 6;
   if ($numformat) {
     if ($numformat eq 0b11)  {
        #Not sure what to do.. leave it alone for now
     } else {
        if ($reading & 0b10000000) {
          if ($numformat eq 0b01) {
             $reading = 0-((~($reading&0b01111111))&0b1111111);
          } elsif ($numformat eq 0b10) {
             $reading = 0-(((~($reading&0b01111111))&0b1111111)+1);
          }
        }
     }
   }
   if($unitdesc eq "Watts") {
      my $f = ($reading * 3.413);
      $unitdesc = "Watts (" . int($f + .5) . " BTUs/hr)";
      #$f = ($reading * 0.00134);
      #$unitdesc .= " $f horsepower)";
   }
   if($unitdesc eq "C") {
      my $f = ($reading * 9/5) + 32;
      $unitdesc = "C (" . int($f + .5) . " F)";
   }
   if($unitdesc eq "F") {
      my $c = ($reading - 32) * 5/9;
      $unitdesc = "F (" . int($c + .5) . " C)";
   }
   return "$reading $unitdesc";
}

sub ipmicmd {
    my $sessdata = shift;


	my $rc=0;
	my $text="";
	my $error="";
	my @output;
	my $noclose=0;

    $sessdata->{ipmisession}->login(callback=>\&on_bmc_connect,callback_args=>$sessdata);
}

sub on_bmc_connect {
    my $status = shift;
    my $sessdata = shift;
	my $command = $sessdata->{command};
    if ($status =~ /ERROR:/) {
        xCAT::SvrUtils::sendmsg([1,$status],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    #ok, detect some common prereqs here, notably:
    #getdevid
    if ($command eq "getrvidparms") {
        unless (defined $sessdata->{device_id}) {
            $sessdata->{ipmisession}->subcmd(netfn=>6,command=>1,data=>[],callback=>\&gotdevid,callback_args=>$sessdata);
	    return;
        }
        getrvidparms($sessdata);
    }
    #initsdr
    if ($command eq "rinv" or $command eq "reventlog" or $command eq "rvitals") {
        unless (defined $sessdata->{device_id}) {
            $sessdata->{ipmisession}->subcmd(netfn=>6,command=>1,data=>[],callback=>\&gotdevid,callback_args=>$sessdata);
            return;
        }
        unless ($sessdata->{sdr_hash}) {
            initsdr($sessdata);
	    return;
        }
    }
	if($command eq "ping") {
		xCAT::SvrUtils::sendmsg("ping",$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	if ($command eq "rpower") {
        	unless (defined $sessdata->{device_id}) { #need get device id data initted for S3 support
	            $sessdata->{ipmisession}->subcmd(netfn=>6,command=>1,data=>[],callback=>\&gotdevid,callback_args=>$sessdata);
		    return;
	        }
		return power($sessdata);
	} elsif ($command eq "ripmi") {
		return ripmi($sessdata);
	} elsif ($command eq "rspreset") {
        return resetbmc($sessdata);
    } elsif($command eq "rbeacon") {
		return beacon($sessdata);
	} elsif($command eq "rsetboot") {
		return setboot($sessdata);
	} elsif($command eq "rspconfig") {
       shift @{$sessdata->{extraargs}};
       if ($sessdata->{subcommand} =~ /=/) {
           setnetinfo($sessdata);
       } else {
           getnetinfo($sessdata);
       }
	} elsif($command eq "rvitals") {
        vitals($sessdata);
	} elsif($command eq "rinv") {
        inv($sessdata);
    } elsif($command eq "reventlog") {
        eventlog($sessdata);
    } elsif($command eq "renergy") {
        renergy($sessdata);
    }
    return;
    my @output;

    my $rc; #in for testing, evaluated as a TODO
    my $text;
    my $error;
    my $node;
	my $subcommand = "";
	if($command eq "rvitals") {
		($rc,@output) = vitals($subcommand);
	}
	elsif($command eq "renergy") {
		($rc,@output) = renergy($subcommand);
	}
	elsif($command eq "rspreset") {
		($rc,@output) = resetbmc();
		$noclose=1;
	}
	elsif($command eq "reventlog") {
		if($subcommand eq "decodealert") {
			($rc,$text) = decodealert(@_);
		}
		else {
			($rc,@output) = eventlog($subcommand);
		}
	}
	elsif($command eq "rinv") {
		($rc,@output) = inv($subcommand);
	}
	elsif($command eq "fru") {
		($rc,@output) = fru($subcommand);
	}
	elsif($command eq "rgetnetinfo") {
      my @subcommands = ($subcommand);
		if($subcommand eq "all") {
			@subcommands = (
				"ip",
				"netmask",
				"gateway",
				"backupgateway",
				"snmpdest1",
				"snmpdest2",
				"snmpdest3",
				"snmpdest4",
				"community",
			);

			my @coutput;

			foreach(@subcommands) {
				$subcommand = $_;
				($rc,@output) = getnetinfo($subcommand);
				push(@coutput,@output);
			}

			@output = @coutput;
		}
		else {
			($rc,@output) = getnetinfo($subcommand);
		}
	}
	elsif($command eq "generic") {
		($rc,@output) = generic($subcommand);
	}
	elsif($command eq "rfrurewrite") {
		($rc,@output) = writefru($subcommand,shift);
	}
	elsif($command eq "fru") {
		($rc,@output) = fru($subcommand);
	}
	elsif($command eq "rsetboot") {
        	($rc,@output) = setboot($subcommand);
	}

	else {
		$rc = 1;
		$text = "unsupported command $command $subcommand";
	}
	if($debug) {
		print "$node: command completed\n";
	}

	if($text) {
		push(@output,$text);
	}

	return($rc,@output);
}

sub resetbmc {
    my $sessdata = shift;
    $sessdata->{ipmisession}->subcmd(netfn=>6,command=>2,data=>[],callback=>\&resetedbmc,callback_args=>$sessdata);
}
sub resetedbmc {
    my $rsp = shift;
    my $sessdata = shift;
	if ($rsp->{error}) {
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
	} else {
        if ($rsp->{code}) {
            if ($codes{$rsp->{code}}) {
                xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
            } else {
                xCAT::SvrUtils::sendmsg([1,sprintf("Unknown error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
            }
            return;
        } 
        xCAT::SvrUtils::sendmsg("BMC reset",$callback,$sessdata->{node},%allerrornodes);
        $sessdata->{ipmisession} = undef; #throw away now unusable session
	}
}

sub setnetinfo {
    my $sessdata = shift;
	my $subcommand = $sessdata->{subcommand};
   my $argument;
   ($subcommand,$argument) = split(/=/,$subcommand);
	my @input = @_;

	my $netfun = 0x0c;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;
	my $match;
    my $channel_number = $sessdata->{ipmisession}->{currentchannel};

	if($subcommand eq "snmpdest") {
		$subcommand = "snmpdest1";
	}
        

   unless(defined($argument)) { 
      return 0;
   }
   if ($subcommand eq "thermprofile") {
       return idpxthermprofile($argument);
   }
   if ($subcommand eq "alert" and $argument eq "on" or $argument =~ /^en/ or $argument =~ /^enable/) {
      $netfun = 0x4;
      @cmd = (0x12,0x9,0x1,0x18,0x11,0x00);
   } elsif ($subcommand eq "alert" and $argument eq "off" or $argument =~ /^dis/ or $argument =~ /^disable/) {
      $netfun = 0x4;
      @cmd = (0x12,0x9,0x1,0x10,0x11,0x00);
   }
	elsif($subcommand eq "garp") {
		my $halfsec = $argument * 2; #pop(@input) * 2;

		if($halfsec > 255) {
			$halfsec = 255;
		}
		if($halfsec < 4) {
			$halfsec = 4;
		}

		@cmd = (0x01,$channel_number,0x0b,$halfsec);
	}
   elsif($subcommand =~ m/community/ ) {
      my $cindex = 0;
      my @clist;
      foreach (0..17) {
         push @clist,0;
      }
      foreach (split //,$argument)  {
         $clist[$cindex++]=ord($_);
      }
      @cmd = (1,$channel_number,0x10,@clist);
   }
	elsif($subcommand =~ m/snmpdest(\d+)/ ) {
		my $dstip = $argument; #pop(@input);
        $dstip = inet_ntoa(inet_aton($dstip));
		my @dip = split /\./, $dstip;
		@cmd = (0x01,$channel_number,0x13,$1,0x00,0x00,$dip[0],$dip[1],$dip[2],$dip[3],0,0,0,0,0,0);
	}
	#elsif($subcommand eq "alert" ) {
	#    my $action=pop(@input);
            #print "action=$action\n";
        #    $netfun=0x28; #TODO: not right
 
            # mapping alert action to number
        #    my $act_number=8;   
        #    if ($action eq "on") {$act_number=8;}  
        #    elsif ($action eq "off") { $act_number=0;}  
        #    else { return(1,"unsupported alert action $action");}    
	#    @cmd = (0x12, $channel_number,0x09, 0x01, $act_number+16, 0x11,0x00);
	#}
	else {
		return(1,"configuration of $subcommand is not implemented currently");
	}
    my $command = shift @cmd;
    $sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@cmd,callback=>\&netinfo_set,callback_args=>$sessdata);
}
sub netinfo_set {
    my $rsp = shift;
    my $sessdata = shift;
    if ($rsp->{error}) { 
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    if ($rsp->{code}) {
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
        } else {
            xCAT::SvrUtils::sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
        }
        return;
    }
    getnetinfo($sessdata);
    return;
}

sub getnetinfo {
    my $sessdata = shift;
	my $subcommand = $sessdata->{subcommand};
    my $channel_number = $sessdata->{ipmisession}->{currentchannel};
   $subcommand =~ s/=.*//;
   if ($subcommand eq "thermprofile") {
       my $code;
       my @returnd;
       my $thermdata;
       my $netfun=0x2e<<2; #currently combined netfun & lun, to be simplified later
       my @cmd = (0x41,0x4d,0x4f,0x00,0x6f,0xff,0x61,0x00);
       my @bytes;
       my $error = docmd($netfun,\@cmd,\@bytes);
       @bytes=splice @bytes,16;
       my $validprofiles="";
       foreach (keys %idpxthermprofiles) {
           if (sprintf("%02x %02x %02x %02x %02x %02x %02x",@bytes) eq sprintf("%02x %02x %02x %02x %02x %02x %02x",@{$idpxthermprofiles{$_}})) {
               $validprofiles.="$_,";
           }
       }
       if ($validprofiles) {
           chop($validprofiles);
           return (0,"The following thermal profiles are in effect: ".$validprofiles);
       }
       return (1,sprintf("Unable to identify current thermal profile: \"%02x %02x %02x %02x %02x %02x %02x\"",@bytes));
   }

	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;

	if ($subcommand eq "snmpdest") {
		$subcommand = "snmpdest1";
	}

    my $netfun = 0x0c;
   if ($subcommand eq "alert") {
      $netfun = 0x4;
      @cmd = (0x13,9,1,0);
   }
	elsif($subcommand eq "garp") {
		@cmd = (0x02,$channel_number,0x0b,0x00,0x00);
	}
	elsif ($subcommand =~ m/^snmpdest(\d+)/ ) {
		@cmd = (0x02,$channel_number,0x13,$1,0x00);
	}
	elsif ($subcommand eq "ip") {
		@cmd = (0x02,$channel_number,0x03,0x00,0x00);
	}
	elsif ($subcommand eq "netmask") {
		@cmd = (0x02,$channel_number,0x06,0x00,0x00);
	}
	elsif ($subcommand eq "gateway") {
		@cmd = (0x02,$channel_number,0x0C,0x00,0x00);
	}
	elsif ($subcommand eq "backupgateway") {
		@cmd = (0x02,$channel_number,0x0E,0x00,0x00);
	}
	elsif ($subcommand eq "community") {
		@cmd = (0x02,$channel_number,0x10,0x00,0x00);
	}
	else {
		return(1,"unsupported command getnetinfo $subcommand");
	}

    my $command = shift @cmd;
    $sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@cmd,callback=>\&getnetinfo_response,callback_args=>$sessdata);
}
sub getnetinfo_response {
    my $rsp = shift;
    my $sessdata = shift;
    my $subcommand = $sessdata->{subcommand};
    $sessdata->{subcommand} = shift @{$sessdata->{extraargs}};
    if ($rsp->{error}) { 
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    if ($rsp->{code}) {
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
        } else {
            xCAT::SvrUtils::sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
        }
        return;
    }
    if ($subcommand eq "snmpdest") {
        $subcommand = "snmpdest1";
    }
    my $bmcifo="";
    if ($sessdata->{bmcnum} != 1) { $bmcifo.= " on BMC ".$sessdata->{bmcnum}; }
    my @returnd = (0,@{$rsp->{data}});
	my $format = "%-25s";
	if($subcommand eq "garp") {
			my $code = $returnd[2] / 2;
				xCAT::SvrUtils::sendmsg(sprintf("$format %d","Gratuitous ARP seconds:",$code),$callback,$sessdata->{node},%allerrornodes);
	}
    elsif($subcommand eq "alert") {
        if ($returnd[3] & 0x8) { 
           xCAT::SvrUtils::sendmsg("SP Alerting: enabled".$bmcifo,$callback,$sessdata->{node},%allerrornodes);
        } else {
           xCAT::SvrUtils::sendmsg("SP Alerting: disabled".$bmcifo,$callback,$sessdata->{node},%allerrornodes);
        }
     }
	elsif($subcommand =~ m/^snmpdest(\d+)/ ) {
			xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d".$bmcifo,
				"SP SNMP Destination $1:",
				$returnd[5],
				$returnd[6],
				$returnd[7],
				$returnd[8]),$callback,$sessdata->{node},%allerrornodes);
	} elsif($subcommand eq "ip") {
			xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d".$bmcifo,
				"BMC IP:",
				$returnd[2],
				$returnd[3],
				$returnd[4],
				$returnd[5]),$callback,$sessdata->{node},%allerrornodes);
	} elsif($subcommand eq "netmask") {
			xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d".$bmcifo,
				"BMC Netmask:",
				$returnd[2],
				$returnd[3],
				$returnd[4],
				$returnd[5]),$callback,$sessdata->{node},%allerrornodes);
	} elsif($subcommand eq "gateway") {
			xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d".$bmcifo,
				"BMC Gateway:",
				$returnd[2],
				$returnd[3],
				$returnd[4],
				$returnd[5]),$callback,$sessdata->{node},%allerrornodes);
	} elsif($subcommand eq "backupgateway") {
			xCAT::SvrUtils::sendmsg(sprintf("$format %d.%d.%d.%d".$bmcifo,
				"BMC Backup Gateway:",
				$returnd[2],
				$returnd[3],
				$returnd[4],
				$returnd[5]),$callback,$sessdata->{node},%allerrornodes);
	} elsif ($subcommand eq "community") {
			my $text = sprintf("$format ","SP SNMP Community:");
			my $l = 2;
			while ($returnd[$l] ne 0) {
				$l = $l + 1;
			}
			my $i=2;
			while ($i<$l) {
				$text = $text . sprintf("%c",$returnd[$i]);
				$i = $i + 1;
			}
			$text.=$bmcifo;
            xCAT::SvrUtils::sendmsg($text,$callback,$sessdata->{node},%allerrornodes);
	}
    if ($sessdata->{subcommand}) {
        if ($sessdata->{subcommand} =~ /=/) {
            setnetinfo($sessdata);
        } else {
            getnetinfo($sessdata);
        }
    }
    return;
}

sub setboot {
    my $sessdata = shift;
    #This disables the 60 second timer
    $sessdata->{ipmisession}->subcmd(netfn=>0,command=>8,data=>[3,8],callback=>\&setboot_timerdisabled,callback_args=>$sessdata);
}
sub setboot_timerdisabled {
    my $rsp = shift;
    my $sessdata = shift;
    if ($rsp->{error}) { 
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    if ($rsp->{code}) {
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
            return;
        } elsif ($rsp->{code} == 0x80) {
            xCAT::SvrUtils::sendmsg("Unable to disable countdown timer, boot device may revert in 60 seconds",$callback,$sessdata->{node},%allerrornodes);
        } else {
            xCAT::SvrUtils::sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
            return;
        }
    }
    my $error;
    @ARGV=@{$sessdata->{extraargs}};
    my $persistent=0;
    my $uefi=0;
    use Getopt::Long;
    unless(GetOptions(
        'p' => \$persistent,
        'u' => \$uefi,
        )) {
        xCAT::SvrUtils::sendmsg([1,"Error parsing arguments"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    my $subcommand=shift @ARGV;

    my @cmd;
    my $overbootflags=0x80 | $persistent<<6|$uefi << 5;
    if ($subcommand eq "net") {
        @cmd=(0x5,$overbootflags,0x4,0x0,0x0,0x0);
    }
    elsif ($subcommand eq "hd" ) {
        @cmd=(0x5,$overbootflags,0x8,0x0,0x0,0x0);
    }
    elsif ($subcommand eq "cd" ) {
        @cmd=(0x5,$overbootflags,0x14,0x0,0x0,0x0);
    }
    elsif ($subcommand eq "floppy" ) {
        @cmd=(0x5,$overbootflags,0x3c,0x0,0x0,0x0);
    }
    elsif ($subcommand =~ m/^def/) {
        @cmd=(0x5,0x0,0x0,0x0,0x0,0x0);
    }
    elsif ($subcommand eq "setup" ) { #Not supported by BMCs I've checked so far..
        @cmd=(0x5,$overbootflags,0x18,0x0,0x0,0x0);
    }
    elsif ($subcommand =~ m/^stat/) {
        setboot_stat("NOQUERY",$sessdata);
        return;
    }
    else {
        xCAT::SvrUtils::sendmsg([1,"unsupported command setboot $subcommand"],$callback,$sessdata->{node},%allerrornodes);
    }
    $sessdata->{ipmisession}->subcmd(netfn=>0,command=>8,data=>\@cmd,callback=>\&setboot_stat,callback_args=>$sessdata);
}
sub setboot_stat {
    my $rsp = shift;
    my $sessdata = shift;
    if (ref $rsp) {
        if ($rsp->{error}) { xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes); }
        elsif ($rsp->{code}) {
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
        } else {
            xCAT::SvrUtils::sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
        }
        return;
        }
    }
    $sessdata->{ipmisession}->subcmd(netfn=>0,command=>9,data=>[5,0,0],callback=>\&setboot_gotstat,callback_args=>$sessdata);
}
sub setboot_gotstat {
    my $rsp = shift;
    my $sessdata = shift;
    if ($rsp->{error}) { xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes); }
    elsif ($rsp->{code}) {
    if ($codes{$rsp->{code}}) {
        xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
    } else {
        xCAT::SvrUtils::sendmsg([1,sprintf("Unknown ipmi error %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
    }
    return;
    }
    my %bootchoices = (
        0 => 'BIOS default',
        1 => 'Network',
        2 => 'Hard Drive',
        5 => 'CD/DVD',
        6 => 'BIOS Setup',
        15 => 'Floppy'
    );
    my @returnd = ($rsp->{code},@{$rsp->{data}});
    unless ($returnd[3] & 0x80) {
        xCAT::SvrUtils::sendmsg("boot override inactive",$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    my $boot=($returnd[4] & 0x3C) >> 2;
    xCAT::SvrUtils::sendmsg($bootchoices{$boot},$callback,$sessdata->{node},%allerrornodes);
    return;
}

sub idpxthermprofile {
    #iDataplex thermal profiles as of 6/10/2008
    my $subcommand = lc(shift);
    my @returnd;
    my $netfun = 0xb8;
    my @cmd = (0x41,0x4d,0x4f,0x00,0x6f,0xfe,0x60,0,0,0,0,0,0,0,0xff);
    if ($idpxthermprofiles{$subcommand}) {
        push @cmd,@{$idpxthermprofiles{$subcommand}};
    } else {
        return (1,"Not an understood thermal profile, expected a 2 hex digit value corresponding to chassis label on iDataplex server");
    }
    docmd(
        $netfun,
        \@cmd,
        \@returnd
    );
    return (0,"OK");
}


sub getrvidparms {
    my $sessdata = shift;
    unless ($sessdata) { die "not fixed yet" }
#check devide id
    if ($sessdata->{mfg_id} == 20301 and $sessdata->{prod_id} == 220) {
        my $browser = LWP::UserAgent->new();
        my $message = "WEBVAR_USERNAME=".$sessdata->{ipmisession}->{userid}."&WEBVAR_PASSWORD=".$sessdata->{ipmisession}->{password};
        $browser->cookie_jar({});
        my $baseurl = "https://".$sessdata->{ipmisession}->{bmc}."/";
        my $response = $browser->request(GET $baseurl."rpc/WEBSES/validate.asp");
        $response = $browser->request(POST $baseurl."rpc/WEBSES/create.asp",'Content-Type'=>"application/x-www-form-urlencoded",Content=>$message);
        $response = $response->content;
        if ($response and $response =~ /SESSION_COOKIE' : '([^']*)'/) {
            foreach (keys  %{$browser->cookie_jar->{COOKIES}}) {
                $browser->cookie_jar()->set_cookie(1,"SessionCookie",$1,"/",$_);
            }
        }
        $response = $browser->request(GET $baseurl."/Java/jviewer.jnlp?ext_ip=".$sessdata->{ipmisession}->{bmc});
        $response = $response->content;
        xCAT::SvrUtils::sendmsg("method:imm",$callback,$sessdata->{node},%allerrornodes);
        xCAT::SvrUtils::sendmsg("jnlp:$response",$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    unless ($sessdata->{mfg_id} == 2 or $sessdata->{mfg_id} == 20301) { #Only implemented for IBM servers
        xCAT::SvrUtils::sendmsg([1,"Remote video is not supported on this system"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    #TODO: use get bmc capabilities to see if rvid is actually supported before bothering the client java app
    $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x50,data=>[],callback=>\&getrvidparms_with_buildid,callback_args=>$sessdata);
}
sub check_rsp_errors { #TODO: pass in command-specfic error code translation table
    my $rsp = shift;
    my $sessdata = shift;
	if($rsp->{error}) { #non ipmi error
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
        return 1;
	}
    if ($rsp->{code}) { #ipmi error
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback,$sessdata->{node},%allerrornodes);
        } else {
             xCAT::SvrUtils::sendmsg([1,sprintf("Unknown error code %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
        }
        return 1;
    }
    return 0;
}
sub getrvidparms_imm2 {
	my $rsp = shift;
	my $sessdata = shift;
    #wvid should be a possiblity, time to do the http...
    $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; #TODO: for standalone IMMs, automate CSR retrieval and granting at setup itme
					    #for flex, grab the CA from each CMM and store in a way accessible to this command
					    #for now, accept the MITM risk no worse than http, the intent being feature parity
					    #with http: for envs with https only, like Flex
    my $browser = LWP::UserAgent->new();
	if ($sessdata->{ipmisession}->{bmc} =~ /^fe80/ and $sessdata->{ipmisession}->{bmc} =~ /%/) {
        xCAT::SvrUtils::sendmsg ([1,"wvid not supported with IPv6 LLA addressing mode"],$callback,$sessdata->{node},%allerrornodes);
        return;
	}
	my $host = $sessdata->{ipmisession}->{bmc};
        my $ip6mode=0;
	if ($host =~ /:/) { $ip6mode=1; $host = "[".$host."]"; }
    my $message = "user=".$sessdata->{ipmisession}->{userid}."&password=".$sessdata->{ipmisession}->{password}."&SessionTimeout=1200";
    $browser->cookie_jar({});
    my $httpport=443;
    my $baseurl = "https://$host/";
    my $response = $browser->request(POST $baseurl."data/login",Referer=>"https://$host/designs/imm/index.php",'Content-Type'=>"application/x-www-form-urlencoded",Content=>$message);
    if ($response->code == 500) {
	$httpport=80;
       $baseurl = "http://$host/";
       $response = $browser->request(POST $baseurl."data/login",Referer=>"http://$host/designs/imm/index.php",'Content-Type'=>"application/x-www-form-urlencoded",Content=>$message);
    }
    my $sessionid;
    unless ($response->content =~ /\"ok\"?(.*)/ and $response->content =~ /\"authResult\":\"0\"/) {
        xCAT::SvrUtils::sendmsg ([1,"Server returned unexpected data"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    $response = $browser->request(GET $baseurl."/designs/imm/remote-control.php");
    if ($response->content =~ /isRPInstalled\s*=\s*'0'/) {
        xCAT::SvrUtils::sendmsg ([1,"Node does not have feature key for remote video"],$callback,$sessdata->{node},%allerrornodes);
    	$response = $browser->request(GET $baseurl."data/logout");
	return;
    }
    $response = $browser->request(GET $baseurl."designs/imm/viewer(".$sessdata->{ipmisession}->{bmc}.'@'.$ip6mode.'@'.time().'@1@0@1@jnlp)');
     #arguments are host, then ipv6 or not, then timestamp, then whether to encrypte or not, singleusermode, finally 'notwin32'
    my $jnlp = $response->content;
    unless ($jnlp) { #ok, might be the newer syntax...
    	$response = $browser->request(GET $baseurl."designs/imm/viewer(".$sessdata->{ipmisession}->{bmc}.'@'.$httpport.'@'.$ip6mode.'@'.time().'@1@0@1@jnlp'.'@USERID@0@0@0@0'.')');
     	#arguments are host, then ipv6 or not, then timestamp, then whether to encrypte or not, singleusermode, finally 'notwin32'
    	$jnlp = $response->content;
    }
    $response = $browser->request(GET $baseurl."data/logout");
    my $currnode = $sessdata->{node};
    $jnlp =~ s!argument>title=.*Video Viewer</argument>!argument>title=$currnode wvid</argument>!;
    xCAT::SvrUtils::sendmsg("method:imm",$callback,$sessdata->{node},%allerrornodes);
    xCAT::SvrUtils::sendmsg("jnlp:$jnlp",$callback,$sessdata->{node},%allerrornodes);
}

sub getrvidparms_with_buildid {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
    my @build_id = (0,@{$rsp->{data}});
    if ($build_id[1]==0x31 and $build_id[2]==0x41 and $build_id[3]==0x4f and $build_id[4]==0x4f) { #Only know how to cope with yuoo builds
       return getrvidparms_imm2($rsp,$sessdata);
    }

    unless ($build_id[1]==0x59 and $build_id[2]==0x55 and $build_id[3]==0x4f and $build_id[4]==0x4f) { #Only know how to cope with yuoo builds
        xCAT::SvrUtils::sendmsg([1,"Remote video is not supported on this system"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    #wvid should be a possiblity, time to do the http...
    my $browser = LWP::UserAgent->new();
    my $message = $sessdata->{ipmisession}->{userid}.",".$sessdata->{ipmisession}->{password};
    $browser->cookie_jar({});
    my $baseurl = "http://".$sessdata->{ipmisession}->{bmc}."/";
    my $response = $browser->request(POST $baseurl."/session/create",'Content-Type'=>"text/xml",Content=>$message);
    my $sessionid;
    if ($response->content =~ /^ok:?(.*)/) {
        $sessionid=$1;
    } else {
        xCAT::SvrUtils::sendmsg ([1,"Server returned unexpected data"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }

    $response = $browser->request(GET $baseurl."/page/session.html"); #we don't care, but some firmware is confused if we don't
    if ($sessionid) {
        $response = $browser->request(GET $baseurl."/kvm/kvm/jnlp?session_id=$sessionid");
    } else {
        $response = $browser->request(GET $baseurl."/kvm/kvm/jnlp");
    }
    my $jnlp = $response->content;
    if ($jnlp =~ /This advanced option requires the purchase and installation/) {
        xCAT::SvrUtils::sendmsg ([1,"Node does not have feature key for remote video"],$callback,$sessdata->{node},%allerrornodes);
	return;
    }
    my $currnode = $sessdata->{node};
    $jnlp =~ s!argument>title=.*Video Viewer</argument>!argument>title=$currnode wvid</argument>!;
    xCAT::SvrUtils::sendmsg("method:imm",$callback,$sessdata->{node},%allerrornodes);
    xCAT::SvrUtils::sendmsg("jnlp:$jnlp",$callback,$sessdata->{node},%allerrornodes);
    my @cmdargv = @{$sessdata->{extraargs}};
    if (grep /-m/,@cmdargv) {
        if ($sessionid) {
            $response = $browser->request(GET $baseurl."/kvm/vm/jnlp?session_id=$sessionid");
        } else {
            $response = $browser->request(GET $baseurl."/kvm/vm/jnlp");
        }
        xCAT::SvrUtils::sendmsg("mediajnlp:".$response->content,$callback,$sessdata->{node},%allerrornodes);;
    }
    return;
}


sub ripmi { #implement generic raw ipmi commands
	my $sessdata = shift;
	my $netfun = hex(shift @{$sessdata->{extraargs}});
	my $command = hex(shift @{$sessdata->{extraargs}});
	my @data;
	foreach (@{$sessdata->{extraargs}}) {
		push @data,hex($_);
	}
	$sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@data,callback=>\&ripmi_callback,callback_args=>$sessdata);
}

sub ripmi_callback {
    if (check_rsp_errors(@_)) {
        return;
    }
        my $rsp = shift;
	my $sessdata = shift;
	if ($rsp->{error}) {
		xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	my $output=sprintf("%02X "x(scalar(@{$rsp->{data}})),@{$rsp->{data}});
	xCAT::SvrUtils::sendmsg($output,$callback,$sessdata->{node},%allerrornodes);
}
	
sub power {
	my $sessdata = shift;

	my $netfun = 0x00;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;
        if (not $sessdata->{acpistate} and $sessdata->{mfg_id} == 20301) { #Only implemented for IBM servers
		$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,data=>[1],callback=>\&power_with_acpi,callback_args=>$sessdata);
	} else {
		$sessdata->{ipmisession}->subcmd(netfn=>0,command=>1,data=>[],callback=>\&power_with_context,callback_args=>$sessdata);
	}
}
sub power_with_acpi {
	my $rsp = shift;
	my $sessdata = shift;
	if ($rsp->{error}) {
		xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	if ($rsp->{code} == 0) {
		if ($rsp->{data}->[0] == 3) {
			$sessdata->{acpistate} = "suspend";
		}
	}
		#unless ($text) { $text = sprintf("Unknown error code %02xh",$rsp->{code}); }
		#xCAT::SvrUtils::sendmsg([1,$text],$callback,$sessdata->{node},%allerrornodes);
	#}
	$sessdata->{ipmisession}->subcmd(netfn=>0,command=>1,data=>[],callback=>\&power_with_context,callback_args=>$sessdata);
}
sub power_with_context {
	my $rsp = shift;
	my $sessdata = shift;
	my $text="";
	if ($rsp->{error}) {
		xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	if ($rsp->{code} != 0) {
		$text = $codes{$rsp->{code}};
		unless ($text) { $text = sprintf("Unknown error code %02xh",$rsp->{code}); }
		xCAT::SvrUtils::sendmsg([1,$text],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	$sessdata->{powerstatus} = ($rsp->{data}->[0] & 1 ? "on" : "off");
	my $reportstate;
	if ($sessdata->{acpistate}) {
		$reportstate = $sessdata->{acpistate};
	} else {
		$reportstate = $sessdata->{powerstatus};
	}
		
	if ($sessdata->{subcommand} eq "stat" or $sessdata->{subcommand} eq "state" or $sessdata->{subcommand} eq "status") { 
        if ($sessdata->{powerstatprefix}) {
		    xCAT::SvrUtils::sendmsg($sessdata->{powerstatprefix}.$reportstate,$callback,$sessdata->{node},%allerrornodes);
        } else {
		    xCAT::SvrUtils::sendmsg($reportstate,$callback,$sessdata->{node},%allerrornodes);
        }
        if ($sessdata->{sensorstoread} and scalar @{$sessdata->{sensorstoread}}) { #if we are in an rvitals path, hook back into good graces
            $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
            readsensor($sessdata); #next
        }
		return;
	}
	my $subcommand = $sessdata->{subcommand};
	if ($sessdata->{subcommand} eq "boot") {
		$text = $sessdata->{powerstatus}. " ";
		$subcommand = ($sessdata->{powerstatus} eq "on" ? "reset" : "on");
		$sessdata->{subcommand}=$subcommand; #lazy typing..
	}
	my %argmap = ( #english to ipmi dictionary
		"on" => 1,
		"off" => 0,
		"softoff" => 5,
		"reset" => 3,
		"nmi" => 4
		);
	if($subcommand eq "on") {
		if ($sessdata->{powerstatus} eq "on") {
			if ($sessdata->{acpistate} and $sessdata->{acpistate} eq "suspend") { #ok, make this a wake
				$sessdata->{subcommand}="wake";
				$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,data=>[0,0],callback=>\&power_response,callback_args=>$sessdata);
				return;
			} 
			xCAT::SvrUtils::sendmsg("on",$callback,$sessdata->{node},%allerrornodes);
            $allerrornodes{$sessdata->{node}}=1;
			return; # don't bother sending command
		}
	} elsif ($subcommand eq "softoff" or $subcommand eq "off" or $subcommand eq "reset") {
		if ($sessdata->{powerstatus} eq "off") {
			xCAT::SvrUtils::sendmsg("off",$callback,$sessdata->{node},%allerrornodes);
            $allerrornodes{$sessdata->{node}}=1;
			return;
		}
	} elsif ($subcommand eq "suspend") {
		my $waitforsuspend;
		my $failtopowerdown;
		my $failtoreset;
		if ($sessdata->{powerstatus} eq "off") {
		        xCAT::SvrUtils::sendmsg([1,"System is off, cannot be suspended"],$callback,$sessdata->{node},%allerrornodes);
		        return;
		}
			
		if (@{$sessdata->{extraargs}} > 1) {
    			@ARGV=@{$sessdata->{extraargs}};
    			use Getopt::Long;
			    unless(GetOptions(
			        'w:i' => \$waitforsuspend,
				'o' => \$failtopowerdown,
				'r' => \$failtoreset,
		        )) {
		        xCAT::SvrUtils::sendmsg([1,"Error parsing arguments"],$callback,$sessdata->{node},%allerrornodes);
		        return;
		    }
		}
		if (defined $waitforsuspend) {
			if ($waitforsuspend == 0) { $waitforsuspend=30; }
			$sessdata->{waitforsuspend}=time()+$waitforsuspend;
		}
		$sessdata->{failtopowerdown}=$failtopowerdown;
		$sessdata->{failtoreset}=$failtoreset;
		$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,data=>[0,3],callback=>\&power_response,callback_args=>$sessdata);
		return;
	} elsif ($subcommand eq "wake") {
		$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,data=>[0,0],callback=>\&power_response,callback_args=>$sessdata);
	} elsif (not $argmap{$subcommand}) {
		xCAT::SvrUtils::sendmsg([1,"unsupported command power $subcommand"],$callback);
		return;
	}

	$sessdata->{ipmisession}->subcmd(netfn=>0,command=>2,data=>[$argmap{$subcommand}],callback=>\&power_response,callback_args=>$sessdata);
}
sub power_response { 
	my $rsp = shift;
	my $sessdata = shift;
	if($rsp->{error}) {
		xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	my @returnd = ($rsp->{code},@{$rsp->{data}});
	if ($rsp->{code}) {
		my $text = $codes{$rsp->{code}};
		unless ($text) { $text = sprintf("Unknown response %02xh",$rsp->{code}); }
		xCAT::SvrUtils::sendmsg([1,$text],$callback,$sessdata->{node},%allerrornodes);
	}
	if ($sessdata->{waitforsuspend}) { #have to repeatedly power stat until happy or timeout exceeded
		$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,data=>[1],callback=>\&power_wait_for_suspend,callback_args=>$sessdata);
		return;
	}
	xCAT::SvrUtils::sendmsg($sessdata->{subcommand},$callback,$sessdata->{node},%allerrornodes);
}

sub power_wait_for_suspend {
	my $rsp = shift;
	my $sessdata = shift;
	if ($rsp->{error}) {
		xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
		return;
	}
	if ($rsp->{code} == 0) {
		if ($rsp->{data}->[0] == 3) {
			$sessdata->{acpistate} = "suspend";
		}
	}
	if ($sessdata->{acpistate} eq "suspend") {
		xCAT::SvrUtils::sendmsg("suspend",$callback,$sessdata->{node},%allerrornodes);
	} elsif ($sessdata->{waitforsuspend} <= time()) {
		delete $sessdata->{waitforsuspend};
		if ($sessdata->{failtopowerdown}) {
			$sessdata->{subcommand}='off',
			xCAT::SvrUtils::sendmsg([1,"Failed to enter suspend state, forcing off"],$callback,$sessdata->{node},%allerrornodes);
			power($sessdata);
		} elsif ($sessdata->{failtoreset}) {
			$sessdata->{subcommand}='reset',
			xCAT::SvrUtils::sendmsg([1,"Failed to enter suspend state, forcing reset"],$callback,$sessdata->{node},%allerrornodes);
			power($sessdata);
		} else {
			xCAT::SvrUtils::sendmsg([1,"Failed to enter suspend state"],$callback,$sessdata->{node},%allerrornodes);
		}
	} else {
		$sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x1d,delayxmit=>5,data=>[1],callback=>\&power_wait_for_suspend,callback_args=>$sessdata);
	}
}

sub generic {
	my $subcommand = shift;
	my $netfun;
	my @args;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;

	($netfun,@args) = split(/-/,$subcommand);

	$netfun=oct($netfun);
	printf("netfun:  0x%02x\n",$netfun);

	print "command: ";
	foreach(@args) {
		push(@cmd,oct($_));
		printf("0x%02x ",oct($_));
	}
	print "\n\n";

	$error = docmd(
		$netfun,
		\@cmd,
		\@returnd
	);

	if($error) {
		$rc = 1;
		$text = $error;
	}

	$code = $returnd[0];

	if($code == 0x00) {
	}
	else {
		$rc = 1;
		$text = $codes{$code};
	}

	printf("return code: 0x%02x\n\n",$code);

	print "return data:\n";
	my @rdata = @returnd[1..@returnd-2]; 
	hexadump(\@rdata);
	print "\n";

	print "full output:\n";
	hexadump(\@returnd);
	print "\n";

#	if(!$text) {
#		$rc = 1;
#		$text = sprintf("unknown response %02x",$code);
#	}

	return($rc,$text);
}

sub beacon {
    my $sessdata = shift;
	my $subcommand = $sessdata->{subcommand};
    my $ipmiv2=0;
    if ($sessdata->{ipmisession}->{ipmiversion} eq '2.0') {
        $ipmiv2 = 1;
    }
	if($subcommand ne "on" and $subcommand ne "off"){
                xCAT::SvrUtils::sendmsg([1,"please specify on or off for ipmi nodes (stat impossible)"],$callback,$sessdata->{node},%allerrornodes);
     }

    #if stuck with 1.5, say light for 255 seconds.  In 2.0, specify to turn it on forever
	if($subcommand eq "on") {
        if ($ipmiv2) {
            $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0,1],callback=>\&beacon_answer,callback_args=>$sessdata);
        } else {
            $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0xff],callback=>\&beacon_answer,callback_args=>$sessdata);
        }
	} 
	elsif($subcommand eq "off") {
        if ($ipmiv2) {
            $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0,0],callback=>\&beacon_answer,callback_args=>$sessdata);
        } else {
            $sessdata->{ipmisession}->subcmd(netfn=>0,command=>4,data=>[0x0],callback=>\&beacon_answer,callback_args=>$sessdata);
        }
	}
	else {
        return;
	}
}
sub beacon_answer {
    my $rsp = shift;
    my $sessdata = shift;

	if($rsp->{error}) { #non ipmi error
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
        return;
	}
    if ($rsp->{code}) { #ipmi error
        if ($codes{$rsp->{code}}) {
            xCAT::SvrUtils::sendmsg([1,$codes{$rsp->{code}}],$callback);
        } else {
             xCAT::SvrUtils::sendmsg([1,sprintf("Unknown error code %02xh",$rsp->{code})],$callback,$sessdata->{node},%allerrornodes);
        }
        return;
    }
    xCAT::SvrUtils::sendmsg($sessdata->{subcommand},$callback,$sessdata->{node},%allerrornodes);
}

sub inv {
    my $sessdata = shift;
	my $subcommand = $sessdata->{subcommand};

	my $rc = 0;
	my $text;
	my @output;
	my @types;


    unless ($subcommand) {
        $subcommand = "all";
    }
	if($subcommand eq "all") {
		@types = qw(model serial deviceid mprom guid misc hw asset firmware mac wwn);
	}
	elsif($subcommand eq "asset") {
        $sessdata->{skipotherfru}=1;
		@types = qw(asset);
	}
	elsif($subcommand eq "model") {
        $sessdata->{skipotherfru}=1;
		@types = qw(model);
	}
	elsif($subcommand eq "serial") {
        $sessdata->{skipotherfru}=1;
		@types = qw(serial);
	}
	elsif($subcommand eq "vpd") {
        $sessdata->{skipotherfru}=1;
		@types = qw(model serial deviceid mprom);
	}
	elsif($subcommand eq "mprom") {
        $sessdata->{skipfru}=1; #full fru read is expensive, skip it
		@types = qw(mprom);
	}
	elsif($subcommand eq "misc") {
        $sessdata->{skipotherfru}=1;
		@types = qw(misc);
	}
	elsif($subcommand eq "deviceid") {
        $sessdata->{skipfru}=1; #full fru read is expensive, skip it
		@types = qw(deviceid);
	}
	elsif($subcommand eq "guid") {
        $sessdata->{skipfru}=1; #full fru read is expensive, skip it
		@types = qw(guid);
	}
	elsif($subcommand eq "uuid") {
        $sessdata->{skipfru}=1; #full fru read is expensive, skip it
		@types = qw(guid);
	}
	else {
        @types = ($subcommand);
		#return(1,"unsupported BMC inv argument $subcommand");
	}
    $sessdata->{invtypes} = \@types;
	initfru($sessdata);
}
sub fru_initted {
    my $sessdata = shift;
	my $key;
    my @types = @{$sessdata->{invtypes}};
	my $format = "%-20s %s";

	foreach $key (sort keys %{$sessdata->{fru_hash}}) {
		my $fru = $sessdata->{fru_hash}->{$key};
        my $type;
        foreach $type (split /,/,$fru->rec_type) {
    		if(grep {$_ eq $type} @types) {
			my $bmcifo="";
			if ($sessdata->{bmcnum} != 1) { 
				$bmcifo=" on BMC ".$sessdata->{bmcnum};
			}
    			xCAT::SvrUtils::sendmsg(sprintf($format.$bmcifo,$sessdata->{fru_hash}->{$key}->desc . ":",$sessdata->{fru_hash}->{$key}->value),$callback,$sessdata->{node},%allerrornodes);
                last;
            }
        }
	}
	if ($sessdata->{isite} and (grep {$_ eq "mac"} @types)) {
		$needbladeinv{$_}="mac";
        }
}

sub add_textual_fru {
    my $parsedfru = shift;
    my $description = shift;
    my $category = shift;
    my $subcategory = shift;
    my $types = shift;
    my $sessdata = shift;
    my %args = @_;

    if ($parsedfru->{$category} and $parsedfru->{$category}->{$subcategory}) {
        my $fru;
        my @subfrus;

        if (ref $parsedfru->{$category}->{$subcategory} eq 'ARRAY') {
            @subfrus = @{$parsedfru->{$category}->{$subcategory}};
        } else {
            @subfrus = ($parsedfru->{$category}->{$subcategory})
        }
	my $index=0;
        foreach (@subfrus) {
	    $index++;
            $fru = FRU->new();
            $fru->rec_type($types);
	    if ($args{addnumber}) {  
            $fru->desc($description." ".$index);
		} else {
            $fru->desc($description);
		}
            if (not ref $_) {
                $fru->value($_);
            } else {
                if ($_->{encoding} == 3) {
                    $fru->value($_->{value});
                } else {
                    $fru->value(phex($_->{value}));
                }
                    
            }
            $sessdata->{fru_hash}->{$sessdata->{frudex}} = $fru;
            $sessdata->{frudex} += 1;
        }
    }
}
sub add_textual_frus {
    my $parsedfru = shift;
    my $desc = shift;
    my $categorydesc = shift;
    my $category = shift;
    my $type = shift;
    my $sessdata = shift;
    unless ($type) { $type = 'hw'; }
    add_textual_fru($parsedfru,$desc." ".$categorydesc."Part Number",$category,"partnumber",$type,$sessdata);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacturer",$category,"manufacturer",$type,$sessdata);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."Serial Number",$category,"serialnumber",$type,$sessdata);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."FRU Number",$category,"frunum",$type,$sessdata);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."Version",$category,"version",$type,$sessdata);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."MAC Address",$category,"macaddrs","mac",$sessdata,addnumber=>1);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."WWN",$category,"wwns","wwn",$sessdata,addnumber=>1);
    add_textual_fru($parsedfru,$desc." ".$categorydesc."",$category,"name",$type,$sessdata);
    if ($parsedfru->{$category}->{builddate}) {
        add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacture Date",$category,"builddate",$type,$sessdata);
    }
    if ($parsedfru->{$category}->{buildlocation}) {
        add_textual_fru($parsedfru,$desc." ".$categorydesc."Manufacture Location",$category,"buildlocation",$type,$sessdata);
    }
    if ($parsedfru->{$category}->{model})  {
        add_textual_fru($parsedfru,$desc." ".$categorydesc."Model",$category,"model",$type,$sessdata);
    }
    add_textual_fru($parsedfru,$desc." ".$categorydesc."Additional Info",$category,"extra",$type,$sessdata);
}

sub initfru {
	my $netfun = 0x28;
    my $sessdata = shift;
    $sessdata->{fru_hash} = {};

    my $mfg_id = $sessdata->{mfg_id};
    my $prod_id = $sessdata->{prod_id};
	my $device_id=$sessdata->{device_id};

	my $fru = FRU->new();
	$fru->rec_type("deviceid");
	$fru->desc("Manufacturer ID");
	my $value = $mfg_id;
	if($MFG_ID{$mfg_id}) {
		$value = "$MFG_ID{$mfg_id} ($mfg_id)";
	}
	$fru->value($value);
	$sessdata->{fru_hash}->{mfg_id} = $fru;

	$fru = FRU->new();
	$fru->rec_type("deviceid");
	$fru->desc("Product ID");
	$value = $prod_id;
	my $tmp = "$mfg_id:$prod_id";
	if($PROD_ID{$tmp}) {
		$value = "$PROD_ID{$tmp} ($prod_id)";
	}
	$fru->value($value);
	$sessdata->{fru_hash}->{prod_id} = $fru;

	$fru = FRU->new();
	$fru->rec_type("deviceid");
	$fru->desc("Device ID");
	$fru->value($device_id);
	$sessdata->{fru_hash}->{device_id} = $fru;

    $sessdata->{ipmisession}->subcmd(netfn=>0x6,command=>0x37,data=>[],callback=>\&gotguid,callback_args=>$sessdata);
}
sub got_bmc_fw_info {
    my $rsp = shift;
    my $sessdata = shift;
	my $fw_rev1=$sessdata->{firmware_rev1};
    my $fw_rev2=$sessdata->{firmware_rev2};
    my $mprom;
    my $isanimm=0;
    if (ref $rsp and not $rsp->{error} and not $rsp->{code}) { #I am a callback and the command worked
        my @returnd = (@{$rsp->{data}});
			my @a = ($fw_rev2);
            my $prefix = pack("C*",@returnd[0..3]);
            if ($prefix =~ /yuoo/i or $prefix =~ /1aoo/i) { #we have an imm
                $isanimm=1;
            }
			$mprom = sprintf("%d.%s (%s)",$fw_rev1,decodebcd(\@a),getascii(@returnd));
	} else { #either not a callback or IBM call failed
		my @a = ($fw_rev2);
		$mprom = sprintf("%d.%s",$fw_rev1,decodebcd(\@a));
	}
    my $fru = FRU->new();
   	$fru->rec_type("mprom,firmware,bmc,imm");
   	$fru->desc("BMC Firmware");
   	$fru->value($mprom);
   	$sessdata->{fru_hash}->{mprom} = $fru;
    $sessdata->{isanimm}=$isanimm;
    if ($isanimm) {
	#get_imm_property(property=>"/v2/bios/build_id",callback=>\&got_bios_buildid,sessdata=>$sessdata);
	check_for_ite(sessdata=>$sessdata);
    } else {
        initfru_with_mprom($sessdata);
    }
}
sub got_bios_buildid {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{biosbuildid} = $res{data};
	get_imm_property(property=>"/v2/bios/build_version",callback=>\&got_bios_version,sessdata=>$sessdata);
   } else {
        initfru_with_mprom($sessdata);
   }
}
sub got_bios_version {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{biosbuildversion} = $res{data};
	get_imm_property(property=>"/v2/bios/build_date",callback=>\&got_bios_date,sessdata=>$sessdata);
   } else {
        initfru_with_mprom($sessdata);
   }
}
sub got_bios_date {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{biosbuilddate} = $res{data};
	my $fru = FRU->new();
	$fru->rec_type("bios,uefi,firmware");
	$fru->desc("UEFI Version");
	$fru->value($sessdata->{biosbuildversion}." (".$sessdata->{biosbuildid}." ".$sessdata->{biosbuilddate}.")");
	$sessdata->{fru_hash}->{uefi} = $fru;
	get_imm_property(property=>"/v2/fpga/build_id",callback=>\&got_fpga_buildid,sessdata=>$sessdata);
   } else {
        initfru_with_mprom($sessdata);
   }
}
sub got_fpga_buildid {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{fpgabuildid} = $res{data};
	get_imm_property(property=>"/v2/fpga/build_version",callback=>\&got_fpga_version,sessdata=>$sessdata);
   } else {
        initfru_with_mprom($sessdata);
   }
}
sub got_fpga_version {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{fpgabuildversion} = $res{data};
	get_imm_property(property=>"/v2/fpga/build_date",callback=>\&got_fpga_date,sessdata=>$sessdata);
   } else {
        initfru_with_mprom($sessdata);
   }
}
sub got_fpga_date {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{data}) {
        $sessdata->{fpgabuilddate} = $res{data};
	my $fru = FRU->new();
	$fru->rec_type("fpga,firmware");
	$fru->desc("FPGA Version");
	$fru->value($sessdata->{fpgabuildversion}." (".$sessdata->{fpgabuildid}." ".$sessdata->{fpgabuilddate}.")");
	$sessdata->{fru_hash}->{fpga} = $fru;
   }
   initfru_with_mprom($sessdata);
}
sub check_for_ite {
   my %args = @_;
   my @getpropertycommand;
   my $sessdata = $args{sessdata};
   $sessdata->{property_callback} = \&got_ite_check; #$args{callback};
   @getpropertycommand = unpack("C*","/v2/cmm/");
   my $length = 0b10000000 | (scalar @getpropertycommand);#use length to store tlv
	unshift @getpropertycommand,$length;
	#command also needs the overall length
	$length = (scalar @getpropertycommand);
	unshift @getpropertycommand,0; #do not recurse, though it's not going to matter anyway since we are just checking for the existence of the category
	unshift @getpropertycommand,$length&0xff;
	unshift @getpropertycommand,($length>>8)&0xff;
	unshift @getpropertycommand,2; #get all properties command,
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc4,data=>\@getpropertycommand,callback=>\&got_imm_property,callback_args=>$sessdata);
}
sub got_ite_check {
   my %res = @_;
   my $sessdata = $res{sessdata};
   if ($res{ccode} == 9) { #success, end of tree means an ITE, remember this
	$sessdata->{isite}=1;
   } else {
	$sessdata->{isite}=0;
   }
   get_imm_property(property=>"/v2/bios/build_id",callback=>\&got_bios_buildid,sessdata=>$sessdata);
}
sub get_imm_property {
   my %args = @_;
   my @getpropertycommand;
   my $sessdata = $args{sessdata};
   $sessdata->{property_callback} = $args{callback};
   @getpropertycommand = unpack("C*",$args{property});
   my $length = 0b10000000 | (scalar @getpropertycommand);#use length to store tlv
	unshift @getpropertycommand,$length;
	#command also needs the overall length
	$length = (scalar @getpropertycommand);
	unshift @getpropertycommand,$length&0xff;
	unshift @getpropertycommand,($length>>8)&0xff;
	unshift @getpropertycommand,0; #the actual 'get proprety' command is 0.
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc4,data=>\@getpropertycommand,callback=>\&got_imm_property,callback_args=>$sessdata);
}
sub got_imm_property {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
    my @data = @{$rsp->{data}};
    my $propval = shift @data;
    my %res;
    $res{sessdata}=$sessdata;
    $res{ccode}=$propval;
    if ($propval == 0) { #success
    	shift @data; #discard payload size
    	shift @data; #discard payload size
	while (@data) {
		my $tlv = shift @data;
		if ($tlv & 0b10000000) {
			$tlv = $tlv & 0b1111111;
			my @val = splice(@data,0,$tlv);
			$res{data}= unpack("Z*",pack("C*",@val));
		}
	}
   }
   $sessdata->{property_callback}->(%res);
}

sub initfru_withguid {
    my $sessdata = shift;
    my $mfg_id = $sessdata->{mfg_id};
    my $prod_id = $sessdata->{prod_id};
	my $mprom;

	if($mfg_id == 20301 or $mfg_id == 2 && $prod_id != 34869) {
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0x50,data=>[],callback=>\&got_bmc_fw_info,callback_args=>$sessdata);
	} else {
        got_bmc_fw_info(0,$sessdata);
    }
}
sub initfru_with_mprom {
    my $sessdata = shift;
    if ($sessdata->{skipfru}) {
        fru_initted($sessdata);
        return;
    }
    $sessdata->{currfruid}=0;
    $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x10,data=>[0],callback=>\&process_currfruid,callback_args=>$sessdata);
}
sub process_currfruid {
    my $rsp = shift;
    my $sessdata = shift;
    if ($rsp->{code} == 0xcb) {
            $sessdata->{currfrudata}="Not Present";
            $sessdata->{currfrudone}=1;
            add_fruhash($sessdata);
            return;
    }
    if ($rsp and $rsp->{code}) { #non-zero return code..
            $sessdata->{currfrudata}="Unable to read";
	    if  ($codes{$rsp->{code}}) {
		$sessdata->{currfrudata} .= " (".$codes{$rsp->{code}}.")";
	    } else {
		$sessdata->{currfrudata} .= sprintf(" (Unknown reason %02xh)",$rsp->{code});
            }
            $sessdata->{currfrudone}=1;
            add_fruhash($sessdata);
            return;
    }
	
    if (check_rsp_errors($rsp,$sessdata)) {
        return;
    }
    my @bytes =@{$rsp->{data}};
    $sessdata->{currfrusize} = ($bytes[1]<<8)+$bytes[0];
    readcurrfrudevice(0,$sessdata);
}
sub initfru_zero {
    my $sessdata = shift;
    my $fruhash = shift;
    my $frudex=0;
    my $fru;
    if (defined $fruhash->{product}->{manufacturer}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("System Manufacturer");
        if ($fruhash->{product}->{product}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{manufacturer}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{manufacturer}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if (defined $fruhash->{product}->{product}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("model");
    	$fru->desc("System Description");
        if ($fruhash->{product}->{product}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{product}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{product}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if (defined $fruhash->{product}->{model}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("model");
    	$fru->desc("System Model/MTM");
        if ($fruhash->{product}->{model}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{model}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{model}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if (defined $fruhash->{product}->{version}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("System Revision");
        if ($fruhash->{product}->{version}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{version}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{version}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if (defined $fruhash->{product}->{serialnumber}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("serial");
    	$fru->desc("System Serial Number");
        if ($fruhash->{product}->{serialnumber}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{serialnumber}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{serialnumber}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if (defined $fruhash->{product}->{asset}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("asset");
    	$fru->desc("System Asset Number");
        if ($fruhash->{product}->{asset}->{encoding}==3) {
        	$fru->value($fruhash->{product}->{asset}->{value});
        } else {
        	$fru->value(phex($fruhash->{product}->{asset}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    foreach (@{$fruhash->{product}->{extra}}) {
        $fru = FRU->new();
        $fru->rec_type("misc");
        $fru->desc("Product Extra data");
        if ($_->{encoding} == 3) {
            $fru->value($_->{value});
        } else {
            #print Dumper($_);
            #print $_->{encoding};
            next;
            $fru->value(phex($_->{value}));
        }
        $sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    

    if ($fruhash->{chassis}->{serialnumber}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("serial");
    	$fru->desc("Chassis Serial Number");
        if ($fruhash->{chassis}->{serialnumber}->{encoding}==3) {
        	$fru->value($fruhash->{chassis}->{serialnumber}->{value});
        } else {
        	$fru->value(phex($fruhash->{chassis}->{serialnumber}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }

    if ($fruhash->{chassis}->{partnumber}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("model");
    	$fru->desc("Chassis Part Number");
        if ($fruhash->{chassis}->{partnumber}->{encoding}==3) {
        	$fru->value($fruhash->{chassis}->{partnumber}->{value});
        } else {
        	$fru->value(phex($fruhash->{chassis}->{partnumber}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }


    foreach (@{$fruhash->{chassis}->{extra}}) {
        $fru = FRU->new();
        $fru->rec_type("misc");
        $fru->desc("Chassis Extra data");
        if ($_->{encoding} == 3) {
            $fru->value($_->{value});
        } else {
            next;
            #print Dumper($_);
            #print $_->{encoding};
            $fru->value(phex($_->{value}));
        }
        $sessdata->{fru_hash}->{$frudex++} = $fru;
    }

    if ($fruhash->{board}->{builddate})  {
        $fru = FRU->new();
        $fru->rec_type("misc");
        $fru->desc("Board manufacture date");
        $fru->value($fruhash->{board}->{builddate});
        $sessdata->{fru_hash}->{$frudex++} = $fru;
    }

    if ($fruhash->{board}->{manufacturer}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("Board manufacturer");
        if ($fruhash->{board}->{manufacturer}->{encoding}==3) {
        	$fru->value($fruhash->{board}->{manufacturer}->{value});
        } else {
        	$fru->value(phex($fruhash->{board}->{manufacturer}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if ($fruhash->{board}->{frunum}) {
	$fru = FRU->new();
	$fru->rec_type("misc");
	$fru->desc("Board FRU Number");
	$fru->value($fruhash->{board}->{frunum});
	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if ($fruhash->{board}->{revision}) {
	$fru = FRU->new();
	$fru->rec_type("misc");
	$fru->desc("Board Revision");
	$fru->value($fruhash->{board}->{revision});
	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if ($fruhash->{board}->{macaddrs}) {
	my $macindex=1;
	foreach my $mac (@{$fruhash->{board}->{macaddrs}}) {
		$fru = FRU->new();
		$fru->rec_type("mac");
		$fru->desc("MAC Address $macindex");
		$macindex++;
		$fru->value($mac);
		$sessdata->{fru_hash}->{$frudex++} = $fru;
	}
    }
    if ($fruhash->{board}->{wwns}) {
	my $macindex=1;
	foreach my $mac (@{$fruhash->{board}->{wwns}}) {
		$fru = FRU->new();
		$fru->rec_type("wwn");
		$fru->desc("WWN $macindex");
		$macindex++;
		$fru->value($mac);
		$sessdata->{fru_hash}->{$frudex++} = $fru;
	}
    }
    if ($fruhash->{board}->{name}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("Board Description");
        if ($fruhash->{board}->{name}->{encoding}==3) {
        	$fru->value($fruhash->{board}->{name}->{value});
        } else {
        	$fru->value(phex($fruhash->{board}->{name}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if ($fruhash->{board}->{serialnumber}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("Board Serial Number");
        if ($fruhash->{board}->{serialnumber}->{encoding}==3) {
        	$fru->value($fruhash->{board}->{serialnumber}->{value});
        } else {
        	$fru->value(phex($fruhash->{board}->{serialnumber}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    if ($fruhash->{board}->{partnumber}->{value}) {
	    $fru = FRU->new();
    	$fru->rec_type("misc");
    	$fru->desc("Board Model Number");
        if ($fruhash->{board}->{partnumber}->{encoding}==3) {
        	$fru->value($fruhash->{board}->{partnumber}->{value});
        } else {
        	$fru->value(phex($fruhash->{board}->{partnumber}->{value}));
        }
    	$sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    foreach (@{$fruhash->{board}->{extra}}) {
        $fru = FRU->new();
        $fru->rec_type("misc");
        $fru->desc("Board Extra data");
        if ($_->{encoding} == 3) {
            $fru->value($_->{value});
        } else {
            next;
            #print Dumper($_);
            #print $_->{encoding};
            $fru->value(phex($_->{value}));
        }
        $sessdata->{fru_hash}->{$frudex++} = $fru;
    }
    #Ok, done with fru 0, on to the other fru devices from SDR
    $sessdata->{frudex} = $frudex;
    if ($sessdata->{skipotherfru}) { #skip non-primary fru devices
        fru_initted($sessdata);
        return;
    }
    my $key;
    my $subrc;
    my %sdr_hash = %{$sessdata->{sdr_hash}};
    $sessdata->{dimmfru} = [];
    $sessdata->{genhwfru} = [];
    foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) {
        my $sdr = $sdr_hash{$key};
        unless ($sdr->rec_type == 0x11 and $sdr->fru_type == 0x10) { #skip non fru sdr stuff and frus I don't understand
            next;
        }
        
        if ($sdr->fru_type == 0x10) { #supported
            if ($sdr->fru_subtype == 0x1) { #DIMM
                push @{$sessdata->{dimmfru}},$sdr;
            } elsif ($sdr->fru_subtype == 0 or $sdr->fru_subtype == 2) {
                push @{$sessdata->{genhwfru}},$sdr;
            }
        }
    }
   if (scalar @{$sessdata->{dimmfru}}) {
        $sessdata->{currfrusdr} = shift  @{$sessdata->{dimmfru}};
        $sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
        $sessdata->{currfrutype}="dimm";
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x10,data=>[$sessdata->{currfruid}],callback=>\&process_currfruid,callback_args=>$sessdata);
  } elsif (scalar @{$sessdata->{genhwfru}}) {
        $sessdata->{currfrusdr} = shift  @{$sessdata->{genhwfru}};
        $sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
        $sessdata->{currfrutype}="genhw";
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x10,data=>[$sessdata->{currfruid}],callback=>\&process_currfruid,callback_args=>$sessdata);
  } else {
      fru_initted($sessdata);
  }
}
sub get_frusize {
    my $fruid=shift;
    my $netfun = 0x28; # Storage (0x0A << 2)
    my @cmd=(0x10,$fruid);
	my @bytes;
    my $error = docmd($netfun,\@cmd,\@bytes);
    pop @bytes;
    unless (defined $bytes[0] and $bytes[0] == 0) {
        if ($codes{$bytes[0]}) {
            return (0,$codes{$bytes[0]});
        }
        return (0,"FRU device $fruid inaccessible");
    }
    return ($bytes[2]<<8)+$bytes[1];
}

sub formfru {
    my $fruhash = shift;
    my $frusize = shift;
    $frusize-=8; #consume 8 bytes for mandatory header
    my $availindex=1;
    my @bytes=(1,0,0,0,0,0,0,0); #
    if ($fruhash->{internal}) { #Allocate the space at header time
        $bytes[1]=$availindex;
        $availindex+=ceil((scalar @{$fruhash->{internal}})/8);
        $frusize-=(scalar @{$fruhash->{internal}}); #consume internal bytes
        push @bytes,@{$fruhash->{internal}};
    } 
    if ($fruhash->{chassis}) {
        $bytes[2]=$availindex;
        push @bytes,@{$fruhash->{chassis}->{raw}};
        $availindex+=ceil((scalar @{$fruhash->{chassis}->{raw}})/8);
        $frusize -= ceil((scalar @{$fruhash->{chassis}->{raw}})/8)*8;
    }
    if ($fruhash->{board}) {
        $bytes[3]=$availindex;
        push @bytes,@{$fruhash->{board}->{raw}};
        $availindex+=ceil((scalar @{$fruhash->{board}->{raw}})/8);
        $frusize -= ceil((scalar @{$fruhash->{board}->{raw}})/8)*8;
    }
    #xCAT will always have a product FRU in this process
    $bytes[4]=$availindex;
    unless (defined $fruhash->{product}) { #Make sure there is a data structure
                        #to latch onto..
        $fruhash->{product}={};
    }
    my @prodbytes = buildprodfru($fruhash->{product});
    push @bytes,@prodbytes;
    $availindex+=ceil((scalar @prodbytes)/8);
    $frusize -= ceil((scalar @prodbytes)/8)*8;;
    #End of product fru setup
    if ($fruhash->{extra}) {
        $bytes[5]=$availindex;
        push @bytes,@{$fruhash->{extra}};
        $frusize -= ceil((scalar @{$fruhash->{extra}})/8)*8;
        #Don't need to track availindex anymore
    }
    $bytes[7] = dochksum([@bytes[0..6]]);
    if ($frusize<0) {
        return undef;
    } else {
        return \@bytes;
    }
}

sub transfieldtobytes {
    my $hashref=shift;
    unless (defined $hashref) {
        return (0xC0);
    }
    my @data;
    my $size;
    if ($hashref->{encoding} ==3) {
        @data=unpack("C*",$hashref->{value});
    } else {
        @data=@{$hashref->{value}};
    }
    $size=scalar(@data);
    if ($size > 64) {
        die "Field too large for IPMI FRU specification";
    }
    unshift(@data,$size|($hashref->{encoding}<<6));
    return @data;
}
sub mergefru {
    my $sessdata = shift;
    my $phash = shift; #Product hash
    unless ($phash) { die "here" }
    my $currnode = $sessdata->{node};
    if ($vpdhash->{$currnode}->[0]->{mtm}) {
        $phash->{model}->{encoding}=3;
        $phash->{model}->{value}=$vpdhash->{$currnode}->[0]->{mtm};
    }
    if ($vpdhash->{$currnode}->[0]->{serial}) {
        $phash->{serialnumber}->{encoding}=3;
        $phash->{serialnumber}->{value}=$vpdhash->{$currnode}->[0]->{serial};
    }
    if ($vpdhash->{$currnode}->[0]->{asset}) {
        $phash->{asset}->{encoding}=3;
        $phash->{asset}->{value}=$vpdhash->{$currnode}->[0]->{asset};
    }
}

sub buildprodfru {
    my $sessdata = shift;
    my $prod=shift;
    mergefru($sessdata,$prod);
    my $currnode = $sessdata->{node};
    my @bytes=(1,0,0);
    my @data;
    my $padsize;
    push @bytes,transfieldtobytes($prod->{manufacturer});
    push @bytes,transfieldtobytes($prod->{product});
    push @bytes,transfieldtobytes($prod->{model});
    push @bytes,transfieldtobytes($prod->{version});
    push @bytes,transfieldtobytes($prod->{serialnumber});
    push @bytes,transfieldtobytes($prod->{asset});
    push @bytes,transfieldtobytes($prod->{fruid});
    push @bytes,transfieldtobytes($prod->{fruid});
    foreach (@{$prod->{extra}}) {
        my $sig=getascii(transfieldtobytes($_));
        unless ($sig and $sig =~ /FRU by xCAT/) {
            push @bytes,transfieldtobytes($_);
        }
    }
    push @bytes,transfieldtobytes({encoding=>3,value=>"$currnode FRU by xCAT ".xCAT::Utils::Version('short')});
    push @bytes,(0xc1);
    $bytes[1]=ceil((scalar(@bytes)+1)/8);
    $padsize=(ceil((scalar(@bytes)+1)/8)*8)-scalar(@bytes)-1;
    while ($padsize--) {
        push @bytes,(0x00);
    }
    $padsize=dochksum(\@bytes);#reuse padsize for a second to store checksum
    push @bytes,$padsize;

    return @bytes;
}

sub fru {
	my $subcommand = shift;
	my $netfun = 0x28;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my @output;
	my $code;

	@cmd=(0x10,0x00);
	$error = docmd(
		$netfun,
		\@cmd,
		\@returnd
	);

	if($error) {
		$rc = 1;
		$text = $error;
		return($rc,$text);
	}

	$code = $returnd[0];

	if($code == 0x00) {
	}
	else {
		$rc = 1;
		$text = $codes{$code};
	}

	if($rc != 0) {
		if(!$text) {
			$text = sprintf("unknown response %02x",$code);
		}
		return($rc,$text);
	}

	my $fru_size_ls = $returnd[1];
	my $fru_size_ms = $returnd[2];
	my $fru_size = $fru_size_ms*256 + $fru_size_ls;

	if($subcommand eq "dump") {
		print "FRU Size: $fru_size\n";
		my ($rc,@output) = frudump(0,$fru_size,8);
		if($rc) {
			return($rc,@output);
		}
		hexadump(\@output);
		return(0,"");
	}
	if($subcommand eq "wipe") {
		my @bytes = ();

		for(my $i = 0;$i < $fru_size;$i++) {
			push(@bytes,0xff);
		}
		my ($rc,$text) = fruwrite(0,\@bytes,8);
		if($rc) {
			return($rc,$text);
		}
		return(0,"FRU $fru_size bytes wiped");
	}

	return(0,"");
}

sub add_fruhash {
    my $sessdata = shift;
    my $fruhash;
    if ($sessdata->{currfruid} !=0 and not ref $sessdata->{currfrudata}) {
        my $fru = FRU->new();
        if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
            $fru->rec_type("dimm,hw");
        } else {
             $fru->rec_type("hw");
        }
        $fru->value($sessdata->{currfrudata});
        $fru->desc($sessdata->{currfrusdr}->id_string);
        $sessdata->{fru_hash}->{$sessdata->{frudex}} = $fru;
        $sessdata->{frudex} += 1;
    } elsif ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
        $fruhash = decode_spd(@{$sessdata->{currfrudata}});
    } else {
            my $err;
	    $global_sessdata=$sessdata; #pass by global, evil, but practical this time
            ($err,$fruhash) = parsefru($sessdata->{currfrudata});
	    $global_sessdata=undef; #revert state of global 
            if ($err) {
		my $fru = FRU->new();
        if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
            $fru->rec_type("dimm,hw");
        } else {
             $fru->rec_type("hw");
        }
        $fru->value($err);
        $fru->desc($sessdata->{currfrusdr}->id_string);
        $sessdata->{fru_hash}->{$sessdata->{frudex}} = $fru;
        $sessdata->{frudex} += 1;
        undef $sessdata->{currfrudata}; #skip useless calls to add more frus when parsing failed miserably anyway

                #xCAT::SvrUtils::sendmsg([1,":Error reading fru area ".$sessdata->{currfruid}.": $err"],$callback);
                #return;
            }
    }
    if ($sessdata->{currfruid} == 0) {
        initfru_zero($sessdata,$fruhash);
        return;
    } elsif (ref $sessdata->{currfrudata}) {
        if ($sessdata->{currfrutype} and $sessdata->{currfrutype} eq 'dimm') {
            add_textual_frus($fruhash,$sessdata->{currfrusdr}->id_string,"","product","dimm,hw",$sessdata);
        } else {
            add_textual_frus($fruhash,$sessdata->{currfrusdr}->id_string,"Board ","board",undef,$sessdata);
            add_textual_frus($fruhash,$sessdata->{currfrusdr}->id_string,"Product ","product",undef,$sessdata);
            add_textual_frus($fruhash,$sessdata->{currfrusdr}->id_string,"Chassis ","chassis",undef,$sessdata);
        }
    }
    if (scalar @{$sessdata->{dimmfru}}) {
        $sessdata->{currfrusdr} = shift  @{$sessdata->{dimmfru}};
        $sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
        $sessdata->{currfrutype}="dimm";
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x10,data=>[$sessdata->{currfruid}],callback=>\&process_currfruid,callback_args=>$sessdata);
    } elsif (scalar @{$sessdata->{genhwfru}}) {
        $sessdata->{currfrusdr} = shift  @{$sessdata->{genhwfru}};
        $sessdata->{currfruid} = $sessdata->{currfrusdr}->sensor_number;
        $sessdata->{currfrutype}="genhw";
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x10,data=>[$sessdata->{currfruid}],callback=>\&process_currfruid,callback_args=>$sessdata);
    } else {
        fru_initted($sessdata);
    }
}

sub readcurrfrudevice {
    my $rsp = shift;
    my $sessdata = shift;
    my $chunk=16; #we have no idea how much will be supported to grab at a time, stick to 16 as a magic number for the moment
    if (not ref $rsp) {
        $sessdata->{currfruoffset}=0;
        $sessdata->{currfrudata}=[];
        $sessdata->{currfrudone}=0;
        $sessdata->{currfruchunk}=16;
    } else {
        if ($rsp->{code} != 0xcb and check_rsp_errors($rsp,$sessdata)) {
            return;
        } elsif ($rsp->{code} == 0xcb) {
            $sessdata->{currfrudata}="Not Present";
            $sessdata->{currfrudone}=1;
            add_fruhash($sessdata);
            return;
        }
        my @data = @{$rsp->{data}};
        if ($data[0] != $sessdata->{currfruchunk}) {
            xCAT::SvrUtils::sendmsg([1,"Received incorrect data from BMC"],$callback,$sessdata->{node},%allerrornodes);
            return;
        }
        shift @data;
        push @{$sessdata->{currfrudata}},@data;
        if ($sessdata->{currfrudone}) {
	    if ($sessdata->{isite}) {
		#IBM OEM command, d0,51,0 further qualifies the command name, we'll first take a stop at block 0, offset 2, one byte, to get VPD version number
		#command structured as:
		#d0,51,0 = command set identifier
		#lsb of offset
		#msb of offset
		#address type (1 for fru id)
		#address (fru id for our use)
		#1 - fixed value
		#lsb - size
		#msb - size
		#vpd_base_specivication_ver2.x
    		$sessdata->{ipmisession}->subcmd(netfn=>0x2e,command=>0x51,data=>[0xd0,0x51,0,0x2,0x0,1,$sessdata->{currfruid},1,1,0],callback=>\&got_vpd_version,callback_args=>$sessdata);
	    } else {
            	add_fruhash($sessdata);
	    }
            return;
        }
    }

    my $ms=$sessdata->{currfruoffset}>>8;
    my $ls=$sessdata->{currfruoffset}&0xff;
    if ($sessdata->{currfruoffset}+16  >= $sessdata->{currfrusize}) {
        $chunk =  $sessdata->{currfrusize}-$sessdata->{currfruoffset}; # shrink chunk to only get the remainder data
        $sessdata->{currfrudone}=1;
    } else {
        $sessdata->{currfruoffset}+=$chunk;
    }
    $sessdata->{currfruchunk}=$chunk;
    $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x11,data=>[$sessdata->{currfruid},$ls,$ms,$chunk],callback=>\&readcurrfrudevice,callback_args=>$sessdata);
}

sub got_vpd_version {
    my $rsp = shift;
    my $sessdata = shift;
        unless ($rsp and not $rsp->{error} and $rsp->{code} == 0 and $rsp->{data}->[5] == 2) { #unless the query was successful and major vpd version was 2
												#short over to adding the fru hash as-is
            	add_fruhash($sessdata);
		return;
        }
	#making it this far, we have affirmative confirmation of ibm oem vpd data, time to chase component mac, wwpn, and maybe mezz firmware
	#will need:
	#	block 0, offset 0c8h use the offset to add to block 1 offsets (usually 400h), denoting as $blone
	#       block 1, $blone+6 - 216 bytes: 6 sets of 36 byte version information (TODO)
	#	block 1, $blone+0x1d0: port type first 4 bits protocol, last 3 bits addressing
	#	block 1, $blone+0x240: 64 bytes, up to 8 sets of addresses, mac is left aligned
	#	block 1, $blone+0x300: if mac+wwn, grab wwn
        $sessdata->{ipmisession}->subcmd(netfn=>0x2e,command=>0x51,data=>[0xd0,0x51,0,0xc8,0x0,1,$sessdata->{currfruid},1,2,0],callback=>\&got_vpd_block1,callback_args=>$sessdata);
}
sub got_vpd_block1 {
    my $rsp = shift;
    my $sessdata = shift;
    unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
            	add_fruhash($sessdata);
		return;
    }
    $sessdata->{vpdblock1offset}=$rsp->{data}->[5]<<8+$rsp->{data}->[6];
    my $ptoffset = $sessdata->{vpdblock1offset} + 0x1d0;
    $sessdata->{ipmisession}->subcmd(netfn=>0x2e,command=>0x51,data=>[0xd0,0x51,0,$ptoffset&0xff,$ptoffset>>8,1,$sessdata->{currfruid},1,1,0],callback=>\&got_portaddr_type,callback_args=>$sessdata);
}
sub got_portaddr_type {
    my $rsp = shift;
    my $sessdata = shift;
    unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
            	add_fruhash($sessdata);
		return;
    }
    my $addrtype = $rsp->{data}->[5] & 0b111;
    if ($addrtype == 0b101) { 
	$sessdata->{needmultiaddr}=1;
	$sessdata->{curraddrtype}="mac";
    } elsif ($addrtype == 0b1) {
	$sessdata->{curraddrtype}="mac";
    } elsif ($addrtype == 0b10) {
	$sessdata->{curraddrtype}="wwn";
    } else { #for now, skip polling addresses I haven't examined directly
            	add_fruhash($sessdata);
		return;
    }
    my $addroffset = $sessdata->{vpdblock1offset} + 0x240;
    $sessdata->{ipmisession}->subcmd(netfn=>0x2e,command=>0x51,data=>[0xd0,0x51,0,$addroffset&0xff,$addroffset>>8,1,$sessdata->{currfruid},1,64,0],callback=>\&got_vpd_addresses,callback_args=>$sessdata);

    
}
sub got_vpd_addresses { 
    my $rsp = shift;
    my $sessdata = shift;
    unless ($rsp and not $rsp->{error} and $rsp->{code} == 0) { # if this should go wonky, jump ahead
            	add_fruhash($sessdata);
		return;
    }
    my @addrdata = @{$rsp->{data}};
    splice @addrdata,0,5; # remove the header info
        my $macstring = "1";
	while ($macstring !~ /^00:00:00:00:00:00/) {
		my @currmac = splice @addrdata,0,8;
		unless ((scalar @currmac) == 8) {
			last;
		}
		$macstring = sprintf("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",@currmac);
		if ($macstring =~ /^00:00:00:00:00:00:00:00/) { 
			last;
		}
		if ($sessdata->{curraddrtype} eq "mac") {
			$macstring =~ s/:..:..$//;
			push @{$sessdata->{currmacs}},$macstring;
		} elsif ($sessdata->{curraddrtype} eq "wwn") {
			push @{$sessdata->{currwwns}},$macstring;
		}
	}
	if ($sessdata->{needmultiaddr}) {
		$sessdata->{needmultiaddr}=0;
		$sessdata->{curraddrtype}="wwn";
    		my $addroffset = $sessdata->{vpdblock1offset} + 0x300;
    		$sessdata->{ipmisession}->subcmd(netfn=>0x2e,command=>0x51,data=>[0xd0,0x51,0,$addroffset&0xff,$addroffset>>8,1,$sessdata->{currfruid},1,64,0],callback=>\&got_vpd_addresses,callback_args=>$sessdata);
		return;
	}
         	add_fruhash($sessdata);
}

sub parsefru {
    my $bytes = shift;
    my $fruhash;
    my $curridx; #store indexes as needed for convenience
    my $currsize; #store current size
    my $subidx;
    my @currarea;
    unless (ref $bytes) {
        return $bytes,undef;
    }
    unless ($bytes->[0]==1) {
        if ($bytes->[0]==0 or $bytes->[0]==0xff) { #not in spec, but probably unitialized, xCAT probably will rewrite fresh
            return "clear",undef;
        } else { #some meaning suggested, but not parsable, xCAT shouldn't meddle
            return "Unrecognized FRU format",undef;
        }
    }
    if ($bytes->[1]) { #The FRU spec, unfortunately, gave no easy way to tell the size of internal area
        #consequently, will find the next defined field and preserve the addressing and size of current FRU 
        #area until then
        my $internal_size;
        if ($bytes->[2]) {
            $internal_size=$bytes->[2]*8-($bytes->[1]*8);
        } elsif ($bytes->[3]) {
            $internal_size=$bytes->[3]*8-($bytes->[1]*8);
        } elsif ($bytes->[4]) {
            $internal_size=$bytes->[4]*8-($bytes->[1]*8);
        } elsif ($bytes->[5]) {
            $internal_size=$bytes->[5]*8-($bytes->[1]*8);
        } else { #The FRU area is intact enough to signify xCAT can't safely manipulate contents
            return "unknown-winternal",undef;
        }
        #capture slice of bytes
        $fruhash->{internal}=[@{$bytes}[($bytes->[1]*8)..($bytes->[1]*8+$internal_size-1)]]; #,$bytes->[1]*8,$internal_size];
    }
    if ($bytes->[2]) { #Chassis info area, xCAT will preserve fields, not manipulate them
        $curridx=$bytes->[2]*8;
        unless ($bytes->[$curridx]==1) { #definitely unparsable, but the section is preservable
            return "unknown-COULDGUESS",undef; #be lazy for now, TODO revisit this and add guessing if it ever matters
        }
        $currsize=($bytes->[$curridx+1])*8;
        @currarea=@{$bytes}[$curridx..($curridx+$currsize-1)]; #splice @$bytes,$curridx,$currsize;
        $fruhash->{chassis} = parsechassis(@currarea);
    }
    if ($bytes->[3]) { #Board info area, to be preserved
        $curridx=$bytes->[3]*8;
        unless ($bytes->[$curridx]==1) {
            return "unknown-COULDGUESS",undef;
        }
        $currsize=($bytes->[$curridx+1])*8;
        @currarea=@{$bytes}[$curridx..($curridx+$currsize-1)];
        $fruhash->{board} = parseboard(@currarea);
    }
    if (ref $global_sessdata->{currmacs}) {
	$fruhash->{board}->{macaddrs}=[];
	push @{$fruhash->{board}->{macaddrs}},@{$global_sessdata->{currmacs}};
	delete $global_sessdata->{currmacs}; # consume the accumulated mac addresses to avoid afflicting subsequent fru
    }
    if (ref $global_sessdata->{currwwns}) {
	push @{$fruhash->{board}->{wwns}},@{$global_sessdata->{currwwns}};
	delete $global_sessdata->{currwwns}; # consume wwns
    }
    if ($bytes->[4]) { #Product info area present, will probably be thoroughly modified
        $curridx=$bytes->[4]*8;
        unless ($bytes->[$curridx]==1) {
            return "unknown-COULDGUESS",undef;
        }
        $currsize=($bytes->[$curridx+1])*8;
        @currarea=@{$bytes}[$curridx..($curridx+$currsize-1)];
        $fruhash->{product} = parseprod(@currarea);
    }
    if ($bytes->[5]) { #Generic multirecord present..
        $fruhash->{extra}=[];
        my $last=0;
        $curridx=$bytes->[5]*8;
        my $currsize;
	if ($bytes->[$curridx] <= 5) { #don't even try to parse unknown stuff
			#some records don't comply to any SPEC
	        while (not $last) {
	            if ($bytes->[$curridx+1] & 128) {
	                $last=1;
	            }
	            $currsize=$bytes->[$curridx+2];
	            push @{$fruhash->{extra}},$bytes->[$curridx..$curridx+4+$currsize-1];
        	}
        }
    }
    return 0,$fruhash;
}

sub parseprod {
    my @area = @_;
    my %info;
    my $language=$area[2];
    my $idx=3;
    my $currsize;
    my $currdata;
    my $encode;
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{manufacturer}->{encoding}=$encode;
        $info{manufacturer}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{product}->{encoding}=$encode;
        $info{product}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{model}->{encoding}=$encode;
        $info{model}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{version}->{encoding}=$encode;
        $info{version}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{serialnumber}->{encoding}=$encode;
        $info{serialnumber}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{asset}->{encoding}=$encode;
        $info{asset}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%info;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $info{fruid}->{encoding}=$encode;
        $info{fruid}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    if ($currsize) {
        $info{extra}=[];
    }
    while ($currsize>0) {
        if ($currsize>1) {
            push @{$info{extra}},{value=>$currdata,encoding=>$encode};
        }
        $idx+=$currsize;
        ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    }
    return \%info;

}
sub parseboard {
    my @area = @_;
    my %boardinf;
    my $idx=6;
    my $language=$area[2];
    my $tstamp = ($area[3]+($area[4]<<8)+($area[5]<<16))*60+820472400; #820472400 is meant to be 1/1/1996
    $boardinf{raw}=[@area]; #store for verbatim replacement
    unless ($tstamp == 820472400) {
        $boardinf{builddate}=scalar localtime($tstamp);
    }
    my $encode;
    my $currsize;
    my $currdata;
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%boardinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $boardinf{manufacturer}->{encoding}=$encode;
        $boardinf{manufacturer}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%boardinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $boardinf{name}->{encoding}=$encode;
        $boardinf{name}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%boardinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $boardinf{serialnumber}->{encoding}=$encode;
        $boardinf{serialnumber}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%boardinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $boardinf{partnumber}->{encoding}=$encode;
        $boardinf{partnumber}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    unless ($currsize) {
        return \%boardinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $boardinf{fruid}->{encoding}=$encode;
        $boardinf{fruid}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    if ($currsize) {
        $boardinf{extra}=[];
    }
    while ($currsize>0) {
        if ($currsize>1) {
            push @{$boardinf{extra}},{value=>$currdata,encoding=>$encode};
        }
        $idx+=$currsize;
        ($currsize,$currdata,$encode)=extractfield(\@area,$idx);
    }
    if ($global_sessdata->{isanimm}) { #we can understand more specifically some of the extra fields...
	$boardinf{frunum}=$boardinf{extra}->[0]->{value};
	$boardinf{revision}=$boardinf{extra}->[4]->{value};
	#time to process the mac field...
	my $macdata = $boardinf{extra}->[6]->{value};
        my $macstring = "1";
	while ($macstring !~ /00:00:00:00:00:00/ and not ref $global_sessdata->{currmacs}) {
		my @currmac = splice @$macdata,0,6;
		unless ((scalar @currmac) == 6) {
			last;
		}
		$macstring = sprintf("%02x:%02x:%02x:%02x:%02x:%02x",@currmac);
		if ($macstring !~ /00:00:00:00:00:00/) { 
			push @{$boardinf{macaddrs}},$macstring;
		}
	}
	delete $boardinf{extra};
    } 
    return \%boardinf;
}
sub parsechassis {
    my @chassarea=@_;
    my %chassisinf;
    my $currsize;
    my $currdata;
    my $idx=3;
    my $encode;
    $chassisinf{raw}=[@chassarea]; #store for verbatim replacement
    $chassisinf{type}="unknown";
    if ($chassis_types{$chassarea[2]}) {
        $chassisinf{type}=$chassis_types{$chassarea[2]};
    }
    if ($chassarea[$idx] == 0xc1) {
        return \%chassisinf;
    }
    ($currsize,$currdata,$encode)=extractfield(\@chassarea,$idx);
    unless ($currsize) {
        return \%chassisinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $chassisinf{partnumber}->{encoding}=$encode;
        $chassisinf{partnumber}->{value}=$currdata;
    } 
    ($currsize,$currdata,$encode)=extractfield(\@chassarea,$idx);
    unless ($currsize) {
        return \%chassisinf;
    }
    $idx+=$currsize;
    if ($currsize>1) {
        $chassisinf{serialnumber}->{encoding}=$encode;
        $chassisinf{serialnumber}->{value}=$currdata;
    }
    ($currsize,$currdata,$encode)=extractfield(\@chassarea,$idx);
    if ($currsize) {
        $chassisinf{extra}=[];
    }
    while ($currsize>0) {
        if ($currsize>1) {
            push @{$chassisinf{extra}},{value=>$currdata,encoding=>$encode};
        }
        $idx+=$currsize;
        ($currsize,$currdata,$encode)=extractfield(\@chassarea,$idx);
    }
    return \%chassisinf;
}

sub extractfield { #idx is location of the type/length byte, returns something appropriate
    my $area = shift;
    my $idx = shift;
    my $language=shift;
    my $data;
    if ($idx >= scalar @$area)  {
        xCAT::SvrUtils::sendmsg([1,"Error parsing FRU data from BMC"],$callback);
        return 1,undef,undef;
    }
    my $size = $area->[$idx] & 0b00111111;
    my $encoding = ($area->[$idx] & 0b11000000)>>6;
    unless ($size) {
        return 1,undef,undef;
    }
    if ($size==1 && $encoding==3) { 
        return 0,'','';
    }
    if ($encoding==3) {
        $data=getascii(@$area[$idx+1..$size+$idx]);
    } else {
        $data = [@$area[$idx+1..$size+$idx]];
    }
    return $size+1,$data,$encoding;
}






sub writefru {
    my $netfun = 0x28; # Storage (0x0A << 2)
    my @cmd=(0x10,0);
	my @bytes;
    my $error = docmd($netfun,\@cmd,\@bytes);
    pop @bytes;
    unless (defined $bytes[0] and $bytes[0] == 0) {
        return (1,"FRU device 0 inaccessible");
    }
    my $frusize=($bytes[2]<<8)+$bytes[1];
    ($error,@bytes) = frudump(0,$frusize,16);
    if ($error) {
        return (1,"Error retrieving FRU: ".$error);
    }
    my $fruhash; 
    ($error,$fruhash) = parsefru(\@bytes);
    my $newfru=formfru($fruhash,$frusize);
    unless ($newfru) {
        return (1,"FRU data will not fit in BMC FRU space, fields too long");
    }
    my $rc=1;
    my $writeattempts=0;
    my $text;
    while ($rc and $writeattempts<15) {
        if ($writeattempts) {
            sleep 1;
        }
    	($rc,$text) = fruwrite(0,$newfru,8);
        if ($text =~ /rotected/) {
            last;
        }
        $writeattempts++;
    }
	if($rc) {
		return($rc,$text);
	}
	return(0,"FRU Updated");
}

sub fruwrite {
	my $offset = shift;
	my $bytes = shift;
	my $chunk = shift;
	my $length = @$bytes;

	my $netfun = 0x28;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my @output;
	my $code;
	my @fru_data=();

	for(my $c=$offset;$c < $length+$offset;$c += $chunk) {
		my $ms = int($c / 0x100);
		my $ls = $c - $ms * 0x100;

		@cmd=(0x12,0x00,$ls,$ms,@$bytes[$c-$offset..$c-$offset+$chunk-1]);
		$error = docmd(
			$netfun,
			\@cmd,
			\@returnd
		);

		if($error) {
			$rc = 1;
			$text = $error;
			return($rc,$text);
		}

		$code = $returnd[0];

		if($code == 0x00) {
		}
		else {
			$rc = 1;
			$text = $codes{$code};
		}

		if($rc != 0) {
            if ($code == 0x80) {
                $text = "Write protected FRU";
            }
			if(!$text) {
				$text = sprintf("unknown response %02x",$code);
			}
			return($rc,$text);
		}

		my $count = $returnd[1];
		if($count != $chunk) {
			$rc = 1;
			$text = "FRU write error (bytes requested: $chunk, wrote: $count)";
			return($rc,$text);
		}
	}

	return(0);
}

sub decodealert {
    my $sessdata = shift;
  my $skip_sdrinit=0;
    unless (ref $sessdata) { #called from xcat traphandler
        $sessdata = { sdr_hash => {} };
        $skip_sdrinit=1; #TODO sdr_init, cache only to avoid high trap handling overhead
    }
  my $trap = shift;
  if ($trap =~ /xCAT_plugin::ipmi/) {
    $trap=shift;
    $skip_sdrinit=1;
  }
	my $node = shift;
	my @pet = @_;
	my $rc;
	my $text;
    
	my $type;
	my $desc;
	#my $ipmisensoreventtab = "$ENV{XCATROOT}/lib/GUMI/ipmisensorevent.tab";
	#my $ipmigenericeventtab = "$ENV{XCATROOT}/lib/GUMI/ipmigenericevent.tab";

	my $offsetmask     = 0b00000000000000000000000000001111;
	my $offsetrmask    = 0b00000000000000000000000001110000;
	my $assertionmask  = 0b00000000000000000000000010000000;
	my $eventtypemask  = 0b00000000000000001111111100000000;
	my $sensortypemask = 0b00000000111111110000000000000000;
	my $reservedmask   = 0b11111111000000000000000000000000;

	my $offset      = $trap & $offsetmask;
	my $offsetr     = $trap & $offsetrmask;
	my $event_dir   = $trap & $assertionmask;
	my $event_type  = ($trap & $eventtypemask) >> 8;
	my $sensor_type = ($trap & $sensortypemask) >> 16;
	my $reserved    = ($trap & $reservedmask) >> 24;

	if($debug >= 2) {
		printf("offset:     %02xh\n",$offset);
		printf("offsetr:    %02xh\n",$offsetr);
		printf("assertion:  %02xh\n",$event_dir);
		printf("eventtype:  %02xh\n",$event_type);
		printf("sensortype: %02xh\n",$sensor_type);
		printf("reserved:   %02xh\n",$reserved);
	}

	my @hex = (0,@pet);
	my $pad = $hex[0];
	my @uuid = @hex[1..16];
	my @seqnum = @hex[17,18];
	my @timestamp = @hex[19,20,21,22];
	my @utcoffset = @hex[23,24];
	my $trap_source_type = $hex[25];
	my $event_source_type = $hex[26];
	my $sev = $hex[27];
	my $sensor_device = $hex[28];
	my $sensor_num = $hex[29];
	my $entity_id = $hex[30];
	my $entity_instance = $hex[31];
	my $event_data_1 = $hex[32];
	my $event_data_2 = $hex[33];
	my $event_data_3 = $hex[34];
	my @event_data = @hex[35..39];
	my $langcode = $hex[40];
	my $mfg_id = $hex[41] + $hex[42] * 0x100 + $hex[43] * 0x10000 + $hex[44] * 0x1000000;
	my $prod_id = $hex[45] + $hex[46] * 0x100;
	my @oem = $hex[47..@hex-1];

	if($sev == 0x00) {
		$sev = "LOG";
	}
	elsif($sev == 0x01) {
		$sev = "MONITOR";
	}
	elsif($sev == 0x02) {
		$sev = "INFORMATION";
	}
	elsif($sev == 0x04) {
		$sev = "OK";
	}
	elsif($sev == 0x08) {
		$sev = "WARNING";
	}
	elsif($sev == 0x10) {
		$sev = "CRITICAL";
	}
	elsif($sev == 0x20) {
		$sev = "NON-RECOVERABLE";
	}
	else {
		$sev = "UNKNOWN-SEVERITY:$sev";
	}
	$text = "$sev:";

	($rc,$type,$desc) = getsensorevent($sensor_type,$offset,"ipmisensorevents");
	if($rc == 1) {
		$type = "Unknown Type $sensor_type";
		$desc = "Unknown Event $offset";
		$rc = 0;
	}

	if($event_type <= 0x0c) {
		my $gtype;
		my $gdesc;
		($rc,$gtype,$gdesc) = getsensorevent($event_type,$offset,"ipmigenericevents");
		if($rc == 1) {
			$gtype = "Unknown Type $gtype";
			$gdesc = "Unknown Event $offset";
			$rc = 0;
		}

		$desc = $gdesc;
	}

	if($type eq "" || $type eq "-") {
		$type = "OEM Sensor Type $sensor_type"
	}
	if($desc eq "" || $desc eq "-") {
		$desc = "OEM Sensor Event $offset"
	}

	if($type eq $desc) {
		$desc = "";
	}

	my $extra_info = getaddsensorevent($sensor_type,$offset,$event_data_1,$event_data_2,$event_data_3);
	if($extra_info) {
		if($desc) {
			$desc = "$desc $extra_info";
		}
		else {
			$desc = "$extra_info";
		}
	}

	$text = "$text $type,";
	$text = "$text $desc";

	my $key;
	my $sensor_desc = sprintf("Sensor 0x%02x",$sensor_num);
    my %sdr_hash = %{$sessdata->{sdr_hash}};
	foreach $key (keys %sdr_hash) {
		my $sdr = $sdr_hash{$key};
		if($sdr->sensor_number == $sensor_num  and $sdr->rec_type != 192 and $sdr->rec_type != 17) {
			$sensor_desc = $sdr_hash{$key}->id_string;
			if($sdr->rec_type == 0x01) {
				last;
			}
		}
	}

	$text = "$text ($sensor_desc)";

	if($event_dir) {
		$text = "$text - Recovered";
	}

	return(0,$text);
}

sub readauxentry {
    my $netfn=0x2e<<2;
    my $entrynum = shift;
    my $entryls = ($entrynum&0xff);
    my $entryms = ($entrynum>>8);
    my @cmd = (0x93,0x4d,0x4f,0x00,$entryls,$entryms,0,0,0xff,0x5); #Get log size andup to 1275 bytes of data, keeping it under 1500 to accomodate mixed-mtu circumstances
    my @data;
    my $error = docmd(
        $netfn,
        \@cmd,
        \@data
        );
    if ($error) { return $error; }
    if ($data[0]) { return $data[0]; }
    my $text;
    unless ($data[1] == 0x4d and $data[2] == 0x4f and $data[3] == 0) { return "Unrecognized response format" }
    $entrynum=$data[6]+($data[7]<<8);
    if (($data[10]&1) == 1) {
        $text="POSSIBLY INCOMPLETE DATA FOLLOWS:\n";
    }
    my $addtext="";
    if ($data[5] > 5) {
        $addtext="\nTODO:SUPPORT MORE DATA THAT WAS SEEN HERE";
    }
    @data = splice @data,11;
    pop @data;
    while(scalar(@data)) {
        my @subdata = splice @data,0,30;
        my $numbytes = scalar(@subdata);
        my $formatstring="%02x"x$numbytes;
        $formatstring =~ s/%02x%02x/%02x%02x /g;
        $text.=sprintf($formatstring."\n",@subdata);
    }
    $text.=$addtext;
    return (0,$entrynum,$text);


}



sub eventlog {
    my $sessdata = shift;
	my $subcommand = $sessdata->{subcommand};
        my $cmdargv = $sessdata->{extraargs};
    unless ($sessdata) { die "not fixed yet" }

	my $netfun = 0x0a;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;
	my @output;
	my $entry;
    $sessdata->{fullsel}=0;
	my @sel;
	my $mfg_id;
	my $prod_id;
	my $device_id;

#device id needed here
	$rc=0;
#reventlog <node> [[all] [-s] | <num> [-s]| clear]
        $subcommand = undef;
        my $arg = shift(@$cmdargv);
        while ($arg) {
            if ($arg eq "all" or $arg eq "clear" or $arg =~ /^\d+$/) {
                if (defined($subcommand)) {
                    return(1,"revenglog $subcommand $arg invalid");
                }
                $subcommand = $arg;
            } elsif ($arg =~ /^-s$/) {
                $sessdata->{sort}=1;
            } else {
                return(1,"unsupported command eventlog $arg");
            } 
            $arg = shift(@$cmdargv);
        }        

   unless (defined($subcommand)) {
      $subcommand = 'all';
   }
	if($subcommand eq "all") {
         $sessdata->{fullsel}=1;
	}
	elsif($subcommand eq "clear") {
            if (exists($sessdata->{sort})) {
                return(1,"option \"first\" can not work with $subcommand");
            }
	}
	elsif($subcommand =~ /^\d+$/) {
        $sessdata->{numevents} = $subcommand;
	$sessdata->{displayedevents} = 0;
	}
	else {
		return(1,"unsupported command eventlog $subcommand");
	}
        $sessdata->{subcommand} = $subcommand;
    $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x48,data=>[],callback=>\&eventlog_with_time,callback_args=>$sessdata);
}
sub eventlog_with_time {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
    my @returnd = (0,@{$rsp->{data}});

   #Here we set tfactor based on the delta between the BMC reported time and our
   #time.  The IPMI spec says the BMC should return seconds since 1970 in local
   #time, but the reality is the firmware pushing to the BMC has no context
   #to know, so here we guess and adjust all timestamps based on delta between
   #our now and the BMC's now
   $sessdata->{tfactor} = $returnd[4]<<24 | $returnd[3]<<16 | $returnd[2]<<8 | $returnd[1];
   if ($sessdata->{tfactor} > 0x20000000) {
      $sessdata->{tfactor} -= time(); 
   } else {
      $sessdata->{tfactor} = 0;
   }
   $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x40,data=>[],callback=>\&eventlog_with_selinfo,callback_args=>$sessdata);
}
sub eventlog_with_selinfo {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
	my $code = $rsp->{code}; 
    my @returnd = (0,@{$rsp->{data}});

	#sif($code == 0x81) { 
	#	$rc = 1;
	#	$text = "cannot execute command, SEL erase in progress";
	#}

	my $sel_version = $returnd[1];
	if($sel_version != 0x51) {
		xCAT::SvrUtils::sendmsg(sprintf("SEL version 51h support only, version reported: %x",$sel_version),$callback,$sessdata->{node},%allerrornodes);
		return;
	}

    hexdump(\@returnd);
	my $num_entries = ($returnd[3]<<8) + $returnd[2];
	if($num_entries <= 0) {
		xCAT::SvrUtils::sendmsg("no SEL entries",$callback,$sessdata->{node},%allerrornodes);
        return;
	}

	my $canres = $returnd[14] & 0b00000010;
	if(!$canres) {
        xCAT::SvrUtils::sendmsg([1,"SEL reservation not supported"],$callback,$sessdata->{node},%allerrornodes);
        return;
	}

    my $subcommand = $sessdata->{subcommand};
    if ($subcommand =~ /clear/) { #Don't bother with a reservation unless a clear is involved
        #atomic SEL retrieval need not require it, so an event during retrieval will not kill reventlog effort off
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x42,data=>[],callback=>\&clear_sel_with_reservation,callback_args=>$sessdata);
        return;
    } elsif ($sessdata->{mfg_id} == 2) {
        #read_ibm_auxlog($sessdata); #TODO JBJ fix this back in
        #return;
        #For requests other than clear, we check for IBM extended auxillary log data
    }
    $sessdata->{selentries} = [];
    $sessdata->{selentry}=0;
    if (exists($sessdata->{sort})) {
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x43,data=>[0,0,0xFF,0xFF,0x00,0xFF],callback=>\&got_sel,callback_args=>$sessdata);
    } else {
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x43,data=>[0,0,0x00,0x00,0x00,0xFF],callback=>\&got_sel,callback_args=>$sessdata);
    }
}
sub got_sel {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
    my @returnd = (0,@{$rsp->{data}});
		#elsif($code == 0x81) {
		#	$rc = 1;
		#	$text = "cannot execute command, SEL erase in progress";
		#}


	my $next_rec_ls;
	my $next_rec_ms;
	my @sel_data = @returnd[3..19];
        if (exists($sessdata->{sort})) {
            $next_rec_ls = $sel_data[0] - 1;
            $next_rec_ms = $sel_data[1];
            if (($next_rec_ls < 0) && ($next_rec_ms > 0)) {
                $next_rec_ls += 256;
                $next_rec_ms -= 1;
            }
        } else {
            $next_rec_ls = $returnd[1];
            $next_rec_ms = $returnd[2];
        }
		$sessdata->{selentry}+=1;
        if ($debug) {
			print $sessdata->{selentry}.": ";
			hexdump(\@sel_data);
        }

		my $record_id = $sel_data[0] + $sel_data[1]*256;
		my $record_type = $sel_data[2];

		if($record_type == 0x02) {
		}
		else {
			my $text=getoemevent($record_type,$sessdata->{mfg_id},\@sel_data);
            my $entry =  $sessdata->{selentry};
            if ($sessdata->{auxloginfo} and $sessdata->{auxloginfo}->{$entry}) { 
                $text.=" With additional data:\n".$sessdata->{auxloginfo}->{$entry};
            }
            if ($sessdata->{fullsel}) {
               xCAT::SvrUtils::sendmsg($text,$callback,$sessdata->{node},%allerrornodes);
            } else {
			    push(@{$sessdata->{selentries}},$text);
            }
			if(($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) or 
                           ($next_rec_ms == 0x0 && $next_rec_ls == 0x0)) {
				sendsel($sessdata);
                return;
			}
            $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x43,data=>[0,0,$next_rec_ls,$next_rec_ms,0x00,0xFF],callback=>\&got_sel,callback_args=>$sessdata);
            return;
		}

		my $timestamp = ($sel_data[3] | $sel_data[4]<<8 | $sel_data[5]<<16 | $sel_data[6]<<24);
      unless ($timestamp < 0x20000000) { #IPMI Spec says below this is effectively BMC uptime, not correctable
         $timestamp -= $sessdata->{tfactor}; #apply correction factor based on how off the current BMC clock is from management server
      }
		my ($seldate,$seltime) = timestamp2datetime($timestamp);
#		$text = "$entry: $seldate $seltime";
		my $text = ":$seldate $seltime";

#		my $gen_id_slave_addr = ($sel_data[7] & 0b11111110) >> 1;
#		my $gen_id_slave_addr_hs = ($sel_data[7] & 0b00000001);
#		my $gen_id_ch_num = ($sel_data[8] & 0b11110000) >> 4;
#		my $gen_id_ipmb = ($sel_data[8] & 0b00000011);

		my $sensor_owner_id = $sel_data[7];
		my $sensor_owner_lun = $sel_data[8];

		my $sensor_type = $sel_data[10];
		my $sensor_num = $sel_data[11];
		my $event_dir = $sel_data[12] & 0b10000000;
		my $event_type = $sel_data[12] & 0b01111111;
		my $offset = $sel_data[13] & 0b00001111;
		my $event_data_1 = $sel_data[13];
		my $event_data_2 = $sel_data[14];
		my $event_data_3 = $sel_data[15];
		my $sev = 0;
		$sev = ($sel_data[14] & 0b11110000) >> 4;
#		if($event_type != 1) {
#			$sev = ($sel_data[14] & 0b11110000) >> 4;
#		}
#		$text = "$text $sev:";

		my $type;
		my $desc;
        my $rc;
		($rc,$type,$desc) = getsensorevent($sensor_type,$offset,"ipmisensorevents");
		if($rc == 1) {
			$type = "Unknown Type $sensor_type";
			$desc = "Unknown Event $offset";
			$rc = 0;
		}

		if($event_type <= 0x0c) {
			my $gtype;
			my $gdesc;
			($rc,$gtype,$gdesc) = getsensorevent($event_type,$offset,"ipmigenericevents");
			if($rc == 1) {
				$gtype = "Unknown Type $gtype";
				$gdesc = "Unknown Event $offset";
				$rc = 0;
			}

			$desc = $gdesc;
		}

		if($type eq "" || $type eq "-") {
			$type = "OEM Sensor Type $sensor_type"
		}
		if($desc eq "" || $desc eq "-") {
			$desc = "OEM Sensor Event $offset"
		}

		if($type eq $desc) {
			$desc = "";
		}

		my $extra_info = getaddsensorevent($sensor_type,$offset,$event_data_1,$event_data_2,$event_data_3);
		if($extra_info) {
			if($desc) {
				$desc = "$desc $extra_info";
			}
			else {
				$desc = "$extra_info";
			}
		}

		$text = "$text $type,";
		$text = "$text $desc";

#		my $key;
		my $key = $sensor_owner_id . "." . $sensor_owner_lun . "." . $sensor_num;
		my $sensor_desc = sprintf("Sensor 0x%02x",$sensor_num);
#		foreach $key (keys %sdr_hash) {
#			my $sdr = $sdr_hash{$key};
#			if($sdr->sensor_number == $sensor_num) {
#				$sensor_desc = $sdr_hash{$key}->id_string;
#				last;
#			}
#		}
        my %sdr_hash = %{$sessdata->{sdr_hash}};
		if(defined $sdr_hash{$key}) {
			$sensor_desc = $sdr_hash{$key}->id_string;
         if ($sdr_hash{$key}->event_type_code == 1) {
            if (($event_data_1 & 0b11000000) == 0b01000000) {
               $sensor_desc .= " reading ".translate_sensor($event_data_2,$sdr_hash{$key});
               if (($event_data_1 & 0b00110000) == 0b00010000) {
                  $sensor_desc .= " with threshold " . translate_sensor($event_data_3,$sdr_hash{$key});
               }
            }
         }
		}

		$text = "$text ($sensor_desc)";

		if($event_dir) {
			$text = "$text - Recovered";
		}
        my $entry = $sessdata->{selentry};
        if ($sessdata->{bmcnum} !=1) {
		$text .= " on BMC ".$sessdata->{bmcnum};
	}
        if ($sessdata->{auxloginfo} and $sessdata->{auxloginfo}->{$entry}) {
             $text.=" with additional data:";
             if ($sessdata->{fullsel} || ( $sessdata->{numevents}
	        && $sessdata->{numevents} > $sessdata->{displayedevents})) {
                xCAT::SvrUtils::sendmsg($text,$callback,$sessdata->{node},%allerrornodes);
                foreach (split /\n/,$sessdata->{auxloginfo}->{$entry}) {
                    xCAT::SvrUtils::sendmsg($_,$sessdata->{node});
                }
                $sessdata->{displayedevents}++;
             } else {
        		push(@{$sessdata->{selentries}},$text);
                push @{$sessdata->{selentries}},split /\n/,$sessdata->{auxloginfo}->{$entry};
             } 

        } else {
            if ($sessdata->{fullsel} || ($sessdata->{numevents}
	        && $sessdata->{numevents} > $sessdata->{displayedevents})) {
                xCAT::SvrUtils::sendmsg($text,$callback,$sessdata->{node},%allerrornodes);
		$sessdata->{displayedevents}++;
            } else {
        		push(@{$sessdata->{selentries}},$text);
            }
        }

		if(($next_rec_ms == 0xFF && $next_rec_ls == 0xFF) or 
                   ($next_rec_ms == 0x0 && $next_rec_ls == 0x0)) {
				sendsel($sessdata);
                return;
		}
        $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x43,data=>[0,0,$next_rec_ls,$next_rec_ms,0x00,0xFF],callback=>\&got_sel,callback_args=>$sessdata);
}

sub sendsel {
    my $sessdata = shift;
####my @routput = reverse(@output);
####my @noutput;
####my $c;
####foreach(@routput) {
####	$c++;
####	if($c > $num) {
####		last;
####	}
####	push(@noutput,$_);
####}
####@output = reverse(@noutput);

####return($rc,@output);
}
sub clear_sel_with_reservation {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
    my @returnd = (0,@{$rsp->{data}});
    	#elsif($code == 0x81) {
    	#	$rc = 1;
    	#	$text = "cannot execute command, SEL erase in progress";
    	#}
        $sessdata->{res_id_ls} = $returnd[1];
        $sessdata->{res_id_ms} = $returnd[2];
    $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x47,data=>[$sessdata->{res_id_ls},$sessdata->{res_id_ms},0x43,0x4c,0x52,0xaa],callback=>\&wait_for_selerase,callback_args=>$sessdata);
}
sub wait_for_selerase {
    my $rsp = shift;
    my $sessdata = shift;
    my @returnd = (0,@{$rsp->{data}});
	my $erase_status = $returnd[1] & 0b00000001;
    xCAT::SvrUtils::sendmsg("SEL cleared",$callback,$sessdata->{node},%allerrornodes);
}

#commenting out usless 'while 0' loop.
#skip test for now, need to get new res id for some machines
#   	while($erase_status == 0 && 0) {
#   		sleep(1);
#   		@cmd=(0x47,$res_id_ls,$res_id_ms,0x43,0x4c,0x52,0x00);
#   		$error = docmd(
#   			$netfun,
#   			\@cmd,
#   			\@returnd
#   		);

#   		if($error) {
#   			$rc = 1;
#   			$text = $error;
#   			return($rc,$text);
#   		}

#   		$code = $returnd[0];

#   		if($code == 0x00) {
#   		}
#   		else {
#   			$rc = 1;
#   			$text = $codes{$code};
#   		}

#   		if($rc != 0) {
#   			if(!$text) {
#   				$text = sprintf("unknown response %02x",$code);
#   			}
#   			return($rc,$text);
#   		}

#   		$erase_status = $returnd[1] & 0b00000001;
#   	}

sub read_ibm_auxlog {
    my $sessdata = shift;
    my $entry = $sessdata->{selentry};
        my @auxdata;
        my $netfn = 0xa << 2;
        my @auxlogcmd = (0x5a,1);
        my $error = docmd(
            $netfn,
            \@auxlogcmd,
            \@auxdata);
        #print Dumper(\@auxdata);
        unless ($error or $auxdata[0] or $auxdata[5] != 0x4d or $auxdata[6] != 0x4f or $auxdata[7] !=0x0 ) { #Don't bother if support cannot be confirmed by service processor
            $netfn=0x2e<<2; #switch netfunctions to read
            my $numauxlogs = $auxdata[8]+($auxdata[9]<<8);
            my $auxidx=1;
            my $rc;
            my $entry;
            my $extdata;
            while ($auxidx<=$numauxlogs) {
                ($rc,$entry,$extdata) = readauxentry($auxidx++);
                unless ($rc) {
                    if ($sessdata->{auxloginfo}->{$entry}) {
                        $sessdata->{auxloginfo}->{$entry}.="!".$extdata;
                    } else {
                        $sessdata->{auxloginfo}->{$entry}=$extdata;
                    }
                }
            }
            if ($sessdata->{auxloginfo}->{0}) {
                if ($sessdata->{fullsel}) {
                    foreach (split /!/,$sessdata->{auxloginfo}->{0}) {
                        sendoutput(0,":Unassociated auxillary data detected:");
                        foreach (split /\n/,$_) {
                            sendoutput(0,$_);
                        }
                    }
                }
            }
            #print Dumper(\%auxloginfo);
        }
}

sub getoemevent {
	my $record_type = shift;
	my $mfg_id = shift;
	my $sel_data = shift;
    my $sessdata;
	my $text=":";
	if ($record_type < 0xE0 && $record_type > 0x2F) { #Should be timestampped, whatever it is
		my $timestamp =  (@$sel_data[3] | @$sel_data[4]<<8 | @$sel_data[5]<<16 | @$sel_data[6]<<24);
      unless ($timestamp < 0x20000000) {
         $timestamp -= $sessdata->{tfactor};
      }
		my ($seldate,$seltime) = timestamp2datetime($timestamp);
		my @rest = @$sel_data[7..15];
		if ($mfg_id==2) {
			$text.="$seldate $seltime IBM OEM Event-";
			if ($rest[3]==0 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."PCI Event/Error, details in next event"
			} elsif ($rest[3]==1 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."Processor Event/Error occurred, details in next event"
			} elsif ($rest[3]==2 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."Memory Event/Error occurred, details in next event"
			} elsif ($rest[3]==3 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."Scalability Event/Error occurred, details in next event"
			} elsif ($rest[3]==4 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."PCI bus Event/Error occurred, details in next event"
			} elsif ($rest[3]==5 && $rest[4]==0 && $rest[7]==0) {
				$text=$text."Chipset Event/Error occurred, details in next event"
			} elsif ($rest[3]==6 && $rest[4]==1 && $rest[7]==0) {
				$text=$text."BIOS/BMC Power Executive mismatch (BIOS $rest[5], BMC $rest[6])"
			} elsif ($rest[3]==6 && $rest[4]==2 && $rest[7]==0) {
				$text=$text."Boot denied due to power limitations"
			} else {
				$text=$text."Unknown event ". phex(\@rest);
			}
		} else {
		     $text .= "$seldate $seltime " . sprintf("Unknown OEM SEL Type %02x:",$record_type) . phex(\@rest);
		}
	} else { #Non-timestamped
		my %memerrors = (
			0x00 => "DIMM enabled",
			0x01 => "DIMM disabled, failed ECC test",
			0x02 => "POST/BIOS memory test failed, DIMM disabled",
			0x03 => "DIMM disabled, non-supported memory device",
			0x04 => "DIMM disabled, non-matching or missing DIMM(s)",
		);
		my %pcierrors = (
			0x00 => "Device OK",
			0x01 => "Required ROM space not available",
			0x02 => "Required I/O Space not available",
			0x03 => "Required memory not available",
			0x04 => "Required memory below 1MB not available",
			0x05 => "ROM checksum failed",
			0x06 => "BIST failed",
			0x07 => "Planar device missing or disabled by user",
			0x08 => "PCI device has an invalid PCI configuration space header",
			0x09 => "FRU information for added PCI device",
			0x0a => "FRU information for removed PCI device",
			0x0b => "A PCI device was added, PCI FRU information is stored in next log entry",
			0x0c => "A PCI device was removed, PCI FRU information is stored in next log entry",
			0x0d => "Requested resources not available",
			0x0e => "Required I/O Space Not Available",
			0x0f => "Required I/O Space Not Available",
			0x10 => "Required I/O Space Not Available",
			0x11 => "Required I/O Space Not Available",
			0x12 => "Required I/O Space Not Available",
			0x13 => "Planar video disabled due to add in video card",
			0x14 => "FRU information for PCI device partially disabled ",
			0x15 => "A PCI device was partially disabled, PCI FRU information is stored in next log entry",
			0x16 => "A 33Mhz device is installed on a 66Mhz bus, PCI device information is stored in next log entry",
			0x17 => "FRU information, 33Mhz device installed on 66Mhz bus",
			0x18 => "Merge cable missing",
			0x19 => "Node 1 to Node 2 cable missing",
			0x1a => "Node 1 to Node 3 cable missing",
			0x1b => "Node 2 to Node 3 cable missing",
			0x1c => "Nodes could not merge",
			0x1d => "No 8 way SMP cable",
			0x1e => "Primary North Bridge to PCI Host Bridge IB Link has failed",
			0x1f => "Redundant PCI Host Bridge IB Link has failed",
		);
		my %procerrors = (
			0x00 => "Processor has failed BIST",
			0x01 => "Unable to apply processor microcode update",
			0x02 => "POST does not support current stepping level of processor",
			0x03 => "CPU mismatch detected",
		);
		my @rest = @$sel_data[3..15];
		if ($record_type == 0xE0 && $rest[0]==2 && $mfg_id==2 && $rest[1]==0 && $rest[12]==1) { #Rev 1 POST memory event
			$text="IBM Memory POST Event-";
			my $msuffix=sprintf(", chassis %d, card %d, dimm %d",$rest[3],$rest[4],$rest[5]);
			#the next bit is a basic lookup table, should implement as a table ala ibmleds.tab, or a hash... yeah, a hash...
			$text=$text.$memerrors{$rest[2]}.$msuffix;
		} elsif ($record_type == 0xE0 && $rest[0]==1 && $mfg_id==2 && $rest[12]==0) { #A processor error or event, rev 0 only known in the spec I looked at
			$text=$text.$procerrors{$rest[1]};
		} elsif ($record_type == 0xE0 && $rest[0]==0 && $mfg_id==2) { #A PCI error or event, rev 1 or 2, the revs differe in endianness
			my $msuffix;
			if ($rest[12]==0) {
				$msuffix=sprintf("chassis %d, slot %d, bus %s, device %02x%02x:%02x%02x",$rest[2],$rest[3],$rest[4],$rest[5],$rest[6],$rest[7],$rest[8]);
			} elsif ($rest[12]==1) {
				$msuffix=sprintf("chassis %d, slot %d, bus %s, device %02x%02x:%02x%02x",$rest[2],$rest[3],$rest[4],$rest[5],$rest[6],$rest[7],$rest[8]);
			} else {
				return ("Unknown IBM PCI event/error format");
			}
			$text=$text.$pcierrors{$rest[1]}.$msuffix;
		} else {
			#Some event we can't define that is OEM or some otherwise unknown event
			$text = sprintf("SEL Type %02x:",$record_type) . phex(\@rest);
		}
	} #End timestampped intepretation
	return ($text);
}

sub getsensorevent
{
	my $sensortype = sprintf("%02Xh",shift);
	my $sensoroffset = sprintf("%02Xh",shift);
	my $file = shift;

	my @line;
	my $type;
	my $code;
	my $desc;
	my $offset;
	my $rc = 1;

    if ($file eq "ipmigenericevents") {
      if ($xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,$sensoroffset"}) {
        ($type,$desc) = split (/,/,$xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,$sensoroffset"},2);
	    return(0,$type,$desc);
      }
      if ($xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,-"}) {
        ($type,$desc) = split (/,/,$xCAT::data::ipmigenericevents::ipmigenericevents{"$sensortype,-"},2);
	    return(0,$type,$desc);
       }
    }
    if ($file eq "ipmisensorevents") {
      if ($xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,$sensoroffset"}) {
        ($type,$desc) = split (/,/,$xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,$sensoroffset"},2);
	    return(0,$type,$desc);
      }
      if ($xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,-"}) {
        ($type,$desc) = split (/,/,$xCAT::data::ipmisensorevents::ipmisensorevents{"$sensortype,-"},2);
	    return(0,$type,$desc);
       }
    }
    return (0,"No Mappings found ($sensortype)","No Mappings found ($sensoroffset)");
}

sub getaddsensorevent {
	my $sensor_type = shift;
	my $offset = shift;
	my $event_data_1 = shift;
	my $event_data_2 = shift;
	my $event_data_3 = shift;
	my $text = "";

    if ($sensor_type == 0x08 && $offset == 6) {
        my %extra = (
            0x0 => "Vendor mismatch",
            0x1 => "Revision mismatch",
            0x2 => "Processor missing",
	    0x3 => "Power Supply rating mismatch",
	    0x4 => "Voltage rating mismatch",
            );
        if ($extra{$event_data_3}) {
            $text = $extra{$event_data_3};
        }
    }
    if ($sensor_type == 0x0C) {
        $text = sprintf ("Memory module %d",$event_data_3);
    }

	if($sensor_type == 0x0f) {
		if($offset == 0x00) {
			my %extra = (
				0x00 => "Unspecified",
				0x01 => "No system memory installed",
				0x02 => "No usable system memory",
				0x03 => "Unrecoverable hard disk failure",
				0x04 => "Unrecoverable system board failure",
				0x05 => "Unrecoverable diskette failure",
				0x06 => "Unrecoverable hard disk controller failure",
				0x07 => "Unrecoverable keyboard failure",
				0x08 => "Removable boot media not found",
				0x09 => "Unrecoverable video controller failure",
				0x0a => "No video device detected",
				0x0b => "Firmware (BIOS) ROM corruption detected",
				0x0c => "CPU voltage mismatch",
				0x0d => "CPU speed matching failure",
			);
			$text = $extra{$event_data_2};
		}
		if($offset == 0x02) {
			my %extra = (
				0x00 => "Unspecified",
				0x01 => "Memory initialization",
				0x02 => "Hard-disk initialization",
				0x03 => "Secondary processor(s) initialization",
				0x04 => "User authentication",
				0x05 => "User-initiated system setup",
				0x06 => "USB resource configuration",
				0x07 => "PCI resource configuration",
				0x08 => "Option ROM initialization",
				0x09 => "Video initialization",
				0x0a => "Cache initialization",
				0x0b => "SM Bus initialization",
				0x0c => "Keyboard controller initialization",
				0x0d => "Embedded controller/management controller initialization",
				0x0e => "Docking station attachement",
				0x0f => "Enabling docking station",
				0x10 => "Docking staion ejection",
				0x11 => "Disable docking station",
				0x12 => "Calling operation system wake-up vector",
				0x13 => "Starting operation system boot process, call init 19h",
				0x14 => "Baseboard or motherboard initialization",
				0x16 => "Floppy initialization",
				0x17 => "Keyboard test",
				0x18 => "Pointing device test",
				0x19 => "Primary processor initialization",
			);
			$text = $extra{$event_data_2};
		}
	}
    if ($sensor_type == 0x10) {
        if ($offset == 0x0) {
            $text = sprintf("Memory module %d",$event_data_2);
        } elsif ($offset == 0x01) {
            $text = "Disabled for ";
            unless ($event_data_3 & 0x20) {
                if ($event_data_3 & 0x10) {
                    $text .= "assertions of";
                } else {
                    $text .= "deassertions of";
                } 
            }
            $text .= sprintf ("type %02xh/offset %02xh",$event_data_2,$event_data_3&0x0F);
        } elsif ($offset == 0x05) {
            $text = "$event_data_3% full";
        }elsif($offset==0x06){
	    if(defined $event_data_2){
	    	if(defined($event_data_3) and ($event_data_3 & 0x80 == 0x80)){
			$text="Vendor-specific processor number:";
	    	}else{
	        	$text="Entity Instance number:";
	   	}
		$text.=sprintf("%02xh",$event_data_2 & 0xff);
	    }else{
		$text="for all Processor sensors";
	    }
	}
    }
            
	if($sensor_type == 0x12) {
		if($offset == 0x03) {
			my %extra={
				    0x0 => "Log Entry Action: entry added",
				    0x1 => "Log Entry Action: entry added because event did not be map to standard IPMI event",
				    0x2 => "Log Entry Action: entry added along with one or more corresponding SEL entries",
				    0x3 => "Log Entry Action: log cleared",
				    0x4 => "Log Entry Action: log disabled",
				    0x5 => "Log Entry Action: log enabled",
				  };
			$text="$text, ".$extra{($event_data_2>>4) & 0x0f};
			%extra={
				   0x0 => "Log Type:MCA Log",
				   0x1 => "Log Type:OEM 1",
				   0x2 => "Log Type:OEM 2",
				};
			$text="$text, ".$extra{($event_data_2) & 0x0f};
			$text =~ s/^, //;
		}

		if($offset == 0x04) {
			if($event_data_2 & 0b00100000) {
				$text = "$text, NMI";
			}
			if($event_data_2 & 0b00010000) {
				$text = "$text, OEM action";
			}
			if($event_data_2 & 0b00001000) {
				$text = "$text, power cycle";
			}
			if($event_data_2 & 0b00000100) {
				$text = "$text, reset";
			}
			if($event_data_2 & 0b00000010) {
				$text = "$text, power off";
			}
			if($event_data_2 & 0b00000001) {
				$text = "$text, Alert";
			}
			$text =~ s/^, //;
		}
	        if($offset == 0x05){
			if($event_data_2 & 0x80){
				$text="$text, event is second of pair";
			}elsif($event_data_2 & 0x80==0){
				$text="$text, event is first of pair";
			}		
			if($event_data_2 & 0x0F == 0x1){
				$text="$text, SDR Timestamp Clock updated";	
			}elsif($event_data_2 & 0x0F == 0x0){
				 $text="$text, SEL Timestamp Clock updated";
			}
			$text =~ s/^, //;
		}
     }

    if ($sensor_type == 0x1d && $offset == 0x07) {
        my %causes = (
            0 => "Unknown",
            1 => "Chassis reset via User command to BMC",
            2 => "Reset button",
            3 => "Power button",
            4 => "Watchdog action",
            5 => "OEM",
            6 => "AC Power apply force on",
            7 => "Restore previous power state on AC",
            8 => "PEF initiated reset",
            9 => "PEF initiated power cycle",
            10 => "Soft reboot",
            11 => "RTC Wake",
        );
        if ($causes{$event_data_2 & 0xf}) {
            $text = $causes{$event_data_2};
        } else {
            $text = "Unrecognized cause ".$event_data_2 & 0xf;
        }
        $text .= "via channel $event_data_3";
    }
    if ($sensor_type == 0x21) {
        my %extra = (
            0 => "PCI slot",
            1 => "Drive array",
            2 => "External connector",
            3 => "Docking port",
            4 => "Other slot",
            5 => "Sensor ID",
            6 => "AdvncedTCA",
            7 => "Memory slot",
            8 => "FAN",
            9 => "PCIe",
            10 => "SCSI",
            11 => "SATA/SAS",
         );

        $text=$extra{$event_data_2 & 127};
        unless ($text) {
            $text = "Unknown slot/conn type ".$event_data_2&127;
        }
        $text .= " $event_data_3";
    }
    if ($sensor_type == 0x23) {
        my %extra = (
            0x10 => "SMI",
            0x20 => "NMI",
            0x30 => "Messaging Interrupt",
            0xF0 => "Unspecified",
            0x01 => "BIOS FRB2",
            0x02 => "BIOS/POST",
            0x03 => "OS Load",
            0x04 => "SMS/OS",
            0x05 => "OEM",
            0x0F => "Unspecified"
        );
        if ($extra{$event_data_2 & 0xF0}) {
            $text = $extra{$event_data_2 & 0xF0};
        }
        if ($extra{$event_data_2 & 0x0F}) {
            $text .= ", ".$extra{$event_data_2 & 0x0F};
        }
        $text =~ s/^, //;
    }
    if ($sensor_type == 0x28) {
        if ($offset == 0x4) {
            $text = "Sensor $event_data_2";
        } elsif ($offset == 0x5) {
            $text = "";
            my $logicalfru=0;
            if ($event_data_2 & 128) {
                $logicalfru=1;
            }
            my $intelligent=1;
            if ($event_data_2 & 24) {
                $text .= "LUN ".($event_data_2&24)>>3;
            } else {
                $intelligent=0;
            }
            if ($event_data_2 & 7) {
                $text .= "Bus ID ".($event_data_2&7);
            }
            if ($logicalfru) {
                $text .= "FRU ID ".$event_data_3;
            } elsif (not $intelligent) {
                $text .= "I2C addr ".$event_data_3>>1;
            }
        }
    }

    if ($sensor_type == 0x2a) {
        $text = sprintf("Channel %d, User %d",$event_data_3&0x0f,$event_data_2&0x3f);
        if ($offset == 1) {
            if (($event_data_3 & 207) == 1) {
                $text .= " at user request";
            } elsif (($event_data_3 & 207) == 2) {
                $text .= " timed out";
            } elsif (($event_data_3 & 207) == 3) {
                $text .= " configuration change";
            }
        }
    }
    if ($sensor_type == 0x2b) {
        my %extra = (
            0x0 => "Unspecified",
            0x1 => "BMC device ID",
            0x2 => "BMC Firmware",
            0x3 => "BMC Hardware",
            0x4 => "BMC manufacturer",
            0x5 => "IPMI Version",
            0x6 => "BMC aux firmware ID",
            0x7 => "BMC boot block",
            0x8 => "Other BMC Firmware",
            0x09 => "BIOS/EFI change",
            0x0a => "SMBIOS change",
            0x0b => "OS change",
            0x0c => "OS Loader change",
            0x0d => "Diagnostics change",
            0x0e => "Management agent change",
            0x0f => "Management software change",
            0x10 => "Management middleware change",
            0x11 => "FPGA/CPLD/PSoC change",
            0x12 => "FRU change",
            0x13 => "device addition/removal",
            0x14 => "Equivalent replacement",
            0x15 => "Newer replacement",
            0x16 => "Older replacement",
            0x17 => "DIP/Jumper change",
        );
        if ($extra{$event_data_2}) {
            $text = $extra{$event_data_2};
        } else {
            $text = "Unknown version change type $event_data_2";
        }
    }
    if ($sensor_type == 0x2c) {
        my %extra = (
            0 => "",
            1 => "Software dictated",
            2 => "Latch operated",
            3 => "Hotswap buton pressed",
            4 => "automatic operation",
            5 => "Communication lost",
            6 => "Communication lost locally",
            7 => "Unexpected removal",
            8 => "Operator intervention",
            9 => "Unknwon IPMB address",
            10 => "Unexpected deactivation",
            0xf => "unknown",
            );
        if ($extra{$event_data_2>>4}) {
              $text = $extra{$event_data_2>>4};
          } else {
              $text = "Unrecognized cause ".$event_data_2>>4;
          }
          my $prev_state=$event_data_2 & 0xf;
          unless ($prev_state == $offset) {
              my %oldstates = ( 
                0 => "Not Installed",
                1 => "Inactive",
                2 => "Activation requested",
                3 => "Activating",
                4 => "Active",
                5 => "Deactivation requested",
                6 => "Deactivating",
                7 => "Communication lost",
            );
            if ($oldstates{$prev_state}) {
                $text .= "(was ".$oldstates{$prev_state}.")";
            } else {
                $text .= "(was in unrecognized state $prev_state)";
            }
          }
    }



	return($text);
}

sub initiem {
    my $sessdata = shift;
    $sessdata->{iem} =  IBM::EnergyManager->new();
    my @payload = $sessdata->{iem}->get_next_payload();
    my $netfun = shift @payload;
    my $command = shift @payload;
    $sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@payload,callback=>\&ieminitted,callback_args=>$sessdata);
}
sub ieminitted {
    my $rsp = shift;
    my $sessdata = shift;
    my @returnd = ($rsp->{code},@{$rsp->{data}});
    $sessdata->{iem}->handle_next_payload(@returnd);
    $sessdata->{iemcallback}->($sessdata);
}

sub readenergy {
    my $sessdata = shift;
    unless ($iem_support) { 
        xCAT::SvrUtils::sendmsg([1,"IBM::EnergyManager package required for this value"],$callback,$sessdata->{node},%allerrornodes);
        return;
    }
    my @entries;
    $sessdata->{iemcallback}=\&readenergy_withiem;
    initiem($sessdata);
}
sub readenergy_withiem {
    my $sessdata = shift;
    $sessdata->{iem}->prep_get_precision();
    $sessdata->{iemcallback} = \&got_precision;
    execute_iem_commands($sessdata); #this gets all precision data initialized for AC and DC
					# we need not make use of the generic extraction function, so we call execute_iem instead of process_data
					#sorry the perl api I wrote sucks..
}
sub got_precision {
    my $sessdata = shift;
    $sessdata->{iem}->prep_get_ac_energy();
    $sessdata->{iemcallback} = \&got_ac_energy;
    process_data_from_iem($sessdata);
}
sub got_ac_energy {
    my $sessdata = shift;
    unless ($sessdata->{abortediem}) {
    	$sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%",$sessdata->{iem}->energy_ac_precision()*0.1); #note while \x{B1} would be cool, it's non-trivial to support
	xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
	$sessdata->{abortediem}=0;
   }
   #this would be 'if sessdata->{abortediem}'.  Thus far this is only triggered in the case of a system that fairly obviously
   #shouldn't have an AC meter.  As a consequence, don't output data that would suggest a user might actually get it
   #in that case, another entity can provide a measure of the AC usage, but only an aggregate measure not an individual measure
#        $sessdata->{iemtextdata} = "AC Energy Usage: ";
#        if ($sessdata->{abortediemreason}) {
#             $sessdata->{iemtextdata} .= $sessdata->{abortediemreason};
#         }
#        xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
    $sessdata->{iem}->prep_get_dc_energy();
    $sessdata->{iemcallback} = \&got_dc_energy;
    process_data_from_iem($sessdata);
}
sub got_ac_energy_with_precision {
    my $sessdata=shift;
    $sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%",$sessdata->{iem}->energy_ac_precision()*0.1); #note while \x{B1} would be cool, it's non-trivial to support
    xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
    $sessdata->{iem}->prep_get_dc_energy();
    $sessdata->{iemcallback} = \&got_dc_energy;
    process_data_from_iem($sessdata);
}
sub got_dc_energy {
    my $sessdata = shift;
    $sessdata->{iemtextdata} .= sprintf(" +/-%.1f%%",$sessdata->{iem}->energy_dc_precision()*0.1);
    xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
    if (scalar @{$sessdata->{sensorstoread}}) {
        $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
        readsensor($sessdata); #next sensor
    }

}

sub execute_iem_commands {
    my $sessdata = shift;
    my @payload = $sessdata->{iem}->get_next_payload();
    if (scalar @payload) {
        my $netfun = shift @payload;
        my $command = shift @payload;
        $sessdata->{ipmisession}->subcmd(netfn=>$netfun,command=>$command,data=>\@payload,callback=>\&executed_iem_command,callback_args=>$sessdata);
    } else { #complete, return to callback
        $sessdata->{iemcallback}->($sessdata);
    }
}
sub executed_iem_command {
    my $rsp = $_[0];
    my $sessdata = $_[1];
    if ($rsp->{code} == 0xcb) {
	$sessdata->{abortediem}=1;
	$sessdata->{abortediemreason}="Not Present";
	$sessdata->{iemcallback}->($sessdata);
        return;	
    }
    if (check_rsp_errors(@_)) { #error while in an IEM transaction, skip to the end
	$sessdata->{abortediem}=1;
	$sessdata->{iemcallback}->($sessdata);
        return;
    }
    my @returnd = ($rsp->{code},@{$rsp->{data}});
    $sessdata->{iem}->handle_next_payload(@returnd);
    execute_iem_commands($sessdata);
}

sub process_data_from_iem {
    my $sessdata = shift;
    
    my @returnd;
    $sessdata->{iemdatacallback} = $sessdata->{iemcallback};
    $sessdata->{iemcallback} = \&got_data_to_process_from_iem;
    execute_iem_commands($sessdata);
}
sub got_data_to_process_from_iem {
    my $sessdata = shift;
    my @iemdata = $sessdata->{iem}->extract_data;
    my $label = shift @iemdata;
    my $units = shift @iemdata;
    my $value=0;
    my $shift=0;
    while (scalar @iemdata) { #stuff the 64-bits of data into an int, would break in 32 bit
        $value+=pop(@iemdata)<<$shift;
        #$value.=sprintf("%02x ",shift @iemdata);
        $shift+=8;
    }
    if ($units eq "mJ") {
        $units = "kWh";
        $value = $value / 3600000000;
        $sessdata->{iemtextdata} = sprintf("$label: %.4f $units",$value);
    } elsif ($units eq "mW") {
        $units = "W";
        $value = $value / 1000.0;
        $sessdata->{iemtextdata} = sprintf("$label: %.1f $units",$value);
    }
    $sessdata->{iemdatacallback}->($sessdata);
}

sub gotchassis { #get chassis status command
    my $rsp = shift;
    my $sessdata = shift;
    unless (check_rsp_errors($rsp,$sessdata)) {
        my @data = @{$rsp->{data}};
        my $powerstat;
        if ($data[0] & 1) { 
            $powerstat="on";
        } else {
            $powerstat="off";
        }
		xCAT::SvrUtils::sendmsg("Power Status: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[0] & 0b10) {
            $powerstat="true";
        } else {
            $powerstat="false";
        }
		xCAT::SvrUtils::sendmsg("Power Overload: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[0] & 0b100) {
            $powerstat="active";
        } else {
            $powerstat="inactive";
        }
		xCAT::SvrUtils::sendmsg("Power Interlock: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[0] & 0b1000) {
            $sessdata->{healthsummary} &= 2; #set to critical state
            $powerstat="true";
        } else {
            $powerstat="false";
        }
		xCAT::SvrUtils::sendmsg("Power Fault: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[0] & 0b10000) {
            $sessdata->{healthsummary} &= 2; #set to critical state
            $powerstat="true";
        } else {
            $powerstat="false";
        }
		xCAT::SvrUtils::sendmsg("Power Control Fault: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        my $powpolicy = ($data[0] & 0b1100000) >> 5;
        my %powpolicies = ( 0 => "Always off", 1 => "Last State", 2 => "Always on", 3 => "Unknown" );
		xCAT::SvrUtils::sendmsg("Power Restore Policy: ".$powpolicies{$powpolicy},$callback,$sessdata->{node},%allerrornodes);
        my @lastevents;
        if ($data[1] & 0b1) {
            push @lastevents,"AC failed";
        }
        if ($data[1] & 0b10) {
            $sessdata->{healthsummary} &= 2; #set to critical state
            push @lastevents,"Power overload";
        }
        if ($data[1] & 0b100) {
            $sessdata->{healthsummary} &= 1; #set to critical state
            push @lastevents,"Interlock activated";
        }
        if ($data[1] & 0b1000) {
            $sessdata->{healthsummary} &= 2; #set to critical state
            push @lastevents,"Power Fault";
        }
        if ($data[1] & 0b10000) {
            push @lastevents,"By Request";
        }
        my $lastevent = join(",",@lastevents);
		xCAT::SvrUtils::sendmsg("Last Power Event: $lastevent",$callback,$sessdata->{node},%allerrornodes);
        if ($data[2] & 0b1) {
            $sessdata->{healthsummary} &= 1; #set to warn state
            $powerstat = "active";
        } else {
            $powerstat = "inactive";
        }
		xCAT::SvrUtils::sendmsg("Chassis intrusion: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[2] & 0b10) {
            $powerstat = "active";
        } else { 
            $powerstat = "inactive";
        }
		xCAT::SvrUtils::sendmsg("Front Panel Lockout: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[2] & 0b100) { # drive fault
            $sessdata->{healthsummary} &= 2; #set to critical state
            $powerstat = "true";
        } else { 
            $powerstat = "false";
        }
		xCAT::SvrUtils::sendmsg("Drive Fault: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[2] & 0b1000) { # fan fault
            $sessdata->{healthsummary} &= 1; #set to warn state
            $powerstat = "true";
        } else { 
            $powerstat = "false";
        }
		xCAT::SvrUtils::sendmsg("Cooling Fault: $powerstat",$callback,$sessdata->{node},%allerrornodes);
        if ($data[2] & 0b1000000) { #can look at light status
            my $idstat = ($data[2] & 0b110000) >> 4;
            my %idstats = ( 0 => "off", 1 => "on", 2 => "on", 3 => "unknown" );
            xCAT::SvrUtils::sendmsg("Identify Light: ".$idstats{$idstat},$callback,$sessdata->{node},%allerrornodes);
        }
    }
            #$sessdata->{powerstatprefix}="Power Status: ";
    if ($sessdata->{sensorstoread} and scalar @{$sessdata->{sensorstoread}}) {
        $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
        readsensor($sessdata); #next sensor
    }
}
sub readchassis {
    my $sessdata = shift;
    $sessdata->{ipmisession}->subcmd(netfn=>0x0,command=>0x1,data=>[],callback=>\&gotchassis,callback_args=>$sessdata);
    
}
sub checkleds {
    my $sessdata = shift;
	my @cmd;
	my @returnd = ();
	my $error;
	my $led_id_ms;
	my $led_id_ls;
	my $rc = 0;
	my @output =();
	my $text="";
	my $key;
	my $mfg_id=$sessdata->{mfg_id};
#TODO device id
	if ($mfg_id != 2 and $mfg_id != 20301) {
		xCAT::SvrUtils::sendmsg("LED status not supported on this system",$callback,$sessdata->{node},%allerrornodes);
        return;
	}
	
    my %sdr_hash = %{$sessdata->{sdr_hash}};
    $sessdata->{doleds} = [];
	foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) {
		my $sdr = $sdr_hash{$key};
		if($sdr->rec_type == 0xC0 && $sdr->sensor_type == 0xED) {
			#this stuff is to help me build the file from spec paste
			#my $tehstr=sprintf("grep 0x%04X /opt/xcat/lib/x3755led.tab",$sdr->led_id);
			#my $tehstr=`$tehstr`;
			#$tehstr =~ s/^0x....//;
			
			#printf("%X.%X.0x%04x",$mfg_id,$prod_id,$sdr->led_id);
			#print $tehstr;
		
			#We are inconsistant in our spec, first try a best guess
			#at endianness, assume the smaller value is MSB
			if (($sdr->led_id&0xff) > ($sdr->led_id>>8)) {
				$led_id_ls=$sdr->led_id&0xff;
				$led_id_ms=$sdr->led_id>>8;
			} else {	
				$led_id_ls=$sdr->led_id>>8;
				$led_id_ms=$sdr->led_id&0xff;
			}
				
            push @{$sessdata->{doleds}},[$led_id_ms,$led_id_ls,$sdr];
        }
    }
    $sessdata->{doled} = shift @{$sessdata->{doleds}};
    if ($sessdata->{doled}) {
        $sessdata->{current_led_sdr} = pop @{$sessdata->{doled}};
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata);
    } else {
        xCAT::SvrUtils::sendmsg("No supported LEDs found in system",$callback,$sessdata->{node},%allerrornodes);
    }
#	if ($#output==-1) {
#		push(@output,"No active error LEDs detected");
#	}
}

sub did_led {
    my $rsp = $_[0];
    my $sessdata = $_[1];
    my $mfg_id = $sessdata->{mfg_id};
    my $prod_id = $sessdata->{prod_id};
    my $sdr = $sessdata->{current_led_sdr};
    if (not $sessdata->{ledswappedendian} and $_[0]->{code} == 0xc9) { #missed an endian guess probably
        $sessdata->{ledswappedendian}=1;
        my @doled;
        $doled[0]=$sessdata->{doled}->[1];
        $doled[1]=$sessdata->{doled}->[0];
        $sessdata->{doled} = \@doled;
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata);
        return;
    } elsif ( $_[0]->{code} == 0xc9) {
        $_[0]->{code} = 0; #TODO: some system actually gives an led locator record that doesn't exist....
                    print "DEBUG: unfindable LED record\n";
    }
    $sessdata->{ledswappedendian}=0; #reset ledswappedendian flag to allow future swaps
    if (check_rsp_errors(@_)) {
        return;
    }
    my $led_id_ls = $sessdata->{doled}->[1];
    my $led_id_ms = $sessdata->{doled}->[0];
    my @returnd = (0,@{$rsp->{data}});
		if ($returnd[2]) { # != 0) {
			#It's on...
			if ($returnd[6] == 4) {
				xCAT::SvrUtils::sendmsg(sprintf("BIOS or admininstrator has %s lit",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds",$sdr)),$callback,$sessdata->{node},%allerrornodes);
            $sessdata->{activeleds}=1;
			}
			elsif ($returnd[6] == 3) {
				xCAT::SvrUtils::sendmsg(sprintf("A user has manually requested LED 0x%04x (%s) be active",$sdr->led_id,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds",$sdr)),$callback,$sessdata->{node},%allerrornodes);
            $sessdata->{activeleds}=1;
			}
			elsif ($returnd[6] == 1 && $sdr->led_id !=0) {
				xCAT::SvrUtils::sendmsg(sprintf("LED 0x%02x%02x (%s) active to indicate LED 0x%02x%02x (%s) is active",$led_id_ms,$led_id_ls,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds",$sdr),$returnd[4],$returnd[5],getsensorname($mfg_id,$prod_id,($returnd[4]<<8)+$returnd[5],"ibmleds")),$callback,$sessdata->{node},%allerrornodes);
            $sessdata->{activeleds}=1;
			}
			elsif ($sdr->led_id ==0 and $led_id_ms == 0 and $led_id_ls == 0) {
				xCAT::SvrUtils::sendmsg(sprintf("LED 0x0000 (%s) active to indicate system error condition.",getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds",$sdr)),$callback,$sessdata->{node},%allerrornodes);
            $sessdata->{activeleds}=1;
				if ($returnd[6] == 1 and $returnd[4] == 0xf and $returnd[5] == 0xff) {
					$sessdata->{doled} = [$returnd[4],$returnd[5]];
               $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata);
               return;
            }
			}
			elsif ($returnd[6] == 2) {
				my $sensor_desc;
				#Ok, LED is tied to a sensor..
				my $sensor_num=$returnd[5];
                my %sdr_hash = %{$sessdata->{sdr_hash}};
			        foreach my $key (keys %sdr_hash) {
					my $osdr = $sdr_hash{$key};
			                if($osdr->sensor_number == $sensor_num  and $osdr->rec_type != 192 and $osdr->rec_type != 17) {
			                        $sensor_desc = $sdr_hash{$key}->id_string;
			                        if($osdr->rec_type == 0x01) {
		                                	last;
						}
		                        }
		                }
				#push(@output,sprintf("Sensor 0x%02x (%s) has activated LED 0x%04x",$sensor_num,$sensor_desc,$sdr->led_id));
            if ($led_id_ms == 0xf and $led_id_ls == 0xff) { 
 	           xCAT::SvrUtils::sendmsg(sprintf("LED active to indicate Sensor 0x%02x (%s) error.",$sensor_num,$sensor_desc),$callback,$sessdata->{node},%allerrornodes);
            } else {
               xCAT::SvrUtils::sendmsg(sprintf("LED %02x%02x (%s) active to indicate Sensor 0x%02x (%s) error.",$led_id_ms,$led_id_ls,getsensorname($mfg_id,$prod_id,$sdr->led_id,"ibmleds",$sdr),$sensor_num,$sensor_desc),$callback,$sessdata->{node},%allerrornodes);
            }
            $sessdata->{activeleds}=1;
		        } else { #an LED is on for some other reason
                    #print "DEBUG: unknown LED reason code ".$returnd[6]."\n";
                    #TODO: discern meaning of more 'reason' codes, 5 and ff have come up
                }
                    
		} 
    $sessdata->{doled} = shift @{$sessdata->{doleds}};
    if ($sessdata->{doled}) {
        $sessdata->{current_led_sdr} = pop @{$sessdata->{doled}};
        $sessdata->{ipmisession}->subcmd(netfn=>0x3a,command=>0xc0,data=>$sessdata->{doled},callback=>\&did_led,callback_args=>$sessdata);
    } elsif (not $sessdata->{activeleds}) {
        xCAT::SvrUtils::sendmsg("No active error LEDs detected",$callback,$sessdata->{node},%allerrornodes);
    }
    if (scalar @{$sessdata->{sensorstoread}}) {
        $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
        readsensor($sessdata); #next sensor
    }
}
	
sub renergy {
    my $sessdata = shift;
    my @subcommands = @{$sessdata->{extraargs}};
    unless ($iem_support) {
        xCAT::SvrUtils::sendmsg("Command unsupported without IBM::EnergyManager installed",$callback,$sessdata->{node});
        return;
    }
    my @directives=();
    foreach (@subcommands) {
        if ($_ eq 'cappingmaxmin') {
            push @directives,'cappingmax','cappingmin';
        }
        push @directives,split /,/,$_;
    }
    $sessdata->{directives} = \@directives;
    $sessdata->{iemcallback}=\&renergy_withiem;
    initiem($sessdata);
}
sub renergy_withiem {
    my $sessdata = shift;
    my @settable_keys = qw/savingstatus cappingstatus cappingwatt cappingvalue/;
    my $directive = shift (@{$sessdata->{directives}});
    if ($sessdata->{iemtextdata}) {
        xCAT::SvrUtils::sendmsg($sessdata->{iemtextdata},$callback,$sessdata->{node},%allerrornodes);
        $sessdata->{iemtextdata}="";
    }
    if ($sessdata->{gotcapstatus}) {
        $sessdata->{gotcapstatus}=0;
        my $capenabled = $sessdata->{iem}->capping_enabled();
        xCAT::SvrUtils::sendmsg("cappingstatus: ".($capenabled ? "on" : "off"),$callback,$sessdata->{node},%allerrornodes);
    }
    if ($sessdata->{gothistogram}) {
        $sessdata->{gothistogram}=0;
        my @histdata = $sessdata->{iem}->extract_relative_histogram;
        foreach (sort { $a <=> $b } keys %{$histdata[0]}) {
            xCAT::SvrUtils::sendmsg("$_: ".$histdata[0]->{$_},$callback,$sessdata->{node},%allerrornodes);
        }
    }

    unless ($directive) { 
        return;
    }
    my $value=undef;
    my $key=undef;
    $sessdata->{iemcallback} = \&renergy_withiem;
    if ($directive =~ /(.*)=(.*)\z/) {
        $key = $1;
        $value = $2;
        unless (grep /$key/,@settable_keys and $value) {
            return (1,"Malformed argument $directive");
        }
        if ($key eq "cappingwatt" or $key eq "cappingvalue") {
            $value = $value*1000; #convert to milliwatts
            $sessdata->{iem}->prep_set_cap($value);
            execute_iem_commands($sessdata); #this gets all precision data initialized
        }
        if ($key eq "cappingstatus") {
            if (grep /$value/,qw/enable on 1/) {
                $value = 1;
            } else {
                $value = 0;
            }
            $sessdata->{iem}->prep_set_capenable($value);
            execute_iem_commands($sessdata); #this gets all precision data initialized
        }

    }
    if ($directive =~ /cappingmin/) {
        $sessdata->{iem}->prep_get_mincap();
        process_data_from_iem($sessdata);
    } elsif ($directive =~ /cappingmax$/) {
        $sessdata->{iem}->prep_get_maxcap();
        process_data_from_iem($sessdata);
    }
    if ($directive =~ /cappingvalue/) {
        $sessdata->{iem}->prep_get_cap();
        process_data_from_iem($sessdata);
    }
    if ($directive =~ /cappingstatus/) {
        $sessdata->{iem}->prep_get_powerstatus();
        $sessdata->{gotcapstatus}=1;
        execute_iem_commands($sessdata);
    }
    if ($directive =~ /relhistogram/) {
        $sessdata->{gothistogram}=1;
        $sessdata->{iem}->prep_retrieve_histogram();
        execute_iem_commands($sessdata);
    }
    return;
}
sub vitals {
    my $sessdata = shift;
    $sessdata->{healthsummary} = 0; #0 means healthy for now
    my %sdr_hash = %{$sessdata->{sdr_hash}};
    my @textfilters;
    foreach (@{$sessdata->{extraargs}}) {
        push @textfilters,(split /,/,$_);
    }
    unless (scalar @textfilters) { @textfilters = ("all"); }

	my $rc = 0;
	my $text;
	my $key;
	my %sensor_filters=();
	my @output;
	my $reading;
	my $unitdesc;
	my $value;
	my $extext;
	my $format = "%-30s%8s %-20s";
    my $doall;
    $doall=0;
	$rc=0;
    #filters: defined in sensor type codes and data table
    # 1 == temp, 2 == voltage 3== current (we lump in wattage here for lack of a better spot), 4 == fan

	if(grep { $_ eq "all"} @textfilters) {
	  $sensor_filters{1}=1; #,0x02,0x03,0x04); rather than filtering, unfiltered results
      $sensor_filters{energy}=1;
      $sensor_filters{chassis}=1;
      $sensor_filters{leds}=1;
      $doall=1;
	}
	if(grep /temp/,@textfilters) {
		$sensor_filters{0x01}=1;
	}
	if(grep /volt/,@textfilters) {
		$sensor_filters{0x02}=1;
	}
    if(grep /watt/,@textfilters) {
        $sensor_filters{0x03}=1;
    }
	if(grep /fan/,@textfilters) {
		$sensor_filters{0x04}=1;
	}
	if(grep /power/,@textfilters) {  #power does not really include energy, but most people use 'power' to mean both
        $sensor_filters{0x03}=1;
        $sensor_filters{powerstate}=1;
        $sensor_filters{energy}=1;
	}
	if(grep /energy/,@textfilters) { 
        $sensor_filters{energy}=1;
	}
	if(grep /led/,@textfilters) {
        $sensor_filters{leds}=1;
	}
	if(grep /chassis/,@textfilters) {
        $sensor_filters{chassis}=1;
	}
	unless (keys %sensor_filters) {
        xCAT::SvrUtils::sendmsg([1,"Unrecognized rvitals arguments ".join(" ",@{$sessdata->{extraargs}})],$callback,$sessdata->{node},%allerrornodes);;
	}

    $sessdata->{sensorstoread} = [];
    my %usedkeys;
	foreach(keys %sensor_filters) {
		my $filter = $_;
        if ($filter eq "energy" or $filter eq "leds") { next; }

		foreach $key (sort {$sdr_hash{$a}->id_string cmp $sdr_hash{$b}->id_string} keys %sdr_hash) {
            if ($usedkeys{$key}) { next; } #avoid duplicate requests for sensor data
			my $sdr = $sdr_hash{$key};
			if(($doall and not $sdr->rec_type == 0x11 and not $sdr->sensor_type==0xed) or ($sdr->rec_type == 0x01 and $sdr->sensor_type == $filter)) {
				my $lformat = $format;
                push @{$sessdata->{sensorstoread}},$sdr;
                $usedkeys{$key}=1;
            }
		}
	}

	if($sensor_filters{leds}) {
        push @{$sessdata->{sensorstoread}},"leds";
		#my @cleds;
		#($rc,@cleds) = checkleds();
        #push @output,@cleds;
    }
    if ($sensor_filters{powerstate} and not $sensor_filters{chassis}) {
        push @{$sessdata->{sensorstoread}},"powerstat";
		#($rc,$text) = power("stat");
		#$text = sprintf($format,"Power Status:",$text,"");
		#push(@output,$text);
    }
    if ($sensor_filters{energy}) {
        if ($iem_support) {
            push @{$sessdata->{sensorstoread}},"energy";
        } elsif (not $doall) {
            xCAT::SvrUtils::sendmsg([1,"Energy data requires additional IBM::EnergyManager plugin in conjunction with IMM managed IBM equipment"],$callback,$sessdata->{node},%allerrornodes);
        }
        #my @energies;
        #($rc,@energies)=readenergy();
        #push @output,@energies;
	}
    if ($sensor_filters{chassis}) {
        unshift  @{$sessdata->{sensorstoread}},"chassis";
    }
    if (scalar @{$sessdata->{sensorstoread}}) {
        $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
        readsensor($sessdata); #and we are off
    }
}



sub sensorformat {
    my $sessdata = shift;
    my $sdr = $sessdata->{currsdr};
    my $rc = shift;
    my $reading = shift;
    my $extext = shift;
	my $unitdesc = "";
    my $value;
	my $lformat = "%-30s %-20s";
	my $per = " ";
    my $data;
	if($rc == 0) {
        $data = translate_sensor($reading,$sdr);
	} else {
        $data = "N/A";
    }
#$unitdesc.= sprintf(" %x",$sdr->sensor_type);
#    use Data::Dumper;
#    print Dumper($lformat,$sdr->id_string,$data);
	my $text = sprintf($lformat,$sdr->id_string . ":",$data);
	if ($extext) {
		$text="$text ($extext)";
	}
	if ($sessdata->{bmcnum} != 1) { $text.=" on BMC ".$sessdata->{bmcnum}; }
    xCAT::SvrUtils::sendmsg($text,$callback,$sessdata->{node},%allerrornodes);
    if (scalar @{$sessdata->{sensorstoread}}) {
        $sessdata->{currsdr} = shift @{$sessdata->{sensorstoread}};
        readsensor($sessdata); #next
    }
}

sub readsensor {
    my $sessdata = shift;
    if (not ref $sessdata->{currsdr}) {
        if ($sessdata->{currsdr} eq "leds") {
            checkleds($sessdata);
            return;
        } elsif ($sessdata->{currsdr} eq "powerstat") {
            $sessdata->{powerstatprefix}="Power Status: ";
            $sessdata->{subcommand}="stat";
            power($sessdata);
            return;
        } elsif ($sessdata->{currsdr} eq "chassis") {
            readchassis($sessdata);
            return;
        } elsif ($sessdata->{currsdr} eq "energy") {
            readenergy($sessdata);
            return;
        } else {
        xCAT::SvrUtils::sendmsg([1,"TODO: make ".$sessdata->{currsdr}." work again"],$callback,$sessdata->{node},%allerrornodes);
        }
        return;
    }
	my $sensor = $sessdata->{currsdr}->sensor_number;
    $sessdata->{ipmisession}->subcmd(netfn=>0x4,command=>0x2d,data=>[$sensor],callback=>\&sensor_was_read,callback_args=>$sessdata);
}

sub sensor_was_read {
    my $rsp = shift;
    my $sessdata = shift;
    if ($rsp->{error}) {
        xCAT::SvrUtils::sendmsg([1,$rsp->{error}],$callback,$sessdata->{node},%allerrornodes);
	return;
    }
    if ($rsp->{code}) {
        my $text = $codes{$rsp->{code}};
        unless ($text) { $text = sprintf("Unknown error %02xh",$rsp->{code}) };
        return sensorformat($sessdata,1,$text);
    }

    my @returnd = (0,@{$rsp->{data}});
	
	if ($returnd[2] & 0x20) {
		return sensorformat($sessdata,1,"N/A");
	}
	my $text = $returnd[1];
    my $exdata1 = $returnd[3];
    my $exdata2 = $returnd[3];
    my $extext;
    my @exparts;
    my $sdr = $sessdata->{currsdr};
    if ($sdr->event_type_code == 0x1) {
        if ($exdata1 & 1<<5) {
            $extext = "At or above upper non-recoverable threshold";
        } elsif ($exdata1 & 1<<4)  {
            $extext = "At or above upper critical threshold";
        } elsif ($exdata1 & 1<<3) {
            $extext = "At or above upper non-critical threshold";
        } 
        if ($exdata1 & 1<<2) {
            $extext = "At or below lower non-critical threshold";
        } elsif ($exdata1 & 1<<1) {
            $extext = "At or below lower critical threshold";
        } elsif ($exdata1 & 1) {
            $extext = "At or below lower non-recoverable threshold";
        }
    } elsif ($sdr->event_type_code == 0x6f) {
        if ($sdr->sensor_type == 0x10) {
	    @exparts=();
            if ($exdata1 & 1<<4) {
                push @exparts,"SEL full";
            } elsif ($exdata1 & 1<<5) {
                push @exparts,"SEL almost full";
            }
	    if ($exdata1 & 1) {
	       push @exparts,"Correctable Memory Error Logging Disabled";
	    } 
	    if ($exdata1 & 1<<3) {
	       push @exparts,"All logging disabled";
	    } elsif ($exdata1 & 1<<1) {
	       push @exparts,"Some logging disabled";
	    }
	    if (@exparts) {
	       $extext = join(",",@exparts);
	    }
        } elsif ($sdr->sensor_type == 0x7) {
	   @exparts=();
	   if ($exdata1 & 1) {
	      push @exparts,"IERR";
	   }
	   if ($exdata1 & 1<<1) {
	      push @exparts,"Thermal trip";
	   }
	   if ($exdata1 & 1<<2) {
	      push @exparts,"FRB1/BIST failure";
	   }
	   if ($exdata1 & 1<<3) {
	      push @exparts,"FRB2/Hang in POST due to processor";
	   }
	   if ($exdata1 & 1<<4) {
	      push @exparts,"FRB3/Processor Initialization failure";
	   }
	   if ($exdata1 & 1<<5) {
	      push @exparts,"Configuration error";
	   }
	   if ($exdata1 & 1<<6) {
	      push @exparts,"Uncorrectable CPU-complex error";
	   }
	   if ($exdata1 & 1<<7) {
	      push @exparts,"Present";
	   }
	   if ($exdata1 & 1<<8) {
	      push @exparts,"Processor disabled";
	   }
	   if ($exdata1 & 1<<9) {
	      push @exparts,"Terminator present";
	   }
	   if ($exdata1 & 1<<10) {
	      push @exparts,"Hardware throttled";
	   }
        } elsif ($sdr->sensor_type == 0x8) {
	   @exparts=();
	   if ($exdata1 & 1) {
	        push @exparts,"Present";
	   }
	   if ($exdata1 & 1<<1) {
	        push @exparts,"Failed";
	   }
	   if ($exdata1 & 1<<2) {
	        push @exparts,"Failure predicted";
	   }
	   if ($exdata1 & 1<<3) {
	        push @exparts,"AC Lost";
	   }
	   if ($exdata1 & 1<<4) {
	        push @exparts,"AC input lost or out of range";
	   }
	   if ($exdata1 & 1<<5) {
	        push @exparts,"AC input out of range";
	   }
	   if ($exdata1 & 1<<6) {
	        push @exparts,"Configuration error";
	   }
	   if (@exparts) {
	      $extext = join(",",@exparts);
	   }
        } elsif ($sdr->sensor_type == 0x13) {
            @exparts=();
            if ($exdata1 & 1) {
                push @exparts,"Front panel NMI/Diagnostic";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Bus timeout";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"I/O channel check NMI";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Software NMI";
            }
            if ($exdata1 & 1<<4) {
                push @exparts,"PCI PERR";
            }
            if ($exdata1 & 1<<5) {
                push @exparts,"PCI SERR";
            }
            if ($exdata1 & 1<<6) {
                push @exparts,"EISA failsafe timeout";
            }
            if ($exdata1 & 1<<7) {
                push @exparts,"Bus correctable .rror";
            }
            if ($exdata1 & 1<<8) {
                push @exparts,"Bus uncorrectable error";
            }
            if ($exdata1 & 1<<9) {
                push @exparts,"Fatal NMI";
            }
            if ($exdata1 & 1<<10) {
                push @exparts,"Bus fatal error";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0xc) {
            @exparts=();
            if ($exdata1 & 1) {
                push @exparts,"Correctable error(s)";
            } 
            if ($exdata1 & 1<<1) {
                push @exparts,"Uncorrectable error(s)";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Parity";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Memory scrub failure";
            }
            if ($exdata1 & 1<<4) {
                push @exparts,"DIMM disabled";
            }
            if ($exdata1 & 1<<5) {
                push @exparts,"Correctable error limit reached";
            }
            if ($exdata1 & 1<<6) {
                push @exparts,"Present";
            }
            if ($exdata1 & 1<<7) {
                push @exparts,"Configuration error";
            }
            if ($exdata1 & 1<<8) {
                push @exparts,"Spare";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0x21) {
            @exparts=();
            if ($exdata1 & 1) {
                push @exparts,"Fault";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Identify";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Installed/attached";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Ready for install";
            }
            if ($exdata1 & 1<<4) {
                push @exparts,"Ready for removal";
            }
            if ($exdata1 & 1<<5) {
                push @exparts,"Powered off";
            }
            if ($exdata1 & 1<<6) {
                push @exparts,"Removal requested";
            }
            if ($exdata1 & 1<<7) {
                push @exparts,"Interlocked";
            }
            if ($exdata1 & 1<<8) {
                push @exparts,"Disabled";
            }
            if ($exdata1 & 1<<9) {
                push @exparts,"Spare";
            }
        } elsif ($sdr->sensor_type == 0xf) {
            @exparts=();
            if ($exdata1 & 1) {
                push @exparts,"POST error";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Firmware hang";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Firmware progress";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0x9) {
	   @exparts=();
	   if ($exdata1 & 1) {
	      push @exparts,"Power off";
	   }
	   if ($exdata1 & 1<<1) {
	      push @exparts,"Power off";
	   }
	   if ($exdata1 & 1<<2) {
	      push @exparts,"240VA Power Down";
	   }
	   if ($exdata1 & 1<<3) {
	      push @exparts,"Interlock Power Down";
	   }
	   if ($exdata1 & 1<<4) {
	      push @exparts,"AC lost";
	   }
	   if ($exdata1 & 1<<5) {
	      push @exparts,"Soft power control failure";
	   }
	   if ($exdata1 & 1<<6) {
	      push @exparts,"Power unit failure";
	   }
	   if ($exdata1 & 1<<7) {
	      push @exparts,"Power unit failure predicted";
	   }
	   if (@exparts) {
	      $extext = join(",",@exparts);
	   }
        } elsif ($sdr->sensor_type == 0x12) {
            @exparts=();
            if ($exdata1 & 1) {
                push @exparts,"System Reconfigured";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"OEM System Boot Event";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Undetermined system hardware failure";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Aux log manipulated";
            }
            if ($exdata1 & 1<<4) {
                push @exparts,"PEF Action";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0x25) {
            if ($exdata1 & 1) {
                push @exparts,"Present";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Absent";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Disabled";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0x23) {
            if ($exdata1 & 1) {
                push @exparts,"Expired";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Hard Reset";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Power Down";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Power Cycle";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } elsif ($sdr->sensor_type == 0xd) {
            if ($exdata1 & 1) {
                push @exparts,"Present";
            }
            if ($exdata1 & 1<<1) {
                push @exparts,"Fault";
            }
            if ($exdata1 & 1<<2) {
                push @exparts,"Failure Predicted";
            }
            if ($exdata1 & 1<<3) {
                push @exparts,"Hot Spare";
            }
            if ($exdata1 & 1<<4) {
                push @exparts,"Consistency Check";
            }
            if ($exdata1 & 1<<5) {
                push @exparts,"Critical Array";
            }
            if ($exdata1 & 1<<6) {
                push @exparts,"Failed Array";
            }
            if ($exdata1 & 1<<7) {
                push @exparts,"Rebuilding";
            }
            if ($exdata1 & 1<<8) {
                push @exparts,"Rebuild aborted";
            }
            if (@exparts) {
                $extext = join(",",@exparts);
            }
        } else {
            $extext = "xCAT needs to add support for ".$sdr->sensor_type;
        }
    }

	return sensorformat($sessdata,0,$text,$extext);
}

sub initsdr {
    my $sessdata=shift;
	my $netfun;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;

	my $resv_id_ls;
	my $resv_id_ms;
	my $sdr_type;
	my $sdr_offset;
	my $sdr_len;
	my @sdr_data = ();
	my $offset;
	my $len;
	my $i;
#	my $numbytes = 27;
	my $ipmisensortab = "$ENV{XCATROOT}/lib/GUMI/ipmisensor.tab";
	my $byte_format;
	my $cache_file;

     #device id data TODO
    $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x20,data=>[],callback=>\&got_sdr_rep_info,callback_args=>$sessdata);
}

sub  initsdr_withrepinfo {
    my $sessdata = shift;
	my $mfg_id=$sessdata->{mfg_id};
	my $prod_id=$sessdata->{prod_id};
	my $device_id=$sessdata->{device_id};
	my $dev_rev=$sessdata->{device_rev};
	my $fw_rev1=$sessdata->{firmware_rev1};
	my $fw_rev2=$sessdata->{firmware_rev2};
    #TODO: beware of dynamic SDR contents

	my $cache_file = "$cache_dir/sdr_$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2";
    $sessdata->{sdrcache_file} = $cache_file;
	if($enable_cache eq "yes") {
        if ($sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"}) {
            $sessdata->{sdr_hash} = $sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"};
            on_bmc_connect("SUCCESS",$sessdata); #retry bmc_connect since sdr_cache is validated
            return; #don't proceed to slow load
        } else {
    		my $rc = loadsdrcache($sessdata,$cache_file);
    		if($rc == 0) {
                $sdr_caches{"$mfg_id.$prod_id.$device_id.$dev_rev.$fw_rev1.$fw_rev2"} = $sessdata->{sdr_hash};
                on_bmc_connect("SUCCESS",$sessdata); #retry bmc_connect since sdr_cache is validated
                return; #don't proceed to slow load
    		}
        }
	}


	if($sessdata->{sdr_info}->{version} != 0x51) {
        sendoutput(1,"SDR version unsupported.");
		return(1); #bail, do not try to continue
	}

	if($sessdata->{sdr_info}->{resv_sdr} != 1) {
        sendoutput(1,"SDR reservation unsupported.");
        return 1;
	}

    $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x22,data=>[],callback=>\&reserved_sdr_repo,callback_args=>$sessdata);
}
sub initsdr_withreservation {
    my $sessdata = shift;
	my $rid_ls = 0;
	my $rid_ms = 0;
	if ($sessdata->{sdr_nrid_ls}) { $rid_ls = $sessdata->{sdr_nrid_ls}; }
	if ($sessdata->{sdr_nrid_ms}) { $rid_ms = $sessdata->{sdr_nrid_ms}; }

####if($debug) {
####	print "mfg,prod,dev: $mfg_id, $prod_id, $device_id\n";
####	printf("SDR info: %02x %d %d\n",$sdr_rep_info->version,$sdr_rep_info->rec_count,$sdr_rep_info->resv_sdr);
####	print "resv_id: $resv_id_ls $resv_id_ms\n";
####}
    my $resv_id_ls = $sessdata->{resv_id_ls};
    my $resv_id_ms = $sessdata->{resv_id_ms};
    if ( $rid_ls == 0xff and $rid_ms == 0xff) {
	    if($enable_cache eq "yes") { #cache SDR repository for future use
		    storsdrcache($sessdata->{sdrcache_file},$sessdata);
	    }
        on_bmc_connect("SUCCESS",$sessdata); #go back armed with a capable reserviction
        return; #Have reached the end
    }
    $sessdata->{sdr_fetch_args} = [$resv_id_ls,$resv_id_ms,$rid_ls,$rid_ms,0,5];
    $sessdata->{ipmisession}->subcmd(netfn=>0xa,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&start_sdr_record,callback_args=>$sessdata);
    return;
}

sub start_sdr_record {
    my $rsp = shift;
    my $sessdata = shift;
	if($rsp->{error}) {
        sendoutput(1,$rsp->{error});
		return;
	}
    my $resv_id_ls = shift @{$sessdata->{sdr_fetch_args}};
    my $resv_id_ms = shift @{$sessdata->{sdr_fetch_args}};
    my $rid_ls = shift @{$sessdata->{sdr_fetch_args}};
    my $rid_ms = shift @{$sessdata->{sdr_fetch_args}};
    my @returnd = ($rsp->{code},@{$rsp->{data}});
	my $code = $returnd[0];
	if($code != 0x00) {
		my $text = $codes{$code};
		if(!$text) {
			$text = sprintf("unknown response %02x",$code);
		}
        sendoutput(1,$text);
		return;
	}
	$sessdata->{sdr_nrid_ls} = $returnd[1];
	$sessdata->{sdr_nrid_ms} = $returnd[2];
	my $sdr_ver = $returnd[5];
	my $sdr_type = $returnd[6];
   	$sessdata->{curr_sdr_type} = $sdr_type;
	$sessdata->{curr_sdr_len} = $returnd[7] + 5;

	if($sdr_type == 0x01) {
		$sessdata->{total_sdr_offset} = 0;
	}
	elsif($sdr_type == 0x02) {
		$sessdata->{total_sdr_offset} = 16; #TODO: understand this..
	}
	elsif($sdr_type == 0xC0) {
		#LED descriptor, maybe
	}
	elsif($sdr_type == 0x11) { #FRU locator
	}
	elsif($sdr_type == 0x12) {
        initsdr_withreservation($sessdata); #next, skip this unsupported record type
        return;
	}
	else {
        initsdr_withreservation($sessdata); #next
        return;
	}

	$sessdata->{sdr_data} = [0,0,0,$sdr_ver,$sdr_type,$sessdata->{curr_sdr_len}]; #seems that an extra zero is prepended to allow other code to do 1 based counting out of laziness to match our index to the spec indicated index
	$sessdata->{sdr_offset} = 5;
    my $offset=5; #why duplicate? to make for shorter typing
	my $numbytes = 22;
    if (5<$sessdata->{curr_sdr_len}) { #can't imagine this not bing the case,but keep logic in case
        if($offset+$numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder
            $numbytes = $sessdata->{curr_sdr_len} - $offset;
        }
        $sessdata->{sdr_fetch_args} = [$resv_id_ls,$resv_id_ms,$rid_ls,$rid_ms,$offset,$numbytes];
        $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&add_sdr_data,callback_args=>$sessdata);
        return;
    } else {
        initsdr_withreservation($sessdata); #next
        return;
    }
}
sub add_sdr_data {
    my $rsp = shift;
    my $sessdata = shift;
	my $numbytes = $sessdata->{sdr_fetch_args}->[5];
    my $offset = $sessdata->{sdr_offset}; #shorten typing a little
    if ($rsp->{error}) {
        sendoutput([1,$rsp->{error}]);
        return; #give up
    }
    my @returnd = ($rsp->{code},@{$rsp->{data}});
	my $code = $returnd[0];
	if($code != 0x00) {
		my $text = $codes{$code};
		if(!$text) {
			$text = sprintf("unknown response %02x",$code);
		}
        sendoutput([1,$text]);
        return; #abort the whole mess
	}
    push @{$sessdata->{sdr_data}},@returnd[3..@returnd-1];
	$sessdata->{sdr_offset} += $numbytes;
    if($sessdata->{sdr_offset}+$numbytes > $sessdata->{curr_sdr_len}) { #scale back request for remainder
       $numbytes = $sessdata->{curr_sdr_len} - $sessdata->{sdr_offset};
    }
    $sessdata->{sdr_fetch_args}->[4] = $sessdata->{sdr_offset};
    $sessdata->{sdr_fetch_args}->[5] = $numbytes;
    if ($sessdata->{sdr_offset}<$sessdata->{curr_sdr_len}) {
        $sessdata->{ipmisession}->subcmd(netfn=>0x0a,command=>0x23,data=>$sessdata->{sdr_fetch_args},callback=>\&add_sdr_data,callback_args=>$sessdata);
        return;
    } else { #in this case, time to parse the accumulated data
        parse_sdr($sessdata);
    }
}
sub parse_sdr { #parse sdr data, then cann initsdr_withreserveation to advance to next record
    my $sessdata = shift;
    my @sdr_data =  @{$sessdata->{sdr_data}};
    #not bothering trying to keep a packet pending concurrent with operation, harder to code that
	my $mfg_id=$sessdata->{mfg_id};
	my $prod_id=$sessdata->{prod_id};
	my $device_id=$sessdata->{device_id};
	my $dev_rev=$sessdata->{device_rev};
	my $fw_rev1=$sessdata->{firmware_rev1};
	my $fw_rev2=$sessdata->{firmware_rev2};
    my $sdr_type = $sessdata->{curr_sdr_type};
	if($sdr_type == 0x11) { #FRU locator
        my $sdr = decode_fru_locator(@sdr_data);
        if ($sdr) {
	        $sessdata->{sdr_hash}->{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr;
        }
        initsdr_withreservation($sessdata); #advance to next record
        return;
    }

####if($debug) {
####	hexadump(\@sdr_data);
####}


    if($sdr_type == 0x12) { #if required, TODO support type 0x12
	   hexadump(\@sdr_data);
       initsdr_withreservation($sessdata); #next record
       return;
	}

	my $sdr = SDR->new();

	if (($mfg_id == 2 || $mfg_id == 20301) && $sdr_type==0xC0 && $sdr_data[9] == 0xED) {
			#printf("%02x%02x\n",$sdr_data[13],$sdr_data[12]);
		$sdr->rec_type($sdr_type);
		$sdr->sensor_type($sdr_data[9]);
			#Using an impossible sensor number to not conflict with decodealert
			$sdr->sensor_owner_id(260);
			$sdr->sensor_owner_lun(260);
            $sdr->id_string("LED");
			if ($sdr_data[12] > $sdr_data[13]) {
				$sdr->led_id(($sdr_data[13]<<8)+$sdr_data[12]);
			} else {
				$sdr->led_id(($sdr_data[12]<<8)+$sdr_data[13]);
			}
			if (scalar(@sdr_data) > 17) { #well what do you know, we have an ascii description, probably...
				my $id = unpack("Z*",pack("C*",@sdr_data[16..$#sdr_data]));
				if ($id) { $sdr->id_string($id); }
			}
			#$sdr->led_id_ms($sdr_data[13]);
			#$sdr->led_id_ls($sdr_data[12]);
			$sdr->sensor_number(sprintf("%04x",$sdr->led_id));
			#printf("%02x,%02x,%04x\n",$mfg_id,$prod_id,$sdr->led_id);	
			#Was going to have a human readable name, but specs
			#seem to not to match reality...
			#$override_string = getsensorname($mfg_id,$prod_id,$sdr->sensor_number,$ipmiledtab);
			#I'm hacking in owner and lun of 260 for LEDs....
			$sessdata->{sdr_hash}->{"260.260.".$sdr->led_id} = $sdr;
            initsdr_withreservation($sessdata); #next record
            return;
		}


		$sdr->rec_type($sdr_type);
		$sdr->sensor_owner_id($sdr_data[6]);
		$sdr->sensor_owner_lun($sdr_data[7]);
		$sdr->sensor_number($sdr_data[8]);
		$sdr->entity_id($sdr_data[9]);
		$sdr->entity_instance($sdr_data[10]);
		$sdr->sensor_type($sdr_data[13]);
		$sdr->event_type_code($sdr_data[14]);
		$sdr->sensor_units_2($sdr_data[22]);
		$sdr->sensor_units_3($sdr_data[23]);

		if($sdr_type == 0x01) {
		   $sdr->sensor_units_1($sdr_data[21]);
			$sdr->linearization($sdr_data[24] & 0b01111111);
			$sdr->M(comp2int(10,(($sdr_data[26] & 0b11000000) << 2) + $sdr_data[25]));
			$sdr->B(comp2int(10,(($sdr_data[28] & 0b11000000) << 2) + $sdr_data[27]));
			$sdr->R_exp(comp2int(4,($sdr_data[30] & 0b11110000) >> 4));
			$sdr->B_exp(comp2int(4,$sdr_data[30] & 0b00001111));
		} elsif ($sdr_type == 0x02) {
		   $sdr->sensor_units_1($sdr_data[21]);
        }

		$sdr->id_string_type($sdr_data[48-$sessdata->{total_sdr_offset}]);

		my $override_string = getsensorname($mfg_id,$prod_id,$sdr->sensor_number);

		if($override_string ne "") {
			$sdr->id_string($override_string);
		}
		else {
            unless (defined $sdr->id_string_type) { initsdr_withreservation($sessdata); return; }
			my $byte_format = ($sdr->id_string_type & 0b11000000) >> 6;
			if($byte_format == 0b11) {
				my $len = ($sdr->id_string_type & 0b00011111) - 1;
				if($len > 1) {
					$sdr->id_string(pack("C*",@sdr_data[49-$sessdata->{total_sdr_offset}..49-$sessdata->{total_sdr_offset}+$len]));
				}
				else {
					$sdr->id_string("no description");
				}
			}
			elsif($byte_format == 0b10) {
				$sdr->id_string("ASCII packed unsupported");
			}
			elsif($byte_format == 0b01) {
				$sdr->id_string("BCD unsupported");
			}
			elsif($byte_format == 0b00) {
                my $len = ($sdr->id_string_type & 0b00011111) - 1;
                if ($len > 1) { #It should be something, but need sample to code
				    $sdr->id_string("unicode unsupported");
                } else {
                    initsdr_withreservation($sessdata); return;
                }
			}
		}

		$sessdata->{sdr_hash}->{$sdr->sensor_owner_id . "." . $sdr->sensor_owner_lun . "." . $sdr->sensor_number} = $sdr;
        initsdr_withreservation($sessdata); return;
}

sub getsensorname
{
	my $mfgid = shift;
	my $prodid = shift;
	my $sensor = shift;
	my $file = shift;
	my $sdr = shift;

	my $mfg;
	my $prod;
	my $type;
	my $desc;
	my $name="";

    if ($file and $file eq "ibmleds") {
	    if ($sdr and $sdr->id_string ne "LED") { return $sdr->id_string; } # this is preferred mechanism
            if ($xCAT::data::ibmleds::leds{"$mfgid,$prodid"}->{$sensor}) {
              return $xCAT::data::ibmleds::leds{"$mfgid,$prodid"}->{$sensor}. " LED";
            } elsif ($ndebug) {
              return "Unknown $sensor/$mfgid/$prodid";
            } else {
              return sprintf ("LED 0x%x",$sensor);
            }
    } else {
      return "";
    }
}

sub getchassiscap {
	my $netfun = 0x00;
	my @cmd;
	my @returnd = ();
	my $error;
	my $rc = 0;
	my $text;
	my $code;

	@cmd = (0x00);
	$error = docmd(
		$netfun,
		\@cmd,
		\@returnd
	);

	if($error) {
		$rc = 1;
		$text = $error;
		return($rc,$text);
	}

	$code = $returnd[0];
	if($code == 0x00) {
		$text = "";
	}
	else {
		$rc = 1;
		$text = $codes{$code};
		if(!$text) {
			$rc = 1;
			$text = sprintf("unknown response %02x",$code);
		}
		return($rc,$text);
	}

	return($rc,@returnd[1..@returnd-2]);
}

sub gotdevid {
	#($rc,$text,$mfg_id,$prod_id,$device_id,$dev_rev,$fw_rev1,$fw_rev2) = getdevid();
    my $rsp = shift;
    my $sessdata = shift;
    my $text;

	if($rsp->{error}) {
        sendoutput([1,$rsp->{error}]);
        return;
	}
	else {
		my $code = $rsp->{code};

		if($code != 0x00) {
			my $text = $codes{$code};
			if(!$text) {
				$text = sprintf("unknown response %02x",$code);
			}
            sendoutput([1,$text]);
            return;
		}
	}
    my @returnd = ($rsp->{code},@{$rsp->{data}});

	$sessdata->{device_id} = $returnd[1];
	$sessdata->{device_rev} = $returnd[2] & 0b00001111;
	$sessdata->{firmware_rev1} = $returnd[3] & 0b01111111;
	$sessdata->{firmware_rev2} = $returnd[4];
	$sessdata->{ipmi_ver} = $returnd[5];
	$sessdata->{dev_support} = $returnd[6];
####my $sensor_device = 0;
####my $SDR = 0;
####my $SEL = 0;
####my $FRU = 0;
####my $IPMB_ER = 0;
####my $IPMB_EG = 0;
####my $BD = 0;
####my $CD = 0;
####if($dev_support & 0b00000001) {
####	$sensor_device = 1;
####}
####if($dev_support & 0b00000010) {
####	$SDR = 1;
####}
####if($dev_support & 0b00000100) {
####	$SEL = 1;
####}
####if($dev_support & 0b00001000) {
####	$FRU = 1;
####}
####if($dev_support & 0b00010000) {
####	$IPMB_ER = 1;
####}
####if($dev_support & 0b00100000) {
####	$IPMB_EG = 1;
####}
####if($dev_support & 0b01000000) {
####	$BD = 1;
####}
####if($dev_support & 0b10000000) {
####	$CD = 1;
####}
	$sessdata->{mfg_id} = $returnd[7] + $returnd[8]*0x100 +  $returnd[9]*0x10000;
	$sessdata->{prod_id} = $returnd[10] + $returnd[11]*0x100;
    on_bmc_connect("SUCCESS",$sessdata);
#	my @data = @returnd[12..@returnd-2];

#	return($rc,$text,$mfg_id,$prod_id,$device_id,$device_rev,$firmware_rev1,$firmware_rev2);
}

sub gotguid {
    if (check_rsp_errors(@_)) {
        return;
    }
    my $rsp = shift;
    my $sessdata = shift;
	#my @guidcmd = (0x18,0x37);
	#if($mfg_id == 2 && $prod_id == 34869) { TODO: if GUID is inaccurate on the products mentioned, this code may be uncommented
	#	@guidcmd = (0x18,0x08);
	#}
	#if($mfg_id == 2 && $prod_id == 4) {
	#	@guidcmd = (0x18,0x08);
	#}
	#if($mfg_id == 2 && $prod_id == 3) {
	#	@guidcmd = (0x18,0x08);
	#}
	my $fru = FRU->new();
	$fru->rec_type("guid");
	$fru->desc("UUID/GUID");
	$fru->value(sprintf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",@{$rsp->{data}}));
	$sessdata->{fru_hash}->{guid} = $fru;
    initfru_withguid($sessdata);
}

sub got_sdr_rep_info {
    my $rsp = shift;
    my $sessdata = shift;

	if($rsp->{error}) {
        sendoutput([1,$rsp->{error}]);
        return;
	}
	else {
		my $code = $rsp->{code};

		if($code != 0x00) {
			my $text = $codes{$code};
			if(!$text) {
				$text = sprintf("unknown response %02x",$code);
			}
            sendoutput(1,$text);
            return;
		}
	}
    my @returnd = @{$rsp->{data}};
    $sessdata->{sdr_info}->{version} = $returnd[0];
    $sessdata->{sdr_info}->{rec_count} = $returnd[1] + $returnd[2]<<8;
    $sessdata->{sdr_info}->{resv_sdr} = ($returnd[13] & 0b00000010)>>1;
    initsdr_withrepinfo($sessdata);
}

sub reserved_sdr_repo {
    my $rsp = shift;
    my $sessdata = shift;
	if($rsp->{error}) {
        sendoutput([1,$rsp->{error}]);;
        return;
	}
	else {
		my $code = $rsp->{code};

		if($code != 0x00) {
			my $text = $codes{$code};
			if(!$text) {
				$text = sprintf("unknown response %02x",$code);
			}
            sendoutput([1,$text]);
		}
	}
    my @returnd = @{$rsp->{data}};

    $sessdata->{resv_id_ls} =  $returnd[0];
    $sessdata->{resv_id_ms} =  $returnd[1];
    initsdr_withreservation($sessdata);
}

sub dochksum()
{
	my $data = shift;
	my $sum = 0;

	foreach(@$data) {
		$sum += $_;
	}

	$sum = ~$sum + 1;
	return($sum & 0xFF);
}

sub hexdump {
	my $data = shift;

	foreach(@$data) {
		printf("%02x ",$_);
	}
	print "\n";
}

sub getascii {
        my @alpha;
        my $text ="";
        my $c = 0;

        foreach(@_) {
                if (defined $_ and $_ < 128 and $_ > 0x20) {
                    $alpha[$c] = sprintf("%c",$_);
                } else {
                    $alpha[$c]=" ";
                }
                if($alpha[$c] !~ /[\S]/) {
        			if ($alpha[($c-1)] !~ /\s/) {
                    	    $alpha[$c] = " ";
	          		} else {
			        	$c--;
			        }
                }
                $c++;
        }
        foreach(@alpha) {
                $text=$text.$_;
        }
	$text =~ s/^\s+|\s+$//;
	return $text;
}
sub phex {
        my $data = shift;
        my @alpha;
        my $text ="";
        my $c = 0;

        foreach(@$data) {
                $text = $text . sprintf("%02x ",$_);
                $alpha[$c] = sprintf("%c",$_);
                if($alpha[$c] !~ /\w/) {
                        $alpha[$c] = " ";
                }
                $c++;
        }
        $text = $text . "(";
        foreach(@alpha) {
                $text=$text.$_;
        }
        $text = $text . ")";
        return $text;
}

sub hexadump {
	my $data = shift;
	my @alpha;
	my $c = 0;

	foreach(@$data) {
		printf("%02x ",$_);
		$alpha[$c] = sprintf("%c",$_);
		if($_ < 0x20 or $_ > 0x7e) {
			$alpha[$c] = ".";
		}
		$c++;
		if($c == 16) {
			print "   ";
			foreach(@alpha) {
				print $_;
			}
			print "\n";
			@alpha=();
			$c=0;
		}
	}
	foreach($c..16) {
		print "   ";
	}
	foreach(@alpha) {
		print $_;
	}
	print "\n";
}

sub comp2int {
	my $length = shift;
	my $bits = shift;
	my $neg = 0;

	if($bits & 2**($length - 1)) {
		$neg = 1;
	}

	$bits &= (2**($length - 1) - 1);

	if($neg) {
		$bits -= 2**($length - 1);
	}

	return($bits);
}

sub timestamp2datetime {
	my $ts = shift;
   if ($ts < 0x20000000) {
      return "BMC Uptime",sprintf("%6d s",$ts);
   }
	my @t = localtime($ts);
	my $time = strftime("%H:%M:%S",@t);
	my $date = strftime("%m/%d/%Y",@t);

	return($date,$time);
}

sub decodebcd {
	my $numbers = shift;
	my @bcd;
	my $text;
	my $ms;
	my $ls;

	foreach(@$numbers) {
		$ms = ($_ & 0b11110000) >> 4;
		$ls = ($_ & 0b00001111);
		push(@bcd,$ms);
		push(@bcd,$ls);
	}

	foreach(@bcd) {
		if($_ < 0x0a) {
			$text .= $_;
		}
		elsif($_ == 0x0a) {
			$text .= " ";
		}
		elsif($_ == 0x0b) {
			$text .= "-";
		}
		elsif($_ == 0x0c) {
			$text .= ".";
		}
	}

	return($text);
}

sub storsdrcache {
	my $file = shift;
    my $sessdata = shift;
    unless ($sessdata) { die "need to fix this one too" }
	my $key;
	my $fh;

	system("mkdir -p $cache_dir");
	if(!open($fh,">$file")) {
		return(1);
	}

	flock($fh,LOCK_EX) || return(1);

	my $hdr;
        $hdr->{xcat_sdrcacheversion} = $cache_version;
	nstore_fd($hdr,$fh);
	foreach $key (keys %{$sessdata->{sdr_hash}}) {
		my $r = $sessdata->{sdr_hash}->{$key};
		nstore_fd($r,$fh);
	}

	close($fh);

	return(0);
}

sub loadsdrcache {
    my $sessdata = shift;
	my $file = shift;
	my $r;
	my $c=0;
	my $fh;

	if(!open($fh,"<$file")) {
		return(1);
	}
	$r = retrieve_fd($fh);
        unless ($r) { close($fh); return 1; }
        unless ($r->{xcat_sdrcacheversion} and $r->{xcat_sdrcacheversion} == $cache_version) { close($fh); return 1; } #version mismatch

	flock($fh,LOCK_SH) || return(1);

	while() {
		eval {
			$r = retrieve_fd($fh);
		} || last;

		$sessdata->{sdr_hash}->{$r->sensor_owner_id . "." . $r->sensor_owner_lun . "." . $r->sensor_number} = $r;
	}

	close($fh);

	return(0);
}

sub randomizelist { #in place shuffle of list
	my $list = shift;
	my $index = @$list;
	while ($index--) {
		my $swap=int(rand($index+1));
		@$list[$index,$swap]=@$list[$swap,$index];
	}
}

sub preprocess_request { 
  my $request = shift;
  if (defined $request->{_xcatpreprocessed}->[0] and $request->{_xcatpreprocessed}->[0] == 1) { return [$request]; }
  #exit if preprocessed
  my $callback=shift;
  my @requests;

  my $realnoderange = $request->{node}; #Should be arrayref
  my $command = $request->{command}->[0];
  my $extrargs = $request->{arg};
  my @exargs=($request->{arg});
  my $delay=0;
  my $delayincrement=0;
  my $chunksize=0;
  if (ref($extrargs)) {
    @exargs=@$extrargs;
  }

  my $usage_string=xCAT::Usage->parseCommand($command, @exargs);
  if ($usage_string) {
    $callback->({data=>$usage_string});
    $request = {};
    return;
  }

  if ($command eq "rpower") {
      my $subcmd=$exargs[0];
			if($subcmd eq ''){
	  		$callback->({data=>["Please enter an action (eg: boot,off,on, etc)",  $usage_string]});
	  		$request = {};
				return 0;

			}
      if ( ($subcmd ne 'stat') && ($subcmd ne 'state') && ($subcmd ne 'status') && ($subcmd ne 'on') && ($subcmd ne 'off') && ($subcmd ne 'softoff') && ($subcmd ne 'nmi')&& ($subcmd ne 'cycle') && ($subcmd ne 'reset') && ($subcmd ne 'boot') && ($subcmd ne 'wake') && ($subcmd ne 'suspend')) {
	  $callback->({data=>["Unsupported command: $command $subcmd", $usage_string]});
	  $request = {};
	  return;
      }
      if (($subcmd eq 'on' or $subcmd eq 'reset' or $subcmd eq 'boot') and $::XCATSITEVALS{syspowerinterval}) {
		unless($::XCATSITEVALS{syspowermaxnodes}) {
			$callback->({errorcode=>[1],error=>["IPMI plugin requires syspowermaxnodes be defined if syspowerinterval is defined"]});
		        $request = {};
			return 0;
		}
	$chunksize=$::XCATSITEVALS{syspowermaxnodes};
        $delayincrement=$::XCATSITEVALS{syspowerinterval};
      }
  } elsif ($command eq "renergy") {
      # filter out the nodes which should be handled by ipmi.pm
      my (@bmcnodes, @nohandle);
      xCAT::Utils->filter_nodes($request, undef, undef, \@bmcnodes, \@nohandle);
      $realnoderange = \@bmcnodes;
  } elsif ($command eq "rspconfig") {
      # filter out the nodes which should be handled by ipmi.pm
      my (@bmcnodes, @nohandle);
      xCAT::Utils->filter_nodes($request, undef, undef, \@bmcnodes, \@nohandle);
      $realnoderange = \@bmcnodes;
  }

  if (!$realnoderange) {
    $usage_string=xCAT::Usage->getUsage($command);
    $callback->({data=>$usage_string});
    $request = {};
    return;
  }   
  
  #print "noderange=@$noderange\n";

  # find service nodes for requested nodes
  # build an individual request for each service node
  my @noderanges;
  srand();
  if ($chunksize) {
     #first, we try to spread out the chunks so they don't happen to correlate to constrained service nodes or circuits
     #for now, will get the sn map for all of them and interleave if dispatching
     #if not dispatching, will randomize the noderange instead to lower likelihood of turning everything on a circuit at once
     if (defined $::XCATSITEVALS{ipmidispatch} and $::XCATSITEVALS{ipmidispatch} =~ /0|n/i) { #no SN indicated, instead do randomize
	randomizelist($realnoderange);
     } else { # sn is indicated
	my $bigsnmap = xCAT::ServiceNodeUtils->get_ServiceNode($realnoderange, "xcat", "MN");
     	foreach my $servicenode (keys %$bigsnmap) { #let's also shuffle within each service node responsibliity
		randomizelist($bigsnmap->{$servicenode})
	}
	#now merge the per-servicenode list into a big list again
	$realnoderange=[];
	while (keys %$bigsnmap) {
		foreach my $servicenode (keys %$bigsnmap) {
			if (@{$bigsnmap->{$servicenode}}) {
				push(@$realnoderange,pop(@{$bigsnmap->{$servicenode}}));
			} else {
				delete $bigsnmap->{$servicenode};
			}
		}
	}
	
     }
     while (scalar(@$realnoderange)) {
             my @tmpnoderange;
	     while (scalar(@$realnoderange) and $chunksize) {
		push @tmpnoderange,(shift @$realnoderange);
		$chunksize--;
	     }
	     push @noderanges,\@tmpnoderange;
	     $chunksize=$::XCATSITEVALS{syspowermaxnodes};
      }	
  } else {
     @noderanges=($realnoderange);
  }
  foreach my $noderange (@noderanges) {  
     my $sn;
     if (defined $::XCATSITEVALS{ipmidispatch} and $::XCATSITEVALS{ipmidispatch} =~ /0|n/i) {
        $sn = { '!xcatlocal!' => $noderange };
     } else {
        $sn = xCAT::ServiceNodeUtils->get_ServiceNode($noderange, "xcat", "MN");
     }

     # build each request for each service node
 
     foreach my $snkey (keys %$sn)
     {
       #print "snkey=$snkey\n";
       my $reqcopy = {%$request};
       $reqcopy->{node} = $sn->{$snkey};
       unless ($snkey eq '!xcatlocal!') {
          $reqcopy->{'_xcatdest'} = $snkey;
       }
       $reqcopy->{_xcatpreprocessed}->[0] = 1;
       if ($delay) { $reqcopy->{'_xcatdelay'} = $delay; }
       push @requests, $reqcopy;
     }
     $delay += $delayincrement;
  }
  return \@requests;
}
    
     
sub getipmicons {
    my $argr=shift;
    #$argr is [$node,$nodeip,$nodeuser,$nodepass];
    my $cb = shift;
    my $ipmicons={node=>[{name=>[$argr->[0]]}]};
    $ipmicons->{node}->[0]->{bmcaddr}->[0]=$argr->[1];
    $ipmicons->{node}->[0]->{bmcuser}->[0]=$argr->[2];
    $ipmicons->{node}->[0]->{bmcpass}->[0]=$argr->[3];
    my $ipmisess =  xCAT::IPMI->new(bmc=>$argr->[1],userid=>$argr->[2],password=>$argr->[3]);
    if ($ipmisess->{error}) {
        xCAT::SvrUtils::sendmsg([1,$ipmisess->{error}],$cb,$argr->[0],%allerrornodes);
        return;
    } 
    $ipmisess->{ipmicons} = $ipmicons;
    $ipmisess->{cb} = $cb;
    $ipmisess->subcmd(netfn=>0x6,command=>0x38,data=>[0x0e,0x04],callback=>\&got_channel_auth_cap_foripmicons,callback_args=>$ipmisess);
}
sub got_channel_auth_cap_foripmicons {
    my $rsp = shift;
    my $ipmis = shift;
    if ($rsp->{error}) {
        return;
    }
    if ($rsp->{code} != 0) { return; }
    my $cb = $ipmis->{cb};
    $cb->($ipmis->{ipmicons}); #ipmicons);
}


# scan subroutine is used to scan the hardware devices which installed on the host node
# In current implementation, only the mic cards will be scanned.
# scan
# scan -u/-w/-z
my @rscan_header = (
  ["type",          "%-8s" ],
  ["name",          "" ],
  ["id",            "%-8s" ],
  ["host",           "" ]);

sub scan {
    my $request = shift;
    my $subreq = shift;
    my $nodes = shift;
    my $args = shift;

    my $usage_string = "rscan [-u][-w][-z]";

    my ($update, $write, $stanza);
    foreach (@$args) {
        if (/-w/) {
            $write= 1;
        } elsif (/-u/) {
            $update = 1;
        } elsif (/-z/) {
            $stanza = 1;
        } else {
            $callback->({error=>[$usage_string]});
            return;
        }
    }

    my $output = xCAT::Utils->runxcmd({ command => ['xdsh'], 
                                       node => $nodes,
                                       arg => ['/opt/intel/mic/bin/micinfo', '-listDevices'] }, $subreq, 0, 1);

    # parse the output from 'xdsh micinfo -listDevices'
    my %host2mic;
    my $maxhostname = 0;
    foreach (@$output) {
        foreach (split /\n/, $_) {
            if (/([^:]*):\s+(\d+)\s*\|/) {
                my $host = $1;
                my $deviceid = $2;
                push @{$host2mic{$host}}, $deviceid;
                if (length($host) > $maxhostname) {
                    $maxhostname = length($host);
                }
            }
        }
    }

    # generate the display message
    my @displaymsg;
    my $format = sprintf "%%-%ds",($maxhostname+10);
    $rscan_header[1][1] = $format;
    $format = sprintf "%%-%ds",($maxhostname+2);
    $rscan_header[3][1] = $format;
    if ($stanza) {
        # generate the stanza for each mic
        foreach (keys %host2mic) {
            my $host = $_;
            foreach (@{$host2mic{$host}}) {
                my $micid = $_;
                push @displaymsg, "$host-mic$micid:";
                push @displaymsg, "\tobjtype=node";
                push @displaymsg, "\tmichost=$host";
                push @displaymsg, "\tmicid=$micid";
                push @displaymsg, "\thwtype=mic";
                push @displaymsg, "\tmgt=mic";
            }
        }
    } else {
        # generate the headers for scan message
        my $header;
        foreach ( @rscan_header ) {
            $header .= sprintf @$_[1],@$_[0];
        }
        push @displaymsg, $header;

        # generate every entries
        foreach (keys %host2mic) {
            my $host = $_;
            foreach (@{$host2mic{$host}}) {
                my $micid = $_;
                my @data = ("mic", "$host-mic$micid", "$micid", "$host");
                my $i = 0;
                my $entry;
                foreach ( @rscan_header ) {
                    $entry .= sprintf @$_[1],$data[$i++];
                }
                push @displaymsg, $entry;
            }
        }
    }

    $callback->({data=>\@displaymsg});

    unless ($update || $write) {
        return;
    }

    # for -u / -w, write or update the mic node in the xCAT DB
    my $nltab = xCAT::Table->new('nodelist');
    my $mictab = xCAT::Table->new('mic');
    my $nhmtab = xCAT::Table->new('nodehm');
    if (!$nltab || !$mictab || !$nhmtab) {
        $callback->({error=>["Open database table failed."], errorcode=>1});
        return;
    }

    # update the node to the database
    foreach (keys %host2mic) {
        my $host = $_;
        foreach (@{$host2mic{$host}}) {
            my $micid = $_;
            my $micname = "$host-mic$micid";
            # update the nodelist table
            $nltab->setAttribs({node=>$micname}, {groups=>"all,mic"});
            # update the mic table
            $mictab->setAttribs({node=>$micname}, {host=>$host, id=>$micid, nodetype=>'mic'});
            # update the nodehm table
            $nhmtab->setAttribs({node=>$micname}, {mgt=>'mic',cons=>'mic'});
        }
    }
}

   
sub process_request {
  my $request = shift;
  $callback = shift;
  my $subreq = shift;
  my $noderange = $request->{node}; #Should be arrayref
  my $command = $request->{command}->[0];
  my $extrargs = $request->{arg};
  my @exargs=($request->{arg});
  if (ref($extrargs)) {
    @exargs=@$extrargs;
  }
	my $ipmiuser = 'USERID';
	my $ipmipass = 'PASSW0RD';
	my $ipmitrys = 3;
	my $ipmitimeout = 2;
	my $ipmitab = xCAT::Table->new('ipmi');
	my $tmp;
        if ($::XCATSITEVALS{ipmitimeout}) { $ipmitimeout = $::XCATSITEVALS{ipmitimeout} };
        if ($::XCATSITEVALS{ipmiretries}) { $ipmitrys = $::XCATSITEVALS{ipmitretries} };
        if ($::XCATSITEVALS{ipmisdrcache}) { $enable_cache = $::XCATSITEVALS{ipmisdrcache} };

    #my @threads;
    my @donargs=();
    if ($request->{command}->[0] =~ /fru/) {
        my $vpdtab = xCAT::Table->new('vpd');
        $vpdhash = $vpdtab->getNodesAttribs($noderange,[qw(serial mtm asset)]);
    }
	my $ipmihash = $ipmitab->getNodesAttribs($noderange,['bmc','username','password']) ;
	my $authdata = xCAT::PasswordUtils::getIPMIAuth(noderange=>$noderange,ipmihash=>$ipmihash);
	foreach(@$noderange) {
		my $node=$_;
		my $nodeuser=$authdata->{$node}->{username};
		my $nodepass=$authdata->{$node}->{password};
		my $nodeip = $node;
		my $ent;
		if (defined($ipmitab)) {
			$ent=$ipmihash->{$node}->[0];
			if (ref($ent) and defined $ent->{bmc}) { $nodeip = $ent->{bmc}; }
		}
	if ($nodeip =~ /,/ and grep ({ $_ eq $request->{command}->[0] } qw/rinv reventlog rvitals rspconfig/)) { #multi-node x3950 X5, for example
		my $bmcnum=1;
		foreach (split /,/,$nodeip) {
        		push @donargs,[$node,$_,$nodeuser,$nodepass,$bmcnum];
			$bmcnum+=1;
		}
	} else {
		$nodeip =~ s/,.*//; #stri
        	push @donargs,[$node,$nodeip,$nodeuser,$nodepass,1];
	}
    }
    if ($request->{command}->[0] eq "getipmicons") {
        foreach (@donargs) {
            getipmicons($_,$callback);
        }
    	while (xCAT::IPMI->waitforrsp()) { yield };
        return;
    }
    if ($request->{command}->[0] eq "rspconfig") {
        my $updatepasswd = 0;
        my $index = 0;
        foreach (@{$request->{arg}}) {
            if ($_ =~ /^USERID=\*$/) {
                 $updatepasswd = 1;
                 last;
            }
            $index++;
        }
        if ($updatepasswd) {
           splice(@{$request->{arg}}, $index, 1);
           @exargs=@{$request->{arg}};
           foreach (@donargs) {
               my $cliuser = $authdata->{$_->[0]}->{cliusername};
               my $clipass = $authdata->{$_->[0]}->{clipassword};
               xCAT::IMMUtils::setupIMM($_->[0],curraddr=>$_->[1],skipbmcidcheck=>1,skipnetconfig=>1,cliusername=>$cliuser,clipassword=>$clipass,callback=>$callback);
           }
           if ($#exargs == -1) {
               return;
           }     
       }
    }

    # handle the rscan to scan the mic on the target host
    if ($request->{command}->[0] eq "rscan") {
        scan ($request, $subreq, $noderange, $extrargs);
        return;
    }

  #get new node status
  my %oldnodestatus=(); #saves the old node status
  my $check=0;
  my $global_check=1;
  if (defined $::XCATSITEVALS{nodestatus} and $::XCATSITEVALS{nodestatus} =~ /0|n|N/) { $global_check=0; }


  if ($command eq 'rpower') {
    if (($global_check) && ($extrargs->[0] ne 'stat') && ($extrargs->[0] ne 'status') && ($extrargs->[0] ne 'state') && ($extrargs->[0] ne 'suspend') && ($extrargs->[0] ne 'wake')) { 
      $check=1; 
      my @allnodes=();
      foreach (@donargs) { push(@allnodes, $_->[0]); }

      #save the old status
      my $nodelisttab = xCAT::Table->new('nodelist');
      if ($nodelisttab) {
        my $tabdata     = $nodelisttab->getNodesAttribs(\@allnodes, ['node', 'status']);
        foreach my $node (@allnodes)
        {
            my $tmp1 = $tabdata->{$node}->[0];
            if ($tmp1) { 
		if ($tmp1->{status}) { $oldnodestatus{$node}=$tmp1->{status}; }
		else { $oldnodestatus{$node}=""; }
	    }
	}
      }
      #print "oldstatus:" . Dumper(\%oldnodestatus);
      
      #set the new status to the nodelist.status
      my %newnodestatus=(); 
      my $newstat;
      if (($extrargs->[0] eq 'off') || ($extrargs->[0] eq 'softoff')) { 
	  my $newstat=$::STATUS_POWERING_OFF; 
	  $newnodestatus{$newstat}=\@allnodes;
      } else {
        #get the current nodeset stat
        if (@allnodes>0) {
	  my $nsh={};
          my ($ret, $msg)=xCAT::SvrUtils->getNodesetStates(\@allnodes, $nsh);
          if (!$ret) { 
            foreach (keys %$nsh) {
		my $newstat=xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState($_, "rpower");
		$newnodestatus{$newstat}=$nsh->{$_};
	    }
	  } else {
	      $callback->({data=>$msg});
	  }
        }
      }
      #donot update node provision status (installing or netbooting) here
      xCAT::Utils->filter_nostatusupdate(\%newnodestatus);
      #print "newstatus" . Dumper(\%newnodestatus);
      xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%newnodestatus, 1);
    }
  }

    my $children = 0;
    my $sub_fds = new IO::Select;
    foreach (@donargs) {
      donode($_->[0],$_->[1],$_->[2],$_->[3],$_->[4],$ipmitimeout,$ipmitrys,$command,-args=>\@exargs);
	}
    while (xCAT::IPMI->waitforrsp()) { yield };
    my $node;
    foreach $node (keys %sessiondata) {
        if ($sessiondata{$node}->{ipmisession}) {
            $sessiondata{$node}->{ipmisession}->logout();
        }
    }
    while (xCAT::IPMI->waitforrsp()) { yield };
    if (keys %needbladeinv) {
	#ok, we have some inventory data that, for now, suggests blade plugin to getdata from blade plugin
#	my @bladenodes = keys %needbladeinv;
#	$request->{arg}=['mac'];
#        $request->{node}=\@bladenodes;
#	require xCAT_plugin::blade;
#	xCAT_plugin::blade::process_request($request,$callback);
    }
####return;
####while ($sub_fds->count > 0 and $children > 0) {
####  my $handlednodes={};
####  forward_data($callback,$sub_fds,$handlednodes);
####  #update the node status to the nodelist.status table
####  if ($check) {
####    updateNodeStatus($handlednodes, \@allerrornodes);
####  }
####}
####
#####Make sure they get drained, this probably is overkill but shouldn't hurt
####my $rc=1;
####while ( $rc>0 ) {
####  my $handlednodes={};
####  $rc=forward_data($callback,$sub_fds,$handlednodes);
####  #update the node status to the nodelist.status table
####  if ($check) {
####    updateNodeStatus($handlednodes, \@allerrornodes);
####  }
####} 

    if ($check) {
        #print "allerrornodes=@allerrornodes\n";
        #revert the status back for there is no-op for the nodes
        my %old=(); 
        foreach my $node (keys %allerrornodes) {
    	    my $stat=$oldnodestatus{$node};
    	    if (exists($old{$stat})) {
    		    my $pa=$old{$stat};
        		push(@$pa, $node);
    	    } else {
          		$old{$stat}=[$node];
	        }
        } 
        xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%old, 1);
    }  
}

#sub updateNodeStatus {
#  my $handlednodes=shift;
#  my $allerrornodes=shift;
#  foreach my $node (keys(%$handlednodes)) {
#    if ($handlednodes->{$node} == -1) { push(@$allerrornodes, $node); }  
#  }
#}



#sub forward_data { #unserialize data from pipe, chunk at a time, use magic to determine end of data structure
# my $callback = shift;
# my $fds = shift;
# my $errornodes=shift;

# my @ready_fds = $fds->can_read(1);
# my $rfh;
# my $rc = @ready_fds;
# foreach $rfh (@ready_fds) {
#   my $data;
#   if ($data = <$rfh>) {
#     while ($data !~ /ENDOFFREEZE6sK4ci/) {
#       $data .= <$rfh>;
#     }
#     eval { print $rfh "ACK\n"; };  # Ignore ack loss to child that has given up and exited
#     my $responses=thaw($data);
#     foreach (@$responses) {
#       #save the nodes that has errors and the ones that has no-op for use by the node status monitoring
#       my $no_op=0;
#       if (exists($_->{node}->[0]->{errorcode})) { $no_op=1; }
#       else { 
#         my $text=$_->{node}->[0]->{data}->[0]->{contents}->[0];
#         #print "data:$text\n";
#         if (($text) && ($text =~ /$status_noop/)) {
#       $no_op=1;
#           #remove the symbols that meant for use by node status
#           $_->{node}->[0]->{data}->[0]->{contents}->[0] =~ s/ $status_noop//; 
#         }
#       }  
#   #print "data:". $_->{node}->[0]->{data}->[0]->{contents}->[0] . "\n";
#       if ($no_op) {
#         if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=-1; } 
#       } else {
#         if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=1; } 
#       }
#       $callback->($_);
#     }
#   } else {
#     $fds->remove($rfh);
#     close($rfh);
#   }
# }
# yield; #Avoid useless loop iterations by giving children a chance to fill pipes
# return $rc;
#}

sub donode {
  my $node = shift;
  my $bmcip = shift;
  my $user = shift;
  my $pass = shift;
  my $bmcnum = shift;
  my $timeout = shift;
  my $retries = shift;
  my $command = shift;
  my %namedargs=@_;
  my $extra=$namedargs{-args};
  my @exargs=@$extra;
  $sessiondata{$node} = {
      node => $node, #this seems redundant, but some code will not be privy to what the key was
      bmcnum => $bmcnum,
      ipmisession => xCAT::IPMI->new(bmc=>$bmcip,userid=>$user,password=>$pass),
      command => $command,
      extraargs => \@exargs,
      subcommand => $exargs[0],
  };
  if ($sessiondata{$node}->{ipmisession}->{error}) {
      xCAT::SvrUtils::sendmsg([1,$sessiondata{$node}->{ipmisession}->{error}],$callback,$node,%allerrornodes);
  } else {
    my ($rc,@output) = ipmicmd($sessiondata{$node});
    sendoutput($rc,@output);
    yield;
    return $rc;
  }
  #my $msgtoparent=freeze(\@outhashes);
 # print $outfd $msgtoparent;
}

sub sendoutput {
    my $rc=shift;
    foreach (@_) {
        my %output;
        (my $desc,my $text) = split(/:/,$_,2);
        unless ($text) {
          $text=$desc;
        } else {
          $desc =~ s/^\s+//;
          $desc =~ s/\s+$//;
          if ($desc) {
             $output{node}->[0]->{data}->[0]->{desc}->[0]=$desc;
          }
        }
        $text =~ s/^\s+//;
        $text =~ s/\s+$//;
        $output{node}->[0]->{name}->[0]="BADCODE";
        if ($rc) {
          $output{node}->[0]->{errorcode}=[$rc];
            $output{node}->[0]->{error}->[0]=$text;
        } else {
            $output{node}->[0]->{data}->[0]->{contents}->[0]=$text;
        }
        $callback->(\%output);
        #push @outhashes,\%output; #Save everything for the end, don't know how to be slicker with Storable and a pipe
#        print $outfd freeze([\%output]);
#        print $outfd "\nENDOFFREEZE6sK4ci\n";
#        yield;
#        waitforack($outfd);
    }
}

##########################################################################
# generate hardware tree, called from lstree.
##########################################################################
sub genhwtree
{
    my $nodelist = shift;  # array ref
	my $callback = shift;
	my %hwtree;

    my $bmchash;
    # read ipmi.bmc
    my $ipmitab = xCAT::Table->new('ipmi');
    if ($ipmitab)
    {
        $bmchash = $ipmitab->getNodesAttribs($nodelist, ['bmc']);
    }
    else
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Can not open ipmi table.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
    }

    foreach my $node (@$nodelist)
    {
        if ($bmchash->{$node}->[0]->{'bmc'})
        {
            push @{$hwtree{$bmchash->{$node}->[0]->{'bmc'}}}, $node;
        }
    
    }

    return \%hwtree;

}




1;