package Revision;
#<copyright>
# ----------------------------------------------------------
# Sun Proprietary/Confidential Code
# Copyright 2001, Sun Microsystems, Inc. All rights reserved.
# ----------------------------------------------------------
#</copyright>

use strict;
use System;
use Util;
use Matrix;
use PDM::ConfigFile;

#
#  returns: [type, rc, curr_version, expected_version, current_patch, expected_patch, desc]
#  rc = PASS , DOWNREV, UPREV, NI, (Not installed), ERR
use vars qw ($ERR);

use vars qw ($ERR);

sub baseRev {
  my($class, $patch) = @_;
  my($base, $req_rev);
  if ($patch =~ /-/) {
      ($base, $req_rev) = split (/-/, $patch);
   } else {
      $base = $patch;
      $req_rev = "";
   }
  return ($base, $req_rev);
}

#
# uses the matrix to verify if the right patch is present for a certain version of
# software, see SDS, SEVM in matrix.db for example
# 'SDS' => {
#   '4.2.1' => [ '108693-04', 'Solstice DiskSuite' ]
# },
#

sub checkSoftPatch {
  my($class, $category, $Matrix, $InstalledP, $curr_version) = @_;

  my $x = $Matrix->{$category};
  my (@R);
  foreach my $version (keys %$x) {
      next if ($version ne $curr_version);

      my $entry = $x->{$version};
      my $req_patch = $entry->[0];
      my $desc      = $entry->[1];
      my($req_base, $req_rev) = $class->baseRev($req_patch);

      if ($InstalledP->{patch}{$req_patch}) {
         push(@R, [$category, undef,'PASS',$curr_version, undef, $req_patch, $req_patch, $desc]);
         last;
      } elsif ($req_rev) {
         my $rev = $InstalledP->{base}{$req_base};
         if (!$rev) {
            push(@R, [$category, undef,'NI', $curr_version, undef, undef, $req_patch, $desc]);
            last;
         } elsif ($rev eq $req_rev) {
            push(@R, [$category, undef,'PASS', $curr_version, undef, "$req_base-$rev", $req_patch, $desc]);
         } elsif ($rev < $req_rev) {
            push(@R, [$category, undef,'DOWNREV', $curr_version, undef, "$req_base-$rev", $req_patch, $desc]);
            last;
         } else {
            push(@R, [$category, undef,'UPREV', $curr_version, undef, "$req_base-$rev", $req_patch, $desc] );
            last;
         }
     }
  }
  if ($#R < 0) {
    push(@R, [$category, undef,'ERR',$curr_version,undef, undef,undef,"No patches available for current version" ]);
  }
  return \@R;
}

#
# uses the matrix to verify if all required patches are present on the host
# see HOST in matrix.db
# 'PCI' => {
#      '109189-02' => [ 1, 'ifp' ],
#      '109399-03' => [ '0', 'PCI FC100 fcode' ]
#    },
#    'DEFAULT' => {
#      '108983-08' => [ 1, 'leadville fcip' ],
#      '108982-09' => [ 1, 'leadville fctl, fc & fp' ],
#      '109529-04' => [ 1, 'luxadm' ],
#

sub checkHostPatch {
  my($class, $section, $category, $Matrix, $InstalledP) = @_;

  my $x = $Matrix->{$section}{$category};
  my $cat = "Driver";
  my (@R);
  foreach my $req_patch (keys %$x) {  # read all req. patches

      my $entry = $x->{$req_patch};
      my $present = $entry->[0];
      my $desc    = $entry->[1];
      my($req_base, $req_rev) = $class->baseRev($req_patch);

      if ($present == 0) { # patch not present, probably t3 patch etc..
         push(@R, [$cat, undef, 'PASS', undef, undef, $req_patch, $req_patch, $desc]);

      } elsif ($InstalledP->{patch}{$req_patch}) {
         push(@R, [$cat, undef, 'PASS', undef, undef, $req_patch, $req_patch, $desc]);

      } elsif ($req_rev) {
         my $rev = $InstalledP->{base}{$req_base};
         if (!$rev) {
            push(@R, [$cat,undef, 'NI', undef,undef, undef, $req_patch, $desc]);
         } elsif ($rev eq $req_rev) {
            push(@R, [$cat, undef, 'PASS', undef, undef,"$req_base-$rev", $req_patch, $desc]);
         } elsif ($rev < $req_rev) {
            push(@R, [$cat, undef, 'DOWNREV', undef, undef,"$req_base-$rev", $req_patch, $desc]);
         } else {
            push(@R, [$cat, undef, 'UPREV', undef, undef,"$req_base-$rev", $req_patch, $desc]);
         }
      }
  }
  return \@R;
}

