Merge branch 'vendor/GREP'
authorJohn Marino <draco@marino.st>
Wed, 4 Nov 2015 12:38:11 +0000 (13:38 +0100)
committerJohn Marino <draco@marino.st>
Wed, 4 Nov 2015 12:38:11 +0000 (13:38 +0100)
1  2 
contrib/grep/src/grep.c

diff --combined contrib/grep/src/grep.c
@@@ -1,5 -1,5 +1,5 @@@
  /* grep.c - main driver file for grep.
-    Copyright (C) 1992, 1997-2002, 2004-2014 Free Software Foundation, Inc.
+    Copyright (C) 1992, 1997-2002, 2004-2015 Free Software Foundation, Inc.
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@@ -55,7 -55,7 +55,7 @@@
  
  #define AUTHORS \
    proper_name ("Mike Haertel"), \
 -  _("others, see <http://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>")
 +  _("others, see\n<http://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>")
  
  /* When stdout is connected to a regular file, save its stat
     information here, so that we can automatically skip it, thus
@@@ -65,20 -65,46 +65,46 @@@ static struct stat out_stat
  /* if non-zero, display usage information and exit */
  static int show_help;
  
- /* If non-zero, print the version on standard output and exit.  */
- static int show_version;
+ /* Print the version on standard output and exit.  */
+ static bool show_version;
  
- /* If nonzero, suppress diagnostics for nonexistent or unreadable files.  */
- static int suppress_errors;
+ /* Suppress diagnostics for nonexistent or unreadable files.  */
+ static bool suppress_errors;
  
  /* If nonzero, use color markers.  */
  static int color_option;
  
- /* If nonzero, show only the part of a line matching the expression. */
- static int only_matching;
+ /* Show only the part of a line matching the expression. */
+ static bool only_matching;
  
  /* If nonzero, make sure first content char in a line is on a tab stop. */
- static int align_tabs;
+ static bool align_tabs;
+ #if HAVE_ASAN
+ /* Record the starting address and length of the sole poisoned region,
+    so that we can unpoison it later, just before each following read.  */
+ static void const *poison_buf;
+ static size_t poison_len;
+ static void
+ clear_asan_poison (void)
+ {
+   if (poison_buf)
+     __asan_unpoison_memory_region (poison_buf, poison_len);
+ }
+ static void
+ asan_poison (void const *addr, size_t size)
+ {
+   poison_buf = addr;
+   poison_len = size;
+   __asan_poison_memory_region (poison_buf, poison_len);
+ }
+ #else
+ static void clear_asan_poison (void) { }
+ static void asan_poison (void const volatile *addr, size_t size) { }
+ #endif
  
  /* The group separator used when context is requested. */
  static const char *group_separator = SEP_STR_GROUP;
@@@ -281,13 -307,13 +307,13 @@@ enu
  {
    BINARY_FILES_OPTION = CHAR_MAX + 1,
    COLOR_OPTION,
-   INCLUDE_OPTION,
+   EXCLUDE_DIRECTORY_OPTION,
    EXCLUDE_OPTION,
    EXCLUDE_FROM_OPTION,
+   GROUP_SEPARATOR_OPTION,
+   INCLUDE_OPTION,
    LINE_BUFFERED_OPTION,
-   LABEL_OPTION,
-   EXCLUDE_DIRECTORY_OPTION,
-   GROUP_SEPARATOR_OPTION
+   LABEL_OPTION
  };
  
  /* Long options equivalences. */
@@@ -347,19 -373,21 +373,21 @@@ static struct option const long_options
  };
  
  /* Define flags declared in grep.h. */
- int match_icase;
- int match_words;
- int match_lines;
- unsigned char eolbyte;
+ bool match_icase;
+ bool match_words;
+ bool match_lines;
+ char eolbyte;
+ enum textbin input_textbin;
  
  static char const *matcher;
  
  /* For error messages. */
  /* The input file name, or (if standard input) "-" or a --label argument.  */
  static char const *filename;
- static size_t filename_prefix_len;
- static int errseen;
- static int write_error_seen;
+ /* Omit leading "./" from file names in diagnostics.  */
+ static bool omit_dot_slash;
+ static bool errseen;
+ static bool write_error_seen;
  
  enum directories_type
    {
@@@ -392,27 -420,43 +420,43 @@@ static enu
      SKIP_DEVICES
    } devices = READ_COMMAND_LINE_DEVICES;
  
- static int grepfile (int, char const *, int, int);
- static int grepdesc (int, int);
+ static bool grepfile (int, char const *, bool, bool);
+ static bool grepdesc (int, bool);
  
  static void dos_binary (void);
  static void dos_unix_byte_offsets (void);
- static int undossify_input (char *, size_t);
+ static size_t undossify_input (char *, size_t);
  
- static int
+ static bool
  is_device_mode (mode_t m)
  {
    return S_ISCHR (m) || S_ISBLK (m) || S_ISSOCK (m) || S_ISFIFO (m);
  }
  
- /* Return nonzero if ST->st_size is defined.  Assume the file is not a
+ static bool
+ skip_devices (bool command_line)
+ {
+   return (devices == SKIP_DEVICES
+           || (devices == READ_COMMAND_LINE_DEVICES && !command_line));
+ }
+ /* Return if ST->st_size is defined.  Assume the file is not a
     symbolic link.  */
- static int
+ static bool
  usable_st_size (struct stat const *st)
  {
    return S_ISREG (st->st_mode) || S_TYPEISSHM (st) || S_TYPEISTMO (st);
  }
  
+ /* Lame substitutes for SEEK_DATA and SEEK_HOLE on platforms lacking them.
+    Do not rely on these finding data or holes if they equal SEEK_SET.  */
+ #ifndef SEEK_DATA
+ enum { SEEK_DATA = SEEK_SET };
+ #endif
+ #ifndef SEEK_HOLE
+ enum { SEEK_HOLE = SEEK_SET };
+ #endif
  /* Functions we'll use to search. */
  typedef void (*compile_fp_t) (char const *, size_t);
  typedef size_t (*execute_fp_t) (char const *, size_t, size_t *, char const *);
