#!/usr/bin/perl

# Don't give warnings about patches for bundled but uninstalled packages
$CHECK_PACKAGES=1;

# Try harder to compare the archicture type
$CHECK_MACH=0;

# Check unbundled products as well as core components of Solaris
$CHECK_UNBUNDLED=1;

# Shows the names of unbundled packages
$SHOW_UNBUNDLED=0;

$0=~s!.*/!!;
$interactive=(-t STDIN) && (-t STDOUT) && (-t STDERR);
$custom=0;
$short=0;
while (@ARGV)
{
	$_=shift;
	if ($_ eq '-h' || $_ eq '--help')
	{
		my $s=" "x(length($0));
		print <<"ENDOFHELP";

Usage: $0 [-i|-q] [-n] [-p pkginfo] [-s showrev] [-x xref] [-a arch] 
       $s [-m mach] [-v ver]

   -i interactive (prints progress dots)
   -q quiet
   -n just output the patch numbers instead of printing formatted output

   -p get package database from specified file instead of 'pkginfo -l'
   -s get patch database from specified file instead of 'showrev -p'
   -x get cross reference file from specified file instead of ./patchdiag.xref
   -a use specified architecture instead of 'uname -p' [sparc|i386|ppc|...]
   -m use specified machine instead of 'uname -m' [sun4u|sun4m|sun4d|...]
   -v use specified OS version instead of 'uname -r' [5.5|5.51|5.6|...]

ENDOFHELP

		exit(0);			
	}
	($interactive=1, next) if $_ eq '-i';
	($interactive=0, next) if $_ eq '-q';
	($short=1, next) if $_ eq '-n';
	if ($_=~/^-[psxam]$/)
	{
		$arg=shift || die("$0: $_ needs an argument");
	}
	else
	{
		die("$0: Unrecognised option $_\n");
	}
	$custom=1;
	($pkginfofile=$arg, next) if $_ eq '-p';
	($showreffile=$arg, next) if $_ eq '-s';
	($xreffile=$arg, next) if $_ eq '-x';
	($arch=$arg, next) if $_ eq '-a';
	($mach=$arg, next) if $_ eq '-m';
	($osver=$arg, next) if $_ eq '-v';
}

$pkginfofile="pkginfo -l |" unless defined($pkginfofile);
$showrevfile="showrev -p |" unless defined($showrevfile);
$xreffile="patchdiag.xref" unless defined($xreffile);
unless (defined($arch))
{
	chomp($arch=`uname -p`) || die("error from uname -p\n");
}
unless (defined($mach))
{
	chomp($mach=`uname -m`) || die("error from uname -m\n");
}
unless (defined($osver))
{
	chomp($osver=`uname -r`) || die("error from uname -r\n");
}
$osver=~s/^5/2/;
unless ($custom)
{
	chomp($machine=`uname -n`) || die("error from uname -n\n");
}

print STDERR "Reading package database." if $interactive;
open(PKGINFO, $pkginfofile) || die("Can't open pkginfo ($pkginfofile): $!\n");
my $pack;
my $pc=0;
while (<PKGINFO>)
{
	$pc=0 if $pc++==100; print STDERR "." if $interactive && !$pc;
	chomp();
	$pack=$' if /^\s*PKGINST\s*:\s*/;
	$pkg{"$pack:$'"}=1 if /^\s*VERSION\s*:\s*/;
}
close(PKGINFO);

print STDERR "\nReading patch cross reference file." if $interactive;
open(XREF, $xreffile) || die("Can't open cross reference file ($xreffile): $!\n");
chomp($xrefdate=<XREF>);
$xrefdate=~s/^.* OF (.*) \#\#$/$1/;
$pc=0;
while (<XREF>)
{
	$pc=0 if $pc++==100; print STDERR "." if $interactive && !$pc;
	chomp();
	my ($patch, $rev, $date, 
	    $recommended, $security, $obsolete, $badpatch, 
	    $vers, $archs, $pkgs, $synopsis)=split /\|/;
	undef $security unless $security eq 'S';
	undef $recommended unless $recommended=~/^[?R]$/;
	undef $obsolete unless $obsolete eq 'O';
	undef $badpatch unless $badpatch eq 'B';
	$oldrev=(@{$xref{$patch}})[1];
	next if defined($oldrev) && $oldrev>$rev;
	$xref=$xref{$patch}=[$patch, $rev, $date, 
			     $recommended, $security, $obsolete, $badpatch, 
			     $vers, $archs,  $pkgs, $synopsis];
	$irev{$patch}=" -";

	next unless $vers eq $osver
	    || ($CHECK_UNBUNDLED && $vers eq "Unbundled");

	next unless $CHECK_MACH || $archs=~/$arch[\.;]|all;/;
	my $found=0;
	if ($CHECK_MACH)
	{
		$archs=~s/;$//;
		my @archs=split(/;/, $archs);
		my $longestsub='';
		for (@archs)
		{
			($found=1, last) if $_ eq 'all';
			($found=1, last) if $_ eq "$arch.$mach";
			if ("$arch.$mach"=~/^$_/)
			{
				$longestsub=$_ 
				    if length($_)>length($longestsub);
				$found=2;
				next;
			}
			$found=0 if "$arch.$mach"!~/^$_/ && /^$longestsub/;
		}
	       	next unless $found;
	}

	if ($CHECK_PACKAGES || $vers eq "Unbundled")
	{
		my $found=0;
		$pkgs=~s/;$//;
		my @pkgs=split(/;/, $pkgs);
		for (@pkgs)
		{
			($found=1, last) if exists($pkg{$_});
		}
		next unless $found;
	}

	$uninstrecommended{$patch}=$xref if $recommended;
	$uninstsecurity{$patch}=$xref if $security;
}
close(XREF);

