#!/bin/sh -- # Really perl eval 'exec perl -w $0 ${1+"$@"}' if 0; # PCA - Patch Check Advanced # Analyze, download and install patches for Sun Solaris # # Author : Martin Paul # Home : http://www.par.univie.ac.at/solaris/pca/ # Version: 3.0 (2006/03/09) use strict; # SunSolve login data # my $sunsolve_user= ''; my $sunsolve_passwd= ''; ($ENV{'SUNSOLVE_USER'} ) && ($sunsolve_user = $ENV{'SUNSOLVE_USER'} ); ($ENV{'SUNSOLVE_PASSWD'}) && ($sunsolve_passwd= $ENV{'SUNSOLVE_PASSWD'}); # Executables - Only needed for download and install functions. # 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. 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 for download and install functions. # my $patchdir= ''; # Patches are uncompressed into this directory. It will be created and # removed by pca. Only needed for install function. # my $patchxdir= '/tmp/pca.' . time(); # Modules use Getopt::Std; use Time::Local; # Variable declarations my $debug=0; my (%o, %input, %p, %pkgs, %u); my ($plist, $slist); my $runningdl=''; # Force flush to stdout right after every print command without "\n" $|= 1; # Set signal handler $SIG{'INT'} = \&handler; $SIG{'QUIT'}= \&handler; # Main # &parse_args; &check_prerequisites; &get_current_xref; if ($o{download_xref} && !$o{args}) { if (!$o{list} && !$o{download} && !$o{install}) { exit 0; } } &get_uname; &get_installed_packages; &get_installed_patches; &get_current_patches; &get_ignore_list; if ($o{readme_id}) { &show_readme($o{readme_id}); exit 0; } # Set defaults $slist="$o{args}"; if (!$slist) { $slist="missingrs"; } if (!$o{download} && !$o{install}) { $o{list}=1; } &create_patch_list; if ($o{list}) { &show_patches; } if ($o{download}) { &download_patches; } if ($o{install} ) { &download_patches; &install_patches; } # Functions sub create_patch_list() { my $tlist; $tlist=$slist; $slist=''; foreach my $s (split (/ /, $tlist)) { ($s =~ /.zip/) && ($s =~ s/.zip//); ($s =~ /.tar.Z/) && ($s =~ s/.tar.Z//); ($s =~ /.tar/) && ($s =~ s/.tar//); $slist .= "$s "; } $tlist=$slist; $slist=''; foreach my $s (split (/ /, $tlist)) { if (($s =~ /^\/.*\/$/) || (! -f $s)) { $slist .= "$s "; next; } open (LIST, "<$s") || die "Can't open $s: $!\n"; while () { chomp; $slist .= "$_ "; } close (LIST); } $debug && print "## Expanded patch list: $slist\n"; foreach my $id (sort keys %p) { add_patch_list ($id,0); } } sub add_patch_list() { my $id=$_[0]; my $type=$_[1]; # Ignore patches which have been listed already. ($p{$id}{listed}) && return (0); $type=&match_patch_list($id,$type); $type || return (0); if ($p{$id}{requires} ne '') { REQ: 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 "## $r_id-$r_rev required by $id: obsolete, replaced with $oby_id-$oby_rev\n"; ($r_id, $r_rev)= ($oby_id, $oby_rev); } # Check if the required patch is in our database. Normally we should # stop with an error here, but maybe information in patchdiag.xref # is wrong and the patch will install without the missing required patch. if ($p{$r_id}{crev} eq "00") { $debug && print "## $r_id-$r_rev required by $id: unknown patch\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 "## $r_id-$r_rev required by $id: Circular patch dependency\n"; next REQ; } } } # Ignore patches already in our list. if ($p{$r_id}{listed}) { $debug && print "## $r_id-$r_rev required by $id: already listed\n"; next; } # Ignore patches already installed. if ($p{$r_id}{irev} ge $r_rev) { $debug && print "## $r_id-$r_rev required by $id: already installed\n"; next; } $debug && print "## $r_id-$r_rev required by $id\n"; if (!&add_patch_list($r_id,$type)) { $debug && print "## $r_id-$r_rev required by $id: does not match\n"; } } } $p{$id}{listed}=1; $plist .= "$id;"; return (1); } sub match_patch_list() { my $id=$_[0]; my $type=$_[1]; my $found; foreach my $s (split (/ /, $slist)) { # Complete patch id with revision (123456-78) if ($s =~ /\d{6}-\d{2}/) { my ($s_id,$s_rev)= split(/-/,$s); &init_patch($s_id); if ($id eq $s_id) { $p{$id}{prev}=$s_rev; return (1); } } # Incomplete patch id (123456) if ($s =~ /\d{6}/) { &init_patch($id); if ($id eq $s) { return (2); } if ($type == 2) { return (2); } } # installed or all if (($s =~ /^i/) || ($s =~ /^a/)) { # If the user chose to see R/S patches only, ignore others. if ($s =~ /rs/) { if ((!$p{$id}{rec}) && (!$p{$id}{sec})) { next; } } # Check if patch is installed. if ($p{$id}{irev} ne '00') { return (3); } } # unbundled if ($s =~ /^u/) { # 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}{obs} || $p{$id}{bad}) { next; } # Ignore patches in the ignore list. if ($p{$id}{ignore} eq "00") { next; } if ($p{$id}{ignore} eq $p{$id}{crev}) { next; } # If the user chose to see R/S patches only, ignore others. if ($s =~ /rs/) { if ((!$p{$id}{rec}) && (!$p{$id}{sec})) { next; } } return (4); } # missing or all if (($s =~ /^m/) || ($s =~ /^a/)) { # Ignore obsolete and bad patches if ($p{$id}{obs} || $p{$id}{bad}) { next; } # Ignore patches which are installed in the current or higher revision if ($p{$id}{irev} ge $p{$id}{crev}) { next; } # Ignore patches in the ignore list. if ($p{$id}{ignore} eq "00") { next; } if ($p{$id}{ignore} eq $p{$id}{crev}) { next; } # Ignore patches for foreign architectures. $found=0; foreach my $j (split (/\;/, $p{$id}{archs})) { if (($j eq $u{arch}) || ($j eq "all") || ($j eq "$u{arch}.$u{model}")) { $found=1; last; } } if (!$found) { next; } # Ignore patches for packages that are not installed. $found=0; foreach my $j (split (/\;/, $p{$id}{pkgs})) { my ($package, $version)= split (/:/, $j); if ($pkgs{$package} && ($pkgs{$package} eq $version)) { $found=1; last; } } if (!$found) { next; } if (!patch_apply_check($id)) { next; } # If the user chose to see R/S patches only, ignore others. if ($s =~ /rs/) { if ((!$p{$id}{rec}) && (!$p{$id}{sec})) { if ($type != 5) { next; } } } return (5); } # Search pattern (/.*/) if ($s =~ /^\/.*\/$/) { $s=~ s/^.(.*).$/$1/; if ($p{$id}{synopsis} =~ /$s/) { return (6); } } } return (0); } sub show_patches() { $plist || return; &print_header; foreach my $id (split (/;/, $plist)) { print_patch ($id); } &print_footer; } sub download_patches { $plist || return; $o{header} && print "\nDownloading patches to $patchdir\n".'-' x 78 ."\n"; ID: foreach my $id (split (/;/, $plist)) { # Add revision to patch id if ($p{$id}{prev} ne "00") { $id= "$id-$p{$id}{prev}"; } elsif ($p{$id}{crev} ne "00") { $id= "$id-$p{$id}{crev}"; } else { die "Unknown patch-id $id\n"; } print "Retrieving patch $id ... "; # Check if patch exists. If not, try download from public patch server. foreach my $ext ('.zip','.tar.Z','.tar') { if (-f "$patchdir/$id$ext") { print "skipping (file exists)\n"; next ID; } $wget || die "\nCan't find wget executable\n"; (-w $patchdir) || die "\nCan't write to patch download directory ($patchdir)\n"; $runningdl="$patchdir/$id$ext"; `$wget -q "http://patches.sun.com/all_unsigned/$id$ext" -O $runningdl`; $runningdl=""; if (!$?) { print "done\n"; next ID; } system ("$rm -f $patchdir/$id$ext"); } # Retry failed download from alternate URL with Sunsolve login. This # will only work for *.zip patches (which modern Solaris versions use). 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" -O $patchdir/$id.zip`; $runningdl=""; if (!$?) { print "done\n"; next ID; } system ("$rm -f $patchdir/$id.zip"); } print "failed\n"; } } sub install_patches { $plist || return; my $mustreconfigure=0; my $mustreboot=0; $o{header} && print "\nInstalling patches\n".'-' x 78 ."\n"; mkdir $patchxdir,0755 || die "Can't create temporary directory $patchxdir: $!\n"; foreach my $id (split (/;/, $plist)) { if ($p{$id}{prev} ne "00") { $id= "$id-$p{$id}{prev}"; } else { $id= "$id-$p{$id}{crev}"; } print "Installing $id ... "; if (-f "$patchdir/$id.zip") { `$unzip -n $patchdir/$id.zip -d $patchxdir 2>&1`; } elsif (-f "$patchdir/$id.tar.Z") { `cd $patchxdir; $uncompress -c $patchdir/$id.tar.Z | $tar xf -`; } elsif (-f "$patchdir/$id.tar") { `cd $patchxdir; $tar xf $patchdir/$id.tar`; } if (($?) || (! -d "$patchxdir/$id")) { print "skipping (uncompress failed)\n"; next; } # Do we need a reboot? my $patchmustreboot= 0; my $patchmustreconfigure= 0; my $readme= "$patchxdir/$id/README.$id"; 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 ($o{install_noreboot_only} && ($patchmustreconfigure || $patchmustreboot)) { print "skipping (reboot required)\n"; system ("cd $patchxdir; $rm -rf $id"); next; } # If the patchadd command doesn't exist, try installpatch, which # comes with patches for Solaris <= 2.5.1. (-x $patchadd) || ($patchadd="$patchxdir/$id/installpatch"); (-x $patchadd) || die "Can't execute patchadd/installpatch\n"; if ($o{pretend}) { print "skipping (pretend only)"; } else { `$patchadd -R $o{root} $o{patchadd_options} $patchxdir/$id 2>&1`; if ($?) { printf "failed (Exit code %d)\n", $? / 256; system ("cd $patchxdir; $rm -rf $id"); next; } print "done"; } if ($patchmustreconfigure) { print " (reconfigure required)"; $mustreconfigure= 1; } elsif ($patchmustreboot) { print " (reboot required)"; $mustreboot= 1; } print "\n"; system ("cd $patchxdir; $rm -rf $id"); } 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 effect.\n"; } } sub check_prerequisites { # Must be root to install patches if ($o{install} && ($< != 0) && (!$o{pretend})) { die "You must be root to install patches.\n"; } # Check for wget executable my $found=''; foreach my $i (split (/ /, $wget)) { if (-x $i) { $found= $i; $debug && print "## Using $found\n"; last; } } $wget=$found; # Get patchdiag.xref location $input{xrefdir}= $o{xrefdir} || $ENV{"XREF"} || '/var/tmp'; $input{xref}="$input{xrefdir}/patchdiag.xref"; # Get pca.ignore location my $tdir=$0; if ($tdir =~ /\//) { $tdir =~ s/\/[^\/]*$//; if (-f "$tdir/pca.ignore") { $input{ignore}= "$tdir/pca.ignore"; } } if (-f "$input{xrefdir}/pca.ignore") { $input{ignore}= "$input{xrefdir}/pca.ignore"; } if (-f "./pca.ignore") { $input{ignore}= "./pca.ignore"; } # Check and set patch download directory chomp($patchdir= `pwd`) unless (-d $patchdir); $debug && print "## patchdir: $patchdir\n"; # Check for pager if ($ENV{'PAGER'}) { $pager= $ENV{'PAGER'}; } # Check for valid prefix in $from_files if ($o{from_files}) { if (! -f "$o{from_files}uname.out") { if (! -f "$o{from_files}/uname.out") { die "Can't open $o{from_files}uname.out or $o{from_files}/uname.out\n"; } else { $o{from_files}="$o{from_files}/"; } } $debug && print "## Using $o{from_files} as prefix to read .out files\n"; } # Determine and set input files/commands if ($o{from_files}) { $input{pkginfo}= "<$o{from_files}pkginfo.out"; $input{showrev}= "<$o{from_files}showrev.out"; $input{uname} = "<$o{from_files}uname.out"; } else { $input{pkginfo}= "/usr/bin/pkginfo -x -R $o{root} |"; $input{showrev}= "/usr/bin/showrev -p -R $o{root} |"; $input{uname} = "/usr/bin/uname -a |"; } } sub patch_apply_check { my $id=$_[0]; if ($id =~ /113039|113040|113041|113042|113043/) { if (!$pkgs{"SUNWsan"}) { return (0); } } if ($id =~ /114045/) { if ((exists $p{114049}) && ($p{114049}{irev} gt '03')) { return (0); } } if (($id =~ /114046|119209/) && ($u{osrel} ne "5.8")) { return (0); } if (($id =~ /114049|114050/) && ($u{osrel} ne "5.9")) { return (0); } if (($id =~ /119211|119212/) && ($u{osrel} ne "5.9")) { return (0); } if ($id =~ /114790/) { if (!$pkgs{"SUNWdcar"} || $pkgs{"SUNWdcar"} ne "1.1.0,REV=2002.05.29.15.02") { return (0); } if (!$pkgs{"SUNWcrypr"} || $pkgs{"SUNWcrypr"} ne "1.1.0,REV=2002.05.29.15.00") { return (0); } } if (($id =~ /117765|117766/) && ($u{osrel} ne "5.8")) { return (0); } if (($id =~ /117767|117768/) && ($u{osrel} ne "5.9")) { return (0); } if ($id =~ /113332/) { if (($pkgs{"SUNWhea"}) || ($pkgs{"SUNWmdb"})) { return (1); } if (($u{model} eq 'sun4u') || ($u{model} eq 'sun4us')) { return (1); } return (0); } if ($id =~ /115010|116478/) { if (($pkgs{"SUNWhea"}) || ($pkgs{"SUNWmdb"})) { return (1); } if ($u{model} eq 'sun4u') { return (1); } return (0); } if ($id =~ /109077|109078/) { if ((!$pkgs{"SUNWdhcm"}) && (!$pkgs{"SUNWdhcsu"})) { return (0); } if ($pkgs{"SUNWj3rt"}) { return (1); } return (0); } if ($id =~ /118739|116706/) { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.04.03.21.27") { return (0); } } if ($id =~ /118740|116707/) { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.04.03.19.26") { return (0); } } if ($id =~ /118741/) { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.11.11.23.55") { return (0); } } if ($id =~ /118742/) { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.11.11.20.36") { return (0); } } if ($id =~ /110692/) { if ((exists $p{108806}) && ($p{108806}{irev} ge '01')) { return (0); } if ((exists $p{108806}) && ($p{108806}{crev} ge '01')) { return (0); } } if ($id =~ /111412/) { if (!$pkgs{"SUNWmdi"} || $pkgs{"SUNWmdi"} ne "11.8.0,REV=2001.01.19.01.02") { return (0); } if (!$pkgs{"SUNWsan"}) { return (0); } } if ($id =~ /111095|111096|111413/) { if (!$pkgs{"SUNWsan"}) { return (0); } } if ($id =~ /111097/) { if (!$pkgs{"SUNWsan"}) { return (0); } if (!$pkgs{"SUNWqlc"}) { return (0); } } if ($id =~ /111656/) { if (!((exists $p{109460}) && ($p{109460}{irev} eq '05'))) { return (0); } } if ($id =~ /111658/) { if (!((exists $p{107469}) && ($p{107469}{irev} eq '08'))) { return (0); } } if ($id =~ /111079/) { if (!((exists $p{105375}) && ($p{105375}{irev} eq '26'))) { return (0); } } if ($id =~ /107474/) { if ((exists $p{107292}) && ($p{107292}{irev} ge '02')) { return (1); } return (0); } if ($id =~ /106533/) { if ($u{platform} ne 'SUNW,UltraSPARC-IIi-cEngine') { return (0); } } if ($id =~ /106629/) { if ($u{platform} ne 'CYRS,Superserver-6400') { return (0); } } if ($id =~ /112780/) { if (!($u{model} eq 'sun4u')) { return (0); } } if ($id =~ /112327/) { if (($u{osrel} ne "5.6") && ($u{osrel} ne "5.7")) { return (0); } } if ($id =~ /11464[456789]|11465[0123]|11481[67]|11578[01]|11752[01]/) { if ($u{osrel} ne "5.8") { return (0); } } if ($id =~ /11468[6789]|11469[012345]|11481[89]|11578[23]|11752[67]/) { if ($u{osrel} ne "5.9") { return (0); } } if ($id =~ /111891/) { if (!$pkgs{"SUNWutr"} || $pkgs{"SUNWutr"} ne "1.3_12.c,REV=2001.07.16.20.52") { return (0); } } if (($id =~ /119145/) && ($p{$id}{crev} eq '07') && (exists $p{119081}) && ($p{119081}{irev} eq '10')) { return (0); } if (($id =~ /119250/) && ($p{$id}{crev} eq '04') && (exists $p{119081}) && ($p{119081}{irev} eq '10')) { return (0); } return (1); } sub get_uname { # Get information about host open(UNAME, $input{uname}) || die "Can't open $input{uname}: $!\n"; $_=; chomp; close UNAME; ($u{osname}, $u{hostname}, $u{osrel}, $u{osversion}, $u{model}, $u{arch}, $u{platform})= split (/ /, $_); #$u{platform} && chop ($u{platform}); ($u{osname} && $u{hostname} && $u{osrel} && $u{osversion} && $u{model} && $u{arch} && $u{platform}) || die "Can't parse uname ouput:\n $_\n"; } sub get_installed_packages { # Read pkginfo open (PKGINFO, $input{pkginfo}) || die "Can't open $input{pkginfo}: $!\n"; while () { my ($package, $version); ($_ =~ /^(\S+) /) || die "Can't parse pkginfo output:\n $_\n"; $package=$1; # Removing trailing .2/.3/... (multiple versions of same package) $package =~ s/\..*//; $_= ; ($_ =~ / (\S+)$/) || die "Can't parse pkginfo output:\n $_\n"; $version=$1; $pkgs{$package}=$version; } } sub get_installed_patches { # Read showrev -p output # open(SHOWREV, $input{showrev}) || die "Can't open $input{showrev}: $!\n"; $/=""; my $showrev= ; $/="\n"; close SHOWREV; my @showrev= split(/\n/, $showrev); foreach my $i (sort @showrev) { # Known formats of patch IDs: # 123456-78 : Regular Sun patches # IDR123456-78 : Unsupported (pre-release) Sun patches # 123-45 : EMC patches # CKPSP123456-78: Checkpoint patches if (($i =~ /^Patch:\s+(\d{3,6})-(\d{2}).*/) || ($i =~ /^Patch:\s+IDR(\d{6})-(\d{2}).*/) || ($i =~ /^Patch:\s+CKPSP(\d{6})-(\d{2}).*/)) { &init_patch($1); $p{$1}{irev}= $2; next; } next if ($i =~ "No patches are installed"); die "Can't parse showrev output:\n $i\n"; } } sub get_ignore_list { $input{ignore} || return; open(IGNORE, "$input{ignore}") || die "Can't open $input{ignore}: $!\n"; $debug && print "## Using $input{ignore}\n"; while() { next unless /^\d/; chomp; $_ =~ s/[ ]*#.*//; my ($id,$rev)= split(/-/,$_); $rev || ($rev= "00"); &init_patch($id); $p{$id}{ignore}= $rev; } close IGNORE; } sub get_current_xref { # Download most recent patchdiag.xref, if requested # If the xref file is older than 24 hours, we try to download it, but # only if we can write to the directory where it is located. if ((-f $input{xref}) && (!$o{download_xref})) { if ((-M $input{xref}) <= 1) { return; } elsif (! -w $input{xrefdir}) { print "Can't download xref file, as $input{xrefdir} is unwritable\n"; return; } } $o{header} && print "Retrieving xref-file to $input{xref} ... "; $wget || die "\nCan't find wget executable\n"; (-w $input{xrefdir}) || die "\nCan't write to xref download directory ($input{xrefdir})\n"; if ((-f $input{xref}) && (! -w $input{xref})) { print "Can't write to $input{xref}\n"; if ($o{download_xref}) { exit 1; } else { return; } } $runningdl="$input{xref}"; `$wget -qN "http://patches.sun.com/reports/patchdiag.xref" -P $input{xrefdir}`; $runningdl=""; if (!$?) { $o{header} && print "done\n"; chmod 0666, $input{xref}; } else { $o{header} && print "failed\n"; } } sub get_current_patches { # Read patchdiag.xref # open(XREF, "<$input{xref}") || die "Can't open xref file $input{xref}: $!\n"; $_=; if ($_ =~ /PATCHDIAG TOOL CROSS-REFERENCE FILE AS OF (.*) /) { $o{header} && print "Using $input{xref} from $1\n"; } $/=""; 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 ($id =~ /^\d/) || next; &init_patch($id); # If a patch revision is obsoleted or bad, use either the highest # non-obsoleted revision, or the highest obsoleted revision if all # revisions are obsoleted or bad. # if ($p{$id}{crev} ne "00") { if (($oFlag eq "O") || ($byFlag =~ ".B")) { if (!$p{$id}{obs} && !$p{$id}{bad}) { next; } } } $p{$id}{crev}=$crev; if ($reldate ne '') { $p{$id}{reldate}=$reldate; } $p{$id}{rec}=0; if ($rFlag eq 'R' ) { $p{$id}{rec}=1; } $p{$id}{sec}=0; if ($sFlag eq 'S' ) { $p{$id}{sec}=1; } $p{$id}{obs}=0; if ($oFlag eq 'O' ) { $p{$id}{obs}=1; } $p{$id}{bad}=0; if ($byFlag =~ ".B") { $p{$id}{bad}=1; } $p{$id}{y2k}=0; if ($byFlag =~ "Y.") { $p{$id}{y2k}=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}{obs}) { if ($synopsis =~ /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 =~ /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 =~ /^\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 =~ /^\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}= $p{$id}{crev}= $p{$id}{prev}= '00'; $p{$id}{synopsis}= 'NOT FOUND IN CROSS REFERENCE FILE!'; $p{$id}{rec}= $p{$id}{sec}= $p{$id}{obs}= $p{$id}{bad}= $p{$id}{y2k}= 0; $p{$id}{os}= ''; $p{$id}{pkgs}= ''; $p{$id}{ignore}= ''; $p{$id}{reldate}= 'Jan/01/70'; $p{$id}{obsoletedby}= ''; $p{$id}{archs}= ''; $p{$id}{requires}= ''; $p{$id}{incompatible}= ''; $p{$id}{listed}= 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 $rec; my $sec; my $age; my $synopsis; if ($p{$id}{irev} lt $p{$id}{crev}) { $char='<'; $h_char='<'; } if ($p{$id}{irev} eq $p{$id}{crev}) { $char='='; $h_char='='; } if ($p{$id}{irev} gt $p{$id}{crev}) { $char='>'; $h_char='>'; } $irev= $p{$id}{irev}; if ($irev eq "00") { $irev= '--' }; $crev= $p{$id}{crev}; if ($crev eq "00") { $crev= '--' }; $rec=' '; if ($p{$id}{rec}) { $rec='R'; } $sec=' '; if ($p{$id}{sec}) { $sec='S'; } $synopsis= $p{$id}{synopsis}; $age=calculateage($p{$id}{reldate}); if ($age > 999) { $age=999; } if (!$o{show_html}) { printf "%6d %2s %1s %2s %1s%1s %3d %s\n", $id, $irev, $char, $crev, $rec, $sec, $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 ""; if (($sunsolve_user ne '') && ($sunsolve_passwd ne '')) { printf "%6d", $id; } else { printf "%6d", $id; } printf "%2s%1s%2s%1s%1s%3s", $irev, $h_char, $crev, $rec, $sec, $age; if (($sunsolve_user ne '') && ($sunsolve_passwd ne '')) { printf "%s\n", $synopsis; } else { printf "%s\n", $synopsis; } } } sub print_header { if (!$o{show_html} && $o{header}) { print "Host: $u{hostname} ($u{osname} $u{osrel}/$u{arch}/$u{model})\n\n"; print "Patch IR CR RS Age Synopsis\n"; print "------ -- - -- -- --- " . '-' x 56 . "\n"; } if ($o{show_html}) { print "\n"; print "\n\n"; print "PCA report for $u{hostname}\n"; print "\n\n"; print "

