#!/var/opt/STORtools/bin/perl 

# 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 a5k-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.
# This program supports checks for
#       Rev 1.14
#       Rev 1.15
#       Rev 1.16
#       Rev 1.17
#       Rev 1.18
#       Rev 1.19
#       Rev 1.20
#       Rev 1.21
#       Rev 1.22
#       Rev 1.23
#       Rev 1.24
#       Rev 2.00
#       Rev 2.01
#       Rev 2.02
# 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 = "${BINDIR}/a5k-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`);

# 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;

# Store enclosure names and wwn
# $ENCLOSURE{$name} = $enc_wwn

# Save disk inquiry data in
# @INQUIRY

# Flush buffers for each print
$| = 1;

# We want the program to run without an A5K Series attached.
$ENCLOSURES_FOUND = "FALSE";


# Get command line arguments sets numerous globals (Upper case)
# used in conditional execution
&proc_cli;

# 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 that the ses driver is running
    chop ($count = `modinfo | egrep -c ' ses ' 2>&1`);
    if ( ( $count == 0 ) && ( $STORAGE eq "A5" ) ) {
        $ENCLOSURES_FOUND = "FALSE";
        &ss_logger("ERROR", 4024, "ses driver not configured");
    } else {
        # LUXADM_PROBE and LUXADM_DISPLAY are set in these routines
        &luxadm_probe;
        if ( $STORAGE eq "A5" ) {
            &get_enclosure_names;           
            &luxadm_display_enclosures;
        }
        &count_disks;
    }
} 

# All tests or Driver Check
if ( ($ALL or $DRIVER) and ($PATCH_CHECK_REQUIRED{$RELEASE}) ) {
    if ($HBA eq "S") {
        &check_socal_driver;
    } elsif ( $HBA eq "P" ) {
        &check_ifp_driver;
    } else {
        print "Undefined HBA type in $PLATFORM\n";
    }
}

# All tests or firmware tests
if (($ALL or $FW) and ($PATCH_CHECK_REQUIRED{$RELEASE})) {
    &inquiry;
    if ($STORAGE eq "T3") {
        &check_t300_fw;
    }
    &check_disk_firmware if ($STORAGE ne "T3");
    &check_ib_firmware if ($ENCLOSURES_FOUND eq "TRUE");
    if ($HBA eq "S") {
        &check_socal_onboard_fcode();
        &check_socal_sbus_fcode();
        &check_socal_fcode_results();
    } elsif ( $HBA eq "P" ) {
        &check_2100_fcode();
    } else {
        print "Undefined HBA type in $PLATFORM\n";
    }
    # &check_103346; # we no longer check OB Prom levels
}

# Start FRU Checks

# All tests or fan check
if ($ENCLOSURES_FOUND eq "TRUE") {

    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";
}


