1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*           Copyright (c) 1995-2009 AT&T Knowledge Ventures            *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                      by AT&T Knowledge Ventures                      *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21
22static const char usage[] =
23"[-?\n@(#)$Id: grep (AT&T Research) 2006-06-14 $\n]"
24USAGE_LICENSE
25"[+NAME?grep - search lines in files for matching patterns]"
26"[+DESCRIPTION?The \bgrep\b commands search the named input files"
27"	for lines containing a match for the given \apatterns\a."
28"	Matching lines are printed by default. The standard input is searched"
29"	if no files are given or when the file \b-\b is specified.]"
30"[+?There are six variants of \bgrep\b, each one using a different form of"
31"	\apattern\a, controlled either by option or the command path"
32"	base name. Details of each variant may be found in \bregex\b(3).]"
33"	{"
34"	[+grep?The default basic regular expressions (no alternations.)]"
35"	[+egrep?Extended regular expressions (alternations, one or more.)]"
36"	[+pgrep?\bperl\b(1) regular expressions (lenient extended.)]"
37"	[+xgrep?Augmented regular expressions (conjunction, negation.)]"
38"	[+fgrep?Fixed string expressions.]"
39"	[+agrep?Approximate regular expressions (not implemented.)]"
40"	}"
41"[G:basic-regexp?\bgrep\b mode (default): basic regular expression \apatterns\a.]"
42"[E:extended-regexp?\begrep\b mode: extended regular expression \apatterns\a.]"
43"[X:augmented-regexp?\bxgrep\b mode: augmented regular expression \apatterns\a.]"
44"[P:perl-regexp?\bpgrep\b mode: \bperl\b(1) regular expression \apatterns\a.]"
45"[F:fixed-string?\bfgrep\b mode: fixed string \apatterns\a.]"
46"[A:approximate-regexp?\bagrep\b mode: approximate regular expression \apatterns\a (not implemented.)]"
47
48"[C:context?Set the matched line context \abefore\a and \aafter\a count."
49"	By default only matched lines are printed.]:?"
50"		[before[,after]]:=2,2]"
51"[c:count?Only print a matching line count for each file.]"
52"[e:expression|pattern|regexp?Specify a matching \apattern\a. More than one"
53"	\apattern\a implies alternation. If this option is specified"
54"	then the command line \apattern\a must be omitted.]:"
55"		[pattern]"
56"[f:file?Each line in \apattern-file\a is a \apattern\a, placed into a single"
57"	alternating expression.]:"
58"		[pattern-file]"
59"[H:filename|with-filename?Prefix each matched line with the containing file name.]"
60"[h:no-filename?Suppress containing file name prefix for each matched line.]"
61"[i:ignore-case?Ignore case when matching.]"
62"[l:files-with-matches?Only print file names with at least one match.]"
63"[L:files-without-matches?Only print file names with no matches.]"
64"[b:highlight?Highlight matches using the ansi terminal bold sequence.]"
65"[v:invert-match|revert-match?Invert the \apattern\a match sense.]"
66"[m:label?All patterns must be of the form \alabel\a:\apattern\a. Match and"
67"	count output will be prefixed by the corresponding \alabel\a:.]"
68"[O:lenient?Enable lenient \apattern\a interpretation. This is the default.]"
69"[x:line-match|line-regexp?Force \apatterns\a to match complete lines.]"
70"[n:number|line-number?Prefix each matched line with its line number.]"
71"[N:name?Set the standard input file name prefix to"
72"	\aname\a.]:[name:=empty]"
73"[q:quiet|silent?Do not print matching lines.]"
74"[S:strict?Enable strict \apattern\a interpretation with diagnostics.]"
75"[s:suppress|no-messages?Suppress error and warning messages.]"
76"[t:total?Only print a single matching line count for all files.]"
77"[T:test?Enable implementation specific tests.]:"
78"		[test]"
79"[w:word-match|word-regexp?Force \apatterns\a to match complete words.]"
80"[a?Ignored for GNU compatibility.]"
81"\n"
82"\n[ pattern ] [ file ... ]\n"
83"\n"
84"[+DIAGNOSTICS?Exit status 0 if matches were found, 1 if no matches were found,"
85"	where \b-v\b invertes the exit status. Exit status 2 for other"
86"	errors that are accompanied by a message on the standard error.]"
87"[+SEE ALSO?\bed\b(1), \bsed\b(1), \bperl\b(1), \bregex\b(3)]"
88"[+CAVEATS?Some expressions of necessity require exponential space"
89"	and/or time.]"
90"[+BUGS?Some expressions may use sub-optimal algorithms. For example,"
91"	don't use this implementation to compute primes.]"
92;
93
94#include <ast.h>
95#include <ctype.h>
96#include <ccode.h>
97#include <error.h>
98#include <regex.h>
99
100#ifndef EISDIR
101#define EISDIR		(-1)
102#endif
103
104/*
105 * snarfed from Doug McElroy's C++ version
106 *
107 * this grep is based on the Posix re package.
108 * unfortunately it has to have a nonstandard interface.
109 * 1. fgrep does not have usual operators. REG_LITERAL
110 * caters for this.
111 * 2. grep allows null expressions, hence REG_NULL.
112 * 3. it may be possible to combine the multiple
113 * patterns of grep into single patterns.  important
114 * special cases are handled by regcomb().
115 * 4. anchoring by -x has to be done separately from
116 * compilation (remember that fgrep has no ^ or $ operator),
117 * hence REG_LEFT|REG_RIGHT.  (An honest, but slow alternative:
118 * run regexec with REG_NOSUB off and nmatch=1 and check
119 * whether the match is full length)
120 */
121
122typedef struct Item_s			/* list item - sue me for waste	*/
123{
124	struct Item_s*	next;		/* next in list			*/
125	regex_t		re;		/* compiled re			*/
126	Sfulong_t	hits;		/* labeled pattern matches	*/
127	Sfulong_t	total;		/* total hits			*/
128	char		string[1];	/* string value			*/
129} Item_t;
130
131typedef struct List_s			/* generic list			*/
132{
133	Item_t*		head;		/* list head			*/
134	Item_t*		tail;		/* list tail			*/
135} List_t;
136
137typedef struct State_s			/* program state		*/
138{
139	struct
140	{
141	char*		base;		/* sfsetbuf buffer		*/
142	size_t		size;		/* sfsetbuf size		*/
143	int		noshare;	/* turn off SF_SHARE		*/
144	}		buffer;
145
146	List_t		file;		/* pattern file list		*/
147	List_t		pattern;	/* pattern list			*/
148	List_t		re;		/* re list			*/
149
150	regmatch_t	posvec[1];	/* match position vector	*/
151	regmatch_t*	pos;		/* match position pointer	*/
152	int		posnum;		/* number of match positions	*/
153
154	int		any;		/* if any pattern hit		*/
155	int		list;		/* list files with hits		*/
156	int		notfound;	/* some input file not found	*/
157	int		options;	/* regex options		*/
158
159	Sfulong_t	hits;		/* total matched pattern count	*/
160
161	unsigned char	byline;		/* multiple pattern line by line*/
162	unsigned char	count;		/* count number of hits		*/
163	unsigned char	label;		/* all patterns labeled		*/
164	unsigned char	match;		/* match sense			*/
165	unsigned char	query;		/* return status but no output	*/
166	unsigned char	number;		/* line numbers			*/
167	unsigned char	prefix;		/* print file prefix		*/
168	unsigned char	suppress;	/* no unopenable file messages	*/
169	unsigned char	words;		/* word matches only		*/
170} State_s;
171
172static void
173addre(State_s *state, List_t* p, char* s)
174{
175	int	c;
176	char*	b;
177	Item_t*	x;
178	Sfio_t*	t;
179
180	b = s;
181	if (state->label)
182	{
183		if (!(s = strchr(s, ':')))
184			error(3, "%s: label:pattern expected", b);
185		c = s - b;
186		s++;
187	}
188	else
189		c = 0;
190	if (!(x = newof(0, Item_t, 1, c)))
191		error(ERROR_SYSTEM|3, "out of space (pattern `%s')", b);
192	if (c)
193		memcpy(x->string, b, c);
194	if (state->words)
195	{
196		if (!(t = sfstropen()))
197			error(ERROR_SYSTEM|3, "out of space (word pattern `%s')", s);
198		if (!(state->options & REG_AUGMENTED))
199			sfputc(t, '\\');
200		sfputc(t, '<');
201		sfputr(t, s, -1);
202		if (!(state->options & REG_AUGMENTED))
203			sfputc(t, '\\');
204		sfputc(t, '>');
205		if (!(s = sfstruse(t)))
206			error(ERROR_SYSTEM|3, "out of space");
207	}
208	else
209		t = 0;
210	if (c = regcomp(&x->re, s, state->options|REG_MULTIPLE))
211		regfatal(&x->re, 3, c);
212	if (t)
213		sfstrclose(t);
214	if (!p->head)
215	{
216		p->head = p->tail = x;
217		if (state->number || !regrecord(&x->re))
218			state->byline = 1;
219	}
220	else if (state->label || regcomb(&p->tail->re, &x->re))
221	{
222		p->tail = p->tail->next = x;
223		if (!state->byline && (state->number || !state->label || !regrecord(&x->re)))
224			state->byline = 1;
225	}
226	else
227		free(x);
228}
229
230static void
231addstring(State_s *state, List_t* p, char* s)
232{
233	Item_t*	x;
234
235	if (!(x = newof(0, Item_t, 1, strlen(s))))
236		error(ERROR_SYSTEM|3, "out of space (string `%s')", s);
237	strcpy(x->string, s);
238	if (p->head)
239		p->tail->next = x;
240	else
241		p->head = x;
242	p->tail = x;
243}
244
245static void
246compile(State_s *state)
247{
248	int	line;
249	size_t	n;
250	char*	s;
251	char*	t;
252	char*	file;
253	Item_t*	x;
254	Sfio_t*	f;
255
256	for (x = state->pattern.head; x; x = x->next)
257		addre(state, &state->re, x->string);
258	for (x = state->file.head; x; x = x->next)
259	{
260		s = x->string;
261		if (!(f = sfopen(NiL, s, "r")))
262			error(ERROR_SYSTEM|4, "%s: cannot open", s);
263		else
264		{
265			file = error_info.file;
266			error_info.file = s;
267			line = error_info.line;
268			error_info.line = 0;
269			while (s = (char*)sfreserve(f, SF_UNBOUND, SF_LOCKR))
270			{
271				if (!(n = sfvalue(f)))
272					break;
273				if (s[n - 1] != '\n')
274				{
275					for (t = s + n; t > s && *--t != '\n'; t--);
276					if (t == s)
277					{
278						sfread(f, s, 0);
279						break;
280					}
281					n = t - s + 1;
282				}
283				s[n - 1] = 0;
284				addre(state, &state->re, s);
285				s[n - 1] = '\n';
286				sfread(f, s, n);
287			}
288			while ((s = sfgetr(f, '\n', 1)) || (s = sfgetr(f, '\n', -1)))
289			{
290				error_info.line++;
291				addre(state, &state->re, s);
292			}
293			error_info.file = file;
294			error_info.line = line;
295			sfclose(f);
296		}
297	}
298	if (!state->re.head)
299		error(3, "no pattern");
300}
301
302static void
303highlight(Sfio_t* sp, const char* s, int n, int so, int eo)
304{
305	static const char	bold[] =	{CC_esc,'[','1','m'};
306	static const char	normal[] =	{CC_esc,'[','0','m'};
307
308	sfwrite(sp, s, so);
309	sfwrite(sp, bold, sizeof(bold));
310	sfwrite(sp, s + so, eo - so);
311	sfwrite(sp, normal, sizeof(normal));
312	sfwrite(sp, s + eo, n - eo);
313}
314
315typedef struct
316{
317    State_s *state;
318    Item_t  *item;
319} record_handle;
320
321static int
322record(void* handle, const char* s, size_t len)
323{
324	record_handle	*r_x = (record_handle *)handle;
325	State_s		*state = r_x->state;
326	Item_t		*item  = r_x->item;
327
328	item->hits++;
329	if (state->query || state->list)
330		return -1;
331	if (!state->count)
332	{
333		if (state->prefix)
334			sfprintf(sfstdout, "%s:", error_info.file);
335		if (state->label)
336			sfprintf(sfstdout, "%s:", item->string);
337		if (state->pos)
338			highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
339		else
340			sfwrite(sfstdout, s, len + 1);
341	}
342	return 0;
343}
344
345static void
346execute(State_s *state, Sfio_t* input, char* name)
347{
348	register char*	s;
349	char*		file;
350	Item_t*		x;
351	size_t		len;
352	int		result;
353	int		line;
354
355	Sfulong_t	hits = 0;
356
357	if (state->buffer.noshare)
358		sfset(input, SF_SHARE, 0);
359	if (state->buffer.size)
360		sfsetbuf(input, state->buffer.base, state->buffer.size);
361	if (!name)
362		name = "/dev/stdin";
363	file = error_info.file;
364	error_info.file = name;
365	line = error_info.line;
366	error_info.line = 0;
367	if (state->byline)
368	{
369		for (;;)
370		{
371			error_info.line++;
372			if (s = sfgetr(input, '\n', 0))
373				len = sfvalue(input) - 1;
374			else if (s = sfgetr(input, '\n', -1))
375			{
376				len = sfvalue(input);
377				s[len] = '\n';
378#if _you_like_the_noise
379				error(1, "newline appended");
380#endif
381			}
382			else
383			{
384				if (sferror(input) && errno != EISDIR)
385					error(ERROR_SYSTEM|2, "read error");
386				break;
387			}
388			x = state->re.head;
389			do
390			{
391				if (!(result = regnexec(&x->re, s, len, state->posnum, state->pos, 0)))
392				{
393					if (!state->label)
394						break;
395					x->hits++;
396					if (state->query || state->list)
397						goto done;
398					if (!state->count)
399					{
400						if (state->prefix)
401							sfprintf(sfstdout, "%s:", name);
402						if (state->number)
403							sfprintf(sfstdout, "%d:", error_info.line);
404						sfprintf(sfstdout, "%s:", x->string);
405						if (state->pos)
406							highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
407						else
408							sfwrite(sfstdout, s, len + 1);
409					}
410				}
411				else if (result != REG_NOMATCH)
412					regfatal(&x->re, 3, result);
413			} while (x = x->next);
414			if (!state->label && (x != 0) == state->match)
415			{
416				hits++;
417				if (state->query || state->list)
418					break;
419				if (!state->count)
420				{
421					if (state->prefix)
422						sfprintf(sfstdout, "%s:", name);
423					if (state->number)
424						sfprintf(sfstdout, "%d:", error_info.line);
425					if (state->pos)
426						highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
427					else
428						sfwrite(sfstdout, s, len + 1);
429				}
430			}
431		}
432	}
433	else
434	{
435		register char*	e;
436		register char*	t;
437		char*		r;
438
439		static char*	span = 0;
440		static size_t	spansize = 0;
441
442		s = e = 0;
443		for (;;)
444		{
445			if (s < e)
446			{
447				t = span;
448				for (;;)
449				{
450					len = 2 * (e - s) + t - span + 1;
451					len = roundof(len, SF_BUFSIZE);
452					if (spansize < len)
453					{
454						spansize = len;
455						len = t - span;
456						if (!(span = newof(span, char, spansize, 0)))
457							error(ERROR_SYSTEM|3, "%s: line longer than %lu characters", name, len + e - s);
458						t = span + len;
459					}
460					len = e - s;
461					memcpy(t, s, len);
462					t += len;
463					if (!(s = sfreserve(input, SF_UNBOUND, 0)) || (len = sfvalue(input)) <= 0)
464					{
465						if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
466							error(ERROR_SYSTEM|2, "%s: read error", name);
467						break;
468					}
469					else if (!(e = memchr(s, '\n', len)))
470						e = s + len;
471					else
472					{
473						r = s + len;
474						len = (e - s) + t - span;
475						len = roundof(len, SF_BUFSIZE);
476						if (spansize < len)
477						{
478							spansize = len;
479							len = t - span;
480							if (!(span = newof(span, char, spansize, 0)))
481								error(ERROR_SYSTEM|3, "%s: line longer than %lu characters", name, len + e - s);
482							t = span + len;
483						}
484						len = e - s;
485						memcpy(t, s, len);
486						t += len;
487						s += len + 1;
488						e = r;
489						break;
490					}
491				}
492				*t = '\n';
493				x = state->re.head;
494				do
495				{
496					record_handle r_x = { state, x };
497					if ((result = regrexec(&x->re, span, t - span, state->posnum, state->pos, state->options, '\n', (void*)&r_x, record)) < 0)
498						goto done;
499					if (result && result != REG_NOMATCH)
500						regfatal(&x->re, 3, result);
501				} while (x = x->next);
502				if (!s)
503					break;
504			}
505			else
506			{
507				if (!(s = sfreserve(input, SF_UNBOUND, 0)))
508				{
509					if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
510						error(ERROR_SYSTEM|2, "%s: read error", name);
511					break;
512				}
513				if ((len = sfvalue(input)) <= 0)
514					break;
515				e = s + len;
516			}
517			t = e;
518			while (t > s)
519				if (*--t == '\n')
520				{
521					x = state->re.head;
522					do
523					{
524						record_handle r_x = { state, x };
525						if ((result = regrexec(&x->re, s, t - s, state->posnum, state->pos, state->options, '\n', (void*)&r_x, record)) < 0)
526							goto done;
527						if (result && result != REG_NOMATCH)
528							regfatal(&x->re, 3, result);
529					} while (x = x->next);
530					s = t + 1;
531					break;
532				}
533		}
534	}
535 done:
536	error_info.file = file;
537	error_info.line = line;
538	if (state->byline && !state->label)
539	{
540		if (hits && state->list >= 0)
541			state->any = 1;
542		if (!state->query)
543		{
544			if (!state->list)
545			{
546				if (state->count)
547				{
548					if (state->count & 2)
549						state->hits += hits;
550					else
551					{
552						if (state->prefix)
553							sfprintf(sfstdout, "%s:", name);
554						sfprintf(sfstdout, "%I*u\n", sizeof(hits), hits);
555					}
556				}
557			}
558			else if ((hits != 0) == (state->list > 0))
559			{
560				if (state->list < 0)
561					state->any = 1;
562				sfprintf(sfstdout, "%s\n", name);
563			}
564		}
565	}
566	else
567	{
568		x = state->re.head;
569		do
570		{
571			if (x->hits && state->list >= 0)
572			{
573				state->any = 1;
574				if (state->query)
575					break;
576			}
577			if (!state->query)
578			{
579				if (!state->list)
580				{
581					if (state->count)
582					{
583						if (state->count & 2)
584						{
585							x->total += x->hits;
586							state->hits += x->hits;
587						}
588						else
589						{
590							if (state->prefix)
591								sfprintf(sfstdout, "%s:", name);
592							if (state->label)
593								sfprintf(sfstdout, "%s:", x->string);
594							sfprintf(sfstdout, "%I*u\n", sizeof(x->hits), x->hits);
595						}
596					}
597				}
598				else if ((x->hits != 0) == (state->list > 0))
599				{
600					if (state->list < 0)
601						state->any = 1;
602					if (state->label)
603						sfprintf(sfstdout, "%s:%s\n", name, x->string);
604					else
605						sfprintf(sfstdout, "%s\n", name);
606				}
607			}
608			x->hits = 0;
609		} while (x = x->next);
610	}
611}
612
613
614static
615int grep_main(int argc, char** argv, void *context)
616{
617	int	c;
618	char*	s;
619	char*	h;
620	Sfio_t*	f;
621	State_s state;
622	memset(&state, 0, sizeof(state));
623
624	NoP(argc);
625	state.match = 1;
626	state.options = REG_FIRST|REG_NOSUB|REG_NULL;
627	h = 0;
628	if (strcmp(astconf("CONFORMANCE", NiL, NiL), "standard"))
629		state.options |= REG_LENIENT;
630	if (s = strrchr(argv[0], '/'))
631		s++;
632	else
633		s = argv[0];
634	switch (*s)
635	{
636	case 'e':
637	case 'E':
638		s = "egrep";
639		state.options |= REG_EXTENDED;
640		break;
641	case 'f':
642	case 'F':
643		s = "fgrep";
644		state.options |= REG_LITERAL;
645		break;
646	case 'p':
647	case 'P':
648		s = "pgrep";
649		state.options |= REG_EXTENDED|REG_LENIENT;
650		break;
651	case 'x':
652	case 'X':
653		s = "xgrep";
654		state.options |= REG_AUGMENTED;
655		break;
656	default:
657		s = "grep";
658		break;
659	}
660	error_info.id = s;
661	while (c = optget(argv, usage))
662		switch (c)
663		{
664		case 'E':
665			state.options |= REG_EXTENDED;
666			break;
667		case 'F':
668			state.options |= REG_LITERAL;
669			break;
670		case 'G':
671			state.options &= ~(REG_AUGMENTED|REG_EXTENDED);
672			break;
673		case 'H':
674			state.prefix = opt_info.num;
675			break;
676		case 'L':
677			state.list = -opt_info.num;
678			break;
679		case 'N':
680			h = opt_info.arg;
681			break;
682		case 'O':
683			state.options |= REG_LENIENT;
684			break;
685		case 'P':
686			state.options |= REG_EXTENDED|REG_LENIENT;
687			break;
688		case 'S':
689			state.options &= ~REG_LENIENT;
690			break;
691		case 'T':
692			s = opt_info.arg;
693			switch (*s)
694			{
695			case 'b':
696			case 'm':
697				c = *s++;
698				state.buffer.size = strton(s, &s, NiL, 1);
699				if (c == 'b' && !(state.buffer.base = newof(0, char, state.buffer.size, 0)))
700					error(ERROR_SYSTEM|3, "out of space [test buffer]");
701				if (*s)
702					error(3, "%s: invalid characters after test", s);
703				break;
704			case 'f':
705				state.options |= REG_FIRST;
706				break;
707			case 'l':
708				state.options |= REG_LEFT;
709				break;
710			case 'n':
711				state.buffer.noshare = 1;
712				break;
713			case 'r':
714				state.options |= REG_RIGHT;
715				break;
716			default:
717				error(3, "%s: unknown test", s);
718				break;
719			}
720			break;
721		case 'X':
722			state.options |= REG_AUGMENTED;
723			break;
724		case 'a':
725			break;
726		case 'b':
727			state.options &= ~(REG_FIRST|REG_NOSUB);
728			break;
729		case 'c':
730			state.count |= 1;
731			break;
732		case 'e':
733			addstring(&state, &state.pattern, opt_info.arg);
734			break;
735		case 'f':
736			addstring(&state, &state.file, opt_info.arg);
737			break;
738		case 'h':
739			state.prefix = 2;
740			break;
741		case 'i':
742			state.options |= REG_ICASE;
743			break;
744		case 'l':
745			state.list = opt_info.num;
746			break;
747		case 'm':
748			state.label = 1;
749			break;
750		case 'n':
751			state.number = 1;
752			break;
753		case 'q':
754			state.query = 1;
755			break;
756		case 's':
757			state.suppress = opt_info.num;
758			break;
759		case 't':
760			state.count |= 2;
761			break;
762		case 'v':
763			if (state.match = !opt_info.num)
764				state.options &= ~REG_INVERT;
765			else
766				state.options |= REG_INVERT;
767			break;
768		case 'w':
769			state.words = 1;
770			break;
771		case 'x':
772			state.options |= REG_LEFT|REG_RIGHT;
773			break;
774		case '?':
775			error(ERROR_USAGE|4, "%s", opt_info.arg);
776			break;
777		case ':':
778			error(2, "%s", opt_info.arg);
779			break;
780		default:
781			error(3, "%s: not implemented", opt_info.name);
782			break;
783		}
784	argv += opt_info.index;
785	if ((state.options & REG_LITERAL) && (state.options & (REG_AUGMENTED|REG_EXTENDED)))
786		error(3, "-F and -A or -P or -X are incompatible");
787	if ((state.options & REG_LITERAL) && state.words)
788		error(ERROR_SYSTEM|3, "-F and -w are incompatible");
789	if (!state.file.head && !state.pattern.head)
790	{
791		if (!argv[0])
792			error(3, "no pattern");
793		addstring(&state, &state.pattern, *argv++);
794	}
795	if (!(state.options & (REG_FIRST|REG_NOSUB)))
796	{
797		if (state.count || state.list || state.query || (state.options & REG_INVERT))
798			state.options |= REG_FIRST|REG_NOSUB;
799		else
800		{
801			state.pos = state.posvec;
802			state.posnum = elementsof(state.posvec);
803		}
804	}
805	compile(&state);
806	if (!argv[0])
807	{
808		state.prefix = h ? 1 : 0;
809		execute(&state, sfstdin, h);
810	}
811	else
812	{
813		if (state.prefix > 1)
814			state.prefix = 0;
815		else if (argv[1])
816			state.prefix = 1;
817		while (s = *argv++)
818		{
819			if (f = sfopen(NiL, s, "r"))
820			{
821				execute(&state, f, s);
822				sfclose(f);
823				if (state.query && state.any)
824					break;
825			}
826			else
827			{
828				state.notfound = 1;
829				if (!state.suppress)
830					error(ERROR_SYSTEM|2, "%s: cannot open", s);
831			}
832		}
833	}
834	if ((state.count & 2) && !state.query && !state.list)
835	{
836		if (state.label)
837		{
838			Item_t*		x;
839
840			x = state.re.head;
841			do
842			{
843				sfprintf(sfstdout, "%s:%I*u\n", x->string, sizeof(x->total), x->total);
844			} while (x = x->next);
845		}
846		else
847			sfprintf(sfstdout, "%I*u\n", sizeof(state.hits), state.hits);
848	}
849	return (state.notfound && !state.query) ? 2 : !state.any;
850}
851
852
853int b_egrep(int argc, char** argv, void *context)
854{
855	return grep_main(argc, argv, context);
856}
857
858int b_grep(int argc, char** argv, void *context)
859{
860	return grep_main(argc, argv, context);
861}
862
863int b_fgrep(int argc, char** argv, void *context)
864{
865	return grep_main(argc, argv, context);
866}
867
868int b_pgrep(int argc, char** argv, void *context)
869{
870	return grep_main(argc, argv, context);
871}
872
873int b_xgrep(int argc, char** argv, void *context)
874{
875	return grep_main(argc, argv, context);
876}
877