mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
dd: use seek for stdin skip when possible (#9821)
* dd: use seek for stdin skip when possible * Add Rust tests for skip with seekable stdin * Address review comments: fix return value, add comment, refactor tests * dd: use ibs-sized buffer for ESPIPE fallback in skip
This commit is contained in:
+33
-7
@@ -272,14 +272,40 @@ impl Source {
|
||||
return Ok(len);
|
||||
}
|
||||
}
|
||||
let m = read_and_discard(f, n, ibs)?;
|
||||
if m < n {
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("dd-error-cannot-skip-offset", "file" => "standard input")
|
||||
);
|
||||
// Get file length before seeking to avoid race condition
|
||||
let file_len = f.metadata().map(|m| m.len()).unwrap_or(u64::MAX);
|
||||
// Try seek first; fall back to read if not seekable
|
||||
match n.try_into().ok().map(|n| f.seek(SeekFrom::Current(n))) {
|
||||
Some(Ok(pos)) => {
|
||||
if pos > file_len {
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("dd-error-cannot-skip-offset", "file" => "standard input")
|
||||
);
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
// ESPIPE means the file descriptor is not seekable (e.g., a pipe),
|
||||
// so fall back to reading and discarding bytes using ibs-sized buffer
|
||||
Some(Err(e)) if e.raw_os_error() == Some(libc::ESPIPE) => {
|
||||
let m = read_and_discard(f, n, ibs)?;
|
||||
if m < n {
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("dd-error-cannot-skip-offset", "file" => "standard input")
|
||||
);
|
||||
}
|
||||
Ok(m)
|
||||
}
|
||||
_ => {
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("dd-error-cannot-skip-invalid", "file" => "standard input")
|
||||
);
|
||||
set_exit_code(1);
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
Ok(m)
|
||||
}
|
||||
Self::File(f) => f.seek(SeekFrom::Current(n.try_into().unwrap())),
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -669,6 +669,39 @@ fn test_skip_beyond_file() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_skip_beyond_file_seekable_stdin() {
|
||||
// When stdin is a seekable file, dd should use seek to skip bytes.
|
||||
// This tests that skipping beyond the file size issues a warning.
|
||||
use std::process::Stdio;
|
||||
|
||||
// Test cases: (bs, skip) pairs that skip beyond a 4-byte file
|
||||
let test_cases = [
|
||||
("bs=1", "skip=5"), // skip 5 bytes
|
||||
("bs=3", "skip=2"), // skip 6 bytes
|
||||
];
|
||||
|
||||
for (bs, skip) in test_cases {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.write("in", "abcd");
|
||||
|
||||
let stdin = OwnedFileDescriptorOrHandle::open_file(
|
||||
OpenOptions::new().read(true),
|
||||
at.plus("in").as_path(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ucmd.args(&[bs, skip, "count=0", "status=noxfer"])
|
||||
.set_stdin(Stdio::from(stdin))
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.stderr_contains(
|
||||
"'standard input': cannot skip to specified offset\n0+0 records in\n0+0 records out\n",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seek_do_not_overwrite() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
Reference in New Issue
Block a user