if ( $STORAGE eq "A5" ) {
        if (($ENCLOSURES_FOUND eq "FALSE") and (! ($VRTS))) {
                print "No A5K Series enclosures were found.\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:$STORAGE_DEVICE: 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",
    "          [-P] [-S] [-T3] [-A5] [-v] [-warn] [-dots] \n",
    "A5   - A5K Series Storage array (default)\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",
    "P    - Select PCI Host Adapters\n",
    "pc   - Patch check\n",
    "ps   - Check power supply status (A5K Only)\n",
    "S    - Select SBUS Host Adapters (default)\n",
    "T3   - T300 Series Storage array\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";
    $STORAGE = "A5"; # default

    $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 =~ /-vrts/i) {
            $VRTS = "TRUE";
            $do_something = "TRUE";
        } elsif ($arg =~ /-dots/i) {
            $SHOWDOTS     = "TRUE";
        } elsif ($arg =~ /-A5/i) {
            $STORAGE      = "A5";
        } elsif ($arg =~ /-T3/i) {
            $STORAGE      = "T3";
        } elsif ($arg =~ /-warn/i) {
            $WARN         = TRUE;
        } elsif ($arg =~ /^-S/) {
            $HBA = "S";
            check_bus_option( $HBA );
        } elsif ($arg =~ /^-P/) {
            $HBA = "P";
            check_bus_option( $HBA );
        } else {
            die @usage; 
        }

        # Get the next value. 
        # The last command in the loop
        $arg = shift @ARGV;
    }
        
    if ( $STORAGE eq "A5" ) {
        $STORAGE_DEVICE = "A5000";
    } elsif ( $STORAGE eq "T3" ) {
        if ( $DISK || $FAN || $FRU || $MIN || $PS  ) {
            die @usage; 
        }
        $STORAGE_DEVICE = "T300";
    }

    # 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'});       

    if ( ( $HBA ne "P" ) and ( $HBA ne "S" ) ) {
        &get_fcal_hbas();
        &select_fcal_hbas();
    }
    print "HBA: $HBA\n" if ($DEBUG);
}

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'`;
    print "$LUXADM_PROBE" if ($DEBUG);
    if ( ( $LUXADM_PROBE =~ /No Network Array enclosures found/ ) &&
                 ( $STORAGE eq "A5" ) ) {
        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 get_enclosure_names {

    my @data = split(/\n/, $LUXADM_PROBE);

    my ($line, $name, $enc_wwn);

    foreach $line (@data) {
        chomp $line;
        next if ($line !~ /Name:.*WWN/);
        # Example line SENA may change
        # SENA               Name:z   Node WWN:50800200000001a0   
        ($name, $enc_wwn) = $line =~ /Name:(\S+)\s.*WWN:(\w+)/;
        $ENCLOSURE{$name} = $enc_wwn;
        $ENCLOSURES_FOUND = "TRUE";
    }
    if ($DEBUG) {
        print "DEBUG : ENCLOSURES:\n";
        foreach $key (keys %ENCLOSURE) {
            print "$key = $ENCLOSURE{$key}\n";
        }
    }
}

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);

    if ($RELEASE eq "5.5.1") {
        foreach $patch (sort (keys %PATCH_MATRIX_251)) {
            $result = &check_patch($patch, $PATCH_MATRIX_251{$patch});
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
    } elsif ($RELEASE eq "5.6") {
        foreach $patch (sort (keys %PATCH_MATRIX_26)) {
            $result = &check_patch($patch, $PATCH_MATRIX_26{$patch});
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
    # Release string is changing, check if the release contains 7
    } elsif ($RELEASE =~ /7/) {
        foreach $patch (sort (keys %PATCH_MATRIX_7)) {
            $result = &check_patch($patch, $PATCH_MATRIX_7{$patch});
            if ($result eq "FAIL") {
                $patch_error_flag = "FAIL";
            }
        }
    } else {
        &ss_logger("ERROR", 4000, "Unsupported version of the OS $RELEASE");
        $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";  
    my $req_106129  = "FALSE";
    my $req_108104  = "FALSE";
    my $req_108102  = "FALSE";


    print "\nDISK 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/;
        # 14 Drive photons
        if (($line =~ /SEAGATE/) and ($line =~ /ST19171F/)) {
            $drive_found = "TRUE";
            ($current_rev) = $current_rev =~ /(\w\w)\w+/;
            if ($current_rev < $ST19171FC) {
                # Set the local status
                $disk_status      = "FAIL";
                $req_106129       = "TRUE";
                &ss_logger("ERROR", 4003, "Incorrect disk fw : $device : ST19171FC : has ${current_rev}XX : needs ${ST19171FC}XX");
            } elsif ($current_rev > $ST19171FC) {
                # Set the local status
                &ss_logger("WARNING", 3022, "disk fw does not match matrix: $device : ST19171FC : has ${current_rev}XX : matrix ${ST19171FC}XX");
            } else {
                print "\t$line\n" if $VERBOSE;
            }
        }
        # 22 Drive photon
        if (($line =~ /SEAGATE/) and ($line =~ /ST39102F/)) {
            $drive_found = "TRUE";
            if ($current_rev < $ST39102FC) {
                # Set the local status
                $disk_status = "FAIL";
                $req_108104  = "TRUE";
                &ss_logger("ERROR", 4003, "Incorrect disk fw : $device : ST39102FC : has $current_rev : needs $ST39102FC");
            } elsif ($current_rev > $ST39102FC) {
                # Set the local status
                &ss_logger("WARNING", 3022, "disk fw does not match matrix: $device : ST39102FC : has ${current_rev} : matrix ${ST39102FC}");
            } else {
                print "\t$line\n" if $VERBOSE;
            }
        }
        # Seagate 18G
        if (($line =~ /SEAGATE/) and ($line =~ /ST118273F/)) {
            $drive_found = "TRUE";
            if ($current_rev < $ST118273FC) {
                # Set the local status
                $disk_status = "FAIL";
                $req_108102  = "TRUE";
                &ss_logger("ERROR", 4003, "Incorrect disk fw : $device : ST118273FC : has $current_rev : needs $ST118273FC");
            } elsif ($current_rev > $ST118273FC) {
                # Set the local status
                &ss_logger("WARNING", 3022, "disk fw does not match matrix: $device : ST118273FC : has ${current_rev} : matrix ${ST118273FC}");
            } else {
                print "\t$line\n" if $VERBOSE;
            }
        }
    }
    if ($drive_found =~ /FALSE/) {
        $disk_status = "NORESULT";
        print "\tNo drives matched the patch matrix!\n" if $VERBOSE;
    }
    # Tell them which patch to install
    if ($req_106129 eq "TRUE") {
        print "Patch $rev_106129 will upgrade disk firmware for Seagate ST19171FC drives.\n"
    }
    if ($req_108104 eq "TRUE") {
        print "Patch $rev_108104 will upgrade disk firmware for Seagate ST39102FC drives.\n"
    }
    if ($req_108102 eq "TRUE") {
        print "Patch $rev_108102 will upgrade disk firmware for Seagate ST118273FC drives.\n"
    }
    print "\n\t$disk_status\n" if $VERBOSE;
}       

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

    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");
                        }
                    } 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");
                        }
                    }

                } 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");
                        }
                    } 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");
                        }
                    }
                } 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";
        if ($HBA eq "P") {
            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")) {
        if ($RELEASE =~ /5.5.1/) {           
            print "\tInstall patch 105310 and follow the special instructions.\n";
        } elsif ($RELEASE =~ /5.6/) {          
            print "\tInstall patch 105375 and follow the special instructions.\n";              
        }  elsif ($RELEASE =~ /7/) {
            print "\tPatch not available for S7 to upgrade your firmware.\n";
        }
    }
    print "\n\t$ib_status\n" if ($VERBOSE);
}      

