Skip to content

Commit be2ffdd

Browse files
committed
Auto merge of #31417 - alexcrichton:cloexec-all-the-things, r=brson
These commits finish up closing out #24237 by filling out all locations we create new file descriptors with variants that atomically create the file descriptor and set CLOEXEC where possible. Previous support for doing this in `File::open` was added in #27971 and support for `try_clone` was added in #27980. This commit fills out: * `Socket::new` now passes `SOCK_CLOEXEC` * `Socket::accept` now uses `accept4` * `pipe2` is used instead of `pipe` Unfortunately most of this support is Linux-specific, and most of it is post-2.6.18 (our oldest supported version), so all of the detection here is done dynamically. It looks like OSX does not have equivalent variants for these functions, so there's nothing more we can do there. Support for BSDs can be added over time if they also have these functions. Closes #24237
2 parents 695c907 + 812b309 commit be2ffdd

File tree

9 files changed

+202
-45
lines changed

9 files changed

+202
-45
lines changed

src/liblibc

src/libstd/sys/unix/fd.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ impl FileDesc {
7777
// follow a strategy similar to musl [1] where if passing
7878
// F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not
7979
// supported (the third parameter, 0, is always valid), so we stop
80-
// trying that. We also *still* call the `set_cloexec` method as
81-
// apparently some kernel at some point stopped setting CLOEXEC even
82-
// though it reported doing so on F_DUPFD_CLOEXEC.
80+
// trying that.
8381
//
8482
// Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to
8583
// resolve so we at least compile this.
@@ -95,14 +93,25 @@ impl FileDesc {
9593
fd.set_cloexec();
9694
fd
9795
};
98-
static TRY_CLOEXEC: AtomicBool = AtomicBool::new(true);
96+
static TRY_CLOEXEC: AtomicBool =
97+
AtomicBool::new(!cfg!(target_os = "android"));
9998
let fd = self.raw();
100-
if !cfg!(target_os = "android") && TRY_CLOEXEC.load(Ordering::Relaxed) {
99+
if TRY_CLOEXEC.load(Ordering::Relaxed) {
101100
match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) {
101+
// We *still* call the `set_cloexec` method as apparently some
102+
// linux kernel at some point stopped setting CLOEXEC even
103+
// though it reported doing so on F_DUPFD_CLOEXEC.
104+
Ok(fd) => {
105+
return Ok(if cfg!(target_os = "linux") {
106+
make_filedesc(fd)
107+
} else {
108+
FileDesc::new(fd)
109+
})
110+
}
102111
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {
103112
TRY_CLOEXEC.store(false, Ordering::Relaxed);
104113
}
105-
res => return res.map(make_filedesc),
114+
Err(e) => return Err(e),
106115
}
107116
}
108117
cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).map(make_filedesc)

src/libstd/sys/unix/fs.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,18 @@ impl File {
413413
libc::open(path.as_ptr(), flags, opts.mode as c_int)
414414
}));
415415
let fd = FileDesc::new(fd);
416-
// Even though we open with the O_CLOEXEC flag, still set CLOEXEC here,
417-
// in case the open flag is not supported (it's just ignored by the OS
418-
// in that case).
419-
fd.set_cloexec();
416+
417+
// Currently the standard library supports Linux 2.6.18 which did not
418+
// have the O_CLOEXEC flag (passed above). If we're running on an older
419+
// Linux kernel then the flag is just ignored by the OS, so we continue
420+
// to explicitly ask for a CLOEXEC fd here.
421+
//
422+
// The CLOEXEC flag, however, is supported on versions of OSX/BSD/etc
423+
// that we support, so we only do this on Linux currently.
424+
if cfg!(target_os = "linux") {
425+
fd.set_cloexec();
426+
}
427+
420428
Ok(File(fd))
421429
}
422430

src/libstd/sys/unix/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ use ops::Neg;
2727
#[cfg(target_os = "openbsd")] pub use os::openbsd as platform;
2828
#[cfg(target_os = "solaris")] pub use os::solaris as platform;
2929

