Skip to content

Commit

Permalink
Remove Linux workarounds for missing CLOEXEC support
Browse files Browse the repository at this point in the history
Now that #74163 updated the minimum Linux kernel to 2.6.32, we can
assume the availability of APIs that open file descriptors that are
already set to close on exec, including the flags `O_CLOEXEC`,
`SOCK_CLOEXEC`, and `F_DUPFD_CLOEXEC`.
  • Loading branch information
cuviper committed Jul 21, 2020
1 parent 8ad7bc3 commit 37dd7a0
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 187 deletions.
48 changes: 3 additions & 45 deletions src/libstd/sys/unix/fd.rs
Expand Up @@ -3,7 +3,6 @@
use crate::cmp;
use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read};
use crate::mem;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys::cvt;
use crate::sys_common::AsInner;

Expand Down Expand Up @@ -223,50 +222,9 @@ impl FileDesc {
pub fn duplicate(&self) -> io::Result<FileDesc> {
// We want to atomically duplicate this file descriptor and set the
// CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
// flag, however, isn't supported on older Linux kernels (earlier than
// 2.6.24).
//
// To detect this and ensure that CLOEXEC is still set, we
// follow a strategy similar to musl [1] where if passing
// F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not
// supported (the third parameter, 0, is always valid), so we stop
// trying that.
//
// Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to
// resolve so we at least compile this.
//
// [1]: http://comments.gmane.org/gmane.linux.lib.musl.general/2963
#[cfg(any(target_os = "android", target_os = "haiku"))]
use libc::F_DUPFD as F_DUPFD_CLOEXEC;
#[cfg(not(any(target_os = "android", target_os = "haiku")))]
use libc::F_DUPFD_CLOEXEC;

let make_filedesc = |fd| {
let fd = FileDesc::new(fd);
fd.set_cloexec()?;
Ok(fd)
};
static TRY_CLOEXEC: AtomicBool = AtomicBool::new(!cfg!(target_os = "android"));
let fd = self.raw();
if TRY_CLOEXEC.load(Ordering::Relaxed) {
match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) {
// We *still* call the `set_cloexec` method as apparently some
// linux kernel at some point stopped setting CLOEXEC even
// though it reported doing so on F_DUPFD_CLOEXEC.
Ok(fd) => {
return Ok(if cfg!(target_os = "linux") {
make_filedesc(fd)?
} else {
FileDesc::new(fd)
});
}
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {
TRY_CLOEXEC.store(false, Ordering::Relaxed);
}
Err(e) => return Err(e),
}
}
cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).and_then(make_filedesc)
// is a POSIX flag that was added to Linux in 2.6.24.
let fd = cvt(unsafe { libc::fcntl(self.raw(), libc::F_DUPFD_CLOEXEC, 0) })?;
Ok(FileDesc::new(fd))
}
}

Expand Down
51 changes: 1 addition & 50 deletions src/libstd/sys/unix/fs.rs
Expand Up @@ -708,56 +708,7 @@ impl File {
// However, since this is a variadic function, C integer promotion rules mean that on
// the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
let fd = FileDesc::new(fd);

// Currently the standard library supports Linux 2.6.18 which did not
// have the O_CLOEXEC flag (passed above). If we're running on an older
// Linux kernel then the flag is just ignored by the OS. After we open
// the first file, we check whether it has CLOEXEC set. If it doesn't,
// we will explicitly ask for a CLOEXEC fd for every further file we
// open, if it does, we will skip that step.
//
// The CLOEXEC flag, however, is supported on versions of macOS/BSD/etc
// that we support, so we only do this on Linux currently.
#[cfg(target_os = "linux")]
fn ensure_cloexec(fd: &FileDesc) -> io::Result<()> {
use crate::sync::atomic::{AtomicUsize, Ordering};

const OPEN_CLOEXEC_UNKNOWN: usize = 0;
const OPEN_CLOEXEC_SUPPORTED: usize = 1;
const OPEN_CLOEXEC_NOTSUPPORTED: usize = 2;
static OPEN_CLOEXEC: AtomicUsize = AtomicUsize::new(OPEN_CLOEXEC_UNKNOWN);

let need_to_set;
match OPEN_CLOEXEC.load(Ordering::Relaxed) {
OPEN_CLOEXEC_UNKNOWN => {
need_to_set = !fd.get_cloexec()?;
OPEN_CLOEXEC.store(
if need_to_set {
OPEN_CLOEXEC_NOTSUPPORTED
} else {
OPEN_CLOEXEC_SUPPORTED
},
Ordering::Relaxed,
);
}
OPEN_CLOEXEC_SUPPORTED => need_to_set = false,
OPEN_CLOEXEC_NOTSUPPORTED => need_to_set = true,
_ => unreachable!(),
}
if need_to_set {
fd.set_cloexec()?;
}
Ok(())
}

#[cfg(not(target_os = "linux"))]
fn ensure_cloexec(_: &FileDesc) -> io::Result<()> {
Ok(())
}

ensure_cloexec(&fd)?;
Ok(File(fd))
Ok(File(FileDesc::new(fd)))
}

