Skip to content

Commit 905ad3f

Browse files
committed
add std::os::fd::CommandExt::fd
1 parent e8a792d commit 905ad3f

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

library/std/src/os/fd/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ mod raw;
1212
// `OwnedFd`, `AsFd`, etc.
1313
mod owned;
1414

15+
// `CommandExt`, etc.
16+
#[cfg(unix)]
17+
#[unstable(feature = "command_pass_fds", issue = "144989")]
18+
pub mod process;
19+
1520
// Implementations for `AsRawFd` etc. for network types.
1621
#[cfg(not(target_os = "trusty"))]
1722
mod net;

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use libc::{F_GETFD, F_SETFD, FD_CLOEXEC};
2+
3+
use crate::os::fd::raw::AsRawFd;
4+
use crate::os::fd::{OwnedFd, RawFd};
5+
use crate::process::Command;
6+
use crate::sealed::Sealed;
7+
use crate::sys::{cvt, cvt_r};
8+
use crate::sys_common::AsInnerMut;
9+
10+
/// Extensions to the [`crate::process::Command`] builder for Unix and WASI, platforms that support file
11+
/// descriptors.
12+
///
13+
/// This trait is sealed: it cannot be implemented outside the standard library.
14+
/// This is so that future additional methods are not breaking changes.
15+
#[unstable(feature = "command_pass_fds", issue = "144989")]
16+
pub trait CommandExt: Sealed {
17+
/// Pass a file descriptor to a child process.
18+
///
19+
/// Getting this right is tricky. It is recommended to provide further information to the child
20+
/// process by some other mechanism. This could be an argument confirming file descriptors that
21+
/// the child can use, device/inode numbers to allow for sanity checks, or something similar.
22+
///
23+
/// If `new_fd` is an open file descriptor and closing it would produce one or more errors,
24+
/// those errors will be lost when this function is called. See
25+
/// [`man 2 dup`](https://www.man7.org/linux/man-pages/man2/dup.2.html#NOTES) for more information.
26+
///
27+
/// If this method is called multiple times with the same `new_fd`, all but one file descriptor
28+
/// will be lost.
29+
///
30+
/// ```
31+
/// #![feature(command_pass_fds)]
32+
///
33+
/// use std::process::{Command, Stdio};
34+
/// use std::os::fd::process::CommandExt;
35+
/// use std::io::{self, Write};
36+
///
37+
/// fn main() -> io::Result<()> {
38+
/// let (pipe_reader, mut pipe_writer) = io::pipe()?;
39+
///
40+
/// let fd_num = 123;
41+
///
42+
/// let mut cmd = Command::new("cat");
43+
/// cmd.arg(format!("/dev/fd/{fd_num}")).stdout(Stdio::piped()).fd(fd_num, pipe_reader);
44+
///
45+
/// let mut child = cmd.spawn()?;
46+
/// let mut stdout = child.stdout.take().unwrap();
47+
///
48+
/// pipe_writer.write_all(b"Hello, world!")?;
49+
/// drop(pipe_writer);
50+
///
51+
/// child.wait()?;
52+
/// assert_eq!(io::read_to_string(&mut stdout)?, "Hello, world!");
53+
///
54+
/// Ok(())
55+
/// }
56+
/// ```
57+
///
58+
/// ```
59+
/// #![feature(command_pass_fds)]
60+
///
61+
/// use std::process::{Command, Stdio};
62+
/// use std::os::fd::process::CommandExt;
63+
/// use std::io::{self, Write};
64+
///
65+
/// fn main() -> io::Result<()> {
66+
/// let (pipe_reader1, mut pipe_writer1) = io::pipe()?;
67+
/// let (pipe_reader2, mut pipe_writer2) = io::pipe()?;
68+
///
69+
/// let fd_num = 123;
70+
///
71+
/// let mut cmd = Command::new("cat");
72+
/// cmd.arg(format!("/dev/fd/{fd_num}"))
73+
/// .stdout(Stdio::piped())
74+
/// .fd(fd_num, pipe_reader1)
75+
/// .fd(fd_num, pipe_reader2);
76+
///
77+
/// let mut child = cmd.spawn()?;
78+
/// let mut stdout = child.stdout.take().unwrap();
79+
///
80+
/// pipe_writer1.write_all(b"Hello from pipe 1!")?;
81+
/// drop(pipe_writer1);
82+
///
83+
/// pipe_writer2.write_all(b"Hello from pipe 2!")?;
84+
/// drop(pipe_writer2);
85+
///
86+
/// child.wait()?;
87+
/// assert_eq!(io::read_to_string(&mut stdout)?, "Hello from pipe 2!");
88+
///
89+
/// Ok(())
90+
/// }
91+
/// ```
92+
fn fd(&mut self, new_fd: RawFd, old_fd: impl Into<OwnedFd>) -> &mut Self;
93+
}
94+
95+
#[unstable(feature = "command_pass_fds", issue = "144989")]
96+
impl CommandExt for Command {
97+
fn fd(&mut self, new_fd: RawFd, old_fd: impl Into<OwnedFd>) -> &mut Self {
98+
let old = old_fd.into();
99+
unsafe {
100+
self.as_inner_mut().pre_exec(Box::new(move || {
101+
cvt_r(|| libc::dup2(old.as_raw_fd(), new_fd))?;
102+
let flags = cvt(libc::fcntl(new_fd, F_GETFD))?;
103+
cvt(libc::fcntl(new_fd, F_SETFD, flags & !FD_CLOEXEC))?;
104+
cvt_r(|| libc::close(old.as_raw_fd()))?;
105+
Ok(())
106+
}))
107+
}
108+
109+
self
110+
}
111+
}

library/std/tests/fd_passing.rs

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

0 commit comments

Comments
 (0)