1 /* $NetBSD: grep.c,v 1.4 2011/02/16 01:31:33 joerg Exp $ */ 2 /* $FreeBSD$ */ 3 /* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */ 4 5 /*- 6 * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav 7 * Copyright (C) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __FBSDID("$FreeBSD$"); 34 35 #include <sys/stat.h> 36 #include <sys/types.h> 37 38 #include <ctype.h> 39 #include <err.h> 40 #include <errno.h> 41 #include <getopt.h> 42 #include <limits.h> 43 #include <libgen.h> 44 #include <locale.h> 45 #include <stdbool.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 51 #include "grep.h" 52 53 #ifndef WITHOUT_NLS 54 #include <nl_types.h> 55 nl_catd catalog; 56 #endif 57 58 /* 59 * Default messags to use when NLS is disabled or no catalogue 60 * is found. 61 */ 62 const char *errstr[] = { 63 "", 64 /* 1*/ "(standard input)", 65 /* 2*/ "cannot read bzip2 compressed file", 66 /* 3*/ "unknown %s option", 67 /* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZ] [-A num] [-B num] [-C[num]]\n", 68 /* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n", 69 /* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n", 70 /* 7*/ "\t[--null] [pattern] [file ...]\n", 71 /* 8*/ "Binary file %s matches\n", 72 /* 9*/ "%s (BSD grep) %s\n", 73 }; 74 75 /* Flags passed to regcomp() and regexec() */ 76 int cflags = REG_NOSUB; 77 int eflags = REG_STARTEND; 78 79 /* Shortcut for matching all cases like empty regex */ 80 bool matchall; 81 82 /* Searching patterns */ 83 unsigned int patterns, pattern_sz; 84 char **pattern; 85 regex_t *r_pattern; 86 fastgrep_t *fg_pattern; 87 88 /* Filename exclusion/inclusion patterns */ 89 unsigned int fpatterns, fpattern_sz; 90 unsigned int dpatterns, dpattern_sz; 91 struct epat *dpattern, *fpattern; 92 93 /* For regex errors */ 94 char re_error[RE_ERROR_BUF + 1]; 95 96 /* Command-line flags */ 97 unsigned long long Aflag; /* -A x: print x lines trailing each match */ 98 unsigned long long Bflag; /* -B x: print x lines leading each match */ 99 bool Hflag; /* -H: always print file name */ 100 bool Lflag; /* -L: only show names of files with no matches */ 101 bool bflag; /* -b: show block numbers for each match */ 102 bool cflag; /* -c: only show a count of matching lines */ 103 bool hflag; /* -h: don't print filename headers */ 104 bool iflag; /* -i: ignore case */ 105 bool lflag; /* -l: only show names of files with matches */ 106 bool mflag; /* -m x: stop reading the files after x matches */ 107 unsigned long long mcount; /* count for -m */ 108 bool nflag; /* -n: show line numbers in front of matching lines */ 109 bool oflag; /* -o: print only matching part */ 110 bool qflag; /* -q: quiet mode (don't output anything) */ 111 bool sflag; /* -s: silent mode (ignore errors) */ 112 bool vflag; /* -v: only show non-matching lines */ 113 bool wflag; /* -w: pattern must start and end on word boundaries */ 114 bool xflag; /* -x: pattern must match entire line */ 115 bool lbflag; /* --line-buffered */ 116 bool nullflag; /* --null */ 117 char *label; /* --label */ 118 const char *color; /* --color */ 119 int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */ 120 int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */ 121 int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */ 122 int devbehave = DEV_READ; /* -D: handling of devices */ 123 int dirbehave = DIR_READ; /* -dRr: handling of directories */ 124 int linkbehave = LINK_READ; /* -OpS: handling of symlinks */ 125 126 bool dexclude, dinclude; /* --exclude-dir and --include-dir */ 127 bool fexclude, finclude; /* --exclude and --include */ 128 129 enum { 130 BIN_OPT = CHAR_MAX + 1, 131 COLOR_OPT, 132 HELP_OPT, 133 MMAP_OPT, 134 LINEBUF_OPT, 135 LABEL_OPT, 136 NULL_OPT, 137 R_EXCLUDE_OPT, 138 R_INCLUDE_OPT, 139 R_DEXCLUDE_OPT, 140 R_DINCLUDE_OPT 141 }; 142 143 static inline const char *init_color(const char *); 144 145 /* Housekeeping */ 146 bool first = true; /* flag whether we are processing the first match */ 147 bool prev; /* flag whether or not the previous line matched */ 148 int tail; /* lines left to print */ 149 bool notfound; /* file not found */ 150 151 extern char *__progname; 152 153 /* 154 * Prints usage information and returns 2. 155 */ 156 static void 157 usage(void) 158 { 159 fprintf(stderr, getstr(4), __progname); 160 fprintf(stderr, "%s", getstr(5)); 161 fprintf(stderr, "%s", getstr(5)); 162 fprintf(stderr, "%s", getstr(6)); 163 fprintf(stderr, "%s", getstr(7)); 164 exit(2); 165 } 166 167 static const char *optstr = "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxy"; 168 169 struct option long_options[] = 170 { 171 {"binary-files", required_argument, NULL, BIN_OPT}, 172 {"help", no_argument, NULL, HELP_OPT}, 173 {"mmap", no_argument, NULL, MMAP_OPT}, 174 {"line-buffered", no_argument, NULL, LINEBUF_OPT}, 175 {"label", required_argument, NULL, LABEL_OPT}, 176 {"null", no_argument, NULL, NULL_OPT}, 177 {"color", optional_argument, NULL, COLOR_OPT}, 178 {"colour", optional_argument, NULL, COLOR_OPT}, 179 {"exclude", required_argument, NULL, R_EXCLUDE_OPT}, 180 {"include", required_argument, NULL, R_INCLUDE_OPT}, 181 {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT}, 182 {"include-dir", required_argument, NULL, R_DINCLUDE_OPT}, 183 {"after-context", required_argument, NULL, 'A'}, 184 {"text", no_argument, NULL, 'a'}, 185 {"before-context", required_argument, NULL, 'B'}, 186 {"byte-offset", no_argument, NULL, 'b'}, 187 {"context", optional_argument, NULL, 'C'}, 188 {"count", no_argument, NULL, 'c'}, 189 {"devices", required_argument, NULL, 'D'}, 190 {"directories", required_argument, NULL, 'd'}, 191 {"extended-regexp", no_argument, NULL, 'E'}, 192 {"regexp", required_argument, NULL, 'e'}, 193 {"fixed-strings", no_argument, NULL, 'F'}, 194 {"file", required_argument, NULL, 'f'}, 195 {"basic-regexp", no_argument, NULL, 'G'}, 196 {"no-filename", no_argument, NULL, 'h'}, 197 {"with-filename", no_argument, NULL, 'H'}, 198 {"ignore-case", no_argument, NULL, 'i'}, 199 {"bz2decompress", no_argument, NULL, 'J'}, 200 {"files-with-matches", no_argument, NULL, 'l'}, 201 {"files-without-match", no_argument, NULL, 'L'}, 202 {"max-count", required_argument, NULL, 'm'}, 203 {"line-number", no_argument, NULL, 'n'}, 204 {"only-matching", no_argument, NULL, 'o'}, 205 {"quiet", no_argument, NULL, 'q'}, 206 {"silent", no_argument, NULL, 'q'}, 207 {"recursive", no_argument, NULL, 'r'}, 208 {"no-messages", no_argument, NULL, 's'}, 209 {"binary", no_argument, NULL, 'U'}, 210 {"unix-byte-offsets", no_argument, NULL, 'u'}, 211 {"invert-match", no_argument, NULL, 'v'}, 212 {"version", no_argument, NULL, 'V'}, 213 {"word-regexp", no_argument, NULL, 'w'}, 214 {"line-regexp", no_argument, NULL, 'x'}, 215 {"decompress", no_argument, NULL, 'Z'}, 216 {NULL, no_argument, NULL, 0} 217 }; 218 219 /* 220 * Adds a searching pattern to the internal array. 221 */ 222 static void 223 add_pattern(char *pat, size_t len) 224 { 225 226 /* Check if we can do a shortcut */ 227 if (len == 0 || matchall) { 228 matchall = true; 229 return; 230 } 231 /* Increase size if necessary */ 232 if (patterns == pattern_sz) { 233 pattern_sz *= 2; 234 pattern = grep_realloc(pattern, ++pattern_sz * 235 sizeof(*pattern)); 236 } 237 if (len > 0 && pat[len - 1] == '\n') 238 --len; 239 /* pat may not be NUL-terminated */ 240 pattern[patterns] = grep_malloc(len + 1); 241 memcpy(pattern[patterns], pat, len); 242 pattern[patterns][len] = '\0'; 243 ++patterns; 244 } 245 246 /* 247 * Adds a file include/exclude pattern to the internal array. 248 */ 249 static void 250 add_fpattern(const char *pat, int mode) 251 { 252 253 /* Increase size if necessary */ 254 if (fpatterns == fpattern_sz) { 255 fpattern_sz *= 2; 256 fpattern = grep_realloc(fpattern, ++fpattern_sz * 257 sizeof(struct epat)); 258 } 259 fpattern[fpatterns].pat = grep_strdup(pat); 260 fpattern[fpatterns].mode = mode; 261 ++fpatterns; 262 } 263 264 /* 265 * Adds a directory include/exclude pattern to the internal array. 266 */ 267 static void 268 add_dpattern(const char *pat, int mode) 269 { 270 271 /* Increase size if necessary */ 272 if (dpatterns == dpattern_sz) { 273 dpattern_sz *= 2; 274 dpattern = grep_realloc(dpattern, ++dpattern_sz * 275 sizeof(struct epat)); 276 } 277 dpattern[dpatterns].pat = grep_strdup(pat); 278 dpattern[dpatterns].mode = mode; 279 ++dpatterns; 280 } 281 282 /* 283 * Reads searching patterns from a file and adds them with add_pattern(). 284 */ 285 static void 286 read_patterns(const char *fn) 287 { 288 FILE *f; 289 char *line; 290 size_t len; 291 292 if ((f = fopen(fn, "r")) == NULL) 293 err(2, "%s", fn); 294 while ((line = fgetln(f, &len)) != NULL) 295 add_pattern(line, *line == '\n' ? 0 : len); 296 if (ferror(f)) 297 err(2, "%s", fn); 298 fclose(f); 299 } 300 301 static inline const char * 302 init_color(const char *d) 303 { 304 char *c; 305 306 c = getenv("GREP_COLOR"); 307 return (c != NULL && c[0] != '\0' ? c : d); 308 } 309 310 int 311 main(int argc, char *argv[]) 312 { 313 char **aargv, **eargv, *eopts; 314 char *ep; 315 unsigned long long l; 316 unsigned int aargc, eargc, i; 317 int c, lastc, needpattern, newarg, prevoptind; 318 319 setlocale(LC_ALL, ""); 320 321 #ifndef WITHOUT_NLS 322 catalog = catopen("grep", NL_CAT_LOCALE); 323 #endif 324 325 /* Check what is the program name of the binary. In this 326 way we can have all the funcionalities in one binary 327 without the need of scripting and using ugly hacks. */ 328 switch (__progname[0]) { 329 case 'e': 330 grepbehave = GREP_EXTENDED; 331 break; 332 case 'f': 333 grepbehave = GREP_FIXED; 334 break; 335 case 'g': 336 grepbehave = GREP_BASIC; 337 break; 338 case 'z': 339 filebehave = FILE_GZIP; 340 switch(__progname[1]) { 341 case 'e': 342 grepbehave = GREP_EXTENDED; 343 break; 344 case 'f': 345 grepbehave = GREP_FIXED; 346 break; 347 case 'g': 348 grepbehave = GREP_BASIC; 349 break; 350 } 351 break; 352 } 353 354 lastc = '\0'; 355 newarg = 1; 356 prevoptind = 1; 357 needpattern = 1; 358 359 eopts = getenv("GREP_OPTIONS"); 360 361 /* support for extra arguments in GREP_OPTIONS */ 362 eargc = 0; 363 if (eopts != NULL && eopts[0] != '\0') { 364 char *str; 365 366 /* make an estimation of how many extra arguments we have */ 367 for (unsigned int j = 0; j < strlen(eopts); j++) 368 if (eopts[j] == ' ') 369 eargc++; 370 371 eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1)); 372 373 eargc = 0; 374 /* parse extra arguments */ 375 while ((str = strsep(&eopts, " ")) != NULL) 376 if (str[0] != '\0') 377 eargv[eargc++] = grep_strdup(str); 378 379 aargv = (char **)grep_calloc(eargc + argc + 1, 380 sizeof(char *)); 381 382 aargv[0] = argv[0]; 383 for (i = 0; i < eargc; i++) 384 aargv[i + 1] = eargv[i]; 385 for (int j = 1; j < argc; j++, i++) 386 aargv[i + 1] = argv[j]; 387 388 aargc = eargc + argc; 389 } else { 390 aargv = argv; 391 aargc = argc; 392 } 393 394 while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) != 395 -1)) { 396 switch (c) { 397 case '0': case '1': case '2': case '3': case '4': 398 case '5': case '6': case '7': case '8': case '9': 399 if (newarg || !isdigit(lastc)) 400 Aflag = 0; 401 else if (Aflag > LLONG_MAX / 10) { 402 errno = ERANGE; 403 err(2, NULL); 404 } 405 Aflag = Bflag = (Aflag * 10) + (c - '0'); 406 break; 407 case 'C': 408 if (optarg == NULL) { 409 Aflag = Bflag = 2; 410 break; 411 } 412 /* FALLTHROUGH */ 413 case 'A': 414 /* FALLTHROUGH */ 415 case 'B': 416 errno = 0; 417 l = strtoull(optarg, &ep, 10); 418 if (((errno == ERANGE) && (l == ULLONG_MAX)) || 419 ((errno == EINVAL) && (l == 0))) 420 err(2, NULL); 421 else if (ep[0] != '\0') { 422 errno = EINVAL; 423 err(2, NULL); 424 } 425 if (c == 'A') 426 Aflag = l; 427 else if (c == 'B') 428 Bflag = l; 429 else 430 Aflag = Bflag = l; 431 break; 432 case 'a': 433 binbehave = BINFILE_TEXT; 434 break; 435 case 'b': 436 bflag = true; 437 break; 438 case 'c': 439 cflag = true; 440 break; 441 case 'D': 442 if (strcasecmp(optarg, "skip") == 0) 443 devbehave = DEV_SKIP; 444 else if (strcasecmp(optarg, "read") == 0) 445 devbehave = DEV_READ; 446 else 447 errx(2, getstr(3), "--devices"); 448 break; 449 case 'd': 450 if (strcasecmp("recurse", optarg) == 0) { 451 Hflag = true; 452 dirbehave = DIR_RECURSE; 453 } else if (strcasecmp("skip", optarg) == 0) 454 dirbehave = DIR_SKIP; 455 else if (strcasecmp("read", optarg) == 0) 456 dirbehave = DIR_READ; 457 else 458 errx(2, getstr(3), "--directories"); 459 break; 460 case 'E': 461 grepbehave = GREP_EXTENDED; 462 break; 463 case 'e': 464 add_pattern(optarg, strlen(optarg)); 465 needpattern = 0; 466 break; 467 case 'F': 468 grepbehave = GREP_FIXED; 469 break; 470 case 'f': 471 read_patterns(optarg); 472 needpattern = 0; 473 break; 474 case 'G': 475 grepbehave = GREP_BASIC; 476 break; 477 case 'H': 478 Hflag = true; 479 break; 480 case 'h': 481 Hflag = false; 482 hflag = true; 483 break; 484 case 'I': 485 binbehave = BINFILE_SKIP; 486 break; 487 case 'i': 488 case 'y': 489 iflag = true; 490 cflags |= REG_ICASE; 491 break; 492 case 'J': 493 filebehave = FILE_BZIP; 494 break; 495 case 'L': 496 lflag = false; 497 Lflag = true; 498 break; 499 case 'l': 500 Lflag = false; 501 lflag = true; 502 break; 503 case 'm': 504 mflag = true; 505 errno = 0; 506 mcount = strtoull(optarg, &ep, 10); 507 if (((errno == ERANGE) && (mcount == ULLONG_MAX)) || 508 ((errno == EINVAL) && (mcount == 0))) 509 err(2, NULL); 510 else if (ep[0] != '\0') { 511 errno = EINVAL; 512 err(2, NULL); 513 } 514 break; 515 case 'n': 516 nflag = true; 517 break; 518 case 'O': 519 linkbehave = LINK_EXPLICIT; 520 break; 521 case 'o': 522 oflag = true; 523 cflags &= ~REG_NOSUB; 524 break; 525 case 'p': 526 linkbehave = LINK_SKIP; 527 break; 528 case 'q': 529 qflag = true; 530 break; 531 case 'S': 532 linkbehave = LINK_READ; 533 break; 534 case 'R': 535 case 'r': 536 dirbehave = DIR_RECURSE; 537 Hflag = true; 538 break; 539 case 's': 540 sflag = true; 541 break; 542 case 'U': 543 binbehave = BINFILE_BIN; 544 break; 545 case 'u': 546 case MMAP_OPT: 547 /* noop, compatibility */ 548 break; 549 case 'V': 550 printf(getstr(9), __progname, VERSION); 551 exit(0); 552 case 'v': 553 vflag = true; 554 break; 555 case 'w': 556 wflag = true; 557 cflags &= ~REG_NOSUB; 558 break; 559 case 'x': 560 xflag = true; 561 cflags &= ~REG_NOSUB; 562 break; 563 case 'Z': 564 filebehave = FILE_GZIP; 565 break; 566 case BIN_OPT: 567 if (strcasecmp("binary", optarg) == 0) 568 binbehave = BINFILE_BIN; 569 else if (strcasecmp("without-match", optarg) == 0) 570 binbehave = BINFILE_SKIP; 571 else if (strcasecmp("text", optarg) == 0) 572 binbehave = BINFILE_TEXT; 573 else 574 errx(2, getstr(3), "--binary-files"); 575 break; 576 case COLOR_OPT: 577 color = NULL; 578 if (optarg == NULL || strcasecmp("auto", optarg) == 0 || 579 strcasecmp("tty", optarg) == 0 || 580 strcasecmp("if-tty", optarg) == 0) { 581 char *term; 582 583 term = getenv("TERM"); 584 if (isatty(STDOUT_FILENO) && term != NULL && 585 strcasecmp(term, "dumb") != 0) 586 color = init_color("01;31"); 587 } else if (strcasecmp("always", optarg) == 0 || 588 strcasecmp("yes", optarg) == 0 || 589 strcasecmp("force", optarg) == 0) { 590 color = init_color("01;31"); 591 } else if (strcasecmp("never", optarg) != 0 && 592 strcasecmp("none", optarg) != 0 && 593 strcasecmp("no", optarg) != 0) 594 errx(2, getstr(3), "--color"); 595 cflags &= ~REG_NOSUB; 596 break; 597 case LABEL_OPT: 598 label = optarg; 599 break; 600 case LINEBUF_OPT: 601 lbflag = true; 602 break; 603 case NULL_OPT: 604 nullflag = true; 605 break; 606 case R_INCLUDE_OPT: 607 finclude = true; 608 add_fpattern(optarg, INCL_PAT); 609 break; 610 case R_EXCLUDE_OPT: 611 fexclude = true; 612 add_fpattern(optarg, EXCL_PAT); 613 break; 614 case R_DINCLUDE_OPT: 615 dinclude = true; 616 add_dpattern(optarg, INCL_PAT); 617 break; 618 case R_DEXCLUDE_OPT: 619 dexclude = true; 620 add_dpattern(optarg, EXCL_PAT); 621 break; 622 case HELP_OPT: 623 default: 624 usage(); 625 } 626 lastc = c; 627 newarg = optind != prevoptind; 628 prevoptind = optind; 629 } 630 aargc -= optind; 631 aargv += optind; 632 633 /* Fail if we don't have any pattern */ 634 if (aargc == 0 && needpattern) 635 usage(); 636 637 /* Process patterns from command line */ 638 if (aargc != 0 && needpattern) { 639 add_pattern(*aargv, strlen(*aargv)); 640 --aargc; 641 ++aargv; 642 } 643 644 switch (grepbehave) { 645 case GREP_FIXED: 646 case GREP_BASIC: 647 break; 648 case GREP_EXTENDED: 649 cflags |= REG_EXTENDED; 650 break; 651 default: 652 /* NOTREACHED */ 653 usage(); 654 } 655 656 fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern)); 657 r_pattern = grep_calloc(patterns, sizeof(*r_pattern)); 658 /* 659 * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance. 660 * Optimizations should be done there. 661 */ 662 /* Check if cheating is allowed (always is for fgrep). */ 663 if (grepbehave == GREP_FIXED) { 664 for (i = 0; i < patterns; ++i) 665 fgrepcomp(&fg_pattern[i], pattern[i]); 666 } else { 667 for (i = 0; i < patterns; ++i) { 668 if (fastcomp(&fg_pattern[i], pattern[i])) { 669 /* Fall back to full regex library */ 670 c = regcomp(&r_pattern[i], pattern[i], cflags); 671 if (c != 0) { 672 regerror(c, &r_pattern[i], re_error, 673 RE_ERROR_BUF); 674 errx(2, "%s", re_error); 675 } 676 } 677 } 678 } 679 680 if (lbflag) 681 setlinebuf(stdout); 682 683 if ((aargc == 0 || aargc == 1) && !Hflag) 684 hflag = true; 685 686 if (aargc == 0) 687 exit(!procfile("-")); 688 689 if (dirbehave == DIR_RECURSE) 690 c = grep_tree(aargv); 691 else 692 for (c = 0; aargc--; ++aargv) { 693 if ((finclude || fexclude) && !file_matching(*aargv)) 694 continue; 695 c+= procfile(*aargv); 696 } 697 698 #ifndef WITHOUT_NLS 699 catclose(catalog); 700 #endif 701 702 /* Find out the correct return value according to the 703 results and the command line option. */ 704 exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1)); 705 } 706