#!/usr/bin/perl -I/opt/SUNWrasag/lib

# Be sure to avoid subroutine name duplication between
# any files defined in these require statements and
# any subroutines defined in this program.
require "subroutines.pm";
&st_globals();

####################################################################
#
# PROGRAM NAME : storstat
#
# This program checks the patch and firmware levels for the A5K Series.
# It uses the config-matrix included with the 
# package to check patch and firmware levels.
#
# Joe Harman's "Official SSA & A5x00 Series Software/Firmware Configuration 
# Matrix" is the basis for the patch and firmware check.
# Checking the patch and firmware levels for the A5K Series is a very
# complex process and includes checks for OS, System Type, System
# architecture, system bus, and disk types. Always retrieve the
# latest version of package when checking out an installation
# so that you can take advantage of the new features included
# in storstat to support new versions of the patch matrix.
#
# The default behavior is always to check against the latest
# REV of the patch matrix.
#
# This program written by David Halliwell SQE Network Storage
# 
# Copyright (c) 1999  Sun Microsystems Computer Corporation, Inc.
####################################################################

# Program style guidelines
#   1) Upper case variables are intended to be globals.
#          (This is the default in PERL)
#      my or local variables should be lower case
#   2) This program uses 4 space indentation for tabs
#   3) Run perl -cw storstat.pl before checking in changes
#      and resolve any problems.

# The patch and firmware configuration file included
# with stortools. This can also be specified on the 
# command line.
$CONFIGURATION_MATRIX = "${SYSTEMDIR}/config-matrix";

# storstat sets the PASS/FAIL criteria in ss_logger
# Individual sections maintain their own PASS/FAIL
# criteria and when messages are sent to
# /var/adm/messages any ERROR messages will
# set the global program result for storstat.

# Global variables use upper case letters

$PROGNAME = "storstat";

$SIG{INT} = 'int';

&check_root_access( $PROGNAME );

# Check for DEBUG flag
$DEBUG = "TRUE" if ($ENV{'DEBUG'}); 

# Define a log for ERROR messages
$ERROR_LOG  = "${LOGDIR}/error_log";

# Some system information
# uname -i platform => SUNW,Ultra-1
chomp($PLATFORM = `/usr/bin/uname -i`);

# uname -r release  => 5.6
chop($RELEASE  = `/bin/uname -r`);

&make_port_list;
# get the Veritas release
$VERITAS_VERSION_IS_SUPPORTED = 0;  # approved Veritas/Solaris combination
&get_veritas_version;

# get the Solstice release
$SDS_VERSION_IS_SUPPORTED = 0;  # approved SDS/Solaris combination
&get_solstice_version;

# Default case is PASS
$ERROR_FLAG = "PASS";

# $LUXADM_PROBE contains the output from luxadm probe

# Store the output from luxadm display by enclosure name
# in a hash array
# $LUXADM_DISPLAY{$enclosure_name} contains the output for each enclosure
# How many enclosures to query at once. This is an issue on large configurations
# and appears to be related to the max string length Unix can handle.
$MAX_DISPLAY = 50;

# Save disk inquiry data in
# @INQUIRY

# Flush buffers for each print
$| = 1;

# Get command line arguments sets numerous globals (Upper case)
# used in conditional execution
&proc_cli;
$DEBUG = 1;
$VERBOSE = 1;

# We are using our own version of this matrix and this
# routine must be updated every time the matrix gets upgraded
&get_configuration_matrix;

# Open the Log file

open(MAIL, ">${ERROR_LOG}") or die "Unable to open mail log : $ERROR_LOG\n";

# All tests or patch check
if (($ALL or $PC) and ($PATCH_CHECK_REQUIRED{$RELEASE})) {
    &patch_check;
    &veritas_patch_check;
    &solstice_patch_check;
} elsif ($VRTS) {
    &veritas_patch_check;
}

# Query the system

# All tests or FAN Check or Power supply check 
if ($ALL or $FAN or $FW or $MIN or $PS or $PC or $DISK or $FRU or $DRIVER) {
    print "\nSYSTEM QUERY:\n" if ($VERBOSE);
    my $count;
    # check here for errors in luxadm output
    &luxadm_probe;
    %ENCLOSURE = %A5000;  # from make_port_list
    if ($A5000_PRESENT) {
        &luxadm_display_enclosures;
    }
    &count_disks;
} 

# All tests or Driver Check
if ( ($ALL or $DRIVER) and ($PATCH_CHECK_REQUIRED{$RELEASE}) ) {
    &check_all_drivers;
}

# All tests or firmware tests
if (($ALL or $FW) and ($PATCH_CHECK_REQUIRED{$RELEASE})) {
    &inquiry;
    &check_t300_fw if $T300_PRESENT;
    &check_disk_firmware if $A5000_PRESENT;
    &check_ib_firmware if $A5000_PRESENT;
    &check_hba_fcode;
}

# Start FRU Checks

# All tests or fan check
if ($A5000_PRESENT) {

    if ($ALL or $FAN or $FRU) {
        &check_fans;
    }

    # All tests or Power supply check
    if ($ALL or $MIN or $FRU) {
        # This sets DISK_COUNT used in the IB fw check
        &check_min_configuration;
    }

    # All tests or Power supply check
    if ($ALL or $PS or $FRU) {
        &check_power_supplies;
    }

    # All tests or Power supply check or FRU check
    if ($ALL or $DISK or $FRU) {
        &check_disk_status;
    }
}

# Determine if an error was encountered and if so remind them.
if ($VERBOSE and ($ERROR_FLAG eq "FAIL")) {
    print "\nErrors were detected during the program execution!\n";
    print "Please review the program output and perform any \n";
    print "required upgrades and replace any defective components.\n";
}

if ($VERBOSE) {
    &st_matrix_msg();
}

# If no patch matrix is defined for this release
if (! $PATCH_CHECK_REQUIRED{$RELEASE}) {
    print "No patch matrix defined for this release : $RELEASE\n";
    print "No patch or firmware checking possible!\n";
}


print "${PROGNAME} result (Rev. ${PATCH_MATRIX_REVISION}) : $ERROR_FLAG\n";

# If we are sending mail close the file and send it

close(MAIL);

$subject="$PRODUCT:storstat: Errors or Warnings!";

if ($MAIL_RECIPIENT) {
    if(!mail_message($MAIL_RECIPIENT, $subject, ${ERROR_LOG})) {
		printf("fatal: error sending mail to $MAIL_RECIPIENT\n");
		exit 1;
    }
}

`/usr/bin/rm ${ERROR_LOG}`;
if ($ERROR_FLAG) {
    exit 1;
} else {
    exit 0;
}

##############################################
#
#               Functions
#
##############################################

sub proc_cli {

    # For every command line argument update the usage array
    my @usage = (
    "$PROGNAME -all | -disk | -drvr | -fan | -fru | -fw | -min | -pc | -ps | -vrts\n",
    "          [-log] [-mail email address(es)] [-mf matrix_file]\n",
    "          [-v] [-warn] [-dots] \n",
    "all  - Perform all checks\n",
    "disk - Perform disk status checks (A5K Only)\n",
    "drvr - Perform driver status checks\n",
    "dots - show dots during enclosure queries\n",
    "fan  - Check Fans (A5K Only)\n",
    "fru  - Check FRU's disks, fans, power supplies (A5K Only)\n",
    "fw   - Firmware check\n",
    "help - prints out the usage\n",
    "log  - Turn on logging to /var/adm/messages\n",
    "mail - mail errors and/or warnings to email address(es)\n",
    "mf   - patch matrix file\n",
    "min  - Check for recommended disk placement (A5K Only)\n",
    "pc   - Patch check\n",
    "ps   - Check power supply status (A5K Only)\n",
    "v    - Verbose mode\n",
    "vrts - Veritas version/patch checks\n",
    "warn - Mail warnings as well as errors\n",
    );

    if ($#ARGV<0) {
        print "No arguments!\n";
        die @usage;
    }
    my $do_something = "FALSE";

    $arg = shift @ARGV;
    while($arg) {
        # For every command line argument have an entry
        # It's more readable to keep these items in order
        # Set a unique global variable for each argument
        if ($arg =~ /help/)  {
            die @usage if ($arg =~ /help/); 
        } elsif ($arg =~ /-all/i) {
            $ALL          = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-disk/i) {
            $DISK         = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-drvr/i) {
            $DRIVER       = "TRUE";
            $do_something = "TRUE";
        } elsif ($arg =~ /-fan/i) {
            $FAN          = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-fru/i) {
            $FRU          = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-fw/i) {
            $FW           = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-log/i) {
            $LOGGING      = TRUE;
        } elsif ($arg =~ /-mail/i) {
            $MAIL      = TRUE;
            $MAIL_RECIPIENT = shift @ARGV;
        } elsif ($arg =~ /-mf/i) {
            $CONFIGURATION_MATRIX = shift @ARGV;
        } elsif ($arg =~ /-min/i) {
            $MIN          = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-pc/i) {
            $PC           = TRUE;
            $do_something = "TRUE";
        } elsif ($arg =~ /-ps/i) {
            $PS           = TRUE;
            $do_something = "TRUE";
        } elsif ($arg eq "-v") {
            $VERBOSE      = TRUE;
        } elsif ($arg eq "-X") {
            $DEBUG = TRUE;
        } elsif ($arg =~ /-vrts/i) {
            $VRTS = "TRUE";
            $do_something = "TRUE";
        } elsif ($arg =~ /-dots/i) {
            $SHOWDOTS     = "TRUE";
        } elsif ($arg =~ /-warn/i) {
            $WARN         = TRUE;
        } else {
            die @usage; 
        }

        # Get the next value. 
        # The last command in the loop
        $arg = shift @ARGV;
    }
        
    # Check to see that the configuration matrix exists
    if ( ! -e $CONFIGURATION_MATRIX) {
        print "The configuration matrix does not exist : ";
        print "$CONFIGURATION_MATRIX\n";
        die   "@usage";
    }
    if ( ! -r $CONFIGURATION_MATRIX) {
        print "The configuration matrix is not readable : ";
        print "$CONFIGURATION_MATRIX\n";
        die   "@usage";
    }
    if ( ! -f $CONFIGURATION_MATRIX) {
        print "The configuration matrix is not a regular file : ";
        print "$CONFIGURATION_MATRIX\n";
        die   "@usage";
    }
    if ( ! -T $CONFIGURATION_MATRIX) {
        print "The configuration matrix is not a text file : ";
        print "$CONFIGURATION_MATRIX\n";
        die   "@usage";
    }

    # Did we set a required flag?
    if ($do_something eq "FALSE") {
        print "No required parameters specified!\n";
        die @usage;
    }

    # The mail recipient must be specified if we want to send mail
    die @usage if ($MAIL and (! $MAIL_RECIPIENT));

    # If the environment variable VERBOSE is set enable verbose mode
    $VERBOSE = TRUE if ($ENV{'VERBOSE'});       

}

