use std::env;
use std::ffi::{CString, OsStr, OsString};
use std::fs::File;
use std::io::{Error, Result};
use std::iter;
use std::marker::PhantomData;
use std::mem;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, RawFd};
use std::ptr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use libc::{c_char, c_int};
use crate::exec::Redirection;
use crate::process::ExitStatus;
use crate::spawn::StandardStream;
pub use libc::ECHILD;
fn check_err<T: Ord + Default>(num: T) -> Result<T> {
if num < T::default() {
return Err(Error::last_os_error());
}
Ok(num)
}
pub fn set_nonblocking(f: &File) -> Result<()> {
unsafe {
let flags = check_err(libc::fcntl(f.as_raw_fd(), libc::F_GETFL))?;
check_err(libc::fcntl(
f.as_raw_fd(),
libc::F_SETFL,
flags | libc::O_NONBLOCK,
))?;
}
Ok(())
}
#[cfg(not(any(target_os = "aix", target_vendor = "apple", target_os = "haiku")))]
pub fn pipe() -> Result<(File, File)> {
let mut fds = [0; 2];
check_err(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) })?;
Ok(unsafe { (File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1])) })
}
#[cfg(any(target_os = "aix", target_vendor = "apple", target_os = "haiku"))]
pub fn pipe() -> Result<(File, File)> {
let mut fds = [0; 2];
check_err(unsafe { libc::pipe(fds.as_mut_ptr()) })?;
unsafe {
check_err(libc::fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC))?;
check_err(libc::fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC))?;
}
Ok(unsafe { (File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1])) })
}
pub unsafe fn fork() -> Result<Option<u32>> {
let pid = check_err(unsafe { libc::fork() })?;
if pid == 0 {
Ok(None) } else {
Ok(Some(pid as u32)) }
}
pub fn setuid(uid: u32) -> Result<()> {
check_err(unsafe { libc::setuid(uid as libc::uid_t) })?;
Ok(())
}
pub fn setgid(gid: u32) -> Result<()> {
check_err(unsafe { libc::setgid(gid as libc::gid_t) })?;
Ok(())
}
pub fn setpgid(pid: u32, pgid: u32) -> Result<()> {
check_err(unsafe { libc::setpgid(pid as _, pgid as _) })?;
Ok(())
}
fn os_to_cstring(s: &OsStr) -> Result<CString> {
CString::new(s.as_bytes()).map_err(|_| Error::from_raw_os_error(libc::EINVAL))
}
#[derive(Debug)]
struct CVec {
#[allow(dead_code)]
strings: Vec<CString>,
ptrs: Vec<*const c_char>,
}
impl CVec {
fn new(slice: &[impl AsRef<OsStr>]) -> Result<CVec> {
let maybe_strings: Result<Vec<CString>> =
slice.iter().map(|x| os_to_cstring(x.as_ref())).collect();
let strings = maybe_strings?;
let ptrs: Vec<_> = strings
.iter()
.map(|s| s.as_bytes_with_nul().as_ptr() as _)
.chain(iter::once(ptr::null()))
.collect();
Ok(CVec { strings, ptrs })
}
pub fn as_c_vec(&self) -> *const *const c_char {
self.ptrs.as_ptr()
}
}
fn split_path(mut path: &OsStr) -> impl Iterator<Item = &OsStr> {
std::iter::from_fn(move || {
while let Some(pos) = path.as_bytes().iter().position(|&c| c == b':') {
let piece = OsStr::from_bytes(&path.as_bytes()[..pos]);
path = OsStr::from_bytes(&path.as_bytes()[pos + 1..]);
if !piece.is_empty() {
return Some(piece);
}
}
let piece = path;
path = OsStr::new("");
if !piece.is_empty() {
return Some(piece);
}
None
})
}
struct PrepExec {
cmd: OsString,
argvec: CVec,
envvec: Option<CVec>,
search_path: Option<OsString>,
prealloc_exe: Vec<u8>,
}
impl PrepExec {
fn new(
cmd: OsString,
argvec: CVec,
envvec: Option<CVec>,
search_path: Option<OsString>,
) -> PrepExec {
let mut max_exe_len = cmd.len() + 1;
if let Some(ref search_path) = search_path {
max_exe_len += 1 + split_path(search_path).map(OsStr::len).max().unwrap_or(0);
}
PrepExec {
cmd,
argvec,
envvec,
search_path,
prealloc_exe: Vec::with_capacity(max_exe_len),
}
}
fn exec(self) -> Result<()> {
let mut this = std::mem::ManuallyDrop::new(self);
let mut exe = std::mem::ManuallyDrop::new(std::mem::take(&mut this.prealloc_exe));
if let Some(ref search_path) = this.search_path {
let mut err = Ok(());
for dir in split_path(search_path.as_os_str()) {
err = this.libc_exec(PrepExec::assemble_exe(
&mut exe,
&[dir.as_bytes(), b"/", this.cmd.as_bytes()],
));
assert!(err.is_err());
}
return err;
}
this.libc_exec(PrepExec::assemble_exe(&mut exe, &[this.cmd.as_bytes()]))?;
unreachable!();
}
fn assemble_exe<'a>(storage: &'a mut Vec<u8>, components: &[&[u8]]) -> &'a [u8] {
storage.truncate(0);
for comp in components {
storage.extend_from_slice(comp);
}
storage.push(0u8);
storage.as_slice()
}
fn libc_exec(&self, exe: &[u8]) -> Result<()> {
unsafe {
match self.envvec.as_ref() {
Some(envvec) => {
libc::execve(exe.as_ptr() as _, self.argvec.as_c_vec(), envvec.as_c_vec())
}
None => libc::execv(exe.as_ptr() as _, self.argvec.as_c_vec()),
}
};
Err(Error::last_os_error())
}
}
pub fn prep_exec(
cmd: impl AsRef<OsStr>,
args: &[impl AsRef<OsStr>],
env: Option<&[impl AsRef<OsStr>]>,
) -> Result<impl FnOnce() -> Result<()>> {
let cmd = cmd.as_ref().to_owned();
let argvec = CVec::new(args)?;
let envvec = if let Some(env) = env {
Some(CVec::new(env)?)
} else {
None
};
let search_path = if !cmd.as_bytes().contains(&b'/') {
env::var_os("PATH")
.and_then(|p| if p.is_empty() { None } else { Some(p) })
} else {
None
};
let prep = PrepExec::new(cmd, argvec, envvec, search_path);
Ok(move || prep.exec())
}
pub fn prep_chdir(dir: &OsStr) -> Result<impl FnOnce() -> Result<()>> {
let dir = os_to_cstring(dir)?;
Ok(move || {
let ret = check_err(unsafe { libc::chdir(dir.as_ptr()) });
std::mem::forget(dir);
ret.map(|_| ())
})
}
pub fn _exit(status: u8) -> ! {
unsafe { libc::_exit(status as c_int) }
}
pub const WNOHANG: i32 = libc::WNOHANG;
pub fn waitpid(pid: u32, flags: i32) -> Result<(u32, ExitStatus)> {
let mut status = 0 as c_int;
let pid = check_err(unsafe {
libc::waitpid(
pid as libc::pid_t,
&mut status as *mut c_int,
flags as c_int,
)
})?;
Ok((pid as u32, ExitStatus::from_raw(status)))
}
#[cfg(target_os = "linux")]
pub fn pidfd_open(pid: u32) -> Result<std::os::unix::io::OwnedFd> {
let fd =
check_err(unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0) })? as RawFd;
Ok(unsafe { std::os::unix::io::OwnedFd::from_raw_fd(fd) })
}
pub use libc::{SIGKILL, SIGTERM};
pub fn kill(pid: u32, signal: i32) -> Result<()> {
check_err(unsafe { libc::kill(pid as c_int, signal) })?;
Ok(())
}
pub fn killpg(pgid: u32, signal: i32) -> Result<()> {
check_err(unsafe { libc::killpg(pgid as c_int, signal) })?;
Ok(())
}
pub const F_GETFD: i32 = libc::F_GETFD;
pub const F_SETFD: i32 = libc::F_SETFD;
pub const FD_CLOEXEC: i32 = libc::FD_CLOEXEC;
pub fn fcntl(fd: i32, cmd: i32, arg1: Option<i32>) -> Result<i32> {
check_err(unsafe {
match arg1 {
Some(arg1) => libc::fcntl(fd, cmd, arg1),
None => libc::fcntl(fd, cmd),
}
})
}
pub fn dup2(oldfd: i32, newfd: i32) -> Result<()> {
check_err(unsafe { libc::dup2(oldfd, newfd) })?;
Ok(())
}
pub fn make_redirection_to_standard_stream(which: StandardStream) -> Result<Arc<Redirection>> {
let file = unsafe { File::from_raw_fd(which as RawFd) };
let stream = Arc::new(Redirection::File(file));
mem::forget(Arc::clone(&stream));
Ok(stream)
}
pub fn reset_sigpipe() -> Result<()> {
unsafe {
let mut set: mem::MaybeUninit<libc::sigset_t> = mem::MaybeUninit::uninit();
check_err(libc::sigemptyset(set.as_mut_ptr()))?;
let set = set.assume_init();
check_err(libc::pthread_sigmask(
libc::SIG_SETMASK,
&set,
ptr::null_mut(),
))?;
if libc::signal(libc::SIGPIPE, libc::SIG_DFL) == libc::SIG_ERR {
return Err(Error::last_os_error());
}
}
Ok(())
}
#[repr(C)]
pub struct PollFd<'a>(libc::pollfd, PhantomData<&'a ()>);
impl PollFd<'_> {
pub fn new(fd: Option<BorrowedFd<'_>>, events: i16) -> PollFd<'_> {
PollFd(
libc::pollfd {
fd: fd.map(|f| f.as_raw_fd()).unwrap_or(-1),
events,
revents: 0,
},
PhantomData,
)
}
pub fn test(&self, mask: i16) -> bool {
self.0.revents & mask != 0
}
}
pub use libc::{POLLHUP, POLLIN, POLLOUT};
pub fn poll(fds: &mut [PollFd<'_>], mut timeout: Option<Duration>) -> Result<usize> {
let deadline = timeout.map(|timeout| Instant::now() + timeout);
loop {
let (timeout_ms, overflow) = timeout
.map(|timeout| {
let timeout = timeout.as_millis();
if timeout <= i32::MAX as u128 {
(timeout as i32, false)
} else {
(i32::MAX, true)
}
})
.unwrap_or((-1, false));
let fds_ptr = fds.as_ptr() as *mut libc::pollfd;
let cnt = unsafe { check_err(libc::poll(fds_ptr, fds.len() as libc::nfds_t, timeout_ms))? };
if cnt != 0 || !overflow {
return Ok(cnt as usize);
}
let deadline = deadline.unwrap();
let now = Instant::now();
if now >= deadline {
return Ok(0);
}
timeout = Some(deadline - now);
}
}
#[cfg(test)]
mod tests {
use super::split_path;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
fn s(s: &str) -> Vec<&str> {
split_path(OsStr::new(s))
.map(|osstr| std::str::from_utf8(osstr.as_bytes()).unwrap())
.collect()
}
#[test]
fn test_split_path() {
let empty = Vec::<&OsStr>::new();
assert_eq!(s("a:b"), vec!["a", "b"]);
assert_eq!(s("one:twothree"), vec!["one", "twothree"]);
assert_eq!(s("a:"), vec!["a"]);
assert_eq!(s(""), empty);
assert_eq!(s(":"), empty);
assert_eq!(s("::"), empty);
assert_eq!(s(":::"), empty);
assert_eq!(s("a::b"), vec!["a", "b"]);
assert_eq!(s(":a::::b:"), vec!["a", "b"]);
}
}