#!/bin/sh -- # Really perl eval 'exec perl -w $0 ${1+"$@"}' if 0; # PCA - Patch Check Advanced # # Version 2.2 (2005/11/03): # Handle possible empty fields in patchdiag.xref # Show exit code of patchadd if it fails # Remove unnecessary sort for showrev output # Add option to show debug output (-V) # Updated patch-specific function to avoid showing uninstallable patches # # Version 2.1 (2005/10/04): # Add option to pretend patch installation (-P) # Look at machine class to avoid showing uninstallable patches # New patch-specific function to avoid showing uninstallable patches # Show OS version and architecture information in report header # # Version 2.0 (2005/09/08): # Add handling of required patches which are obsoleted by other patches # Extract and display README from patch zip file if available (with -R ) # Pipe README (with -R ) into more or $PAGER # Add a signal handler to remove stale files after interrupted downloads # Extract patches into /tmp/pca.time() by default # Remove extracted files after patch installation # Fix small bug with flushing output to stdout for older perl versions # Set default patch download dir to absolute path instead of "." # # Version 1.5.1 (2005/08/26): # Display age of patch in a new column # Fix small bug in handling showrev -p output # # Version 1.5.0 (2005/08/08): # Add experimental support for downloading contract-only patches # Fix handling of IDR patches # Add support for installpatch (Solaris <= 2.5.1) # # Version 1.4.7 (2005/07/08): # Fix error handling # Fix handling of required patches # # Version 1.4.6 (2005/06/02): # Add option to specify directory location of patchdiag.xref (-X) # Add host name to header # Fix HTML code generation # Add option to run patchadd without backing up files (-k) # Fix usage information # # Version 1.4.5 (2005/05/25): # Fix handling of tar patches # Add missing options to usage information # Add option to show pca version information (-v) # # Version 1.4.4 (2005/02/07): # Fix handling of multiple installations of the same package # # Version 1.4.3 (2005/02/01): # Fix bug in patchdiag.xref reading code to handle inconsistencies # Fix bug which made pca ignore some unbundled patches # # Version 1.4.2 (2004/12/22): # Add option to only apply patches which do not require a reboot (-n) # # Version 1.4.1 (2004/10/27): # Add option to read uname/showrev/pkginfo from files (-F) # # Version 1.4 (2004/09/21): # Add experimental HTML output option (-b) # Add -R option to show the README of a patch # Add -D option to download one patch # Command line option to not show headers is now called -H # New command line option -h to print usage information # Internal code redesign # # Version 1.3.1 (2004/09/01): # Switch from FTP downloads to HTTP downloads for patchdiag.xref/patches # Internal code redesign, now runs with "perl -w" and "use strict" # # Version 1.3 (2004/08/19): # Added experimental function to show unbundled patches # Ignore comments at the end of lines in pca.ignore # Check for circular patch dependencies # Fix small bug in handling showrev -p output # Internal redesign of command line option handling # # Version 1.2 (2004/07/05): # Check prerequisites of patches and list them, too (in the correct order) # Speedup of factor 2.5 in comparison to PCA 1.1 # Allow specification of multiple paths to wget # Fix small bug in patchdiag.xref download code # Fix handling of tar/tar.Z patches # # Version 1.1 (2004/06/16): # Added pca.ignore file to ignore patches (contributed by Damian Hole) # Added patchdiag.xref download function # Added patch download function (contributed by Damian Hole) # Added patch install function (contributed by Damian Hole) # # Version 1.0 (2003/09/09): # First release # # Author: Martin Paul # Home : http://www.par.univie.ac.at/solaris/pca/ # # PCA is a replacement for Sun's Patch Check tool. It shows lists of # installed and uninstalled patches. With the help of PCA the system # administrator can easily find out which patches should/need to be # applied to a system. use strict; # SunSolve login data # my $sunsolve_user = ''; my $sunsolve_passwd = ''; # Executables - only needed if download (-d/-x) or install (-p) is used. # my $wget = '/usr/sfw/bin/wget /usr/local/bin/wget /opt/csw/bin/wget'; my $unzip = '/usr/bin/unzip'; my $patchadd = '/usr/sbin/patchadd'; my $uncompress = '/usr/bin/uncompress'; my $tar = '/usr/sbin/tar'; my $uname = '/usr/bin/uname'; my $rm = '/usr/bin/rm'; my $pager = '/usr/bin/more'; # Patches are downloaded into this directory, so it must exist and be # writable for the user running pca. If empty (''), or if it doesn't exist, # the current directory will be used. Only needed if patch download (-d) # or install (-p) is used. # my $patchdir = ''; # Patches are uncompressed into this directory. It will be created and # removed by pca. Only needed if patch install (-p) is used. # my $patchxdir = '/tmp/pca.' . time(); # You should not need to modify anything below this line. # Modules use Getopt::Std; use Time::Local; # Variable declarations # my $show_installed=0; my $show_uninstalled=0; my $show_unbundled=0; my $show_rsonly=0; my $show_header=1; my $download_xref=0; my $download_patches=0; my $apply_patches=0; my $pretend_apply_patches=0; my $show_html=0; my $from_files=0; my $apply_noreboot_only=0; my $debug=0; my $patchadd_options=''; my $first_header=1; my $readme_id=''; my $download_id=''; my $xrefdir; my $osname=''; my $hostname=''; my $release=''; my $osversion=''; my $model=''; my $arch=''; my $platform=''; my %p; my %instpkgs; my %instpkgs_short; my $ilist; my %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); my $currenttime=time(); my $runningdl=''; # Force flush right away on every print to stdout, for those print commands # that have no "\n" by design. # $| = 1; # Set signal handler # $SIG{'INT'} = \&handler; $SIG{'QUIT'} = \&handler; # Check for command line options # my %opts; &getopts("iufraxX:dpPnkHR:D:bFhVv", \%opts) || &usage && exit 1; if ($opts{i} || $opts{a}) { $show_installed=1; } if ($opts{u} || $opts{a}) { $show_uninstalled=1; } if ($opts{f}) { $show_unbundled=1; } if ($opts{r}) { $show_rsonly=1; } if ($opts{x}) { $download_xref=1; } if ($opts{d}) { $download_patches=1; } if ($opts{p}) { $apply_patches=1; } if ($opts{P}) { $apply_patches=1; $pretend_apply_patches=1; } if ($opts{n}) { $apply_noreboot_only=1; } if ($opts{k}) { $patchadd_options='-d'; } if ($opts{H}) { $show_header=0; } if ($opts{R}) { $readme_id=$opts{R}; } if ($opts{D}) { $download_id=$opts{D}; } if ($opts{b}) { $show_html=1; } if ($opts{F}) { $from_files=1; } if ($opts{h}) { &usage; exit 0; } if ($opts{V}) { $debug=1; } if ($opts{v}) { &version; exit 0; } # Set default option if ($show_installed == 0 && $show_uninstalled == 0 && $show_unbundled == 0) { $show_uninstalled=1; $show_rsonly=1; } # Check prerequisites (paths to needed binaries, root for patch install, etc.) &check_prerequisites; # Get architecture if ($from_files == 1) { open(UNAME, "); ($osname && $hostname && $release && $osversion && $model && $arch && $platform) || die "Can't parse uname ouput"; chop ($platform); close UNAME; if ($readme_id) { &show_readme($readme_id); exit 0; } # Get list of installed patches &get_installed_patches; # Get list of current patches &get_current_patches; if ($download_id) { &do_download_patch($download_id); exit 0; } # Read ignore list &get_ignore_list; if ($show_installed == 1) { &do_show_installed; } if ($show_uninstalled == 1) { &do_show_uninstalled; } if ($show_unbundled == 1) { &do_show_unbundled; } if (($download_patches || $apply_patches) && $ilist) { &do_download_patches; } if ($apply_patches && $ilist) { &do_install_patches; } if ($show_html) { print "\n\n"; } sub do_show_installed { print_header ("INSTALLED"); foreach my $id (sort keys %p) { next unless ($p{$id}{'irev'} != '-1'); print_patch ($id); } &print_footer; } sub do_show_uninstalled { if ($show_rsonly == 1) { print_header ("UNINSTALLED RECOMMENDED/SECURITY"); } else { print_header ("UNINSTALLED"); } # Read pkginfo if ($from_files == 1) { open( PKGINFO, ") { my $package; my $version; if ($_ =~ m/^(\S+) /) { $package=$1; # Removing trailing .2/.3/... - this happens when multiple versions # of a package with the same name are installed (like SPRO*). $package =~ s/\..*//; } else { die "Can't parse pkginfo output\n"; } $_ = ; if ($_ =~ m/ (\S+)$/) { $version=$1; } else { die "Can't parse pkginfo output\n"; } my $pkgidx = $package.":".$version; $instpkgs{$pkgidx}=1; $instpkgs_short{"$package"}=1; } # Check all patches whether they apply. # foreach my $id (sort keys %p) { # Ignore obsolete and bad patches if ($p{$id}{'oflag'} == 1) { next; } if ($p{$id}{'bflag'} == 1) { next; } # Ignore patches which are installed in their current revision # Sometimes installed patch revisions are higher than patchdiag.xref, # therefore we use ">=" instead of "==". if ($p{$id}{'irev'} >= $p{$id}{'crev'}) { next; } # Ignore patches in the ignore list. if ($p{$id}{'ignore'} eq "-1") { next; } if ($p{$id}{'ignore'} eq $p{$id}{'crev'}) { next; } # Check if patch is for our architecture. If not, ignore it. my $found=0; foreach my $j (split (/\;/, $p{$id}{'archs'})) { if (($j eq $arch) || ($j eq "all") || ($j eq "$arch.$model")) { $found=1; } } if ($found == 0) { next; } # Check if patch is for a package we have installed. If not, ignore it. $found=0; foreach my $j (split (/\;/, $p{$id}{'pkgs'})) { if ($instpkgs{$j} && ($instpkgs{$j} == 1)) { $found=1; } } if ($found == 0) { next; } if (patch_apply_check($id) == 0) { next; } $p{$id}{'applies'} = 1; } # List uninstalled patches # foreach my $id (sort keys %p) { # If the user chose to see R/S patches only, ignore others. if ($show_rsonly == 1) { if (($p{$id}{'rflag'} == 0) && ($p{$id}{'sflag'} == 0)) { next; } } # Ignore patches which have been shown already (via check_requires). if ($p{$id}{'shown'} == 1) { next; } # Ignore patches that don't apply. if ($p{$id}{'applies'} == 0) { next; } # Check prerequisites (recursively), show patch, mark it as shown # and add it to the list of patches to be possibly installed later. # check_requires ($id); print_patch ($id); $p{$id}{'shown'}=1; $ilist .= "$id;"; } &print_footer; } sub do_show_unbundled { if ($show_rsonly == 1) { print_header ("UNBUNDLED RECOMMENDED/SECURITY"); } else { print_header ("UNBUNDLED"); } # Check all patches whether they apply. # foreach my $id (sort keys %p) { # Check if patch is Unbundled and has an empy packages list. if (!(($p{$id}{'os'} eq "Unbundled") && ($p{$id}{'pkgs'} eq ""))) { next; } # Ignore obsolete and bad patches if ($p{$id}{'oflag'} == 1) { next; } if ($p{$id}{'bflag'} == 1) { next; } # Ignore patches in the ignore list. if ($p{$id}{'ignore'} eq "-1") { next; } if ($p{$id}{'ignore'} eq $p{$id}{'crev'}) { next; } # If the user chose to see R/S patches only, ignore others. if ($show_rsonly == 1) { if (($p{$id}{'rflag'} == 0) && ($p{$id}{'sflag'} == 0)) { next; } } # We do not check for required patches, as this would add patches # to the $ilist (patches to be downloaded and installed) - unbundled # patches can't be installed automatically. Also required patches # for unbundled patches are rare, and usually explicitely mentioned # in the README, and might require other precautions. # Print patch print_patch ($id); } &print_footer; } sub check_requires { my $id=$_[0]; return if ($p{$id}{'requires'} eq ''); foreach my $r (split (/;/, $p{$id}{'requires'})) { my ($r_id, $r_rev) = split (/-/, $r); # If a required patch has been obsoleted by another patch, we # continue with the patch that obsoleted it. while ($p{$r_id}{'obsoletedby'} ne '') { my ($oby_id, $oby_rev) = split (/-/, $p{$r_id}{'obsoletedby'}); $debug && print "## Required $r_id-$r_rev replaced with $oby_id-$oby_rev\n"; $r_id= $oby_id; $r_rev= $oby_rev; } # Check if required patch are in our database. Normally we should # stop with an error here, but maybe information in patchdiag.xref # is wrong and the patch will apply without the missing required patch. if ($p{$r_id}{'crev'} == -1) { $debug && print "## WARNING: Required patch $r_id unknown\n"; next; } # Check circular patch dependencies (only one level). This won't # catch patch A req B, B req C, and C req A. if ($p{$r_id}{'requires'} ne '') { foreach my $s (split (/;/, $p{$r_id}{'requires'})) { (my $s_id, my $s_rev) = split (/-/, $s); if ($id eq $s_id) { $debug && print "## WARNING: Circular patch dependency $id <-> $r_id\n"; return; } } } # Ignore patches already in our list, or already installed. if ($p{$r_id}{'shown'} == 1) { next; } if ($p{$r_id}{'irev'} >= $r_rev) { next; } # Check if the required patch can be applied if (! $p{$r_id}{'applies'}) { # See README (2004/07/02) why we ignore this. $debug && print "## WARNING: $id requires $r_id-$r_rev, does not apply\n"; next; } # So we require a patch which exists, but isn't listed yet. # We recursively check the requires for the patch, and then print it. # check_requires ($r_id); print_patch ($r_id); $p{$r_id}{'shown'}=1; $ilist .= "$r_id;"; } } sub do_download_patches { if ($show_header == 1) { print "\nDownloading Patches into $patchdir\n"; print '-' x 78 . "\n"; } foreach my $id (split (/;/, $ilist)) { &do_download_patch("$id-$p{$id}{'crev'}"); } } sub do_download_patch { my $id=$_[0]; # If there is no explicit patch revision given, we have to read current # patches information to get the most current revision. # if ($id =~ /^\d\d\d\d\d\d$/ ) { &init_patch($id); if ($p{$id}{'crev'} == -1) { die "Unknown patch-id $id"; } $id= "$id-$p{$id}{'crev'}"; } if ($id =~ /^\d\d\d\d\d\d-\d\d$/) { print "Retrieving patch $id..."; if (-f "$patchdir/$id.zip" || -f "$patchdir/$id.tar.Z" || -f "$patchdir/$id.tar") { print "skipping (file exists)\n"; } else { my $download_successful = 0; for my $ext ('.zip','.tar.Z','.tar') { $runningdl="$patchdir/$id$ext"; `$wget -q "http://patches.sun.com/all_unsigned/$id$ext" -P $patchdir`; $runningdl=""; if ($? == 0) { $download_successful = 1; last; } } # Retry failed download from alternate URL with Sunsolve login. # Warning - this will work for *.zip patches (which modern Solaris # version use) only. if ($download_successful == 0) { if (($sunsolve_user ne '') && ($sunsolve_passwd ne '')) { $runningdl="$patchdir/$id.zip"; `$wget -q --http-user=$sunsolve_user --http-passwd=$sunsolve_passwd "http://sunsolve.sun.com/private-cgi/pdownload.pl?target=$id&method=h" --output-document=$patchdir/$id.zip`; $runningdl=""; if ($? == 0) { $download_successful = 1; } } } if ($download_successful) { print "done\n"; } else { print "failed\n"; } } return; } die "Invalid patch-id $id"; } sub do_install_patches { my $mustreconfigure=0; my $mustreboot=0; if ($show_header == 1) { print "\nApplying patches\n"; print '-' x 78 . "\n"; } mkdir $patchxdir,0755 || die "Can't create temporary directory $patchxdir: $!\n"; foreach my $id (split (/;/, $ilist)) { my $patchname = "$id-$p{$id}{'crev'}"; print "Applying $patchname..."; if (-f "$patchdir/$patchname.zip") { `$unzip -n $patchdir/$patchname.zip -d $patchxdir 2>&1`; } elsif (-f "$patchdir/$patchname.tar.Z") { `cd $patchxdir; $uncompress -c $patchdir/$patchname.tar.Z | $tar xf -`; } elsif (-f "$patchdir/$patchname.tar") { `cd $patchxdir; $tar xf $patchdir/$patchname.tar`; } if (($? != 0) || (! -d "$patchxdir/$patchname")) { print "skipping (uncompress failed)\n"; next; } # Do we need a reboot? my $patchmustreboot = 0; my $patchmustreconfigure = 0; my $readme = "$patchxdir/$patchname/README.$patchname"; if (-f $readme) { open(README,$readme) || die "Can't open $readme: $!\n"; while() { if (/Reconfigure after installation/ || /Reconfigure immediately after patch is installed/) { $patchmustreconfigure = 1; } elsif (/Reboot after installation/ || /Reboot immediately after patch is installed/) { $patchmustreboot = 1; } } close README; } if ($apply_noreboot_only == 1 && ($patchmustreconfigure || $patchmustreboot)) { print "skipping (reboot required)\n"; system ("cd $patchxdir; $rm -rf $patchname"); next; } # If the patchadd command doesn't exist, try installpatch, which # comes with patches for Solaris <= 2.5.1. $patchadd="$patchxdir/$patchname/installpatch" unless (-x $patchadd); die "Cannot execute patchadd/installpatch" unless (-x $patchadd); if ($pretend_apply_patches == 1) { print "skipping (pretend only)"; } else { `$patchadd $patchadd_options $patchxdir/$patchname 2>&1`; if ($? != 0) { printf "failed (Exit code %d)\n", $? / 256; system ("cd $patchxdir; $rm -rf $patchname"); next; } print "done"; } if ($patchmustreconfigure) { print " (reconfigure required)"; $mustreconfigure = 1; } elsif ($patchmustreboot) { print " (reboot required)"; $mustreboot = 1; } print "\n"; system ("cd $patchxdir; $rm -rf $patchname"); } rmdir $patchxdir || die "Can't remove temporary directory $patchxdir: $!\n"; if ($mustreconfigure) { print "\nPlease reconfigure to complete patch process.\n"; } elsif ($mustreboot) { print "\nPlease reboot for patches to take affect.\n"; } } sub check_prerequisites { # Must be root to apply patches if ($apply_patches && ($< != 0) && ($pretend_apply_patches == 0)) { die "You must be root to apply patches.\n"; } # Check for missing executables and directories my $missingexec=''; if ($download_xref || $download_patches || $apply_patches || $readme_id || $download_id) { my $found=''; foreach my $i (split (/ /, $wget)) { next unless ($found eq ''); if (-x $i) { $found = $i; } } if ($found eq '') { $missingexec = " wget ($wget)" } else { $wget = $found; $debug && print "## Using $wget\n"; } } if ($missingexec) { die "The following executables are missing:\n$missingexec"; } # Check and set patch download directory, and check if writable. chomp($patchdir = `pwd`) unless (-d $patchdir); $debug && print "## patchdir: $patchdir\n"; if ($download_patches || $apply_patches || $download_id) { if (! -w $patchdir) { print "Can't write to patch download directory ($patchdir)\n"; exit 1; } } # Check for pager if ($ENV{'PAGER'}) { $pager= $ENV{'PAGER'}; } } sub patch_apply_check { my $id=$_[0]; # SAN 4.4.x: Sun StorEdge Traffic Manager patch # SAN 4.4.x: fctl/fp/fcp driver patch # SAN 4.4.x: fcip driver patch # SAN 4.4.x: qlc driver patch # SAN 4.4.x: luxadm, liba5k and libg_fc patch if (($id=='113039') || ($id=='113040') || ($id=='113041') || ($id=='113042') || ($id=='113043')) { if (!$instpkgs_short{"SUNWsan"}) { return (0); } } # Security 3.3.4.5: NSPR 4.1.6 / NSS 3.3.4.5 if ($id=='114045') { if ((exists $p{'114049'}) && ($p{'114049'}{'irev'} gt '03')) { return (0); } } # SunOS 5.8_x86: Netscape Portable Runtime(4.1.4)/Network Security System(3.3.4) if (($id=='114046') && ($release != "5.8")) { return (0); } # SunOS 5.9: NSPR 4.1.6 / NSS 3.3.4.5 # SunOS 5.9_x86: NSPR 4.1.6 / NSS 3.3.4.5 if (($id=='114049') || ($id=='114050')) { if ($release != "5.9") { return (0); } } # Security 3.10: NSPR 4.5.2 / NSS 3.10.1 / JSS 4.1 if (($id=='119209') && ($release != "5.8")) { return (0); } # SunOS 5.9: NSPR 4.5.2 / NSS 3.10.1 / JSS 4.1 # SunOS 5.9_x86: NSPR 4.5.2 / NSS 3.10.1 / JSS 4.1 if (($id=='119211') || ($id=='119212')) { if ($release != "5.9") { return (0); } } # Sun Crypto Accelerator 1000 1.1: patch if ($id=='114790') { if (!$instpkgs{"SUNWdcar:1.1.0,REV=2002.05.29.15.02"}) { return (0); } if (!$instpkgs{"SUNWcrypr:1.1.0,REV=2002.05.29.15.00"}) { return (0); } # the prepatch checks /etc/release for s9s_u[3-9] (9 4/03 or higher), too. } # Mozilla 1.4: Base Libraries patch if ((($id=='117765')||($id=='117766'))&&($release != "5.8")) { return (0); } if ((($id=='117767')||($id=='117768'))&&($release != "5.9")) { return (0); } # SunOS 5.9: libc_psr.so.1 libmd5_psr.so.1 Patch # SunOS 5.9: Providing Platform Support for Enchilada Tower Server # SunOS 5.9: usr platform links Patch if (($id=='113332') || ($id=='115010') || ($id=='116478')) { if (($instpkgs_short{"SUNWhea"}) || ($instpkgs_short{"SUNWmdb"})) { return (1); } if (($model eq 'sun4u') && ($model eq 'sun4us')) { return (0); } return (0); } # SunOS 5.8: dhcp server and admin patch if (($id=='109077') || ($id=='109078')) { if ((!$instpkgs_short{"SUNWdhcm"}) && (!$instpkgs_short{"SUNWdhcsu"})) { return (0); } if ($instpkgs_short{"SUNWj3rt"}) { return (1); } return (0); } # Trusted_Solaris_8_HW_12/02: apache patch # Trusted_Solaris_8_HW_12/02: CDE header files patch if (($id=='118739') || ($id=='116706')) { if (!$instpkgs{"SUNWtsr:2.5.0,REV=2003.04.03.21.27"}) { return (0); } } # Trusted_Solaris_8_HW_12/02_x86: apache patch # Trusted_Solaris_8_HW_12/02_x86: CDE header files patch if (($id=='118740') || ($id=='116707')) { if (!$instpkgs{"SUNWtsr:2.5.0,REV=2003.04.03.19.26"}) { return (0); } } # Trusted_Solaris_8_HW_7/03: apache patch if ($id=='118741') { if (!$instpkgs{"SUNWtsr:2.5.0,REV=2003.11.11.23.55"}) { return (0); } } # Trusted_Solaris_8_HW_7/03_x86: apache patch if ($id=='118742') { if (!$instpkgs{"SUNWtsr:2.5.0,REV=2003.11.11.20.36"}) { return (0); } } # NSS 1.0: patch for Netra Software Suite Network Resilience if ($id=='110692') { if ((exists $p{'108806'}) && ($p{'108806'}{'irev'} ge '01')) { return (0); } if ((exists $p{'108806'}) && ($p{'108806'}{'crev'} ge '01') && ($p{'108806'}{'applies'} == 1)) { return (0); } } # SAN 4.4.x: Sun StorEdge Traffic Manager patch if ($id=='111412') { if (!$instpkgs{"SUNWmdi:11.8.0,REV=2001.01.19.01.02"}) { return (0); } if (!$instpkgs_short{"SUNWsan"}) { return (0); } } # SAN 4.4.x: fctl/fp/fcp/usoc driver patch # SAN 4.4.x: fcip driver patch # SAN 4.4.x: luxadm, liba5k and libg_fc patch if (($id=='111095') || ($id=='111096') || ($id=='111413')) { if (!$instpkgs_short{"SUNWsan"}) { return (0); } } # SAN 4.4.x: qlc driver patch if ($id=='111097') { if (!$instpkgs_short{"SUNWsan"}) { return (0); } if (!$instpkgs_short{"SUNWqlc"}) { return (0); } } # SunOS 5.8: Supplemental socal and sf drivers patch if ($id=='111656') { if (!((exists $p{'109460'}) && ($p{'109460'}{'irev'} eq '05'))) { return (0); } } # SunOS 5.7: Supplemental socal and sf drivers patch if ($id=='111658') { if (!((exists $p{'107469'}) && ($p{'107469'}{'irev'} eq '08'))) { return (0); } } # SunOS 5.6: Supplemental socal and sf drivers patch if ($id=='111079') { if (!((exists $p{'105375'}) && ($p{'105375'}{'irev'} eq '26'))) { return (0); } } # SunOS 5.7: ifp adb macro patch if ($id=='107474') { if ((exists $p{'107292'}) && ($p{'107292'}{'irev'} ge '02')) { return (1); } if ((exists $p{'107292'}) && ($p{'107292'}{'crev'} ge '02') && ($p{'107292'}{'applies'} == 1)) { return (1); } return (0); } # SunOS 5.6: ucblinks and su driver patch if ($id=='106533') { if ($platform ne 'SUNW,UltraSPARC-IIi-cEngine') { return (0); } } # SunOS 5.6: CS6400 kernel update patch if ($id=='106629') { if ($platform ne 'CYRS,Superserver-6400') { return (0); } } # FT1800 TOD fixes if ($id=='112780') { if (!($model eq 'sun4u')) { return (0); } } # Sun GigaSwift Ethernet 1.0: driver patch if (($id=='112327')&&(($release != "5.6")&&($release != "5.7"))) { return (0); } return (1); } sub get_installed_patches { # Read showrev -p output # if ($from_files == 1) { open(SHOWREV, ") { if (($_ =~ m/^Patch:\s+(\d{6})-(\d{2}).*/) || ($_ =~ m/^Patch:\s+IDR(\d{6})-(\d{2}).*/)) { &init_patch($1); # showrev -p output is unsorted. Only store the top patch revision. if ($2 > $p{$1}{'irev'}) { $p{$1}{'irev'} = $2; } next; } next if ($_ =~ "No patches are installed"); die "Can't parse showrev output\n"; } close SHOWREV; } sub get_ignore_list { return unless (-f "$xrefdir/pca.ignore"); open(IGNORE, "$xrefdir/pca.ignore") || die "Can't open $xrefdir/pca.ignore: $!\n"; $debug && print "## Using $xrefdir/pca.ignore\n"; while() { next unless /^\d/; chomp; $_ =~ s/[ ]*#.*//; my ($id,$rev) = split(/-/,$_); $rev = "-1" unless $rev; &init_patch($id); $p{$id}{'ignore'} = $rev; } close IGNORE; } sub get_current_patches { # Get patchdiag.xref location. # $xrefdir= $opts{X} || $ENV{"XREF"}; if (! $xrefdir) { $xrefdir = $0; if ($xrefdir !~ /\//) { $xrefdir = '.'; } else { $xrefdir =~ s/\/[^\/]*$//; } } my $XREF="$xrefdir/patchdiag.xref"; # Download most recent patchdiag.xref, if requested # if ($download_xref == 1) { print "Retrieving xref-file to $xrefdir/patchdiag.xref..."; $runningdl="$xrefdir/patchdiag.xref"; `$wget -qN "http://patches.sun.com/reports/patchdiag.xref" -P $xrefdir`; $runningdl=""; if ($? == 0) { print "done\n"; } else { print "failed\n"; } } # Read patchdiag.xref # open(XREF, "<$XREF") || die "Can't find xref file $XREF"; $/=""; my $xref = ; $/="\n"; close XREF; my @xref = split( /\n/, $xref ); # Build our patch information table from the xref file. # patchdiag.xref is sorted, so if multiple revisions of a patch are listed, # the one with the highest revision comes last. # foreach my $i (sort @xref) { my ($id, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $os, $archs, $pkgs, $synopsis ) = split( /\|/, $i); # Ignore comment lines next unless $id =~ m/^\d/; &init_patch($id); # If we already have data for this patch ID (a lower revision), # and the higher revision is OBS or BAD, don't put it into our table. # if ($p{$id}{'crev'} != -1) { if (($oFlag eq "O") || ($byFlag =~ ".B")) { next; } } $p{$id}{'crev'}=$crev; if ($reldate ne '') { $p{$id}{'reldate'}=$reldate; } $p{$id}{'rflag'}=0; if ($rFlag eq 'R' ) { $p{$id}{'rflag'}=1; } $p{$id}{'sflag'}=0; if ($sFlag eq 'S' ) { $p{$id}{'sflag'}=1; } $p{$id}{'oflag'}=0; if ($oFlag eq 'O' ) { $p{$id}{'oflag'}=1; } $p{$id}{'bflag'}=0; if ($byFlag =~ ".B") { $p{$id}{'bflag'}=1; } $p{$id}{'yflag'}=0; if ($byFlag =~ "Y.") { $p{$id}{'yflag'}=1; } $p{$id}{'os'}=$os; $p{$id}{'synopsis'}=$synopsis; # If a patch is obsoleted by another patch, note it. # There are (at least) two forms, one with a patch revision # and one without. We check for both. # if ($p{$id}{'oflag'} == 1) { if ($synopsis =~ m/Obsoleted by[ :]*(\d{6})-(\d{2})/) { if ($id ne $1) { $p{$id}{'obsoletedby'}="$1-$2"; #$debug && print "## $id-$crev obsoleted by $p{$id}{'obsoletedby'}\n"; } } if ($synopsis =~ m/OBSOLETED by (\d{6})/) { if ($id ne $1) { $p{$id}{'obsoletedby'}="$1-01"; #$debug && print "## $id-$crev obsoleted by $p{$id}{'obsoletedby'}\n"; } } } # Patch requires are coded into the archs field - separate them. $p{$id}{'archs'}=''; $p{$id}{'requires'}=''; foreach my $r (split /\;/, $archs) { if ($r =~ m/\d{6}-\d{2}/) { $p{$id}{'requires'} .= "$r;"; # We run init_patch here for required patches because they might # be missing in the xref file, and would be uninitialized later. my ($r_id, $r_rev) = split (/-/, $r); &init_patch($r_id); } else { $p{$id}{'archs'} .= "$r;"; } } # Patch incompatibilities are coded into the pkgs field $p{$id}{'pkgs'}=''; $p{$id}{'incompatible'}=''; foreach my $r (split /\;/, $pkgs) { if ($r =~ m/\d{6}-\d{2}/) { $p{$id}{'incompatible'} .= "$r;"; my ($r_id, $r_rev) = split (/-/, $r); &init_patch($r_id); } else { $p{$id}{'pkgs'} .= "$r;"; } } } } sub init_patch { my $id=$_[0]; # Every patch should be initialized only once. return if ($p{$id}{'init'}); $p{$id}{'irev'}=-1; $p{$id}{'crev'}=-1; $p{$id}{'synopsis'}='NOT FOUND IN CROSS REFERENCE FILE!'; $p{$id}{'rflag'}=0; $p{$id}{'sflag'}=0; $p{$id}{'oflag'}=0; $p{$id}{'bflag'}=0; $p{$id}{'yflag'}=0; $p{$id}{'os'}=''; $p{$id}{'pkgs'}=''; $p{$id}{'ignore'}=0; $p{$id}{'reldate'}='Jan/01/70'; $p{$id}{'obsoletedby'}=''; $p{$id}{'archs'}=''; $p{$id}{'requires'}=''; $p{$id}{'incompatible'}=''; $p{$id}{'applies'}=0; $p{$id}{'shown'}=0; $p{$id}{'init'}=1; } sub print_patch { # Use %-.62s for synopsis if you want to limit it so that lines won't wrap. my $id=$_[0]; my $char; my $h_char; my $irev; my $crev; my $rflag; my $sflag; my $age; my $synopsis; if ($p{$id}{'irev'} < $p{$id}{'crev'}) { $char='<'; $h_char='<'; } if ($p{$id}{'irev'} == $p{$id}{'crev'}) { $char='='; $h_char='='; } if ($p{$id}{'irev'} > $p{$id}{'crev'}) { $char='>'; $h_char='>'; } $irev = $p{$id}{'irev'}; if ($irev eq "-1") { $irev = '--' }; $crev = $p{$id}{'crev'}; if ($crev eq "-1") { $crev = '--' }; $rflag=' '; if ($p{$id}{'rflag'} == 1) { $rflag='R'; } $sflag=' '; if ($p{$id}{'sflag'} == 1) { $sflag='S'; } $synopsis = $p{$id}{'synopsis'}; $age=calculateage($p{$id}{'reldate'}); if ($age > 999) { $age=999; } if ($show_html == 0) { printf "%6d %2s %1s %2s %1s%1s %3d %s\n", $id, $irev, $char, $crev, $rflag, $sflag, $age, $synopsis; } else { # The patch download link will only work for patches in zip format, # there is no way to determine if it's in zip or tar.Z here. # $synopsis =~ s/\&/\&/; printf ""; printf "%6d", $id; printf "%2s%1s%2s%1s%1s%3s", $irev, $h_char, $crev, $rflag, $sflag, $age; printf "%s\n", $synopsis; } } sub print_header { return unless ($show_header == 1); if ($show_html == 0) { if ($first_header == 1) { $first_header=0; } else { print "\n\n"; } print "$_[0] PATCHES for $hostname ($osname $release/$arch/$model)\n"; print "Patch IR CR RS Age Synopsis\n"; print "------ -- - -- -- --- " . '-' x 56 . "\n"; } else { if ($first_header == 1) { print "\n"; print "\n\n"; print "PCA report for $hostname\n"; print "\n\n"; $first_header=0; } print "

