Question:
> Some time ago one of you sent me a script which checks installed
> patches against a list of actual patches (from file or by ftp from SUN)
> and gives the differences (or ftps the new patches from SUN) and so on...
Answers by (in chronological order and without flames... :-):
Tom Mornini tmornini@sun630mp.infomania.com
Richard Skelton rich@brake.demon.co.uk
Kevin Davidson tkld@cogsci.ed.ac.uk
Charlie Mengler charliem@anchorchips.com
Celeste Stokely celeste@celestial.stokely.com
Louis Avrami L.Avrami@dialogic.com
Susan Feng sfeng@smi.Stanford.EDU
John D Groenveld groenvel@cse.psu.edu
Rick Reineman reineman1@llnl.gov
Systems Admin sysadmin@lvision.com
Answers:
- look at SUNSOLVE (CD or http://sunsolve1.sun.com)
- attached patchcheck
- attached patchreporter
- get the info from SUN: "showrev -a | mail patch@East.Sun.COM"
- PatchReport or PatchDiag,
see http://www.stokely.com/unix.sysadm.resources/faqs.s.html#solaris.patch.mgmt
(and don't forget to see the rest of their information!!!)
- PatchReport at ftp://x86.cs.duke.edu/pub/PatchReport/
- patchdiag at http://sunsolve.sun.com/sunsolve/whats-new.html
Bye,
Chris
--Boundary_(ID_nKp72Ya2TqSE7jrUWqiEIg)
Content-type: TEXT/PLAIN; CHARSET=US-ASCII; NAME=patchcheck
Content-disposition: ATTACHMENT; FILENAME=patchcheck
X-Sun-Charset: us-ascii
X-Sun-Data-type: octet-stream
#!/usr/local/bin/perl
#
# This script analyzes a Sun patch report (e.g. a Solaris2.x.PatchReport
# file off of ftp://sunsolve.sun.com/pub/patches), compares it against the
# list of currently installed patches, and generates a listing of patches
# which need to be installed or updated.
#
# -- JJC, 10/3/96
#
require "getopts.pl";
$patchdir = '/var/sadm/patch';
$patchpattern = '\d{6,6}-\d{2,2}';
$verspattern = '-\d{2,2}';
($script) = $0 =~ /([^\/]*)$/; # Get script name from invocation path.
&Getopts("p:");
die "Usage: $script [-p patchlistsource] patchreport_file\n" if (@ARGV != 1);
$patchreport = $ARGV[0];
&parse_report($patchreport);
if (!$opt_p) {
$patchlistsource = "ls $patchdir |";
} else {
$patchlistsource = ($opt_p =~ /\s/ ? "$opt_p |" : $opt_p);
}
open(PATCHLIST, $patchlistsource) ||
die "$script: can't get patch list from '$patchlistsource'\n";
@ourpatches = &justlatestpatches(grep(chomp, <PATCHLIST>));
close(PATCHLIST);
foreach $patch (@ourpatches) {
($justpatch, $vers) = $patch =~ /([^-]+)(.*)/;
$ourpatchvers{$justpatch} = $vers;
}
@ourobsolete = &intersect(*ourpatches, *obsoletedby, 0);
@haverec = &intersect(*recommended, *ourpatches, 1);
@otherrec = &difference(*recommended, *haverec, 1);
@updaterec = &addversions(&intersect(*otherrec, *ourpatches, 0));
@missingrec = &difference(*otherrec, *updaterec, 1);
@havesec = &intersect(*security, *ourpatches, 1);
@othersec = &difference(*security, *havesec, 1);
@updatesec = &addversions(&intersect(*othersec, *ourpatches, 0));
@missingsec = &difference(*othersec, *updatesec, 1);
@haveall = &intersect(*allpatches, *ourpatches, 1);
@otherall = &difference(*allpatches, *haveall, 1);
@updateall = &addversions(&intersect(*otherall, *ourpatches, 0));
@masterlist = &union(*updateall, *missingrec, 1);
@masterlist = &union(*masterlist, *missingsec, 1);
@shoppinglist = &difference(*otherall, *masterlist, 1);
$boxline = $report_title;
$boxline =~ s/./\*/g;
print "$boxline\n$report_title\n$boxline\n\n\n";
if (@ourobsolete) {
print <<EOF;
=== [0] OBSOLETE PATCHES ==============================================
The following installed patches have been obsoleted by other patches.
=======================================================================
EOF
foreach $patch (@ourobsolete) {
print "Patch $patch$ourpatchvers{$patch} obsoleted by ",
$obsoletedby{$patch},
$patchvers{$obsoletedby{$patch}}, "\n";
}
print "\n\n";
}
if (@masterlist) {
print <<EOF;
=== [1] MASTER LIST ======================================================
This list includes all patches which either a) are on the recommended or
security lists but are not installed on the system, or b) are currently
installed on the system but need to be updated to a later version. It's
a superset of lists 2 through 4.
==========================================================================
EOF
&printpatchlist(*masterlist, 1);
}
if (@updateall) {
print <<EOF;
=== [2] PATCHES REQUIRING UPDATES ====================================
This is the list of all currently installed patches which need to be
updated to a later version.
======================================================================
EOF
&printpatchlist(*updateall, 1);
}
if (@missingrec) {
print <<EOF;
=== [3a] MISSING RECOMMENDED PATCHES =======================================
This is the list of recommended patches which are not currently installed.
============================================================================
EOF
&printpatchlist(*missingrec, 0);
}
if (@updaterec) {
print <<EOF;
=== [3b] RECOMMENDED PATCHES REQUIRING UPDATES ====================
This is the list of recommended patches which need to be updated.
===================================================================
EOF
&printpatchlist(*updaterec, 1);
}
if (@missingsec) {
print <<EOF;
=== [4a] MISSING SECURITY PATCHES =======================================
This is the list of security patches which are not currently installed.
=========================================================================
EOF
&printpatchlist(*missingsec, 0);
}
if (@updatesec) {
print <<EOF;
=== [4b] SECURITY PATCHES REQUIRING UPDATES ====================
This is the list of security patches which need to be updated.
================================================================
EOF
&printpatchlist(*updatesec, 1);
}
if (@haveall) {
print <<EOF;
=== [5] UP-TO-DATE PATCHES ============================================
These are all currently installed patches which are up-to-date. It's
mainly included to make you feel good about yourself.
=======================================================================
EOF
&printpatchlist(*haveall, 0);
}
if (@shoppinglist) { # if (!@shoppinglist) you patch too damn often
print <<EOF;
=== [6] SHOPPING LIST ==================================================
These are all the remaining patches listed in the report, just in case
you feel like adding a few.
========================================================================
EOF
&printpatchlist(*shoppinglist, 0);
}
exit 0;
##################################################################
# Subroutines #
##################################################################
sub printpatchlist {
local(*plist, $showcur) = @_;
local($patch, $justpatch, $vers);
if ($showcur) {
print " PatchID Cur Rec Sec Description\n";
print "--------- --- --- --- -----------\n";
}
else {
print " PatchID Rec Sec Description\n";
print "--------- --- --- -----------\n";
}
foreach $patch (@plist) {
($justpatch, $vers) = $patch =~ /([^-]+)(.*)/;
if (!$vers) {
$vers = $patchvers{$justpatch} || "-??";
$patch .= $vers;
}
printf "%-9s", $patch;
print " ", $ourpatchvers{$justpatch} || " x " if $showcur;
print " ", $recommended{$patch} ? " * " : " ",
" ", $security{$patch} ? " * " : " ",
" ", $allpatches{$patch} || "<no description>",
"\n";
}
print "\nTotal patches listed: ", scalar(@plist), "\n" if (@plist > 1);
}
sub intersect {
local(*plist1, *plist2, $keepversion) = @_;
local(@p1) = defined(@plist1) ? @plist1 : keys %plist1;
local(@p2) = defined(@plist2) ? @plist2 : keys %plist2;
local(%temp);
$keepversion || grep(s/$verspattern$//, @p1);
$keepversion || grep(s/$verspattern$//, @p2);
grep($temp{$_}++, @p1);
return sort(grep($temp{$_}, @p2));
}
sub difference {
local(*plist1, *plist2, $keepversion) = @_;
local(@p1) = defined(@plist1) ? @plist1 : keys %plist1;
local(@p2) = defined(@plist2) ? @plist2 : keys %plist2;
local(%temp);
$keepversion || grep(s/$verspattern$//, @p1);
$keepversion || grep(s/$verspattern$//, @p2);
grep($temp{$_}++, @p2);
return sort(grep(!$temp{$_}, @p1));
}
sub union {
local(*plist1, *plist2, $keepversion) = @_;
local(@p1) = defined(@plist1) ? @plist1 : keys %plist1;
local(@p2) = defined(@plist2) ? @plist2 : keys %plist2;
$keepversion || grep(s/$verspattern$//, @p1);
$keepversion || grep(s/$verspattern$//, @p2);
return &justlatestpatches(@p1, @p2);
}
sub justlatestpatches {
local(@plist) = @_;
local($justpatch, $patch, @result, %temppatch, $vers);
foreach $patch (sort @plist) {
($justpatch, $vers) = $patch =~/([^-]+)(.*)/;
$temppatch{$justpatch} = $vers || "";
}
foreach $patch (sort keys %temppatch) {
push(@result, "$patch$temppatch{$patch}");
}
return @result;
}
sub addversions {
local(@plist) = @_;
local($patch, @result);
foreach $patch (@plist) {
push(@result, "$patch".($patchvers{$patch} || "-??"));
}
return @result;
}
sub parse_report {
local($patchreport) = @_;
local($description, $justpatch, $newpatchID, $patchID, $vers, $x);
open(PATCHREPORT, $patchreport) ||
die "$script: unable to open $patchreport\n";
lookfor(PATCHREPORT, '/\s*Title/');
$report_title = $_.<PATCHREPORT>;
$report_title =~ s/\s+/ /g;
lookfor(PATCHREPORT, '/Recommended Patches:\s*$/');
lookfor(PATCHREPORT, "/^$patchpattern/");
while () {
last unless /^$patchpattern/;
($patchID, $description) = /^($patchpattern)\s+(.*)\n/;
$recommended{$patchID} = $description;
$_ = <PATCHREPORT>;
}
lookfor(PATCHREPORT, '/Security Fixes:\s*$/');
lookfor(PATCHREPORT, "/^$patchpattern/");
while () {
last unless /^$patchpattern/;
($patchID, $description) = /^($patchpattern)\s+(.*)\n/;
$security{$patchID} = $description;
$_ = <PATCHREPORT>;
}
lookfor(PATCHREPORT, '/Obsoleted Patches:\s*$/');
lookfor(PATCHREPORT, "/^$patchpattern/");
while () {
last unless /^$patchpattern/;
($patchID, $newpatchID) =
/^($patchpattern)\s+OBSOLETED by\s+(\d+)/;
($justpatch, $vers) = $patchID =~ /([^-]+)(.*)/;
$obsoletedby{$justpatch} = $newpatchID;
$_ = <PATCHREPORT>;
}
lookfor(PATCHREPORT, '/Complete Listing of Released Patches:\s*$/');
while () {
lookfor(PATCHREPORT, '/^Patch-ID#\s+'.$patchpattern.'\s*$/');
last if eof(PATCHREPORT);
($patchID) = /($patchpattern)/;
chomp($_ = <PATCHREPORT>);
($x, $description) = split(/\s+/, $_, 2);
$allpatches{$patchID} = $description;
($justpatch, $vers) = $patchID =~ /([^-]+)(.*)/;
$patchvers{$justpatch} = $vers;
}
close PATCHREPORT;
}
sub lookfor {
local($filehandle, $regex) = @_;
while (<$filehandle>) {
last if eval $regex;
}
}
--Boundary_(ID_nKp72Ya2TqSE7jrUWqiEIg)
Content-type: TEXT/PLAIN; CHARSET=US-ASCII; NAME=patchreporter
Content-disposition: ATTACHMENT; FILENAME=patchreporter
X-Sun-Charset: us-ascii
#!/usr/local/bin/perl
'di';
'ig00';
# patchreporter -- Kevin Davidson
#
# $Log: patchreporter,v $
# Revision 1.1 1997/07/17 10:52:22 tkld
# Initial revision
#
$RCSHEADER = '$Header: /home/tkld/src/sunpatches/RCS/patchreporter,v 1.1 1997/07/17 10:52:22 tkld Exp $'; #'
'$Revision: 1.1 $' =~ /^\$\w+:\s+([.1234567890]+)\s+\$$/; #'
$VERSION = $1;
require URI::URL;
use LWP::Simple;
$patchreporturl="ftp://online.sunsolve.sun.co.uk/pub/patches/Solaris2.5.1.PatchReport";
$patchdir="/usr/local/install/jumpstart/patches";
$section="head";
if ($#ARGV == 0) {
$patchreporturl=$ARGV[0];
}
# Fetch latest patch report
$url = new URI::URL $patchreporturl;
if (!($patchtext=get($url))) {
die "Cannot get patch report from $patchreporturl\n";
}
# Assemble list of currently available patches
opendir(P1,$patchdir) || die "$patchdir: $!\n";
foreach $dir (grep { /^\d+/} readdir(P1)) {
opendir (P2,"$patchdir/$dir") || die "$patchdir/$dir: $!\n";
foreach $ddir (grep { /^\d+/} readdir(P2)) {
$ddir =~ /^(\d+)-(\d+)$/;
$patches{$1}=$2; # Save rev of current patch
}
closedir(P2);
}
closedir(P1);
$got=0;
foreach $_ (split(/\n/,$patchtext)) {
$section="new" if /New Patches Released Since/;
$section="recommended" if /Recommended Patches/;
$section="security" if /Patches Containing Security Fixes/;
$section="obsolete" if /Obsoleted Patches/;
$section="fulllist" if /Complete Listing of Released/;
if ($section ne "fullist") {
next unless /^\s*(\d\d\d\d\d\d)-(\d\d)\s*(.*)$/;
$patch=$1;
$rev=$2;
$description=$3;
®ister($section,$patch,$rev,$description);
} else {
if (/^Patch-ID.*(\d+)-(\d+)/) {
$patch=$1;
$rev=$2;
$got=1;
}
if ($got && /^Synopsis:\s*(.*)$/) {
$got=0;
$description=$1;
®ister($section,$patch,$rev,$description);
}
}
# print "$section: $patch (rev $rev): $description\n";
}
print "Comparing patches available in $patchdir\nwith current patch report\n";
print "$patchreporturl\n\n";
print "New patches to consider:\n";
foreach $p (sort keys %consider) {
print "$p-$consider{$p}\t$descriptions{$p}\n";
}
print "\nMissing recommended patches:\n";
foreach $p (sort keys %new) {
print "$p-$new{$p}\t$descriptions{$p}\n";
}
print "\nUpdated revs of existing patches:\n";
foreach $p (sort keys %updated) {
print "$p-$updated{$p} ($patches{$p})\t$descriptions{$p}\n";
}
print "\nObsolete patches:\n";
foreach $p (sort keys %obsolete) {
print "$p-$obsolete{$p}\t$descriptions{$p}\n";
}
sub register {
my($section,$patch,$rev,$description) = @_;
$descriptions{$patch}=$description;
if (!($section eq "obsolete") && $patches{$patch} && $patches{$patch} < $rev) {
$updated{$patch} = $rev;
}
if ($section eq "obsolete" && $patches{$patch} && $patches{$patch} <= $rev) {
$obsolete{$patch}=$patches{$patch};
}
if (($section eq "recommended" || $section eq "security")
&& !$patches{$patch} ) {
$new{$patch}=$rev;
}
if ($section eq "new" && !$patches{$patch}) {
$consider{$patch}=$rev;
}
}
########################################################
# These next few lines are legal in both Perl and Nroff.
.00; # finish .ig
'di \" finish diversion--previous line must be blank
.nr nl 0-1 \" fake up transition to first page again
.nr % 0 \" start at page 1
'; __END__ #### From here on it's a standard manual page ####
.TH PATCHREPORTER 8 "Thu Jun 5 1997" "Centre for Cognitive Science, University of Edinburgh"
.SH NAME
patchreporter \- Check for latest Sun patches
.SH SYNOPSIS
.B patchreporter [url]
.SH DESCRIPTION
Fetches the latest Solaris patch report, from the specified URL if given,
or from a built in default and compares that patch list with the
versions of patches available in the JumpStart patch directory.
A report is created of newly released patches to consider, recommended
or security patches that are missing and updated revisions of existing
patches. Also any patches in the patch directory that are no obsolete
are flagged and should be removed when the replacement patch is fetched.
.SH FILES
.TP
/usr/local/install/jumpstart/patches
Patch directory. Beneath this are directories named `01', `02', etc.
containing patches to be installed in numerical order. Typically `01'
will contain patches that are prerequisites for other patches to
ensure they are installed first, `02' will contain other system
patches, `03' contains OpenWindows patches and `04' CDE patches.
.SH AUTHOR
Kevin Davidson tkld@cogsci.ed.ac.uk
.SH SEE ALSO
JumpStart
-- ("`-''-/").___..--''"`-._ Christian.Masopust@siemens.at `o_ o ) `-. ( ).`-.__.`) System Manager Phone: +43-1-1707-24516 (_Y_.)' ._ ) `._ `. ``-..-' Siemens AG Austria Fax: +43-1-1707-53759 _..`--'_..-_/ /--'_.' .' Siemensstr. 88-92 (il).-'' (li).' ((!.-' A-1210 Wien, Austria--Boundary_(ID_nKp72Ya2TqSE7jrUWqiEIg)--