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