maint: update all copyright year number ranges
[platform/upstream/coreutils.git] / src / tac.c
index 06deca2..20a45bf 100644 (file)
--- a/src/tac.c
+++ b/src/tac.c
@@ -1,10 +1,10 @@
 /* tac - concatenate and print files in reverse
-   Copyright (C) 88,89,90,91,95,96,97, 1998 Free Software Foundation, Inc.
+   Copyright (C) 1988-2013 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   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
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -12,8 +12,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software Foundation,
-   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by Jay Lepreau (lepreau@cs.utah.edu).
    GNU enhancements by David MacKenzie (djm@gnu.ai.mit.edu). */
@@ -27,7 +26,7 @@
 
    Options:
    -b, --before                        The separator is attached to the beginning
-                               of the record that it precedes in the file.
+                                of the record that it precedes in the file.
    -r, --regex                 The separator is a regular expression.
    -s, --separator=separator   Use SEPARATOR as the record separator.
 
@@ -45,7 +44,28 @@ tac -r -s '.\|
 #include <regex.h>
 
 #include "error.h"
+#include "filenamecat.h"
+#include "quote.h"
+#include "quotearg.h"
 #include "safe-read.h"
+#include "stdlib--.h"
+#include "xfreopen.h"
+
+/* The official name of this program (e.g., no 'g' prefix).  */
+#define PROGRAM_NAME "tac"
+
+#define AUTHORS \
+  proper_name ("Jay Lepreau"), \
+  proper_name ("David MacKenzie")
+
+#if defined __MSDOS__ || defined _WIN32
+/* Define this to non-zero on systems for which the regular mechanism
+   (of unlinking an open file and expecting to be able to write, seek
+   back to the beginning, then reread it) doesn't work.  E.g., on Windows
+   and DOS systems.  */
+# define DONT_UNLINK_WHILE_OPEN 1
+#endif
+
 
 #ifndef DEFAULT_TMPDIR
 # define DEFAULT_TMPDIR "/tmp"
@@ -57,83 +77,81 @@ tac -r -s '.\|
 /* The number of bytes per atomic write. */
 #define WRITESIZE 8192
 
-char *mktemp ();
-
-/* The name this program was run with. */
-char *program_name;
-
 /* The string that separates the records of the file. */
-static char *separator;
+static char const *separator;
+
+/* True if we have ever read standard input.  */
+static bool have_read_stdin = false;
 
-/* If nonzero, print `separator' along with the record preceding it
+/* If true, print 'separator' along with the record preceding it
    in the file; otherwise with the record following it. */
-static int separator_ends_record;
+static bool separator_ends_record;
 
-/* 0 if `separator' is to be matched as a regular expression;
-   otherwise, the length of `separator', used as a sentinel to
+/* 0 if 'separator' is to be matched as a regular expression;
+   otherwise, the length of 'separator', used as a sentinel to
    stop the search. */
-static int sentinel_length;
+static size_t sentinel_length;
 
-/* The length of a match with `separator'.  If `sentinel_length' is 0,
-   `match_length' is computed every time a match succeeds;
-   otherwise, it is simply the length of `separator'. */
-static int match_length;
+/* The length of a match with 'separator'.  If 'sentinel_length' is 0,
+   'match_length' is computed every time a match succeeds;
+   otherwise, it is simply the length of 'separator'. */
+static size_t match_length;
 
 /* The input buffer. */
 static char *G_buffer;
 
-/* The number of bytes to read at once into `buffer'. */
+/* The number of bytes to read at once into 'buffer'. */
 static size_t read_size;
 
-/* The size of `buffer'.  This is read_size * 2 + sentinel_length + 2.
-   The extra 2 bytes allow `past_end' to have a value beyond the
-   end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */
-static unsigned G_buffer_size;
+/* The size of 'buffer'.  This is read_size * 2 + sentinel_length + 2.
+   The extra 2 bytes allow 'past_end' to have a value beyond the
+   end of 'G_buffer' and 'match_start' to run off the front of 'G_buffer'. */
+static size_t G_buffer_size;
 
-/* The compiled regular expression representing `separator'. */
+/* The compiled regular expression representing 'separator'. */
 static struct re_pattern_buffer compiled_separator;
