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