1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * grep - pattern matching program - combined grep, egrep, and fgrep. 29 * Based on MKS grep command, with XCU & Solaris mods. 30 */ 31 32 /* 33 * Copyright 1985, 1992 by Mortice Kern Systems Inc. All rights reserved. 34 * 35 */ 36 37 /* Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ 38 39 /* 40 * Copyright 2013 Damian Bogel. All rights reserved. 41 */ 42 43 #include <string.h> 44 #include <stdlib.h> 45 #include <ctype.h> 46 #include <stdarg.h> 47 #include <regex.h> 48 #include <limits.h> 49 #include <sys/types.h> 50 #include <sys/stat.h> 51 #include <fcntl.h> 52 #include <stdio.h> 53 #include <locale.h> 54 #include <wchar.h> 55 #include <errno.h> 56 #include <unistd.h> 57 #include <wctype.h> 58 #include <ftw.h> 59 #include <sys/param.h> 60 61 #define STDIN_FILENAME gettext("(standard input)") 62 63 #define BSIZE 512 /* Size of block for -b */ 64 #define BUFSIZE 8192 /* Input buffer size */ 65 #define MAX_DEPTH 1000 /* how deep to recurse */ 66 67 #define AFTER 1 /* 'After' Context */ 68 #define BEFORE 2 /* 'Before' Context */ 69 #define CONTEXT (AFTER|BEFORE) /* Full Context */ 70 71 #define M_CSETSIZE 256 /* singlebyte chars */ 72 static int bmglen; /* length of BMG pattern */ 73 static char *bmgpat; /* BMG pattern */ 74 static int bmgtab[M_CSETSIZE]; /* BMG delta1 table */ 75 76 typedef struct _PATTERN { 77 char *pattern; /* original pattern */ 78 wchar_t *wpattern; /* wide, lowercased pattern */ 79 struct _PATTERN *next; 80 regex_t re; /* compiled pattern */ 81 } PATTERN; 82 83 static PATTERN *patterns; 84 static char errstr[128]; /* regerror string buffer */ 85 static int regflags = 0; /* regcomp options */ 86 static int matched = 0; /* return of the grep() */ 87 static int errors = 0; /* count of errors */ 88 static uchar_t fgrep = 0; /* Invoked as fgrep */ 89 static uchar_t egrep = 0; /* Invoked as egrep */ 90 static uchar_t nvflag = 1; /* Print matching lines */ 91 static uchar_t cflag; /* Count of matches */ 92 static uchar_t iflag; /* Case insensitve matching */ 93 static uchar_t Hflag; /* Precede lines by file name */ 94 static uchar_t hflag; /* Supress printing of filename */ 95 static uchar_t lflag; /* Print file names of matches */ 96 static uchar_t nflag; /* Precede lines by line number */ 97 static uchar_t rflag; /* Search directories recursively */ 98 static uchar_t bflag; /* Preccede matches by block number */ 99 static uchar_t sflag; /* Suppress file error messages */ 100 static uchar_t qflag; /* Suppress standard output */ 101 static uchar_t wflag; /* Search for expression as a word */ 102 static uchar_t xflag; /* Anchoring */ 103 static uchar_t Eflag; /* Egrep or -E flag */ 104 static uchar_t Fflag; /* Fgrep or -F flag */ 105 static uchar_t Rflag; /* Like rflag, but follow symlinks */ 106 static uchar_t outfn; /* Put out file name */ 107 static uchar_t conflag; /* show context of matches */ 108 static char *cmdname; 109 110 static int use_wchar, use_bmg, mblocale; 111 112 static size_t outbuflen, prntbuflen, conbuflen; 113 static unsigned long conalen, conblen, conmatches; 114 static char *prntbuf, *conbuf; 115 static wchar_t *outline; 116 117 static void addfile(const char *fn); 118 static void addpattern(char *s); 119 static void fixpatterns(void); 120 static void usage(void); 121 static int grep(int, const char *); 122 static void bmgcomp(char *, int); 123 static char *bmgexec(char *, char *); 124 static int recursive(const char *, const struct stat *, int, struct FTW *); 125 static void process_path(const char *); 126 static void process_file(const char *, int); 127 128 /* 129 * mainline for grep 130 */ 131 int 132 main(int argc, char **argv) 133 { 134 char *ap, *test; 135 int c; 136 int fflag = 0; 137 int i, n_pattern = 0, n_file = 0; 138 char **pattern_list = NULL; 139 char **file_list = NULL; 140 141 (void) setlocale(LC_ALL, ""); 142 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 143 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 144 #endif 145 (void) textdomain(TEXT_DOMAIN); 146 147 /* 148 * true if this is running on the multibyte locale 149 */ 150 mblocale = (MB_CUR_MAX > 1); 151 /* 152 * Skip leading slashes 153 */ 154 cmdname = argv[0]; 155 if (ap = strrchr(cmdname, '/')) 156 cmdname = ap + 1; 157 158 ap = cmdname; 159 /* 160 * Detect egrep/fgrep via command name, map to -E and -F options. 161 */ 162 if (*ap == 'e' || *ap == 'E') { 163 regflags |= REG_EXTENDED; 164 egrep++; 165 } else { 166 if (*ap == 'f' || *ap == 'F') { 167 fgrep++; 168 } 169 } 170 171 /* check for non-standard "-line-count" option */ 172 for (i = 1; i < argc; i++) { 173 if (strcmp(argv[i], "--") == 0) 174 break; 175 176 if ((argv[i][0] == '-') && isdigit(argv[i][1])) { 177 if (strlen(&argv[i][1]) != 178 strspn(&argv[i][1], "0123456789")) { 179 (void) fprintf(stderr, gettext( 180 "%s: Bad number flag\n"), argv[0]); 181 usage(); 182 } 183 184 conalen = conblen = strtoul(&argv[i][1], (char **)NULL, 185 10); 186 187 /* isdigit() check prevents negative arguments */ 188 if (conalen >= ULONG_MAX) { 189 (void) fprintf(stderr, gettext( 190 "%s: Bad context argument\n"), argv[0]); 191 } 192 193 if (conalen) 194 conflag = CONTEXT; 195 196 while (i < argc) { 197 argv[i] = argv[i + 1]; 198 i++; 199 } 200 argc--; 201 } 202 } 203 204 while ((c = getopt(argc, argv, "vwchHilnrbse:f:qxEFIRA:B:C:")) != EOF) { 205 unsigned long tval; 206 switch (c) { 207 case 'v': /* POSIX: negate matches */ 208 nvflag = 0; 209 break; 210 211 case 'c': /* POSIX: write count */ 212 cflag++; 213 break; 214 215 case 'i': /* POSIX: ignore case */ 216 iflag++; 217 regflags |= REG_ICASE; 218 break; 219 220 case 'l': /* POSIX: Write filenames only */ 221 lflag++; 222 break; 223 224 case 'n': /* POSIX: Write line numbers */ 225 nflag++; 226 break; 227 228 case 'r': /* Solaris: search recursively */ 229 rflag++; 230 break; 231 232 case 'b': /* Solaris: Write file block numbers */ 233 bflag++; 234 break; 235 236 case 's': /* POSIX: No error msgs for files */ 237 sflag++; 238 break; 239 240 case 'e': /* POSIX: pattern list */ 241 n_pattern++; 242 pattern_list = realloc(pattern_list, 243 sizeof (char *) * n_pattern); 244 if (pattern_list == NULL) { 245 (void) fprintf(stderr, 246 gettext("%s: out of memory\n"), 247 cmdname); 248 exit(2); 249 } 250 *(pattern_list + n_pattern - 1) = optarg; 251 break; 252 253 case 'f': /* POSIX: pattern file */ 254 fflag = 1; 255 n_file++; 256 file_list = realloc(file_list, 257 sizeof (char *) * n_file); 258 if (file_list == NULL) { 259 (void) fprintf(stderr, 260 gettext("%s: out of memory\n"), 261 cmdname); 262 exit(2); 263 } 264 *(file_list + n_file - 1) = optarg; 265 break; 266 267 /* based on options order h or H is set as in GNU grep */ 268 case 'h': /* Solaris: supress printing of file name */ 269 hflag = 1; 270 Hflag = 0; 271 break; 272 /* Solaris: precede every matching with file name */ 273 case 'H': 274 Hflag = 1; 275 hflag = 0; 276 break; 277 278 case 'q': /* POSIX: quiet: status only */ 279 qflag++; 280 break; 281 282 case 'w': /* Solaris: treat pattern as word */ 283 wflag++; 284 break; 285 286 case 'x': /* POSIX: full line matches */ 287 xflag++; 288 regflags |= REG_ANCHOR; 289 break; 290 291 case 'E': /* POSIX: Extended RE's */ 292 regflags |= REG_EXTENDED; 293 Eflag++; 294 break; 295 296 case 'F': /* POSIX: strings, not RE's */ 297 Fflag++; 298 break; 299 300 case 'R': /* Solaris: like rflag, but follow symlinks */ 301 Rflag++; 302 rflag++; 303 break; 304 305 case 'A': /* print N lines after each match */ 306 conalen = strtoul(optarg, &test, 10); 307 /* *test will be non-null if optarg is negative */ 308 if (*test != '\0' || conalen >= ULONG_MAX) { 309 (void) fprintf(stderr, gettext( 310 "%s: Bad context argument\n"), argv[0]); 311 exit(2); 312 } 313 if (conalen) 314 conflag |= AFTER; 315 else 316 conflag &= ~AFTER; 317 break; 318 case 'B': /* print N lines before each match */ 319 conblen = strtoul(optarg, &test, 10); 320 /* *test will be non-null if optarg is negative */ 321 if (*test != '\0' || conblen >= ULONG_MAX) { 322 (void) fprintf(stderr, gettext( 323 "%s: Bad context argument\n"), argv[0]); 324 exit(2); 325 } 326 if (conblen) 327 conflag |= BEFORE; 328 else 329 conflag &= ~BEFORE; 330 break; 331 case 'C': /* print N lines around each match */ 332 tval = strtoul(optarg, &test, 10); 333 /* *test will be non-null if optarg is negative */ 334 if (*test != '\0' || tval >= ULONG_MAX) { 335 (void) fprintf(stderr, gettext( 336 "%s: Bad context argument\n"), argv[0]); 337 exit(2); 338 } 339 if (tval) { 340 if (!(conflag & BEFORE)) 341 conblen = tval; 342 if (!(conflag & AFTER)) 343 conalen = tval; 344 conflag = CONTEXT; 345 } 346 break; 347 348 default: 349 usage(); 350 } 351 } 352 /* 353 * If we're invoked as egrep or fgrep we need to do some checks 354 */ 355 356 if (egrep || fgrep) { 357 /* 358 * Use of -E or -F with egrep or fgrep is illegal 359 */ 360 if (Eflag || Fflag) 361 usage(); 362 /* 363 * Don't allow use of wflag with egrep / fgrep 364 */ 365 if (wflag) 366 usage(); 367 /* 368 * For Solaris the -s flag is equivalent to XCU -q 369 */ 370 if (sflag) 371 qflag++; 372 /* 373 * done with above checks - set the appropriate flags 374 */ 375 if (egrep) 376 Eflag++; 377 else /* Else fgrep */ 378 Fflag++; 379 } 380 381 if (wflag && (Eflag || Fflag)) { 382 /* 383 * -w cannot be specified with grep -F 384 */ 385 usage(); 386 } 387 388 /* 389 * -E and -F flags are mutually exclusive - check for this 390 */ 391 if (Eflag && Fflag) 392 usage(); 393 394 /* 395 * -l overrides -H like in GNU grep 396 */ 397 if (lflag) 398 Hflag = 0; 399 400 /* 401 * -c, -l and -q flags are mutually exclusive 402 * We have -c override -l like in Solaris. 403 * -q overrides -l & -c programmatically in grep() function. 404 */ 405 if (cflag && lflag) 406 lflag = 0; 407 408 argv += optind - 1; 409 argc -= optind - 1; 410 411 /* 412 * Now handling -e and -f option 413 */ 414 if (pattern_list) { 415 for (i = 0; i < n_pattern; i++) { 416 addpattern(pattern_list[i]); 417 } 418 free(pattern_list); 419 } 420 if (file_list) { 421 for (i = 0; i < n_file; i++) { 422 addfile(file_list[i]); 423 } 424 free(file_list); 425 } 426 427 /* 428 * No -e or -f? Make sure there is one more arg, use it as the pattern. 429 */ 430 if (patterns == NULL && !fflag) { 431 if (argc < 2) 432 usage(); 433 addpattern(argv[1]); 434 argc--; 435 argv++; 436 } 437 438 /* 439 * If -x flag is not specified or -i flag is specified 440 * with fgrep in a multibyte locale, need to use 441 * the wide character APIs. Otherwise, byte-oriented 442 * process will be done. 443 */ 444 use_wchar = Fflag && mblocale && (!xflag || iflag); 445 446 /* 447 * Compile Patterns and also decide if BMG can be used 448 */ 449 fixpatterns(); 450 451 /* Process all files: stdin, or rest of arg list */ 452 if (argc < 2) { 453 matched = grep(0, STDIN_FILENAME); 454 } else { 455 if (Hflag || (argc > 2 && hflag == 0)) 456 outfn = 1; /* Print filename on match line */ 457 for (argv++; *argv != NULL; argv++) { 458 process_path(*argv); 459 } 460 } 461 /* 462 * Return() here is used instead of exit 463 */ 464 465 (void) fflush(stdout); 466 467 if (errors) 468 return (2); 469 return (matched ? 0 : 1); 470 } 471 472 static void 473 process_path(const char *path) 474 { 475 struct stat st; 476 int walkflags = FTW_CHDIR; 477 char *buf = NULL; 478 479 if (rflag) { 480 if (stat(path, &st) != -1 && 481 (st.st_mode & S_IFMT) == S_IFDIR) { 482 outfn = 1; /* Print filename */ 483 484 /* 485 * Add trailing slash if arg 486 * is directory, to resolve symlinks. 487 */ 488 if (path[strlen(path) - 1] != '/') { 489 (void) asprintf(&buf, "%s/", path); 490 if (buf != NULL) 491 path = buf; 492 } 493 494 /* 495 * Search through subdirs if path is directory. 496 * Don't follow symlinks if Rflag is not set. 497 */ 498 if (!Rflag) 499 walkflags |= FTW_PHYS; 500 501 if (nftw(path, recursive, MAX_DEPTH, walkflags) != 0) { 502 if (!sflag) 503 (void) fprintf(stderr, 504 gettext("%s: can't open \"%s\"\n"), 505 cmdname, path); 506 errors = 1; 507 } 508 return; 509 } 510 } 511 process_file(path, 0); 512 } 513 514 /* 515 * Read and process all files in directory recursively. 516 */ 517 static int 518 recursive(const char *name, const struct stat *statp, int info, struct FTW *ftw) 519 { 520 /* 521 * Process files and follow symlinks if Rflag set. 522 */ 523 if (info != FTW_F) { 524 /* Report broken symlinks and unreadable files */ 525 if (!sflag && 526 (info == FTW_SLN || info == FTW_DNR || info == FTW_NS)) { 527 (void) fprintf(stderr, 528 gettext("%s: can't open \"%s\"\n"), cmdname, name); 529 } 530 return (0); 531 } 532 533 534 /* Skip devices and pipes if Rflag is not set */ 535 if (!Rflag && !S_ISREG(statp->st_mode)) 536 return (0); 537 /* Pass offset to relative name from FTW_CHDIR */ 538 process_file(name, ftw->base); 539 return (0); 540 } 541 542 /* 543 * Opens file and call grep function. 544 */ 545 static void 546 process_file(const char *name, int base) 547 { 548 int fd; 549 550 if ((fd = open(name + base, O_RDONLY)) == -1) { 551 errors = 1; 552 if (!sflag) /* Silent mode */ 553 (void) fprintf(stderr, gettext( 554 "%s: can't open \"%s\"\n"), 555 cmdname, name); 556 return; 557 } 558 matched |= grep(fd, name); 559 (void) close(fd); 560 561 if (ferror(stdout)) { 562 (void) fprintf(stderr, gettext( 563 "%s: error writing to stdout\n"), 564 cmdname); 565 (void) fflush(stdout); 566 exit(2); 567 } 568 569 } 570 571 /* 572 * Add a file of strings to the pattern list. 573 */ 574 static void 575 addfile(const char *fn) 576 { 577 FILE *fp; 578 char *inbuf; 579 char *bufp; 580 size_t bufsiz, buflen, bufused; 581 582 /* 583 * Open the pattern file 584 */ 585 if ((fp = fopen(fn, "r")) == NULL) { 586 (void) fprintf(stderr, gettext("%s: can't open \"%s\"\n"), 587 cmdname, fn); 588 exit(2); 589 } 590 bufsiz = BUFSIZE; 591 if ((inbuf = malloc(bufsiz)) == NULL) { 592 (void) fprintf(stderr, 593 gettext("%s: out of memory\n"), cmdname); 594 exit(2); 595 } 596 bufp = inbuf; 597 bufused = 0; 598 /* 599 * Read in the file, reallocing as we need more memory 600 */ 601 while (fgets(bufp, bufsiz - bufused, fp) != NULL) { 602 buflen = strlen(bufp); 603 bufused += buflen; 604 if (bufused + 1 == bufsiz && bufp[buflen - 1] != '\n') { 605 /* 606 * if this line does not fit to the buffer, 607 * realloc larger buffer 608 */ 609 bufsiz += BUFSIZE; 610 if ((inbuf = realloc(inbuf, bufsiz)) == NULL) { 611 (void) fprintf(stderr, 612 gettext("%s: out of memory\n"), 613 cmdname); 614 exit(2); 615 } 616 bufp = inbuf + bufused; 617 continue; 618 } 619 if (bufp[buflen - 1] == '\n') { 620 bufp[--buflen] = '\0'; 621 } 622 addpattern(inbuf); 623 624 bufp = inbuf; 625 bufused = 0; 626 } 627 free(inbuf); 628 free(prntbuf); 629 free(conbuf); 630 (void) fclose(fp); 631 } 632 633 /* 634 * Add a string to the pattern list. 635 */ 636 static void 637 addpattern(char *s) 638 { 639 PATTERN *pp; 640 char *wordbuf; 641 char *np; 642 643 for (; ; ) { 644 np = strchr(s, '\n'); 645 if (np != NULL) 646 *np = '\0'; 647 if ((pp = malloc(sizeof (PATTERN))) == NULL) { 648 (void) fprintf(stderr, gettext( 649 "%s: out of memory\n"), 650 cmdname); 651 exit(2); 652 } 653 if (wflag) { 654 /* 655 * Solaris wflag support: Add '<' '>' to pattern to 656 * select it as a word. Doesn't make sense with -F 657 * but we're Libertarian. 658 */ 659 size_t slen, wordlen; 660 661 slen = strlen(s); 662 wordlen = slen + 5; /* '\\' '<' s '\\' '>' '\0' */ 663 if ((wordbuf = malloc(wordlen)) == NULL) { 664 (void) fprintf(stderr, 665 gettext("%s: out of memory\n"), 666 cmdname); 667 exit(2); 668 } 669 (void) strcpy(wordbuf, "\\<"); 670 (void) strcpy(wordbuf + 2, s); 671 (void) strcpy(wordbuf + 2 + slen, "\\>"); 672 } else { 673 if ((wordbuf = strdup(s)) == NULL) { 674 (void) fprintf(stderr, 675 gettext("%s: out of memory\n"), 676 cmdname); 677 exit(2); 678 } 679 } 680 pp->pattern = wordbuf; 681 pp->next = patterns; 682 patterns = pp; 683 if (np == NULL) 684 break; 685 s = np + 1; 686 } 687 } 688 689 /* 690 * Fix patterns. 691 * Must do after all arguments read, in case later -i option. 692 */ 693 static void 694 fixpatterns(void) 695 { 696 PATTERN *pp; 697 int rv, fix_pattern, npatterns; 698 699 /* 700 * As REG_ANCHOR flag is not supported in the current Solaris, 701 * need to fix the specified pattern if -x is specified with 702 * grep or egrep 703 */ 704 fix_pattern = !Fflag && xflag; 705 706 for (npatterns = 0, pp = patterns; pp != NULL; pp = pp->next) { 707 npatterns++; 708 if (fix_pattern) { 709 char *cp, *cq; 710 size_t plen, nplen; 711 712 plen = strlen(pp->pattern); 713 /* '^' pattern '$' */ 714 nplen = 1 + plen + 1 + 1; 715 if ((cp = malloc(nplen)) == NULL) { 716 (void) fprintf(stderr, 717 gettext("%s: out of memory\n"), 718 cmdname); 719 exit(2); 720 } 721 cq = cp; 722 *cq++ = '^'; 723 cq = strcpy(cq, pp->pattern) + plen; 724 *cq++ = '$'; 725 *cq = '\0'; 726 free(pp->pattern); 727 pp->pattern = cp; 728 } 729 730 if (Fflag) { 731 if (use_wchar) { 732 /* 733 * Fflag && mblocale && iflag 734 * Fflag && mblocale && !xflag 735 */ 736 size_t n; 737 n = strlen(pp->pattern) + 1; 738 if ((pp->wpattern = 739 malloc(sizeof (wchar_t) * n)) == NULL) { 740 (void) fprintf(stderr, 741 gettext("%s: out of memory\n"), 742 cmdname); 743 exit(2); 744 } 745 if (mbstowcs(pp->wpattern, pp->pattern, n) == 746 (size_t)-1) { 747 (void) fprintf(stderr, 748 gettext("%s: failed to convert " 749 "\"%s\" to wide-characters\n"), 750 cmdname, pp->pattern); 751 exit(2); 752 } 753 if (iflag) { 754 wchar_t *wp; 755 for (wp = pp->wpattern; *wp != L'\0'; 756 wp++) { 757 *wp = towlower((wint_t)*wp); 758 } 759 } 760 free(pp->pattern); 761 } else { 762 /* 763 * Fflag && mblocale && !iflag 764 * Fflag && !mblocale && iflag 765 * Fflag && !mblocale && !iflag 766 */ 767 if (iflag) { 768 unsigned char *cp; 769 for (cp = (unsigned char *)pp->pattern; 770 *cp != '\0'; cp++) { 771 *cp = tolower(*cp); 772 } 773 } 774 } 775 /* 776 * fgrep: No regular expressions. 777 */ 778 continue; 779 } 780 781 /* 782 * For non-fgrep, compile the regular expression, 783 * give an informative error message, and exit if 784 * it didn't compile. 785 */ 786 if ((rv = regcomp(&pp->re, pp->pattern, regflags)) != 0) { 787 (void) regerror(rv, &pp->re, errstr, sizeof (errstr)); 788 (void) fprintf(stderr, 789 gettext("%s: RE error in %s: %s\n"), 790 cmdname, pp->pattern, errstr); 791 exit(2); 792 } 793 free(pp->pattern); 794 } 795 796 /* 797 * Decide if we are able to run the Boyer-Moore-Gosper algorithm. 798 * Use the Boyer-Moore-Gosper algorithm if: 799 * - fgrep (Fflag) 800 * - singlebyte locale (!mblocale) 801 * - no ignoring case (!iflag) 802 * - no printing line numbers (!nflag) 803 * - no negating the output (nvflag) 804 * - only one pattern (npatterns == 1) 805 * - non zero length pattern (strlen(patterns->pattern) != 0) 806 * - no context required (!conflag) 807 * 808 * It's guaranteed patterns->pattern is still alive 809 * when Fflag && !mblocale. 810 */ 811 use_bmg = Fflag && !mblocale && !iflag && !nflag && nvflag && 812 (npatterns == 1) && (strlen(patterns->pattern) != 0) && !conflag; 813 } 814 815 /* 816 * Search a newline from the beginning of the string 817 */ 818 static char * 819 find_nl(const char *ptr, size_t len) 820 { 821 while (len-- != 0) { 822 if (*ptr++ == '\n') { 823 return ((char *)--ptr); 824 } 825 } 826 return (NULL); 827 } 828 829 /* 830 * Search a newline from the end of the string 831 */ 832 static char * 833 rfind_nl(const char *ptr, size_t len) 834 { 835 const char *uptr = ptr + len; 836 while (len--) { 837 if (*--uptr == '\n') { 838 return ((char *)uptr); 839 } 840 } 841 return (NULL); 842 } 843 844 /* 845 * Duplicate the specified string converting each character 846 * into a lower case. 847 */ 848 static char * 849 istrdup(const char *s1) 850 { 851 static size_t ibuflen = 0; 852 static char *ibuf = NULL; 853 size_t slen; 854 char *p; 855 856 slen = strlen(s1); 857 if (slen >= ibuflen) { 858 /* ibuf does not fit to s1 */ 859 ibuflen = slen + 1; 860 ibuf = realloc(ibuf, ibuflen); 861 if (ibuf == NULL) { 862 (void) fprintf(stderr, 863 gettext("%s: out of memory\n"), cmdname); 864 exit(2); 865 } 866 } 867 p = ibuf; 868 do { 869 *p++ = tolower(*s1); 870 } while (*s1++ != '\0'); 871 return (ibuf); 872 } 873 874 /* 875 * Do grep on a single file. 876 * Return true in any lines matched. 877 * 878 * We have two strategies: 879 * The fast one is used when we have a single pattern with 880 * a string known to occur in the pattern. We can then 881 * do a BMG match on the whole buffer. 882 * This is an order of magnitude faster. 883 * Otherwise we split the buffer into lines, 884 * and check for a match on each line. 885 */ 886 static int 887 grep(int fd, const char *fn) 888 { 889 PATTERN *pp; 890 off_t data_len; /* length of the data chunk */ 891 off_t line_len; /* length of the current line */ 892 off_t line_offset; /* current line's offset from the beginning */ 893 off_t blkoffset; /* line_offset but context-compatible */ 894 long long lineno, linenum; 895 long long matches = 0; /* Number of matching lines */ 896 long long conacnt = 0, conbcnt = 0; /* context line count */ 897 int newlinep; /* 0 if the last line of file has no newline */ 898 char *ptr, *ptrend, *prntptr, *prntptrend; 899 char *nextptr = NULL, *nextend = NULL; 900 char *conptr = NULL, *conptrend = NULL; 901 char *matchptr = NULL; 902 int conaprnt = 0, conbprnt = 0, lastmatch = 0; 903 int nearmatch = conmatches ? 1 : 0; /* w/in N+1 of last match */ 904 size_t prntlen; 905 906 if (patterns == NULL) 907 return (0); /* no patterns to match -- just return */ 908 909 pp = patterns; 910 911 if (use_bmg) { 912 bmgcomp(pp->pattern, strlen(pp->pattern)); 913 } 914 915 if (use_wchar && outline == NULL) { 916 outbuflen = BUFSIZE + 1; 917 outline = malloc(sizeof (wchar_t) * outbuflen); 918 if (outline == NULL) { 919 (void) fprintf(stderr, gettext("%s: out of memory\n"), 920 cmdname); 921 exit(2); 922 } 923 } 924 925 if (prntbuf == NULL) { 926 prntbuflen = BUFSIZE; 927 if ((prntbuf = malloc(prntbuflen + 1)) == NULL) { 928 (void) fprintf(stderr, gettext("%s: out of memory\n"), 929 cmdname); 930 exit(2); 931 } 932 } 933 934 if (conflag && (conbuf == NULL)) { 935 conbuflen = BUFSIZE; 936 if ((conbuf = malloc(BUFSIZE+1)) == NULL) { 937 (void) fprintf(stderr, gettext("%s: out of memory\n"), 938 cmdname); 939 exit(2); 940 } 941 } 942 943 blkoffset = line_offset = 0; 944 lineno = 0; 945 linenum = 1; 946 newlinep = 1; 947 data_len = 0; 948 for (; ; ) { 949 long count; 950 off_t offset = 0; 951 int eof = 0, rv = REG_NOMATCH; 952 char separate; 953 954 if (data_len == 0) { 955 /* 956 * If no data in the buffer, reset ptr 957 */ 958 ptr = prntbuf; 959 if (conptr == NULL) 960 conptrend = conptr = conbuf; 961 } 962 if (ptr == prntbuf) { 963 /* 964 * The current data chunk starts from prntbuf. 965 * This means either the buffer has no data 966 * or the buffer has no newline. 967 * So, read more data from input. 968 */ 969 count = read(fd, ptr + data_len, prntbuflen - data_len); 970 if (count < 0) { 971 /* read error */ 972 if (cflag) { 973 if (outfn && !rflag) { 974 (void) fprintf(stdout, 975 "%s:", fn); 976 } 977 if (!qflag && !rflag) { 978 (void) fprintf(stdout, "%lld\n", 979 matches); 980 } 981 } 982 return (0); 983 } else if (count == 0) { 984 /* no new data */ 985 eof = 1; 986 987 /* we never want to match EOF */ 988 pp = (PATTERN *) !nvflag; 989 990 if (data_len == 0) { 991 /* end of file already reached */ 992 if (conflag) { 993 *conptrend = '\n'; 994 goto L_next_line; 995 } else { 996 goto out; 997 } 998 } 999 /* last line of file has no newline */ 1000 ptrend = ptr + data_len; 1001 newlinep = 0; 1002 goto L_start_process; 1003 } 1004 offset = data_len; 1005 data_len += count; 1006 } 1007 1008 /* 1009 * Look for newline in the chunk 1010 * between ptr + offset and ptr + data_len - offset. 1011 */ 1012 ptrend = find_nl(ptr + offset, data_len - offset); 1013 if (ptrend == NULL) { 1014 /* no newline found in this chunk */ 1015 if (ptr > prntbuf) { 1016 /* 1017 * Move remaining data to the beginning 1018 * of the buffer. 1019 * Remaining data lie from ptr for 1020 * data_len bytes. 1021 */ 1022 (void) memmove(prntbuf, ptr, data_len); 1023 } 1024 if (data_len == prntbuflen) { 1025 /* 1026 * Not enough room in the buffer 1027 */ 1028 prntbuflen += BUFSIZE; 1029 prntbuf = realloc(prntbuf, prntbuflen + 1); 1030 if (prntbuf == NULL) { 1031 (void) fprintf(stderr, 1032 gettext("%s: out of memory\n"), 1033 cmdname); 1034 exit(2); 1035 } 1036 } 1037 ptr = prntbuf; 1038 /* read the next input */ 1039 continue; 1040 } 1041 L_start_process: 1042 1043 /* 1044 * Beginning of the chunk: ptr 1045 * End of the chunk: ptr + data_len 1046 * Beginning of the line: ptr 1047 * End of the line: ptrend 1048 */ 1049 1050 if (use_bmg) { 1051 /* 1052 * Use Boyer-Moore-Gosper algorithm to find out if 1053 * this chunk (not this line) contains the specified 1054 * pattern. If not, restart from the last line 1055 * of this chunk. 1056 */ 1057 char *bline; 1058 bline = bmgexec(ptr, ptr + data_len); 1059 if (bline == NULL) { 1060 /* 1061 * No pattern found in this chunk. 1062 * Need to find the last line 1063 * in this chunk. 1064 */ 1065 ptrend = rfind_nl(ptr, data_len); 1066 1067 /* 1068 * When this chunk does not contain newline, 1069 * ptrend becomes NULL, which should happen 1070 * when the last line of file does not end 1071 * with a newline. At such a point, 1072 * newlinep should have been set to 0. 1073 * Therefore, just after jumping to 1074 * L_skip_line, the main for-loop quits, 1075 * and the line_len value won't be 1076 * used. 1077 */ 1078 line_len = ptrend - ptr; 1079 goto L_skip_line; 1080 } 1081 if (bline > ptrend) { 1082 /* 1083 * Pattern found not in the first line 1084 * of this chunk. 1085 * Discard the first line. 1086 */ 1087 line_len = ptrend - ptr; 1088 goto L_skip_line; 1089 } 1090 /* 1091 * Pattern found in the first line of this chunk. 1092 * Using this result. 1093 */ 1094 *ptrend = '\0'; 1095 line_len = ptrend - ptr; 1096 1097 /* 1098 * before jumping to L_next_line, 1099 * need to handle xflag if specified 1100 */ 1101 if (xflag && (line_len != bmglen || 1102 strcmp(bmgpat, ptr) != 0)) { 1103 /* didn't match */ 1104 pp = NULL; 1105 } else { 1106 pp = patterns; /* to make it happen */ 1107 } 1108 goto L_next_line; 1109 } 1110 lineno++; 1111 /* 1112 * Line starts from ptr and ends at ptrend. 1113 * line_len will be the length of the line. 1114 */ 1115 *ptrend = '\0'; 1116 line_len = ptrend - ptr; 1117 1118 /* 1119 * From now, the process will be performed based 1120 * on the line from ptr to ptrend. 1121 */ 1122 if (use_wchar) { 1123 size_t len; 1124 1125 if (line_len >= outbuflen) { 1126 outbuflen = line_len + 1; 1127 outline = realloc(outline, 1128 sizeof (wchar_t) * outbuflen); 1129 if (outline == NULL) { 1130 (void) fprintf(stderr, 1131 gettext("%s: out of memory\n"), 1132 cmdname); 1133 exit(2); 1134 } 1135 } 1136 1137 len = mbstowcs(outline, ptr, line_len); 1138 if (len == (size_t)-1) { 1139 (void) fprintf(stderr, gettext( 1140 "%s: input file \"%s\": line %lld: invalid multibyte character\n"), 1141 cmdname, fn, lineno); 1142 /* never match a line with invalid sequence */ 1143 goto L_skip_line; 1144 } 1145 outline[len] = L'\0'; 1146 1147 if (iflag) { 1148 wchar_t *cp; 1149 for (cp = outline; *cp != '\0'; cp++) { 1150 *cp = towlower((wint_t)*cp); 1151 } 1152 } 1153 1154 if (xflag) { 1155 for (pp = patterns; pp; pp = pp->next) { 1156 if (outline[0] == pp->wpattern[0] && 1157 wcscmp(outline, 1158 pp->wpattern) == 0) { 1159 /* matched */ 1160 rv = REG_OK; 1161 break; 1162 } 1163 } 1164 } else { 1165 for (pp = patterns; pp; pp = pp->next) { 1166 if (wcswcs(outline, pp->wpattern) 1167 != NULL) { 1168 /* matched */ 1169 rv = REG_OK; 1170 break; 1171 } 1172 } 1173 } 1174 } else if (Fflag) { 1175 /* fgrep in byte-oriented handling */ 1176 char *fptr; 1177 if (iflag) { 1178 fptr = istrdup(ptr); 1179 } else { 1180 fptr = ptr; 1181 } 1182 if (xflag) { 1183 /* fgrep -x */ 1184 for (pp = patterns; pp; pp = pp->next) { 1185 if (fptr[0] == pp->pattern[0] && 1186 strcmp(fptr, pp->pattern) == 0) { 1187 /* matched */ 1188 rv = REG_OK; 1189 break; 1190 } 1191 } 1192 } else { 1193 for (pp = patterns; pp; pp = pp->next) { 1194 if (strstr(fptr, pp->pattern) != NULL) { 1195 /* matched */ 1196 rv = REG_OK; 1197 break; 1198 } 1199 } 1200 } 1201 } else { 1202 /* grep or egrep */ 1203 for (pp = patterns; pp; pp = pp->next) { 1204 rv = regexec(&pp->re, ptr, 0, NULL, 0); 1205 if (rv == REG_OK) { 1206 /* matched */ 1207 break; 1208 } 1209 1210 switch (rv) { 1211 case REG_NOMATCH: 1212 break; 1213 case REG_ECHAR: 1214 (void) fprintf(stderr, gettext( 1215 "%s: input file \"%s\": line %lld: invalid multibyte character\n"), 1216 cmdname, fn, lineno); 1217 break; 1218 default: 1219 (void) regerror(rv, &pp->re, errstr, 1220 sizeof (errstr)); 1221 (void) fprintf(stderr, gettext( 1222 "%s: input file \"%s\": line %lld: %s\n"), 1223 cmdname, fn, lineno, errstr); 1224 exit(2); 1225 } 1226 } 1227 } 1228 1229 /* 1230 * Context is set up as follows: 1231 * For a 'Before' context, we maintain a set of pointers 1232 * containing 'N' lines of context. If the current number of 1233 * lines contained is greater than N, and N isn't a match, the 1234 * start pointer is moved forward to the next newline. 1235 * 1236 * If we ever find a match, we print out immediately. 1237 * 'nearmatch' tells us if we're within N+1 lines of the last 1238 * match ; if we are, and we find another match, we don't 1239 * separate the matches. 'nearmatch' becomes false when 1240 * a line gets rotated out of the context. 1241 * 1242 * For an 'After' context, we simply wait until we've found a 1243 * match, then create a context N+1 lines big. If we don't find 1244 * a match within the context, we print out the current context. 1245 * Otherwise, we save a reference to the new matching line, 1246 * print out the other context, and reset our context pointers 1247 * to the new matching line. 1248 * 1249 * 'nearmatch' becomes false when we find a non-matching line 1250 * that isn't a part of any context. 1251 * 1252 * A full-context is implemented as a combination of the 1253 * 'Before' and 'After' context logic. Before we find a match, 1254 * we follow the Before logic. When we find a match, we 1255 * follow the After logic. 'nearmatch' is handled by the Before 1256 * logic. 1257 */ 1258 1259 if (!conflag) 1260 goto L_next_line; 1261 1262 if (line_len + (conptrend - conbuf) > conbuflen) { 1263 char *oldconbuf = conbuf; 1264 char *oldconptr = conptr; 1265 long tmp = matchptr - conptr; 1266 1267 conbuflen += BUFSIZE; 1268 conbuf = realloc(conbuf, conbuflen + 1); 1269 if (conbuf == NULL) { 1270 (void) fprintf(stderr, 1271 gettext("%s: out of memory\n"), 1272 cmdname); 1273 exit(2); 1274 } 1275 1276 conptr = conbuf + (conptr - oldconbuf); 1277 conptrend = conptr + (conptrend - oldconptr); 1278 if (matchptr) 1279 matchptr = conptr + tmp; 1280 } 1281 (void) memcpy((conptrend > conptr) ? 1282 conptrend + 1 : conptrend, ptr, line_len); 1283 conptrend += line_len + (conptrend > conptr); 1284 *conptrend = '\n'; 1285 1286 if (!nvflag == rv) { 1287 /* matched */ 1288 if (lastmatch) { 1289 if (conflag & AFTER) { 1290 conaprnt = 1; 1291 nextend = conptrend; 1292 conptrend = conptr + lastmatch; 1293 nextptr = conptrend + 1; 1294 *nextend = '\n'; 1295 } 1296 } else { 1297 if (conflag == AFTER) { 1298 conptr = conptrend - (line_len); 1299 linenum = lineno; 1300 blkoffset = line_offset; 1301 } 1302 blkoffset = line_offset - 1303 (conptrend - conptr - line_len); 1304 } 1305 1306 if (conflag == BEFORE) 1307 conbprnt = 1; 1308 1309 lastmatch = conptrend - conptr; 1310 goto L_next_line; 1311 } 1312 1313 if (!lastmatch) { 1314 if (conflag & BEFORE) { 1315 if (conbcnt >= conblen) { 1316 char *tmp = conptr; 1317 conptr = find_nl(conptr, 1318 conptrend - conptr) + 1; 1319 if (bflag) 1320 blkoffset += conptr - tmp; 1321 linenum++; 1322 nearmatch = 1; 1323 } else { 1324 conbcnt++; 1325 } 1326 } 1327 if (conflag == AFTER) 1328 nearmatch = 1; 1329 } else { 1330 if (++conacnt >= conalen && !conaprnt && conalen) 1331 conaprnt = 1; 1332 else 1333 lastmatch = conptrend - conptr; 1334 } 1335 1336 L_next_line: 1337 /* 1338 * Here, if pp points to non-NULL, something has been matched 1339 * to the pattern. 1340 */ 1341 if (nvflag == (pp != NULL)) { 1342 matches++; 1343 if (!nextend) 1344 matchptr = conflag ? conptrend : ptrend; 1345 } 1346 1347 /* 1348 * Set up some print context so that we can treat 1349 * single-line matches as a zero-N context. 1350 * Apply CLI flags to each line of the context. 1351 * 1352 * For context, we only print if we both have a match and are 1353 * either at the end of the data stream, or we've previously 1354 * declared that we want to print for a particular context. 1355 */ 1356 if (lastmatch && (eof || conaprnt || conbprnt)) { 1357 1358 /* 1359 * We'd normally do this earlier, but we had to 1360 * escape early because we reached the end of the data. 1361 */ 1362 if (eof && nextptr) 1363 conptrend = nextend; 1364 1365 prntlen = conptrend - conptr + 1; 1366 prntptrend = prntptr = conptr; 1367 if (conmatches++ && nearmatch && !cflag) 1368 (void) fwrite("--\n", 1, 3, stdout); 1369 } else if (!conflag && nvflag == (pp != NULL)) { 1370 *ptrend = '\n'; 1371 prntlen = line_len + 1; 1372 prntptrend = prntptr = ptr; 1373 linenum = lineno; 1374 blkoffset = line_offset; 1375 } else if (eof) { 1376 /* No match and no more data */ 1377 goto out; 1378 } else { 1379 /* No match, or we're not done building context */ 1380 goto L_skip_line; 1381 } 1382 1383 while ((prntptrend = find_nl(prntptrend+1, prntlen)) != NULL) { 1384 1385 /* 1386 * GNU grep uses '-' for context lines and ':' for 1387 * matching lines, so replicate that here. 1388 */ 1389 if (prntptrend == matchptr) { 1390 if (eof && nextptr) { 1391 matchptr = nextend; 1392 nextptr = NULL; 1393 } else { 1394 matchptr = NULL; 1395 } 1396 separate = ':'; 1397 } else { 1398 separate = '-'; 1399 } 1400 1401 /* 1402 * Handle q, l, and c flags. 1403 */ 1404 if (qflag) { 1405 /* no need to continue */ 1406 /* 1407 * End of this line is ptrend. 1408 * We have read up to ptr + data_len. 1409 */ 1410 off_t pos; 1411 pos = ptr + data_len - (ptrend + 1); 1412 (void) lseek(fd, -pos, SEEK_CUR); 1413 exit(0); 1414 } 1415 if (lflag) { 1416 (void) printf("%s\n", fn); 1417 goto out; 1418 } 1419 if (!cflag) { 1420 if (Hflag || outfn) { 1421 (void) printf("%s%c", fn, separate); 1422 } 1423 if (bflag) { 1424 (void) printf("%lld%c", (offset_t) 1425 (blkoffset / BSIZE), separate); 1426 } 1427 if (nflag) { 1428 (void) printf("%lld%c", linenum, 1429 separate); 1430 } 1431 (void) fwrite(prntptr, 1, 1432 prntptrend - prntptr + 1, stdout); 1433 } 1434 if (ferror(stdout)) { 1435 return (0); 1436 } 1437 linenum++; 1438 prntlen -= prntptrend - prntptr + 1; 1439 blkoffset += prntptrend - prntptr + 1; 1440 prntptr = prntptrend + 1; 1441 } 1442 1443 if (eof) 1444 goto out; 1445 1446 /* 1447 * Update context buffer and variables post-print 1448 */ 1449 if (conflag) { 1450 conptr = conbuf; 1451 conaprnt = conbprnt = 0; 1452 nearmatch = 0; 1453 conacnt = conbcnt = 0; 1454 1455 if (nextptr) { 1456 (void) memmove(conbuf, nextptr, 1457 nextend - nextptr + 1); 1458 blkoffset += nextptr - conptrend - 1; 1459 conptrend = conptr + (nextend - nextptr); 1460 matchptr = conptrend; 1461 linenum = lineno; 1462 lastmatch = conptrend - conptr; 1463 } else { 1464 conptrend = conptr; 1465 conacnt = 0; 1466 lastmatch = 0; 1467 } 1468 nextptr = nextend = NULL; 1469 } 1470 1471 L_skip_line: 1472 if (!newlinep) 1473 break; 1474 1475 data_len -= line_len + 1; 1476 line_offset += line_len + 1; 1477 ptr = ptrend + 1; 1478 } 1479 1480 out: 1481 if (cflag) { 1482 if (Hflag || outfn) { 1483 (void) printf("%s:", fn); 1484 } 1485 if (!qflag) { 1486 (void) printf("%lld\n", matches); 1487 } 1488 } 1489 return (matches != 0); 1490 } 1491 1492 /* 1493 * usage message for grep 1494 */ 1495 static void 1496 usage(void) 1497 { 1498 if (egrep || fgrep) { 1499 (void) fprintf(stderr, gettext("Usage:\t%s"), cmdname); 1500 (void) fprintf(stderr, 1501 gettext(" [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1502 "[-bhHinsvx] pattern_list [file ...]\n")); 1503 1504 (void) fprintf(stderr, "\t%s", cmdname); 1505 (void) fprintf(stderr, 1506 gettext(" [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1507 "[-bhHinsvx] [-e pattern_list]... " 1508 "[-f pattern_file]... [file...]\n")); 1509 } else { 1510 (void) fprintf(stderr, gettext("Usage:\t%s"), cmdname); 1511 (void) fprintf(stderr, 1512 gettext(" [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1513 "[-bhHinsvx] pattern_list [file ...]\n")); 1514 1515 (void) fprintf(stderr, "\t%s", cmdname); 1516 (void) fprintf(stderr, 1517 gettext(" [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1518 "[-bhHinsvx] [-e pattern_list]... " 1519 "[-f pattern_file]... [file...]\n")); 1520 1521 (void) fprintf(stderr, "\t%s", cmdname); 1522 (void) fprintf(stderr, 1523 gettext(" -E [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1524 "[-bhHinsvx] pattern_list [file ...]\n")); 1525 1526 (void) fprintf(stderr, "\t%s", cmdname); 1527 (void) fprintf(stderr, 1528 gettext(" -E [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1529 "[-bhHinsvx] [-e pattern_list]... " 1530 "[-f pattern_file]... [file...]\n")); 1531 1532 (void) fprintf(stderr, "\t%s", cmdname); 1533 (void) fprintf(stderr, 1534 gettext(" -F [-c|-l|-q] [-r|-R] [-A #|-B #|-C #|-#] " 1535 "[-bhHinsvx] pattern_list [file ...]\n")); 1536 1537 (void) fprintf(stderr, "\t%s", cmdname); 1538 (void) fprintf(stderr, 1539 gettext(" -F [-c|-l|-q] [-A #|-B #|-C #|-#] " 1540 "[-bhHinsvx] [-e pattern_list]... " 1541 "[-f pattern_file]... [file...]\n")); 1542 } 1543 exit(2); 1544 /* NOTREACHED */ 1545 } 1546 1547 /* 1548 * Compile literal pattern into BMG tables 1549 */ 1550 static void 1551 bmgcomp(char *pat, int len) 1552 { 1553 int i; 1554 int tlen; 1555 unsigned char *uc = (unsigned char *)pat; 1556 1557 bmglen = len; 1558 bmgpat = pat; 1559 1560 for (i = 0; i < M_CSETSIZE; i++) { 1561 bmgtab[i] = len; 1562 } 1563 1564 len--; 1565 for (tlen = len, i = 0; i <= len; i++, tlen--) { 1566 bmgtab[*uc++] = tlen; 1567 } 1568 } 1569 1570 /* 1571 * BMG search. 1572 */ 1573 static char * 1574 bmgexec(char *str, char *end) 1575 { 1576 int t; 1577 char *k, *s, *p; 1578 1579 k = str + bmglen - 1; 1580 if (bmglen == 1) { 1581 return (memchr(str, bmgpat[0], end - str)); 1582 } 1583 for (; ; ) { 1584 /* inner loop, should be most optimized */ 1585 while (k < end && (t = bmgtab[(unsigned char)*k]) != 0) { 1586 k += t; 1587 } 1588 if (k >= end) { 1589 return (NULL); 1590 } 1591 for (s = k, p = bmgpat + bmglen - 1; *--s == *--p; ) { 1592 if (p == bmgpat) { 1593 return (s); 1594 } 1595 } 1596 k++; 1597 } 1598 /* NOTREACHED */ 1599 }