#!/bin/sh -- # Really perl
eval 'exec perl $0 ${1+"$@"}'
if 0;
#########################################################################
# Name: PatchDiag Tool
#
# Revision: 1.25 
#
# External version #: 1.0.4
#
# Description
# ===========
# 
# The PatchDiag Tool is a diagnostic tool that enables system administrators
# to examine a profile of the patches installed on their Solaris system against
# the most current profiles available from Sun Microsystems with respect to:
# 
#  - latest revisions
#  - recommended patches
#  - security patches
#  - year 2000 patches
#  - other patches relevant to the software environment
# 
# The results of PatchDiag Tool can help a system administrator quickly
# determine and analyze the need for applying additional patches or newer 
# revisions of existing patches.
# 
# PatchDiag Tool provides only patch profile information.  PatchDiag does not
# deliver and apply any patches.  If a system administrator wants to acquire
# and apply additional patches to a system, the system administrator should	
# use their normal Sun authorized service channel, or can access patches		
# via the web at:								 
# 
# http://sunsolve.sun.com/private-cgi/patchpage.pl  
# 
# For installation and other notes, please read the README file that comes with
# the PatchDiag Tool.
#
# Notes
# =====
#	First of all credit should go to: Charlie Labrec for lot of
#	the concepts that this script uses that I borrowed from his
#	"pch". Also to Martin Englund for some of the concepts in his 
#	"showrev-cruncher".
#
#########################################################################
#
#    This program is distributed WITHOUT ANY WARRANTY; without even the 
#    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
#    PURPOSE.  
#
#########################################################################
#
#       Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
#             Manufactured in the United States of America.
# 2550 Garcia Avenue, Mountain View, California, 94043-1100 U.S.A.
# 
# This product and related documentation is protected by copyright and
# distributed under licenses restricting its use, copying, distribution
# and decompilation.  No part of this product or related documentation
# may be reproduced in any form by any means without prior written
# authorization of Sun and its licensors, if any.
# 
# Portions of this product may be derived from the UNIX(R) and Berkeley
# 4.3 BSD systems, licensed from UNIX Systems Laboratories, Inc. a wholly
# owned subsidiary of Novell, Inc. and the University of California,
# respectively.  Third party software, including font technology, in this
# product is protected by copyright and licensed from Sun's suppliers.
# 
# RESTRICTED RIGHTS LEGEND:  Use, duplication, or disclosure by the
# government is subject to restrictions as set forth in subparagraph
# (c)(1)(ii) of the Rights in Technical Data and Computer Software clause
# at DFARS 252.227-7013 and FAR 52.227-19.
# 
# Sun, Sun Microsystems, the Sun Logo, Solaris, SunOS, SunSoft, the
# SunSoft Logo, ONC, NFS, OpenWindows, DeskSet, AnswerBook, SunLink,
# SunView, SunDiag, NeWS, OpenBoot, OpenFonts, SunInstall, SunNet,
# ToolTalk, X11/NeWS and XView are trademarks or registered trademarks of
# Sun Microsystems, Inc in the U.S.  and certain other countries.  OPEN
# LOOK is a registered trademark of Novell, Inc.  UNIX is a registered
# trademark in the U.S. and other countries, exclusively licensed through
# X/Open, Ltd.  PostScript is a registered trademark of Adobe Systems,
# Inc.  All other product names mentioned herein are the trademarks of
# their respective owners.
# 
# All SPARC trademarks, including the SCD Compliant Logo, are trademarks
# or registered trademarks of SPARC International, Inc.  SPARCstation,
# SPARCserver, SPARCengine, SPARCworks and SPARCompiler are licensed
# exclusively to Sun Microsystems, Inc.  Products bearing SPARC
# trademarks are based upon an architecture developed by Sun
# Microsystems, Inc.
# 
# The OPEN LOOK(R) and Sun(TM) Graphical User Interfaces were developed
# by Sun Microsystems, Inc. for its users and licensees.  Sun
# acknowledges the pioneering efforts of Xerox in researching and
# developing the concept of visual or graphical user interfaces for the
# computer industry.  Sun holds a non-exclusive license from Xerox to the
# Xerox Graphical User Interface, which license also covers Sun's
# licensees who implement OPEN LOOK GUIs and otherwise comply with Sun's
# written license agreements.
# 
# The X Window System is a product of the Massachusetts Institute of
# Technology.
# 
#########################################################################
#
$usage = <<"THEEND";

