Annotation of loncom/build/check-rpms, revision 1.2
1.1 harris41 1: #!/usr/bin/perl
2: #
3: # check-rpms, version 2.1.0
4: # Martin Siegert, SFU, siegert@sfu.ca, Feb 02
5: #
1.2 ! harris41 6: =pod
! 7:
! 8: =head1 NAME
! 9:
! 10: check-rpms - compare installed rpms with up-to-date distribution
! 11:
! 12: =head1 DESCRIPTION
! 13:
! 14: I<check-rpms> compares installed RPM packages (listed by the command
! 15: "rpm -qa") on a Linux system with an up-to-date distribution. That
! 16: distribution may either reside in a local directory (possibly NFS
! 17: mounted) or on a ftp server. If the B<-ftp> option is specified,
! 18: I<check-rpms> retrieves directory listings from the I<ftpserver>'s
! 19: I<directory>/<arch> directories, where <arch> is set to noarch, i386,
! 20: i586, i686, and athlon consecutively. If I<ftpserver/directory> is
! 21: not specified, $FTPSERVER/$FTPUPDATES is used. The $FTPSERVER and
! 22: $FTPUPDATES variables can be set in the configuration file. If
! 23: either of the two is not set, the default server "updates.redhat.com"
! 24: and the default directory "$RHversion/en/os" is used,
! 25: where $RHversion is obtained from the /etc/redhat-release file. If
! 26: run with the B<-ftp> option, all rpm packages that need to be downloaded
! 27: (see the B<--download>, B<--recheck>, and B<--update> options) will
! 28: be downloaded into the directory specified by the B<-d> directory
! 29: option. If that option is omitted the $RPMDIR directory is used.
! 30: The $RPMDIR variable that can be set in the configuration file. If
! 31: $RPMDIR variable is not set either, the default directory
! 32: "/mnt/redhat/RedHat/RPMS" is used.
! 33:
! 34: If the B<-ftp> is omitted, it is assumed that B<-d> I<directory> specifies
! 35: a local directory that contains up-to-date rpm packages. If B<-d>
! 36: I<directory> is omitted as well, the $RPMDIR directory is used. If
! 37: $RPMDIR is not set, the default directory "/mnt/redhat/Red-
! 38: Hat/RPMS" is used.
! 39:
! 40: I<check-rpms> uses a lexical sort on the version string and the
! 41: release string of the package in order to decide whether the
! 42: installed package or the package form the distribution is newer.
! 43: I<check-rpms> lists packages of the distribution that are found to be
! 44: newer than the installed packages or, if B<--update> is specified,
! 45: will update the packages using the "rpm -Fvh <list of packages>"
! 46: command. In the latter case I<check-rpms> must be run as root. Fur-
! 47: thermore, the $RPMUSER variable should be set to a non-root user-
! 48: name (see the B<-c> option below). I<check-rpms> will switch to that
! 49: user and run most of the script under that user id.Only the
! 50: final "rpm -Fvh ..." command will be run as root. If $RPMUSER is
! 51: not set, the "nobody" user id will be used. It is recommended to
! 52: set $RPMUSER to an ordinary username (such as yourself). Further-
! 53: more, if a ftp server is used, create the download directory
! 54: (which is specified in the B<-d> directory option or in the $RPMDIR
! 55: variable), change the owner ship of that directory to that user,
! 56: and set the permissions to 700 before running I<check-rpms> with the
! 57: B<--update> option. Note, that B<--update> implies the B<--no-kernel>
! 58: option, i.e., I<check-rpms> refuses to update the kernel directly.
! 59:
! 60: =cut
! 61:
1.1 harris41 62: # ************ WARNING *****************************************************
63: # THIS PROGRAM IS PROVIDED "AS IS" WITHOUT
64: # WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLICIT.
65: # IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
66: # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
67: # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
68: # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
69: # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
70: # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
71: # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
72: # SUCH DAMAGE.
73: # **************************************************************************
74:
75: # check-rpms.pl is free software; you can redistribute it and/or modify
76: # it under the terms of the GNU General Public License as published by
77: # the Free Software Foundation; either version 2 of the License, or
78: # (at your option) any later version.
79: #
80: # check-rpms.pl is distributed in the hope that it will be useful,
81: # but WITHOUT ANY WARRANTY; without even the implied warranty of
82: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
83: # GNU General Public License for more details:
84: # http://www.gnu.org/licenses/gpl.html
85:
86: use Getopt::Long;
87:
88: my $retval = &GetOptions("verbose|v","lm|list-missing","lq|list-questionable",
89: "dir|d=s","ftp:s","noftp","download|dl","recheck|r",
90: "nk|no-kernel","update","c=s");
91:
1.2 ! harris41 92: =pod
! 93:
! 94: =head1 OPTIONS
! 95:
! 96: =over 4
! 97:
! 98: =item B<-v> B<--verbose>
! 99:
! 100: verbose mode: prints additional progress information on
! 101: standard output
! 102:
! 103: =item B<-ftp> [I<ftpserver/directory>]
! 104:
! 105: compare the installed packages with the rpm packages found
! 106: on the ftp server I<ftpserver> in the directories I<directory>/<arch>,
! 107: where arch is set to noarch, i386, i586, i686,
! 108: and athlon consecutively. If I<ftpserver/directory> is not
! 109: specified, the $FTPSERVER and $FTPUPDATES variables are
! 110: checked. These variables can be set in the configuration
! 111: file (see the B<-c> option below). If those variables are not
! 112: set either, the default server "updates.redhat.com" and the
! 113: default directory "$RHversion/en/os" is used, where $RHversion
! 114: is obtained from the I</etc/redhat-release> file.
! 115:
! 116: =item B<-noftp>
! 117:
! 118: use a local directory as the source for new rpm packages
! 119: even if the $FTP veriable is set to 1 in the configuration
! 120: file.
! 121:
! 122: =item B<-d> I<directory> B<--rpm-directory> I<directory>
! 123:
! 124: if B<-ftp> is specified download all rpm packages that need to
! 125: be downloaded into I<directory>. If B<-ftp> is not specified,
! 126: regard the rpm packages found in I<directory> as an up-to-date
! 127: distribution against which the installed packages are
! 128: compared to.
! 129:
! 130: =item B<-lm> B<--list-missing>
! 131:
! 132: list installed packages that do not have an equivalent in
! 133: the up-to-date distribution. This will generate lots of
! 134: output when the comparison is made with the updates directory
! 135: of a ftp server.
! 136:
! 137: =item B<-lq> B<--list-questionable>
! 138:
! 139: list packages for which the lexical sort algorithm does not
! 140: give a conclusive result on whether the installed package
! 141: is older than the package in the distribution. These are
! 142: packages that have version and/or release strings that contain
! 143: letters. For example, it is not absolutely clear
! 144: whether the version 1.2.3b is actually newer or older than
! 145: 1.2.3. The lexical sort would classify 1.2.3b to be newer
! 146: than 1.2.3; with B<-lq> specified the package would be listed
! 147: in any case. See also B<--recheck> below.
! 148:
! 149: =item B<-dl> B<--download>
! 150:
! 151: download packages from the remote ftp server that are found
! 152: to be newer than installed packages into the directory that
! 153: is specified in the B<-d> I<directory> option or in the $RPMDIR
! 154: variable or, if neither of the two are specified, into
! 155: "/mnt/redhat/RedHat/RPMS". If the download directory does
! 156: not exist, I<check-rpms> will create it.
! 157:
! 158: =item B<-r> B<--recheck>
! 159:
! 160: Use the "rpm -Uvh --test --nodeps <package>" command to
! 161: check all packages that have letters in their version
! 162: and/or release string; B<--recheck> implies B<--list-questionable>
! 163: (see above). At the time of writing (Feb. 2002) there
! 164: is one known case for which the lexical sort algorithm
! 165: fails to detect a new package: mutt-1.2.5.1 was released to
! 166: replace mutt-1.2.5i, however, the lexical sort algorithm
! 167: incorrectly classifies mutt-1.2.5i to be newer than
! 168: mutt-1.2.5.1. In this case using the B<--recheck> option is
! 169: essential. In all other cases it is not. It is nevertheless
! 170: probably a good idea to use B<--recheck> at least once in a
! 171: while. B<--recheck> can increase the run-time of I<check-rpms>
! 172: substantially, particularly if a ftp server is used. In
! 173: that case the questionable packages must be downloaded from
! 174: the server into a directory I<directory> (as specified in the
! 175: -d option or the $RPMDIR variable) which will be created,
! 176: if it does not exist.
! 177:
! 178: =item B<-nk> B<--no-kernel>
! 179:
! 180: do not list kernel packages. That is, kernel, kernel-smp,
! 181: kernel-enterprise, kernel-BOOT, and kernel-debug will not
! 182: be checked and listed. However, kernel-headers and kernel-source
! 183: will be checked. The B<--update> option (see below)
! 184: implies B<--no-kernel>.
! 185:
! 186: =item B<--update>
! 187:
! 188: update all packages that were found to have newer versions.
! 189: For this to work I<check-rpms> must be run as root and a suitable
! 190: $RPMUSER must exist (see DESCRIPTION above). It is
! 191: strongly advisable to do a dry run B<check-rpms -v -lq> before
! 192: running B<check-rpms --update>.
! 193:
! 194: =item B<-c> I<configurationfile>
! 195:
! 196: The optional configuration file to use. This file can be
! 197: used to specify the $RPMDIR variable, the $FTP, $FTPSERVER,
! 198: and $FTPUPDATES, variables, and the $RPMUSER variable. An
! 199: example configuration file is given below. If the B<-c> option
! 200: is omitted, I<check-rpms> will use the default configuration
! 201: file I</usr/local/etc/check-rpms.conf>, if it exists.
! 202:
! 203: =back
! 204:
! 205: =head1 EXAMPLES
! 206:
! 207: =over 4
! 208:
! 209: =item check-rpms
! 210:
! 211: will 1) check whether /usr/local/etc/check-rpms.conf exists; 2) if
! 212: it does it will read the variables specified in that file, if it
! 213: doesn't exist, $RPMDIR is set to /mnt/redhat/RedHat/RPMS; 3) if
! 214: $RPMDIR is set, this directory will be regarded as the source of
! 215: the up-to-date distribution, unless $FTP is set to 1. In that latter
! 216: case the $FTPSERVER and $FTPUPDATES are used, if those variables are
! 217: set. Otherwise "updates.redhat.com" and "<RHversion>/en/os"
! 218: will be used; 4) the installed packages are compared
! 219:
! 220: =item check-rpms -v -lq -d /mnt/redhat/7.1/RedHat/RPMS
! 221:
! 222: will use the distribution in the directory /mnt/redhat/7.1/RedHat/RPMS
! 223: for comparison with the installed packages. The command
! 224: will give more detailed information on its progress and will list
! 225: the packages that need upgrading and in another section it will
! 226: list packages they may need to be upgraded.
! 227:
! 228: =item check-rpms -v -lq -ftp updates.redhat.com/7.1/en/os
! 229:
! 230: same as above, but the directories 7.1/en/os/noarch,
! 231: 7.1/en/os/i386, 7.1/en/os/i586, 7.1/en/os/i686, and
! 232: 7.1/en/os/athlon on updates.redhat.com will be searched for new
! 233: packages.
! 234:
! 235: =item check-rpms -v -r --updates
! 236:
! 237: will use the default location for updated packages (determined as
! 238: indicated in the first example); if a ftp server is used, it will
! 239: download all newer and all packages with letters in the version
! 240: and/or release strings (i.e., "questionable" packages) from that
! 241: ftp server, recheck the questionable packages, and finally update
! 242: all packages that need to be updated.
! 243:
! 244: =back
! 245:
! 246: =cut
! 247:
1.1 harris41 248: if ( $retval == 0 ) {
249: usage();
250: }
251:
252: # executables
253: $FTPLS = "ncftpls";
254: $FTPGET = "ncftpget";
255: $GREP = "grep";
256:
257: # default values
258: $RHversion = (split /\s/, `cat /etc/redhat-release`)[4];
259: $DEFCONF = "/usr/local/etc/check-rpms.conf";
260: $DEFRPMDIR = "/mnt/redhat/RedHat/RPMS";
261: $DEFFTPSERVER = "updates.redhat.com";
262: $DEFFTPUPDATES = "$RHversion/en/os";
263: $DEFRPMUSER = "nobody";
264:
265: $RPMDIR=$DEFRPMDIR;
266:
267: # configuration
268: # the configuration file should set the $RPMDIR variable and/or $FTPSERVER,
269: # $FTPUPDATES and $DOWNLOADDIR variables, and the $RPMUSER variable.
270: if ($opt_c) {
271: $CONF = $opt_c;
272: } else {
273: $CONF = $DEFCONF;
274: }
275:
1.2 ! harris41 276: =pod
! 277:
! 278: =head1 The Configuration File
! 279:
! 280: All variables must be defined using perl syntax, i.e., in the form
! 281:
! 282: $variable = value;
! 283:
! 284: (do not forget the semicolon at the end of a line). Comments
! 285: start with "#" and blank lines may be included as well.
! 286:
! 287: Example configuration file:
! 288:
! 289: # check-rpms configuration file
! 290:
! 291: # $RPMDIR is the directory where up-to-date RPMs can be found and/or
! 292: # rpm packages are downloaded into.
! 293: $RPMDIR = "/mnt/redhat/RedHat/RPMS";
! 294:
! 295: # $RPMUSER is the user name that check-rpms switches to for most of
! 296: # the script when run as root
! 297: $RPMUSER = "joe";
! 298:
! 299: # $FTPSERVER and $FTPUPDATES are the hostname of a ftp server and the
! 300: # directory where RPM updates can be found without the <arch> directory.
! 301: # I.e., $FTPUPDATES should be set to something like pub/7.2, if the RPMs
! 302: # are located in pub/7.2/i386, pub/7.2/i686, etc.
! 303: # $FTPSERVER and $FTPUPDATES are used if -ftp is specified or if the following
! 304: # line is uncommented.
! 305: # $FTP = 1;
! 306: $FTPSERVER = "updates.redhat.com";
! 307: $FTPUPDATES = "7.2/en/os";
! 308:
! 309: =cut
! 310:
1.1 harris41 311: if ( -f $CONF) {
312: require($CONF);
313: } else {
314: $FTPSERVER = $DEFFTPSERVER;
315: $FTPUPDATES = $DEFFTPUPDATES;
316: }
317:
318: # check whether we are running as root
319: if ($< == 0){
320: if (! $RPMUSER) {
321: $RPMUSER = $DEFRPMUSER;
322: }
323: $RPMUID = getpwnam($RPMUSER);
324: if (! $RPMUID) {
325: die "You do not seem to have a $RPMUSER user on your system.\nSet the \$RPMUSER variable in the $CONF configuration file to a non-root user.\n";
326: }
327: if ($RPMUID == 0) {
328: die "You must set the \$RPMUSER variable in $CONF to a non-root user.\n";
329: }
330: # switch to $RPMUID
331: $> = $RPMUID;
332: if ($> != $RPMUID) { die "switching to $RPMUID uid failed.\n" }
333: }
334:
335: # command-line arguments
336: $verbose = $opt_verbose;
337: $list_missing = $opt_lm;
338: $questionable = $opt_lq;
339: $no_kernel = $opt_nk;
340: $download = $opt_download;
341: $recheck = $opt_recheck;
342: $update = $opt_update;
343:
344: if (defined $opt_update && $< != 0) {
345: die "You must be root in order to update rpms.\n";
346: }
347:
348: if ( defined $opt_dir ){
349: $RPMDIR = $opt_dir;
350: }
351:
352: if (defined $opt_ftp && defined $opt_noftp) {
353: die "Setting -ftp and -noftp does not make sense, does it?\n";
354: }
355:
356: if (defined $opt_noftp) { $FTP = 0; }
357:
358: if (defined $opt_ftp || $FTP) {
359: $ftp = 1;
360: if ( $opt_ftp ) {
361: $_ = $opt_ftp;
362: ($FTPSERVER, $FTPUPDATES) = m/^([^\/]+)\/(.*)$/;
363: } elsif ( ! ($FTPSERVER && $FTPUPDATES)) {
364: $FTPSERVER = $DEFFTPSERVER;
365: $FTPUPDATES = $DEFFTPUPDATES;
366: }
367:
368: if (defined $opt_update){
369: $download=1;
370: }
371:
372: if ($download || $recheck) {
373: if ( ! -d $RPMDIR) {
374: $retval = system("mkdir -p $RPMDIR; chmod 700 $RPMDIR");
375: if ($retval) { die "error: could not create $RPMDIR\n"; }
376: }
377: }
378: } elsif ( (! -d $RPMDIR) || system("ls $RPMDIR/*.rpm > /dev/null 2>&1")) {
379: die "Either $RPMDIR does not exist or it does not contain any packages.\n";
380: }
381:
382: if ($recheck) {
383: $questionable=1;
384: }
385:
386: if (defined $opt_update || defined $opt_nk) {
387: $no_kernel=1;
388: }
389:
390: $PROC = `grep -i athlon /proc/cpuinfo`;
391: if ( ! "$PROC" ) {
392: $PROC = `uname -m`;
393: chomp($PROC);
394: } else {
395: $PROC = "athlon";
396: }
397:
398: @ARCHITECTURES = ("noarch", "i386", "i586", "i686");
399: if ( $RHversion > 7.0 ){
400: push(@ARCHITECTURES, "athlon");
401: }
402:
403: # get the local list of installed packages
404:
405: if ($verbose) {
406: print "updates for $PROC processor, RH $RHversion\n";
407: print "Getting list of installed packages\n";
408: }
409:
410: if ($< == 0) {
411: @local_rpm_list = `su $RPMUSER -c 'rpm -qa'`;
412: } else {
413: @local_rpm_list = `rpm -qa`;
414: }
415: chop(@local_rpm_list);
416:
417: %local_rpm = %remote_rpm = ();
418:
419: for (@local_rpm_list) {
420: # good place to test the regular expressions...
421: # ($pkg, $ver, $release) = m/^(.*)-([^-]*)-([^-]+)/;
422: # print "$_\t->$pkg, $ver, $release\n";
423:
424: my ($pkg, $pver) = m/([^ ]*)-([^-]+-[^-]+)/;
425: $local_rpm{$pkg} = $pver;
426: }
427:
428: # now connect to the remote host
429:
430: my @templist;
431: if ($ftp) {
432: if ( `rpm -q ncftp --pipe "grep 'not installed'"` ) {
433: die "you must have the ncftp package installed in order to use a\n",
434: "ftp server with check-rpms.\n";
435: }
436: $SOURCE = $FTPSERVER;
437: for (@ARCHITECTURES) {
438: my $FTPDIR = "$FTPUPDATES/$_";
439: if ($verbose) {
440: print ("Getting package lists from $FTPSERVER/$FTPDIR ...\n");
441: }
442: push(@templist, grep(/\.rpm$/, `$FTPLS -x "-1a" "ftp://$FTPSERVER/$FTPDIR/"`));
443: if ($?) { print STDERR "$FTPLS failed with status ",$?/256,".\n"; }
444: }
445: } else {
446: $SOURCE = $RPMDIR;
447: if ($verbose) {
448: print ("Getting package lists from $RPMDIR ...\n");
449: }
450: @templist = grep(/\.rpm$/, `(cd $RPMDIR;ls -1)`);
451: }
452:
453: #
454: # If two versions of the same RPM appear with different architectures
455: # and/or different versions, the right one must be found.
456: #
457:
458: $giveup = 0;
459: for (@templist) {
460: ($rpm, $pkg, $pver, $arch) = m/(([^ ]*)-([^- ]+-[^-]+\.(\w+)\.rpm))/;
461: if ($remote_rpm{$pkg}) {
462: # problem: there are several versions of the same package.
463: # this means that the package exists for different architectures
464: # (e.g., kernel, glibc, etc.) and/or that the remote server
465: # has several versions of the same package in which case the
466: # latest version must be picked.
467: my ($pkg1) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
468: my ($pkg2) = ($pver =~ m/([^-]+-[^-]+)\.\w+.rpm/);
469: my ($vcmp, $qflag) = cmp_versions($pkg1, $pkg2);
470: if ($qflag && $questionable) {
471: # cannot decide which of the two is newer - what should we do?
472: # print a warning that lists the two rpms.
473: # If running with --update, both packages must be rechecked with
474: # rpm -qp --queryformat '%{SERIAL}' <pkg>
475: if ($recheck || $update) {
476: my $decision = pkg_compare("$pkg-$remote_rpm{$pkg}",$rpm, $vcmp);
477: if ($decision < 0) {
478: # an error in the ftp download routine accured: giveup
479: $remote_rpm{$pkg} = undef;
480: $giveup = 1;
481: } elsif ($decision > 0) {
482: # second package is newer
483: $remote_rpm{$pkg} = $pver;
484: }
485: next;
486: } else {
487: mulpkg_msg("$pkg-$remote_rpm{$pkg}", $rpm, $vcmp);
488: print "** check whether this is correct or rerun with --recheck option.\n";
489: if ($vcmp < 0) {
490: $remote_rpm{$pkg} = $pver;
491: }
492: }
493: }
494: if ($vcmp == 0) {
495: # versions are equal: must be different architecture
496: # procedure to select the correct architecture:
497: # if $PROC = athlon: if available use $arch = athlon (exist for
498: # RH 7.1 or newer) otherwise use i686
499: # if $PROC = ix86: choose pkg with $PROC cmp $arch >= 0 and
500: # $arch cmp $prev_arch = 1
501: $_ = $remote_rpm{$pkg};
502: ($prev_arch) = m/.*\.(\w+)\.rpm$/;
503: if (cmp_arch($arch,$prev_arch)) { $remote_rpm{$pkg} = $pver };
504: } elsif ($vcmp < 0) { # second rpm is newer
505: $remote_rpm{$pkg} = $pver;
506: }
507: } else {
508: $remote_rpm{$pkg} = $pver;
509: }
510: }
511:
512: if ($giveup && defined $opt_update) {
513: die "Multiple versions of the same package were found on the server.\n",
514: "However, due to ftp download problems it could not be verified\n",
515: "which of the packages are the most recent ones.\n",
516: "If the choices specified above appear to be correct, rerun check-rpms\n",
517: "without the -lq (or --list-questionable) option. Otherwise, fix the download\n",
518: "problems or install those packages separately first.\n";
519: }
520:
521: #
522: # check for UPDated and DIFferent packages...
523: #
524:
525: for (@local_rpm_list) {
526: my ($pkg, $version) = m/^([^ ]*)-([^-]+-[^-]+)$/;
527: if (! $pkg) { print "Couldn't parse $_\n"; next; }
528: if ($no_kernel) {
529: if ($pkg eq 'kernel' || $pkg eq 'kernel-smp'
530: || $pkg eq 'kernel-enterprise' || $pkg eq 'kernel-BOOT'
531: || $pkg eq 'kernel-debug') { next; }
532: }
533: if (defined $remote_rpm{$pkg}) {
534: # this package has an update
535: my ($rversion) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
536: my $rpm = ($pkg . '-' . $remote_rpm{$pkg});
537: my ($vcmp,$qflag) = cmp_versions($version, $rversion);
538: if ( $qflag && $questionable ) {
539: # at least one of the version strings contains letters
540: push(@q_updates, $rpm);
541: } elsif ( $vcmp < 0 ) {
542: # local version is lower
543: if ( $qflag ) {
544: push(@q_updates, $rpm);
545: } else {
546: push(@updates, $rpm);
547: }
548: }
549: } elsif ($list_missing) {
550: print "Package '$pkg' missing from remote repository\n";
551: }
552: }
553:
554: if ($recheck && @q_updates) {
555: if ($ftp) {
556: for (@q_updates) {
557: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
558: push(@ftp_files, "$FTPUPDATES/$arch/$_");
559: }
560: if ($verbose) {
561: print "Getting questionable packages form $FTPSERVER ...\n";
562: }
563: my $status = system("$FTPGET $FTPSERVER $RPMDIR @ftp_files");
564: if ($status) {
565: if ($< == 0) {
566: # if we are running as root exit to avoid symlink attacks, etc.
567: die "$FTPGET failed with status ", $status/256, ".\n";
568: } else {
569: print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
570: }
571: }
572: }
573: for (@q_updates) {
574: if ($verbose) {print "** rechecking $_ ... ";}
575: my $errmsg = `rpm -Uvh --test --nodeps --pipe 'grep -v ^Preparing' $RPMDIR/$_ 2>&1`;
576: if (! $errmsg) {
577: # no error message, i.e., the rpm is needed.
578: push(@updates,$_);
579: if ($verbose) {print "needed!\n";}
580: } elsif ($verbose) {
581: print "not needed:\n$errmsg\n";
582: }
583: }
584: @q_updates=();
585: }
586:
587: #
588: # print list of new files and download ...
589: #
590:
591: @updates = sort @updates;
592: if (@updates) {
593: if ($verbose) {
594: print "\nRPM files to be updated:\n\n";
595: }
596: for (@updates) {
597: print "$_\n";
598: }
599: if ($download) {
600: @ftp_files=();
601: for (@updates) {
602: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
603: push(@ftp_files, "$FTPUPDATES/$arch/$_");
604: }
605: if ($verbose) {
606: print "starting downloads ... \n";
607: }
608: my $status = system("$FTPGET $FTPSERVER $RPMDIR @ftp_files");
609: if ($status) {
610: if ($< == 0) {
611: # if we are running as root exit to avoid symlink attacks, etc.
612: die "$FTPGET failed with status ", $status/256, ".\n";
613: } else {
614: print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
615: }
616: } elsif ($verbose) {
617: print "... done.\n";
618: }
619: }
620: }
621:
622: @q_updates = sort @q_updates;
623: if (@q_updates && $questionable) {
624: if ($verbose) {
625: print "\nRPM files that may need to be updated:\n\n";
626: for (@q_updates) {
627: my ($old) = m/^([^ ]*)-[^-]+-[^-]+\.\w+\.rpm$/;
628: $old = `rpm -q $old`;
629: chomp($old);
630: print "upgrade ", $old, " to ", $_, " ?\n";
631: }
632: } else {
633: for (@q_updates) {
634: print "$_\n";
635: }
636: }
637: if ($download) {
638: @ftp_files=();
639: for (@updates) {
640: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
641: push(@ftp_files, $FTPUPDATES/$arch/$_);
642: }
643: if ($verbose) {
644: print "starting downloads ... \n";
645: system("$FTPGET $FTPSERVER $$RPMDIR @ftp_files");
646: print "... done.\n";
647: } else {
648: system("$FTPGET $FTPSERVER $$RPMDIR @ftp_files");
649: }
650: }
651: }
652:
653: if ($verbose && !(@updates || @q_updates)) {
654: print "No new updates are available in $SOURCE\n";
655: }
656:
657: if ($opt_update) {
658: if (@q_updates){
659: push(@updates,@q_updates);
660: }
661: if (@updates) {
662: if ($verbose) {
663: print "Running rpm -Fvh ...\n";
664: }
665: # switch to UID=0
666: $> = $<;
667: system("(cd $RPMDIR;rpm -Fvh @updates)");
668: }
669: }
670:
671: # download routine
672: sub ftp_download {
673: my ($FTPSERVER, $FTPDIR, $downloaddir, @packages) = @_;
674: my @ftp_packages=();
675: for (@packages) {
676: my ($arch) = m/[^ ]*-[^-]+-[^-]*\.(\w+)\.rpm$/;
677: push(@ftp_packages,"$FTPDIR/$arch/$_");
678: }
679: my $status = system("$FTPGET $FTPSERVER $downloaddir @ftp_packages");
680: return $status;
681: }
682:
683: sub pkg_compare($$$) {
684: my ($pkg1, $pkg2, $cmp) = @_;
685: if (defined $opt_ftp) {
686: if ($verbose) {
687: my ($pkg) = ($pkg1 =~ /([^ ]*)-[^-]+-[^-]+\.\w+\.rpm/);
688: print "The ftp server provides multiple versions of the $pkg package.\n",
689: "Downloading $pkg1 and $pkg2 in order to find out which is newer.\n";
690: }
691: my $status = ftp_download($FTPSERVER, $FTPUPDATES, $RPMDIR, ($pkg1, $pkg2));
692: if ($status) {
693: # at this point just give up ...
694: print STDERR "** $FTPGET failed with status ", $status/256, ".\n";
695: mulpkg_msg($pkg1, $pkg2, $cmp);
696: return -1;
697: }
698: }
699: my $serial1 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg1`;
700: my $serial2 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg2`;
701: ($serial2 > $serial1) ? return 1 : return 0;
702: }
703:
704: sub mulpkg_msg($$$) {
705: my ($pkg1, $pkg2, $cmp) = @_;
706: print "** The server provides two versions of the same package:\n",
707: "** $pkg1 and $pkg2.\n";
708: if ($cmp > 0) {
709: print "** It appears that $pkg-$remote_rpm{$pkg} is newer.\n"
710: } else {
711: print "** It appears that $pkg-$pver is newer.\n";
712: }
713: }
714:
715: #############################################################################
716: #
717: # Version comparison utilities
718: #
719:
720: sub hack_version($) {
721: my ($pver) = @_;
722: $pver =~ s/(\d+)/sprintf("%08d", $1)/eg; # pad numbers with leading zeros to make alphabetical sort do the right thing
723: $pver = (sprintf "%-80s", $pver); # pad with spaces so that "3.2.1" is greater than "3.2"
724: return $pver;
725: }
726:
727: sub cmp_versions($$) {
728: my ($pkg1, $pkg2) = @_;
729:
730: # shortcut if they're obviously the same.
731: return (0,0) if ($pkg1 eq $pkg2);
732:
733: # split into version and release
734: my ($ver1, $rel1) = ($pkg1 =~ m/([^-]+)-([^-]+)/);
735: my ($ver2, $rel2) = ($pkg2 =~ m/([^-]+)-([^-]+)/);
736:
737: if ($ver1 ne $ver2) {
738: my $qflag = ((grep /[A-z]/, $ver1) || (grep /[A-z]/, $ver2));
739: $ver1 = hack_version($ver1);
740: $ver2 = hack_version($ver2);
741: return ($ver1 cmp $ver2, $qflag);
742: } else {
743: my $qflag = ((grep /[A-z]/, $rel1) || (grep /[A-z]/, $rel2));
744: $rel1 = hack_version($rel1);
745: $rel2 = hack_version($rel2);
746: return ($rel1 cmp $rel2, $qflag);
747: }
748: }
749:
750: sub cmp_arch($$) {
751: my ($arch1, $arch2) = @_;
752: my $retval = 0;
753: $archcmp = ($arch1 cmp $arch2) > 0;
754: if ( "$PROC" eq "athlon" ) {
755: if ( "$arch2" ne "athlon"
756: && ( "$arch1" eq "athlon" || $archcmp )){
757: $retval = 1;
758: }
759: } elsif ( $archcmp && ($PROC cmp $arch1) >= 0 ) {
760: $retval = 1;
761: }
762: return $retval;
763: }
764:
765: # @tests = ('3.2', '3.2',
766: # '3.2a', '3.2a',
767: # '3.2', '3.2a',
768: # '3.2', '3.3',
769: # '3.2', '3.2.1',
770: # '1.2.5i', '1.2.5.1',
771: # '1.6.3p6', '1.6.4');
772: #
773: # while (@tests) {
774: # $a = shift(@tests);
775: # $b = shift(@tests);
776: # printf "%-10s < %-10s = %d\n", $a, $b, cmp_versions($a, $b);
777: # }
778: #
779: # And the correct output is...
780: #
781: # 3.2 < 3.2 = 0
782: # 3.2a < 3.2a = 0
783: # 3.2 < 3.2a = -1
784: # 3.2 < 3.3 = -1
785: # 3.2 < 3.2.1 = -1
786: # 1.2.5i < 1.2.5.1 = -1
787: # 1.6.3p6 < 1.6.4 = -1
788: #
789: # the lexical sort does not give the correct result in the second to last case.
790:
791:
792: sub usage(){
793: die "usage: check-rpms [-v | --verbose] [-d directory | --dir directory]\n",
794: " [-ftp [server/directory]] [-noftp] [-lm | --list-missing]\n",
795: " [-lq | --list-questionable] [-r | --recheck ]\n",
796: " [-nk | --no-kernel] [--update] [-c configurationfile]\n";
797: }
1.2 ! harris41 798:
! 799: =pod
! 800:
! 801: =head1 SEE ALSO
! 802:
! 803: rpm(8), ncftpls(1), ncftpget(1)
! 804:
! 805: =head1 AUTHOR
! 806:
! 807: Martin Siegert, Simon Fraser University, siegert@sfu.ca
! 808:
! 809: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>