1use cap_std::fs::Dir;
9use cap_std::io_lifetimes;
10use cap_tempfile::cap_std;
11use io_lifetimes::OwnedFd;
12use rustix::fd::{AsFd, FromRawFd, IntoRawFd};
13use rustix::io::FdFlags;
14use std::os::fd::AsRawFd;
15use std::os::unix::process::CommandExt;
16use std::sync::Arc;
17
18pub trait CapStdExtCommandExt {
22 fn take_fd_n(&mut self, fd: Arc<OwnedFd>, target: i32) -> &mut Self;
24
25 fn cwd_dir(&mut self, dir: Dir) -> &mut Self;
27}
28
29#[allow(unsafe_code)]
30impl CapStdExtCommandExt for std::process::Command {
31 fn take_fd_n(&mut self, fd: Arc<OwnedFd>, target: i32) -> &mut Self {
32 unsafe {
33 self.pre_exec(move || {
34 let mut target = OwnedFd::from_raw_fd(target);
35 if target.as_raw_fd() == fd.as_raw_fd() {
38 let fl = rustix::io::fcntl_getfd(&target)?;
39 rustix::io::fcntl_setfd(&mut target, fl.difference(FdFlags::CLOEXEC))?;
40 } else {
41 rustix::io::dup2(&*fd, &mut target)?;
43 }
44 let _ = target.into_raw_fd();
46 Ok(())
47 });
48 }
49 self
50 }
51
52 fn cwd_dir(&mut self, dir: Dir) -> &mut Self {
53 unsafe {
54 self.pre_exec(move || {
55 rustix::process::fchdir(dir.as_fd())?;
56 Ok(())
57 });
58 }
59 self
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use std::sync::Arc;
67
68 #[test]
69 fn test_take_fdn() -> anyhow::Result<()> {
70 for i in 0..=1 {
72 let tempd = cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
73 let tempd_fd = Arc::new(tempd.as_fd().try_clone_to_owned()?);
74 let n = tempd_fd.as_raw_fd() + i;
75 let st = std::process::Command::new("ls")
76 .arg(format!("/proc/self/fd/{n}"))
77 .take_fd_n(tempd_fd, n)
78 .status()?;
79 assert!(st.success());
80 }
81 Ok(())
82 }
83}