grep.c revision 225736
1/* $NetBSD: grep.c,v 1.4 2011/02/16 01:31:33 joerg Exp $ */ 2/* $FreeBSD: stable/9/usr.bin/grep/grep.c 224937 2011-08-17 13:56:33Z gabor $ */ 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: stable/9/usr.bin/grep/grep.c 224937 2011-08-17 13:56:33Z gabor $"); 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> 55nl_catd catalog; 56#endif 57 58/* 59 * Default messags to use when NLS is disabled or no catalogue 60 * is found. 61 */ 62const 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() */ 76int cflags = REG_NOSUB; 77int eflags = REG_STARTEND; 78 79/* Shortcut for matching all cases like empty regex */ 80bool matchall; 81 82/* Searching patterns */ 83unsigned int patterns, pattern_sz; 84char **pattern; 85regex_t *r_pattern; 86fastgrep_t *fg_pattern; 87 88/* Filename exclusion/inclusion patterns */ 89unsigned int fpatterns, fpattern_sz; 90unsigned int dpatterns, dpattern_sz; 91struct epat *dpattern, *fpattern; 92 93/* For regex errors */ 94char re_error[RE_ERROR_BUF + 1]; 95 96/* Command-line flags */ 97unsigned long long Aflag; /* -A x: print x lines trailing each match */ 98unsigned long long Bflag; /* -B x: print x lines leading each match */ 99bool Hflag; /* -H: always print file name */ 100bool Lflag; /* -L: only show names of files with no matches */ 101bool bflag; /* -b: show block numbers for each match */ 102bool cflag; /* -c: only show a count of matching lines */ 103bool hflag; /* -h: don't print filename headers */ 104bool iflag; /* -i: ignore case */ 105bool lflag; /* -l: only show names of files with matches */ 106bool mflag; /* -m x: stop reading the files after x matches */ 107unsigned long long mcount; /* count for -m */ 108bool nflag; /* -n: show line numbers in front of matching lines */ 109bool oflag; /* -o: print only matching part */ 110bool qflag; /* -q: quiet mode (don't output anything) */ 111bool sflag; /* -s: silent mode (ignore errors) */ 112bool vflag; /* -v: only show non-matching lines */ 113bool wflag; /* -w: pattern must start and end on word boundaries */ 114bool xflag; /* -x: pattern must match entire line */ 115bool lbflag; /* --line-buffered */ 116bool nullflag; /* --null */ 117char *label; /* --label */ 118const char *color; /* --color */ 119int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */ 120int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */ 121int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */ 122int devbehave = DEV_READ; /* -D: handling of devices */ 123int dirbehave = DIR_READ; /* -dRr: handling of directories */ 124int linkbehave = LINK_READ; /* -OpS: handling of symlinks */ 125 126bool dexclude, dinclude; /* --exclude-dir and --include-dir */ 127bool fexclude, finclude; /* --exclude and --include */ 128 129enum { 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 143static inline const char *init_color(const char *); 144 145/* Housekeeping */ 146bool first = true; /* flag whether we are processing the first match */ 147bool prev; /* flag whether or not the previous line matched */ 148int tail; /* lines left to print */ 149bool notfound; /* file not found */ 150 151extern char *__progname; 152 153/* 154 * Prints usage information and returns 2. 155 */ 156static void 157usage(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 167static const char *optstr = "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxy"; 168 169struct 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 */ 222static void 223add_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 */ 249static void 250add_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 */ 267static void 268add_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 */ 285static void 286read_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 301static inline const char * 302init_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 310int 311main(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