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_sync_contained_command, configure_sync_daemon_command, configure_trampoline_command,
current_executable_build_id, exact_trace_capability, exit_code, monitor_console_windows,
parent_has_console, prepare_capture_reader, run_bounded_command, run_bounded_command_async,
set_process_name, shell_command, soft_terminate_process_group, spawn_sync, spawn_sync_daemon,
start_descendant_monitor, start_exact_trace, sync_child_native_handle, trampoline_exit_code,
unix_mark_extra_fds_close_on_exec, BoundedProcessAsyncError, BoundedProcessError,
BoundedProcessOutput, CaptureCancellation, PlatformChild, ProcessCaptureError, ProcessExit,
ProcessOutput, ProcessOutputChunk, ProcessOutputCompletion, ProcessOutputEvent,
ProcessOutputFault, ProcessPostExitDrain, ProcessPriority, ProcessSession, ProcessSessionExit,
ProcessSessionOptions, SpawnSpec, StreamMode, TracedChild, WindowsJobHandle,
};
#[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>,
}
#[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,
}
#[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,
}
#[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,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DescendantEvent {
Started {
pid: u32,
parent_pid: Option<u32>,
},
Exited(u32),
Completed,
}
pub struct DescendantMonitorStop {
stopped: std::sync::atomic::AtomicBool,
mutex: std::sync::Mutex<()>,
wake: std::sync::Condvar,
}
impl DescendantMonitorStop {
pub fn new() -> Self {
Self {
stopped: std::sync::atomic::AtomicBool::new(false),
mutex: std::sync::Mutex::new(()),
wake: std::sync::Condvar::new(),
}
}
pub fn is_stopped(&self) -> bool {
self.stopped.load(std::sync::atomic::Ordering::Acquire)
}
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();
}
}
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()
}
}
#[derive(Clone, Copy)]
pub enum CaptureStream {
Stdout,
Stderr,
}
#[derive(Debug, Clone)]
pub struct ConsoleWindowInfo {
pub pid: u32,
pub title: String,
pub hwnd: u64,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ProcessId(u32);
impl ProcessId {
pub fn new(pid: u32) -> Result<Self, ProcessInspectError> {
if pid == 0 || pid > i32::MAX as u32 {
Err(ProcessInspectError::stated(
ProcessInspectErrorKind::InvalidPid,
"pid outside the range a host issues for a single process",
))
} else {
Ok(Self(pid))
}
}
#[must_use]
pub fn current() -> Self {
Self(std::process::id())
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[cfg(unix)]
#[must_use]
pub(crate) const fn native_signed(self) -> i32 {
self.0 as i32
}
}
impl TryFrom<u32> for ProcessId {
type Error = ProcessInspectError;
fn try_from(pid: u32) -> Result<Self, Self::Error> {
Self::new(pid)
}
}
impl std::fmt::Display for ProcessId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ProcessIdentity {
pid: u32,
creation_key: [u64; 2],
}
impl ProcessIdentity {
pub(crate) const fn from_native(pid: u32, creation_key: [u64; 2]) -> Self {
Self { pid, creation_key }
}
pub const fn pid(self) -> u32 {
self.pid
}
#[cfg(target_os = "windows")]
pub(crate) fn has_native_key(self, creation_key: [u64; 2]) -> bool {
self.creation_key == creation_key
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessIdentityUnavailable {
PermissionDenied,
Unsupported,
}
#[derive(Debug)]
pub enum ProcessIdentityCapture {
Found(ProcessIdentity),
Exited,
Unavailable(ProcessIdentityUnavailable),
Error(std::io::Error),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessIdentityAction {
Performed,
AlreadyExited,
}
#[derive(Debug)]
pub enum ProcessIdentityActionError {
StaleIdentity,
Unavailable(ProcessIdentityUnavailable),
Host(std::io::Error),
}
impl std::fmt::Display for ProcessIdentityActionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StaleIdentity => f.write_str("process PID was reused by a different instance"),
Self::Unavailable(reason) => write!(f, "process identity is unavailable: {reason:?}"),
Self::Host(error) => write!(f, "process identity action failed: {error}"),
}
}
}
impl std::error::Error for ProcessIdentityActionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Host(error) => Some(error),
Self::StaleIdentity | Self::Unavailable(_) => None,
}
}
}
pub fn foreground_status(
command: &mut std::process::Command,
) -> std::io::Result<std::process::ExitStatus> {
command.status()
}
pub fn foreground_output(
command: &mut std::process::Command,
) -> std::io::Result<std::process::Output> {
command.output()
}
pub fn capture_identity(pid: u32) -> ProcessIdentityCapture {
crate::platform_imp::capture_process_identity(pid)
}
pub fn force_kill(
identity: ProcessIdentity,
) -> Result<ProcessIdentityAction, ProcessIdentityActionError> {
crate::platform_imp::force_kill_identity(identity)
}
pub fn signal_terminate(
identity: ProcessIdentity,
) -> Result<ProcessIdentityAction, ProcessIdentityActionError> {
crate::platform_imp::signal_terminate_identity(identity)
}
pub fn kill_tree(
identity: ProcessIdentity,
timeout: std::time::Duration,
) -> Result<u32, ProcessIdentityActionError> {
crate::platform_imp::kill_tree_identity(identity, timeout)
}
#[cfg(test)]
pub(crate) fn act_on_current_identity(
identity: ProcessIdentity,
capture: impl FnOnce(u32) -> ProcessIdentityCapture,
action: impl FnOnce() -> Result<(), std::io::Error>,
) -> Result<ProcessIdentityAction, ProcessIdentityActionError> {
match capture(identity.pid()) {
ProcessIdentityCapture::Found(current) if current == identity => action()
.map(|()| ProcessIdentityAction::Performed)
.map_err(ProcessIdentityActionError::Host),
ProcessIdentityCapture::Found(_) => Err(ProcessIdentityActionError::StaleIdentity),
ProcessIdentityCapture::Exited => Ok(ProcessIdentityAction::AlreadyExited),
ProcessIdentityCapture::Unavailable(reason) => {
Err(ProcessIdentityActionError::Unavailable(reason))
}
ProcessIdentityCapture::Error(error) => Err(ProcessIdentityActionError::Host(error)),
}
}
#[cfg(any(target_os = "macos", all(test, target_os = "linux")))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ProcessSnapshot {
pub(crate) identity: ProcessIdentity,
pub(crate) parent_pid: u32,
}
#[cfg(test)]
mod identity_tests {
use super::*;
use std::cell::Cell;
#[test]
fn recycled_pid_is_refused_and_never_touches_the_replacement() {
let original = ProcessIdentity::from_native(41, [10, 0]);
let replacement = ProcessIdentity::from_native(41, [11, 0]);
let touched = Cell::new(false);
let outcome = act_on_current_identity(
original,
|_| ProcessIdentityCapture::Found(replacement),
|| {
touched.set(true);
Ok(())
},
);
assert!(matches!(
outcome,
Err(ProcessIdentityActionError::StaleIdentity)
));
assert!(
!touched.get(),
"the replacement process must never be touched"
);
}
#[test]
fn an_exited_identity_is_idempotent_without_attempting_a_mutation() {
let identity = ProcessIdentity::from_native(41, [10, 0]);
let touched = Cell::new(false);
let outcome = act_on_current_identity(
identity,
|_| ProcessIdentityCapture::Exited,
|| {
touched.set(true);
Ok(())
},
);
assert!(matches!(outcome, Ok(ProcessIdentityAction::AlreadyExited)));
assert!(!touched.get());
}
#[test]
fn matching_identity_performs_the_requested_action() {
let identity = ProcessIdentity::from_native(41, [10, 0]);
let touched = Cell::new(false);
let outcome = act_on_current_identity(
identity,
|_| ProcessIdentityCapture::Found(identity),
|| {
touched.set(true);
Ok(())
},
);
assert!(matches!(outcome, Ok(ProcessIdentityAction::Performed)));
assert!(touched.get());
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SyncEnvironment {
Inherit,
Explicit(Vec<(std::ffi::OsString, std::ffi::OsString)>),
}
#[allow(dead_code)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct WorkerLimits {
pub(crate) active_processes: Option<u32>,
pub(crate) process_memory_bytes: Option<u64>,
pub(crate) job_memory_bytes: Option<u64>,
}
#[cfg(all(feature = "tauri-webview", feature = "wasm-sketch-worker"))]
pub(crate) fn configure_native_worker_environment(command: &mut std::process::Command) {
crate::platform_imp::configure_native_worker_environment(command);
}
#[allow(dead_code)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum WorkerStage {
Pipe,
ConfigureContainment,
Create,
AssignContainment,
Resume,
Terminate,
Reap,
}
#[allow(dead_code)] #[derive(Debug)]
pub(crate) struct WorkerError {
stage: WorkerStage,
source: std::io::Error,
}
#[allow(dead_code)] impl WorkerError {
pub(crate) fn new(stage: WorkerStage, source: std::io::Error) -> Self {
Self { stage, source }
}
pub(crate) fn stage(&self) -> WorkerStage {
self.stage
}
}
impl std::fmt::Display for WorkerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"contained worker failed at {:?}: {}",
self.stage, self.source
)
}
}
impl std::error::Error for WorkerError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
#[allow(dead_code)] pub(crate) trait WorkerChildControl: Send {
fn try_wait(&mut self) -> std::io::Result<Option<i32>>;
fn force_and_reap(&mut self, timeout: std::time::Duration) -> Result<(), WorkerError>;
fn shutdown(&mut self);
}
#[allow(dead_code)] pub(crate) struct WorkerChild {
stdin: Option<std::process::ChildStdin>,
stdout: Option<std::process::ChildStdout>,
pid: u32,
inner: Option<Box<dyn WorkerChildControl>>,
contained: bool,
}
#[allow(dead_code)] pub(crate) struct WorkerControl {
pid: u32,
inner: Option<Box<dyn WorkerChildControl>>,
contained: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum WorkerNormalReap {
Clean,
Nonzero,
}
#[allow(dead_code)] impl WorkerChild {
pub(crate) fn new(
stdin: Option<std::process::ChildStdin>,
stdout: Option<std::process::ChildStdout>,
pid: u32,
inner: Box<dyn WorkerChildControl>,
) -> Self {
Self {
stdin,
stdout,
pid,
inner: Some(inner),
contained: false,
}
}
pub(crate) fn id(&self) -> u32 {
self.pid
}
pub(crate) fn take_stdin(&mut self) -> Option<std::process::ChildStdin> {
self.stdin.take()
}
pub(crate) fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
self.stdout.take()
}
pub(crate) fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
let exit = self
.inner
.as_mut()
.map_or(Ok(None), |inner| inner.try_wait())?;
if exit.is_some() {
self.contained = true;
}
Ok(exit)
}
pub(crate) fn force_and_reap(
&mut self,
timeout: std::time::Duration,
) -> Result<(), WorkerError> {
drop(self.stdin.take());
let Some(inner) = self.inner.as_mut() else {
return Ok(());
};
inner.force_and_reap(timeout)?;
self.contained = true;
Ok(())
}
pub(crate) fn into_parts(
mut self,
) -> (
WorkerControl,
Option<std::process::ChildStdin>,
Option<std::process::ChildStdout>,
) {
let control = WorkerControl {
pid: self.pid,
inner: self.inner.take(),
contained: self.contained,
};
let stdin = self.stdin.take();
let stdout = self.stdout.take();
(control, stdin, stdout)
}
}
impl Drop for WorkerChild {
fn drop(&mut self) {
drop(self.stdin.take());
if !self.contained {
if let Some(inner) = self.inner.as_mut() {
inner.shutdown();
}
}
}
}
#[allow(dead_code)] impl WorkerControl {
pub(crate) fn id(&self) -> u32 {
self.pid
}
pub(crate) fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
let exit = self
.inner
.as_mut()
.map_or(Ok(None), |inner| inner.try_wait())?;
if exit.is_some() {
self.contained = true;
}
Ok(exit)
}
pub(crate) fn reap_clean(
&mut self,
timeout: std::time::Duration,
) -> Result<WorkerNormalReap, WorkerError> {
let deadline = std::time::Instant::now() + timeout;
loop {
match self
.try_wait()
.map_err(|source| WorkerError::new(WorkerStage::Reap, source))?
{
Some(0) => {
self.contained = true;
return Ok(WorkerNormalReap::Clean);
}
Some(code) => {
let _ = code;
self.contained = true;
return Ok(WorkerNormalReap::Nonzero);
}
None if std::time::Instant::now() >= deadline => {
return Err(WorkerError::new(
WorkerStage::Reap,
std::io::Error::new(
std::io::ErrorKind::TimedOut,
"worker clean reap timed out",
),
));
}
None => std::thread::sleep(std::time::Duration::from_millis(1)),
}
}
}
pub(crate) fn force_and_reap(
&mut self,
timeout: std::time::Duration,
) -> Result<(), WorkerError> {
let Some(inner) = self.inner.as_mut() else {
return Ok(());
};
inner.force_and_reap(timeout)?;
self.contained = true;
Ok(())
}
}
impl Drop for WorkerControl {
fn drop(&mut self) {
if !self.contained {
if let Some(inner) = self.inner.as_mut() {
inner.shutdown();
}
}
}
}
#[cfg(test)]
mod worker_child_tests {
use super::{WorkerChild, WorkerChildControl, WorkerError, WorkerStage};
use std::io;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[derive(Default)]
struct Counts {
waits: AtomicUsize,
forces: AtomicUsize,
shutdowns: AtomicUsize,
failed_forces_remaining: AtomicUsize,
observed_exit: AtomicBool,
}
struct FakeControl {
counts: Arc<Counts>,
}
impl WorkerChildControl for FakeControl {
fn try_wait(&mut self) -> io::Result<Option<i32>> {
self.counts.waits.fetch_add(1, Ordering::Relaxed);
Ok(self
.counts
.observed_exit
.load(Ordering::Relaxed)
.then_some(0))
}
fn force_and_reap(&mut self, _timeout: Duration) -> Result<(), WorkerError> {
self.counts.forces.fetch_add(1, Ordering::Relaxed);
if self
.counts
.failed_forces_remaining
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |remaining| {
remaining.checked_sub(1)
})
.is_ok()
{
return Err(WorkerError::new(
WorkerStage::Reap,
io::Error::new(io::ErrorKind::TimedOut, "fake reap timeout"),
));
}
Ok(())
}
fn shutdown(&mut self) {
self.counts.shutdowns.fetch_add(1, Ordering::Relaxed);
}
}
fn fake_worker(counts: Arc<Counts>) -> WorkerChild {
WorkerChild::new(None, None, 77, Box::new(FakeControl { counts }))
}
#[test]
fn unsplit_drop_contains_exactly_once() {
let counts = Arc::new(Counts::default());
drop(fake_worker(Arc::clone(&counts)));
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 1);
}
#[test]
fn split_pipes_do_not_contain_before_control_drop() {
let counts = Arc::new(Counts::default());
let (control, stdin, stdout) = fake_worker(Arc::clone(&counts)).into_parts();
drop(stdin);
drop(stdout);
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 0);
drop(control);
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 1);
}
#[test]
fn successful_force_is_not_repeated_by_drop() {
let counts = Arc::new(Counts::default());
let (mut control, _stdin, _stdout) = fake_worker(Arc::clone(&counts)).into_parts();
control
.force_and_reap(Duration::from_millis(1))
.expect("fake force succeeds");
drop(control);
assert_eq!(counts.forces.load(Ordering::Relaxed), 1);
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 0);
}
#[test]
fn timed_out_force_retains_control_for_retry_and_drop() {
let counts = Arc::new(Counts::default());
counts.failed_forces_remaining.store(1, Ordering::Relaxed);
let (mut control, _stdin, _stdout) = fake_worker(Arc::clone(&counts)).into_parts();
let error = control
.force_and_reap(Duration::from_millis(1))
.expect_err("first fake force times out");
assert_eq!(error.stage(), WorkerStage::Reap);
assert_eq!(control.try_wait().expect("fake wait"), None);
control
.force_and_reap(Duration::from_millis(1))
.expect("retry keeps the backend owner");
drop(control);
assert_eq!(counts.forces.load(Ordering::Relaxed), 2);
assert_eq!(counts.waits.load(Ordering::Relaxed), 1);
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 0);
}
#[test]
fn clean_observed_exit_never_forces() {
let counts = Arc::new(Counts::default());
counts.observed_exit.store(true, Ordering::Relaxed);
let (mut control, _stdin, _stdout) = fake_worker(Arc::clone(&counts)).into_parts();
assert_eq!(control.try_wait().expect("fake exit"), Some(0));
drop(control);
assert_eq!(counts.forces.load(Ordering::Relaxed), 0);
assert_eq!(counts.shutdowns.load(Ordering::Relaxed), 0);
}
}
pub struct SpawnStdio<'a> {
pub stdin: StdioSource<'a>,
pub stdout: StdioSource<'a>,
pub stderr: StdioSource<'a>,
pub drain_timeout: Option<std::time::Duration>,
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,
}
}
}
pub struct DaemonStdio<'a> {
pub stdout: DaemonStdioSource<'a>,
pub stderr: DaemonStdioSource<'a>,
}
impl Default for DaemonStdio<'_> {
fn default() -> Self {
Self {
stdout: DaemonStdioSource::Null,
stderr: DaemonStdioSource::Null,
}
}
}
pub enum DaemonStdioSource<'a> {
Null,
File(&'a std::fs::File),
}
pub enum StdioSource<'a> {
Null,
Parent,
File(&'a std::fs::File),
Pipe,
}
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 {
pub fn id(&self) -> u32 {
self.pid
}
pub fn kill(&mut self) -> std::io::Result<()> {
self.inner.kill()
}
pub fn wait(&mut self) -> std::io::Result<i32> {
self.inner.wait()
}
pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
self.inner.try_wait()
}
}
pub struct SpawnedChild {
pub stdin: Option<std::process::ChildStdin>,
pub stdout: Option<std::process::ChildStdout>,
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 {
#[allow(dead_code)] pub(crate) fn into_worker_parts(
self,
) -> (
Option<std::process::ChildStdin>,
Option<std::process::ChildStdout>,
u32,
Box<dyn SpawnedChildControl>,
) {
let mut child = std::mem::ManuallyDrop::new(self);
let stdin = child.stdin.take();
let stdout = child.stdout.take();
drop(child.stderr.take());
let pid = child.pid;
let inner = unsafe { std::ptr::read(&child.inner) };
(stdin, stdout, pid, inner)
}
pub fn id(&self) -> u32 {
self.pid
}
pub fn kill(&mut self) -> std::io::Result<()> {
self.inner.kill()
}
pub fn wait(&mut self) -> std::io::Result<i32> {
self.inner.wait()
}
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_cmdline;
pub use crate::platform_imp::read_process_file_handles;
#[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,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnerDeathCleanup {
OwnerDeathSignal,
KillOnOwnerHandleClose,
AlreadyContained,
SupervisorRequired,
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnerDeathCleanupStage {
RequestSignal,
CreateContainer,
JoinContainer,
}
#[derive(Debug)]
pub struct OwnerDeathCleanupError {
pub stage: OwnerDeathCleanupStage,
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,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessInspectErrorKind {
InvalidPid,
NotFound,
Unsupported,
Host,
}
#[derive(Debug)]
pub struct ProcessInspectError {
pub kind: ProcessInspectErrorKind,
pub source: std::io::Error,
}
impl ProcessInspectError {
pub fn last_os_error(kind: ProcessInspectErrorKind) -> Self {
Self {
kind,
source: std::io::Error::last_os_error(),
}
}
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_same_executable_path as same_executable_path, ProcessLiveness};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessExitObservation {
Reported(ProcessSessionExit),
Unreported,
}
pub use crate::ProcessExitWatch;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LifetimeOwner {
Spawner,
Process(ProcessId),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LifetimeEnforcement {
KernelContainer,
ParentDeathSignal,
Watcher,
}
impl LifetimeEnforcement {
#[must_use]
pub const fn is_kernel_enforced(self) -> bool {
matches!(self, Self::KernelContainer | Self::ParentDeathSignal)
}
}
#[must_use]
pub fn lifetime_enforcement_for(owner: LifetimeOwner) -> LifetimeEnforcement {
match owner {
LifetimeOwner::Spawner => crate::process_spawner_lifetime_enforcement(),
LifetimeOwner::Process(_) => LifetimeEnforcement::Watcher,
}
}
pub struct ShutdownRequest {
flag: &'static std::sync::atomic::AtomicBool,
}
impl ShutdownRequest {
pub fn watching(flag: &'static std::sync::atomic::AtomicBool) -> Self {
Self { flag }
}
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;
pub use crate::{
process_can_replace_current_image as can_replace_current_image,
process_replace_current_image as replace_current_image,
};