#!/usr/bin/perl

# 
# Compare patches installed across Sun servers
# NEPD Consulting
# Paul Guglielmino <paulg@nepd.com>
#
# Please send improvements or comments to me!
#

#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#

use Getopt::Long;
#use strict;
#use warnings;

my($version) = "1";

my($want_only_diffs, $showrev, $patchk, $debug, $usage, $v, $noheader);
Getopt::Long::Configure( 'permute' );
GetOptions( 'diffs' => \$want_only_diffs, 
	    'showrev' => \$showrev, 'patchk' => \$patchk,
	    'debug' => \$debug, 'help' => \$usage,
	    'version' => \$v, 'noheader' => \$noheader );

if ( $usage ) { print_usage(); exit 0; }
if ( $v ) { print_version(); exit 0; }

#####
my(@server_files, @server_patch_levels) = ();
my(%server_patch_levels) = ();

foreach my $datafile (@ARGV) {
  if ( ! -f $datafile ) {
    print "File $datafile not available, skipping\n";
  } else {
    push( @server_files, $datafile );
  }    
}

if ( $#server_files < 1 ) {
  print "Sorry need at least two files to analyze.\n";
  print_usage();
  exit 2;
}

# Assume showrev output unless told otherwise
if ( $patchk ) {
  $sub_ref = \&parse_patchk_file;
} else {
  $sub_ref = \&parse_showrev_file;
}

# Load each file into memory
for my $i (0..$#server_files) {
  $debug && print "Parsing $server_files[$i] ...\n";
  open(F, "$server_files[$i]");
  &$sub_ref(F,$i);
  close(F);
}

if ( ! $noheader ) {
  print_header();
}

my($patchdiffs) = 0;
my(%count) = ();

# Determine all patch ids in use by iterating over all servers and patches
foreach my $s (keys %server_patch_levels) {
  foreach my $p (keys %{$server_patch_levels{$s}}) {
    $count{$p} = 1;
  }
}

my($numpatches) = scalar keys %count;
my(@all_patches) = keys %count;

# Key off the patch id to see what level each server is at
foreach my $p (@all_patches) {
  my($linestring, $diff_string) = "";
  my($diff_patch_flag) = 0;

  for my $i (0..$#server_files) {
    $linestring .= sprintf " %s: %3s |", $server_files[$i], $server_patch_levels{$i}{$p} || "N/A";
  }

  for my $j (0..$#server_files-1) {
    if ( $server_patch_levels{$j}{$p} != $server_patch_levels{$j+1}{$p} ) {
      $diff_patch_flag = 1;
    }
  }

  if ( $diff_patch_flag ) {
    $patchdiffs++;
    $diff_string .= "*";
  } 

  if ( (! defined $want_only_diffs) ||
       ($diff_patch_flag && $want_only_diffs)  ) {
      printf "%-6s%-2s || %s\n", "$p", $diff_string, $linestring; 
    }
}

if ( ! $noheader ) {
  print "\nDifferent patches: $patchdiffs out of total $numpatches\n";
}

exit 0;


# Load patch information from "showrev -p" file
sub parse_showrev_file {
  my($fh,$i) = @_;
  while( <$fh> ) {
    # Match only the lines that have installed patches
    if ( /^Patch: (\d{6})-(\d{2})/ ) {
      $debug && print "Matched patch: $1 - $2\n";
      # Load the patch and revision number into our hash if it:
      # Is not already there or if it is there with a lower revision number
      if ( (! defined $server_patch_levels{$i}{$1}) ||
	   ($server_patch_levels{$i}{$1} < $2) ) { 
	$server_patch_levels{$i}{$1} = $2;
      }
    }
  }
}

# Load patch information from Sun "patchk.pl" file
sub parse_patchk_file {
  my($fh,$i) = @_;
  while( <$fh> ) {
    if ( /^(\d{6})    (\d{2})/ ) {
      $debug && print "Matched patch: $1 - $2\n";
      # Load the patch and revision number into our hash if it:
      # Is not already there or if it is there with a lower revision number
      if ( (! defined $server_patch_levels{$i}{$1}) ||
           ($server_patch_levels{$i}{$1} < $2) ) {
        $server_patch_levels{$i}{$1} = $2;
      }
    }
  }
}

# Print results header to the screen
sub print_header {
  $line = "Patch ID ||  ";
  foreach $s (@server_files) {
    $len = length($s) - 1;
    $line .= "Rev #" . " "x$len . "  | ";
  }
  print "$line\n";
}

sub print_usage {

  print<<EOT;

Show differences between patch levels on Solaris servers

Usage: $0 [OPTIONS]... [FILES]...
\t --help              [This message]
\t --debug             [Show debugging output]
\t --diff              [Show only differences]
\t --noheader          [Print header (default)]
\t --showrev           [Input files are in showrev -p format (default)]
\t --patchk            [Input files are in Sun patchk.pl format]
\t --version           [Print version number]
EOT

}

sub print_version {
  print "\n$0: Version $version\n\n";
}

