main.c revision 264325
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 264325 2014-04-10 19:51:33Z asomers $"; 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 while ((ch = getc(fp)) != EOF) { 318 if (isspace(ch)) 319 break; 320 *cp++ = ch; 321 } 322 *cp = 0; 323 if (ch == EOF) 324 return ((char *)EOF); 325 (void) ungetc(ch, fp); 326 return (line); 327} 328 329/* 330 * get_quoted_word 331 * like get_word but will accept something in double or single quotes 332 * (to allow embedded spaces). 333 */ 334char * 335get_quoted_word(FILE *fp) 336{ 337 static char line[256]; 338 int ch; 339 char *cp; 340 int escaped_nl = 0; 341 342begin: 343 while ((ch = getc(fp)) != EOF) 344 if (ch != ' ' && ch != '\t') 345 break; 346 if (ch == EOF) 347 return ((char *)EOF); 348 if (ch == '\\'){ 349 escaped_nl = 1; 350 goto begin; 351 } 352 if (ch == '\n') { 353 if (escaped_nl){ 354 escaped_nl = 0; 355 goto begin; 356 } 357 else 358 return (NULL); 359 } 360 cp = line; 361 if (ch == '"' || ch == '\'') { 362 int quote = ch; 363 364 escaped_nl = 0; 365 while ((ch = getc(fp)) != EOF) { 366 if (ch == quote && !escaped_nl) 367 break; 368 if (ch == '\n' && !escaped_nl) { 369 *cp = 0; 370 printf("config: missing quote reading `%s'\n", 371 line); 372 exit(2); 373 } 374 if (ch == '\\' && !escaped_nl) { 375 escaped_nl = 1; 376 continue; 377 } 378 if (ch != quote && escaped_nl) 379 *cp++ = '\\'; 380 *cp++ = ch; 381 escaped_nl = 0; 382 } 383 } else { 384 *cp++ = ch; 385 while ((ch = getc(fp)) != EOF) { 386 if (isspace(ch)) 387 break; 388 *cp++ = ch; 389 } 390 if (ch != EOF) 391 (void) ungetc(ch, fp); 392 } 393 *cp = 0; 394 if (ch == EOF) 395 return ((char *)EOF); 396 return (line); 397} 398 399/* 400 * prepend the path to a filename 401 */ 402char * 403path(const char *file) 404{ 405 char *cp = NULL; 406 407 if (file) 408 asprintf(&cp, "%s/%s", destdir, file); 409 else 410 cp = strdup(destdir); 411 return (cp); 412} 413 414/* 415 * Generate configuration file based on actual settings. With this mode, user 416 * will be able to obtain and build conifguration file with one command. 417 */ 418static void 419configfile_dynamic(struct sbuf *sb) 420{ 421 struct cputype *cput; 422 struct device *d; 423 struct opt *ol; 424 char *lend; 425 unsigned int i; 426 427 asprintf(&lend, "\\n\\\n"); 428 assert(lend != NULL); 429 sbuf_printf(sb, "options\t%s%s", OPT_AUTOGEN, lend); 430 sbuf_printf(sb, "ident\t%s%s", ident, lend); 431 sbuf_printf(sb, "machine\t%s%s", machinename, lend); 432 SLIST_FOREACH(cput, &cputype, cpu_next) 433 sbuf_printf(sb, "cpu\t%s%s", cput->cpu_name, lend); 434 SLIST_FOREACH(ol, &mkopt, op_next) 435 sbuf_printf(sb, "makeoptions\t%s=%s%s", ol->op_name, 436 ol->op_value, lend); 437 SLIST_FOREACH(ol, &opt, op_next) { 438 if (strncmp(ol->op_name, "DEV_", 4) == 0) 439 continue; 440 sbuf_printf(sb, "options\t%s", ol->op_name); 441 if (ol->op_value != NULL) { 442 sbuf_putc(sb, '='); 443 for (i = 0; i < strlen(ol->op_value); i++) { 444 if (ol->op_value[i] == '"') 445 sbuf_printf(sb, "\\%c", 446 ol->op_value[i]); 447 else 448 sbuf_printf(sb, "%c", 449 ol->op_value[i]); 450 } 451 sbuf_printf(sb, "%s", lend); 452 } else { 453 sbuf_printf(sb, "%s", lend); 454 } 455 } 456 /* 457 * Mark this file as containing everything we need. 458 */ 459 STAILQ_FOREACH(d, &dtab, d_next) 460 sbuf_printf(sb, "device\t%s%s", d->d_name, lend); 461 free(lend); 462} 463 464/* 465 * Generate file from the configuration files. 466 */ 467static void 468configfile_filebased(struct sbuf *sb) 469{ 470 FILE *cff; 471 struct cfgfile *cf; 472 int i; 473 474 /* 475 * Try to read all configuration files. Since those will be present as 476 * C string in the macro, we have to slash their ends then the line 477 * wraps. 478 */ 479 STAILQ_FOREACH(cf, &cfgfiles, cfg_next) { 480 cff = fopen(cf->cfg_path, "r"); 481 if (cff == NULL) { 482 warn("Couldn't open file %s", cf->cfg_path); 483 continue; 484 } 485 while ((i = getc(cff)) != EOF) { 486 if (i == '\n') 487 sbuf_printf(sb, "\\n\\\n"); 488 else if (i == '"' || i == '\'') 489 sbuf_printf(sb, "\\%c", i); 490 else 491 sbuf_putc(sb, i); 492 } 493 fclose(cff); 494 } 495} 496 497static void 498configfile(void) 499{ 500 FILE *fo; 501 struct sbuf *sb; 502 char *p; 503 504 /* Add main configuration file to the list of files to be included */ 505 cfgfile_add(PREFIX); 506 p = path("config.c.new"); 507 fo = fopen(p, "w"); 508 if (!fo) 509 err(2, "%s", p); 510 sb = sbuf_new(NULL, NULL, 2048, SBUF_AUTOEXTEND); 511 assert(sb != NULL); 512 sbuf_clear(sb); 513 if (filebased) { 514 /* Is needed, can be used for backward compatibility. */ 515 configfile_filebased(sb); 516 } else { 517 configfile_dynamic(sb); 518 } 519 sbuf_finish(sb); 520 /* 521 * We print first part of the template, replace our tag with 522 * configuration files content and later continue writing our 523 * template. 524 */ 525 p = strstr(kernconfstr, KERNCONFTAG); 526 if (p == NULL) 527 errx(EXIT_FAILURE, "Something went terribly wrong!"); 528 *p = '\0'; 529 fprintf(fo, "%s", kernconfstr); 530 fprintf(fo, "%s", sbuf_data(sb)); 531 p += strlen(KERNCONFTAG); 532 fprintf(fo, "%s", p); 533 sbuf_delete(sb); 534 fclose(fo); 535 moveifchanged(path("config.c.new"), path("config.c")); 536 cfgfile_removeall(); 537} 538 539/* 540 * moveifchanged -- 541 * compare two files; rename if changed. 542 */ 543void 544moveifchanged(const char *from_name, const char *to_name) 545{ 546 char *p, *q; 547 int changed; 548 size_t tsize; 549 struct stat from_sb, to_sb; 550 int from_fd, to_fd; 551 552 changed = 0; 553 554 if ((from_fd = open(from_name, O_RDONLY)) < 0) 555 err(EX_OSERR, "moveifchanged open(%s)", from_name); 556 557 if ((to_fd = open(to_name, O_RDONLY)) < 0) 558 changed++; 559 560 if (!changed && fstat(from_fd, &from_sb) < 0) 561 err(EX_OSERR, "moveifchanged fstat(%s)", from_name); 562 563 if (!changed && fstat(to_fd, &to_sb) < 0) 564 err(EX_OSERR, "moveifchanged fstat(%s)", to_name); 565 566 if (!changed && from_sb.st_size != to_sb.st_size) 567 changed++; 568 569 tsize = (size_t)from_sb.st_size; 570 571 if (!changed) { 572 p = mmap(NULL, tsize, PROT_READ, MAP_SHARED, from_fd, (off_t)0); 573 if (p == MAP_FAILED) 574 err(EX_OSERR, "mmap %s", from_name); 575 q = mmap(NULL, tsize, PROT_READ, MAP_SHARED, to_fd, (off_t)0); 576 if (q == MAP_FAILED) 577 err(EX_OSERR, "mmap %s", to_name); 578 579 changed = memcmp(p, q, tsize); 580 munmap(p, tsize); 581 munmap(q, tsize); 582 } 583 if (changed) { 584 if (rename(from_name, to_name) < 0) 585 err(EX_OSERR, "rename(%s, %s)", from_name, to_name); 586 } else { 587 if (unlink(from_name) < 0) 588 err(EX_OSERR, "unlink(%s)", from_name); 589 } 590} 591 592static void 593cleanheaders(char *p) 594{ 595 DIR *dirp; 596 struct dirent *dp; 597 struct file_list *fl; 598 struct hdr_list *hl; 599 size_t len; 600 601 remember("y.tab.h"); 602 remember("setdefs.h"); 603 STAILQ_FOREACH(fl, &ftab, f_next) 604 remember(fl->f_fn); 605 606 /* 607 * Scan the build directory and clean out stuff that looks like 608 * it might have been a leftover NFOO header, etc. 609 */ 610 if ((dirp = opendir(p)) == NULL) 611 err(EX_OSERR, "opendir %s", p); 612 while ((dp = readdir(dirp)) != NULL) { 613 len = strlen(dp->d_name); 614 /* Skip non-headers */ 615 if (len < 2 || dp->d_name[len - 2] != '.' || 616 dp->d_name[len - 1] != 'h') 617 continue; 618 /* Skip special stuff, eg: bus_if.h, but check opt_*.h */ 619 if (strchr(dp->d_name, '_') && 620 strncmp(dp->d_name, "opt_", 4) != 0) 621 continue; 622 /* Check if it is a target file */ 623 for (hl = htab; hl != NULL; hl = hl->h_next) { 624 if (eq(dp->d_name, hl->h_name)) { 625 break; 626 } 627 } 628 if (hl) 629 continue; 630 printf("Removing stale header: %s\n", dp->d_name); 631 if (unlink(path(dp->d_name)) == -1) 632 warn("unlink %s", dp->d_name); 633 } 634 (void)closedir(dirp); 635} 636 637void 638remember(const char *file) 639{ 640 char *s; 641 struct hdr_list *hl; 642 643 if ((s = strrchr(file, '/')) != NULL) 644 s = ns(s + 1); 645 else 646 s = ns(file); 647 648 if (strchr(s, '_') && strncmp(s, "opt_", 4) != 0) { 649 free(s); 650 return; 651 } 652 for (hl = htab; hl != NULL; hl = hl->h_next) { 653 if (eq(s, hl->h_name)) { 654 free(s); 655 return; 656 } 657 } 658 hl = calloc(1, sizeof(*hl)); 659 if (hl == NULL) 660 err(EXIT_FAILURE, "calloc"); 661 hl->h_name = s; 662 hl->h_next = htab; 663 htab = hl; 664} 665 666/* 667 * This one is quick hack. Will be probably moved to elf(3) interface. 668 * It takes kernel configuration file name, passes it as an argument to 669 * elfdump -a, which output is parsed by some UNIX tools... 670 */ 671static void 672kernconfdump(const char *file) 673{ 674 struct stat st; 675 FILE *fp, *pp; 676 int error, len, osz, r; 677 unsigned int i, off, size, t1, t2, align; 678 char *cmd, *o; 679 680 r = open(file, O_RDONLY); 681 if (r == -1) 682 err(EXIT_FAILURE, "Couldn't open file '%s'", file); 683 error = fstat(r, &st); 684 if (error == -1) 685 err(EXIT_FAILURE, "fstat() failed"); 686 if (S_ISDIR(st.st_mode)) 687 errx(EXIT_FAILURE, "'%s' is a directory", file); 688 fp = fdopen(r, "r"); 689 if (fp == NULL) 690 err(EXIT_FAILURE, "fdopen() failed"); 691 osz = 1024; 692 o = calloc(1, osz); 693 if (o == NULL) 694 err(EXIT_FAILURE, "Couldn't allocate memory"); 695 /* ELF note section header. */ 696 asprintf(&cmd, "/usr/bin/elfdump -c %s | grep -A 8 kern_conf" 697 "| tail -5 | cut -d ' ' -f 2 | paste - - - - -", file); 698 if (cmd == NULL) 699 errx(EXIT_FAILURE, "asprintf() failed"); 700 pp = popen(cmd, "r"); 701 if (pp == NULL) 702 errx(EXIT_FAILURE, "popen() failed"); 703 free(cmd); 704 len = fread(o, osz, 1, pp); 705 pclose(pp); 706 r = sscanf(o, "%d%d%d%d%d", &off, &size, &t1, &t2, &align); 707 free(o); 708 if (r != 5) 709 errx(EXIT_FAILURE, "File %s doesn't contain configuration " 710 "file. Either unsupported, or not compiled with " 711 "INCLUDE_CONFIG_FILE", file); 712 r = fseek(fp, off, SEEK_CUR); 713 if (r != 0) 714 err(EXIT_FAILURE, "fseek() failed"); 715 for (i = 0; i < size; i++) { 716 r = fgetc(fp); 717 if (r == EOF) 718 break; 719 if (r == '\0') { 720 assert(i == size - 1 && 721 ("\\0 found in the middle of a file")); 722 break; 723 } 724 fputc(r, stdout); 725 } 726 fclose(fp); 727} 728 729static void 730badversion(int versreq) 731{ 732 fprintf(stderr, "ERROR: version of config(8) does not match kernel!\n"); 733 fprintf(stderr, "config version = %d, ", CONFIGVERS); 734 fprintf(stderr, "version required = %d\n\n", versreq); 735 fprintf(stderr, "Make sure that /usr/src/usr.sbin/config is in sync\n"); 736 fprintf(stderr, "with your /usr/src/sys and install a new config binary\n"); 737 fprintf(stderr, "before trying this again.\n\n"); 738 fprintf(stderr, "If running the new config fails check your config\n"); 739 fprintf(stderr, "file against the GENERIC or LINT config files for\n"); 740 fprintf(stderr, "changes in config syntax, or option/device naming\n"); 741 fprintf(stderr, "conventions\n\n"); 742 exit(1); 743} 744 745static void 746checkversion(void) 747{ 748 FILE *ifp; 749 char line[BUFSIZ]; 750 int versreq; 751 752 ifp = open_makefile_template(); 753 while (fgets(line, BUFSIZ, ifp) != 0) { 754 if (*line != '%') 755 continue; 756 if (strncmp(line, "%VERSREQ=", 9) != 0) 757 continue; 758 versreq = atoi(line + 9); 759 if (MAJOR_VERS(versreq) == MAJOR_VERS(CONFIGVERS) && 760 versreq <= CONFIGVERS) 761 continue; 762 badversion(versreq); 763 } 764 fclose(ifp); 765} 766