running-process-platform-internal 4.10.11

Blessed platform process operations for running-process (implementation detail)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
//! Process spawning, containment, inspection, termination, and stdio.

pub use crate::{
    assign_child_to_windows_job, cancel_capture_reader, canonical_environment_pairs,
    capture_reader_done, compat_shell_command, configure_exact_trace, configure_process_command,
    configure_process_command_for_bounded_owner_death, configure_sync_contained_command,
    configure_sync_daemon_command, configure_sync_daemon_command_with_inheritance,
    configure_trampoline_command, current_executable_build_id, exact_trace_capability, exit_code,
    monitor_console_windows, parent_has_console, prepare_capture_reader, set_process_name,
    shell_command, soft_terminate_process_group, spawn_sync, spawn_sync_daemon,
    spawn_sync_daemon_with_inheritance, start_descendant_monitor, start_exact_trace,
    sync_child_native_handle, trampoline_exit_code, unix_mark_extra_fds_close_on_exec,
    CaptureCancellation, TracedChild, WindowsJobHandle,
};

#[cfg(feature = "async-process")]
pub use crate::{
    PlatformChild, PlatformEmergencySignal, PlatformLifecycle, PlatformOutput, PlatformStdin,
    SpawnSpec, StreamMode,
};

#[cfg(feature = "process-inspection")]
pub use crate::{kill_tree, process_snapshot, process_snapshot_for_pid};

/// Host-neutral command options selected by the caller before spawning.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ProcessCommandConfig {
    pub creation_flags: Option<u32>,
    pub create_process_group: bool,
    pub nice: Option<i32>,
    pub address_space_limit_bytes: Option<u64>,
}

/// Opaque descriptor that a daemon spawn deliberately preserves through exec.
///
/// Normal daemon spawns retain the close-extra-descriptors default. The IPC
/// listener handoff creates this value only after preparing its listener, and
/// the Unix spawn boundary reopens exactly this descriptor after applying the
/// default close-on-exec sweep.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DaemonExecInheritance {
    descriptor: i32,
}

impl DaemonExecInheritance {
    // The token is constructed and consumed only by Unix IPC backends. Keep
    // its representation host-neutral here so platform selection stays in
    // those backend modules rather than leaking into the shared facade.
    #[allow(dead_code)]
    pub(crate) fn preserving_descriptor(descriptor: i32) -> Self {
        Self { descriptor }
    }

    #[allow(dead_code)]
    pub(crate) fn descriptor(self) -> i32 {
        self.descriptor
    }
}

/// Availability of an invasive, lossless launched-tree trace backend.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExactTraceCapability {
    pub available: bool,
    pub backend: &'static str,
    pub reason: &'static str,
    pub non_invasive_backend: &'static str,
    pub non_invasive_grade: NonInvasiveObservationGrade,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NonInvasiveObservationGrade {
    KernelNotification,
    KernelHintReconciled,
    SnapshotInferred,
}

/// A raw, bounded spawning-thread capture collected while a tracee is stopped.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TraceOriginArtifact {
    pub origin_pid: u32,
    pub thread_id: u32,
    pub architecture: String,
    pub register_format: String,
    pub executable: Option<std::path::PathBuf>,
    pub registers: Vec<u8>,
    pub stack_pointer: Option<u64>,
    pub instruction_pointer: Option<u64>,
    pub stack: Vec<u8>,
    pub truncated: bool,
    pub module_map: Vec<u8>,
    pub module_map_truncated: bool,
}

/// Native launched-tree event produced by an exact trace backend.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExactTraceEvent {
    pub sequence: u64,
    pub pid: u32,
    pub parent_pid: Option<u32>,
    pub parent_start_key: Option<u64>,
    pub start_key: Option<u64>,
    pub timestamp: std::time::SystemTime,
    pub kind: ExactTraceEventKind,
    pub executable: Option<std::path::PathBuf>,
    pub argv: Option<Vec<std::ffi::OsString>>,
    pub origin: Option<TraceOriginArtifact>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExactTraceEventKind {
    Spawn,
    Exec,
    Exit {
        exit_code: Option<i32>,
        signal: Option<i32>,
        raw_status: i64,
    },
    Loss {
        reason: String,
    },
}

/// A descendant lifecycle fact reported by the host monitor.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DescendantEvent {
    Started {
        pid: u32,
        /// Immediate parent of the new descendant, when the discovery
        /// mechanism knows it: the Linux `children`-file walk and the
        /// macOS process-snapshot inversion both do; the Windows job
        /// IOCP notification is PID-only, so it reports `None` rather
        /// than paying a racy toolhelp scan per event.
        parent_pid: Option<u32>,
    },
    Exited(u32),
    /// The platform backend has completed its final reconciliation and no
    /// further descendant events can arrive.
    Completed,
}

