#! /usr/bin/perl -w use strict; # RPMFu - Compare locally installed RPMs against those available # from one or more ftp sites. Download newer versions. # http://www.highprogrammer.com/alan/perl/#rpmfu # # Copyright 2001 Alan De Smet # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html # 0.1 - 20010127 - Initial release # 0.2 - 20010211 - Fixed parsing of versions in filenames. (Apparently # it never worked, which is creepy, since it _did_ work.) # my($RPM) = '/bin/rpm'; my($NCFTPLS) = '/usr/bin/ncftpls -F'; my($WGET) = '/usr/bin/wget'; my(@SRCS) = ( 'ftp://mirror.cs.wisc.edu/pub/mirrors/linux/redhat/updates/6.2/en/os/i686/', 'ftp://mirror.cs.wisc.edu/pub/mirrors/linux/redhat/updates/6.2/en/os/i586/', 'ftp://mirror.cs.wisc.edu/pub/mirrors/linux/redhat/updates/6.2/en/os/i386/', 'ftp://mirror.cs.wisc.edu/pub/mirrors/linux/redhat/updates/6.2/en/os/noarch/', ); main(); exit; package RPMVersion; use Carp; sub new { my $receiver = shift; my $class = (ref $receiver or $receiver); my $self = setup(@_); bless $self, $class; return $self; } sub break_up_version { my($src) = @_; carp "break up what?" if not defined $src; my(@v); while(length $src) { if($src =~ /^\./) { $src =~ s/^\.//; next; } if($src =~ /^\d/) { my($chunk) = ($src =~ /^(\d+)/); push @v, $chunk; $src =~ s/^(\d+)//; next; } if($src =~ /^\D/) { my($chunk) = ($src =~ /^(\D+)/); push @v, $chunk; $src =~ s/^(\D+)//; next; } # Odd, I don't know how to handle this. Throw away first letter. $src = substr $src, 1; } return @v; } sub setup { my($src) = @_; my(@chunks) = split(/-/, $src); my $release = pop @chunks; my $version = pop @chunks; if(not defined $version or not defined $release) { print STDERR "Warning: Unable to parse $src\n"; } my(@version) = break_up_version($version); my(@release) = break_up_version($release); my($self) = { 'version' => \@version, 'release' => \@release, }; return $self; } sub list_diff_test { my($reflhs, $refrhs) = @_; my @lhs = @{ $reflhs }; my @rhs = @{ $refrhs }; while(scalar @lhs or scalar @rhs) { my($lhs) = shift @lhs || 0; my($rhs) = shift @rhs || 0; if($lhs =~ /^\d/ and $rhs =~ /^\d/) { return -1 if $lhs > $rhs; return 1 if $lhs < $rhs; } else { return -1 if $lhs gt $rhs; return 1 if $lhs lt $rhs; } } return 0; } sub release { my($self) = @_; carp "release is read only" if scalar @_ != 1; return @{ $self->{'release'} }; } sub version { my($self) = @_; carp "version is read only" if scalar @_ != 1; return @{ $self->{'version'} }; } sub diff_test { my($lhs, $rhs) = @_; my($ret, @lhs, @rhs); @lhs = $lhs->version(); @rhs = $rhs->version(); $ret = list_diff_test( \@lhs, \@rhs ); return $ret if $ret != 0; @lhs = $lhs->release(); @rhs = $rhs->release(); $ret = list_diff_test( \@lhs, \@rhs ); return $ret; } sub test_aide { my($lhs, $rhs, $want) = @_; my($ret) = diff_test( RPMVersion->new($lhs),RPMVersion->new($rhs) ); if($ret != $want) { die "$lhs <=> $rhs == $ret != $want"; } if($want != 0) { my($want) = -$want; my($ret) = diff_test( RPMVersion->new($rhs),RPMVersion->new($lhs) ); if($ret != $want) { die "$rhs <=> $lhs == $ret != $want (R)"; } } } sub test { test_aide("2.2.3-24x", "1.2.3-23x", -1); test_aide("1.2.3-23x", "1.2.3-24", 1); test_aide("1.2.3-23x", "1.2.3-23x", 0); test_aide("1.2.3-24x", "1.2.3-23x", -1); test_aide("1.3.3-24x", "1.2.3-23x", -1); test_aide("1.2.4-24x", "1.2.3-23x", -1); test_aide("1.2.3-24x", "1.2.4-23x", 1); test_aide("1.2.3-24x", "1.2.4-23y", 1); test_aide("1000-2", "2000-1", 1); } package main; sub debug_print { print STDERR @_; } sub main { #RPMVersion::test(); #sanity_check_names(); debug_print "Getting available rpms..."; my(%avail) = get_available_rpms(); debug_print "done\n"; debug_print "Getting local rpms..."; my(@rpms) = get_all_rpms(); debug_print "done\n"; foreach my $rpm (@rpms) { my($name) = get_canonical_name($rpm); next if not defined $avail{$name}; my $remote = $avail{$name}; my $version_local = RPMVersion->new($rpm); my $name_remote = get_available_name_only($remote); my $version_remote = RPMVersion->new($name_remote); my $diff = RPMVersion::diff_test($version_local, $version_remote); if( $diff > 0 ) { print "$name, have $rpm, getting $name_remote\n"; system("$WGET $remote"); } } } sub get_all_rpms { my(@list) = `$RPM --query --all`; chomp @list; return @list; } sub get_canonical_name { my($name) = @_; my($first_line) = `$RPM --query -i $name`; my($canonical_name) = ($first_line =~ /^Name\s*:\s*(\S+)/); return $canonical_name; } # I rely on the names of the files matching a certain pattern. Check it. sub sanity_check_names { my(@rpms) = get_all_rpms(); foreach my $long_name (@rpms) { my $short_name = get_canonical_name($long_name); if($long_name !~ /^\Q$short_name\E-\d[.\w]*-\d[.\w]*/) { print STDERR "Unhappy! $long_name $short_name\n"; } } } sub get_available_rpms { my(%rpms); foreach my $src (@SRCS) { $src .= "/" if $src !~ /\/$/; my(@files) = `$NCFTPLS -1 $src`; chomp @files; if(scalar @files == 0) { print STDERR "$src has no files?\n"; } foreach my $file (@files) { next if $file !~ /\.rpm$/; my($core_name) = ($file =~ /^(.+)-\d[^-]*-\d/); die "$src - $file" if not defined $core_name; if(not exists $rpms{$core_name}) { $rpms{$core_name} = $src.$file; } } } return %rpms; } sub get_available_name_only { my($s) = @_; my($file_only) = ($s =~ /\/([^\/]+)$/); my($name_only) = ($file_only =~ /^(.+)\.[^.]+\.rpm$/); die if not defined $name_only; return $name_only; }