Don't include version.h.
[platform/upstream/coreutils.git] / src / cat.c
index 764778f..390abc4 100644 (file)
--- a/src/cat.c
+++ b/src/cat.c
    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., 675 Mass Ave, Cambridge, MA 02139, USA.  */
-\f
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
 /* Differences from the Unix cat:
    * Always unbuffered, -u is ignored.
-   * 100 times faster with -v -u.
-   * 20 times faster with -v.
+   * Usually much faster than other versions of cat, the difference
+   is especially apparent when using the -v option.
 
    By tege@sics.se, Torbjorn Granlund, advised by rms, Richard Stallman. */
 
 #include <sys/ioctl.h>
 #endif
 #include "system.h"
-#include "version.h"
 #include "error.h"
 
+/* Undefine, to avoid warning about redefinition on some systems.  */
+#undef max
 #define max(h,i) ((h) > (i) ? (h) : (i))
 
 char *stpcpy ();
@@ -41,10 +42,6 @@ char *xmalloc ();
 int full_write ();
 int safe_read ();
 
-static void cat ();
-static void next_line_num ();
-static void simple_cat ();
-
 /* Name under which this program was invoked.  */
 char *program_name;
 
@@ -78,19 +75,19 @@ static int newlines2 = 0;
 static int exit_stat = 0;
 
 static void
