#!/usr/bin/perl
#
use strict;
use Carp;
use Data::Dumper;

$ENV{'LANG'} = 'C';

use lib 'lib';
use Parse::DMIDecode ();


use constant {
		CPU_FILE 		=> '/proc/cpuinfo',
		CPU_INFO_MATCH		=> { 
						'frequency' 	=> '^cpu\sMHz\s+\:(.+)$',
						'processor' 	=> '^processor\s+\:\s(\d+)$',
						'64bit'		=> '^flags\s+:.+\s+(lm)\s+.+$',
						'model-name'	=> '^model\sname\s+:\s+(.+)$',
					    }, # processor       : 0
		CPU_VERSION		=> { 
						'Celeron' 		=> { 'match' => [ 'Intel Celeron\(R\)', 'Intel\(R\) Celeron\(R\) CPU', ], 	'number' => 1 },
						'Cel'			=> { 'match' => [ 'Celeron \(Coppermine\)' ], 					'number' => 1 },
						'Piv' 			=> { 'match' => [ 'Intel Pentium\(R\) 4', 'Intel\(R\) Pentium\(R\) 4 CPU'], 	'number' => 1 },
						'Piv-HT'		=> { 'match' => [ 'Intel Pentium\(R\) 4', 'Intel\(R\) Pentium\(R\) 4 CPU'], 	'number' => 2 },
						'pdc'			=> { 'match' => [ 'Intel\(R\) Pentium\(R\) Dual  CPU  E2180', ], 		'number' => 2 },
						'Pentium'		=> { 'match' => [ 'Intel\(R\) Pentium\(R\) 4 CPU', ], 				'number' => 2 },
						'PentiumDual'		=> { 'match' => [ 'Intel\(R\) Pentium\(R\) D CPU', ], 				'number' => 2 },
						'Athlon'                => { 'match' => [ 'AMD Athlon\(tm\) 64 Processor 2800+'],                       'number' => 1 },
						'Opteron'  		=> { 'match' => [ 'AMD Opteron\(tm\) Processor 150', ], 			'number' => 1 },
						'OpteronDual'		=> { 'match' => [ 'Dual Core AMD Opteron\(tm\) Processor 180', ], 		'number' => 2 },
						'Core2duoE'		=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 CPU\s+66', ], 			'number' => 2 },
						'E5345'			=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 CPU\s+E5345', ], 			'number' => 1 },
						'E6300' 		=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 CPU\s+63', ], 			'number' => 2 },
						'E6550' 		=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 CPU\s+E6550'], 			'number' => 2 },
						'E6750' 		=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 Duo CPU\s+E6750', ], 			'number' => 2 },
						'Q6600'			=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 Quad CPU', ], 			'number' => 4 },
						'T5500'			=> { 'match' => [ 'Intel\(R\) Core\(TM\)2 CPU\s+T5500', ], 			'number' => 2 },
						'xeon'			=> { 'match' => [ 'Intel\(R\) Xeon\(TM\) CPU', ], 				'number' => 2 },
						'xeonx2'		=> { 'match' => [ 'Intel\(R\) Xeon\(TM\) CPU', ], 				'number' => 4 },
						'xeondx2'		=> { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+5150', ], 			'number' => 8 },
						'xeonQuad'		=> { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+X5345', ], 			'number' => 8 },
						'xeonQuadx2'		=> { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+E5345', ], 			'number' => 8 },
						'xeonWoodcrest'		=> { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+E5345', ], 			'number' => 4 },
						'xeonWoodcrestx2'	=> { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+5150', ], 			'number' => 4 },
						'xeonWoodcrestx2'       => { 'match' => [ 'Intel\(R\) Xeon\(R\) CPU\s+X5355', ],                        'number' => 8 },
						'Xex2'			=> { 'match' => [], 								'number' => 1 },
					},
                SMART_CTL               => '/usr/sbin/smartctl',
                DMIDECODE               => '/usr/local/sbin/dmidecode',
                LSSCSI                  => '/root/profile/bin/lsscsi',
                TW_CLI                  => '/sbin/tw_cli',
                HDD_DB                  => '/root/profile/lib/hddtemp.db',
};

####################################################################"
# new dmidecode
#
# allowed for keyword()
#  	bios-vendor
#  	bios-version
#  	bios-release-date
#  	system-manufacturer
#  	system-product-name
#  	system-version
#  	system-serial-number
#  	system-uuid
#  	baseboard-manufacturer
#  	baseboard-product-name
#  	baseboard-version
#  	baseboard-serial-number
#  	baseboard-asset-tag
#  	chassis-manufacturer
#  	chassis-type
#  	chassis-version
#  	chassis-serial-number
# 	chassis-asset-tag
#  	processor-family
#  	processor-manufacturer
#  	processor-version
#  	processor-frequency
# allowed for keywords() method
#  	bios
#  	system
#  	baseboard
#  	chassis
#  	processor
#  	memory
#  	cache
#  	connector
#  	slot
my $dmidecode = Parse::DMIDecode->new( 'dmidecode' => DMIDECODE, 'nowarnings' => 1 );
$dmidecode->probe;

# retrieve info from  CPU_FILE
my @cpuFileLines;
open CPUINFO, "< ".CPU_FILE or croak "Unable to open ".CPU_FILE;
	@cpuFileLines = <CPUINFO>;
close CPUINFO;
map { chomp $_ } @cpuFileLines;

# detect processeur information from dmidecode or /proc/cpuinfo
my %processor = (
			'manufacturer' 		=> $dmidecode->keyword("processor-manufacturer"),
			#'version'		=> $dmidecode->keyword("processor-version"),
			'version'		=> get_cpu_info( 'model-name'),
			'family'		=> $dmidecode->keyword("processor-family"),
			'frequency'		=> $dmidecode->keyword("processor-frequency") || get_cpu_info( 'frequency'),
			'64bit'         	=> get_cpu_info( '64bit') ? 1 : 0,
			'processor'     	=> get_cpu_info( 'processor'),
);
#print Dumper \%processor;

# find disk informations (type, device, size)
my %disk 	= %{get_disk_type()};
#print Dumper \%disk;
$disk{'size'}	= get_disk_size( get_disk_type()->{'model'});


# find ram size
my $ram_size = 0;
for my $handle ($dmidecode->get_handles) # foreach handle
{
	next unless $handle->description eq 'Memory Device';
	next unless $handle->keyword( 'memory-size') =~ /^\d+\sMB/;
        my $bankSize = $handle->keyword( 'memory-size');
        $bankSize =~ s/\s.+$//;
        $ram_size += $bankSize;
}

# set 64 bit on profile if we 64bit is activated and this is not a sm
my $profile     = $dmidecode->keyword( 'system-manufacturer' ) =~ /supermicro/i ? 'sm.' : $processor{'64bit'} ? '64.' : '';

# find cpu version
$profile .= '(';
foreach my $cpu ( keys %{&CPU_VERSION} )
{
        my $match = &CPU_VERSION->{$cpu}->{'match'};
        if ( (grep { $processor{'version'} =~ $_ } @{$match} ) && $processor{'processor'} == CPU_VERSION->{$cpu}->{'number'} )
        {
                $profile .= '|' unless $profile =~ /\($/;
                $profile .= $cpu;
        }
}
$profile .= ')';
$profile =~ /^(sm\.|64\.){0,1}([^\|.]+)/ and $profile =~ s/\.\((.+)\)$/\.$1/;

$profile .= '.'.$disk{'size'};
if (  $disk{'type'} eq 'raid')
{
        $profile .= 'sax'.$disk{'port_number'};
}
elsif ( $disk{'scsi'} eq 'ATA' )
{
        if ( $disk{'port_number'} && $disk{'port_number'} > 1 )
        {
                $profile .= 'sax'.$disk{'port_number'};
        }
        else
        {
                $profile .= 'sa';
        }
}
elsif ( $disk{'scsi'} )
{
        $profile .= 'sx'.$disk{'port_number'};
}
else
{
        $profile .= '(Go|i)';
}

$profile .= ".$ram_size";
$profile = "(1|2)U.$profile";

print "$profile\n";

##################################################################
# get_cpu_info( keyword)
# keyword allowed: manufacturer family version frequency
# return value of keyword
sub get_cpu_info
{
	my $keyword = shift;
	!grep { $keyword eq $_ } keys %{&CPU_INFO_MATCH} and  croak "Invalid keyord $keyword";
	
	my $value = '';
	foreach my $line ( @cpuFileLines)
	{
		my $match 	= CPU_INFO_MATCH->{$keyword};
		next unless $line =~ /$match/;
		$value = $1;
		$value =~ s/^\s*//;
		$value =~ s/\s*$//;
		return $value unless $keyword eq 'processor'; # want the last processor 
	}
	
	return $keyword eq 'processor' ? $value+1 : $value;
}


##################################################################
# get_disk_type()
# return 
sub get_disk_type
{
	my $value = '';
	my $device = '';

	if ( -b '/dev/rd/c0d0') # raid
	{
		$value 	= 'raid'; # XXX obsolete
		$device = '/dev/rd/c0d0'; 
	}
	elsif (  -b '/dev/sda' ) # scsi
	{
		$value 	= 'scsi'; # scsi LSI/ 3ware
		$device	= '/dev/sda';
	}
	elsif ( -b '/dev/hda' )
	{
		$value 	= 'ide';
		$device = '/dev/hda';
	}

	my ($model, $scsi, $scsi_version, $port_number, $is3ware );
	my @output;
	open SMART, SMART_CTL." -i $device |" or croak "Unable to retrieve disk size: $!";
		@output = <SMART>;
	close SMART;
	map { chomp $_ } @output;

	# Device Model:     ST340014A
	if ( my @capacity = grep { $_ =~ /^Device\sModel\:\s+(.+)$/ } @output )
	{
		my $line        = $capacity[0];
		$line		=~  /^Device\sModel\:\s+(.+)$/;
		$model 		= $1;
	}

        # Maybe ATA, model given into Device line
        # Device: ATA      ST3250820AS
        if ( !$model && (my @capacity = grep { $_ =~ /^Device:\s+ATA\s+(.+)\s+Version/ } @output) )
        {
                my $line        = $capacity[0];
                $line           =~ s/^\s+//;
                $line           =~ s/\s+$//;
                $model          = $line;

                open LIST_SCSI, LSSCSI."|" or croak "Unable to execute lsscsi: $!";
                    @output = <LIST_SCSI>;
                close LIST_SCSI;
                map { chomp $_ } @output;

                # [0:0:0:0]    disk    ATA      ST3250820AS      3.AA  -
                foreach my $line ( @output )
                {
                        if ( $line =~ /^\[0\:0\:0\:0\]\s+disk\s+(ATA)\s+(\S+)\s+(\S+)\s.+$/ )
                        {
                                $scsi = $1;
                                $scsi_version = $3;
                                next;
                        }
                        elsif ( $line =~ /^\[(1)\:0\:0\:0\]\s+disk\s+(ATA)\s+(\S+)\s+(\S+)\s.+$/ )
                        {
                                if ( $1 == 0 )
                                {
                                        $model = $2;
                                }
                                else
                                {
                                        $port_number = $1+1;
                                }
                        }
                }
        }

        # 3ware devices
	# Device: 3ware    Logical Disk 0   Version: 1.2
        if ( !$model && (my @capacity = grep { $_ =~ /^Device:\s+(3ware|AMCC).+$/ } @output) )
        {
		$is3ware = 1;
                open SMART, SMART_CTL." -i -d 3ware,0 /dev/twa0 |" or croak "Unable to retrieve disk size: $!";
                        @output = <SMART>;
                close SMART;
                map { chomp $_ } @output;
                if ( my @capacity = grep { $_ =~ /^Device\sModel\:\s+(.+)$/ } @output )
                {
                        my $line        = $capacity[0];
                        $line           =~  /^Device\sModel\:\s+(.+)$/;
                        $model          = $1;
			
                }

		if ( !$model )
		{
			# get p0 model
			# rescue:~/profile# tw_cli info c0 p0 model     
			# /c0/p0 Model = ST3250820AS
			open TWCLI, TW_CLI." info c0 p0 model |" or croak "Unable to launch tw_cli: $!";
				 @output = <TWCLI>;
			close TWCLI;
			$value = 'raid';
			foreach my $line ( @output )
			{
			        next unless ( $line && $line =~ /^\/c0\/p0\sModel\s=\s+(.+)$/);
				$model = $1;
			}
		}

		# get port number
		#/c0 Number of Ports = 8
		open TWCLI, TW_CLI." info c0 numdrives |" or croak "Unable to launch tw_cli: $!";
			@output = <TWCLI>;
		close TWCLI;
		map { chomp $_ } @output;

		foreach my $line ( @output )
		{
			next unless ( $line && $line =~ /^\/c0\sNumber\sof\sDrives\s+=\s+(\d+)$/);
			$port_number = $1;
			last;
		}


        }

	if ( !$model ) # scsi device
	{
		open LIST_SCSI, LSSCSI."|" or croak "Unable to execute lsscsi: $!";
			@output = <LIST_SCSI>;
		close LIST_SCSI;
		map { chomp $_ } @output;

		foreach my $line ( @output )
		{
			if ( $line =~ /^\[0\:0\:0\:0\]\s+disk\s+(\S+)\s+(\S+)\s+.+$device$/ )
			{
				$scsi = $1;
				$scsi_version = $2;
				next;
			}
			elsif ( $line =~ /^\[0\:1\:(\d+)\:0\]\s+disk\s+(\S+)\s+(\S+)\s+.+$/ )
			{
				if ( $1 == 0 )
				{
					$model = $3;
				}
				else
				{
					$port_number = $1+1;
				}
			}
		} 
	}

	
	return { 'device' => $device, 'type' => $value , 'model' => $model, 'scsi' => $scsi, 'scsi_version' => $scsi_version, 'port_number' => $port_number, '3ware' => $is3ware };
}

##################################################################
# get_disk_size()
# return 
sub get_disk_size
{
        my $model       = shift;
        my $value       = '';
        my @output;

        not defined $model and croak "Missing device name as argument!";

        my @output;
        open HDD_FH, HDD_DB or croak "Unable to open ".HDD_DB." : $!";
                @output = <HDD_FH>;
        close HDD_FH or carp "Failed to close ".HDD_DB." : $!";
        map { chomp $_ } @output;

        foreach my $line ( @output )
        {
                $line =~ /^\"([^\".]+)\".+$/;
                my $regexp = $1;
                next unless ( $regexp && $model =~ /$regexp/ );
                $line =~ /\s(\d+)\s?Gb/i;
                $value = $1;
		# on family products no size is specified
		# have to detect \[\d+\]0 into regexp 
		if ( !$value )
		{
			if ( $regexp =~ /(\[\d+\]0)/ ) # this is regexp size
			{
				my $availableSize = $1;
				if ( $model =~ /($availableSize)/ )
				{
					$value = $1;
				}
			}
		}
		last;
        }

        !$value and croak "Unable to found model disk $model into db file ".HDD_DB;

        return $value; # Go
}