/// Shared cancellation handle for a host-native descendant monitor.
pub struct DescendantMonitorStop {
    stopped: std::sync::atomic::AtomicBool,
    mutex: std::sync::Mutex<()>,
    wake: std::sync::Condvar,
}

impl DescendantMonitorStop {
    /// Create an untriggered monitor cancellation handle.
    pub fn new() -> Self {
        Self {
            stopped: std::sync::atomic::AtomicBool::new(false),
            mutex: std::sync::Mutex::new(()),
            wake: std::sync::Condvar::new(),
        }
    }

    /// Report whether monitoring was cancelled.
    pub fn is_stopped(&self) -> bool {
        self.stopped.load(std::sync::atomic::Ordering::Acquire)
    }

    /// Cancel monitoring and wake a sleeping monitor immediately.
    pub fn stop(&self) {
        let _guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
        if !self.stopped.swap(true, std::sync::atomic::Ordering::AcqRel) {
            self.wake.notify_all();
        }
    }

    /// Wait until cancelled or `timeout` expires, returning whether cancelled.
    pub fn wait_timeout(&self, timeout: std::time::Duration) -> bool {
        if self.is_stopped() {
            return true;
        }
        let guard = self.mutex.lock().unwrap_or_else(|error| error.into_inner());
        if self.is_stopped() {
            return true;
        }
        let (_guard, _wait_result) = self
            .wake
            .wait_timeout(guard, timeout)
            .unwrap_or_else(|error| error.into_inner());
        self.is_stopped()
    }
}

impl Default for DescendantMonitorStop {
    fn default() -> Self {
        Self::new()
    }
}

/// Identifies one captured child output stream.
#[derive(Clone, Copy)]
pub enum CaptureStream {
    Stdout,
    Stderr,
}

/// Metadata about one visible window observed by console-popup monitoring.
#[derive(Debug, Clone)]
pub struct ConsoleWindowInfo {
    pub pid: u32,
    pub title: String,
    pub hwnd: u64,
}

/// A platform-owned identity record used when observing a process tree.
/// The timestamp fields are opaque host-native creation-time components and
/// must only be compared for equality.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProcessSnapshot {
    pub pid: u32,
    pub parent_pid: u32,
    pub start_time_a: u64,
    pub start_time_b: u64,
}

/// Environment base selected by the shared caller for a synchronous spawn.
///
/// Explicit `Command::env` additions and removals remain on the command and
/// are applied after this base by the selected platform implementation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SyncEnvironment {
    /// Start with the spawning process's ambient environment.
    Inherit,
    /// Start with this complete, caller-assembled base environment.
    Explicit(Vec<(std::ffi::OsString, std::ffi::OsString)>),
}

/// Caller-supplied stdio bindings for a contained synchronous child.
///
/// Each stream is independently configured. `drain_timeout` bounds how long
/// wrapper-owned pipe ends remain open after the child exits; `None` leaves
/// pipe closure entirely to the caller. `show_console` only affects Windows.
pub struct SpawnStdio<'a> {
    /// Child standard input source.
    pub stdin: StdioSource<'a>,
    /// Child standard output destination.
    pub stdout: StdioSource<'a>,
    /// Child standard error destination.
    pub stderr: StdioSource<'a>,
    /// Maximum post-exit pipe drain interval.
    pub drain_timeout: Option<std::time::Duration>,
    /// Whether a Windows child may inherit or allocate a visible console.
    pub show_console: bool,
}

impl Default for SpawnStdio<'_> {
    fn default() -> Self {
        Self {
            stdin: StdioSource::Null,
            stdout: StdioSource::Parent,
            stderr: StdioSource::Parent,
            drain_timeout: Some(std::time::Duration::from_secs(2)),
            show_console: false,
        }
    }
}

/// Caller-supplied output bindings for a detached synchronous child.
///
/// Detached children may write only to the platform null device or to a
/// caller-owned file. Parent stdio and anonymous pipes are intentionally not
/// available because either can retain or depend on the launching process.
pub struct DaemonStdio<'a> {
    /// Child standard output destination.
    pub stdout: DaemonStdioSource<'a>,
    /// Child standard error destination.
    pub stderr: DaemonStdioSource<'a>,
}

impl Default for DaemonStdio<'_> {
    fn default() -> Self {
        Self {
            stdout: DaemonStdioSource::Null,
            stderr: DaemonStdioSource::Null,
        }
    }
}

/// Output destination accepted by the detached-child path.
pub enum DaemonStdioSource<'a> {
    /// Route output to the platform null device.
    Null,
    /// Duplicate a caller-owned file into the child.
    File(&'a std::fs::File),
}

/// Standard-stream source or destination for a contained child.
pub enum StdioSource<'a> {
    /// Route the stream to the platform null device.
    Null,
    /// Inherit the matching stream from the parent process.
    Parent,
    /// Duplicate a caller-owned file into the child.
    File(&'a std::fs::File),
    /// Create and return an anonymous parent/child pipe pair.
    Pipe,
}

