diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 567f803d390..e2344952a0f 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -183,6 +183,32 @@ impl Num { } } +/// Read and discard `n` bytes from `reader` using a buffer of size `buf_size`. +/// +/// This is more efficient than `io::copy` with `BufReader` because it reads +/// directly in `buf_size`-sized chunks, matching GNU dd's behavior. +/// Returns the total number of bytes actually read. +fn read_and_discard(reader: &mut R, n: u64, buf_size: usize) -> io::Result { + let mut buf = vec![0u8; buf_size]; + let mut total = 0u64; + let mut remaining = n; + + while remaining > 0 { + let to_read = cmp::min(remaining, buf_size as u64) as usize; + match reader.read(&mut buf[..to_read]) { + Ok(0) => break, // EOF + Ok(bytes_read) => { + total += bytes_read as u64; + remaining -= bytes_read as u64; + } + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + } + + Ok(total) +} + /// Data sources. /// /// Use [`Source::stdin_as_file`] if available to enable more @@ -219,20 +245,19 @@ impl Source { Self::StdinFile(f) } - fn skip(&mut self, n: u64) -> io::Result { + fn skip(&mut self, n: u64, ibs: usize) -> io::Result { match self { #[cfg(not(unix))] - Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) { - Ok(m) if m < n => { + Self::Stdin(stdin) => { + let m = read_and_discard(stdin, n, ibs)?; + if m < n { show_error!( "{}", translate!("dd-error-cannot-skip-offset", "file" => "standard input") ); - Ok(m) } - Ok(m) => Ok(m), - Err(e) => Err(e), - }, + Ok(m) + } #[cfg(unix)] Self::StdinFile(f) => { if let Ok(Some(len)) = try_get_len_of_block_device(f) { @@ -247,21 +272,18 @@ impl Source { return Ok(len); } } - match io::copy(&mut f.take(n), &mut io::sink()) { - Ok(m) if m < n => { - show_error!( - "{}", - translate!("dd-error-cannot-skip-offset", "file" => "standard input") - ); - Ok(m) - } - Ok(m) => Ok(m), - Err(e) => Err(e), + let m = read_and_discard(f, n, ibs)?; + if m < n { + show_error!( + "{}", + translate!("dd-error-cannot-skip-offset", "file" => "standard input") + ); } + Ok(m) } Self::File(f) => f.seek(SeekFrom::Current(n.try_into().unwrap())), #[cfg(unix)] - Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), + Self::Fifo(f) => read_and_discard(f, n, ibs), } } @@ -346,7 +368,7 @@ impl<'a> Input<'a> { } } if settings.skip > 0 { - src.skip(settings.skip)?; + src.skip(settings.skip, settings.ibs)?; } Ok(Self { src, settings }) } @@ -369,7 +391,7 @@ impl<'a> Input<'a> { let mut src = Source::File(src); if settings.skip > 0 { - src.skip(settings.skip)?; + src.skip(settings.skip, settings.ibs)?; } Ok(Self { src, settings }) } @@ -383,7 +405,7 @@ impl<'a> Input<'a> { opts.custom_flags(make_linux_iflags(&settings.iflags).unwrap_or(0)); let mut src = Source::Fifo(opts.open(filename)?); if settings.skip > 0 { - src.skip(settings.skip)?; + src.skip(settings.skip, settings.ibs)?; } Ok(Self { src, settings }) } @@ -605,7 +627,8 @@ impl Dest { } } - fn seek(&mut self, n: u64) -> io::Result { + #[cfg_attr(not(unix), allow(unused_variables))] + fn seek(&mut self, n: u64, obs: usize) -> io::Result { match self { Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), Self::File(f, _) => { @@ -627,7 +650,7 @@ impl Dest { #[cfg(unix)] Self::Fifo(f) => { // Seeking in a named pipe means *reading* from the pipe. - io::copy(&mut f.take(n), &mut io::sink()) + read_and_discard(f, n, obs) } #[cfg(unix)] Self::Sink => Ok(0), @@ -781,7 +804,7 @@ impl<'a> Output<'a> { /// Instantiate this struct with stdout as a destination. fn new_stdout(settings: &'a Settings) -> UResult { let mut dst = Dest::Stdout(io::stdout()); - dst.seek(settings.seek) + dst.seek(settings.seek, settings.obs) .map_err_context(|| translate!("dd-error-write-error"))?; Ok(Self { dst, settings }) } @@ -829,7 +852,7 @@ impl<'a> Output<'a> { Density::Dense }; let mut dst = Dest::File(dst, density); - dst.seek(settings.seek) + dst.seek(settings.seek, settings.obs) .map_err_context(|| translate!("dd-error-failed-to-seek"))?; Ok(Self { dst, settings }) } @@ -859,7 +882,7 @@ impl<'a> Output<'a> { // file for reading. But then we need to close the file and // re-open it for writing. if settings.seek > 0 { - Dest::Fifo(File::open(filename)?).seek(settings.seek)?; + Dest::Fifo(File::open(filename)?).seek(settings.seek, settings.obs)?; } // If `count=0`, then we don't bother opening the file for // writing because that would cause this process to block