sub check_prom_revs {

#  103346-13
#Current System Board PROM Revisions:
#------------------------------------
#Board  0: CPU/Memory OBP   3.2.10 1997/09/08 13:47 POST  3.7.2 1997/09/04 09:51
#Board  2: CPU/Memory OBP   3.2.10 1997/09/08 13:47 POST  3.7.2 1997/09/04 09:51
#Board  1: I/O Type 1 FCODE 1.8.1  1996/12/12 18:23 iPOST 3.4.2 1997/01/10 13:34
#Board  5: I/O Type 1 FCODE 1.8.1  1996/12/12 18:23 iPOST 3.4.2 1997/01/10 13:34
#Board  7: I/O Type 1 FCODE 1.8.1  1996/12/12 18:23 iPOST 3.4.2 1997/01/10 13:34
#
#
#Available 'Update' Revisions:
#-----------------------------
#          CPU/Memory OBP   3.2.12 1998/01/13 19:51 POST  3.8.4 1998/01/21 17:09
#          I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.4 1997/08/26 17:37
#          I/O Type 2 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.4 1997/08/26 17:37
#          I/O Type 3 FCODE 1.8.7  1997/05/09 11:18 iPOST 3.0.2 1997/05/01 10:56
#          I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.4 1997/08/26 17:37
#          I/O Type 5 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.4 1997/08/26 17:37
#
#Verifying Checksums: Okay

# 103346-19
# Current System Board PROM Revisions:
# ------------------------------------
#Board  0: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
#Board  2: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
#Board  4: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
#Board  1: I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.6 1998/04/16 14:23
#Board  3: I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.6 1998/04/16 14:23
#Board  5: I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.6 1998/04/16 14:23
#Board  7: I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.6 1998/04/16 14:23
# 
# 
# Available 'Update' Revisions:
# -----------------------------
#         CPU/Memory OBP   3.2.19 1998/10/20 18:13 POST  3.9.8 1998/11/09 15:09
#         I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.8 1998/10/27 12:24
#         I/O Type 2 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.8 1998/10/27 12:24
#         I/O Type 3 FCODE 1.8.7  1997/05/09 11:18 iPOST 3.0.2 1997/05/01 10:56
#         I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.8 1998/10/27 12:24
#         I/O Type 5 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.8 1998/10/27 12:24

    local ($result_proms) = "PASS";

    foreach $line (@FLASH) {
        print "$line\n" if ($VERBOSE);

#Board  2: CPU/Memory OBP   3.2.10 1997/09/08 13:57 POST  3.7.2 1997/09/04 09:51
#Board  0: CPU/Memory OBP   3.2.14 1998/03/24 13:34 POST  3.9.2 1998/03/04 11:32

        if ($line =~ /^Board\s+(\d+):\s+CPU\/Memory\s+OBP\s+(\S+).*POST\s+(\S+)/) {
            $board_number = $1;
            $current_obp  = $2;
            $current_post = $3;
            # Check the OBP
            print "\n\tBoard $board_number : CPU/Memory OBP $current_obp expected $CPU_OBP\n"
                if $VERBOSE;
            if (&version_ok($CPU_OBP, $current_obp)) {
                print "\tPASS\n" if ($VERBOSE);
            } else {
                print "\tFAIL\n" if ($VERBOSE);
                $result_proms = "FAIL";
                &ss_logger("ERROR", 4005, "103346 not installed : Board $board_number : CPU/Memory OBP $current_obp expected $CPU_OBP");
            }
            # Check the POST
            print "\n\tBoard $board_number : CPU/Memory POST $current_post expected $CPU_POST\n"
                if $VERBOSE;
            if (&version_ok($CPU_POST, $current_post)) {
                print "\tPASS\n" if ($VERBOSE);
            } else {
                print "\tFAIL\n" if ($VERBOSE);
                $result_proms = "FAIL";
                &ss_logger("ERROR", 4005, "103346 not installed : Board $board_number : CPU/Memory POST $current_post expected $CPU_POST");
            }
        }

#Board  1: I/O Type 1 FCODE 1.8.1  1996/12/12 18:23 iPOST 3.4.2 1997/01/10 13:34
        if ($line =~ /^Board\s+(\d+):\s+I\/O\s+Type\s+(\d+)\s+FCODE\s+(\S+).*iPOST\s+(\S+)/) {
            $board_number  = $1;
            $board_type    = $2;
            $current_fcode = $3;
            $current_ipost = $4;
            if ($VERBOSE) {
                print "\n\tBoard $board_number : I/O Type $board_type ";
                print ": FCODE found $current_fcode expected $IO_FCODE[$board_type] \n";
            }
            if (&version_ok($IO_FCODE[$board_type], $current_fcode)) {
                print "\tPASS\n" if ($VERBOSE);
            } else {
                print "\tFAIL\n" if ($VERBOSE);
                &ss_logger("ERROR", 4006, "103346 not installed : Board $board_number : I/O Type $board_type : FCODE found $current_fcode expected $IO_FCODE[$board_type]")
            } 
            if ($VERBOSE) {
                print "\n\tBoard $board_number : I/O Type $board_type ";
                print ": iPOST found $current_ipost expected $IO_IPOST[$board_type] \n";
            }
            if (&version_ok($IO_IPOST[$board_type], $current_ipost)) {
                print "\tPASS\n" if ($VERBOSE);
            } else {
                print "\tFAIL\n" if ($VERBOSE);
                &ss_logger("ERROR", 4006, "103346 not installed : Board $board_number : I/O Type $board_type : iPOST found $current_ipost expected $IO_IPOST[$board_type]")
            } 
        }
    } 
}

