#!/usr/bin/env perl # # 2004-03-01 # patch_diff by Dean Brundage # # Originally released to the wild here: # http://blog.deanandadie.net/2010/06/quick-diff-of-solaris-patch-levels/ # # Compares current machine's patchlist with another one. # sub Usage { print $_[0] if $_[0]; print <<_END_; Summary: $0 [-hsg] [-d ] [-1 ] [-2 ] This utility generates patch list databases from hosts and compares them. Step 1. Log into each host and run $0 -g to generate a patchlist. Step 2. Run $0 to compare Or run $0 to compare host1 to the localhost. For each host you want to compare, you first must generate a list using -g. Using the -d option you can control where the database is created. An nfs partition is suggested to save the trouble of copying files around. The script will use the following directories, in order, when -d is not provided \$PATCH_DIFF_DIR ./patch_diff. You should re-generate a patchlist periodically. -1 Use as the first host -2 Use as the second host -d Use as the database directory -g Generate a patch list on localhost -h Help -s Read from stdin when generating list _END_ exit $_[1] if defined $_[1]; return; } use strict; use Getopt::Std; use NDBM_File; use Fcntl; use vars qw( $opt_1 $opt_2 $opt_d $opt_g $opt_h $opt_s ); chomp( my $hostname = `uname -n` ); my( $patchlist_1, $patchlist_2 ); my $showrev = "/usr/bin/showrev"; getopts('1:2:d:ghs') or die "Usage.\n"; &Usage( undef, 0 ) if $opt_h; $opt_1 = $ARGV[0] || $hostname unless $opt_1; $opt_2 = $ARGV[1] || $hostname unless $opt_2; die "No host to compare to (-1 ). Usage.\n" if not $opt_1 and not $opt_g; unless( $opt_d ) { if( $ENV{"PATCH_DIFF_DIR"} ) { $opt_d = $ENV{"PATCH_DIFF_DIR"}; } else { $opt_d = "./patch_diff"; } } unless( -d $opt_d ) { my @ret = system("mkdir", "-p", $opt_d ); die "mkdir -p $opt_d failed: ", $!, "\n" if scalar @ret == 0; } $patchlist_1 = &Open_DB( $opt_1 ); if( $opt_g ) { # Generate a patchlist. my $tmp; &Clear_Hash( $patchlist_1 ); $tmp = &Get_Patchlist( $opt_s ); $patchlist_1->{"_Gentime_"} = time; foreach ( keys %{$tmp} ) { $patchlist_1->{$_} = $tmp->{$_}; } } else { $patchlist_2 = &Open_DB( $opt_2 ); my( @extra_1, @extra_2, $c ); my( @downrev_1, @downrev_2 ); foreach ( keys %{$patchlist_1} ) { next if $_ eq "_Gentime_"; unless( $patchlist_2->{$_} ) { push( @extra_1, "$_-$patchlist_1->{$_}" ); } elsif( $patchlist_1->{$_} > $patchlist_2->{$_} ) { push( @downrev_1, "$_-$patchlist_1->{$_}" ); push( @downrev_2, "$_-$patchlist_2->{$_}" ); } } foreach ( keys %{$patchlist_2} ) { next if $_ eq "_Gentime_"; unless( $patchlist_1->{$_} ) { push( @extra_2, "$_-$patchlist_2->{$_}" ); } elsif( $patchlist_2->{$_} > $patchlist_1->{$_} ) { push( @downrev_1, "$_-$patchlist_1->{$_}" ); push( @downrev_2, "$_-$patchlist_2->{$_}" ); } } @extra_1 = sort { $a <=> $b } @extra_1; @extra_2 = sort { $a <=> $b } @extra_2; print "Patches on each system that are missing from the other.\n"; printf " %36s | %-s\n", $opt_1, $opt_2; printf " %36s | %-s\n", scalar localtime $patchlist_1->{"_Gentime_"}, scalar localtime $patchlist_2->{"_Gentime_"}; print "-" x 39, "+", "-" x 40, "\n"; $c = 0; while( $extra_1[$c] or $extra_2[$c] ) { printf " %36s | %-s\n", $extra_1[$c] || undef, $extra_2[$c] || undef; $c++; } print "\n"; print "Patches on each system that are downrev on the other.\n"; printf " %36s | %-s\n", $opt_1, $opt_2; printf " %36s | %-s\n", scalar localtime $patchlist_1->{"_Gentime_"}, scalar localtime $patchlist_2->{"_Gentime_"}; print "-" x 39, "+", "-" x 40, "\n"; $c = 0; while( $downrev_1[$c] or $downrev_2[$c] ) { printf " %36s | %-s\n", $downrev_1[$c] || undef, $downrev_2[$c] || undef; $c++; } &Close_DB( $patchlist_2 ); } &Close_DB( $patchlist_1 ); exit; sub Get_Patchlist { my( $stdin ) = @_; my( $return, $handle, $patch, $rev, $line, %obsoleted ); if( $stdin ) { $handle = \*STDIN; } else { if( open( SHOWREV, "$showrev -p |" ) ) { $handle = \*SHOWREV; } else { print STDERR "Couldn't run $showrev -p: $!\n"; } } while( $line = <$handle> ) { if( $line =~ m/^Patch:\s*([0-9]+)-([0-9]+)/ ) { ( $patch, $rev ) = ( $1, $2 ); $return->{$patch} = $rev; } if( $line =~ m/Obsoletes:\s+([-,\s0-9]+)\s+Requires/ ) { foreach $patch ( split( /\s+/, $1 ) ) { $patch =~ s/,?$//g; ( $patch, $rev ) = $patch =~ m/([0-9]+)-([0-9]+)/; $obsoleted{ $patch } = $rev; } } } unless( $stdin ) { close( $handle ); } # Remove obsoleted patches from the list foreach $patch ( keys %obsoleted ) { # Check revisions first (why?) if( $return->{ $patch } == $obsoleted{ $patch } ) { delete $return->{ $patch }; } } return $return; } sub Open_DB { my( $dbname ) = @_; my $return; unless( tie( %{$return}, 'NDBM_File', "$opt_d/$dbname", O_RDWR|O_CREAT, 0666) ) { print "Couldn't open $opt_d/$dbname: $!\n"; } return $return; } sub Close_DB { my( $hash ) = @_; untie %{$hash}; } sub Clear_Hash { my( $hash ) = @_; foreach ( keys %{$hash} ) { delete $hash->{$_}; } }