sub luxadm_probe {

    print "\tluxadm probe\n" if ($VERBOSE);
 
    # Probe once and store in a global variable
    print STDERR "." if $SHOWDOTS;
    $last_command = "/usr/sbin/luxadm probe";
    $LUXADM_PROBE = `/usr/bin/ksh -c '/usr/sbin/luxadm probe 2>&1'` if ($LUXADM_PROBE eq "");
    if (($LUXADM_PROBE =~ /No Network Array enclosures found/ ) && ($A5000_PRESENT)){
        print "\n\t${LUXADM_PROBE}\n" if ($VERBOSE);
    } elsif ($LUXADM_PROBE =~ /Error:/) {
        open(LUXADM_PROBE_ERROR, ">/tmp/luxadm_probe");
        print LUXADM_PROBE_ERROR $LUXADM_PROBE;
        close(LUXADM_PROBE_ERROR);
        # This covers : I/O errors : Connection timeout Errors : ...
        print "\t",$LUXADM_PROBE;
        &ss_logger("ERROR", 4019, "luxadm parse error : luxadm probe");
        print "luxadm probe must complete without errors to complete the patch/firmware/FRU check.\n";
        print "The usual cause of this problem is a heavy workload causing\n";
        print "loop problems associated with the loop initialization primitive (LIP). \n";
        print "Output : /tmp/luxadm_probe\n";
        print "Please resolve this issue and run the program again.\n";
    }
    $last_command = "";
}

sub luxadm_display_enclosures {
    
    # Get all the data for all the enclosures. In the interest of saving
    # time, issue a single command rather than individual commands for
    # each enclosure.

    my $key;
    my $enclosure_count = 0;
    my $enclosure_names = "";

    # sort the enclosure names so we can index into them with keyptr
    foreach $key (sort (keys %ENCLOSURE)) {
        next if ($ENCLOSURE{$key} =~ /^\s*$/);
        push(@enclosure_names_array, $key);
    }

    $enclosure_count = $#enclosure_names_array;      

    $enclosure_count++;

    if ($enclosure_count > 1) {
        print "\tluxadm display on $enclosure_count enclosures\n" if $VERBOSE;
    } else {
        print "\tluxadm display on $enclosure_count enclosure\n" if $VERBOSE;
    }

    my $count     = 0;
    my $curkey    = "";
    my $enclosure;
    my $keyptr    = 0;

    foreach $enclosure (@enclosure_names_array) {

        $count++;
        $enclosure_names .= " $enclosure";

        # Create a list of names
        if ($count < $MAX_DISPLAY) {
            next;

        # If the count is equal to the MAX_DISPLAY run luxadm
        } else {
            $count   = 0;
            print "/usr/sbin/luxadm display $enclosure_names \n" if ($DEBUG);
            $lux_out = `/usr/bin/ksh -c '/usr/sbin/luxadm display $enclosure_names 2>&1'`;
            @lux_out = split(/\n/, $lux_out);
            foreach $curline (@lux_out) {
                if ($curline =~ /^\s+SENA\s*$/) {
                    # Trigger to capture output for next enclosure
                    $curkey = $enclosure_names_array[$keyptr];
                    print STDERR "." if $SHOWDOTS;
                    $keyptr += 1;
                }
                if ($curkey) {
                    $LUXADM_DISPLAY{$curkey} .= "$curline\n";
                }
            }
            $enclosure_names   = "";
            print STDERR "\n" if $SHOWDOTS;
        }
    }

    # pick up the tail of the list
    if ($count > 0) {
        $lux_out = `/usr/bin/ksh -c '/usr/sbin/luxadm display $enclosure_names 2>&1'`;
        @lux_out = split(/\n/, $lux_out);
        foreach $curline (@lux_out) {
            if ($curline =~ /^\s+SENA\s*$/) {
                # Trigger to capture output for next enclosure
                $curkey = $enclosure_names_array[$keyptr];
                print STDERR "." if $SHOWDOTS;
                $keyptr += 1;
            }
            if ($curkey) {
                $LUXADM_DISPLAY{$curkey} .= "$curline\n";
            }
        }
        print STDERR "\n" if $SHOWDOTS;
    } 

    # Check that luxadm probe completed
    my $luxadm_display_parse_error = "";
    foreach $key (keys %LUXADM_DISPLAY) {
        if ($LUXADM_DISPLAY{$key} !~ /Language/) {
            &ss_logger("ERROR", 4019, "luxadm parse error : luxadm display $key");
            $luxadm_display_parse_error = "TRUE";
        }
    }
    if ($luxadm_display_parse_error) {
        print "${PROGNAME} has dependencies on luxadm display output!\n";
        die "Please resolve the luxadm display issues before running the program again.\n";
    }

    # Debug code added to check data structure integrity
    my $check_count = 0; 
    foreach $key (keys %LUXADM_DISPLAY) {
        $check_count++;
        print "########## Enclosure name = ${key} #############\n" if ($DEBUG);
        print "$LUXADM_DISPLAY{$key}"                              if ($DEBUG);
    }
    if ($check_count != $enclosure_count) {
        print "Corrupt data structures!\n";
        print "check_count     = $check_count\n";
        print "enclosure_count = $enclosure_count\n";
        die   "Data structure Error!\n"; 
    }
}

sub check_power_supplies {

    my (@luxadm, $line, $luxadm_display, $index, $enclosure, $status);
    my ($ps_status) = "PASS";

    print "\nPOWER SUPPLIES :\n" if ($VERBOSE);

    foreach $enclosure (keys (%LUXADM_DISPLAY)) {

        $PS_COUNT = 0;
        $power_supply_string_found = "";

        print "\n\tChecking enclosure $enclosure\n" if ($VERBOSE);

        @luxadm = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $index (0 .. $#luxadm ) {

            if ($line =~ /Error: Invalid path name/) {
                print "Inconsistency detected between luxadm probe and display\n";
                print "for enclosure $key\n";
                last;
            } 

            next if ($luxadm[$index] !~ /^Power Supplies/);
            $power_supply_string_found = "TRUE";

            if ($VERBOSE) {
                print "\t$luxadm[$index]\n";
                print "\t$luxadm[$index+1]\n";
            }
            ($ps0_stat, $ps1_stat, $ps2_stat) = $luxadm[$index+1] =~
                        /\s+0\s+(.+)\s+1\s+(.+)\s+2\s+(.+)/;
        
            $status = &check_ps_stat(0, $ps0_stat, $enclosure);

            $status = &check_ps_stat(1, $ps1_stat, $enclosure);

            $status = &check_ps_stat(2, $ps2_stat, $enclosure);

            last;
        }
        if (! $power_supply_string_found) {
            print "Program error: power supply information not found while\n";
            print "parsing the luxadm display $enclosure output.\n";
            &ss_logger("ERROR", 4019, "luxadm parse error");
        } else {
            print "\tCount of O.K. power supplies = $PS_COUNT\n" if $VERBOSE;
            if ($PS_COUNT >= 2) {
                print "\tPASS\n" if ($VERBOSE);
            } elsif ($PS_COUNT == 1) {
                &ss_logger("ERROR", 4008, "Need two or more power supplies");
                print "\tFAIL\n" if ($VERBOSE);
                $ps_status = "FAIL";
            }
        }
    }
    print "\n\t$ps_status\n" if ($VERBOSE);
}

sub check_ps_stat {

    my ($ps_num, $ps_stat, $enclosure) = @_;

    my $status = "PASS";

    print "\tPower supply ${ps_num} status = $ps_stat\n"
        if ($VERBOSE);

    if ($ps_stat =~ /^O\.K\./) {
        $PS_COUNT++;
    } else {
        &ss_logger("WARNING", 3000, "Enclosure $enclosure : Power Supply $ps_num $ps_stat");
        $status = "FAIL";
    }
    return $status;
}

