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