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 : 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) { 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 eargv[eargc++] = grep_strdup(str); 377 378 aargv = (char **)grep_calloc(eargc + argc + 1, 379 sizeof(char *)); 380 381 aargv[0] = argv[0]; 382 for (i = 0; i < eargc; i++) 383 aargv[i + 1] = eargv[i]; 384 for (int j = 1; j < argc; j++, i++) 385 aargv[i + 1] = argv[j]; 386 387 aargc = eargc + argc; 388 } else { 389 aargv = argv; 390 aargc = argc; 391 } 392 393 while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) != 394 -1)) { 395 switch (c) { 396 case '0': case '1': case '2': case '3': case '4': 397 case '5': case '6': case '7': case '8': case '9': 398 if (newarg || !isdigit(lastc)) 399 Aflag = 0; 400 else if (Aflag > LLONG_MAX / 10) { 401 errno = ERANGE; 402 err(2, NULL); 403 } 404 Aflag = Bflag = (Aflag * 10) + (c - '0'); 405 break; 406 case 'C': 407 if (optarg == NULL) { 408 Aflag = Bflag = 2; 409 break; 410 } 411 /* FALLTHROUGH */ 412 case 'A': 413 /* FALLTHROUGH */ 414 case 'B': 415 errno = 0; 416 l = strtoull(optarg, &ep, 10); 417 if (((errno == ERANGE) && (l == ULLONG_MAX)) || 418 ((errno == EINVAL) && (l == 0))) 419 err(2, NULL); 420 else if (ep[0] != '\0') { 421 errno = EINVAL; 422 err(2, NULL); 423 } 424 if (c == 'A') 425 Aflag = l; 426 else if (c == 'B') 427 Bflag = l; 428 else 429 Aflag = Bflag = l; 430 break; 431 case 'a': 432 binbehave = BINFILE_TEXT; 433 break; 434 case 'b': 435 bflag = true; 436 break; 437 case 'c': 438 cflag = true; 439 break; 440 case 'D': 441 if (strcasecmp(optarg, "skip") == 0) 442 devbehave = DEV_SKIP; 443 else if (strcasecmp(optarg, "read") == 0) 444 devbehave = DEV_READ; 445 else 446 errx(2, getstr(3), "--devices"); 447 break; 448 case 'd': 449 if (strcasecmp("recurse", optarg) == 0) { 450 Hflag = true; 451 dirbehave = DIR_RECURSE; 452 } else if (strcasecmp("skip", optarg) == 0) 453 dirbehave = DIR_SKIP; 454 else if (strcasecmp("read", optarg) == 0) 455 dirbehave = DIR_READ; 456 else 457 errx(2, getstr(3), "--directories"); 458 break; 459 case 'E': 460 grepbehave = GREP_EXTENDED; 461 break; 462 case 'e': 463 add_pattern(optarg, strlen(optarg)); 464 needpattern = 0; 465 break; 466 case 'F': 467 grepbehave = GREP_FIXED; 468 break; 469 case 'f': 470 read_patterns(optarg); 471 needpattern = 0; 472 break; 473 case 'G': 474 grepbehave = GREP_BASIC; 475 break; 476 case 'H': 477 Hflag = true; 478 break; 479 case 'h': 480 Hflag = false; 481 hflag = true; 482 break; 483 case 'I': 484 binbehave = BINFILE_SKIP; 485 break; 486 case 'i': 487 case 'y': 488 iflag = true; 489 cflags |= REG_ICASE; 490 break; 491 case 'J': 492 filebehave = FILE_BZIP; 493 break; 494 case 'L': 495 lflag = false; 496 Lflag = true; 497 break; 498 case 'l': 499 Lflag = false; 500 lflag = true; 501 break; 502 case 'm': 503 mflag = true; 504 errno = 0; 505 mcount = strtoull(optarg, &ep, 10); 506 if (((errno == ERANGE) && (mcount == ULLONG_MAX)) || 507 ((errno == EINVAL) && (mcount == 0))) 508 err(2, NULL); 509 else if (ep[0] != '\0') { 510 errno = EINVAL; 511 err(2, NULL); 512 } 513 break; 514 case 'n': 515 nflag = true; 516 break; 517 case 'O': 518 linkbehave = LINK_EXPLICIT; 519 break; 520 case 'o': 521 oflag = true; 522 cflags &= ~REG_NOSUB; 523 break; 524 case 'p': 525 linkbehave = LINK_SKIP; 526 break; 527 case 'q': 528 qflag = true; 529 break; 530 case 'S': 531 linkbehave = LINK_READ; 532 break; 533 case 'R': 534 case 'r': 535 dirbehave = DIR_RECURSE; 536 Hflag = true; 537 break; 538 case 's': 539 sflag = true; 540 break; 541 case 'U': 542 binbehave = BINFILE_BIN; 543 break; 544 case 'u': 545 case MMAP_OPT: 546 /* noop, compatibility */ 547 break; 548 case 'V': 549 printf(getstr(9), __progname, VERSION); 550 exit(0); 551 case 'v': 552 vflag = true; 553 break; 554 case 'w': 555 wflag = true; 556 cflags &= ~REG_NOSUB; 557 break; 558 case 'x': 559 xflag = true; 560 cflags &= ~REG_NOSUB; 561 break; 562 case 'Z': 563 filebehave = FILE_GZIP; 564 break; 565 case BIN_OPT: 566 if (strcasecmp("binary", optarg) == 0) 567 binbehave = BINFILE_BIN; 568 else if (strcasecmp("without-match", optarg) == 0) 569 binbehave = BINFILE_SKIP; 570 else if (strcasecmp("text", optarg) == 0) 571 binbehave = BINFILE_TEXT; 572 else 573 errx(2, getstr(3), "--binary-files"); 574 break; 575 case COLOR_OPT: 576 color = NULL; 577 if (optarg == NULL || strcasecmp("auto", optarg) == 0 || 578 strcasecmp("tty", optarg) == 0 || 579 strcasecmp("if-tty", optarg) == 0) { 580 char *term; 581 582 term = getenv("TERM"); 583 if (isatty(STDOUT_FILENO) && term != NULL && 584 strcasecmp(term, "dumb") != 0) 585 color = init_color("01;31"); 586 } else if (strcasecmp("always", optarg) == 0 || 587 strcasecmp("yes", optarg) == 0 || 588 strcasecmp("force", optarg) == 0) { 589 color = init_color("01;31"); 590 } else if (strcasecmp("never", optarg) != 0 && 591 strcasecmp("none", optarg) != 0 && 592 strcasecmp("no", optarg) != 0) 593 errx(2, getstr(3), "--color"); 594 cflags &= ~REG_NOSUB; 595 break; 596 case LABEL_OPT: 597 label = optarg; 598 break; 599 case LINEBUF_OPT: 600 lbflag = true; 601 break; 602 case NULL_OPT: 603 nullflag = true; 604 break; 605 case R_INCLUDE_OPT: 606 finclude = true; 607 add_fpattern(optarg, INCL_PAT); 608 break; 609 case R_EXCLUDE_OPT: 610 fexclude = true; 611 add_fpattern(optarg, EXCL_PAT); 612 break; 613 case R_DINCLUDE_OPT: 614 dinclude = true; 615 add_dpattern(optarg, INCL_PAT); 616 break; 617 case R_DEXCLUDE_OPT: 618 dexclude = true; 619 add_dpattern(optarg, EXCL_PAT); 620 break; 621 case HELP_OPT: 622 default: 623 usage(); 624 } 625 lastc = c; 626 newarg = optind != prevoptind; 627 prevoptind = optind; 628 } 629 aargc -= optind; 630 aargv += optind; 631 632 /* Fail if we don't have any pattern */ 633 if (aargc == 0 && needpattern) 634 usage(); 635 636 /* Process patterns from command line */ 637 if (aargc != 0 && needpattern) { 638 add_pattern(*aargv, strlen(*aargv)); 639 --aargc; 640 ++aargv; 641 } 642 643 switch (grepbehave) { 644 case GREP_FIXED: 645 case GREP_BASIC: 646 break; 647 case GREP_EXTENDED: 648 cflags |= REG_EXTENDED; 649 break; 650 default: 651 /* NOTREACHED */ 652 usage(); 653 } 654 655 fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern)); 656 r_pattern = grep_calloc(patterns, sizeof(*r_pattern)); 657 /* 658 * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance. 659 * Optimizations should be done there. 660 */ 661 /* Check if cheating is allowed (always is for fgrep). */ 662 if (grepbehave == GREP_FIXED) { 663 for (i = 0; i < patterns; ++i) 664 fgrepcomp(&fg_pattern[i], pattern[i]); 665 } else { 666 for (i = 0; i < patterns; ++i) { 667 if (fastcomp(&fg_pattern[i], pattern[i])) { 668 /* Fall back to full regex library */ 669 c = regcomp(&r_pattern[i], pattern[i], cflags); 670 if (c != 0) { 671 regerror(c, &r_pattern[i], re_error, 672 RE_ERROR_BUF); 673 errx(2, "%s", re_error); 674 } 675 } 676 } 677 } 678 679 if (lbflag) 680 setlinebuf(stdout); 681 682 if ((aargc == 0 || aargc == 1) && !Hflag) 683 hflag = true; 684 685 if (aargc == 0) 686 exit(!procfile("-")); 687 688 if (dirbehave == DIR_RECURSE) 689 c = grep_tree(aargv); 690 else 691 for (c = 0; aargc--; ++aargv) { 692 if ((finclude || fexclude) && !file_matching(*aargv)) 693 continue; 694 c+= procfile(*aargv); 695 } 696 697 #ifndef WITHOUT_NLS 698 catclose(catalog); 699 #endif 700 701 /* Find out the correct return value according to the 702 results and the command line option. */ 703 exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1)); 704 } 705