diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index eaf89ca55..00be1558c 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -27,11 +27,14 @@ use std::cmp; use std::env; use std::ffi::OsString; use std::fs::{File, OpenOptions}; -use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write}; -#[cfg(unix)] -use std::os::unix::fs::FileTypeExt; +use std::io::{self, Read, Seek, SeekFrom, Stdout, Write}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; +#[cfg(unix)] +use std::os::unix::{ + fs::FileTypeExt, + io::{AsRawFd, FromRawFd}, +}; use std::path::Path; use std::sync::mpsc; use std::thread; @@ -93,21 +96,44 @@ impl Num { } /// Data sources. +/// +/// Use [`Source::stdin_as_file`] if available to enable more +/// fine-grained access to reading from stdin. enum Source { /// Input from stdin. - Stdin(Stdin), + #[cfg(not(unix))] + Stdin(io::Stdin), /// Input from a file. File(File), + /// Input from stdin, opened from its file descriptor. + #[cfg(unix)] + StdinFile(File), + /// Input from a named pipe, also known as a FIFO. #[cfg(unix)] Fifo(File), } impl Source { + /// Create a source from stdin using its raw file descriptor. + /// + /// This returns an instance of the `Source::StdinFile` variant, + /// using the raw file descriptor of [`std::io::Stdin`] to create + /// the [`std::fs::File`] parameter. You can use this instead of + /// `Source::Stdin` to allow reading from stdin without consuming + /// the entire contents of stdin when this process terminates. + #[cfg(unix)] + fn stdin_as_file() -> Self { + let fd = io::stdin().as_raw_fd(); + let f = unsafe { File::from_raw_fd(fd) }; + Self::StdinFile(f) + } + fn skip(&mut self, n: u64) -> 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 => { show_error!("'standard input': cannot skip to specified offset"); @@ -116,6 +142,15 @@ impl Source { Ok(m) => Ok(m), Err(e) => Err(e), }, + #[cfg(unix)] + Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) { + Ok(m) if m < n => { + show_error!("'standard input': cannot skip to specified offset"); + Ok(m) + } + Ok(m) => Ok(m), + Err(e) => Err(e), + }, Self::File(f) => f.seek(io::SeekFrom::Start(n)), #[cfg(unix)] Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), @@ -126,9 +161,12 @@ impl Source { impl Read for Source { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { + #[cfg(not(unix))] Self::Stdin(stdin) => stdin.read(buf), Self::File(f) => f.read(buf), #[cfg(unix)] + Self::StdinFile(f) => f.read(buf), + #[cfg(unix)] Self::Fifo(f) => f.read(buf), } } @@ -151,7 +189,10 @@ struct Input<'a> { impl<'a> Input<'a> { /// Instantiate this struct with stdin as a source. fn new_stdin(settings: &'a Settings) -> UResult { + #[cfg(not(unix))] let mut src = Source::Stdin(io::stdin()); + #[cfg(unix)] + let mut src = Source::stdin_as_file(); if settings.skip > 0 { src.skip(settings.skip)?; } diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 5deeb12f0..b0d5db8f3 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1514,3 +1514,17 @@ fn test_skip_input_fifo() { assert!(output.stdout.is_empty()); assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); } + +/// Test for reading part of stdin from each of two child processes. +#[cfg(all(not(windows), feature = "printf"))] +#[test] +fn test_multiple_processes_reading_stdin() { + // TODO Investigate if this is possible on Windows. + let printf = format!("{TESTS_BINARY} printf 'abcdef\n'"); + let dd_skip = format!("{TESTS_BINARY} dd bs=1 skip=3 count=0"); + let dd = format!("{TESTS_BINARY} dd"); + UCommand::new() + .arg(format!("{printf} | ( {dd_skip} && {dd} ) 2> /dev/null")) + .succeeds() + .stdout_only("def\n"); +}