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