@@ -8,6 +8,7 @@ use crate::ffi::OsStr;
88use crate :: os:: unix:: io:: { AsFd , AsRawFd , BorrowedFd , FromRawFd , IntoRawFd , OwnedFd , RawFd } ;
99use crate :: path:: Path ;
1010use crate :: sealed:: Sealed ;
11+ use crate :: sys:: { cvt, cvt_r} ;
1112use crate :: sys_common:: { AsInner , AsInnerMut , FromInner , IntoInner } ;
1213use crate :: { io, process, sys} ;
1314
@@ -213,6 +214,84 @@ pub trait CommandExt: Sealed {
213214
214215 #[ unstable( feature = "process_setsid" , issue = "105376" ) ]
215216 fn setsid ( & mut self , setsid : bool ) -> & mut process:: Command ;
217+
218+ /// Pass a file descriptor to a child process.
219+ ///
220+ /// Getting this right is tricky. It is recommended to provide further information to the child
221+ /// process by some other mechanism. This could be an argument confirming file descriptors that
222+ /// the child can use, device/inode numbers to allow for sanity checks, or something similar.
223+ ///
224+ /// If `new_fd` is an open file descriptor and closing it would produce one or more errors,
225+ /// those errors will be lost when this function is called. See
226+ /// [`man 2 dup`](https://www.man7.org/linux/man-pages/man2/dup.2.html#NOTES) for more information.
227+ ///
228+ /// ```
229+ /// #![feature(command_pass_fds)]
230+ ///
231+ /// use std::process::{Command, Stdio};
232+ /// use std::os::fd::process::CommandExt;
233+ /// use std::io::{self, Write};
234+ ///
235+ /// # fn main() -> io::Result<()> {
236+ /// let (pipe_reader, mut pipe_writer) = io::pipe()?;
237+ ///
238+ /// let fd_num = 123;
239+ ///
240+ /// let mut cmd = Command::new("cat");
241+ /// cmd.arg(format!("/dev/fd/{fd_num}")).stdout(Stdio::piped()).fd(fd_num, pipe_reader);
242+ ///
243+ /// let mut child = cmd.spawn()?;
244+ /// let mut stdout = child.stdout.take().unwrap();
245+ ///
246+ /// pipe_writer.write_all(b"Hello, world!")?;
247+ /// drop(pipe_writer);
248+ ///
249+ /// child.wait()?;
250+ /// assert_eq!(io::read_to_string(&mut stdout)?, "Hello, world!");
251+ ///
252+ /// # Ok(())
253+ /// # }
254+ /// ```
255+ ///
256+ /// If this method is called multiple times with the same `new_fd`, all but one file descriptor
257+ /// will be lost.
258+ ///
259+ /// ```
260+ /// #![feature(command_pass_fds)]
261+ ///
262+ /// use std::process::{Command, Stdio};
263+ /// use std::os::fd::process::CommandExt;
264+ /// use std::io::{self, Write};
265+ ///
266+ /// # fn main() -> io::Result<()> {
267+ /// let (pipe_reader1, mut pipe_writer1) = io::pipe()?;
268+ /// let (pipe_reader2, mut pipe_writer2) = io::pipe()?;
269+ ///
270+ /// let fd_num = 123;
271+ ///
272+ /// let mut cmd = Command::new("cat");
273+ /// cmd.arg(format!("/dev/fd/{fd_num}"))
274+ /// .stdout(Stdio::piped())
275+ /// .fd(fd_num, pipe_reader1)
276+ /// .fd(fd_num, pipe_reader2);
277+ ///
278+ /// let mut child = cmd.spawn()?;
279+ /// let mut stdout = child.stdout.take().unwrap();
280+ ///
281+ /// pipe_writer1.write_all(b"Hello from pipe 1!")?;
282+ /// drop(pipe_writer1);
283+ ///
284+ /// pipe_writer2.write_all(b"Hello from pipe 2!")?;
285+ /// drop(pipe_writer2);
286+ ///
287+ /// child.wait()?;
288+ /// assert_eq!(io::read_to_string(&mut stdout)?, "Hello from pipe 2!");
289+ ///
290+ /// # Ok(())
291+ /// # }
292+ /// ```
293+ #[ unstable( feature = "command_pass_fds" , issue = "144989" ) ]
294+ fn fd ( & mut self , new_fd : RawFd , old_fd : impl Into < OwnedFd > ) -> & mut Self ;
216295}
217296
218297#[ stable( feature = "rust1" , since = "1.0.0" ) ]
@@ -268,6 +347,19 @@ impl CommandExt for process::Command {
268347 self . as_inner_mut ( ) . setsid ( setsid) ;
269348 self
270349 }
350+
351+ fn fd ( & mut self , new_fd : RawFd , old_fd : impl Into < OwnedFd > ) -> & mut Self {
352+ let old = old_fd. into ( ) ;
353+ unsafe {
354+ self . pre_exec ( move || {
355+ cvt_r ( || libc:: dup2 ( old. as_raw_fd ( ) , new_fd) ) ?;
356+ let flags = cvt ( libc:: fcntl ( new_fd, libc:: F_GETFD ) ) ?;
357+ cvt ( libc:: fcntl ( new_fd, libc:: F_SETFD , flags & !libc:: FD_CLOEXEC ) ) ?;
358+ cvt_r ( || libc:: close ( old. as_raw_fd ( ) ) ) ?;
359+ Ok ( ( ) )
360+ } )
361+ }
362+ }
271363}
272364
273365/// Unix-specific extensions to [`process::ExitStatus`] and
0 commit comments