Include safe-read.h instead of merely declaring safe_read.
[platform/upstream/coreutils.git] / src / tac.c
index f8ad7e2..f281c45 100644 (file)
--- a/src/tac.c
+++ b/src/tac.c
@@ -1,5 +1,5 @@
 /* tac - concatenate and print files in reverse
-   Copyright (C) 1988, 1989, 1990, 1991, 1995 Free Software Foundation, Inc.
+   Copyright (C) 88,89,90,91,95,96,97, 1998 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
@@ -12,8 +12,8 @@
    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, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 /* Written by Jay Lepreau (lepreau@cs.utah.edu).
    GNU enhancements by David MacKenzie (djm@gnu.ai.mit.edu). */
@@ -40,19 +40,19 @@ tac -r -s '.\|
 #include <stdio.h>
 #include <getopt.h>
 #include <sys/types.h>
-#include <signal.h>
-#include <regex.h>
 #include "system.h"
-#include "version.h"
-#include "error.h"
 
-#ifndef STDC_HEADERS
-char *malloc ();
-char *realloc ();
+#if WITH_REGEX
+# include <regex.h>
+#else
+# include <rx.h>
 #endif
 
+#include "error.h"
+#include "safe-read.h"
+
 #ifndef DEFAULT_TMPDIR
-#define DEFAULT_TMPDIR "/tmp"
+# define DEFAULT_TMPDIR "/tmp"
 #endif
 
 /* The number of bytes per atomic read. */
@@ -63,13 +63,6 @@ char *realloc ();
 
 char *mktemp ();
 
-static char *xmalloc ();
-static char *xrealloc ();
-static void xwrite ();
-
-int full_write ();
-int safe_read ();
-
 /* The name this program was run with. */
 char *program_name;
 
@@ -91,26 +84,23 @@ static int sentinel_length;
 static int match_length;
 
 /* The input buffer. */
-static char *buffer;
+static char *G_buffer;
 
 /* The number of bytes to read at once into `buffer'. */
 static unsigned 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 `buffer' and `match_start' to run off the front of `buffer'. */
-static unsigned buffer_size;
+   end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */
+static unsigned G_buffer_size;
 
 /* The compiled regular expression representing `separator'. */
 static struct re_pattern_buffer compiled_separator;
 
-/* The name of a temporary file containing a copy of pipe input. */
-static char *tempfile;
-
-/* If non-zero, display usage information and exit.  */
+/* If nonzero, display usage information and exit.  */
 static int show_help;
 
-/* If non-zero, print the version on standard output then exit.  */
+/* If nonzero, print the version on standard output then exit.  */
 static int show_version;
 
 static struct option const longopts[] =
@@ -124,8 +114,7 @@ static struct option const longopts[] =
 };
 
 static void
