mod errno;
mod file_system;
mod open_flag;
mod resource;
mod signal;
use super::AT_FDCWD;
use super::CaughtSignals;
use super::Chdir;
use super::ChildProcessStarter;
use super::Clock;
use super::Close;
use super::CpuTimes;
use super::Dir;
use super::DirEntry;
use super::Disposition;
use super::Dup;
use super::Errno;
use super::Exec;
use super::Exit;
use super::Fcntl;
use super::FdFlag;
use super::Fork;
use super::Fstat;
use super::GetCwd;
use super::GetPid;
use super::GetPw;
use super::GetRlimit;
use super::GetSigaction;
use super::GetUid;
use super::Gid;
use super::IsExecutableFile;
use super::Isatty;
use super::Mode;
use super::OfdAccess;
use super::Open;
use super::OpenFlag;
use super::Pipe;
use super::Read;
use super::Result;
use super::Seek;
use super::Select;
use super::SendSignal;
use super::SetPgid;
use super::SetRlimit;
use super::ShellPath;
use super::Sigaction;
use super::Sigmask;
use super::SigmaskOp;
use super::Signals;
use super::Stat as _;
use super::Sysconf;
use super::TcGetPgrp;
use super::TcSetPgrp;
use super::Times;
use super::Uid;
use super::Umask;
use super::Wait;
use super::Write;
use super::resource::LimitPair;
use super::resource::Resource;
#[cfg(doc)]
use crate::Env;
use crate::io::Fd;
use crate::job::Pid;
use crate::job::ProcessResult;
use crate::job::ProcessState;
use crate::path::Path;
use crate::path::PathBuf;
use crate::semantics::ExitStatus;
use crate::str::UnixStr;
use crate::str::UnixString;
use enumset::EnumSet;
pub use file_system::Stat;
use libc::DIR;
use std::convert::Infallible;
use std::convert::TryInto;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
use std::ffi::c_int;
use std::future::ready;
use std::io::SeekFrom;
use std::mem::MaybeUninit;
use std::num::NonZero;
use std::ops::RangeInclusive;
use std::os::unix::ffi::OsStrExt as _;
use std::os::unix::io::IntoRawFd;
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering;
use std::sync::atomic::compiler_fence;
use std::time::Duration;
use std::time::Instant;
trait ErrnoIfM1: PartialEq + Sized {
const MINUS_1: Self;
fn errno_if_m1(self) -> Result<Self> {
if self == Self::MINUS_1 {
Err(Errno::last())
} else {
Ok(self)
}
}
}
impl ErrnoIfM1 for i8 {
const MINUS_1: Self = -1;
}
impl ErrnoIfM1 for i16 {
const MINUS_1: Self = -1;
}
impl ErrnoIfM1 for i32 {
const MINUS_1: Self = -1;
}
impl ErrnoIfM1 for i64 {
const MINUS_1: Self = -1;
}
impl ErrnoIfM1 for isize {
const MINUS_1: Self = -1;
}
#[must_use]
fn to_timespec(duration: Duration) -> MaybeUninit<libc::timespec> {
let seconds = duration.as_secs().try_into().unwrap_or(libc::time_t::MAX);
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
unsafe {
(&raw mut (*timespec.as_mut_ptr()).tv_sec).write(seconds);
(&raw mut (*timespec.as_mut_ptr()).tv_nsec).write(duration.subsec_nanos() as _);
}
timespec
}
static CAUGHT_SIGNALS: [AtomicIsize; 8] = [const { AtomicIsize::new(0) }; 8];
extern "C" fn catch_signal(signal: c_int) {
let signal = signal as isize;
for slot in &CAUGHT_SIGNALS {
match slot.compare_exchange(0, signal, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => break,
Err(slot_value) if slot_value == signal => break,
_ => continue,
}
}
}
fn sigaction_impl(signal: signal::Number, disposition: Option<Disposition>) -> Result<Disposition> {
let new_action = disposition.map(Disposition::to_sigaction);
let new_action_ptr = new_action
.as_ref()
.map_or(std::ptr::null(), |action| action.as_ptr());
let mut old_action = MaybeUninit::<libc::sigaction>::uninit();
let old_mask_ptr = unsafe { &raw mut (*old_action.as_mut_ptr()).sa_mask };
unsafe { libc::sigemptyset(old_mask_ptr) }.errno_if_m1()?;
unsafe { libc::sigaction(signal.as_raw(), new_action_ptr, old_action.as_mut_ptr()) }
.errno_if_m1()?;
let old_disposition = unsafe { Disposition::from_sigaction(&old_action) };
Ok(old_disposition)
}
#[derive(Debug)]
pub struct RealSystem(());
impl RealSystem {
pub unsafe fn new() -> Self {
RealSystem(())
}
#[cfg(not(target_os = "redox"))]
fn has_execute_permission(&self, path: &CStr) -> bool {
(unsafe { libc::faccessat(libc::AT_FDCWD, path.as_ptr(), libc::X_OK, libc::AT_EACCESS) })
!= -1
}
#[cfg(target_os = "redox")]
fn has_execute_permission(&self, path: &CStr) -> bool {
(unsafe { libc::access(path.as_ptr(), libc::X_OK) }) != -1
}
}
impl Fstat for RealSystem {
type Stat = file_system::Stat;
fn fstat(&self, fd: Fd) -> Result<file_system::Stat> {
let mut stat = MaybeUninit::<libc::stat>::uninit();
unsafe { libc::fstat(fd.0, stat.as_mut_ptr()) }.errno_if_m1()?;
let stat = unsafe { file_system::Stat::from_raw(stat) };
Ok(stat)
}
fn fstatat(&self, dir_fd: Fd, path: &CStr, follow_symlinks: bool) -> Result<file_system::Stat> {
let flags = if follow_symlinks {
0
} else {
libc::AT_SYMLINK_NOFOLLOW
};
let mut stat = MaybeUninit::<libc::stat>::uninit();
unsafe { libc::fstatat(dir_fd.0, path.as_ptr(), stat.as_mut_ptr(), flags) }
.errno_if_m1()?;
let stat = unsafe { file_system::Stat::from_raw(stat) };
Ok(stat)
}
}
impl IsExecutableFile for RealSystem {
fn is_executable_file(&self, path: &CStr) -> bool {
self.fstatat(AT_FDCWD, path, true)
.is_ok_and(|stat| stat.is_regular_file())
&& self.has_execute_permission(path)
}
}
impl Pipe for RealSystem {
fn pipe(&self) -> Result<(Fd, Fd)> {
let mut fds = MaybeUninit::<[c_int; 2]>::uninit();
unsafe { libc::pipe(fds.as_mut_ptr().cast()) }.errno_if_m1()?;
let fds = unsafe { fds.assume_init() };
Ok((Fd(fds[0]), Fd(fds[1])))
}
}
impl Dup for RealSystem {
fn dup(&self, from: Fd, to_min: Fd, flags: EnumSet<FdFlag>) -> Result<Fd> {
let command = if flags.contains(FdFlag::CloseOnExec) {
libc::F_DUPFD_CLOEXEC
} else {
libc::F_DUPFD
};
unsafe { libc::fcntl(from.0, command, to_min.0) }
.errno_if_m1()
.map(Fd)
}
fn dup2(&self, from: Fd, to: Fd) -> Result<Fd> {
loop {
let result = unsafe { libc::dup2(from.0, to.0) }.errno_if_m1().map(Fd);
if result != Err(Errno::EINTR) {
return result;
}
}
}
}
impl Open for RealSystem {
fn open(
&self,
path: &CStr,
access: OfdAccess,
flags: EnumSet<OpenFlag>,
mode: Mode,
) -> impl Future<Output = Result<Fd>> + use<> {
ready((|| {
let mut raw_flags = access.to_real_flag().ok_or(Errno::EINVAL)?;
for flag in flags {
raw_flags |= flag.to_real_flag().ok_or(Errno::EINVAL)?;
}
#[cfg(not(target_os = "redox"))]
let mode_bits = mode.bits() as std::ffi::c_uint;
#[cfg(target_os = "redox")]
let mode_bits = mode.bits() as c_int;
let result = unsafe { libc::open(path.as_ptr(), raw_flags, mode_bits) };
result.errno_if_m1().map(Fd)
})())
}
fn open_tmpfile(&self, parent_dir: &Path) -> Result<Fd> {
let parent_dir = OsStr::from_bytes(parent_dir.as_unix_str().as_bytes());
let file = tempfile::tempfile_in(parent_dir)
.map_err(|errno| Errno(errno.raw_os_error().unwrap_or(0)))?;
let fd = Fd(file.into_raw_fd());
_ = self.fcntl_setfd(fd, EnumSet::empty());
Ok(fd)
}
fn fdopendir(&self, fd: Fd) -> Result<impl Dir + use<>> {
let dir = unsafe { libc::fdopendir(fd.0) };
let dir = NonNull::new(dir).ok_or_else(Errno::last)?;
Ok(RealDir(dir))
}
fn opendir(&self, path: &CStr) -> Result<impl Dir + use<>> {
let dir = unsafe { libc::opendir(path.as_ptr()) };
let dir = NonNull::new(dir).ok_or_else(Errno::last)?;
Ok(RealDir(dir))
}
}
impl Close for RealSystem {
fn close(&self, fd: Fd) -> Result<()> {
loop {
let result = unsafe { libc::close(fd.0) }.errno_if_m1().map(drop);
match result {
Err(Errno::EBADF) => return Ok(()),
Err(Errno::EINTR) => continue,
other => return other,
}
}
}
}
impl Fcntl for RealSystem {
fn ofd_access(&self, fd: Fd) -> Result<OfdAccess> {
let flags = unsafe { libc::fcntl(fd.0, libc::F_GETFL) }.errno_if_m1()?;
Ok(OfdAccess::from_real_flag(flags))
}
fn get_and_set_nonblocking(&self, fd: Fd, nonblocking: bool) -> Result<bool> {
let old_flags = unsafe { libc::fcntl(fd.0, libc::F_GETFL) }.errno_if_m1()?;
let new_flags = if nonblocking {
old_flags | libc::O_NONBLOCK
} else {
old_flags & !libc::O_NONBLOCK
};
if new_flags != old_flags {
unsafe { libc::fcntl(fd.0, libc::F_SETFL, new_flags) }.errno_if_m1()?;
}
let was_nonblocking = old_flags & libc::O_NONBLOCK != 0;
Ok(was_nonblocking)
}
fn fcntl_getfd(&self, fd: Fd) -> Result<EnumSet<FdFlag>> {
let bits = unsafe { libc::fcntl(fd.0, libc::F_GETFD) }.errno_if_m1()?;
let mut flags = EnumSet::empty();
if bits & libc::FD_CLOEXEC != 0 {
flags.insert(FdFlag::CloseOnExec);
}
Ok(flags)
}
fn fcntl_setfd(&self, fd: Fd, flags: EnumSet<FdFlag>) -> Result<()> {
let mut bits = 0 as c_int;
if flags.contains(FdFlag::CloseOnExec) {
bits |= libc::FD_CLOEXEC;
}
unsafe { libc::fcntl(fd.0, libc::F_SETFD, bits) }
.errno_if_m1()
.map(drop)
}
}
impl Read for RealSystem {
fn read<'a>(
&self,
fd: Fd,
buffer: &'a mut [u8],
) -> impl Future<Output = Result<usize>> + use<'a> {
let result =
unsafe { libc::read(fd.0, buffer.as_mut_ptr().cast(), buffer.len()) }.errno_if_m1();
ready(result.map(|len| len.try_into().unwrap()))
}
}
impl Write for RealSystem {
fn write<'a>(&self, fd: Fd, buffer: &'a [u8]) -> impl Future<Output = Result<usize>> + use<'a> {
let result =
unsafe { libc::write(fd.0, buffer.as_ptr().cast(), buffer.len()) }.errno_if_m1();
ready(result.map(|len| len.try_into().unwrap()))
}
}
impl Seek for RealSystem {
fn lseek(&self, fd: Fd, position: SeekFrom) -> Result<u64> {
let (offset, whence) = match position {
SeekFrom::Start(offset) => {
let offset = offset.try_into().map_err(|_| Errno::EOVERFLOW)?;
(offset, libc::SEEK_SET)
}
SeekFrom::End(offset) => (offset, libc::SEEK_END),
SeekFrom::Current(offset) => (offset, libc::SEEK_CUR),
};
let new_offset = unsafe { libc::lseek(fd.0, offset, whence) }.errno_if_m1()?;
Ok(new_offset.try_into().unwrap())
}
}
impl Umask for RealSystem {
fn umask(&self, new_mask: Mode) -> Mode {
Mode::from_bits_retain(unsafe { libc::umask(new_mask.bits()) })
}
}
impl GetCwd for RealSystem {
fn getcwd(&self) -> Result<PathBuf> {
let mut buffer = Vec::<u8>::new();
for capacity in [1 << 10, 1 << 12, 1 << 14, 1 << 16] {
buffer.reserve_exact(capacity);
let result = unsafe { libc::getcwd(buffer.as_mut_ptr().cast(), capacity) };
if !result.is_null() {
let len = unsafe { CStr::from_ptr(buffer.as_ptr().cast()) }.count_bytes();
unsafe { buffer.set_len(len) }
buffer.shrink_to_fit();
return Ok(PathBuf::from(UnixString::from_vec(buffer)));
}
let errno = Errno::last();
if errno != Errno::ERANGE {
return Err(errno);
}
}
Err(Errno::ERANGE)
}
}
impl Chdir for RealSystem {
fn chdir(&self, path: &CStr) -> Result<()> {
let result = unsafe { libc::chdir(path.as_ptr()) };
result.errno_if_m1().map(drop)
}
}
impl Clock for RealSystem {
fn now(&self) -> Instant {
Instant::now()
}
}
impl Times for RealSystem {
fn times(&self) -> Result<CpuTimes> {
let mut usage = MaybeUninit::<libc::rusage>::uninit();
unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) }.errno_if_m1()?;
let self_user = unsafe {
(*usage.as_ptr()).ru_utime.tv_sec as f64
+ (*usage.as_ptr()).ru_utime.tv_usec as f64 * 1e-6
};
let self_system = unsafe {
(*usage.as_ptr()).ru_stime.tv_sec as f64
+ (*usage.as_ptr()).ru_stime.tv_usec as f64 * 1e-6
};
unsafe { libc::getrusage(libc::RUSAGE_CHILDREN, usage.as_mut_ptr()) }.errno_if_m1()?;
let children_user = unsafe {
(*usage.as_ptr()).ru_utime.tv_sec as f64
+ (*usage.as_ptr()).ru_utime.tv_usec as f64 * 1e-6
};
let children_system = unsafe {
(*usage.as_ptr()).ru_stime.tv_sec as f64
+ (*usage.as_ptr()).ru_stime.tv_usec as f64 * 1e-6
};
Ok(CpuTimes {
self_user,
self_system,
children_user,
children_system,
})
}
}
impl GetPid for RealSystem {
fn getsid(&self, pid: Pid) -> Result<Pid> {
unsafe { libc::getsid(pid.0) }.errno_if_m1().map(Pid)
}
fn getpid(&self) -> Pid {
Pid(unsafe { libc::getpid() })
}
fn getppid(&self) -> Pid {
Pid(unsafe { libc::getppid() })
}
fn getpgrp(&self) -> Pid {
Pid(unsafe { libc::getpgrp() })
}
}
impl SetPgid for RealSystem {
fn setpgid(&self, pid: Pid, pgid: Pid) -> Result<()> {
let result = unsafe { libc::setpgid(pid.0, pgid.0) };
result.errno_if_m1().map(drop)
}
}
const fn to_signal_number(sig: c_int) -> signal::Number {
signal::Number::from_raw_unchecked(NonZero::new(sig).unwrap())
}
impl Signals for RealSystem {
const SIGABRT: signal::Number = to_signal_number(libc::SIGABRT);
const SIGALRM: signal::Number = to_signal_number(libc::SIGALRM);
const SIGBUS: signal::Number = to_signal_number(libc::SIGBUS);
const SIGCHLD: signal::Number = to_signal_number(libc::SIGCHLD);
#[cfg(any(
target_os = "aix",
target_os = "horizon",
target_os = "illumos",
target_os = "solaris",
))]
const SIGCLD: Option<signal::Number> = Some(to_signal_number(libc::SIGCLD));
#[cfg(not(any(
target_os = "aix",
target_os = "horizon",
target_os = "illumos",
target_os = "solaris",
)))]
const SIGCLD: Option<signal::Number> = None;
const SIGCONT: signal::Number = to_signal_number(libc::SIGCONT);
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "linux",
target_os = "redox",
)))]
const SIGEMT: Option<signal::Number> = Some(to_signal_number(libc::SIGEMT));
#[cfg(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "linux",
target_os = "redox",
))]
const SIGEMT: Option<signal::Number> = None;
const SIGFPE: signal::Number = to_signal_number(libc::SIGFPE);
const SIGHUP: signal::Number = to_signal_number(libc::SIGHUP);
const SIGILL: signal::Number = to_signal_number(libc::SIGILL);
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "linux",
target_os = "redox",
)))]
const SIGINFO: Option<signal::Number> = Some(to_signal_number(libc::SIGINFO));
#[cfg(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "linux",
target_os = "redox",
))]
const SIGINFO: Option<signal::Number> = None;
const SIGINT: signal::Number = to_signal_number(libc::SIGINT);
#[cfg(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "horizon",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "solaris",
))]
const SIGIO: Option<signal::Number> = Some(to_signal_number(libc::SIGIO));
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "horizon",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "solaris",
)))]
const SIGIO: Option<signal::Number> = None;
const SIGIOT: signal::Number = to_signal_number(libc::SIGIOT);
const SIGKILL: signal::Number = to_signal_number(libc::SIGKILL);
#[cfg(target_os = "horizon")]
const SIGLOST: Option<signal::Number> = Some(to_signal_number(libc::SIGLOST));
#[cfg(not(target_os = "horizon"))]
const SIGLOST: Option<signal::Number> = None;
const SIGPIPE: signal::Number = to_signal_number(libc::SIGPIPE);
#[cfg(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "horizon",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "solaris",
))]
const SIGPOLL: Option<signal::Number> = Some(to_signal_number(libc::SIGPOLL));
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "horizon",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "solaris",
)))]
const SIGPOLL: Option<signal::Number> = None;
const SIGPROF: signal::Number = to_signal_number(libc::SIGPROF);
#[cfg(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "redox",
target_os = "solaris",
))]
const SIGPWR: Option<signal::Number> = Some(to_signal_number(libc::SIGPWR));
#[cfg(not(any(
target_os = "aix",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "nto",
target_os = "redox",
target_os = "solaris",
)))]
const SIGPWR: Option<signal::Number> = None;
const SIGQUIT: signal::Number = to_signal_number(libc::SIGQUIT);
const SIGSEGV: signal::Number = to_signal_number(libc::SIGSEGV);
#[cfg(all(
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
))]
const SIGSTKFLT: Option<signal::Number> = Some(to_signal_number(libc::SIGSTKFLT));
#[cfg(not(all(
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
)))]
const SIGSTKFLT: Option<signal::Number> = None;
const SIGSTOP: signal::Number = to_signal_number(libc::SIGSTOP);
const SIGSYS: signal::Number = to_signal_number(libc::SIGSYS);
const SIGTERM: signal::Number = to_signal_number(libc::SIGTERM);
#[cfg(target_os = "freebsd")]
const SIGTHR: Option<signal::Number> = Some(to_signal_number(libc::SIGTHR));
#[cfg(not(target_os = "freebsd"))]
const SIGTHR: Option<signal::Number> = None;
const SIGTRAP: signal::Number = to_signal_number(libc::SIGTRAP);
const SIGTSTP: signal::Number = to_signal_number(libc::SIGTSTP);
const SIGTTIN: signal::Number = to_signal_number(libc::SIGTTIN);
const SIGTTOU: signal::Number = to_signal_number(libc::SIGTTOU);
const SIGURG: signal::Number = to_signal_number(libc::SIGURG);
const SIGUSR1: signal::Number = to_signal_number(libc::SIGUSR1);
const SIGUSR2: signal::Number = to_signal_number(libc::SIGUSR2);
const SIGVTALRM: signal::Number = to_signal_number(libc::SIGVTALRM);
const SIGWINCH: signal::Number = to_signal_number(libc::SIGWINCH);
const SIGXCPU: signal::Number = to_signal_number(libc::SIGXCPU);
const SIGXFSZ: signal::Number = to_signal_number(libc::SIGXFSZ);
fn sigrt_range(&self) -> Option<RangeInclusive<signal::Number>> {
let raw_range = signal::rt_range();
let start = signal::Number::from_raw_unchecked(NonZero::new(*raw_range.start())?);
let end = signal::Number::from_raw_unchecked(NonZero::new(*raw_range.end())?);
Some(start..=end).filter(|range| !range.is_empty())
}
}
impl Sigmask for RealSystem {
fn sigmask(
&self,
op: Option<(SigmaskOp, &[signal::Number])>,
old_mask: Option<&mut Vec<signal::Number>>,
) -> impl Future<Output = Result<()>> + use<> {
ready((|| unsafe {
let (how, raw_mask) = match op {
None => (libc::SIG_BLOCK, None),
Some((op, mask)) => {
let how = match op {
SigmaskOp::Add => libc::SIG_BLOCK,
SigmaskOp::Remove => libc::SIG_UNBLOCK,
SigmaskOp::Set => libc::SIG_SETMASK,
};
let mut raw_mask = MaybeUninit::<libc::sigset_t>::uninit();
libc::sigemptyset(raw_mask.as_mut_ptr()).errno_if_m1()?;
for &signal in mask {
libc::sigaddset(raw_mask.as_mut_ptr(), signal.as_raw()).errno_if_m1()?;
}
(how, Some(raw_mask))
}
};
let mut old_mask_pair = match old_mask {
None => None,
Some(old_mask) => {
let mut raw_old_mask = MaybeUninit::<libc::sigset_t>::uninit();
libc::sigemptyset(raw_old_mask.as_mut_ptr()).errno_if_m1()?;
Some((old_mask, raw_old_mask))
}
};
let raw_set_ptr = raw_mask
.as_ref()
.map_or(std::ptr::null(), |raw_set| raw_set.as_ptr());
let raw_old_set_ptr = old_mask_pair
.as_mut()
.map_or(std::ptr::null_mut(), |(_, raw_old_mask)| {
raw_old_mask.as_mut_ptr()
});
let result = libc::sigprocmask(how, raw_set_ptr, raw_old_set_ptr);
result.errno_if_m1().map(drop)?;
if let Some((old_mask, raw_old_mask)) = old_mask_pair {
old_mask.clear();
signal::sigset_to_vec(raw_old_mask.as_ptr(), old_mask);
}
Ok(())
})())
}
}
impl GetSigaction for RealSystem {
fn get_sigaction(&self, signal: signal::Number) -> Result<Disposition> {
sigaction_impl(signal, None)
}
}
impl Sigaction for RealSystem {
fn sigaction(&self, signal: signal::Number, handling: Disposition) -> Result<Disposition> {
sigaction_impl(signal, Some(handling))
}
}
impl CaughtSignals for RealSystem {
fn caught_signals(&self) -> Vec<signal::Number> {
let mut signals = Vec::new();
for slot in &CAUGHT_SIGNALS {
compiler_fence(Ordering::Acquire);
let number = slot.swap(0, Ordering::Relaxed);
let Some(number) = NonZero::new(number as signal::RawNumber) else {
break;
};
signals.push(signal::Number::from_raw_unchecked(number));
}
signals
}
}
impl SendSignal for RealSystem {
fn kill(
&self,
target: Pid,
signal: Option<signal::Number>,
) -> impl Future<Output = Result<()>> + use<> {
let raw = signal.map_or(0, signal::Number::as_raw);
let result = unsafe { libc::kill(target.0, raw) }.errno_if_m1().map(drop);
ready(result)
}
fn raise(&self, signal: signal::Number) -> impl Future<Output = Result<()>> + use<> {
let raw = signal.as_raw();
let result = unsafe { libc::raise(raw) }.errno_if_m1().map(drop);
ready(result)
}
}
impl Select for RealSystem {
fn select<'a>(
&self,
readers: &'a mut Vec<Fd>,
writers: &'a mut Vec<Fd>,
timeout: Option<Duration>,
signal_mask: Option<&[signal::Number]>,
) -> impl Future<Output = Result<c_int>> + use<'a> {
ready((|| {
use std::ptr::{null, null_mut};
let max_fd = readers.iter().chain(writers.iter()).max();
let nfds = max_fd
.map(|fd| fd.0.checked_add(1).ok_or(Errno::EBADF))
.transpose()?
.unwrap_or(0);
fn to_raw_fd_set(fds: &[Fd]) -> MaybeUninit<libc::fd_set> {
let mut raw_fds = MaybeUninit::<libc::fd_set>::uninit();
unsafe {
libc::FD_ZERO(raw_fds.as_mut_ptr());
for fd in fds {
libc::FD_SET(fd.0, raw_fds.as_mut_ptr());
}
}
raw_fds
}
let mut raw_readers = to_raw_fd_set(readers);
let mut raw_writers = to_raw_fd_set(writers);
let readers_ptr = raw_readers.as_mut_ptr();
let writers_ptr = raw_writers.as_mut_ptr();
let errors = null_mut();
let timeout_spec = to_timespec(timeout.unwrap_or_default());
let timeout_ptr = if timeout.is_some() {
timeout_spec.as_ptr()
} else {
null()
};
let mut raw_mask = MaybeUninit::<libc::sigset_t>::uninit();
let raw_mask_ptr = match signal_mask {
None => null(),
Some(signal_mask) => {
unsafe { libc::sigemptyset(raw_mask.as_mut_ptr()) }.errno_if_m1()?;
for &signal in signal_mask {
unsafe { libc::sigaddset(raw_mask.as_mut_ptr(), signal.as_raw()) }
.errno_if_m1()?;
}
raw_mask.as_ptr()
}
};
let count = unsafe {
libc::pselect(
nfds,
readers_ptr,
writers_ptr,
errors,
timeout_ptr,
raw_mask_ptr,
)
}
.errno_if_m1()?;
readers.retain(|fd| unsafe { libc::FD_ISSET(fd.0, readers_ptr) });
writers.retain(|fd| unsafe { libc::FD_ISSET(fd.0, writers_ptr) });
Ok(count)
})())
}
}
impl Isatty for RealSystem {
fn isatty(&self, fd: Fd) -> bool {
(unsafe { libc::isatty(fd.0) } != 0)
}
}
impl TcGetPgrp for RealSystem {
fn tcgetpgrp(&self, fd: Fd) -> Result<Pid> {
unsafe { libc::tcgetpgrp(fd.0) }.errno_if_m1().map(Pid)
}
}
impl TcSetPgrp for RealSystem {
fn tcsetpgrp(&self, fd: Fd, pgid: Pid) -> impl Future<Output = Result<()>> + use<> {
let result = unsafe { libc::tcsetpgrp(fd.0, pgid.0) };
ready(result.errno_if_m1().map(drop))
}
}
impl Fork for RealSystem {
fn new_child_process(&self) -> Result<ChildProcessStarter<Self>> {
let raw_pid = unsafe { libc::fork() }.errno_if_m1()?;
if raw_pid != 0 {
return Ok(Box::new(move |_env, _task| Pid(raw_pid)));
}
Ok(Box::new(|env, task| {
let system = Rc::clone(&env.system);
match system.run_real(task(env)) {}
}))
}
}
impl Wait for RealSystem {
fn wait(&self, target: Pid) -> Result<Option<(Pid, ProcessState)>> {
let mut status = 0;
let options = libc::WUNTRACED | libc::WCONTINUED | libc::WNOHANG;
match unsafe { libc::waitpid(target.0, &mut status, options) } {
-1 => Err(Errno::last()),
0 => Ok(None),
pid => {
let state = if libc::WIFCONTINUED(status) {
ProcessState::Running
} else if libc::WIFEXITED(status) {
let exit_status = libc::WEXITSTATUS(status);
ProcessState::exited(exit_status)
} else if libc::WIFSIGNALED(status) {
let signal = libc::WTERMSIG(status);
let core_dump = libc::WCOREDUMP(status);
let raw_number = unsafe { NonZero::new_unchecked(signal) };
let signal = signal::Number::from_raw_unchecked(raw_number);
let process_result = ProcessResult::Signaled { signal, core_dump };
process_result.into()
} else if libc::WIFSTOPPED(status) {
let signal = libc::WSTOPSIG(status);
let raw_number = unsafe { NonZero::new_unchecked(signal) };
let signal = signal::Number::from_raw_unchecked(raw_number);
ProcessState::stopped(signal)
} else {
unreachable!()
};
Ok(Some((Pid(pid), state)))
}
}
}
}
impl Exec for RealSystem {
fn execve(
&self,
path: &CStr,
args: &[CString],
envs: &[CString],
) -> impl Future<Output = Result<Infallible>> + use<> {
fn to_pointer_array<S: AsRef<CStr>>(strs: &[S]) -> Vec<*const libc::c_char> {
strs.iter()
.map(|s| s.as_ref().as_ptr())
.chain(std::iter::once(std::ptr::null()))
.collect()
}
let args = to_pointer_array(args);
let envs = to_pointer_array(envs);
loop {
let _ = unsafe { libc::execve(path.as_ptr(), args.as_ptr(), envs.as_ptr()) };
let errno = Errno::last();
if errno != Errno::EINTR {
return ready(Err(errno));
}
}
}
}
impl Exit for RealSystem {
#[allow(unreachable_code)]
fn exit(&self, exit_status: ExitStatus) -> impl Future<Output = Infallible> + use<> {
ready(unsafe { libc::_exit(exit_status.0) })
}
}
impl GetUid for RealSystem {
fn getuid(&self) -> Uid {
Uid(unsafe { libc::getuid() })
}
fn geteuid(&self) -> Uid {
Uid(unsafe { libc::geteuid() })
}
fn getgid(&self) -> Gid {
Gid(unsafe { libc::getgid() })
}
fn getegid(&self) -> Gid {
Gid(unsafe { libc::getegid() })
}
}
impl GetPw for RealSystem {
fn getpwnam_dir(&self, name: &CStr) -> Result<Option<PathBuf>> {
Errno::clear();
let passwd = unsafe { libc::getpwnam(name.as_ptr()) };
if passwd.is_null() {
let errno = Errno::last();
return if errno == Errno::NO_ERROR {
Ok(None)
} else {
Err(errno)
};
}
let dir = unsafe { CStr::from_ptr((*passwd).pw_dir) };
Ok(Some(UnixString::from_vec(dir.to_bytes().to_vec()).into()))
}
}
impl Sysconf for RealSystem {
fn confstr_path(&self) -> Result<UnixString> {
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos"
))]
unsafe {
let size = libc::confstr(libc::_CS_PATH, std::ptr::null_mut(), 0);
if size == 0 {
return Err(Errno::last());
}
let mut buffer = Vec::<u8>::with_capacity(size);
let final_size = libc::confstr(libc::_CS_PATH, buffer.as_mut_ptr() as *mut _, size);
if final_size == 0 {
return Err(Errno::last());
}
if final_size > size {
return Err(Errno::ERANGE);
}
buffer.set_len(final_size - 1); return Ok(UnixString::from_vec(buffer));
}
#[allow(unreachable_code)]
Err(Errno::ENOSYS)
}
}
impl ShellPath for RealSystem {
fn shell_path(&self) -> CString {
#[cfg(any(target_os = "linux", target_os = "android"))]
if self.is_executable_file(c"/proc/self/exe") {
return c"/proc/self/exe".to_owned();
}
if let Ok(path) = self.confstr_path() {
if let Some(full_path) = path
.as_bytes()
.split(|b| *b == b':')
.map(|dir| Path::new(UnixStr::from_bytes(dir)).join("sh"))
.filter(|full_path| full_path.is_absolute())
.filter_map(|full_path| CString::new(full_path.into_unix_string().into_vec()).ok())
.find(|full_path| self.is_executable_file(full_path))
{
return full_path;
}
}
c"/bin/sh".to_owned()
}
}
impl GetRlimit for RealSystem {
fn getrlimit(&self, resource: Resource) -> Result<LimitPair> {
let raw_resource = resource.as_raw_type().ok_or(Errno::EINVAL)?;
let mut limits = MaybeUninit::<libc::rlimit>::uninit();
unsafe { libc::getrlimit(raw_resource as _, limits.as_mut_ptr()) }.errno_if_m1()?;
Ok(LimitPair {
soft: unsafe { (*limits.as_ptr()).rlim_cur },
hard: unsafe { (*limits.as_ptr()).rlim_max },
})
}
}
impl SetRlimit for RealSystem {
fn setrlimit(&self, resource: Resource, limits: LimitPair) -> Result<()> {
let raw_resource = resource.as_raw_type().ok_or(Errno::EINVAL)?;
let mut rlimit = MaybeUninit::<libc::rlimit>::uninit();
unsafe {
(&raw mut (*rlimit.as_mut_ptr()).rlim_cur).write(limits.soft);
(&raw mut (*rlimit.as_mut_ptr()).rlim_max).write(limits.hard);
}
unsafe { libc::setrlimit(raw_resource as _, rlimit.as_ptr()) }.errno_if_m1()?;
Ok(())
}
}
#[derive(Debug)]
struct RealDir(NonNull<DIR>);
impl Drop for RealDir {
fn drop(&mut self) {
unsafe {
libc::closedir(self.0.as_ptr());
}
}
}
impl Dir for RealDir {
fn next(&mut self) -> Result<Option<DirEntry<'_>>> {
Errno::clear();
let entry = unsafe { libc::readdir(self.0.as_ptr()) };
let errno = Errno::last();
if entry.is_null() {
if errno == Errno::NO_ERROR {
Ok(None)
} else {
Err(errno)
}
} else {
let name = unsafe { CStr::from_ptr((&raw const (*entry).d_name).cast()) };
let name = UnixStr::from_bytes(name.to_bytes());
Ok(Some(DirEntry { name }))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn real_system_directory_entries() {
let system = unsafe { RealSystem::new() };
let mut dir = system.opendir(c".").unwrap();
let mut count = 0;
while dir.next().unwrap().is_some() {
count += 1;
}
assert!(count > 0);
}
#[test]
fn real_system_caught_signals() {
unsafe {
let system = RealSystem::new();
let result = system.caught_signals();
assert_eq!(result, []);
catch_signal(libc::SIGINT);
catch_signal(libc::SIGTERM);
catch_signal(libc::SIGTERM);
catch_signal(libc::SIGCHLD);
let sigint = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGINT).unwrap());
let sigterm = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGTERM).unwrap());
let sigchld = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGCHLD).unwrap());
let result = system.caught_signals();
assert_eq!(result, [sigint, sigterm, sigchld]);
let result = system.caught_signals();
assert_eq!(result, []);
}
}
}