sub run_flash_update {

#  103346-13
# -----------------------------
#           CPU/Memory OBP   3.2.12 1998/01/13 19:51 POST  3.8.4 1998/01/21 17:09
#           I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.4 1997/08/26 17:37
#           I/O Type 2 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.4 1997/08/26 17:37
#           I/O Type 3 FCODE 1.8.7  1997/05/09 11:18 iPOST 3.0.2 1997/05/01 10:56
#           I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.4 1997/08/26 17:37
#           I/O Type 5 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.4 1997/08/26 17:37
 
#  103346-19
# Current System Board PROM Revisions:
# ------------------------------------
# Board  0: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
# Board  2: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
# Board  4: CPU/Memory OBP   3.2.16 1998/06/08 16:58 POST  3.9.4 1998/06/09 16:25
# Board  1: I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.6 1998/04/16 14:23
# Board  3: I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.6 1998/04/16 14:23
# Board  5: I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.6 1998/04/16 14:23
# Board  7: I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.6 1998/04/16 14:23

# 
#  103346-22
# Current System Board PROM Revisions:
# ------------------------------------
#CPU/Memory Board:
#	OBP   3.2.22 1999/05/12 15:34
#	POST  3.9.22 1999/05/12 15:37

#IO Graphics Board:
#	I/O Type 1 FCODE 1.8.22 1999/05/12 15:32
#	iPOST 3.4.22 1999/05/12 15:37

#IO Graphics + Board:
#	I/O Type 5 FCODE 1.8.22 1999/05/12 15:33
#	iPOST 3.4.22 1999/05/12 15:37

#Dual Sbus IO Board:
#	I/O Type 2 FCODE 1.8.22 1999/05/12 15:33
#	iPOST 3.4.22 1999/05/12 15:37

#Dual Sbus + IO Board:
#	I/O Type 4 FCODE 1.8.22 1999/05/12 15:33
#	iPOST 3.4.22 1999/05/12 15:37

#Dual PCI IO Board:  
#	I/O Type 3 FCODE 1.8.22 1999/05/12 15:33
#	iPOST 3.0.22 1999/05/12 15:37


# 
# Available 'Update' Revisions:
# -----------------------------
#           CPU/Memory OBP   3.2.19 1998/10/20 18:13 POST  3.9.8 1998/11/09 15:09
#           I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.8 1998/10/27 12:24
#           I/O Type 2 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.8 1998/10/27 12:24
#           I/O Type 3 FCODE 1.8.7  1997/05/09 11:18 iPOST 3.0.2 1997/05/01 10:56
#           I/O Type 4 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.8 1998/10/27 12:24
#           I/O Type 5 FCODE 1.8.7  1997/12/08 15:39 iPOST 3.4.8 1998/10/27 12:24
    # Check to see if the reconfigure file exists. This must be a global
    # so that the interupt routine can also check to see if the query
    # created the file.
    $RECONFIGURE = "FALSE";
    $RECONFIGURE = "TRUE" if (-e "/reconfigure");

    if ($VERBOSE) {
        print "\n\tflashprom messages if present are generated\n";
        print "\tby the flash-update program which appears to be \n";
        print "\tblocking Unix file redirection. They can be ignored.\n";
    }
    # Here shell scripts to get the flashupdate data
    # Not well defined in the patch matrix
    my $flashupdate;
    if ($PATCH_MATRIX_REVISION =~ /1.14/) {
    # Patch matrix version 1.14 used 103346-13 
        $last_command   = "${BINDIR}/flash-update-here-13";
        $flashupdate = `/usr/bin/ksh -c '${BINDIR}/flash-update-here-13 2>&1'`;
        $last_command   = "";
    } elsif ($PATCH_MATRIX_REVISION =~ /1.15|1.16|1.17|/) {
    # Patch matrix version 1.15 uses 103346-19 
        $last_command   = "${BINDIR}/flash-update-here-19";
        $flashupdate = `/usr/bin/ksh -c '${BINDIR}/flash-update-here-19 2>&1'`;
        $last_command   = "";
    } elsif ($PATCH_MATRIX_REVISION =~ /|1.18|1.19|1.20|1.21|1.22|1.23|1.24|2.00|2.01|2.02/) {
    # Patch matrix version 1.18 uses 103346-22 
        $last_command   = "${BINDIR}/flash-update-here-22";
        $flashupdate = `/usr/bin/ksh -c '${BINDIR}/flash-update-here-22 2>&1'`;
        $last_command   = "";
    } else {
        if ($VERBOSE) {
            print "The flash-update program to use for this revision ";
            print "($PATCH_MATRIX_REVISION) is not defined.\n";
            print "The default is to use 103346-22 for PROM checks\n";
        }
        $last_command   = "${BINDIR}/flash-update-here-22";
        $flashupdate = `/usr/bin/ksh -c '${BINDIR}/flash-update-here-22 2>&1'`;
        $last_command   = "";
    }

    # Global variable that contains flash update date 103346
    @FLASH = split(/\n/, $flashupdate);
    my $found_avalable;
    foreach $line (@FLASH) {

        $FOUND_PROM_REVS = "TRUE" if ($line =~ /^Available/); 

        if ($FOUND_PROM_REVS) {

#          CPU/Memory OBP   3.2.12 1998/01/13 19:51 POST  3.8.4 1998/01/21 17:09
           if ($line =~ /\s+CPU.*OBP\s+(\S+).*POST\s+(\S+)/) {
                $CPU_OBP  = $1;
                $CPU_POST = $2;

#          I/O Type 1 FCODE 1.8.3  1997/11/14 12:41 iPOST 3.4.4 1997/08/26 17:37
            } elsif ($line =~ /\s+I\/O\s+Type\s+(\d+)\s+FCODE\s+(\S+).*iPOST\s+(\S+)/) {
                $IO_FCODE[$1] = $2;
                $IO_IPOST[$1] = $3;
            }
        } 
    }
    if ((-e "/reconfigure") and ($RECONFIGURE eq "FALSE")) {
        system("rm /reconfigure");
    }
}

