diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 21f312cfb4..2e3427a612 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -19,7 +19,7 @@ byteorder = { workspace = true } clap = { workspace = true } half = { workspace = true } rustix = { workspace = true, features = ["stdio"] } -uucore = { workspace = true, features = ["fs", "parser-size"] } +uucore = { workspace = true, features = ["fs", "parser-size", "pipes"] } fluent = { workspace = true } libc.workspace = true diff --git a/src/uu/od/src/multifile_reader.rs b/src/uu/od/src/multifile_reader.rs index 5be2150815..66e02cf870 100644 --- a/src/uu/od/src/multifile_reader.rs +++ b/src/uu/od/src/multifile_reader.rs @@ -41,6 +41,16 @@ impl io::Read for CurrentReader { } } +#[cfg(any(target_os = "linux", target_os = "android"))] +impl rustix::fd::AsFd for CurrentReader { + fn as_fd(&self) -> rustix::fd::BorrowedFd<'_> { + match self { + Self::File(f) => f.as_fd(), + Self::Stdin(s) => s.0, + } + } +} + // MultifileReader - concatenate all our input, file or stdin. pub struct MultifileReader<'a> { ni: Vec>, @@ -186,8 +196,17 @@ fn skip_in_file(curr: &mut CurrentReader, n_skip: u64) -> io::Result { } } } - let read = uucore::io::read_and_discard(curr, n_skip, SKIP_BUFFER_SIZE)?; - Ok(n_skip - read) + #[cfg(any(target_os = "linux", target_os = "android"))] + match uucore::pipes::discard_n_bytes(&curr, n_skip as usize) { + Ok(spliced) => Ok(n_skip - spliced as u64), + Err(spliced) => { + let read = + uucore::io::read_and_discard(curr, n_skip - spliced as u64, SKIP_BUFFER_SIZE)?; + Ok(n_skip - spliced as u64 - read) + } + } + #[cfg(not(any(target_os = "linux", target_os = "android")))] + Ok(n_skip - uucore::io::read_and_discard(curr, n_skip, SKIP_BUFFER_SIZE)?) } /// Seek `f` forward by `n` bytes. Returns `Ok(true)` if the seek happened, or diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index 02c13dc8ba..8dd50b50d5 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -181,6 +181,35 @@ pub fn send_n_bytes(input: impl AsFd, target: impl AsFd, n: u64) -> std::io::Res Ok(bytes_written) } +/// discard `n` bytes by splice +/// return actually discarded bytes +/// Err(b) means we discarded b bytes, but we should try to discarding remaining bytes by read +#[inline] +pub fn discard_n_bytes(fd: impl AsFd, n: usize) -> Result { + let mut discarded = 0; + let dev_null = dev_null().ok_or(0_usize)?; + while discarded < n + && let Ok(s) = splice(&fd, &dev_null, n - discarded) + { + if s == 0 { + return Ok(discarded); + } + discarded += s; + } + // else, input is not a pipe + let (pipe_read, pipe_write) = pipe::().map_err(|_| discarded)?; + while discarded < n + && let Ok(s @ 1..) = splice(&fd, &pipe_write, n - discarded) + { + discarded += s; + // pipe to null is not blocked. So this returns the same length at most cases + // next splice does not hang if we discarded 1+ pages + splice(&pipe_read, &dev_null, s).map_err(|_| discarded)?; + } + + Ok(discarded) +} + /// Return verified /dev/null /// /// `splice` to /dev/null is faster than `read` when we skip or count the non-seekable input