Skip to content

Commit 60e5a33

Browse files
authored
Merge pull request #54 from belovdv/err-extend-type
Error type for `from_env_ext` function
2 parents 6646f83 + 3c0739e commit 60e5a33

File tree

5 files changed

+195
-81
lines changed

5 files changed

+195
-81
lines changed

src/error.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#[cfg(unix)]
2+
type RawFd = std::os::fd::RawFd;
3+
#[cfg(not(unix))]
4+
type RawFd = std::convert::Infallible;
5+
6+
/// Error type for `from_env_ext` function.
7+
#[derive(Debug)]
8+
pub struct FromEnvError {
9+
pub(crate) inner: FromEnvErrorInner,
10+
}
11+
12+
/// Kind of an error returned from `from_env_ext` function.
13+
#[derive(Debug)]
14+
#[non_exhaustive]
15+
pub enum FromEnvErrorKind {
16+
/// There is no environment variable that describes jobserver to inherit.
17+
NoEnvVar,
18+
/// Cannot parse jobserver environment variable value, incorrect format.
19+
CannotParse,
20+
/// Cannot open path or name from the jobserver environment variable value.
21+
CannotOpenPath,
22+
/// Cannot open file descriptor from the jobserver environment variable value.
23+
CannotOpenFd,
24+
/// File descriptor from the jobserver environment variable value is not a pipe.
25+
NotAPipe,
26+
/// Jobserver inheritance is not supported on this platform.
27+
Unsupported,
28+
}
29+
30+
impl FromEnvError {
31+
/// Get the error kind.
32+
pub fn kind(&self) -> FromEnvErrorKind {
33+
match self.inner {
34+
FromEnvErrorInner::NoEnvVar => FromEnvErrorKind::NoEnvVar,
35+
FromEnvErrorInner::CannotParse(_) => FromEnvErrorKind::CannotParse,
36+
FromEnvErrorInner::CannotOpenPath(..) => FromEnvErrorKind::CannotOpenPath,
37+
FromEnvErrorInner::CannotOpenFd(..) => FromEnvErrorKind::CannotOpenFd,
38+
FromEnvErrorInner::NotAPipe(..) => FromEnvErrorKind::NotAPipe,
39+
FromEnvErrorInner::Unsupported => FromEnvErrorKind::Unsupported,
40+
}
41+
}
42+
}
43+
44+
impl std::fmt::Display for FromEnvError {
45+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46+
match &self.inner {
47+
FromEnvErrorInner::NoEnvVar => write!(f, "there is no environment variable that describes jobserver to inherit"),
48+
FromEnvErrorInner::CannotParse(s) => write!(f, "cannot parse jobserver environment variable value: {s}"),
49+
FromEnvErrorInner::CannotOpenPath(s, err) => write!(f, "cannot open path or name {s} from the jobserver environment variable value: {err}"),
50+
FromEnvErrorInner::CannotOpenFd(fd, err) => write!(f, "cannot open file descriptor {fd} from the jobserver environment variable value: {err}"),
51+
FromEnvErrorInner::NotAPipe(fd, None) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe"),
52+
FromEnvErrorInner::NotAPipe(fd, Some(err)) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe: {err}"),
53+
FromEnvErrorInner::Unsupported => write!(f, "jobserver inheritance is not supported on this platform"),
54+
}
55+
}
56+
}
57+
impl std::error::Error for FromEnvError {
58+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59+
match &self.inner {
60+
FromEnvErrorInner::CannotOpenPath(_, err) => Some(err),
61+
FromEnvErrorInner::NotAPipe(_, Some(err)) | FromEnvErrorInner::CannotOpenFd(_, err) => {
62+
Some(err)
63+
}
64+
_ => None,
65+
}
66+
}
67+
}
68+
69+
#[allow(dead_code)]
70+
#[derive(Debug)]
71+
pub(crate) enum FromEnvErrorInner {
72+
NoEnvVar,
73+
CannotParse(String),
74+
CannotOpenPath(String, std::io::Error),
75+
CannotOpenFd(RawFd, std::io::Error),
76+
NotAPipe(RawFd, Option<std::io::Error>),
77+
Unsupported,
78+
}

src/lib.rs

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,12 @@
8282
#![doc(html_root_url = "https://docs.rs/jobserver/0.1")]
8383

8484
use std::env;
85+
use std::ffi::OsString;
8586
use std::io;
8687
use std::process::Command;
8788
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
8889

90+
mod error;
8991
#[cfg(unix)]
9092
#[path = "unix.rs"]
9193
mod imp;
@@ -151,40 +153,30 @@ struct HelperInner {
151153
consumer_done: bool,
152154
}
153155