print STDERR "\nReading patch database." if $interactive;
open(SHOWREV, $showrevfile) || die("Can't open showrev ($showrevfile): $!\n");
$pc=0;
while(<SHOWREV>)
{
	$pc=0 if $pc++==100; print STDERR "." if $interactive && !$pc;
	next unless /^Patch: (\d+)-(\d+)/;
	my ($patch, $irev)=($1, $2);
	next if defined($irev{$patch}) && $irev{$patch}>$irev;
	if (exists($xref{$patch}))
	{
		my $xref=$xref{$patch};
		my ($xrev, $recommended, $security, $synopsis)
		    =(@{$xref})[1,3,4,10];
		$irev{$patch}=$irev;
		delete $uninstrecommended{$patch};
		delete $uninstsecurity{$patch};
		if ($irev==$xrev)
		{
			delete $outofdaterecommended{$patch};
			delete $outofdatesecurity{$patch};
		}
		else
		{
			$outofdaterecommended{$patch}=$xref if $recommended;
			$outofdatesecurity{$patch}=$xref if $security;
		}
	}
}
close(SHOWREV);
print STDERR "\n" if $interactive;

print "PATCH REPORT FOR $xrefdate", $custom?"\n":" - $machine\n" unless $short;
format LONG=
@<<<<<<@<< @<< @@ @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$patch, $xrev, $irev{$patch}, $recommended, $security, $synopsis
~                 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$pkgs
.
format SHORT=
@<<<<<-@<<
$patch, $xrev
.
$~ = ('LONG', 'SHORT')[$short];

header('OUT OF DATE RECOMMENDED PATCHES (not including security patches)');
$done=0;
for (keys (%outofdaterecommended))
{
	($patch, $xrev, $recommended, $security, $vers, $pkgs, $synopsis)
	    =(@{$outofdaterecommended{$_}})[0,1,3,4,7,9,10];
	unless ($security)
	{
		if ($SHOW_UNBUNDLED && $vers eq 'Unbundled')
		{
			$pkgs=~s/:.*;/;/g;
			$pkgs=~s/;$//;
		}
		else
		{
			$pkgs='';
		}
		write;
		$done=1;
	}
}
print "(No patches)\n" unless $done || $short;

header('UNINSTALLED RECOMMENDED PATCHES (not including security patches)');
$done=0;
for (keys (%uninstrecommended))
{
	($patch, $xrev, $recommended, $security, $vers, $pkgs, $synopsis)
	    =(@{$uninstrecommended{$_}})[0,1,3,4,7,9,10];
	unless ($security)
	{
		if ($SHOW_UNBUNDLED && $vers eq 'Unbundled')
		{
			$pkgs=~s/:.*;/;/g;
			$pkgs=~s/;$//;
		}
		else
		{
			$pkgs='';
		}
		write;
		$done=1;
	}
}
print "(No patches)\n" unless $done || $short;

header('OUT OF DATE SECURITY PATCHES');
$done=0;
for (keys (%outofdatesecurity))
{
	($patch, $xrev, $recommended, $security, $vers, $pkgs, $synopsis)
	    =(@{$outofdatesecurity{$_}})[0,1,3,4,7,9,10];
	if ($SHOW_UNBUNDLED && $vers eq 'Unbundled')
	{
		$pkgs=~s/:.*;/;/g;
		$pkgs=~s/;$//;
	}
	else
	{
		$pkgs='';
	}
	write;
	$done=1;
}
print "(No patches)\n" unless $done || $short;

header('UNINSTALLED SECURITY PATCHES');
$done=0;
for (keys (%uninstsecurity))
{
	($patch, $xrev, $recommended, $security, $vers, $pkgs, $synopsis)
	    =(@{$uninstsecurity{$_}})[0,1,3,4,7,9,10];
	if ($SHOW_UNBUNDLED && $vers eq 'Unbundled')
	{
		$pkgs=~s/:.*?;/;/g;
		$pkgs=~s/;$//;
	}
	else
	{
		$pkgs='';
	}
	write;
	$done=1;
}
print "(No patches)\n" unless $done || $short;

sep();
print "END OF PATCH REPORT\n" unless $short;

sub header
{
	return if $short;
	sep();
	print "\n$_[0]\n";
	print "  Latest  Inst RS Synopsis\n--------- ---- -- ", "-"x61, "\n";
}

sub sep
{
	print "="x79,"\n" unless $short;
}
