Skip to content

Commit 893c8ea

Browse files
committed
Fix passing of fds to children
Record jobserver creation arg in a separate field.
1 parent 194ccdc commit 893c8ea

File tree

2 files changed

+63
-56
lines changed

2 files changed

+63
-56
lines changed

src/error.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub enum FromEnvErrorKind {
3636
NotAPipe,
3737
/// Jobserver inheritance is not supported on this platform.
3838
Unsupported,
39+
/// Cannot clone the jobserver fifo fd
40+
CannotClone,
3941
}
4042

4143
impl FromEnvError {
@@ -50,6 +52,7 @@ impl FromEnvError {
5052
FromEnvErrorInner::NegativeFd(..) => FromEnvErrorKind::NegativeFd,
5153
FromEnvErrorInner::NotAPipe(..) => FromEnvErrorKind::NotAPipe,
5254
FromEnvErrorInner::Unsupported => FromEnvErrorKind::Unsupported,
55+
FromEnvErrorInner::CannotClone(..) => FromEnvErrorKind::CannotClone,
5356
}
5457
}
5558
}
@@ -66,16 +69,17 @@ impl std::fmt::Display for FromEnvError {
6669
FromEnvErrorInner::NotAPipe(fd, None) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe"),
6770
FromEnvErrorInner::NotAPipe(fd, Some(err)) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe: {err}"),
6871
FromEnvErrorInner::Unsupported => write!(f, "jobserver inheritance is not supported on this platform"),
72+
FromEnvErrorInner::CannotClone(fd, err) => write!(f, "file descriptor {fd} created fromjobserver environment variable value cannot be cloned: {err}"),
6973
}
7074
}
7175
}
7276
impl std::error::Error for FromEnvError {
7377
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
7478
match &self.inner {
7579
FromEnvErrorInner::CannotOpenPath(_, err) => Some(err),
76-
FromEnvErrorInner::NotAPipe(_, Some(err)) | FromEnvErrorInner::CannotOpenFd(_, err) => {
77-
Some(err)
78-
}
80+
FromEnvErrorInner::NotAPipe(_, Some(err))
81+
| FromEnvErrorInner::CannotOpenFd(_, err)
82+
| FromEnvErrorInner::CannotClone(_, err) => Some(err),
7983
_ => None,
8084
}
8185
}
@@ -92,4 +96,5 @@ pub(crate) enum FromEnvErrorInner {
9296
NegativeFd(RawFd),
9397
NotAPipe(RawFd, Option<std::io::Error>),
9498
Unsupported,
99+
CannotClone(RawFd, std::io::Error),
95100
}

src/unix.rs

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ use std::sync::{
1616
use std::thread::{self, Builder, JoinHandle};
1717
use std::time::Duration;
1818

19+
#[derive(Debug)]
20+
enum ClientCreationArg {
21+
Fds { read: c_int, write: c_int },
22+
Fifo(Box<Path>),
23+
}
24+
1925
#[derive(Debug)]
2026
pub struct Client {
2127
read: File,
22-
write: Option<File>,
23-
/// If path is not None, then a fifo jobserver is created.
24-
path: Option<Box<Path>>,
25-
supports_non_blocking: bool,
26-
/// it can only go from false -> true but not the other way around, since that
28+
write: File,
29+
creation_arg: ClientCreationArg,
30+
/// it can only go from Some(false) -> Some(true) but not the other way around, since that
2731
/// could cause a race condition.
28-
is_non_blocking: AtomicBool,
32+
is_non_blocking: Option<AtomicBool>,
2933
}
3034

3135
#[derive(Debug)]
@@ -41,7 +45,7 @@ impl Client {
4145
// wrong!
4246
const BUFFER: [u8; 128] = [b'|'; 128];
4347

44-
let mut write = client.write();
48+
let mut write = &client.write;
4549

4650
set_nonblocking(write.as_raw_fd(), true)?;
4751

@@ -109,18 +113,20 @@ impl Client {
109113
FromEnvErrorInner::CannotParse("expected a path after `fifo:`".to_string())
110114
})?;
111115
let path = Path::new(path_str);
116+
112117
let file = OpenOptions::new()
113118
.read(true)
114119
.write(true)
115120
.open(path)
116121
.map_err(|err| FromEnvErrorInner::CannotOpenPath(path_str.to_string(), err))?;
117122

118123
Ok(Some(Client {
119-
read: file,
120-
write: None,
121-
path: Some(path.into()),
122-
supports_non_blocking: true,
123-
is_non_blocking: AtomicBool::new(false),
124+
read: file
125+
.try_clone()
126+
.map_err(|err| FromEnvErrorInner::CannotClone(file.as_raw_fd(), err))?,
127+
write: file,
128+
creation_arg: ClientCreationArg::Fifo(path.into()),
129+
is_non_blocking: Some(AtomicBool::new(false)),
124130
}))
125131
}
126132

@@ -148,6 +154,8 @@ impl Client {
148154
return Err(FromEnvErrorInner::NegativeFd(write));
149155
}
150156