Host: $u{hostname} ($u{osname} $u{osrel}/$u{arch}/$u{model})

\n\n"; print ""; print ""; print ""; print ""; print ""; print "\n"; } } sub print_footer { if ($o{show_html}) { print "
PatchIRCRRSAgeSynopsis
\n\n\n"; } } sub show_readme { my $id=$_[0]; # If there is no explicit patch revision given, we use the current rev. if ($id =~ /^\d{6}$/ ) { &init_patch($id); if ($p{$id}{crev} eq "00") { die "Unknown patch-id $id\n"; } $id= "$id-$p{$id}{crev}"; } ($id =~ /^\d{6}-\d{2}$/) || die "Invalid patch-id $id\n"; my $success= 0; my $tmpf="/tmp/README.$id"; # If we have the zip file, extract README from there. This doesn't work # for tar/tar.Z files, as Sun's tar cannot extract files to stdout. if (-f "$patchdir/$id.zip") { $debug && print "## Getting README from $patchdir/$id.zip\n"; `$unzip -p $patchdir/$id.zip $id/README.$id >$tmpf 2>/dev/null`; if (!$?) { $success= 1 ; } } # Alternatively download the README directly from patches.sun.com. if (!$success) { $debug && print "## Getting README from patches.sun.com\n"; $runningdl="$tmpf"; $wget || die "Can't find wget executable\n"; `$wget -q -O $tmpf http://patches.sun.com/all_unsigned/$id.README`; $runningdl=""; if (!$?) { $success= 1 ; } } # If the patch README is not on the public sun server, try again # from sunsolve.sun.com, using sunsolve authentication data. if ((!$success) && ($sunsolve_user ne '') && ($sunsolve_passwd ne '')) { $debug && print "## Getting README from sunsolve.sun.com\n"; $runningdl="$tmpf"; $wget || die "Can't find wget executable\n"; `$wget -q -O $tmpf --http-user='$sunsolve_user' --http-passwd='$sunsolve_passwd' "http://sunsolve.sun.com/private-cgi/pdownload.pl?target=$id.README&method=h"`; $runningdl=""; if (!$?) { $success= 1 ; } } if (!$success) { # wget might have created a size 0 file. system ("$rm -f $tmpf"); die "Could not find README for patch-id $id\n"; } # Show README in pager, and remove it afterwards. system ("$pager $tmpf"); system ("$rm -f $tmpf"); } sub calculateage { my ($tmonth, $day, $year)=split(/\//, $_[0]); 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 $month=$months{$tmonth}; my $currenttime=time(); 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 parse_args() { # Check for command line options # $o{header}=1; $o{root}='/'; $o{patchadd_options}=''; my %opts; &getopts("lLdiIxX:R:nkGHr:f:hVv", \%opts) || &usage && exit 1; if ($opts{l}) { $o{list}=1; } if ($opts{L}) { $o{list}=1; $o{show_html}=1; } if ($opts{d}) { $o{download}=1; } if ($opts{i}) { $o{install}=1; } if ($opts{I}) { $o{install}=1; $o{pretend}=1; } if ($opts{x}) { $o{download_xref}=1; } if ($opts{X}) { $o{xrefdir}=$opts{X}; } if ($opts{R}) { $o{root}=$opts{R}; } if ($opts{n}) { $o{install_noreboot_only}=1; } if ($opts{k}) { $o{patchadd_options}.='-d '; } if ($opts{G}) { $o{patchadd_options}.='-G '; } if ($opts{H}) { delete $o{header}; } if ($opts{r}) { $o{readme_id}=$opts{r}; } if ($opts{f}) { $o{from_files}=$opts{f}; } if ($opts{h}) { &usage; exit 0; } if ($opts{V}) { $debug=1; } if ($opts{v}) { &version; exit 0; } $o{args}="@ARGV"; } sub usage { print "Usage: $0 [OPTION] .. [OPERAND] ..\n\n"; print "Operands:\n"; print " patch group: missing, missingrs, installed, installedrs, all, unbundled\n"; print " patch ID: 123456, 123456-78\n"; print " patch file: 123456-78.zip, 123456-78.tar.Z\n"; print " file name: patchlist.txt\n"; print " pattern: /dtmail/\n\n"; print "Options:\n"; print " -l List patches\n"; print " -L List patches, produce HTML output\n"; print " -d Download patches\n"; print " -i Install patches\n"; print " -I Pretend to install patches\n"; print " -x Download patches cross-reference file\n"; print " -X Set location of patches cross-reference file\n"; print " -R Set alternative root directory\n"; print " -n Install only patches which do not require a reboot\n"; print " -k Make patchadd not back up files to be patched\n"; print " -G Make patchadd modify packages in the current zone only\n"; print " -H Don't display descriptive headers\n"; print " -r Display patch README\n"; print " -f Read uname/showrev/pkginfo output from files in \n"; print " -h Display this help\n"; print " -V Display debug output\n"; print " -v Display version information\n"; } sub version { print "pca 3.0 (2006/03/09)\n"; }