sub check_fans {

    my (@luxadm, $line, $luxadm_display, $index, $enclosure, $status);
    my ($check_fan_result) = "PASS";

    print "\nFAN CHECK : \n" if ($VERBOSE);

    foreach $enclosure (keys (%LUXADM_DISPLAY)) {

        $FAN_COUNT = 0;

        print "\n\tChecking enclosure $enclosure\n" if ($VERBOSE);

        @luxadm = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $index (0 .. $#luxadm ) {

            if ($luxadm[$index] =~ /Error: Invalid path name/) {
                print "Inconsistency detected between luxadm probe and display\n";
                print "for enclosure $key\n";
                last;
            } 

            next if ($luxadm[$index] !~ /^Fans/);

            if ($VERBOSE) {
                print "\t$luxadm[$index]\n";
                print "\t$luxadm[$index+1]\n";
            }
            ($fan0_stat, $fan1_stat) = $luxadm[$index+1] =~
                        /^\s+0\s+(\S+)\s+1\s+(\S+)/;
        
            $status = &check_fan_stat(0, $fan0_stat, $enclosure, 
                           $luxadm[$index], $luxadm[$index+1]);

            $status = &check_fan_stat(1, $fan1_stat, $enclosure, 
                           $luxadm[$index], $luxadm[$index+1]);

            last;

        }
        print "\tCount of O.K. fans = $FAN_COUNT\n" if ($VERBOSE);
        if ($FAN_COUNT == 2) {
            print "\tPASS\n" if ($VERBOSE);
        } else {
            &ss_logger("ERROR", 4018, "Fan Failure in enclosure : $enclosure");
            print "\tFAIL\n" if ($VERBOSE);
            $check_fan_result = "FAIL";
        }
    }
    print "\n\t$check_fan_result\n" if ($VERBOSE);
}

sub check_fan_stat {

    my ($fan_num, $fan_stat, $enclosure, $line1, $line2) = @_;

    my $status = "PASS";

    print "\tFan ${fan_num} status = $fan_stat\n" if ($VERBOSE);

    if ($fan_stat =~ /^O\.K\./) {
        $FAN_COUNT++;
    } else {
        print "\t$line1\n"               if ($VERBOSE);
        print "\t$line2\n"               if ($VERBOSE);
        $status = "FAIL";
    }
    return $status;
}

