1use std::ffi::OsString;
2use std::io::{self, BufRead, IoSlice, IoSliceMut, Write};
3use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
4use std::os::unix::net::UnixStream;
5use std::os::unix::process::CommandExt;
6use std::process::{Command, ExitStatus, Output};
7use std::time::Duration;
8
9use nix::sys::signal::{Signal, kill};
10use nix::sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, recvmsg, sendmsg};
11use nix::unistd::Pid;
12
13pub fn args<S: AsRef<str>>(items: &[S]) -> Vec<OsString> {
15 items.iter().map(|s| OsString::from(s.as_ref())).collect()
16}
17
18pub fn exec_replace(bin: &str, args: &[OsString]) -> anyhow::Error {
23 let mut cmd = Command::new(bin);
24 cmd.args(args);
25 let err = cmd.exec();
26 anyhow::Error::from(err).context(format!("failed to exec {bin}"))
27}
28
29pub fn run_piped(bin: &str, args: &[OsString]) -> anyhow::Result<Output> {
31 let output = Command::new(bin)
32 .args(args)
33 .stdout(std::process::Stdio::piped())
34 .stderr(std::process::Stdio::piped())
35 .spawn()?
36 .wait_with_output()?;
37 Ok(output)
38}
39
40pub fn spawn_interactive(bin: &str, args: &[OsString]) -> anyhow::Result<ExitStatus> {
42 let status = Command::new(bin).args(args).status()?;
43 Ok(status)
44}
45
46pub fn run_with_log(
53 bin: &str,
54 args: &[OsString],
55 log: &mut std::fs::File,
56 mirror: bool,
57) -> anyhow::Result<ExitStatus> {
58 let mut child = Command::new(bin)
59 .args(args)
60 .stdout(std::process::Stdio::piped())
61 .stderr(std::process::Stdio::piped())
62 .spawn()
63 .map_err(|e| anyhow::Error::new(e).context(format!("failed to execute {bin}")))?;
64
65 let mut out = child.stdout.take().expect("stdout piped");
66 let mut err = child.stderr.take().expect("stderr piped");
67 let mut log_out = log.try_clone()?;
68 let mut log_err = log.try_clone()?;
69
70 let t_out = std::thread::spawn(move || tee_stream(&mut out, &mut log_out, false, mirror));
71 let t_err = std::thread::spawn(move || tee_stream(&mut err, &mut log_err, true, mirror));
72
73 let status = child.wait()?;
74 let _ = t_out.join();
75 let _ = t_err.join();
76 let _ = log.flush();
77 Ok(status)
78}
79
80fn tee_stream<R: io::Read, W: io::Write>(
82 src: &mut R,
83 dst: &mut W,
84 to_stderr: bool,
85 mirror: bool,
86) {
87 let mut reader = io::BufReader::new(src);
88 let mut line = String::new();
89 loop {
90 line.clear();
91 match reader.read_line(&mut line) {
92 Ok(0) | Err(_) => break,
93 Ok(_) => {
94 if dst.write_all(line.as_bytes()).is_err() {
95 break;
96 }
97 if mirror {
98 let _ = if to_stderr {
99 io::stderr().write_all(line.as_bytes())
100 } else {
101 io::stdout().write_all(line.as_bytes())
102 };
103 }
104 }
105 }
106 }
107}
108
109pub fn run_piped_timeout(
113 bin: &str,
114 args: &[OsString],
115 timeout: Duration,
116) -> anyhow::Result<Output> {
117 let child = Command::new(bin)
118 .args(args)
119 .stdout(std::process::Stdio::piped())
120 .stderr(std::process::Stdio::piped())
121 .spawn()?;
122 wait_child_timeout(child, timeout)
123}
124
125pub fn spawn_interactive_timeout(
131 bin: &str,
132 args: &[OsString],
133 timeout: Duration,
134) -> anyhow::Result<ExitStatus> {
135 let mut child = Command::new(bin).args(args).spawn()?;
136 let pid = Pid::from_raw(child.id().cast_signed());
137 let (tx, rx) = std::sync::mpsc::channel();
138 std::thread::spawn(move || {
139 if rx.recv_timeout(timeout).is_err() {
140 let _ = kill(pid, Signal::SIGTERM);
141 std::thread::sleep(Duration::from_secs(5));
142 let _ = kill(pid, Signal::SIGKILL);
143 }
144 });
145 let status = child.wait()?;
146 let _ = tx.send(());
147 Ok(status)
148}
149
150pub fn wait_child_timeout(child: std::process::Child, timeout: Duration) -> anyhow::Result<Output> {
152 let pid = Pid::from_raw(child.id().cast_signed());
153 let (tx, rx) = std::sync::mpsc::channel();
154 std::thread::spawn(move || {
155 if rx.recv_timeout(timeout).is_err() {
156 let _ = kill(pid, Signal::SIGKILL);
157 }
158 });
159 let output = child.wait_with_output()?;
160 let _ = tx.send(());
161 Ok(output)
162}
163
164pub fn open_pidfd(pid: i32) -> io::Result<OwnedFd> {
168 let pid = rustix::process::Pid::from_raw(pid)
169 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid PID"))?;
170 rustix::process::pidfd_open(pid, rustix::process::PidfdFlags::empty()).map_err(io::Error::from)
171}
172
173pub fn send_fd(stream: &UnixStream, fd: RawFd) -> io::Result<()> {
178 let raw_fd = stream.as_raw_fd();
179 let cmsg = ControlMessage::ScmRights(&[fd]);
180 let iov = [IoSlice::new(&[0u8])];
181 loop {
182 match sendmsg::<()>(raw_fd, &iov, &[cmsg], MsgFlags::empty(), None) {
183 Ok(_) => return Ok(()),
184 Err(nix::errno::Errno::EINTR) => {}
185 Err(e) => return Err(io::Error::from(e)),
186 }
187 }
188}
189
190pub fn adopt_scm_fd(raw: RawFd) -> OwnedFd {
197 #[allow(unsafe_code)]
202 unsafe {
203 OwnedFd::from_raw_fd(raw)
204 }
205}
206
207pub fn recv_fd(stream: &UnixStream) -> io::Result<Option<RawFd>> {
212 let raw_fd = stream.as_raw_fd();
213 let mut buf = [0u8; 1];
214 let mut iov = [IoSliceMut::new(&mut buf)];
215 let mut cmsg_buf = vec![0u8; 256];
216 let msg = loop {
217 match recvmsg::<()>(raw_fd, &mut iov, Some(&mut cmsg_buf), MsgFlags::empty()) {
218 Ok(m) => break m,
219 Err(nix::errno::Errno::EINTR) => {}
220 Err(e) => return Err(io::Error::from(e)),
221 }
222 };
223
224 if msg.bytes == 0 {
225 return Ok(None);
226 }
227
228 if let Ok(cmsgs) = msg.cmsgs() {
229 for cmsg in cmsgs {
230 if let ControlMessageOwned::ScmRights(fds) = cmsg {
231 if let Some(&fd) = fds.first() {
232 return Ok(Some(fd));
233 }
234 }
235 }
236 }
237 Ok(None)
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn args_builds_osstring_vec() {
246 let v = args(&["foo", "bar", "baz"]);
247 assert_eq!(v.len(), 3);
248 assert_eq!(v[0], "foo");
249 assert_eq!(v[1], "bar");
250 assert_eq!(v[2], "baz");
251 }
252
253 #[test]
254 fn args_accepts_mixed_types() {
255 let s = String::from("hello");
256 let v = args(&["a", &s, "c"]);
257 assert_eq!(v[1], "hello");
258 }
259}