From 7e69f902cb4d5ef6af4d8b8b5da01e23ea2d2478 Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Wed, 24 Dec 2025 03:11:32 +0000 Subject: [PATCH 1/3] dd: use ibs/obs-sized buffer for skip/seek on non-seekable files --- src/uu/dd/src/dd.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 567f803d390..0bd1c5ece04 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -30,7 +30,7 @@ use std::cmp; use std::env; use std::ffi::OsString; use std::fs::{File, OpenOptions}; -use std::io::{self, Read, Seek, SeekFrom, Stdout, Write}; +use std::io::{self, BufReader, Read, Seek, SeekFrom, Stdout, Write}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::AsFd; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -219,10 +219,13 @@ 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()) { + Self::Stdin(stdin) => match io::copy( + &mut BufReader::with_capacity(ibs, stdin.take(n)), + &mut io::sink(), + ) { Ok(m) if m < n => { show_error!( "{}", @@ -247,7 +250,10 @@ impl Source { return Ok(len); } } - match io::copy(&mut f.take(n), &mut io::sink()) { + match io::copy( + &mut BufReader::with_capacity(ibs, f.take(n)), + &mut io::sink(), + ) { Ok(m) if m < n => { show_error!( "{}", @@ -261,7 +267,10 @@ impl Source { } 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) => io::copy( + &mut BufReader::with_capacity(ibs, f.take(n)), + &mut io::sink(), + ), } } @@ -346,7 +355,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 +378,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 +392,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 +614,7 @@ impl Dest { } } - fn seek(&mut self, n: u64) -> io::Result { + 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 +636,10 @@ 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()) + io::copy( + &mut BufReader::with_capacity(obs, f.take(n)), + &mut io::sink(), + ) } #[cfg(unix)] Self::Sink => Ok(0), @@ -781,7 +793,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 +841,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 +871,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 From 8e70aff7ad1c06ed84e0f9e8d64c23ea4667e327 Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Wed, 24 Dec 2025 05:51:53 +0000 Subject: [PATCH 2/3] dd: fix unused variable warning on Windows --- src/uu/dd/src/dd.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 0bd1c5ece04..162bb04c47f 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -614,6 +614,7 @@ impl Dest { } } + #[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), From 3aca9fe423bbf47e1001093adb3f68ac86a0e8db Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Mon, 5 Jan 2026 17:40:41 +0000 Subject: [PATCH 3/3] dd: use direct read loop instead of io::copy for skip/seek --- src/uu/dd/src/dd.rs | 72 ++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 162bb04c47f..e2344952a0f 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -30,7 +30,7 @@ use std::cmp; use std::env; use std::ffi::OsString; use std::fs::{File, OpenOptions}; -use std::io::{self, BufReader, Read, Seek, SeekFrom, Stdout, Write}; +use std::io::{self, Read, Seek, SeekFrom, Stdout, Write}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::AsFd; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -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 @@ -222,20 +248,16 @@ impl Source { fn skip(&mut self, n: u64, ibs: usize) -> io::Result { match self { #[cfg(not(unix))] - Self::Stdin(stdin) => match io::copy( - &mut BufReader::with_capacity(ibs, 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) { @@ -250,27 +272,18 @@ impl Source { return Ok(len); } } - match io::copy( - &mut BufReader::with_capacity(ibs, 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 BufReader::with_capacity(ibs, f.take(n)), - &mut io::sink(), - ), + Self::Fifo(f) => read_and_discard(f, n, ibs), } } @@ -637,10 +650,7 @@ impl Dest { #[cfg(unix)] Self::Fifo(f) => { // Seeking in a named pipe means *reading* from the pipe. - io::copy( - &mut BufReader::with_capacity(obs, f.take(n)), - &mut io::sink(), - ) + read_and_discard(f, n, obs) } #[cfg(unix)] Self::Sink => Ok(0),