@@@ -425,7 -469,7 +469,7 @@@ suppressible_error (char const *mesg, i
  {
    if (! suppress_errors)
      error (0, errnum, "%s", mesg);
-   errseen = 1;
+   errseen = true;
  }
  
  /* If there has already been a write error, don't bother closing
@@@ -437,50 -481,143 +481,143 @@@ clean_up_stdout (void
      close_stdout ();
  }
  
- /* Return 1 if a file is known to be binary for the purpose of 'grep'.
-    BUF, of size BUFSIZE, is the initial buffer read from the file with
-    descriptor FD and status ST.  */
- static int
- file_is_binary (char const *buf, size_t bufsize, int fd, struct stat const *st)
+ static bool
+ textbin_is_binary (enum textbin textbin)
+ {
+   return textbin < TEXTBIN_UNKNOWN;
+ }
+ /* The high-order bit of a byte.  */
+ enum { HIBYTE = 0x80 };
+ /* True if every byte with HIBYTE off is a single-byte character.
+    UTF-8 has this property.  */
+ static bool easy_encoding;
+ static void
+ init_easy_encoding (void)
+ {
+   easy_encoding = true;
+   for (int i = 0; i < HIBYTE; i++)
+     easy_encoding &= mbclen_cache[i] == 1;
+ }
+ /* A cast to TYPE of VAL.  Use this when TYPE is a pointer type, VAL
+    is properly aligned for TYPE, and 'gcc -Wcast-align' cannot infer
+    the alignment and would otherwise complain about the cast.  */
+ #if 4 < __GNUC__ + (6 <= __GNUC_MINOR__)
+ # define CAST_ALIGNED(type, val)                           \
+     ({ __typeof__ (val) val_ = val;                        \
+        _Pragma ("GCC diagnostic push")                     \
+        _Pragma ("GCC diagnostic ignored \"-Wcast-align\"") \
+        (type) val_;                                        \
+        _Pragma ("GCC diagnostic pop")                      \
+     })
+ #else
+ # define CAST_ALIGNED(type, val) ((type) (val))
+ #endif
+ /* An unsigned type suitable for fast matching.  */
+ typedef uintmax_t uword;
+ /* Skip the easy bytes in a buffer that is guaranteed to have a sentinel
+    that is not easy, and return a pointer to the first non-easy byte.
+    In easy encodings, the easy bytes all have HIBYTE off.
+    In other encodings, no byte is easy.  */
+ static char const * _GL_ATTRIBUTE_PURE
+ skip_easy_bytes (char const *buf)
+ {
+   if (!easy_encoding)
+     return buf;
+   uword uword_max = -1;
+   /* 0x8080..., extended to be wide enough for uword.  */
+   uword hibyte_mask = uword_max / UCHAR_MAX * HIBYTE;
+   /* Search a byte at a time until the pointer is aligned, then a
+      uword at a time until a match is found, then a byte at a time to
+      identify the exact byte.  The uword search may go slightly past
+      the buffer end, but that's benign.  */
+   char const *p;
+   uword const *s;
+   for (p = buf; (uintptr_t) p % sizeof (uword) != 0; p++)
+     if (*p & HIBYTE)
+       return p;
+   for (s = CAST_ALIGNED (uword const *, p); ! (*s & hibyte_mask); s++)
+     continue;
+   for (p = (char const *) s; ! (*p & HIBYTE); p++)
+     continue;
+   return p;
+ }
+ /* Return the text type of data in BUF, of size SIZE.
+    BUF must be followed by at least sizeof (uword) bytes,
+    which may be arbitrarily written to or read from.  */
+ static enum textbin
+ buffer_textbin (char *buf, size_t size)
  {
-   #ifndef SEEK_HOLE
-   enum { SEEK_HOLE = SEEK_END };
-   #endif
-   /* If -z, test only whether the initial buffer contains '\200';
-      knowing about holes won't help.  */
-   if (! eolbyte)
-     return memchr (buf, '\200', bufsize) != 0;
-   /* If the initial buffer contains a null byte, guess that the file
-      is binary.  */
-   if (memchr (buf, '\0', bufsize))
-     return 1;
-   /* If the file has holes, it must contain a null byte somewhere.  */
-   if (SEEK_HOLE != SEEK_END && usable_st_size (st))
+   if (eolbyte && memchr (buf, '\0', size))
+     return TEXTBIN_BINARY;
+   if (1 < MB_CUR_MAX)
      {
-       off_t cur = bufsize;
-       if (O_BINARY || fd == STDIN_FILENO)
+       mbstate_t mbs = { 0 };
+       size_t clen;
+       char const *p;
+       buf[size] = -1;
+       for (p = buf; (p = skip_easy_bytes (p)) < buf + size; p += clen)
          {
-           cur = lseek (fd, 0, SEEK_CUR);
-           if (cur < 0)
-             return 0;
+           clen = mbrlen (p, buf + size - p, &mbs);
+           if ((size_t) -2 <= clen)
+             return clen == (size_t) -2 ? TEXTBIN_UNKNOWN : TEXTBIN_BINARY;
          }
+     }
+   return TEXTBIN_TEXT;
+ }
  
-       /* Look for a hole after the current location.  */
-       off_t hole_start = lseek (fd, cur, SEEK_HOLE);
-       if (0 <= hole_start)
+ /* Return the text type of a file.  BUF, of size SIZE, is the initial
+    buffer read from the file with descriptor FD and status ST.
+    BUF must be followed by at least sizeof (uword) bytes,
+    which may be arbitrarily written to or read from.  */
+ static enum textbin
+ file_textbin (char *buf, size_t size, int fd, struct stat const *st)
+ {
+   enum textbin textbin = buffer_textbin (buf, size);
+   if (textbin_is_binary (textbin))
+     return textbin;
+   if (usable_st_size (st))
+     {
+       if (st->st_size <= size)
+         return textbin == TEXTBIN_UNKNOWN ? TEXTBIN_BINARY : textbin;
+       /* If the file has holes, it must contain a null byte somewhere.  */
+       if (SEEK_HOLE != SEEK_SET && eolbyte)
          {
-           if (lseek (fd, cur, SEEK_SET) < 0)
-             suppressible_error (filename, errno);
-           if (hole_start < st->st_size)
-             return 1;
+           off_t cur = size;
+           if (O_BINARY || fd == STDIN_FILENO)
+             {
+               cur = lseek (fd, 0, SEEK_CUR);
+               if (cur < 0)
+                 return TEXTBIN_UNKNOWN;
+             }
+           /* Look for a hole after the current location.  */
+           off_t hole_start = lseek (fd, cur, SEEK_HOLE);
+           if (0 <= hole_start)
+             {
+               if (lseek (fd, cur, SEEK_SET) < 0)
+                 suppressible_error (filename, errno);
+               if (hole_start < st->st_size)
+                 return TEXTBIN_BINARY;
+             }
          }
      }
  
-   /* Guess that the file does not contain binary data.  */
-   return 0;
+   return TEXTBIN_UNKNOWN;
  }
  
  /* Convert STR to a nonnegative integer, storing the result in *OUT.
@@@ -503,15 -640,15 +640,15 @@@ context_length_arg (char const *str, in
      }
  }
  
- /* Return nonzero if the file with NAME should be skipped.
-    If COMMAND_LINE is nonzero, it is a command-line argument.
-    If IS_DIR is nonzero, it is a directory.  */
- static int
- skipped_file (char const *name, int command_line, int is_dir)
+ /* Return true if the file with NAME should be skipped.
+    If COMMAND_LINE, it is a command-line argument.
+    If IS_DIR, it is a directory.  */
+ static bool
+ skipped_file (char const *name, bool command_line, bool is_dir)
  {
    return (is_dir
            ? (directories == SKIP_DIRECTORIES
-              || (! (command_line && filename_prefix_len != 0)
+              || (! (command_line && omit_dot_slash)
                   && excluded_directory_patterns
                   && excluded_file_name (excluded_directory_patterns, name)))
            : (excluded_patterns
@@@ -533,6 -670,10 +670,10 @@@ static off_t bufoffset;          /* Read offset
  static off_t after_last_match;        /* Pointer after last matching line that
                                     would have been output if we were
                                     outputting characters. */
+ static bool skip_nuls;                /* Skip '\0' in data.  */
+ static bool skip_empty_lines; /* Skip empty lines in data.  */
+ static bool seek_data_failed; /* lseek with SEEK_DATA failed.  */
+ static uintmax_t totalnl;     /* Total newline count before lastnl. */
  
  /* Return VAL aligned to the next multiple of ALIGNMENT.  VAL can be
     an integer or a pointer.  Both args must be free of side effects.  */
     ? (val) \
     : (val) + ((alignment) - (size_t) (val) % (alignment)))
  
- /* Reset the buffer for a new file, returning zero if we should skip it.
+ /* Add two numbers that count input bytes or lines, and report an
+    error if the addition overflows.  */
+ static uintmax_t
+ add_count (uintmax_t a, uintmax_t b)
+ {
+   uintmax_t sum = a + b;
+   if (sum < a)
+     error (EXIT_TROUBLE, 0, _("input is too large to count"));
+   return sum;
+ }
+ /* Return true if BUF (of size SIZE) is all zeros.  */
+ static bool
+ all_zeros (char const *buf, size_t size)
+ {
+   for (char const *p = buf; p < buf + size; p++)
+     if (*p)
+       return false;
+   return true;
+ }
+ /* Reset the buffer for a new file, returning false if we should skip it.
     Initialize on the first time through. */
- static int
+ static bool
  reset (int fd, struct stat const *st)
  {
    if (! pagesize)
        pagesize = getpagesize ();
        if (pagesize == 0 || 2 * pagesize + 1 <= pagesize)
          abort ();
-       bufalloc = ALIGN_TO (INITIAL_BUFSIZE, pagesize) + pagesize + 1;
+       bufalloc = (ALIGN_TO (INITIAL_BUFSIZE, pagesize)
+                   + pagesize + sizeof (uword));
        buffer = xmalloc (bufalloc);
      }
  
            if (bufoffset < 0)
              {
                suppressible_error (_("lseek failed"), errno);
-               return 0;
+               return false;
              }
          }
      }
-   return 1;
+   return true;
  }
  
  /* Read new stuff into the buffer, saving the specified
     amount of old stuff.  When we're done, 'bufbeg' points
     to the beginning of the buffer contents, and 'buflim'
-    points just after the end.  Return zero if there's an error.  */
- static int
+    points just after the end.  Return false if there's an error.  */
+ static bool
  fillbuf (size_t save, struct stat const *st)
  {
-   ssize_t fillsize;
-   int cc = 1;
+   size_t fillsize;
+   bool cc = true;
    char *readbuf;
    size_t readsize;
  
       that we want to save.  */
    size_t saved_offset = buflim - save - buffer;
  
-   if (pagesize <= buffer + bufalloc - buflim)
+   if (pagesize <= buffer + bufalloc - sizeof (uword) - buflim)
      {
        readbuf = buflim;
        bufbeg = buflim - save;
        char *newbuf;
  
        /* Grow newsize until it is at least as great as minsize.  */
-       for (newsize = bufalloc - pagesize - 1; newsize < minsize; newsize *= 2)
-         if (newsize * 2 < newsize || newsize * 2 + pagesize + 1 < newsize * 2)
+       for (newsize = bufalloc - pagesize - sizeof (uword);
+            newsize < minsize;
+            newsize *= 2)
+         if ((SIZE_MAX - pagesize - sizeof (uword)) / 2 < newsize)
            xalloc_die ();
  
        /* Try not to allocate more memory than the file size indicates,
          }
  
        /* Add enough room so that the buffer is aligned and has room
-          for byte sentinels fore and aft.  */
-       newalloc = newsize + pagesize + 1;
+          for byte sentinels fore and aft, and so that a uword can
+          be read aft.  */
+       newalloc = newsize + pagesize + sizeof (uword);
  
        newbuf = bufalloc < newalloc ? xmalloc (bufalloc = newalloc) : buffer;
        readbuf = ALIGN_TO (newbuf + 1 + save, pagesize);
          }
      }
  
-   readsize = buffer + bufalloc - readbuf;
+   clear_asan_poison ();
+   readsize = buffer + bufalloc - sizeof (uword) - readbuf;
    readsize -= readsize % pagesize;
  
-   fillsize = safe_read (bufdesc, readbuf, readsize);
-   if (fillsize < 0)
-     fillsize = cc = 0;
-   bufoffset += fillsize;
+   while (true)
+     {
+       fillsize = safe_read (bufdesc, readbuf, readsize);
+       if (fillsize == SAFE_READ_ERROR)
+         {
+           fillsize = 0;
+           cc = false;
+         }
+       bufoffset += fillsize;
+       if (fillsize == 0 || !skip_nuls || !all_zeros (readbuf, fillsize))
+         break;
+       totalnl = add_count (totalnl, fillsize);
+       if (SEEK_DATA != SEEK_SET && !seek_data_failed)
+         {
+           /* Solaris SEEK_DATA fails with errno == ENXIO in a hole at EOF.  */
+           off_t data_start = lseek (bufdesc, bufoffset, SEEK_DATA);
+           if (data_start < 0 && errno == ENXIO
+               && usable_st_size (st) && bufoffset < st->st_size)
+             data_start = lseek (bufdesc, 0, SEEK_END);
+           if (data_start < 0)
+             seek_data_failed = true;
+           else
+             {
+               totalnl = add_count (totalnl, data_start - bufoffset);
+               bufoffset = data_start;
+             }
+         }
+     }
    fillsize = undossify_input (readbuf, fillsize);
    buflim = readbuf + fillsize;
+   /* Initialize the following word, because skip_easy_bytes and some
+      matchers read (but do not use) those bytes.  This avoids false
+      positive reports of these bytes being used uninitialized.  */
+   memset (buflim, 0, sizeof (uword));
+   /* Mark the part of the buffer not filled by the read or set by
+      the above memset call as ASAN-poisoned.  */
+   asan_poison (buflim + sizeof (uword),
+                bufalloc - (buflim - buffer) - sizeof (uword));
    return cc;
  }
  