sub patch_check {

    my $patch_error_flag = "PASS";
    my $result           = "PASS";

    print "\nPATCH CHECK: OS $RELEASE\n" if ($VERBOSE);

    foreach $patch (sort (keys %PATCH_MATRIX)) {
        $result = &check_patch($patch, $PATCH_MATRIX{$patch});
        if ($result eq "FAIL") {
            $patch_error_flag = "FAIL";
        }
    }
    if ($A5000_PRESENT) {
        foreach $patch (sort (keys %A5000_PATCH_MATRIX)) {
            $result = &check_patch($patch, $A5000_PATCH_MATRIX{$patch});
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
    }
    if ($T300_PRESENT) {
        foreach $patch (sort (keys %T300_PATCH_MATRIX)) {
            $result = &check_patch($patch, $T300_PATCH_MATRIX{$patch});
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
    }
    print "\n\t$patch_error_flag\n" if ($VERBOSE);
}

sub inquiry {

    # Set the global array INQUIRY
    # so we only have to do this once
    $last_command = "${BINDIR}/disk_inquiry";
    $inquiry = `/usr/bin/ksh -c '${BINDIR}/disk_inquiry 2>&1'`;
    $last_command = "";
    @INQUIRY = split(/\n/, $inquiry);
}

sub check_disk_firmware {

    my $line;
    # This variable is used to print any messages related
    # to an error associated with the SEAGATE drives
    my $disk_status = "PASS";
    my $drive_found = "FALSE";  

    print "\nDISK FW CHECK : \n" if $VERBOSE;

    my ($label_id, $vendor, $disk_fw, %message, $encl_type);
    foreach $line (@INQUIRY) {
        $label_id = "";
        $vendor = "";
        $disk_fw = "";
        $encl_type = "";
        ($device) = $line =~ /\s(c\d+t\d+d\d+)\s/; # cntndn
        next if ($device eq "");


        @line = split //, $line; # separate characters
        foreach $x (22..37) {
            $label_id .= $line[$x];
        }
        $label_id =~ s/\s+$//;
        foreach $x (11..21) {
            $vendor .= $line[$x];
        }
        $vendor =~ s/\s+$//;

        foreach $x (39..43) {
            $disk_fw .= $line[$x];
        }
        $disk_fw =~ s/\s+$//;

        if (($vendor eq "SUN") and ($label_id =~ /^T3/)){
            $encl_type = "T3";
        } else {
            ##############################################
            # see if its an A5x000 drive by checking all #
            # enclosure's scsi targets to find a match   #
            ##############################################
            my $encl;
            foreach $encl (sort values %A5000_DRIVES) {
                my $temp_target;
                foreach $temp_target (split / /, $encl) {
                    if ($temp_target eq $device) {
                        $encl_type = "A5000";
                        last;
                    }
                }
            }
        }
        $cur_key = "$vendor" . "~" . "$label_id" . "~" . "$encl_type"; # this key is important 
        my $matrix_rev;
        $matrix_rev = $1 if ($PATCH_MATRIX_REVISION =~ /^([\d\.]+)\s+/);
        if (&version_ok("3.8", $matrix_rev)) {
            ############################################
            # our configuration matrix is at least 3.8 #
            # this means we have a new section called  #
            # DISK FW MATRIX, so use it now.           # 
            ############################################
            if (&compare_fw_levels($DISK_FW_MATRIX{$cur_key}, $disk_fw)) {
                ##################
                # fw level is ok #
                ##################
                print "\t$line\n" if $VERBOSE;
                if ($DISK_FW_MATRIX{$cur_key} eq "") {
                    print "\tdrive not in matrix, assuming it is ok\n" if $VERBOSE;
                } else {
                    $drive_found = "TRUE";
                }
            } else {
                ##########################
                # fw level is downrevved #
                ##########################
                $drive_found = "TRUE";
                $disk_status = "FAIL";
                $message{$cur_key} = "Patch $DISK_FW_PATCHES{$cur_key} will upgrade disk firmware for $vendor $label_id drives.\n";
                &ss_logger("ERROR", 4003, "Incorrect disk fw : $device : $label_id : has $disk_fw : needs $REQ_DISK_FW");
            }
        } else {
           &ss_logger("ERROR", 4003, "Incorrect config-matrix : storstat requires matrix 3.8 and higher to check disk firmware, apply patch 109090-05");
           last;
        }
        next;
    }
    if ($drive_found =~ /FALSE/) {
        $disk_status = "NORESULT";
        print "\tNo drives matched the patch matrix!\n" if $VERBOSE;
    }
    # Tell them which patch to install
    foreach $line (sort values %message) {
        print $line;
    }

    print "\n\t$disk_status\n" if $VERBOSE;
}       

sub check_ib_firmware {
    my $line;
    my $ib_status   = "PASS";
    my $expected    = "";
    my $message = "";

    print "\nIB FW CHECK : \n" if $VERBOSE;

    $ib_status       = "PASS";
    $found_display   = "FALSE";
    $ib_section      = "";      
    # foreach enclosure
    foreach $enclosure (sort (keys (%LUXADM_DISPLAY))) {
        $found_display  = "TRUE";
        undef @luxadm_display;
        @luxadm_display = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $line (@luxadm_display) {

            # Check to see if any IB boards are missing
            $ib_section = "TRUE" if ($line =~ /^ESI/);
            if ($ib_section eq "TRUE") {
                if ($line =~ /^\s+(\S): Not Installed/) {
                        &ss_logger("WARNING", 3021, "Enclosure $enclosure : Board $1 : IB Board missing : See FCO A0131-1"); 
                }
            }
            $ib_section = "FALSE" if ($line =~ /^DISK/);

            # Check IB firmware
            if ($line =~ /^[\w\s]+:\s*(\S+)\s*.*WWN/i) {

                $current_ib_fw = $1;

                if ($DISK_COUNT{$enclosure} eq "14") {
                    $expected = $IB_5000;
                    # If the spec has | we should use regular expressions
                    if ($IB_5000 =~ /\|/) {
                        if ($current_ib_fw !~ /$IB_5000/) {
                            $ib_status     = "FAIL";
                            &ss_logger("ERROR", 4004, "Enclosure $enclosure : Downrev IB FW : Expected $IB_5000 found $current_ib_fw\n  Please use luxadm to upgrade.");
                            $message{"A5000"} = "\tInstall patch $IB_5000_PATCH and follow the special instructions.\n";
                        }
                    } else {
                        #  Do a numeric comparison 
                        if ($current_ib_fw < $IB_5000) {
                            $ib_status     = "FAIL";
                            &ss_logger("ERROR", 4004, "Enclosure $enclosure : Downrev IB FW : Expected $IB_5000 found $current_ib_fw\n  Please use luxadm to upgrade.");
                            $message{"A5000"} = "\tInstall patch $IB_5000_PATCH and follow the special instructions.\n";
                        }
                    }

                } elsif ($DISK_COUNT{$enclosure} eq "22")  {
                    $expected = $IB_5200;
                    # If the spec has | we should use regular expressions
                    if ($IB_5200 =~ /\|/) {
                        if ($current_ib_fw !~ /$IB_5200/) {
                            $ib_status     = "FAIL";
                            &ss_logger("ERROR", 4004, "Enclosure $enclosure : Downrev IB FW : Expected $IB_5200 found $current_ib_fw\n  Please use luxadm to upgrade.");
                            $message{"A5200"} = "\tInstall patch $IB_5200_PATCH and follow the special instructions.\n";
                        }
                    } else {
                        #  Do a numeric comparison 
                        if ($current_ib_fw < $IB_5200) {
                            $ib_status     = "FAIL";
                            &ss_logger("ERROR", 4004, "Enclosure $enclosure : Downrev IB FW : Expected $IB_5200 found $current_ib_fw\n  Please use luxadm to upgrade.");
                            $message{"A5200"} = "\tInstall patch $IB_5200_PATCH and follow the special instructions.\n";
                        }
                    }
                } else {
                   print "Failed to determine the correct disk count for the enclosure $enclosure\n";
                   print "disk count = $DISK_COUNT{$enclosure}\n";
                }
                    
                if ($ib_status eq "FAIL") {
                    &ss_logger("WARNING", 1001, "$line");
                }
            }
        }
        print "\tEnclosure $enclosure : expected $expected : found $current_ib_fw\n" if ($VERBOSE);
    }
    if ($found_display eq "FALSE") {
        print "No A5K Series enclosures detected!\n";
        $ERROR_FLAG = "NORESULT";
        print "Please verify correct hardware connectivity, functionality and\n";
        print "minimum software/firmware requirements. You may need to attach\n";
        print "a SCI system to use luxadm if the IB firmware is downreved.\n";

    }   
    if ($VERBOSE and ($ib_status eq "FAIL")) {
        foreach $line (sort values %message) {
            print $line;
        }
    }
    print "\n\t$ib_status\n" if ($VERBOSE);
}      

sub version_ok {

    my ($spec, $installed) = @_;

    die "version_ok :  no specified value!\n" if (! $spec);
    die "version_ok :  no installed value!\n" if (! $installed);

    # In perl 0 or the null string are considered false
    # anything else is TRUE. Initialize this variable
    # as false so this function can be called from an
    # if statement.

    my $return_value = 0;

    my ($spec_major_num, $spec_minor_num, $spec_increment) = $spec      
        =~ /(\d+)\D*(\d*)\D*(\d*)/;
    my ($installed_major_num, $installed_minor_num, $installed_increment) 
        = $installed =~ /(\d+)\D*(\d*)\D*(\d*)/;

    # Assign some default values if the minor number
    # or increment didn't match in the regular expression
    $spec_minor_num = 0 if (! $spec_minor_num);
    $spec_increment = 0 if (! $spec_increment);
    $installed_minor_num = 0 if (! $installed_minor_num);
    $installed_increment = 0 if (! $installed_increment);

#    print "spec = $spec =  $spec_major_num, $spec_minor_num, $spec_increment\n";
#    print "installed = $installed = $installed_major_num, $installed_minor_num, $installed_increment\n";
       
    if ($installed_major_num > $spec_major_num) {
        return 1;
    } elsif ($installed_major_num < $spec_major_num) {
        return 0;
    } else { # Check minor number
        if ($installed_minor_num > $spec_minor_num) {
            return 1;
        } elsif ($installed_minor_num < $spec_minor_num) {
            return 0;
        } else { #Check increment
            if ($installed_increment < $spec_increment) {
                return 0;
            } else {
                return 1;
            }
        }
    }
    print "${PROGNAME} : Logic error in version_ok subroutine!\n";
}

sub get_configuration_matrix {

    # We are using our own version of this matrix and this
    # routine must be updated every time the matrix gets upgraded.

    # We have a request from engineering to maintain support
    # for previous versions of the patch matrix. They are
    # interested in a tool that will tell them what level the
    # system is currently passing and what they need to do to
    # get the system at the current patch level. That functionality
    # is best implemented by writing another program which takes
    # advantage of the command line option for the patch matrix
    # available in storstat.

    # Parse the configuration matrix supplied with the package.

    my ($parse_patches)  = "";
    my ($parse_firmware, $parse_fw_matrix, $parse_hba_fcode) = "";
    my ($parse_ib_fw, $parse_new_sds)               = "";
    my ($parse_new_sevm, $patch, $patch_description)              = "";
    my ($parse_t300_fw)                                           = "";
    my ($f1, $f2, $f3)                                            = "";

    print "\tData File : $CONFIGURATION_MATRIX\n" if $VERBOSE;
    open(MATRIX, "$CONFIGURATION_MATRIX") or die "Unable to open : $CONFIGURATION_MATRIX\n";
    while ($line=<MATRIX>) {

        # Skip blank lines
        next if ($line =~ /^\s*$/);

        # Clear flags if we reach an end of data section 
        if ($line =~ /========================/) {
           $parse_patches               = "FALSE";
           $parse_fw_matrix             = "FALSE";
           $parse_hba_fcode             = "FALSE";
           $parse_ib_fw                 = "FALSE";
           $parse_t300_fw               = "FALSE";
           $parse_new_sds               = "FALSE";
           $parse_new_sevm              = "FALSE";
           next;
        }

        # Look for the patch matrix rev embedded in the comments
        if ($line =~ /REV\s+(.*)$/) {
            $PATCH_MATRIX_REVISION = $1;
            next;
        }
        # Skip Comments
        next if ($line =~ /^\s*#/);

        # Set flags if we detect a new section identifier

        # first section sets flags for parsing data
        if ($line =~ /^PATCH_LIST/) {
           $parse_patches = "TRUE";
           next;
        }
        if ($line =~ /^SEVM:/) {
           $parse_new_sevm = "TRUE"; 
           next;
        }
        if ($line =~ /^DISK FW MATRIX/) {
           $parse_fw_matrix  = "TRUE";
           next;
        }
        if ($line =~ /^HBA FCODE/) {
           $parse_hba_fcode  = "TRUE";
           next;
        }
        if ($line =~ /^IB FIRMWARE/) {
           $parse_ib_fw  = "TRUE";
           next;
        }
        if ($line =~ /^SDS:/) {
           $parse_new_sds = "TRUE";
           next;
        }
        if ($line =~ /^T300 FIRMWARE:/) {
           $parse_t300_fw = "TRUE";
           next;
        }

        # this section extracts data based on the flags

        if ($parse_patches eq "TRUE") {

           my ($temp_os, $patch, $storage, $storage2, $storage3, $patch_description) = split /:/, $line;
           $temp_os      =~ s/\s+//g;
           $patch   =~ s/^\s+(.*)\s+$/$1/;
           $storage =~ s/^\s+(.*)\s+$/$1/;
           $storage =~ s/\s+$//;
           ($storage, $storage2, $storage3) = split /,/, $storage;
           $patch_description =~ s/^\s+(.*)\s+$/$1/;

            if ($temp_os eq $RELEASE) {

                # Found a patch, we can do a patch check
                $PATCH_CHECK_REQUIRED{$temp_os} = "TRUE";

                # Some of the patches are platform specific
                if ($patch =~ /103346/) { 
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX{$patch} = $patch_description if ($storage eq "");
                        $T300_PATCH_MATRIX{$patch} = $patch_description if ($storage eq "T300");
                        $A5000_PATCH_MATRIX{$patch} = $patch_description if ($storage eq "A5000");
                    }
                # 105298 has been obsoleted by 105029
                } elsif ($patch =~ /105298|105029/) {
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise-10000") {
                        $last_command = "/bin/pkginfo SUNWapr 2> /dev/null | /bin/wc -l";
                        $aprcount = `/usr/bin/ksh -c '/bin/pkginfo SUNWapr 2> /dev/null' | /bin/wc -l`;
                        $last_command = "/bin/pkginfo SUNWapu 2> /dev/null | /bin/wc -l";
                        $apucount = `/usr/bin/ksh -c '/bin/pkginfo SUNWapu 2> /dev/null' | /bin/wc -l`; 
                        $last_command = "";
                        if (($aprcount !~ /0/) and ($apucount !~ /0/)) {
                            $PATCH_MATRIX{$patch} = $patch_description if ($storage eq "");
                            $T300_PATCH_MATRIX{$patch} = $patch_description if ($storage eq "T300");
                            $A5000_PATCH_MATRIX{$patch} = $patch_description if ($storage eq "A5000");
                        }
                    }
                } else {
                    if (($storage eq "!SR") or ($storage2 eq "!SR") or ($storage3 eq "!SR")) {
                        ###############################################
                        # This patch does not show up with showrev -p #
                        # must be firmware.                           #
                        ###############################################
                    } elsif (($storage eq "SOCAL") and ($SOCAL_PRESENT)) {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    } elsif (($storage eq "USOC") and ($USOC_PRESENT)) {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    } elsif (($storage =~ /PCI/i) and (($IFP_PRESENT) or ($QLC_PRESENT))) {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    } elsif (($storage =~ /IFP/i) and ($IFP_PRESENT)) {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    } elsif (($storage eq "QLC") and ($QLC_PRESENT)) {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    } elsif ($storage eq "T300") {
                        $T300_PATCH_MATRIX{$patch} = $patch_description;
                    } elsif ($storage eq "A5000") {
                        $A5000_PATCH_MATRIX{$patch} = $patch_description;
                    } elsif ($storage eq "") {
                        $PATCH_MATRIX{$patch} = $patch_description;
                    }
                }
            }

        } elsif ($parse_fw_matrix eq "TRUE") {
            #################################################################
            # all disks are now in a matrix                                 #
            # Manufacturer      ID       lvl    Enclosure   limit   patches #
            #################################################################
            my ($manuf, $prod_id, $prod_level, $encl_type, $limit, $patch_list);
            ($manuf, $prod_id, $prod_level, $encl_type, $limit, $patch_list) = split /:\s+/, $line;
            $manuf =~ s/^\s+//;
            $manuf =~ s/\s+$//;
            $prod_id =~ s/^\s+//;
            $prod_id =~ s/\s+$//;
            $prod_level =~ s/^\s+//;
            $prod_level =~ s/\s+$//;
            $encl_type =~ s/^\s+//;
            $encl_type =~ s/\s+$//;
            $limit =~ s/^\s+//;
            $limit =~ s/\s+$//;
            $patch_list =~ s/^\s+//;
            $patch_list =~ s/(.*)#.*$/$1/; # strip comments
            $patch_list =~ s/\s+$//;
            $cur_id = "$manuf" . "~" . "$prod_id" . "~" . "$encl_type";
            $DISK_FW_MATRIX{$cur_id} = "$prod_level";
            $DISK_FW_INFO{$index++} = $line;
            $DISK_FW_PATCHES{$cur_id} = "$patch_list";
            next;

        } elsif ($parse_hba_fcode eq "TRUE") {
            #################################################################
            # all FC hba's are now in a matrix                              #
            # OS | Name | Type | Version | Register | Revision | Patch List #
            #################################################################
            my ($sun_os, $hba_name, $version, $reg, $fw_rev, $patch_list);
            ($sun_os, $hba_name, $version, $reg, $fw_rev, $patch_list) = split /\|/, $line;
            
            $sun_os =~ s/^\s+//;
            $sun_os =~ s/\s+$//;
            next if ($sun_os ne $RELEASE);

            $hba_name =~ s/^\s+//;
            $hba_name =~ s/\s+$//;
            $version =~ s/^\s+//;
            $version =~ s/\s+$//;
            $reg =~ s/^\s+//;
            $reg =~ s/\s+$//;
            $fw_rev =~ s/^\s+//;
            $fw_rev =~ s/\s+$//;
            $patch_list =~ s/^\s+//;
            $patch_list =~ s/\s+$//;
            $HBA_FCODE{$hba_name} = $line;

        } elsif ($parse_new_sevm eq "TRUE") {
           # Veritas changed in Solaris 2.7 to pkg VRTSvxvm
           my ($temp_os, $temp_ver, $temp_patch, $temp_comment) = split /:/, $line;
           $temp_os =~ s/\s+//g;
           $temp_ver =~ s/\s+//g;
           $temp_patch =~ s/^\s+(.*)\s+$/$1/;
           $temp_comment =~ s/^\s+(.*)\s+$/$1/;
           # if $temp_patch is null then Veritas is supported but no patch required
           if ($RELEASE eq $temp_os) {
               if ($VERITAS_VERSION =~ /^$temp_ver/) {
                   $VERITAS_VERSION_IS_SUPPORTED = 1;  # approved Veritas/Solaris combination
                   $NEW_VERITAS{$temp_patch} = $temp_comment if $temp_patch;
               }
               #################################################################
               # keep track of highest Veritas release supported in current OS #
               # according to matrix. Old matrix files could be out of date if #
               # this system has a newer version.                              #
               #################################################################
               $REL_VERITAS_DIGIT1 = "";
               $REL_VERITAS_DIGIT2 = "";
               $REL_VERITAS_DIGIT3 = "";
               ($REL_VERITAS_DIGIT1, $REL_VERITAS_DIGIT2, $REL_VERITAS_DIGIT3) = split /\./, $temp_ver;
           }

        } elsif ($parse_new_sds eq "TRUE") {
           my ($temp_os, $temp_sds, $temp_patch, $temp_comment) = split /:/, $line;
           $temp_os =~ s/\s+//g;
           $temp_sds =~ s/\s+//g;
           $temp_patch =~ s/^\s+(.*)\s+$/$1/;
           $temp_comment =~ s/^\s+(.*)\s+$/$1/;
           # if $temp_patch is null then SDS Release is supported but no patch required
           if (($RELEASE eq $temp_os) and ($SDS_VERSION =~ /^$temp_sds/)) {
               $SDS_VERSION_IS_SUPPORTED = 1;  # approved SDS/Solaris combination
               $NEW_SDS{$temp_patch} = $temp_comment if $temp_patch;
           }
        } elsif ($parse_ib_fw eq "TRUE") {
            # OS     Enclosure   IB firmware    patch
            my ($temp_os, $temp_enclosure, $temp_ib_fw, $temp_patch) = split /:/, $line;
            $temp_os =~ s/\s+//g;
            next if (!(RELEASE eq $temp_os));

            $temp_enclosure =~ s/\s+//g;
            $temp_ib_fw =~ s/\s+//g;
            $temp_patch =~ s/^\s+//;
            $temp_patch =~ s/\s+$//;
            # 14 drive A5K Series
            if ($temp_enclosure =~ /5000/) {
                $IB_5000 = $temp_ib_fw;
                $IB_5000_PATCH = $temp_patch
            # 22 drive A5200
            } elsif ($temp_enclosure =~ /5200/) {
                $IB_5200 = $temp_ib_fw;
                $IB_5200_PATCH = $temp_patch
            }   
        } elsif (($parse_t300_fw eq "TRUE") && ($T300_PRESENT)) {
            # T300 
            $T300FWPATCH = "not available that";
            if ($line =~ /T300FW\s+(\S+)\s+(\S+)/) {
                $T300FWREV = $1;
                $T300FWPATCH = $2;
            }   

        }
    }

    die "The patch matrix doesn't contain the expected version string! (i.e # REV 3.8)\n"
        if (! $PATCH_MATRIX_REVISION);

    # Print the patch requirements

    # If we haven't parsed any data there is nothing to print (4186926)
    return if (! $PATCH_CHECK_REQUIRED{$RELEASE});

    if ($VERBOSE) {
        print "\n\t$RELEASE Patch Matrix\n";
        foreach $key (sort (keys %PATCH_MATRIX)) {
            printf("\t%-14.14s : %-s\n", $key , $PATCH_MATRIX{$key});
        }
        foreach $key (sort (keys %A5000_PATCH_MATRIX)) {
            printf("\t%-14.14s : %-s\n", $key , $A5000_PATCH_MATRIX{$key}); 
        }
        foreach $key (sort (keys %T300_PATCH_MATRIX)) {
            printf("\t%-14.14s : %-s\n", $key , $T300_PATCH_MATRIX{$key}); 
        }
    }
    if ($VERITAS_VERSION) {
        print "\n\tVeritas $VERITAS_VERSION Patch Matrix:\n" if $VERBOSE;
        foreach $key (sort (keys %NEW_VERITAS)) {
            printf("\t%-14.14s : %-s\n", $key , $NEW_VERITAS{$key}) if $VERBOSE;
        }
    }
    if ($VERBOSE) {
        print "\n\tFirmware Requirements:\n";
        my ($sun_os, $hba_name, $hba_manufact, $hba_reg, $fw_version, $patch_list);
        foreach $hba_key (sort keys %HBA_FCODE) {
            ($sun_os, $hba_name, $hba_manufact, $hba_reg, $fw_version, $patch_list) = split /\|/, $HBA_FCODE{$hba_key};
            $fw_version =~ s/\s+//g;
            printf("\t%-20.20s : %-s\n", "HA $hba_name", $fw_version);
        }
    }

    if ($A5000_PRESENT) {
       printf("\t%-20.20s : %-s\n", "5000 IB FW",   $IB_5000)    if (($IB_5000)   and ($VERBOSE));
       printf("\t%-20.20s : %-s\n", "5200 IB FW",   $IB_5200)    if (($IB_5200)   and ($VERBOSE));
       if ($VERBOSE) {
           my ($manuf, $prod_id, $prod_level, $encl_type, $limit, $patch_list);
           foreach $cur_key (sort keys %DISK_FW_INFO) {
               ($manuf, $prod_id, $prod_level, $encl_type, $limit, $patch_list) = split /:\s+/, $DISK_FW_INFO{$cur_key};
               $manuf =~ s/^\s+//;
               $manuf =~ s/\s+$//;
               $prod_id =~ s/^\s+//;
               $prod_id =~ s/\s+$//;
               $prod_level =~ s/^\s+//;
               $prod_level =~ s/\s+$//;
               $encl_type =~ s/^\s+//;
               $encl_type =~ s/\s+$//;
               next if ($encl_type !~ /A5/);
               printf("\t%-20.20s : %-s\n", $prod_id, $prod_level);
           }
       }

    }

    if ($T300_PRESENT) {
       printf("\t%-20.20s : %-s\n", "T300 FW", "$T300FWREV")   if (($T300FWREV) and ($VERBOSE));
    }

    print "\n\tSolstice Disk Suite $SDS_VERSION Patch Matrix: $RELEASE\n" if $VERBOSE;

    foreach $key (sort (keys %NEW_SDS)) {
        printf("\t%-14.14s : %-s\n", $key , $NEW_SDS{$key}) if $VERBOSE;
    }
}


sub check_patch {

    # Checks the patch
    # Sets the FW flag which introduces the dependency
    # that the patch checks should be run before the
    # firmware checks.

    my ($patch, $patch_description) = @_;

    # default is FAIL. Only PASS if we find the patch
    my $patch_status = "FAIL";
 
    if ($patch =~ /-/) {
        ($base, $req_rev) = split (/-/, $patch);
    } else {
        $base = $patch;
        $req_rev = "";
    }

    print "\n\tChecking $patch : $patch_description\n" if ($VERBOSE);

    # Look at patches that show up with showrev
    $last_command = "/bin/showrev -p | awk '{print \$2}' | /bin/egrep -c $patch";
    chop($count =   `/bin/showrev -p | awk '{print \$2}' | /bin/egrep -c $patch`);
    $last_command = "";
    if ($count == 0) {
        $last_command =                    "/bin/showrev -p       | awk '{print \$2}' | /bin/egrep $base | /bin/sort ";
        $other_versions = `/usr/bin/ksh -c '/bin/showrev -p 2>&1' | awk '{print \$2}' | /bin/egrep $base | /bin/sort`;
        $last_command = "";
        (@patch_list) = split(/\n/, $other_versions);
        foreach $installed_patch (@patch_list) {
            print "\t\t$installed_patch installed\n" if $VERBOSE;
            ($installed_rev) = $installed_patch =~ /-(\d+)$/;
            if (! $req_rev ) {
                print "\t\tPatch revision not specified.\n" if $VERBOSE;
                $patch_status = "PASS";
            } elsif ($installed_rev == $req_rev) {
                print "\t\tCorrect revision installed.\n" if $VERBOSE;
                $patch_status = "PASS";
            } elsif ($installed_rev > $req_rev) {
                print "\t\tNewer revision installed.\n" if $VERBOSE;
                $patch_status = "PASS";
            } elsif ($installed_rev < $req_rev) {
                print "\t\tOlder revision installed.\n" if $VERBOSE;
                $patch_status = "FAIL";
            } else {
                print "\t\t$PROGNAME Logic Flaw in check_patch\n";
            }
        }
    } elsif ($count == 1) {
        print "\t\tInstalled\n" if $VERBOSE;
        $patch_status = "PASS";
    } else {
        print "\t\t$PROGNAME Logic Flaw in check_patch\n";
        print "\t\tdetected $count instances of $patch\n";
    }
    if ($patch_status eq "FAIL") {
        &ss_logger("ERROR", 4007, "$patch or newer not installed");
    }
    return $patch_status;
}

sub ss_logger {

    # Log messages using mail.crit for ERROR, mail.warn for WARN,
    # and mail.info for other.

    ($status, $error_number, $message) = @_;
    die "You must define an error_number!" if (! $error_number);
    die "You must define a status!"        if (! $status);
    die "You must define a message!"       if (! $message);

    die "Incorrect status : $status\n"     if ($status =~ /[^A-Z]/);
    die "Incorrect error number : $error_number\n"     
                                           if ($error_number =~ /\D/);

    # Define this global variable and send mail later
    if (($status eq "ERROR") and $MAIL) {
        $MAIL_FLAG  = "TRUE";
        print MAIL "[StorTools:DIAG:$status:$error_number] $message\n";
    }
    if (($status =~ /WARN/) and $WARN) {
        $MAIL_FLAG    = "TRUE";
        print MAIL "[StorTools:DIAG:$status:$error_number] $message\n";
    }
    $ERROR_FLAG = "FAIL" if ($status eq "ERROR");
    
    if ($status eq "ERROR") {
        print "<font color=red>$status : $message</font>\n";
    } elsif (($status =~ /WARNING/) and $WARN) {
        print "<font color=navy>$status : $message</font>\n";
    }
    if ($LOGGING) {
        if ($status eq "ERROR") {
            $exec_str = 
            "/usr/bin/logger -p mail.crit -t \"[StorTools:DIAG:$status:$error_number]\" \"$message\"";
        } elsif ($status eq "WARN") {
            $exec_str = 
            "/usr/bin/logger -p mail.warn -t \"[StorTools:DIAG:$status:$error_number]\" \"$message\"";
        } else {
            $exec_str = 
            "/usr/bin/logger -p mail.info -t \"[StorTools:DIAG:$status:$error_number]\" \"$message\"";
        }   
        my $return_value = `/usr/bin/ksh -c '$exec_str 2>&1'`;
    }
}

sub veritas_patch_check { 
    my ($patch);

    print "\nPATCH CHECK VERITAS : \n" if $VERBOSE;

    if ($VERITAS_VERSION) {

        my $patch_error_flag = "PASS";

        foreach $patch (sort (keys %NEW_VERITAS)) { 
            $result = &check_patch($patch, $NEW_VERITAS{$patch}); 
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
        if ($VERITAS_VERSION_IS_SUPPORTED == 0) {
            #########################################################
            # allow PASS if installed version is simply higher than #
            # what is defined in the matrix.                        #
            #########################################################
            if ($VERITAS_DIGIT1 > $REL_VERITAS_DIGIT1) {
                print "\tVeritas $VERITAS_VERSION higher than what config-matrix can check.\n" if $VERBOSE;
            } elsif (($VERITAS_DIGIT1 eq $REL_VERITAS_DIGIT1) and ($VERITAS_DIGIT2 > $REL_VERITAS_DIGIT2)) {
                print "\tVeritas $VERITAS_VERSION higher than what config-matrix can check.\n" if $VERBOSE;
            } elsif (($VERITAS_DIGIT1 eq $REL_VERITAS_DIGIT1) and ($VERITAS_DIGIT2 eq $REL_VERITAS_DIGIT2) and ($VERITAS_DIGIT3 > $REL_VERITAS_DIGIT3)) {
                print "\tVeritas $VERITAS_VERSION higher than what config-matrix can check.\n" if $VERBOSE;
            } else {
            ############################################################
            # Not approved combination of Veritas and Solaris          #
            ############################################################
                &ss_logger("ERROR", 4009, "Veritas $VERITAS_VERSION not supported with Solaris $RELEASE");
                $patch_error_flag = "FAIL";
            }
        }
        print "\t$patch_error_flag\n" if ($VERBOSE);
    } else {
        print "\n\tVeritas not installed\n" if (($VERBOSE) || ($VRTS));
    }
}

sub get_veritas_version {
    
    ###################################################################
    # Check the different names that Veritas could be installed under #
    # The newest name is VRTSvxvm. Then extract the version number.   #
    ###################################################################
    my ($line, $status);

    $VERITAS_VERSION = '';
    $last_command = "/bin/pkginfo -l SUNWvxvm";
    $pkginfo = `/usr/bin/ksh -c '/bin/pkginfo -l SUNWvxvm 2>&1'`;
    $last_command = "";

    # localized packages would be SUNWvxvmr
    if ($pkginfo =~ /^ERROR/) {
        $last_command = "/bin/pkginfo -l SUNWvxvmr";
        $pkginfo = `/usr/bin/ksh -c '/bin/pkginfo -l SUNWvxvmr 2>&1'`;
        $last_command = "";
    }
    # VRTSvxvm will be the new name
    if ($pkginfo =~ /^ERROR/) {
        $last_command = "/bin/pkginfo -l VRTSvxvm";
        $pkginfo = `/usr/bin/ksh -c '/bin/pkginfo -l VRTSvxvm 2>&1'`;
        $last_command = "";
    }
    # If it were localized VRTSvxvm would become VRTSvxvmr
    if ($pkginfo =~ /^ERROR/) {
        $last_command = "/bin/pkginfo -l VRTSvxvmr";
        $pkginfo = `/usr/bin/ksh -c '/bin/pkginfo -l VRTSvxvmr 2>&1'`;
        $last_command = "";
    }

    @pkginfo = split(/\n/, $pkginfo);
    foreach $line (@pkginfo) {
        if ($line =~ /Sun Cluster Volume Manager/) {
            ##############################################
            # Skip checks for Cluster version of Veritas #
            # bug 4302969                                #
            ##############################################
            last;
        }
        if ($line =~ /ERROR/) {
            last;
        } elsif ($line =~ /^\s+VERSION:\s+(\S+)/) {
            $VERITAS_VERSION = $1;
            $VERITAS_VERSION = $1 if ($VERITAS_VERSION =~ /(.*),(.*)/);
        } elsif ($line =~ /^\s+STATUS:\s+(.*)/) {
            $status = $1;
            if ($status !~ /completely installed/) {
                &ss_logger("ERROR", 4008, "SUNWvxvm not completely installed");
                last;
            }
        }
    }

    print "VERITAS_VERSION = $VERITAS_VERSION\n" if ($DEBUG);
    print "\n\tVeritas : $VERITAS_VERSION : $status\n" if $VERBOSE;
    if ($VERITAS_VERSION ne "") {
        #################################
        # separate digits for use later #
        #################################
        $VERITAS_DIGIT1 = "";
        $VERITAS_DIGIT2 = "";
        $VERITAS_DIGIT3 = "";
        ($VERITAS_DIGIT1, $VERITAS_DIGIT2, $VERITAS_DIGIT3) = split /\./, $VERITAS_VERSION;
    }
}

sub solstice_patch_check { 
    my ($line, $solstice_version, $status); 
    my ($patch);
    $last_command = "/bin/pkginfo -l SUNWmd";
    $pkginfo = `/usr/bin/ksh -c '/bin/pkginfo -l SUNWmd 2>&1'`; 
    $last_command = "";
    @pkginfo = split(/\n/, $pkginfo); 
    my $solstice_installed = "TRUE";

    print "\nPATCH CHECK SOLSTICE DISK SUITE :\n" if $VERBOSE;

    foreach $line (@pkginfo) {
        if ($line =~ /ERROR/) {
            $solstice_installed = "FALSE";
            last;
        } elsif ($line =~ /^\s+VERSION:\s+(\S+)/) {
            $solstice_version = $1;
        } elsif ($line =~ /^\s+STATUS:\s+(.*)/) {
            $status = $1;
            if ($status !~ /completely installed/) {
                &ss_logger("ERROR", 4010, "SUNWmd not completely installed");
                $solstice_installed = "FALSE";
                last;
            }
        }
    }

    # This section of the code is where we make the association between
    # versions of SDS and their supported OS
    if ($solstice_installed eq "TRUE") {

        print " $solstice_version : $status\n" if $VERBOSE;
        my $patch_error_flag = "PASS";
        if ($SDS_VERSION_IS_SUPPORTED) {
            foreach $patch (sort (keys %NEW_SDS)) { 
                $result = &check_patch($patch, $NEW_SDS{$patch}); 
                $patch_error_flag = "FAIL" if ($result eq "FAIL");
            }
        } else {
           &ss_logger("ERROR", 4011, "Solstice Disk Suite Version $SDS_VERSION not supported with Solaris $RELEASE");
        }
        print "\t$patch_error_flag\n" if $VERBOSE;
    } else {
        print "\n\tSDS not installed\n" if $VERBOSE;
    }
}

sub get_solstice_version {
    $last_command = "/bin/pkginfo SUNWmd 2>&1";
    chop($SDS_VERSION = `/usr/bin/ksh -c '/bin/pkginfo SUNWmd 2>&1'`);
    $last_command = "";
    if ($SDS_VERSION =~ /ERROR/) {
        $SDS_VERSION = "";
    } else {
        $last_command = "/bin/pkginfo -l SUNWmd 2> /dev/null | /bin/egrep VERSION | /bin/awk '{print \$2}'";
        chop($SDS_VERSION = 
            `/usr/bin/ksh -c '/bin/pkginfo -l SUNWmd 2> /dev/null' | /bin/egrep VERSION | /bin/awk '{print \$2}'`);
        $last_command = "";
    }
}

sub check_disk_status {

    print "\nCHECK DISK STATUS : " if $VERBOSE;

    my $overall_result = "PASS";

# These strings are changing under us. Reservation conflict can be
# "Rsrv cnflt" or "Reserve cnflt"

# Dexter guarantees the position won't change, but won't guarantee
# the format of the string.

# for luxadm rev 1.36
#SLOT   FRONT DISKS       (Node WWN)          REAR DISKS        (Node WWN)
#print "0000000000111111111122222222223333333333444444444455555555556666666666\n";
#print "0123456789012345678901234567890123456789012345678901234567890123456789\n";
#       4      On (Rsrv cnflt:B) 20000020370e06cf    On (O.K.)         20000020370df120

#                         (luxadm version: 1.29 98/03/18)
#                                   SENA            
#                                 DISK STATUS 
#SLOT   FRONT DISKS       (Node WWN)          REAR DISKS        (Node WWN)
# 4      On (Reserve cnflt)2000002037070c53    On (O.K.)         2000002037041397

    my $result;

    foreach $enclosure (keys (%LUXADM_DISPLAY)) {

        $result = "PASS";

        print "\n\tChecking enclosure $enclosure\n" if ($VERBOSE);
        @luxadm = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $index (0 .. $#luxadm ) {

            next if ($luxadm[$index] !~ /^[S0-9]/);
            next if ($luxadm[$index] =~ /Retrying/);

            print "\t$luxadm[$index]\n" if ($VERBOSE);

            if ($luxadm[$index] =~ /^SLOT/) {
                $p0_start = index($luxadm[$index],  "SLOT");
                $p1_start = index($luxadm[$index],  "FRONT DISKS");
                $p2_start = index($luxadm[$index],  "(Node WWN)");
                $p3_start = index($luxadm[$index],  "REAR DISKS");
                $p4_start = rindex($luxadm[$index], "(Node WWN)");

                $p0_length  = $p1_start - $p0_start;
                $p1_length  = $p2_start - $p1_start;
                $p2_length  = $p3_start - $p2_start;
                $p3_length  = $p4_start - $p3_start;
                $p4_length  = length ($luxadm[$index]) - $p4_start;
                next;
            }
        
            $slot = substr($luxadm[$index], $p0_start, $p0_length);

            # Front disk check
            $bp = "f";
            $fd_status = substr($luxadm[$index], $p1_start, $p1_length);
            $fd_wwn    = substr($luxadm[$index], $p2_start, $p2_length);
            if (! &disk_status_accepted($enclosure, $fd_wwn, $fd_status, $bp, $slot) ) {
                $result = "FAIL";
            }
            # Rear disk check
            $bp = "r";
            $rd_status = substr($luxadm[$index], $p3_start, $p3_length);
            $rd_wwn = substr($luxadm[$index], $p4_start, $p4_length);
            if (! &disk_status_accepted($enclosure, $rd_wwn, $rd_status, $bp, $slot) ) {
                $result = "FAIL";
            }
        }
        $overall_result = "FAIL" if ($result eq "FAIL");
        print "\t$result\n" if ($VERBOSE);
    }
    print "\n\t$overall_result\n" if ($VERBOSE);
}

sub disk_status_accepted {

    # Accept disks that are O.K. or Not Installed or Reservation Conflicts
    # or Bypassed or Loop not accessible

    # Warning for Bypassed disks

    my ($enclosure, $wwn, $status, $bp, $slot) = @_;
    $wwn =~ s/\s+/ /;   
    if ($status =~ /Bypassed/i) {
        &ss_logger("WARNING", 3019, "Disk Bypassed : $enclosure,$bp$slot: $status"); 
    }
    # Loop not acc = (Loop not accessible)
    if ($status =~ /O\.K\.|Not\s+Installed|R\S+\s+cnflt|Bypassed|Loop not acc/i) {
        return 1;
    } else {
        &ss_logger("ERROR", 4019, "Luxadm disk status error : $enclosure : $wwn : $status");
        return 0;
    }
}               

sub check_min_configuration {

    # According to Brian Yunker (A5K Series) the system works best
    # with the min drives installed in specified slots.
    # There may be scenerios where the disks could be balanced
    # in a more evenly distributed manner and the signal noise
    # is not compromised though it is easiest just to specify
    # that the min configuration slots be occupied always.
    # we KNOW this meets our min requirements.

    print "\nCHECK DISK PLACEMENT : " if $VERBOSE;

    my $result;

    my $min_config_result = "PASS";

    # for luxadm rev 1.36
    #SLOT   FRONT DISKS       (Node WWN)          REAR DISKS        (Node WWN)
    #print "0000000000111111111122222222223333333333444444444455555555556666666666\n";
    #print "0123456789012345678901234567890123456789012345678901234567890123456789\n";

    foreach $enclosure (keys (%LUXADM_DISPLAY)) {

        $result = "PASS";

        print "\n\tChecking enclosure $enclosure\n" if ($VERBOSE);

        @luxadm = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $index ( 0 .. $#luxadm ) {

            next if ($luxadm[$index] !~ /^[S0-9]/);
            print "\t$luxadm[$index]\n" if ($VERBOSE);

            if ($luxadm[$index] =~ /^SLOT/) {
                $p0_start = index($luxadm[$index],  "SLOT");
                $p1_start = index($luxadm[$index],  "FRONT DISKS");
                $p2_start = index($luxadm[$index],  "(Node WWN)");
                $p3_start = index($luxadm[$index],  "REAR DISKS");
                $p4_start = rindex($luxadm[$index], "(Node WWN)");

                $p0_length  = $p1_start - $p0_start;
                $p1_length  = $p2_start - $p1_start;
                $p2_length  = $p3_start - $p2_start;
                $p3_length  = $p4_start - $p3_start;
                $p4_length  = length ($luxadm[$index]) - $p4_start;
                next;
            }

            $slot = substr($luxadm[$index], $p0_start, $p0_length);

            $fd_status = substr($luxadm[$index], $p1_start, $p1_length);
            $fd_wwn    = substr($luxadm[$index], $p2_start, $p2_length);
            if ($fd_status =~ /Not Installed/) {
                $fd[$slot] = "0";
            } else {
                $fd[$slot] = "OK";
            }

            $rd_status = substr($luxadm[$index], $p3_start, $p3_length);
            $rd_wwn = substr($luxadm[$index], $p4_start, $p4_length);
            if ($rd_status =~ /Not Installed/) {
                $rd[$slot] = "0";
            } else {
                $rd[$slot] = "OK";
            }
        }

        # Bryan Yunker Spec
        # 22 drive back plane
        if ($DISK_COUNT{$enclosure} eq "22") {
            if (! $fd[0]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in front slot 0");
            }
            if (! $fd[5]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in front slot 5");
            }
            if (! $fd[10]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in front slot 10");
            }
            # Check the rear disk
            if (! $rd[10]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in rear slot 10");
            }
        # 14 drive backplane
        } elsif ($DISK_COUNT{$enclosure} eq "14") {
            # Check the front disk requirements for the 14 drive        
            if (! $fd[3]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in front slot 3");
            }
            if (! $fd[6]) {
                $result = "FAIL";
                &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in front slot 6");
            }
        }
        # These rear disk requirements are the same for 14 drive and 22 drive
        if (! $rd[0]) {
            $result = "FAIL";
            &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in rear slot 0");
        }
        if (! $rd[3]) {
            $result = "FAIL";
            &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in rear slot 3");
        }
        if (! $rd[6]) {
            $result = "FAIL";
            &ss_logger("ERROR", 4020, "Required Disk Placement : $enclosure : drive missing in rear slot 6");
        }
        $min_config_result = "FAIL" if ($result eq "FAIL");

        print "\t$result\n" if ($VERBOSE);
    }
    if ($VERBOSE and ($result eq "FAIL")) {
        print "\tRecommended Disk Placement:\n";
        print "\t\t22 drive :\n";
        print "\t\t\tFront:     0, 5, 10\n";
        print "\t\t\tRear :     0, 3, 6, 10\n";
        print "\t\t14 drive :\n";
        print "\t\t\tFront:     3, 6\n";
        print "\t\t\tRear :     0, 3, 6\n";
        print "\tSee Sun Microsystems FIN#I0400.\n";
    }
    print "\n\t$min_config_result\n" if ($VERBOSE);
}

sub count_disks {

    # Assign DISK_COUNT so we know how many disks
    # are in an enclosure

    my $last_slot;

    foreach $enclosure (keys (%LUXADM_DISPLAY)) {

        @luxadm = split(/\n/, $LUXADM_DISPLAY{$enclosure});
        foreach $index (0 .. $#luxadm) {
            next if ($luxadm[$index] !~ /^[0-9]/);
            ($last_slot) = $luxadm[$index] =~ /^(\d+)/;
        }

        if ($last_slot == 10) {
            $DISK_COUNT{$enclosure} = "22";
        # 14 drive backplane
        } elsif ($last_slot == 6) {
            $DISK_COUNT{$enclosure} = "14";
        }
        print "DISK_COUNT{$enclosure} = $DISK_COUNT{$enclosure}\n" if ($DEBUG);
    }
}

sub check_all_drivers {

    my ($count, %drivers);

    foreach $port (@LD_ALL_PORTS) {
        ##################################################
        # build a list of drivers we are currently using #
        # and perform a modinfo on them.                 #
        # make_port_list creates LD_ALL_PORTS            #
        ##################################################
        if ($port =~ /socal/) {
            $drivers{'socal'} = 1;
            $drivers{'sf'} = 1;
        } elsif ($port =~ /usoc/) {
            $drivers{'usoc'} = 1;
            $drivers{'fp'} = 1;
        } elsif ($port =~ /qlc/) {
            $drivers{'qlc'} = 1;
            $drivers{'fp'} = 1;
        } elsif ($port =~ /ifp/) {
            $drivers{'ifp'} = 1;
        }
    }

    if ($drivers{'ifp'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' ifp '`;
        &ss_logger("ERROR", 4022, "ifp driver not configured") if ($modinfo eq '');
    }

    if ($drivers{'socal'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' socal '`;
        &ss_logger("ERROR", 4022, "socal driver not configured") if ($modinfo eq '');
    }

    if ($drivers{'usoc'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' usoc '`;
        &ss_logger("ERROR", 4022, "usoc driver not configured") if ($modinfo eq '');
    }

    if ($drivers{'qlc'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' qlc '`;
        &ss_logger("ERROR", 4023, "qlc driver not configured") if ($modinfo eq '');
    }

    if ($drivers{'fp'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' fp '`;
        &ss_logger("ERROR", 4022, "fp driver not configured") if ($modinfo eq '');
    }

    if ($drivers{'sf'}) {
        $modinfo = `/usr/sbin/modinfo |  grep ' sf '`;
        &ss_logger("ERROR", 4022, "sf driver not configured") if ($modinfo eq '');
    }
}

sub check_t300_fw {

    my $line;
    my $t300_status = "PASS";
    my $t300_found = "FALSE";   

    print "\nT300 FW CHECK : \n" if $VERBOSE;

    foreach $line (@INQUIRY) {
        # I would like to split the whole line but the field seperator is not consistent
        # and there can be six or seven fields caused by blanks in the label
        ($current_rev) = $line =~ /\s+(\S+)\s+\S+\s*$/;
        ($device)      = $line =~ /\s(c\d+t\d+d\d+)\s/;
        # T300             
        if (($line =~ /SUN/) and ($line =~ /T300/)) {
            $t300_found = "TRUE";
            if ($current_rev < $T300FWREV) {
                # Set the local status
                $t300_status      = "FAIL";
                &ss_logger("ERROR", 4025, "Incorrect T300 fw : $device : T300 : has $current_rev : needs $T300FWREV, \n  Install Patch $T300FWPATCH will upgrade");
            } else {
                print "\t$line\n" if $VERBOSE;
            }
        }
    }
    if ($t300_found =~ /FALSE/) {
        $t300_status = "NORESULT";
        print "\tNo T300 units matched the patch matrix!\n" if $VERBOSE;
    }
    print "\n\t$t300_status\n" if $VERBOSE;
}






sub check_hba_fcode {
    ######################################
    # scan  through prtconf, looking for #
    # all known FC host bus adaptors     #
    # uses: $HBA_FCODE{$hba_name} hash   #
    ######################################

    my ($manufacturer, $version, %message);
    my $fcode_result = "PASS";

    # Assumes fcode defined in the matrix 
    my $count        = 0;
    print "\nCHECK FOR ALL FC100 HBA FCODE VERSIONS\n" if ($VERBOSE);
    $prtconf_output  = `/usr/bin/ksh -c '/usr/sbin/prtconf -pv 2>&1' | /bin/egrep "reg:|manufacturer:|version:"`;
    #############################################################################
    # scan output for each type of hba card found in matrix for this OS release #
    # locate lines around 'manufacturer', they could be before or after...      #
    #############################################################################
    my ($sun_os, $hba_name, $hba_manufact, $hba_reg, $fw_version, $patch_list);
    foreach $hba_line (sort values %HBA_FCODE) {
        ($sun_os, $hba_name, $hba_manufact, $hba_reg, $fw_version, $patch_list) = split /\|/, $hba_line;
        $hba_name =~ s/^\s+//;
        $hba_name =~ s/\s+$//;
        $hba_manufact =~ s/^\s+//;
        $hba_manufact =~ s/\s+$//;
        $hba_reg =~ s/^\s+//;
        $hba_reg =~ s/\s+$//;
        $fw_version =~ s/^\s+//;
        $fw_version =~ s/\s+$//;
        $patch_list =~ s/^\s+//;
        $patch_list =~ s/\s+$//;

        @prtconf_output  = split(/\n/, $prtconf_output);
        my $i;
        for ($i=0; $i<=$#prtconf_output; $i++) {
            if ($prtconf_output[$i] =~ /^\s+manufacturer:(.*)$/) {
                $manufacturer = $1;
                chomp $manufacturer;
                if ($prtconf_output[$i+1] =~ /^\s+version:(.*)$/) {
                    ###########################################
                    # version is next line after manufacturer #
                    ###########################################
                    $version = $1;
                    chomp $version;
                    if ($version =~ /${hba_manufact}(.*)/) {
                        ###################################
                        # found card, extract fw revision #
                        ###################################
                        $current_fcode = $1;
                        $current_fcode =~ s/^\s+//; # strip leading white space
                        $current_fcode =~ s/\s+$//; # strip trailing white space
                        $current_fcode = $1 if ($current_fcode =~ /^(\S+)/);
                    } else {
                        next;
                    }
                    if ($prtconf_output[$i-1] =~ /^\s+reg:\s+(.*)$/) {
                        ########################
                        # reg is one line back #
                        ########################
                        $register = $1;
                        chomp $register;
                    } else {
                        $register = "";
                    }
                } elsif ($prtconf_output[$i-1] =~ /^\s+version:(.*)$/) {
                    ################################################
                    # version is previous line before manufacturer #
                    ################################################
                    $version = $1;
                    chomp $version;
                    if ($version =~ /$hba_manufact(.*)/) {
                        ###################################
                        # found card, extract fw revision #
                        ###################################
                        $current_fcode = $1;
                        $current_fcode =~ s/^\s+//; # strip leading white space
                        $current_fcode =~ s/\s+$//; # strip trailing white space
                        $current_fcode = $1 if ($current_fcode =~ /^(\S+)/);
                    } else {
                        next;
                    }
                    if ($prtconf_output[$i-2] =~ /^\s+reg:\s+(.*)$/) {
                        ##############################
                        # reg line is two lines back #
                        ##############################
                        $register = $1;
                        chomp $register;
                    } else {
                        $register = "";
                    }
                }
                if ($hba_reg ne "") {
                    ####################################
                    # compare register to furthur help #
                    # differentiate between hba cards  #
                    ####################################
                    my $temp_hba_reg = $hba_reg;
                    if ($temp_hba_reg =~ /^!/) {
                        #################
                        # not operation #
                        # skip if match #
                        #################
                        $temp_hba_reg =~ s/!//;
                        next if ($register =~ /^$temp_hba_reg/);
                    } else {
                        ###################
                        # match operation #
                        # skip if differ  #
                        ###################
                        next if ($register !~ /^$temp_hba_reg/);
                    }
                }
                if ($VERBOSE) {
                    print " $hba_name: $version\n";
                   #print " Register: $register\n";
                }
                if (! &version_ok($fw_version, $current_fcode)) {
                    &ss_logger("ERROR", 4006, "$hba_name fcode downreved : expected $fw_version and found $current_fcode");
                    $message{$hba_name} = "\tInstall patch $patch_list and follow the special instructions in the README.\n";
                    $fcode_result = "FAIL";
                    if (!($VERBOSE)) {
                       print " Version String found: $version\n";
                    }

                }
            }
        }
    }
    print "\t$fcode_result\n" if ($VERBOSE);
    if ($fcode_result =~ /FAIL|NORESULT/) {
        foreach $line (sort keys %message) {
            print $message{$line};
        }
        print "\tUse luxadm qlgc_s_download to load new fcode\n";
        print "\tUse the luxadm qlgc_s_download command in single user mode only!\n\n";
    } 
}

sub int {
    print "\nInterrupted\n";
    exit 0;
}

sub compare_fw_levels {
    ############################################
    # Given two hex numbers (usually 4 digits) #
    # simply compare them. A dot is a wildcard #
    # and matches automatically.               #
    # if $drive is >= $matrix then return 1    #
    # side effect: sets global $REQ_DISK_FW    #
    ############################################
    my ($matrix, $drive) = @_;
    my (@matrix, @drive);
    my $indx = 0;

    if ($matrix eq "") {
        return 1; # assume ok if not in matrix
    }
    ####################################################
    # a dot character in the fw version means wildcard #
    # so skip the comparison by removing it            #
    ####################################################
    @matrix = split //, $matrix; # separate characters
    @drive = split //, $drive; # separate characters
    $matrix = "";
    $drive = "";
    $REQ_DISK_FW = "";
    foreach $char (@matrix) {
        if ($char ne "\.") {
            $matrix .= $char;
            $drive .= $drive[$indx];
            $REQ_DISK_FW .= $char;
        } else {
            $REQ_DISK_FW .= "X";
        }
        $indx ++;
    }

    if (hex($drive) >= hex($matrix)) {
        return 1;
    } else {
        return 0;
    }
}

