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,
7    configure_sync_daemon_command_with_inheritance, configure_trampoline_command,
8    current_executable_build_id, exact_trace_capability, exit_code, monitor_console_windows,
9    parent_has_console, prepare_capture_reader, set_process_name, shell_command,
10    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_cmdline;
415pub use crate::platform_imp::read_process_file_handles;
416
417/// Platform-neutral Unix signal selectors used by the compatibility facade.
418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419pub enum UnixSignalKind {
420    Interrupt,
421    Terminate,
422    Kill,
423}
424
425pub use crate::{
426    unix_set_priority, unix_signal_process, unix_signal_process_group, unix_signal_raw,
427};
428
429/// What this host installed so a child outlives its owner no longer than it
430/// should.
431///
432/// The variants name the *guarantee*, not the call that produced it. A caller
433/// deciding whether to spawn a supervisor cares that the kernel will not do
434/// the reaping for it; whether the kernel would have used a parent-death
435/// signal or a job object is not a distinction it can act on.
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437pub enum OwnerDeathCleanup {
438    /// The kernel signals this process when its owner exits.
439    OwnerDeathSignal,
440    /// This process belongs to a container the kernel destroys with its owner.
441    KillOnOwnerHandleClose,
442    /// This process was already in such a container, installed by someone else.
443    AlreadyContained,
444    /// The host offers no kernel mechanism; a supervisor must do the reaping.
445    SupervisorRequired,
446    /// The host offers nothing and no supervisor contract is defined here.
447    Unsupported,
448}
449
450/// Which step of installing owner-death containment failed.
451///
452/// The caller's operator-facing messages distinguish these, and rightly: not
453/// being allowed to *build* a container is a different situation from
454/// building one and not being allowed to *join* it. Collapsing both into one
455/// error would make the two indistinguishable in a log, so the stage travels
456/// with the error rather than being inferred from the host.
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub enum OwnerDeathCleanupStage {
459    /// Asking the kernel to signal this process when its owner exits.
460    RequestSignal,
461    /// Creating the container that the kernel destroys with its owner.
462    CreateContainer,
463    /// Placing this process inside that container.
464    JoinContainer,
465}
466
467/// A failure to install owner-death containment, and the step it failed at.
468#[derive(Debug)]
469pub struct OwnerDeathCleanupError {
470    /// The step that failed.
471    pub stage: OwnerDeathCleanupStage,
472    /// What the host reported.
473    pub source: std::io::Error,
474}
475
476impl std::fmt::Display for OwnerDeathCleanupError {
477    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478        write!(f, "{:?}: {}", self.stage, self.source)
479    }
480}
481
482impl std::error::Error for OwnerDeathCleanupError {
483    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
484        Some(&self.source)
485    }
486}
487
488pub use crate::{
489    process_install_owner_death_cleanup as install_owner_death_cleanup,
490    process_owner_death_cleanup_target as owner_death_cleanup_target,
491};
492
493/// Why a host could not answer a question about a process.
494///
495/// The three named cases are the ones a caller can act on: a PID that could
496/// never name a process, a process that is not there, and a question this
497/// host does not answer. Everything else is the host's own report, kept
498/// whole rather than flattened into one of the three.
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum ProcessInspectErrorKind {
501    /// The PID is outside the range this host issues.
502    InvalidPid,
503    /// No process on this host currently has that PID.
504    NotFound,
505    /// This host has no such primitive.
506    Unsupported,
507    /// The host was asked and refused, or failed.
508    Host,
509}
510
511/// A failure to inspect or signal a process, and what kind of failure it was.
512#[derive(Debug)]
513pub struct ProcessInspectError {
514    /// Which of the four situations this is.
515    pub kind: ProcessInspectErrorKind,
516    /// What the host reported.
517    pub source: std::io::Error,
518}
519
520impl ProcessInspectError {
521    /// Build an error of `kind` carrying the host's last reported error.
522    pub fn last_os_error(kind: ProcessInspectErrorKind) -> Self {
523        Self {
524            kind,
525            source: std::io::Error::last_os_error(),
526        }
527    }
528
529    /// Build an error of `kind` with a message this crate composed itself.
530    pub fn stated(kind: ProcessInspectErrorKind, message: &str) -> Self {
531        Self {
532            kind,
533            source: std::io::Error::other(message.to_string()),
534        }
535    }
536}
537
538impl std::fmt::Display for ProcessInspectError {
539    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540        write!(f, "{:?}: {}", self.kind, self.source)
541    }
542}
543
544impl std::error::Error for ProcessInspectError {
545    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
546        Some(&self.source)
547    }
548}
549
550pub use crate::{
551    process_executable_path as executable_path, process_force_kill as force_kill,
552    process_same_executable_path as same_executable_path,
553    process_signal_terminate as signal_terminate, ProcessLiveness,
554};
555
556/// A standing request from the host that this process shut down.
557///
558/// Hosts deliver this differently -- a POSIX signal, a Windows console
559/// control event injected on a thread of the OS's choosing -- but both arrive
560/// in a context where almost nothing is safe to do. A handler may not
561/// allocate, log, take a lock, or join a thread. So neither host runs the
562/// caller's code: each sets one flag, and the caller reads it whenever it is
563/// somewhere it can act.
564///
565/// That is why this is a poll rather than a callback. A callback would invite
566/// exactly the work the delivery context forbids.
567pub struct ShutdownRequest {
568    flag: &'static std::sync::atomic::AtomicBool,
569}
570
571impl ShutdownRequest {
572    /// Build a handle watching a flag the caller already owns.
573    ///
574    /// The host implementations use this to hand out a view of their own
575    /// static. It is public because a caller that already has a shutdown flag
576    /// -- one set by a supervisor protocol, or by a test -- can present it
577    /// through the same type rather than the loop it feeds needing two shapes
578    /// of "should I stop".
579    ///
580    /// `'static` is not incidental: a handler set by the OS outlives any
581    /// scope, so the flag it writes has to as well.
582    pub fn watching(flag: &'static std::sync::atomic::AtomicBool) -> Self {
583        Self { flag }
584    }
585
586    /// Whether the host has asked this process to shut down.
587    ///
588    /// Latching, not edge-triggered: once true it stays true, so a caller that
589    /// checks between two pieces of work cannot miss a request delivered while
590    /// it was busy.
591    pub fn requested(&self) -> bool {
592        self.flag.load(std::sync::atomic::Ordering::Relaxed)
593    }
594}
595
596impl std::fmt::Debug for ShutdownRequest {
597    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
598        f.debug_struct("ShutdownRequest")
599            .field("requested", &self.requested())
600            .finish()
601    }
602}
603
604pub use crate::process_install_shutdown_request_handler as install_shutdown_request_handler;
605
606/// Whether this host can replace the running image with another program.
607///
608/// Unix can: `execve` keeps the process -- its PID, its open descriptors,
609/// its place in the process tree -- and swaps the program underneath.
610/// Windows has no equivalent; the nearest thing is starting a successor and
611/// exiting, which is a *different* process with a different PID and does not
612/// keep anything a parent or supervisor was holding onto.
613///
614/// Callers that can accept a successor should ask this and fall back. Callers
615/// that genuinely need the same process to continue have no fallback, and
616/// should treat `false` as unsupported rather than approximating it.
617pub use crate::{
618    process_can_replace_current_image as can_replace_current_image,
619    process_replace_current_image as replace_current_image,
620};