sub check_socal_fcode_results {
    if ( ( $onboard_count == 0 ) && ( $sbus_count == 0 ) ) {
        &ss_logger("ERROR", 4006, "Unable to check the fcode with prtconf (failed to detect any soc+ cards)");
    }
}

# Onboard soc+ FCODE check 

sub check_socal_onboard_fcode {

    my $fcode_result = "PASS";
    $onboard_count = 0;

    # Assumes fcode defined in the matrix 
    if (! $FCODE_103346) {
        $fcode_result = "NORESULT";
        print "FCODE for Onboard soc+ not defined for Solaris $RELEASE. Check patch matrix!\n";
        print "\t$fcode_result\n" if ($VERBOSE);
        return;
    } 
    print "\nCHECK Onboard Soc+ FCODE : $FCODE_103346\n" if ($VERBOSE);

    $last_command = "/usr/sbin/prtconf -pv | /bin/egrep 'reg:|device_type:|version:'";
    $prtconf_output  = `/usr/bin/ksh -c '/usr/sbin/prtconf -pv  2>&1' | /bin/egrep "reg:|device_type:|version:"`;
    $last_command = "";
    @prtconf_output  = split(/\n/, $prtconf_output);
    for($i=0;$i<=$#prtconf_output;$i++) {
        if ($prtconf_output[$i] =~ /^\s+device_type:.*socal/) {
            if ($prtconf_output[$i+1] =~ /^\s+version:.*FCode\s+(\S+)/) {

                $current_fcode = $1;

                # Onboard soc+
                if ($prtconf_output[$i-1] =~ /^\s+reg:  0000000d/) {
                    $onboard_count++;
                    if (! &version_ok($FCODE_103346, $current_fcode)) {
                        &ss_logger("ERROR", 4006, "onboard soc+ fcode downreved : expected $FCODE_103346 and found $current_fcode");
                        $fcode_result = "FAIL";
                    } elsif ($VERBOSE) {
                        print "$prtconf_output[$i+1]\n";
                    }
                }
            }
        }
    }
    if ( ( $onboard_count == 0 ) && ( $VERBOSE ) ) {
        print "\tNo Onboard soc+ cards detected.\n";
    }
    print "\t$fcode_result\n" if ($VERBOSE);
    if ($fcode_result =~ /FAIL|NORESULT/) {
        if ($RELEASE =~ /5.5.1/) {
             print "\tInstall patch 105310 and follow the special instructions in the README.\n"
                if $VERBOSE;
        } elsif ($RELEASE =~ /5.6/) {
             print "\tInstall patch 105375 and follow the special instructions in the README.\n"
                if $VERBOSE;
        } elsif ($RELEASE =~ /7/) {
             print "\tInstall patch 107473 and follow the special instructions in the README.\n"
                if $VERBOSE;
        }
    } 
    if ($fcode_result =~ /FAIL|NORESULT/) {
        print "\tOnboard soc+ Install patch 103346 and follow the special instructions in the README.\n";
    } 
}

# SBUS soc+ FCODE check

sub check_socal_sbus_fcode {

    my $fcode_result = "PASS";
    $sbus_count = 0;

    # Assumes fcode defined in the matrix 
    if (! $FCODE_109400) {
        $fcode_result = "NORESULT";
        print "FCODE SBUS soc+ not defined for Solaris $RELEASE. Check patch matrix!\n";
        print "\t$fcode_result\n" if ($VERBOSE);
        return;
    } 
    print "\nCHECK SBUS Soc+ FCODE : $FCODE_109400\n" if ($VERBOSE);

    $last_command = "/usr/sbin/prtconf -pv | /bin/egrep 'reg:|device_type:|version:'";
    $prtconf_output  = `/usr/bin/ksh -c '/usr/sbin/prtconf -pv 2>&1' | /bin/egrep "reg:|device_type:|version:"`;
    $last_command = "";
    @prtconf_output  = split(/\n/, $prtconf_output);
    for($i=0;$i<=$#prtconf_output;$i++) {
        if ($prtconf_output[$i] =~ /^\s+device_type:.*socal/) {
            if ($prtconf_output[$i+1] =~ /^\s+version:.*FCode\s+(\S+)/) {

                $current_fcode = $1;

                # SBUS soc+
                if ($prtconf_output[$i-1] !~ /^\s+reg:  0000000d/) {
                    $sbus_count++;
                    if (! &version_ok($FCODE_109400, $current_fcode)) {
                        &ss_logger("ERROR", 4006, "SBUS soc+ fcode downreved : expected $FCODE_109400 and found $current_fcode");
                        $fcode_result = "FAIL";
                    } elsif ($VERBOSE) {
                        print "$prtconf_output[$i+1]\n";
                    }
                }
            }
        }
    }
    if ( ( $sbus_count == 0 ) && ( $VERBOSE ) ) {
        print "\tNo SBUS soc+ cards detected.\n";
    }
    print "\t$fcode_result\n" if ($VERBOSE);
    if ($fcode_result =~ /FAIL|NORESULT/) {
        if ($RELEASE =~ /5.5.1/) {
             print "\tInstall patch 105310 and follow the special instructions in the README.\n"
                if $VERBOSE;
        } elsif ($RELEASE =~ /5.6/) {
             print "\tInstall patch 105375 and follow the special instructions in the README.\n"
                if $VERBOSE;
        } elsif ($RELEASE =~ /7/) {
             print "\tInstall patch 107473 and follow the special instructions in the README.\n"
                if $VERBOSE;
        }
    } 
    if ($fcode_result =~ /FAIL|NORESULT/) {
        print "\tSBUS soc+ Install patch 109400 and follow the special instructions in the README.\n";
        print "\tluxadm fcal_s_download -f /usr/lib/firmware/fc_s/fcal_s_fcode\n";
        print "\tUse the luxadm fcal_s_download command in single user mode only!\n";
    } 
}

