main.c revision 276280
1/* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30#ifndef lint 31static const char copyright[] = 32"@(#) Copyright (c) 1980, 1993\n\ 33 The Regents of the University of California. All rights reserved.\n"; 34#endif /* not lint */ 35 36#ifndef lint 37#if 0 38static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93"; 39#endif 40static const char rcsid[] = 41 "$FreeBSD: stable/10/usr.sbin/config/main.c 276280 2014-12-27 03:19:04Z ian $"; 42#endif /* not lint */ 43 44#include <sys/types.h> 45#include <sys/stat.h> 46#include <sys/sbuf.h> 47#include <sys/file.h> 48#include <sys/mman.h> 49#include <sys/param.h> 50 51#include <assert.h> 52#include <ctype.h> 53#include <err.h> 54#include <stdio.h> 55#include <string.h> 56#include <sysexits.h> 57#include <unistd.h> 58#include <dirent.h> 59#include "y.tab.h" 60#include "config.h" 61#include "configvers.h" 62 63#ifndef TRUE 64#define TRUE (1) 65#endif 66 67#ifndef FALSE 68#define FALSE (0) 69#endif 70 71#define CDIR "../compile/" 72 73char * PREFIX; 74char destdir[MAXPATHLEN]; 75char srcdir[MAXPATHLEN]; 76 77int debugging; 78int profiling; 79int found_defaults; 80int incignore; 81 82/* 83 * Preserve old behaviour in INCLUDE_CONFIG_FILE handling (files are included 84 * literally). 85 */ 86int filebased = 0; 87 88static void configfile(void); 89static void get_srcdir(void); 90static void usage(void); 91static void cleanheaders(char *); 92static void kernconfdump(const char *); 93static void checkversion(void); 94extern int yyparse(void); 95 96struct hdr_list { 97 char *h_name; 98 struct hdr_list *h_next; 99} *htab; 100 101/* 102 * Config builds a set of files for building a UNIX 103 * system given a description of the desired system. 104 */ 105int 106main(int argc, char **argv) 107{ 108 109 struct stat buf; 110 int ch, len; 111 char *p; 112 char *kernfile; 113 struct includepath* ipath; 114 int printmachine; 115 116 printmachine = 0; 117 kernfile = NULL; 118 SLIST_INIT(&includepath); 119 while ((ch = getopt(argc, argv, "CI:d:gmpVx:")) != -1) 120 switch (ch) { 121 case 'C': 122 filebased = 1; 123 break; 124 case 'I': 125 ipath = (struct includepath *) \ 126 calloc(1, sizeof (struct includepath)); 127 if (ipath == NULL) 128 err(EXIT_FAILURE, "calloc"); 129 ipath->path = optarg; 130 SLIST_INSERT_HEAD(&includepath, ipath, path_next); 131 break; 132 case 'm': 133 printmachine = 1; 134 break; 135 case 'd': 136 if (*destdir == '\0') 137 strlcpy(destdir, optarg, sizeof(destdir)); 138 else 139 errx(EXIT_FAILURE, "directory already set"); 140 break; 141 case 'g': 142 debugging++; 143 break; 144 case 'p': 145 profiling++; 146 break; 147 case 'V': 148 printf("%d\n", CONFIGVERS); 149 exit(0); 150 case 'x': 151 kernfile = optarg; 152 break; 153 case '?': 154 default: 155 usage(); 156 } 157 argc -= optind; 158 argv += optind; 159 160 if (kernfile != NULL) { 161 kernconfdump(kernfile); 162 exit(EXIT_SUCCESS); 163 } 164 165 if (argc != 1) 166 usage(); 167 168 PREFIX = *argv; 169 if (stat(PREFIX, &buf) != 0 || !S_ISREG(buf.st_mode)) 170 err(2, "%s", PREFIX); 171 if (freopen("DEFAULTS", "r", stdin) != NULL) { 172 found_defaults = 1; 173 yyfile = "DEFAULTS"; 174 } else { 175 if (freopen(PREFIX, "r", stdin) == NULL) 176 err(2, "%s", PREFIX); 177 yyfile = PREFIX; 178 } 179 if (*destdir != '\0') { 180 len = strlen(destdir); 181 while (len > 1 && destdir[len - 1] == '/') 182 destdir[--len] = '\0'; 183 get_srcdir(); 184 } else { 185 strlcpy(destdir, CDIR, sizeof(destdir)); 186 strlcat(destdir, PREFIX, sizeof(destdir)); 187 } 188 189 SLIST_INIT(&cputype); 190 SLIST_INIT(&mkopt); 191 SLIST_INIT(&opt); 192 SLIST_INIT(&rmopts); 193 STAILQ_INIT(&cfgfiles); 194 STAILQ_INIT(&dtab); 195 STAILQ_INIT(&fntab); 196 STAILQ_INIT(&ftab); 197 STAILQ_INIT(&hints); 198 if (yyparse()) 199 exit(3); 200 201 /* 202 * Ensure that required elements (machine, cpu, ident) are present. 203 */ 204 if (machinename == NULL) { 205 printf("Specify machine type, e.g. ``machine i386''\n"); 206 exit(1); 207 } 208 if (ident == NULL) { 209 printf("no ident line specified\n"); 210 exit(1); 211 } 212 if (SLIST_EMPTY(&cputype)) { 213 printf("cpu type must be specified\n"); 214 exit(1); 215 } 216 checkversion(); 217 218 if (printmachine) { 219 printf("%s\t%s\n",machinename,machinearch); 220 exit(0); 221 } 222 223 /* Make compile directory */ 224 p = path((char *)NULL); 225 if (stat(p, &buf)) { 226 if (mkdir(p, 0777)) 227 err(2, "%s", p); 228 } else if (!S_ISDIR(buf.st_mode)) 229 errx(EXIT_FAILURE, "%s isn't a directory", p); 230 231 configfile(); /* put config file into kernel*/ 232 options(); /* make options .h files */ 233 makefile(); /* build Makefile */ 234 makeenv(); /* build env.c */ 235 makehints(); /* build hints.c */ 236 headers(); /* make a lot of .h files */ 237 cleanheaders(p); 238 printf("Kernel build directory is %s\n", p); 239 printf("Don't forget to do ``make cleandepend && make depend''\n"); 240 exit(0); 241} 242 243/* 244 * get_srcdir 245 * determine the root of the kernel source tree 246 * and save that in srcdir. 247 */ 248static void 249get_srcdir(void) 250{ 251 struct stat lg, phy; 252 char *p, *pwd; 253 int i; 254 255 if (realpath("../..", srcdir) == NULL) 256 err(EXIT_FAILURE, "Unable to find root of source tree"); 257 if ((pwd = getenv("PWD")) != NULL && *pwd == '/' && 258 (pwd = strdup(pwd)) != NULL) { 259 /* Remove the last two path components. */ 260 for (i = 0; i < 2; i++) { 261 if ((p = strrchr(pwd, '/')) == NULL) { 262 free(pwd); 263 return; 264 } 265 *p = '\0'; 266 } 267 if (stat(pwd, &lg) != -1 && stat(srcdir, &phy) != -1 && 268 lg.st_dev == phy.st_dev && lg.st_ino == phy.st_ino) 269 strlcpy(srcdir, pwd, MAXPATHLEN); 270 free(pwd); 271 } 272} 273 274static void 275usage(void) 276{ 277 278 fprintf(stderr, "usage: config [-CgmpV] [-d destdir] sysname\n"); 279 fprintf(stderr, " config -x kernel\n"); 280 exit(EX_USAGE); 281} 282 283/* 284 * get_word 285 * returns EOF on end of file 286 * NULL on end of line 287 * pointer to the word otherwise 288 */ 289char * 290get_word(FILE *fp) 291{ 292 static char line[80]; 293 int ch; 294 char *cp; 295 int escaped_nl = 0; 296 297begin: 298 while ((ch = getc(fp)) != EOF) 299 if (ch != ' ' && ch != '\t') 300 break; 301 if (ch == EOF) 302 return ((char *)EOF); 303 if (ch == '\\'){ 304 escaped_nl = 1; 305 goto begin; 306 } 307 if (ch == '\n') { 308 if (escaped_nl){ 309 escaped_nl = 0; 310 goto begin; 311 } 312 else 313 return (NULL); 314 } 315 cp = line; 316 *cp++ = ch; 317 /* Negation operator is a word by itself. */ 318 if (ch == '!') { 319 *cp = 0; 320 return (line); 321 } 322 while ((ch = getc(fp)) != EOF) { 323 if (isspace(ch)) 324 break; 325 *cp++ = ch; 326 } 327 *cp = 0; 328 if (ch == EOF) 329 return ((char *)EOF); 330 (void) ungetc(ch, fp); 331 return (line); 332} 333 334/* 335 * get_quoted_word 336 * like get_word but will accept something in double or single quotes 337 * (to allow embedded spaces). 338 */ 339char * 340get_quoted_word(FILE *fp) 341{ 342 static char line[256]; 343 int ch; 344 char *cp; 345 int escaped_nl = 0; 346 347begin: 348 while ((ch = getc(fp)) != EOF) 349 if (ch != ' ' && ch != '\t') 350 break; 351 if (ch == EOF) 352 return ((char *)EOF); 353 if (ch == '\\'){ 354 escaped_nl = 1; 355 goto begin; 356 } 357 if (ch == '\n') { 358 if (escaped_nl){ 359 escaped_nl = 0; 360 goto begin; 361 } 362 else 363 return (NULL); 364 } 365 cp = line; 366 if (ch == '"' || ch == '\'') { 367 int quote = ch; 368 369 escaped_nl = 0; 370 while ((ch = getc(fp)) != EOF) { 371 if (ch == quote && !escaped_nl) 372 break; 373 if (ch == '\n' && !escaped_nl) { 374 *cp = 0; 375 printf("config: missing quote reading `%s'\n", 376 line); 377 exit(2); 378 } 379 if (ch == '\\' && !escaped_nl) { 380 escaped_nl = 1; 381 continue; 382 } 383 if (ch != quote && escaped_nl) 384 *cp++ = '\\'; 385 *cp++ = ch; 386 escaped_nl = 0; 387 } 388 } else { 389 *cp++ = ch; 390 while ((ch = getc(fp)) != EOF) { 391 if (isspace(ch)) 392 break; 393 *cp++ = ch; 394 } 395 if (ch != EOF) 396 (void) ungetc(ch, fp); 397 } 398 *cp = 0; 399 if (ch == EOF) 400 return ((char *)EOF); 401 return (line); 402} 403 404/* 405 * prepend the path to a filename 406 */ 407char * 408path(const char *file) 409{ 410 char *cp = NULL; 411 412 if (file) 413 asprintf(&cp, "%s/%s", destdir, file); 414 else 415 cp = strdup(destdir); 416 return (cp); 417} 418 419/* 420 * Generate configuration file based on actual settings. With this mode, user 421 * will be able to obtain and build conifguration file with one command. 422 */ 423static void 424configfile_dynamic(struct sbuf *sb) 425{ 426 struct cputype *cput; 427 struct device *d; 428 struct opt *ol; 429 char *lend; 430 unsigned int i; 431 432 asprintf(&lend, "\\n\\\n"); 433 assert(lend != NULL); 434 sbuf_printf(sb, "options\t%s%s", OPT_AUTOGEN, lend); 435 sbuf_printf(sb, "ident\t%s%s", ident, lend); 436 sbuf_printf(sb, "machine\t%s%s", machinename, lend); 437 SLIST_FOREACH(cput, &cputype, cpu_next) 438 sbuf_printf(sb, "cpu\t%s%s", cput->cpu_name, lend); 439 SLIST_FOREACH(ol, &mkopt, op_next) 440 sbuf_printf(sb, "makeoptions\t%s=%s%s", ol->op_name, 441 ol->op_value, lend); 442 SLIST_FOREACH(ol, &opt, op_next) { 443 if (strncmp(ol->op_name, "DEV_", 4) == 0) 444 continue; 445 sbuf_printf(sb, "options\t%s", ol->op_name); 446 if (ol->op_value != NULL) { 447 sbuf_putc(sb, '='); 448 for (i = 0; i < strlen(ol->op_value); i++) { 449 if (ol->op_value[i] == '"') 450 sbuf_printf(sb, "\\%c", 451 ol->op_value[i]); 452 else 453 sbuf_printf(sb, "%c", 454 ol->op_value[i]); 455 } 456 sbuf_printf(sb, "%s", lend); 457 } else { 458 sbuf_printf(sb, "%s", lend); 459 } 460 } 461 /* 462 * Mark this file as containing everything we need. 463 */ 464 STAILQ_FOREACH(d, &dtab, d_next) 465 sbuf_printf(sb, "device\t%s%s", d->d_name, lend); 466 free(lend); 467} 468 469/* 470 * Generate file from the configuration files. 471 */ 472static void 473configfile_filebased(struct sbuf *sb) 474{ 475 FILE *cff; 476 struct cfgfile *cf; 477 int i; 478 479 /* 480 * Try to read all configuration files. Since those will be present as 481 * C string in the macro, we have to slash their ends then the line 482 * wraps. 483 */ 484 STAILQ_FOREACH(cf, &cfgfiles, cfg_next) { 485 cff = fopen(cf->cfg_path, "r"); 486 if (cff == NULL) { 487 warn("Couldn't open file %s", cf->cfg_path); 488 continue; 489 } 490 while ((i = getc(cff)) != EOF) { 491 if (i == '\n') 492 sbuf_printf(sb, "\\n\\\n"); 493 else if (i == '"' || i == '\'') 494 sbuf_printf(sb, "\\%c", i); 495 else 496 sbuf_putc(sb, i); 497 } 498 fclose(cff); 499 } 500} 501 502static void 503configfile(void) 504{ 505 FILE *fo; 506 struct sbuf *sb; 507 char *p; 508 509 /* Add main configuration file to the list of files to be included */ 510 cfgfile_add(PREFIX); 511 p = path("config.c.new"); 512 fo = fopen(p, "w"); 513 if (!fo) 514 err(2, "%s", p); 515 sb = sbuf_new(NULL, NULL, 2048, SBUF_AUTOEXTEND); 516 assert(sb != NULL); 517 sbuf_clear(sb); 518 if (filebased) { 519 /* Is needed, can be used for backward compatibility. */ 520 configfile_filebased(sb); 521 } else { 522 configfile_dynamic(sb); 523 } 524 sbuf_finish(sb); 525 /* 526 * We print first part of the template, replace our tag with 527 * configuration files content and later continue writing our 528 * template. 529 */ 530 p = strstr(kernconfstr, KERNCONFTAG); 531 if (p == NULL) 532 errx(EXIT_FAILURE, "Something went terribly wrong!"); 533 *p = '\0'; 534 fprintf(fo, "%s", kernconfstr); 535 fprintf(fo, "%s", sbuf_data(sb)); 536 p += strlen(KERNCONFTAG); 537 fprintf(fo, "%s", p); 538 sbuf_delete(sb); 539 fclose(fo); 540 moveifchanged(path("config.c.new"), path("config.c")); 541 cfgfile_removeall(); 542} 543 544/* 545 * moveifchanged -- 546 * compare two files; rename if changed. 547 */ 548void 549moveifchanged(const char *from_name, const char *to_name) 550{ 551 char *p, *q; 552 int changed; 553 size_t tsize; 554 struct stat from_sb, to_sb; 555 int from_fd, to_fd; 556 557 changed = 0; 558 559 if ((from_fd = open(from_name, O_RDONLY)) < 0) 560 err(EX_OSERR, "moveifchanged open(%s)", from_name); 561 562 if ((to_fd = open(to_name, O_RDONLY)) < 0) 563 changed++; 564 565 if (!changed && fstat(from_fd, &from_sb) < 0) 566 err(EX_OSERR, "moveifchanged fstat(%s)", from_name); 567 568 if (!changed && fstat(to_fd, &to_sb) < 0) 569 err(EX_OSERR, "moveifchanged fstat(%s)", to_name); 570 571 if (!changed && from_sb.st_size != to_sb.st_size) 572 changed++; 573 574 tsize = (size_t)from_sb.st_size; 575 576 if (!changed) { 577 p = mmap(NULL, tsize, PROT_READ, MAP_SHARED, from_fd, (off_t)0); 578 if (p == MAP_FAILED) 579 err(EX_OSERR, "mmap %s", from_name); 580 q = mmap(NULL, tsize, PROT_READ, MAP_SHARED, to_fd, (off_t)0); 581 if (q == MAP_FAILED) 582 err(EX_OSERR, "mmap %s", to_name); 583 584 changed = memcmp(p, q, tsize); 585 munmap(p, tsize); 586 munmap(q, tsize); 587 } 588 if (changed) { 589 if (rename(from_name, to_name) < 0) 590 err(EX_OSERR, "rename(%s, %s)", from_name, to_name); 591 } else { 592 if (unlink(from_name) < 0) 593 err(EX_OSERR, "unlink(%s)", from_name); 594 } 595} 596 597static void 598cleanheaders(char *p) 599{ 600 DIR *dirp; 601 struct dirent *dp; 602 struct file_list *fl; 603 struct hdr_list *hl; 604 size_t len; 605 606 remember("y.tab.h"); 607 remember("setdefs.h"); 608 STAILQ_FOREACH(fl, &ftab, f_next) 609 remember(fl->f_fn); 610 611 /* 612 * Scan the build directory and clean out stuff that looks like 613 * it might have been a leftover NFOO header, etc. 614 */ 615 if ((dirp = opendir(p)) == NULL) 616 err(EX_OSERR, "opendir %s", p); 617 while ((dp = readdir(dirp)) != NULL) { 618 len = strlen(dp->d_name); 619 /* Skip non-headers */ 620 if (len < 2 || dp->d_name[len - 2] != '.' || 621 dp->d_name[len - 1] != 'h') 622 continue; 623 /* Skip special stuff, eg: bus_if.h, but check opt_*.h */ 624 if (strchr(dp->d_name, '_') && 625 strncmp(dp->d_name, "opt_", 4) != 0) 626 continue; 627 /* Check if it is a target file */ 628 for (hl = htab; hl != NULL; hl = hl->h_next) { 629 if (eq(dp->d_name, hl->h_name)) { 630 break; 631 } 632 } 633 if (hl) 634 continue; 635 printf("Removing stale header: %s\n", dp->d_name); 636 if (unlink(path(dp->d_name)) == -1) 637 warn("unlink %s", dp->d_name); 638 } 639 (void)closedir(dirp); 640} 641 642void 643remember(const char *file) 644{ 645 char *s; 646 struct hdr_list *hl; 647 648 if ((s = strrchr(file, '/')) != NULL) 649 s = ns(s + 1); 650 else 651 s = ns(file); 652 653 if (strchr(s, '_') && strncmp(s, "opt_", 4) != 0) { 654 free(s); 655 return; 656 } 657 for (hl = htab; hl != NULL; hl = hl->h_next) { 658 if (eq(s, hl->h_name)) { 659 free(s); 660 return; 661 } 662 } 663 hl = calloc(1, sizeof(*hl)); 664 if (hl == NULL) 665 err(EXIT_FAILURE, "calloc"); 666 hl->h_name = s; 667 hl->h_next = htab; 668 htab = hl; 669} 670 671/* 672 * This one is quick hack. Will be probably moved to elf(3) interface. 673 * It takes kernel configuration file name, passes it as an argument to 674 * elfdump -a, which output is parsed by some UNIX tools... 675 */ 676static void 677kernconfdump(const char *file) 678{ 679 struct stat st; 680 FILE *fp, *pp; 681 int error, len, osz, r; 682 unsigned int i, off, size, t1, t2, align; 683 char *cmd, *o; 684 685 r = open(file, O_RDONLY); 686 if (r == -1) 687 err(EXIT_FAILURE, "Couldn't open file '%s'", file); 688 error = fstat(r, &st); 689 if (error == -1) 690 err(EXIT_FAILURE, "fstat() failed"); 691 if (S_ISDIR(st.st_mode)) 692 errx(EXIT_FAILURE, "'%s' is a directory", file); 693 fp = fdopen(r, "r"); 694 if (fp == NULL) 695 err(EXIT_FAILURE, "fdopen() failed"); 696 osz = 1024; 697 o = calloc(1, osz); 698 if (o == NULL) 699 err(EXIT_FAILURE, "Couldn't allocate memory"); 700 /* ELF note section header. */ 701 asprintf(&cmd, "/usr/bin/elfdump -c %s | grep -A 8 kern_conf" 702 "| tail -5 | cut -d ' ' -f 2 | paste - - - - -", file); 703 if (cmd == NULL) 704 errx(EXIT_FAILURE, "asprintf() failed"); 705 pp = popen(cmd, "r"); 706 if (pp == NULL) 707 errx(EXIT_FAILURE, "popen() failed"); 708 free(cmd); 709 len = fread(o, osz, 1, pp); 710 pclose(pp); 711 r = sscanf(o, "%d%d%d%d%d", &off, &size, &t1, &t2, &align); 712 free(o); 713 if (r != 5) 714 errx(EXIT_FAILURE, "File %s doesn't contain configuration " 715 "file. Either unsupported, or not compiled with " 716 "INCLUDE_CONFIG_FILE", file); 717 r = fseek(fp, off, SEEK_CUR); 718 if (r != 0) 719 err(EXIT_FAILURE, "fseek() failed"); 720 for (i = 0; i < size; i++) { 721 r = fgetc(fp); 722 if (r == EOF) 723 break; 724 if (r == '\0') { 725 assert(i == size - 1 && 726 ("\\0 found in the middle of a file")); 727 break; 728 } 729 fputc(r, stdout); 730 } 731 fclose(fp); 732} 733 734static void 735badversion(int versreq) 736{ 737 fprintf(stderr, "ERROR: version of config(8) does not match kernel!\n"); 738 fprintf(stderr, "config version = %d, ", CONFIGVERS); 739 fprintf(stderr, "version required = %d\n\n", versreq); 740 fprintf(stderr, "Make sure that /usr/src/usr.sbin/config is in sync\n"); 741 fprintf(stderr, "with your /usr/src/sys and install a new config binary\n"); 742 fprintf(stderr, "before trying this again.\n\n"); 743 fprintf(stderr, "If running the new config fails check your config\n"); 744 fprintf(stderr, "file against the GENERIC or LINT config files for\n"); 745 fprintf(stderr, "changes in config syntax, or option/device naming\n"); 746 fprintf(stderr, "conventions\n\n"); 747 exit(1); 748} 749 750static void 751checkversion(void) 752{ 753 FILE *ifp; 754 char line[BUFSIZ]; 755 int versreq; 756 757 ifp = open_makefile_template(); 758 while (fgets(line, BUFSIZ, ifp) != 0) { 759 if (*line != '%') 760 continue; 761 if (strncmp(line, "%VERSREQ=", 9) != 0) 762 continue; 763 versreq = atoi(line + 9); 764 if (MAJOR_VERS(versreq) == MAJOR_VERS(CONFIGVERS) && 765 versreq <= CONFIGVERS) 766 continue; 767 badversion(versreq); 768 } 769 fclose(ifp); 770} 771