diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4e1287939d5..2228c38647d 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -27,7 +27,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, Read, Seek, SeekFrom, Write}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; #[cfg(unix)] @@ -173,6 +173,8 @@ impl Source { /// 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. + /// + /// See also [`Dest::stdout_as_file`]. #[cfg(unix)] fn stdin_as_file() -> Self { let fd = io::stdin().as_raw_fd(); @@ -474,7 +476,12 @@ enum Density { /// Data destinations. enum Dest { /// Output to stdout. - Stdout(Stdout), + #[cfg(not(unix))] + Stdout(std::io::Stdout), + + /// Output to stdout, opened from its file descriptor. + #[cfg(unix)] + StdoutFile(File), /// Output to a file. /// @@ -492,9 +499,31 @@ enum Dest { } impl Dest { + /// Create a destination to stdout using its raw file descriptor. + /// + /// This returns an instance of the `Dest::StdoutFile` variant, + /// using the raw file descriptor of [`std::io::Stdout`] to create + /// the [`std::fs::File`] parameter. You can use this instead of + /// `Dest::Stdout` to allow writing to stdout without dropping the + /// entire contents of stdout when this process terminates. + /// + /// See also [`Source::stdin_as_file`]. + #[cfg(unix)] + fn stdout_as_file() -> Self { + let fd = io::stdout().as_raw_fd(); + let f = unsafe { File::from_raw_fd(fd) }; + Self::StdoutFile(f) + } + fn fsync(&mut self) -> io::Result<()> { match self { + #[cfg(not(unix))] Self::Stdout(stdout) => stdout.flush(), + #[cfg(unix)] + Self::StdoutFile(f) => { + f.flush()?; + f.sync_all() + } Self::File(f, _) => { f.flush()?; f.sync_all() @@ -511,7 +540,13 @@ impl Dest { fn fdatasync(&mut self) -> io::Result<()> { match self { + #[cfg(not(unix))] Self::Stdout(stdout) => stdout.flush(), + #[cfg(unix)] + Self::StdoutFile(f) => { + f.flush()?; + f.sync_data() + } Self::File(f, _) => { f.flush()?; f.sync_data() @@ -528,7 +563,10 @@ impl Dest { fn seek(&mut self, n: u64) -> io::Result { match self { + #[cfg(not(unix))] Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), + #[cfg(unix)] + Self::StdoutFile(f) => io::copy(&mut io::repeat(0).take(n), f), Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), #[cfg(unix)] Self::Fifo(f) => { @@ -596,8 +634,11 @@ impl Write for Dest { Ok(buf.len()) } Self::File(f, _) => f.write(buf), + #[cfg(not(unix))] Self::Stdout(stdout) => stdout.write(buf), #[cfg(unix)] + Self::StdoutFile(f) => f.write(buf), + #[cfg(unix)] Self::Fifo(f) => f.write(buf), #[cfg(unix)] Self::Sink => Ok(buf.len()), @@ -606,7 +647,10 @@ impl Write for Dest { fn flush(&mut self) -> io::Result<()> { match self { + #[cfg(not(unix))] Self::Stdout(stdout) => stdout.flush(), + #[cfg(unix)] + Self::StdoutFile(f) => f.flush(), Self::File(f, _) => f.flush(), #[cfg(unix)] Self::Fifo(f) => f.flush(), @@ -633,7 +677,10 @@ struct Output<'a> { impl<'a> Output<'a> { /// Instantiate this struct with stdout as a destination. fn new_stdout(settings: &'a Settings) -> UResult { + #[cfg(not(unix))] let mut dst = Dest::Stdout(io::stdout()); + #[cfg(unix)] + let mut dst = Dest::stdout_as_file(); dst.seek(settings.seek) .map_err_context(|| "write error".to_string())?; Ok(Self { dst, settings }) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index b43f32c240a..28832a65feb 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1562,3 +1562,17 @@ fn test_nocache_file() { .succeeds() .stderr_only("2048+0 records in\n2048+0 records out\n"); } + +/// Test for writing part of stdout from each of two child processes. +#[cfg(all(not(windows), feature = "printf"))] +#[test] +fn test_multiple_processes_writing_stdout() { + let printf = format!("{TESTS_BINARY} printf 'abcde\n'"); + let dd_skip = format!("{TESTS_BINARY} dd bs=1 skip=1 count=1"); + let dd = format!("{TESTS_BINARY} dd bs=1 skip=1"); + UCommand::new() + .arg(format!("{printf} | ( {dd_skip}; {dd} ) 2> /dev/null")) + .succeeds() + .stdout_only("bde\n"); +} +