@@@ -662,20 -869,19 +869,19 @@@ static enu
  } binary_files;               /* How to handle binary files.  */
  
  static int filename_mask;     /* If zero, output nulls after filenames.  */
- static int out_quiet;         /* Suppress all normal output. */
+ static bool out_quiet;                /* Suppress all normal output. */
  static bool out_invert;               /* Print nonmatching stuff. */
  static int out_file;          /* Print filenames. */
- static int out_line;          /* Print line numbers. */
- static int out_byte;          /* Print byte offsets. */
+ static bool out_line;         /* Print line numbers. */
+ static bool out_byte;         /* Print byte offsets. */
  static intmax_t out_before;   /* Lines of leading context. */
  static intmax_t out_after;    /* Lines of trailing context. */
- static int count_matches;     /* Count matching lines.  */
+ static bool count_matches;    /* Count matching lines.  */
  static int list_files;                /* List matching files.  */
- static int no_filenames;      /* Suppress file names.  */
+ static bool no_filenames;     /* Suppress file names.  */
  static intmax_t max_count;    /* Stop after outputting this many
                                     lines from an input file.  */
- static int line_buffered;       /* If nonzero, use line buffering, i.e.
-                                    fflush everyline out.  */
+ static bool line_buffered;    /* Use line buffering.  */
  static char *label = NULL;      /* Fake filename for stdin */
  
  
