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    pub(crate) kill_on_drop: bool,
344    /// Writable parent end when standard input was configured as a pipe.
345    pub stdin: Option<std::process::ChildStdin>,
346    /// Readable parent end when standard output was configured as a pipe.
347    pub stdout: Option<std::process::ChildStdout>,
348    /// Readable parent end when standard error was configured as a pipe.
349    pub stderr: Option<std::process::ChildStderr>,
350    pub(crate) pid: u32,
351    pub(crate) inner: Box<dyn SpawnedChildControl>,
352}
353
354pub(crate) trait SpawnedChildControl:
355    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
356{
357    fn kill(&mut self) -> std::io::Result<()>;
358    fn wait(&mut self) -> std::io::Result<i32>;
359    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
360    fn shutdown(&mut self);
361    #[cfg(feature = "independent-spawn")]
362    fn retain_exit_identity(&mut self) {}
363    #[cfg(feature = "independent-spawn")]
364    fn detach(&mut self) -> std::io::Result<()> {
365        Ok(())
366    }
367    #[cfg(feature = "independent-spawn")]
368    fn kill_tree(&mut self) -> std::io::Result<()> {
369        self.kill()
370    }
371}
372
373impl SpawnedChild {
374    #[cfg(feature = "independent-spawn")]
375    pub(crate) fn retain_exit_identity(&mut self) {
376        self.inner.retain_exit_identity();
377    }
378    #[cfg(feature = "independent-spawn")]
379    pub(crate) fn commit_detached(&mut self) -> std::io::Result<()> {
380        self.inner.detach()?;
381        self.kill_on_drop = false;
382        Ok(())
383    }
384    #[cfg(feature = "independent-spawn")]
385    pub(crate) fn kill_tree(&mut self) -> std::io::Result<()> {
386        self.inner.kill_tree()
387    }
388    /// Return the operating-system process identifier.
389    pub fn id(&self) -> u32 {
390        self.pid
391    }
392
393    /// Forcibly terminate the child on a best-effort basis.
394    pub fn kill(&mut self) -> std::io::Result<()> {
395        self.inner.kill()
396    }
397
398    /// Wait for the child and return its numeric exit code.
399    pub fn wait(&mut self) -> std::io::Result<i32> {
400        self.inner.wait()
401    }
402
403    /// Return the exit code if the child has finished without blocking.
404    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
405        self.inner.try_wait()
406    }
407}
408
409impl Drop for SpawnedChild {
410    fn drop(&mut self) {
411        if self.kill_on_drop {
412            self.inner.shutdown();
413        }
414    }
415}
416
417#[derive(Clone, Copy)]
418pub enum ObserverScope {
419    SystemWide,
420    LaunchedProcessTree,
421}
422#[derive(Clone, Copy)]
423pub enum ObserverCategory {
424    File,
425    Network,
426    Process,
427}
428#[derive(Clone, Copy)]
429pub enum ObserverSupport {
430    Supported,
431    Partial,
432    Unavailable,
433}
434#[derive(Clone, Copy)]
435pub struct ObserverBackend {
436    pub support: ObserverSupport,
437    pub backend: &'static str,
438    pub reason: &'static str,
439}
440pub use crate::platform_imp::observer_backend;
441pub use crate::platform_imp::read_process_argv;
442pub use crate::platform_imp::read_process_cmdline;
443pub use crate::platform_imp::read_process_file_handles;
444
445/// Platform-neutral Unix signal selectors used by the compatibility facade.
446#[derive(Debug, Clone, Copy, PartialEq, Eq)]
447pub enum UnixSignalKind {
448    Interrupt,
449    Terminate,
450    Kill,
451}
452
453pub use crate::{
454    unix_set_priority, unix_signal_process, unix_signal_process_group, unix_signal_raw,
455};
456
457/// What this host installed so a child outlives its owner no longer than it
458/// should.
459///
460/// The variants name the *guarantee*, not the call that produced it. A caller
461/// deciding whether to spawn a supervisor cares that the kernel will not do
462/// the reaping for it; whether the kernel would have used a parent-death
463/// signal or a job object is not a distinction it can act on.
464#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465pub enum OwnerDeathCleanup {
466    /// The kernel signals this process when its owner exits.
467    OwnerDeathSignal,
468    /// This process belongs to a container the kernel destroys with its owner.
469    KillOnOwnerHandleClose,
470    /// This process was already in such a container, installed by someone else.
471    AlreadyContained,
472    /// The host offers no kernel mechanism; a supervisor must do the reaping.
473    SupervisorRequired,
474    /// The host offers nothing and no supervisor contract is defined here.
475    Unsupported,
476}
477
478/// Which step of installing owner-death containment failed.
479///
480/// The caller's operator-facing messages distinguish these, and rightly: not
481/// being allowed to *build* a container is a different situation from
482/// building one and not being allowed to *join* it. Collapsing both into one
483/// error would make the two indistinguishable in a log, so the stage travels
484/// with the error rather than being inferred from the host.
485#[derive(Debug, Clone, Copy, PartialEq, Eq)]
486pub enum OwnerDeathCleanupStage {
487    /// Asking the kernel to signal this process when its owner exits.
488    RequestSignal,
489    /// Creating the container that the kernel destroys with its owner.
490    CreateContainer,
491    /// Placing this process inside that container.
492    JoinContainer,
493}
494
495/// A failure to install owner-death containment, and the step it failed at.
496#[derive(Debug)]
497pub struct OwnerDeathCleanupError {
498    /// The step that failed.
499    pub stage: OwnerDeathCleanupStage,
500    /// What the host reported.
501    pub source: std::io::Error,
502}
503
504impl std::fmt::Display for OwnerDeathCleanupError {
505    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506        write!(f, "{:?}: {}", self.stage, self.source)
507    }
508}
509
510impl std::error::Error for OwnerDeathCleanupError {
511    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
512        Some(&self.source)
513    }
514}
515
516pub use crate::{
517    process_install_owner_death_cleanup as install_owner_death_cleanup,
518    process_owner_death_cleanup_target as owner_death_cleanup_target,
519};
520
521/// Why a host could not answer a question about a process.
522///
523/// The three named cases are the ones a caller can act on: a PID that could
524/// never name a process, a process that is not there, and a question this
525/// host does not answer. Everything else is the host's own report, kept
526/// whole rather than flattened into one of the three.
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub enum ProcessInspectErrorKind {
529    /// The PID is outside the range this host issues.
530    InvalidPid,
531    /// No process on this host currently has that PID.
532    NotFound,
533    /// This host has no such primitive.
534    Unsupported,
535    /// The host was asked and refused, or failed.
536    Host,
537}
538
539/// A failure to inspect or signal a process, and what kind of failure it was.
540#[derive(Debug)]
541pub struct ProcessInspectError {
542    /// Which of the four situations this is.
543    pub kind: ProcessInspectErrorKind,
544    /// What the host reported.
545    pub source: std::io::Error,
546}
547
548impl ProcessInspectError {
549    /// Build an error of `kind` carrying the host's last reported error.
550    pub fn last_os_error(kind: ProcessInspectErrorKind) -> Self {
551        Self {
552            kind,
553            source: std::io::Error::last_os_error(),
554        }
555    }
556
557    /// Build an error of `kind` with a message this crate composed itself.
558    pub fn stated(kind: ProcessInspectErrorKind, message: &str) -> Self {
559        Self {
560            kind,
561            source: std::io::Error::other(message.to_string()),
562        }
563    }
564}
565
566impl std::fmt::Display for ProcessInspectError {
567    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568        write!(f, "{:?}: {}", self.kind, self.source)
569    }
570}
571
572impl std::error::Error for ProcessInspectError {
573    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
574        Some(&self.source)
575    }
576}
577
578pub use crate::{
579    process_executable_path as executable_path, process_force_kill as force_kill,
580    process_same_executable_path as same_executable_path,
581    process_signal_terminate as signal_terminate, ProcessLiveness,
582};
583
584/// A standing request from the host that this process shut down.
585///
586/// Hosts deliver this differently -- a POSIX signal, a Windows console
587/// control event injected on a thread of the OS's choosing -- but both arrive
588/// in a context where almost nothing is safe to do. A handler may not
589/// allocate, log, take a lock, or join a thread. So neither host runs the
590/// caller's code: each sets one flag, and the caller reads it whenever it is
591/// somewhere it can act.
592///
593/// That is why this is a poll rather than a callback. A callback would invite
594/// exactly the work the delivery context forbids.
595pub struct ShutdownRequest {
596    flag: &'static std::sync::atomic::AtomicBool,
597}
598
599impl ShutdownRequest {
600    /// Build a handle watching a flag the caller already owns.
601    ///
602    /// The host implementations use this to hand out a view of their own
603    /// static. It is public because a caller that already has a shutdown flag
604    /// -- one set by a supervisor protocol, or by a test -- can present it
605    /// through the same type rather than the loop it feeds needing two shapes
606    /// of "should I stop".
607    ///
608    /// `'static` is not incidental: a handler set by the OS outlives any
609    /// scope, so the flag it writes has to as well.
610    pub fn watching(flag: &'static std::sync::atomic::AtomicBool) -> Self {
611        Self { flag }
612    }
613
614    /// Whether the host has asked this process to shut down.
615    ///
616    /// Latching, not edge-triggered: once true it stays true, so a caller that
617    /// checks between two pieces of work cannot miss a request delivered while
618    /// it was busy.
619    pub fn requested(&self) -> bool {
620        self.flag.load(std::sync::atomic::Ordering::Relaxed)
621    }
622}
623
624impl std::fmt::Debug for ShutdownRequest {
625    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626        f.debug_struct("ShutdownRequest")
627            .field("requested", &self.requested())
628            .finish()
629    }
630}
631
632pub use crate::process_install_shutdown_request_handler as install_shutdown_request_handler;
633
634/// Whether this host can replace the running image with another program.
635///
636/// Unix can: `execve` keeps the process -- its PID, its open descriptors,
637/// its place in the process tree -- and swaps the program underneath.
638/// Windows has no equivalent; the nearest thing is starting a successor and
639/// exiting, which is a *different* process with a different PID and does not
640/// keep anything a parent or supervisor was holding onto.
641///
642/// Callers that can accept a successor should ask this and fall back. Callers
643/// that genuinely need the same process to continue have no fallback, and
644/// should treat `false` as unsupported rather than approximating it.
645pub use crate::{
646    process_can_replace_current_image as can_replace_current_image,
647    process_replace_current_image as replace_current_image,
648};