-
-/* If nonzero, display usage information and exit.  */
-static int show_help;
-
-/* If nonzero, print the version on standard output then exit.  */
-static int show_version;
+static char compiled_separator_fastmap[UCHAR_MAX + 1];
+static struct re_registers regs;
 
 static struct option const longopts[] =
 {
   {"before", no_argument, NULL, 'b'},
   {"regex", no_argument, NULL, 'r'},
   {"separator", required_argument, NULL, 's'},
-  {"help", no_argument, &show_help, 1},
-  {"version", no_argument, &show_version, 1},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
 };
 
-static void
+void
 usage (int status)
 {
-  if (status != 0)
-    fprintf (stderr, _("Try `%s --help' for more information.\n"),
-            program_name);
+  if (status != EXIT_SUCCESS)
+    emit_try_help ();
   else
     {
       printf (_("\
 Usage: %s [OPTION]... [FILE]...\n\
 "),
-             program_name);
-      printf (_("\
+              program_name);
+      fputs (_("\
 Write each FILE to standard output, last line first.\n\
 With no FILE, or when FILE is -, read standard input.\n\
 \n\
+"), stdout);
+      fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+      fputs (_("\
   -b, --before             attach the separator before instead of after\n\
   -r, --regex              interpret the separator as a regular expression\n\
   -s, --separator=STRING   use STRING as the separator instead of newline\n\
-      --help               display this help and exit\n\
-      --version            output version information and exit\n\
-"));
-      puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      emit_ancillary_info ();
     }
-  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+  exit (status);
 }
 
 /* Print the characters from START to PAST_END - 1.
@@ -143,9 +161,9 @@ static void
 output (const char *start, const char *past_end)
 {
   static char buffer[WRITESIZE];
-  static int bytes_in_buffer = 0;
-  int bytes_to_add = past_end - start;
-  int bytes_available = WRITESIZE - bytes_in_buffer;
+  static size_t bytes_in_buffer = 0;
+  size_t bytes_to_add = past_end - start;
+  size_t bytes_available = WRITESIZE - bytes_in_buffer;
 
   if (start == 0)
     {
@@ -170,40 +188,39 @@ output (const char *start, const char *past_end)
 }
 
 /* Print in reverse the file open on descriptor FD for reading FILE.
-   Return 0 if ok, 1 if an error occurs. */
+   Return true if successful.  */
 
-static int
+static bool
 tac_seekable (int input_fd, const char *file)
 {
-  /* Pointer to the location in `G_buffer' where the search for
+  /* Pointer to the location in 'G_buffer' where the search for
      the next separator will begin. */
   char *match_start;
 
-  /* Pointer to one past the rightmost character in `G_buffer' that
+  /* Pointer to one past the rightmost character in 'G_buffer' that
      has not been printed yet. */
   char *past_end;
 
-  /* Length of the record growing in `G_buffer'. */
+  /* Length of the record growing in 'G_buffer'. */
   size_t saved_record_size;
 
   /* Offset in the file of the next read. */
   off_t file_pos;
 
-  /* Nonzero if `output' has not been called yet for any file.
+  /* True if 'output' has not been called yet for any file.
      Only used when the separator is attached to the preceding record. */
-  int first_time = 1;
+  bool first_time = true;
   char first_char = *separator;        /* Speed optimization, non-regexp. */
-  char *separator1 = separator + 1; /* Speed optimization, non-regexp. */
-  int match_length1 = match_length - 1; /* Speed optimization, non-regexp. */
-  struct re_registers regs;
+  char const *separator1 = separator + 1; /* Speed optimization, non-regexp. */
+  size_t match_length1 = match_length - 1; /* Speed optimization, non-regexp. */
 
   /* Find the size of the input file. */
-  file_pos = lseek (input_fd, (off_t) 0, SEEK_END);
+  file_pos = lseek (input_fd, 0, SEEK_END);
   if (file_pos < 1)
-    return 0;                  /* It's an empty file. */
+    return true;                       /* It's an empty file. */
 
   /* Arrange for the first read to lop off enough to leave the rest of the
-     file a multiple of `read_size'.  Since `read_size' can change, this may
+     file a multiple of 'read_size'.  Since 'read_size' can change, this may
      not always hold during the program run, but since it usually will, leave
      it here for i/o efficiency (page/sector boundaries and all that).
      Note: the efficiency gain has not been verified. */
@@ -211,16 +228,16 @@ tac_seekable (int input_fd, const char *file)
   if (saved_record_size == 0)
     saved_record_size = read_size;
   file_pos -= saved_record_size;
-  /* `file_pos' now points to the start of the last (probably partial) block
+  /* 'file_pos' now points to the start of the last (probably partial) block
      in the input file. */
 
   if (lseek (input_fd, file_pos, SEEK_SET) < 0)
-    error (0, errno, "%s: seek failed", file);
+    error (0, errno, _("%s: seek failed"), quotearg_colon (file));
 
   if (safe_read (input_fd, G_buffer, saved_record_size) != saved_record_size)
     {
-      error (0, errno, "%s", file);
-      return 1;
+      error (0, errno, _("%s: read error"), quotearg_colon (file));
+      return false;
     }
 
   match_start = past_end = G_buffer + saved_record_size;
@@ -228,412 +245,426 @@ tac_seekable (int input_fd, const char *file)
   if (sentinel_length)
     match_start -= match_length1;
 
-  for (;;)
+  while (true)
     {
-      /* Search backward from `match_start' - 1 to `G_buffer' for a match
-        with `separator'; for speed, use strncmp if `separator' contains no
-        metacharacters.
-        If the match succeeds, set `match_start' to point to the start of
-        the match and `match_length' to the length of the match.
-        Otherwise, make `match_start' < `G_buffer'. */
+      /* Search backward from 'match_start' - 1 to 'G_buffer' for a match
+         with 'separator'; for speed, use strncmp if 'separator' contains no
+         metacharacters.
+         If the match succeeds, set 'match_start' to point to the start of
+         the match and 'match_length' to the length of the match.
+         Otherwise, make 'match_start' < 'G_buffer'. */
       if (sentinel_length == 0)
-       {
-         int i = match_start - G_buffer;
-         int ret;
-
-         ret = re_search (&compiled_separator, G_buffer, i, i - 1, -i, &regs);
-         if (ret == -1)
-           match_start = G_buffer - 1;
-         else if (ret == -2)
-           {
-             error (EXIT_FAILURE, 0,
-                    _("error in regular expression search"));
-           }
-         else
-           {
-             match_start = G_buffer + regs.start[0];
-             match_length = regs.end[0] - regs.start[0];
-           }
-       }
+        {
+          size_t i = match_start - G_buffer;
+          regoff_t ri = i;
+          regoff_t range = 1 - ri;
+          regoff_t ret;
+
+          if (1 < range)
+            error (EXIT_FAILURE, 0, _("record too large"));
+
+          if (range == 1
+              || ((ret = re_search (&compiled_separator, G_buffer,
+                                    i, i - 1, range, &regs))
+                  == -1))
+            match_start = G_buffer - 1;
+          else if (ret == -2)
+            {
+              error (EXIT_FAILURE, 0,
+                     _("error in regular expression search"));
+            }
+          else
+            {
+              match_start = G_buffer + regs.start[0];
+              match_length = regs.end[0] - regs.start[0];
+            }
+        }
       else
-       {
-         /* `match_length' is constant for non-regexp boundaries. */
-         while (*--match_start != first_char
-                || (match_length1 && strncmp (match_start + 1, separator1,
-                                              match_length1)))
-           /* Do nothing. */ ;
-       }
-
-      /* Check whether we backed off the front of `G_buffer' without finding
-         a match for `separator'. */
+        {
+          /* 'match_length' is constant for non-regexp boundaries. */
+          while (*--match_start != first_char
+                 || (match_length1 && strncmp (match_start + 1, separator1,
+                                               match_length1)))
+            /* Do nothing. */ ;
+        }
+
+      /* Check whether we backed off the front of 'G_buffer' without finding
+         a match for 'separator'. */
       if (match_start < G_buffer)
-       {
-         if (file_pos == 0)
-           {
-             /* Hit the beginning of the file; print the remaining record. */
-             output (G_buffer, past_end);
-             return 0;
-           }
-
-         saved_record_size = past_end - G_buffer;
-         if (saved_record_size > read_size)
-           {
-             /* `G_buffer_size' is about twice `read_size', so since
-                we want to read in another `read_size' bytes before
-                the data already in `G_buffer', we need to increase
-                `G_buffer_size'. */
-             char *newbuffer;
-             int offset = sentinel_length ? sentinel_length : 1;
-
-             read_size *= 2;
-             G_buffer_size = read_size * 2 + sentinel_length + 2;
-             newbuffer = xrealloc (G_buffer - offset, G_buffer_size);
-             newbuffer += offset;
-             /* Adjust the pointers for the new buffer location.  */
-             match_start += newbuffer - G_buffer;
-             past_end += newbuffer - G_buffer;
-             G_buffer = newbuffer;
-           }
-
-         /* Back up to the start of the next bufferfull of the file.  */
-         if (file_pos >= read_size)
-           file_pos -= read_size;
-         else
-           {
-             read_size = file_pos;
-             file_pos = 0;
-           }
-         lseek (input_fd, file_pos, SEEK_SET);
-
-         /* Shift the pending record data right to make room for the new.
-            The source and destination regions probably overlap.  */
-         memmove (G_buffer + read_size, G_buffer, saved_record_size);
-         past_end = G_buffer + read_size + saved_record_size;
-         /* For non-regexp searches, avoid unneccessary scanning. */
-         if (sentinel_length)
-           match_start = G_buffer + read_size;
-         else
-           match_start = past_end;
-
-         if (safe_read (input_fd, G_buffer, read_size) != read_size)
-           {
-             error (0, errno, "%s", file);
-             return 1;
-           }
-       }
+        {
+          if (file_pos == 0)
+            {
+              /* Hit the beginning of the file; print the remaining record. */
+              output (G_buffer, past_end);
+              return true;
+            }
+
+          saved_record_size = past_end - G_buffer;
+          if (saved_record_size > read_size)
+            {
+              /* 'G_buffer_size' is about twice 'read_size', so since
+                 we want to read in another 'read_size' bytes before
+                 the data already in 'G_buffer', we need to increase
+                 'G_buffer_size'. */
+              char *newbuffer;
+              size_t offset = sentinel_length ? sentinel_length : 1;
+              ptrdiff_t match_start_offset = match_start - G_buffer;
+              ptrdiff_t past_end_offset = past_end - G_buffer;
+              size_t old_G_buffer_size = G_buffer_size;
+
+              read_size *= 2;
+              G_buffer_size = read_size * 2 + sentinel_length + 2;
+              if (G_buffer_size < old_G_buffer_size)
+                xalloc_die ();
+              newbuffer = xrealloc (G_buffer - offset, G_buffer_size);
+              newbuffer += offset;
+              /* Adjust the pointers for the new buffer location.  */
+              match_start = newbuffer + match_start_offset;
+              past_end = newbuffer + past_end_offset;
+              G_buffer = newbuffer;
+            }
+
+          /* Back up to the start of the next bufferfull of the file.  */
+          if (file_pos >= read_size)
+            file_pos -= read_size;
+          else
+            {
+              read_size = file_pos;
+              file_pos = 0;
+            }
+          if (lseek (input_fd, file_pos, SEEK_SET) < 0)
+            error (0, errno, _("%s: seek failed"), quotearg_colon (file));
+
+          /* Shift the pending record data right to make room for the new.
+             The source and destination regions probably overlap.  */
+          memmove (G_buffer + read_size, G_buffer, saved_record_size);
+          past_end = G_buffer + read_size + saved_record_size;
+          /* For non-regexp searches, avoid unnecessary scanning. */
+          if (sentinel_length)
+            match_start = G_buffer + read_size;
+          else
+            match_start = past_end;
+
+          if (safe_read (input_fd, G_buffer, read_size) != read_size)
+            {
+              error (0, errno, _("%s: read error"), quotearg_colon (file));
+              return false;
+            }
+        }
       else
-       {
-         /* Found a match of `separator'. */
-         if (separator_ends_record)
-           {
-             char *match_end = match_start + match_length;
-
-             /* If this match of `separator' isn't at the end of the
-                file, print the record. */
-             if (first_time == 0 || match_end != past_end)
-               output (match_end, past_end);
-             past_end = match_end;
-             first_time = 0;
-           }
-         else
-           {
-             output (match_start, past_end);
-             past_end = match_start;
-           }
-
-         /* For non-regex matching, we can back up.  */
-         if (sentinel_length > 0)
-           match_start -= match_length - 1;
-       }
+        {
+          /* Found a match of 'separator'. */
+          if (separator_ends_record)
+            {
+              char *match_end = match_start + match_length;
+
+              /* If this match of 'separator' isn't at the end of the
+                 file, print the record. */
+              if (!first_time || match_end != past_end)
+                output (match_end, past_end);
+              past_end = match_end;
+              first_time = false;
+            }
+          else
+            {
+              output (match_start, past_end);
+              past_end = match_start;
+            }
+
+          /* For non-regex matching, we can back up.  */
+          if (sentinel_length > 0)
+            match_start -= match_length - 1;
+        }
     }
 }
 
-/* Print FILE in reverse.
-   Return 0 if ok, 1 if an error occurs. */
+#if DONT_UNLINK_WHILE_OPEN
+
+/* FIXME-someday: remove all of this DONT_UNLINK_WHILE_OPEN junk.
+   Using atexit like this is wrong, since it can fail
+   when called e.g. 32 or more times.
+   But this isn't a big deal, since the code is used only on WOE/DOS
+   systems, and few people invoke tac on that many nonseekable files.  */
 
-static int
-tac_file (const char *file)
+static const char *file_to_remove;
+static FILE *fp_to_close;
+
+static void
+unlink_tempfile (void)
 {
-  int errors;
-  FILE *in;
+  fclose (fp_to_close);
+  unlink (file_to_remove);
+}
 
-  in = fopen (file, "r");
-  if (in == NULL)
-    {
-      error (0, errno, "%s", file);
-      return 1;
-    }
-  errors = tac_seekable (fileno (in), file);
-  if (ferror (in) || fclose (in) == EOF)
+static void
+record_or_unlink_tempfile (char const *fn, FILE *fp)
+{
+  if (!file_to_remove)
     {
-      error (0, errno, "%s", file);
-      return 1;
+      file_to_remove = fn;
+      fp_to_close = fp;
+      atexit (unlink_tempfile);
     }
-  return errors;
 }
 
-/* Make a copy of the standard input in `FIXME'. */
+#else
 
 static void
-save_stdin (FILE **g_tmp, char **g_tempfile)
+record_or_unlink_tempfile (char const *fn, FILE *fp ATTRIBUTE_UNUSED)
 {
-  static char *template = NULL;
-  static char *tempdir;
-  static char *tempfile;
-  FILE *tmp;
-  ssize_t bytes_read;
-  int fd;
+  unlink (fn);
+}
+
+#endif
 
-  if (template == NULL)
+/* A wrapper around mkstemp that gives us both an open stream pointer,
+   FP, and the corresponding FILE_NAME.  Always return the same FP/name
+   pair, rewinding/truncating it upon each reuse.  */
+static bool
+temp_stream (FILE **fp, char **file_name)
+{
+  static char *tempfile = NULL;
+  static FILE *tmp_fp;
+  if (tempfile == NULL)
     {
-      tempdir = getenv ("TMPDIR");
+      char const *t = getenv ("TMPDIR");
+      char const *tempdir = t ? t : DEFAULT_TMPDIR;
+      tempfile = mfile_name_concat (tempdir, "tacXXXXXX", NULL);
       if (tempdir == NULL)
-       tempdir = DEFAULT_TMPDIR;
-      template = xmalloc (strlen (tempdir) + 11);
+        {
+          error (0, 0, _("memory exhausted"));
+          return false;
+        }
+
+      /* FIXME: there's a small window between a successful mkstemp call
+         and the unlink that's performed by record_or_unlink_tempfile.
+         If we're interrupted in that interval, this code fails to remove
+         the temporary file.  On systems that define DONT_UNLINK_WHILE_OPEN,
+         the window is much larger -- it extends to the atexit-called
+         unlink_tempfile.
+         FIXME: clean up upon fatal signal.  Don't block them, in case
+         $TMPFILE is a remote file system.  */
+
+      int fd = mkstemp (tempfile);
+      if (fd < 0)
+        {
+          error (0, errno, _("failed to create temporary file in %s"),
+                 quote (tempdir));
+          goto Reset;
+        }
+
+      tmp_fp = fdopen (fd, (O_BINARY ? "w+b" : "w+"));
+      if (! tmp_fp)
+        {
+          error (0, errno, _("failed to open %s for writing"),
+                 quote (tempfile));
+          close (fd);
+          unlink (tempfile);
+        Reset:
+          free (tempfile);
+          tempfile = NULL;
+          return false;
+        }
+
+      record_or_unlink_tempfile (tempfile, tmp_fp);
     }
-  sprintf (template, "%s/tacXXXXXX", tempdir);
-  tempfile = mktemp (template);
-
-  /*  Open temporary file exclusively, to foil a common
-      denial-of-service attack.  */
-  fd = open (tempfile, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600);
-  if (fd == -1)
-    error (EXIT_FAILURE, errno, "%s", tempfile);
-
-  tmp = fdopen (fd, "w+");
-  if (tmp == NULL)
-    error (EXIT_FAILURE, errno, "%s", tempfile);
-
-  unlink (tempfile);
-
-  while (1)
+  else
     {
-      bytes_read = safe_read (STDIN_FILENO, G_buffer, read_size);
-      if (bytes_read == 0)
-       break;
-      if (bytes_read < 0)
-       error (EXIT_FAILURE, errno, _("stdin: read error"));
-
-      /* Don't bother checking for failure inside the loop -- check after.  */
-      fwrite (G_buffer, 1, bytes_read, tmp);
+      if (fseeko (tmp_fp, 0, SEEK_SET) < 0
+          || ftruncate (fileno (tmp_fp), 0) < 0)
+        {
+          error (0, errno, _("failed to rewind stream for %s"),
+                 quote (tempfile));
+          return false;
+        }
     }
 
-  if (ferror (tmp) || fflush (tmp) == EOF)
-    error (EXIT_FAILURE, errno, "%s", tempfile);
-
-  rewind (tmp);
-
-  *g_tmp = tmp;
-  *g_tempfile = tempfile;
+  *fp = tmp_fp;
+  *file_name = tempfile;
+  return true;
 }
 
-/* Print the standard input in reverse, saving it to temporary
-   file first if it is a pipe.
-   Return 0 if ok, 1 if an error occurs. */
+/* Copy from file descriptor INPUT_FD (corresponding to the named FILE) to
+   a temporary file, and set *G_TMP and *G_TEMPFILE to the resulting stream
+   and file name.  Return true if successful.  */
 
-static int
-tac_stdin (void)
+static bool
+copy_to_temp (FILE **g_tmp, char **g_tempfile, int input_fd, char const *file)
 {
-  int errors;
-  struct stat stats;
-
-  /* No tempfile is needed for "tac < file".
-     Use fstat instead of checking for errno == ESPIPE because
-     lseek doesn't work on some special files but doesn't return an
-     error, either. */
-  if (fstat (STDIN_FILENO, &stats))
-    {
-      error (0, errno, _("standard input"));
-      return 1;
-    }
+  FILE *fp;
+  char *file_name;
+  if (!temp_stream (&fp, &file_name))
+    return false;
 
-  if (S_ISREG (stats.st_mode))
+  while (1)
     {
-      errors = tac_seekable (fileno (stdin), _("standard input"));
+      size_t bytes_read = safe_read (input_fd, G_buffer, read_size);
+      if (bytes_read == 0)
+        break;
+      if (bytes_read == SAFE_READ_ERROR)
+        {
+          error (0, errno, _("%s: read error"), quotearg_colon (file));
+          goto Fail;
+        }
+
+      if (fwrite (G_buffer, 1, bytes_read, fp) != bytes_read)
+        {
+          error (0, errno, _("%s: write error"), quotearg_colon (file_name));
+          goto Fail;
+        }
     }
-  else
+
+  if (fflush (fp) != 0)
     {
-      FILE *tmp_stream;
-      char *tmp_file;
-      save_stdin (&tmp_stream, &tmp_file);
-      errors = tac_seekable (fileno (tmp_stream), tmp_file);
+      error (0, errno, _("%s: write error"), quotearg_colon (file_name));
+      goto Fail;
     }
 
-  return errors;
+  *g_tmp = fp;
+  *g_tempfile = file_name;
+  return true;
+
+ Fail:
+  fclose (fp);
+  return false;
 }
 
-/* BUF_END points one byte past the end of the buffer to be searched.  */
+/* Copy INPUT_FD to a temporary, then tac that file.
+   Return true if successful.  */
 
-static void *
-memrchr (const char *buf_start, const char *buf_end, int c)
+static bool
+tac_nonseekable (int input_fd, const char *file)
 {
-  const char *p = buf_end;
-  while (buf_start <= --p)
-    {
-      if (*(const unsigned char *) p == c)
-       return (void *) p;
-    }
-  return NULL;
+  FILE *tmp_stream;
+  char *tmp_file;
+  if (!copy_to_temp (&tmp_stream, &tmp_file, input_fd, file))
+    return false;
+
+  bool ok = tac_seekable (fileno (tmp_stream), tmp_file);
+  return ok;
 }
 
-/* FIXME: describe */
+/* Print FILE in reverse, copying it to a temporary
+   file first if it is not seekable.
+   Return true if successful.  */
 
-static int
-tac_mem (const char *buf, size_t n_bytes, FILE *out)
+static bool
+tac_file (const char *filename)
 {
-  const char *nl;
-  const char *bol;
-
-  if (n_bytes == 0)
-    return 0;
-
-  nl = memrchr (buf, buf + n_bytes, '\n');
-  bol = (nl == NULL ? buf : nl + 1);
+  bool ok;
+  off_t file_size;
+  int fd;
+  bool is_stdin = STREQ (filename, "-");
 
-  /* If the last line of the input file has no terminating newline,
-     treat it as a special case.  */
-  if (bol < buf + n_bytes)
+  if (is_stdin)
     {
-      /* Print out the line from bol to end of input.  */
-      fwrite (bol, 1, (buf + n_bytes) - bol, out);
-
-      /* Add a newline here.  Otherwise, the first and second lines
-        of output would appear to have been joined.  */
-      fputc ('\n', out);
+      have_read_stdin = true;
+      fd = STDIN_FILENO;
+      filename = _("standard input");
+      if (O_BINARY && ! isatty (STDIN_FILENO))
+        xfreopen (NULL, "rb", stdin);
     }
-
-  while ((nl = memrchr (buf, bol - 1, '\n')) != NULL)
+  else
     {
-      /* Output the line (which includes a trailing newline)
-        from NL+1 to BOL-1.  */
-      fwrite (nl + 1, 1, bol - (nl + 1), out);
-
-      bol = nl + 1;
+      fd = open (filename, O_RDONLY | O_BINARY);
+      if (fd < 0)
+        {
+          error (0, errno, _("failed to open %s for reading"),
+                 quote (filename));
+          return false;
+        }
     }
 
-  /* If there's anything left, output the last line: BUF .. BOL-1.
-     When the first byte of the input is a newline, there is nothing
-     left to do here.  */
-  if (buf < bol)
-    fwrite (buf, 1, bol - buf, out);
-
-  /* FIXME: this is work in progress.... */
-  return ferror (out);
-}
-
-/* FIXME: describe */
+  file_size = lseek (fd, 0, SEEK_END);
 
-static int
-tac_stdin_to_mem (void)
-{
-  char *buf = NULL;
-  size_t bufsiz = 8 * BUFSIZ;
-  size_t delta = 8 * BUFSIZ;
-  size_t n_bytes = 0;
+  ok = (file_size < 0 || isatty (fd)
+        ? tac_nonseekable (fd, filename)
+        : tac_seekable (fd, filename));
 
-  while (1)
+  if (!is_stdin && close (fd) != 0)
     {
-      ssize_t bytes_read;
-      if (buf == NULL)
-       buf = (char *) malloc (bufsiz);
-      else
-       buf = (char *) realloc (buf, bufsiz);
-
-      if (buf == NULL)
-       {
-         /* Free the buffer and fall back on the code that relies on a
-            temporary file.  */
-         free (buf);
-         /* FIXME */
-         abort ();
-       }
-      bytes_read = safe_read (STDIN_FILENO, buf + n_bytes, bufsiz - n_bytes);
-      if (bytes_read == 0)
-       break;
-      if (bytes_read < 0)
-       error (EXIT_FAILURE, errno, _("stdin: read error"));
-      n_bytes += bytes_read;
-
-      bufsiz += delta;
+      error (0, errno, _("%s: read error"), quotearg_colon (filename));
+      ok = false;
     }
-
-  tac_mem (buf, n_bytes, stdout);
-
-  return 0;
+  return ok;
 }
 
 int
 main (int argc, char **argv)
 {
   const char *error_message;   /* Return value from re_compile_pattern. */
-  int optc, errors;
-  int have_read_stdin = 0;
+  int optc;
+  bool ok;
+  size_t half_buffer_size;
+
+  /* Initializer for file_list if no file-arguments
+     were specified on the command line.  */
+  static char const *const default_file_list[] = {"-", NULL};
+  char const *const *file;
 
-  program_name = argv[0];
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
-  errors = 0;
+  atexit (close_stdout);
+
   separator = "\n";
   sentinel_length = 1;
-  separator_ends_record = 1;
+  separator_ends_record = true;
 
   while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1)
     {
       switch (optc)
-       {
-       case 0:
-         break;
-       case 'b':
-         separator_ends_record = 0;
-         break;
-       case 'r':
-         sentinel_length = 0;
-         break;
-       case 's':
-         separator = optarg;
-         if (*separator == 0)
-           error (EXIT_FAILURE, 0, _("separator cannot be empty"));
-         break;
-       default:
-         usage (1);
-       }
+        {
+        case 'b':
+          separator_ends_record = false;
+          break;
+        case 'r':
+          sentinel_length = 0;
+          break;
+        case 's':
+          separator = optarg;
+          if (*separator == 0)
+            error (EXIT_FAILURE, 0, _("separator cannot be empty"));
+          break;
+        case_GETOPT_HELP_CHAR;
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+        default:
+          usage (EXIT_FAILURE);
+        }
     }
 
-  if (show_version)
-    {
-      printf ("tac (%s) %s\n", GNU_PACKAGE, VERSION);
-      exit (EXIT_SUCCESS);
-    }
-
-  if (show_help)
-    usage (0);
-
   if (sentinel_length == 0)
     {
-      compiled_separator.allocated = 100;
-      compiled_separator.buffer = (unsigned char *)
-       xmalloc (compiled_separator.allocated);
-      compiled_separator.fastmap = xmalloc (256);
-      compiled_separator.translate = 0;
+      compiled_separator.buffer = NULL;
+      compiled_separator.allocated = 0;
+      compiled_separator.fastmap = compiled_separator_fastmap;
+      compiled_separator.translate = NULL;
       error_message = re_compile_pattern (separator, strlen (separator),
-                                         &compiled_separator);
+                                          &compiled_separator);
       if (error_message)
-       error (EXIT_FAILURE, 0, "%s", error_message);
+        error (EXIT_FAILURE, 0, "%s", error_message);
     }
   else
     match_length = sentinel_length = strlen (separator);
 
   read_size = INITIAL_READSIZE;
-  /* A precaution that will probably never be needed. */
-  while (sentinel_length * 2 >= read_size)
-    read_size *= 2;
-  G_buffer_size = read_size * 2 + sentinel_length + 2;
+  while (sentinel_length >= read_size / 2)
+    {
+      if (SIZE_MAX / 2 < read_size)
+        xalloc_die ();
+      read_size *= 2;
+    }
+  half_buffer_size = read_size + sentinel_length + 1;
+  G_buffer_size = 2 * half_buffer_size;
+  if (! (read_size < half_buffer_size && half_buffer_size < G_buffer_size))
+    xalloc_die ();
   G_buffer = xmalloc (G_buffer_size);
   if (sentinel_length)
     {
-      strcpy (G_buffer, separator);
+      memcpy (G_buffer, separator, sentinel_length + 1);
       G_buffer += sentinel_length;
     }
   else
@@ -641,33 +672,33 @@ main (int argc, char **argv)
       ++G_buffer;
     }
 
