Skip to content

Commit d93ffd9

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 d93ffd9

File tree

2 files changed

+78
-17
lines changed

2 files changed

+78
-17
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 currect 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: 74 additions & 16 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,32 @@ 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+
drop(set_cloexec(file.as_raw_fd(), true));
103+
Some(Client::Fifo {
104+
file,
105+
path: path.into(),
106+
})
107+
}
108+
109+
/// `--jobserver-auth=R,W`
110+
unsafe fn from_pipe(s: &str) -> Option<Client> {
80111
let mut parts = s.splitn(2, ',');
81112
let read = parts.next().unwrap();
82113
let write = match parts.next() {
@@ -110,12 +141,28 @@ impl Client {
110141
}
111142

112143
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
113-
Client {
144+
Client::Pipe {
114145
read: File::from_raw_fd(read),
115146
write: File::from_raw_fd(write),
116147
}
117148
}
118149

150+
/// Gets the read end of our jobserver client.
151+
fn read(&self) -> &File {
152+
match self {
153+
Client::Pipe { read, .. } => read,
154+
Client::Fifo { file, .. } => file,
155+
}
156+
}
157+
158+
/// Gets the write end of our jobserver client.
159+
fn write(&self) -> &File {
160+
match self {
161+
Client::Pipe { write, .. } => write,
162+
Client::Fifo { file, .. } => file,
163+
}
164+
}
165+
119166
pub fn acquire(&self) -> io::Result<Acquired> {
120167
// Ignore interrupts and keep trying if that happens
121168
loop {
@@ -150,11 +197,12 @@ impl Client {
150197
// to shut us down, so we otherwise punt all errors upwards.
151198
unsafe {
152199
let mut fd: libc::pollfd = mem::zeroed();
153-
fd.fd = self.read.as_raw_fd();
200+
let mut read = self.read();
201+
fd.fd = read.as_raw_fd();
154202
fd.events = libc::POLLIN;
155203
loop {
156204
let mut buf = [0];
157-
match (&self.read).read(&mut buf) {
205+
match read.read(&mut buf) {
158206
Ok(1) => return Ok(Some(Acquired { byte: buf[0] })),
159207
Ok(_) => {
160208
return Err(io::Error::new(
@@ -192,7 +240,7 @@ impl Client {
192240
// always quickly release a token). If that turns out to not be the
193241
// case we'll get an error anyway!
194242
let byte = data.map(|d| d.byte).unwrap_or(b'+');
195-
match (&self.write).write(&[byte])? {
243+
match self.write().write(&[byte])? {
196244
1 => Ok(()),
197245
_ => Err(io::Error::new(
198246
io::ErrorKind::Other,
@@ -202,12 +250,15 @@ impl Client {
202250
}
203251

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

208259
pub fn available(&self) -> io::Result<usize> {
209260
let mut len = MaybeUninit::<c_int>::uninit();
210-
cvt(unsafe { libc::ioctl(self.read.as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
261+
cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
211262
Ok(unsafe { len.assume_init() } as usize)
212263
}
213264

@@ -216,12 +267,19 @@ impl Client {
216267
// we'll configure the read/write file descriptors to *not* be
217268
// cloexec, so they're inherited across the exec and specified as
218269
// integers through `string_arg` above.
219-
let read = self.read.as_raw_fd();
220-
let write = self.write.as_raw_fd();
270+
let read = self.read().as_raw_fd();
271+
let write = self.write().as_raw_fd();
272+
let is_pipe = match self {
273+
Client::Pipe { .. } => true,
274+
_ => false,
275+
};
221276
unsafe {
222277
cmd.pre_exec(move || {
223278
set_cloexec(read, false)?;
224-
set_cloexec(write, false)?;
279+
// Only simple pipe style needs to configure both ends.
280+
if is_pipe {
281+
set_cloexec(write, false)?;
282+
}
225283
Ok(())
226284
});
227285
}

0 commit comments

Comments
 (0)