#[cfg(unix)]
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct JobserverPool {
capacity: usize,
#[cfg(unix)]
inner: PosixPipe,
#[cfg(not(unix))]
_phantom: std::marker::PhantomData<()>,
}
#[allow(dead_code)]
impl JobserverPool {
pub(crate) fn create(capacity: usize) -> std::io::Result<Self> {
if capacity == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"JobserverPool capacity must be > 0; use no jobserver instead of capacity=0",
));
}
#[cfg(unix)]
{
let inner = PosixPipe::create(capacity)?;
Ok(Self { capacity, inner })
}
#[cfg(not(unix))]
{
let _ = capacity;
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"JobserverPool is POSIX-only in this build; Windows support \
(named pipe form, --jobserver-auth=fifo:NAME) is a separate \
sub-task of #813",
))
}
}
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn auth_string(&self) -> String {
#[cfg(unix)]
{
format!("{},{}", self.inner.read_fd(), self.inner.write_fd())
}
#[cfg(not(unix))]
{
unreachable!("JobserverPool cannot be constructed on non-POSIX in this build")
}
}
}
#[cfg(unix)]
#[derive(Debug)]
struct PosixPipe {
read: OwnedFd,
write: OwnedFd,
}
#[cfg(unix)]
impl PosixPipe {
fn create(capacity: usize) -> std::io::Result<Self> {
let mut fds = [0_i32; 2];
let rc = unsafe { Self::sys_pipe(&mut fds) };
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
let read = unsafe { OwnedFd::from_raw_fd(fds[0]) };
let write = unsafe { OwnedFd::from_raw_fd(fds[1]) };
let bytes = vec![b'+'; capacity];
let written = unsafe {
libc::write(
write.as_raw_fd(),
bytes.as_ptr() as *const libc::c_void,
bytes.len(),
)
};
if written < 0 {
return Err(std::io::Error::last_os_error());
}
if (written as usize) != bytes.len() {
return Err(std::io::Error::other(format!(
"jobserver pipe priming wrote {} of {} bytes",
written,
bytes.len()
)));
}
Ok(Self { read, write })
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "illumos",
target_os = "solaris",
target_os = "redox",
target_os = "emscripten",
))]
unsafe fn sys_pipe(fds: &mut [i32; 2]) -> libc::c_int {
libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
unsafe fn sys_pipe(fds: &mut [i32; 2]) -> libc::c_int {
let rc = libc::pipe(fds.as_mut_ptr());
if rc != 0 {
return rc;
}
for &fd in fds.iter() {
let flags = libc::fcntl(fd, libc::F_GETFD);
if flags == -1 {
libc::close(fds[0]);
libc::close(fds[1]);
return -1;
}
if libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) == -1 {
libc::close(fds[0]);
libc::close(fds[1]);
return -1;
}
}
0
}
fn read_fd(&self) -> RawFd {
self.read.as_raw_fd()
}
fn write_fd(&self) -> RawFd {
self.write.as_raw_fd()
}
}
#[cfg(unix)]
use std::os::fd::FromRawFd;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
#[test]
fn create_with_capacity_succeeds() {
let pool = JobserverPool::create(8).unwrap();
assert_eq!(pool.capacity(), 8);
let auth = pool.auth_string();
let parts: Vec<&str> = auth.split(',').collect();
assert_eq!(parts.len(), 2, "auth string should be R,W: got {auth:?}");
let _r: i32 = parts[0].parse().expect("read fd parseable");
let _w: i32 = parts[1].parse().expect("write fd parseable");
}
#[cfg(unix)]
#[test]
fn create_with_zero_capacity_is_invalid() {
let err = JobserverPool::create(0).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[cfg(unix)]
#[test]
fn pipe_contains_capacity_tokens_after_create() {
use std::os::fd::AsRawFd;
let pool = JobserverPool::create(3).unwrap();
let mut buf = [0_u8; 32];
let n = unsafe {
libc::read(
pool.inner.read.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
)
};
assert!(n >= 0, "read should succeed on a freshly-primed pipe");
assert_eq!(n as usize, 3, "pipe should hold exactly 3 tokens");
for &b in &buf[..3] {
assert_eq!(b, b'+', "token byte should be '+': got {b:?}");
}
}
#[cfg(unix)]
#[test]
fn dropping_pool_closes_pipe() {
use std::os::fd::AsRawFd;
let read_fd;
{
let pool = JobserverPool::create(1).unwrap();
read_fd = pool.inner.read.as_raw_fd();
}
let mut buf = [0_u8; 1];
let n = unsafe { libc::read(read_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
assert_eq!(n, -1, "read on closed fd should fail");
let err = std::io::Error::last_os_error();
assert_eq!(err.raw_os_error(), Some(libc::EBADF));
}
#[cfg(not(unix))]
#[test]
fn windows_create_returns_unsupported() {
let err = JobserverPool::create(4).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::Unsupported);
}
}