When --read-bytes=N (-N N) is used, disable input buffering in
authorJim Meyering <jim@meyering.net>
Wed, 19 Sep 2001 10:19:19 +0000 (10:19 +0000)
committerJim Meyering <jim@meyering.net>
Wed, 19 Sep 2001 10:19:19 +0000 (10:19 +0000)
the standard I/O library.  Otherwise, od would read more input
than requested.  This could have caused problems when reading
from pipes, character devices, and open file descriptors inherited
from a parent process.

* src/od.c (open_next_file): New function, factored out of...
(skip): Adapt to use open_next_file.
(read_char): Likewise.
(read_block): Likewise.
(main): Likewise.
(dump): Fix an off-by-one error that could have made od fail to
report a read error when reading from a named file (not stdin).
(check_and_close): Account for the fact that in_stream may now be NULL.
(usage): Correct descriptions of -j and -N options.
Patch by Ian Bruce.

src/od.c

index 3f16e31..c0c4a05 100644 (file)
--- a/src/od.c
+++ b/src/od.c
@@ -214,11 +214,13 @@ static size_t bytes_per_block;
    It differs from *file_list only when *file_list is "-".  */
 static char const *input_filename;
 
-/* A NULL-terminated list of the file-arguments from the command line.
-   If no file-arguments were specified, this variable is initialized
-   to { "-", NULL }.  */
+/* A NULL-terminated list of the file-arguments from the command line.  */
 static char const *const *file_list;
 
+/* Initializer for file_list if no file-arguments
+   were specified on the command line.  */
+static char const *const default_file_list[] = {"-", NULL};
+
 /* The input stream associated with the current file.  */
 static FILE *in_stream;
 
@@ -271,13 +273,15 @@ Usage: %s [OPTION]... [FILE]...\n\
   or:  %s --traditional [FILE] [[+]OFFSET [[+]LABEL]]\n\
 "),
              program_name, program_name);