157+
let creation_arg = ClientCreationArg::Fds { read, write };
158+
151159
// Ok so we've got two integers that look like file descriptors, but
152160
// for extra sanity checking let's see if they actually look like
153161
// valid files and instances of a pipe if feature enabled before we
@@ -174,44 +182,39 @@ impl Client {
174182
//
175183
// I tested this on macOS 14 and Linux 6.5.13
176184
#[cfg(target_os = "linux")]
177-
if let Ok(Some(mut jobserver)) =
178-
Self::from_fifo(&format!("fifo:/dev/fd/{}", read.as_raw_fd()))
179-
{
180-
jobserver.path.take();
181-
return Ok(Some(jobserver));
185+
if let (Ok(read), Ok(write)) = (
186+
File::open(format!("/dev/fd/{}", read)),
187+
OpenOptions::new()
188+
.write(true)
189+
.open(format!("/dev/fd/{}", write)),
190+
) {
191+
return Ok(Some(Client {
192+
read,
193+
write,
194+
creation_arg,
195+
is_non_blocking: Some(AtomicBool::new(false)),
196+
}));
182197
}
183198
}
184199
}
185200

186201
Ok(Some(Client {
187202
read: clone_fd_and_set_cloexec(read)?,
188-
write: Some(clone_fd_and_set_cloexec(write)?),
189-
path: None,
190-
supports_non_blocking: false,
191-
is_non_blocking: AtomicBool::new(false),
203+
write: clone_fd_and_set_cloexec(write)?,
204+
creation_arg,
205+
is_non_blocking: None,
192206
}))
193207
}
194208

195209
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
196210
Client {
197211
read: File::from_raw_fd(read),
198-
write: Some(File::from_raw_fd(write)),
199-
path: None,
200-
supports_non_blocking: false,
201-
is_non_blocking: AtomicBool::new(false),
212+
write: File::from_raw_fd(write),
213+
creation_arg: ClientCreationArg::Fds { read, write },
214+
is_non_blocking: None,
202215
}
203216
}
204217

205-
/// Gets the read end of our jobserver client.
206-
fn read(&self) -> &File {
207-
&self.read
208-
}
209-
210-
/// Gets the write end of our jobserver client.
211-
fn write(&self) -> &File {
212-
self.write.as_ref().unwrap_or(&self.read)
213-
}
214-
215218
pub fn acquire(&self) -> io::Result<Acquired> {
216219
// Ignore interrupts and keep trying if that happens
217220
loop {
@@ -246,7 +249,7 @@ impl Client {
246249
// to shut us down, so we otherwise punt all errors upwards.
247250
unsafe {
248251
let mut fd: libc::pollfd = mem::zeroed();
249-
let mut read = self.read();
252+
let mut read = &self.read;
250253
fd.fd = read.as_raw_fd();
251254
fd.events = libc::POLLIN;
252255
loop {
@@ -285,18 +288,17 @@ impl Client {
285288

286289
pub fn try_acquire(&self) -> io::Result<Option<Acquired>> {
287290
let mut buf = [0];
291+
let mut fifo = &self.read;
288292

289-
if !self.supports_non_blocking {
293+
if let Some(is_non_blocking) = self.is_non_blocking.as_ref() {
294+
if !is_non_blocking.load(Ordering::Relaxed) {
295+
set_nonblocking(fifo.as_raw_fd(), true)?;
296+
is_non_blocking.store(true, Ordering::Relaxed);
297+
}
298+
} else {
290299
return Err(io::ErrorKind::Unsupported.into());
291300
}
292301

293-
let mut fifo = self.read();
294-
295-
if !self.is_non_blocking.load(Ordering::Relaxed) {
296-
set_nonblocking(fifo.as_raw_fd(), true)?;
297-
self.is_non_blocking.store(true, Ordering::Relaxed);
298-
}
299-
300302
loop {
301303
match fifo.read(&mut buf) {
302304
Ok(1) => break Ok(Some(Acquired { byte: buf[0] })),
@@ -321,7 +323,7 @@ impl Client {
321323
// always quickly release a token). If that turns out to not be the
322324
// case we'll get an error anyway!
323325
let byte = data.map(|d| d.byte).unwrap_or(b'+');
324-
match self.write().write(&[byte])? {
326+
match (&self.write).write(&[byte])? {
325327
1 => Ok(()),
326328
_ => Err(io::Error::new(
327329
io::ErrorKind::Other,
@@ -331,20 +333,20 @@ impl Client {
331333
}
332334

333335
pub fn string_arg(&self) -> String {
334-
self.path
335-
.as_deref()
336-
.map(|path| format!("fifo:{}", path.display()))
337-
.unwrap_or_else(|| format!("{},{}", self.read().as_raw_fd(), self.write().as_raw_fd()))
336+
match &self.creation_arg {
337+
ClientCreationArg::Fifo(path) => format!("fifo:{}", path.display()),
338+
ClientCreationArg::Fds { read, write } => format!("{},{}", read, write),
339+
}
338340
}
339341

340342
pub fn available(&self) -> io::Result<usize> {
341343
let mut len = MaybeUninit::<c_int>::uninit();
342-
cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
344+
cvt(unsafe { libc::ioctl(self.read.as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
343345
Ok(unsafe { len.assume_init() } as usize)
344346
}
345347

346348
pub fn configure(&self, cmd: &mut Command) {
347-
if self.path.is_some() {
349+
if matches!(self.creation_arg, ClientCreationArg::Fifo { .. }) {
348350
// We `File::open`ed it when inheriting from environment,
349351
// so no need to set cloexec for fifo.
350352
return;
@@ -353,8 +355,8 @@ impl Client {
353355
// we'll configure the read/write file descriptors to *not* be
354356
// cloexec, so they're inherited across the exec and specified as
355357
// integers through `string_arg` above.
356-
let read = self.read().as_raw_fd();
357-
let write = self.write().as_raw_fd();
358+
let read = self.read.as_raw_fd();
359+
let write = self.write.as_raw_fd();
358360
unsafe {
359361
cmd.pre_exec(move || {
360362
set_cloexec(read, false)?;

0 commit comments

Comments
 (0)