pub fn file_attr(&self) -> io::Result<FileAttr> {
Expand Down
103 changes: 42 additions & 61 deletions src/libstd/sys/unix/net.rs
Expand Up @@ -54,56 +54,47 @@ impl Socket {

pub fn new_raw(fam: c_int, ty: c_int) -> io::Result<Socket> {
unsafe {
// On linux we first attempt to pass the SOCK_CLOEXEC flag to
// atomically create the socket and set it as CLOEXEC. Support for
// this option, however, was added in 2.6.27, and we still support
// 2.6.18 as a kernel, so if the returned error is EINVAL we
// fallthrough to the fallback.
#[cfg(target_os = "linux")]
{
match cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0)) {
Ok(fd) => return Ok(Socket(FileDesc::new(fd))),
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}
Err(e) => return Err(e),
}
}

let fd = cvt(libc::socket(fam, ty, 0))?;
let fd = FileDesc::new(fd);
fd.set_cloexec()?;
let socket = Socket(fd);
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
// On Linux we pass the SOCK_CLOEXEC flag to atomically create
// the socket and set it as CLOEXEC, added in 2.6.27.
let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
Ok(Socket(FileDesc::new(fd)))
} else {
let fd = cvt(libc::socket(fam, ty, 0))?;
let fd = FileDesc::new(fd);
fd.set_cloexec()?;
let socket = Socket(fd);

// macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
// flag to disable `SIGPIPE` emission on socket.
#[cfg(target_vendor = "apple")]
setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
// macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
// flag to disable `SIGPIPE` emission on socket.
#[cfg(target_vendor = "apple")]
setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;

Ok(socket)
Ok(socket)
}
}
}
}

pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> {
unsafe {
let mut fds = [0, 0];

// Like above, see if we can set cloexec atomically
#[cfg(target_os = "linux")]
{
match cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr())) {
Ok(_) => {
return Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1]))));
}
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}
Err(e) => return Err(e),
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
// Like above, set cloexec atomically
cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;
Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1]))))
} else {
cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
let a = FileDesc::new(fds[0]);
let b = FileDesc::new(fds[1]);
a.set_cloexec()?;
b.set_cloexec()?;
Ok((Socket(a), Socket(b)))
}
}

cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
let a = FileDesc::new(fds[0]);
let b = FileDesc::new(fds[1]);
a.set_cloexec()?;
b.set_cloexec()?;
Ok((Socket(a), Socket(b)))
}
}

Expand Down Expand Up @@ -177,30 +168,20 @@ impl Socket {
pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t) -> io::Result<Socket> {
// Unfortunately the only known way right now to accept a socket and
// atomically set the CLOEXEC flag is to use the `accept4` syscall on
// Linux. This was added in 2.6.28, however, and because we support
// 2.6.18 we must detect this support dynamically.
#[cfg(target_os = "linux")]
{
syscall! {
fn accept4(
fd: c_int,
addr: *mut sockaddr,
addr_len: *mut socklen_t,
flags: c_int
) -> c_int
}
let res = cvt_r(|| unsafe { accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC) });
match res {
Ok(fd) => return Ok(Socket(FileDesc::new(fd))),
Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {}
Err(e) => return Err(e),
// Linux. This was added in 2.6.28, glibc 2.10 and musl 0.9.5.
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
let fd = cvt_r(|| unsafe {
libc::accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC)
})?;
Ok(Socket(FileDesc::new(fd)))
} else {
let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
let fd = FileDesc::new(fd);
fd.set_cloexec()?;
Ok(Socket(fd))
}
}

let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
let fd = FileDesc::new(fd);
fd.set_cloexec()?;
Ok(Socket(fd))
}

pub fn duplicate(&self) -> io::Result<Socket> {
Expand Down
43 changes: 12 additions & 31 deletions src/libstd/sys/unix/pipe.rs
@@ -1,58 +1,39 @@
use crate::io::{self, IoSlice, IoSliceMut};
use crate::mem;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys::fd::FileDesc;
use crate::sys::{cvt, cvt_r};

use libc::c_int;

////////////////////////////////////////////////////////////////////////////////
// Anonymous pipes
////////////////////////////////////////////////////////////////////////////////

pub struct AnonPipe(FileDesc);

pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
syscall! { fn pipe2(fds: *mut c_int, flags: c_int) -> c_int }
static INVALID: AtomicBool = AtomicBool::new(false);

let mut fds = [0; 2];

// Unfortunately the only known way right now to create atomically set the
// CLOEXEC flag is to use the `pipe2` syscall on Linux. This was added in
// 2.6.27, however, and because we support 2.6.18 we must detect this
// support dynamically.
// 2.6.27, glibc 2.9 and musl 0.9.3.
if cfg!(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"
)) && !INVALID.load(Ordering::SeqCst)
{
// Note that despite calling a glibc function here we may still
// get ENOSYS. Glibc has `pipe2` since 2.9 and doesn't try to
// emulate on older kernels, so if you happen to be running on
// an older kernel you may see `pipe2` as a symbol but still not
// see the syscall.
match cvt(unsafe { pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) }) {
Ok(_) => {
return Ok((AnonPipe(FileDesc::new(fds[0])), AnonPipe(FileDesc::new(fds[1]))));
}
Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {
INVALID.store(true, Ordering::SeqCst);
}
Err(e) => return Err(e),
}
)) {
cvt(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) })?;
Ok((AnonPipe(FileDesc::new(fds[0])), AnonPipe(FileDesc::new(fds[1]))))
} else {
cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?;

let fd0 = FileDesc::new(fds[0]);
let fd1 = FileDesc::new(fds[1]);
fd0.set_cloexec()?;
fd1.set_cloexec()?;
Ok((AnonPipe(fd0), AnonPipe(fd1)))
}
cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?;

let fd0 = FileDesc::new(fds[0]);
let fd1 = FileDesc::new(fds[1]);
fd0.set_cloexec()?;
fd1.set_cloexec()?;
Ok((AnonPipe(fd0), AnonPipe(fd1)))
}

impl AnonPipe {
Expand Down

0 comments on commit 37dd7a0

Please sign in to comment.