1use std::ffi::OsStr;
2use std::io::Read;
3use std::os::unix::process::CommandExt;
4use std::process::{Child, Command, Stdio};
5
6use anyhow::{Result, bail};
7
8pub struct Process {
23 pid: u32,
24 child: Option<Child>,
25}
26
27impl Process {
28 pub fn spawn<S: AsRef<OsStr>>(cmd: S) -> Result<Self> {
29 let mut command = Self::build_command::<S, _, S>(cmd, []);
30 Self::spawn_child_process(&mut command)
31 }
32
33 pub fn spawn_with_args<S, I, T>(cmd: S, args: I) -> Result<Self>
34 where
35 T: AsRef<OsStr>,
36 I: IntoIterator<Item = T>,
37 S: AsRef<OsStr>,
38 {
39 let mut command = Self::build_command(cmd, args);
40 Self::spawn_child_process(&mut command)
41 }
42
43 fn build_command<S, I, T>(cmd: S, args: I) -> Command
44 where
45 T: AsRef<OsStr>,
46 I: IntoIterator<Item = T>,
47 S: AsRef<OsStr>,
48 {
49 let mut command = Command::new(cmd);
50 command.args(args);
51 command
52 }
53
54 fn spawn_child_process(cmd: &mut Command) -> Result<Self> {
55 let mut child = cmd
56 .stdin(Stdio::null())
57 .stdout(Stdio::piped())
58 .stderr(Stdio::piped());
59
60 unsafe {
61 child = child.pre_exec(|| {
62 libc::setsid();
64 Ok(())
65 });
66 }
67
68 let child_process = child.spawn()?;
69 let pid = child_process.id();
70
71 Ok(Self {
72 pid,
73 child: Some(child_process),
74 })
75 }
76
77 pub fn pid(&self) -> u32 {
79 self.pid
80 }
81
82 pub fn stdout(&mut self) -> Result<String> {
104 if let Some(ref mut child) = self.child
105 && let Some(ref mut stdout) = child.stdout
106 {
107 let mut output = String::new();
108 stdout.read_to_string(&mut output)?;
109 return Ok(output);
110 }
111 Ok(String::new())
112 }
113
114 pub fn stderr(&mut self) -> Result<String> {
136 if let Some(ref mut child) = self.child
137 && let Some(ref mut stderr) = child.stderr
138 {
139 let mut output = String::new();
140 stderr.read_to_string(&mut output)?;
141 return Ok(output);
142 }
143 Ok(String::new())
144 }
145
146 pub fn kill(&self) -> Result<()> {
148 match Command::new("kill").arg(self.pid().to_string()).status() {
149 Ok(status) => {
150 if status.success() {
151 return Ok(());
152 }
153
154 bail!("Failed to kill process with PID: {}", self.pid());
155 }
156 Err(e) => {
157 bail!("Error executing kill command: {}", e);
158 }
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use std::thread;
166 use std::time::Duration;
167
168 use super::*;
169
170 #[test]
171 fn spawn_process() {
172 let process = Process::spawn("sleep").expect("Failed to spawn process");
173 assert!(process.pid() > 0);
174 thread::sleep(Duration::from_millis(100));
175 let result = process.kill();
176 assert!(result.is_ok(), "Failed to kill the process");
177 }
178
179 #[test]
180 fn spawn_process_with_args() {
181 let process = Process::spawn_with_args("sleep", ["1"]).expect("Failed to spawn process");
182 assert!(process.pid() > 0);
183 thread::sleep(Duration::from_millis(100));
184 let result = process.kill();
185 assert!(result.is_ok(), "Failed to kill the process");
186 }
187
188 #[test]
189 fn spawn_process_with_args_different_types() {
190 let process = Process::spawn_with_args("sleep", [String::from("1")])
191 .expect("Failed to spawn process");
192 assert!(process.pid() > 0);
193 thread::sleep(Duration::from_millis(100));
194 let result = process.kill();
195 assert!(result.is_ok(), "Failed to kill the process");
196 }
197
198 #[test]
199 fn capture_stdout() {
200 let mut process =
201 Process::spawn_with_args("echo", ["hello world"]).expect("Failed to spawn process");
202 thread::sleep(Duration::from_millis(100));
203 let stdout = process.stdout().expect("Failed to read stdout");
204 assert_eq!(stdout.trim(), "hello world");
205 process.kill().expect("Failed to kill process");
206 }
207
208 #[test]
209 fn capture_stderr() {
210 let mut process = Process::spawn_with_args("sh", ["-c", "echo error message >&2"])
211 .expect("Failed to spawn process");
212 thread::sleep(Duration::from_millis(100));
213 let stderr = process.stderr().expect("Failed to read stderr");
214 assert_eq!(stderr.trim(), "error message");
215 process.kill().expect("Failed to kill process");
216 }
217
218 #[test]
219 fn capture_both_stdout_and_stderr() {
220 let mut process =
221 Process::spawn_with_args("sh", ["-c", "echo stdout message; echo stderr message >&2"])
222 .expect("Failed to spawn process");
223 thread::sleep(Duration::from_millis(100));
224 let stdout = process.stdout().expect("Failed to read stdout");
225 let stderr = process.stderr().expect("Failed to read stderr");
226 assert_eq!(stdout.trim(), "stdout message");
227 assert_eq!(stderr.trim(), "stderr message");
228 process.kill().expect("Failed to kill process");
229 }
230
231 #[test]
232 fn capture_empty_stdout() {
233 let mut process = Process::spawn_with_args("true", Vec::<String>::new())
234 .expect("Failed to spawn process");
235 thread::sleep(Duration::from_millis(100));
236 let stdout = process.stdout().expect("Failed to read stdout");
237 assert_eq!(stdout, "");
238 process.kill().ok(); }
240}