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