-  if (optind == argc)
-    {
-      have_read_stdin = 1;
-      errors = tac_stdin ();
-    }
-  else
-    {
-      for (; optind < argc; ++optind)
-       {
-         if (STREQ (argv[optind], "-"))
-           {
-             have_read_stdin = 1;
-             errors |= tac_stdin ();
-           }
-         else
-           {
-             errors |= tac_file (argv[optind]);
-           }
-       }
-    }
+  file = (optind < argc
+          ? (char const *const *) &argv[optind]
+          : default_file_list);
+
+  if (O_BINARY && ! isatty (STDOUT_FILENO))
+    xfreopen (NULL, "wb", stdout);
+
+  {
+    size_t i;
+    ok = true;
+    for (i = 0; file[i]; ++i)
+      ok &= tac_file (file[i]);
+  }
 
   /* Flush the output buffer. */
   output ((char *) NULL, (char *) NULL);
 
-  if (have_read_stdin && close (0) < 0)
-    error (EXIT_FAILURE, errno, "-");
-  if (ferror (stdout) || fclose (stdout) == EOF)
-    error (EXIT_FAILURE, errno, _("write error"));
-  exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+  if (have_read_stdin && close (STDIN_FILENO) < 0)
+    {
+      error (0, errno, "-");
+      ok = false;
+    }
+
+#ifdef lint
+  size_t offset = sentinel_length ? sentinel_length : 1;
+  free (G_buffer - offset);
+#endif
+
+  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }