Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diagnostic: extend from_env function #52

Merged
merged 13 commits into from
Mar 27, 2023
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ edition = "2018"
libc = "0.2.50"

[features]
check_pipe = []
do_not_check_pipe = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also not a good idea since features should be additive.

With this do_not_check_pipe feature,if one of the crate enables this, then every other crate is opt out of checking.

Copy link
Contributor Author

@belovdv belovdv Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise if one enables check then one uses smth different from pipe will fail. I'm not sure should be this option or not and, if should, how should it behave, imho it should be decided by maintainer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If checking or not checking the pipe-ness should be configurable, then it should probably be a runtime option passed to from_env.

Not checking is the GNU Make behavior, checking catches errors in practice.


[dev-dependencies]
futures = "0.1"
Expand Down
24 changes: 23 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,36 @@ pub enum ErrFromEnv {
/// Cannot connect following this process's environment.
PlatformSpecific {
/// Error.
err: imp::ErrFromEnv,
err: io::Error,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weihanglo
Could you try this interface in cargo before it's in a "stable" release of jobserver-rs?

It's possible that in practice we'll need to discern between different kinds of erros (e.g. "descriptors are closed" and everything else) to avoid bothering users with warnings too often.

This PR, however, merges all the errors into a single io::Error with multiple vague ErrorKinds.
I'm not sure users will be able to use it reliably without relying on "string typing" (inspecting string descriptions returned by errors) or implementation details ("we know that closed descriptors are ErrorKind::InvalidData by reading source code of jobserver-rs").
(I don't like this part and I wish I read this PR in detail before its merge.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll try taking a look this week.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementation details ("we know that closed descriptors are ErrorKind::InvalidData by reading source code of jobserver-rs").

Maybe jobserver can have the error kind documented on docs.rs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late response. I think the change works fine with Cargo. Cargo can just ignore the jobserver if there are bad pipes. No harm to get this patch released on Cargo side I believe :)

/// Name of gotten env var.
env: &'static str,
/// Value of gotten env var.
var: String,
},
}

belovdv marked this conversation as resolved.
Show resolved Hide resolved
impl std::fmt::Display for ErrFromEnv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrFromEnv::IsNotConfigured => {
write!(f, "couldn't find relevant environment variable")
}
ErrFromEnv::PlatformSpecific { err, env, var } => {
write!(f, "{err} ({env}={var}")
}
}
}
}

impl std::error::Error for ErrFromEnv {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ErrFromEnv::IsNotConfigured => None,
ErrFromEnv::PlatformSpecific { err, .. } => Some(err),
}
}
}

impl Client {
/// Creates a new jobserver initialized with the given parallelism limit.
///
Expand Down
96 changes: 43 additions & 53 deletions src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ pub struct Acquired {
byte: u8,
}

#[derive(Debug)]
pub enum ErrFromEnv {
ParseEnvVar,
OpenFile(String),
InvalidDescriptor(i32, i32),
}

impl Client {
pub fn new(mut limit: usize) -> io::Result<Client> {
let client = unsafe { Client::mk()? };
Expand Down Expand Up @@ -88,51 +81,41 @@ impl Client {
Ok(Client::from_fds(pipes[0], pipes[1]))
}

pub unsafe fn open(s: &str) -> Result<Client, ErrFromEnv> {
match (Self::from_fifo(s), Self::from_pipe(s)) {
(Some(Ok(c)), _) | (_, Some(Ok(c))) => Ok(c),
(Some(Err(e)), _) | (_, Some(Err(e))) => Err(e),
(None, None) => Err(ErrFromEnv::ParseEnvVar),
}
pub unsafe fn open(s: &str) -> io::Result<Client> {
Ok(Self::from_fifo(s)?.unwrap_or(Self::from_pipe(s)?))
}

/// `--jobserver-auth=fifo:PATH`
fn from_fifo(s: &str) -> Option<Result<Client, ErrFromEnv>> {
fn from_fifo(s: &str) -> io::Result<Option<Client>> {
let mut parts = s.splitn(2, ':');
if parts.next().unwrap() != "fifo" {
return None;
return Ok(None);
}
let path = match parts.next() {
Some(p) => Path::new(p),
None => return Some(Err(ErrFromEnv::ParseEnvVar)),
};
let file = match OpenOptions::new().read(true).write(true).open(path) {
Ok(f) => f,
Err(e) => return Some(Err(ErrFromEnv::OpenFile(e.to_string()))),
};
Some(Ok(Client::Fifo {
let path = Path::new(parts.next().ok_or(io::Error::new(
belovdv marked this conversation as resolved.
Show resolved Hide resolved
io::ErrorKind::InvalidInput,
"expected ':' after `fifo`",
))?);
let file = OpenOptions::new().read(true).write(true).open(path)?;
Ok(Some(Client::Fifo {
file,
path: path.into(),
}))
}

/// `--jobserver-auth=R,W`
unsafe fn from_pipe(s: &str) -> Option<Result<Client, ErrFromEnv>> {
unsafe fn from_pipe(s: &str) -> io::Result<Client> {
let mut parts = s.splitn(2, ',');
let read = parts.next().unwrap();
let write = match parts.next() {
Some(s) => s,
None => return Some(Err(ErrFromEnv::ParseEnvVar)),
};

let read = match read.parse() {
Ok(n) => n,
Err(_) => return Some(Err(ErrFromEnv::ParseEnvVar)),
};
let write = match write.parse() {
Ok(n) => n,
Err(_) => return Some(Err(ErrFromEnv::ParseEnvVar)),
};
let write = parts.next().ok_or(io::Error::new(
io::ErrorKind::InvalidInput,
"expected ',' in `auth=R,W`",
))?;
let read = read
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let write = write
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

// Ok so we've got two integers that look like file descriptors, but
// for extra sanity checking let's see if they actually look like
Expand All @@ -142,13 +125,11 @@ impl Client {
// If we're called from `make` *without* the leading + on our rule
// then we'll have `MAKEFLAGS` env vars but won't actually have
// access to the file descriptors.
if check_fd(read) && check_fd(write) {
drop(set_cloexec(read, true));
drop(set_cloexec(write, true));
Some(Ok(Client::from_fds(read, write)))
} else {
Some(Err(ErrFromEnv::InvalidDescriptor(read, write)))
}
check_fd(read)?;
check_fd(write)?;
drop(set_cloexec(read, true));
drop(set_cloexec(write, true));
Ok(Client::from_fds(read, write))
}

unsafe fn from_fds(read: c_int, write: c_int) -> Client {
Expand Down Expand Up @@ -397,25 +378,34 @@ impl Helper {
}
}

fn check_fd(fd: c_int) -> bool {
#[cfg(feature = "check_pipe")]
fn check_fd(fd: c_int) -> io::Result<()> {
#[cfg(not(feature = "do_not_check_pipe"))]
unsafe {
let mut stat = mem::zeroed();
if libc::fstat(fd, &mut stat) == 0 {
if libc::fstat(fd, &mut stat) == -1 {
Err(io::Error::last_os_error())
} else {
// On android arm and i686 mode_t is u16 and st_mode is u32,
belovdv marked this conversation as resolved.
Show resolved Hide resolved
// this generates a type mismatch when S_IFIFO (declared as mode_t)
// is used in operations with st_mode, so we use this workaround
// to get the value of S_IFIFO with the same type of st_mode.
let mut s_ififo = stat.st_mode;
s_ififo = libc::S_IFIFO as _;
stat.st_mode & s_ififo == s_ififo
} else {
false
if stat.st_mode & s_ififo == s_ififo {
return Ok(());
}
Err(io::Error::last_os_error()) //
}
}
#[cfg(not(feature = "check_pipe"))]
#[cfg(feature = "do_not_check_pipe")]
unsafe {
libc::fcntl(fd, libc::F_GETFD) != -1
match libc::fcntl(fd, libc::F_GETFD) {
r if r == -1 => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{fd} is not a pipe"),
)),
_ => Ok(()),
}
}
}

Expand Down
9 changes: 2 additions & 7 deletions src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ struct Inner {
#[derive(Debug)]
pub struct Acquired(());

#[derive(Debug)]
pub enum ErrFromEnv {
UnavailableOnTarget,
}

impl Client {
pub fn new(limit: usize) -> io::Result<Client> {
Ok(Client {
Expand All @@ -32,8 +27,8 @@ impl Client {
})
}

pub unsafe fn open(_s: &str) -> Result<Client, ErrFromEnv> {
Err(ErrFromEnv::UnavailableOnTarget)
pub unsafe fn open(_s: &str) -> io::Result<Client> {
Err(io::ErrorKind::Unsupported.into())
}

pub fn acquire(&self) -> io::Result<Acquired> {
Expand Down
15 changes: 3 additions & 12 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ pub struct Client {
#[derive(Debug)]
pub struct Acquired;

#[derive(Debug)]
pub enum ErrFromEnv {
IsNotCString,
CannotAcquireSemaphore,
}

type BOOL = i32;
type DWORD = u32;
type HANDLE = *mut u8;
Expand Down Expand Up @@ -133,15 +127,12 @@ impl Client {
))
}

pub unsafe fn open(s: &str) -> Result<Client, ErrFromEnv> {
let name = match CString::new(s) {
Ok(s) => s,
Err(_) => return Err(ErrFromEnv::IsNotCString),
};
pub unsafe fn open(s: &str) -> io::Result<Client> {
let name = CString::new(s).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

let sem = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, name.as_ptr());
if sem.is_null() {
Err(ErrFromEnv::CannotAcquireSemaphore)
Err(io::Error::last_os_error())
} else {
Ok(Client {
sem: Handle(sem),
Expand Down