[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: dd bug
From: |
Paul Eggert |
Subject: |
Re: dd bug |
Date: |
12 Oct 2003 21:36:53 -0700 |
User-agent: |
Gnus/5.09 (Gnus v5.9.0) Emacs/21.3 |
Buciuman Adrian <address@hidden> writes:
> With the patch applied : sectors 1-72 from the floppy , (1368+1440) * 512
> bytes of null padding ,
> sectors 1441 - 2880 from the floppy .
>
> ( sector 73 was bad )
>
> Therefore there is still 1 block of null paddding in excess.
Thanks for debugging it further. Can you please try this patch instead?
It supersedes my September 24 patch.
2003-10-12 Paul Eggert <address@hidden>
Fix bug reported by Buciuman Adrian in
<http://mail.gnu.org/archive/html/bug-coreutils/2003-08/msg00105.html>
where 'dd' created a file that was too large. The bug was that dd
assumed that the input file offset does not advance after a failed
read; but POSIX says that the input file offset is undefined after
a failed read.
* src/dd.c (MAX_BLOCKSIZE): New macro.
(input_seekable, input_seek_errno, input_offset,
input_offset_overflow): New vars.
(write_output, dd_copy): Pass EXIT_FAILURE to quit, not 1.
(scanargs): Reject block sizes greater than MAX_BLOCKSIZE>
(advance_input_offset): New function.
(skip_via_lseek): Set errno to zero when reporting our failure,
so that we don't report based on garbage errno.
(skip): If fdesc is standard input, advance the input offset.
Do not quit if reading, and if noerror was specified;
POSIX seems to require this.
If read fails on output file, report the earlier lseek failure
instead; this fixes a FIXME in dd_copy.
(advance_input_after_read_error): New function.
(dd_copy): Use it, instead of assuming that failed reads
do not advance the file pointer. Advance input offset
after nonfailed reads. Advance only a partial block if
the previous read (before the failed read) succeeded, and
do not generate an output block of zeros in this case.
(main): Determine initial input offset, seekability of input,
and error if it wasn't seekable.
Index: dd.c
===================================================================
RCS file: /cvsroot/coreutils/coreutils/src/dd.c,v
retrieving revision 1.150
diff -p -u -r1.150 dd.c
--- dd.c 18 Sep 2003 22:19:03 -0000 1.150
+++ dd.c 13 Oct 2003 04:34:06 -0000
@@ -66,6 +66,12 @@
/* Default input and output blocksize. */
#define DEFAULT_BLOCKSIZE 512
+/* Maximum blocksize. Keep it smaller than SIZE_MAX, so that we can
+ allocate buffers that size. Keep it smaller than SSIZE_MAX, for
+ the benefit of system calls like "read". And keep it smaller than
+ OFF_T_MAX, for the benefit of the large-offset seek code. */
+#define MAX_BLOCKSIZE MIN (SIZE_MAX, MIN (SSIZE_MAX, OFF_T_MAX))
+
/* Conversions bit masks. */
#define C_ASCII 01
#define C_EBCDIC 02
@@ -126,6 +132,19 @@ static uintmax_t r_partial = 0;
/* Number of full blocks read. */
static uintmax_t r_full = 0;
+/* True if input is seekable. */
+static bool input_seekable;
+
+/* Error number corresponding to initial attempt to lseek input.
+ If ESPIPE, do not issue any more diagnostics about it. */
+int input_seek_errno;
+
+/* File offset of the input, in bytes, along with a flag recording
+ whether it overflowed. The offset is valid only if the input is
+ seekable and if the offset has not overflowed. */
+static uintmax_t input_offset;
+bool input_offset_overflow;
+
/* Records truncated by conv=block. */
static uintmax_t r_truncate = 0;
@@ -479,7 +498,7 @@ write_output (void)
error (0, errno, _("writing to %s"), quote (output_file));
if (nwritten != 0)
w_partial++;
- quit (1);
+ quit (EXIT_FAILURE);
}
else
w_full++;
@@ -582,26 +601,20 @@ scanargs (int argc, char **argv)
if (STREQ (name, "ibs"))
{
- /* Ensure that each blocksize is <= SSIZE_MAX. */
- invalid |= SSIZE_MAX < n;
+ invalid |= ! (0 < n && n <= MAX_BLOCKSIZE);
input_blocksize = n;
- invalid |= input_blocksize != n || input_blocksize == 0;
conversions_mask |= C_TWOBUFS;
}
else if (STREQ (name, "obs"))
{
- /* Ensure that each blocksize is <= SSIZE_MAX. */
- invalid |= SSIZE_MAX < n;
+ invalid |= ! (0 < n && n <= MAX_BLOCKSIZE);
output_blocksize = n;
- invalid |= output_blocksize != n || output_blocksize == 0;
conversions_mask |= C_TWOBUFS;
}
else if (STREQ (name, "bs"))
{
- /* Ensure that each blocksize is <= SSIZE_MAX. */
- invalid |= SSIZE_MAX < n;
+ invalid |= ! (0 < n && n <= MAX_BLOCKSIZE);
output_blocksize = input_blocksize = n;
- invalid |= output_blocksize != n || output_blocksize == 0;
}
else if (STREQ (name, "cbs"))
{
@@ -747,6 +760,17 @@ swab_buffer (char *buf, size_t *nread)
return ++bufstart;
}
+/* Add OFFSET to the input offset, setting the overflow flag if
+ necessary. */
+
+static void
+advance_input_offset (uintmax_t offset)
+{
+ input_offset += offset;
+ if (input_offset < offset)
+ input_offset_overflow = true;
+}
+
/* This is a wrapper for lseek. It detects and warns about a kernel
bug that makes lseek a no-op for tape devices, even though the kernel
lseek return value suggests that the function succeeded.
@@ -791,6 +815,7 @@ skip_via_lseek (char const *filename, in
error (0, 0, _("warning: working around lseek kernel bug for file (%s)\n\
of mt_type=0x%0lx -- see <sys/mtio.h> for the list of types"),
filename, s2.mt_type);
+ errno = 0;
new_position = -1;
}
@@ -803,7 +828,7 @@ skip_via_lseek (char const *filename, in
/* Throw away RECORDS blocks of BLOCKSIZE bytes on file descriptor FDESC,
which is open with read permission for FILE. Store up to BLOCKSIZE
bytes of the data at a time in BUF, if necessary. RECORDS must be
- nonzero. */
+ nonzero. If fdesc is STDIN_FILENO, advance the input offset. */
static void
skip (int fdesc, char const *file, uintmax_t records, size_t blocksize,
@@ -815,26 +840,91 @@ skip (int fdesc, char const *file, uintm
or if the the file offset is not representable as an off_t --
fall back on using read. */
- if ((uintmax_t) offset / blocksize != records
- || skip_via_lseek (file, fdesc, offset, SEEK_CUR) < 0)
+ errno = 0;
+ if ((uintmax_t) offset / blocksize == records
+ && 0 <= skip_via_lseek (file, fdesc, offset, SEEK_CUR))
{
+ if (fdesc == STDIN_FILENO)
+ advance_input_offset (offset);
+ }
+ else
+ {
+ int lseek_errno = errno;
while (records--)
{
size_t nread = safe_read (fdesc, buf, blocksize);
if (nread == SAFE_READ_ERROR)
{
- error (0, errno, _("reading %s"), quote (file));
- quit (1);
+ if (fdesc == STDIN_FILENO)
+ {
+ error (0, errno, _("reading %s"), quote (file));
+ if (conversions_mask & C_NOERROR)
+ {
+ print_stats ();
+ continue;
+ }
+ }
+ else
+ error (0, lseek_errno, _("%s: cannot seek"), quote (file));
+ quit (EXIT_FAILURE);
}
/* POSIX doesn't say what to do when dd detects it has been
asked to skip past EOF, so I assume it's non-fatal.
FIXME: maybe give a warning. */
if (nread == 0)
break;
+ if (fdesc == STDIN_FILENO)
+ advance_input_offset (nread);
}
}
}
+/* Advance the input by NBYTES if possible, after a read error.
+ The input file offset may or may not have advanced after the failed
+ read; adjust it to point just after the bad record regardless.
+ Return true if successful, or if the input is already known to not
+ be seekable. */
+
+static bool
+advance_input_after_read_error (size_t nbytes)
+{
+ if (! input_seekable)
+ {
+ if (input_seek_errno == ESPIPE)
+ return true;
+ errno = input_seek_errno;
+ }
+ else
+ {
+ off_t offset;
+ advance_input_offset (nbytes);
+ input_offset_overflow |= (OFF_T_MAX < input_offset);
+ if (input_offset_overflow)
+ {
+ error (0, 0, _("offset overflow while reading file %s"),
+ quote (input_file));
+ return false;
+ }
+ offset = lseek (STDIN_FILENO, 0, SEEK_CUR);
+ if (0 <= offset)
+ {
+ off_t diff;
+ if (offset == input_offset)
+ return true;
+ diff = input_offset - offset;
+ if (! (0 <= diff && diff <= nbytes))
+ error (0, 0, _("warning: screwy file offset after failed read"));
+ if (0 <= skip_via_lseek (input_file, STDIN_FILENO, diff, SEEK_CUR))
+ return true;
+ if (errno == 0)
+ error (0, 0, _("cannot work around kernel bug after all"));
+ }
+ }
+
+ error (0, errno, _("%s: cannot seek"), quote (input_file));
+ return false;
+}
+
/* Copy NREAD bytes of BUF, with no conversions. */
static void
@@ -938,6 +1028,11 @@ dd_copy (void)
char *real_buf; /* real buffer address before alignment */
char *real_obuf;
size_t nread; /* Bytes read in the current block. */
+
+ /* If nonzero, then the previously read block was partial and
+ PARTREAD was its size. */
+ size_t partread = 0;
+
int exit_status = 0;
size_t page_size = getpagesize ();
size_t n_bytes_read;
@@ -983,16 +1078,7 @@ dd_copy (void)
skip (STDIN_FILENO, input_file, skip_records, input_blocksize, ibuf);
if (seek_records != 0)
- {
- /* FIXME: this loses for
- % ./dd if=dd seek=1 |:
- ./dd: standard output: Bad file descriptor
- 0+0 records in
- 0+0 records out
- */
-
- skip (STDOUT_FILENO, output_file, seek_records, output_blocksize, obuf);
- }
+ skip (STDOUT_FILENO, output_file, seek_records, output_blocksize, obuf);
if (max_records == 0)
quit (exit_status);
@@ -1022,8 +1108,15 @@ dd_copy (void)
{
print_stats ();
/* Seek past the bad block if possible. */
- lseek (STDIN_FILENO, (off_t) input_blocksize, SEEK_CUR);
- if (conversions_mask & C_SYNC)
+ if (!advance_input_after_read_error (input_blocksize - partread))
+ {
+ exit_status = EXIT_FAILURE;
+
+ /* Suppress duplicate diagnostics. */
+ input_seekable = false;
+ input_seek_errno = ESPIPE;
+ }
+ if ((conversions_mask & C_SYNC) && !partread)
/* Replace the missing input with null bytes and
proceed normally. */
nread = 0;
@@ -1039,10 +1132,12 @@ dd_copy (void)
}
n_bytes_read = nread;
+ advance_input_offset (nread);
if (n_bytes_read < input_blocksize)
{
r_partial++;
+ partread = n_bytes_read;
if (conversions_mask & C_SYNC)
{
if (!(conversions_mask & C_NOERROR))
@@ -1054,7 +1149,10 @@ dd_copy (void)
}
}
else
- r_full++;
+ {
+ r_full++;
+ partread = 0;
+ }
if (ibuf == obuf) /* If not C_TWOBUFS. */
{
@@ -1062,7 +1160,7 @@ dd_copy (void)
if (nwritten != n_bytes_read)
{
error (0, errno, _("writing %s"), quote (output_file));
- quit (1);
+ quit (EXIT_FAILURE);
}
else if (n_bytes_read == input_blocksize)
w_full++;
@@ -1123,7 +1221,7 @@ dd_copy (void)
if (nwritten != oc)
{
error (0, errno, _("writing %s"), quote (output_file));
- quit (1);
+ quit (EXIT_FAILURE);
}
}
@@ -1182,6 +1280,13 @@ main (int argc, char **argv)
}
else
input_file = _("standard input");
+
+ {
+ off_t offset = lseek (STDIN_FILENO, 0, SEEK_CUR);
+ input_seekable = (0 <= offset);
+ input_offset = offset;
+ input_seek_errno = errno;
+ }
if (output_file != NULL)
{
- Re: dd bug, Buciuman Adrian, 2003/10/12
- Re: dd bug,
Paul Eggert <=