Skip to content

Commit

Permalink
Merge pull request #4189 from jfinkels/dd-stdin-from-file-descriptor
Browse files Browse the repository at this point in the history
dd: open stdin from file descriptor when possible
  • Loading branch information
tertsdiepraam authored Mar 18, 2023
2 parents 88d9517 + 7a6092b commit e3aab30
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 4 deletions.
49 changes: 45 additions & 4 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u64> {
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");
Expand All @@ -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()),
Expand All @@ -126,9 +161,12 @@ impl Source {
impl Read for Source {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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),
}
}
Expand All @@ -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<Self> {
#[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)?;
}
Expand Down
14 changes: 14 additions & 0 deletions tests/by-util/test_dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

0 comments on commit e3aab30

Please sign in to comment.