#[cfg(unix)]
use std::os::fd::BorrowedFd;
#[cfg(windows)]
use std::os::windows::io::BorrowedHandle;
use std::process::Command;
use std::time::Duration;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum EnvironmentPolicy {
#[default]
Auto,
Inherit,
UserBaseline,
Clear,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SpawnLifetime {
Contained,
Daemon,
}
impl EnvironmentPolicy {
pub(crate) fn resolve(self, lifetime: SpawnLifetime) -> Self {
match (self, lifetime) {
(Self::Auto, SpawnLifetime::Contained) => Self::Inherit,
(Self::Auto, SpawnLifetime::Daemon) => Self::UserBaseline,
(explicit, _) => explicit,
}
}
}
pub struct SpawnStdio<'a> {
pub stdin: StdioSource<'a>,
pub stdout: StdioSource<'a>,
pub stderr: StdioSource<'a>,
pub drain_timeout: Option<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(Duration::from_secs(2)),
show_console: false,
}
}
}
pub enum StdioSource<'a> {
Null,
Parent,
#[cfg(windows)]
Handle(BorrowedHandle<'a>),
#[cfg(unix)]
Fd(BorrowedFd<'a>),
Pipe,
#[doc(hidden)]
_Phantom(std::marker::PhantomData<&'a ()>),
}
pub struct DaemonChild {
pid: u32,
#[cfg(windows)]
handle: imp::OwnedHandle,
#[cfg(unix)]
child: std::process::Child,
}
impl DaemonChild {
pub fn id(&self) -> u32 {
self.pid
}
pub fn kill(&mut self) -> std::io::Result<()> {
#[cfg(windows)]
{
imp::terminate(&self.handle)
}
#[cfg(unix)]
{
self.child.kill()
}
}
pub fn wait(&mut self) -> std::io::Result<i32> {
#[cfg(windows)]
{
imp::wait(&self.handle)
}
#[cfg(unix)]
{
let status = self.child.wait()?;
Ok(unix_exit_code(status))
}
}
pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
#[cfg(windows)]
{
imp::try_wait(&self.handle)
}
#[cfg(unix)]
{
Ok(self.child.try_wait()?.map(unix_exit_code))
}
}
}
pub struct SpawnedChild {
pub stdin: Option<std::process::ChildStdin>,
pub stdout: Option<std::process::ChildStdout>,
pub stderr: Option<std::process::ChildStderr>,
pid: u32,
#[cfg(windows)]
inner: imp::SpawnedInner,
#[cfg(unix)]
inner: unix_impl::SpawnedInner,
}
impl SpawnedChild {
pub fn id(&self) -> u32 {
self.pid
}
pub fn kill(&mut self) -> std::io::Result<()> {
#[cfg(windows)]
{
self.inner.kill()
}
#[cfg(unix)]
{
self.inner.kill()
}
}
pub fn wait(&mut self) -> std::io::Result<i32> {
#[cfg(windows)]
{
self.inner.wait()
}
#[cfg(unix)]
{
self.inner.wait()
}
}
pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
#[cfg(windows)]
{
self.inner.try_wait()
}
#[cfg(unix)]
{
self.inner.try_wait()
}
}
}
impl Drop for SpawnedChild {
fn drop(&mut self) {
#[cfg(windows)]
{
self.inner.shutdown();
}
#[cfg(unix)]
{
self.inner.shutdown();
}
}
}
pub fn spawn_daemon(command: &mut Command) -> std::io::Result<DaemonChild> {
spawn_daemon_with_env_policy(command, EnvironmentPolicy::Auto)
}
pub fn spawn_daemon_with_clear_env(
command: &mut Command,
clear_env: bool,
) -> std::io::Result<DaemonChild> {
let policy = if clear_env {
EnvironmentPolicy::Clear
} else {
EnvironmentPolicy::Auto
};
spawn_daemon_with_env_policy(command, policy)
}
pub fn spawn_daemon_with_env_policy(
command: &mut Command,
policy: EnvironmentPolicy,
) -> std::io::Result<DaemonChild> {
let policy = policy.resolve(SpawnLifetime::Daemon);
#[cfg(windows)]
{
imp::spawn_daemon(command, policy)
}
#[cfg(unix)]
{
unix_impl::spawn_daemon(command, policy)
}
}
pub fn spawn(command: &mut Command, stdio: SpawnStdio<'_>) -> std::io::Result<SpawnedChild> {
spawn_with_env_policy(command, stdio, EnvironmentPolicy::Auto)
}
pub fn spawn_with_env_policy(
command: &mut Command,
stdio: SpawnStdio<'_>,
policy: EnvironmentPolicy,
) -> std::io::Result<SpawnedChild> {
let policy = policy.resolve(SpawnLifetime::Contained);
#[cfg(windows)]
{
imp::spawn(command, stdio, policy)
}
#[cfg(unix)]
{
unix_impl::spawn(command, stdio, policy)
}
}
#[cfg(unix)]
fn unix_exit_code(status: std::process::ExitStatus) -> i32 {
use std::os::unix::process::ExitStatusExt;
status
.code()
.unwrap_or_else(|| -status.signal().unwrap_or(1))
}
#[cfg(windows)]
#[path = "spawn_imp_windows.rs"]
mod imp;
#[cfg(unix)]
#[path = "spawn_imp_unix.rs"]
mod unix_impl;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spawn_stdio_default_has_sane_values() {
let s = SpawnStdio::default();
assert!(matches!(s.stdin, StdioSource::Null));
assert!(matches!(s.stdout, StdioSource::Parent));
assert!(matches!(s.stderr, StdioSource::Parent));
assert_eq!(s.drain_timeout, Some(Duration::from_secs(2)));
assert!(!s.show_console);
}
#[test]
fn auto_environment_policy_depends_on_lifetime() {
assert_eq!(
EnvironmentPolicy::Auto.resolve(SpawnLifetime::Contained),
EnvironmentPolicy::Inherit
);
assert_eq!(
EnvironmentPolicy::Auto.resolve(SpawnLifetime::Daemon),
EnvironmentPolicy::UserBaseline
);
}
#[test]
fn explicit_environment_policy_is_not_rewritten() {
for policy in [
EnvironmentPolicy::Inherit,
EnvironmentPolicy::UserBaseline,
EnvironmentPolicy::Clear,
] {
assert_eq!(policy.resolve(SpawnLifetime::Contained), policy);
assert_eq!(policy.resolve(SpawnLifetime::Daemon), policy);
}
}
}