1 /*
2
3 grepcidr 2.0 - Filter IPv4 and IPv6 addresses matching CIDR patterns
4 Copyright (C) 2004 - 2014 Jem E. Berkes <jem@berkes.ca>
5 www.berkes.ca
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21 */
22
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <memory.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35
36 #define EXIT_OK 0
37 #define EXIT_NOMATCH 1
38 #define EXIT_ERROR 2
39
40 #define TXT_VERSION "grepcidr 2.0\nCopyright (C) 2004 - 2014 Jem E. Berkes <jem@berkes.ca>\n"
41 #define TXT_USAGE "Usage:\n" \
42 "\tgrepcidr [-V] [-cisvx] PATTERN [FILE...]\n" \
43 "\tgrepcidr [-V] [-cisvx] [-e PATTERN | -f PATFILE] [FILE...]\n"
44 #define TXT_USAGE2 "grepcidr: Specify PATTERN or -f FILE to read patterns from\n"
45 #define TXT_BADPAT "grepcidr: Not a valid pattern"
46 #define TXT_FATAL "grepcidr: Fatal error: unexpected size of data type(s) on this system!\n"
47 #define TXT_MEMORY "grepcidr: Fatal error: out of memory!\n"
48
49 /* Use GREPERROR instead of perror */
50 #define GREPERROR(prefix) fprintf(stderr, "grepcidr: %s: %s\n", prefix, strerror(errno));
51
52 #define MAXFIELD 512
53 #define TOKEN_SEPS " \t,\r\n" /* so user can specify multiple patterns on command line */
54 #define INIT_NETWORKS 8192
55
56 /*
57 Specifies a network. Whether originally in CIDR format (IP/mask)
58 or a range of IPs (IP_start-IP_end), spec is converted to a range.
59 The range is min to max (32-bit IPs) inclusive.
60 */
61 struct netspec
62 {
63 unsigned int min;
64 unsigned int max;
65 };
66
67 /* IPv6 version of pattern */
68 struct netspec6
69 {
70 unsigned char min[16];
71 unsigned char max[16];
72 };
73
74
75 /* Macro to test for valid IP address in four integers */
76 #define VALID_IP(IP) ((IP[0]<256) && (IP[1]<256) && (IP[2]<256) && (IP[3]<256))
77 /* Macro to build 32-bit IP from four integers */
78 #define BUILD_IP(IP) ((IP[0]<<24) | (IP[1]<<16) | (IP[2]<<8) | IP[3])
79
80 /* Parameters and macros for hints-based search */
81 #define HINT_LOOKAHEAD 4 /* e.g. if hints looks at P[4], then use 4 */
82
83 #define IPV4_FIELD "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz."
84 #define IPV4_BUFSIZE 16
85 #define IPV4_HINT(P) (isdigit((int)P[0]) && ((P[1]=='.') || (P[2]=='.') || (P[3]=='.')))
86
87 #define IPV6_FIELD "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:."
88 #define IPV6_BUFSIZE 46
89 #define IPV6_HINT1(P) ((P[0]==':') && (P[1]==':') && isxdigit((int)P[2]))
90 #define IPV6_HINT2(P) (isxdigit((int)P[0]) && (P[1]==':'))
91 #define IPV6_HINT3(P) (isxdigit((int)P[0]) && isxdigit((int)P[1]) && (P[2]==':'))
92 #define IPV6_HINT4(P) (isxdigit((int)P[0]) && isxdigit((int)P[1]) && isxdigit((int)P[2]) && (P[3]==':'))
93 #define IPV6_HINT5(P) (isxdigit((int)P[0]) && isxdigit((int)P[1]) && isxdigit((int)P[2]) && isxdigit((int)P[3]) && (P[4]==':'))
94
95
96 /* Global variables */
97 int anymatch = 0; /* did anything match? for exit code */
98 int invert = 0; /* flag for inverted mode */
99 unsigned int counting = 0; /* when non-zero, counts matches */
100 int include_noip = 0; /* flag to include lines without IPs when inverting */
101 int strict_align = 0; /* flag to enforce strict base alignment */
102 int strict_nosearch = 0; /* flag for original style, single IP match */
103 int match_one = 0; /* for -v, have matched one IP on this line */
104 int seen_ip = 0; /* for -v, have seen an IP on this line */
105 int shownames = 0; /* show file names with output lines */
106
107 unsigned int patterns = 0; /* total patterns in IPv4 array */
108 unsigned int capacity = 0; /* current capacity of IPv4 array */
109 struct netspec* array = NULL; /* IPv4 array of patterns */
110
111 unsigned int patterns6 = 0; /* total patterns in IPv6 array */
112 unsigned int capacity6 = 0; /* current capacity of IPv6 array */
113 struct netspec6* array6 = NULL; /* IPv6 array of patterns */
114
115 /*
116 Insert new spec inside array of network spec
117 Dynamically grow array buffer as needed
118 The array must have already been initially allocated, with valid capacity
119 */
array_insert(struct netspec * newspec)120 void array_insert(struct netspec* newspec)
121 {
122 if (patterns == capacity)
123 {
124 capacity *= 2;
125 array = realloc(array, capacity*sizeof(struct netspec));
126 }
127 array[patterns++] = *newspec;
128 }
129
array_insert6(struct netspec6 * newspec)130 void array_insert6(struct netspec6* newspec)
131 {
132 if (patterns6 == capacity6)
133 {
134 capacity6 *= 2;
135 array6 = realloc(array6, capacity6*sizeof(struct netspec6));
136 }
137 array6[patterns6++] = *newspec;
138 }
139
140
141 /*
142 Convert IPv4 address string at location p, string length len,
143 into result which must point to an unsigned int.
144 Returns 1 on success, 0 on failure
145 */
ipv4_to_uint(const char * p,unsigned long len,unsigned int * result)146 int ipv4_to_uint(const char* p, unsigned long len, unsigned int* result)
147 {
148 unsigned char parsed[4] = { 0 };
149 char buf[IPV4_BUFSIZE];
150 if (len > IPV4_BUFSIZE-1) return 0; /* too long to be a valid IPv4 */
151 memset(buf, 0, sizeof(buf));
152 memcpy(buf, p, len);
153 if (inet_pton(AF_INET, buf, &parsed) == 1)
154 {
155 *result = BUILD_IP(parsed);
156 return 1;
157 }
158 else
159 return 0;
160 }
161
162
163 /*
164 Convert IPv6 address string at location p, string length len,
165 into result which must point to 16 byte unsigned char array.
166 This uses system's inet_pton() which is quite fast on Linux.
167 Returns 1 on success, 0 on failure
168 */
ipv6_to_uchar(const char * p,unsigned long len,unsigned char * result)169 int ipv6_to_uchar(const char* p, unsigned long len, unsigned char* result)
170 {
171 char buf[IPV6_BUFSIZE];
172 if (len > IPV6_BUFSIZE-1) return 0; /* too long to be a valid IPv6 */
173 memset(buf, 0, sizeof(buf));
174 memcpy(buf, p, len);
175 if (inet_pton(AF_INET6, buf, result) == 1)
176 return 1;
177 else
178 return 0;
179 }
180
181
182 /*
183 Increment IPv6 address (array at input) and store result.
184 Both input and result must point to 16 byte unsigned char array.
185 */
ipv6_increment(unsigned char * input,unsigned char * result)186 void ipv6_increment(unsigned char* input, unsigned char* result)
187 {
188 int i, carry=1;
189 for (i=15; i>=0; i--) /* network byte order is big endian */
190 {
191 int sum = input[i] + carry;
192 result[i] = (unsigned char)(sum & 0xFF);
193 carry = sum >> 8;
194 }
195 }
196
197
198 /*
199 Given string, fills in the struct netspec (must be allocated)
200 Accept CIDR IP/mask format or IP_start-IP_end range.
201 Returns true (nonzero) on success, false (zero) on failure.
202 */
net_parse(const char * line,struct netspec * spec)203 int net_parse(const char* line, struct netspec* spec)
204 {
205 unsigned int IP1[4], IP2[4];
206 int maskbits = 32; /* if using CIDR IP/mask format */
207
208 /* Try parsing IP/mask, CIDR format */
209 if (strchr(line, '/') && (sscanf(line, "%u.%u.%u.%u/%d", &IP1[0], &IP1[1], &IP1[2], &IP1[3], &maskbits) == 5)
210 && VALID_IP(IP1))
211 {
212 unsigned int ipaddress = BUILD_IP(IP1);
213 if (maskbits == 0)
214 {
215 if (strict_align && (ipaddress != 0))
216 return 0; /* invalid */
217 spec->min = 0;
218 spec->max = 0xFFFFFFFF;
219 return 1;
220 }
221 else if ((maskbits < 0) || (maskbits > 32))
222 return 0; /* invalid */
223 if (strict_align && (ipaddress & (((1 << (32-maskbits))-1) & 0xFFFFFFFF)))
224 return 0; /* invalid, there are non-zero host bits */
225 spec->min = ipaddress & (~((1 << (32-maskbits))-1) & 0xFFFFFFFF);
226 spec->max = spec->min | (((1 << (32-maskbits))-1) & 0xFFFFFFFF);
227 return 1;
228 }
229 /* Try parsing a range. Spaces around hyphen are optional. */
230 else if (strchr(line, '-') && (sscanf(line, "%u.%u.%u.%u - %u.%u.%u.%u", &IP1[0], &IP1[1], &IP1[2], &IP1[3],
231 &IP2[0], &IP2[1], &IP2[2], &IP2[3]) == 8) && VALID_IP(IP1) && VALID_IP(IP2))
232 {
233 spec->min = BUILD_IP(IP1);
234 spec->max = BUILD_IP(IP2);
235 if (spec->max >= spec->min)
236 return 1;
237 else
238 return 0;
239 }
240 /* Try simple IP address */
241 else if ((sscanf(line, "%u.%u.%u.%u", &IP1[0], &IP1[1], &IP1[2], &IP1[3]) == 4) && VALID_IP(IP1))
242 {
243 spec->min = BUILD_IP(IP1);
244 spec->max = spec->min;
245 return 1;
246 }
247 return 0; /* could not parse */
248 }
249
250
251 /*
252 Parse an IPv6 pattern (struct netspec6)
253 Accepts IP or IP/x format
254 Returns true (nonzero) on success, false (zero) on failure.
255 */
net_parse6(const char * line,struct netspec6 * v6spec)256 int net_parse6(const char* line, struct netspec6* v6spec)
257 {
258 size_t field_len = strspn(line, IPV6_FIELD);
259 unsigned char address[16] = { 0 };
260 int maskbits = 128;
261
262 if (!ipv6_to_uchar(line, field_len, address))
263 return 0; /* no IPv6 found here */
264 /* Simple IPv6 address is in address */
265 memcpy(v6spec->min, address, 16);
266 memcpy(v6spec->max, address, 16);
267 if (sscanf(line+field_len, "/%d", &maskbits) == 1)
268 {
269 unsigned char mask;
270 int bytenum;
271 if ((maskbits < 0) || (maskbits > 128))
272 return 0; /* invalid */
273 if (maskbits == 0)
274 bytenum = -1;
275 else
276 {
277 bytenum = (maskbits-1)/8;
278 mask = (unsigned char)0xFF << (7 - ((maskbits-1)%8));
279 v6spec->min[bytenum] &= mask;
280 v6spec->max[bytenum] |= (unsigned char)~mask;
281 }
282 for (++bytenum; bytenum<16; bytenum++)
283 {
284 v6spec->min[bytenum] = 0;
285 v6spec->max[bytenum] = 0xFF;
286 }
287 if (strict_align && (memcmp(v6spec->min, address, 16) != 0))
288 return 0; /* bad CIDR alignment */
289 }
290 return 1;
291 }
292
293
294 /* Compare two netspecs, for sorting. Comparison is done on minimum of range */
netsort(const void * a,const void * b)295 int netsort(const void* a, const void* b)
296 {
297 unsigned int c1 = ((struct netspec*)a)->min;
298 unsigned int c2 = ((struct netspec*)b)->min;
299 if (c1 < c2) return -1;
300 if (c1 > c2) return +1;
301 return 0;
302 }
303
304
netsort6(const void * a,const void * b)305 int netsort6(const void* a, const void* b)
306 {
307 unsigned char* c1 = ((struct netspec6*)a)->min;
308 unsigned char* c2 = ((struct netspec6*)b)->min;
309 int n = memcmp(c1, c2, 16);
310 if (n < 0) return -1;
311 if (n > 0) return +1;
312 return 0;
313 }
314
315
316 /* Compare two netspecs, for searching. Test if key (only min) is inside range */
netsearch(const void * a,const void * b)317 int netsearch(const void* a, const void* b)
318 {
319 unsigned int key = ((struct netspec*)a)->min;
320 unsigned int min = ((struct netspec*)b)->min;
321 unsigned int max = ((struct netspec*)b)->max;
322 if (key < min) return -1;
323 if (key > max) return +1;
324 return 0;
325 }
326
327
netsearch6(const void * a,const void * b)328 int netsearch6(const void* a, const void* b)
329 {
330 unsigned char* key = ((struct netspec6*)a)->min;
331 unsigned char* min = ((struct netspec6*)b)->min;
332 unsigned char* max = ((struct netspec6*)b)->max;
333 if (memcmp(key, min, 16) < 0) return -1;
334 if (memcmp(key, max, 16) > 0) return +1;
335 return 0;
336 }
337
338
339 /* Action to take upon a matching line, print the line or count it */
print_or_count(char * line,const char * filename)340 void print_or_count(char* line, const char* filename)
341 {
342 anymatch = 1;
343 if (counting)
344 counting++;
345 else
346 {
347 if (filename && shownames)
348 printf("%s:", filename);
349 printf("%s", line);
350 }
351 }
352
353
354 /*
355 Search for this IP (passed in key) among loaded network patterns and determine if it's a "match"
356 Provide either an IPv4 or IPv6 key, but not both. Returns true or false, whether IP matched patterns.
357 */
match_ip(struct netspec * v4key,struct netspec6 * v6key,char * line,const char * filename)358 int match_ip(struct netspec* v4key, struct netspec6* v6key, char* line, const char* filename)
359 {
360 int match = 0;
361 seen_ip = 1;
362 if (v4key && bsearch(v4key, array, patterns, sizeof(struct netspec), netsearch))
363 match = 1;
364 else if (v6key && bsearch(v6key, array6, patterns6, sizeof(struct netspec6), netsearch6))
365 match = 1;
366 if (match)
367 {
368 match_one = 1;
369 if (!invert) print_or_count(line, filename); /* take action if not using -v */
370 }
371 return match;
372 }
373
374
375 /* Load patterns defining networks */
load_patterns(const char * pat_filename,char * pat_strings)376 void load_patterns(const char* pat_filename, char* pat_strings)
377 {
378 if (pat_filename)
379 {
380 FILE* data = fopen(pat_filename, "r");
381 if (data)
382 {
383 char line[MAXFIELD];
384 while (fgets(line, sizeof(line), data))
385 {
386 struct netspec ipv4_pat;
387 struct netspec6 ipv6_pat;
388 if ((*line=='#')||(*line=='\n')||(*line=='\r'))
389 continue; /* skip blank lines and comments */
390 if (net_parse(line, &ipv4_pat))
391 array_insert(&ipv4_pat);
392 else if (net_parse6(line, &ipv6_pat))
393 array_insert6(&ipv6_pat);
394 else
395 fprintf(stderr, TXT_BADPAT ": %s", line);
396 }
397 fclose(data);
398 }
399 else
400 {
401 GREPERROR(pat_filename);
402 exit(EXIT_ERROR);
403 }
404 }
405 if (pat_strings)
406 {
407 char* token = strtok(pat_strings, TOKEN_SEPS);
408 while (token)
409 {
410 struct netspec ipv4_pat;
411 struct netspec6 ipv6_pat;
412 if (net_parse(token, &ipv4_pat))
413 array_insert(&ipv4_pat);
414 else if (net_parse6(token, &ipv6_pat))
415 array_insert6(&ipv6_pat);
416 else
417 fprintf(stderr, TXT_BADPAT ": %s\n", token);
418 token = strtok(NULL, TOKEN_SEPS);
419 }
420 }
421
422 /* Prepare array for rapid searching */
423 if (patterns)
424 {
425 unsigned int item;
426 qsort(array, patterns, sizeof(struct netspec), netsort);
427 /* cure overlaps so that ranges are disjoint and consistent */
428 for (item=1; item<patterns; item++)
429 {
430 if (array[item].max <= array[item-1].max)
431 array[item] = array[item-1];
432 else if (array[item].min <= array[item-1].max)
433 array[item].min = array[item-1].max + 1; /* overflow possibility */
434 }
435 }
436 if (patterns6)
437 {
438 unsigned int item;
439 qsort(array6, patterns6, sizeof(struct netspec6), netsort6);
440 /* cure overlaps so that ranges are disjoint and consistent */
441 for (item=1; item<patterns6; item++)
442 {
443 if (memcmp(array6[item].max, array6[item-1].max, 16) <= 0)
444 array6[item] = array6[item-1];
445 else if (memcmp(array6[item].min, array6[item-1].max, 16) <= 0)
446 ipv6_increment(array6[item-1].max, array6[item].min);
447 }
448 }
449 }
450
451
452 /*
453 Scan the buffer (one line) for IPv4 and IPv6 addresses.
454 While moving through buffer, look for 'hints' that a valid address may
455 exist at this location before trying parser. This is much faster than regex.
456 We check p[0] so that we stop at terminating nul char.
457 */
scan_with_hints(char * buffer,unsigned long bufsize,const char * filename)458 void scan_with_hints(char* buffer, unsigned long bufsize, const char* filename)
459 {
460 char* p;
461 char* max = buffer + bufsize - 1 - HINT_LOOKAHEAD;
462 if (bufsize <= HINT_LOOKAHEAD)
463 return;
464 for (p = buffer; (p < max) && p[0]; p++)
465 {
466 /* Search for IPv4 */
467 if (patterns && IPV4_HINT(p))
468 {
469 size_t field_len = strspn(p, IPV4_FIELD);
470 struct netspec v4key;
471 if (ipv4_to_uint(p, field_len, &v4key.min))
472 {
473 if (match_ip(&v4key, NULL, buffer, filename))
474 return; /* found match, stop scanning inside line */
475 }
476 p += field_len - 1;
477 continue;
478 }
479 /* Search for IPv6 */
480 if (patterns6)
481 {
482 if (IPV6_HINT1(p)||IPV6_HINT2(p)||IPV6_HINT3(p)||IPV6_HINT4(p)||IPV6_HINT5(p))
483 {
484 size_t field_len = strspn(p, IPV6_FIELD);
485 struct netspec6 v6key;
486 if (ipv6_to_uchar(p, field_len, v6key.min))
487 {
488 if (match_ip(NULL, &v6key, buffer, filename))
489 return; /* found match, stop scanning inside line */
490 }
491 p += field_len - 1;
492 continue;
493 }
494 }
495 }
496 }
497
498
499 /*
500 Somewhat like GNU getline(), returns an arbitrarily long whole line in *bufptr
501 Returns 0 when end of stream occurs and no characters are read, or 1 if a line is read.
502 Buffer remains allocated in multiple calls, but caller should free *bufptr when done.
503 */
fgets_whole_line(char ** bufptr,size_t * bufsize,FILE * stream)504 int fgets_whole_line(char **bufptr, size_t *bufsize, FILE* stream)
505 {
506 size_t bufcur = 0;
507 if (*bufptr == NULL)
508 {
509 *bufptr = malloc(MAXFIELD);
510 *bufsize = MAXFIELD;
511 if (*bufptr == NULL)
512 {
513 fprintf(stderr, TXT_MEMORY);
514 exit(EXIT_ERROR);
515 }
516 }
517 while (fgets((*bufptr)+bufcur, (*bufsize)-bufcur, stream))
518 {
519 size_t len = bufcur + strlen(*bufptr + bufcur);
520 if (len < 1) return 1;
521 if ((bufptr[0][len-1] == '\n') || feof(stream))
522 return 1;
523 *bufsize *= 2;
524 *bufptr = realloc(*bufptr, *bufsize);
525 if (*bufptr == NULL)
526 {
527 fprintf(stderr, TXT_MEMORY);
528 exit(EXIT_ERROR);
529 }
530 bufcur = len;
531 }
532 return 0;
533 }
534
535
536 /* Match IPs from input stream to network patterns */
search_stream(FILE * input_stream,const char * filename)537 void search_stream(FILE* input_stream, const char* filename)
538 {
539 char* line = NULL;
540 size_t linesize = 0;
541 while (fgets_whole_line(&line, &linesize, input_stream))
542 {
543 match_one = 0;
544 seen_ip = include_noip;
545 if (strict_nosearch)
546 {
547 /* Faster old-style search, look for single IP from start of line */
548 int ipv4_match = 0;
549 if (patterns)
550 {
551 size_t field_len = strspn(line, IPV4_FIELD);
552 struct netspec v4key;
553 if (ipv4_to_uint(line, field_len, &v4key.min))
554 ipv4_match = match_ip(&v4key, NULL, line, filename);
555 }
556 if (!ipv4_match && patterns6)
557 {
558 size_t field_len = strspn(line, IPV6_FIELD);
559 struct netspec6 v6key;
560 if (ipv6_to_uchar(line, field_len, v6key.min))
561 match_ip(NULL, &v6key, line, filename);
562 }
563 }
564 else
565 scan_with_hints(line, linesize, filename); /* scan whole line */
566
567 /* If using -i or -v, take action once the whole line has been processed */
568 if (invert && seen_ip && !match_one)
569 print_or_count(line, filename);
570 }
571 free(line);
572 }
573
574
575
main(int argc,char * argv[])576 int main(int argc, char* argv[])
577 {
578 static char shortopts[] = "ce:f:isvxV";
579 char* pat_filename = NULL; /* filename containing patterns */
580 char* pat_strings = NULL; /* pattern strings on command line */
581 int foundopt;
582
583 if ((CHAR_BIT != 8) || (sizeof(unsigned int) < 4) ||
584 (sizeof(struct in_addr) != 4) || (sizeof(struct in6_addr) != 16))
585 {
586 fprintf(stderr, TXT_FATAL);
587 return EXIT_ERROR;
588 }
589
590 if (argc == 1)
591 {
592 fprintf(stderr, TXT_USAGE);
593 return EXIT_ERROR;
594 }
595
596 while ((foundopt = getopt(argc, argv, shortopts)) != -1)
597 {
598 switch (foundopt)
599 {
600 case 'V':
601 puts(TXT_VERSION);
602 return EXIT_ERROR;
603
604 case 'c':
605 counting = 1;
606 break;
607
608 case 's':
609 strict_align = 1;
610 break;
611
612 case 'i':
613 include_noip = 1;
614 case 'v':
615 invert = 1;
616 break;
617
618 case 'x':
619 strict_nosearch = 1;
620 break;
621
622 case 'e':
623 pat_strings = optarg;
624 break;
625
626 case 'f':
627 pat_filename = optarg;
628 break;
629
630 default:
631 fprintf(stderr, TXT_USAGE);
632 return EXIT_ERROR;
633 }
634 }
635
636 if (!pat_filename && !pat_strings)
637 {
638 if (optind < argc)
639 pat_strings = argv[optind++];
640 else
641 {
642 fprintf(stderr, TXT_USAGE2);
643 return EXIT_ERROR;
644 }
645 }
646
647 /* Initial array allocation */
648 capacity = INIT_NETWORKS;
649 array = (struct netspec*) malloc(capacity*sizeof(struct netspec));
650 capacity6 = INIT_NETWORKS;
651 array6 = (struct netspec6*) malloc(capacity6*sizeof(struct netspec6));
652 if (!array || !array6)
653 {
654 fprintf(stderr, TXT_MEMORY);
655 return EXIT_ERROR;
656 }
657
658 load_patterns(pat_filename, pat_strings);
659 do
660 { /* Search each specified file name, or just stdin */
661 const char* curfilename = NULL;
662 FILE* inp_stream;
663 if (optind >= argc)
664 inp_stream = stdin;
665 else
666 {
667 /* One or more file names are specified on the command line */
668 if (optind+1 < argc) shownames = 1; /* more than one file */
669 curfilename = argv[optind++];
670 inp_stream = fopen(curfilename, "r");
671 if (!inp_stream)
672 {
673 GREPERROR(curfilename);
674 return EXIT_ERROR;
675 }
676 }
677 /* Ready to search this stream or file */
678 search_stream(inp_stream, curfilename);
679 if (inp_stream != stdin)
680 fclose(inp_stream);
681 } while (optind < argc);
682
683 if (counting)
684 printf("%u\n", counting-1);
685 /* Cleanup */
686 if (array)
687 free(array);
688 if (array6)
689 free(array6);
690 if (anymatch)
691 return EXIT_OK;
692 else
693 return EXIT_NOMATCH;
694 }
695