5659a7d2b8baf806e7218b9c2cc3a667a39afa08
[oweals/busybox.git] / findutils / grep.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini grep implementation for busybox using libc regex.
4  *
5  * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
6  * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
7  *
8  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
9  */
10 /* BB_AUDIT SUSv3 defects - unsupported option -x.  */
11 /* BB_AUDIT GNU defects - always acts as -a.  */
12 /* http://www.opengroup.org/onlinepubs/007904975/utilities/grep.html */
13 /*
14  * 2004,2006 (C) Vladimir Oleynik <dzo@simtreas.ru> -
15  * correction "-e pattern1 -e pattern2" logic and more optimizations.
16  * precompiled regex
17  */
18 /*
19  * (C) 2006 Jac Goudsmit added -o option
20  */
21
22 #include "busybox.h"
23 #include "xregex.h"
24
25
26 /* options */
27 static unsigned long opt;
28 #define GREP_OPTS "lnqvscFiHhe:f:Lo"
29 #define GREP_OPT_l (1<<0)
30 #define PRINT_FILES_WITH_MATCHES (opt & GREP_OPT_l)
31 #define GREP_OPT_n (1<<1)
32 #define PRINT_LINE_NUM (opt & GREP_OPT_n)
33 #define GREP_OPT_q (1<<2)
34 #define BE_QUIET (opt & GREP_OPT_q)
35 #define GREP_OPT_v (1<<3)
36 typedef char invert_search_t;
37 static invert_search_t invert_search;
38 #define GREP_OPT_s (1<<4)
39 #define SUPPRESS_ERR_MSGS (opt & GREP_OPT_s)
40 #define GREP_OPT_c (1<<5)
41 #define PRINT_MATCH_COUNTS (opt & GREP_OPT_c)
42 #define GREP_OPT_F (1<<6)
43 #define FGREP_FLAG (opt & GREP_OPT_F)
44 #define GREP_OPT_i (1<<7)
45 #define GREP_OPT_H (1<<8)
46 #define GREP_OPT_h (1<<9)
47 #define GREP_OPT_e (1<<10)
48 #define GREP_OPT_f (1<<11)
49 #define GREP_OPT_L (1<<12)
50 #define PRINT_FILES_WITHOUT_MATCHES (opt & GREP_OPT_L)
51 #define GREP_OPT_o (1<<13)
52 #if ENABLE_FEATURE_GREP_CONTEXT
53 #define GREP_OPT_CONTEXT "A:B:C:"
54 #define GREP_OPT_A (1<<14)
55 #define GREP_OPT_B (1<<15)
56 #define GREP_OPT_C (1<<16)
57 #define GREP_OPT_E (1<<17)
58 #else
59 #define GREP_OPT_CONTEXT ""
60 #define GREP_OPT_A 0
61 #define GREP_OPT_B 0
62 #define GREP_OPT_C 0
63 #define GREP_OPT_E (1<<14)
64 #endif
65 #if ENABLE_FEATURE_GREP_EGREP_ALIAS
66 # define OPT_EGREP "E"
67 #else
68 # define OPT_EGREP ""
69 #endif
70
71 static int reflags;
72 static int print_filename;
73
74 #if ENABLE_FEATURE_GREP_CONTEXT
75 static int lines_before;
76 static int lines_after;
77 static char **before_buf;
78 static int last_line_printed;
79 #endif /* ENABLE_FEATURE_GREP_CONTEXT */
80
81 /* globals used internally */
82 static llist_t *pattern_head;   /* growable list of patterns to match */
83 static char *cur_file;          /* the current file we are reading */
84
85 typedef struct GREP_LIST_DATA {
86         char *pattern;
87         regex_t preg;
88 #define PATTERN_MEM_A 1
89 #define COMPILED 2
90         int flg_mem_alocated_compiled;
91 } grep_list_data_t;
92
93 static void print_line(const char *line, int linenum, char decoration)
94 {
95 #if ENABLE_FEATURE_GREP_CONTEXT
96         /* possibly print the little '--' separator */
97         if ((lines_before || lines_after) && last_line_printed &&
98                         last_line_printed < linenum - 1) {
99                 puts("--");
100         }
101         last_line_printed = linenum;
102 #endif
103         if (print_filename > 0)
104                 printf("%s%c", cur_file, decoration);
105         if (PRINT_LINE_NUM)
106                 printf("%i%c", linenum, decoration);
107         /* Emulate weird GNU grep behavior with -ov */
108         if ((opt & (GREP_OPT_v+GREP_OPT_o)) != (GREP_OPT_v+GREP_OPT_o))
109                 puts(line);
110 }
111
112
113 static int grep_file(FILE *file)
114 {
115         char *line;
116         invert_search_t ret;
117         int linenum = 0;
118         int nmatches = 0;
119         regmatch_t regmatch;
120 #if ENABLE_FEATURE_GREP_CONTEXT
121         int print_n_lines_after = 0;
122         int curpos = 0; /* track where we are in the circular 'before' buffer */
123         int idx = 0; /* used for iteration through the circular buffer */
124 #endif /* ENABLE_FEATURE_GREP_CONTEXT */
125
126         while ((line = bb_get_chomped_line_from_file(file)) != NULL) {
127                 llist_t *pattern_ptr = pattern_head;
128                 grep_list_data_t * gl;
129
130                 linenum++;
131                 ret = 0;
132                 while (pattern_ptr) {
133                         gl = (grep_list_data_t *)pattern_ptr->data;
134                         if (FGREP_FLAG) {
135                                 ret = strstr(line, gl->pattern) != NULL;
136                         } else {
137                                 /*
138                                  * test for a postitive-assertion match (regexec returns success (0)
139                                  * and the user did not specify invert search), or a negative-assertion
140                                  * match (regexec returns failure (REG_NOMATCH) and the user specified
141                                  * invert search)
142                                  */
143                                 if (!(gl->flg_mem_alocated_compiled & COMPILED)) {
144                                         gl->flg_mem_alocated_compiled |= COMPILED;
145                                         xregcomp(&(gl->preg), gl->pattern, reflags);
146                                 }
147                                 regmatch.rm_so = 0;
148                                 regmatch.rm_eo = 0;
149                                 ret |= regexec(&(gl->preg), line, 1, &regmatch, 0) == 0;
150                         }
151                         pattern_ptr = pattern_ptr->link;
152                 } /* while (pattern_ptr) */
153
154                 if (ret ^ invert_search) {
155
156                         if (PRINT_FILES_WITH_MATCHES || BE_QUIET)
157                                 free(line);
158
159                         /* if we found a match but were told to be quiet, stop here */
160                         if (BE_QUIET || PRINT_FILES_WITHOUT_MATCHES)
161                                 return -1;
162
163                                 /* keep track of matches */
164                                 nmatches++;
165
166                                 /* if we're just printing filenames, we stop after the first match */
167                                 if (PRINT_FILES_WITH_MATCHES)
168                                         break;
169
170                                 /* print the matched line */
171                                 if (PRINT_MATCH_COUNTS == 0) {
172 #if ENABLE_FEATURE_GREP_CONTEXT
173                                         int prevpos = (curpos == 0) ? lines_before - 1 : curpos - 1;
174
175                                         /* if we were told to print 'before' lines and there is at least
176                                          * one line in the circular buffer, print them */
177                                         if (lines_before && before_buf[prevpos] != NULL) {
178                                                 int first_buf_entry_line_num = linenum - lines_before;
179
180                                                 /* advance to the first entry in the circular buffer, and
181                                                  * figure out the line number is of the first line in the
182                                                  * buffer */
183                                                 idx = curpos;
184                                                 while (before_buf[idx] == NULL) {
185                                                         idx = (idx + 1) % lines_before;
186                                                         first_buf_entry_line_num++;
187                                                 }
188
189                                                 /* now print each line in the buffer, clearing them as we go */
190                                                 while (before_buf[idx] != NULL) {
191                                                         print_line(before_buf[idx], first_buf_entry_line_num, '-');
192                                                         free(before_buf[idx]);
193                                                         before_buf[idx] = NULL;
194                                                         idx = (idx + 1) % lines_before;
195                                                         first_buf_entry_line_num++;
196                                                 }
197                                         }
198
199                                         /* make a note that we need to print 'after' lines */
200                                         print_n_lines_after = lines_after;
201 #endif
202                                         if (opt & GREP_OPT_o) {
203                                                 line[regmatch.rm_eo] = '\0';
204                                                 print_line(line + regmatch.rm_so, linenum, ':');
205                                         } else {
206                                                 print_line(line, linenum, ':');
207                                         }
208                                 }
209                         }
210 #if ENABLE_FEATURE_GREP_CONTEXT
211                         else { /* no match */
212                                 /* Add the line to the circular 'before' buffer */
213                                 if (lines_before) {
214                                         free(before_buf[curpos]);
215                                         before_buf[curpos] = xstrdup(line);
216                                         curpos = (curpos + 1) % lines_before;
217                                 }
218                         }
219
220                         /* if we need to print some context lines after the last match, do so */
221                         if (print_n_lines_after && (last_line_printed != linenum)) {
222                                 print_line(line, linenum, '-');
223                                 print_n_lines_after--;
224                         }
225 #endif /* ENABLE_FEATURE_GREP_CONTEXT */
226                 free(line);
227         }
228
229         /* special-case file post-processing for options where we don't print line
230          * matches, just filenames and possibly match counts */
231
232         /* grep -c: print [filename:]count, even if count is zero */
233         if (PRINT_MATCH_COUNTS) {
234                 if (print_filename > 0)
235                         printf("%s:", cur_file);
236                 printf("%d\n", nmatches);
237         }
238
239         /* grep -l: print just the filename, but only if we grepped the line in the file  */
240         if (PRINT_FILES_WITH_MATCHES && nmatches > 0) {
241                 puts(cur_file);
242         }
243
244         /* grep -L: print just the filename, but only if we didn't grep the line in the file  */
245         if (PRINT_FILES_WITHOUT_MATCHES && nmatches == 0) {
246                 puts(cur_file);
247         }
248
249         return nmatches;
250 }
251
252 #if ENABLE_FEATURE_CLEAN_UP
253 #define new_grep_list_data(p, m) add_grep_list_data(p, m)
254 static char * add_grep_list_data(char *pattern, int flg_used_mem)
255 #else
256 #define new_grep_list_data(p, m) add_grep_list_data(p)
257 static char * add_grep_list_data(char *pattern)
258 #endif
259 {
260         grep_list_data_t *gl = xmalloc(sizeof(grep_list_data_t));
261         gl->pattern = pattern;
262 #if ENABLE_FEATURE_CLEAN_UP
263         gl->flg_mem_alocated_compiled = flg_used_mem;
264 #else
265         gl->flg_mem_alocated_compiled = 0;
266 #endif
267         return (char *)gl;
268 }
269
270
271 static void load_regexes_from_file(llist_t *fopt)
272 {
273         char *line;
274         FILE *f;
275
276         while (fopt) {
277                 llist_t *cur = fopt;
278                 char *ffile = cur->data;
279
280                 fopt = cur->link;
281                 free(cur);
282                 f = xfopen(ffile, "r");
283                 while ((line = bb_get_chomped_line_from_file(f)) != NULL) {
284                         llist_add_to(&pattern_head,
285                                 new_grep_list_data(line, PATTERN_MEM_A));
286                 }
287         }
288 }
289
290
291 int grep_main(int argc, char **argv)
292 {
293         FILE *file;
294         int matched;
295         llist_t *fopt = NULL;
296         int error_open_count = 0;
297
298         /* do normal option parsing */
299 #if ENABLE_FEATURE_GREP_CONTEXT
300         char *junk;
301         char *slines_after;
302         char *slines_before;
303         char *Copt;
304
305         bb_opt_complementally = "H-h:e::f::C-AB";
306         opt = bb_getopt_ulflags(argc, argv,
307                 GREP_OPTS GREP_OPT_CONTEXT OPT_EGREP,
308                 &pattern_head, &fopt,
309                 &slines_after, &slines_before, &Copt);
310
311         if (opt & GREP_OPT_C) {
312                 /* C option unseted A and B options, but next -A or -B
313                    may be ovewrite own option */
314                 if (!(opt & GREP_OPT_A))         /* not overwtited */
315                         slines_after = Copt;
316                 if (!(opt & GREP_OPT_B))         /* not overwtited */
317                         slines_before = Copt;
318                 opt |= GREP_OPT_A|GREP_OPT_B;   /* set for parse now */
319         }
320         if (opt & GREP_OPT_A) {
321                 lines_after = strtoul(slines_after, &junk, 10);
322                 if (*junk != '\0')
323                         bb_error_msg_and_die(bb_msg_invalid_arg, slines_after, "-A");
324         }
325         if (opt & GREP_OPT_B) {
326                 lines_before = strtoul(slines_before, &junk, 10);
327                 if (*junk != '\0')
328                         bb_error_msg_and_die(bb_msg_invalid_arg, slines_before, "-B");
329         }
330         /* sanity checks after parse may be invalid numbers ;-) */
331         if (opt & (GREP_OPT_c|GREP_OPT_q|GREP_OPT_l|GREP_OPT_L)) {
332                 opt &= ~GREP_OPT_n;
333                 lines_before = 0;
334                 lines_after = 0;
335         } else if (lines_before > 0)
336                 before_buf = (char **)xzalloc(lines_before * sizeof(char *));
337 #else
338         /* with auto sanity checks */
339         bb_opt_complementally = "H-h:e::f::c-n:q-n:l-n";
340         opt = bb_getopt_ulflags(argc, argv, GREP_OPTS OPT_EGREP,
341                 &pattern_head, &fopt);
342 #endif
343         invert_search = (opt & GREP_OPT_v) != 0;        /* 0 | 1 */
344
345         if (opt & GREP_OPT_H)
346                 print_filename++;
347         if (opt & GREP_OPT_h)
348                 print_filename--;
349         if (pattern_head != NULL) {
350                 /* convert char *argv[] to grep_list_data_t */
351                 llist_t *cur;
352
353                 for (cur = pattern_head; cur; cur = cur->link)
354                         cur->data = new_grep_list_data(cur->data, 0);
355         }
356         if (opt & GREP_OPT_f)
357                 load_regexes_from_file(fopt);
358
359         if (ENABLE_FEATURE_GREP_FGREP_ALIAS && bb_applet_name[0] == 'f')
360                 opt |= GREP_OPT_F;
361
362         if (!(opt & GREP_OPT_o))
363                 reflags = REG_NOSUB;
364
365         if (ENABLE_FEATURE_GREP_EGREP_ALIAS &&
366                         (bb_applet_name[0] == 'e' || (opt & GREP_OPT_E)))
367                 reflags |= REG_EXTENDED;
368
369         if (opt & GREP_OPT_i)
370                 reflags |= REG_ICASE;
371
372         argv += optind;
373         argc -= optind;
374
375         /* if we didn't get a pattern from a -e and no command file was specified,
376          * argv[optind] should be the pattern. no pattern, no worky */
377         if (pattern_head == NULL) {
378                 if (*argv == NULL)
379                         bb_show_usage();
380                 else {
381                         char *pattern = new_grep_list_data(*argv++, 0);
382
383                         llist_add_to(&pattern_head, pattern);
384                         argc--;
385                 }
386         }
387
388         /* argv[(optind)..(argc-1)] should be names of file to grep through. If
389          * there is more than one file to grep, we will print the filenames */
390         if (argc > 1) {
391                 print_filename++;
392
393         /* If no files were specified, or '-' was specified, take input from
394          * stdin. Otherwise, we grep through all the files specified. */
395         } else if (argc == 0) {
396                 argc++;
397         }
398         matched = 0;
399         while (argc--) {
400                 cur_file = *argv++;
401                 if (!cur_file || (*cur_file == '-' && !cur_file[1])) {
402                         cur_file = "(standard input)";
403                         file = stdin;
404                 } else {
405                         file = fopen(cur_file, "r");
406                 }
407                 if (file == NULL) {
408                         if (!SUPPRESS_ERR_MSGS)
409                                 bb_perror_msg("%s", cur_file);
410                         error_open_count++;
411                 } else {
412                         matched += grep_file(file);
413                         if (matched < 0) {
414                                 /* we found a match but were told to be quiet, stop here and
415                                 * return success */
416                                 break;
417                         }
418                         fclose(file);
419                 }
420         }
421
422         /* destroy all the elments in the pattern list */
423         if (ENABLE_FEATURE_CLEAN_UP) {
424                 while (pattern_head) {
425                         llist_t *pattern_head_ptr = pattern_head;
426                         grep_list_data_t *gl =
427                                 (grep_list_data_t *)pattern_head_ptr->data;
428
429                         pattern_head = pattern_head->link;
430                         if ((gl->flg_mem_alocated_compiled & PATTERN_MEM_A))
431                                 free(gl->pattern);
432                         if ((gl->flg_mem_alocated_compiled & COMPILED))
433                                 regfree(&(gl->preg));
434                         free(pattern_head_ptr);
435                 }
436         }
437         /* 0 = success, 1 = failed, 2 = error */
438         /* If the -q option is specified, the exit status shall be zero
439          * if an input line is selected, even if an error was detected.  */
440         if (BE_QUIET && matched)
441                 return 0;
442         if (error_open_count)
443                 return 2;
444         return !matched; /* invert return value 0 = success, 1 = failed */
445 }