1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1995-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
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 
22 static const char usage[] =
23 "[-?\n@(#)$Id: grep (AT&T Research) 2012-05-07 $\n]"
24 USAGE_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 "	If ,\aafter\a is omitted then it is set to \abefore\a."
50 "	By default only matched lines are printed.]:?"
51 "		[before[,after]]:=2,2]"
52 "[c:count?Only print a matching line count for each file.]"
53 "[e:expression|pattern|regexp?Specify a matching \apattern\a. More than one"
54 "	\apattern\a implies alternation. If this option is specified"
55 "	then the command line \apattern\a must be omitted.]:"
56 "		[pattern]"
57 "[f:file?Each line in \apattern-file\a is a \apattern\a, placed into a single"
58 "	alternating expression.]:"
59 "		[pattern-file]"
60 "[H:filename|with-filename?Prefix each matched line with the containing file name.]"
61 "[h:no-filename?Suppress containing file name prefix for each matched line.]"
62 "[i:ignore-case?Ignore case when matching.]"
63 "[l:files-with-matches?Only print file names with at least one match.]"
64 "[L:files-without-matches?Only print file names with no matches.]"
65 "[b:highlight?Highlight matches using the ansi terminal bold sequence.]"
66 "[v:invert-match|revert-match?Invert the \apattern\a match sense.]"
67 "[m:label?All patterns must be of the form \alabel\a:\apattern\a. Match and"
68 "	count output will be prefixed by the corresponding \alabel\a:.]"
69 "[O:lenient?Enable lenient \apattern\a interpretation. This is the default.]"
70 "[x:line-match|line-regexp?Force \apatterns\a to match complete lines.]"
71 "[n:number|line-number?Prefix each matched line with its line number.]"
72 "[N:name?Set the standard input file name prefix to"
73 "	\aname\a.]:[name:=empty]"
74 "[q:quiet|silent?Do not print matching lines.]"
75 "[r|R:recursive?Recursively process all files in each named directory. "
76     "Use \btw -e\b \aexpression\a \bgrep ...\b to control the directory "
77     "traversal.]"
78 "[S:strict?Enable strict \apattern\a interpretation with diagnostics.]"
79 "[s:suppress|no-messages?Suppress error and warning messages.]"
80 "[t:total?Only print a single matching line count for all files.]"
81 "[T:test?Enable implementation specific tests.]:"
82 "		[test]"
83 "[w:word-match|word-regexp?Force \apatterns\a to match complete words.]"
84 "[a?Ignored for GNU compatibility.]"
85 "[Y:color|colour?Ignored for GNU compatibility.]:[when]"
86 "\n"
87 "\n[ pattern ] [ file ... ]\n"
88 "\n"
89 "[+DIAGNOSTICS?Exit status 0 if matches were found, 1 if no matches were found,"
90 "	where \b-v\b invertes the exit status. Exit status 2 for other"
91 "	errors that are accompanied by a message on the standard error.]"
92 "[+SEE ALSO?\bed\b(1), \bsed\b(1), \bperl\b(1), \btw\b(1), \bregex\b(3)]"
93 "[+CAVEATS?Some expressions of necessity require exponential space"
94 "	and/or time.]"
95 "[+BUGS?Some expressions may use sub-optimal algorithms. For example,"
96 "	don't use this implementation to compute primes.]"
97 ;
98 
99 #include <ast.h>
100 #include <ctype.h>
101 #include <ccode.h>
102 #include <error.h>
103 #include <fts.h>
104 #include <regex.h>
105 
106 #ifndef EISDIR
107 #define EISDIR		(-1)
108 #endif
109 
110 /*
111  * snarfed from Doug McElroy's C++ version
112  *
113  * this grep is based on the Posix re package.
114  * unfortunately it has to have a nonstandard interface.
115  * 1. fgrep does not have usual operators. REG_LITERAL
116  * caters for this.
117  * 2. grep allows null expressions, hence REG_NULL.
118  * 3. it may be possible to combine the multiple
119  * patterns of grep into single patterns.  important
120  * special cases are handled by regcomb().
121  * 4. anchoring by -x has to be done separately from
122  * compilation (remember that fgrep has no ^ or $ operator),
123  * hence REG_LEFT|REG_RIGHT.  (An honest, but slow alternative:
124  * run regexec with REG_NOSUB off and nmatch=1 and check
125  * whether the match is full length)
126  */
127 
128 typedef struct Item_s			/* list item - sue me for waste	*/
129 {
130 	struct Item_s*	next;		/* next in list			*/
131 	regex_t		re;		/* compiled re			*/
132 	Sfulong_t	hits;		/* labeled pattern matches	*/
133 	Sfulong_t	total;		/* total hits			*/
134 	char		string[1];	/* string value			*/
135 } Item_t;
136 
137 typedef struct List_s			/* generic list			*/
138 {
139 	Item_t*		head;		/* list head			*/
140 	Item_t*		tail;		/* list tail			*/
141 } List_t;
142 
143 static struct State_s			/* program state		*/
144 {
145 	struct
146 	{
147 	char*		base;		/* sfsetbuf buffer		*/
148 	size_t		size;		/* sfsetbuf size		*/
149 	int		noshare;	/* turn off SF_SHARE		*/
150 	}		buffer;
151 
152 	List_t		file;		/* pattern file list		*/
153 	List_t		pattern;	/* pattern list			*/
154 	List_t		re;		/* re list			*/
155 
156 	regmatch_t	posvec[1];	/* match position vector	*/
157 	regmatch_t*	pos;		/* match position pointer	*/
158 	int		posnum;		/* number of match positions	*/
159 
160 	int		any;		/* if any pattern hit		*/
161 	int		after;		/* # lines to list after match	*/
162 	int		before;		/* # lines to list before match	*/
163 	int		list;		/* list files with hits		*/
164 	int		notfound;	/* some input file not found	*/
165 	int		options;	/* regex options		*/
166 
167 	Sfulong_t	hits;		/* total matched pattern count	*/
168 
169 	unsigned char	byline;		/* multiple pattern line by line*/
170 	unsigned char	count;		/* count number of hits		*/
171 	unsigned char	label;		/* all patterns labeled		*/
172 	unsigned char	match;		/* match sense			*/
173 	unsigned char	query;		/* return status but no output	*/
174 	unsigned char	number;		/* line numbers			*/
175 	unsigned char	prefix;		/* print file prefix		*/
176 	unsigned char	suppress;	/* no unopenable file messages	*/
177 	unsigned char	words;		/* word matches only		*/
178 } state;
179 
180 static void
addre(List_t * p,char * s)181 addre(List_t* p, char* s)
182 {
183 	int	c;
184 	char*	b;
185 	Item_t*	x;
186 	Sfio_t*	t;
187 
188 	b = s;
189 	if (state.label)
190 	{
191 		if (!(s = strchr(s, ':')))
192 			error(3, "%s: label:pattern expected", b);
193 		c = s - b;
194 		s++;
195 	}
196 	else
197 		c = 0;
198 	if (!(x = newof(0, Item_t, 1, c)))
199 		error(ERROR_SYSTEM|3, "out of space (pattern `%s')", b);
200 	if (c)
201 		memcpy(x->string, b, c);
202 	if (state.words)
203 	{
204 		if (!(t = sfstropen()))
205 			error(ERROR_SYSTEM|3, "out of space (word pattern `%s')", s);
206 		if (!(state.options & REG_AUGMENTED))
207 			sfputc(t, '\\');
208 		sfputc(t, '<');
209 		sfputr(t, s, -1);
210 		if (!(state.options & REG_AUGMENTED))
211 			sfputc(t, '\\');
212 		sfputc(t, '>');
213 		if (!(s = sfstruse(t)))
214 			error(ERROR_SYSTEM|3, "out of space");
215 	}
216 	else
217 		t = 0;
218 	if (c = regcomp(&x->re, s, state.options|REG_MULTIPLE))
219 		regfatal(&x->re, 4, c);
220 	if (t)
221 		sfstrclose(t);
222 	if (!p->head)
223 	{
224 		p->head = p->tail = x;
225 		if (state.number || state.before || state.after || !regrecord(&x->re))
226 			state.byline = 1;
227 	}
228 	else if (state.label || regcomb(&p->tail->re, &x->re))
229 	{
230 		p->tail = p->tail->next = x;
231 		if (!state.byline && (state.number || !state.label || !regrecord(&x->re)))
232 			state.byline = 1;
233 	}
234 	else
235 		free(x);
236 }
237 
238 static void
addstring(List_t * p,char * s)239 addstring(List_t* p, char* s)
240 {
241 	Item_t*	x;
242 
243 	if (!(x = newof(0, Item_t, 1, strlen(s))))
244 		error(ERROR_SYSTEM|3, "out of space (string `%s')", s);
245 	strcpy(x->string, s);
246 	if (p->head)
247 		p->tail->next = x;
248 	else
249 		p->head = x;
250 	p->tail = x;
251 }
252 
253 static void
compile(void)254 compile(void)
255 {
256 	int	line;
257 	size_t	n;
258 	char*	s;
259 	char*	t;
260 	char*	file;
261 	Item_t*	x;
262 	Sfio_t*	f;
263 
264 	for (x = state.pattern.head; x; x = x->next)
265 		addre(&state.re, x->string);
266 	for (x = state.file.head; x; x = x->next)
267 	{
268 		s = x->string;
269 		if (!(f = sfopen(NiL, s, "r")))
270 			error(ERROR_SYSTEM|4, "%s: cannot open", s);
271 		else
272 		{
273 			file = error_info.file;
274 			error_info.file = s;
275 			line = error_info.line;
276 			error_info.line = 0;
277 			while (s = (char*)sfreserve(f, SF_UNBOUND, SF_LOCKR))
278 			{
279 				if (!(n = sfvalue(f)))
280 					break;
281 				if (s[n - 1] != '\n')
282 				{
283 					for (t = s + n; t > s && *--t != '\n'; t--);
284 					if (t == s)
285 					{
286 						sfread(f, s, 0);
287 						break;
288 					}
289 					n = t - s + 1;
290 				}
291 				s[n - 1] = 0;
292 				addre(&state.re, s);
293 				s[n - 1] = '\n';
294 				sfread(f, s, n);
295 			}
296 			while ((s = sfgetr(f, '\n', 1)) || (s = sfgetr(f, '\n', -1)))
297 			{
298 				error_info.line++;
299 				addre(&state.re, s);
300 			}
301 			error_info.file = file;
302 			error_info.line = line;
303 			sfclose(f);
304 		}
305 	}
306 	if (!state.re.head)
307 		error(3, "no pattern");
308 }
309 
310 static void
highlight(Sfio_t * sp,const char * s,int n,int so,int eo)311 highlight(Sfio_t* sp, const char* s, int n, int so, int eo)
312 {
313 	static const char	bold[] =	{CC_esc,'[','1','m'};
314 	static const char	normal[] =	{CC_esc,'[','0','m'};
315 
316 	sfwrite(sp, s, so);
317 	sfwrite(sp, bold, sizeof(bold));
318 	sfwrite(sp, s + so, eo - so);
319 	sfwrite(sp, normal, sizeof(normal));
320 	sfwrite(sp, s + eo, n - eo);
321 }
322 
323 static int
record(void * handle,const char * s,size_t len)324 record(void* handle, const char* s, size_t len)
325 {
326 	Item_t*		item = (Item_t*)handle;
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 
345 static void
execute(Sfio_t * input,char * name)346 execute(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 = "(standard input)"; /* posix! (ast prefers /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, 4, 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 				if (!(len = t - span))
494 					len++;
495 				x = state.re.head;
496 				do
497 				{
498 					if ((result = regrexec(&x->re, span, len, state.posnum, state.pos, state.options, '\n', (void*)x, record)) < 0)
499 						goto done;
500 					if (result && result != REG_NOMATCH)
501 						regfatal(&x->re, 4, result);
502 				} while (x = x->next);
503 				if (!s)
504 					break;
505 			}
506 			else
507 			{
508 				if (!(s = sfreserve(input, SF_UNBOUND, 0)))
509 				{
510 					if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
511 						error(ERROR_SYSTEM|2, "%s: read error", name);
512 					break;
513 				}
514 				if ((len = sfvalue(input)) <= 0)
515 					break;
516 				e = s + len;
517 			}
518 			t = e;
519 			while (t > s)
520 				if (*--t == '\n')
521 				{
522 					len = t - s;
523 					if (!len || t > s && *(t - 1) == '\n')
524 						len++;
525 					x = state.re.head;
526 					do
527 					{
528 						if ((result = regrexec(&x->re, s, len, state.posnum, state.pos, state.options, '\n', (void*)x, record)) < 0)
529 							goto done;
530 						if (result && result != REG_NOMATCH)
531 							regfatal(&x->re, 4, result);
532 					} while (x = x->next);
533 					s = t + 1;
534 					break;
535 				}
536 		}
537 	}
538  done:
539 	error_info.file = file;
540 	error_info.line = line;
541 	if (state.byline && !state.label)
542 	{
543 		if (hits && state.list >= 0)
544 			state.any = 1;
545 		if (!state.query)
546 		{
547 			if (!state.list)
548 			{
549 				if (state.count)
550 				{
551 					if (state.count & 2)
552 						state.hits += hits;
553 					else
554 					{
555 						if (state.prefix)
556 							sfprintf(sfstdout, "%s:", name);
557 						sfprintf(sfstdout, "%I*u\n", sizeof(hits), hits);
558 					}
559 				}
560 			}
561 			else if ((hits != 0) == (state.list > 0))
562 			{
563 				if (state.list < 0)
564 					state.any = 1;
565 				sfprintf(sfstdout, "%s\n", name);
566 			}
567 		}
568 	}
569 	else
570 	{
571 		x = state.re.head;
572 		do
573 		{
574 			if (x->hits && state.list >= 0)
575 			{
576 				state.any = 1;
577 				if (state.query)
578 					break;
579 			}
580 			if (!state.query)
581 			{
582 				if (!state.list)
583 				{
584 					if (state.count)
585 					{
586 						if (state.count & 2)
587 						{
588 							x->total += x->hits;
589 							state.hits += x->hits;
590 						}
591 						else
592 						{
593 							if (state.prefix)
594 								sfprintf(sfstdout, "%s:", name);
595 							if (state.label)
596 								sfprintf(sfstdout, "%s:", x->string);
597 							sfprintf(sfstdout, "%I*u\n", sizeof(x->hits), x->hits);
598 						}
599 					}
600 				}
601 				else if ((x->hits != 0) == (state.list > 0))
602 				{
603 					if (state.list < 0)
604 						state.any = 1;
605 					if (state.label)
606 						sfprintf(sfstdout, "%s:%s\n", name, x->string);
607 					else
608 						sfprintf(sfstdout, "%s\n", name);
609 				}
610 			}
611 			x->hits = 0;
612 		} while (x = x->next);
613 	}
614 }
615 
616 int
main(int argc,char ** argv)617 main(int argc, char** argv)
618 {
619 	int	c;
620 	char*	s;
621 	char*	h;
622 	Sfio_t*	f;
623 	int	flags;
624 	int	old = 0;
625 	FTS*	fts;
626 	FTSENT*	ent;
627 
628 	NoP(argc);
629 	flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
630 	state.match = 1;
631 	state.options = REG_FIRST|REG_NOSUB|REG_NULL;
632 	h = 0;
633 	if (!conformance(0, 0))
634 		state.options |= REG_LENIENT;
635 	if (s = strrchr(argv[0], '/'))
636 		s++;
637 	else
638 		s = argv[0];
639 	switch (*s)
640 	{
641 	case 'e':
642 	case 'E':
643 		s = "egrep";
644 		state.options |= REG_EXTENDED;
645 		break;
646 	case 'f':
647 	case 'F':
648 		s = "fgrep";
649 		state.options |= REG_LITERAL;
650 		break;
651 	case 'p':
652 	case 'P':
653 		s = "pgrep";
654 		state.options |= REG_EXTENDED|REG_LENIENT;
655 		break;
656 	case 'x':
657 	case 'X':
658 		s = "xgrep";
659 		state.options |= REG_AUGMENTED;
660 		break;
661 	default:
662 		s = "grep";
663 		break;
664 	}
665 	error_info.id = s;
666 	while (c = optget(argv, usage))
667 		switch (c)
668 		{
669 		case 'C':
670 			if (opt_info.arg)
671 			{
672 				state.before = (int)strtol(opt_info.arg, &s, 0);
673 				state.after = (*s == ',') ? (int)strtol(s + 1, &s, 0) : state.before;
674 				if (*s)
675 					error(3, "%s: invalid context line count", s);
676 			}
677 			else
678 				state.before = state.after = 2;
679 			break;
680 		case 'E':
681 			state.options |= REG_EXTENDED;
682 			break;
683 		case 'F':
684 			state.options |= REG_LITERAL;
685 			break;
686 		case 'G':
687 			state.options &= ~(REG_AUGMENTED|REG_EXTENDED);
688 			break;
689 		case 'H':
690 			state.prefix = opt_info.num;
691 			break;
692 		case 'L':
693 			state.list = -opt_info.num;
694 			break;
695 		case 'N':
696 			h = opt_info.arg;
697 			break;
698 		case 'O':
699 			state.options |= REG_LENIENT;
700 			break;
701 		case 'P':
702 			state.options |= REG_EXTENDED|REG_LENIENT;
703 			break;
704 		case 'S':
705 			state.options &= ~REG_LENIENT;
706 			break;
707 		case 'T':
708 			s = opt_info.arg;
709 			switch (*s)
710 			{
711 			case 'b':
712 			case 'm':
713 				c = *s++;
714 				state.buffer.size = strton(s, &s, NiL, 1);
715 				if (c == 'b' && !(state.buffer.base = newof(0, char, state.buffer.size, 0)))
716 					error(ERROR_SYSTEM|3, "out of space [test buffer]");
717 				if (*s)
718 					error(3, "%s: invalid characters after test", s);
719 				break;
720 			case 'f':
721 				state.options |= REG_FIRST;
722 				break;
723 			case 'l':
724 				state.options |= REG_LEFT;
725 				break;
726 			case 'n':
727 				state.buffer.noshare = 1;
728 				break;
729 			case 'r':
730 				state.options |= REG_RIGHT;
731 				break;
732 			default:
733 				error(3, "%s: unknown test", s);
734 				break;
735 			}
736 			break;
737 		case 'X':
738 			state.options |= REG_AUGMENTED;
739 			break;
740 		case 'a':
741 			break;
742 		case 'b':
743 			state.options &= ~(REG_FIRST|REG_NOSUB);
744 			break;
745 		case 'c':
746 			state.count |= 1;
747 			break;
748 		case 'e':
749 			addstring(&state.pattern, opt_info.arg);
750 			break;
751 		case 'f':
752 			addstring(&state.file, opt_info.arg);
753 			break;
754 		case 'h':
755 			state.prefix = 2;
756 			break;
757 		case 'i':
758 			state.options |= REG_ICASE;
759 			break;
760 		case 'l':
761 			state.list = opt_info.num;
762 			break;
763 		case 'm':
764 			state.label = 1;
765 			break;
766 		case 'n':
767 			state.number = 1;
768 			break;
769 		case 'q':
770 			state.query = 1;
771 			break;
772 		case 'r':
773 			if (opt_info.num)
774 				flags &= ~FTS_TOP;
775 			else
776 				old = 1;
777 			break;
778 		case 's':
779 			state.suppress = opt_info.num;
780 			break;
781 		case 't':
782 			state.count |= 2;
783 			break;
784 		case 'v':
785 			if (state.match = !opt_info.num)
786 				state.options &= ~REG_INVERT;
787 			else
788 				state.options |= REG_INVERT;
789 			break;
790 		case 'w':
791 			state.words = 1;
792 			break;
793 		case 'x':
794 			state.options |= REG_LEFT|REG_RIGHT;
795 			break;
796 		case 'Y':
797 			/* ignored for GNU compatibility */
798 			break;
799 		case '?':
800 			error(ERROR_USAGE|4, "%s", opt_info.arg);
801 			break;
802 		case ':':
803 			error(2, "%s", opt_info.arg);
804 			break;
805 		default:
806 			error(3, "%s: not implemented", opt_info.name);
807 			break;
808 		}
809 	argv += opt_info.index;
810 	if ((state.options & REG_LITERAL) && (state.options & (REG_AUGMENTED|REG_EXTENDED)))
811 		error(3, "-F and -A or -P or -X are incompatible");
812 	if ((state.options & REG_LITERAL) && state.words)
813 		error(ERROR_SYSTEM|3, "-F and -w are incompatible");
814 	if (!state.file.head && !state.pattern.head)
815 	{
816 		if (!argv[0])
817 			error(3, "no pattern");
818 		addstring(&state.pattern, *argv++);
819 	}
820 	if (!(state.options & (REG_FIRST|REG_NOSUB)))
821 	{
822 		if (state.count || state.list || state.query || (state.options & REG_INVERT))
823 			state.options |= REG_FIRST|REG_NOSUB;
824 		else
825 		{
826 			state.pos = state.posvec;
827 			state.posnum = elementsof(state.posvec);
828 		}
829 	}
830 	compile();
831 	if (!argv[0])
832 	{
833 		state.prefix = h ? 1 : 0;
834 		execute(sfstdin, h);
835 	}
836 	else if (old) /* just in case the fts logic is incompatible */
837 	{
838 		if (state.prefix > 1)
839 			state.prefix = 0;
840 		else if (argv[1])
841 			state.prefix = 1;
842 		while (s = *argv++)
843 		{
844 			if (f = sfopen(NiL, s, "r"))
845 			{
846 				execute(f, s);
847 				sfclose(f);
848 				if (state.query && state.any)
849 					break;
850 			}
851 			else
852 			{
853 				state.notfound = 1;
854 				if (!state.suppress)
855 					error(ERROR_SYSTEM|2, "%s: cannot open", s);
856 			}
857 		}
858 	}
859 	else
860 	{
861 		if (state.prefix > 1)
862 			state.prefix = 0;
863 		else if (!(flags & FTS_TOP) || argv[1])
864 			state.prefix = 1;
865 		if (!(fts = fts_open(argv, flags, NiL)))
866 			error(ERROR_SYSTEM|3, "%s: not found", argv[0]);
867 		while (ent = fts_read(fts))
868 			switch (ent->fts_info)
869 			{
870 			case FTS_F:
871 				if (f = sfopen(NiL, ent->fts_accpath, "r"))
872 				{
873 					execute(f, ent->fts_path);
874 					sfclose(f);
875 					if (state.query && state.any)
876 						goto quit;
877 					break;
878 				}
879 				/*FALLTHROUGH*/
880 			case FTS_NS:
881 			case FTS_SLNONE:
882 				state.notfound = 1;
883 				if (!state.suppress)
884 					error(ERROR_SYSTEM|2, "%s: cannot open", ent->fts_path);
885 				break;
886 			case FTS_DC:
887 				error(ERROR_WARNING|1, "%s: directory causes cycle", ent->fts_path);
888 				break;
889 			case FTS_DNR:
890 				error(ERROR_SYSTEM|2, "%s: cannot read directory", ent->fts_path);
891 				break;
892 			case FTS_DNX:
893 				error(ERROR_SYSTEM|2, "%s: cannot search directory", ent->fts_path);
894 				break;
895 			}
896  quit:
897 		fts_close(fts);
898 	}
899 	if ((state.count & 2) && !state.query && !state.list)
900 	{
901 		if (state.label)
902 		{
903 			Item_t*		x;
904 
905 			x = state.re.head;
906 			do
907 			{
908 				sfprintf(sfstdout, "%s:%I*u\n", x->string, sizeof(x->total), x->total);
909 			} while (x = x->next);
910 		}
911 		else
912 			sfprintf(sfstdout, "%I*u\n", sizeof(state.hits), state.hits);
913 	}
914 	return (state.notfound && !state.query) ? 2 : !state.any;
915 }
916