diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index f490bb11a8b..ab9deee4daf 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -97,6 +97,10 @@ enum Source { /// Input from a file. File(File), + + /// Input from a named pipe, also known as a FIFO. + #[cfg(unix)] + Fifo(File), } impl Source { @@ -111,6 +115,8 @@ impl Source { 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()), } } } @@ -120,6 +126,8 @@ impl Read for Source { match self { Self::Stdin(stdin) => stdin.read(buf), Self::File(f) => f.read(buf), + #[cfg(unix)] + Self::Fifo(f) => f.read(buf), } } } @@ -169,6 +177,20 @@ impl<'a> Input<'a> { } Ok(Self { src, settings }) } + + /// Instantiate this struct with the named pipe as a source. + #[cfg(unix)] + fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult { + let mut opts = OpenOptions::new(); + opts.read(true); + #[cfg(any(target_os = "linux", target_os = "android"))] + 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)?; + } + Ok(Self { src, settings }) + } } #[cfg(any(target_os = "linux", target_os = "android"))] @@ -889,6 +911,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )?; let i = match settings.infile { + #[cfg(unix)] + Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?, Some(ref infile) => Input::new_file(Path::new(&infile), &settings)?, None => Input::new_stdin(&settings)?, }; diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 9bff11fda6e..94d974d1ab2 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1453,3 +1453,36 @@ fn test_seek_output_fifo() { assert!(output.stdout.is_empty()); assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); } + +/// Test that a skip on an input FIFO results in a read. +#[test] +#[cfg(not(windows))] +fn test_skip_input_fifo() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.mkfifo("fifo"); + + // TODO When `dd` is a bit more advanced, we could use the uutils + // version of dd here as well. + let child = Command::new("dd") + .current_dir(&at.subdir) + .args([ + "count=1", + "if=/dev/zero", + &format!("of={}", at.plus_as_string("fifo")), + "status=noxfer", + ]) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to execute child process"); + + ts.ucmd() + .args(&["count=0", "skip=1", "if=fifo", "status=noxfer"]) + .succeeds() + .stderr_only("0+0 records in\n0+0 records out\n"); + + let output = child.wait_with_output().unwrap(); + assert!(output.status.success()); + assert!(output.stdout.is_empty()); + assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); +}