30+
#[macro_use]
31+
pub mod weak;
32+
3033
pub mod backtrace;
3134
pub mod condvar;
3235
pub mod ext;

src/libstd/sys/unix/net.rs

+46-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use prelude::v1::*;
1212

1313
use ffi::CStr;
1414
use io;
15-
use libc::{self, c_int, size_t};
15+
use libc::{self, c_int, size_t, sockaddr, socklen_t};
1616
use net::{SocketAddr, Shutdown};
1717
use str;
1818
use sys::fd::FileDesc;
@@ -25,6 +25,16 @@ pub use libc as netc;
2525

2626
pub type wrlen_t = size_t;
2727

28+
// See below for the usage of SOCK_CLOEXEC, but this constant is only defined on
29+
// Linux currently (e.g. support doesn't exist on other platforms). In order to
30+
// get name resolution to work and things to compile we just define a dummy
31+
// SOCK_CLOEXEC here for other platforms. Note that the dummy constant isn't
32+
// actually ever used (the blocks below are wrapped in `if cfg!` as well.
33+
#[cfg(target_os = "linux")]
34+
use libc::SOCK_CLOEXEC;
35+
#[cfg(not(target_os = "linux"))]
36+
const SOCK_CLOEXEC: c_int = 0;
37+
2838
pub struct Socket(FileDesc);
2939

3040
pub fn init() {}
@@ -48,15 +58,48 @@ impl Socket {
4858
SocketAddr::V6(..) => libc::AF_INET6,
4959
};
5060
unsafe {
61+
// On linux we first attempt to pass the SOCK_CLOEXEC flag to
62+
// atomically create the socket and set it as CLOEXEC. Support for
63+
// this option, however, was added in 2.6.27, and we still support
64+
// 2.6.18 as a kernel, so if the returned error is EINVAL we
65+
// fallthrough to the fallback.
66+
if cfg!(target_os = "linux") {
67+
match cvt(libc::socket(fam, ty | SOCK_CLOEXEC, 0)) {
68+
Ok(fd) => return Ok(Socket(FileDesc::new(fd))),
69+
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {}
70+
Err(e) => return Err(e),
71+
}
72+
}
73+
5174
let fd = try!(cvt(libc::socket(fam, ty, 0)));
5275
let fd = FileDesc::new(fd);
5376
fd.set_cloexec();
5477
Ok(Socket(fd))
5578
}
5679
}
5780

