harn_hostlib/process/
real.rs1use std::io::{self, Read, Write};
5use std::process::{Child, ChildStderr, ChildStdin, ChildStdout, Stdio};
6use std::sync::{Arc, LazyLock};
7use std::thread;
8use std::time::{Duration, Instant};
9
10use harn_vm::process_sandbox;
11
12use super::handle::{
13 EnvMode, ExitStatus, ProcessError, ProcessHandle, ProcessKiller, ProcessSpawner, SpawnSpec,
14};
15
16pub struct RealSpawner;
18
19static REAL_SPAWNER: LazyLock<Arc<dyn ProcessSpawner>> =
20 LazyLock::new(|| Arc::new(RealSpawner) as Arc<dyn ProcessSpawner>);
21
22pub fn default_spawner() -> Arc<dyn ProcessSpawner> {
24 Arc::clone(&REAL_SPAWNER)
25}
26
27impl ProcessSpawner for RealSpawner {
28 fn spawn(&self, spec: SpawnSpec) -> Result<Box<dyn ProcessHandle>, ProcessError> {
29 if spec.program.is_empty() {
30 return Err(ProcessError::InvalidArgv(
31 "first element of argv must be a non-empty program name".to_string(),
32 ));
33 }
34
35 let mut command = process_sandbox::std_command_for(&spec.program, &spec.args)
36 .map_err(|e| ProcessError::SandboxSetup(format!("{e:?}")))?;
37
38 if let Some(cwd) = spec.cwd.as_ref() {
39 process_sandbox::enforce_process_cwd(cwd)
40 .map_err(|e| ProcessError::SandboxCwd(format!("{e:?}")))?;
41 command.current_dir(cwd);
42 }
43
44 if matches!(spec.env_mode, EnvMode::Replace) {
45 command.env_clear();
46 }
47 for (key, value) in &spec.env {
48 command.env(key, value);
49 }
50
51 if spec.configure_process_group {
52 configure_background_process_group(&mut command);
53 }
54
55 command.stdout(Stdio::piped());
56 command.stderr(Stdio::piped());
57 command.stdin(if spec.use_stdin {
58 Stdio::piped()
59 } else {
60 Stdio::null()
61 });
62
63 let child = command.spawn().map_err(|e| {
64 if let Some(violation) = process_sandbox::process_spawn_error(&e) {
65 return ProcessError::SandboxSpawn(format!("{violation:?}"));
66 }
67 ProcessError::Spawn(format!("{e}"))
68 })?;
69
70 let pid = child.id();
71 let pgid = child_process_group_id(pid);
72 let killer: Arc<dyn ProcessKiller> = Arc::new(RealKiller { pid });
73
74 Ok(Box::new(RealProcess {
75 pid,
76 pgid,
77 killer,
78 child: Some(child),
79 stdin: None,
80 stdout: None,
81 stderr: None,
82 stdin_taken: false,
83 stdout_taken: false,
84 stderr_taken: false,
85 }))
86 }
87}
88
89struct RealProcess {
90 pid: u32,
91 pgid: Option<u32>,
92 killer: Arc<dyn ProcessKiller>,
93 child: Option<Child>,
94 stdin: Option<ChildStdin>,
95 stdout: Option<ChildStdout>,
96 stderr: Option<ChildStderr>,
97 stdin_taken: bool,
98 stdout_taken: bool,
99 stderr_taken: bool,
100}
101
102impl RealProcess {
103 fn ensure_pipes_taken(&mut self) {
104 if let Some(child) = self.child.as_mut() {
105 if self.stdin.is_none() && !self.stdin_taken {
106 self.stdin = child.stdin.take();
107 }
108 if self.stdout.is_none() && !self.stdout_taken {
109 self.stdout = child.stdout.take();
110 }
111 if self.stderr.is_none() && !self.stderr_taken {
112 self.stderr = child.stderr.take();
113 }
114 }
115 }
116}
117
118impl ProcessHandle for RealProcess {
119 fn pid(&self) -> Option<u32> {
120 Some(self.pid)
121 }
122
123 fn process_group_id(&self) -> Option<u32> {
124 self.pgid
125 }
126
127 fn killer(&self) -> Arc<dyn ProcessKiller> {
128 Arc::clone(&self.killer)
129 }
130
131 fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>> {
132 self.ensure_pipes_taken();
133 self.stdin_taken = true;
134 self.stdin
135 .take()
136 .map(|s| Box::new(s) as Box<dyn Write + Send>)
137 }
138
139 fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>> {
140 self.ensure_pipes_taken();
141 self.stdout_taken = true;
142 self.stdout
143 .take()
144 .map(|s| Box::new(s) as Box<dyn Read + Send>)
145 }
146
147 fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>> {
148 self.ensure_pipes_taken();
149 self.stderr_taken = true;
150 self.stderr
151 .take()
152 .map(|s| Box::new(s) as Box<dyn Read + Send>)
153 }
154
155 fn wait_with_timeout(
156 &mut self,
157 timeout: Option<Duration>,
158 ) -> io::Result<(Option<ExitStatus>, bool)> {
159 let Some(child) = self.child.as_mut() else {
160 return Ok((None, false));
161 };
162 let Some(timeout) = timeout else {
163 let status = child.wait()?;
164 return Ok((Some(decode_status(status)), false));
165 };
166 let start = Instant::now();
167 loop {
168 match child.try_wait()? {
169 Some(status) => return Ok((Some(decode_status(status)), false)),
170 None => {
171 let elapsed = start.elapsed();
172 if elapsed >= timeout {
173 self.killer.kill();
181 let _ = child.kill();
182 let _ = child.wait();
183 return Ok((None, true));
184 }
185 let remaining = timeout.checked_sub(elapsed).unwrap_or_default();
186 thread::sleep(remaining.min(Duration::from_millis(20)));
187 }
188 }
189 }
190 }
191
192 fn wait(&mut self) -> io::Result<ExitStatus> {
193 let child = self
194 .child
195 .as_mut()
196 .ok_or_else(|| io::Error::other("child already reaped"))?;
197 let status = child.wait()?;
198 Ok(decode_status(status))
199 }
200}
201
202struct RealKiller {
203 pid: u32,
204}
205
206impl ProcessKiller for RealKiller {
207 fn kill(&self) {
208 kill_pid_or_group(self.pid);
209 }
210}
211
212#[cfg(unix)]
213fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
214 use std::os::unix::process::ExitStatusExt;
215 if let Some(code) = status.code() {
216 ExitStatus::from_code(code)
217 } else if let Some(sig) = status.signal() {
218 ExitStatus::from_signal(sig)
219 } else {
220 ExitStatus {
221 code: None,
222 signal: None,
223 }
224 }
225}
226
227#[cfg(not(unix))]
228fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
229 ExitStatus::from_code(status.code().unwrap_or(-1))
230}
231
232pub(crate) fn child_process_group_id(pid: u32) -> Option<u32> {
233 #[cfg(unix)]
234 {
235 extern "C" {
236 fn getpgid(pid: i32) -> i32;
237 }
238 let pgid = unsafe { getpgid(pid as i32) };
239 if pgid > 0 {
240 Some(pgid as u32)
241 } else {
242 None
243 }
244 }
245 #[cfg(not(unix))]
246 {
247 Some(pid)
248 }
249}
250
251pub(crate) fn configure_background_process_group(command: &mut std::process::Command) {
252 #[cfg(unix)]
253 unsafe {
254 use std::os::unix::process::CommandExt;
255 command.pre_exec(|| {
256 extern "C" {
257 fn setpgid(pid: i32, pgid: i32) -> i32;
258 }
259 if setpgid(0, 0) == -1 {
260 return Err(std::io::Error::last_os_error());
261 }
262 Ok(())
263 });
264 }
265 #[cfg(not(unix))]
266 {
267 let _ = command;
268 }
269}
270
271pub(crate) fn kill_pid_or_group(pid: u32) {
275 #[cfg(unix)]
276 {
277 extern "C" {
280 fn kill(pid: i32, sig: i32) -> i32;
281 }
282 unsafe {
283 kill(-(pid as i32), 9);
284 kill(pid as i32, 9);
285 }
286 }
287 #[cfg(not(unix))]
288 {
289 let _ = pid;
290 }
291}