use std::ffi::CString;
use std::io;
use std::os::unix::io::RawFd;
use std::thread;
use crossbeam_channel::{Receiver, Sender};
unsafe extern "C" {
static environ: *const *const libc::c_char;
}
pub(crate) struct SpawnRequest {
pub(crate) program: CString,
pub(crate) args: Vec<CString>,
pub(crate) request_id: u64,
pub(crate) response_tx: Sender<SpawnResponse>,
pub(crate) wake_handle: crate::wakeup::WakeHandle,
}
pub(crate) struct SpawnResponse {
pub(crate) request_id: u64,
pub(crate) result: io::Result<SpawnResult>,
}
pub(crate) struct SpawnResult {
pub(crate) pid: u32,
pub(crate) pidfd: RawFd,
}
pub(crate) struct SpawnerPool {
pub(crate) request_tx: Sender<SpawnRequest>,
_threads: Vec<thread::JoinHandle<()>>,
}
impl SpawnerPool {
pub(crate) fn start(num_threads: usize) -> Self {
let (request_tx, request_rx) = crossbeam_channel::unbounded::<SpawnRequest>();
let mut threads = Vec::with_capacity(num_threads);
for i in 0..num_threads {
let rx = request_rx.clone();
let handle = thread::Builder::new()
.name(format!("ringline-spawner-{i}"))
.spawn(move || spawner_thread(rx))
.expect("failed to spawn spawner thread");
threads.push(handle);
}
SpawnerPool {
request_tx,
_threads: threads,
}
}
}
fn spawner_thread(rx: Receiver<SpawnRequest>) {
while let Ok(req) = rx.recv() {
let result = do_spawn(&req.program, &req.args);
let _ = req.response_tx.send(SpawnResponse {
request_id: req.request_id,
result,
});
req.wake_handle.wake();
}
}
fn do_spawn(program: &CString, args: &[CString]) -> io::Result<SpawnResult> {
let mut argv: Vec<*const libc::c_char> = Vec::with_capacity(args.len() + 2);
argv.push(program.as_ptr());
for arg in args {
argv.push(arg.as_ptr());
}
argv.push(std::ptr::null());
let mut pid: libc::pid_t = 0;
let ret = unsafe {
libc::posix_spawnp(
&mut pid,
program.as_ptr(),
std::ptr::null(), std::ptr::null(), argv.as_ptr() as *const *mut libc::c_char,
environ as *const *mut libc::c_char, )
};
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
#[cfg(target_os = "linux")]
let pidfd = {
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0 as libc::c_int) } as RawFd;
if fd < 0 {
let err = io::Error::last_os_error();
unsafe {
libc::kill(pid, libc::SIGKILL);
}
unsafe {
libc::waitpid(pid, std::ptr::null_mut(), 0);
}
return Err(err);
}
fd
};
#[cfg(not(target_os = "linux"))]
let pidfd: RawFd = -1;
Ok(SpawnResult {
pid: pid as u32,
pidfd,
})
}