/// Handle for a detached child that is not terminated when dropped.
pub struct DaemonChild {
    pub(crate) pid: u32,
    pub(crate) inner: Box<dyn DaemonChildControl>,
}

pub(crate) trait DaemonChildControl:
    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
{
    fn kill(&mut self) -> std::io::Result<()>;
    fn wait(&mut self) -> std::io::Result<i32>;
    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
}

impl DaemonChild {
    /// Return the operating-system process identifier.
    pub fn id(&self) -> u32 {
        self.pid
    }

    /// Terminate the child process.
    pub fn kill(&mut self) -> std::io::Result<()> {
        self.inner.kill()
    }

    /// Wait for the child and return its numeric exit code.
    pub fn wait(&mut self) -> std::io::Result<i32> {
        self.inner.wait()
    }

    /// Return the exit code if the child has finished without blocking.
    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
        self.inner.try_wait()
    }
}

/// Handle and optional parent pipe ends for a contained child.
///
/// Dropping this value shuts down the contained process group.
pub struct SpawnedChild {
    /// Writable parent end when standard input was configured as a pipe.
    pub stdin: Option<std::process::ChildStdin>,
    /// Readable parent end when standard output was configured as a pipe.
    pub stdout: Option<std::process::ChildStdout>,
    /// Readable parent end when standard error was configured as a pipe.
    pub stderr: Option<std::process::ChildStderr>,
    pub(crate) pid: u32,
    pub(crate) inner: Box<dyn SpawnedChildControl>,
}

pub(crate) trait SpawnedChildControl:
    Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe
{
    fn kill(&mut self) -> std::io::Result<()>;
    fn wait(&mut self) -> std::io::Result<i32>;
    fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
    fn shutdown(&mut self);
}

impl SpawnedChild {
    /// Return the operating-system process identifier.
    pub fn id(&self) -> u32 {
        self.pid
    }

    /// Forcibly terminate the child on a best-effort basis.
    pub fn kill(&mut self) -> std::io::Result<()> {
        self.inner.kill()
    }

    /// Wait for the child and return its numeric exit code.
    pub fn wait(&mut self) -> std::io::Result<i32> {
        self.inner.wait()
    }

    /// Return the exit code if the child has finished without blocking.
    pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
        self.inner.try_wait()
    }
}

impl Drop for SpawnedChild {
    fn drop(&mut self) {
        self.inner.shutdown();
    }
}

#[derive(Clone, Copy)]
pub enum ObserverScope {
    SystemWide,
    LaunchedProcessTree,
}
#[derive(Clone, Copy)]
pub enum ObserverCategory {
    File,
    Network,
    Process,
}
#[derive(Clone, Copy)]
pub enum ObserverSupport {
    Supported,
    Partial,
    Unavailable,
}
#[derive(Clone, Copy)]
pub struct ObserverBackend {
    pub support: ObserverSupport,
    pub backend: &'static str,
    pub reason: &'static str,
}
pub use crate::platform_imp::observer_backend;
pub use crate::platform_imp::read_process_argv;
pub use crate::platform_imp::read_process_cmdline;
pub use crate::platform_imp::read_process_file_handles;

/// Platform-neutral Unix signal selectors used by the compatibility facade.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnixSignalKind {
    Interrupt,
    Terminate,
    Kill,
}

pub use crate::{
    unix_set_priority, unix_signal_process, unix_signal_process_group, unix_signal_raw,
};

/// What this host installed so a child outlives its owner no longer than it
/// should.
///
/// The variants name the *guarantee*, not the call that produced it. A caller
/// deciding whether to spawn a supervisor cares that the kernel will not do
/// the reaping for it; whether the kernel would have used a parent-death
/// signal or a job object is not a distinction it can act on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnerDeathCleanup {
    /// The kernel signals this process when its owner exits.
    OwnerDeathSignal,
    /// This process belongs to a container the kernel destroys with its owner.
    KillOnOwnerHandleClose,
    /// This process was already in such a container, installed by someone else.
    AlreadyContained,
    /// The host offers no kernel mechanism; a supervisor must do the reaping.
    SupervisorRequired,
    /// The host offers nothing and no supervisor contract is defined here.
    Unsupported,
}

/// Which step of installing owner-death containment failed.
///
/// The caller's operator-facing messages distinguish these, and rightly: not
/// being allowed to *build* a container is a different situation from
/// building one and not being allowed to *join* it. Collapsing both into one
/// error would make the two indistinguishable in a log, so the stage travels
/// with the error rather than being inferred from the host.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnerDeathCleanupStage {
    /// Asking the kernel to signal this process when its owner exits.
    RequestSignal,
    /// Creating the container that the kernel destroys with its owner.
    CreateContainer,
    /// Placing this process inside that container.
    JoinContainer,
}