sub check_2100_fcode {
#######################################################
# Check version of firmware for PCI Host Adapter Card #
#       if FALSE then accepts rev number              #
#######################################################
    my $fcode_result = "PASS";
    my $count        = 0;
    print "\nCHECK 2100 FCODE : $FCODE_2100\n" if ($VERBOSE);
    $prtconf_output  = `/usr/sbin/prtconf -pv | egrep 'device_type|version' 2>&1
`;
    @prtconf_output  = split(/\n/, $prtconf_output);
    for($i=0;$i<=$#prtconf_output;$i++) {
        if ($prtconf_output[$i] =~ /^\s+version:.*FC100\/+P FC-AL Host Adapter Driver:\s+(\S+)/) {
            $count++;
            $current_fcode = $1;
            if (! &version_ok($FCODE_2100, $current_fcode) ) {
                &ss_logger("ERROR", 4006, "Qlogic 2100 fcode downreved : expected $FCODE_2100 and found $current_fcode");
                $fcode_result = "FAIL";
            } elsif ($VERBOSE) {
                print "$prtconf_output[$i]\n";
            }
        }
    }
    if ($count == 0) {
        &ss_logger("ERROR", 4006, "Unable to check the fcode with prtconf (failed to detect pci cards)");
        $fcode_result = "ERROR";
    }
    print "\t$fcode_result\n" if ($VERBOSE);
    if ($fcode_result =~ /FAIL|NORESULT/) {
        print "\tInstall patch 109399 and follow the special instructions in the README.\n";
        print "\tUse the luxadm qlgc_s_download command in single user mode only!\n"
    }
}
1;

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_major_num, $spec_minor_num, $spec_increment\n";
#    print "$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_251_patches, $parse_26_A5_patches, $parse_7_A5_patches)  = "";
    my ($parse_26_T3_patches, $parse_7_T3_patches)                = "";
    my ($parse_firmware, $parse_disk_fw)                          = "";
    my ($parse_ib_fw, $parse_fcode, $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_251_patches           = "FALSE";
           $parse_26_A5_patches         = "FALSE";
           $parse_26_T3_patches         = "FALSE";
           $parse_7_A5_patches          = "FALSE";
           $parse_7_T3_patches          = "FALSE";
           $parse_disk_fw               = "FALSE";
           $parse_ib_fw                 = "FALSE";
           $parse_t300_fw               = "FALSE";
           $parse_fcode                 = "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 =~ /^251_PATCH_LIST/) {
           $parse_251_patches = "TRUE";
           next;
        }
        if ($line =~ /^26_PATCH_A5_LIST/) {
           $parse_26_A5_patches  = "TRUE";
           next;
        }
        if ($line =~ /7_PATCH_A5_LIST/) {
           $parse_7_A5_patches  = "TRUE";
           next;
        }
        if ($line =~ /^26_PATCH_T300_LIST/) {
           $parse_26_T3_patches  = "TRUE";
           next;
        }
        if ($line =~ /7_PATCH_T300_LIST/) {
           $parse_7_T3_patches  = "TRUE";
           next;
        }
        if ($line =~ /^SEVM:/) {
           $parse_new_sevm = "TRUE"; 
           next;
        }
        if ($line =~ /^DISK FIRMWARE/) {
           $parse_disk_fw  = "TRUE";
           next;
        }
        if ($line =~ /^IB FIRMWARE/) {
           $parse_ib_fw  = "TRUE";
           next;
        }
        if ($line =~ /^FCODE ONBOARD-SOC/) {
           $parse_fcode  = "TRUE";
           next;
        }
        if ($line =~ /^FCODE SBUS-SOC/) {
           $parse_fcode  = "TRUE";
           next;
        }
        if ($line =~ /^FCODE 2100/) {
           $parse_fcode  = "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_251_patches eq "TRUE") {

            if ($line =~ /^\s+(\d\S+)\s+:\s+(.*)/) {
                $patch             = $1;
                $patch_description = $2;

                # Found a 2.5.1 patch, we can do a patch check
                if ($RELEASE =~ /5.5.1/) {
                    $PATCH_CHECK_REQUIRED{$RELEASE} = "TRUE";
                }

                # Some of the patches are platform specific
                if ($patch =~ /103346/) { 
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX_251{$patch} = $patch_description;
                    }
                # 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_251{$patch} = $patch_description;
                        }
                    }
                } else {
                    $PATCH_MATRIX_251{$patch} = $patch_description;
                }
            }

        } elsif (($parse_26_A5_patches eq "TRUE") && ($STORAGE eq "A5")) {

            if ($line =~ /^\s+(\d\S+)\s+:\s+(.*)/) {

                $patch             = $1;
                $patch_description = $2;

                # Found a 2.6 patch, we can do a patch check
                if ($RELEASE =~ /5.6/) {
                    $PATCH_CHECK_REQUIRED{$RELEASE} = "TRUE";
                }
                if ($patch =~ /103346/) { 
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX_26{$patch} = $patch_description;
                    }
                } else {
                        $PATCH_MATRIX_26{$patch} = $patch_description;
                }
            }
        } elsif (($parse_7_A5_patches eq "TRUE") && ($STORAGE eq "A5")) {

            if ($line =~ /^\s+(\d\S+)\s+:\s+(.*)/) {
                $patch             = $1;
                $patch_description = $2;

                # Found a 7 patch, we can do a patch check
                # Release string is changing, check if the release contains 7
                if ($RELEASE =~ /7/) {
                    $PATCH_CHECK_REQUIRED{$RELEASE} = "TRUE";
                }
                if ($patch =~ /103346/) {
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX_7{$patch} = $patch_description;
                    }
                } else {
                        $PATCH_MATRIX_7{$patch} = $patch_description;
                }
            }
        } elsif (($parse_26_T3_patches eq "TRUE") && ($STORAGE eq "T3")) {

            if ($line =~ /^\s+(\d\S+)\s+:\s+(.*)/) {

                $patch             = $1;
                $patch_description = $2;

                # Found a 2.6 patch, we can do a patch check
                if ($RELEASE =~ /5.6/) {
                    $PATCH_CHECK_REQUIRED{$RELEASE} = "TRUE";
                }
                if ($patch =~ /103346/) { 
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX_26{$patch} = $patch_description;
                    }
                } else {
                        $PATCH_MATRIX_26{$patch} = $patch_description;
                }
            }
        } elsif (($parse_7_T3_patches eq "TRUE") && ($STORAGE eq "T3")) {

            if ($line =~ /^\s+(\d\S+)\s+:\s+(.*)/) {
                $patch             = $1;
                $patch_description = $2;

                # Found a 7 patch, we can do a patch check
                # Release string is changing, check if the release contains 7
                if ($RELEASE =~ /7/) {
                    $PATCH_CHECK_REQUIRED{$RELEASE} = "TRUE";
                }
                if ($patch =~ /103346/) {
                    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {
                        $PATCH_MATRIX_7{$patch} = $patch_description;
                    }
                } else {
                        $PATCH_MATRIX_7{$patch} = $patch_description;
                }
            }
        } 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) and ($VERITAS_VERSION =~ /^$temp_ver/)) {
               $VERITAS_VERSION_IS_SUPPORTED = 1;  # approved Veritas/Solaris combination
               $NEW_VERITAS{$temp_patch} = $temp_comment if $temp_patch;
           }

        } 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_disk_fw eq "TRUE") {
            # ST19171FC, ST39102FC, ST118273FC are disk model numbers
            if ($line =~ /ST19171F/) {
                ($f1, $ST19171FC, $f3) =  split(' ',$line);
            } elsif ($line =~ /ST39102F/) {
                ($f1, $ST39102FC) = split(' ',$line);
            } elsif ($line =~ /ST118273F/) {
                ($f1, $ST118273FC) = split(' ',$line);
            }
        } elsif ($parse_ib_fw eq "TRUE") {
            # 14 drive A5K Series
            if ($line =~ /5000\s+(\S+)/) {
                $IB_5000 = $1;
            # 22 drive A5200
            } elsif ($line =~ /5200\s+(\S+)/) {
                $IB_5200 = $1;
            }   
        } elsif (($parse_t300_fw eq "TRUE") && ($STORAGE eq "T3")) {
            # T300 
            if ($line =~ /T300FW\s+(\S+)/) {
                $T300FWREV = $1;
            }   

        } elsif ($parse_fcode eq "TRUE") {
            if ($line =~ /^\s+FCODE 2100\s+(\S+)/) {
                $FCODE_2100 = $1;
            } elsif ($line =~ /^\s+FCODE 103346\s+(\S+)/) {
                $FCODE_103346 = $1;
            } elsif ($line =~ /^\s+FCODE 109400\s+(\S+)/) {
                $FCODE_109400 = $1;
            }
        }
    }

    die "The patch matrix doesn't contain the expected version string! (i.e # REV 2.02)\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 ($RELEASE eq "5.5.1") {
        print "\n\tSolaris 2.5.1 Patch Matrix\n" if $VERBOSE;
        foreach $key (sort (keys %PATCH_MATRIX_251)) {
            printf("\t%-12.12s : %-s\n", $key , $PATCH_MATRIX_251{$key}) if $VERBOSE;
        }
    } elsif ($RELEASE eq "5.6") {
        print "\n\tSolaris 2.6 Patch Matrix\n" if $VERBOSE;
        foreach $key (sort (keys %PATCH_MATRIX_26)) {
            printf("\t%-12.12s : %-s\n", $key , $PATCH_MATRIX_26{$key}) if $VERBOSE;
        }
    # Release string is changing, check if the release contains 7
    } elsif ($RELEASE =~ /7/) {
        print "\n\tSolaris 7 Patch Matrix\n" if $VERBOSE;
        foreach $key (sort (keys %PATCH_MATRIX_7)) {
            printf("\t%-12.12s : %-s\n", $key , $PATCH_MATRIX_7{$key}) if $VERBOSE;
        }
    }
    if ($VERITAS_VERSION) {
        print "\n\tVeritas $VERITAS_VERSION Patch Matrix:\n" if $VERBOSE;
        foreach $key (sort (keys %NEW_VERITAS)) {
            printf("\t%-12.12s : %-s\n", $key , $NEW_VERITAS{$key}) if $VERBOSE;
        }
    }
    print "\n\tFirmware Requirements:\n" if $VERBOSE;

    if  ($VERBOSE) {
        if ( $HBA eq "P" ) {
            printf("\t%-12.12s : %-s\n", "Qlogic 2100",       $FCODE_2100);
        } else {
            printf("\t%-12.12s : %-s\n", "Onboard SOC+",  $FCODE_103346);
            printf("\t%-12.12s : %-s\n", "SBUS SOC+",  $FCODE_109400);
        }
    }

    if ($STORAGE eq "A5") {
       printf("\t%-12.12s : %-s\n", "5000 IB FW",   $IB_5000)    if (($IB_5000)   and ($VERBOSE));
       printf("\t%-12.12s : %-s\n", "5200 IB FW",   $IB_5200)    if (($IB_5200)   and ($VERBOSE));
       printf("\t%-12.12s : %-s\n", "ST19171FC", "$ST19171FC")   if (($ST19171FC) and ($VERBOSE));
       printf("\t%-12.12s : %-s\n", "ST39102FC", "$ST39102FC")     if (($ST39102FC)  and ($VERBOSE));
       printf("\t%-12.12s : %-s\n", "ST118273FC", "$ST118273FC")     if (($ST118273FC)  and ($VERBOSE));
    }

    if ($STORAGE eq "T3") {
       printf("\t%-12.12s : %-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%-12.12s : %-s\n", $key , $NEW_SDS{$key}) if $VERBOSE;
    }
}

