Skip to content

Commit d0d72cd

Browse files
jfinkelssylvestre
authored andcommitted
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
1 parent 61ef63f commit d0d72cd

File tree

1 file changed

+45
-4
lines changed

1 file changed

+45
-4
lines changed

src/uu/dd/src/dd.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ use std::cmp;
2525
use std::env;
2626
use std::ffi::OsString;
2727
use std::fs::{File, OpenOptions};
28-
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
29-
#[cfg(unix)]
30-
use std::os::unix::fs::FileTypeExt;
28+
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
3129
#[cfg(any(target_os = "linux", target_os = "android"))]
3230
use std::os::unix::fs::OpenOptionsExt;
31+
#[cfg(unix)]
32+
use std::os::unix::{
33+
fs::FileTypeExt,
34+
io::{AsRawFd, FromRawFd},
35+
};
3336
use std::path::Path;
3437
use std::sync::mpsc;
3538
use std::thread;
@@ -91,21 +94,44 @@ impl Num {
9194
}
9295

9396
/// Data sources.
97+
///
98+
/// Use [`Source::stdin_as_file`] if available to enable more
99+
/// fine-grained access to reading from stdin.
94100
enum Source {
95101
/// Input from stdin.
96-
Stdin(Stdin),
102+
#[cfg(not(unix))]
103+
Stdin(io::Stdin),
97104

98105
/// Input from a file.
99106
File(File),
100107

108+
/// Input from stdin, opened from its file descriptor.
109+
#[cfg(unix)]
110+
StdinFile(File),
111+
101112
/// Input from a named pipe, also known as a FIFO.
102113
#[cfg(unix)]
103114
Fifo(File),
104115
}
105116

106117
impl Source {
118+
/// Create a source from stdin using its raw file descriptor.
119+
///
120+
/// This returns an instance of the `Source::StdinFile` variant,
121+
/// using the raw file descriptor of [`std::io::Stdin`] to create
122+
/// the [`std::fs::File`] parameter. You can use this instead of
123+
/// `Source::Stdin` to allow reading from stdin without consuming
124+
/// the entire contents of stdin when this process terminates.
125+
#[cfg(unix)]
126+
fn stdin_as_file() -> Self {
127+
let fd = io::stdin().as_raw_fd();
128+
let f = unsafe { File::from_raw_fd(fd) };
129+
Self::StdinFile(f)
130+
}
131+
107132
fn skip(&mut self, n: u64) -> io::Result<u64> {
108133
match self {
134+
#[cfg(not(unix))]
109135
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
110136
Ok(m) if m < n => {
111137
show_error!("'standard input': cannot skip to specified offset");
@@ -114,6 +140,15 @@ impl Source {
114140
Ok(m) => Ok(m),
115141
Err(e) => Err(e),
116142
},
143+
#[cfg(unix)]
144+
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) {
145+
Ok(m) if m < n => {
146+
show_error!("'standard input': cannot skip to specified offset");
147+
Ok(m)
148+
}
149+
Ok(m) => Ok(m),
150+
Err(e) => Err(e),
151+
},
117152
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
118153
#[cfg(unix)]
119154
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
@@ -124,9 +159,12 @@ impl Source {
124159
impl Read for Source {
125160
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
126161
match self {
162+
#[cfg(not(unix))]
127163
Self::Stdin(stdin) => stdin.read(buf),
128164
Self::File(f) => f.read(buf),
129165
#[cfg(unix)]
166+
Self::StdinFile(f) => f.read(buf),
167+
#[cfg(unix)]
130168
Self::Fifo(f) => f.read(buf),
131169
}
132170
}
@@ -149,7 +187,10 @@ struct Input<'a> {
149187
impl<'a> Input<'a> {
150188
/// Instantiate this struct with stdin as a source.
151189
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
190+
#[cfg(not(unix))]
152191
let mut src = Source::Stdin(io::stdin());
192+
#[cfg(unix)]
193+
let mut src = Source::stdin_as_file();
153194
if settings.skip > 0 {
154195
src.skip(settings.skip)?;
155196
}

0 commit comments

Comments
 (0)