Annotation of loncom/build/check-rpms, revision 1.6

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>