#[cfg(feature = "unix-runtime")]
mod backend;
mod fd;
#[cfg(feature = "test-support")]
mod test_runtimes;
#[cfg(feature = "test-support")]
mod test_stdio;
#[cfg(feature = "unix-runtime")]
mod unix_exec;
mod wait;
use std::ffi::CString;
use std::io;
#[cfg(any(feature = "unix-runtime", feature = "test-support"))]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "unix-runtime")]
use self::backend::{FdAction, exec, pid_from_handle, spawn_command};
pub use self::fd::{FileDescriptor, OsPipe};
#[cfg(feature = "test-support")]
pub use self::test_runtimes::{DeterministicRuntime, InMemoryCommand, InMemoryRuntime};
#[cfg(feature = "test-support")]
pub use self::test_stdio::{StringStdioIn, StringStdioOut};
use self::wait::wait_child_status;
#[cfg(feature = "unix-runtime")]
use self::wait::wait_pid_status;
#[cfg(feature = "unix-runtime")]
use self::wait::wait_process;
pub use self::wait::{ProcessEvent, WaitMode};
pub fn parent_pid() -> u32 {
unsafe { libc::getppid() as u32 }
}
pub fn get_umask() -> u32 {
let mask = unsafe { libc::umask(0) };
unsafe { libc::umask(mask) };
mask as u32
}
pub fn set_umask(mask: u32) {
unsafe { libc::umask(mask as libc::mode_t) };
}
pub struct ProcessTimes {
pub user_secs: f64,
pub sys_secs: f64,
pub child_user_secs: f64,
pub child_sys_secs: f64,
}
pub fn get_times() -> ProcessTimes {
let mut tms = libc::tms {
tms_utime: 0,
tms_stime: 0,
tms_cutime: 0,
tms_cstime: 0,
};
unsafe { libc::times(&mut tms) };
let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as f64;
ProcessTimes {
user_secs: tms.tms_utime as f64 / ticks,
sys_secs: tms.tms_stime as f64 / ticks,
child_user_secs: tms.tms_cutime as f64 / ticks,
child_sys_secs: tms.tms_cstime as f64 / ticks,
}
}
pub fn glob_expand(pattern: &str) -> Vec<String> {
let c_pattern = match CString::new(pattern) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let mut glob_buf: libc::glob_t = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::glob(c_pattern.as_ptr(), 0, None, &mut glob_buf) };
let mut results = Vec::new();
if ret == 0 {
for i in 0..glob_buf.gl_pathc {
let path = unsafe { *glob_buf.gl_pathv.add(i) };
if !path.is_null() {
let s = unsafe { std::ffi::CStr::from_ptr(path) };
if let Ok(s) = s.to_str() {
results.push(s.to_string());
}
}
}
}
unsafe { libc::globfree(&mut glob_buf) };
results
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProcessHandle(u64);
impl ProcessHandle {
pub const fn new(raw: u64) -> Self {
Self(raw)
}
pub const fn as_u64(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpawnMode {
Foreground,
BackgroundJob,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuntimeSignal {
Continue,
Stop,
Interrupt,
Terminate,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SpawnedProcess {
pub handle: ProcessHandle,
pub display_pid: Option<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PassedFileDescriptor {
pub parent_fd: FileDescriptor,
pub child_fd: FileDescriptor,
}
#[derive(Debug, Clone)]
pub struct ExternalCommand {
pub program: String,
pub argv: Vec<String>,
pub env: Vec<(String, String)>,
pub cwd: PathBuf,
pub create_process_group: bool,
pub passed_fds: Vec<PassedFileDescriptor>,
}
#[cfg(feature = "embed")]
pub(crate) fn spawn_error_exit_status(err: &io::Error) -> i32 {
if err.kind() == io::ErrorKind::NotFound || err.raw_os_error() == Some(libc::ENOENT) {
127
} else {
126
}
}
pub trait Runtime {
type ForegroundGuard;
fn fork(&self) -> Result<Self, std::io::Error>
where
Self: Sized;
fn spawn_external_command(
&mut self,
command: &ExternalCommand,
stdio: SpawnStdio,
close_fds: &[FileDescriptor],
mode: SpawnMode,
) -> Result<SpawnedProcess, std::io::Error>;
fn wait_process(
&mut self,
process: ProcessHandle,
mode: WaitMode,
) -> Result<ProcessEvent, std::io::Error>;
fn wait_display_pid(&mut self, _display_pid: u32) -> Result<Option<i32>, io::Error> {
Ok(None)
}
fn wait_child(&mut self, process: ProcessHandle) -> i32 {
wait_child_status(|| self.wait_process(process, WaitMode::Block))
}
fn signal_process_group(
&mut self,
process: ProcessHandle,
signal: RuntimeSignal,
) -> Result<(), std::io::Error>;
fn claim_foreground(
&mut self,
process: ProcessHandle,
tty: FileDescriptor,
) -> Result<Self::ForegroundGuard, std::io::Error>;
fn release_foreground(&mut self, guard: Self::ForegroundGuard) -> Result<(), std::io::Error>;
fn has_command(&self, _program: &str) -> bool {
false
}
fn resolve_command_path(&self, program: &str, _path_var: &str) -> Result<PathBuf, io::Error> {
if self.has_command(program) {
Ok(PathBuf::from(program))
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
format!("{program}: command not found"),
))
}
}
fn exec_replace(
&self,
program: &str,
argv: &[String],
env: &[(String, String)],
cwd: &Path,
) -> Result<(), std::io::Error>;
}
#[cfg(any(feature = "unix-runtime", feature = "test-support"))]
fn resolve_executable_path(path: &Path) -> Result<PathBuf, io::Error> {
let meta = match std::fs::metadata(path) {
Ok(meta) => meta,
Err(err) => {
return Err(io::Error::new(io::ErrorKind::NotFound, err.to_string()));
}
};
if !meta.is_file() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("{}: Permission denied", path.display()),
));
}
if meta.permissions().mode() & 0o111 == 0 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("{}: Permission denied", path.display()),
));
}
Ok(path.to_path_buf())
}
#[cfg(any(feature = "unix-runtime", feature = "test-support"))]
fn resolve_command_path(program: &str, path_var: &str) -> Result<PathBuf, io::Error> {
if program.contains('/') {
return resolve_executable_path(Path::new(program));
}
for dir in path_var.split(':') {
let candidate = Path::new(dir).join(program);
match resolve_executable_path(&candidate) {
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
other => return other,
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
format!("{program}: command not found"),
))
}
#[cfg(feature = "unix-runtime")]
#[derive(Clone, Debug)]
pub struct UnixRuntime {}
#[cfg(feature = "unix-runtime")]
#[derive(Clone, Copy, Debug)]
pub struct UnixForegroundGuard {
tty: FileDescriptor,
previous_pgid: libc::pid_t,
}
#[cfg(feature = "unix-runtime")]
impl UnixRuntime {
pub fn new() -> Self {
Self {}
}
}
#[cfg(feature = "unix-runtime")]
impl Default for UnixRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "unix-runtime")]
impl Runtime for UnixRuntime {
type ForegroundGuard = UnixForegroundGuard;
fn fork(&self) -> Result<Self, std::io::Error> {
Ok(Self::new())
}
fn spawn_external_command(
&mut self,
command: &ExternalCommand,
stdio: SpawnStdio,
close_fds: &[FileDescriptor],
mode: SpawnMode,
) -> Result<SpawnedProcess, std::io::Error> {
let fd_actions: Vec<FdAction> = close_fds.iter().copied().map(FdAction::Close).collect();
let create_process_group =
command.create_process_group || matches!(mode, SpawnMode::BackgroundJob);
let pid = spawn_command(command, create_process_group, stdio, &fd_actions)?;
Ok(SpawnedProcess {
handle: ProcessHandle::new(pid as u64),
display_pid: Some(pid as u32),
})
}
fn wait_process(
&mut self,
process: ProcessHandle,
mode: WaitMode,
) -> Result<ProcessEvent, std::io::Error> {
wait_process(pid_from_handle(process)?, mode)
}
fn wait_display_pid(&mut self, display_pid: u32) -> Result<Option<i32>, io::Error> {
wait_pid_status(display_pid as libc::pid_t).map(Some)
}
fn signal_process_group(
&mut self,
process: ProcessHandle,
signal: RuntimeSignal,
) -> Result<(), std::io::Error> {
let pid = pid_from_handle(process)?;
let signal = match signal {
RuntimeSignal::Continue => libc::SIGCONT,
RuntimeSignal::Stop => libc::SIGSTOP,
RuntimeSignal::Interrupt => libc::SIGINT,
RuntimeSignal::Terminate => libc::SIGTERM,
};
if unsafe { libc::kill(-pid, signal) } == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
fn claim_foreground(
&mut self,
process: ProcessHandle,
tty: FileDescriptor,
) -> Result<Self::ForegroundGuard, std::io::Error> {
let pid = pid_from_handle(process)?;
let previous_pgid = unsafe { libc::getpgrp() };
if unsafe { libc::tcsetpgrp(tty.into_raw_fd(), pid) } == 0 {
Ok(UnixForegroundGuard { tty, previous_pgid })
} else {
Err(std::io::Error::last_os_error())
}
}
fn release_foreground(&mut self, guard: Self::ForegroundGuard) -> Result<(), std::io::Error> {
if unsafe { libc::tcsetpgrp(guard.tty.into_raw_fd(), guard.previous_pgid) } == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
fn has_command(&self, _program: &str) -> bool {
false
}
fn resolve_command_path(&self, program: &str, path_var: &str) -> Result<PathBuf, io::Error> {
resolve_command_path(program, path_var)
}
fn exec_replace(
&self,
program: &str,
argv: &[String],
env: &[(String, String)],
cwd: &Path,
) -> Result<(), std::io::Error> {
exec(program, argv, env, cwd)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpawnStdio {
pub stdin_fd: FileDescriptor,
pub stdout_fd: FileDescriptor,
pub stderr_fd: FileDescriptor,
}
impl Default for SpawnStdio {
fn default() -> Self {
Self {
stdin_fd: FileDescriptor::STDIN,
stdout_fd: FileDescriptor::STDOUT,
stderr_fd: FileDescriptor::STDERR,
}
}
}
#[cfg(all(test, feature = "test-support", feature = "unix-runtime"))]
mod tests;