sub readInstalledPatches {
  my($class) = @_;
  my($l,%PATCHES);
  $ERR = undef;
  my($err, $sr) = Util->run_command("/bin/showrev -p", "installed_patches.text");
  if ($err) {
    $ERR = $err;
    return undef;
  }
  foreach $l (@$sr) {
     chop($l);
     my($head, $a,$b,$obs) = split(/\s+/, $l);
     if ($a =~ /\-/) {
       my($base, $rev) = split(/\-/, $a);
       $PATCHES{base}{$base} = $rev if ($rev >  $PATCHES{base}{$base});
     } else {
       $PATCHES{patch}{$a} = 1;
     }
  }
  return \%PATCHES;
}
my $A3500 = undef;

sub a3500Present {
    my($class) = @_;
    my($l);
    return $A3500 if defined($A3500);
    $A3500 = 0;
    
    $ERR = undef;
    my($err, $com) = Util->run_command("/usr/lib/osa/bin/lad","lad1.txt");

   if ("@$com" =~ /No RAID devices found/) {
        return undef;
   }
   elsif ($err){
        $ERR = $err;
        return undef;
   }
   foreach $l (@$com) {
         if ( $l =~ /LUNS:/) {
            $A3500 = 1;
            last;
         }
   }
   return $A3500; 
}


my $A5 = undef;

sub a5kPresent {
  my($class) = @_;
  my($l);
  return $A5 if (defined($A5));
  $A5= 0;

  $ERR = undef;
  my($err, $lp) = Util->run_command("/usr/sbin/luxadm probe", "lux_probe.txt");
  if ($err) {
     $ERR = $err;
     return undef;
  }
  foreach $l (@$lp) {
     if ($l =~ /SENA/) {
        $A5 = 1;
        last;
     }
  }
  return $A5;

}

sub checkAllDrivers {
  my($class) = @_;

  my @LD_ALL_PORTS = split(/\n/, `/usr/sbin/luxadm -e port | grep CONN`);

  my($text, %S);

  $ERR = undef;
  my($err, $mi) = Util->run_command("/usr/sbin/modinfo", "modinfo.text");
  if ($err) {
     $ERR = $err;
     return undef;
  }
  my $mod = "@$mi";

  foreach my $port (@LD_ALL_PORTS) {
     if ($port =~ /socal/) {
        $S{socal} = 1; $S{sf} = 1;

     } elsif ($port =~ /usoc/) {
        $S{usoc} = 1; $S{fp} = 1;

     } elsif ($port =~ /qlc/) {
        $S{qlc} = 1; $S{fp} = 1;

     } elsif ($port =~ /ifp/) {
        $S{ifp} = 1;
     }
  }
  foreach my $k (keys %S) {
    if ($mod !~ / $k /) {
       $err++;
       $text .= "  $k driver not configured\n";
    }
  }
  return ($err, $text);

}

my $DI  = undef;

sub runDiskInquiry {
  my($class, $test) = @_;
  my($l, @L);
  if ($test) {
     open(O,  System->get_home() . "/Test/di.txt");
     while ($l = <O>) {
       push(@L, $l);
     }
     close(O);
     return \@L;
  }
  return $DI if ($DI);

  my $F = System->get_home() . "/bin/disk_inquiry";
  open(O, "$F 2>&1|");
  while ($l = <O>) {
    push(@L, $l);
  }
  close(O);
  $DI = \@L;
  return $DI;
}


