Skip to content

Commit 8b2da68

Browse files
committed
Fix Client::configure* on unix
Fixed #99
1 parent c0c2898 commit 8b2da68

File tree

2 files changed

+70
-66
lines changed

2 files changed

+70
-66
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: 62 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::io::{self, Read, Write};
66
use std::mem;
77
use std::mem::MaybeUninit;
88
use std::os::unix::prelude::*;
9-
use std::path::{Path, PathBuf};
9+
use std::path::Path;
1010
use std::process::Command;
1111
use std::ptr;
1212
use std::sync::{
@@ -17,17 +17,19 @@ use std::thread::{self, Builder, JoinHandle};
1717
use std::time::Duration;
1818

1919
#[derive(Debug)]
20-
pub enum Client {
21-
/// `--jobserver-auth=R,W`
22-
Pipe { read: File, write: File },
23-
/// `--jobserver-auth=fifo:PATH`
24-
Fifo {
25-
file: File,
26-
path: PathBuf,
27-
/// it can only go from false -> true but not the other way around, since that
28-
/// could cause a race condition.
29-
is_non_blocking: AtomicBool,
30-
},
20+
enum ClientCreationArg {
21+
Fds { read: c_int, write: c_int },
22+
Fifo(Box<Path>),
23+
}
24+
25+
#[derive(Debug)]
26+
pub struct Client {
27+
read: File,
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
31+
/// could cause a race condition.
32+
is_non_blocking: Option<AtomicBool>,
3133
}
3234

3335
#[derive(Debug)]
@@ -43,7 +45,7 @@ impl Client {
4345
// wrong!
4446
const BUFFER: [u8; 128] = [b'|'; 128];
4547

46-
let mut write = client.write();
48+
let mut write = &client.write;
4749

4850
set_nonblocking(write.as_raw_fd(), true)?;
4951

@@ -111,16 +113,20 @@ impl Client {
111113
FromEnvErrorInner::CannotParse("expected a path after `fifo:`".to_string())
112114
})?;
113115
let path = Path::new(path_str);
116+
114117
let file = OpenOptions::new()
115118
.read(true)
116119
.write(true)
117120
.open(path)
118121
.map_err(|err| FromEnvErrorInner::CannotOpenPath(path_str.to_string(), err))?;
119122

120-
Ok(Some(Client::Fifo {
121-
file,
122-
path: path.into(),
123-
is_non_blocking: AtomicBool::new(false),
123+
Ok(Some(Client {
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,40 +182,36 @@ 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(jobserver)) =
178-
Self::from_fifo(&format!("fifo:/dev/fd/{}", read.as_raw_fd()))
179-
{
180-
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+
}));
181197
}
182198
}
183199
}
184200

185-
Ok(Some(Client::Pipe {
201+
Ok(Some(Client {
186202
read: clone_fd_and_set_cloexec(read)?,
187203
write: clone_fd_and_set_cloexec(write)?,
204+
creation_arg,
205+
is_non_blocking: None,
188206
}))
189207
}
190208

191209
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
192-
Client::Pipe {
210+
Client {
193211
read: File::from_raw_fd(read),
194212
write: File::from_raw_fd(write),
195-
}
196-
}
197-
198-
/// Gets the read end of our jobserver client.
199-
fn read(&self) -> &File {
200-
match self {
201-
Client::Pipe { read, .. } => read,
202-
Client::Fifo { file, .. } => file,
203-
}
204-
}
205-
206-
/// Gets the write end of our jobserver client.
207-
fn write(&self) -> &File {
208-
match self {
209-
Client::Pipe { write, .. } => write,
210-
Client::Fifo { file, .. } => file,
213+
creation_arg: ClientCreationArg::Fds { read, write },
214+
is_non_blocking: None,
211215
}
212216
}
213217

@@ -245,7 +249,7 @@ impl Client {
245249
// to shut us down, so we otherwise punt all errors upwards.
246250
unsafe {
247251
let mut fd: libc::pollfd = mem::zeroed();
248-
let mut read = self.read();
252+
let mut read = &self.read;
249253
fd.fd = read.as_raw_fd();
250254
fd.events = libc::POLLIN;
251255
loop {
@@ -284,19 +288,15 @@ impl Client {
284288

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

288-
let (mut fifo, is_non_blocking) = match self {
289-
Self::Fifo {
290-
file,
291-
is_non_blocking,
292-
..
293-
} => (file, is_non_blocking),
294-
_ => return Err(io::ErrorKind::Unsupported.into()),
295-
};
296-
297-
if !is_non_blocking.load(Ordering::Relaxed) {
298-
set_nonblocking(fifo.as_raw_fd(), true)?;
299-
is_non_blocking.store(true, Ordering::Relaxed);
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 {
299+
return Err(io::ErrorKind::Unsupported.into());
300300
}
301301

302302
loop {
@@ -323,7 +323,7 @@ impl Client {
323323
// always quickly release a token). If that turns out to not be the
324324
// case we'll get an error anyway!
325325
let byte = data.map(|d| d.byte).unwrap_or(b'+');
326-
match self.write().write(&[byte])? {
326+
match (&self.write).write(&[byte])? {
327327
1 => Ok(()),
328328
_ => Err(io::Error::new(
329329
io::ErrorKind::Other,
@@ -333,31 +333,30 @@ impl Client {
333333
}
334334

335335
pub fn string_arg(&self) -> String {
336-
match self {
337-
Client::Pipe { read, write } => format!("{},{}", read.as_raw_fd(), write.as_raw_fd()),
338-
Client::Fifo { path, .. } => format!("fifo:{}", path.to_str().unwrap()),
336+
match &self.creation_arg {
337+
ClientCreationArg::Fifo(path) => format!("fifo:{}", path.display()),
338+
ClientCreationArg::Fds { read, write } => format!("{},{}", read, write),
339339
}
340340
}
341341

342342
pub fn available(&self) -> io::Result<usize> {
343343
let mut len = MaybeUninit::<c_int>::uninit();
344-
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()) })?;
345345
Ok(unsafe { len.assume_init() } as usize)
346346
}
347347

348348
pub fn configure(&self, cmd: &mut Command) {
349-
match self {
349+
if matches!(self.creation_arg, ClientCreationArg::Fifo { .. }) {
350350
// We `File::open`ed it when inheriting from environment,
351351
// so no need to set cloexec for fifo.
352-
Client::Fifo { .. } => return,
353-
Client::Pipe { .. } => {}
354-
};
352+
return;
353+
}
355354
// Here we basically just want to say that in the child process
356355
// we'll configure the read/write file descriptors to *not* be
357356
// cloexec, so they're inherited across the exec and specified as
358357
// integers through `string_arg` above.
359-
let read = self.read().as_raw_fd();
360-
let write = self.write().as_raw_fd();
358+
let read = self.read.as_raw_fd();
359+
let write = self.write.as_raw_fd();
361360
unsafe {
362361
cmd.pre_exec(move || {
363362
set_cloexec(read, false)?;

0 commit comments

Comments
 (0)