mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
dd: open stdin from file descriptor when possible
Open stdin using its file descriptor so that a `dd skip=N` command in a subshell does not consume all bytes from stdin. For example, before this commit, multiple instances of `dd` reading from stdin and appearing in a single command line would incorrectly result in an empty stdin for each instance of `dd` after the first: $ printf "abcdef\n" | (dd bs=1 skip=3 count=0 && dd) 2> /dev/null # incorrectly results in no output After this commit, the `dd skip=3` process reads three bytes from the file descriptor referring to stdin without draining the remaining three bytes when it terminates: $ printf "abcdef\n" | (dd bs=1 skip=3 count=0 && dd) 2> /dev/null def
This commit is contained in:
parent
7c0063ae3e
commit
9cb6b4a3c0
2 changed files with 59 additions and 4 deletions
|
@ -27,11 +27,14 @@ use std::cmp;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::{
|
||||||
|
fs::FileTypeExt,
|
||||||
|
io::{AsRawFd, FromRawFd},
|
||||||
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
@ -93,21 +96,44 @@ impl Num {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data sources.
|
/// Data sources.
|
||||||
|
///
|
||||||
|
/// Use [`Source::stdin_as_file`] if available to enable more
|
||||||
|
/// fine-grained access to reading from stdin.
|
||||||
enum Source {
|
enum Source {
|
||||||
/// Input from stdin.
|
/// Input from stdin.
|
||||||
Stdin(Stdin),
|
#[cfg(not(unix))]
|
||||||
|
Stdin(io::Stdin),
|
||||||
|
|
||||||
/// Input from a file.
|
/// Input from a file.
|
||||||
File(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.
|
/// Input from a named pipe, also known as a FIFO.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Fifo(File),
|
Fifo(File),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
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> {
|
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(unix))]
|
||||||
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
||||||
Ok(m) if m < n => {
|
Ok(m) if m < n => {
|
||||||
show_error!("'standard input': cannot skip to specified offset");
|
show_error!("'standard input': cannot skip to specified offset");
|
||||||
|
@ -116,6 +142,15 @@ impl Source {
|
||||||
Ok(m) => Ok(m),
|
Ok(m) => Ok(m),
|
||||||
Err(e) => Err(e),
|
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)),
|
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
||||||
|
@ -126,9 +161,12 @@ impl Source {
|
||||||
impl Read for Source {
|
impl Read for Source {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(unix))]
|
||||||
Self::Stdin(stdin) => stdin.read(buf),
|
Self::Stdin(stdin) => stdin.read(buf),
|
||||||
Self::File(f) => f.read(buf),
|
Self::File(f) => f.read(buf),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
Self::StdinFile(f) => f.read(buf),
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Fifo(f) => f.read(buf),
|
Self::Fifo(f) => f.read(buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +189,10 @@ struct Input<'a> {
|
||||||
impl<'a> Input<'a> {
|
impl<'a> Input<'a> {
|
||||||
/// Instantiate this struct with stdin as a source.
|
/// Instantiate this struct with stdin as a source.
|
||||||
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||||
|
#[cfg(not(unix))]
|
||||||
let mut src = Source::Stdin(io::stdin());
|
let mut src = Source::Stdin(io::stdin());
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut src = Source::stdin_as_file();
|
||||||
if settings.skip > 0 {
|
if settings.skip > 0 {
|
||||||
src.skip(settings.skip)?;
|
src.skip(settings.skip)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1514,3 +1514,17 @@ fn test_skip_input_fifo() {
|
||||||
assert!(output.stdout.is_empty());
|
assert!(output.stdout.is_empty());
|
||||||
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
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");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue