#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
use Pod::Usage;
use Sys::Hostname;
use XML::Simple;

my $VERSION = '1.0';
my $DD      = "/usr/sbin/dmidecode";
my $hpdiags = '/opt/hp/hpdiags/hpdiags';

# define options, show help if requested
my ($help, $file, $h, $x, $ver);
GetOptions("help|h"         => \$help,
           "file|f=s"       => \$file,
           "version|v=s"    => \$ver,
          );
pod2usage(2) if $help;
if ($ver) { print $VERSION.'\n'; exit; }

my %dmidata           = get_dmidata($DD);
$h->{"product_number"}= get_dmi_field($DD, "SKU Number");
$h->{"manufacturer"}  = get_dmi_field($DD, "system-manufacturer");
$h->{"model"}         = get_dmi_field($DD, "system-product-name");
$h->{"serial"}        = get_dmi_field($DD, "system-serial-number");
$h->{"rom_model"}     = get_dmi_field($DD, "bios-version");
$h->{"rom_version"}   = get_dmi_field($DD, "bios-release-date");

my $xml = new XML::Simple;
if ($file) {
	$x = $xml->XMLin($file) or die "$file not found";
} else {
	die "Could not locate $hpdiags" if (!-f $hpdiags);
	$x = $xml->XMLin(join("", `$hpdiags -v 5 -t 2>/dev/null`)) or die "";
}

# Skip non-memory devices
next if ($x->{category}->{memory}->{device}->{class} ne "memoryDevice");

# Find errors
my $out;
while (my ($k,$v) = each %{$x->{category}->{memory}->{device}->{structure}}) {
	next if !defined($v->{property}->{spdMemoryType});
	next if (($v->{property}->{spdSingleBitTCount}->{value} == 0) &&
		 ($v->{property}->{spdMultiBitCount}->{value} == 0));
	if ($v->{property}->{spdSingleBitTCount}->{value} != 0) {
		$out .= print_failure("spdSingleBitTCount", $v);
	}
	if ($v->{property}->{spdMultiBitCount}->{value} != 0) {
		$out .= print_failure("spdMultiBitCount", $v);
	}
}

# Print output, if any failures were found.  Otherwise exit.
exit if !$out;
printf ("%s:\n", hostname);
printf ("    Product Number : %s\n", $h->{product_number});
printf ("    Serial Number  : %s\n", $h->{serial});
printf ("    Model          : %s %s\n", $h->{manufacturer}, $h->{model});
printf ("    ROM            : %s %s\n", $h->{rom_model}, $h->{rom_version});
print $out;

###
### Create a printed string of a DIMM failure
###
sub print_failure {
	my ($type, $h) = @_;
	my $bittype = "Uncorrectable multibit error(s)";
	$bittype = "Corrected single bit error(s)" if ($type eq "spdSingleBitTCount");

	$h->{caption}=~s/^.* - //g;
	return sprintf("        (%s) %s on %s\n            %s %s %s %s (P/N %s)\n",
		$h->{property}->{$type}->{value},
		$bittype,
		$h->{caption} || "",
		$h->{property}->{spdDescription}->{value} || "",
		$h->{property}->{spdMemoryType}->{value} || "",
		$h->{property}->{spdMemoryDRAMType}->{value} || "",
		$h->{property}->{spdMemoryDRAMSpeed}->{value} || "",
		$h->{property}->{spdPartNumber}->{value} || '?',
	);
}

###
### Pull all DMI data and break it into parsable components
###
sub get_dmidata {
        my ($DMIDECODE) = shift;
        my (%h);
        open (DMI, "$DMIDECODE|");
        my $dmi = join("", grep {!/^#/} <DMI>);
        close(DMI);

        my (@b) = split(/Handle 0x/, $dmi);
        foreach (@b) {
                my (%x, $id, @f);
                @f = split(/\n/, $_);
                # RHEL5 dmidecode
                if ($f[0] =~ /,/) {
                        ($id, $x{type}) = split(/,/, shift(@f));
                # RHEL4 dmidecode
                } else {
                        $id = shift(@f);
                        $x{type} = shift(@f);
                }
                $x{type} =~ s/^\s*DMI type //g;
                $x{type} =~ s/,.*$//g;
                $x{data} = \@f;
                $h{$id} = \%x;
        }
        return %h;
}

###
### Return a DMI field
###
sub get_dmi_field {
        my ($DMIDECODE, $field) = @_;
	my @ret;

        chomp (@ret = grep {!/^#/} split(/\n/, `$DMIDECODE -s $field 2>&1`));
        $ret[0]=~s/^\s{1,}//g;
        $ret[0]=~s/\s{1,}$//g;
        return $ret[0] if ($ret[0] !~ /^Invalid string keyword:/);

        chomp (@ret = grep {/$field/} split(/\n/, `$DMIDECODE 2>&1`));
	my ($attr, $val) = split(/: /, $ret[0]);
	return $val || "";
}

=head1 hpdiags_ramcheck

Run hpdiags and find failing DIMMs

=head1 SYNOPSIS

  hpdiags_ramcheck [options]

  Options:
    -f,--file=         Use an existing hpdiags.xml file instead of 
                       running hpdiags online now.
    -h,--help          Print this help menu and exit.

=head1 LIMITATIONS

  * Requires a version of dmidecode which supports the -s option 

=head1 LICENSE

  GPLv2

=head1 AUTHOR

  Geoff Silver <geoff@rootflags.net>

=cut

