File:  [LON-CAPA] / loncom / build / check-rpms
Revision 1.6: download - view: text, annotated - select for diffs
Mon Nov 4 02:52:39 2002 UTC (21 years, 6 months ago) by harris41
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_99_0, version_2_9_1, version_2_9_0, version_2_8_X, version_2_8_99_1, version_2_8_99_0, version_2_8_2, version_2_8_1, version_2_8_0, version_2_7_X, version_2_7_99_1, version_2_7_99_0, version_2_7_1, version_2_7_0, version_2_6_X, version_2_6_99_1, version_2_6_99_0, version_2_6_3, version_2_6_2, version_2_6_1, version_2_6_0, version_2_5_X, version_2_5_99_1, version_2_5_99_0, version_2_5_2, version_2_5_1, version_2_5_0, version_2_4_X, version_2_4_99_0, version_2_4_2, version_2_4_1, version_2_4_0, version_2_3_X, version_2_3_99_0, version_2_3_2, version_2_3_1, version_2_3_0, version_2_2_X, version_2_2_99_1, version_2_2_99_0, version_2_2_2, version_2_2_1, version_2_2_0, version_2_1_X, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_12_X, version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0_RC1, version_2_11_0, version_2_10_X, version_2_10_1, version_2_10_0_RC2, version_2_10_0_RC1, version_2_10_0, version_2_0_X, version_2_0_99_1, version_2_0_2, version_2_0_1, version_2_0_0, version_1_99_3, version_1_99_2, version_1_99_1_tmcc, version_1_99_1, version_1_99_0_tmcc, version_1_99_0, version_1_3_X, version_1_3_3, version_1_3_2, version_1_3_1, version_1_3_0, version_1_2_X, version_1_2_99_1, version_1_2_99_0, version_1_2_1, version_1_2_0, version_1_1_X, version_1_1_99_5, version_1_1_99_4, version_1_1_99_3, version_1_1_99_2, version_1_1_99_1, version_1_1_99_0, version_1_1_3, version_1_1_2, version_1_1_1, version_1_1_0, version_1_0_99_3, version_1_0_99_2, version_1_0_99_1, version_1_0_99, version_1_0_3, version_1_0_2, version_1_0_1, version_1_0_0, version_0_99_5, version_0_99_4, version_0_99_3, version_0_99_2, version_0_99_1, version_0_99_0, version_0_6_2, version_0_6, loncapaMITrelate_1, language_hyphenation_merge, language_hyphenation, conference_2003, bz6209-base, bz6209, bz5969, bz5610, bz2851, PRINT_INCOMPLETE_base, PRINT_INCOMPLETE, HEAD, GCI_3, GCI_2, GCI_1, BZ5971-printing-apage, BZ5434-fox, BZ4492-merge, BZ4492-feature_horizontal_radioresponse, BZ4492-feature_Support_horizontal_radioresponse, BZ4492-Support_horizontal_radioresponse
allow ftp server authentication flags with the ncftpget command

    1: #!/usr/bin/perl
    2: #
    3: # check-rpms, version 2.1.1
    4: # Martin Siegert, SFU, siegert@sfu.ca, Feb 02
    5: #
    6: # documentation and minor patches,
    7: # Scott Harrison sharrison@users.sourceforge.net 2002
    8: 
    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: 
   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",
   93:                          "nk|no-kernel","update","c=s","rpmuser=s");
   94: 
   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: 
  206: =item B<--rpmuser> I<user name>
  207: 
  208: Specifying $RPMUSER on the command line.
  209: 
  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: 
  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";
  270: $DEFRPMUSER = "nobody";
  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: 
  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
  304:  $RPMUSER = "adminjoe";
  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: 
  318: if ( -f $CONF) {
  319:    require($CONF);
  320: } else {
  321:    $FTPSERVER = $DEFFTPSERVER;
  322:    $FTPUPDATES = $DEFFTPUPDATES;
  323: }
  324: 
  325: if ($opt_rpmuser) {
  326:    $DEFRPMUSER = $opt_rpmuser;
  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) {
  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:           }
  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: 
  398: my $FTPSERVER_auth = $FTPSERVER;
  399: $FTPSERVER_auth =~ s/^(\w+)\:(\w+)\@/-u $1 -p $2 /;
  400: 
  401: if ($recheck) {
  402:    $questionable=1;
  403: }
  404: 
  405: if (defined $opt_update || defined $opt_nk) {
  406:     $no_kernel=1;
  407: }
  408: 
  409: $PROC = `grep -i "athlon\|amd" /proc/cpuinfo`;
  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))/;
  480:    if (! defined $local_rpm{$pkg}) { next; }
  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
  563:             push(@updates, $rpm);
  564: 	}
  565:     } elsif ($list_missing) {
  566: 	print "Package '$pkg' missing from remote repository\n";
  567:     }
  568: }
  569: 
  570: 
  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:       }
  580:       my $status = system("$FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
  581:       if ($status) {
  582:          if ($< == 0) {
  583:             # if we are running as root exit to avoid symlink attacks, etc.
  584: print("DEBUG DEBUG $FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
  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:        }
  626:        my $status = system("$FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
  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";
  663:           system("$FTPGET $FTPSERVER_auth $$RPMDIR @ftp_files");
  664:           print "... done.\n";
  665:        } else {
  666:           system("$FTPGET $FTPSERVER_auth $$RPMDIR @ftp_files");
  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:     }
  697:     my $status = system("$FTPGET $FTPSERVER_auth $downloaddir @ftp_packages");
  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`;
  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:    }
  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: }
  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>