File:  [LON-CAPA] / loncom / build / check-rpms
Revision 1.2: download - view: text, annotated - select for diffs
Wed Feb 27 01:47:31 2002 UTC (22 years, 4 months ago) by harris41
Branches: MAIN
CVS tags: HEAD
merging Martin's man page into perl script by using POD

    1: #!/usr/bin/perl
    2: #
    3: # check-rpms, version 2.1.0
    4: # Martin Siegert, SFU, siegert@sfu.ca, Feb 02
    5: #
    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: 
   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: 
   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: 
  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: 
  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: 
  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: }
  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>