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 <glenn.s.fowler@gmail.com>                *
18 *              Doug McIlroy <doug@research.bell-labs.com>              *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 
23 static const char usage[] =
24 "[-?\n@(#)$Id: grep (AT&T Research) 2012-05-11 $\n]"
25 USAGE_LICENSE
26 "[--plugin?ksh]"
27 "[+NAME?grep - search lines in files for matching patterns]"
28 "[+DESCRIPTION?The \bgrep\b commands search the named input files for "
29     "lines containing a match for the given \apatterns\a. Matching lines are "
30     "printed by default. The standard input is searched if no files are "
31     "given or when the file \b-\b is specified.]"
32 "[+?There are six variants of \bgrep\b, each one using a different form "
33     "of \apattern\a, controlled either by option or the command path base "
34     "name. Details of each variant may be found in \bregex\b(3).]"
35     "{"
36         "[+grep?The default basic regular expressions (no "
37             "alternations.)]"
38         "[+egrep?Extended regular expressions (alternations, one or "
39             "more.)]"
40         "[+pgrep?\bperl\b(1) regular expressions (lenient extended.)]"
41         "[+xgrep?Augmented regular expressions (conjunction, negation.)]"
42         "[+fgrep?Fixed string expressions.]"
43         "[+agrep?Approximate regular expressions (not implemented.)]"
44     "}"
45 "[G:basic-regexp?\bgrep\b mode (default): basic regular expression "
46     "\apatterns\a.]"
47 "[E:extended-regexp?\begrep\b mode: extended regular expression "
48     "\apatterns\a.]"
49 "[X:augmented-regexp?\bxgrep\b mode: augmented regular expression "
50     "\apatterns\a.]"
51 "[P:perl-regexp?\bpgrep\b mode: \bperl\b(1) regular expression "
52     "\apatterns\a.]"
53 "[F:fixed-string?\bfgrep\b mode: fixed string \apatterns\a.]"
54 "[A:approximate-regexp?\bagrep\b mode: approximate regular expression "
55     "\apatterns\a (not implemented.)]"
56 "[C:context?Set the matched line context \abefore\a and \aafter\a count. "
57     "If ,\aafter\a is omitted then it is set to \abefore\a. By default only "
58     "matched lines are printed.]:?[before[,after]]:=2,2]"
59 "[c:count?Only print a matching line count for each file.]"
60 "[e:expression|pattern|regexp?Specify a matching \apattern\a. More than "
61     "one \apattern\a implies alternation. If this option is specified then "
62     "the command line \apattern\a must be omitted.]: [pattern]"
63 "[f:file?Each line in \apattern-file\a is a \apattern\a, placed into a "
64     "single alternating expression.]: [pattern-file]"
65 "[H:filename|with-filename?Prefix each matched line with the containing "
66     "file name.]"
67 "[h:no-filename?Suppress containing file name prefix for each matched "
68     "line.]"
69 "[i:ignore-case?Ignore case when matching.]"
70 "[l:files-with-matches?Only print file names with at least one match.]"
71 "[L:files-without-matches?Only print file names with no matches.]"
72 "[b:highlight?Highlight matches using the ansi terminal bold sequence.]"
73 "[v:invert-match|revert-match?Invert the \apattern\a match sense.]"
74 "[m:label?All patterns must be of the form \alabel\a:\apattern\a. Match "
75     "and count output will be prefixed by the corresponding \alabel\a:. At "
76     "most one label is output for each line; if more than one label matches "
77     "a line then it is undefined what label is output.]"
78 "[O:lenient?Enable lenient \apattern\a interpretation. This is the "
79     "default.]"
80 "[x:line-match|line-regexp?Force \apatterns\a to match complete lines.]"
81 "[n:number|line-number?Prefix each matched line with its line number.]"
82 "[N:name?Set the standard input file name prefix to "
83     "\aname\a.]:[name:=empty]"
84 "[q:quiet|silent?Do not print matching lines.]"
85 "[r|R:recursive?Recursively process all files in each named directory. "
86     "Use \btw -e\b \aexpression\a \bgrep ...\b to control the directory "
87     "traversal.]"
88 "[S:strict?Enable strict \apattern\a interpretation with diagnostics.]"
89 "[s:suppress|no-messages?Suppress error and warning messages.]"
90 "[t:total?Only print a single matching line count for all files.]"
91 "[T:test?Enable implementation specific tests.]: [test]"
92 "[w:word-match|word-regexp?Force \apatterns\a to match complete words.]"
93 "[a?Ignored for GNU compatibility.]"
94 "[Y:color|colour?Ignored for GNU compatibility.]:[when]"
95 "\n"
96 "\n[ pattern ] [ file ... ]\n"
97 "\n"
98 "[+DIAGNOSTICS?Exit status 0 if matches were found, 1 if no matches were "
99     "found, where \b-v\b invertes the exit status. Exit status 2 for other "
100     "errors that are accompanied by a message on the standard error.]"
101 "[+SEE ALSO?\bed\b(1), \bsed\b(1), \bperl\b(1), \btw\b(1), \bregex\b(3)]"
102 "[+CAVEATS?Some expressions of necessity require exponential space "
103     "and/or time.]"
104 "[+BUGS?Some expressions may use sub-optimal algorithms. For example, "
105     "don't use this implementation to compute primes.]"
106 ;
107 
108 #include <cmd.h>
109 #include <ctype.h>
110 #include <ccode.h>
111 #include <error.h>
112 #include <fts.h>
113 #include <regex.h>
114 #include <vmalloc.h>
115 
116 #ifndef EISDIR
117 #define EISDIR		(-1)
118 #endif
119 
120 /*
121  * snarfed from Doug McElroy's C++ version
122  *
123  * this grep is based on the Posix re package.
124  * unfortunately it has to have a nonstandard interface.
125  * 1. fgrep does not have usual operators. REG_LITERAL
126  * caters for this.
127  * 2. grep allows null expressions, hence REG_NULL.
128  * 3. it may be possible to combine the multiple
129  * patterns of grep into single patterns.  important
130  * special cases are handled by regcomb().
131  * 4. anchoring by -x has to be done separately from
132  * compilation (remember that fgrep has no ^ or $ operator),
133  * hence REG_LEFT|REG_RIGHT.  (An honest, but slow alternative:
134  * run regexec with REG_NOSUB off and nmatch=1 and check
135  * whether the match is full length)
136  */
137 
138 struct State_s;
139 typedef struct State_s State_t;
140 
141 typedef struct Item_s			/* list item			*/
142 {
143 	struct Item_s*	next;		/* next in list			*/
144 	uintmax_t	hits;		/* labeled pattern matches	*/
145 	uintmax_t	total;		/* total hits			*/
146 	char		string[1];	/* string value			*/
147 } Item_t;
148 
149 typedef struct List_s			/* generic list			*/
150 {
151 	Item_t*		head;		/* list head			*/
152 	Item_t*		tail;		/* list tail			*/
153 } List_t;
154 
155 struct State_s				/* program state		*/
156 {
157 	regdisc_t	redisc;		/* regex discipline		*/
158 	regex_t		re;		/* main compiled re		*/
159 
160 	Vmalloc_t*	vm;		/* allocation region		*/
161 
162 	struct
163 	{
164 	char*		base;		/* sfsetbuf buffer		*/
165 	size_t		size;		/* sfsetbuf size		*/
166 	int		noshare;	/* turn off SF_SHARE		*/
167 	}		buffer;
168 
169 	Item_t*		hit;		/* label for most recent match	*/
170 
171 	Sfio_t*		tmp;		/* tmp re compile string	*/
172 
173 	List_t		files;		/* pattern file list		*/
174 	List_t		patterns;	/* pattern list			*/
175 	List_t		labels;		/* labelled re list		*/
176 
177 	regmatch_t	posvec[1];	/* match position vector	*/
178 	regmatch_t*	pos;		/* match position pointer	*/
179 	int		posnum;		/* number of match positions	*/
180 
181 	char*		span;		/* line span buffer		*/
182 	size_t		spansize;	/* span buffer size		*/
183 
184 	int		any;		/* if any pattern hit		*/
185 	int		after;		/* # lines to list after match	*/
186 	int		before;		/* # lines to list before match	*/
187 	int		list;		/* list files with hits		*/
188 	int		notfound;	/* some input file not found	*/
189 	int		options;	/* regex options		*/
190 
191 	uintmax_t	hits;		/* total matched pattern count	*/
192 
193 	unsigned char	byline;		/* multiple pattern line by line*/
194 	unsigned char	count;		/* count number of hits		*/
195 	unsigned char	label;		/* all patterns labelled	*/
196 	unsigned char	match;		/* match sense			*/
197 	unsigned char	query;		/* return status but no output	*/
198 	unsigned char	number;		/* line numbers			*/
199 	unsigned char	prefix;		/* print file prefix		*/
200 	unsigned char	suppress;	/* no unopenable file messages	*/
201 	unsigned char	words;		/* word matches only		*/
202 };
203 
204 static void*
labelcomp(const regex_t * re,const char * s,size_t len,regdisc_t * disc)205 labelcomp(const regex_t* re, const char* s, size_t len, regdisc_t* disc)
206 {
207 	const char*	e = s + len;
208 	uintmax_t	n;
209 
210 	n = 0;
211 	while (s < e)
212 		n = (n << 3) + (*s++ - '0');
213 	return (void*)((char*)0 + n);
214 }
215 
216 static int
labelexec(const regex_t * re,void * data,const char * xstr,size_t xlen,const char * sstr,size_t slen,char ** snxt,regdisc_t * disc)217 labelexec(const regex_t* re, void* data, const char* xstr, size_t xlen, const char* sstr, size_t slen, char** snxt, regdisc_t* disc)
218 {
219 	((State_t*)disc)->hit = (Item_t*)data;
220 	return 0;
221 }
222 
223 static int
addre(State_t * state,char * s)224 addre(State_t* state, char* s)
225 {
226 	int		c;
227 	int		r;
228 	char*		b;
229 	Item_t*		x;
230 
231 	x = 0;
232 	r = -1;
233 	b = s;
234 	if (state->label)
235 	{
236 		if (!(s = strchr(s, ':')))
237 		{
238 			error(2, "%s: label:pattern expected", b);
239 			goto done;
240 		}
241 		c = s - b;
242 		s++;
243 		if (!(x = vmnewof(state->vm, 0, Item_t, 1, c)))
244 		{
245 			error(ERROR_SYSTEM|2, "out of space (pattern `%s')", b);
246 			goto done;
247 		}
248 		if (c)
249 			memcpy(x->string, b, c);
250 		x->string[c] = 0;
251 	}
252 	if (sfstrtell(state->tmp))
253 		sfputc(state->tmp, '\n');
254 	if (state->words)
255 	{
256 		if (!(state->options & REG_AUGMENTED))
257 			sfputc(state->tmp, '\\');
258 		sfputc(state->tmp, '<');
259 	}
260 	sfputr(state->tmp, s, -1);
261 	if (state->words)
262 	{
263 		if (!(state->options & REG_AUGMENTED))
264 			sfputc(state->tmp, '\\');
265 		sfputc(state->tmp, '>');
266 	}
267 	if (x)
268 	{
269 		b = (state->options & (REG_AUGMENTED|REG_EXTENDED)) ? "" : "\\";
270 		sfprintf(state->tmp, "%s(?{%I*o})", b, sizeof(ptrdiff_t), (char*)x - (char*)0);
271 		if (state->labels.tail)
272 			state->labels.tail = state->labels.tail->next = x;
273 		else
274 			state->labels.head = state->labels.tail = x;
275 	}
276 	state->any = 1;
277 	r = 0;
278  done:
279 	if (r && x)
280 		vmfree(state->vm, x);
281 	return r;
282 }
283 
284 static int
addstring(State_t * state,List_t * p,char * s)285 addstring(State_t* state, List_t* p, char* s)
286 {
287 	Item_t*	x;
288 
289 	if (!(x = vmnewof(state->vm, 0, Item_t, 1, strlen(s))))
290 	{
291 		error(ERROR_SYSTEM|2, "out of space (string `%s')", s);
292 		return -1;
293 	}
294 	strcpy(x->string, s);
295 	if (p->head)
296 		p->tail->next = x;
297 	else
298 		p->head = x;
299 	p->tail = x;
300 	return 0;
301 }
302 
303 static int
compile(State_t * state)304 compile(State_t* state)
305 {
306 	int	line;
307 	int	c;
308 	int	r;
309 	size_t	n;
310 	char*	s;
311 	char*	t;
312 	char*	file;
313 	Item_t*	x;
314 	Sfio_t*	f;
315 
316 	r = 1;
317 	if (!(state->tmp = sfstropen()))
318 	{
319 		error(ERROR_SYSTEM|2, "out of space");
320 		goto done;
321 	}
322 	if (state->number || state->before || state->after)
323 		state->byline = 1;
324 	for (x = state->patterns.head; x; x = x->next)
325 		if (addre(state, x->string))
326 			return r;
327 	file = error_info.file;
328 	line = error_info.line;
329 	f = 0;
330 	for (x = state->files.head; x; x = x->next)
331 	{
332 		s = x->string;
333 		if (!(f = sfopen(NiL, s, "r")))
334 		{
335 			error(ERROR_SYSTEM|2, "%s: cannot open", s);
336 			r = 2;
337 			goto done;
338 		}
339 		error_info.file = s;
340 		error_info.line = 0;
341 		while (s = (char*)sfreserve(f, SF_UNBOUND, SF_LOCKR))
342 		{
343 			if (!(n = sfvalue(f)))
344 				break;
345 			if (s[n - 1] != '\n')
346 			{
347 				for (t = s + n; t > s && *--t != '\n'; t--);
348 				if (t == s)
349 				{
350 					sfread(f, s, 0);
351 					break;
352 				}
353 				n = t - s + 1;
354 			}
355 			s[n - 1] = 0;
356 			if (addre(state, s))
357 				goto done;
358 			s[n - 1] = '\n';
359 			sfread(f, s, n);
360 		}
361 		while ((s = sfgetr(f, '\n', 1)) || (s = sfgetr(f, '\n', -1)))
362 		{
363 			error_info.line++;
364 			if (addre(state, s))
365 				goto done;
366 		}
367 		error_info.file = file;
368 		error_info.line = line;
369 		sfclose(f);
370 		f = 0;
371 	}
372 	if (!state->any)
373 	{
374 		error(2, "no pattern");
375 		goto done;
376 	}
377 	state->any = 0;
378 	if (!(s = sfstruse(state->tmp)))
379 	{
380 		error(ERROR_SYSTEM|2, "out of space");
381 		goto done;
382 	}
383 	error(-1, "RE ``%s''", s);
384 	state->re.re_disc = &state->redisc;
385 	if (state->label)
386 	{
387 		state->redisc.re_compf = labelcomp;
388 		state->redisc.re_execf = labelexec;
389 	}
390 	if (c = regcomp(&state->re, s, state->options))
391 	{
392 		regfatal(&state->re, 2, c);
393 		goto done;
394 	}
395 	if (!regrecord(&state->re))
396 		state->byline = 1;
397 	if (!state->label)
398 	{
399 		if (!(state->hit = vmnewof(state->vm, 0, Item_t, 1, 0)))
400 		{
401 			error(ERROR_SYSTEM|2, "out of space");
402 			goto done;
403 		}
404 		state->labels.head = state->labels.tail = state->hit;
405 	}
406 	r = 0;
407  done:
408 	error_info.file = file;
409 	error_info.line = line;
410 	if (f)
411 		sfclose(f);
412 	if (state->tmp)
413 		sfstrclose(state->tmp);
414 	return r;
415 }
416 
417 static void
highlight(Sfio_t * sp,const char * s,int n,int so,int eo)418 highlight(Sfio_t* sp, const char* s, int n, int so, int eo)
419 {
420 	static const char	bold[] =	{CC_esc,'[','1','m'};
421 	static const char	normal[] =	{CC_esc,'[','0','m'};
422 
423 	sfwrite(sp, s, so);
424 	sfwrite(sp, bold, sizeof(bold));
425 	sfwrite(sp, s + so, eo - so);
426 	sfwrite(sp, normal, sizeof(normal));
427 	sfwrite(sp, s + eo, n - eo);
428 }
429 
430 static int
hit(State_t * state,const char * prefix,int line,const char * s,size_t len)431 hit(State_t* state, const char* prefix, int line, const char* s, size_t len)
432 {
433 	state->hit->hits++;
434 	if (state->query || state->list)
435 		return -1;
436 	if (!state->count)
437 	{
438 		if (state->prefix)
439 			sfprintf(sfstdout, "%s:", prefix);
440 		if (state->number && line)
441 			sfprintf(sfstdout, "%d:", line);
442 		if (state->label)
443 			sfprintf(sfstdout, "%s:", state->hit->string);
444 		if (state->pos)
445 			highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
446 		else
447 			sfwrite(sfstdout, s, len + 1);
448 	}
449 	return 0;
450 }
451 
452 static int
record(void * handle,const char * s,size_t len)453 record(void* handle, const char* s, size_t len)
454 {
455 	return hit((State_t*)handle, error_info.file, 0, s, len);
456 }
457 
458 static int
execute(State_t * state,Sfio_t * input,char * name,Shbltin_t * context)459 execute(State_t* state, Sfio_t* input, char* name, Shbltin_t* context)
460 {
461 	register char*	s;
462 	char*		file;
463 	Item_t*		x;
464 	size_t		len;
465 	int		result;
466 	int		line;
467 
468 	int		r = 1;
469 
470 	if (state->buffer.noshare)
471 		sfset(input, SF_SHARE, 0);
472 	if (state->buffer.size)
473 		sfsetbuf(input, state->buffer.base, state->buffer.size);
474 	if (!name)
475 		name = "(standard input)"; /* posix! (ast prefers /dev/stdin) */
476 	file = error_info.file;
477 	error_info.file = name;
478 	line = error_info.line;
479 	error_info.line = 0;
480 	if (state->byline)
481 	{
482 		for (;;)
483 		{
484 			if (sh_checksig(context))
485 				goto bad;
486 			error_info.line++;
487 			if (s = sfgetr(input, '\n', 0))
488 				len = sfvalue(input) - 1;
489 			else if (s = sfgetr(input, '\n', -1))
490 			{
491 				len = sfvalue(input);
492 				s[len] = '\n';
493 #if _you_like_the_noise
494 				error(1, "newline appended");
495 #endif
496 			}
497 			else if (sferror(input) && errno != EISDIR)
498 			{
499 				error(ERROR_SYSTEM|2, "read error");
500 				goto bad;
501 			}
502 			else
503 				break;
504 			if ((result = regnexec(&state->re, s, len, state->posnum, state->pos, 0)) && result != REG_NOMATCH)
505 			{
506 				regfatal(&state->re, 2, result);
507 				goto bad;
508 			}
509 			if ((result == 0) == state->match && hit(state, name, error_info.line, s, len) < 0)
510 				break;
511 		}
512 	}
513 	else
514 	{
515 		register char*	e;
516 		register char*	t;
517 		char*		r;
518 
519 		s = e = 0;
520 		for (;;)
521 		{
522 			if (sh_checksig(context))
523 				goto bad;
524 			if (s < e)
525 			{
526 				t = state->span;
527 				for (;;)
528 				{
529 					len = 2 * (e - s) + t - state->span + 1;
530 					len = roundof(len, SF_BUFSIZE);
531 					if (state->spansize < len)
532 					{
533 						state->spansize = len;
534 						len = t - state->span;
535 						if (!(state->span = vmnewof(state->vm, state->span, char, state->spansize, 0)))
536 						{
537 							error(ERROR_SYSTEM|2, "%s: line longer than %lu characters", name, len + e - s);
538 							goto bad;
539 						}
540 						t = state->span + len;
541 					}
542 					len = e - s;
543 					memcpy(t, s, len);
544 					t += len;
545 					if (!(s = sfreserve(input, SF_UNBOUND, 0)) || (len = sfvalue(input)) <= 0)
546 					{
547 						if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
548 							error(ERROR_SYSTEM|2, "%s: read error", name);
549 						break;
550 					}
551 					else if (!(e = memchr(s, '\n', len)))
552 						e = s + len;
553 					else
554 					{
555 						r = s + len;
556 						len = (e - s) + t - state->span;
557 						len = roundof(len, SF_BUFSIZE);
558 						if (state->spansize < len)
559 						{
560 							state->spansize = len;
561 							len = t - state->span;
562 							if (!(state->span = vmnewof(state->vm, state->span, char, state->spansize, 0)))
563 							{
564 								error(ERROR_SYSTEM|2, "%s: line longer than %lu characters", name, len + e - s);
565 								goto bad;
566 							}
567 							t = state->span + len;
568 						}
569 						len = e - s;
570 						memcpy(t, s, len);
571 						t += len;
572 						s += len + 1;
573 						e = r;
574 						break;
575 					}
576 				}
577 				*t = '\n';
578 				if (!(len = t - state->span))
579 					len++;
580 				if (result = regrexec(&state->re, state->span, len, state->posnum, state->pos, state->options, '\n', (void*)state, record))
581 				{
582 					if (result < 0)
583 						goto done;
584 					if (result != REG_NOMATCH)
585 					{
586 						regfatal(&state->re, 2, result);
587 						goto bad;
588 					}
589 				}
590 				if (!s)
591 					break;
592 			}
593 			else
594 			{
595 				if (!(s = sfreserve(input, SF_UNBOUND, 0)))
596 				{
597 					if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
598 						error(ERROR_SYSTEM|2, "%s: read error", name);
599 					break;
600 				}
601 				if ((len = sfvalue(input)) <= 0)
602 					break;
603 				e = s + len;
604 			}
605 			t = e;
606 			while (t > s)
607 				if (*--t == '\n')
608 				{
609 					len = t - s;
610 					if (!len || t > s && *(t - 1) == '\n')
611 						len++;
612 					if (result = regrexec(&state->re, s, len, state->posnum, state->pos, state->options, '\n', (void*)state, record))
613 					{
614 						if (result < 0)
615 							goto done;
616 						if (result != REG_NOMATCH)
617 						{
618 							regfatal(&state->re, 2, result);
619 							goto bad;
620 						}
621 					}
622 					s = t + 1;
623 					break;
624 				}
625 		}
626 	}
627  done:
628 	error_info.file = file;
629 	error_info.line = line;
630 	x = state->labels.head;
631 	do
632 	{
633 		if (x->hits && state->list >= 0)
634 		{
635 			state->any = 1;
636 			if (state->query)
637 				break;
638 		}
639 		if (!state->query)
640 		{
641 			if (!state->list)
642 			{
643 				if (state->count)
644 				{
645 					if (state->count & 2)
646 						x->total += x->hits;
647 					else
648 					{
649 						if (state->prefix)
650 							sfprintf(sfstdout, "%s:", name);
651 						if (*x->string)
652 							sfprintf(sfstdout, "%s:", x->string);
653 						sfprintf(sfstdout, "%I*u\n", sizeof(x->hits), x->hits);
654 					}
655 				}
656 			}
657 			else if ((x->hits != 0) == (state->list > 0))
658 			{
659 				if (state->list < 0)
660 					state->any = 1;
661 				if (*x->string)
662 					sfprintf(sfstdout, "%s:%s\n", name, x->string);
663 				else
664 					sfprintf(sfstdout, "%s\n", name);
665 			}
666 		}
667 		x->hits = 0;
668 	} while (x = x->next);
669 	r = 0;
670  bad:
671 	error_info.file = file;
672 	error_info.line = line;
673 	return r;
674 }
675 
676 static int
grep(char * id,int options,int argc,char ** argv,Shbltin_t * context)677 grep(char* id, int options, int argc, char** argv, Shbltin_t* context)
678 {
679 	int	c;
680 	char*	s;
681 	char*	h;
682 	Sfio_t*	f;
683 	int	flags;
684 	int	r = 1;
685 	FTS*	fts;
686 	FTSENT*	ent;
687 	State_t	state;
688 
689 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
690 	flags = fts_flags() | FTS_META | FTS_TOP | FTS_NOPOSTORDER | FTS_NOSEEDOTDIR;
691 	memset(&state, 0, sizeof(state));
692 	if (!(state.vm = vmopen(Vmdcheap, Vmbest, 0)))
693 	{
694 		error(ERROR_SYSTEM|2, "out of space");
695 		return 2;
696 	}
697 	state.redisc.re_version = REG_VERSION;
698 	state.redisc.re_flags = REG_NOFREE;
699 	state.redisc.re_resizef = (regresize_t)vmgetmem;
700 	state.redisc.re_resizehandle = (void*)state.vm;
701 	state.match = 1;
702 	state.options = REG_FIRST|REG_NOSUB|REG_NULL|REG_DISCIPLINE|REG_MULTIPLE|options;
703 	if (strcmp(astconf("CONFORMANCE", NiL, NiL), "standard"))
704 		state.options |= REG_LENIENT;
705 	error_info.id = id;
706 	h = 0;
707 	fts = 0;
708 	while (c = optget(argv, usage))
709 		switch (c)
710 		{
711 		case 'C':
712 			if (opt_info.arg)
713 			{
714 				state.before = (int)strtol(opt_info.arg, &s, 0);
715 				state.after = (*s == ',') ? (int)strtol(s + 1, &s, 0) : state.before;
716 				if (*s)
717 					error(3, "%s: invalid context line count", s);
718 			}
719 			else
720 				state.before = state.after = 2;
721 			break;
722 		case 'E':
723 			state.options |= REG_EXTENDED;
724 			break;
725 		case 'F':
726 			state.options |= REG_LITERAL;
727 			break;
728 		case 'G':
729 			state.options &= ~(REG_AUGMENTED|REG_EXTENDED);
730 			break;
731 		case 'H':
732 			state.prefix = opt_info.num;
733 			break;
734 		case 'L':
735 			state.list = -opt_info.num;
736 			break;
737 		case 'N':
738 			h = opt_info.arg;
739 			break;
740 		case 'O':
741 			state.options |= REG_LENIENT;
742 			break;
743 		case 'P':
744 			state.options |= REG_EXTENDED|REG_LENIENT;
745 			break;
746 		case 'S':
747 			state.options &= ~REG_LENIENT;
748 			break;
749 		case 'T':
750 			s = opt_info.arg;
751 			switch (*s)
752 			{
753 			case 'b':
754 			case 'm':
755 				c = *s++;
756 				state.buffer.size = strton(s, &s, NiL, 1);
757 				if (c == 'b' && !(state.buffer.base = newof(0, char, state.buffer.size, 0)))
758 				{
759 					error(ERROR_SYSTEM|2, "out of space [test buffer]");
760 					goto done;
761 				}
762 				if (*s)
763 				{
764 					error(2, "%s: invalid characters after test", s);
765 					goto done;
766 				}
767 				break;
768 			case 'f':
769 				state.options |= REG_FIRST;
770 				break;
771 			case 'l':
772 				state.options |= REG_LEFT;
773 				break;
774 			case 'n':
775 				state.buffer.noshare = 1;
776 				break;
777 			case 'r':
778 				state.options |= REG_RIGHT;
779 				break;
780 			case 'L':
781 				state.byline = 1;
782 				break;
783 			default:
784 				error(3, "%s: unknown test", s);
785 				break;
786 			}
787 			break;
788 		case 'X':
789 			state.options |= REG_AUGMENTED;
790 			break;
791 		case 'a':
792 			break;
793 		case 'b':
794 			state.options &= ~(REG_FIRST|REG_NOSUB);
795 			break;
796 		case 'c':
797 			state.count |= 1;
798 			break;
799 		case 'e':
800 			if (addstring(&state, &state.patterns, opt_info.arg))
801 				goto done;
802 			break;
803 		case 'f':
804 			if (addstring(&state, &state.files, opt_info.arg))
805 				goto done;
806 			break;
807 		case 'h':
808 			state.prefix = 2;
809 			break;
810 		case 'i':
811 			state.options |= REG_ICASE;
812 			break;
813 		case 'l':
814 			state.list = opt_info.num;
815 			break;
816 		case 'm':
817 			state.label = 1;
818 			break;
819 		case 'n':
820 			state.number = 1;
821 			break;
822 		case 'q':
823 			state.query = 1;
824 			break;
825 		case 'r':
826 			if (opt_info.num)
827 				flags &= ~FTS_TOP;
828 			break;
829 		case 's':
830 			state.suppress = opt_info.num;
831 			break;
832 		case 't':
833 			state.count |= 2;
834 			break;
835 		case 'v':
836 			if (state.match = !opt_info.num)
837 				state.options &= ~REG_INVERT;
838 			else
839 				state.options |= REG_INVERT;
840 			break;
841 		case 'w':
842 			state.words = 1;
843 			break;
844 		case 'x':
845 			state.options |= REG_LEFT|REG_RIGHT;
846 			break;
847 		case 'Y':
848 			/* ignored for GNU compatibility */
849 			break;
850 		case '?':
851 			vmclose(state.vm);
852 			error(ERROR_USAGE|4, "%s", opt_info.arg);
853 			break;
854 		case ':':
855 			error(2, "%s", opt_info.arg);
856 			goto done;
857 		default:
858 			error(2, "%s: not implemented", opt_info.name);
859 			goto done;
860 		}
861 	argv += opt_info.index;
862 	if ((state.options & REG_LITERAL) && (state.options & (REG_AUGMENTED|REG_EXTENDED)))
863 	{
864 		error(2, "-F and -A or -P or -X are incompatible");
865 		goto done;
866 	}
867 	if ((state.options & REG_LITERAL) && state.words)
868 	{
869 		error(ERROR_SYSTEM|2, "-F and -w are incompatible");
870 		goto done;
871 	}
872 	if (!state.files.head && !state.patterns.head)
873 	{
874 		if (!argv[0])
875 		{
876 			error(2, "no pattern");
877 			goto done;
878 		}
879 		if (addstring(&state, &state.patterns, *argv++))
880 			goto done;
881 	}
882 	if (!(state.options & (REG_FIRST|REG_NOSUB)))
883 	{
884 		if (state.count || state.list || state.query || (state.options & REG_INVERT))
885 			state.options |= REG_FIRST|REG_NOSUB;
886 		else
887 		{
888 			state.pos = state.posvec;
889 			state.posnum = elementsof(state.posvec);
890 		}
891 	}
892 	if (r = compile(&state))
893 		goto done;
894 	sfset(sfstdout, SF_LINE, 1);
895 	if (!argv[0])
896 	{
897 		state.prefix = h ? 1 : 0;
898 		if (r = execute(&state, sfstdin, h, context))
899 			goto done;
900 	}
901 	if (state.prefix > 1)
902 		state.prefix = 0;
903 	else if (!(flags & FTS_TOP) || argv[1])
904 		state.prefix = 1;
905 	if (!(fts = fts_open(argv, flags, NiL)))
906 	{
907 		error(ERROR_SYSTEM|2, "%s: not found", argv[0]);
908 		r = 1;
909 		goto done;
910 	}
911 	while (!sh_checksig(context) && (ent = fts_read(fts)))
912 		switch (ent->fts_info)
913 		{
914 		case FTS_F:
915 			if (f = sfopen(NiL, ent->fts_accpath, "r"))
916 			{
917 				r = execute(&state, f, ent->fts_path, context);
918 				sfclose(f);
919 				if (r)
920 					goto done;
921 				if (state.query && state.any)
922 					goto quit;
923 				break;
924 			}
925 			/*FALLTHROUGH*/
926 		case FTS_NS:
927 		case FTS_SLNONE:
928 			state.notfound = 1;
929 			if (!state.suppress)
930 				error(ERROR_SYSTEM|2, "%s: cannot open", ent->fts_path);
931 			break;
932 		case FTS_DC:
933 			error(ERROR_WARNING|1, "%s: directory causes cycle", ent->fts_path);
934 			break;
935 		case FTS_DNR:
936 			error(ERROR_SYSTEM|2, "%s: cannot read directory", ent->fts_path);
937 			break;
938 		case FTS_DNX:
939 			error(ERROR_SYSTEM|2, "%s: cannot search directory", ent->fts_path);
940 			break;
941 		}
942  quit:
943 	if ((state.count & 2) && !state.query && !state.list)
944 	{
945 		Item_t*		x;
946 
947 		x = state.labels.head;
948 		do
949 		{
950 			if (*x->string)
951 				sfprintf(sfstdout, "%s:", x->string);
952 			sfprintf(sfstdout, "%I*u\n", sizeof(x->total), x->total);
953 		} while (x = x->next);
954 	}
955 	r = (state.notfound && !state.query) ? 2 : !state.any;
956  done:
957 	if (fts)
958 		fts_close(fts);
959 	vmclose(state.vm);
960 	sfset(sfstdout, SF_LINE, 0);
961 	if (sfsync(sfstdout))
962 		error(ERROR_SYSTEM|2, "write error");
963 	if (sh_checksig(context))
964 	{
965 		errno = EINTR;
966 		r = 2;
967 	}
968 	return r;
969 }
970 
971 int
b_grep(int argc,char ** argv,Shbltin_t * context)972 b_grep(int argc, char** argv, Shbltin_t* context)
973 {
974 	char*	s;
975 	int	options;
976 
977 	NoP(argc);
978 	options = 0;
979 	if (s = strrchr(argv[0], '/'))
980 		s++;
981 	else
982 		s = argv[0];
983 	switch (*s)
984 	{
985 	case 'e':
986 	case 'E':
987 		s = "egrep";
988 		options = REG_EXTENDED;
989 		break;
990 	case 'f':
991 	case 'F':
992 		s = "fgrep";
993 		options = REG_LITERAL;
994 		break;
995 	case 'p':
996 	case 'P':
997 		s = "pgrep";
998 		options = REG_EXTENDED|REG_LENIENT;
999 		break;
1000 	case 'x':
1001 	case 'X':
1002 		s = "xgrep";
1003 		options = REG_AUGMENTED;
1004 		break;
1005 	default:
1006 		s = "grep";
1007 		break;
1008 	}
1009 	return grep(s, options, argc, argv, context);
1010 }
1011 
1012 int
b_egrep(int argc,char ** argv,Shbltin_t * context)1013 b_egrep(int argc, char** argv, Shbltin_t* context)
1014 {
1015 	NoP(argc);
1016 	return grep("egrep", REG_EXTENDED, argc, argv, context);
1017 }
1018 
1019 int
b_fgrep(int argc,char ** argv,Shbltin_t * context)1020 b_fgrep(int argc, char** argv, Shbltin_t* context)
1021 {
1022 	NoP(argc);
1023 	return grep("fgrep", REG_LITERAL, argc, argv, context);
1024 }
1025 
1026 int
b_pgrep(int argc,char ** argv,Shbltin_t * context)1027 b_pgrep(int argc, char** argv, Shbltin_t* context)
1028 {
1029 	NoP(argc);
1030 	return grep("pgrep", REG_EXTENDED|REG_LENIENT, argc, argv, context);
1031 }
1032 
1033 int
b_xgrep(int argc,char ** argv,Shbltin_t * context)1034 b_xgrep(int argc, char** argv, Shbltin_t* context)
1035 {
1036 	NoP(argc);
1037 	return grep("xgrep", REG_AUGMENTED, argc, argv, context);
1038 }
1039 
1040 #if STANDALONE
1041 
1042 int
main(int argc,char ** argv)1043 main(int argc, char** argv)
1044 {
1045 	return b_grep(argc, argv, 0);
1046 }
1047 
1048 #endif
1049