1use std::collections::HashMap;
2
3use anyhow::{Result, bail};
4use oxdock_fs::{GuardedPath, PolicyPath};
5#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
6use std::process::ExitStatus;
7
8use std::sync::{Arc, Mutex};
9
10#[derive(Clone, Debug)]
14pub struct CommandContext {
15 cwd: PolicyPath,
16 envs: Arc<HashMap<String, String>>,
17 cargo_target_dir: GuardedPath,
18 workspace_root: GuardedPath,
19 build_context: GuardedPath,
20}
21
22impl CommandContext {
23 pub fn new(
24 cwd: &PolicyPath,
25 envs: Arc<HashMap<String, String>>,
26 cargo_target_dir: &GuardedPath,
27 workspace_root: &GuardedPath,
28 build_context: &GuardedPath,
29 ) -> Self {
30 Self {
31 cwd: cwd.clone(),
32 envs,
33 cargo_target_dir: cargo_target_dir.clone(),
34 workspace_root: workspace_root.clone(),
35 build_context: build_context.clone(),
36 }
37 }
38
39 pub fn from_map(
41 cwd: &PolicyPath,
42 envs: &HashMap<String, String>,
43 cargo_target_dir: &GuardedPath,
44 workspace_root: &GuardedPath,
45 build_context: &GuardedPath,
46 ) -> Self {
47 Self::new(
48 cwd,
49 Arc::new(envs.clone()),
50 cargo_target_dir,
51 workspace_root,
52 build_context,
53 )
54 }
55
56 pub fn cwd(&self) -> &PolicyPath {
57 &self.cwd
58 }
59
60 pub fn envs(&self) -> &Arc<HashMap<String, String>> {
61 &self.envs
62 }
63
64 pub fn cargo_target_dir(&self) -> &GuardedPath {
65 &self.cargo_target_dir
66 }
67
68 pub fn workspace_root(&self) -> &GuardedPath {
69 &self.workspace_root
70 }
71
72 pub fn build_context(&self) -> &GuardedPath {
73 &self.build_context
74 }
75}
76
77pub trait BackgroundHandle: Send {
79 fn try_wait(&mut self) -> Result<Option<ExitStatus>>;
80 fn kill(&mut self) -> Result<()>;
81 fn wait(&mut self) -> Result<ExitStatus>;
82}
83
84pub type SharedInput = Arc<Mutex<dyn std::io::Read + Send>>;
85pub type SharedOutput = Arc<Mutex<dyn std::io::Write + Send>>;
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
88pub enum CommandMode {
89 #[default]
90 Foreground,
91 Background,
92}
93
94#[derive(Clone, Default)]
95pub enum CommandStdout {
96 #[default]
97 Inherit,
98 Stream(SharedOutput),
99 Capture,
100 #[cfg(not(miri))]
105 OsPipe(OsPipeWriter),
106}
107
108#[cfg(not(miri))]
114#[derive(Clone)]
115pub struct OsPipeReader {
116 inner: Arc<Mutex<Option<std::io::PipeReader>>>,
117}
118
119#[cfg(not(miri))]
122#[derive(Clone)]
123pub struct OsPipeWriter {
124 inner: Arc<Mutex<Option<std::io::PipeWriter>>>,
125}
126
127#[cfg(not(miri))]
128impl OsPipeReader {
129 fn new(reader: std::io::PipeReader) -> Self {
130 Self {
131 inner: Arc::new(Mutex::new(Some(reader))),
132 }
133 }
134
135 pub fn take(&self) -> Result<std::io::PipeReader> {
139 self.inner
140 .lock()
141 .map_err(|_| anyhow::anyhow!("os pipe reader lock poisoned"))?
142 .take()
143 .ok_or_else(|| {
144 anyhow::anyhow!("os pipe handle has already been consumed by another process")
145 })
146 }
147}
148
149#[cfg(not(miri))]
150impl OsPipeWriter {
151 fn new(writer: std::io::PipeWriter) -> Self {
152 Self {
153 inner: Arc::new(Mutex::new(Some(writer))),
154 }
155 }
156
157 pub fn take(&self) -> Result<std::io::PipeWriter> {
161 self.inner
162 .lock()
163 .map_err(|_| anyhow::anyhow!("os pipe writer lock poisoned"))?
164 .take()
165 .ok_or_else(|| {
166 anyhow::anyhow!("os pipe handle has already been consumed by another process")
167 })
168 }
169}
170
171#[cfg(not(miri))]
175pub fn create_os_pipe() -> Result<(OsPipeReader, OsPipeWriter)> {
176 let (reader, writer) = std::io::pipe()?;
177 Ok((OsPipeReader::new(reader), OsPipeWriter::new(writer)))
178}
179
180#[derive(Clone, Default)]
181pub enum CommandStdin {
182 #[default]
184 Null,
185 Inherit,
186 Stream(SharedInput),
187 #[cfg(not(miri))]
190 OsPipe(OsPipeReader),
191}
192
193impl From<Option<SharedInput>> for CommandStdin {
194 fn from(stdin: Option<SharedInput>) -> Self {
195 match stdin {
196 Some(reader) => CommandStdin::Stream(reader),
197 None => CommandStdin::Null,
198 }
199 }
200}
201
202#[derive(Clone, Default)]
203pub enum CommandStderr {
204 #[default]
205 Inherit,
206 Stream(SharedOutput),
207 #[cfg(not(miri))]
211 OsPipe(OsPipeWriter),
212}
213
214#[derive(Clone, Default)]
215pub struct CommandOptions {
216 pub mode: CommandMode,
217 pub stdin: CommandStdin,
218 pub stdout: CommandStdout,
219 pub stderr: CommandStderr,
220}
221
222impl CommandOptions {
223 pub fn foreground() -> Self {
224 Self::default()
225 }
226
227 pub fn background() -> Self {
228 Self {
229 mode: CommandMode::Background,
230 ..Self::default()
231 }
232 }
233}
234
235pub enum CommandResult<H> {
236 Completed,
237 Captured(Vec<u8>),
238 Background(H),
239}
240
241pub const INHERIT_STDOUT_ENV_VAR: &str = "OXDOCK_INHERIT_STDOUT";
247
248pub const PROCESS_DEBUG_ENV_VAR: &str = "OXBOOK_DEBUG";
252
253pub trait ProcessManager: Clone + Send + 'static {
258 type Handle: BackgroundHandle + Clone + Send + 'static;
259
260 fn run_command(
261 &mut self,
262 ctx: &CommandContext,
263 script: &str,
264 options: CommandOptions,
265 ) -> Result<CommandResult<Self::Handle>>;
266
267 fn spawn_command(
271 &mut self,
272 ctx: &CommandContext,
273 script: &str,
274 options: CommandOptions,
275 ) -> Result<CommandResult<Self::Handle>> {
276 self.run_command(ctx, script, options)
277 }
278
279 fn run_argv(
284 &mut self,
285 _ctx: &CommandContext,
286 argv: &[String],
287 _options: CommandOptions,
288 ) -> Result<CommandResult<Self::Handle>> {
289 bail!("run_argv not implemented for argv {argv:?}")
290 }
291
292 fn spawn_argv(
295 &mut self,
296 ctx: &CommandContext,
297 argv: &[String],
298 options: CommandOptions,
299 ) -> Result<CommandResult<Self::Handle>> {
300 self.run_argv(ctx, argv, options)
301 }
302}