-usage (status)
-     int status;
+usage (int status)
 {
   if (status != 0)
-    fprintf (stderr, "Try `%s --help' for more information.\n",
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
             program_name);
   else
     {
-      printf ("\
+      printf (_("\
 Usage: %s [OPTION] [FILE]...\n\
-",
+"),
              program_name);
-      printf ("\
+      printf (_("\
+Concatenate FILE(s), or standard input, to standard output.\n\
 \n\
   -b, --number-nonblank    number nonblank output lines\n\
   -e                       equivalent to -vE\n\
@@ -106,624 +103,622 @@ Usage: %s [OPTION] [FILE]...\n\
       --version            output version information and exit\n\
 \n\
 With no FILE, or when FILE is -, read standard input.\n\
-");
+"));
     }
   exit (status);
 }
 
-\f
-void
-main (argc, argv)
-     int argc;
-     char *argv[];
+/* Compute the next line number.  */
+
+static void
+next_line_num (void)
 {
-  /* Optimal size of i/o operations of output.  */
-  int outsize;
+  char *endp = line_num_end;
+  do
+    {
+      if ((*endp)++ < '9')
+       return;
+      *endp-- = '0';
+    }
+  while (endp >= line_num_start);
+  *--line_num_start = '1';
+  if (line_num_start < line_num_print)
+    line_num_print--;
+}
 
-  /* Optimal size of i/o operations of input.  */
-  int insize;
+/* Plain cat.  Copies the file behind `input_desc' to the file behind
+   `output_desc'.  */
 
-  /* Pointer to the input buffer.  */
-  unsigned char *inbuf;
+static void
+simple_cat (
+     /* Pointer to the buffer, used by reads and writes.  */
+     unsigned char *buf,
 
-  /* Pointer to the output buffer.  */
-  unsigned char *outbuf;
+     /* Number of characters preferably read or written by each read and write
+        call.  */
+     int bufsize)
+{
+  /* Actual number of characters read, and therefore written.  */
+  int n_read;
 
-  int c;
+  /* Loop until the end of the file.  */
 
-  /* Index in argv to processed argument.  */
-  int argind;
+  for (;;)
+    {
+      /* Read a block of input.  */
 
-  /* Device number of the output (file or whatever).  */
-  int out_dev;
+      n_read = safe_read (input_desc, buf, bufsize);
+      if (n_read < 0)
+       {
+         error (0, errno, "%s", infile);
+         exit_stat = 1;
+         return;
+       }
 
-  /* I-node number of the output.  */
-  int out_ino;
+      /* End of this file?  */
 
-  /* Nonzero if the output file should not be the same as any input file. */
-  int check_redirection = 1;
+      if (n_read == 0)
+       break;
 
-  /* Nonzero if we have ever read standard input. */
-  int have_read_stdin = 0;
+      /* Write this block out.  */
 
-  struct stat stat_buf;
+      if (full_write (output_desc, buf, n_read) < 0)
+       error (1, errno, _("write error"));
+    }
+}
 
-  /* Variables that are set according to the specified options.  */
-  int numbers = 0;
-  int numbers_at_empty_lines = 1;
-  int squeeze_empty_lines = 0;
-  int mark_line_ends = 0;
-  int quote = 0;
-  int output_tabs = 1;
+/* Cat the file behind INPUT_DESC to the file behind OUTPUT_DESC.
+   Called if any option more than -u was specified.
 
-/* If non-zero, call cat, otherwise call simple_cat to do the actual work. */
-  int options = 0;
+   A newline character is always put at the end of the buffer, to make
+   an explicit test for buffer end unnecessary.  */
 
-  /* If non-zero, display usage information and exit.  */
-  static int show_help;
+static void
+cat (
+     /* Pointer to the beginning of the input buffer.  */
+     unsigned char *inbuf,
 
-  /* If non-zero, print the version on standard output then exit.  */
-  static int show_version;
+     /* Number of characters read in each read call.  */
+     int insize,
 
-  static struct option const long_options[] =
-  {
-    {"number-nonblank", no_argument, NULL, 'b'},
-    {"number", no_argument, NULL, 'n'},
-    {"squeeze-blank", no_argument, NULL, 's'},
-    {"show-nonprinting", no_argument, NULL, 'v'},
-    {"show-ends", no_argument, NULL, 'E'},
-    {"show-tabs", no_argument, NULL, 'T'},
-    {"show-all", no_argument, NULL, 'A'},
-    {"help", no_argument, &show_help, 1},
-    {"version", no_argument, &show_version, 1},
-    {NULL, 0, NULL, 0}
-  };
+     /* Pointer to the beginning of the output buffer.  */
+     unsigned char *outbuf,
 
-  program_name = argv[0];
+     /* Number of characters written by each write call.  */
+     int outsize,
 
-  /* Parse command line options.  */
+     /* Variables that have values according to the specified options.  */
+     int quote,
+     int output_tabs,
+     int numbers,
+     int numbers_at_empty_lines,
+     int mark_line_ends,
+     int squeeze_empty_lines)
+{
+  /* Last character read from the input buffer.  */
+  unsigned char ch;
 
-  while ((c = getopt_long (argc, argv, "benstuvAET", long_options, (int *) 0))
-        != EOF)
-    {
-      switch (c)
-       {
-       case 0:
-         break;
+  /* Pointer to the next character in the input buffer.  */
+  unsigned char *bpin;
 
-       case 'b':
-         ++options;
-         numbers = 1;
-         numbers_at_empty_lines = 0;
-         break;
+  /* Pointer to the first non-valid byte in the input buffer, i.e. the
+     current end of the buffer.  */
+  unsigned char *eob;
 
-       case 'e':
-         ++options;
-         mark_line_ends = 1;
-         quote = 1;
-         break;
+  /* Pointer to the position where the next character shall be written.  */
+  unsigned char *bpout;
 
-       case 'n':
-         ++options;
-         numbers = 1;
-         break;
+  /* Number of characters read by the last read call.  */
+  int n_read;
 
-       case 's':
-         ++options;
-         squeeze_empty_lines = 1;
-         break;
+  /* Determines how many consecutive newlines there have been in the
+     input.  0 newlines makes NEWLINES -1, 1 newline makes NEWLINES 1,
+     etc.  Initially 0 to indicate that we are at the beginning of a
+     new line.  The "state" of the procedure is determined by
+     NEWLINES.  */
+  int newlines = newlines2;
 
-       case 't':
-         ++options;
-         output_tabs = 0;
-         quote = 1;
-         break;
+#ifdef FIONREAD
+  /* If nonzero, use the FIONREAD ioctl, as an optimization.
+     (On Ultrix, it is not supported on NFS filesystems.)  */
+  int use_fionread = 1;
+#endif
 
-       case 'u':
-         /* We provide the -u feature unconditionally.  */
-         break;
+  /* The inbuf pointers are initialized so that BPIN > EOB, and thereby input
+     is read immediately.  */
 
-       case 'v':
-         ++options;
-         quote = 1;
-         break;
+  eob = inbuf;
+  bpin = eob + 1;
 
-       case 'A':
-         ++options;
-         quote = 1;
-         mark_line_ends = 1;
-         output_tabs = 0;
-         break;
+  bpout = outbuf;
 
-       case 'E':
-         ++options;
-         mark_line_ends = 1;
-         break;
+  for (;;)
+    {
+      do
+       {
+         /* Write if there are at least OUTSIZE bytes in OUTBUF.  */
 
-       case 'T':
-         ++options;
-         output_tabs = 0;
-         break;
+         if (bpout - outbuf >= outsize)
+           {
+             unsigned char *wp = outbuf;
+             do
+               {
+                 if (full_write (output_desc, wp, outsize) < 0)
+                   error (1, errno, _("write error"));
+                 wp += outsize;
+               }
+             while (bpout - wp >= outsize);
 
-       default:
-         usage (2);
-       }
-    }
+             /* Move the remaining bytes to the beginning of the
+                buffer.  */
 
-  if (show_version)
-    {
-      printf ("cat - %s\n", version_string);
-      exit (0);
-    }
+             memmove (outbuf, wp, bpout - wp);
+             bpout = outbuf + (bpout - wp);
+           }
 
-  if (show_help)
-    usage (0);
+         /* Is INBUF empty?  */
 
-  output_desc = 1;
+         if (bpin > eob)
+           {
+#ifdef FIONREAD
+             int n_to_read = 0;
 
-  /* Get device, i-node number, and optimal blocksize of output.  */
+             /* Is there any input to read immediately?
+                If not, we are about to wait,
+                so write all buffered output before waiting.  */
 
-  if (fstat (output_desc, &stat_buf) < 0)
-    error (1, errno, "standard output");
+             if (use_fionread
+                 && ioctl (input_desc, FIONREAD, &n_to_read) < 0)
+               {
+                 /* Ultrix returns EOPNOTSUPP on NFS;
+                    HP-UX returns ENOTTY on pipes.
+                    SunOS returns EINVAL and
+                    More/BSD returns ENODEV on special files
+                    like /dev/null.
+                    Irix-5 returns ENOSYS on pipes.  */
+                 if (errno == EOPNOTSUPP || errno == ENOTTY
+                     || errno == EINVAL || errno == ENODEV
+#ifdef ENOSYS
+                     || errno == ENOSYS
+#endif
+                     )
+                   use_fionread = 0;
+                 else
+                   {
+                     error (0, errno, _("cannot do ioctl on `%s'"), infile);
+                     exit_stat = 1;
+                     newlines2 = newlines;
+                     return;
+                   }
+               }
+             if (n_to_read == 0)
+#endif
+               {
+                 int n_write = bpout - outbuf;
 
-  outsize = ST_BLKSIZE (stat_buf);
-  /* Input file can be output file for non-regular files.
-     fstat on pipes returns S_IFSOCK on some systems, S_IFIFO
-     on others, so the checking should not be done for those types,
-     and to allow things like cat < /dev/tty > /dev/tty, checking
-     is not done for device files either. */
+                 if (full_write (output_desc, outbuf, n_write) < 0)
+                   error (1, errno, _("write error"));
+                 bpout = outbuf;
+               }
 
-  if (S_ISREG (stat_buf.st_mode))
-    {
-      out_dev = stat_buf.st_dev;
-      out_ino = stat_buf.st_ino;
-    }
-  else
-    {
-      check_redirection = 0;
-#ifdef lint  /* Suppress `used before initialized' warning.  */
-      out_dev = 0;
-      out_ino = 0;
-#endif
-    }
-
-  /* Check if any of the input files are the same as the output file.  */
-
-  /* Main loop.  */
+             /* Read more input into INBUF.  */
 
-  infile = "-";
-  argind = optind;
+             n_read = safe_read (input_desc, inbuf, insize);
+             if (n_read < 0)
+               {
+                 error (0, errno, "%s", infile);
+                 exit_stat = 1;
+                 newlines2 = newlines;
+                 return;
+               }
+             if (n_read == 0)
+               {
+                 newlines2 = newlines;
+                 return;
+               }
 
-  do
-    {
-      if (argind < argc)
-       infile = argv[argind];
+             /* Update the pointers and insert a sentinel at the buffer
+                end.  */
 
-      if (infile[0] == '-' && infile[1] == 0)
-       {
-         have_read_stdin = 1;
-         input_desc = 0;
-       }
-      else
-       {
-         input_desc = open (infile, O_RDONLY);
-         if (input_desc < 0)
-           {
-             error (0, errno, "%s", infile);
-             exit_stat = 1;
-             continue;
+             bpin = inbuf;
+             eob = bpin + n_read;
+             *eob = '\n';
            }
-       }
-
-      if (fstat (input_desc, &stat_buf) < 0)
-       {
-         error (0, errno, "%s", infile);
-         exit_stat = 1;
-         goto contin;
-       }
-      insize = ST_BLKSIZE (stat_buf);
-
-      /* Compare the device and i-node numbers of this input file with
-        the corresponding values of the (output file associated with)
-        stdout, and skip this input file if they coincide.  Input
-        files cannot be redirected to themselves.  */
+         else
+           {
+             /* It was a real (not a sentinel) newline.  */
 
-      if (check_redirection
-         && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino)
-       {
-         error (0, 0, "%s: input file is output file", infile);
-         exit_stat = 1;
-         goto contin;
-       }
+             /* Was the last line empty?
+                (i.e. have two or more consecutive newlines been read?)  */
 
-      /* Select which version of `cat' to use. If any options (more than -u,
-        --version, or --help) were specified, use `cat', otherwise use
-        `simple_cat'.  */
+             if (++newlines > 0)
+               {
+                 /* Are multiple adjacent empty lines to be substituted by
+                    single ditto (-s), and this was the second empty line?  */
 
-      if (options == 0)
-       {
-         insize = max (insize, outsize);
-         inbuf = (unsigned char *) xmalloc (insize);
+                 if (squeeze_empty_lines && newlines >= 2)
+                   {
+                     ch = *bpin++;
+                     continue;
+                   }
 
-         simple_cat (inbuf, insize);
-       }
-      else
-       {
-         inbuf = (unsigned char *) xmalloc (insize + 1);
+                 /* Are line numbers to be written at empty lines (-n)?  */
 
-         /* Why are (OUTSIZE  - 1 + INSIZE * 4 + 13) bytes allocated for
-            the output buffer?
+                 if (numbers && numbers_at_empty_lines)
+                   {
+                     next_line_num ();
+                     bpout = (unsigned char *) stpcpy (bpout, line_num_print);
+                   }
+               }
 
-            A test whether output needs to be written is done when the input
-            buffer empties or when a newline appears in the input.  After
-            output is written, at most (OUTSIZE - 1) bytes will remain in the
-            buffer.  Now INSIZE bytes of input is read.  Each input character
-            may grow by a factor of 4 (by the prepending of M-^).  If all
-            characters do, and no newlines appear in this block of input, we
-            will have at most (OUTSIZE - 1 + INSIZE) bytes in the buffer.  If
-            the last character in the preceding block of input was a
-            newline, a line number may be written (according to the given
-            options) as the first thing in the output buffer. (Done after the
-            new input is read, but before processing of the input begins.)  A
-            line number requires seldom more than 13 positions.  */
+             /* Output a currency symbol if requested (-e).  */
 
-         outbuf = (unsigned char *) xmalloc (outsize - 1 + insize * 4 + 13);
+             if (mark_line_ends)
+               *bpout++ = '$';
 
-         cat (inbuf, insize, outbuf, outsize, quote,
-              output_tabs, numbers, numbers_at_empty_lines, mark_line_ends,
-              squeeze_empty_lines);
+             /* Output the newline.  */
 
-         free (outbuf);
+             *bpout++ = '\n';
+           }
+         ch = *bpin++;
        }
+      while (ch == '\n');
 
-      free (inbuf);
+      /* Are we at the beginning of a line, and line numbers are requested?  */
 
-    contin:
-      if (strcmp (infile, "-") && close (input_desc) < 0)
+      if (newlines >= 0 && numbers)
        {
-         error (0, errno, "%s", infile);
-         exit_stat = 1;
+         next_line_num ();
+         bpout = (unsigned char *) stpcpy (bpout, line_num_print);
        }
-    }
-  while (++argind < argc);
 
-  if (have_read_stdin && close (0) < 0)
-    error (1, errno, "-");
-  if (close (1) < 0)
-    error (1, errno, "write error");
+      /* Here CH cannot contain a newline character.  */
 
-  exit (exit_stat);
-}
-\f
-/* Plain cat.  Copies the file behind `input_desc' to the file behind
-   `output_desc'.  */
+      /* The loops below continue until a newline character is found,
+        which means that the buffer is empty or that a proper newline
+        has been found.  */
 
-static void
-simple_cat (buf, bufsize)
-     /* Pointer to the buffer, used by reads and writes.  */
-     unsigned char *buf;
+      /* If quoting, i.e. at least one of -v, -e, or -t specified,
+        scan for chars that need conversion.  */
+      if (quote)
+       for (;;)
+         {
+           if (ch >= 32)
+             {
+               if (ch < 127)
+                 *bpout++ = ch;
+               else if (ch == 127)
+                 *bpout++ = '^',
+                   *bpout++ = '?';
+               else
+                 {
+                   *bpout++ = 'M',
+                     *bpout++ = '-';
+                   if (ch >= 128 + 32)
+                     if (ch < 128 + 127)
+                       *bpout++ = ch - 128;
+                     else
+                       *bpout++ = '^',
+                         *bpout++ = '?';
+                   else
+                     *bpout++ = '^',
+                       *bpout++ = ch - 128 + 64;
+                 }
+             }
+           else if (ch == '\t' && output_tabs)
+             *bpout++ = '\t';
+           else if (ch == '\n')
+             {
+               newlines = -1;
+               break;
+             }
+           else
+             *bpout++ = '^',
+               *bpout++ = ch + 64;
 
-     /* Number of characters preferably read or written by each read and write
-        call.  */
-     int bufsize;
-{
-  /* Actual number of characters read, and therefore written.  */
-  int n_read;
+           ch = *bpin++;
+         }
+      else
+       /* Not quoting, neither of -v, -e, or -t specified.  */
+       for (;;)
+         {
+           if (ch == '\t' && !output_tabs)
+             *bpout++ = '^',
+               *bpout++ = ch + 64;
+           else if (ch != '\n')
+             *bpout++ = ch;
+           else
+             {
+               newlines = -1;
+               break;
+             }
 
-  /* Loop until the end of the file.  */
+           ch = *bpin++;
+         }
+    }
+}
 
-  for (;;)
-    {
-      /* Read a block of input.  */
+void
+main (int argc, char **argv)
+{
+  /* Optimal size of i/o operations of output.  */
+  int outsize;
 
-      n_read = safe_read (input_desc, buf, bufsize);
-      if (n_read < 0)
-       {
-         error (0, errno, "%s", infile);
-         exit_stat = 1;
-         return;
-       }
+  /* Optimal size of i/o operations of input.  */
+  int insize;
 
-      /* End of this file?  */
+  /* Pointer to the input buffer.  */
+  unsigned char *inbuf;
 
-      if (n_read == 0)
-       break;
+  /* Pointer to the output buffer.  */
+  unsigned char *outbuf;
 
-      /* Write this block out.  */
+  int c;
 
-      if (full_write (output_desc, buf, n_read) < 0)
-       error (1, errno, "write error");
-    }
-}
-\f
-/* Cat the file behind INPUT_DESC to the file behind OUTPUT_DESC.
-   Called if any option more than -u was specified.
+  /* Index in argv to processed argument.  */
+  int argind;
 
-   A newline character is always put at the end of the buffer, to make
-   an explicit test for buffer end unnecessary.  */
+  /* Device number of the output (file or whatever).  */
+  int out_dev;
 
-static void
-cat (inbuf, insize, outbuf, outsize, quote,
-     output_tabs, numbers, numbers_at_empty_lines,
-     mark_line_ends, squeeze_empty_lines)
+  /* I-node number of the output.  */
+  int out_ino;
 
-     /* Pointer to the beginning of the input buffer.  */
-     unsigned char *inbuf;
+  /* Nonzero if the output file should not be the same as any input file. */
+  int check_redirection = 1;
 
-     /* Number of characters read in each read call.  */
-     int insize;
+  /* Nonzero if we have ever read standard input. */
+  int have_read_stdin = 0;
 
-     /* Pointer to the beginning of the output buffer.  */
-     unsigned char *outbuf;
+  struct stat stat_buf;
 
-     /* Number of characters written by each write call.  */
-     int outsize;
+  /* Variables that are set according to the specified options.  */
+  int numbers = 0;
+  int numbers_at_empty_lines = 1;
+  int squeeze_empty_lines = 0;
+  int mark_line_ends = 0;
+  int quote = 0;
+  int output_tabs = 1;
 
-     /* Variables that have values according to the specified options.  */
-     int quote;
-     int output_tabs;
-     int numbers;
-     int numbers_at_empty_lines;
-     int mark_line_ends;
-     int squeeze_empty_lines;
-{
-  /* Last character read from the input buffer.  */
-  unsigned char ch;
+/* If nonzero, call cat, otherwise call simple_cat to do the actual work. */
+  int options = 0;
 
-  /* Pointer to the next character in the input buffer.  */
-  unsigned char *bpin;
+  /* If nonzero, display usage information and exit.  */
+  static int show_help;
 
-  /* Pointer to the first non-valid byte in the input buffer, i.e. the
-     current end of the buffer.  */
-  unsigned char *eob;
+  /* If nonzero, print the version on standard output then exit.  */
+  static int show_version;
 
-  /* Pointer to the position where the next character shall be written.  */
-  unsigned char *bpout;
+  static struct option const long_options[] =
+  {
+    {"number-nonblank", no_argument, NULL, 'b'},
+    {"number", no_argument, NULL, 'n'},
+    {"squeeze-blank", no_argument, NULL, 's'},
+    {"show-nonprinting", no_argument, NULL, 'v'},
+    {"show-ends", no_argument, NULL, 'E'},
+    {"show-tabs", no_argument, NULL, 'T'},
+    {"show-all", no_argument, NULL, 'A'},
+    {"help", no_argument, &show_help, 1},
+    {"version", no_argument, &show_version, 1},
+    {NULL, 0, NULL, 0}
+  };
 
-  /* Number of characters read by the last read call.  */
-  int n_read;
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
 
-  /* Determines how many consecutive newlines there have been in the
-     input.  0 newlines makes NEWLINES -1, 1 newline makes NEWLINES 1,
-     etc.  Initially 0 to indicate that we are at the beginning of a
-     new line.  The "state" of the procedure is determined by
-     NEWLINES.  */
-  int newlines = newlines2;
+  /* Parse command line options.  */
 
-#ifdef FIONREAD
-  /* If nonzero, use the FIONREAD ioctl, as an optimization.
-     (On Ultrix, it is not supported on NFS filesystems.)  */
-  int use_fionread = 1;
-#endif
+  while ((c = getopt_long (argc, argv, "benstuvAET", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 0:
+         break;
 
-  /* The inbuf pointers are initialized so that BPIN > EOB, and thereby input
-     is read immediately.  */
+       case 'b':
+         ++options;
+         numbers = 1;
+         numbers_at_empty_lines = 0;
+         break;
 
-  eob = inbuf;
-  bpin = eob + 1;
+       case 'e':
+         ++options;
+         mark_line_ends = 1;
+         quote = 1;
+         break;
 
-  bpout = outbuf;
+       case 'n':
+         ++options;
+         numbers = 1;
+         break;
 
-  for (;;)
-    {
-      do
-       {
-         /* Write if there are at least OUTSIZE bytes in OUTBUF.  */
+       case 's':
+         ++options;
+         squeeze_empty_lines = 1;
+         break;
 
-         if (bpout - outbuf >= outsize)
-           {
-             unsigned char *wp = outbuf;
-             do
-               {
-                 if (full_write (output_desc, wp, outsize) < 0)
-                   error (1, errno, "write error");
-                 wp += outsize;
-               }
-             while (bpout - wp >= outsize);
+       case 't':
+         ++options;
+         output_tabs = 0;
+         quote = 1;
+         break;
 
-             /* Move the remaining bytes to the beginning of the
-                buffer.  */
+       case 'u':
+         /* We provide the -u feature unconditionally.  */
+         break;
 
-             memmove (outbuf, wp, bpout - wp);
-             bpout = outbuf + (bpout - wp);
-           }
+       case 'v':
+         ++options;
+         quote = 1;
+         break;
 
-         /* Is INBUF empty?  */
+       case 'A':
+         ++options;
+         quote = 1;
+         mark_line_ends = 1;
+         output_tabs = 0;
+         break;
 
-         if (bpin > eob)
-           {
-#ifdef FIONREAD
-             int n_to_read = 0;
+       case 'E':
+         ++options;
+         mark_line_ends = 1;
+         break;
 
-             /* Is there any input to read immediately?
-                If not, we are about to wait,
-                so write all buffered output before waiting.  */
+       case 'T':
+         ++options;
+         output_tabs = 0;
+         break;
 
-             if (use_fionread
-                 && ioctl (input_desc, FIONREAD, &n_to_read) < 0)
-               {
-                 /* Ultrix returns EOPNOTSUPP on NFS;
-                    HP-UX returns ENOTTY on pipes.
-                    SunOS returns EINVAL and
-                    More/BSD returns ENODEV on special files
-                    like /dev/null.
-                    Irix-5 returns ENOSYS on pipes.  */
-                 if (errno == EOPNOTSUPP || errno == ENOTTY
-                     || errno == EINVAL || errno == ENODEV
-#ifdef ENOSYS
-                     || errno == ENOSYS
-#endif
-                     )
-                   use_fionread = 0;
-                 else
-                   {
-                     error (0, errno, "cannot do ioctl on `%s'", infile);
-                     exit_stat = 1;
-                     newlines2 = newlines;
-                     return;
-                   }
-               }
-             if (n_to_read == 0)
-#endif
-               {
-                 int n_write = bpout - outbuf;
+       default:
+         usage (2);
+       }
+    }
 
-                 if (full_write (output_desc, outbuf, n_write) < 0)
-                   error (1, errno, "write error");
-                 bpout = outbuf;
-               }
+  if (show_version)
+    {
+      printf ("cat - %s\n", PACKAGE_VERSION);
+      exit (0);
+    }
 
-             /* Read more input into INBUF.  */
+  if (show_help)
+    usage (0);
 
-             n_read = safe_read (input_desc, inbuf, insize);
-             if (n_read < 0)
-               {
-                 error (0, errno, "%s", infile);
-                 exit_stat = 1;
-                 newlines2 = newlines;
-                 return;
-               }
-             if (n_read == 0)
-               {
-                 newlines2 = newlines;
-                 return;
-               }
+  output_desc = 1;
 
-             /* Update the pointers and insert a sentinel at the buffer
-                end.  */
+  /* Get device, i-node number, and optimal blocksize of output.  */
 
-             bpin = inbuf;
-             eob = bpin + n_read;
-             *eob = '\n';
-           }
-         else
-           {
-             /* It was a real (not a sentinel) newline.  */
+  if (fstat (output_desc, &stat_buf) < 0)
+    error (1, errno, _("standard output"));
 
-             /* Was the last line empty?
-                (i.e. have two or more consecutive newlines been read?)  */
+  outsize = ST_BLKSIZE (stat_buf);
+  /* Input file can be output file for non-regular files.
+     fstat on pipes returns S_IFSOCK on some systems, S_IFIFO
+     on others, so the checking should not be done for those types,
+     and to allow things like cat < /dev/tty > /dev/tty, checking
+     is not done for device files either. */
 
-             if (++newlines > 0)
-               {
-                 /* Are multiple adjacent empty lines to be substituted by
-                    single ditto (-s), and this was the second empty line?  */
+  if (S_ISREG (stat_buf.st_mode))
+    {
+      out_dev = stat_buf.st_dev;
+      out_ino = stat_buf.st_ino;
+    }
+  else
+    {
+      check_redirection = 0;
+#ifdef lint  /* Suppress `used before initialized' warning.  */
+      out_dev = 0;
+      out_ino = 0;
+#endif
+    }
 
-                 if (squeeze_empty_lines && newlines >= 2)
-                   {
-                     ch = *bpin++;
-                     continue;
-                   }
+  /* Check if any of the input files are the same as the output file.  */
 
-                 /* Are line numbers to be written at empty lines (-n)?  */
+  /* Main loop.  */
 
-                 if (numbers && numbers_at_empty_lines)
-                   {
-                     next_line_num ();
-                     bpout = (unsigned char *) stpcpy (bpout, line_num_print);
-                   }
-               }
+  infile = "-";
+  argind = optind;
 
-             /* Output a currency symbol if requested (-e).  */
+  do
+    {
+      if (argind < argc)
+       infile = argv[argind];
 
-             if (mark_line_ends)
-               *bpout++ = '$';
+      if (infile[0] == '-' && infile[1] == 0)
+       {
+         have_read_stdin = 1;
+         input_desc = 0;
+       }
+      else
+       {
+         input_desc = open (infile, O_RDONLY);
+         if (input_desc < 0)
+           {
+             error (0, errno, "%s", infile);
+             exit_stat = 1;
+             continue;
+           }
+       }
 
-             /* Output the newline.  */
+      if (fstat (input_desc, &stat_buf) < 0)
+       {
+         error (0, errno, "%s", infile);
+         exit_stat = 1;
+         goto contin;
+       }
+      insize = ST_BLKSIZE (stat_buf);
 
-             *bpout++ = '\n';
-           }
-         ch = *bpin++;
+      /* Compare the device and i-node numbers of this input file with
+        the corresponding values of the (output file associated with)
+        stdout, and skip this input file if they coincide.  Input
+        files cannot be redirected to themselves.  */
+
+      if (check_redirection
+         && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino
+         && (input_desc != fileno (stdin) || output_desc != fileno (stdout)))
+       {
+         error (0, 0, _("%s: input file is output file"), infile);
+         exit_stat = 1;
+         goto contin;
        }
-      while (ch == '\n');
 
-      /* Are we at the beginning of a line, and line numbers are requested?  */
+      /* Select which version of `cat' to use. If any options (more than -u,
+        --version, or --help) were specified, use `cat', otherwise use
+        `simple_cat'.  */
 
-      if (newlines >= 0 && numbers)
+      if (options == 0)
        {
-         next_line_num ();
-         bpout = (unsigned char *) stpcpy (bpout, line_num_print);
+         insize = max (insize, outsize);
+         inbuf = (unsigned char *) xmalloc (insize);
+
+         simple_cat (inbuf, insize);
        }
+      else
+       {
+         inbuf = (unsigned char *) xmalloc (insize + 1);
 
-      /* Here CH cannot contain a newline character.  */
+         /* Why are (OUTSIZE  - 1 + INSIZE * 4 + 13) bytes allocated for
+            the output buffer?
 
-      /* The loops below continue until a newline character is found,
-        which means that the buffer is empty or that a proper newline
-        has been found.  */
+            A test whether output needs to be written is done when the input
+            buffer empties or when a newline appears in the input.  After
+            output is written, at most (OUTSIZE - 1) bytes will remain in the
+            buffer.  Now INSIZE bytes of input is read.  Each input character
+            may grow by a factor of 4 (by the prepending of M-^).  If all
+            characters do, and no newlines appear in this block of input, we
+            will have at most (OUTSIZE - 1 + INSIZE) bytes in the buffer.  If
+            the last character in the preceding block of input was a
+            newline, a line number may be written (according to the given
+            options) as the first thing in the output buffer. (Done after the
+            new input is read, but before processing of the input begins.)  A
+            line number requires seldom more than 13 positions.  */
 
-      /* If quoting, i.e. at least one of -v, -e, or -t specified,
-        scan for chars that need conversion.  */
-      if (quote)
-       for (;;)
-         {
-           if (ch >= 32)
-             {
-               if (ch < 127)
-                 *bpout++ = ch;
-               else if (ch == 127)
-                 *bpout++ = '^',
-                   *bpout++ = '?';
-               else
-                 {
-                   *bpout++ = 'M',
-                     *bpout++ = '-';
-                   if (ch >= 128 + 32)
-                     if (ch < 128 + 127)
-                       *bpout++ = ch - 128;
-                     else
-                       *bpout++ = '^',
-                         *bpout++ = '?';
-                   else
-                     *bpout++ = '^',
-                       *bpout++ = ch - 128 + 64;
-                 }
-             }
-           else if (ch == '\t' && output_tabs)
-             *bpout++ = '\t';
-           else if (ch == '\n')
-             {
-               newlines = -1;
-               break;
-             }
-           else
-             *bpout++ = '^',
-               *bpout++ = ch + 64;
+         outbuf = (unsigned char *) xmalloc (outsize - 1 + insize * 4 + 13);
 
-           ch = *bpin++;
-         }
-      else
-       /* Not quoting, neither of -v, -e, or -t specified.  */
-       for (;;)
-         {
-           if (ch == '\t' && !output_tabs)
-             *bpout++ = '^',
-               *bpout++ = ch + 64;
-           else if (ch != '\n')
-             *bpout++ = ch;
-           else
-             {
-               newlines = -1;
-               break;
-             }
+         cat (inbuf, insize, outbuf, outsize, quote,
+              output_tabs, numbers, numbers_at_empty_lines, mark_line_ends,
+              squeeze_empty_lines);
 
-           ch = *bpin++;
-         }
-    }
-}
+         free (outbuf);
+       }
 
-/* Compute the next line number.  */
+      free (inbuf);
 
-static void
-next_line_num ()
-{
-  char *endp = line_num_end;
-  do
-    {
-      if ((*endp)++ < '9')
-       return;
-      *endp-- = '0';
+    contin:
+      if (strcmp (infile, "-") && close (input_desc) < 0)
+       {
+         error (0, errno, "%s", infile);
+         exit_stat = 1;
+       }
     }
-  while (endp >= line_num_start);
-  *--line_num_start = '1';
-  if (line_num_start < line_num_print)
-    line_num_print--;
+  while (++argind < argc);
+
+  if (have_read_stdin && close (0) < 0)
+    error (1, errno, "-");
+  if (close (1) < 0)
+    error (1, errno, _("write error"));
+
+  exit (exit_stat);
 }