#0: c0t0d0      SEAGATE    ST34371W SUN4.2G     7462    9716D29086##    primary                /sbus@1f,0/SUNW,fas@e,8800000/sd@0,0
# 3: c1t1d1      SUN        T300                 0111    0000098111##    alternate

sub checkDiskFirmware {
  my($class, $Matrix, $InstalledP, $a5k_list) = @_;
  my($line, @L);
  
  my $di = $class->runDiskInquiry(0);

  my $disk_fw = $Matrix->{DISK_FW};
  my $category = "A5K_DISK_FW";
  my $cnt= 0;
  my $found=0;
  foreach $line (@$di) {
       next if ($line =~ /No such device/) ;
       next if ($cnt++ < 6) ;
       next if ($line =~ /T300/ );
       my($dev, $vp,  $vp2) = split(/\t+/, Util->ltrim($line));
       if ( is_dev_a5k($dev,$a5k_list) ) {
          $found = 1;
          my($vendor, $prod) = split(/\s+/, $vp, 2);
          my($curr_fw, $serial) = split(/\s+/, $vp2, 2);
          $prod = Util->rtrim($prod);
          my $desc = $prod;

          next if ($prod eq "T3");
          next if ($prod eq "StorEdgeA3500FCd");
          next if ($prod eq "SESS01");

          my $key = "$vendor:$prod:A5000";
          if ($disk_fw->{$key}) {
            my $d = $disk_fw->{$key};
            my $req_fw = $d->[0];
            my $req_patch = $d->[2];
            my($req_base, $req_rev) = $class->baseRev($req_patch);
            my $comp;
            my $min;
            my $max; 
            if ( $req_fw eq "7F7."){ # special case
               $desc = $prod." ----   Note: version 7F7. matches range from 7F70 to 7F7F";
               $min= "7F70";
               $max= "7F7F";
               $comp = $class->compare_special_disk($curr_fw, $min,$max);
            
            } else {   # general case
               $comp = $class->compareFwLevels($curr_fw, $req_fw);
            }
            if ($comp == 0) {
             push(@L, [$category, $dev, 'PASS', $curr_fw, $req_fw, undef, $req_patch, $desc]);
	    } elsif ($comp > 0) {
             push(@L, [$category, $dev, 'UPREV', $curr_fw, $req_fw, undef, $req_patch, $desc]);
	    } elsif ($comp < 0) {
             push(@L, [$category, $dev, 'DOWNREV', $curr_fw, $req_fw, undef, $req_patch, $desc]);
   	    }
	    else {
             push(@L, [$category, $dev, 'ERR', $curr_fw, $req_fw, undef, $req_patch, "Unable to check device "]);
	    }
            
         }
         else {
            push(@L, [$category, $dev, 'ERR', $curr_fw,undef,undef,undef,"Unable to check the $vendor device, please see sunsolve online for potential patch information or contact your SUN representative"]);
         }

      }
   }
   if ($found ==0)
   {
          push(@L, [$category, 'N/A', 'ERR', undef, undef,undef,undef, "No A5k device are present, please check system"]);
   }
   return \@L;
}

