use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use crate::platform::process::{
ProcessId, ProcessIdentity, ProcessIdentityAction, ProcessIdentityActionError,
ProcessIdentityCapture,
};
pub fn configure_session_leader_command(command: &mut std::process::Command) {
use std::os::unix::process::CommandExt;
unsafe {
command.pre_exec(|| {
if libc::setsid() == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
});
}
}
pub fn force_terminate_child_process_group(child: &std::process::Child) -> io::Result<()> {
let pid = ProcessId::new(child.id())
.map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?;
ensure_unreaped_child(pid)?;
if unsafe { libc::killpg(pid.native_signed(), libc::SIGKILL) } == 0 {
return Ok(());
}
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(libc::ESRCH) {
Ok(())
} else {
Err(error)
}
}
fn ensure_unreaped_child(pid: ProcessId) -> io::Result<()> {
loop {
let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
let rc = unsafe {
libc::waitid(
libc::P_PID,
pid.get() as libc::id_t,
&mut info,
libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
)
};
if rc == 0 {
return Ok(());
}
let error = io::Error::last_os_error();
match error.raw_os_error() {
Some(libc::EINTR) => continue,
Some(libc::ECHILD) => {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"child was already reaped; its process group can no longer be proven",
))
}
_ => return Err(error),
}
}
}
pub fn set_priority_identity(
identity: ProcessIdentity,
priority: crate::ProcessPriority,
) -> Result<ProcessIdentityAction, ProcessIdentityActionError> {
let nice = match priority {
crate::ProcessPriority::Normal => None,
crate::ProcessPriority::Low => Some(10),
crate::ProcessPriority::Idle => Some(19),
crate::ProcessPriority::High => Some(-5),
};
match crate::platform_imp::capture_process_identity(identity.pid()) {
ProcessIdentityCapture::Found(current) if current == identity => {}
ProcessIdentityCapture::Found(_) => return Err(ProcessIdentityActionError::StaleIdentity),
ProcessIdentityCapture::Exited => return Ok(ProcessIdentityAction::AlreadyExited),
ProcessIdentityCapture::Unavailable(reason) => {
return Err(ProcessIdentityActionError::Unavailable(reason))
}
ProcessIdentityCapture::Error(error) => return Err(ProcessIdentityActionError::Host(error)),
}
let Some(nice) = nice else {
return Ok(ProcessIdentityAction::Performed);
};
let pid = ProcessId::new(identity.pid()).map_err(|error| {
ProcessIdentityActionError::Host(io::Error::new(io::ErrorKind::InvalidInput, error))
})?;
if unsafe { libc::setpriority(libc::PRIO_PROCESS, pid.get(), nice) } == 0 {
return Ok(ProcessIdentityAction::Performed);
}
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(libc::ESRCH) {
Ok(ProcessIdentityAction::AlreadyExited)
} else {
Err(ProcessIdentityActionError::Host(error))
}
}
pub fn detach_standard_streams() {
let null = unsafe { libc::open(c"/dev/null".as_ptr(), libc::O_RDWR | libc::O_CLOEXEC) };
if null < 0 {
return;
}
for target in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
let _ = unsafe { libc::dup2(null, target) };
}
if null > libc::STDERR_FILENO {
let _ = unsafe { libc::close(null) };
}
}
pub fn redirect_standard_streams_to_log(path: &std::path::Path) -> bool {
use std::os::unix::ffi::OsStrExt;
let Ok(path) = std::ffi::CString::new(path.as_os_str().as_bytes()) else {
return false;
};
let null = unsafe { libc::open(c"/dev/null".as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if null < 0 {
return false;
}
let _ = unsafe { libc::dup2(null, libc::STDIN_FILENO) };
if null > libc::STDERR_FILENO {
let _ = unsafe { libc::close(null) };
}
let log = unsafe {
libc::open(
path.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND | libc::O_CLOEXEC,
0o644,
)
};
if log < 0 {
return false;
}
let _ = unsafe { libc::dup2(log, libc::STDOUT_FILENO) };
let _ = unsafe { libc::dup2(log, libc::STDERR_FILENO) };
if log > libc::STDERR_FILENO {
let _ = unsafe { libc::close(log) };
}
true
}
pub fn native_jobserver_supported() -> bool {
true
}
#[derive(Debug)]
pub struct NativeJobserver {
read: OwnedFd,
write: OwnedFd,
}
impl NativeJobserver {
pub fn create(capacity: usize) -> io::Result<Self> {
if capacity == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"jobserver capacity must be greater than zero",
));
}
let mut fds = [0_i32; 2];
if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
return Err(io::Error::last_os_error());
}
let (read, write) = unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) };
let tokens = vec![b'+'; capacity];
let written =
unsafe { libc::write(write.as_raw_fd(), tokens.as_ptr().cast(), tokens.len()) };
if written < 0 {
return Err(io::Error::last_os_error());
}
if written as usize != tokens.len() {
return Err(io::Error::other(format!(
"jobserver pipe priming wrote {written} of {} bytes",
tokens.len()
)));
}
Ok(Self { read, write })
}
pub fn auth_string(&self) -> String {
format!("{},{}", self.read.as_raw_fd(), self.write.as_raw_fd())
}
}