Skip to content

Commit add04dd

Browse files
committed
Support --jobserver-auth=fifo:PATH
GNU `make` 4.4, released in October 2022[^1]. The jobserver defaults to use named pipes (via `mkfifo(3)`) on supported platforms by introducing a new IPC style `--jobserver-auth=fifo:PATH`, which `PATH` is the path of fifo[^2]. This commit makes sure that the new style `--jobserver-auth=fifo:PATH` can be forwarded to inherited processes correctly. The support of creating a new client with named pipe will come as a follow-up pull request. [^1]: https://lists.gnu.org/archive/html/info-gnu/2022-10/msg00008.html [^2]: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html
1 parent 23c7fa6 commit add04dd

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
//! The jobserver implementation can be found in [detail online][docs] but
1212
//! basically boils down to a cross-process semaphore. On Unix this is
1313
//! implemented with the `pipe` syscall and read/write ends of a pipe and on
14-
//! Windows this is implemented literally with IPC semaphores.
14+
//! Windows this is implemented literally with IPC semaphores. Starting from
15+
//! GNU `make` version 4.4, named pipe becomes the default way in communication
16+
//! on Unix. This crate also supports that feature in the sense of inheriting
17+
//! and forwarding the correct environment.
1518
//!
1619
//! The jobserver protocol in `make` also dictates when tokens are acquired to
1720
//! run child work, and clients using this crate should take care to implement

src/unix.rs

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
use libc::c_int;
22

3-
use std::fs::File;
3+
use std::fs::{File, OpenOptions};
44
use std::io::{self, Read, Write};
55
use std::mem;
66
use std::mem::MaybeUninit;
77
use std::os::unix::prelude::*;
8+
use std::path::{Path, PathBuf};
89
use std::process::Command;
910
use std::ptr;
1011
use std::sync::{Arc, Once};
1112
use std::thread::{self, Builder, JoinHandle};
1213
use std::time::Duration;
1314

