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>
59nl_catd	 catalog;
60#endif
61
62/*
63 * Default messags to use when NLS is disabled or no catalogue
64 * is found.
65 */
66const 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() */
80int		 cflags = 0;
81int		 eflags = REG_STARTEND;
82
83/* Searching patterns */
84unsigned int	 patterns, pattern_sz;
85char		**pattern;
86regex_t		*r_pattern;
87fastgrep_t	*fg_pattern;
88
89/* Filename exclusion/inclusion patterns */
90unsigned int	 fpatterns, fpattern_sz;
91unsigned int	 dpatterns, dpattern_sz;
92struct epat	*dpattern, *fpattern;
93
94/* For regex errors  */
95char	 re_error[RE_ERROR_BUF + 1];
96
97/* Command-line flags */
98unsigned long long Aflag;	/* -A x: print x lines trailing each match */
99unsigned long long Bflag;	/* -B x: print x lines leading each match */
100bool	 Hflag;		/* -H: always print file name */
101bool	 Lflag;		/* -L: only show names of files with no matches */
102bool	 bflag;		/* -b: show block numbers for each match */
103bool	 cflag;		/* -c: only show a count of matching lines */
104bool	 hflag;		/* -h: don't print filename headers */
105bool	 iflag;		/* -i: ignore case */
106bool	 lflag;		/* -l: only show names of files with matches */
107bool	 mflag;		/* -m x: stop reading the files after x matches */
108unsigned long long mcount;	/* count for -m */
109bool	 nflag;		/* -n: show line numbers in front of matching lines */
110bool	 oflag;		/* -o: print only matching part */
111bool	 qflag;		/* -q: quiet mode (don't output anything) */
112bool	 sflag;		/* -s: silent mode (ignore errors) */
113bool	 vflag;		/* -v: only show non-matching lines */
114bool	 wflag;		/* -w: pattern must start and end on word boundaries */
115bool	 xflag;		/* -x: pattern must match entire line */
116bool	 lbflag;	/* --line-buffered */
117bool	 nullflag;	/* --null */
118bool	 nulldataflag;	/* --null-data */
119unsigned char line_sep = '\n';	/* 0 for --null-data */
120char	*label;		/* --label */
121const char *color;	/* --color */
122int	 grepbehave = GREP_BASIC;	/* -EFGP: type of the regex */
123int	 binbehave = BINFILE_BIN;	/* -aIU: handling of binary files */
124int	 filebehave = FILE_STDIO;	/* -JZ: normal, gzip or bzip2 file */
125int	 devbehave = DEV_READ;		/* -D: handling of devices */
126int	 dirbehave = DIR_READ;		/* -dRr: handling of directories */
127int	 linkbehave = LINK_READ;	/* -OpS: handling of symlinks */
128
129bool	 dexclude, dinclude;	/* --exclude-dir and --include-dir */
130bool	 fexclude, finclude;	/* --exclude and --include */
131
132enum {
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
146static inline const char	*init_color(const char *);
147
148/* Housekeeping */
149int	 tail;		/* lines left to print */
150bool	 notfound;	/* file not found */
151
152extern char	*__progname;
153
154/*
155 * Prints usage information and returns 2.
156 */
157__dead static void
158usage(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
167static const char optstr[] =
168    "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxyz";
169
170struct 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 */
224static void
225add_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 */
248static void
249add_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 */
266static void
267add_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 */
284static void
285read_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
304static inline const char *
305init_color(const char *d)
306{
307	char *c;
308
309	c = getenv("GREP_COLOR");
310	return (c != NULL ? c : d);
311}
312
313int
314main(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