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 deadline = Instant::now() + timeout;
167 loop {
168 match child.try_wait()? {
169 Some(status) => return Ok((Some(decode_status(status)), false)),
170 None => {
171 if Instant::now() >= deadline {
172 let _ = child.kill();
173 let _ = child.wait();
174 return Ok((None, true));
175 }
176 thread::sleep(Duration::from_millis(20));
179 }
180 }
181 }
182 }
183
184 fn wait(&mut self) -> io::Result<ExitStatus> {
185 let child = self
186 .child
187 .as_mut()
188 .ok_or_else(|| io::Error::other("child already reaped"))?;
189 let status = child.wait()?;
190 Ok(decode_status(status))
191 }
192}
193
194struct RealKiller {
195 pid: u32,
196}
197
198impl ProcessKiller for RealKiller {
199 fn kill(&self) {
200 kill_pid_or_group(self.pid);
201 }
202}
203
204#[cfg(unix)]
205fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
206 use std::os::unix::process::ExitStatusExt;
207 if let Some(code) = status.code() {
208 ExitStatus::from_code(code)
209 } else if let Some(sig) = status.signal() {
210 ExitStatus::from_signal(sig)
211 } else {
212 ExitStatus {
213 code: None,
214 signal: None,
215 }
216 }
217}
218
219#[cfg(not(unix))]
220fn decode_status(status: std::process::ExitStatus) -> ExitStatus {
221 ExitStatus::from_code(status.code().unwrap_or(-1))
222}
223
224pub(crate) fn child_process_group_id(pid: u32) -> Option<u32> {
225 #[cfg(unix)]
226 {
227 extern "C" {
228 fn getpgid(pid: i32) -> i32;
229 }
230 let pgid = unsafe { getpgid(pid as i32) };
231 if pgid > 0 {
232 Some(pgid as u32)
233 } else {
234 None
235 }
236 }
237 #[cfg(not(unix))]
238 {
239 Some(pid)
240 }
241}
242
243pub(crate) fn configure_background_process_group(command: &mut std::process::Command) {
244 #[cfg(unix)]
245 unsafe {
246 use std::os::unix::process::CommandExt;
247 command.pre_exec(|| {
248 extern "C" {
249 fn setpgid(pid: i32, pgid: i32) -> i32;
250 }
251 if setpgid(0, 0) == -1 {
252 return Err(std::io::Error::last_os_error());
253 }
254 Ok(())
255 });
256 }
257 #[cfg(not(unix))]
258 {
259 let _ = command;
260 }
261}
262
263pub(crate) fn kill_pid_or_group(pid: u32) {
267 #[cfg(unix)]
268 {
269 extern "C" {
272 fn kill(pid: i32, sig: i32) -> i32;
273 }
274 unsafe {
275 kill(-(pid as i32), 9);
276 kill(pid as i32, 9);
277 }
278 }
279 #[cfg(not(unix))]
280 {
281 let _ = pid;
282 }
283}