Skip to content

Commit fd8c770

Browse files
committed
add std::os::unix::process::CommandExt::fd
1 parent e8a792d commit fd8c770

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

library/std/src/os/unix/process.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
55
#![stable(feature = "rust1", since = "1.0.0")]
66

7+
use libc::{F_GETFD, F_SETFD, FD_CLOEXEC};
8+
79
use crate::ffi::OsStr;
810
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
911
use crate::path::Path;
1012
use crate::sealed::Sealed;
13+
use crate::sys::{cvt, cvt_r};
1114
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
1215
use crate::{io, process, sys};
1316

@@ -213,6 +216,84 @@ pub trait CommandExt: Sealed {
213216

214217
#[unstable(feature = "process_setsid", issue = "105376")]
215218
fn setsid(&mut self, setsid: bool) -> &mut process::Command;
219+
220+
/// Pass a file descriptor to a child process.
221+
///
222+
/// Getting this right is tricky. It is recommended to provide further information to the child
223+
/// process by some other mechanism. This could be an argument confirming file descriptors that
224+
/// the child can use, device/inode numbers to allow for sanity checks, or something similar.
225+
///
226+
/// If `new_fd` is an open file descriptor and closing it would produce one or more errors,
227+
/// those errors will be lost when this function is called. See
228+
/// [`man 2 dup`](https://www.man7.org/linux/man-pages/man2/dup.2.html#NOTES) for more information.
229+
///
230+
/// If this method is called multiple times with the same `new_fd`, all but one file descriptor
231+
/// will be lost.
232+
///
233+
/// ```
234+
/// #![feature(command_pass_fds)]
235+
///
236+
/// use std::process::{Command, Stdio};
237+
/// use std::os::fd::process::CommandExt;
238+
/// use std::io::{self, Write};
239+
///
240+
/// fn main() -> io::Result<()> {
241+
/// let (pipe_reader, mut pipe_writer) = io::pipe()?;
242+
///
243+
/// let fd_num = 123;
244+
///
245+
/// let mut cmd = Command::new("cat");
246+
/// cmd.arg(format!("/dev/fd/{fd_num}")).stdout(Stdio::piped()).fd(fd_num, pipe_reader);
247+
///
248+
/// let mut child = cmd.spawn()?;
249+
/// let mut stdout = child.stdout.take().unwrap();
250+
///
251+
/// pipe_writer.write_all(b"Hello, world!")?;
252+
/// drop(pipe_writer);
253+
///
254+
/// child.wait()?;
255+
/// assert_eq!(io::read_to_string(&mut stdout)?, "Hello, world!");
256+
///
257+
/// Ok(())
258+
/// }
259+
/// ```
260+
///
261+
/// ```
262+
/// #![feature(command_pass_fds)]
263+
///
264+
/// use std::process::{Command, Stdio};
265+
/// use std::os::fd::process::CommandExt;
266+
/// use std::io::{self, Write};
267+
///
268+
/// fn main() -> io::Result<()> {
269+
/// let (pipe_reader1, mut pipe_writer1) = io::pipe()?;
270+
/// let (pipe_reader2, mut pipe_writer2) = io::pipe()?;
271+
///
272+
/// let fd_num = 123;
273+
///
274+
/// let mut cmd = Command::new("cat");
275+
/// cmd.arg(format!("/dev/fd/{fd_num}"))
276+
/// .stdout(Stdio::piped())
277+
/// .fd(fd_num, pipe_reader1)
278+
/// .fd(fd_num, pipe_reader2);
279+
///
280+
/// let mut child = cmd.spawn()?;
281+
/// let mut stdout = child.stdout.take().unwrap();
282+
///
283+
/// pipe_writer1.write_all(b"Hello from pipe 1!")?;
284+
/// drop(pipe_writer1);
285+
///
286+
/// pipe_writer2.write_all(b"Hello from pipe 2!")?;
287+
/// drop(pipe_writer2);
288+
///
289+
/// child.wait()?;
290+
/// assert_eq!(io::read_to_string(&mut stdout)?, "Hello from pipe 2!");
291+
///
292+
/// Ok(())
293+
/// }
294+
/// ```
295+
#[unstable(feature = "command_pass_fds", issue = "144989")]
296+
fn fd(&mut self, new_fd: RawFd, old_fd: impl Into<OwnedFd>) -> &mut Self;
216297
}
217298