Usage:  patchdiag [ -l ]
                 [ -s <sfile> <os_ver arch> [<remotename>] ]
                 [ -p <pfile> <sfile> <os_ver> <arch> [<remotename>] ]
                 [ -x <xref> ]
                 [ -h | -? ]

        where:
       
        <arch>   is the architecture of the system where the data was obtained. 
        	        Valid settings for <arch> are: 					
       
        	        - sparc
        	        - i386
        
        	        If the architecture of the system is not specified, the system
        	        assumes 'sparc'.
       
        <sfile>  is a file containing showrev -p output.
       
        <pfile>  is a file containing pkginfo -l output.
       
        <os_ver> is the OS version; this must be a SunOS number, not Solaris. 
        	        Use "5.5", not "2.5" for "Solaris 2.5".

        <remotename> is remote host name other than that is on the same host
                     machine.

	<xref>	 is a file containing the patch cross-reference data; the default 
		 name for this is "patchdiag.xref". 

        With no flags, the PatchDiag Tool will invoke a showrev -p on the
        system where it is running and print out the standard audit report
        which contains the installed, uninstalled recommended and uninstalled
        security patches.
	
        -l	prints out a long audit report which includes other patches
		pertinent to packages that have been already installed.

	-s <sfile> <os_ver> <arch>
		prints out the standard audit report using file containing
		the output from showrev -p.

	-p <pfile> <sfile> <os_ver> <arch>
		prints out the long audit report using files containing
		the outputs from showrev -p and pkginfo -l.

	-x <xref>
		uses a different cross reference file than that
		is in the same directory as the patchdiag script.

	-h | -?	
		prints this help message.

THEEND

# run in debug mode
#$debug = 1;

$separator= "======================================================================================\n";

$sec_header=<<'ROGUE';
Patch  Installed Latest   Synopsis
  ID   Revision  Revision
------ --------- -------- ------------------------------------------------------------
ROGUE

$sec_header_w_age=<<'ZION';
Patch  Ins Lat Age  Require    Incomp  Synopsis
  ID   Rev Rev        ID         ID
------ --- --- --- --------- --------- -----------------------------------------
ZION

$pkg_header=<<'LARN';
Patch    Package   Lat Age Synopsis
  ID     Name      Rev
------ - --------- --- --- ------------------------------------------------------------
LARN

$disclaimer=<<'MORIA';
Report Note:

Recommended patches are considered the most important and highly
recommended patches that avoid the most critical system, user, or
security related bugs which have been reported and fixed to date.
A patch not listed on the recommended list does not imply that it
should not be used if needed.  Some patches listed in this report
may have certain platform specific or application specific dependencies
and thus may not be applicable to your system.  It is important to
carefully review the README file of each patch to fully determine
the applicability of any patch with your system.
MORIA

$PROGNAME = "patchdiag";
$REVISION = "1.25";
$VERSION = "1.0.4";

#============================================== 
# Setting parameters
#============================================== 

# include timelocal from standard library
#require "timelocal.pl";
use Time::Local;

# location of the cross reference file
$DATADIR=@ENV{XREF};
if ( $DATADIR eq '' ) {
	$DATADIR=$0;
	if ( $DATADIR !~ /\// ) {
		$DATADIR='.';
	}
	else {
		$DATADIR=~s/\/[^\/]*$//;
	}
}

$XREF="$DATADIR/patchdiag.xref";


#============================================== 
# Subroutines
#============================================== 
sub alreadydisplayed{
#	
#	true if patch has already been displayed
#
	local($searchid) = $_[0];
	shift @_;
	local(@searcharray) = @_;
	local($i);
#	$TRUE = 1;
#	$FALSE = 0;
        foreach $i ( @searcharray ) {
             if ($searchid == $i) { return $TRUE }
        }
	return $FALSE
}

