Skip to main content

running_process_platform_internal/platform/
process.rs

1//! Process spawning, containment, inspection, termination, and stdio.
2
3pub use crate::{
4    assign_child_to_windows_job, cancel_capture_reader, canonical_environment_pairs,
5    capture_reader_done, compat_shell_command, configure_exact_trace, configure_process_command,
6    configure_sync_contained_command, configure_sync_daemon_command, configure_trampoline_command,
7    current_executable_build_id, exact_trace_capability, exit_code, monitor_console_windows,
8    parent_has_console, prepare_capture_reader, process_snapshot, process_snapshot_for_pid,
9    set_process_name, shell_command, soft_terminate_process_group, spawn_sync, spawn_sync_daemon,
10    start_descendant_monitor, start_exact_trace, sync_child_native_handle, trampoline_exit_code,
11    unix_mark_extra_fds_close_on_exec, CaptureCancellation, PlatformChild, PlatformEmergencySignal,
12    PlatformLifecycle, PlatformOutput, PlatformStdin, SpawnSpec, StreamMode, TracedChild,
13    WindowsJobHandle,
14};
15
16/// Host-neutral command options selected by the caller before spawning.
17#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
18pub struct ProcessCommandConfig {
19    pub creation_flags: Option<u32>,
20    pub create_process_group: bool,
21    pub nice: Option<i32>,
22    pub address_space_limit_bytes: Option<u64>,
23}
24
25/// Availability of an invasive, lossless launched-tree trace backend.
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct ExactTraceCapability {
28    pub available: bool,
29    pub backend: &'static str,
30    pub reason: &'static str,
31    pub non_invasive_backend: &'static str,
32    pub non_invasive_grade: NonInvasiveObservationGrade,
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum NonInvasiveObservationGrade {
37    KernelNotification,
38    KernelHintReconciled,
39    SnapshotInferred,
40}
41
42/// A raw, bounded spawning-thread capture collected while a tracee is stopped.
43#[derive(Clone, Debug, Default, Eq, PartialEq)]
44pub struct TraceOriginArtifact {
45    pub origin_pid: u32,
46    pub thread_id: u32,
47    pub architecture: String,
48    pub register_format: String,
49    pub executable: Option<std::path::PathBuf>,
50    pub registers: Vec<u8>,
51    pub stack_pointer: Option<u64>,
52    pub instruction_pointer: Option<u64>,
53    pub stack: Vec<u8>,
54    pub truncated: bool,
55    pub module_map: Vec<u8>,
56    pub module_map_truncated: bool,
57}
58
59/// Native launched-tree event produced by an exact trace backend.
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct ExactTraceEvent {
62    pub sequence: u64,
63    pub pid: u32,
64    pub parent_pid: Option<u32>,
65    pub parent_start_key: Option<u64>,
66    pub start_key: Option<u64>,
67    pub timestamp: std::time::SystemTime,
68    pub kind: ExactTraceEventKind,
69    pub executable: Option<std::path::PathBuf>,
70    pub argv: Option<Vec<std::ffi::OsString>>,
71    pub origin: Option<TraceOriginArtifact>,
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub enum ExactTraceEventKind {
76    Spawn,
77    Exec,
78    Exit {
79        exit_code: Option<i32>,
80        signal: Option<i32>,
81        raw_status: i64,
82    },
83    Loss {
84        reason: String,
85    },
86}
87
88/// A descendant lifecycle fact reported by the host monitor.
89#[derive(Clone, Copy, Debug, Eq, PartialEq)]
90pub enum DescendantEvent {
91    Started {
92        pid: u32,
93        /// Immediate parent of the new descendant, when the discovery
94        /// mechanism knows it: the Linux `children`-file walk and the
95        /// macOS process-snapshot inversion both do; the Windows job
96        /// IOCP notification is PID-only, so it reports `None` rather
97        /// than paying a racy toolhelp scan per event.
98        parent_pid: Option<u32>,
99    },
100    Exited(u32),
101    /// The platform backend has completed its final reconciliation and no
102    /// further descendant events can arrive.
103    Completed,
104}
105
106/// Shared cancellation handle for a host-native descendant monitor.
107pub struct DescendantMonitorStop {
108    stopped: std::sync::atomic::AtomicBool,
109    mutex: std::sync::Mutex<()>,
110    wake: std::sync::Condvar,
111}
112
113impl DescendantMonitorStop {
114    /// Create an untriggered monitor cancellation handle.
115    pub fn new() -> Self {
116        Self {
117            stopped: std::sync::atomic::AtomicBool::new(false),
118            mutex: std::sync::Mutex::new(()),
119            wake: std::sync::Condvar::new(),
120        }
121    }
122
123    /// Report whether monitoring was cancelled.
124    pub fn is_stopped(&self) -> bool {
125        self.stopped.load(std::sync::atomic::Ordering::Acquire)
126    }
127
128    /// Cancel monitoring and wake a sleeping monitor immediately.
129    pub fn stop(&self) {
130        let _guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
131        if !self.stopped.swap(true, std::sync::atomic::Ordering::AcqRel) {
132            self.wake.notify_all();
133        }
134    }
135
136    /// Wait until cancelled or `timeout` expires, returning whether cancelled.
137    pub fn wait_timeout(&self, timeout: std::time::Duration) -> bool {
138        if self.is_stopped() {
139            return true;
140        }
141        let guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
142        if self.is_stopped() {
143            return true;
144        }
145        let (_guard, _wait_result) = self
146            .wake
147            .wait_timeout(guard, timeout)
148            .unwrap_or_else(|error| error.into_inner());
149        self.is_stopped()
150    }
151}
152
153impl Default for DescendantMonitorStop {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159/// Identifies one captured child output stream.
160#[derive(Clone, Copy)]
161pub enum CaptureStream {
162    Stdout,
163    Stderr,
164}
165
166/// Metadata about one visible window observed by console-popup monitoring.
167#[derive(Debug, Clone)]
168pub struct ConsoleWindowInfo {
169    pub pid: u32,
170    pub title: String,
171    pub hwnd: u64,
172}
173
174/// A platform-owned identity record used when observing a process tree.
175/// The timestamp fields are opaque host-native creation-time components and
176/// must only be compared for equality.
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub struct ProcessSnapshot {
179    pub pid: u32,
180    pub parent_pid: u32,
181    pub start_time_a: u64,
182    pub start_time_b: u64,
183}
184
185/// Environment base selected by the shared caller for a synchronous spawn.
186///
187/// Explicit `Command::env` additions and removals remain on the command and
188/// are applied after this base by the selected platform implementation.
189#[derive(Clone, Debug, Eq, PartialEq)]
190pub enum SyncEnvironment {
191    /// Start with the spawning process's ambient environment.
192    Inherit,
193    /// Start with this complete, caller-assembled base environment.
194    Explicit(Vec<(std::ffi::OsString, std::ffi::OsString)>),
195}
196
197/// Caller-supplied stdio bindings for a contained synchronous child.
198///
199/// Each stream is independently configured. `drain_timeout` bounds how long
200/// wrapper-owned pipe ends remain open after the child exits; `None` leaves
201/// pipe closure entirely to the caller. `show_console` only affects Windows.
202pub struct SpawnStdio<'a> {
203    /// Child standard input source.
204    pub stdin: StdioSource<'a>,
205    /// Child standard output destination.
206    pub stdout: StdioSource<'a>,
207    /// Child standard error destination.
208    pub stderr: StdioSource<'a>,
209    /// Maximum post-exit pipe drain interval.
210    pub drain_timeout: Option<std::time::Duration>,
211    /// Whether a Windows child may inherit or allocate a visible console.
212    pub show_console: bool,
213}
214
215impl Default for SpawnStdio<'_> {
216    fn default() -> Self {
217        Self {
218            stdin: StdioSource::Null,
219            stdout: StdioSource::Parent,
220            stderr: StdioSource::Parent,
221            drain_timeout: Some(std::time::Duration::from_secs(2)),
222            show_console: false,
223        }
224    }
225}
226
227/// Caller-supplied output bindings for a detached synchronous child.
228///
229/// Detached children may write only to the platform null device or to a
230/// caller-owned file. Parent stdio and anonymous pipes are intentionally not
231/// available because either can retain or depend on the launching process.
232pub struct DaemonStdio<'a> {
233    /// Child standard output destination.
234    pub stdout: DaemonStdioSource<'a>,
235    /// Child standard error destination.
236    pub stderr: DaemonStdioSource<'a>,
237}
238
239impl Default for DaemonStdio<'_> {
240    fn default() -> Self {
241        Self {
242            stdout: DaemonStdioSource::Null,
243            stderr: DaemonStdioSource::Null,
244        }
245    }
246}
247
248/// Output destination accepted by the detached-child path.
249pub enum DaemonStdioSource<'a> {
250    /// Route output to the platform null device.
251    Null,
252    /// Duplicate a caller-owned file into the child.
253    File(&'a std::fs::File),
254}
255
256/// Standard-stream source or destination for a contained child.
257pub enum StdioSource<'a> {
258    /// Route the stream to the platform null device.
259    Null,
260    /// Inherit the matching stream from the parent process.
261    Parent,
262    /// Duplicate a caller-owned file into the child.
263    File(&'a std::fs::File),
264    /// Create and return an anonymous parent/child pipe pair.
265    Pipe,
266}
267
268/// Handle for a detached child that is not terminated when dropped.
269pub struct DaemonChild {
270    pub(crate) pid: u32,
271    pub(crate) inner: Box<dyn DaemonChildControl>,
272}
273
274pub(crate) trait DaemonChildControl:
275    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
276{
277    fn kill(&mut self) -> std::io::Result<()>;
278    fn wait(&mut self) -> std::io::Result<i32>;
279    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
280}
281
282impl DaemonChild {
283    /// Return the operating-system process identifier.
284    pub fn id(&self) -> u32 {
285        self.pid
286    }
287
288    /// Terminate the child process.
289    pub fn kill(&mut self) -> std::io::Result<()> {
290        self.inner.kill()
291    }
292
293    /// Wait for the child and return its numeric exit code.
294    pub fn wait(&mut self) -> std::io::Result<i32> {
295        self.inner.wait()
296    }
297
298    /// Return the exit code if the child has finished without blocking.
299    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
300        self.inner.try_wait()
301    }
302}
303
304/// Handle and optional parent pipe ends for a contained child.
305///
306/// Dropping this value shuts down the contained process group.
307pub struct SpawnedChild {
308    /// Writable parent end when standard input was configured as a pipe.
309    pub stdin: Option<std::process::ChildStdin>,
310    /// Readable parent end when standard output was configured as a pipe.
311    pub stdout: Option<std::process::ChildStdout>,
312    /// Readable parent end when standard error was configured as a pipe.
313    pub stderr: Option<std::process::ChildStderr>,
314    pub(crate) pid: u32,
315    pub(crate) inner: Box<dyn SpawnedChildControl>,
316}
317
318pub(crate) trait SpawnedChildControl:
319    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
320{
321    fn kill(&mut self) -> std::io::Result<()>;
322    fn wait(&mut self) -> std::io::Result<i32>;
323    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
324    fn shutdown(&mut self);
325}
326
327impl SpawnedChild {
328    /// Return the operating-system process identifier.
329    pub fn id(&self) -> u32 {
330        self.pid
331    }
332
333    /// Forcibly terminate the child on a best-effort basis.
334    pub fn kill(&mut self) -> std::io::Result<()> {
335        self.inner.kill()
336    }
337
338    /// Wait for the child and return its numeric exit code.
339    pub fn wait(&mut self) -> std::io::Result<i32> {
340        self.inner.wait()
341    }
342
343    /// Return the exit code if the child has finished without blocking.
344    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
345        self.inner.try_wait()
346    }
347}
348
349impl Drop for SpawnedChild {
350    fn drop(&mut self) {
351        self.inner.shutdown();
352    }
353}
354
355#[derive(Clone, Copy)]
356pub enum ObserverScope {
357    SystemWide,
358    LaunchedProcessTree,
359}
360#[derive(Clone, Copy)]
361pub enum ObserverCategory {
362    File,
363    Network,
364    Process,
365}
366#[derive(Clone, Copy)]
367pub enum ObserverSupport {
368    Supported,
369    Partial,
370    Unavailable,
371}
372#[derive(Clone, Copy)]
373pub struct ObserverBackend {
374    pub support: ObserverSupport,
375    pub backend: &'static str,
376    pub reason: &'static str,
377}
378pub use crate::platform_imp::observer_backend;
379pub use crate::platform_imp::read_process_cmdline;
380pub use crate::platform_imp::read_process_file_handles;
381
382/// Platform-neutral Unix signal selectors used by the compatibility facade.
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384pub enum UnixSignalKind {
385    Interrupt,
386    Terminate,
387    Kill,
388}
389
390pub use crate::{
391    kill_tree, unix_set_priority, unix_signal_process, unix_signal_process_group, unix_signal_raw,
392};