Skip to content

Commit 6646f83

Browse files
authored
Merge pull request #52 from BelovDV/new-extended-diagnostic
Diagnostic: extend from_env function
2 parents ca812d9 + c78d26d commit 6646f83

File tree

4 files changed

+141
-69
lines changed

4 files changed

+141
-69
lines changed

src/lib.rs

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,44 @@ struct HelperInner {
151151
consumer_done: bool,
152152
}
153153

154+
/// Error type for `from_env` function.
155+
#[derive(Debug)]
156+
pub enum ErrFromEnv {
157+
/// There isn't env var, that describes jobserver to inherit.
158+
IsNotConfigured,
159+
/// Cannot connect following this process's environment.
160+
PlatformSpecific {
161+
/// Error.
162+
err: io::Error,
163+
/// Name of gotten env var.
164+
env: &'static str,
165+
/// Value of gotten env var.
166+
var: String,
167+
},
168+
}
169+
170+
impl std::fmt::Display for ErrFromEnv {
171+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172+
match self {
173+
ErrFromEnv::IsNotConfigured => {
174+
write!(f, "couldn't find relevant environment variable")
175+
}
176+
ErrFromEnv::PlatformSpecific { err, env, var } => {
177+
write!(f, "{err} ({env}={var}")
178+
}
179+
}
180+
}
181+
}
182+
183+
impl std::error::Error for ErrFromEnv {
184+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
185+
match self {
186+
ErrFromEnv::IsNotConfigured => None,
187+
ErrFromEnv::PlatformSpecific { err, .. } => Some(err),
188+
}
189+
}
190+
}
191+
154192
impl Client {
155193
/// Creates a new jobserver initialized with the given parallelism limit.
156194
///
@@ -197,8 +235,9 @@ impl Client {
197235
/// # Return value
198236
///
199237
/// If a jobserver was found in the environment and it looks correct then
200-
/// `Some` of the connected client will be returned. If no jobserver was
201-
/// found then `None` will be returned.
238+
/// `Ok` of the connected client will be returned. If no relevant env var
239+
/// was found then `Err(IsNotConfigured)` will be returned. In other cases
240+
/// `Err(PlatformSpecific)` will be returned.
202241
///
203242
/// Note that on Unix the `Client` returned **takes ownership of the file
204243
/// descriptors specified in the environment**. Jobservers on Unix are
@@ -211,6 +250,9 @@ impl Client {
211250
/// with `CLOEXEC` so they're not automatically inherited by spawned
212251
/// children.
213252
///
253+
/// On unix if `unix_check_is_pipe` enabled this function will check if
254+
/// provided files are actually pipes.
255+
///
214256
/// # Safety
215257
///
216258
/// This function is `unsafe` to call on Unix specifically as it
@@ -227,28 +269,36 @@ impl Client {
227269
///
228270
/// Note, though, that on Windows it should be safe to call this function
229271
/// any number of times.
230-
pub unsafe fn from_env() -> Option<Client> {
231-
let var = match env::var("CARGO_MAKEFLAGS")
232-
.or_else(|_| env::var("MAKEFLAGS"))
233-
.or_else(|_| env::var("MFLAGS"))
234-
{
235-
Ok(s) => s,
236-
Err(_) => return None,
237-
};
238-
let mut arg = "--jobserver-fds=";
239-
let pos = match var.find(arg) {
240-
Some(i) => i,
241-
None => {
242-
arg = "--jobserver-auth=";
243-
match var.find(arg) {
244-
Some(i) => i,
245-
None => return None,
246-
}
247-
}
248-
};
272+
pub unsafe fn from_env_ext(check_pipe: bool) -> Result<Client, ErrFromEnv> {
273+
let (env, var) = ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"]
274+
.iter()
275+
.map(|&env| env::var(env).map(|var| (env, var)))
276+
.find_map(|p| p.ok())
277+
.ok_or(ErrFromEnv::IsNotConfigured)?;
278+
279+
let (arg, pos) = ["--jobserver-fds=", "--jobserver-auth="]
280+
.iter()
281+
.map(|&arg| var.find(arg).map(|pos| (arg, pos)))
282+
.find_map(|pos| pos)
283+
.ok_or(ErrFromEnv::IsNotConfigured)?;
249284

250285
let s = var[pos + arg.len()..].split(' ').next().unwrap();
251-
imp::Client::open(s).map(|c| Client { inner: Arc::new(c) })
286+
#[cfg(unix)]
287+
let imp_client = imp::Client::open(s, check_pipe);
288+
#[cfg(not(unix))]
289+
let imp_client = imp::Client::open(s);
290+
match imp_client {
291+
Ok(c) => Ok(Client { inner: Arc::new(c) }),
292+
Err(err) => Err(ErrFromEnv::PlatformSpecific { err, env, var }),
293+
}
294+
}
295+
296+
/// Attempts to connect to the jobserver specified in this process's
297+
/// environment.
298+
///
299+
/// Wraps `from_env_ext` and discards error details.
300+
pub unsafe fn from_env() -> Option<Client> {
301+
Self::from_env_ext(false).ok()
252302
}
253303

254304
/// Acquires a token from this jobserver client.

src/unix.rs

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -81,62 +81,63 @@ impl Client {
8181
Ok(Client::from_fds(pipes[0], pipes[1]))
8282
}
8383

84-
pub unsafe fn open(s: &str) -> Option<Client> {
85-
Client::from_fifo(s).or_else(|| Client::from_pipe(s))
84+
pub unsafe fn open(s: &str, check_pipe: bool) -> io::Result<Client> {
85+
if let Some(client) = Self::from_fifo(s)? {
86+
return Ok(client);
87+
}
88+
if let Some(client) = Self::from_pipe(s, check_pipe)? {
89+
return Ok(client);
90+
}
91+
Err(io::Error::new(
92+
io::ErrorKind::InvalidInput,
93+
"unrecognized format of environment variable",
94+
))
8695
}
8796

8897
/// `--jobserver-auth=fifo:PATH`
89-
fn from_fifo(s: &str) -> Option<Client> {
98+
fn from_fifo(s: &str) -> io::Result<Option<Client>> {
9099
let mut parts = s.splitn(2, ':');
91100
if parts.next().unwrap() != "fifo" {
92-
return None;
101+
return Ok(None);
93102
}
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+
let path = Path::new(parts.next().ok_or_else(|| {
104+
io::Error::new(io::ErrorKind::InvalidInput, "expected ':' after `fifo`")
105+
})?);
106+
let file = OpenOptions::new().read(true).write(true).open(path)?;
107+
Ok(Some(Client::Fifo {
103108
file,
104109
path: path.into(),
105-
})
110+
}))
106111
}
107112

108113
/// `--jobserver-auth=R,W`
109-
unsafe fn from_pipe(s: &str) -> Option<Client> {
114+
unsafe fn from_pipe(s: &str, check_pipe: bool) -> io::Result<Option<Client>> {
110115
let mut parts = s.splitn(2, ',');
111116
let read = parts.next().unwrap();
112117
let write = match parts.next() {
113-
Some(s) => s,
114-
None => return None,
115-
};
116-
117-
let read = match read.parse() {
118-
Ok(n) => n,
119-
Err(_) => return None,
120-
};
121-
let write = match write.parse() {
122-
Ok(n) => n,
123-
Err(_) => return None,
118+
Some(w) => w,
119+
None => return Ok(None),
124120
};
121+
let read = read
122+
.parse()
123+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
124+
let write = write
125+
.parse()
126+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
125127

126128
// Ok so we've got two integers that look like file descriptors, but
127129
// for extra sanity checking let's see if they actually look like
128-
// instances of a pipe before we return the client.
130+
// instances of a pipe if feature enabled or valid files otherwise
131+
// before we return the client.
129132
//
130133
// If we're called from `make` *without* the leading + on our rule
131134
// then we'll have `MAKEFLAGS` env vars but won't actually have
132135
// access to the file descriptors.
133-
if is_valid_fd(read) && is_valid_fd(write) {
134-
drop(set_cloexec(read, true));
135-
drop(set_cloexec(write, true));
136-
Some(Client::from_fds(read, write))
137-
} else {
138-
None
139-
}
136+
check_fd(read, check_pipe)?;
137+
check_fd(write, check_pipe)?;
138+
drop(set_cloexec(read, true));
139+
drop(set_cloexec(write, true));
140+
Ok(Some(Client::from_fds(read, write)))
140141
}
141142

142143
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
@@ -207,7 +208,7 @@ impl Client {
207208
return Err(io::Error::new(
208209
io::ErrorKind::Other,
209210
"early EOF on jobserver pipe",
210-
))
211+
));
211212
}
212213
Err(e) => match e.kind() {
213214
io::ErrorKind::WouldBlock => { /* fall through to polling */ }
@@ -326,7 +327,7 @@ pub(crate) fn spawn_helper(
326327
client: client.inner.clone(),
327328
data,
328329
disabled: false,
329-
}))
330+
}));
330331
}
331332
Err(e) => break f(Err(e)),
332333
Ok(None) if helper.producer_done() => break,
@@ -385,8 +386,32 @@ impl Helper {
385386
}
386387
}
387388