1415
#[derive(Debug)]
15-
pub struct Client {
16-
read: File,
17-
write: File,
16+
pub enum Client {
17+
/// `--jobserver-auth=R,W`
18+
Pipe { read: File, write: File },
19+
/// `--jobserver-auth=fifo:PATH`
20+
Fifo { file: File, path: PathBuf },
1821
}
1922

2023
#[derive(Debug)]
@@ -30,16 +33,18 @@ impl Client {
3033
// wrong!
3134
const BUFFER: [u8; 128] = [b'|'; 128];
3235

33-
set_nonblocking(client.write.as_raw_fd(), true)?;
36+
let mut write = client.write();
37+
38+
set_nonblocking(write.as_raw_fd(), true)?;
3439

3540
while limit > 0 {
3641
let n = limit.min(BUFFER.len());
3742

38-
(&client.write).write_all(&BUFFER[..n])?;
43+
write.write_all(&BUFFER[..n])?;
3944
limit -= n;
4045
}
4146

42-
set_nonblocking(client.write.as_raw_fd(), false)?;
47+
set_nonblocking(write.as_raw_fd(), false)?;
4348

4449
Ok(client)
4550
}
@@ -77,6 +82,31 @@ impl Client {
7782
}
7883

7984
pub unsafe fn open(s: &str) -> Option<Client> {
85+
Client::from_fifo(s).or_else(|| Client::from_pipe(s))
86+
}
87+
88+
/// `--jobserver-auth=fifo:PATH`
89+
fn from_fifo(s: &str) -> Option<Client> {
90+
let mut parts = s.splitn(2, ':');
91+
if parts.next().unwrap() != "fifo" {
92+
return None;
93+
}
94+
let path = match parts.next() {
95+
Some(p) => Path::new(p),
96+
None => return None,
97+
};
98+
let file = match OpenOptions::new().read(true).write(true).open(path) {
99+
Ok(f) => f,
100+
Err(_) => return None,
101+
};
102+
Some(Client::Fifo {
103+
file,
104+
path: path.into(),
105+
})
106+
}
107+
108+
/// `--jobserver-auth=R,W`
109+
unsafe fn from_pipe(s: &str) -> Option<Client> {
80110
let mut parts = s.splitn(2, ',');
81111
let read = parts.next().unwrap();
82112
let write = match parts.next() {
@@ -110,12 +140,28 @@ impl Client {
110140
}
111141

112142
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
113-
Client {
143+
Client::Pipe {
114144
read: File::from_raw_fd(read),
115145
write: File::from_raw_fd(write),
116146
}
117147
}
118148

149+
/// Gets the read end of our jobserver client.
150+
fn read(&self) -> &File {
151+
match self {
152+
Client::Pipe { read, .. } => read,
153+
Client::Fifo { file, .. } => file,
154+
}
155+
}
156+
157+
/// Gets the write end of our jobserver client.
158+
fn write(&self) -> &File {
159+
match self {
160+
Client::Pipe { write, .. } => write,
161+
Client::Fifo { file, .. } => file,
162+
}
163+
}
164+
119165
pub fn acquire(&self) -> io::Result<Acquired> {
120166
// Ignore interrupts and keep trying if that happens
121167
loop {
@@ -150,11 +196,12 @@ impl Client {
150196
// to shut us down, so we otherwise punt all errors upwards.
151197
unsafe {
152198
let mut fd: libc::pollfd = mem::zeroed();
153-
fd.fd = self.read.as_raw_fd();
199+
let mut read = self.read();
200+
fd.fd = read.as_raw_fd();
154201
fd.events = libc::POLLIN;
155202
loop {
156203
let mut buf = [0];
157-
match (&self.read).read(&mut buf) {
204+
match read.read(&mut buf) {
158205
Ok(1) => return Ok(Some(Acquired { byte: buf[0] })),
159206
Ok(_) => {
160207
return Err(io::Error::new(
@@ -192,7 +239,7 @@ impl Client {
192239
// always quickly release a token). If that turns out to not be the
193240
// case we'll get an error anyway!
194241
let byte = data.map(|d| d.byte).unwrap_or(b'+');
195-
match (&self.write).write(&[byte])? {
242+
match self.write().write(&[byte])? {
196243
1 => Ok(()),
197244
_ => Err(io::Error::new(
198245
io::ErrorKind::Other,
@@ -202,22 +249,31 @@ impl Client {
202249
}
203250

204251
pub fn string_arg(&self) -> String {
205-
format!("{},{}", self.read.as_raw_fd(), self.write.as_raw_fd())
252+
match self {
253+
Client::Pipe { read, write } => format!("{},{}", read.as_raw_fd(), write.as_raw_fd()),
254+
Client::Fifo { path, .. } => format!("fifo:{}", path.to_str().unwrap()),
255+
}
206256
}
207257

208258
pub fn available(&self) -> io::Result<usize> {
209259
let mut len = MaybeUninit::<c_int>::uninit();
210-
cvt(unsafe { libc::ioctl(self.read.as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
260+
cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
211261
Ok(unsafe { len.assume_init() } as usize)
212262
}
213263

214264
pub fn configure(&self, cmd: &mut Command) {
265+
match self {
266+
// We `File::open`ed it when inheriting from environment,
267+
// so no need to set cloexec for fifo.
268+
Client::Fifo { .. } => return,
269+
Client::Pipe { .. } => {}
270+
};
215271
// Here we basically just want to say that in the child process
216272
// we'll configure the read/write file descriptors to *not* be
217273
// cloexec, so they're inherited across the exec and specified as
218274
// integers through `string_arg` above.
219-
let read = self.read.as_raw_fd();
220-
let write = self.write.as_raw_fd();
275+
let read = self.read().as_raw_fd();
276+
let write = self.write().as_raw_fd();
221277
unsafe {
222278
cmd.pre_exec(move || {
223279
set_cloexec(read, false)?;

0 commit comments

Comments
 (0)