@@@ -685,26 -891,14 +891,14 @@@ static char const *lastnl;      /* Pointer a
  static char const *lastout;   /* Pointer after last character output;
                                     NULL if no character has been output
                                     or if it's conceptually before bufbeg. */
- static uintmax_t totalnl;     /* Total newline count before lastnl. */
  static intmax_t outleft;      /* Maximum number of lines to be output.  */
  static intmax_t pending;      /* Pending lines of output.
                                     Always kept 0 if out_quiet is true.  */
- static int done_on_match;     /* Stop scanning file on first match.  */
- static int exit_on_match;     /* Exit on first match.  */
+ static bool done_on_match;    /* Stop scanning file on first match.  */
+ static bool exit_on_match;    /* Exit on first match.  */
  
  #include "dosbuf.c"
  
- /* Add two numbers that count input bytes or lines, and report an
-    error if the addition overflows.  */
- static uintmax_t
- add_count (uintmax_t a, uintmax_t b)
- {
-   uintmax_t sum = a + b;
-   if (sum < a)
-     error (EXIT_TROUBLE, 0, _("input is too large to count"));
-   return sum;
- }
  static void
  nlscan (char const *lim)
  {
@@@ -768,15 -962,15 +962,15 @@@ print_offset (uintmax_t pos, int min_wi
  
  /* Print a whole line head (filename, line, byte).  */
  static void
- print_line_head (char const *beg, char const *lim, int sep)
+ print_line_head (char const *beg, char const *lim, char sep)
  {
-   int pending_sep = 0;
+   bool pending_sep = false;
  
    if (out_file)
      {
        print_filename ();
        if (filename_mask)
-         pending_sep = 1;
+         pending_sep = true;
        else
          fputc (0, stdout);
      }
        if (pending_sep)
          print_sep (sep);
        print_offset (totalnl, 4, line_num_color);
-       pending_sep = 1;
+       pending_sep = true;
      }
  
    if (out_byte)
        if (pending_sep)
          print_sep (sep);
        print_offset (pos, 6, byte_num_color);
-       pending_sep = 1;
+       pending_sep = true;
      }
  
    if (pending_sep)
@@@ -828,8 -1022,8 +1022,8 @@@ print_line_middle (const char *beg, con
    const char *mid = NULL;
  
    while (cur < lim
-          && ((match_offset = execute (beg, lim - beg, &match_size,
-                                       beg + (cur - beg))) != (size_t) -1))
+          && ((match_offset = execute (beg, lim - beg, &match_size, cur))
+              != (size_t) -1))
      {
        char const *b = beg + match_offset;
  
@@@ -903,9 -1097,9 +1097,9 @@@ print_line_tail (const char *beg, cons
  }
  
  static void
- prline (char const *beg, char const *lim, int sep)
+ prline (char const *beg, char const *lim, char sep)
  {
-   int matching;
+   bool matching;
    const char *line_color;
    const char *match_color;
  
  
    if (ferror (stdout))
      {
-       write_error_seen = 1;
+       write_error_seen = true;
        error (EXIT_TROUBLE, 0, _("write error"));
      }
  
@@@ -1048,52 -1242,30 +1242,30 @@@ prtext (char const *beg, char const *li
    outleft -= n;
  }
  
- /* Invoke the matcher, EXECUTE, on buffer BUF of SIZE bytes.  If there
-    is no match, return (size_t) -1.  Otherwise, set *MATCH_SIZE to the
-    length of the match and return the offset of the start of the match.  */
- static size_t
- do_execute (char const *buf, size_t size, size_t *match_size)
+ /* Replace all NUL bytes in buffer P (which ends at LIM) with EOL.
+    This avoids running out of memory when binary input contains a long
+    sequence of zeros, which would otherwise be considered to be part
+    of a long line.  P[LIM] should be EOL.  */
+ static void
+ zap_nuls (char *p, char *lim, char eol)
  {
-   size_t result;
-   const char *line_next;
-   /* With the current implementation, using --ignore-case with a multi-byte
-      character set is very inefficient when applied to a large buffer
-      containing many matches.  We can avoid much of the wasted effort
-      by matching line-by-line.
-      FIXME: this is just an ugly workaround, and it doesn't really
-      belong here.  Also, PCRE is always using this same per-line
-      matching algorithm.  Either we fix -i, or we should refactor
-      this code---for example, we could add another function pointer
-      to struct matcher to split the buffer passed to execute.  It would
-      perform the memchr if line-by-line matching is necessary, or just
-      return buf + size otherwise.  */
-   if (! (execute == Fexecute || execute == Pexecute)
-       || MB_CUR_MAX == 1 || !match_icase)
-     return execute (buf, size, match_size, NULL);
-   for (line_next = buf; line_next < buf + size; )
-     {
-       const char *line_buf = line_next;
-       const char *line_end = memchr (line_buf, eolbyte,
-                                      (buf + size) - line_buf);
-       if (line_end == NULL)
-         line_next = line_end = buf + size;
-       else
-         line_next = line_end + 1;
-       result = execute (line_buf, line_next - line_buf, match_size, NULL);
-       if (result != (size_t) -1)
-         return (line_buf - buf) + result;
-     }
-   return (size_t) -1;
+   if (eol)
+     while (true)
+       {
+         *lim = '\0';
+         p += strlen (p);
+         *lim = eol;
+         if (p == lim)
+           break;
+         do
+           *p++ = eol;
+         while (!*p);
+       }
  }
  
  /* Scan the specified portion of the buffer, matching lines (or
     between matching lines if OUT_INVERT is true).  Return a count of
-    lines printed. */
+    lines printed.  Replace all NUL bytes with NUL_ZAPPER as we go.  */
  static intmax_t
  grepbuf (char const *beg, char const *lim)
  {
    for (p = beg; p < lim; p = endp)
      {
        size_t match_size;
-       size_t match_offset = do_execute (p, lim - p, &match_size);
+       size_t match_offset = execute (p, lim - p, &match_size, NULL);
        if (match_offset == (size_t) -1)
          {
            if (!out_invert)
@@@ -1141,12 -1313,15 +1313,15 @@@ static intmax_
  grep (int fd, struct stat const *st)
  {
    intmax_t nlines, i;
-   int not_text;
+   enum textbin textbin;
    size_t residue, save;
    char oldc;
    char *beg;
    char *lim;
    char eol = eolbyte;
+   char nul_zapper = '\0';
+   bool done_on_match_0 = done_on_match;
+   bool out_quiet_0 = out_quiet;
  
    if (! reset (fd, st))
      return 0;
    outleft = max_count;
    after_last_match = 0;
    pending = 0;
+   skip_nuls = skip_empty_lines && !eol;
+   seek_data_failed = false;
  
    nlines = 0;
    residue = 0;
  
    if (! fillbuf (save, st))
      {
 -      suppressible_error (filename, errno);
 +      if (errno != EINVAL)
 +        suppressible_error (filename, errno);
        return 0;
      }
  
-   not_text = (((binary_files == BINARY_BINARY_FILES && !out_quiet)
-                || binary_files == WITHOUT_MATCH_BINARY_FILES)
-               && file_is_binary (bufbeg, buflim - bufbeg, fd, st));
-   if (not_text && binary_files == WITHOUT_MATCH_BINARY_FILES)
-     return 0;
-   done_on_match += not_text;
-   out_quiet += not_text;
+   if (binary_files == TEXT_BINARY_FILES)
+     textbin = TEXTBIN_TEXT;
+   else
+     {
+       textbin = file_textbin (bufbeg, buflim - bufbeg, fd, st);
+       if (textbin_is_binary (textbin))
+         {
+           if (binary_files == WITHOUT_MATCH_BINARY_FILES)
+             return 0;
+           done_on_match = out_quiet = true;
+           nul_zapper = eol;
+           skip_nuls = skip_empty_lines;
+         }
+       else if (execute != Pexecute)
+         textbin = TEXTBIN_TEXT;
+     }
  
    for (;;)
      {
+       input_textbin = textbin;
        lastnl = bufbeg;
        if (lastout)
          lastout = bufbeg;
        if (beg == buflim)
          break;
  
+       zap_nuls (beg, buflim, nul_zapper);
        /* Determine new residue (the length of an incomplete line at the end of
           the buffer, 0 means there is no incomplete last line).  */
        oldc = beg[-1];
          }
  
        /* Detect whether leading context is adjacent to previous output.  */
-       if (beg != lastout)
-         lastout = 0;
+       if (lastout)
+         {
+           if (textbin == TEXTBIN_UNKNOWN)
+             textbin = TEXTBIN_TEXT;
+           if (beg != lastout)
+             lastout = 0;
+         }
  
        /* Handle some details and read more data to scan.  */
        save = residue + lim - beg;
            suppressible_error (filename, errno);
            goto finish_grep;
          }
+       /* If the file's textbin has not been determined yet, assume
+          it's binary if the next input buffer suggests so.  */
+       if (textbin == TEXTBIN_UNKNOWN)
+         {
+           enum textbin tb = buffer_textbin (bufbeg, buflim - bufbeg);
+           if (textbin_is_binary (tb))
+             {
+               if (binary_files == WITHOUT_MATCH_BINARY_FILES)
+                 return 0;
+               textbin = tb;
+               done_on_match = out_quiet = true;
+               nul_zapper = eol;
+               skip_nuls = skip_empty_lines;
+             }
+         }
      }
    if (residue)
      {
      }
  
   finish_grep:
-   done_on_match -= not_text;
-   out_quiet -= not_text;
-   if ((not_text & ~out_quiet) && nlines != 0)
+   done_on_match = done_on_match_0;
+   out_quiet = out_quiet_0;
+   if (textbin_is_binary (textbin) && !out_quiet && nlines != 0)
      printf (_("Binary file %s matches\n"), filename);
    return nlines;
  }
  
- static int
- grepdirent (FTS *fts, FTSENT *ent, int command_line)
+ static bool
+ grepdirent (FTS *fts, FTSENT *ent, bool command_line)
  {
-   int follow, dirdesc;
-   struct stat *st = ent->fts_statp;
+   bool follow;
+   int dirdesc;
    command_line &= ent->fts_level == FTS_ROOTLEVEL;
  
    if (ent->fts_info == FTS_DP)
      {
        if (directories == RECURSE_DIRECTORIES && command_line)
          out_file &= ~ (2 * !no_filenames);
-       return 1;
+       return true;
      }
  
-   if (skipped_file (ent->fts_name, command_line,
-                     (ent->fts_info == FTS_D || ent->fts_info == FTS_DC
-                      || ent->fts_info == FTS_DNR)))
+   if (!command_line
+       && skipped_file (ent->fts_name, false,
+                        (ent->fts_info == FTS_D || ent->fts_info == FTS_DC
+                         || ent->fts_info == FTS_DNR)))
      {
        fts_set (fts, ent, FTS_SKIP);
-       return 1;
+       return true;
      }
  
-   filename = ent->fts_path + filename_prefix_len;
+   filename = ent->fts_path;
+   if (omit_dot_slash && filename[1])
+     filename += 2;
    follow = (fts->fts_options & FTS_LOGICAL
              || (fts->fts_options & FTS_COMFOLLOW && command_line));
  
        if (directories == RECURSE_DIRECTORIES)
          {
            out_file |= 2 * !no_filenames;
-           return 1;
+           return true;
          }
        fts_set (fts, ent, FTS_SKIP);
        break;
        if (!suppress_errors)
          error (0, 0, _("warning: %s: %s"), filename,
                 _("recursive directory loop"));
-       return 1;
+       return true;
  
      case FTS_DNR:
      case FTS_ERR:
      case FTS_NS:
        suppressible_error (filename, ent->fts_errno);
-       return 1;
+       return true;
  
      case FTS_DEFAULT:
      case FTS_NSOK:
-       if (devices == SKIP_DEVICES
-           || (devices == READ_COMMAND_LINE_DEVICES && !command_line))
+       if (skip_devices (command_line))
          {
+           struct stat *st = ent->fts_statp;
            struct stat st1;
            if (! st->st_mode)
              {
                if (fstatat (fts->fts_cwd_fd, ent->fts_accpath, &st1, flag) != 0)
                  {
                    suppressible_error (filename, errno);
-                   return 1;
+                   return true;
                  }
                st = &st1;
              }
            if (is_device_mode (st->st_mode))
-             return 1;
+             return true;
          }
        break;
  
  
      case FTS_SL:
      case FTS_W:
-       return 1;
+       return true;
  
      default:
        abort ();
    return grepfile (dirdesc, ent->fts_accpath, follow, command_line);
  }
  
- static int
- grepfile (int dirdesc, char const *name, int follow, int command_line)
+ /* True if errno is ERR after 'open ("symlink", ... O_NOFOLLOW ...)'.
+    POSIX specifies ELOOP, but it's EMLINK on FreeBSD and EFTYPE on NetBSD.  */
+ static bool
+ open_symlink_nofollow_error (int err)
+ {
+   if (err == ELOOP || err == EMLINK)
+     return true;
+ #ifdef EFTYPE
+   if (err == EFTYPE)
+     return true;
+ #endif
+   return false;
+ }
+ static bool
+ grepfile (int dirdesc, char const *name, bool follow, bool command_line)
  {
-   int desc = openat_safer (dirdesc, name, O_RDONLY | (follow ? 0 : O_NOFOLLOW));
+   int oflag = (O_RDONLY | O_NOCTTY
+                | (follow ? 0 : O_NOFOLLOW)
+                | (skip_devices (command_line) ? O_NONBLOCK : 0));
+   int desc = openat_safer (dirdesc, name, oflag);
    if (desc < 0)
      {
-       if (follow || (errno != ELOOP && errno != EMLINK))
+       if (follow || ! open_symlink_nofollow_error (errno))
          suppressible_error (filename, errno);
-       return 1;
+       return true;
      }
    return grepdesc (desc, command_line);
  }
  
- static int
- grepdesc (int desc, int command_line)
+ static bool
+ grepdesc (int desc, bool command_line)
  {
    intmax_t count;
-   int status = 1;
+   bool status = true;
    struct stat st;
  
    /* Get the file status, possibly for the second time.  This catches
        goto closeout;
      }
  
+   if (desc != STDIN_FILENO && skip_devices (command_line)
+       && is_device_mode (st.st_mode))
+     goto closeout;
    if (desc != STDIN_FILENO && command_line
-       && skipped_file (filename, 1, S_ISDIR (st.st_mode)))
+       && skipped_file (filename, true, S_ISDIR (st.st_mode) != 0))
      goto closeout;
  
    if (desc != STDIN_FILENO
      {
        if (! suppress_errors)
          error (0, 0, _("input file %s is also the output"), quote (filename));
-       errseen = 1;
+       errseen = true;
        goto closeout;
      }
  
    return status;
  }
  
- static int
+ static bool
  grep_command_line_arg (char const *arg)
  {
    if (STREQ (arg, "-"))
      {
        filename = label ? label : _("(standard input)");
-       return grepdesc (STDIN_FILENO, 1);
+       return grepdesc (STDIN_FILENO, true);
      }
    else
      {
        filename = arg;
-       return grepfile (AT_FDCWD, arg, 1, 1);
+       return grepfile (AT_FDCWD, arg, true, true);
      }
  }
  
@@@ -1537,10 -1770,10 +1771,10 @@@ Example: %s -i 'hello world' menu.h mai
  Regexp selection and interpretation:\n"), program_name);
        printf (_("\
    -E, --extended-regexp     PATTERN is an extended regular expression (ERE)\n\
-   -F, --fixed-strings       PATTERN is a set of newline-separated fixed strings\n\
+   -F, --fixed-strings       PATTERN is a set of newline-separated strings\n\
    -G, --basic-regexp        PATTERN is a basic regular expression (BRE)\n\
    -P, --perl-regexp         PATTERN is a Perl regular expression\n"));
-   /* -X is undocumented on purpose. */
+   /* -X is deliberately undocumented.  */
        printf (_("\
    -e, --regexp=PATTERN      use PATTERN for matching\n\
    -f, --file=FILE           obtain PATTERN from FILE\n\
@@@ -1584,7 -1817,8 +1818,8 @@@ Output control:\n
  "));
        printf (_("\
        --include=FILE_PATTERN  search only files that match FILE_PATTERN\n\
-       --exclude=FILE_PATTERN  skip files and directories matching FILE_PATTERN\n\
+       --exclude=FILE_PATTERN  skip files and directories matching\
+  FILE_PATTERN\n\
        --exclude-from=FILE   skip files matching any file pattern from FILE\n\
        --exclude-dir=PATTERN  directories that match PATTERN will be skipped.\n\
  "));
@@@ -1618,12 -1852,7 +1853,7 @@@ When FILE is -, read standard input.  W
  -r is given, - otherwise.  If fewer than two FILEs are given, assume -h.\n\
  Exit status is 0 if any line is selected, 1 otherwise;\n\
  if any error occurs and -q is not given, the exit status is 2.\n"));
-       printf (_("\nReport bugs to: %s\n"), PACKAGE_BUGREPORT);
-       printf (_("GNU Grep home page: <%s>\n"),
-               "http://www.gnu.org/software/grep/");
-       fputs (_("General help using GNU software: <http://www.gnu.org/gethelp/>\n"),
-              stdout);
+       emit_bug_reporting_address ();
      }
    exit (status);
  }
  static void
  Gcompile (char const *pattern, size_t size)
  {
-   GEAcompile (pattern, size, RE_SYNTAX_GREP | RE_NO_EMPTY_RANGES);
+   GEAcompile (pattern, size, RE_SYNTAX_GREP);
  }
  
  static void
  Ecompile (char const *pattern, size_t size)
  {
-   GEAcompile (pattern, size, RE_SYNTAX_POSIX_EGREP | RE_NO_EMPTY_RANGES);
+   GEAcompile (pattern, size, RE_SYNTAX_EGREP);
  }
  
  static void
@@@ -1767,14 -1996,15 +1997,15 @@@ static in
  get_nondigit_option (int argc, char *const *argv, intmax_t *default_context)
  {
    static int prev_digit_optind = -1;
-   int this_digit_optind, was_digit;
+   int this_digit_optind;
+   bool was_digit;
    char buf[INT_BUFSIZE_BOUND (intmax_t) + 4];
    char *p = buf;
    int opt;
  
-   was_digit = 0;
+   was_digit = false;
    this_digit_optind = optind;
-   while (1)
+   while (true)
      {
        opt = getopt_long (argc, (char **) argv, short_options,
                           long_options, NULL);
          }
        *p++ = opt;
  
-       was_digit = 1;
+       was_digit = true;
        prev_digit_optind = this_digit_optind;
        this_digit_optind = optind;
      }
@@@ -1883,9 -2113,9 +2114,9 @@@ contains_encoding_error (char const *pa
    mbstate_t mbs = { 0 };
    size_t i, charlen;
  
-   for (i = 0; i < patlen; i += charlen + (charlen == 0))
+   for (i = 0; i < patlen; i += charlen)
      {
-       charlen = mbrlen (pat + i, patlen - i, &mbs);
+       charlen = mb_clen (pat + i, patlen - i, &mbs);
        if ((size_t) -2 <= charlen)
          return true;
      }
@@@ -1903,8 -2133,7 +2134,7 @@@ fgrep_to_grep_pattern (size_t len, cha
  
    for (; len; keys += n, len -= n)
      {
-       wchar_t wc;
-       n = mbrtowc (&wc, keys, len, &mb_state);
+       n = mb_clen (keys, len, &mb_state);
        switch (n)
          {
          case (size_t) -2:
@@@ -1936,9 -2165,9 +2166,9 @@@ main (int argc, char **argv
  {
    char *keys;
    size_t keycc, oldcc, keyalloc;
-   int with_filenames;
+   bool with_filenames;
    size_t cc;
-   int opt, status, prepended;
+   int opt, prepended;
    int prev_optind, last_recursive;
    int fread_errno;
    intmax_t default_context;
  
    keys = NULL;
    keycc = 0;
-   with_filenames = 0;
+   with_filenames = false;
    eolbyte = '\n';
    filename_mask = ~0;
  
    /* Default before/after context: changed by -C/-NUM options */
    default_context = -1;
    /* Changed by -o option */
-   only_matching = 0;
+   only_matching = false;
  
    /* Internationalization. */
  #if defined HAVE_SETLOCALE
    atexit (clean_up_stdout);
  
    last_recursive = 0;
    prepended = prepend_default_options (getenv ("GREP_OPTIONS"), &argc, &argv);
+   if (prepended)
+     error (0, 0, _("warning: GREP_OPTIONS is deprecated;"
+                    " please use an alias or script"));
    compile = matchers[0].compile;
    execute = matchers[0].execute;
  
          break;
  
        case 'H':
-         with_filenames = 1;
-         no_filenames = 0;
+         with_filenames = true;
+         no_filenames = false;
          break;
  
        case 'I':
          break;
  
        case 'T':
-         align_tabs = 1;
+         align_tabs = true;
          break;
  
        case 'U':
          break;
  
        case 'V':
-         show_version = 1;
+         show_version = true;
          break;
  
        case 'a':
          break;
  
        case 'b':
-         out_byte = 1;
+         out_byte = true;
          break;
  
        case 'c':
-         count_matches = 1;
+         count_matches = true;
          break;
  
        case 'd':
          break;
  
        case 'h':
-         with_filenames = 0;
-         no_filenames = 1;
+         with_filenames = false;
+         no_filenames = true;
          break;
  
        case 'i':
        case 'y':                       /* For old-timers . . . */
-         match_icase = 1;
+         match_icase = true;
          break;
  
        case 'L':
          break;
  
        case 'n':
-         out_line = 1;
+         out_line = true;
          break;
  
        case 'o':
-         only_matching = 1;
+         only_matching = true;
          break;
  
        case 'q':
-         exit_on_match = 1;
+         exit_on_match = true;
          exit_failure = 0;
          break;
  
          break;
  
        case 's':
-         suppress_errors = 1;
+         suppress_errors = true;
          break;
  
        case 'v':
          break;
  
        case 'w':
-         match_words = 1;
+         match_words = true;
          break;
  
        case 'x':
-         match_lines = 1;
+         match_lines = true;
          break;
  
        case 'Z':
          if (!excluded_patterns)
            excluded_patterns = new_exclude ();
          add_exclude (excluded_patterns, optarg,
-                      (EXCLUDE_WILDCARDS
+                      (EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS
                        | (opt == INCLUDE_OPTION ? EXCLUDE_INCLUDE : 0)));
          break;
        case EXCLUDE_FROM_OPTION:
          if (!excluded_patterns)
            excluded_patterns = new_exclude ();
          if (add_exclude_file (add_exclude, excluded_patterns, optarg,
-                               EXCLUDE_WILDCARDS, '\n') != 0)
+                               EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS, '\n') != 0)
            {
              error (EXIT_TROUBLE, errno, "%s", optarg);
            }
          if (!excluded_directory_patterns)
            excluded_directory_patterns = new_exclude ();
          strip_trailing_slashes (optarg);
-         add_exclude (excluded_directory_patterns, optarg, EXCLUDE_WILDCARDS);
+         add_exclude (excluded_directory_patterns, optarg,
+                      EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS);
          break;
  
        case GROUP_SEPARATOR_OPTION:
          break;
  
        case LINE_BUFFERED_OPTION:
-         line_buffered = 1;
+         line_buffered = true;
          break;
  
        case LABEL_OPTION:
      list_files = 0;
    if (exit_on_match | list_files)
      {
-       count_matches = 0;
-       done_on_match = 1;
+       count_matches = false;
+       done_on_match = true;
      }
    out_quiet = count_matches | done_on_match;
  
      {
        version_etc (stdout, program_name, PACKAGE_NAME, VERSION, AUTHORS,
                     (char *) NULL);
-       exit (EXIT_SUCCESS);
+       return EXIT_SUCCESS;
      }
  
    if (show_help)
          {
            /* No keys were specified (e.g. -f /dev/null).  Match nothing.  */
            out_invert ^= true;
-           match_lines = match_words = 0;
+           match_lines = match_words = false;
          }
        else
          /* Strip trailing newline. */
    else
      usage (EXIT_TROUBLE);
  
-   /* If fgrep in a multibyte locale, then use grep if either
+   build_mbclen_cache ();
+   init_easy_encoding ();
+   /* In a unibyte locale, switch from fgrep to grep if
+      the pattern matches words (where grep is typically faster).
+      In a multibyte locale, switch from fgrep to grep if either
       (1) case is ignored (where grep is typically faster), or
       (2) the pattern has an encoding error (where fgrep might not work).  */
-   if (compile == Fcompile && MB_CUR_MAX > 1
-       && (match_icase || contains_encoding_error (keys, keycc)))
+   if (compile == Fcompile
+       && (MB_CUR_MAX <= 1
+           ? match_words
+           : match_icase || contains_encoding_error (keys, keycc)))
      {
        size_t new_keycc;
        char *new_keys;
        execute = EGexecute;
      }
  
-   if (MB_CUR_MAX > 1)
-     build_mbclen_cache ();
    compile (keys, keycc);
    free (keys);
+   /* We need one byte prior and one after.  */
+   char eolbytes[3] = { 0, eolbyte, 0 };
+   size_t match_size;
+   skip_empty_lines = ((execute (eolbytes + 1, 1, &match_size, NULL) == 0)
+                       == out_invert);
  
    if ((argc - optind > 1 && !no_filenames) || with_filenames)
      out_file = 1;
  #ifdef SET_BINARY
    /* Output is set to binary mode because we shouldn't convert
       NL to CR-LF pairs, especially when grepping binary files.  */
-   if (!isatty (1))
-     SET_BINARY (1);
+   if (!isatty (STDOUT_FILENO))
+     SET_BINARY (STDOUT_FILENO);
  #endif
  
    if (max_count == 0)
-     exit (EXIT_FAILURE);
+     return EXIT_FAILURE;
  
    if (fts_options & FTS_LOGICAL && devices == READ_COMMAND_LINE_DEVICES)
      devices = READ_DEVICES;
  
+   char *const *files;
    if (optind < argc)
      {
-       status = 1;
-       do
-         status &= grep_command_line_arg (argv[optind]);
-       while (++optind < argc);
+       files = argv + optind;
      }
    else if (directories == RECURSE_DIRECTORIES && prepended < last_recursive)
      {
-       /* Grep through ".", omitting leading "./" from diagnostics.  */
-       filename_prefix_len = 2;
-       status = grep_command_line_arg (".");
+       static char *const cwd_only[] = { (char *) ".", NULL };
+       files = cwd_only;
+       omit_dot_slash = true;
      }
    else
-     status = grep_command_line_arg ("-");
+     {
+       static char *const stdin_only[] = { (char *) "-", NULL };
+       files = stdin_only;
+     }
+   bool status = true;
+   do
+     status &= grep_command_line_arg (*files++);
+   while (*files != NULL);
  
    /* We register via atexit() to test stdout.  */
-   exit (errseen ? EXIT_TROUBLE : status);
+   return errseen ? EXIT_TROUBLE : status;
  }
- /* vim:set shiftwidth=2: */