58-
pub fn accept(&self, storage: *mut libc::sockaddr,
59-
len: *mut libc::socklen_t) -> io::Result<Socket> {
81+
pub fn accept(&self, storage: *mut sockaddr, len: *mut socklen_t)
82+
-> io::Result<Socket> {
83+
// Unfortunately the only known way right now to accept a socket and
84+
// atomically set the CLOEXEC flag is to use the `accept4` syscall on
85+
// Linux. This was added in 2.6.28, however, and because we support
86+
// 2.6.18 we must detect this support dynamically.
87+
if cfg!(target_os = "linux") {
88+
weak! {
89+
fn accept4(c_int, *mut sockaddr, *mut socklen_t, c_int) -> c_int
90+
}
91+
if let Some(accept) = accept4.get() {
92+
let res = cvt_r(|| unsafe {
93+
accept(self.0.raw(), storage, len, SOCK_CLOEXEC)
94+
});
95+
match res {
96+
Ok(fd) => return Ok(Socket(FileDesc::new(fd))),
97+
Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {}
98+
Err(e) => return Err(e),
99+
}
100+
}
101+
}
102+
60103
let fd = try!(cvt_r(|| unsafe {
61104
libc::accept(self.0.raw(), storage, len)
62105
}));

src/libstd/sys/unix/pipe.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use sys::fd::FileDesc;
1211
use io;
13-
use libc;
12+
use libc::{self, c_int};
13+
use sys::cvt_r;
14+
use sys::fd::FileDesc;
1415

1516
////////////////////////////////////////////////////////////////////////////////
1617
// Anonymous pipes
@@ -20,6 +21,24 @@ pub struct AnonPipe(FileDesc);
2021

2122
pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
2223
let mut fds = [0; 2];
24+
25+
// Unfortunately the only known way right now to create atomically set the
26+
// CLOEXEC flag is to use the `pipe2` syscall on Linux. This was added in
27+
// 2.6.27, however, and because we support 2.6.18 we must detect this
28+
// support dynamically.
29+
if cfg!(target_os = "linux") {
30+
weak! { fn pipe2(*mut c_int, c_int) -> c_int }
31+
if let Some(pipe) = pipe2.get() {
32+
match cvt_r(|| unsafe { pipe(fds.as_mut_ptr(), libc::O_CLOEXEC) }) {
33+
Ok(_) => {
34+
return Ok((AnonPipe(FileDesc::new(fds[0])),
35+
AnonPipe(FileDesc::new(fds[1]))))
36+
}
37+
Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => {}
38+
Err(e) => return Err(e),
39+
}
40+
}
41+
}
2342
if unsafe { libc::pipe(fds.as_mut_ptr()) == 0 } {
2443
Ok((AnonPipe::from_fd(fds[0]), AnonPipe::from_fd(fds[1])))
2544
} else {

src/libstd/sys/unix/thread.rs

+2-27
Original file line numberDiff line numberDiff line change
@@ -317,37 +317,12 @@ pub mod guard {
317317
// storage. We need that information to avoid blowing up when a small stack
318318
// is created in an application with big thread-local storage requirements.
319319
// See #6233 for rationale and details.
320-
//
321-
// Use dlsym to get the symbol value at runtime, both for
322-
// compatibility with older versions of glibc, and to avoid creating
323-
// dependencies on GLIBC_PRIVATE symbols. Assumes that we've been
324-
// dynamically linked to libpthread but that is currently always the
325-
// case. We previously used weak linkage (under the same assumption),
326-
// but that caused Debian to detect an unnecessarily strict versioned
327-
// dependency on libc6 (#23628).
328320
#[cfg(target_os = "linux")]
329321
#[allow(deprecated)]
330322
fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
331-
use dynamic_lib::DynamicLibrary;
332-
use sync::Once;
333-
334-
type F = unsafe extern "C" fn(*const libc::pthread_attr_t) -> libc::size_t;
335-
static INIT: Once = Once::new();
336-
static mut __pthread_get_minstack: Option<F> = None;
337-
338-
INIT.call_once(|| {
339-
let lib = match DynamicLibrary::open(None) {
340-
Ok(l) => l,
341-
Err(..) => return,
342-
};
343-
unsafe {
344-
if let Ok(f) = lib.symbol("__pthread_get_minstack") {
345-
__pthread_get_minstack = Some(mem::transmute::<*const (), F>(f));
346-
}
347-
}
348-
});
323+
weak!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
349324

350-
match unsafe { __pthread_get_minstack } {
325+
match __pthread_get_minstack.get() {
351326
None => libc::PTHREAD_STACK_MIN as usize,
352327
Some(f) => unsafe { f(attr) as usize },
353328
}

src/libstd/sys/unix/weak.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Support for "weak linkage" to symbols on Unix
12+
//!
13+
//! Some I/O operations we do in libstd require newer versions of OSes but we
14+
//! need to maintain binary compatibility with older releases for now. In order
15+
//! to use the new functionality when available we use this module for
16+
//! detection.
17+
//!
18+
//! One option to use here is weak linkage, but that is unfortunately only
19+
//! really workable on Linux. Hence, use dlsym to get the symbol value at
20+
//! runtime. This is also done for compatibility with older versions of glibc,
21+
//! and to avoid creating dependencies on GLIBC_PRIVATE symbols. It assumes that
22+
//! we've been dynamically linked to the library the symbol comes from, but that
23+
//! is currently always the case for things like libpthread/libc.
24+
//!
25+
//! A long time ago this used weak linkage for the __pthread_get_minstack
26+
//! symbol, but that caused Debian to detect an unnecessarily strict versioned
27+
//! dependency on libc6 (#23628).
28+
29+
use libc;
30+
31+
use ffi::CString;
32+
use marker;
33+
use mem;
34+
use sync::atomic::{AtomicUsize, Ordering};
35+
36+
macro_rules! weak {
37+
(fn $name:ident($($t:ty),*) -> $ret:ty) => (
38+
static $name: ::sys::weak::Weak<unsafe extern fn($($t),*) -> $ret> =
39+
::sys::weak::Weak::new(stringify!($name));
40+
)
41+
}
42+
43+
pub struct Weak<F> {
44+
name: &'static str,
45+
addr: AtomicUsize,
46+
_marker: marker::PhantomData<F>,
47+
}
48+
49+
impl<F> Weak<F> {
50+
pub const fn new(name: &'static str) -> Weak<F> {
51+
Weak {
52+
name: name,
53+
addr: AtomicUsize::new(1),
54+
_marker: marker::PhantomData,
55+
}
56+
}
57+
58+
pub fn get(&self) -> Option<&F> {
59+
assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
60+
unsafe {
61+
if self.addr.load(Ordering::SeqCst) == 1 {
62+
self.addr.store(fetch(self.name), Ordering::SeqCst);
63+
}
64+
if self.addr.load(Ordering::SeqCst) == 0 {
65+
None
66+
} else {
67+
mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
68+
}
69+
}
70+
}
71+
}
72+
73+
unsafe fn fetch(name: &str) -> usize {
74+
let name = match CString::new(name) {
75+
Ok(cstr) => cstr,
76+
Err(..) => return 0,
77+
};
78+
let lib = libc::dlopen(0 as *const _, libc::RTLD_LAZY);
79+
if lib.is_null() {
80+
return 0
81+
}
82+
let ret = libc::dlsym(lib, name.as_ptr()) as usize;
83+
libc::dlclose(lib);
84+
return ret
85+
}

src/test/run-pass/fds-are-cloexec.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
extern crate libc;
1717

1818
use std::env;
19-
use std::fs::{self, File};
19+
use std::fs::File;
2020
use std::io;
2121
use std::net::{TcpListener, TcpStream, UdpSocket};
2222
use std::os::unix::prelude::*;
23-
use std::process::Command;
23+
use std::process::{Command, Stdio};
2424
use std::thread;
2525

2626
fn main() {
@@ -45,6 +45,17 @@ fn parent() {
4545
let udp1 = UdpSocket::bind("127.0.0.1:0").unwrap();
4646
let udp2 = udp1.try_clone().unwrap();
4747

48+
let mut child = Command::new(env::args().next().unwrap())
49+
.arg("100")
50+
.stdout(Stdio::piped())
51+
.stdin(Stdio::piped())
52+
.stderr(Stdio::piped())
53+
.spawn().unwrap();
54+
let pipe1 = child.stdin.take().unwrap();
55+
let pipe2 = child.stdout.take().unwrap();
56+
let pipe3 = child.stderr.take().unwrap();
57+
58+
4859
let status = Command::new(env::args().next().unwrap())
4960
.arg(file.as_raw_fd().to_string())
5061
.arg(tcp1.as_raw_fd().to_string())
@@ -55,9 +66,13 @@ fn parent() {
5566
.arg(tcp6.as_raw_fd().to_string())
5667
.arg(udp1.as_raw_fd().to_string())
5768
.arg(udp2.as_raw_fd().to_string())
69+
.arg(pipe1.as_raw_fd().to_string())
70+
.arg(pipe2.as_raw_fd().to_string())
71+
.arg(pipe3.as_raw_fd().to_string())
5872
.status()
5973
.unwrap();
6074
assert!(status.success());
75+
child.wait().unwrap();
6176
}
6277

6378
fn child(args: &[String]) {

0 commit comments

Comments
 (0)