sub checkpkginfofile{
#
#	true if pkginfo is specified, exists and is readable
#
   	$pfile = shift @ARGV;
	if ( $pfile eq '' ) { 
		print "$PROGNAME: no pkginfo file specified.\n";
		print "\t  Rerun command with proper parameters \n";
		die "$usage"; 
	}
	-r $pfile || die "$PROGNAME: Can't find pkginfo file ($pfile)!";
}

sub checkshowrevfile{
#
#	true if showrev file is specified, exists and is readable
#
   	$sfile = shift @ARGV;
	if ( $sfile eq '' ) { 
		print "$PROGNAME: no showrev file specified.\n";
		print "\t  Rerun command with proper parameters \n";
		die "$usage"; 
	}
        -r $sfile || die "$PROGNAME: Can't find showrev file ($sfile)!"
}

sub checkarch{
#
#	true if valid architecture
#
	$system_arch = shift @ARGV;
	if ( $system_arch ne "sparc" && 
	     $system_arch ne "i386" &&
	     $system_arch ne "ppc" &&
	     $system_arch ne "" ) {
		print "$PROGNAME: incorrect architecture parameter ($system_arch)\n";
		print "\t  Rerun command with correct arch.\n";
		print "\t  $PROGNAME accepts: \'sparc\', \'i386\' and \'ppc\'.\n";
		exit;
	}
}

sub checkos{
#
#	true if OS type is valid
#
	$system_os = shift @ARGV; 
	if ( $system_os !~ /[0-9][0-9]*\.[0-9].*/) {
		print "$PROGNAME: OS version specified ($system_os)\n";
		print "\t  doesn't seems to be valid.\n";
		print "\t  Please specify a valid SunOS version number\n";
		print "\t  such as '5.5' and try again.\n";
		exit;
	}
}

sub checkhostname{
#
#       set flag on if remote host name is specified
#
        $hostname = shift @ARGV;
        if ( $hostname ne "" ) { $localhost = $FALSE; }
}

sub checkxref{
#
#	true if xref file exists and is readable
#
	if ( ! -e $XREF ) {
		print "$PROGNAME: patchdiag cross reference file specified by\n";
		print "\t  the environment variable XREF ($XREF) or the -x option\n";
		print "\t  does not exist.\n";
		print "\t  Please check the directory and make sure the file\n";
		print "\t  exists or please modify the XREF environment variable\n";
		print "\t  in the startup script.\n";
		exit;
	}
	if ( ! -r $XREF ) {
		print "$PROGNAME: patchdiag cross reference file specified by\n";
		print "\t  the environment variable XREF ($XREF) or the -x option\n";
		print "\t  is not readable.\n";
		print "\t  Please check the permissions on the file and the directory\n";
		print "\t  and modify if necessary before retrying.\n";
		exit;
	}
	if ( ! -f $XREF ) {
		print "$PROGNAME: patchdiag cross reference file specified by\n";
		print "\t  the environment variable XREF ($XREF) or the -x option\n";
		print "\t  is not a file.\n";
		print "\t  Please check the path or the name that you specified\n";
		print "\t  and modify if necessary before retrying.\n";
		exit;
	}
}