154-
/// Error type for `from_env` function.
156+
use error::FromEnvErrorInner;
157+
pub use error::{FromEnvError, FromEnvErrorKind};
158+
159+
/// Return type for `from_env_ext` function.
155160
#[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-
},
161+
pub struct FromEnv {
162+
/// Result of trying to get jobserver client from env.
163+
pub client: Result<Client, FromEnvError>,
164+
/// Name and value of the environment variable.
165+
/// `None` if no relevant environment variable is found.
166+
pub var: Option<(&'static str, OsString)>,
168167
}
169168

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-
}
169+
impl FromEnv {
170+
fn new_ok(client: Client, var_name: &'static str, var_value: OsString) -> FromEnv {
171+
FromEnv {
172+
client: Ok(client),
173+
var: Some((var_name, var_value)),
179174
}
180175
}
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),
176+
fn new_err(kind: FromEnvErrorInner, var_name: &'static str, var_value: OsString) -> FromEnv {
177+
FromEnv {
178+
client: Err(FromEnvError { inner: kind }),
179+
var: Some((var_name, var_value)),
188180
}
189181
}
190182
}
@@ -234,10 +226,10 @@ impl Client {
234226
///
235227
/// # Return value
236228
///
229+
/// `FromEnv` contains result and relevant environment variable.
237230
/// If a jobserver was found in the environment and it looks correct then
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.
231+
/// result with the connected client will be returned. In other cases
232+
/// result will contain `Err(FromEnvErr)`.
241233
///
242234
/// Note that on Unix the `Client` returned **takes ownership of the file
243235
/// descriptors specified in the environment**. Jobservers on Unix are
@@ -250,8 +242,8 @@ impl Client {
250242
/// with `CLOEXEC` so they're not automatically inherited by spawned
251243
/// children.
252244
///
253-
/// On unix if `unix_check_is_pipe` enabled this function will check if
254-
/// provided files are actually pipes.
245+
/// On unix if `check_pipe` enabled this function will check if provided
246+
/// files are actually pipes.
255247
///
256248
/// # Safety
257249
///
@@ -269,27 +261,42 @@ impl Client {
269261
///
270262
/// Note, though, that on Windows it should be safe to call this function
271263
/// any number of times.
272-
pub unsafe fn from_env_ext(check_pipe: bool) -> Result<Client, ErrFromEnv> {
273-
let (env, var) = ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"]
264+
pub unsafe fn from_env_ext(check_pipe: bool) -> FromEnv {
265+
let (env, var_os) = match ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"]
274266
.iter()
275-
.map(|&env| env::var(env).map(|var| (env, var)))
276-
.find_map(|p| p.ok())
277-
.ok_or(ErrFromEnv::IsNotConfigured)?;
267+
.map(|&env| env::var_os(env).map(|var| (env, var)))
268+
.find_map(|p| p)
269+
{
270+
Some((env, var_os)) => (env, var_os),
271+
None => return FromEnv::new_err(FromEnvErrorInner::NoEnvVar, "", Default::default()),
272+
};
273+
274+
let var = match var_os.to_str() {
275+
Some(var) => var,
276+
None => {
277+
let err = FromEnvErrorInner::CannotParse("not valid UTF-8".to_string());
278+
return FromEnv::new_err(err, env, var_os);
279+
}
280+
};
278281

279-
let (arg, pos) = ["--jobserver-fds=", "--jobserver-auth="]
282+
let (arg, pos) = match ["--jobserver-fds=", "--jobserver-auth="]
280283
.iter()
281284
.map(|&arg| var.find(arg).map(|pos| (arg, pos)))
282285
.find_map(|pos| pos)
283-
.ok_or(ErrFromEnv::IsNotConfigured)?;
286+
{
287+
Some((arg, pos)) => (arg, pos),
288+
None => {
289+
let err = FromEnvErrorInner::CannotParse(
290+
"expected `--jobserver-fds=` or `--jobserver-auth=`".to_string(),
291+
);
292+
return FromEnv::new_err(err, env, var_os);
293+
}
294+
};
284295

285296
let s = var[pos + arg.len()..].split(' ').next().unwrap();
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 }),
297+
match imp::Client::open(s, check_pipe) {
298+
Ok(c) => FromEnv::new_ok(Client { inner: Arc::new(c) }, env, var_os),
299+
Err(err) => FromEnv::new_err(err, env, var_os),
293300
}
294301
}
295302

@@ -298,7 +305,7 @@ impl Client {
298305
///
299306
/// Wraps `from_env_ext` and discards error details.
300307
pub unsafe fn from_env() -> Option<Client> {
301-
Self::from_env_ext(false).ok()
308+
Self::from_env_ext(false).client.ok()
302309
}
303310

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

src/unix.rs

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use libc::c_int;
22

3+
use crate::FromEnvErrorInner;
34
use std::fs::{File, OpenOptions};
45
use std::io::{self, Read, Write};
56
use std::mem;
@@ -81,37 +82,41 @@ impl Client {
8182
Ok(Client::from_fds(pipes[0], pipes[1]))
8283
}
8384

84-
pub unsafe fn open(s: &str, check_pipe: bool) -> io::Result<Client> {
85+
pub(crate) unsafe fn open(s: &str, check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
8586
if let Some(client) = Self::from_fifo(s)? {
8687
return Ok(client);
8788
}
8889
if let Some(client) = Self::from_pipe(s, check_pipe)? {
8990
return Ok(client);
9091
}
91-
Err(io::Error::new(
92-
io::ErrorKind::InvalidInput,
93-
"unrecognized format of environment variable",
94-
))
92+
Err(FromEnvErrorInner::CannotParse(format!(
93+
"expected `fifo:PATH` or `R,W`, found `{s}`"
94+
)))
9595
}
9696

9797
/// `--jobserver-auth=fifo:PATH`
98-
fn from_fifo(s: &str) -> io::Result<Option<Client>> {
98+
fn from_fifo(s: &str) -> Result<Option<Client>, FromEnvErrorInner> {
9999
let mut parts = s.splitn(2, ':');
100100
if parts.next().unwrap() != "fifo" {
101101
return Ok(None);
102102
}
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)?;
103+
let path_str = parts.next().ok_or_else(|| {
104+
FromEnvErrorInner::CannotParse("expected a path after `fifo:`".to_string())
105+
})?;
106+
let path = Path::new(path_str);
107+
let file = OpenOptions::new()
108+
.read(true)
109+
.write(true)
110+
.open(path)
111+
.map_err(|err| FromEnvErrorInner::CannotOpenPath(path_str.to_string(), err))?;
107112
Ok(Some(Client::Fifo {
108113
file,
109114
path: path.into(),
110115
}))
111116
}
112117

113118
/// `--jobserver-auth=R,W`
114-
unsafe fn from_pipe(s: &str, check_pipe: bool) -> io::Result<Option<Client>> {
119+
unsafe fn from_pipe(s: &str, check_pipe: bool) -> Result<Option<Client>, FromEnvErrorInner> {
115120
let mut parts = s.splitn(2, ',');
116121
let read = parts.next().unwrap();
117122
let write = match parts.next() {
@@ -120,21 +125,30 @@ impl Client {
120125
};
121126
let read = read
122127
.parse()
123-
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
128+
.map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `read` fd: {e}")))?;
124129
let write = write
125130
.parse()
126-
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
131+
.map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `write` fd: {e}")))?;
127132

128133
// Ok so we've got two integers that look like file descriptors, but
129134
// for extra sanity checking let's see if they actually look like
130-
// instances of a pipe if feature enabled or valid files otherwise
131-
// before we return the client.
135+
// valid files and instances of a pipe if feature enabled before we
136+
// return the client.
132137
//
133138
// If we're called from `make` *without* the leading + on our rule
134139
// then we'll have `MAKEFLAGS` env vars but won't actually have
135140
// access to the file descriptors.
136-
check_fd(read, check_pipe)?;
137-
check_fd(write, check_pipe)?;
141+
//
142+
// `NotAPipe` is a worse error, return it if it's reported for any of the two fds.
143+
match (fd_check(read, check_pipe), fd_check(write, check_pipe)) {
144+
(read_err @ Err(FromEnvErrorInner::NotAPipe(..)), _) => read_err?,
145+
(_, write_err @ Err(FromEnvErrorInner::NotAPipe(..))) => write_err?,
146+
(read_err, write_err) => {
147+
read_err?;
148+
write_err?;
149+
}
150+
}
151+
138152
drop(set_cloexec(read, true));
139153
drop(set_cloexec(write, true));
140154
Ok(Some(Client::from_fds(read, write)))
@@ -386,31 +400,38 @@ impl Helper {
386400
}
387401
}
388402

389-
unsafe fn check_fd(fd: c_int, check_pipe: bool) -> io::Result<()> {
403+
unsafe fn fcntl_check(fd: c_int) -> Result<(), FromEnvErrorInner> {
404+
match libc::fcntl(fd, libc::F_GETFD) {
405+
-1 => Err(FromEnvErrorInner::CannotOpenFd(
406+
fd,
407+
io::Error::last_os_error(),
408+
)),
409+
_ => Ok(()),
410+
}
411+
}
412+
413+
unsafe fn fd_check(fd: c_int, check_pipe: bool) -> Result<(), FromEnvErrorInner> {
390414
if check_pipe {
391415
let mut stat = mem::zeroed();
392416
if libc::fstat(fd, &mut stat) == -1 {
393-
Err(io::Error::last_os_error())
417+
let last_os_error = io::Error::last_os_error();
418+
fcntl_check(fd)?;
419+
Err(FromEnvErrorInner::NotAPipe(fd, Some(last_os_error)))
394420
} else {
395421
// On android arm and i686 mode_t is u16 and st_mode is u32,
396422
// this generates a type mismatch when S_IFIFO (declared as mode_t)
397423
// is used in operations with st_mode, so we use this workaround
398424
// to get the value of S_IFIFO with the same type of st_mode.
425+
#[allow(unused_assignments)]
399426
let mut s_ififo = stat.st_mode;
400427
s_ififo = libc::S_IFIFO as _;
401428
if stat.st_mode & s_ififo == s_ififo {
402429
return Ok(());
403430
}
404-
Err(io::Error::last_os_error()) //
431+
Err(FromEnvErrorInner::NotAPipe(fd, None))
405432
}
406433
} 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-
}
434+
fcntl_check(fd)
414435
}
415436
}
416437

0 commit comments

Comments
 (0)