-      printf (_("\
-Write an unambiguous representation, octal bytes by default, of FILE\n\
-to standard output.  With no FILE, or when FILE is -, read standard input.\n\
+      printf (_("\n\
+Write an unambiguous representation, octal bytes by default,\n\
+of FILE to standard output.  With more than one FILE argument,\n\
+concatenate them in the listed order to form the input.\n\
+With no FILE, or when FILE is -, read standard input.\n\
 \n\
   -A, --address-radix=RADIX   decide how file offsets are printed\n\
-  -j, --skip-bytes=BYTES      skip BYTES input bytes first on each file\n\
-  -N, --read-bytes=BYTES      limit dump to BYTES input bytes per file\n\
+  -j, --skip-bytes=BYTES      skip BYTES input bytes first\n\
+  -N, --read-bytes=BYTES      limit dump to BYTES input bytes\n\
   -s, --strings[=BYTES]       output strings of at least BYTES graphic chars\n\
   -t, --format=TYPE           select output format or formats\n\
   -v, --output-duplicates     do not use * to mark line suppression\n\
@@ -892,6 +896,88 @@ this system doesn't provide a %lu-byte floating point type"), s_orig, size);
   return 0;
 }
 
+/* Given a list of one or more input filenames FILE_LIST, set the global
+   file pointer IN_STREAM and the global string INPUT_FILENAME to the
+   first one that can be successfully opened. Modify FILE_LIST to
+   reference the next filename in the list.  A file name of "-" is
+   interpreted as standard input.  If any file open fails, give an error
+   message and return nonzero.  */
+
+static int
+open_next_file (void)
+{
+  int err = 0;
+
+  do
+    {
+      input_filename = *file_list;
+      if (input_filename == NULL)
+       return err;
+      ++file_list;
+
+      if (STREQ (input_filename, "-"))
+       {
+         input_filename = _("standard input");
+         in_stream = stdin;
+         have_read_stdin = 1;
+       }
+      else
+       {
+         in_stream = fopen (input_filename, "r");
+         if (in_stream == NULL)
+           {
+             error (0, errno, "%s", input_filename);
+             err = 1;
+           }
+       }
+    }
+  while (in_stream == NULL);
+
+  if (limit_bytes_to_format && !flag_dump_strings)
+    setbuf (in_stream, NULL);
+  SET_BINARY (fileno (in_stream));
+
+  return err;
+}
+
+/* Test whether there have been errors on in_stream, and close it if
+   it is not standard input.  Return nonzero if there has been an error
+   on in_stream or stdout; return zero otherwise.  This function will
+   report more than one error only if both a read and a write error
+   have occurred.  */
+
+static int
+check_and_close (void)
+{
+  int err = 0;
+
+  if (in_stream != NULL)
+    {
+      if (ferror (in_stream))
+       {
+         error (0, errno, "%s", input_filename);
+         if (in_stream != stdin)
+           fclose (in_stream);
+         err = 1;
+       }
+      else if (in_stream != stdin && fclose (in_stream) == EOF)
+       {
+         error (0, errno, "%s", input_filename);
+         err = 1;
+       }
+
+      in_stream = NULL;
+    }
+
+  if (ferror (stdout))
+    {
+      error (0, errno, _("standard output"));
+      err = 1;
+    }
+
+  return err;
+}
+
 /* Decode the POSIX-style od format string S.  Append the decoded
    representation to the global array SPEC, reallocating SPEC if
    necessary.  Return zero if S is valid, nonzero otherwise.  */
@@ -933,42 +1019,20 @@ decode_format_string (const char *s)
    file pointer IN_STREAM to position N_SKIP in the concatenation of
    those files.  If any file operation fails or if there are fewer than
    N_SKIP bytes in the combined input, give an error message and return
-   nonzero.  When possible, use seek- rather than read operations to
-   advance IN_STREAM.  A file name of "-" is interpreted as standard
-   input.  */
+   nonzero.  When possible, use seek rather than read operations to
+   advance IN_STREAM.  */
 
 static int
 skip (off_t n_skip)
 {
-  int err;
+  int err = 0;
 
-  err = 0;
-  for ( /* empty */ ; *file_list != NULL; ++file_list)
+  if (n_skip == 0)
+    return 0;
+
+  while (in_stream != NULL)    /* EOF.  */
     {
       struct stat file_stats;
-      int j;
-
-      if (STREQ (*file_list, "-"))
-       {
-         input_filename = _("standard input");
-         in_stream = stdin;
-         have_read_stdin = 1;
-       }
-      else
-       {
-         input_filename = *file_list;
-         in_stream = fopen (input_filename, "r");
-         if (in_stream == NULL)
-           {
-             error (0, errno, "%s", input_filename);
-             err = 1;
-             continue;
-           }
-       }
-      SET_BINARY (fileno (in_stream));
-
-      if (n_skip == 0)
-       break;
 
       /* First try seeking.  For large offsets, this extra work is
         worthwhile.  If the offset is below some threshold it may be
@@ -980,58 +1044,60 @@ skip (off_t n_skip)
             Try to do that by getting file's size using fstat.
             But that will work only for regular files.  */
 
-      if (fstat (fileno (in_stream), &file_stats))
+      if (fstat (fileno (in_stream), &file_stats) == 0)
        {
-         error (0, errno, "%s", input_filename);
-         err = 1;
-         continue;
-       }
+         /* The st_size field is valid only for regular files
+            (and for symbolic links, which cannot occur here).
+            If the number of bytes left to skip is at least
+            as large as the size of the current file, we can
+            decrement n_skip and go on to the next file.  */
 
-      /* The st_size field is valid only for regular files
-        (and for symbolic links, which cannot occur here).
-        If the number of bytes left to skip is at least as large as
-        the size of the current file, we can decrement
-        n_skip and go on to the next file.  */
-      if (S_ISREG (file_stats.st_mode))
-       {
-         if (file_stats.st_size <= n_skip)
+         if (S_ISREG (file_stats.st_mode) && file_stats.st_size <= n_skip)
            {
              n_skip -= file_stats.st_size;
-             if (in_stream != stdin && fclose (in_stream) == EOF)
-               {
-                 error (0, errno, "%s", input_filename);
-                 err = 1;
-               }
-             continue;
            }
+
+         /* If the number of bytes left to skip is less than the size
+            of the current file, try seeking to the correct offset.  */
+
+         else if (S_ISREG (file_stats.st_mode) &&
+                  fseek (in_stream, n_skip, SEEK_CUR) == 0)
+           {
+             n_skip = 0;
+           }
+
+         /* If seek didn't work or wasn't attempted,
+            position the file pointer by reading.  */
+
          else
            {
-             if (0 <= lseek (fileno (in_stream), n_skip, SEEK_CUR))
+             char buf[BUFSIZ];
+             size_t n_bytes_read, n_bytes_to_read = BUFSIZ;
+
+             while (0 < n_skip)
                {
-                 n_skip = 0;
-                 break;
+                 if (n_skip < n_bytes_to_read)
+                   n_bytes_to_read = n_skip;
+                 n_bytes_read = fread (buf, 1, n_bytes_to_read, in_stream);
+                 n_skip -= n_bytes_read;
+                 if (n_bytes_read != n_bytes_to_read)
+                   break;
                }
            }
-       }
 
-      /* Seek didn't work or wasn't attempted;  position the file pointer
-        by reading.  */
+         if (n_skip == 0)
+           break;
+       }
 
-      for (j = n_skip / BUFSIZ; 0 <= j; j--)
+      else   /* cannot fstat() file */
        {
-         char buf[BUFSIZ];
-         size_t n_bytes_to_read = (0 < j
-                                   ? BUFSIZ
-                                   : n_skip % BUFSIZ);
-         size_t n_bytes_read;
-         n_bytes_read = fread (buf, 1, n_bytes_to_read, in_stream);
-         n_skip -= n_bytes_read;
-         if (n_bytes_read != n_bytes_to_read)
-           break;
+         error (0, errno, "%s", input_filename);
+         err = 1;
        }
 
-      if (n_skip == 0)
-       break;
+      err |= check_and_close ();
+
+      err |= open_next_file ();
     }
 
   if (n_skip != 0)
@@ -1162,119 +1228,57 @@ write_block (off_t current_offset, off_t n_bytes,
   first = 0;
 }
 
-/* Test whether there have been errors on in_stream, and close it if
-   it is not standard input.  Return nonzero if there has been an error
-   on in_stream or stdout; return zero otherwise.  This function will
-   report more than one error only if both a read and a write error
-   have occurred.  */
-
-static int
-check_and_close (void)
-{
-  int err;
-
-  err = 0;
-  if (ferror (in_stream))
-    {
-      error (0, errno, "%s", input_filename);
-      if (in_stream != stdin)
-       fclose (in_stream);
-      err = 1;
-    }
-  else if (in_stream != stdin && fclose (in_stream) == EOF)
-    {
-      error (0, errno, "%s", input_filename);
-      err = 1;
-    }
-
-  if (ferror (stdout))
-    {
-      error (0, errno, _("standard output"));
-      err = 1;
-    }
-
-  return err;
-}
-
 /* Read a single byte into *C from the concatenation of the input files
    named in the global array FILE_LIST.  On the first call to this
    function, the global variable IN_STREAM is expected to be an open
-   stream associated with the input file *FILE_LIST.  If IN_STREAM is
-   at end-of-file, close it and update the global variables IN_STREAM,
-   FILE_LIST, and INPUT_FILENAME so they correspond to the next file in
-   the list.  Then try to read a byte from the newly opened file.
-   Repeat if necessary until *FILE_LIST is NULL.  When EOF is reached
-   for the last file in FILE_LIST, set *C to EOF and return.  Subsequent
-   calls do likewise.  The return value is nonzero if any errors
-   occured, zero otherwise.  */
+   stream associated with the input file INPUT_FILENAME.  If IN_STREAM
+   is at end-of-file, close it and update the global variables IN_STREAM
+   and INPUT_FILENAME so they correspond to the next file in the list.
+   Then try to read a byte from the newly opened file.  Repeat if
+   necessary until EOF is reached for the last file in FILE_LIST, then
+   set *C to EOF and return.  Subsequent calls do likewise.  The return
+   value is nonzero if any errors occured, zero otherwise.  */
 
 static int
 read_char (int *c)
 {
-  int err;
+  int err = 0;
 
-  if (*file_list == NULL)
-    {
-      *c = EOF;
-      return 0;
-    }
+  *c = EOF;
 
-  err = 0;
-  while (1)
+  while (in_stream != NULL)    /* EOF.  */
     {
       *c = fgetc (in_stream);
 
       if (*c != EOF)
-       return err;
+       break;
 
       err |= check_and_close ();
 
-      do
-       {
-         ++file_list;
-         if (*file_list == NULL)
-           return err;
-
-         if (STREQ (*file_list, "-"))
-           {
-             input_filename = _("standard input");
-             in_stream = stdin;
-             have_read_stdin = 1;
-           }
-         else
-           {
-             input_filename = *file_list;
-             in_stream = fopen (input_filename, "r");
-             if (in_stream == NULL)
-               {
-                 error (0, errno, "%s", input_filename);
-                 err = 1;
-               }
-           }
-         SET_BINARY (fileno (in_stream));
-       }
-      while (in_stream == NULL);
+      err |= open_next_file ();
     }
+
+  return err;
 }
 
 /* Read N bytes into BLOCK from the concatenation of the input files
    named in the global array FILE_LIST.  On the first call to this
    function, the global variable IN_STREAM is expected to be an open
-   stream associated with the input file *FILE_LIST.  On subsequent
-   calls, if *FILE_LIST is NULL, don't modify BLOCK and return zero.
-   If all N bytes cannot be read from IN_STREAM, close IN_STREAM and
-   update the global variables IN_STREAM, FILE_LIST, and INPUT_FILENAME.
-   Then try to read the remaining bytes from the newly opened file.
-   Repeat if necessary until *FILE_LIST is NULL.  Set *N_BYTES_IN_BUFFER
-   to the number of bytes read.  If an error occurs, it will be detected
-   through ferror when the stream is about to be closed.  If there is an
-   error, give a message but continue reading as usual and return nonzero.
-   Otherwise return zero.  */
+   stream associated with the input file INPUT_FILENAME.  If all N
+   bytes cannot be read from IN_STREAM, close IN_STREAM and update
+   the global variables IN_STREAM and INPUT_FILENAME.  Then try to
+   read the remaining bytes from the newly opened file.  Repeat if
+   necessary until EOF is reached for the last file in FILE_LIST.
+   On subsequent calls, don't modify BLOCK and return zero.  Set
+   *N_BYTES_IN_BUFFER to the number of bytes read.  If an error occurs,
+   it will be detected through ferror when the stream is about to be
+   closed.  If there is an error, give a message but continue reading
+   as usual and return nonzero.  Otherwise return zero.  */
 
 static int
 read_block (size_t n, char *block, size_t *n_bytes_in_buffer)
 {
-  int err;
+  int err = 0;
 
   assert (0 < n && n <= bytes_per_block);
 
@@ -1283,11 +1287,7 @@ read_block (size_t n, char *block, size_t *n_bytes_in_buffer)
   if (n == 0)
     return 0;
 
-  if (*file_list == NULL)
-    return 0;                  /* EOF.  */
-
-  err = 0;
-  while (1)
+  while (in_stream != NULL)    /* EOF.  */
     {
       size_t n_needed;
       size_t n_read;
@@ -1298,36 +1298,14 @@ read_block (size_t n, char *block, size_t *n_bytes_in_buffer)
       *n_bytes_in_buffer += n_read;
 
       if (n_read == n_needed)
-       return err;
+       break;
 
       err |= check_and_close ();
 
-      do
-       {
-         ++file_list;
-         if (*file_list == NULL)
-           return err;
-
-         if (STREQ (*file_list, "-"))
-           {
-             input_filename = _("standard input");
-             in_stream = stdin;
-             have_read_stdin = 1;
-           }
-         else
-           {
-             input_filename = *file_list;
-             in_stream = fopen (input_filename, "r");
-             if (in_stream == NULL)
-               {
-                 error (0, errno, "%s", input_filename);
-                 err = 1;
-               }
-           }
-         SET_BINARY (fileno (in_stream));
-       }
-      while (in_stream == NULL);
+      err |= open_next_file ();
     }
+
+  return err;
 }
 
 /* Return the least common multiple of the sizes associated
@@ -1475,7 +1453,7 @@ dump (void)
 
   format_address (current_offset, '\n');
 
-  if (limit_bytes_to_format && current_offset > end_offset)
+  if (limit_bytes_to_format && current_offset >= end_offset)
     err |= check_and_close ();
 
   return err;
@@ -1593,7 +1571,7 @@ dump_strings (void)
     }
 
   /* We reach this point only if we search through
-     (max_bytes_to_format - string_min) bytes before reachine EOF.  */
+     (max_bytes_to_format - string_min) bytes before reaching EOF.  */
 
   free (buf);
 
@@ -1913,17 +1891,27 @@ it must be one character from [doxn]"),
     }
 
   if (n_files > 0)
-    file_list = (char const *const *) &argv[optind];
+    {
+      /* Set the global pointer FILE_LIST so that it
+        references the first file-argument on the command-line.  */
+
+      file_list = (char const *const *) &argv[optind];
+    }
   else
     {
-      /* If no files were listed on the command line, set up the
-        global array FILE_LIST so that it contains the null-terminated
-        list of one name: "-".  */
-      static char const *const default_file_list[] = {"-", NULL};
+      /* No files were listed on the command line.
+        Set the global pointer FILE_LIST so that it
+        references the null-terminated list of one name: "-".  */
 
       file_list = default_file_list;
     }
 
+  /* open the first input file */
+  err |= open_next_file ();
+  if (in_stream == NULL)
+    goto cleanup;
+
+  /* skip over any unwanted header bytes */
   err |= skip (n_bytes_to_skip);
   if (in_stream == NULL)
     goto cleanup;