/// A failure to install owner-death containment, and the step it failed at.
#[derive(Debug)]
pub struct OwnerDeathCleanupError {
    /// The step that failed.
    pub stage: OwnerDeathCleanupStage,
    /// What the host reported.
    pub source: std::io::Error,
}

impl std::fmt::Display for OwnerDeathCleanupError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}: {}", self.stage, self.source)
    }
}

impl std::error::Error for OwnerDeathCleanupError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

pub use crate::{
    process_install_owner_death_cleanup as install_owner_death_cleanup,
    process_owner_death_cleanup_target as owner_death_cleanup_target,
};

/// Why a host could not answer a question about a process.
///
/// The three named cases are the ones a caller can act on: a PID that could
/// never name a process, a process that is not there, and a question this
/// host does not answer. Everything else is the host's own report, kept
/// whole rather than flattened into one of the three.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessInspectErrorKind {
    /// The PID is outside the range this host issues.
    InvalidPid,
    /// No process on this host currently has that PID.
    NotFound,
    /// This host has no such primitive.
    Unsupported,
    /// The host was asked and refused, or failed.
    Host,
}

/// A failure to inspect or signal a process, and what kind of failure it was.
#[derive(Debug)]
pub struct ProcessInspectError {
    /// Which of the four situations this is.
    pub kind: ProcessInspectErrorKind,
    /// What the host reported.
    pub source: std::io::Error,
}

impl ProcessInspectError {
    /// Build an error of `kind` carrying the host's last reported error.
    pub fn last_os_error(kind: ProcessInspectErrorKind) -> Self {
        Self {
            kind,
            source: std::io::Error::last_os_error(),
        }
    }

    /// Build an error of `kind` with a message this crate composed itself.
    pub fn stated(kind: ProcessInspectErrorKind, message: &str) -> Self {
        Self {
            kind,
            source: std::io::Error::other(message.to_string()),
        }
    }
}

impl std::fmt::Display for ProcessInspectError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}: {}", self.kind, self.source)
    }
}

impl std::error::Error for ProcessInspectError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

pub use crate::{
    process_executable_path as executable_path, process_force_kill as force_kill,
    process_same_executable_path as same_executable_path,
    process_signal_terminate as signal_terminate, ProcessLiveness,
};

/// A standing request from the host that this process shut down.
///
/// Hosts deliver this differently -- a POSIX signal, a Windows console
/// control event injected on a thread of the OS's choosing -- but both arrive
/// in a context where almost nothing is safe to do. A handler may not
/// allocate, log, take a lock, or join a thread. So neither host runs the
/// caller's code: each sets one flag, and the caller reads it whenever it is
/// somewhere it can act.
///
/// That is why this is a poll rather than a callback. A callback would invite
/// exactly the work the delivery context forbids.
pub struct ShutdownRequest {
    flag: &'static std::sync::atomic::AtomicBool,
}

impl ShutdownRequest {
    /// Build a handle watching a flag the caller already owns.
    ///
    /// The host implementations use this to hand out a view of their own
    /// static. It is public because a caller that already has a shutdown flag
    /// -- one set by a supervisor protocol, or by a test -- can present it
    /// through the same type rather than the loop it feeds needing two shapes
    /// of "should I stop".
    ///
    /// `'static` is not incidental: a handler set by the OS outlives any
    /// scope, so the flag it writes has to as well.
    pub fn watching(flag: &'static std::sync::atomic::AtomicBool) -> Self {
        Self { flag }
    }

    /// Whether the host has asked this process to shut down.
    ///
    /// Latching, not edge-triggered: once true it stays true, so a caller that
    /// checks between two pieces of work cannot miss a request delivered while
    /// it was busy.
    pub fn requested(&self) -> bool {
        self.flag.load(std::sync::atomic::Ordering::Relaxed)
    }
}

impl std::fmt::Debug for ShutdownRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ShutdownRequest")
            .field("requested", &self.requested())
            .finish()
    }
}

pub use crate::process_install_shutdown_request_handler as install_shutdown_request_handler;

/// Whether this host can replace the running image with another program.
///
/// Unix can: `execve` keeps the process -- its PID, its open descriptors,
/// its place in the process tree -- and swaps the program underneath.
/// Windows has no equivalent; the nearest thing is starting a successor and
/// exiting, which is a *different* process with a different PID and does not
/// keep anything a parent or supervisor was holding onto.
///
/// Callers that can accept a successor should ask this and fall back. Callers
/// that genuinely need the same process to continue have no fallback, and
/// should treat `false` as unsupported rather than approximating it.
pub use crate::{
    process_can_replace_current_image as can_replace_current_image,
    process_replace_current_image as replace_current_image,
};