-usage (status)
-     int status;
+usage (int status)
 {
   if (status != 0)
     fprintf (stderr, _("Try `%s --help' for more information.\n"),
@@ -146,70 +135,16 @@ With no FILE, or when FILE is -, read standard input.\n\
       --help               display this help and exit\n\
       --version            output version information and exit\n\
 "));
+      puts (_("\nReport bugs to <textutils-bugs@gnu.org>."));
     }
-  exit (status);
-}
-
-static RETSIGTYPE
-cleanup ()
-{
-  unlink (tempfile);
-  exit (1);
-}
-
-/* Allocate N bytes of memory dynamically, with error checking.  */
-
-static char *
-xmalloc (n)
-     unsigned n;
-{
-  char *p;
-
-  p = malloc (n);
-  if (p == 0)
-    {
-      error (0, 0, _("virtual memory exhausted"));
-      cleanup ();
-    }
-  return p;
-}
-
-/* Change the size of memory area P to N bytes, with error checking. */
-
-static char *
-xrealloc (p, n)
-     char *p;
-     unsigned n;
-{
-  p = realloc (p, n);
-  if (p == 0)
-    {
-      error (0, 0, _("virtual memory exhausted"));
-      cleanup ();
-    }
-  return p;
-}
-
-static void
-xwrite (desc, buffer, size)
-     int desc;
-     char *buffer;
-     int size;
-{
-  if (full_write (desc, buffer, size) < 0)
-    {
-      error (0, errno, _("write error"));
-      cleanup ();
-    }
+  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
 /* Print the characters from START to PAST_END - 1.
    If START is NULL, just flush the buffer. */
 
 static void
-output (start, past_end)
-     char *start;
-     char *past_end;
+output (const char *start, const char *past_end)
 {
   static char buffer[WRITESIZE];
   static int bytes_in_buffer = 0;
@@ -218,7 +153,7 @@ output (start, past_end)
 
   if (start == 0)
     {
-      xwrite (STDOUT_FILENO, buffer, bytes_in_buffer);
+      fwrite (buffer, 1, bytes_in_buffer, stdout);
       bytes_in_buffer = 0;
       return;
     }
@@ -229,7 +164,7 @@ output (start, past_end)
       memcpy (buffer + bytes_in_buffer, start, bytes_available);
       bytes_to_add -= bytes_available;
       start += bytes_available;
-      xwrite (STDOUT_FILENO, buffer, WRITESIZE);
+      fwrite (buffer, 1, WRITESIZE, stdout);
       bytes_in_buffer = 0;
       bytes_available = WRITESIZE;
     }
@@ -242,18 +177,22 @@ output (start, past_end)
    Return 0 if ok, 1 if an error occurs. */
 
 static int
-tac (fd, file)
-     int fd;
-     char *file;
+tac_stream (FILE *in, const char *file)
 {
-  /* Pointer to the location in `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 `buffer' that
+
+  /* Pointer to one past the rightmost character in `G_buffer' that
      has not been printed yet. */
   char *past_end;
-  unsigned saved_record_size;  /* Length of the record growing in `buffer'. */
-  off_t file_pos;              /* Offset in the file of the next read. */
+
+  /* Length of the record growing in `G_buffer'. */
+  unsigned 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.
      Only used when the separator is attached to the preceding record. */
   int first_time = 1;
@@ -263,7 +202,7 @@ tac (fd, file)
   struct re_registers regs;
 
   /* Find the size of the input file. */
-  file_pos = lseek (fd, (off_t) 0, SEEK_END);
+  file_pos = lseek (fileno (in), (off_t) 0, SEEK_END);
   if (file_pos < 1)
     return 0;                  /* It's an empty file. */
 
@@ -279,42 +218,42 @@ tac (fd, file)
   /* `file_pos' now points to the start of the last (probably partial) block
      in the input file. */
 
-  lseek (fd, file_pos, SEEK_SET);
-  if (safe_read (fd, buffer, saved_record_size) != saved_record_size)
+  lseek (fileno (in), file_pos, SEEK_SET);
+  if (safe_read (fileno (in), G_buffer, saved_record_size) != saved_record_size)
     {
       error (0, errno, "%s", file);
       return 1;
     }
 
-  match_start = past_end = buffer + saved_record_size;
+  match_start = past_end = G_buffer + saved_record_size;
   /* For non-regexp search, move past impossible positions for a match. */
   if (sentinel_length)
     match_start -= match_length1;
 
   for (;;)
     {
-      /* Search backward from `match_start' - 1 to `buffer' for a match
+      /* 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' < `buffer'. */
+        Otherwise, make `match_start' < `G_buffer'. */
       if (sentinel_length == 0)
        {
-         int i = match_start - buffer;
+         int i = match_start - G_buffer;
          int ret;
 
-         ret = re_search (&compiled_separator, buffer, i, i - 1, -i, &regs);
+         ret = re_search (&compiled_separator, G_buffer, i, i - 1, -i, &regs);
          if (ret == -1)
-           match_start = buffer - 1;
+           match_start = G_buffer - 1;
          else if (ret == -2)
            {
-             error (0, 0, _("error in regular expression search"));
-             cleanup ();
+             error (EXIT_FAILURE, 0,
+                    _("error in regular expression search"));
            }
          else
            {
-             match_start = buffer + regs.start[0];
+             match_start = G_buffer + regs.start[0];
              match_length = regs.end[0] - regs.start[0];
            }
        }
@@ -327,34 +266,35 @@ tac (fd, file)
            /* Do nothing. */ ;
        }
 
-      /* Check whether we backed off the front of `buffer' without finding
+      /* Check whether we backed off the front of `G_buffer' without finding
          a match for `separator'. */
-      if (match_start < buffer)
+      if (match_start < G_buffer)
        {
          if (file_pos == 0)
            {
              /* Hit the beginning of the file; print the remaining record. */
-             output (buffer, past_end);
+             output (G_buffer, past_end);
              return 0;
            }
 
-         saved_record_size = past_end - buffer;
+         saved_record_size = past_end - G_buffer;
          if (saved_record_size > read_size)
            {
-             /* `buffer_size' is about twice `read_size', so since
+             /* `G_buffer_size' is about twice `read_size', so since
                 we want to read in another `read_size' bytes before
-                the data already in `buffer', we need to increase
-                `buffer_size'. */
+                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;
-             buffer_size = read_size * 2 + sentinel_length + 2;
-             newbuffer = xrealloc (buffer - offset, buffer_size) + offset;
+             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 - buffer;
-             past_end += newbuffer - buffer;
-             buffer = newbuffer;
+             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.  */
@@ -365,19 +305,19 @@ tac (fd, file)
              read_size = file_pos;
              file_pos = 0;
            }
-         lseek (fd, file_pos, SEEK_SET);
+         lseek (fileno (in), file_pos, SEEK_SET);
 
          /* Shift the pending record data right to make room for the new.
             The source and destination regions probably overlap.  */
-         memmove (buffer + read_size, buffer, saved_record_size);
-         past_end = buffer + read_size + saved_record_size;
+         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 = buffer + read_size;
+           match_start = G_buffer + read_size;
          else
            match_start = past_end;
 
-         if (safe_read (fd, buffer, read_size) != read_size)
+         if (safe_read (fileno (in), G_buffer, read_size) != read_size)
            {
              error (0, errno, "%s", file);
              return 1;
@@ -411,19 +351,19 @@ tac (fd, file)
    Return 0 if ok, 1 if an error occurs. */
 
 static int
-tac_file (file)
-     char *file;
+tac_file (const char *file)
 {
-  int fd, errors;
+  int errors;
+  FILE *in;
 
-  fd = open (file, O_RDONLY);
-  if (fd == -1)
+  in = fopen (file, "r");
+  if (in == NULL)
     {
       error (0, errno, "%s", file);
       return 1;
     }
-  errors = tac (fd, file);
-  if (close (fd) < 0)
+  errors = tac_stream (in, file);
+  if (ferror (in) || fclose (in) == EOF)
     {
       error (0, errno, "%s", file);
       return 1;
@@ -431,15 +371,17 @@ tac_file (file)
   return errors;
 }
 
-/* Make a copy of the standard input in `tempfile'. */
+/* Make a copy of the standard input in `FIXME'. */
 
 static void
-save_stdin ()
+save_stdin (FILE **g_tmp, char **g_tempfile)
 {
   static char *template = NULL;
   static char *tempdir;
-  int fd;
+  static char *tempfile;
+  FILE *tmp;
   int bytes_read;
+  int fd;
 
   if (template == NULL)
     {
@@ -451,44 +393,39 @@ save_stdin ()
   sprintf (template, "%s/tacXXXXXX", tempdir);
   tempfile = mktemp (template);
 
-  fd = creat (tempfile, 0600);
-  if (fd == -1)
-    {
-      error (0, errno, "%s", tempfile);
-      cleanup ();
-    }
-  while ((bytes_read = safe_read (0, buffer, read_size)) > 0)
-    if (full_write (fd, buffer, bytes_read) < 0)
-      {
-       error (0, errno, "%s", tempfile);
-       cleanup ();
-      }
-  if (close (fd) < 0)
-    {
-      error (0, errno, "%s", tempfile);
-      cleanup ();
-    }
+  fd = open (tempfile, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
+  if (fd == -1 || (tmp = fdopen (fd, "rw")) == NULL)
+    error (EXIT_FAILURE, errno, "%s", tempfile);
+  tmp = fdopen (fd, "rw");
+  if (tmp == NULL)
+    error (EXIT_FAILURE, errno, "%s", tempfile);
+  unlink (tempfile);
+
+  while ((bytes_read = safe_read (0, G_buffer, read_size)) > 0)
+    fwrite (G_buffer, 1, bytes_read, tmp);
+
+  if (ferror (tmp) || fflush (tmp) == EOF)
+    error (EXIT_FAILURE, errno, "%s", tempfile);
+
+  if (fseek (tmp, (long int) 0, SEEK_SET))
+    error (EXIT_FAILURE, errno, "%s", tempfile);
+
   if (bytes_read == -1)
-    {
-      error (0, errno, _("read error"));
-      cleanup ();
-    }
+    error (EXIT_FAILURE, errno, _("read error"));
+
+  *g_tmp = tmp;
+  *g_tempfile = tempfile;
 }
 
 /* Print the standard input in reverse, saving it to temporary
-   file `tempfile' first if it is a pipe.
+   file first if it is a pipe.
    Return 0 if ok, 1 if an error occurs. */
 
 static int
-tac_stdin ()
+tac_stdin (void)
 {
-  /* Previous values of signal handlers. */
-  RETSIGTYPE (*sigint) (), (*sighup) (), (*sigpipe) (), (*sigterm) ();
   int errors;
   struct stat stats;
-#ifdef SA_INTERRUPT
-    struct sigaction oldact, newact;
-#endif                         /* SA_INTERRUPT */
 
   /* No tempfile is needed for "tac < file".
      Use fstat instead of checking for errno == ESPIPE because
@@ -499,93 +436,141 @@ tac_stdin ()
       error (0, errno, _("standard input"));
       return 1;
     }
+
   if (S_ISREG (stats.st_mode))
-    return tac (0, _("standard input"));
-
-#ifdef SA_INTERRUPT
-  newact.sa_handler = cleanup;
-  sigemptyset (&newact.sa_mask);
-  newact.sa_flags = 0;
-
-  sigaction (SIGINT, NULL, &oldact);
-  sigint = oldact.sa_handler;
-  if (sigint != SIG_IGN)
-    sigaction (SIGINT, &newact, NULL);
-
-  sigaction (SIGHUP, NULL, &oldact);
-  sighup = oldact.sa_handler;
-  if (sighup != SIG_IGN)
-    sigaction (SIGHUP, &newact, NULL);
-
-  sigaction (SIGPIPE, NULL, &oldact);
-  sigpipe = oldact.sa_handler;
-  if (sigpipe != SIG_IGN)
-    sigaction (SIGPIPE, &newact, NULL);
-
-  sigaction (SIGTERM, NULL, &oldact);
-  sigterm = oldact.sa_handler;
-  if (sigterm != SIG_IGN)
-    sigaction (SIGTERM, &newact, NULL);
-#else                          /* !SA_INTERRUPT */
-  sigint = signal (SIGINT, SIG_IGN);
-  if (sigint != SIG_IGN)
-    signal (SIGINT, cleanup);
-
-  sighup = signal (SIGHUP, SIG_IGN);
-  if (sighup != SIG_IGN)
-    signal (SIGHUP, cleanup);
-
-  sigpipe = signal (SIGPIPE, SIG_IGN);
-  if (sigpipe != SIG_IGN)
-    signal (SIGPIPE, cleanup);
-
-  sigterm = signal (SIGTERM, SIG_IGN);
-  if (sigterm != SIG_IGN)
-    signal (SIGTERM, cleanup);
-#endif                         /* SA_INTERRUPT */
-
-  save_stdin ();
-
-  errors = tac_file (tempfile);
+    {
+      errors = tac_stream (stdin, _("standard input"));
+    }
+  else
+    {
+      FILE *tmp_stream;
+      char *tmp_file;
+      save_stdin (&tmp_stream, &tmp_file);
+      errors = tac_stream (tmp_stream, tmp_file);
+    }
 
-  unlink (tempfile);
+  return errors;
+}
 
-#ifdef SA_INTERRUPT
-  newact.sa_handler = sigint;
-  sigaction (SIGINT, &newact, NULL);
-  newact.sa_handler = sighup;
-  sigaction (SIGHUP, &newact, NULL);
-  newact.sa_handler = sigterm;
-  sigaction (SIGTERM, &newact, NULL);
-  newact.sa_handler = sigpipe;
-  sigaction (SIGPIPE, &newact, NULL);
-#else                          /* !SA_INTERRUPT */
-  signal (SIGINT, sigint);
-  signal (SIGHUP, sighup);
-  signal (SIGTERM, sigterm);
-  signal (SIGPIPE, sigpipe);
-#endif                         /* SA_INTERRUPT */
+/* BUF_END_PLUS_ONE points one byte past the end of the buffer
+   to be searched.  */
 
-  return errors;
+static void *
+memrchr (const char *buf_start, const char *buf_end_plus_one, int c)
+{
+  const char *p = buf_end_plus_one;
+  while (buf_start <= --p)
+    {
+      if (*(const unsigned char *) p == c)
+       return (void *) p;
+    }
+  return NULL;
 }
 
-void
-main (argc, argv)
-     int argc;
-     char **argv;
+/* FIXME: describe */
+
+static int
+tac_mem (const char *buf, size_t n_bytes, FILE *out)
+{
+  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);
+
+  /* If the last line of the input file has no terminating newline,
+     treat it as a special case.  */
+  if (bol < buf + n_bytes)
+    {
+      /* 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);
+    }
+
+  while ((nl = memrchr (buf, bol - 1, '\n')) != NULL)
+    {
+      /* 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;
+    }
+
+  /* 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 */
+
+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;
+
+  while (1)
+    {
+      int 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;
+      n_bytes += bytes_read;
+      if (bytes_read < 0)
+       error (1, errno, _("read error"));
+
+      bufsiz += delta;
+    }
+
+  tac_mem (buf, n_bytes, stdout);
+
+  return 0;
+}
+
+int
+main (int argc, char **argv)
 {
   const char *error_message;   /* Return value from re_compile_pattern. */
   int optc, errors;
   int have_read_stdin = 0;
 
   program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
   errors = 0;
   separator = "\n";
   sentinel_length = 1;
   separator_ends_record = 1;
 
-  while ((optc = getopt_long (argc, argv, "brs:", longopts, (int *) 0))
-        != EOF)
+  while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1)
     {
       switch (optc)
        {
@@ -600,7 +585,7 @@ main (argc, argv)
        case 's':
          separator = optarg;
          if (*separator == 0)
-           error (1, 0, _("separator cannot be empty"));
+           error (EXIT_FAILURE, 0, _("separator cannot be empty"));
          break;
        default:
          usage (1);
@@ -609,8 +594,8 @@ main (argc, argv)
 
   if (show_version)
     {
-      printf ("tac - %s\n", version_string);
-      exit (0);
+      printf ("tac (%s) %s\n", GNU_PACKAGE, VERSION);
+      exit (EXIT_SUCCESS);
     }
 
   if (show_help)
@@ -626,7 +611,7 @@ main (argc, argv)
       error_message = re_compile_pattern (separator, strlen (separator),
                                          &compiled_separator);
       if (error_message)
-       error (1, 0, "%s", error_message);
+       error (EXIT_FAILURE, 0, "%s", error_message);
     }
   else
     match_length = sentinel_length = strlen (separator);
@@ -635,20 +620,20 @@ main (argc, argv)
   /* A precaution that will probably never be needed. */
   while (sentinel_length * 2 >= read_size)
     read_size *= 2;
-  buffer_size = read_size * 2 + sentinel_length + 2;
-  buffer = xmalloc (buffer_size);
+  G_buffer_size = read_size * 2 + sentinel_length + 2;
+  G_buffer = xmalloc (G_buffer_size);
   if (sentinel_length)
     {
-      strcpy (buffer, separator);
-      buffer += sentinel_length;
+      strcpy (G_buffer, separator);
+      G_buffer += sentinel_length;
     }
   else
-    ++buffer;
+    ++G_buffer;
 
   if (optind == argc)
     {
       have_read_stdin = 1;
-      errors = tac_stdin ();
+      errors = tac_stdin_to_mem ();
     }
   else
     for (; optind < argc; ++optind)
@@ -656,7 +641,7 @@ main (argc, argv)
        if (strcmp (argv[optind], "-") == 0)
          {
            have_read_stdin = 1;
-           errors |= tac_stdin ();
+           errors |= tac_stdin_to_mem ();
          }
        else
          errors |= tac_file (argv[optind]);
@@ -666,8 +651,8 @@ main (argc, argv)
   output ((char *) NULL, (char *) NULL);
 
   if (have_read_stdin && close (0) < 0)
-    error (1, errno, "-");
-  if (close (1) < 0)
-    error (1, errno, _("write error"));
-  exit (errors);
+    error (EXIT_FAILURE, errno, "-");
+  if (ferror (stdout) || fclose (stdout) == EOF)
+    error (EXIT_FAILURE, errno, _("write error"));
+  exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }