diff --git a/Cargo.toml b/Cargo.toml index 3117b8e2..4158cd27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,12 @@ edition = "2018" [dependencies] jobserver = { version = "0.1.16", optional = true } -os_pipe = "1" + +[target.'cfg(unix)'.dependencies] +libc = "0.2.62" + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.48.0", features = ["Win32_Foundation", "Win32_System_Pipes", "Win32_Security"] } [features] parallel = ["jobserver"] diff --git a/src/lib.rs b/src/lib.rs index 26242428..0532e528 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,7 @@ use std::collections::{hash_map, HashMap}; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt::{self, Display, Formatter}; -use std::fs; +use std::fs::{self, File}; use std::hash::Hasher; use std::io::{self, BufRead, BufReader, Read, Write}; use std::path::{Component, Path, PathBuf}; @@ -69,6 +69,8 @@ use std::process::{Child, Command, Stdio}; use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; +mod os_pipe; + // These modules are all glue to support reading the MSVC version from // the registry and from COM interfaces #[cfg(windows)] @@ -3494,11 +3496,7 @@ fn wait_on_child(cmd: &Command, program: &str, child: &mut Child) -> Result<(), } } -fn run_inner( - cmd: &mut Command, - program: &str, - pipe_writer: os_pipe::PipeWriter, -) -> Result<(), Error> { +fn run_inner(cmd: &mut Command, program: &str, pipe_writer: File) -> Result<(), Error> { let mut child = spawn(cmd, program, pipe_writer)?; wait_on_child(cmd, program, &mut child) } @@ -3529,11 +3527,7 @@ fn run_output(cmd: &mut Command, program: &str) -> Result, Error> { Ok(stdout) } -fn spawn( - cmd: &mut Command, - program: &str, - pipe_writer: os_pipe::PipeWriter, -) -> Result { +fn spawn(cmd: &mut Command, program: &str, pipe_writer: File) -> Result { struct ResetStderr<'cmd>(&'cmd mut Command); impl Drop for ResetStderr<'_> { @@ -3773,7 +3767,7 @@ impl AsmFileExt { struct PrintThread { handle: Option>, - pipe_writer: Option, + pipe_writer: Option, } impl PrintThread { @@ -3804,14 +3798,14 @@ impl PrintThread { }) } - fn pipe_writer(&mut self) -> &mut Option { + fn pipe_writer(&mut self) -> &mut Option { &mut self.pipe_writer } - fn pipe_writer_cloned(&self) -> Result, Error> { + fn pipe_writer_cloned(&self) -> Result, Error> { self.pipe_writer .as_ref() - .map(os_pipe::PipeWriter::try_clone) + .map(File::try_clone) .transpose() .map_err(From::from) } diff --git a/src/os_pipe.rs b/src/os_pipe.rs new file mode 100644 index 00000000..218c9bc9 --- /dev/null +++ b/src/os_pipe.rs @@ -0,0 +1,28 @@ +//! Adapted from: +//! - https://doc.rust-lang.org/src/std/sys/unix/pipe.rs.html +//! - https://doc.rust-lang.org/src/std/sys/unix/fd.rs.html#385 +//! - https://github.com/rust-lang/rust/blob/master/library/std/src/sys/mod.rs#L57 +//! - https://github.com/oconnor663/os_pipe.rs +use std::fs::File; + +/// Open a new pipe and return a pair of [`File`] objects for the reader and writer. +/// +/// This corresponds to the `pipe2` library call on Posix and the +/// `CreatePipe` library call on Windows (though these implementation +/// details might change). These pipes are non-inheritable, so new child +/// processes won't receive a copy of them unless they're explicitly +/// passed as stdin/stdout/stderr. +pub fn pipe() -> std::io::Result<(File, File)> { + sys::pipe() +} + +#[cfg(unix)] +#[path = "os_pipe/unix.rs"] +mod sys; + +#[cfg(windows)] +#[path = "os_pipe/windows.rs"] +mod sys; + +#[cfg(all(not(unix), not(windows)))] +compile_error!("Only unix and windows support os_pipe!"); diff --git a/src/os_pipe/unix.rs b/src/os_pipe/unix.rs new file mode 100644 index 00000000..ec4e547f --- /dev/null +++ b/src/os_pipe/unix.rs @@ -0,0 +1,121 @@ +use std::{ + fs::File, + io, + os::{raw::c_int, unix::io::FromRawFd}, +}; + +pub(super) fn pipe() -> io::Result<(File, File)> { + let mut fds = [0; 2]; + + // The only known way right now to create atomically set the CLOEXEC flag is + // to use the `pipe2` syscall. This was added to Linux in 2.6.27, glibc 2.9 + // and musl 0.9.3, and some other targets also have it. + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + ))] + { + unsafe { + cvt(libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC))?; + } + } + + #[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + )))] + { + unsafe { + cvt(libc::pipe(fds.as_mut_ptr()))?; + } + + cloexec::set_cloexec(fds[0])?; + cloexec::set_cloexec(fds[1])?; + } + + unsafe { Ok((File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1]))) } +} + +fn cvt(t: c_int) -> io::Result { + if t == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} + +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" +)))] +mod cloexec { + use super::{c_int, cvt, io}; + + #[cfg(not(any( + target_env = "newlib", + target_os = "solaris", + target_os = "illumos", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "l4re", + target_os = "linux", + target_os = "haiku", + target_os = "redox", + target_os = "vxworks", + target_os = "nto", + )))] + pub(super) fn set_cloexec(fd: c_int) -> io::Result<()> { + unsafe { + cvt(libc::ioctl(fd, libc::FIOCLEX))?; + } + + Ok(()) + } + + #[cfg(any( + all( + target_env = "newlib", + not(any(target_os = "espidf", target_os = "horizon")) + ), + target_os = "solaris", + target_os = "illumos", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "l4re", + target_os = "linux", + target_os = "haiku", + target_os = "redox", + target_os = "vxworks", + target_os = "nto", + ))] + pub(super) fn set_cloexec(fd: c_int) -> io::Result<()> { + unsafe { + let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?; + let new = previous | libc::FD_CLOEXEC; + if new != previous { + cvt(libc::fcntl(fd, libc::F_SETFD, new))?; + } + } + + Ok(()) + } + + // FD_CLOEXEC is not supported in ESP-IDF and Horizon OS but there's no need to, + // because neither supports spawning processes. + #[cfg(any(target_os = "espidf", target_os = "horizon"))] + pub(super) fn set_cloexec(_fd: c_int) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/os_pipe/windows.rs b/src/os_pipe/windows.rs new file mode 100644 index 00000000..4f7a5709 --- /dev/null +++ b/src/os_pipe/windows.rs @@ -0,0 +1,24 @@ +use std::{fs::File, io, os::windows::prelude::*, ptr}; +use windows_sys::Win32::{Foundation::INVALID_HANDLE_VALUE, System::Pipes::CreatePipe}; + +/// NOTE: These pipes do not support IOCP. +/// +/// If IOCP is needed, then you might want to emulate +/// anonymous pipes with CreateNamedPipe, as Rust's stdlib does. +pub(super) fn pipe() -> io::Result<(File, File)> { + let mut read_pipe = INVALID_HANDLE_VALUE; + let mut write_pipe = INVALID_HANDLE_VALUE; + + let ret = unsafe { CreatePipe(&mut read_pipe, &mut write_pipe, ptr::null_mut(), 0) }; + + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + unsafe { + Ok(( + File::from_raw_handle(read_pipe as RawHandle), + File::from_raw_handle(write_pipe as RawHandle), + )) + } + } +}