sub check_103346 {

    # Needs to be an Ultra Enterprise 3x00/4x00/5x00/6x00
    if ($PLATFORM eq "SUNW,Ultra-Enterprise") {

        print "\nCHECK PROM : SUNW,Ultra-Enterprise : 103346\n" if $VERBOSE;

        &run_flash_update;

        if ($FOUND_PROM_REVS) {
            &check_prom_revs;
        } else {
            print "\n\tUnable to determine the required prom revs!\n";
            print "\tPlease install the latest version of 103346\n";
            print "\tand run the flash-update program to insure that\n";
            print "\tany SOC+I/O Boards are at the correct level.\n";
            &ss_logger("WARNING", 3001, "Unable to run flash-update");
        }
    }
}

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) = @_;
    if ( ( $patch_description =~ /ifp/ ) && ( $HBA eq "S" ) ) {
        return;
    }
    if ( ( $patch_description =~ /socal/ ) && ( $HBA eq "P" ) ) {
        return;
    }

    # 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 = "";
    }

    # Eliminate firmware patches that don't show up with showrev -p
    if ($patch =~ /103346|106129|108102|108104/) {
        $FW = TRUE;
        if ( $patch =~ /103346/ ) {
           $rev_103346= $patch;
        } elsif ( $patch =~ /106129/ ) {
           $rev_106129= $patch;
        } elsif ( $patch =~ /108102/ ) {
           $rev_108102= $patch;
        } elsif ( $patch =~ /108104/ ) {
           $rev_108104= $patch;
        }
        return "NORESULT";
    } 

    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\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 "[$STORAGE_DEVICE:DIAG:$status:$error_number] $message\n";
    }
    if (($status =~ /WARN/) and $WARN) {
        $MAIL_FLAG    = "TRUE";
        print MAIL "[$STORAGE_DEVICE:DIAG:$status:$error_number] $message\n";
    }
    $ERROR_FLAG = "FAIL" if ($status eq "ERROR");
    
    if ($status eq "ERROR") {
        print "$status : $message\n";
    } elsif (($status =~ /WARNING/) and $WARN) {
        print "$status : $message\n";
    }
    if ($LOGGING) {
        if ($status eq "ERROR") {
            $exec_str = 
            "/usr/bin/logger -p mail.crit -t \"[$STORAGE_DEVICE:DIAG:$status:$error_number]\" \"$message\"";
        } elsif ($status eq "WARN") {
            $exec_str = 
            "/usr/bin/logger -p mail.warn -t \"[$STORAGE_DEVICE:DIAG:$status:$error_number]\" \"$message\"";
        } else {
            $exec_str = 
            "/usr/bin/logger -p mail.info -t \"[$STORAGE_DEVICE: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) {
            ############################################################
            # 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;
}

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 int {
    # Remove the /reconfigure file if it was caused
    # by running this program.
    if ((-e "/reconfigure") and ($RECONFIGURE eq "FALSE")) {
        system("rm /reconfigure");
    }
    print "Interrupted $last_command\n";
    exit;
}

sub check_ifp_driver {

    # Add this check because ifp is not bundled
    # and we need this driver to talk with the enclosures.
    # pci is the bus architecture. ifp is the driver.

    # HBA is defined in the supporting routines
    # HBA = P   is PCI
    # HBA = S   is SBUS

    # This check is only meaningful on PCI systems
    return if ($HBA ne "P");

    if (! -e "/kernel/drv/ifp") {
        &ss_logger("ERROR", 4023, "ifp driver not installed : SUNWifp : /kernel/drv/ifp");
    } else {
        my $count;
        chop($count = `/usr/sbin/modinfo |  egrep -c ' ifp '`);
        if ($count == 0) {
            &ss_logger("ERROR", 4023, "ifp driver not configured");
        }
    }
}

sub check_socal_driver {

    # Insure the socal driver is installed. We had some
    # versions of the OS where the socal driver was
    # not installed with the base distribution, but
    # showed up when a full install was performed.
    
    # HBA is defined in the supporting routines
    # HBA = P   is PCI
    # HBA = S   is SBUS

    # This check is only meaningful on SBUS systems
    return if ($HBA ne "S");

    if (! -e "/kernel/drv/socal") {
        &ss_logger("ERROR", 4022, "socal driver not installed : SUNWluxal : /kernel/drv/socal");
    } else {
        my $count;
        chop($count = `/usr/sbin/modinfo |  egrep -c ' socal '`);
        if ($count == 0) {
            &ss_logger("ERROR", 4022, "socal driver not configured");
        }
    }
}

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/;
        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 Patch 109115-xx");
            } 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 int {
    print "\nInterrupted\n";
    exit 0;
}

