diff options
author | Pádraig Brady <P@draigBrady.com> | 2008-11-20 10:28:31 +0000 |
---|---|---|
committer | Pádraig Brady <P@draigBrady.com> | 2009-01-28 15:14:57 +0000 |
commit | 460ebb078ed7bf249af745d8725d6c37e7f15b57 (patch) | |
tree | a421119bbced4575fad05e74245ad7995a889355 /src | |
parent | 0d5508de1358319c32b757d8593c3862465fe916 (diff) | |
download | coreutils-460ebb078ed7bf249af745d8725d6c37e7f15b57.tar.xz |
dd: Better handle user specified offsets that are too big
Following are the before and after operations for seekable files,
for the various erroneous offsets handled by this patch:
skip beyond end of file
before: immediately exit(0);
after : immediately printf("cannot skip to specified offset"); exit(0);
skip > max file size
before: read whole file and exit(0);
after : immediately printf("cannot skip: Invalid argument"); exit(1);
seek > max file size
before: immediately printf("truncate error: EFBIG"); exit(1);
after : immediately printf("truncate error: EFBIG"); exit(1);
skip > OFF_T_MAX
before: read whole device/file and exit(0);
after : immediately printf("cannot skip:"); exit(1);
seek > OFF_T_MAX
before: immediately printf("truncate error: offset too large"); exit(1);
after : immediately printf("truncate error: offset too large"); exit(1);
skip > device size
before: read whole device and exit(0);
after : immediately printf("cannot skip: Invalid argument"); exit(1);
seek > device size
before: read whole device and printf("write error: ENOSPC"); exit(1);
after : immediately printf("cannot seek: Invalid argument"); exit(1);
* NEWS: Summarize this change in behavior.
* src/dd.c (skip): Add error checking for large seek/skip offsets on
seekable files, rather than deferring to using read() to advance offset.
(dd_copy): Print a warning if skip past EOF, as per FIXME comment.
* test/Makefile.am: Add 2 new tests.
* tests/dd/seek-skip-past-file: Add tests for first 3 cases above.
* tests/dd/seek-skip-past-dev: Add root only test for last case above.
Diffstat (limited to 'src')
-rw-r--r-- | src/dd.c | 77 |
1 files changed, 69 insertions, 8 deletions
@@ -200,8 +200,7 @@ static bool input_seekable; static 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. */ + whether it overflowed. */ static uintmax_t input_offset; static bool input_offset_overflow; @@ -1259,12 +1258,62 @@ skip (int fdesc, char const *file, uintmax_t records, size_t blocksize, && 0 <= skip_via_lseek (file, fdesc, offset, SEEK_CUR)) { if (fdesc == STDIN_FILENO) - advance_input_offset (offset); - return 0; + { + struct stat st; + if (fstat (STDIN_FILENO, &st) != 0) + error (EXIT_FAILURE, errno, _("cannot fstat %s"), quote (file)); + if (S_ISREG (st.st_mode) && st.st_size < (input_offset + offset)) + { + /* When skipping past EOF, return the number of _full_ blocks + * that are not skipped, and set offset to EOF, so the caller + * can determine the requested skip was not satisfied. */ + records = ( offset - st.st_size ) / blocksize; + offset = st.st_size - input_offset; + } + else + records = 0; + advance_input_offset (offset); + } + else + records = 0; + return records; } else { int lseek_errno = errno; + off_t soffset; + + /* The seek request may have failed above if it was too big + (> device size, > max file size, etc.) + Or it may not have been done at all (> OFF_T_MAX). + Therefore try to seek to the end of the file, + to avoid redundant reading. */ + if ((soffset = skip_via_lseek (file, fdesc, 0, SEEK_END)) >= 0) + { + /* File is seekable, and we're at the end of it, and + size <= OFF_T_MAX. So there's no point using read to advance. */ + + if (!lseek_errno) + { + /* The original seek was not attempted as offset > OFF_T_MAX. + We should error for write as can't get to the desired + location, even if OFF_T_MAX < max file size. + For read we're not going to read any data anyway, + so we should error for consistency. + It would be nice to not error for /dev/{zero,null} + for any offset, but that's not a significant issue. */ + lseek_errno = EOVERFLOW; + } + + if (fdesc == STDIN_FILENO) + error (0, lseek_errno, _("%s: cannot skip"), quote (file)); + else + error (0, lseek_errno, _("%s: cannot seek"), quote (file)); + /* If the file has a specific size and we've asked + to skip/seek beyond the max allowable, then quit. */ + quit (EXIT_FAILURE); + } + /* else file_size && offset > OFF_T_MAX or file ! seekable */ do { @@ -1537,10 +1586,22 @@ dd_copy (void) if (skip_records != 0) { - skip (STDIN_FILENO, input_file, skip_records, input_blocksize, ibuf); + uintmax_t us_bytes = input_offset + (skip_records * input_blocksize); + uintmax_t us_blocks = skip (STDIN_FILENO, input_file, + skip_records, input_blocksize, ibuf); + us_bytes -= input_offset; + /* 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 if the - call to 'skip' returns nonzero. FIXME: maybe give a warning. */ + asked to skip past EOF, so I assume it's non-fatal. + There are 3 reasons why there might be unskipped blocks/bytes: + 1. file is too small + 2. pipe has not enough data + 3. short reads */ + if (us_blocks || (!input_offset_overflow && us_bytes)) + { + error (0, 0, + _("%s: cannot skip to specified offset"), quote (input_file)); + } } if (seek_records != 0) @@ -1778,7 +1839,7 @@ main (int argc, char **argv) offset = lseek (STDIN_FILENO, 0, SEEK_CUR); input_seekable = (0 <= offset); - input_offset = offset; + input_offset = MAX(0, offset); input_seek_errno = errno; if (output_file == NULL) |