Skip to content

Commit

Permalink
remove the dependency on nix
Browse files Browse the repository at this point in the history
Previously the nix crate was taking care of emulating pipe2 on macOS for
us. But that implementation is deprecated in nix, and the nix crate is
also a pretty beefy dependency. Copy the implementation here, and
simplify it a bit since we only need O_CLOEXEC.
  • Loading branch information
oconnor663 committed Oct 3, 2019
1 parent b2bb35b commit 68c8bf5
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ license = "MIT"
edition = "2018"

[target.'cfg(not(windows))'.dependencies]
nix = "0.15.0"
libc = "0.2.62"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["handleapi", "namedpipeapi", "processenv", "winbase"] }
19 changes: 11 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,23 +340,26 @@ mod tests {
let (input_reader, mut input_writer) = crate::pipe().unwrap();
let (mut output_reader, output_writer) = crate::pipe().unwrap();

// Spawn the child. Note that this temporary Command object takes ownership of our copies
// of the child's stdin and stdout, and then closes them immediately when it drops. That
// stops us from blocking our own read below. We use our own simple implementation of cat
// for compatibility with Windows.
// Spawn the child. Note that this temporary Command object takes
// ownership of our copies of the child's stdin and stdout, and then
// closes them immediately when it drops. That stops us from blocking
// our own read below. We use our own simple implementation of cat for
// compatibility with Windows.
let mut child = Command::new(path_to_exe("cat"))
.stdin(input_reader)
.stdout(output_writer)
.spawn()
.unwrap();

// Write to the child's stdin. This is a small write, so it shouldn't block.
// Write to the child's stdin. This is a small write, so it shouldn't
// block.
input_writer.write_all(b"hello").unwrap();
drop(input_writer);

// Read from the child's stdout. If this child has accidentally inherited the write end of
// its own stdin, then it will never exit, and this read will block forever. That's the
// what this test is all about.
// Read from the child's stdout. If this child has accidentally
// inherited the write end of its own stdin, then it will never exit,
// and this read will block forever. That's what this test is all
// about.
let mut output = Vec::new();
output_reader.read_to_end(&mut output).unwrap();
child.wait().unwrap();
Expand Down
60 changes: 44 additions & 16 deletions src/unix.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,54 @@
use crate::PipeReader;
use crate::PipeWriter;
use libc::c_int;
use std::fs::File;
use std::io;
use std::mem::ManuallyDrop;
use std::os::unix::prelude::*;

use crate::PipeReader;
use crate::PipeWriter;
// We need to atomically create pipes and set the CLOEXEC flag on them. This is
// done with the pipe2() API. However, macOS doesn't support pipe2. There, all
// we can do is call pipe() followed by fcntl(), and hope that no other threads
// fork() in between. The following code is copied from the nix crate, where it
// works but is deprecated.
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
))]
fn pipe2_cloexec() -> io::Result<(c_int, c_int)> {
let mut fds: [c_int; 2] = [0; 2];
let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) };
if res != 0 {
return Err(io::Error::last_os_error());
}
Ok((fds[0], fds[1]))
}

pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
// O_CLOEXEC prevents children from inheriting these pipes. Nix's pipe2() will make a best
// effort to make that atomic on platforms that support it, to avoid the case where another
// thread forks right after the pipes are created but before O_CLOEXEC is set.
let (read_fd, write_fd) =
nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC).map_err(nix_err_to_io_err)?;
#[cfg(any(target_os = "ios", target_os = "macos"))]
fn pipe2_cloexec() -> io::Result<(c_int, c_int)> {
let mut fds: [c_int; 2] = [0; 2];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
if res != 0 {
return Err(io::Error::last_os_error());
}
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC) };
if res != 0 {
return Err(io::Error::last_os_error());
}
let res = unsafe { libc::fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC) };
if res != 0 {
return Err(io::Error::last_os_error());
}
Ok((fds[0], fds[1]))
}

pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
let (read_fd, write_fd) = pipe2_cloexec()?;
unsafe {
Ok((
PipeReader::from_raw_fd(read_fd),
Expand All @@ -21,14 +57,6 @@ pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
}
}

fn nix_err_to_io_err(err: nix::Error) -> io::Error {
if let nix::Error::Sys(err_no) = err {
io::Error::from(err_no)
} else {
panic!("unexpected nix error type: {:?}", err)
}
}

pub(crate) fn dup<T: AsRawFd>(wrapper: T) -> io::Result<File> {
let fd = wrapper.as_raw_fd();
let temp_file = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
Expand Down

0 comments on commit 68c8bf5

Please sign in to comment.