sub calculateage{
#
#       calculate the age of the patch according to current date
#
        $DAY = 24 * 60 * 60;
        %MONTHS = ( "Jan", 0,
                    "Feb", 1,
                    "Mar", 2,
                    "Apr", 3,
                    "May", 4,
                    "Jun", 5,
                    "Jul", 6,
                    "Aug", 7,
                    "Sep", 8,
                    "Oct", 9,
                    "Nov", 10,
                    "Dec", 11 );
        ( $lmonth, $day, $year ) = split(/\//, $_[0]);
        $month = $MONTHS{$lmonth};
        $start = timelocal( 1, 1, 1, $day, $month, $year );
        $end = time;
#print "this is test:start=$start,end=$end,day=$day,month=$month,yr=$year\n";
        $age = int( ( $end - $start ) / $DAY );
}

sub retrieveids{
#
#	retrieve require or incompatible patch ids which are embedded in architecture or
#	package field
#
	$i = 0;
	local(@temp_list) = split( /\;/, $_[0] );
        local(@id_list);
	foreach $token( @temp_list ) {
		if ( $token =~ /^\d+/ ) {
			$id_list[$i++] = $token;
		}
	}
	return @id_list;
}

sub preformat{
#
#	preformat the patch id print
#	
	$reqId = "         ";
	$incmpId = "         ";
	$reqCount = @req_list;
	$incmpCount = @incmp_list;
	$firstTime = $TRUE;

	if (($reqCount | $incmpCount) == 0)
	{
		&printuninstalled;
	}
	else {
		while ( ( $reqCount != 0 ) || ( $incmpCount != 0 ) ) {
			$reqId = "         ";
			$incmpId = "         ";
			if ($reqCount != 0) {
				$reqId = shift( @req_list );
				$reqCount = @req_list;
			}
			if ($incmpCount != 0) {
				$incmpId = shift( @incmp_list );
				$incmpCount = @incmp_list;
			}

			if ($firstTime eq $TRUE) {
				&printuninstalled;
				$firstTime = $FALSE;
			}
			else {
				&printpatchid;
			}
		}
	}
}

sub printinstalled{
	print "$id    $irev        $crev    $synopsis\n";
}

sub printcurrent{
	print "$id    $irev     CURRENT  $synopsis\n";
}

sub printuninstalled{
	printf "%6d %3s %3s %3d %9s %9s %s\n", $id, $irev, $crev, $age, $reqId,
$incmpId, $synopsis;
}

sub printpatchid{
        print "                   $reqId $incmpId\n";
}

sub printlong{
        printf "%6d %1s %-10s %2s %3d %s\n", $id, $glyph, $package, $crev, $age, $synopsis;

}

sub printpkg{
	print "         $package\n";
}
#============================================== 
# BEGIN
#============================================== 

# Boolean defines
$TRUE = 1;
$FALSE = 0;

$blank=' ';

# process the commandline arguments

$mode = "AUDIT";     # default mode
$datamode = "LOCAL"; # default data mode - xref coadjacent
$localhost = $TRUE;

while ( @ARGV ) {
	$_ = shift;
	if    (/-[^- ]*l/ ) { $mode = "LONG"; }
	elsif (/-[^- ]*p/ ) { 
		$mode = "PKGINFO"; 

		&checkpkginfofile;
		&checkshowrevfile;
		&checkos;
		&checkarch;
		&checkhostname;
	}
	elsif (/-[^- ]*s/ ) { 
		if ( $mode ne "PKGINFO" ) { $mode = "SHOWREV"; }

		&checkshowrevfile;
		&checkos;
		&checkarch;
		&checkhostname;
	}
	elsif (/-[^- ]*x/ ) { 
		$datamode = "EXTRA";
		$XREF = shift;
	}
	elsif (/-[^- ]*[h?]/ ) { 
		print $usage;
		exit;
	}
	else { 
		print "Unknown option: $_\n";
		print "$usage";
		die "$PROGNAME: Please specify the correct option and try the command again.\n";
	}
}

&checkxref;
# if mode requires input files, then make sure that the system_os is specified
if (( $mode eq "PKGINFO" || $mode eq "SHOWREV" ) && $system_os eq '' ) {
	print "$PROGNAME: no os version specified.\n";
	print "\t  Please specify the SunOS version of the system from which the\n";
	print "\t  information was obtained and rerun the command.\n";
	print "\t  Please note that SunOS version numbers are like \"5.4\".\n";
	exit;
}
	
if ( $mode eq "PKGINFO" && $sfile eq '' ) {
	print "$PROGNAME: -p option requires a showrev output file as well.\n";
	print "\t  Please provide the name of the showrev output file and try\n";
	print "\t  the command again\n";
	exit;
}

# capture the uname output - needed for all options

open( UNAME, "/usr/bin/uname -nrp |" );
$uname = <UNAME>;
close( UNAME );

if ( $mode eq "AUDIT" || $mode eq "LONG" ){
# 	parse uname output to get OS & Arch
	($name, $os, $arch) = split( / /, $uname);
	$arch =~ s/[\n\t ]*//g;
	if ( $arch eq "" ) { $arch = "ERROR";}
}
else {
	$name = "Remote System";
	$arch = "sparc";
	if ( $system_arch ne '' ){ $arch = $system_arch;}
	$os = $system_os;
}

if ($localhost == $FALSE) {$name=$hostname};

# capture the date stamp of the XREF file

open( XREF, "head -1 $XREF |");
$datestamp=<XREF>;
$datestamp=~s/^.* OF //;
$datestamp=~s/ ##//;

# standard processing

if ( $mode eq "AUDIT" || $mode eq "LONG" || $mode eq "PKGINFO" || $mode eq "SHOWREV" )
{
print $separator;
print "System Name: $name\t SunOS Vers: $os\t Arch: $arch\n";
print "Cross Reference File Date: $datestamp\n";
print "PatchDiag Version: $VERSION\n";
print $separator;
print $disclaimer;
print $separator;

print "INSTALLED PATCHES\n";

print $sec_header;

#	capture the showrev listing
	if( $mode eq "SHOWREV" || $mode eq "PKGINFO" ) 
	{
		open( SHOWREV, "<$sfile" );
	}
	else
	{
		open( SHOWREV, "/usr/bin/showrev -p |" );
	}
	
	$/="";
	$showrev = <SHOWREV>;
	$/="\n";
	close( SHOWREV );
	
#	split showrev output, so that we get one patch per line
	@showrev = split(/\n/, $showrev); 
	
#	pick out the patch number (######-##)
#	and put it into an associative array with the patch number as key
	foreach $line (@showrev) {
		if ($line =~ m/^Patch:\s+(\d{6})-(\d{2}).*/) {
	
			#Via match above $1 is patchid, $2 is rev

			if( $installed_patches{$1} eq '' || $2 > $installed_patches{$1} ) {
				$installed_patches{$1} = $2;
				$installed_patches++;
			}
			# else the patch is an old rev of the patch & we just ignore
		}
	}
	
	
# 	Read cross-reference file into memeory
#
	open(XREF, "<$XREF");
	$/="";
	$xref = <XREF>;
	$/="\n";
	close XREF;

	@xref = split( /\n/, $xref );

#	iterate thru all installed patches...
	foreach $id (sort(keys(%installed_patches))) {
		@ret=grep( /^$id\|/, @xref );
		@return = reverse sort @ret;
	
		( $junk1, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $junk2, 
		  $archs, $pkgs, $synopsis ) = split( /\|/, $return[0]); # this ok since
									 # we should get
									 # 1 line back
	
		$irev = $installed_patches{$id};
		if ( @return eq "" ) { 
			$crev = "N/A";
			$synopsis = "NOT FOUND IN CROSS REFERENCE FILE!" 
		}
		if ( $irev == $crev ) { &printcurrent; }
		else { &printinstalled; }

#		Store id number in mentioned array
		@displayed[++$#displayed] = $id;
	}
	
#	find all the recommended patches for OS that's NOT installed
	
#	print new headers for recommended patches
	print $separator;
	print "\n";
	print "UNINSTALLED RECOMMENDED PATCHES\n\n";
	print $sec_header_w_age;
	
	$foundone = $FALSE;
	($solaris_vers = $os) =~ s/^5/2/;
#
# Check for Solaris 7 and above
#
        $os7 = substr($os,2,1);   # get the number after the 1st (.) of the SunOS

        if ( $os7 ge 7 ) { $solaris_vers = $os7 };
#
        if ($arch =~ "i386") {$solaris_vers = $solaris_vers."_x86";}

	@recmd=grep( /$arch[\.;]|all/, grep( /\|R\|/, grep( /\|$solaris_vers\|/, @xref ) ) );

	foreach $recommend ( @recmd ) {
		( $id, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $junk2, 
	  	  $archs, $pkgs, $synopsis ) = split( /\|/, $recommend);
	
		if ( $rFlag ne "R" ) { next;}
		if ( $archs !~ /$arch[\.;]|all/ ) { next;}
		$irev = "N/A";

		@req_list = &retrieveids( $archs );
		@incmp_list = &retrieveids( $pkgs );

		$already_installed = $FALSE; 
		foreach $i ( keys(%installed_patches) ) {
			if ($id == $i) { $already_installed = $TRUE;last }
		}
		if ( $already_installed ne $TRUE ) {
			&calculateage( $reldate );
			&preformat;

#			Store id number in mentioned array
			$displayed[++$#displayed] = $id;
	
#			Setting this for nice print out
			$foundone = $TRUE;
		}
	}
	if ( $foundone ne $TRUE) {print "All Recommended patches installed!\n"}
	print $separator;
	close( RECMD );
	
#	find all the security patches for OS that's NOT installed
	
#	print new headers for recommended patches
	print "\n";
	print "UNINSTALLED SECURITY PATCHES\n\n";
	print "NOTE: This list includes the Security patches that are also Recommended\n\n";
	print $sec_header_w_age;
	

	$foundone = $FALSE;
	@scrty=grep( /\|S\|/, grep( /\|$solaris_vers\|/, @xref ) );

	foreach $security( @scrty ) {
		( $id, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $junk2, 
	  	  $archs, $pkgs, $synopsis ) = split( /\|/, $security);
	
		if ( $sFlag ne "S" ) { next;}
		if ( $archs !~ /$arch[\.;]|all/ ) { next;}
		$irev = "N/A";

                @req_list = &retrieveids( $archs );
                @incmp_list = &retrieveids( $pkgs );

		$already_installed = $FALSE; # using F and T as booleans here.
		foreach $i ( keys(%installed_patches) ) {
			if ($id == $i) { $already_installed = $TRUE;last }
		}
		if ( $already_installed ne $TRUE ) {
			&calculateage( $reldate );
			&preformat;

#			Store id number in mentioned array
			$displayed[++$#displayed] = $id;
			$foundone = $TRUE;
		}
	}
	if ( $foundone ne $TRUE) {print "All security patches installed!\n"}
	print $separator;
	close( SECURITY );

#	find all the Y2K patches for OS that's NOT installed
	
#	print new headers for recommended patches
	print "\n";
	print "UNINSTALLED Y2K PATCHES\n\n";
	print "NOTE: This list includes the Y2K patches that are also Recommended\n\n";
	print $sec_header_w_age;
	

	$foundone = $FALSE;
	@y2k_list=grep( /\|Y/, grep( /\|$solaris_vers\|/, @xref ) );

	foreach $y2k( @y2k_list ) {
		( $id, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $junk2, 
	  	  $archs, $pkgs, $synopsis ) = split( /\|/, $y2k);
#		if ( ( $byFlag ne "Y " ) && ( $byFlag ne "YB" ) ){ next;}
		if ($byFlag !~ /Y/){ next;}
		if ( $archs !~ /$arch[\.;]|all/ ) { next;}
		$irev = "N/A";

                @req_list = &retrieveids( $archs );
                @incmp_list = &retrieveids( $pkgs );

		$already_installed = $FALSE; # using F and T as booleans here.
		foreach $i ( keys(%installed_patches) ) {
			if ($id == $i) { $already_installed = $TRUE;last }
		}
		if ( $already_installed ne $TRUE ) {
			&calculateage( $reldate );
			&preformat;

#			Store id number in mentioned array
			$displayed[++$#displayed] = $id;
			$foundone = $TRUE;
		}
	}
	if ( $foundone ne $TRUE) {print "All Y2K patches installed!\n"}
	print $separator;
}

if ($mode eq "LONG" || $mode eq "PKGINFO" ) {

        # slurp the output of pkginfo -l
	if( $mode eq "PKGINFO" ) 
	{
		open( PKGINFO, "<$pfile" );
	}
	else
	{
        	open( PKGINFO, "/usr/bin/pkginfo -l|" );
	}

        $/="\0x07"; # 0x07 is the bell; arbitrary choice -MN
        $pkginfo = <PKGINFO>;
        $/="\n";

        close( PKGINFO );

        # massaging text to make searches & split easier
        $pkginfo =~ s/\n\n/#/g;
        $pkginfo =~ s/\n/|/g;
        $pkginfo =~ s/\s*[A-Z]+:\s*//g;
        @pkginfo = split(/#/, $pkginfo);

#	print the headers for the other patches
	print "\n";
	print "OTHER RELATED UNINSTALLED PATCHES\n\n";
	print "NOTE: This is determined by the packages that have been\n";
	print "      installed on the system.\n\n";
	print "      When one patch refers to multiple packages, we list the\n";
	print "      additional packages in the next lines.\n\n";
	print "      The various 'S','R','*' marks denote unbundled packages\n";
	print "      that is designated as an 'Security' or 'Recommended'.\n\n";	
	print "         S = Security\n"; 
	print "         R = Recommened Unbundled\n"; 
	print "         * = Both Security and Recommended Unbundled\n\n"; 
	print $pkg_header;

#	taking a arch specific version of the xref. (attempt to speed things
#	up a bit)
#
	@arch = grep( /$arch[\.;]|all/, @xref );

	$= = $#xref; # setting the page length to be max possilbe
		     # this is so that we don't see TOP format again.

	$count = 1;
        foreach $section (@pkginfo) {
                ( $package, $name, $category, $archs, $version,
                  $basedir, $vendor, $desc, $pstamp, $instdate,
                  $hotline, $status, $files ) = split(/\|/, $section);

                $pattern = $package.":".$version.";";
                $pattern =~ s/\+\+/\\+\\+/;
		@result = grep( /$pattern/, @arch );
                for($i=0;$i <= $#result; $i++) {
                        # Checking to see if the package is mentioned
                        # in the cross reference file (maybe multiple
                        # so we're going all the way thru w/ the for loop)
                        if ( $result[$i] =~ m/^(\d{6})\|(\d{2})\|([^\|]+)\|([^\|]+)\|([^\|]+)\|[^\|]+\|[^\|]+\|[^\|]+\|[^\|]+\|[^\|]*$pattern[^\|]*\|(.*)$/) {
				$id = $1;
				$crev = $2;
				$reldate = $3;
				$r_flag = $4;
				$s_flag = $5;
				$synopsis = $6;

				if( $r_flag eq 'R' && $s_flag eq 'S' ) {
					$glyph = '*';
				}
				elsif ( $r_flag eq 'R' ) {
					$glyph = 'R';
				}
				elsif ( $s_flag eq 'S' ) {
					$glyph = 'S';
				}
				else { $glyph = " " ;}

			#	Now need to check if the id has
			#	been displayed before.
				$return = &alreadydisplayed( $id, @displayed ); 
				if ( ! $return ) {
					if ( $sortlist{$id} eq '' ) {
	                                	$sortlist{$id}="$crev|$reldate|$glyph|$synopsis|$package";
					}
					else {
						$tmp = $sortlist{$id};
	                                	$sortlist{$id}="$tmp;$package";
					}
				}
                        }
                }
        }

#	sort patchids & printing
	foreach $id (sort(keys(%sortlist))) {
		( $crev, $reldate, $glyph, $synopsis, $package ) =split(/\|/, $sortlist{$id});
		&calculateage( $reldate );
		if ( $package !~ m/.*;.*/) { 
			&printlong;
		}
		else { 
			$pack = $package; 
			$after = $FALSE;
	
			foreach $package ( split( /\;/, $pack ) ) {
				if ( $after eq $TRUE ) { 
					&printpkg;
				}
				else {
				        &printlong;
				}
				$after = $TRUE;
			}
		}
	}

	print $separator;
}	
#============================================== 
# END
#============================================== 
