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_process_command_for_bounded_owner_death, configure_sync_contained_command,
7    configure_sync_daemon_command, configure_sync_daemon_command_with_inheritance,
8    configure_trampoline_command, current_executable_build_id, exact_trace_capability, exit_code,
9    monitor_console_windows, parent_has_console, prepare_capture_reader, set_process_name,
10    shell_command, soft_terminate_process_group, spawn_sync, spawn_sync_daemon,
11    spawn_sync_daemon_with_inheritance, start_descendant_monitor, start_exact_trace,
12    sync_child_native_handle, trampoline_exit_code, unix_mark_extra_fds_close_on_exec,
13    CaptureCancellation, TracedChild, WindowsJobHandle,
14};
15
16#[cfg(feature = "async-process")]
17pub use crate::{
18    PlatformChild, PlatformEmergencySignal, PlatformLifecycle, PlatformOutput, PlatformStdin,
19    SpawnSpec, StreamMode,
20};
21
22#[cfg(feature = "process-inspection")]
23pub use crate::{kill_tree, process_snapshot, process_snapshot_for_pid};
24
25/// Host-neutral command options selected by the caller before spawning.
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27pub struct ProcessCommandConfig {
28    pub creation_flags: Option<u32>,
29    pub create_process_group: bool,
30    pub nice: Option<i32>,
31    pub address_space_limit_bytes: Option<u64>,
32}
33
34/// Opaque descriptor that a daemon spawn deliberately preserves through exec.
35///
36/// Normal daemon spawns retain the close-extra-descriptors default. The IPC
37/// listener handoff creates this value only after preparing its listener, and
38/// the Unix spawn boundary reopens exactly this descriptor after applying the
39/// default close-on-exec sweep.
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub struct DaemonExecInheritance {
42    descriptor: i32,
43}
44
45impl DaemonExecInheritance {
46    // The token is constructed and consumed only by Unix IPC backends. Keep
47    // its representation host-neutral here so platform selection stays in
48    // those backend modules rather than leaking into the shared facade.
49    #[allow(dead_code)]
50    pub(crate) fn preserving_descriptor(descriptor: i32) -> Self {
51        Self { descriptor }
52    }
53
54    #[allow(dead_code)]
55    pub(crate) fn descriptor(self) -> i32 {
56        self.descriptor
57    }
58}
59
60/// Availability of an invasive, lossless launched-tree trace backend.
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct ExactTraceCapability {
63    pub available: bool,
64    pub backend: &'static str,
65    pub reason: &'static str,
66    pub non_invasive_backend: &'static str,
67    pub non_invasive_grade: NonInvasiveObservationGrade,
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum NonInvasiveObservationGrade {
72    KernelNotification,
73    KernelHintReconciled,
74    SnapshotInferred,
75}
76
77/// A raw, bounded spawning-thread capture collected while a tracee is stopped.
78#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub struct TraceOriginArtifact {
80    pub origin_pid: u32,
81    pub thread_id: u32,
82    pub architecture: String,
83    pub register_format: String,
84    pub executable: Option<std::path::PathBuf>,
85    pub registers: Vec<u8>,
86    pub stack_pointer: Option<u64>,
87    pub instruction_pointer: Option<u64>,
88    pub stack: Vec<u8>,
89    pub truncated: bool,
90    pub module_map: Vec<u8>,
91    pub module_map_truncated: bool,
92}
93
94/// Native launched-tree event produced by an exact trace backend.
95#[derive(Clone, Debug, Eq, PartialEq)]
96pub struct ExactTraceEvent {
97    pub sequence: u64,
98    pub pid: u32,
99    pub parent_pid: Option<u32>,
100    pub parent_start_key: Option<u64>,
101    pub start_key: Option<u64>,
102    pub timestamp: std::time::SystemTime,
103    pub kind: ExactTraceEventKind,
104    pub executable: Option<std::path::PathBuf>,
105    pub argv: Option<Vec<std::ffi::OsString>>,
106    pub origin: Option<TraceOriginArtifact>,
107}
108
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub enum ExactTraceEventKind {
111    Spawn,
112    Exec,
113    Exit {
114        exit_code: Option<i32>,
115        signal: Option<i32>,
116        raw_status: i64,
117    },
118    Loss {
119        reason: String,
120    },
121}
122
123/// A descendant lifecycle fact reported by the host monitor.
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub enum DescendantEvent {
126    Started {
127        pid: u32,
128        /// Immediate parent of the new descendant, when the discovery
129        /// mechanism knows it: the Linux `children`-file walk and the
130        /// macOS process-snapshot inversion both do; the Windows job
131        /// IOCP notification is PID-only, so it reports `None` rather
132        /// than paying a racy toolhelp scan per event.
133        parent_pid: Option<u32>,
134    },
135    Exited(u32),
136    /// The platform backend has completed its final reconciliation and no
137    /// further descendant events can arrive.
138    Completed,
139}
140
141/// Shared cancellation handle for a host-native descendant monitor.
142pub struct DescendantMonitorStop {
143    stopped: std::sync::atomic::AtomicBool,
144    mutex: std::sync::Mutex<()>,
145    wake: std::sync::Condvar,
146}
147
148impl DescendantMonitorStop {
149    /// Create an untriggered monitor cancellation handle.
150    pub fn new() -> Self {
151        Self {
152            stopped: std::sync::atomic::AtomicBool::new(false),
153            mutex: std::sync::Mutex::new(()),
154            wake: std::sync::Condvar::new(),
155        }
156    }
157
158    /// Report whether monitoring was cancelled.
159    pub fn is_stopped(&self) -> bool {
160        self.stopped.load(std::sync::atomic::Ordering::Acquire)
161    }
162
163    /// Cancel monitoring and wake a sleeping monitor immediately.
164    pub fn stop(&self) {
165        let _guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
166        if !self.stopped.swap(true, std::sync::atomic::Ordering::AcqRel) {
167            self.wake.notify_all();
168        }
169    }
170
171    /// Wait until cancelled or `timeout` expires, returning whether cancelled.
172    pub fn wait_timeout(&self, timeout: std::time::Duration) -> bool {
173        if self.is_stopped() {
174            return true;
175        }
176        let guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
177        if self.is_stopped() {
178            return true;
179        }
180        let (_guard, _wait_result) = self
181            .wake
182            .wait_timeout(guard, timeout)
183            .unwrap_or_else(|error| error.into_inner());
184        self.is_stopped()
185    }
186}
187
188impl Default for DescendantMonitorStop {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194/// Identifies one captured child output stream.
195#[derive(Clone, Copy)]
196pub enum CaptureStream {
197    Stdout,
198    Stderr,
199}
200
201/// Metadata about one visible window observed by console-popup monitoring.
202#[derive(Debug, Clone)]
203pub struct ConsoleWindowInfo {
204    pub pid: u32,
205    pub title: String,
206    pub hwnd: u64,
207}
208
209/// A platform-owned identity record used when observing a process tree.
210/// The timestamp fields are opaque host-native creation-time components and
211/// must only be compared for equality.
212#[derive(Clone, Copy, Debug, Eq, PartialEq)]
213pub struct ProcessSnapshot {
214    pub pid: u32,
215    pub parent_pid: u32,
216    pub start_time_a: u64,
217    pub start_time_b: u64,
218}
219
220/// Environment base selected by the shared caller for a synchronous spawn.
221///
222/// Explicit `Command::env` additions and removals remain on the command and
223/// are applied after this base by the selected platform implementation.
224#[derive(Clone, Debug, Eq, PartialEq)]
225pub enum SyncEnvironment {
226    /// Start with the spawning process's ambient environment.
227    Inherit,
228    /// Start with this complete, caller-assembled base environment.
229    Explicit(Vec<(std::ffi::OsString, std::ffi::OsString)>),
230}
231
232/// Caller-supplied stdio bindings for a contained synchronous child.
233///
234/// Each stream is independently configured. `drain_timeout` bounds how long
235/// wrapper-owned pipe ends remain open after the child exits; `None` leaves
236/// pipe closure entirely to the caller. `show_console` only affects Windows.
237pub struct SpawnStdio<'a> {
238    /// Child standard input source.
239    pub stdin: StdioSource<'a>,
240    /// Child standard output destination.
241    pub stdout: StdioSource<'a>,
242    /// Child standard error destination.
243    pub stderr: StdioSource<'a>,
244    /// Maximum post-exit pipe drain interval.
245    pub drain_timeout: Option<std::time::Duration>,
246    /// Whether a Windows child may inherit or allocate a visible console.
247    pub show_console: bool,
248}
249
250impl Default for SpawnStdio<'_> {
251    fn default() -> Self {
252        Self {
253            stdin: StdioSource::Null,
254            stdout: StdioSource::Parent,
255            stderr: StdioSource::Parent,
256            drain_timeout: Some(std::time::Duration::from_secs(2)),
257            show_console: false,
258        }
259    }
260}
261
262/// Caller-supplied output bindings for a detached synchronous child.
263///
264/// Detached children may write only to the platform null device or to a
265/// caller-owned file. Parent stdio and anonymous pipes are intentionally not
266/// available because either can retain or depend on the launching process.
267pub struct DaemonStdio<'a> {
268    /// Child standard output destination.
269    pub stdout: DaemonStdioSource<'a>,
270    /// Child standard error destination.
271    pub stderr: DaemonStdioSource<'a>,
272}
273
274impl Default for DaemonStdio<'_> {
275    fn default() -> Self {
276        Self {
277            stdout: DaemonStdioSource::Null,
278            stderr: DaemonStdioSource::Null,
279        }
280    }
281}
282
283/// Output destination accepted by the detached-child path.
284pub enum DaemonStdioSource<'a> {
285    /// Route output to the platform null device.
286    Null,
287    /// Duplicate a caller-owned file into the child.
288    File(&'a std::fs::File),
289}
290
291/// Standard-stream source or destination for a contained child.
292pub enum StdioSource<'a> {
293    /// Route the stream to the platform null device.
294    Null,
295    /// Inherit the matching stream from the parent process.
296    Parent,
297    /// Duplicate a caller-owned file into the child.
298    File(&'a std::fs::File),
299    /// Create and return an anonymous parent/child pipe pair.
300    Pipe,
301}
302
303/// Handle for a detached child that is not terminated when dropped.
304pub struct DaemonChild {
305    pub(crate) pid: u32,
306    pub(crate) inner: Box<dyn DaemonChildControl>,
307}
308
309pub(crate) trait DaemonChildControl:
310    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
311{
312    fn kill(&mut self) -> std::io::Result<()>;
313    fn wait(&mut self) -> std::io::Result<i32>;
314    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
315}
316
317impl DaemonChild {
318    /// Return the operating-system process identifier.
319    pub fn id(&self) -> u32 {
320        self.pid
321    }
322
323    /// Terminate the child process.
324    pub fn kill(&mut self) -> std::io::Result<()> {
325        self.inner.kill()
326    }
327
328    /// Wait for the child and return its numeric exit code.
329    pub fn wait(&mut self) -> std::io::Result<i32> {
330        self.inner.wait()
331    }
332
333    /// Return the exit code if the child has finished without blocking.
334    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
335        self.inner.try_wait()
336    }
337}
338
339/// Handle and optional parent pipe ends for a contained child.
340///
341/// Dropping this value shuts down the contained process group.
342pub struct SpawnedChild {
343    /// Writable parent end when standard input was configured as a pipe.
344    pub stdin: Option<std::process::ChildStdin>,
345    /// Readable parent end when standard output was configured as a pipe.
346    pub stdout: Option<std::process::ChildStdout>,
347    /// Readable parent end when standard error was configured as a pipe.
348    pub stderr: Option<std::process::ChildStderr>,
349    pub(crate) pid: u32,
350    pub(crate) inner: Box<dyn SpawnedChildControl>,
351}
352
353pub(crate) trait SpawnedChildControl:
354    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
355{
356    fn kill(&mut self) -> std::io::Result<()>;
357    fn wait(&mut self) -> std::io::Result<i32>;
358    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
359    fn shutdown(&mut self);
360}
361
362impl SpawnedChild {
363    /// Return the operating-system process identifier.
364    pub fn id(&self) -> u32 {
365        self.pid
366    }
367
368    /// Forcibly terminate the child on a best-effort basis.
369    pub fn kill(&mut self) -> std::io::Result<()> {
370        self.inner.kill()
371    }
372
373    /// Wait for the child and return its numeric exit code.
374    pub fn wait(&mut self) -> std::io::Result<i32> {
375        self.inner.wait()
376    }
377
378    /// Return the exit code if the child has finished without blocking.
379    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
380        self.inner.try_wait()
381    }
382}
383
384impl Drop for SpawnedChild {
385    fn drop(&mut self) {
386        self.inner.shutdown();
387    }
388}
389
390#[derive(Clone, Copy)]
391pub enum ObserverScope {
392    SystemWide,
393    LaunchedProcessTree,
394}
395#[derive(Clone, Copy)]
396pub enum ObserverCategory {
397    File,
398    Network,
399    Process,
400}
401#[derive(Clone, Copy)]
402pub enum ObserverSupport {
403    Supported,
404    Partial,
405    Unavailable,
406}
407#[derive(Clone, Copy)]
408pub struct ObserverBackend {
409    pub support: ObserverSupport,
410    pub backend: &'static str,
411    pub reason: &'static str,
412}
413pub use crate::platform_imp::observer_backend;
414pub use crate::platform_imp::read_process_argv;
415pub use crate::platform_imp::read_process_cmdline;
416pub use crate::platform_imp::read_process_file_handles;
417
418/// Platform-neutral Unix signal selectors used by the compatibility facade.
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum UnixSignalKind {
421    Interrupt,
422    Terminate,
423    Kill,
424}
425
426pub use crate::{
427    unix_set_priority, unix_signal_process, unix_signal_process_group, unix_signal_raw,
428};
429
430/// What this host installed so a child outlives its owner no longer than it
431/// should.
432///
433/// The variants name the *guarantee*, not the call that produced it. A caller
434/// deciding whether to spawn a supervisor cares that the kernel will not do
435/// the reaping for it; whether the kernel would have used a parent-death
436/// signal or a job object is not a distinction it can act on.
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub enum OwnerDeathCleanup {
439    /// The kernel signals this process when its owner exits.
440    OwnerDeathSignal,
441    /// This process belongs to a container the kernel destroys with its owner.
442    KillOnOwnerHandleClose,
443    /// This process was already in such a container, installed by someone else.
444    AlreadyContained,
445    /// The host offers no kernel mechanism; a supervisor must do the reaping.
446    SupervisorRequired,
447    /// The host offers nothing and no supervisor contract is defined here.
448    Unsupported,
449}
450
451/// Which step of installing owner-death containment failed.
452///
453/// The caller's operator-facing messages distinguish these, and rightly: not
454/// being allowed to *build* a container is a different situation from
455/// building one and not being allowed to *join* it. Collapsing both into one
456/// error would make the two indistinguishable in a log, so the stage travels
457/// with the error rather than being inferred from the host.
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum OwnerDeathCleanupStage {
460    /// Asking the kernel to signal this process when its owner exits.
461    RequestSignal,
462    /// Creating the container that the kernel destroys with its owner.
463    CreateContainer,
464    /// Placing this process inside that container.
465    JoinContainer,
466}
467
468/// A failure to install owner-death containment, and the step it failed at.
469#[derive(Debug)]
470pub struct OwnerDeathCleanupError {
471    /// The step that failed.
472    pub stage: OwnerDeathCleanupStage,
473    /// What the host reported.
474    pub source: std::io::Error,
475}
476
477impl std::fmt::Display for OwnerDeathCleanupError {
478    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479        write!(f, "{:?}: {}", self.stage, self.source)
480    }
481}
482
483impl std::error::Error for OwnerDeathCleanupError {
484    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
485        Some(&self.source)
486    }
487}
488
489pub use crate::{
490    process_install_owner_death_cleanup as install_owner_death_cleanup,
491    process_owner_death_cleanup_target as owner_death_cleanup_target,
492};
493
494/// Why a host could not answer a question about a process.
495///
496/// The three named cases are the ones a caller can act on: a PID that could
497/// never name a process, a process that is not there, and a question this
498/// host does not answer. Everything else is the host's own report, kept
499/// whole rather than flattened into one of the three.
500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ProcessInspectErrorKind {
502    /// The PID is outside the range this host issues.
503    InvalidPid,
504    /// No process on this host currently has that PID.
505    NotFound,
506    /// This host has no such primitive.
507    Unsupported,
508    /// The host was asked and refused, or failed.
509    Host,
510}
511
512/// A failure to inspect or signal a process, and what kind of failure it was.
513#[derive(Debug)]
514pub struct ProcessInspectError {
515    /// Which of the four situations this is.
516    pub kind: ProcessInspectErrorKind,
517    /// What the host reported.
518    pub source: std::io::Error,
519}
520
521impl ProcessInspectError {
522    /// Build an error of `kind` carrying the host's last reported error.
523    pub fn last_os_error(kind: ProcessInspectErrorKind) -> Self {
524        Self {
525            kind,
526            source: std::io::Error::last_os_error(),
527        }
528    }
529
530    /// Build an error of `kind` with a message this crate composed itself.
531    pub fn stated(kind: ProcessInspectErrorKind, message: &str) -> Self {
532        Self {
533            kind,
534            source: std::io::Error::other(message.to_string()),
535        }
536    }
537}
538
539impl std::fmt::Display for ProcessInspectError {
540    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
541        write!(f, "{:?}: {}", self.kind, self.source)
542    }
543}
544
545impl std::error::Error for ProcessInspectError {
546    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
547        Some(&self.source)
548    }
549}
550
551pub use crate::{
552    process_executable_path as executable_path, process_force_kill as force_kill,
553    process_same_executable_path as same_executable_path,
554    process_signal_terminate as signal_terminate, ProcessLiveness,
555};
556
557/// A standing request from the host that this process shut down.
558///
559/// Hosts deliver this differently -- a POSIX signal, a Windows console
560/// control event injected on a thread of the OS's choosing -- but both arrive
561/// in a context where almost nothing is safe to do. A handler may not
562/// allocate, log, take a lock, or join a thread. So neither host runs the
563/// caller's code: each sets one flag, and the caller reads it whenever it is
564/// somewhere it can act.
565///
566/// That is why this is a poll rather than a callback. A callback would invite
567/// exactly the work the delivery context forbids.
568pub struct ShutdownRequest {
569    flag: &'static std::sync::atomic::AtomicBool,
570}
571
572impl ShutdownRequest {
573    /// Build a handle watching a flag the caller already owns.
574    ///
575    /// The host implementations use this to hand out a view of their own
576    /// static. It is public because a caller that already has a shutdown flag
577    /// -- one set by a supervisor protocol, or by a test -- can present it
578    /// through the same type rather than the loop it feeds needing two shapes
579    /// of "should I stop".
580    ///
581    /// `'static` is not incidental: a handler set by the OS outlives any
582    /// scope, so the flag it writes has to as well.
583    pub fn watching(flag: &'static std::sync::atomic::AtomicBool) -> Self {
584        Self { flag }
585    }
586
587    /// Whether the host has asked this process to shut down.
588    ///
589    /// Latching, not edge-triggered: once true it stays true, so a caller that
590    /// checks between two pieces of work cannot miss a request delivered while
591    /// it was busy.
592    pub fn requested(&self) -> bool {
593        self.flag.load(std::sync::atomic::Ordering::Relaxed)
594    }
595}
596
597impl std::fmt::Debug for ShutdownRequest {
598    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599        f.debug_struct("ShutdownRequest")
600            .field("requested", &self.requested())
601            .finish()
602    }
603}
604
605pub use crate::process_install_shutdown_request_handler as install_shutdown_request_handler;
606
607/// Whether this host can replace the running image with another program.
608///
609/// Unix can: `execve` keeps the process -- its PID, its open descriptors,
610/// its place in the process tree -- and swaps the program underneath.
611/// Windows has no equivalent; the nearest thing is starting a successor and
612/// exiting, which is a *different* process with a different PID and does not
613/// keep anything a parent or supervisor was holding onto.
614///
615/// Callers that can accept a successor should ask this and fall back. Callers
616/// that genuinely need the same process to continue have no fallback, and
617/// should treat `false` as unsupported rather than approximating it.
618pub use crate::{
619    process_can_replace_current_image as can_replace_current_image,
620    process_replace_current_image as replace_current_image,
621};