From 6c939fb3aa91c56f65fc6cd5feef1c84c8456f33 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 8 Oct 2017 11:05:01 -0600 Subject: [PATCH] Use the real pipe2(2) on all BSD targets. All supported non-Apple platforms now use the native syscall. Only ios and macos lack it. Deprecate pipe2 on those platforms, because it's impossible to guarantee atomicity with a userland implementation. It was added in: * DragonflyBSD 4.2 * FreeBSD 10.0 * NetBSD 6.0 * OpenBSD 5.7 --- CHANGELOG.md | 2 ++ src/unistd.rs | 39 +++++++++++++++++++++++++++++---------- test/test_unistd.rs | 24 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3994c823..bda3e81c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#800](https://github.com/nix-rust/nix/pull/800)) ### Changed +- Use native `pipe2` on all BSD targets. Users should notice no difference. + ([#777](https://github.com/nix-rust/nix/pull/777)) - Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692)) - Marked `sys::ptrace::ptrace` as `unsafe`. - Changed function signature of `socket()` and `socketpair()`. The `protocol` argument diff --git a/src/unistd.rs b/src/unistd.rs index 4e27d675db..1a065174e0 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -876,10 +876,22 @@ pub fn pipe() -> Result<(RawFd, RawFd)> { } } -// libc only defines `pipe2` in `libc::notbsd`. -#[cfg(any(target_os = "linux", - target_os = "android", - target_os = "emscripten"))] +/// Like `pipe`, but allows setting certain file descriptor flags. +/// +/// The following flags are supported, and will be set atomically as the pipe is +/// created: +/// +/// `O_CLOEXEC`: Set the close-on-exec flag for the new file descriptors. +/// `O_NONBLOCK`: Set the non-blocking flag for the ends of the pipe. +/// +/// See also [pipe(2)](http://man7.org/linux/man-pages/man2/pipe.2.html) +#[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { let mut fds: [c_int; 2] = unsafe { mem::uninitialized() }; @@ -890,9 +902,18 @@ pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { Ok((fds[0], fds[1])) } -#[cfg(not(any(target_os = "linux", - target_os = "android", - target_os = "emscripten")))] +/// Like `pipe`, but allows setting certain file descriptor flags. +/// +/// The following flags are supported, and will be set after the pipe is +/// created: +/// +/// `O_CLOEXEC`: Set the close-on-exec flag for the new file descriptors. +/// `O_NONBLOCK`: Set the non-blocking flag for the ends of the pipe. +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[deprecated( + since="0.10.0", + note="pipe2(2) is not actually atomic on these platforms. Use pipe(2) and fcntl(2) instead" +)] pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { let mut fds: [c_int; 2] = unsafe { mem::uninitialized() }; @@ -905,9 +926,7 @@ pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { Ok((fds[0], fds[1])) } -#[cfg(not(any(target_os = "linux", - target_os = "android", - target_os = "emscripten")))] +#[cfg(any(target_os = "ios", target_os = "macos"))] fn pipe2_setflags(fd1: RawFd, fd2: RawFd, flags: OFlag) -> Result<()> { use fcntl::FdFlag; use fcntl::FcntlArg::F_SETFL; diff --git a/test/test_unistd.rs b/test/test_unistd.rs index b29ece3e37..3fe123f056 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -1,5 +1,6 @@ extern crate tempdir; +use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; use nix::unistd::*; use nix::unistd::ForkResult::*; use nix::sys::wait::*; @@ -380,3 +381,26 @@ fn test_sysconf_unsupported() { let open_max = sysconf(SysconfVar::_XOPEN_CRYPT); assert!(open_max.expect("sysconf failed").is_none()) } + +// Test that we can create a pair of pipes. No need to verify that they pass +// data; that's the domain of the OS, not nix. +#[test] +fn test_pipe() { + let (fd0, fd1) = pipe().unwrap(); + let m0 = stat::SFlag::from_bits_truncate(stat::fstat(fd0).unwrap().st_mode); + // S_IFIFO means it's a pipe + assert_eq!(m0, SFlag::S_IFIFO); + let m1 = stat::SFlag::from_bits_truncate(stat::fstat(fd1).unwrap().st_mode); + assert_eq!(m1, SFlag::S_IFIFO); +} + +// pipe2(2) is the same as pipe(2), except it allows setting some flags. Check +// that we can set a flag. +#[test] +fn test_pipe2() { + let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap(); + let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap()); + assert!(f0.contains(FdFlag::FD_CLOEXEC)); + let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap()); + assert!(f1.contains(FdFlag::FD_CLOEXEC)); +}