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 match spec.env_mode {
45 EnvMode::Replace => {
47 command.env_clear();
48 }
49 EnvMode::InheritClean | EnvMode::Patch => {
56 for (key, _) in std::env::vars_os() {
57 if let Some(name) = key.to_str() {
58 if super::handle::is_sensitive_env_name(name) {
59 command.env_remove(&key);
60 }
61 }
62 }
63 }
64 }
65 for (key, value) in &spec.env {
66 command.env(key, value);
67 }
68
69 for (key, value) in process_sandbox::active_workspace_tmpdir_env() {
79 if spec.env.contains_key(&key) {
80 continue;
81 }
82 command.env(key, value);
83 }
84
85 if spec.configure_process_group {
86 configure_background_process_group(&mut command);
87 }
88
89 command.stdout(Stdio::piped());
90 command.stderr(Stdio::piped());
91 command.stdin(if spec.use_stdin {
92 Stdio::piped()
93 } else {
94 Stdio::null()
95 });
96
97 let child = command.spawn().map_err(|e| {
98 if let Some(violation) = process_sandbox::process_spawn_error(&e) {
99 return ProcessError::SandboxSpawn(format!("{violation:?}"));
100 }
101 ProcessError::Spawn(format!("{e}"))
102 })?;
103
104 let pid = child.id();
105 let pgid = child_process_group_id(pid);
106 let killer: Arc<dyn ProcessKiller> = Arc::new(RealKiller { pid });
107
108 Ok(Box::new(RealProcess {
109 pid,
110 pgid,
111 killer,
112 child: Some(child),
113 stdin: None,
114 stdout: None,
115 stderr: None,
116 stdin_taken: false,
117 stdout_taken: false,
118 stderr_taken: false,
119 }))
120 }
121}
122
123struct RealProcess {
124 pid: u32,
125 pgid: Option<u32>,
126 killer: Arc<dyn ProcessKiller>,
127 child: Option<Child>,
128 stdin: Option<ChildStdin>,
129 stdout: Option<ChildStdout>,
130 stderr: Option<ChildStderr>,
131 stdin_taken: bool,
132 stdout_taken: bool,
133 stderr_taken: bool,
134}
135
136impl RealProcess {
137 fn ensure_pipes_taken(&mut self) {
138 if let Some(child) = self.child.as_mut() {
139 if self.stdin.is_none() && !self.stdin_taken {
140 self.stdin = child.stdin.take();
141 }
142 if self.stdout.is_none() && !self.stdout_taken {
143 self.stdout = child.stdout.take();
144 }
145 if self.stderr.is_none() && !self.stderr_taken {
146 self.stderr = child.stderr.take();
147 }
148 }
149 }
150}
151
152impl ProcessHandle for RealProcess {
153 fn pid(&self) -> Option<u32> {
154 Some(self.pid)
155 }
156
157 fn process_group_id(&self) -> Option<u32> {
158 self.pgid
159 }
160
161 fn killer(&self) -> Arc<dyn ProcessKiller> {
162 Arc::clone(&self.killer)
163 }
164
165 fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>> {
166 self.ensure_pipes_taken();
167 self.stdin_taken = true;
168 self.stdin
169 .take()
170 .map(|s| Box::new(s) as Box<dyn Write + Send>)
171 }
172
173 fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>> {
174 self.ensure_pipes_taken();
175 self.stdout_taken = true;
176 self.stdout
177 .take()
178 .map(|s| Box::new(s) as Box<dyn Read + Send>)
179 }
180
181 fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>> {
182 self.ensure_pipes_taken();
183 self.stderr_taken = true;
184 self.stderr
185 .take()
186 .map(|s| Box::new(s) as Box<dyn Read + Send>)
187 }
188
189 fn wait_with_timeout(
190 &mut self,
191 timeout: Option<Duration>,
192 ) -> io::Result<(Option<ExitStatus>, bool)> {
193 let Some(child) = self.child.as_mut() else {
194 return Ok((None, false));
195 };
196 let Some(timeout) = timeout else {
197 let status = child.wait()?;
198 return Ok((Some(decode_status(status)), false));
199 };
200 let start = Instant::now();
201 loop {
202 match child.try_wait()? {
203 Some(status) => return Ok((Some(decode_status(status)), false)),
204 None => {
205 let elapsed = start.elapsed();
206 if elapsed >= timeout {
207 self.killer.kill();
215 let _ = child.kill();
216 let _ = child.wait();
217 return Ok((None, true));
218 }
219 let remaining = timeout.checked_sub(elapsed).unwrap_or_default();
220 thread::sleep(remaining.min(Duration::from_millis(20)));
221 }
222 }
223 }
224 }
225
226 fn wait(&mut self) -> io::Result<ExitStatus> {
227 let child = self
228 .child
229 .as_mut()
230 .ok_or_else(|| io::Error::other("child already reaped"))?;
231 let status = child.wait()?;
232 Ok(decode_status(status))
233 }
234}
235
236struct RealKiller {
237 pid: u32,
238}
239
240impl ProcessKiller for RealKiller {
241 fn kill(&self) {
242 kill_pid_or_group(self.pid);
243 }
244}
245
246#[cfg(unix)]
247fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
248 use std::os::unix::process::ExitStatusExt;
249 if let Some(code) = status.code() {
250 ExitStatus::from_code(code)
251 } else if let Some(sig) = status.signal() {
252 ExitStatus::from_signal(sig)
253 } else {
254 ExitStatus {
255 code: None,
256 signal: None,
257 }
258 }
259}
260
261#[cfg(not(unix))]
262fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
263 ExitStatus::from_code(status.code().unwrap_or(-1))
264}
265
266pub(crate) fn child_process_group_id(pid: u32) -> Option<u32> {
267 #[cfg(unix)]
268 {
269 extern "C" {
270 fn getpgid(pid: i32) -> i32;
271 }
272 let pgid = unsafe { getpgid(pid as i32) };
273 if pgid > 0 {
274 Some(pgid as u32)
275 } else {
276 None
277 }
278 }
279 #[cfg(not(unix))]
280 {
281 Some(pid)
282 }
283}
284
285pub(crate) fn configure_background_process_group(command: &mut std::process::Command) {
286 #[cfg(unix)]
287 unsafe {
288 use std::os::unix::process::CommandExt;
289 command.pre_exec(|| {
290 extern "C" {
291 fn setpgid(pid: i32, pgid: i32) -> i32;
292 }
293 if setpgid(0, 0) == -1 {
294 return Err(std::io::Error::last_os_error());
295 }
296 Ok(())
297 });
298 }
299 #[cfg(not(unix))]
300 {
301 let _ = command;
302 }
303}
304
305pub(crate) fn kill_pid_or_group(pid: u32) {
309 #[cfg(unix)]
310 {
311 extern "C" {
314 fn kill(pid: i32, sig: i32) -> i32;
315 }
316 unsafe {
317 kill(-(pid as i32), 9);
318 kill(pid as i32, 9);
319 }
320 }
321 #[cfg(not(unix))]
322 {
323 let _ = pid;
324 }
325}