218299
#[stable(feature = "rust1", since = "1.0.0")]
@@ -268,6 +349,21 @@ impl CommandExt for process::Command {
268349
self.as_inner_mut().setsid(setsid);
269350
self
270351
}
352+
353+
fn fd(&mut self, new_fd: RawFd, old_fd: impl Into<OwnedFd>) -> &mut Self {
354+
let old = old_fd.into();
355+
unsafe {
356+
self.as_inner_mut().pre_exec(Box::new(move || {
357+
cvt_r(|| libc::dup2(old.as_raw_fd(), new_fd))?;
358+
let flags = cvt(libc::fcntl(new_fd, F_GETFD))?;
359+
cvt(libc::fcntl(new_fd, F_SETFD, flags & !FD_CLOEXEC))?;
360+
cvt_r(|| libc::close(old.as_raw_fd()))?;
361+
Ok(())
362+
}))
363+
}
364+
365+
self
366+
}
271367
}
272368

273369
/// Unix-specific extensions to [`process::ExitStatus`] and

library/std/tests/fd_passing.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#![feature(command_pass_fds)]
2+
#![cfg(unix)]
3+
4+
use std::io::{self, Write};
5+
use std::os::unix::io::AsRawFd;
6+
use std::os::unix::process::CommandExt;
7+
use std::process::{Command, Stdio};
8+
9+
use libc::{F_GETFD, fcntl};
10+
11+
#[test]
12+
fn fd_test_stdin() {
13+
let (pipe_reader, mut pipe_writer) = io::pipe().unwrap();
14+
15+
let fd_num = 0;
16+
17+
let mut cmd = Command::new("cat");
18+
cmd.stdout(Stdio::piped()).fd(fd_num, pipe_reader);
19+
20+
let mut child = cmd.spawn().unwrap();
21+
let mut stdout = child.stdout.take().unwrap();
22+
23+
pipe_writer.write_all(b"Hello, world!").unwrap();
24+
drop(pipe_writer);
25+
26+
child.wait().unwrap();
27+
assert_eq!(io::read_to_string(&mut stdout).unwrap(), "Hello, world!");
28+
}
29+
30+
#[test]
31+
fn fd_test_swap() {
32+
let (pipe_reader1, mut pipe_writer1) = io::pipe().unwrap();
33+
let (pipe_reader2, mut pipe_writer2) = io::pipe().unwrap();
34+
35+
let num1 = pipe_reader1.as_raw_fd();
36+
let num2 = pipe_reader2.as_raw_fd();
37+
38+
let mut cmd = Command::new("cat");
39+
cmd.arg(format!("/dev/fd/{}", num1))
40+
.stdout(Stdio::piped())
41+
.fd(num2, pipe_reader1)
42+
.fd(num1, pipe_reader2);
43+
44+
let mut child = cmd.spawn().unwrap();
45+
let mut stdout = child.stdout.take().unwrap();
46+
47+
pipe_writer1.write_all(b"Hello from pipe 1!").unwrap();
48+
drop(pipe_writer1);
49+
50+
pipe_writer2.write_all(b"Hello from pipe 2!").unwrap();
51+
drop(pipe_writer2);
52+
53+
child.wait().unwrap();
54+
assert_eq!(io::read_to_string(&mut stdout).unwrap(), "Hello from pipe 1!");
55+
}
56+
57+
#[test]
58+
fn fd_test_close_time() {
59+
let (pipe_reader, mut pipe_writer) = io::pipe().unwrap();
60+
61+
let fd_num = 123;
62+
63+
let original = pipe_reader.as_raw_fd();
64+
65+
let mut cmd = Command::new("cat");
66+
cmd.arg(format!("/dev/fd/{fd_num}")).stdout(Stdio::piped()).fd(fd_num, pipe_reader);
67+
68+
assert_ne!(unsafe { fcntl(original, F_GETFD) }, -1);
69+
70+
let mut child = cmd.spawn().unwrap();
71+
let mut stdout = child.stdout.take().unwrap();
72+
73+
pipe_writer.write_all(b"Hello, world!").unwrap();
74+
drop(pipe_writer);
75+
76+
child.wait().unwrap();
77+
assert_eq!(io::read_to_string(&mut stdout).unwrap(), "Hello, world!");
78+
assert_eq!(unsafe { fcntl(original, F_GETFD) }, -1);
79+
}

0 commit comments

Comments
 (0)