1 /* 2 * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. 3 * Copyright (c) 1988, 1989 by Adam de Boor 4 * Copyright (c) 1989 by Berkeley Softworks 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Adam de Boor. 9 * 10 * Redistribution and use in source and binary forms are permitted 11 * provided that the above copyright notice and this paragraph are 12 * duplicated in all such forms and that any documentation, 13 * advertising materials, and other materials related to such 14 * distribution and use acknowledge that the software was developed 15 * by the University of California, Berkeley. The name of the 16 * University may not be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 21 */ 22 23 #ifndef lint 24 char copyright[] = 25 "@(#) Copyright (c) 1989 The Regents of the University of California.\n\ 26 All rights reserved.\n"; 27 #endif /* not lint */ 28 29 #ifndef lint 30 static char sccsid[] = "@(#)main.c 5.20 (Berkeley) 05/21/90"; 31 #endif /* not lint */ 32 33 /*- 34 * main.c -- 35 * The main file for this entire program. Exit routines etc 36 * reside here. 37 * 38 * Utility functions defined in this file: 39 * Main_ParseArgLine Takes a line of arguments, breaks them and 40 * treats them as if they were given when first 41 * invoked. Used by the parse module to implement 42 * the .MFLAGS target. 43 * 44 * Error Print a tagged error message. The global 45 * MAKE variable must have been defined. This 46 * takes a format string and two optional 47 * arguments for it. 48 * 49 * Fatal Print an error message and exit. Also takes 50 * a format string and two arguments. 51 * 52 * Punt Aborts all jobs and exits with a message. Also 53 * takes a format string and two arguments. 54 * 55 * Finish Finish things up by printing the number of 56 * errors which occured, as passed to it, and 57 * exiting. 58 */ 59 60 #include <sys/param.h> 61 #include <sys/signal.h> 62 #include <sys/stat.h> 63 #include <fcntl.h> 64 #include <stdio.h> 65 #include <varargs.h> 66 #include "make.h" 67 #include "pathnames.h" 68 69 #ifndef DEFMAXLOCAL 70 #define DEFMAXLOCAL DEFMAXJOBS 71 #endif DEFMAXLOCAL 72 73 #define MAKEFLAGS ".MAKEFLAGS" 74 75 Lst create; /* Targets to be made */ 76 time_t now; /* Time at start of make */ 77 GNode *DEFAULT; /* .DEFAULT node */ 78 Boolean allPrecious; /* .PRECIOUS given on line by itself */ 79 80 static Boolean noBuiltins; /* -r flag */ 81 static Lst makefiles; /* ordered list of makefiles to read */ 82 int maxJobs; /* -J argument */ 83 static int maxLocal; /* -L argument */ 84 Boolean debug; /* -d flag */ 85 Boolean noExecute; /* -n flag */ 86 Boolean keepgoing; /* -k flag */ 87 Boolean queryFlag; /* -q flag */ 88 Boolean touchFlag; /* -t flag */ 89 Boolean usePipes; /* !-P flag */ 90 Boolean ignoreErrors; /* -i flag */ 91 Boolean beSilent; /* -s flag */ 92 Boolean oldVars; /* variable substitution style */ 93 Boolean checkEnvFirst; /* -e flag */ 94 static Boolean jobsRunning; /* TRUE if the jobs might be running */ 95 96 static Boolean ReadMakefile(); 97 98 static char *curdir; /* if chdir'd for an architecture */ 99 100 /*- 101 * MainParseArgs -- 102 * Parse a given argument vector. Called from main() and from 103 * Main_ParseArgLine() when the .MAKEFLAGS target is used. 104 * 105 * XXX: Deal with command line overriding .MAKEFLAGS in makefile 106 * 107 * Results: 108 * None 109 * 110 * Side Effects: 111 * Various global and local flags will be set depending on the flags 112 * given 113 */ 114 static void 115 MainParseArgs(argc, argv) 116 int argc; 117 char **argv; 118 { 119 extern int optind; 120 extern char *optarg; 121 register int i; 122 register char *cp; 123 char c; 124 125 rearg: while((c = getopt(argc, argv, "D:I:d:ef:ij:knqrst")) != -1) { 126 switch(c) { 127 case 'D': 128 Var_Set(optarg, "1", VAR_GLOBAL); 129 Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); 130 Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); 131 break; 132 case 'I': 133 Parse_AddIncludeDir(optarg); 134 Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); 135 Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); 136 break; 137 #ifdef notdef 138 case 'L': 139 maxLocal = atoi(optarg); 140 Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL); 141 Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); 142 break; 143 case 'P': 144 usePipes = FALSE; 145 Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL); 146 break; 147 case 'S': 148 keepgoing = FALSE; 149 Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); 150 break; 151 #endif 152 case 'd': { 153 char *modules = optarg; 154 155 for (; *modules; ++modules) 156 switch (*modules) { 157 case 'A': 158 debug = ~0; 159 break; 160 case 'a': 161 debug |= DEBUG_ARCH; 162 break; 163 case 'c': 164 debug |= DEBUG_COND; 165 break; 166 case 'd': 167 debug |= DEBUG_DIR; 168 break; 169 case 'g': 170 if (modules[1] == '1') { 171 debug |= DEBUG_GRAPH1; 172 ++modules; 173 } 174 else if (modules[1] == '2') { 175 debug |= DEBUG_GRAPH2; 176 ++modules; 177 } 178 break; 179 case 'j': 180 debug |= DEBUG_JOB; 181 break; 182 case 'm': 183 debug |= DEBUG_MAKE; 184 break; 185 case 's': 186 debug |= DEBUG_SUFF; 187 break; 188 case 't': 189 debug |= DEBUG_TARG; 190 break; 191 case 'v': 192 debug |= DEBUG_VAR; 193 break; 194 } 195 Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); 196 Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); 197 break; 198 } 199 case 'e': 200 checkEnvFirst = TRUE; 201 Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); 202 break; 203 case 'f': 204 (void)Lst_AtEnd(makefiles, (ClientData)optarg); 205 break; 206 case 'i': 207 ignoreErrors = TRUE; 208 Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); 209 break; 210 case 'j': 211 maxJobs = atoi(optarg); 212 Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); 213 Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); 214 break; 215 case 'k': 216 keepgoing = TRUE; 217 Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); 218 break; 219 case 'n': 220 noExecute = TRUE; 221 Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); 222 break; 223 case 'q': 224 queryFlag = TRUE; 225 /* Kind of nonsensical, wot? */ 226 Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); 227 break; 228 case 'r': 229 noBuiltins = TRUE; 230 Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); 231 break; 232 case 's': 233 beSilent = TRUE; 234 Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); 235 break; 236 case 't': 237 touchFlag = TRUE; 238 Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); 239 break; 240 default: 241 case '?': 242 usage(); 243 } 244 } 245 246 oldVars = TRUE; 247 248 /* 249 * See if the rest of the arguments are variable assignments and 250 * perform them if so. Else take them to be targets and stuff them 251 * on the end of the "create" list. 252 */ 253 for (argv += optind, argc -= optind; *argv; ++argv, --argc) 254 if (Parse_IsVar(*argv)) 255 Parse_DoVar(*argv, VAR_CMD); 256 else { 257 if (!**argv) 258 Punt("illegal (null) argument."); 259 if (**argv == '-') { 260 optind = 0; 261 goto rearg; 262 } 263 (void)Lst_AtEnd(create, (ClientData)*argv); 264 } 265 } 266 267 /*- 268 * Main_ParseArgLine -- 269 * Used by the parse module when a .MFLAGS or .MAKEFLAGS target 270 * is encountered and by main() when reading the .MAKEFLAGS envariable. 271 * Takes a line of arguments and breaks it into its 272 * component words and passes those words and the number of them to the 273 * MainParseArgs function. 274 * The line should have all its leading whitespace removed. 275 * 276 * Results: 277 * None 278 * 279 * Side Effects: 280 * Only those that come from the various arguments. 281 */ 282 void 283 Main_ParseArgLine(line) 284 char *line; /* Line to fracture */ 285 { 286 char **argv; /* Manufactured argument vector */ 287 int argc; /* Number of arguments in argv */ 288 289 if (line == NULL) 290 return; 291 for (; *line == ' '; ++line); 292 if (!*line) 293 return; 294 295 argv = brk_string(line, &argc); 296 MainParseArgs(argc, argv); 297 } 298 299 /*- 300 * main -- 301 * The main function, for obvious reasons. Initializes variables 302 * and a few modules, then parses the arguments give it in the 303 * environment and on the command line. Reads the system makefile 304 * followed by either Makefile, makefile or the file given by the 305 * -f argument. Sets the .MAKEFLAGS PMake variable based on all the 306 * flags it has received by then uses either the Make or the Compat 307 * module to create the initial list of targets. 308 * 309 * Results: 310 * If -q was given, exits -1 if anything was out-of-date. Else it exits 311 * 0. 312 * 313 * Side Effects: 314 * The program exits when done. Targets are created. etc. etc. etc. 315 */ 316 main(argc, argv) 317 int argc; 318 char **argv; 319 { 320 Lst targs; /* target nodes to create -- passed to Make_Init */ 321 Boolean outOfDate; /* FALSE if all targets up to date */ 322 struct stat sb; 323 char *p, *path, *getenv(); 324 325 /* 326 * if the MAKEOBJDIR (or by default, the _PATH_OBJDIR) directory 327 * exists, change into it and build there. Once things are 328 * initted, have to add the original directory to the search path, 329 * and modify the paths for the Makefiles apropriately. The 330 * current directory is also placed as a variable for make scripts. 331 */ 332 if (!(path = getenv("MAKEOBJDIR"))) 333 path = _PATH_OBJDIR; 334 if (!lstat(path, &sb)) { 335 if (S_ISDIR(sb.st_mode)) 336 curdir = ".."; 337 else { 338 curdir = emalloc((u_int)MAXPATHLEN + 1); 339 if (!getwd(curdir)) { 340 (void)fprintf(stderr, "make: %s.\n", curdir); 341 exit(2); 342 } 343 } 344 if (chdir(path)) { 345 extern int errno; 346 347 (void)fprintf(stderr, "make: %s: %s.\n", 348 path, strerror(errno)); 349 exit(2); 350 } 351 } 352 353 create = Lst_Init(FALSE); 354 makefiles = Lst_Init(FALSE); 355 beSilent = FALSE; /* Print commands as executed */ 356 ignoreErrors = FALSE; /* Pay attention to non-zero returns */ 357 noExecute = FALSE; /* Execute all commands */ 358 keepgoing = FALSE; /* Stop on error */ 359 allPrecious = FALSE; /* Remove targets when interrupted */ 360 queryFlag = FALSE; /* This is not just a check-run */ 361 noBuiltins = FALSE; /* Read the built-in rules */ 362 touchFlag = FALSE; /* Actually update targets */ 363 usePipes = TRUE; /* Catch child output in pipes */ 364 debug = 0; /* No debug verbosity, please. */ 365 jobsRunning = FALSE; 366 367 maxJobs = DEFMAXJOBS; /* Set default max concurrency */ 368 maxLocal = DEFMAXLOCAL; /* Set default local max concurrency */ 369 370 /* 371 * Initialize the parsing, directory and variable modules to prepare 372 * for the reading of inclusion paths and variable settings on the 373 * command line 374 */ 375 Dir_Init(); /* Initialize directory structures so -I flags 376 * can be processed correctly */ 377 Parse_Init(); /* Need to initialize the paths of #include 378 * directories */ 379 Var_Init(); /* As well as the lists of variables for 380 * parsing arguments */ 381 382 if (curdir) { 383 Dir_AddDir(dirSearchPath, curdir); 384 Var_Set(".CURDIR", curdir, VAR_GLOBAL); 385 } else 386 Var_Set(".CURDIR", ".", VAR_GLOBAL); 387 388 /* 389 * Initialize various variables. 390 * MAKE also gets this name, for compatibility 391 * .MAKEFLAGS gets set to the empty string just in case. 392 * MFLAGS also gets initialized empty, for compatibility. 393 */ 394 Var_Set("MAKE", argv[0], VAR_GLOBAL); 395 Var_Set(MAKEFLAGS, "", VAR_GLOBAL); 396 Var_Set("MFLAGS", "", VAR_GLOBAL); 397 Var_Set("MACHINE", MACHINE, VAR_GLOBAL); 398 399 /* 400 * First snag any flags out of the MAKE environment variable. 401 * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's 402 * in a different format). 403 */ 404 #ifdef POSIX 405 Main_ParseArgLine(getenv("MAKEFLAGS")); 406 #else 407 Main_ParseArgLine(getenv("MAKE")); 408 #endif 409 410 MainParseArgs(argc, argv); 411 412 /* 413 * Initialize archive, target and suffix modules in preparation for 414 * parsing the makefile(s) 415 */ 416 Arch_Init(); 417 Targ_Init(); 418 Suff_Init(); 419 420 DEFAULT = NILGNODE; 421 (void)time(&now); 422 423 /* 424 * Set up the .TARGETS variable to contain the list of targets to be 425 * created. If none specified, make the variable empty -- the parser 426 * will fill the thing in with the default or .MAIN target. 427 */ 428 if (!Lst_IsEmpty(create)) { 429 LstNode ln; 430 431 for (ln = Lst_First(create); ln != NILLNODE; 432 ln = Lst_Succ(ln)) { 433 char *name = (char *)Lst_Datum(ln); 434 435 Var_Append(".TARGETS", name, VAR_GLOBAL); 436 } 437 } else 438 Var_Set(".TARGETS", "", VAR_GLOBAL); 439 440 /* 441 * Read in the built-in rules first, followed by the specified makefile, 442 * if it was (makefile != (char *) NULL), or the default Makefile and 443 * makefile, in that order, if it wasn't. 444 */ 445 if (!noBuiltins && !ReadMakefile(_PATH_DEFSYSMK)) 446 Fatal("make: no system rules (%s).", _PATH_DEFSYSMK); 447 448 if (!Lst_IsEmpty(makefiles)) { 449 LstNode ln; 450 451 ln = Lst_Find(makefiles, (ClientData)NULL, ReadMakefile); 452 if (ln != NILLNODE) 453 Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); 454 } else if (!ReadMakefile("makefile")) 455 (void)ReadMakefile("Makefile"); 456 457 (void)ReadMakefile(".depend"); 458 459 Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL), VAR_GLOBAL); 460 461 /* Install all the flags into the MAKE envariable. */ 462 if ((p = Var_Value(MAKEFLAGS, VAR_GLOBAL)) && *p) 463 #ifdef POSIX 464 setenv("MAKEFLAGS", p, 1); 465 #else 466 setenv("MAKE", p, 1); 467 #endif 468 469 /* 470 * For compatibility, look at the directories in the VPATH variable 471 * and add them to the search path, if the variable is defined. The 472 * variable's value is in the same format as the PATH envariable, i.e. 473 * <directory>:<directory>:<directory>... 474 */ 475 if (Var_Exists("VPATH", VAR_CMD)) { 476 char *vpath, *path, *cp, savec; 477 /* 478 * GCC stores string constants in read-only memory, but 479 * Var_Subst will want to write this thing, so store it 480 * in an array 481 */ 482 static char VPATH[] = "${VPATH}"; 483 484 vpath = Var_Subst(VPATH, VAR_CMD, FALSE); 485 path = vpath; 486 do { 487 /* skip to end of directory */ 488 for (cp = path; *cp != ':' && *cp != '\0'; cp++); 489 /* Save terminator character so know when to stop */ 490 savec = *cp; 491 *cp = '\0'; 492 /* Add directory to search path */ 493 Dir_AddDir(dirSearchPath, path); 494 *cp = savec; 495 path = cp + 1; 496 } while (savec == ':'); 497 (void)free((Address)vpath); 498 } 499 500 /* 501 * Now that all search paths have been read for suffixes et al, it's 502 * time to add the default search path to their lists... 503 */ 504 Suff_DoPaths(); 505 506 /* print the initial graph, if the user requested it */ 507 if (DEBUG(GRAPH1)) 508 Targ_PrintGraph(1); 509 510 /* 511 * Have now read the entire graph and need to make a list of targets 512 * to create. If none was given on the command line, we consult the 513 * parsing module to find the main target(s) to create. 514 */ 515 if (Lst_IsEmpty(create)) 516 targs = Parse_MainName(); 517 else 518 targs = Targ_FindList(create, TARG_CREATE); 519 520 /* 521 * this was original amMake -- want to allow parallelism, so put this 522 * back in, eventually. 523 */ 524 if (0) { 525 /* 526 * Initialize job module before traversing the graph, now that 527 * any .BEGIN and .END targets have been read. This is done 528 * only if the -q flag wasn't given (to prevent the .BEGIN from 529 * being executed should it exist). 530 */ 531 if (!queryFlag) { 532 if (maxLocal == -1) 533 maxLocal = maxJobs; 534 Job_Init(maxJobs, maxLocal); 535 jobsRunning = TRUE; 536 } 537 538 /* Traverse the graph, checking on all the targets */ 539 outOfDate = Make_Run(targs); 540 } else 541 /* 542 * Compat_Init will take care of creating all the targets as 543 * well as initializing the module. 544 */ 545 Compat_Run(targs); 546 547 /* print the graph now it's been processed if the user requested it */ 548 if (DEBUG(GRAPH2)) 549 Targ_PrintGraph(2); 550 551 if (queryFlag && outOfDate) 552 exit(1); 553 else 554 exit(0); 555 } 556 557 /*- 558 * ReadMakefile -- 559 * Open and parse the given makefile. 560 * 561 * Results: 562 * TRUE if ok. FALSE if couldn't open file. 563 * 564 * Side Effects: 565 * lots 566 */ 567 static Boolean 568 ReadMakefile(fname) 569 char *fname; /* makefile to read */ 570 { 571 extern Lst parseIncPath, sysIncPath; 572 FILE *stream; 573 char *name, path[MAXPATHLEN + 1]; 574 575 if (!strcmp(fname, "-")) { 576 Parse_File("(stdin)", stdin); 577 Var_Set("MAKEFILE", "", VAR_GLOBAL); 578 } else { 579 if (stream = fopen(fname, "r")) 580 goto found; 581 /* if we've chdir'd, rebuild the path name */ 582 if (curdir && *fname != '/') { 583 (void)sprintf(path, "%s/%s", curdir, fname); 584 if (stream = fopen(path, "r")) { 585 fname = path; 586 goto found; 587 } 588 } 589 /* look in -I and system include directories. */ 590 name = Dir_FindFile(fname, parseIncPath); 591 if (!name) 592 name = Dir_FindFile(fname, sysIncPath); 593 if (!name || !(stream = fopen(name, "r"))) 594 return(FALSE); 595 fname = name; 596 /* 597 * set the MAKEFILE variable desired by System V fans -- the 598 * placement of the setting here means it gets set to the last 599 * makefile specified, as it is set by SysV make. 600 */ 601 found: Var_Set("MAKEFILE", fname, VAR_GLOBAL); 602 Parse_File(fname, stream); 603 (void)fclose(stream); 604 } 605 return(TRUE); 606 } 607 608 /*- 609 * Error -- 610 * Print an error message given its format. 611 * 612 * Results: 613 * None. 614 * 615 * Side Effects: 616 * The message is printed. 617 */ 618 /* VARARGS */ 619 void 620 Error(va_alist) 621 va_dcl 622 { 623 va_list ap; 624 char *fmt; 625 626 va_start(ap); 627 fmt = va_arg(ap, char *); 628 (void)vfprintf(stderr, fmt, ap); 629 va_end(ap); 630 (void)fprintf(stderr, "\n"); 631 (void)fflush(stderr); 632 } 633 634 /*- 635 * Fatal -- 636 * Produce a Fatal error message. If jobs are running, waits for them 637 * to finish. 638 * 639 * Results: 640 * None 641 * 642 * Side Effects: 643 * The program exits 644 */ 645 /* VARARGS */ 646 void 647 Fatal(va_alist) 648 va_dcl 649 { 650 va_list ap; 651 char *fmt; 652 653 if (jobsRunning) 654 Job_Wait(); 655 656 va_start(ap); 657 fmt = va_arg(ap, char *); 658 (void)vfprintf(stderr, fmt, ap); 659 va_end(ap); 660 (void)fprintf(stderr, "\n"); 661 (void)fflush(stderr); 662 663 if (DEBUG(GRAPH2)) 664 Targ_PrintGraph(2); 665 exit(2); /* Not 1 so -q can distinguish error */ 666 } 667 668 /* 669 * Punt -- 670 * Major exception once jobs are being created. Kills all jobs, prints 671 * a message and exits. 672 * 673 * Results: 674 * None 675 * 676 * Side Effects: 677 * All children are killed indiscriminately and the program Lib_Exits 678 */ 679 /* VARARGS */ 680 void 681 Punt(va_alist) 682 va_dcl 683 { 684 va_list ap; 685 char *fmt; 686 687 (void)fprintf(stderr, "make: "); 688 va_start(ap); 689 fmt = va_arg(ap, char *); 690 (void)vfprintf(stderr, fmt, ap); 691 va_end(ap); 692 (void)fprintf(stderr, "\n"); 693 (void)fflush(stderr); 694 695 DieHorribly(); 696 } 697 698 /*- 699 * DieHorribly -- 700 * Exit without giving a message. 701 * 702 * Results: 703 * None 704 * 705 * Side Effects: 706 * A big one... 707 */ 708 void 709 DieHorribly() 710 { 711 if (jobsRunning) 712 Job_AbortAll(); 713 if (DEBUG(GRAPH2)) 714 Targ_PrintGraph(2); 715 exit(2); /* Not 1, so -q can distinguish error */ 716 } 717 718 /* 719 * Finish -- 720 * Called when aborting due to errors in child shell to signal 721 * abnormal exit. 722 * 723 * Results: 724 * None 725 * 726 * Side Effects: 727 * The program exits 728 */ 729 void 730 Finish(errors) 731 int errors; /* number of errors encountered in Make_Make */ 732 { 733 Fatal("%d error%s", errors, errors == 1 ? "" : "s"); 734 } 735 736 /* 737 * emalloc -- 738 * malloc, but die on error. 739 */ 740 char * 741 emalloc(len) 742 u_int len; 743 { 744 extern int errno; 745 char *p, *malloc(); 746 747 if (!(p = malloc(len))) 748 enomem(); 749 return(p); 750 } 751 752 /* 753 * enomem -- 754 * die when out of memory. 755 */ 756 enomem() 757 { 758 (void)fprintf(stderr, "make: %s.\n", strerror(errno)); 759 exit(2); 760 } 761 762 /* 763 * usage -- 764 * exit with usage message 765 */ 766 usage() 767 { 768 (void)fprintf(stderr, 769 "usage: make [-eiknqrst] [-D variable] [-d flags] [-f makefile ]\n\t\ 770 [-I directory] [-j max_jobs] [variable=value]\n"); 771 exit(2); 772 } 773