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