$_[0] PATCHES for $hostname ($osname $release/$arch/$model)

\n\n"; print ""; print ""; print ""; print ""; print ""; print "\n"; } } sub print_footer { return unless ($show_html == 1); print "
PatchIRCRRSAgeSynopsis
\n"; } sub show_readme { my $id=$_[0]; # If there is no explicit patch revision given, we have to read current # patches information to get the most current revision. # if ($id =~ /^\d\d\d\d\d\d$/ ) { &init_patch($id); &get_current_patches; if ($p{$id}{'crev'} == -1) { die "Unknown patch-id $id"; } $id= "$id-$p{$id}{'crev'}"; } die "Invalid patch-id $id" unless ($id =~ /^\d\d\d\d\d\d-\d\d$/); # If we have the zip file, extract README from there. # Unfortunately this doesn't work for tar/tar.Z files, as Sun's # tar cannot extract files to stdout. Using a temporary file # would be an option, be we don't like temporary files. # if (-f "$patchdir/$id.zip") { $debug && print "## Getting README from $patchdir/$id.zip\n"; system ("$unzip -p $patchdir/$id.zip $id/README.$id 2>/dev/null | $pager"); if ($? == 0) { return; } } # Alternatively download and display the README directly from sunsolve # $debug && print "## Getting README from sunsolve.sun.com\n"; system ("$wget -q -O - http://patches.sun.com/all_unsigned/$id.README | $pager"); if ($? != 0) { die "Could not find README for patch-id $id"; } } sub calculateage { my ($tmonth, $day, $year)=split(/\//, $_[0]); my $month=$months{$tmonth}; return (int(($currenttime-timelocal(0,0,0,$day,$month,$year))/86400)); } sub handler { my($sig) = @_; print "Caught a SIG$sig\n"; if ($runningdl ne "") { print "Removing $runningdl\n"; system ("rm -f $runningdl"); } # We might want to remove $patchxdir, too, if necessary. exit(0); } sub usage { print "Usage: $0 [-iufraxdpPnkHbFhVv] [-X dir] [-R patch-id] [-D patch-id]\n"; print " -i Show installed patches\n"; print " -u Show uninstalled patches\n"; print " -f Show unbundled patches\n"; print " -r Show recommended/security uninstalled patches only\n"; print " -a Show all installed/uninstalled patches (combines -iu)\n"; print " -x Download xref-file\n"; print " -X Set location of xref-file\n"; print " -d Download patches\n"; print " -p Apply patches (after downloading)\n"; print " -P Pretend to apply patches (after downloading)\n"; print " -n Only apply patches which do not require a reboot\n"; print " -k Make patchadd not back up files to be patched\n"; print " -H Don't show descriptive headers\n"; print " -R Show patch README\n"; print " -D Download patch\n"; print " -b Show HTML output\n"; print " -F Read uname/showrev/pkginfo output from files\n"; print " -h Print this help\n"; print " -V Show debug output\n"; print " -v Print pca version information\n"; } sub version { print "pca 2.2 (2005/11/03)\n"; }