#!/bin/sh #! -*- perl -*- eval 'exec perl -x -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/ my $version='20071005-03'; use strict; # Default paths my $unzip= '/usr/bin/unzip'; my $showrev= '/usr/bin/showrev'; my $pkginfo= '/usr/bin/pkginfo'; my $pkgchk= '/usr/sbin/pkgchk'; my $uncompress= '/usr/bin/uncompress'; my $tar= '/usr/sbin/tar'; my $uname= '/usr/bin/uname'; my $pager= '/usr/bin/more'; my $file= '/usr/bin/file'; # Supported options, format is: # Long name, short name, argument type, argument text, default value, help my @options=( "list|l|||0|List patches", "listhtml|L|||0|List patches, produce HTML output", "download|d|||0|Download patches", "install|i|||0|Install patches", "pretend|I|||0|Pretend to install patches", "readme|r|||0|Display patch READMEs", "getxref|x|||0|Download patch xref file", "xrefdir|X|s|DIR|/var/tmp|Location of patch xref file", "nocheckxref|y|||0|Do not check for updated patch xref file", "xrefown||||0|Give write permissions on xref file to user only", "nocache||||0|Tell proxy to not cache xref file", "patchdir|P|s|DIR|.|Patch download directory", "askauth|a|||0|Ask for Sun Online Account data interactively", "user||s|USER||Sun Online Account user name", "passwd||s|PASS||Sun Online Account password", "localurl||s|URL||DEPRECATED", "patchurl||s|URL||Local URL for patches and READMEs", "xrefurl||s|URL||Local URL for patchdiag.xref", "stop||s@|ID||Stop after patch ID", "ignore||s@|ID||Ignore patch ID", "rec||s@|ID||Set Recommended flag on patch ID", "sec||s@|ID||Set Security flag on patch ID", "pattern|p|s|REGEX||List only patches whose synopsis matches REGEX", "noreboot|n|||0|Install only patches which do not require a reboot", "minage||i|DAYS|0|List only patches which are at least DAYS old", "syslog||s|TYPE||Log successful patch installs to syslog facility TYPE", "nobackup|k|||0|Make patchadd not back up files to be patched", "backdir|B|s|DIR||Saves patch backout data to DIR", "safe|s|||0|Check locally modified files for safe patch installation", "currentzone|G|||0|Make patchadd install patches in the current zone only", "patchadd||s|FILE|/usr/sbin/patchadd|Path to patchadd command", "noheader|H|||0|Don't display descriptive headers", "format||s|FORMAT|%p %i %e %c %r%s%b %a %y|Set output format to FORMAT", "fromfiles|f|s|DIR||Read uname/showrev/pkginfo output from files in DIR", "dltries||i|NUM|1|Try downloads from Sun download server NUM times", "force|F|||0|Force local caching proxy to download from Sun server", "root|R|s|DIR||Alternative root directory", "wget||s|FILE|/usr/sfw/bin/wget /usr/local/bin/wget /opt/csw/bin/wget /usr/bin/wget|Path to wget command", "wgetproxy||s|URL||Default proxy for wget", "logger||s|FILE|/usr/bin/logger|Path to logger command", "update||s|TYPE|never|Update pca (TYPE is never, check, now or auto)", "pcaurl||s|URL|http://www.par.univie.ac.at/solaris/pca/|URL for pca update", "debug|V|||0|Print debug information", "help|h|||0|Display this help", "version|v|||0|Display version information", "operands||||missing|ENVFILE", "wgetq||||-q|INTERNAL", "wgetout|||||INTERNAL", "tmpdir||||/tmp|INTERNAL", "proxy||||0|INTERNAL", "pforce||||0|INTERNAL", "dbgfile||||/tmp/pca-proxy-debug.txt|INTERNAL" ); # Modules use Getopt::Long; use Time::Local; use Cwd; use File::Path; use Fcntl; use File::Basename; use File::Copy; # Variable declarations my (%o, %input, %p, %pkgs, %u, %c, %locks); my (@plist, @slist, @rlist); my $xrefdl=''; my $sttyset=0; my $patchxdir=''; my $currenttime=time(); my $stopreached=0; # Force flush to stdout right after every print command without "\n" $|= 1; # Set signal handler $SIG{HUP} = 'IGNORE'; $SIG{TERM} = $SIG{INT} = $SIG{QUIT} = \&handler; # Main # parse_args(); check_prerequisites(); $o{proxy} && proxy(); update(); expand_operands(); if ($o{readme} && ("@slist" =~ /^(\d{6}-\d{2} *)+$/)) { foreach my $pp (@slist) { my ($id, $rev)= split (/-/, $pp); init_patch ($id); $p{$id}{prev}=$rev; push (@plist, $id); } do_patch_list(); exit 0; } get_current_xref(); if (!$o{list} && !$o{download} && !$o{install} && !$o{readme}) { exit 0 } get_uname(); get_installed_packages(); get_installed_patches(); get_current_patches(); create_patch_list(); do_patch_list(); exit 0; # Functions sub do_patch_list { (@plist) || return; # Counters $c{current}=0; $c{total}=$#plist+1; $c{dl}=$c{skipdl}=$c{faildl}=$c{inst}=$c{skipinst}=$c{failinst}=0; $c{p_ci}=$c{p_bi}=$c{p_c}=$c{p_b}=0; print_header(); foreach my $id (@plist) { $c{current}++; # Add revision to patch id my $pp=""; ($p{$id}{irev} ne "00") && ($pp="$id-$p{$id}{irev}"); ($p{$id}{crev} ne "00") && ($pp="$id-$p{$id}{crev}"); ($p{$id}{prev} ne "00") && ($pp="$id-$p{$id}{prev}"); $pp || err ("Unknown patch-id $id"); if ($o{list} || $o{download} || $o{install}) { print_patch ($id); } if ($o{download} || $o{install}) { printf " " x 23 . "Download %d/%d: ", $c{current}, $c{total}; download_patch($pp); print $p{$id}{dloutput}; } if ($o{install}) { printf " " x 23 . "Install %d/%d: ", $c{current}, $c{total}; install_patch($pp); if (-x '/var/run/nopatch') { `/var/run/nopatch`; last } } if ($o{readme}) { my $rtmp=get_readme ($pp); ($rtmp) && (push (@rlist, $rtmp)); } ($o{download} || $o{install}) && print "\n"; } if ($o{download} || $o{install}) { printf "Download Summary: %d total, %d successful, ", $c{total}, $c{dl}; printf "%d skipped, %d failed\n", $c{skipdl}, $c{faildl}; } if ($o{install}) { printf "Install Summary : %d total, %d successful, ", $c{total}, $c{inst}; printf "%d skipped, %d failed\n", $c{skipinst}, $c{failinst}; if ($c{p_ci}) { print "\nReconfiguration reboot (boot -r) required.\n" } elsif ($c{p_bi}) { print "\nReboot required.\n" } elsif ($c{p_c }) { print "\nReconfiguation reboot (boot -r) recommended.\n" } elsif ($c{p_b }) { print "\nReboot recommended.\n"} } if ($o{readme} && (@rlist)) { system ("$pager @rlist"); unlink (@rlist); } print_footer(); } sub expand_operands { my @tlist=@ARGV; my $again=1; my %fc; while ($again) { $again=0; @slist=(); foreach my $s (@tlist) { if ($s =~ /^(missingr?s?|installedr?s?|allr?s?|totalr?s?|unbundledr?s?|badr?s?)$/) { push (@slist, $s); } elsif ($s =~ /^(mr?s?|ir?s?|ar?s?|tr?s?|ur?s?|br?s?)$/) { push (@slist, $s); } elsif ($s =~ /^(\d{6}|\d{6}-\d{2})$/) { push (@slist, $s); } elsif ($s =~ /(\d{6}-\d{2})\.(zip|tar\.Z|tar)$/) { push (@slist, $1); } elsif (-f $s) { if ($fc{$s}) { err ("Recursive file inclusion: $s") } else { $fc{$s}=1 } open (LIST, "<$s") || err ("Can't open $s ($!)"); while () { chomp; next unless $_; push (@slist, (split (/ /, $_))[0]); $again=1; } } else { err ("Unknown operand: $s"); } } @tlist=@slist; } dbg ("Expanded patch list: @slist"); } sub create_patch_list { if ("@slist" =~ /^(\d{6}-\d{2} *)+$/) { foreach my $pp (@slist) { my ($id, $rev)= split (/-/, $pp); init_patch ($id); $p{$id}{prev}=$rev; push (@plist, $id); } } else { 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}); dbg ("$r_id-$r_rev required by $id: obsolete, replaced with $oby_id-$oby_rev"); ($r_id, $r_rev)= ($oby_id, $oby_rev); } # Check if patch requires itself if ($r_id eq $id) { dbg ("$r_id-$r_rev required by $id: patch requires itself"); next; } # 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") { dbg ("$r_id-$r_rev required by $id: unknown patch"); 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) { dbg ("$r_id-$r_rev required by $id: Circular patch dependency"); next REQ; } } } # Ignore patches already in our list. if ($p{$r_id}{listed}) { dbg ("$r_id-$r_rev required by $id: already listed"); next; } # Ignore patches already installed. if ($p{$r_id}{irev} ge $r_rev) { dbg ("$r_id-$r_rev required by $id: already installed"); next; } dbg ("$r_id-$r_rev required by $id"); if (!add_patch_list($r_id,$type)) { dbg ("$r_id-$r_rev required by $id: does not match"); } } } $p{$id}{listed}=1; push (@plist, $id); return (1); } sub match_patch_list { my $id=$_[0]; my $type=$_[1]; my $found; S: foreach my $s (@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/)) { # Check for R/S, minage, pattern, ignore if (!check_rs($s,$id)) { 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; } # Check for R/S, minage, pattern, ignore if (!check_rs($s,$id)) { 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 for foreign architectures. $found=0; foreach my $j (split (/\;/, $p{$id}{archs})) { if (($j eq $u{arch}) || ($j eq "all") || ($j eq "Solaris") || ($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; } # Check for R/S, minage, pattern, ignore if (!check_rs($s,$id) && ($type != 5)) { next; } return (5); } # Total set of patches if ($s =~ /^t/) { if ($p{$id}{crev} eq "00") { next; } # Check for R/S, minage, pattern, ignore if (!check_rs($s,$id)) { next; } return (6); } # Installed bad patches if ($s =~ /^b/) { if (!$p{$id}{ibad}) { next; } # Check if bad patch has been obsoleted by an installed patch my $oby_id= $id; my $oby_rev; while ($p{$oby_id}{obsoletedby} ne '') { ($oby_id, $oby_rev)= split (/-/, $p{$oby_id}{obsoletedby}); if ($p{$oby_id}{irev} ge $oby_rev) { next S; } } # Check for R/S, minage, pattern, ignore if (!check_rs($s,$id)) { next; } return (7); } } return (0); } sub check_rs { my $s=$_[0]; my $id=$_[1]; # Check that a "stop" patch hasn't been seen if ($stopreached) { return(0); } # Check that this isn't a "stop" patch if ($p{$id}{stop} eq "00") { $stopreached = 1; } if ($p{$id}{stop} eq $p{$id}{crev}) { $stopreached = 1; } # Check for R/S flags if ($s =~ /rs$/) { if (!($p{$id}{rec} || $p{$id}{recf} || $p{$id}{sec} || $p{$id}{secf})) { return(0); } } else { if (($s =~ /r$/) && (!$p{$id}{rec}) && (!$p{$id}{recf})) { return(0); } if (($s =~ /s$/) && (!$p{$id}{sec}) && (!$p{$id}{secf})) { return(0); } } # Ignore patches in the ignore list. if ($p{$id}{ignore} eq "00") { return 0 } if ($p{$id}{ignore} eq $p{$id}{crev}) { return 0 } # Check for minage and pattern if (($o{minage}) && (calculateage($p{$id}{reldate}) <= $o{minage})) { return(0); } if (($o{pattern}) && ($p{$id}{synopsis} !~ /$o{pattern}/)) { return(0); } return(1); } sub download_patch { my $pp=$_[0]; my ($id, $rev)= split (/-/, $pp); lock_free($o{patchdir}, "download.$pp", 300) || err ("Another instance of pca is downloading $pp to $o{patchdir} right now"); # Check if patch exists if (-d "$o{patchdir}/$pp") { $p{$id}{dloutput}= "skipped - directory exists\n"; $c{skipdl}++; return; } foreach my $ext ('.zip','.tar.Z','.tar') { if (-f "$o{patchdir}/$pp$ext") { if (-s "$o{patchdir}/$pp$ext") { $p{$id}{dloutput}= "skipped - file exists\n"; $c{skipdl}++; return; } unlink "$o{patchdir}/$pp$ext"; } } # Remember if we downloaded the patch for install only $o{download} || ($p{$id}{dfori}=1); (-w $o{patchdir}) || err ("Can't write to patch download directory $o{patchdir} ($!)"); lock_create($o{patchdir}, "download.$pp", 1) || err ("Another instance of pca is downloading $pp to $o{patchdir} right now"); # Try to get patch from local patch server if ($o{patchurl} =~ /^file:/) { my $path=$o{patchurl}; $path =~ s/^file://; foreach my $ext ('.zip','.tar.Z','.tar') { dbg ("Getting $pp$ext from $o{patchurl}"); $p{$id}{dlfile}="$o{patchdir}/$pp$ext"; (-r "$path/$pp$ext") && copy ("$path/$pp$ext", $p{$id}{dlfile}); $p{$id}{dlfile}=""; if (-s "$o{patchdir}/$pp$ext") { $p{$id}{dloutput}= "done\n"; $c{dl}++; lock_remove($o{patchdir}, "download.$pp"); return; } unlink "$o{patchdir}/$pp$ext"; } } # Without wget we can't download the patch if (!$o{wget}) { $p{$id}{dloutput}= "failed - can't find wget executable\n"; $c{faildl}++; lock_remove($o{patchdir}, "download.$pp"); return; } # Try to get patch from local patch server with wget if ($o{patchurl} =~ /^http:|^https:|^ftp:/) { foreach my $ext ('.zip','.tar.Z','.tar') { dbg ("Getting $pp$ext from $o{patchurl}"); $p{$id}{dlfile}="$o{patchdir}/$pp$ext"; my $force=''; if ($o{force} && ($o{patchurl} =~ /pca-proxy\.cgi/)) { $force=":force" } `$o{wget} $o{wgetq} --timeout=3600 "$o{patchurl}$pp$ext$force" -O $p{$id}{dlfile} $o{wgetout}`; $p{$id}{dlfile}=""; if ((!$?) && (-s "$o{patchdir}/$pp$ext")) { $p{$id}{dloutput}= "done\n"; $c{dl}++; lock_remove($o{patchdir}, "download.$pp"); return; } unlink "$o{patchdir}/$pp$ext"; } } # Try download from restricted patch server, if the user provided # Sun Online Account data if ($o{user} && $o{passwd}) { my $try=1; while ($try <= $o{dltries} ) { dbg ("Getting $pp from sunsolve.sun.com"); $p{$id}{dlfile}="$o{patchdir}/$pp.tmp"; `$o{wget} $o{wgetq} $o{wgetproxy} "http://sunsolve.sun.com/pdownload.do?target=$pp&method=h" -O $o{patchdir}/$pp.tmp --http-user='$o{user}' --http-passwd='$o{passwd}' $o{wgetout}`; $p{$id}{dlfile}=""; if ((!$?) && (-s "$o{patchdir}/$pp.tmp")) { my $file=`LC_MESSAGES=C; export LC_MESSAGES; $file $o{patchdir}/$pp.tmp`; if ($file =~ /tar archive/i) { rename ("$o{patchdir}/$pp.tmp", "$o{patchdir}/$pp.tar"); $p{$id}{dloutput}= "done\n"; $c{dl}++; lock_remove($o{patchdir}, "download.$pp"); return; } elsif ($file =~ /compress/i) { rename ("$o{patchdir}/$pp.tmp", "$o{patchdir}/$pp.tar.Z"); $p{$id}{dloutput}= "done\n"; $c{dl}++; lock_remove($o{patchdir}, "download.$pp"); return; } elsif ($file =~ /zip/i) { rename ("$o{patchdir}/$pp.tmp", "$o{patchdir}/$pp.zip"); $p{$id}{dloutput}= "done\n"; $c{dl}++; lock_remove($o{patchdir}, "download.$pp"); return; } else { dbg ("Unknown file type: $file"); } } unlink "$o{patchdir}/$pp.tmp"; $try++; if ($try <= $o{dltries}) { sleep ($try*2) } } } else { $p{$id}{dloutput}= "failed - no Sun Online Account data\n"; $c{faildl}++; lock_remove($o{patchdir}, "download.$pp"); return; } $p{$id}{dloutput}= "failed - patch not found\n"; $c{faildl}++; lock_remove($o{patchdir}, "download.$pp"); } sub install_patch { my $pp=$_[0]; my ($id, $rev)= split (/-/, $pp); my $output; my $dfile=''; my $opt=''; $patchxdir= "$o{tmpdir}/pca." . time() . $$; mkdir $patchxdir,0755 || err ("Can't create temporary directory $patchxdir ($!)"); if (-d "$o{patchdir}/$pp") { symlink ("$o{patchdir}/$pp", "$patchxdir/$pp"); } elsif (-f "$o{patchdir}/$pp.zip") { `$unzip -n $o{patchdir}/$pp.zip -d $patchxdir 2>&1`; $p{$id}{dfori} && ($dfile= "$o{patchdir}/$pp.zip") } elsif (-f "$o{patchdir}/$pp.tar.Z") { `cd $patchxdir; $uncompress -c $o{patchdir}/$pp.tar.Z | $tar xf -`; $p{$id}{dfori} && ($dfile= "$o{patchdir}/$pp.tar.Z") } elsif (-f "$o{patchdir}/$pp.tar") { `cd $patchxdir; $tar xf $o{patchdir}/$pp.tar`; $p{$id}{dfori} && ($dfile= "$o{patchdir}/$pp.tar") } else { print "failed - missing patch file\n"; rmdir $patchxdir; $patchxdir=""; $c{failinst}++; return; } if (($?) || (! -d "$patchxdir/$pp")) { print "failed - uncompress failed\n"; rmtree ($patchxdir); $patchxdir=""; $c{failinst}++; return; } my $readme= "$patchxdir/$pp/README.$pp"; my $patchinfo= "$patchxdir/$pp/patchinfo"; if (($o{safe}) && !verify_files($id, $readme)) { rmtree ($patchxdir); $patchxdir=""; $c{failinst}++; return; } # Do we need a reboot? my $p_b=0; my $p_bi=0; my $p_c=0; my $p_ci=0; if (-f $patchinfo) { open(PATCHINFO,$patchinfo) || err ("Can't open $patchinfo ($!)"); dbg ("Checking for reboot/reconfig in patchinfo"); while () { if (/PATCH_PROPERTIES=.*reconfigimmediate/) { $p_ci=1; last } if (/PATCH_PROPERTIES=.*rebootimmediate/) { $p_bi=1; last } if (/PATCH_PROPERTIES=.*reconfigafter/) { $p_c=1; last } if (/PATCH_PROPERTIES=.*rebootafter/) { $p_b=1; last } } close PATCHINFO; } elsif (-f $readme) { open(README,$readme) || err ("Can't open $readme ($!)"); dbg ("Checking for reboot/reconfig in README"); while() { if (/Reconfig.*immediate.*after.*install/) { $p_ci=1; last } if (/Reboot.*immediate.*after.*install/) { $p_bi=1; last } if (/Reconfig.*after.*install/) { $p_c=1; last } if (/Reboot.*after.*install/) { $p_b=1; last } } close README; } # If the patchadd command doesn't exist, try installpatch, which # comes with patches for Solaris <= 2.5.1. (-x $o{patchadd}) || ($o{patchadd}="$patchxdir/$pp/installpatch"); (-x $o{patchadd}) || err ("Can't execute patchadd/installpatch"); # Sun Studio 11 patches on Solaris 10 must be installed with -G # Patches 119254-34 and 119255-34 fix this in patchadd my ($major, $minor) = split (/\./, $u{osrel}); if (($minor == 10) && ($p{$id}{synopsis} =~ /^Sun Studio 11/) && (!$o{currentzone})) { if (!($p{119254}{irev} ge '34') && !($p{119255}{irev} ge '34')) { dbg ("Adding -G to patchadd for Sun Studio 11 on Solaris 10"); $opt .= "-G "; } } # Ignore currentzone option on Solaris <= 9 ($minor <= 9) && ($o{currentzone}=0); if ($o{noreboot} && ($p_ci || $p_bi || $p_c || $p_b)) { print "skipped"; $c{skipinst}++; } elsif ($o{pretend}) { print "pretended"; $c{skipinst}++; } else { lock_create($o{tmpdir}, "install", 1) || err ("Another instance of pca is installing patches right now"); $o{currentzone} && ($opt .= "-G "); $o{nobackup} && ($opt .= "-d "); $o{backdir} && ($opt .= "-B $o{backdir} "); dbg ("$o{patchadd} $o{root} $opt $patchxdir/$pp"); $SIG{INT}='IGNORE'; $output=`$o{patchadd} $o{root} $opt $patchxdir/$pp &1`; $SIG{INT}=\&handler; my $rc=$?; lock_remove($o{tmpdir}, "install"); if ($rc) { print "\n$output\n"; printf "failed - Exit code %d\n", $rc / 256; rmtree ($patchxdir); $patchxdir=""; $c{failinst}++; return; } dbg ("\n$output"); print "done"; $c{inst}++; $dfile && unlink ($dfile); log_msg("Installed patch $pp ($p{$id}{synopsis})"); } if ($p_ci) { print " - reconfig required"; $o{noreboot} || $c{p_ci}++ } elsif ($p_bi) { print " - reboot required"; $o{noreboot} || $c{p_bi}++ } elsif ($p_c ) { print " - reconfig recommended"; $o{noreboot} || $c{p_c}++ } elsif ($p_b ) { print " - reboot recommended"; $o{noreboot} || $c{p_b}++ } print "\n"; rmtree ($patchxdir); $patchxdir=""; } sub proxy { my $f=$o{proxy}; my $odir; dbg ("Requested file: $f"); if ($f =~ /patchdiag.xref/) { $o{xrefown}=1; $o{nocheckxref}=0; $odir=$o{xrefdir}; if ($o{pforce}) { unlink ("$odir/$f") } get_current_xref(); } if ($f =~ /README/) { my $pp=$f; $pp =~ s/^.*(\d{6}-\d{2}).*$/$1/; $odir=$o{patchdir}; if ($o{pforce}) { unlink ("$odir/$f") } if (! -f "$odir/$f") { my $rtmp=get_readme ($pp); if ($rtmp) { copy ($rtmp, "$odir/$f"); unlink ($rtmp); } } } if ($f =~ /\d{6}-\d{2}\.(zip|tar|tar\.Z)/) { my $pp=$f; $pp =~ s/^.*(\d{6}-\d{2}).*$/$1/; $odir=$o{patchdir}; if ($o{pforce}) { unlink ("$odir/$f") } download_patch($pp); } if (-f "$odir/$f") { ($f =~ /patchdiag.xref/) && print "Content-type: text/plain\n"; ($f =~ /README/) && print "Content-type: text/plain\n"; ($f =~ /\.zip$/) && print "Content-type: application/zip\n"; ($f =~ /\.tar$/) && print "Content-type: application/x-tar\n"; ($f =~ /\.tar.Z$/) && print "Content-type: application/x-compress\n"; my $size=(stat("$odir/$f"))[7]; print "Content-length: $size\n\n"; open (F, "<$odir/$f"); read (F, my $content, $size); close (F); print $content; } else { err ("$f not found"); } exit 0; } sub check_prerequisites { # Must be root to install patches if ($o{install} && ($< != 0) && !$o{pretend}) { err ("You must be root to install patches"); } if ($o{install} && $o{safe} && !$o{proxy} && ($< != 0)) { err ("You must be root to use safe mode"); } # Set umask (esp. for patchxdir) umask (0022); # Check for wget executable my $found=''; foreach my $i (split (/ /, $o{wget})) { if (-x $i) { $found= $i; dbg ("Using $found"); last; } } $o{wget}=$found; # Get patchdiag.xref location $input{xref}="$o{xrefdir}/patchdiag.xref"; # Check patch download directory (-d $o{patchdir}) || err ("Can't find patch directory $o{patchdir}"); # Check for pager $ENV{PAGER} && ($pager=$ENV{PAGER}); # Check for valid prefix in $fromfiles and set input files/commands if ($o{fromfiles}) { if (-f "$o{fromfiles}/sysconfig/uname-a.out") { $input{pkginfo}= "<$o{fromfiles}/patch+pkg/pkginfo-l.out"; $input{showrev}= "<$o{fromfiles}/patch+pkg/showrev-p.out"; $input{uname} = "<$o{fromfiles}/sysconfig/uname-a.out"; } elsif (-f "$o{fromfiles}uname.out") { $input{pkginfo}= "<$o{fromfiles}pkginfo.out"; $input{showrev}= "<$o{fromfiles}showrev.out"; $input{uname} = "<$o{fromfiles}uname.out"; } elsif (-f "$o{fromfiles}/uname.out") { $input{pkginfo}= "<$o{fromfiles}/pkginfo.out"; $input{showrev}= "<$o{fromfiles}/showrev.out"; $input{uname} = "<$o{fromfiles}/uname.out"; } else { err ("Can't find pkginfo/showrev/uname output with prefix $o{fromfiles}"); } dbg ("Using $o{fromfiles} as prefix to read .out files"); } else { $input{pkginfo}= "$pkginfo -x $o{root} |"; $input{uname} = "$uname -a |"; } # Ask for Sun Online Account data interactively if ($o{download} || $o{install} || $o{readme} || $o{listhtml}) { if ($o{askauth}) { print "Sun Online Account User: "; chomp($o{user} = ); } if ($o{askauth} || ($o{user} && !$o{passwd})) { system "stty -echo"; $sttyset=1; print "Sun Online Account Password"; if ($o{askauth}) { print ": " } else { print " for $o{user}: " } chomp($o{passwd} = ); print "\n"; system "stty echo"; $sttyset=0; } } $o{user} && dbg ("Sun Online Account user is set"); $o{passwd} && dbg ("Sun Online Account passwd is set"); # Set default locale for forks $ENV{LC_ALL}='C'; } sub verify_files { my $id=$_[0]; my $readme=$_[1]; my @files=(); my %wl; # All $wl{all}="/etc/name_to_major /etc/driver_aliases /etc/driver_classes /etc/minor_perm /etc/security/exec_attr"; # 7/SPARC $wl{106541}="/etc/devlink.tab /etc/rmmount.conf /etc/syslog.conf /etc/vold.conf"; $wl{106857}="/usr/openwin/share/locale/C/props/basic_setting"; $wl{106978}="/etc/nsswitch.conf"; $wl{107589}="/etc/default/kbd"; $wl{107684}="/etc/inet/services /etc/mail/main.cf /etc/mail/subsidiary.cf"; $wl{107738}="/usr/openwin/lib/locale/compose.dir /usr/openwin/lib/locale/locale.alias /usr/openwin/lib/locale/locale.dir"; $wl{108800}="/etc/inet/inetd.conf /etc/init.d/cachefs.daemon"; # 8/SPARC $wl{108725}="/kernel/drv/st.conf"; $wl{108968}="/etc/rmmount.conf /etc/vold.conf"; $wl{108993}="/etc/asppp.cf /etc/nsswitch.conf /etc/pam.conf /etc/default/login"; $wl{108999}="/etc/pam.conf"; $wl{109077}="/etc/security/auth_attr /etc/security/prof_attr"; $wl{109134}="/etc/security/auth_attr /etc/security/prof_attr"; $wl{109695}="/etc/smartcard/opencard.properties"; $wl{109766}="/usr/openwin/lib/locale/ja/X11/fonts/TT/fonts.alias"; $wl{109887}="/etc/smartcard/ocf.classpath"; $wl{110369}="/etc/iu.ap"; $wl{110386}="/etc/security/auth_attr /etc/security/prof_attr"; $wl{110615}="/etc/mail/main.cf /etc/mail/subsidiary.cf"; $wl{110896}="/etc/inet/inetd.conf"; $wl{112438}="/etc/devlink.tab"; $wl{112663}="/usr/openwin/server/etc/OWconfig"; $wl{114542}="/usr/openwin/lib/X11/fonts/TrueType/ttmap/ttmaps.dir /usr/openwin/lib/X11/fonts/encodings/encodings.dir"; $wl{116973}="/etc/apache/mime.types"; $wl{117518}="/usr/openwin/lib/X11/fonts/F3bitmaps/fonts.dir"; # 9/SPARC $wl{112233}="/etc/iu.ap"; $wl{112874}="/etc/name_to_sysnum /etc/security/crypt.conf /etc/security/policy.conf"; $wl{112908}="/etc/krb5/krb5.conf"; $wl{112954}="/kernel/drv/uata.conf"; $wl{113073}="/etc/inet/inetd.conf"; $wl{113085}="/usr/openwin/lib/X11/fonts/TrueType/ttmap/ttmaps.dir /usr/openwin/lib/X11/fonts/encodings/encodings.dir"; $wl{113096}="/usr/openwin/server/etc/OWconfig"; $wl{113277}="/kernel/drv/st.conf /kernel/drv/sd.conf"; $wl{113471}="/usr/bin/cputrack"; $wl{113575}="/etc/mail/main.cf /etc/mail/subsidiary.cf"; $wl{114320}="/usr/openwin/server/etc/OWconfig"; $wl{114352}="/etc/inet/inetd.conf"; $wl{123184}="/usr/openwin/lib/X11/fonts/TrueType/ttmap/ttmaps.dir /usr/openwin/lib/X11/fonts/encodings/encodings.dir"; # 9/x86 $wl{114137}="/etc/mail/main.cf /etc/mail/subsidiary.cf"; $wl{114353}="/etc/inet/inetd.conf"; $wl{115168}="/etc/krb5/krb5.conf"; $wl{122300}="/etc/rc0.d/K05volmgt /etc/rc1.d/K05volmgt /etc/rc2.d/K05volmgt /etc/rc3.d/S81volmgt /etc/rcS.d/K05volmgt /etc/security/audit_class /etc/security/audit_event"; # 10/SPARC $wl{116298}="/usr/bin/wscompile /usr/bin/wsdeploy"; $wl{118666}="/etc/.java/.systemPrefs/.system.lock /etc/.java/.systemPrefs/.systemRootModFile"; $wl{118833}="/etc/logindevperm /etc/security/prof_attr /etc/vold.conf /etc/security/auth_attr"; $wl{119090}="/etc/ima.conf /kernel/drv/iscsi.conf"; $wl{119130}="/kernel/drv/fp.conf /kernel/drv/qlc.conf"; $wl{119313}="/etc/security/auth_attr"; $wl{120011}="/etc/inet/hosts /etc/inet/ipnodes /etc/inet/services /etc/nscd.conf /etc/security/auth_attr /etc/security/prof_attr /etc/default/dhcpagent /etc/security/device_policy /etc/iu.ap"; $wl{120222}="/kernel/drv/emlxs.conf"; $wl{120346}="/etc/hba.conf"; $wl{120410}="/etc/gtk-2.0/gtk.immodules /etc/sparcv9/gtk-2.0/gtk.immodules"; $wl{120460}="/etc/gtk-2.0/gtk.immodules /etc/sparcv9/gtk-2.0/gtk.immodules"; $wl{121430}="/etc/default/lu"; $wl{122212}="/etc/gconf/gconf.xml.defaults/apps/panel/default_setup/general/%gconf.xml"; $wl{124393}="/etc/security/auth_attr /etc/security/prof_attr"; $wl{125131}="/etc/passwd /etc/shadow"; $wl{125166}="/kernel/drv/qlc.conf"; $wl{125369}="/etc/logadm.conf"; # 10/x86 $wl{118855}="/etc/logindevperm /etc/security/prof_attr /etc/vold.conf /lib/libc.so.1 /etc/security/device_policy /etc/ipf/pfil.ap /boot/solaris/devicedb/master"; $wl{119091}="/etc/ima.conf /kernel/drv/iscsi.conf"; $wl{119131}="/kernel/drv/fp.conf /kernel/drv/qlc.conf"; $wl{119314}="/etc/security/auth_attr"; $wl{120012}="/boot/solaris/bootenv.rc /boot/solaris/devicedb/master /etc/default/dhcpagent /etc/inet/hosts /etc/inet/ipnodes /etc/inet/services /etc/nscd.conf /etc/security/auth_attr /etc/security/device_policy /etc/security/prof_attr"; $wl{120223}="/kernel/drv/emlxs.conf"; $wl{120273}="/etc/sma/snmp/snmpd.conf"; $wl{120347}="/etc/hba.conf"; $wl{120411}="/etc/gtk-2.0/gtk.immodules /etc/amd64/gtk-2.0/gtk.immodules"; $wl{120461}="/etc/gtk-2.0/gtk.immodules /etc/amd64/gtk-2.0/gtk.immodules"; $wl{121431}="/etc/default/lu"; $wl{122213}="/etc/gconf/gconf.xml.defaults/apps/panel/default_setup/general/%gconf.xml"; $wl{122255}="/platform/sun4u-us3/lib/libc_psr.so.1 /platform/sun4u-us3/lib/sparcv9/libc_psr.so.1"; $wl{124394}="/etc/security/auth_attr /etc/security/prof_attr"; $wl{125132}="/etc/passwd /etc/shadow"; $wl{125165}="/kernel/drv/qlc.conf"; $wl{125216}="/etc/wgetrc"; $wl{125370}="/etc/logadm.conf"; (-f $readme) || return (1); open (README, "<$readme") || err ("Can't open $readme ($!)"); FILE: while () { next if ($_ !~ /Files included with this patch:/); LINE: while () { chomp; next if (/^$/); last FILE if (! /\//); next if (/ELF/); # Ignore files with "ELF" in pathname - pkgchk bug s/\s+\(deleted\)//; s/^\s+//; s/^/\// unless /^\//; foreach my $i (split (/ /, $wl{all})) { ($_ eq $i) && next LINE; } if ($wl{$id}) { foreach my $i (split (/ /, $wl{$id})) { ($_ eq $i) && next LINE; } } push (@files, $_); } } close (README); dbg ("Number of files to check: %d", $#files+1); ($#files == -1) && return (1); # pkgchk has a limit of 1024 pathnames my @tfiles=@files; my $out=''; while ($#tfiles != -1) { my $fc=$#tfiles; ($fc >= 1023) && ($fc=1023); my $pfile="$o{tmpdir}/pca.pkgchk." . time() . $$; open (PFILE, ">$pfile") || err ("Can't open $pfile ($!)"); foreach my $f (@tfiles[0..$fc]) { print PFILE "$f\n" } close PFILE; $out .= `$pkgchk $o{root} -q -i $pfile 2>&1`; unlink $pfile; for (0..1023) { shift @tfiles; } } ($out) || return (1); if ($out =~ /file size |file cksum |pathname |pkgchk: ERROR/) { print "failed file verification:\n\n$out"; return (0); } return (1); } sub patch_apply_check { my $id=$_[0]; if ($id =~ /113039|113040|113041|113042|113043/) { if (!$pkgs{"SUNWsan"}) { return (0); } } if ($id eq "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 eq "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 eq "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 eq "118741") { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.11.11.23.55") { return (0); } } if ($id eq "118742") { if (!$pkgs{"SUNWtsr"} || $pkgs{"SUNWtsr"} ne "2.5.0,REV=2003.11.11.20.36") { return (0); } } if ($id eq "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 eq "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 eq "111097") { if (!$pkgs{"SUNWsan"}) { return (0); } if (!$pkgs{"SUNWqlc"}) { return (0); } } if ($id eq "111656") { if (!((exists $p{109460}) && ($p{109460}{irev} eq '05'))) { return (0); } } if ($id eq "111658") { if (!((exists $p{107469}) && ($p{107469}{irev} eq '08'))) { return (0); } } if ($id eq "111079") { if (!((exists $p{105375}) && ($p{105375}{irev} eq '26'))) { return (0); } } if ($id eq "107474") { if ((exists $p{107292}) && ($p{107292}{irev} ge '02')) { return (1); } return (0); } if ($id eq "106533") { if ($u{platform} ne 'SUNW,UltraSPARC-IIi-cEngine') { return (0); } } if ($id eq "106629") { if ($u{platform} ne 'CYRS,Superserver-6400') { return (0); } } if ($id eq "112780") { if (!($u{model} eq 'sun4u')) { return (0); } } if ($id eq "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 eq "111891") { if (!$pkgs{"SUNWutr"} || $pkgs{"SUNWutr"} ne "1.3_12.c,REV=2001.07.16.20.52") { return (0); } } if (($id eq "114255") && ($u{arch} ne "sparc")) { return (0); } if (($id eq "114256") && ($u{arch} ne "i386")) { return (0); } if (($id eq "115328") && ($u{osrel} ne "5.8")) { return (0); } if (($id eq "115342") && ($u{osrel} ne "5.9")) { return (0); } if (($id eq "115343") && ($u{osrel} ne "5.9")) { return (0); } if (($id eq "119346") && ($u{osrel} ne "5.10")) { return (0); } if (($id eq "115766") && ($u{arch} ne "sparc")) { return (0); } if (($id eq "120091") && ($u{arch} ne "i386")) { return (0); } if (($id eq "120879") && ($u{arch} ne "sparc")) { return (0); } if (($id eq "120880") && ($u{arch} ne "i386")) { return (0); } if (($id eq "120954") && ($u{arch} ne "sparc")) { return (0); } if (($id eq "120955") && ($u{arch} ne "i386")) { return (0); } if (($id =~ /115835|115836/) && (!$pkgs{"SUNWgscr"})) { return (0); } if (($id eq "119300") && ($u{osrel} ne "5.8")) { return (0); } if (($id eq "119301") && ($u{osrel} ne "5.9")) { return (0); } if (($id eq "119302") && ($u{osrel} ne "5.10")) { return (0); } if ($id =~ /109357/) { if ((exists $p{109778}) && ($p{109778}{irev} ge '08')) { return (0); } if ((exists $p{109778}) && ($p{109778}{crev} ge '08')) { return (0); } } if (($id eq "113434") && (!$pkgs{"SUNWwbsup"})) { return (0) } if (($id eq "109700") && ($u{osrel} ne "5.6")) { return (0); } if (($id eq "109701") && ($u{osrel} ne "5.7")) { return (0); } if (($id eq "111248") && ($u{osrel} ne "5.6")) { return (0); } if (($id eq "111249") && ($u{osrel} ne "5.7")) { return (0); } if (($id eq "111250") && ($u{osrel} ne "5.8")) { return (0); } if (($id eq "115548") && ($u{osrel} ne "5.9")) { return (0); } if (($id eq "108553") && ($u{osrel} ne "5.8")) { return (0); } if (($id eq "108834") && (($u{osrel} ne "5.5.1") || ($u{osrel} ne "5.6") || ($u{osrel} ne "5.7"))) { return (0); } if (($id eq "112125") && (($u{osrel} ne "5.6") || ($u{osrel} ne "5.7"))) { return (0); } if (($id eq "112126") && (($u{osrel} ne "5.8") || ($u{osrel} ne "5.9"))) { return (0); } if (($id eq "123200") && ($u{osrel} ne "5.8")) { return (0); } if (($id eq "123201") && ($u{osrel} ne "5.9")) { return (0); } if (($id eq "119527") && ($u{arch} ne "sparc")) { return (0) } if (($id eq "119528") && ($u{arch} ne "i386")) { return (0) } if (($id eq "119530") && ($u{arch} ne "sparc")) { return (0) } if (($id eq "119531") && ($u{arch} ne "i386")) { return (0) } if (($id eq "119325") && ($u{arch} ne "sparc")) { return (0) } if (($id eq "119326") && ($u{arch} ne "i386")) { return (0) } if (($id eq "127498") && ($u{osrel} ne "5.8")) { return (0) } if (($id eq "127499") && ($u{osrel} ne "5.8")) { return (0) } if (($id eq "125950") && ($u{osrel} ne "5.9")) { return (0) } if (($id eq "125951") && ($u{osrel} ne "5.9")) { return (0) } if (($id eq "125952") && ($u{osrel} ne "5.10")) { return (0) } if (($id eq "125953") && ($u{osrel} ne "5.10")) { return (0) } if (($id eq "121430") && ($p{121430}{crev} ge '16') && (!$pkgs{"SUNWlucfg"})) { return (0) } if (($id eq "121431") && ($p{121431}{crev} ge '17') && (!$pkgs{"SUNWlucfg"})) { return (0) } if (($id eq "121428") && ($p{121430}{crev} ge '08')) { if (!((exists $p{121430}) && ($p{121430}{irev} ge '16'))) { return (0) } } if (($id eq "125276") && ($u{arch} ne "sparc")) { return (0) } if (($id eq "125277") && (($u{osrel} ne "5.9") || ($u{arch} ne "i386"))) { return (0) } if (($id eq "125278") && (($u{osrel} ne "5.10") || ($u{arch} ne "i386"))) { return (0) } return (1); } sub get_uname { # Get information about host open(UNAME, $input{uname}) || err ("Can't open $input{uname} ($!)"); $_=; $_ || err ("Empty uname output"); chomp; close UNAME; ($u{osname}, $u{hostname}, $u{osrel}, $u{osversion}, $u{model}, $u{arch}, $u{platform})= split (/ /, $_); ($u{osname} && $u{hostname} && $u{osrel} && $u{osversion} && $u{model} && $u{arch} && $u{platform}) || err ("Can't parse ouput from $input{uname}:\n $_"); } sub get_installed_packages { my $package; # Read pkginfo open(PKGINFO, $input{pkginfo}) || err ("Can't open $input{pkginfo} ($!)"); if ($input{pkginfo} =~ /pkginfo-l.out/) { while() { if (/\s+PKGINST:\s+(\S+)$/) { $package = $1; } if (/\s+VERSION:\s+(\S+)$/) { $pkgs{$package}=$1; } } } else { while() { ($_ =~ /^(\S+) /) || err ("Can't parse output from $input{pkginfo}:\n $_"); $package=$1; # Removing trailing .2/.3/... (multiple versions of same package) $package =~ s/\..*//; $_= ; ($_ =~ / (\S+)$/) || err ("Can't parse output from $input{pkginfo}:\n $_"); $pkgs{$package}=$1; } } close(PKGINFO); } sub get_installed_patches { my $list=''; my $done=0; my $showrev_cmd=$showrev; ( -x $showrev_cmd) || ($showrev_cmd=''); my $patchadd_cmd=$o{patchadd}; ( -x $patchadd_cmd) || ($patchadd_cmd=''); # On Solaris <= 8, showrev doesn't support -R. Use patchadd instead. my ($major, $minor) = split (/\./, $u{osrel}); if (($minor <= 8) && $o{root}) { $showrev_cmd='' } if ($o{fromfiles}) { dbg ("Reading from $input{showrev}"); open(SHOWREV, $input{showrev}) || err ("Can't open $input{showrev} ($!)"); $/=""; $list= ; $/="\n"; close SHOWREV; $done=1; } else { foreach my $cmd ($showrev_cmd, $patchadd_cmd) { next unless $cmd; $input{showrev}="$cmd -p $o{root}"; dbg ("Reading from $input{showrev}"); $list=`$input{showrev}`; if (!$?) { $done=1; last } else { dbg ("Failed: $list") } } } $done || err ("Couldn't get list of installed patches"); $list || ($list= "No patches are installed\n"); my @list= split(/\n/, $list); foreach my $i (sort @list) { # Known formats of patch IDs: # 123456-78 : Regular Sun # IDR123456-78 : Unsupported (pre-release) Sun # 123-45 : EMC # CKPSP123456-78 : Checkpoint # CPFWSP410002-01: Checkpoint # KDE20060107-01 : KDE # IDCE32-02 : DCE # DP550001-05 : HP Data Protector # DP550011-1 : HP Data Protector # PSE400SOL023 : Citrix # ME113SB222 : Citrix # Q995801-01 : SUNWluxop # T000000-01 : Terix DST patch 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}).*/) || ($i =~ /^Patch:\s+CPFWSP(\d{6})-(\d{2}).*/) || ($i =~ /^Patch:\s+KDE(\d{8})-(\d{2}).*/) || ($i =~ /^Patch:\s+IDCE(\d{2})-(\d{2}).*/) || ($i =~ /^Patch:\s+DP(\d{6})-(\d{2}).*/) || ($i =~ /^Patch:\s+DP(\d{6})-(\d{1}).*/) || ($i =~ /^Patch:\s+PSE(\d{3})SOL(\d{3}).*/) || ($i =~ /^Patch:\s+ME(\d{3})SB(\d{3}).*/) || ($i =~ /^Patch:\s+Q(\d{6})-(\d{2}).*/) || ($i =~ /^Patch:\s+T(\d{6})-(\d{2}).*/) ) { my ($id, $rev)=($1,$2); init_patch($id); $p{$id}{irev}= $rev; if ($i =~ / Obsoletes: ([-0-9, ]*) /) { for my $j (split (/,* /, $1)) { my ($oid, $orev) = split (/-/, $j); ($id eq $oid) && next; init_patch($oid); $p{$oid}{iobsoletedby}="$id-$rev"; #dbg ("$oid-$orev obsoleted by $id-$rev"); } } if ($i =~ / Incompatibles: ([-0-9, ]*) /) { for my $j (split (/,* /, $1)) { my ($iid, $irev) = split (/-/, $j); init_patch($iid); #dbg ("$iid-$irev incompatible with $id-$rev"); } } next; } next if ($i =~ "No patches are installed"); next if ($i =~ "No patches installed"); next if ($i =~ /^$/); print ("WARNING: Can't parse output from $input{showrev}:\n $i\n"); } } sub get_current_xref { # Download most recent patchdiag.xref, if requested return if ($o{nocheckxref}); lock_free($o{xrefdir}, "xref", 60) || err ("Another instance of pca is downloading $input{xref} right now"); # Remove possibly left-over size zero file if (-z $input{xref}) { unlink ($input{xref}) } # If a local copy of the xref file exists, and is older than a # certain time period, we try to download it, but only if we # can write to the directory where it is located. if ((-f $input{xref}) && (!$o{getxref})) { my $interval=10800; # 3 hours my $current=(stat($input{xref}))[9]; my $now=time(); my $age=$now-$current; dbg ("xref now : " . localtime($now)); dbg ("xref current: " . localtime($current)); dbg ("xref age : " . $age); if ($age < $interval) { return; } elsif (! -w $o{xrefdir}) { print "Can't download xref file, as $o{xrefdir} is unwritable\n"; return; } } print "Download xref-file to $input{xref}: " unless ($o{noheader} || $o{proxy}); (-w $o{xrefdir}) || err ("Can't write to xref download directory $o{xrefdir}"); if ((-f $input{xref}) && (! -w $input{xref})) { if ($o{getxref}) { err ("Can't write to $input{xref}\n"); } else { print "Can't write to $input{xref}\n"; return; } } lock_create($o{xrefdir}, "xref", 1) || err ("Another instance of pca is downloading $input{xref} right now"); my $success=0; if ($o{xrefurl} =~ /^file:/) { dbg ("Getting patchdiag.xref from $o{xrefurl}"); my $path=$o{xrefurl}; $path =~ s/^file://; if (-r "$path/patchdiag.xref") { if (-s $input{xref}) { my $mtime1=(stat("$path/patchdiag.xref"))[9]; my $mtime2=(stat($input{xref}))[9]; if ($mtime2 >= $mtime1) { $success=1; } } if (!$success) { copy ("$path/patchdiag.xref", $input{xref}); my ($atime, $mtime)=(stat("$path/patchdiag.xref"))[8..9]; $success=1; } } } if (!$success && ($o{xrefurl} =~ /^http:|^https:|^ftp/)) { dbg ("Getting patchdiag.xref from $o{xrefurl}"); $o{wget} || err ("Can't find wget executable"); $xrefdl="$input{xref}"; my $force=''; if ($o{force} && ($o{xrefurl} =~ /pca-proxy\.cgi/)) { $force=":force" } `$o{wget} $o{wgetq} "$o{xrefurl}patchdiag.xref$force" -O $input{xref} $o{wgetout}`; $xrefdl=""; if (!$? && (-s $input{xref})) { $success=1 } } if (!$success) { dbg ("Getting patchdiag.xref from sunsolve.sun.com"); $o{wget} || err ("Can't find wget executable"); $xrefdl="$input{xref}"; (-s $input{xref}) && rename ("$input{xref}", "$input{xref}.tmp"); if ($o{nocache}) { $o{nocache}='--cache=off' } else { $o{nocache}='' } my $try=1; while ($try <= $o{dltries} ) { if ($o{user} && $o{passwd}) { `$o{wget} $o{wgetq} $o{wgetproxy} $o{nocache} "http://sunsolve.sun.com/pdownload.do?target=patchdiag.xref" -O $input{xref} --http-user='$o{user}' --http-passwd='$o{passwd}' $o{wgetout}`; } else { `$o{wget} $o{wgetq} $o{wgetproxy} $o{nocache} --header "Cookie: SunSolve_SLA=accept=y" "http://sunsolve.sun.com/pdownload.do?target=patchdiag.xref" -O $input{xref} $o{wgetout}`; } last if (!$? && (-s $input{xref})); $try++; if ($try <= $o{dltries}) { sleep ($try*2) } } $xrefdl=""; if (!$? && (-s $input{xref})) { $success=1; (-s "$input{xref}.tmp") && unlink ("$input{xref}.tmp") } else { (-s "$input{xref}.tmp") && rename ("$input{xref}.tmp", "$input{xref}") } } lock_remove($o{xrefdir}, "xref"); if ($success) { print "done\n" unless ($o{noheader} || $o{proxy}); my $now=time(); utime $now, $now, $input{xref}; if ($o{xrefown} || ($o{xrefdir} =~ /\/home\//)) { chmod 0644, $input{xref}; } else { chmod 0666, $input{xref}; } } else { (-z $input{xref}) && unlink ($input{xref}); print "failed\n" unless ($o{noheader} || $o{proxy}); } } sub get_current_patches { # Read patchdiag.xref # open(XREF, "<$input{xref}") || err ("Can't open xref file $input{xref} ($!)"); $_=; $_ || err ("Empty file $input{xref}"); if ($_ =~ /PATCHDIAG TOOL CROSS-REFERENCE FILE AS OF (.*) /) { print "Using $input{xref} from $1\n" unless $o{noheader}; } $/=""; my $xref= ; $/="\n"; close XREF; my @xref= split( /\n/, $xref ); # Temporary push (@xref, '126509|02|Jun/28/07| | | | |Unbundled|||QFS 4.6 Linux Patch'); # 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) { # Ignore comment lines if ($i =~ /^##/) { next; } if ($i !~ /^\d{6}\|\d{2}\|.*\|.\|.\|.\|..\|.*\|.*\|.*\|.*$/) { err ("Can't parse input from $input{xref}:\n $i") } my ($id, $crev, $reldate, $rFlag, $sFlag, $oFlag, $byFlag, $os, $archs, $pkgs, $synopsis )= split( /\|/, $i); init_patch($id); # If an installed patch revision is marked bad, note this. if (($p{$id}{irev} eq $crev) && ($byFlag =~ ".B")) { $p{$id}{ibad}= 1; dbg ("Bad patch installed: $id-$p{$id}{irev}"); } # 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"; #dbg ("$id-$crev obsoleted by $p{$id}{obsoletedby}"); } } if ($synopsis =~ /OBSOLETED by (\d{6})/) { if ($id ne $1) { $p{$id}{obsoletedby}="$1-01"; #dbg ("$id-$crev obsoleted by $p{$id}{obsoletedby}"); } } } # Patches might be obsoleted by installed patches, which are not # (yet) listed in patchdiag.xref. # if (($p{$id}{iobsoletedby}) && (!$p{$id}{obs})) { $p{$id}{obs}=1; $p{$id}{obsoletedby}=$p{$id}{iobsoletedby}; } # 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}=''; foreach my $r (split /\;/, $pkgs) { if ($r =~ /^\d{6}-\d{2}/) { my ($r_id, $r_rev)= split (/-/, $r); init_patch($r_id); #dbg ("$r_id-$r_rev incompatible with $id-$crev"); } 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}{recf}= $p{$id}{secf}= 0; $p{$id}{os}= ''; $p{$id}{pkgs}= ''; $p{$id}{stop}= ''; $p{$id}{ignore}= ''; $p{$id}{reldate}= 'Jan/01/71'; $p{$id}{obsoletedby}= ''; $p{$id}{iobsoletedby}= ''; $p{$id}{archs}= ''; $p{$id}{requires}= ''; $p{$id}{listed}= 0; $p{$id}{ibad}= 0; $p{$id}{init}= 1; $p{$id}{dfori}= 0; $p{$id}{dloutput}= ''; $p{$id}{dlfile}= ''; } sub print_patch { my $id=$_[0]; my ($char, $h_char, $irev, $crev, $rec, $sec, $bad, $age, $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}{recf}) { $rec='r'; }; if ($p{$id}{rec}) { $rec='R'; } $sec='-'; if ($p{$id}{secf}) { $sec='s'; }; if ($p{$id}{sec}) { $sec='S'; } $bad='-'; if (($p{$id}{irev} eq "00") && ($p{$id}{bad} )) { $bad='B' } if (($p{$id}{irev} ne "00") && ($p{$id}{ibad})) { $bad='B' } $synopsis= $p{$id}{synopsis}; $age=calculateage($p{$id}{reldate}); if ($age > 999) { $age=999; } if (!$o{listhtml}) { my $out=$o{format}; $id=sprintf ("%6d", $id); $out =~ s/%p/$id/; $out =~ s/%i/$irev/; $out =~ s/%e/$char/; $out =~ s/%c/$crev/; $out =~ s/%r/$rec/; $out =~ s/%s/$sec/; $out =~ s/%b/$bad/; $age=sprintf ("%3d", $age); $out =~ s/%a/$age/; $out =~ s/%y/$synopsis/; print "$out\n"; } 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 ($o{patchurl} =~ /pca-proxy\.cgi/) { printf "%6d", $id; } else { printf "%6d", $id; } printf "%2s%1s%2s%1s%1s%1s%3s", $irev, $h_char, $crev, $rec, $sec, $bad, $age; if ($o{patchurl} =~ /pca-proxy\.cgi/) { printf "%s\n", $synopsis; } elsif ($o{user} && $o{passwd}) { printf "%s\n", $synopsis; } else { printf "%s\n", $synopsis; } } } sub print_header { if (!$o{listhtml} && !$o{noheader}) { print "Host: $u{hostname} ($u{osname} $u{osrel}/$u{osversion}/$u{arch}/$u{model})\n"; if ($o{root}) { my $r=$o{root}; $r =~ s/-R //; print "Root: $r\n" } print "List: @slist\n\n"; my $hdr= my $sep=$o{format}; $hdr =~ s/%p/Patch /; $hdr =~ s/%i/IR/; $hdr =~ s/%e/ /; $hdr =~ s/%c/CR/; $hdr =~ s/%r/R/; $hdr =~ s/%s/S/; $hdr =~ s/%b/B/; $hdr =~ s/%a/Age/; $hdr =~ s/%y/Synopsis/; $sep =~ s/%p/------/; $sep =~ s/%i/--/; $sep =~ s/%e/-/; $sep =~ s/%c/--/; $sep =~ s/%r/-/; $sep =~ s/%s/-/; $sep =~ s/%b/-/; $sep =~ s/%a/---/; if ($sep =~ /%y/) { my $ysep = '-' x (78 - (length ($sep)-2)); $sep =~ s/%y/$ysep/; } print "$hdr\n$sep\n"; } if ($o{listhtml}) { print "\n"; print "\n\n"; print "PCA report for $u{hostname}\n"; print "\n\n"; print "