388-
fn is_valid_fd(fd: c_int) -> bool {
389-
unsafe { libc::fcntl(fd, libc::F_GETFD) != -1 }
389+
unsafe fn check_fd(fd: c_int, check_pipe: bool) -> io::Result<()> {
390+
if check_pipe {
391+
let mut stat = mem::zeroed();
392+
if libc::fstat(fd, &mut stat) == -1 {
393+
Err(io::Error::last_os_error())
394+
} else {
395+
// On android arm and i686 mode_t is u16 and st_mode is u32,
396+
// this generates a type mismatch when S_IFIFO (declared as mode_t)
397+
// is used in operations with st_mode, so we use this workaround
398+
// to get the value of S_IFIFO with the same type of st_mode.
399+
let mut s_ififo = stat.st_mode;
400+
s_ififo = libc::S_IFIFO as _;
401+
if stat.st_mode & s_ififo == s_ififo {
402+
return Ok(());
403+
}
404+
Err(io::Error::last_os_error()) //
405+
}
406+
} else {
407+
match libc::fcntl(fd, libc::F_GETFD) {
408+
r if r == -1 => Err(io::Error::new(
409+
io::ErrorKind::InvalidData,
410+
format!("{fd} is not a pipe"),
411+
)),
412+
_ => Ok(()),
413+
}
414+
}
390415
}
391416

392417
fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> {

src/wasm.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ impl Client {
2727
})
2828
}
2929

30-
pub unsafe fn open(_s: &str) -> Option<Client> {
31-
None
30+
pub unsafe fn open(_s: &str) -> io::Result<Client> {
31+
Err(io::ErrorKind::Unsupported.into())
3232
}
3333

3434
pub fn acquire(&self) -> io::Result<Acquired> {

src/windows.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,14 @@ impl Client {
127127
))
128128
}
129129

130-
pub unsafe fn open(s: &str) -> Option<Client> {
131-
let name = match CString::new(s) {
132-
Ok(s) => s,
133-
Err(_) => return None,
134-
};
130+
pub unsafe fn open(s: &str) -> io::Result<Client> {
131+
let name = CString::new(s).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
135132

136133
let sem = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, name.as_ptr());
137134
if sem.is_null() {
138-
None
135+
Err(io::Error::last_os_error())
139136
} else {
140-
Some(Client {
137+
Ok(Client {
141138
sem: Handle(sem),
142139
name: s.to_string(),
143140
})

0 commit comments

Comments
 (0)