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