grep.c revision 1.6 1 /* $NetBSD: grep.c,v 1.6 2011/04/18 03:48:23 joerg Exp $ */
2 /* $FreeBSD: head/usr.bin/grep/grep.c 211519 2010-08-19 22:55:17Z delphij $ */
3 /* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */
4
5 /*-
6 * Copyright (c) 1999 James Howard and Dag-Erling Codan Smrgrav
7 * Copyright (C) 2008-2009 Gabor Kovesdan <gabor (at) FreeBSD.org>
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #if HAVE_NBTOOL_CONFIG_H
33 #include "nbtool_config.h"
34 #endif
35
36 #include <sys/cdefs.h>
37 __RCSID("$NetBSD: grep.c,v 1.6 2011/04/18 03:48:23 joerg Exp $");
38
39 #include <sys/stat.h>
40 #include <sys/types.h>
41
42 #include <ctype.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <getopt.h>
46 #include <limits.h>
47 #include <libgen.h>
48 #include <locale.h>
49 #include <stdbool.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54
55 #include "grep.h"
56
57 #ifndef WITHOUT_NLS
58 #include <nl_types.h>
59 nl_catd catalog;
60 #endif
61
62 /*
63 * Default messags to use when NLS is disabled or no catalogue
64 * is found.
65 */
66 const char *errstr[] = {
67 "",
68 /* 1*/ "(standard input)",
69 /* 2*/ "cannot read bzip2 compressed file",
70 /* 3*/ "unknown %s option",
71 /* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZ] [-A num] [-B num] [-C[num]]\n",
72 /* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n",
73 /* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n",
74 /* 7*/ "\t[--null] [pattern] [file ...]\n",
75 /* 8*/ "Binary file %s matches\n",
76 /* 9*/ "%s (BSD grep) %s\n",
77 };
78
79 /* Flags passed to regcomp() and regexec() */
80 int cflags = 0;
81 int eflags = REG_STARTEND;
82
83 /* Shortcut for matching all cases like empty regex */
84 bool matchall;
85
86 /* Searching patterns */
87 unsigned int patterns, pattern_sz;
88 char **pattern;
89 regex_t *r_pattern;
90 fastgrep_t *fg_pattern;
91
92 /* Filename exclusion/inclusion patterns */
93 unsigned int fpatterns, fpattern_sz;
94 unsigned int dpatterns, dpattern_sz;
95 struct epat *dpattern, *fpattern;
96
97 /* For regex errors */
98 char re_error[RE_ERROR_BUF + 1];
99
100 /* Command-line flags */
101 unsigned long long Aflag; /* -A x: print x lines trailing each match */
102 unsigned long long Bflag; /* -B x: print x lines leading each match */
103 bool Hflag; /* -H: always print file name */
104 bool Lflag; /* -L: only show names of files with no matches */
105 bool bflag; /* -b: show block numbers for each match */
106 bool cflag; /* -c: only show a count of matching lines */
107 bool hflag; /* -h: don't print filename headers */
108 bool iflag; /* -i: ignore case */
109 bool lflag; /* -l: only show names of files with matches */
110 bool mflag; /* -m x: stop reading the files after x matches */
111 unsigned long long mcount; /* count for -m */
112 bool nflag; /* -n: show line numbers in front of matching lines */
113 bool oflag; /* -o: print only matching part */
114 bool qflag; /* -q: quiet mode (don't output anything) */
115 bool sflag; /* -s: silent mode (ignore errors) */
116 bool vflag; /* -v: only show non-matching lines */
117 bool wflag; /* -w: pattern must start and end on word boundaries */
118 bool xflag; /* -x: pattern must match entire line */
119 bool lbflag; /* --line-buffered */
120 bool nullflag; /* --null */
121 char *label; /* --label */
122 const char *color; /* --color */
123 int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */
124 int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */
125 int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */
126 int devbehave = DEV_READ; /* -D: handling of devices */
127 int dirbehave = DIR_READ; /* -dRr: handling of directories */
128 int linkbehave = LINK_READ; /* -OpS: handling of symlinks */
129
130 bool dexclude, dinclude; /* --exclude-dir and --include-dir */
131 bool fexclude, finclude; /* --exclude and --include */
132
133 enum {
134 BIN_OPT = CHAR_MAX + 1,
135 COLOR_OPT,
136 HELP_OPT,
137 MMAP_OPT,
138 LINEBUF_OPT,
139 LABEL_OPT,
140 NULL_OPT,
141 R_EXCLUDE_OPT,
142 R_INCLUDE_OPT,
143 R_DEXCLUDE_OPT,
144 R_DINCLUDE_OPT
145 };
146
147 static inline const char *init_color(const char *);
148
149 /* Housekeeping */
150 bool first = true; /* flag whether we are processing the first match */
151 bool prev; /* flag whether or not the previous line matched */
152 int tail; /* lines left to print */
153 bool notfound; /* file not found */
154
155 extern char *__progname;
156
157 /*
158 * Prints usage information and returns 2.
159 */
160 static void
161 usage(void)
162 {
163 fprintf(stderr, getstr(4), __progname);
164 fprintf(stderr, "%s", getstr(5));
165 fprintf(stderr, "%s", getstr(5));
166 fprintf(stderr, "%s", getstr(6));
167 fprintf(stderr, "%s", getstr(7));
168 exit(2);
169 }
170
171 static const char *optstr = "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxy";
172
173 struct option long_options[] =
174 {
175 {"binary-files", required_argument, NULL, BIN_OPT},
176 {"help", no_argument, NULL, HELP_OPT},
177 {"mmap", no_argument, NULL, MMAP_OPT},
178 {"line-buffered", no_argument, NULL, LINEBUF_OPT},
179 {"label", required_argument, NULL, LABEL_OPT},
180 {"null", no_argument, NULL, NULL_OPT},
181 {"color", optional_argument, NULL, COLOR_OPT},
182 {"colour", optional_argument, NULL, COLOR_OPT},
183 {"exclude", required_argument, NULL, R_EXCLUDE_OPT},
184 {"include", required_argument, NULL, R_INCLUDE_OPT},
185 {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT},
186 {"include-dir", required_argument, NULL, R_DINCLUDE_OPT},
187 {"after-context", required_argument, NULL, 'A'},
188 {"text", no_argument, NULL, 'a'},
189 {"before-context", required_argument, NULL, 'B'},
190 {"byte-offset", no_argument, NULL, 'b'},
191 {"context", optional_argument, NULL, 'C'},
192 {"count", no_argument, NULL, 'c'},
193 {"devices", required_argument, NULL, 'D'},
194 {"directories", required_argument, NULL, 'd'},
195 {"extended-regexp", no_argument, NULL, 'E'},
196 {"regexp", required_argument, NULL, 'e'},
197 {"fixed-strings", no_argument, NULL, 'F'},
198 {"file", required_argument, NULL, 'f'},
199 {"basic-regexp", no_argument, NULL, 'G'},
200 {"no-filename", no_argument, NULL, 'h'},
201 {"with-filename", no_argument, NULL, 'H'},
202 {"ignore-case", no_argument, NULL, 'i'},
203 {"bz2decompress", no_argument, NULL, 'J'},
204 {"files-with-matches", no_argument, NULL, 'l'},
205 {"files-without-match", no_argument, NULL, 'L'},
206 {"max-count", required_argument, NULL, 'm'},
207 {"line-number", no_argument, NULL, 'n'},
208 {"only-matching", no_argument, NULL, 'o'},
209 {"quiet", no_argument, NULL, 'q'},
210 {"silent", no_argument, NULL, 'q'},
211 {"recursive", no_argument, NULL, 'r'},
212 {"no-messages", no_argument, NULL, 's'},
213 {"binary", no_argument, NULL, 'U'},
214 {"unix-byte-offsets", no_argument, NULL, 'u'},
215 {"invert-match", no_argument, NULL, 'v'},
216 {"version", no_argument, NULL, 'V'},
217 {"word-regexp", no_argument, NULL, 'w'},
218 {"line-regexp", no_argument, NULL, 'x'},
219 {"decompress", no_argument, NULL, 'Z'},
220 {NULL, no_argument, NULL, 0}
221 };
222
223 /*
224 * Adds a searching pattern to the internal array.
225 */
226 static void
227 add_pattern(char *pat, size_t len)
228 {
229
230 /* Check if we can do a shortcut */
231 if (len == 0 || matchall) {
232 matchall = true;
233 return;
234 }
235 /* Increase size if necessary */
236 if (patterns == pattern_sz) {
237 pattern_sz *= 2;
238 pattern = grep_realloc(pattern, ++pattern_sz *
239 sizeof(*pattern));
240 }
241 if (len > 0 && pat[len - 1] == '\n')
242 --len;
243 /* pat may not be NUL-terminated */
244 pattern[patterns] = grep_malloc(len + 1);
245 memcpy(pattern[patterns], pat, len);
246 pattern[patterns][len] = '\0';
247 ++patterns;
248 }
249
250 /*
251 * Adds a file include/exclude pattern to the internal array.
252 */
253 static void
254 add_fpattern(const char *pat, int mode)
255 {
256
257 /* Increase size if necessary */
258 if (fpatterns == fpattern_sz) {
259 fpattern_sz *= 2;
260 fpattern = grep_realloc(fpattern, ++fpattern_sz *
261 sizeof(struct epat));
262 }
263 fpattern[fpatterns].pat = grep_strdup(pat);
264 fpattern[fpatterns].mode = mode;
265 ++fpatterns;
266 }
267
268 /*
269 * Adds a directory include/exclude pattern to the internal array.
270 */
271 static void
272 add_dpattern(const char *pat, int mode)
273 {
274
275 /* Increase size if necessary */
276 if (dpatterns == dpattern_sz) {
277 dpattern_sz *= 2;
278 dpattern = grep_realloc(dpattern, ++dpattern_sz *
279 sizeof(struct epat));
280 }
281 dpattern[dpatterns].pat = grep_strdup(pat);
282 dpattern[dpatterns].mode = mode;
283 ++dpatterns;
284 }
285
286 /*
287 * Reads searching patterns from a file and adds them with add_pattern().
288 */
289 static void
290 read_patterns(const char *fn)
291 {
292 FILE *f;
293 char *line;
294 size_t len;
295 ssize_t rlen;
296
297 if ((f = fopen(fn, "r")) == NULL)
298 err(2, "%s", fn);
299 line = NULL;
300 len = 0;
301 while ((rlen = getline(&line, &len, f)) != -1)
302 add_pattern(line, *line == '\n' ? 0 : (size_t)rlen);
303 free(line);
304 if (ferror(f))
305 err(2, "%s", fn);
306 fclose(f);
307 }
308
309 static inline const char *
310 init_color(const char *d)
311 {
312 char *c;
313
314 c = getenv("GREP_COLOR");
315 return (c != NULL ? c : d);
316 }
317
318 int
319 main(int argc, char *argv[])
320 {
321 char **aargv, **eargv, *eopts;
322 char *ep;
323 unsigned long long l;
324 unsigned int aargc, eargc, i;
325 int c, lastc, needpattern, newarg, prevoptind;
326
327 setlocale(LC_ALL, "");
328
329 #ifndef WITHOUT_NLS
330 catalog = catopen("grep", NL_CAT_LOCALE);
331 #endif
332
333 /* Check what is the program name of the binary. In this
334 way we can have all the funcionalities in one binary
335 without the need of scripting and using ugly hacks. */
336 switch (__progname[0]) {
337 case 'e':
338 grepbehave = GREP_EXTENDED;
339 break;
340 case 'f':
341 grepbehave = GREP_FIXED;
342 break;
343 case 'g':
344 grepbehave = GREP_BASIC;
345 break;
346 case 'z':
347 filebehave = FILE_GZIP;
348 switch(__progname[1]) {
349 case 'e':
350 grepbehave = GREP_EXTENDED;
351 break;
352 case 'f':
353 grepbehave = GREP_FIXED;
354 break;
355 case 'g':
356 grepbehave = GREP_BASIC;
357 break;
358 }
359 break;
360 }
361
362 lastc = '\0';
363 newarg = 1;
364 prevoptind = 1;
365 needpattern = 1;
366
367 eopts = getenv("GREP_OPTIONS");
368
369 /* support for extra arguments in GREP_OPTIONS */
370 eargc = 0;
371 if (eopts != NULL) {
372 char *str;
373
374 /* make an estimation of how many extra arguments we have */
375 for (unsigned int j = 0; j < strlen(eopts); j++)
376 if (eopts[j] == ' ')
377 eargc++;
378
379 eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1));
380
381 eargc = 0;
382 /* parse extra arguments */
383 while ((str = strsep(&eopts, " ")) != NULL)
384 eargv[eargc++] = grep_strdup(str);
385
386 aargv = (char **)grep_calloc(eargc + argc + 1,
387 sizeof(char *));
388
389 aargv[0] = argv[0];
390 for (i = 0; i < eargc; i++)
391 aargv[i + 1] = eargv[i];
392 for (int j = 1; j < argc; j++, i++)
393 aargv[i + 1] = argv[j];
394
395 aargc = eargc + argc;
396 } else {
397 aargv = argv;
398 aargc = argc;
399 }
400
401 while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) !=
402 -1)) {
403 switch (c) {
404 case '0': case '1': case '2': case '3': case '4':
405 case '5': case '6': case '7': case '8': case '9':
406 if (newarg || !isdigit(lastc))
407 Aflag = 0;
408 else if (Aflag > LLONG_MAX / 10) {
409 errno = ERANGE;
410 err(2, NULL);
411 }
412 Aflag = Bflag = (Aflag * 10) + (c - '0');
413 break;
414 case 'C':
415 if (optarg == NULL) {
416 Aflag = Bflag = 2;
417 break;
418 }
419 /* FALLTHROUGH */
420 case 'A':
421 /* FALLTHROUGH */
422 case 'B':
423 errno = 0;
424 l = strtoull(optarg, &ep, 10);
425 if (((errno == ERANGE) && (l == ULLONG_MAX)) ||
426 ((errno == EINVAL) && (l == 0)))
427 err(2, NULL);
428 else if (ep[0] != '\0') {
429 errno = EINVAL;
430 err(2, NULL);
431 }
432 if (c == 'A')
433 Aflag = l;
434 else if (c == 'B')
435 Bflag = l;
436 else
437 Aflag = Bflag = l;
438 break;
439 case 'a':
440 binbehave = BINFILE_TEXT;
441 break;
442 case 'b':
443 bflag = true;
444 break;
445 case 'c':
446 cflag = true;
447 break;
448 case 'D':
449 if (strcasecmp(optarg, "skip") == 0)
450 devbehave = DEV_SKIP;
451 else if (strcasecmp(optarg, "read") == 0)
452 devbehave = DEV_READ;
453 else
454 errx(2, getstr(3), "--devices");
455 break;
456 case 'd':
457 if (strcasecmp("recurse", optarg) == 0) {
458 Hflag = true;
459 dirbehave = DIR_RECURSE;
460 } else if (strcasecmp("skip", optarg) == 0)
461 dirbehave = DIR_SKIP;
462 else if (strcasecmp("read", optarg) == 0)
463 dirbehave = DIR_READ;
464 else
465 errx(2, getstr(3), "--directories");
466 break;
467 case 'E':
468 grepbehave = GREP_EXTENDED;
469 break;
470 case 'e':
471 add_pattern(optarg, strlen(optarg));
472 needpattern = 0;
473 break;
474 case 'F':
475 grepbehave = GREP_FIXED;
476 break;
477 case 'f':
478 read_patterns(optarg);
479 needpattern = 0;
480 break;
481 case 'G':
482 grepbehave = GREP_BASIC;
483 break;
484 case 'H':
485 Hflag = true;
486 break;
487 case 'h':
488 Hflag = false;
489 hflag = true;
490 break;
491 case 'I':
492 binbehave = BINFILE_SKIP;
493 break;
494 case 'i':
495 case 'y':
496 iflag = true;
497 cflags |= REG_ICASE;
498 break;
499 case 'J':
500 filebehave = FILE_BZIP;
501 break;
502 case 'L':
503 lflag = false;
504 Lflag = true;
505 break;
506 case 'l':
507 Lflag = false;
508 lflag = true;
509 break;
510 case 'm':
511 mflag = true;
512 errno = 0;
513 mcount = strtoull(optarg, &ep, 10);
514 if (((errno == ERANGE) && (mcount == ULLONG_MAX)) ||
515 ((errno == EINVAL) && (mcount == 0)))
516 err(2, NULL);
517 else if (ep[0] != '\0') {
518 errno = EINVAL;
519 err(2, NULL);
520 }
521 break;
522 case 'n':
523 nflag = true;
524 break;
525 case 'O':
526 linkbehave = LINK_EXPLICIT;
527 break;
528 case 'o':
529 oflag = true;
530 break;
531 case 'p':
532 linkbehave = LINK_SKIP;
533 break;
534 case 'q':
535 qflag = true;
536 break;
537 case 'S':
538 linkbehave = LINK_READ;
539 break;
540 case 'R':
541 case 'r':
542 dirbehave = DIR_RECURSE;
543 Hflag = true;
544 break;
545 case 's':
546 sflag = true;
547 break;
548 case 'U':
549 binbehave = BINFILE_BIN;
550 break;
551 case 'u':
552 case MMAP_OPT:
553 /* noop, compatibility */
554 break;
555 case 'V':
556 printf(getstr(9), __progname, VERSION);
557 exit(0);
558 case 'v':
559 vflag = true;
560 break;
561 case 'w':
562 wflag = true;
563 break;
564 case 'x':
565 xflag = true;
566 break;
567 case 'Z':
568 filebehave = FILE_GZIP;
569 break;
570 case BIN_OPT:
571 if (strcasecmp("binary", optarg) == 0)
572 binbehave = BINFILE_BIN;
573 else if (strcasecmp("without-match", optarg) == 0)
574 binbehave = BINFILE_SKIP;
575 else if (strcasecmp("text", optarg) == 0)
576 binbehave = BINFILE_TEXT;
577 else
578 errx(2, getstr(3), "--binary-files");
579 break;
580 case COLOR_OPT:
581 color = NULL;
582 if (optarg == NULL || strcasecmp("auto", optarg) == 0 ||
583 strcasecmp("tty", optarg) == 0 ||
584 strcasecmp("if-tty", optarg) == 0) {
585 char *term;
586
587 term = getenv("TERM");
588 if (isatty(STDOUT_FILENO) && term != NULL &&
589 strcasecmp(term, "dumb") != 0)
590 color = init_color("01;31");
591 } else if (strcasecmp("always", optarg) == 0 ||
592 strcasecmp("yes", optarg) == 0 ||
593 strcasecmp("force", optarg) == 0) {
594 color = init_color("01;31");
595 } else if (strcasecmp("never", optarg) != 0 &&
596 strcasecmp("none", optarg) != 0 &&
597 strcasecmp("no", optarg) != 0)
598 errx(2, getstr(3), "--color");
599 break;
600 case LABEL_OPT:
601 label = optarg;
602 break;
603 case LINEBUF_OPT:
604 lbflag = true;
605 break;
606 case NULL_OPT:
607 nullflag = true;
608 break;
609 case R_INCLUDE_OPT:
610 finclude = true;
611 add_fpattern(optarg, INCL_PAT);
612 break;
613 case R_EXCLUDE_OPT:
614 fexclude = true;
615 add_fpattern(optarg, EXCL_PAT);
616 break;
617 case R_DINCLUDE_OPT:
618 dinclude = true;
619 add_dpattern(optarg, INCL_PAT);
620 break;
621 case R_DEXCLUDE_OPT:
622 dexclude = true;
623 add_dpattern(optarg, EXCL_PAT);
624 break;
625 case HELP_OPT:
626 default:
627 usage();
628 }
629 lastc = c;
630 newarg = optind != prevoptind;
631 prevoptind = optind;
632 }
633 aargc -= optind;
634 aargv += optind;
635
636 /* Fail if we don't have any pattern */
637 if (aargc == 0 && needpattern)
638 usage();
639
640 /* Process patterns from command line */
641 if (aargc != 0 && needpattern) {
642 add_pattern(*aargv, strlen(*aargv));
643 --aargc;
644 ++aargv;
645 }
646
647 switch (grepbehave) {
648 case GREP_FIXED:
649 case GREP_BASIC:
650 break;
651 case GREP_EXTENDED:
652 cflags |= REG_EXTENDED;
653 break;
654 default:
655 /* NOTREACHED */
656 usage();
657 }
658
659 fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern));
660 r_pattern = grep_calloc(patterns, sizeof(*r_pattern));
661 /*
662 * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance.
663 * Optimizations should be done there.
664 */
665 /* Check if cheating is allowed (always is for fgrep). */
666 if (grepbehave == GREP_FIXED) {
667 for (i = 0; i < patterns; ++i)
668 fgrepcomp(&fg_pattern[i], pattern[i]);
669 } else {
670 for (i = 0; i < patterns; ++i) {
671 if (fastcomp(&fg_pattern[i], pattern[i])) {
672 /* Fall back to full regex library */
673 c = regcomp(&r_pattern[i], pattern[i], cflags);
674 if (c != 0) {
675 regerror(c, &r_pattern[i], re_error,
676 RE_ERROR_BUF);
677 errx(2, "%s", re_error);
678 }
679 }
680 }
681 }
682
683 if (lbflag)
684 setlinebuf(stdout);
685
686 if ((aargc == 0 || aargc == 1) && !Hflag)
687 hflag = true;
688
689 if (aargc == 0)
690 exit(!procfile("-"));
691
692 if (dirbehave == DIR_RECURSE)
693 c = grep_tree(aargv);
694 else
695 for (c = 0; aargc--; ++aargv) {
696 if ((finclude || fexclude) && !file_matching(*aargv))
697 continue;
698 c+= procfile(*aargv);
699 }
700
701 #ifndef WITHOUT_NLS
702 catclose(catalog);
703 #endif
704
705 /* Find out the correct return value according to the
706 results and the command line option. */
707 exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1));
708 }
709