sub compare_special_disk {
    my ($fw, $fw_min, $fw_max) = @_;
    my @drive = split(//, $fw);
    my $m;
    foreach my $char (@drive) {
       if (($char ne ".") && ( $char ne "-") && ( $char ne "V")) {
           $m .=$char;
       }
    }
    if (hex($m) > hex($fw_max)){
          return 1;
    } elsif ( hex($m) < hex($fw_min)) {
          return -1;
    } else {
          return 0;
    } 
}

sub is_dev_a5k {
      my ($device, $a5k_ctr_arr) = @_;
      my $where1 = index($device,"c");
      my $where2 = index($device,"t");
      my $num= substr($device,$where1, $where2-$where1);

      foreach my $x (@$a5k_ctr_arr){
         if ($num eq $x){
            return 1; 
         }
      }       
      return 0;
}


 # 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    #
 # if $drive is < $matrix then return -1    #

sub compareFwLevels {
    my ($class, $drive, $matrix) = @_;
    my (@matrix, @drive);

    @matrix = split(//, $matrix); # separate characters
    @drive = split(//, $drive); # separate characters
    my ($m, $d);
    foreach my $char (@matrix) {
      if (($char ne ".") && ( $char ne "-") && ( $char ne "V") && ( $char ne "R") && ( $char ne "E") && ( $char ne "e")){
            ###################################################################################
            #  1) 2G switch version is like V1.37-17-01, so need to remove "V" "." and "-"    #
            #     or "e43220", need remove "e"                                                #
            #  2) TAPE is like "RE98.24.67"                                                   #
            ################################################################################### 
            $m .= $char;
        }
    }

    foreach my $char (@drive) {
       if (($char ne ".") && ( $char ne "-") && ( $char ne "V") && ( $char ne "R") && ( $char ne "E") && ( $char ne "e")) {
            $d .= $char;
       }
    }
 
    if (hex($d) > hex($m)) {
        return 1;
    } elsif (hex($d) < hex($m)) {
        return -1;
    } else {
        return 0;
    }
}

sub devCompare {
   my($class, $key, $elem, $val, $dev, $mat, $type, $info) = @_;

   my $default_info = "no patch information available, please contact your sun representative";
   my $el  = $mat->{$type};

   my $rev   = $el->[0];
   my $patch = $el->[1];
   my $desc  = $el->[2];
   my $comp = ($dev->{name} || $dev->{ip});
   
   if (!$el) {
       return [$key, "$comp.$type", 'ERR' , undef, undef, undef , $patch, 
          "No entry in Matrix for $key.$type" ];
   }
   if ($val < 0) {
        return [$key, "$comp.$type.$elem", 'ERR' , undef, undef, undef , undef, "Device Revision is not available" ]; 
   }

   my $cp = $class->compareFwLevels($val, $rev);
   if ($val eq $rev) {
       if ( ($type eq "enc") || ($type eq "firmrev")){
         return [$key, "$comp.$type",'PASS' , $val, $rev, undef , undef, $info];
       }
       else {
         return [$key, "$comp.$type.$elem", 'PASS' , $val, $rev, undef , undef, $info];
       }
   } elsif ($cp > 0) {
       if ( ($type eq "enc") || ($type eq "firmrev")){
         if (( $patch =~ /xxxx/ ) && ( !$info ) ) {
            return [$key, "$comp.$type", 'UPREV' , $val, $rev, undef , "N/A", $default_info];
         } else {
            return [$key, "$comp.$type.$elem", 'UPREV' , $val, $rev, undef, $patch, $info];
         }
        
       } else {
         return [$key, "$comp.$type.$elem", 'UPREV' , $val, $rev, undef , $patch, $info];
       }
   } elsif ($cp < 0) {
       if (($type eq "enc") || ($type eq "firmrev")) {
          if (( $patch =~ /xxxx/ ) && ( !$info ) ){ 
             return [$key, "$comp.$type",'DOWNREV' , $val, $rev, undef , "N/A", $default_info];
          } else {
             return [$key, "$comp.$type.$elem", 'DOWNREV' , $val, $rev, undef, $patch, $info];
          } 
         
       } else{
           if (( $patch =~ /xxxx/ ) && ( !$info ) ){
               return [$key, "$comp.$type.$elem", 'DOWNREV' , $val, $rev, undef , "N/A", $default_info];
           } else {
              return [$key, "$comp.$type.$elem", 'DOWNREV' , $val, $rev, undef, $patch, $info];
           }

       }
   } else {
       if ($type eq "enc"){
         return [$key, "$comp.$type",'PASS' , $val, $rev, undef , undef, $info];
       } else {
         return [$key, "$comp.$type.$elem", 'PASS' , $val, $rev, undef , undef, $info];
       }
   }
}



sub version_ok {
    my ($class, $spec, $installed) = @_;

    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);
    
    if ($installed_major_num > $spec_major_num) {
        return 2;
    }elsif ($installed_major_num < $spec_major_num) {
        return 0;
    } else { # Check minor number
        if ($installed_minor_num > $spec_minor_num) {
            return 2;
        } elsif ($installed_minor_num < $spec_minor_num) {
            return 0;
        } else { #Check increment
            if ($installed_increment > $spec_increment) {
                return 2;
            }  elsif ($installed_increment < $spec_increment) {
                return 0;
            } else {
                return 1;
            }
        }
    }
    Debug->err(TEXT => "version_ok: Logic error in version_ok subroutine!\n");
    return 0;
}

#
#  ($Present ...) = $class->hba_present();
#

sub hbas_present {
   my($class) = @_;
   my($l);
   my $PRESENT = {};
   my($err, $sr) = Util->run_command("/usr/sbin/luxadm -e port", "luxadme.text");
   $ERR = $err;
   return () if ($ERR);

   my(@luxdiag_on_ports, @luxdiag_off_ports);
   my(%LD_ORIG_PORTS, %socpp, %portnum, %phys_path, %TEST);
   my(@LD_OFF_PORTS);

   foreach my $cur_port (@$sr) {
      next if ($cur_port !~ /CONN/);
      my @cur_port_info = split(/\s+/, $cur_port);
      if ($cur_port =~ /^(.*socal.*):(\d+)(\s+.*)$/ ) {
          $cur_port = "$1/sf\@$2,0:devctl$3";
          $LD_ORIG_PORTS{"$1/sf\@$2,0:devctl"} =  $cur_port_info[0];
      } else {
          $LD_ORIG_PORTS{$cur_port_info[0]} = $cur_port_info[0];
      }
      if ($cur_port =~ /^(\S+)\s+NOT\s+CONNECTED/) {
          # allow offline ports to trigger storstat firmware checks #

          push @luxdiag_off_ports, $1;

          $PRESENT->{SOCAL} = 1 if ($cur_port =~ /^(.*socal.*)\/sf\@(\d+)/);
          $PRESENT->{USOC} = 1 if ($cur_port =~ /^(.*usoc.*)\/fp\@(\d+)/);
          $PRESENT->{QLC}  = 1 if ($cur_port =~ /^(.*qlc.*)\/fp\@(\d+)/);
          $PRESENT->{IFP}  = 1 if ($cur_port =~ /^(.*pci.*ifp\@)(\d+)/);

      } elsif ($cur_port =~ /^(\S+)\s+CONNECTED/) {
          push @luxdiag_on_ports, $1;
      }
    }
    @LD_OFF_PORTS = @luxdiag_off_ports;

    return () if (@luxdiag_on_ports == -1);


    # convert luxdiag output to match disklist and path-to-inst

    foreach my $ldprt (@luxdiag_on_ports) {
        chomp $ldprt;
        if ($ldprt =~ /^(.*socal.*)\/sf\@(\d+)/) {
            $socpp{$ldprt} = "$1";
            $portnum{$ldprt} = $2;
            $phys_path{$ldprt} = $ldprt;
            $phys_path{$ldprt} =~ s/\/devices//;
            $phys_path{$ldprt} =~ s/:devctl//;
            $TEST{$ldprt} = "lbf dex con";
            $PRESENT->{SOCAL} = 1;

        } elsif ($ldprt =~ /^(.*usoc.*)\/fp\@(\d+)/) {
            $socpp{$ldprt} = "$1";
            $portnum{$ldprt} = $2;
            $phys_path{$ldprt} = $ldprt;
            $phys_path{$ldprt} =~ s/\/devices//;
            $phys_path{$ldprt} =~ s/:devctl//;
            $TEST{$ldprt} = "lbf dex con";
            $PRESENT->{USOC} = 1;

        } elsif ($ldprt =~ /^(.*qlc.*)\/fp\@(\d+)/) {
            $socpp{$ldprt} = "$1";
            $portnum{$ldprt} = $2;
            $phys_path{$ldprt} = $ldprt;
            $phys_path{$ldprt} =~ s/\/devices//;
            $phys_path{$ldprt} =~ s/:devctl//;
            $TEST{$ldprt} = "lbf dex";
            $PRESENT->{QLC} = 1;

        } elsif ($ldprt =~ /^(.*pci.*ifp\@)(\d+)/) {
            $socpp{$ldprt} = "$1";
            #$portnum{$ldprt} = $2;
            $portnum{$ldprt} = 0;
            $phys_path{$ldprt} = $ldprt;
            $phys_path{$ldprt} =~ s/\/devices//;
            $phys_path{$ldprt} =~ s/:devctl//;
            $TEST{$ldprt} = "dex con";
            $PRESENT->{IFP}= 1;

        } elsif ($ldprt =~ /^(.*pci.*)\/scsi\@(\d+)/) {
            $socpp{$ldprt} = "$1";
            $portnum{$ldprt} = $2;
            $phys_path{$ldprt} = $ldprt;
            $phys_path{$ldprt} =~ s/\/devices//;
            $phys_path{$ldprt} =~ s/:devctl//;
            $TEST{$ldprt} = "lbf dex con";
            $PRESENT->{IFP} = 1;
        } else {
            print "Error: unknown Host Adapter port $ldprt\n";
        }
    }

    return ($PRESENT, \@luxdiag_on_ports, \@luxdiag_off_ports,
            \%LD_ORIG_PORTS, \%socpp, \%portnum, \%phys_path, \%TEST);
}


sub warning {
    my $class = @_;
    my @rc;
    push (@rc, [undef, undef, undef, undef, undef, undef, undef, "No known patch information about this operating system level at this time, once we get this information, the revision check can be done"]);
    return \@rc; 

} 

sub switch_Compare {
   my($class, $key, $val, $dev, $mat, $type, $info) = @_;

   my $default_info = "no patch information available, please contact your sun representative";
   my $el  = $mat->{$type};

   my $rev   = $el->[0];
   my $patch = $el->[1];
   my $desc  = $el->[2];
   my $comp = ($dev->{name} || $dev->{ip});

   if (!$el) {
       return [$key, "$comp", 'ERR' , undef, undef, undef , $patch,
          "No entry in Matrix for $key.$type" ];
   }
   if ($val < 0) {
        return [$key, "$comp", 'ERR' , undef, undef, undef , undef, "Device Revision is not available" ];
   }

   my $cp = $class->compareFwLevels($val, $rev);
   if ($val eq $rev) {
         return [$key, "$comp",'PASS' , $val, $rev, undef , $patch, $desc];
   } elsif ($cp > 0) {
         if (( $patch =~ /xxxx/ ) && ( !$info ) ) {
            return [$key, "$comp", 'UPREV' , $val, $rev, undef , "N/A", $default_info];
         } else {
            return [$key, "$comp", 'UPREV' , $val, $rev, undef, $patch, $desc];
         }

   } elsif ($cp < 0) {
       if (($type eq "switch") || ($type eq "firmrev") || ($type eq "enc")) {
          if (( $patch =~ /xxxx/ ) && ( !$info ) ){
             return [$key, "$comp",'DOWNREV' , $val, $rev, undef , "N/A", $default_info];
          } else {
             return [$key, "$comp", 'DOWNREV' , $val, $rev, undef, $patch, $desc];
          }

       } else{
           if (( $patch =~ /xxxx/ ) && ( !$info ) ){
               return [$key, "$comp", 'DOWNREV' , $val, $rev, undef , "N/A", $default_info];
           } else {
              return [$key, "$comp", 'DOWNREV' , $val, $rev, undef, $patch, $desc];
           }

       }
   } else {
         return [$key, "$comp.$type",'PASS' , $val, $rev, undef , undef, $desc];
   }
}

sub get_s8_level {  # used by v880 patch check
   my $class = @_;
   my $file = "/etc/release";
   # Solaris 8 2/02 s28s_u7wos_08a SPARC
   open ( tem, $file);
   while (my $l= <tem>) {
     $l =~ s/^\s+//;  # strip the leading space
     my ($solaris, $os_level, $date, $update_level, $sparc ) = split(/\s+/,$l);
     if ($os_level == 9) {
        close (tem);
        return (9);
     } elsif ($os_level == 8) {
        # need get update level
        if ( $update_level =~ /u(\d+)wos/ ) {
          close (tem);
          my $tem_level = $1;
          return ($tem_level);
        }
     } 
   } 
   close (tem); 
   return (-1);    # can not get os update level info
}

1;
