File:  [LON-CAPA] / loncom / build / check-rpms
Revision 1.1: download - view: text, annotated - select for diffs
Sun Feb 24 22:33:45 2002 UTC (22 years, 4 months ago) by harris41
Branches: MAIN
CVS tags: HEAD
Martin Siegert's script to monitor RPM status

    1: #!/usr/bin/perl
    2: #
    3: # check-rpms, version 2.1.0
    4: # Martin Siegert, SFU, siegert@sfu.ca, Feb 02
    5: #
    6: # ************ WARNING *****************************************************
    7: # THIS PROGRAM IS PROVIDED "AS IS" WITHOUT
    8: # WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLICIT.
    9: # IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   10: # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   11: # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   12: # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   13: # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   14: # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   15: # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   16: # SUCH DAMAGE.
   17: # **************************************************************************
   18: 
   19: # check-rpms.pl is free software; you can redistribute it and/or modify
   20: # it under the terms of the GNU General Public License as published by
   21: # the Free Software Foundation; either version 2 of the License, or
   22: # (at your option) any later version.
   23: #
   24: # check-rpms.pl is distributed in the hope that it will be useful,
   25: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   26: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   27: # GNU General Public License for more details:
   28: # http://www.gnu.org/licenses/gpl.html
   29: 
   30: use Getopt::Long;
   31: 
   32: my $retval = &GetOptions("verbose|v","lm|list-missing","lq|list-questionable",
   33:                          "dir|d=s","ftp:s","noftp","download|dl","recheck|r",
   34:                          "nk|no-kernel","update","c=s");
   35: 
   36: if ( $retval == 0 ) {
   37:     usage();
   38: }
   39: 
   40: # executables
   41: $FTPLS = "ncftpls";
   42: $FTPGET = "ncftpget";
   43: $GREP = "grep";
   44: 
   45: # default values
   46: $RHversion = (split /\s/, `cat /etc/redhat-release`)[4];
   47: $DEFCONF = "/usr/local/etc/check-rpms.conf";
   48: $DEFRPMDIR = "/mnt/redhat/RedHat/RPMS";
   49: $DEFFTPSERVER = "updates.redhat.com";
   50: $DEFFTPUPDATES = "$RHversion/en/os";
   51: $DEFRPMUSER = "nobody";
   52: 
   53: $RPMDIR=$DEFRPMDIR;
   54: 
   55: # configuration
   56: # the configuration file should set the $RPMDIR variable and/or $FTPSERVER,
   57: # $FTPUPDATES and $DOWNLOADDIR variables, and the $RPMUSER variable.
   58: if ($opt_c) {
   59:    $CONF = $opt_c;
   60: } else {
   61:    $CONF = $DEFCONF;
   62: }
   63: 
   64: if ( -f $CONF) {
   65:    require($CONF);
   66: } else {
   67:    $FTPSERVER = $DEFFTPSERVER;
   68:    $FTPUPDATES = $DEFFTPUPDATES;
   69: }
   70: 
   71: # check whether we are running as root
   72: if ($< == 0){
   73:    if (! $RPMUSER) {
   74:       $RPMUSER = $DEFRPMUSER;
   75:    }
   76:    $RPMUID = getpwnam($RPMUSER);
   77:    if (! $RPMUID) {
   78:       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";
   79:    }
   80:    if ($RPMUID == 0) {
   81:       die "You must set the \$RPMUSER variable in $CONF to a non-root user.\n";
   82:    }
   83:    # switch to $RPMUID
   84:    $> = $RPMUID;
   85:    if ($> != $RPMUID) { die "switching to $RPMUID uid failed.\n" }
   86: }
   87: 
   88: # command-line arguments
   89: $verbose         = $opt_verbose;
   90: $list_missing    = $opt_lm;
   91: $questionable    = $opt_lq;
   92: $no_kernel       = $opt_nk;
   93: $download        = $opt_download;
   94: $recheck         = $opt_recheck;
   95: $update          = $opt_update;
   96: 
   97: if (defined $opt_update && $< != 0) {
   98:     die "You must be root in order to update rpms.\n";
   99: }
  100: 
  101: if ( defined $opt_dir ){
  102:    $RPMDIR = $opt_dir;
  103: }
  104: 
  105: if (defined $opt_ftp && defined $opt_noftp) {
  106:    die "Setting -ftp and -noftp does not make sense, does it?\n";
  107: }
  108: 
  109: if (defined $opt_noftp) { $FTP = 0; }
  110: 
  111: if (defined $opt_ftp || $FTP) {
  112:    $ftp = 1;
  113:    if ( $opt_ftp ) {
  114:       $_ = $opt_ftp;
  115:       ($FTPSERVER, $FTPUPDATES) = m/^([^\/]+)\/(.*)$/;
  116:    } elsif ( ! ($FTPSERVER && $FTPUPDATES)) {
  117:       $FTPSERVER = $DEFFTPSERVER;
  118:       $FTPUPDATES = $DEFFTPUPDATES;
  119:    }
  120: 
  121:    if (defined $opt_update){
  122:       $download=1;
  123:    }
  124: 
  125:    if ($download || $recheck) {
  126:        if ( ! -d $RPMDIR) {
  127:           $retval = system("mkdir -p $RPMDIR; chmod 700 $RPMDIR");
  128:           if ($retval) { die "error: could not create $RPMDIR\n"; }
  129:       }
  130:    }
  131: } elsif ( (! -d $RPMDIR) || system("ls $RPMDIR/*.rpm > /dev/null 2>&1")) {
  132:    die "Either $RPMDIR does not exist or it does not contain any packages.\n";
  133: }
  134: 
  135: if ($recheck) {
  136:    $questionable=1;
  137: }
  138: 
  139: if (defined $opt_update || defined $opt_nk) {
  140:     $no_kernel=1;
  141: }
  142: 
  143: $PROC = `grep -i athlon /proc/cpuinfo`;
  144: if ( ! "$PROC" ) {
  145:     $PROC = `uname -m`;
  146:     chomp($PROC);
  147: } else {
  148:     $PROC = "athlon";
  149: }
  150: 
  151: @ARCHITECTURES = ("noarch", "i386", "i586", "i686");
  152: if ( $RHversion > 7.0 ){ 
  153:     push(@ARCHITECTURES, "athlon");
  154: }
  155: 
  156: # get the local list of installed packages
  157: 
  158: if ($verbose) {
  159:    print "updates for $PROC processor, RH $RHversion\n";
  160:    print "Getting list of installed packages\n";
  161: }
  162: 
  163: if ($< == 0) {
  164:    @local_rpm_list = `su $RPMUSER -c 'rpm -qa'`;
  165: } else {
  166:    @local_rpm_list = `rpm -qa`;
  167: }
  168: chop(@local_rpm_list);
  169: 
  170: %local_rpm = %remote_rpm = ();
  171: 
  172: for (@local_rpm_list) {
  173: #    good place to test the regular expressions...
  174: #    ($pkg, $ver, $release) = m/^(.*)-([^-]*)-([^-]+)/;
  175: #    print "$_\t->$pkg, $ver, $release\n";
  176: 
  177:     my ($pkg, $pver) = m/([^ ]*)-([^-]+-[^-]+)/;
  178:     $local_rpm{$pkg} = $pver;
  179: }
  180: 
  181: # now connect to the remote host
  182: 
  183: my @templist;
  184: if ($ftp) {
  185:    if ( `rpm -q ncftp --pipe "grep 'not installed'"` ) {
  186:       die "you must have the ncftp package installed in order to use a\n",
  187:           "ftp server with check-rpms.\n";
  188:    }
  189:    $SOURCE = $FTPSERVER;
  190:    for (@ARCHITECTURES) {
  191:       my $FTPDIR = "$FTPUPDATES/$_";
  192:       if ($verbose) {
  193:          print ("Getting package lists from $FTPSERVER/$FTPDIR ...\n");
  194:       }
  195:       push(@templist, grep(/\.rpm$/, `$FTPLS -x "-1a" "ftp://$FTPSERVER/$FTPDIR/"`));
  196:       if ($?) { print STDERR "$FTPLS failed with status ",$?/256,".\n"; }
  197:    }
  198: } else {
  199:    $SOURCE = $RPMDIR;
  200:    if ($verbose) {
  201:        print ("Getting package lists from $RPMDIR ...\n");
  202:    }
  203:    @templist = grep(/\.rpm$/, `(cd $RPMDIR;ls -1)`);
  204: }
  205: 
  206: #
  207: # If two versions of the same RPM appear with different architectures
  208: # and/or different versions, the right one must be found.
  209: #
  210: 
  211: $giveup = 0;
  212: for (@templist) {
  213:    ($rpm, $pkg, $pver, $arch) = m/(([^ ]*)-([^- ]+-[^-]+\.(\w+)\.rpm))/;
  214:    if ($remote_rpm{$pkg}) {
  215:       # problem: there are several versions of the same package.
  216:       # this means that the package exists for different architectures
  217:       # (e.g., kernel, glibc, etc.) and/or that the remote server
  218:       # has several versions of the same package in which case the
  219:       # latest version must be picked.
  220:       my ($pkg1) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
  221:       my ($pkg2) = ($pver =~ m/([^-]+-[^-]+)\.\w+.rpm/);
  222:       my ($vcmp, $qflag) = cmp_versions($pkg1, $pkg2);
  223:       if ($qflag && $questionable) {
  224:          # cannot decide which of the two is newer - what should we do?
  225:          # print a warning that lists the two rpms.
  226:          # If running with --update, both packages must be rechecked with 
  227:          # rpm -qp --queryformat '%{SERIAL}' <pkg>
  228:          if ($recheck || $update) {
  229:             my $decision = pkg_compare("$pkg-$remote_rpm{$pkg}",$rpm, $vcmp);
  230:             if ($decision < 0) {
  231:                # an error in the ftp download routine accured: giveup
  232:                $remote_rpm{$pkg} = undef;
  233:                $giveup = 1;
  234:             } elsif ($decision > 0) {
  235:                # second package is newer
  236:                $remote_rpm{$pkg} = $pver;
  237:             }
  238:             next;
  239:          } else {
  240:             mulpkg_msg("$pkg-$remote_rpm{$pkg}", $rpm, $vcmp);
  241:             print "** check whether this is correct or rerun with --recheck option.\n";
  242:             if ($vcmp < 0) {
  243:                $remote_rpm{$pkg} = $pver;
  244:             }
  245:          }
  246:       }
  247:       if ($vcmp == 0) {        
  248:          # versions are equal: must be different architecture
  249:          # procedure to select the correct architecture:
  250:          # if $PROC = athlon: if available use $arch = athlon (exist for
  251:          # RH 7.1 or newer) otherwise use i686
  252:          # if $PROC = ix86: choose pkg with $PROC cmp $arch >= 0 and
  253:          # $arch cmp $prev_arch = 1
  254:          $_ = $remote_rpm{$pkg};
  255:          ($prev_arch) =  m/.*\.(\w+)\.rpm$/;
  256:          if (cmp_arch($arch,$prev_arch)) { $remote_rpm{$pkg} = $pver };
  257:       } elsif ($vcmp < 0) {    # second rpm is newer
  258:          $remote_rpm{$pkg} = $pver;
  259:       }
  260:    } else {
  261:       $remote_rpm{$pkg} = $pver;
  262:    }
  263: }
  264: 
  265: if ($giveup && defined $opt_update) {
  266:    die "Multiple versions of the same package were found on the server.\n",
  267:        "However, due to ftp download problems it could not be verified\n",
  268:        "which of the packages are the most recent ones.\n",
  269:        "If the choices specified above appear to be correct, rerun check-rpms\n",
  270:        "without the -lq (or --list-questionable) option. Otherwise, fix the download\n",
  271:        "problems or install those packages separately first.\n";
  272: }
  273: 
  274: #
  275: # check for UPDated and DIFferent packages...
  276: #
  277: 
  278: for (@local_rpm_list) {
  279:     my ($pkg,  $version) = m/^([^ ]*)-([^-]+-[^-]+)$/;
  280:     if (! $pkg) { print "Couldn't parse $_\n"; next; }
  281:     if ($no_kernel) {
  282:        if ($pkg eq 'kernel' || $pkg eq 'kernel-smp'
  283:            || $pkg eq 'kernel-enterprise' || $pkg eq 'kernel-BOOT'
  284:            || $pkg eq 'kernel-debug') { next; }
  285:     }
  286:     if (defined $remote_rpm{$pkg}) { 
  287:         # this package has an update
  288: 	my ($rversion) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
  289: 	my $rpm = ($pkg . '-' . $remote_rpm{$pkg});
  290: 	my ($vcmp,$qflag) = cmp_versions($version, $rversion);
  291: 	if ( $qflag && $questionable ) {
  292:             # at least one of the version strings contains letters
  293:             push(@q_updates, $rpm);
  294:         } elsif ( $vcmp < 0 ) {
  295:             # local version is lower
  296:             if ( $qflag ) {
  297:                push(@q_updates, $rpm);
  298:             } else {
  299:                push(@updates, $rpm);
  300:             }
  301: 	}
  302:     } elsif ($list_missing) {
  303: 	print "Package '$pkg' missing from remote repository\n";
  304:     }
  305: }
  306: 
  307: if ($recheck && @q_updates) {
  308:    if ($ftp) {    
  309:       for (@q_updates) {
  310:          ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
  311:          push(@ftp_files, "$FTPUPDATES/$arch/$_");
  312:       }
  313:       if ($verbose) {
  314:          print "Getting questionable packages form $FTPSERVER ...\n";
  315:       }
  316:       my $status = system("$FTPGET $FTPSERVER $RPMDIR @ftp_files");
  317:       if ($status) {
  318:          if ($< == 0) {
  319:             # if we are running as root exit to avoid symlink attacks, etc.
  320:             die "$FTPGET failed with status ", $status/256, ".\n";
  321: 	 } else {
  322:             print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
  323:          } 
  324:       }
  325:    }
  326:    for (@q_updates) {
  327:       if ($verbose) {print "** rechecking $_ ... ";}
  328:       my $errmsg = `rpm -Uvh --test --nodeps --pipe 'grep -v ^Preparing' $RPMDIR/$_ 2>&1`;
  329:       if (! $errmsg) {
  330:          # no error message, i.e., the rpm is needed.
  331:           push(@updates,$_);
  332:           if ($verbose) {print "needed!\n";}
  333:       } elsif ($verbose) {
  334:           print "not needed:\n$errmsg\n";
  335:       }
  336:    }
  337:    @q_updates=();
  338: }
  339:        
  340: #
  341: # print list of new files and download ...
  342: #
  343: 
  344: @updates = sort @updates;
  345: if (@updates) {
  346:     if ($verbose) {
  347:        print "\nRPM files to be updated:\n\n";
  348:     }
  349:     for (@updates) {
  350:        print "$_\n";
  351:     }
  352:     if ($download) {
  353:        @ftp_files=();
  354:        for (@updates) {
  355:           ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
  356:           push(@ftp_files, "$FTPUPDATES/$arch/$_");
  357:        }
  358:        if ($verbose) {
  359:           print "starting downloads ... \n";
  360:        }
  361:        my $status = system("$FTPGET $FTPSERVER $RPMDIR @ftp_files");
  362:        if ($status) {
  363:           if ($< == 0) {
  364:              # if we are running as root exit to avoid symlink attacks, etc.
  365:              die "$FTPGET failed with status ", $status/256, ".\n";
  366: 	  } else {
  367:              print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
  368:           } 
  369:        } elsif ($verbose) {
  370:           print "... done.\n";
  371:        }
  372:     }           
  373: }
  374: 
  375: @q_updates = sort @q_updates;
  376: if (@q_updates && $questionable) {
  377:     if ($verbose) {
  378:        print "\nRPM files that may need to be updated:\n\n";
  379:        for (@q_updates) {
  380:           my ($old) = m/^([^ ]*)-[^-]+-[^-]+\.\w+\.rpm$/;
  381:           $old = `rpm -q $old`;
  382:           chomp($old);
  383:           print "upgrade ", $old, " to ", $_, " ?\n";
  384:        }
  385:     } else {
  386:        for (@q_updates) {
  387:           print "$_\n";
  388:        }
  389:     }
  390:     if ($download) {
  391:        @ftp_files=();
  392:        for (@updates) {
  393:           ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
  394:           push(@ftp_files, $FTPUPDATES/$arch/$_);
  395:        }
  396:        if ($verbose) {
  397:           print "starting downloads ... \n";
  398:           system("$FTPGET $FTPSERVER $$RPMDIR @ftp_files");
  399:           print "... done.\n";
  400:        } else {
  401:           system("$FTPGET $FTPSERVER $$RPMDIR @ftp_files");
  402:        }
  403:     }           
  404: }
  405: 
  406: if ($verbose && !(@updates || @q_updates)) {
  407:     print "No new updates are available in $SOURCE\n";
  408: }
  409: 
  410: if ($opt_update) {
  411:     if (@q_updates){
  412:        push(@updates,@q_updates);
  413:     }
  414:     if (@updates) {
  415:        if ($verbose) {
  416:           print "Running rpm -Fvh ...\n";
  417:        }
  418:        # switch to UID=0
  419:        $> = $<;
  420:        system("(cd $RPMDIR;rpm -Fvh @updates)");
  421:    }
  422: }
  423: 
  424: # download routine
  425: sub ftp_download {
  426:    my ($FTPSERVER, $FTPDIR, $downloaddir, @packages) = @_;
  427:    my @ftp_packages=();
  428:    for (@packages) {
  429:        my ($arch) = m/[^ ]*-[^-]+-[^-]*\.(\w+)\.rpm$/;
  430:        push(@ftp_packages,"$FTPDIR/$arch/$_");
  431:     }
  432:     my $status = system("$FTPGET $FTPSERVER $downloaddir @ftp_packages");
  433:     return $status;
  434: }
  435: 
  436: sub pkg_compare($$$) {
  437:    my ($pkg1, $pkg2, $cmp) = @_;
  438:    if (defined $opt_ftp) {
  439:       if ($verbose) {
  440:          my ($pkg) = ($pkg1 =~ /([^ ]*)-[^-]+-[^-]+\.\w+\.rpm/);
  441:          print "The ftp server provides multiple versions of the $pkg package.\n",
  442:                "Downloading $pkg1 and $pkg2 in order to find out which is newer.\n";
  443:       }
  444:       my $status = ftp_download($FTPSERVER, $FTPUPDATES, $RPMDIR, ($pkg1, $pkg2));
  445:       if ($status) {
  446:          # at this point just give up ...
  447:          print STDERR "** $FTPGET failed with status ", $status/256, ".\n";
  448:          mulpkg_msg($pkg1, $pkg2, $cmp);
  449:          return -1;
  450:       }
  451:    }
  452:    my $serial1 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg1`;
  453:    my $serial2 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg2`;
  454:    ($serial2 > $serial1) ? return 1 : return 0;
  455: }
  456: 
  457: sub mulpkg_msg($$$) {
  458:    my ($pkg1, $pkg2, $cmp) = @_;
  459:    print "** The server provides two versions of the same package:\n",
  460:          "** $pkg1 and $pkg2.\n";
  461:    if ($cmp > 0) {
  462:        print "** It appears that $pkg-$remote_rpm{$pkg} is newer.\n"
  463:    } else {
  464:        print "** It appears that $pkg-$pver is newer.\n";
  465:    }
  466: }
  467: 
  468: #############################################################################
  469: #
  470: # Version comparison utilities
  471: #
  472: 
  473: sub hack_version($) {
  474:     my ($pver) = @_;
  475:     $pver =~ s/(\d+)/sprintf("%08d", $1)/eg; # pad numbers with leading zeros to make alphabetical sort do the right thing
  476:     $pver =  (sprintf "%-80s", $pver);	     # pad with spaces so that "3.2.1" is greater than "3.2"
  477:     return $pver;
  478: }
  479: 
  480: sub cmp_versions($$) {
  481:     my ($pkg1, $pkg2) = @_;
  482: 
  483:     # shortcut if they're obviously the same.
  484:     return (0,0) if ($pkg1 eq $pkg2);
  485: 
  486:     # split into version and release
  487:     my ($ver1, $rel1) = ($pkg1 =~ m/([^-]+)-([^-]+)/);
  488:     my ($ver2, $rel2) = ($pkg2 =~ m/([^-]+)-([^-]+)/);
  489: 
  490:     if ($ver1 ne $ver2) {
  491:        my $qflag = ((grep /[A-z]/, $ver1) || (grep /[A-z]/, $ver2));
  492:        $ver1 = hack_version($ver1);
  493:        $ver2 = hack_version($ver2);
  494:        return ($ver1 cmp $ver2, $qflag);
  495:     } else {
  496:        my $qflag = ((grep /[A-z]/, $rel1) || (grep /[A-z]/, $rel2));
  497:        $rel1 = hack_version($rel1);
  498:        $rel2 = hack_version($rel2);
  499:        return ($rel1 cmp $rel2, $qflag);
  500:     }
  501: }
  502: 
  503: sub cmp_arch($$) {
  504:     my ($arch1, $arch2) = @_;
  505:     my $retval = 0;
  506:     $archcmp = ($arch1 cmp $arch2) > 0;
  507:     if ( "$PROC" eq "athlon" ) {
  508:        if ( "$arch2" ne "athlon" 
  509:               && ( "$arch1" eq "athlon" || $archcmp )){
  510: 	   $retval = 1;
  511:        }
  512:     } elsif ( $archcmp && ($PROC cmp $arch1) >= 0 ) {
  513:        $retval = 1;
  514:     }
  515:     return $retval;
  516: }
  517: 
  518: # @tests = ('3.2', '3.2',
  519: #           '3.2a', '3.2a',
  520: #           '3.2', '3.2a',
  521: #           '3.2', '3.3',
  522: #           '3.2', '3.2.1',
  523: #           '1.2.5i', '1.2.5.1',
  524: #           '1.6.3p6', '1.6.4');
  525: # 
  526: # while (@tests) {
  527: #     $a = shift(@tests);
  528: #     $b = shift(@tests);
  529: #     printf "%-10s < %-10s = %d\n", $a, $b, cmp_versions($a, $b);
  530: # }
  531: #
  532: # And the correct output is...
  533: #
  534: #     3.2        < 3.2        = 0
  535: #     3.2a       < 3.2a       = 0
  536: #     3.2        < 3.2a       = -1
  537: #     3.2        < 3.3        = -1
  538: #     3.2        < 3.2.1      = -1
  539: #     1.2.5i     < 1.2.5.1    = -1
  540: #     1.6.3p6    < 1.6.4      = -1
  541: #
  542: # the lexical sort does not give the correct result in the second to last case.
  543: 
  544: 
  545: sub usage(){
  546:    die "usage: check-rpms [-v | --verbose]  [-d directory | --dir directory]\n",
  547:        "                  [-ftp [server/directory]] [-noftp] [-lm | --list-missing]\n",
  548:        "                  [-lq | --list-questionable] [-r | --recheck ]\n",
  549:        "                  [-nk | --no-kernel] [--update] [-c configurationfile]\n";
  550: }

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