Host: $u{hostname} ($u{osname} $u{osrel}/$u{osversion}/$u{arch}/$u{model})
\n"; if ($o{root}) { my $r=$o{root}; $r =~ s/-R //; print "Root: $r
\n" } print "List: @slist

\n\n"; print ""; print ""; print ""; print ""; print ""; print "\n"; } } sub print_footer { if ($o{listhtml}) { print "
PatchIRCRRSBAgeSynopsis
\n\n\n"; } } sub get_readme { my $pp=$_[0]; my ($id, $rev)= split (/-/, $pp); my $rfile="$o{tmpdir}/README.$pp" . time() . $$; $p{$id}{dlfile}=$rfile; print STDERR "Download README for $pp: " unless $o{proxy}; # If patch is available in unzipped format, use its README if (-f "$o{patchdir}/$pp/README.$pp") { dbg ("Getting README from $o{patchdir}/$pp"); copy ("$o{patchdir}/$pp/README.$pp", $rfile); (-s $rfile) && goto DONE; } # 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 "$o{patchdir}/$pp.zip") && !$o{pforce}) { dbg ("Getting README from $o{patchdir}/$pp.zip"); `$unzip -p $o{patchdir}/$pp.zip $pp/README.$pp >$rfile 2>/dev/null`; (-s $rfile) && goto DONE; } # Get README from local patch server if ($o{patchurl} =~ /^file:/) { dbg ("Getting README from $o{patchurl}"); my $path=$o{patchurl}; $path =~ s/^file://; (-r "$path/README.$pp") && copy ("$path/README.$pp", $rfile); (-s $rfile) && goto DONE; } if ($o{patchurl} =~ /^http:|^https:|^ftp/) { dbg ("Getting README from $o{patchurl}"); $o{wget} || err ("Can't find wget executable"); my $force=''; if ($o{force} && ($o{patchurl} =~ /pca-proxy\.cgi/)) { $force=":force" } `$o{wget} $o{wgetq} -O $rfile $o{patchurl}README.$pp$force $o{wgetout}`; (-s $rfile) && goto DONE; } # Try download from restricted patch server, if the user provided # Sun Online Account data if ($o{user} && $o{passwd}) { dbg ("Getting README from sunsolve.sun.com"); $o{wget} || err ("Can't find wget executable"); my $try=1; while ($try <= $o{dltries}) { `$o{wget} $o{wgetq} $o{wgetproxy} "http://sunsolve.sun.com/pdownload.do?target=$pp&method=r" -O $rfile --http-user='$o{user}' --http-passwd='$o{passwd}' $o{wgetout}`; if (!$? && (-s $rfile)) { last; } $try++; if ($try <= $o{dltries}) { sleep ($try*2) } } } else { print STDERR "No Sun Online Account data - " unless $o{proxy}; } DONE: $p{$id}{dlfile}=""; if (-s $rfile) { print STDERR "done\n" unless $o{proxy}; return ($rfile); } else { print STDERR "failed\n" unless $o{proxy}; unlink ($rfile); return (); } } sub update { if ($o{update} eq "never") { dbg ("Never update"); return; } # If we can't write to pca, update won't work. if (($o{update} eq 'now') || ($o{update} eq 'auto')) { if (! -w $0) { err ("Update option unavailable: Can't write to $0") } } if ($o{update} eq 'auto') { dbg ("Auto update"); my $interval=86400; # One day my $current=(stat($0))[9]; my $now=time(); my $age=$now-$current; dbg ("pca now : " . localtime($now)); dbg ("pca current: " . localtime($current)); dbg ("pca age : " . $age); if ($age < $interval) { dbg ("age lower than interval"); return; } } my $udir= "$o{tmpdir}/pca." . time() . $$; mkdir $udir,0755 || err ("Can't create temporary directory $udir ($!)"); my $ufile= "$udir/" . basename ($0); dbg ("$ufile"); # Copy pca to temporary directory, keeping the timestamps. my ($atime, $mtime) = (stat($0))[8..9]; copy ($0, $ufile); utime $atime, $mtime, $ufile; `cd $udir ; $o{wget} $o{wgetq} $o{wgetproxy} -N "$o{pcaurl}pca"`; $? && err ("Could not get pca from $o{pcaurl}"); my $newv; open (F, "<$ufile"); while () { if ($_ =~ /^my \$version=\'(\d{8}-\d{2})\'/) { $newv=$1; dbg ("Old version: $version"); dbg ("New version: $newv"); last; } } close (F); my $updated=0; if ($newv eq $version) { if ($o{update} eq "auto") { my $now=time(); utime $now, $now, $0; } else { print "No new version available\n"; } } else { print "New version available: $newv (current version: $version)\n"; print "\nChanges:\n\n"; open (F, "<$ufile"); while () { if ($_ =~ /=head2 Version $newv/) { print "Version $newv\n"; while () { if ($_ =~ /=head2 Version $version/) { last } $_ =~ s/=head2 //; print $_; } } } close (F); if ($o{update} ne "check") { print "Updating .. "; dbg ("Copy $ufile to $0\n"); copy ($ufile, $0); print "done.\n"; $updated=1; } } unlink ($ufile); rmdir ($udir); if (($o{update} eq "auto") && !$updated) { return } exit 0; } sub dbg { $o{debug} || return; if ($o{proxy}) { open (F, ">> $o{dbgfile}"); my $now= localtime; print F $now, ": ", @_, "\n"; close (F); } else { print "## ", @_, "\n"; } } 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}; return (int(($currenttime-timelocal(0,0,0,$day,$month,$year))/86400)); } sub lock_create { my $lockd=$_[0]; my $tag=$_[1]; my $maxretry=$_[2]; my $lockf="$lockd/.pcaLock.$tag"; lock_free ($lockd, $tag, $maxretry) || return (0); unlink "$lockf"; sysopen (LOCKF, $lockf, O_RDWR|O_CREAT|O_EXCL) || err ("Can't write $lockf ($!)"); print LOCKF "$$\n"; close LOCKF; chmod 0666, $lockf; $locks{$tag}=$lockf; return (1); } sub lock_free { my $lockd=$_[0]; my $tag=$_[1]; my $maxretry=$_[2]; my $lockf="$lockd/.pcaLock.$tag"; my $retry=0; while ($retry < $maxretry) { if (-s "$lockf") { open (LOCKF, "<$lockf"); chomp(my $pid = ); close LOCKF; if (kill (0, $pid) || ($! eq "Not owner")) { dbg ("Locking $lockf failed"); $retry++; ($retry < $maxretry) && sleep (1); next; } } return (1); } return (0); } sub lock_remove { my $lockd=$_[0]; my $tag=$_[1]; unlink "$locks{$tag}"; $locks{$tag}=''; } sub log_msg { ($o{syslog}) && system("$o{logger} -t pca -p $o{syslog}.info \"@_\""); } sub err { if ($o{proxy}) { print "Content-type: text/plain\n"; print "Status: 500 @_\n\n"; print "Internal Error: @_\n"; } else { print STDERR "\nERROR: @_\n"; } cleanup(); exit 1; } sub handler { err ("Caught a SIG@_"); } sub cleanup { dbg ("Cleanup"); if ($xrefdl) { dbg ("Removing $xrefdl"); unlink "$xrefdl"; } foreach my $id (keys %p) { if ($p{$id}{dlfile}) { dbg ("Removing $p{$id}{dlfile}"); unlink "$p{$id}{dlfile}"; } } if (@rlist) { dbg ("Removing @rlist"); unlink (@rlist); } $patchxdir && rmtree ($patchxdir); foreach my $tag (keys %locks) { if ($locks{$tag}) { dbg ("Removing $locks{$tag}"); unlink "$locks{$tag}"; } } $sttyset && system "stty echo"; } sub parse_args { # Get internal defaults foreach my $opt (@options) { my ($long, $short, $arg, $argtxt, $default, $help)=split (/\|/, $opt); if ($arg =~ /@/) { @{$o{$long}}=split (/ /, $default); } else { $o{$long}=$default; } } (basename($0) eq "pca-proxy.cgi") && ($o{proxy}=1); $o{proxy} && ($o{xrefdir} = getcwd()); # Get defaults from optional configuration file(s) my @conf=(); my $conf_dbg=''; push (@conf, dirname($0)."/pca.conf"); push (@conf, dirname(dirname($0))."/etc/pca.conf"); push (@conf, "/etc/pca.conf"); $ENV{HOME} && push (@conf, $ENV{HOME}."/.pca"); push (@conf, "pca.conf"); foreach my $i (@conf) { open (CONF, "<$i") || next; $conf_dbg .= "$i "; while () { chomp; s/\s*#.*$//; s/^\s*//; s/\s*$//; next if /^$/; # Deprecated if (/(\d{6})\s+ignore/) { push (@{$o{ignore}}, $1); next } if (/(\d{6}-\d{2})\s+ignore/) { push (@{$o{ignore}}, $1); next } if (/(\d{6})\s+\+rec/) { push (@{$o{rec}}, $1); next } if (/(\d{6})\s+\+sec/) { push (@{$o{sec}}, $1); next } next unless (/(\w+)\s*=\s*(.+)/); my ($name, $value)=($1,$2); foreach my $opt (@options) { my ($long, $short, $arg, $argtxt, $default, $help)=split (/\|/, $opt); next if ($help eq "INTERNAL"); next unless ($name eq $long); if ($arg =~ /@/) { push (@{$o{$long}}, split (/ /, $value)) } else { $o{$long}=$value } last } } } # Get defaults from optional environment variables (PCA_*) foreach my $opt (@options) { my ($long, $short, $arg, $argtxt, $default, $help)=split (/\|/, $opt); next if ($help eq "INTERNAL"); my $env=uc("PCA_$long"); next unless ($ENV{$env}); if ($arg =~ /@/) { push (@{$o{$long}}, split (/ /, $ENV{$env})) } else { $o{$long}=$ENV{$env} } } ($ENV{TMPDIR}) && (-d $ENV{TMPDIR}) && ($o{tmpdir}= $ENV{TMPDIR}); # Proxy mode ? if ($o{proxy}) { if ($#ARGV != 0) { err ("Missing argument") } if ($ARGV[0] =~ /:force/) { $ARGV[0] =~ s/:force//; $o{pforce}=1 } if ((($ARGV[0] !~ /^patchdiag.xref$/) && ($ARGV[0] !~ /^\d{6}-\d{2}\.(zip|tar|tar\.Z)$/) && ($ARGV[0] !~ /^README\.\d{6}-\d{2}$/))) { err ("Illegal argument"); } $o{proxy}=$ARGV[0]; } else { # Get command line options my @olist=(); foreach my $opt (@options) { my ($long, $short, $arg, $argtxt, $default, $help)=split (/\|/, $opt); next if (($help eq "INTERNAL") || ($help eq "ENVFILE")); $short && ($long="$long|$short"); $arg && ($long="$long=$arg"); push (@olist, $long); } Getopt::Long::config ("bundling", "no_ignore_case"); GetOptions (\%o, @olist) || usage() && exit 1; } if ($o{help}) { usage(); exit 0 } if ($o{version}) { version(); exit 0 } $o{listhtml} && ($o{list}=1); $o{pretend} && ($o{install}=1); $o{readme} && ($o{noheader}=1); $o{root} && ($o{root}="-R $o{root}"); $o{wgetproxy} && ($o{wgetproxy}="--execute http_proxy=$o{wgetproxy}"); $o{debug} && ($o{wgetq}=''); $o{debug} && $o{proxy} && ($o{wgetout}=">>$o{dbgfile} 2>&1"); if (!$o{patchurl} && $o{localurl}) { $o{patchurl}=$o{localurl} } if (!$o{xrefurl} && $o{localurl}) { $o{xrefurl}=$o{localurl} } if ($o{patchurl} =~ /\.cgi$/) { $o{patchurl} .= "?" } if ($o{xrefurl} =~ /\.cgi$/) { $o{xrefurl} .= "?" } ($o{patchdir} !~ /^\//) && ($o{patchdir}= getcwd()."/$o{patchdir}"); ($o{xrefdir} !~ /^\//) && ($o{xrefdir}= getcwd()."/$o{xrefdir}"); # Set defaults $o{operands} && !@ARGV && (@ARGV=(split (/\s+/, $o{operands}))); ($o{download} || $o{install} || $o{readme} || $o{getxref} || $o{proxy}) || ($o{list}=1); # Show all options and configuration files in debug output foreach my $opt (@options) { my ($long, $short, $arg, $argtxt, $default, $help)=split (/\|/, $opt); if ($arg =~ /@/) { next unless (@{$o{$long}}); dbg ("Option $long: @{$o{$long}}"); } else { next if ($o{$long} eq $default); dbg ("Option $long: $o{$long}") } } dbg ("ARGV: @ARGV"); dbg ("Version: $version"); ($conf_dbg) && dbg ("Config files: $conf_dbg"); foreach my $i ('stop', 'ignore') { foreach my $j (@{$o{$i}}) { #dbg ("$i: $j"); if ($j =~ /^(\d{6})$/) { init_patch($1); ($p{$1}{$i}= "00"); next } if ($j =~ /^(\d{6})-(\d{2})$/) { init_patch($1); ($p{$1}{$i}= $2); next } err ("Invalid patch ID with --$i: $j") } } foreach my $i ('rec', 'sec') { foreach my $j (@{$o{$i}}) { #dbg ("$i: $j"); if ($j =~ /^(\d{6})(-\d{2})*$/) { init_patch($1); ($p{$1}{"${i}f"}= 1); next } err ("Invalid patch ID with --$i: $j") } } if (($o{update} ne "never") && ($o{update} ne "check") && ($o{update} ne "now") && ($o{update} ne "auto")) { err ("Invalid TYPE specified with --update: $o{update}"); } } sub usage { print<, which shows a list of all installed and missing patches: Host: myhost (SunOS 5.9/Generic_117171-09/sparc/sun4u) List: all Patch IR CR RSB Age Synopsis ------ -- - -- --- --- -------------------------------------------------- 112785 42 < 43 RS- 18 X11 6.6.1: Xsun patch 112787 01 = 01 --- 999 X11 6.6.1: twm patch 112807 10 = 10 RS- 9 CDE 1.5: dtlogin patch 113039 -- < 06 --- 76 SAN 4.4.1: Sun StorEdge Traffic Manager patch 113040 -- < 08 R-- 77 SAN 4.4.1: fctl/fp/fcp driver patch 113477 02 > -- --- 999 NOT FOUND IN CROSS REFERENCE FILE! 117114 -- < 02 --- 4 CDE 1.5: sdtwebclient patch The first column (I) contains the patch number, followed by the installed revision (I) and the current revision (I), with one of E, E, or = between them, which tells whether the installed patch revision is lower, equal or higher than the current revision of the patch. The I column lists the Recommended/Security/Bad flag of the patch. I shows the number of days since the patch was released, and I shows a short description of the patch. On this system, revision 42 of patch 112785 is installed, but a newer revision (43) is available. This patch is marked Recommended/Security. Patch 112787 is installed in revision 01, which is the most recent revision of the patch. 112807 is marked Recommended/Security, and it's up-to-date. Patches 113039, 113040 and 117114 are not installed at all, and 113040 is marked Recommended. 113477 is installed, but not listed in the cross-reference file. New Solaris update releases often have patches pre-installed which are not yet listed in patchdiag.xref. Often one patch requires other patches to be installed before it can be installed. pca resolves these dependencies, and lists patches in their correct order. This can be seen in the patch list when a greater patch number is shown before a lower one, or when a patch which is not marked R/S is shown on the list of missing R/S patches. =head1 OPTIONS =over 5 =item -l, --list List patches. See OPERANDS on how to specify which patches are listed. =item -L, --listhtml Like I<-l>, but generates output in HTML format, including links to patch READMEs and downloads. If I is set and points at a local patch proxy, the links in HTML output will point there, too. =item -d, --download Download patches. See OPERANDS on how to specify which patches are downloaded. Patches are placed in the current directory or in I, if set. A Sun Online Account is required. =item -i, --install Download and install patches. See OPERANDS on how to specify which patches are installed. Requires pca to be run as root. Downloaded patches are removed after successful installation, unless I<--download> is used, too. =item -I, --pretend Like I<-i>, but only pretend to install patches. Can be used to find out if any of the patches require a reboot. =item -r, --readme Display patch READMEs. See OPERANDS on how to specify which READMEs are displayed. The patch README is extracted from a previously downloaded patch file or downloaded directly from Sun, if Sun Online Account data is provided. =item -x, --getxref Download most recent patch cross-reference file. If the file does not exist or is older than 3 hours, pca tries to download it on its own before anything else. =item -X, --xrefdir=DIR Set location of the cross-reference file. The default is I (in proxy mode, the default is the current directory). By default, patchdiag.xref is writable for all users. If the I option is set, or the I option contains I, the cross reference file will be writable by the current user only. =item -y, --nocheckxref Do not check for updated patch cross-reference file. Use this option to maintain a global baseline patch set. =item --xrefown If set, patchdiag.xref will be writable for the current user only. =item --nocache If a proxy is used to access the Internet, this option advises it to not cache patchdiag.xref. Useful if the proxy can't be trusted to always return an up-to-date version of the file. =item -P, --patchdir=DIR Set directory to which patches are downloaded. The default is the current working directory. =item -a, --askauth Ask for Sun Online Account data interactively. =item --user=USER Login name for Sun Online Account authentication. =item --passwd=PASS Password for Sun Online Account authentication. =item --patchurl=URL If set, pca tries to download patches and READMEs from this URL first. Any URL starting with file:/, ftp://, http:// or https:// can be used. See LOCAL PATCH SERVER for more information. =item --xrefurl=URL If set, pca tries to download the patchdiag.xref file from this URL first. Any URL starting with file:/, ftp://, http:// or https:// can be used. See LOCAL PATCH SERVER for more information. =item --localurl=URL Deprecated. Use I and I instead. If set, pca tries to download patches, READMEs and patchdiag.xref from this URL first. Any URL starting with file:/, ftp://, http:// or https:// can be used. =item --stop=ID Stop after patch ID. When the specified patch ID is reached during listing, downloading or installing patches all operations are stopped. =item --ignore=ID Ignore patch ID. The patch will not be listed, downloaded or installed. Specify a patch ID without revision (I<123456>) to ignore any revision of patch 123456. Specify I<123456-78> to ignore only revision 78 of patch 123456; newer revisions will not be ignored. =item --rec=ID Set Recommended flag on patch ID. Useful to add single patches to the set of recommended patches. The patch will be marked with a lowercase I in pca's output. =item --sec=ID Set Security flag on patch ID. Useful to add single patches to the set of security patches. The patch will be marked with a lowercase I in pca's output. =item -p, --pattern=REGEX List only patches whose synopsis matches the search pattern REGEX. This can be a simple string like I or a regular expression like I<"[kK]ernel">. =item -n, --noreboot Install only patches that don't require a reboot after installation. =item --minage=DAYS List only patches which are at least DAYS old. =item --syslog=TYPE Syslog facility (eg. user or local0) to log successful patch installs to. =item -k, --nobackup Make patchadd not back up files when installing patches. This works by running patchadd with its I<-d> option. Patches can not be backed out if this option is used. =item -B, --backdir=DIR Saves patch backout data to DIR. This works by running patchadd with its I<-B> option. =item -s, --safe Safe patch installation. Checks all files for local modifications before installing a patch. A patch will not be installed if files with local modifications would be overwritten. =item -G, --currentzone Make patchadd modify packages in the current zone only. This works by running patchadd with its I<-G> option. This option works on Solaris 10 or newer only. =item --patchadd=FILE Path to an alternative patchadd command. =item -H, --noheader Don't display descriptive headers. Useful if re-using pca's output in own scripts. =item --format=FORMAT Set output format to FORMAT. The default format is "%p %i %e %c %r%s%b %a %y". Use I<%p> for the patch number, I<%i> for the installed revision, I<%e> for information whether the installed revision is lower, equal or higher than the current revision (I<%c>). Use I<%r>, I<%s> and I<%b> for the Recommended, Security and Bad flag, I<%a> for the age and I<%y> for the Synopsis. Example: With the format string "%p-%c %y" pca shows patches in the same format as smpatch. Use of this option in combination with I<--listhtml> is unsupported. =item -f, --fromfiles=DIR Read uname/showrev/pkginfo output from files in the specified directory, where DIR can also be a file name prefix. See CREATING PATCH REPORTS FOR REMOTE MACHINES for details. =item --dltries=NUM Try downloads from Sun's download server NUM times. The default is 1. Can be raised to reduce failed patch downloads when Sun's patch download server is unresponsive. =item -F, --force Force local caching proxy to download patchdiag.xref, patches and patch READMEs from Sun's download server, even if the file is already in the cache. Useful to download updated patch READMEs for bad patches. =item -R, --root=DIR Set alternative root directory. This can be useful for Live Upgrade, to analyze patches in an alternate root environment or to point pca at the mini-root of a jumpstart install server. =item --wget=FILE Path to the wget command. =item --wgetproxy=URL Default proxy for wget. =item --logger=FILE Path to (alternative) logger command. =item --update=TYPE Check for available updates for pca itself. TYPE can be I, I, I or I. See UPDATE PCA for more information. =item --pcaurl=URL Set the URL which is used by I to check for new versions of pca. See UPDATE PCA for more information. =item -V, --debug Show debug output. This includes output generated by patchadd. When running in proxy mode, debug output will be written to the file /tmp/pca-proxy-debug.txt. =item -h, --help Print help on command line options. =item -v, --version Print version information. =back If no option is specified, the I<-l> option to list patches is used. =head1 OPERANDS The operands determine which patches are listed (I<-l>), downloaded (I<-d>), installed (I<-i>) or whose READMEs are displayed (I<-r>). Multiple operands can be specified. Supported operands are I (missing, installed, all, total, unbundled, bad), I with or without revision (123456-78 or 123456), I (123456-78.zip) and I (patchlist.txt). The patch groups can be used to specify all missing patches (I), all installed patches (I), both installed and missing patches (I), all patches listed in patchdiag.xref (I), patches not associated with a software package (I) or installed patches which are marked Bad (I). By adding I, I or I to any of the patch groups, only patches from the patch group which are marked Recommended, Security or either Recommended or Security are specified. Examples are I for all missing Security patches, or I for all Recommended/Security patches. Patch groups can be shortened by using the first letter of the patch group plus optional r/s/rs (e.g. I for missings or I for allrs). Patch IDs like I<123456-78> or I<123456> are used to specify single patches. If no revision (I<-78>) is specified, patch dependencies will be resolved. If the name of a patch file like I<123456-78.zip> is specified, it has the same effect as using I<123456-78>. This can be useful to install a list of already downloaded patches with I. If a I is specified, the file is read and its contents are added to the list of operands line-by-line. A file can contain other file names. If the file name is equal to a valid patch group name it will not be read. The patch list can be limited to patches whose synopsis line contains a search pattern by using any patch group in combination with the I<--pattern=REGEX> option. A command like I shows any missing patch containing the I keyword in its description. If the search pattern contains whitespace or special characters, enclose it in quotation marks: I shows patches for all versions of Sun Studio. If no operands are specified, the I operand is used. =head1 CONFIGURATION The behaviour of pca can be configured by setting any option either in a configuration file, as an environment variable with the I prefix or on the command line. See OPTIONS for a complete list; only the long names can be used in configuration files and for environment variables. At first, the configuration files are read. pca reads I in the directory where pca is installed, I<../etc/pca.conf> of the directory where it is installed, I, I<$HOME/.pca> and I in the current directory, in this order. Options are set by specifying I in the file. Example: To set the path of the wget command, use I. To enable debug output, use I. Then, all environment variables matching I are read. Example: To set the patch download directory, set I to I. To set the I option, set I to I<1>. At last, the command line options are read. Example: To set the location of the patch xref file, use I<-X /tmp> or I<--xrefdir=/tmp>. To set the option for safe patch installation, use I<-s> or I<--safe>. The I option can only be used in configuration files and as an environment variable. It sets the default OPERANDS. In a configuration file, everything after a I<#> character is regarded as a comment and ignored. =head1 PATCH DOWNLOAD AND INSTALLATION The I<-d> option downloads patches to the current directory, I<-i> downloads and installs them. The download option can be used as a regular user. The external command wget is used for the actual download. If it can't be found in the default paths, set the I option to contain the path to the wget command. The install option requires pca to be run as root. It uses I to install the patches. Using I<-I> instead of I<-i> pretends to install patches, but does not actually install any patch. The patches are downloaded from Sun's patch download server. To download patches from Sun, a Sun Online Account (SOA) is required. A free SOA will grant access to security and driver patches only. To access all patches, you need to buy a Sun Service Plan and connect it to your SOA. Set the two options I and I to contain the SOA user name and password. If I is set, but I is not, pca will ask for the password. Alternatively, when run with the I option, pca asks for Sun Online Account data interactively. As pca analyzes the information in the cross-reference file to resolve patch dependencies, it lists and installs patches in the correct order. For some patches, a reboot or reconfiguration reboot (boot -r) is recommended or required after installation. When the I or I option is combined with the I option, only patches which do not require a reboot are installed. This information is contained in the patch distribution file. Therefore, even if I is specified, the patches are downloaded anyway; only the installation is skipped. I normally keeps a backup of all files it modifies. Using the I<--nobackup> option with pca instructs I to not keep backup copies of replaced files (see the -d option in patchadd's man page). Sometimes a patch re-delivers and silently overwrites files which have been modified locally. pca tries to overcome this issue with its safe patch installation mode. Before installing a patch, pca checks all files listed in the patch README for local modifications. If any modified file is in danger of being overwritten, a warning is shown and the patch is skipped. Safe mode is off by default, and can be turned on by using I<-s> or I<--safe> in combination with I<-i> (install patches) or I<-I> (pretend to install patches). You must be root to use I<-s>. Running I is a safe way of identifying problematic patches without actually installing them. In rare cases, patches modify or replace files without updating the checksum in the package database used by pca. Installing a more recent revision of the same patch will fail although no local modifications have been made to a file. Patch installation can be forced by not using the I option. Running I on a regular basis is enough to keep the system at the recommended patch level. This is quite comfortable and works without problems in many cases. As some patches require special handling, it's recommended to read the README of every patch before installation. pca's I<-L> option for HTML output and the I<--readme> option to display patch READMEs are handy for that. Sometimes installing a patch might fail because of inconsistencies in the patchdiag.xref file, the patch README and the information contained inside the patch. Often this gets fixed in a new version of patchdiag.xref or in a new revision of the patch. Either try again a few days later or take a look at the I section on the pca web site, where some problematic patches are listed. =head1 LOCAL PATCH SERVER On a local network, it might be useful to have a local patch server. There are two ways to set up a local patch server for pca, using the I and I options. URLs specified with these options are always accessed first when downloading patches, patch READMEs or patchdiag.xref. Only if a file can't be found there, pca falls back to Sun's patch server. Like this, files are downloaded from Sun's patch server only once when installing patches on multiple machines. Create the local patch repository by copying downloaded patch files (e.g. 123456-78.zip), patch READMEs (e.g. README.123456-78) and/or patchdiag.xref to a directory which is available via NFS or on a local web server. Point pca at it by setting the I and/or I options to the URL (e.g. file:/patches/ or http://www.my.org/patches/). The more advanced method uses pca to work as a local caching proxy for itself. Create a directory in the document root of the local web server, and link or copy pca there under the name I. Make sure that the directory (or the directories specified with the I and I options) are owned and writable by the user under which CGI scripts run, as patches, patch READMEs and patchdiag.xref will be stored there. Verify that the web server is configured to run CGI scripts (for apache, check the ExecCGI and AddHandler options in httpd.conf). Create a pca.conf file in the same directory to specify Sun Online Account data. On the client, point pca at the caching proxy by setting the I and I options to e.g. http://www.my.org/patches/pca-proxy.cgi. In proxy mode, if a patch or patch README exists in the local cache directory, it is delivered immediately. If it doesn't, the file is downloaded from Sun's patch server, put into the cache, and delivered. For patchdiag.xref, pca-proxy.cgi will always make sure that it has a recent version of this file and deliver it from its cache. With a local caching proxy in place, client systems running pca and using this proxy do not need direct access to the Internet at all. Example setup of a local caching proxy on a vanilla Solaris 10 system: # mkdir /var/tmp/pca # chown webservd:webservd /var/tmp/pca This is where patches, READMEs and patchdiag.xref will be stored by the proxy. Now put the CGI script in place and create a configuration file: # cd /var/apache2/cgi-bin # cp /usr/local/bin/pca pca-proxy.cgi # chmod 555 pca-proxy.cgi # cat > pca.conf xrefdir=/var/tmp/pca patchdir=/var/tmp/pca user=XXXXXX passwd=YYYYYY ^D # chown webservd:webservd pca.conf # chmod 600 pca.conf If the apache2 server is not running yet, enable it with I: # svcadm enable svc:/network/http:apache2 Test the caching proxy on a client: ./pca -X . --xrefurl=http://server.domain/cgi-bin/pca-proxy.cgi --patchurl=http://server.domain/cgi-bin/pca-proxy.cgi -d 126306-01 The patchdiag.xref and 126301-01.zip will be downloaded by the proxy and stored in I on the server, and both files will be delivered to the client. If it doesn't work, add I to the pca.conf file and look at /tmp/pca-proxy-debug.txt and /var/apache2/logs/ for details. For large setups, you can build a cascade of local caching proxies by pointing one proxy at another proxy by setting I and I to point at the master proxy in the slave proxies' pca.conf. =head1 UNBUNDLED PATCHES Usually a patch is related to one or more software packages installed on a system. Apart from that, there are unbundled patches. They provide firmware updates for machines, disks, or tape drives and fixes for software which doesn't come in package format. Currently there is no way to automatically determine if such patches actually apply to a system. The I operand specifies this type of patches. At first, I will show a long list of patches. To reduce this list to the interesting ones, unnecessary patches can be ignored by using the I option in a pca configuration file. For patches you are absolutely not interested in, use an entry like I in the configuration file; this patch will never be shown again, even if a newer revision of the patch appears. Patches that are installed in their current revision should be put with this revision into the configuration file (e.g. I). The patch will show up again when a newer revision is released. Example: Patch 106121-18 contains the most recent PROM firmware for Ultra 5/10 workstations. As it's installed on all systems, I put I into the configuration file. When a new revision of the patch is released, it will show up in I again. Patch 118324 is the PROM firmware patch for the Sun Fire V440. As I don't have such a machine, I put I into the configuration file to ignore this patch completely. All that pca can do is to notify of new unbundled patches or patch revisions. It's on you to decide whether a patch is needed by checking its README file, and to install it by following the instructions in the README. Unbundled patches cannot be installed by patchadd or pca. =head1 CREATING PATCH REPORTS FOR REMOTE MACHINES pca can create a patch report or download patches for a system which cannot run pca directly, like stripped-down systems without perl or an Internet connection. On such systems, run: uname -a > uname.out showrev -p > showrev.out pkginfo -x > pkginfo.out On systems with a minimal core installation of Solaris, the I command might not be available. Use I showrev.out> instead. Copy the resulting I<*.out> files to a system where pca is installed. Use the I<-f DIR> or I<--fromfiles=DIR> option to point pca at the location of the input files. The argument to I<-f> can be a directory or a file name prefix like I. This allows collecting I<*.out> files for multiple systems and telling pca which ones to read. If Sun Explorer is used to collect information about Sun systems, a directory containing Sun Explorer output can be used as the argument to I<-f> as well. Other options can be used in combination with I<-f>. Example: I downloads all missing patches for the remote system. =head1 ZONES pca can be run both in the global zone or any non-global zone. Patches installed in the global zone are usually installed in all non-global zones, too. It's recommended to install patches in the global zone first, and then run pca in all non-global zones to check for additionally needed patches. This is necessary if packages have been added to or removed from just the global or any non-global zone. When pca is run with the I<-G> option, this option is handed through to patchadd, which will install patches in the current zone only. See the man page for patchadd for further details. =head1 UPDATE PCA Changes to the patch infrastructure by Sun and problems with single patches often make updates to pca necessary. To ease that procedure, the I option can be used. The default is type I - pca will never check for updates. Use the I type to contact the pca webpage and check for available updates. Using I will not only check, but also download and install the updated version of pca. With I, pca will check for updates automatically once per day, keeping itself up to date without user intervention. Unlike I and I which are for interactive usage, this type is best used in a configuration file. The default URL to check for updates is http://www.par.univie.ac.at/solaris/pca/ (official release). It can be set with the I option. Set it to http://www.par.univie.ac.at/solaris/pca/develop/ to check for and update to new development versions of pca. The update mechanism currently does not work for pca in proxy mode and setting I to point at a local caching proxy is not supported either. You can set I to point at some local URL though, to distribute whatever version in your local network. =head1 JUMPSTART You can use pca to install patches in the finish script of a jumpstart install server. Perl is included in the OS image which is booted over the network for installation starting with Solaris 8. As the machine will probably not have an Internet connection during installation, you can either pre-download all necessary patches into a directory accessible via NFS, or set up a local caching proxy. If you use any http or ftp url for I or I, you must put a copy of wget into the directory that contains your finish script and pca, and use the I option to point pca at it. Set I and I (unless you use I) to /tmp to avoid problems with non-writable directories. As the OS which gets installed during jumpstart is mounted at /a, use the I option to instruct pca to install patches there. =head1 EXAMPLES List all missing patches. This is the same as running pca without any arguments: pca -l missing List all installed security patches: pca -l installeds Display the README for the current revision of patch 116532: pca --readme 116532 Show all installed patches which are marked Bad. You should read the patch README to find out how to handle a specific bad patch: pca -l bad Download multiple explicitly specified patches after asking for Sun Online Account data: pca --askauth -d 121308-02 122032 Download and install all missing patches which do not require to reboot the system in safe mode: pca --noreboot --safe --install Download all missing patches for a remote system. Output from uname -a, showrev -p and pkginfo -x has been saved to /tmp/myhost_uname.out etc. before: pca -f /tmp/myhost_ -d missing Check for a new version of pca and install it: pca --update now A sample configuration file: # Sun Online Account user=myuser passwd=secret # Other patchurl=http://www.my.org/patches/pca-proxy.cgi xrefurl=http://www.my.org/patches/pca-proxy.cgi syslog=user safe=1 =head1 ENVIRONMENT VARIABLES All environment variables with the I prefix are evaluated as options; see CONFIGURATION for details. Furthermore, these environment variables are used by pca: =over 5 =item PAGER Path to the command which is used to display patch README files =back =over 5 =item TMPDIR During patch installation, patches are extracted under this directory =back =head1 AUTHORS Martin Paul Emartin@par.univie.ac.atE Thanks to everybody who contributed code or provided feedback: Andrew Brooks, Bruce Riddle, Damian Hole, Peter Van Eynde, Richard Whelan, Eugene MacDougal, Peter Schmitz, Fredrik Lundholm, Dan W. Early, Markus Reger, Constantijn Sikkel, Stephen P. Potter, Fletcher Cocquyt, Timothy J. Howard, Thomas Bluhm, Frank Doeschner, Loris Serena, Marion Biallowons, Ricky Chew, Martin R. Korczak, Imad Soltani, Scott Lucas, Anders Grund, Bernd Senf, Chris Zappala, Ashley Krelle, Mike Patnode, Mats Larsson, Thomas Maier-Komor, Willi Burmeister, Stefaan A. Eeckels, Ian Collins, Leptonux, Joseph Millman, Guenter Zaehle, Frank Fejes, Mark Jeffery, Alberto da Silva, Mauricio Tavares, Kurt Rabitsch, Jeff Wieland, Frank Bertels, Steve Meier, Dan Lorenzini, Gerard Henry, Laurent Blume, Sean Berry, George Moberly, Erik Nordenberg, Mark Ashley, Jim Prescott, Christian Pelissier, Hugues Sapin, Colin A. White, Dale T. Long, Christophe Kalt, Bruno Delbono, Nan Liu, Frank Cusack, Marlon Sanchez-Nunez, Jois Diwakar, Toni Viemero, Jens Larsson, Gordon D. Gregory, Luis Catacora, Erik Larson, Tim Longo, Mike Borkowski, Nicolas Goy, William Bonnet, Dave Love, Thomas Brandstetter, Daniel Kunkel, Gregor Longariva, Miroslav Zubcic, Tim Bradshaw, Chris Quenelle, Christopher Odenbach, Andy Fiddaman, Peter Sundstrom, Andreas F. Borchert, Jonah Simandjuntak, Damian Lilley, Chris Ridd, Albert Lee, James Lick, John Douglass, Andres A. Flores Concepcion, Chris Reece, Toni Viemero, Timothy Meader, John D. Groenveld, Ceri Davies, Martin Wismer, Laszlo Kiss, Mike Moya, Leon Koll, Shawn Boots, Mike Wallace, Robert P. McGraw, Peter Arnold, Matt Kolb, Mike Shackelford, John Dzubera, Donald Teed, Asif Iqbal, Stephen Nash, Jason Loretz, Bryan Howard, Roman, Jonathan Hoefker, Daniel Trinkle, Ron Halstead, Rob Fisher, Chris Coffey, Travis Freeland, Hans-Werner Jouy, Gary Mills, Craig Bell, Mick Russom, Brian King, Ashley Rowland, Guillermo Castellini, Bryan D. Moorehead, Mark Scheufele, Corey Becker, David Robson, Kevin Maguire, Mike Wallace, Marcos Della, Frank Sperber, Horst Scheuermann, Adrian Ulrich, Steve Fox, David Collodel, Jeremiah Johnson, Erik Schubert, David Sullivan, Tom Francen, Matthew Scala, Richard Mayebo, Gerald Sinkiewicz, David Montag, Steve Forman, Jeffrey King, Gerry Van Trieste, Chris Denneen, Greg Barry, Paul Armstrong, Andreas Fineske, Eric Kissinger, Torsten Peter, Yevgeniy Averin, Sean Walmsley, Alexander Skwar, Jeffrey King, Jones Olatunji, Richard Skelton, Kjetil Torgrim Homme, Brian McNamara, Gerry Sinkiewicz, Kazuyuki Sato, Mayuresh Kshirsagar, Mauro Mozzarelli, Judy Illeman Gaukel, Petri Kunnari, William Pool, Steven Faulconer, Rono Jacob, Will Green, Martial Rioux, Zafar Pravaiz. =head1 MAILING LIST There is a mailing list which is used solely for announcements about pca. To be added to the list, send a short note to Emartin@par.univie.ac.atE. =head1 SEE ALSO =over 5 =item pca web site: http://www.par.univie.ac.at/solaris/pca/ =back =head1 CHANGES =head2 Version 20071005-03 * Add documentation about using pca in a jumpstart finish script * Whitelist: modify 120011 * Fix small bug with installing Sun Studio 11 patches on Solaris 10 =head2 Version 20071002-01 * New option to allow pca to update itself (--update=TYPE) * Code cleanup and performance enhancements * Deliver files in proxy mode directly without HTTP redirect * Write debug output to file in proxy mode * Fix a problem with timelocal when running pca under Windows * Fix obscure bug with --safe option and pathnames with commas * Add option to stop after a specified patch ID (--stop=ID) * Allow patch proxy to download files from another patch proxy * Revert to old mechanism of specifying user and password with wget * Include documentation in pca * Handle relative path names in xrefdir correctly * Whitelist: add 120011, 125369, 120012, 125216, 125370 * Whitelist: modify 118833 * Whitelist: remove obsolete patches * Apply check: add 125950, 125951, 125952, 125953 * Apply check: add 121430, 121431, 121428 * Apply check: add 125276, 125277, 125278 * Apply check: remove obsolete patches There is a new version scheme, consisting of the date in ISO format plus a serial number. The same scheme is used for official and development releases. A new option has been added (update=TYPE) which allows pca to check for and install new versions of itself. See UPDATE PCA in the documentation. The documentation is now included in POD format in pca. Use I to view it. There are some enhancements to pca in proxy mode. You can setup a cascade of local proxies by pointing one proxy at another. The xrefdir and patchdir are honored now in proxy mode, so you can keep the cache directory out of the document root of your web server and use an existing cgi-bin directory. Debugging a proxy is simplified; when the debug option is set, debug output will be written to a file.