Skip to content

Commit ec55c61

Browse files
committed
Auto merge of #96837 - tmiasko:stdio-fcntl, r=joshtriplett
Use `fcntl(fd, F_GETFD)` to detect if standard streams are open In the previous implementation, if the standard streams were open, but the RLIMIT_NOFILE value was below three, the poll would fail with EINVAL: > ERRORS: EINVAL The nfds value exceeds the RLIMIT_NOFILE value. Switch to the existing fcntl based implementation to avoid the issue. Fixes #96621.
2 parents f19ccc2 + 2e62fda commit ec55c61

File tree

3 files changed

+103
-35
lines changed

3 files changed

+103
-35
lines changed

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@
239239
#![feature(dropck_eyepatch)]
240240
#![feature(exhaustive_patterns)]
241241
#![feature(intra_doc_pointers)]
242+
#![feature(label_break_value)]
242243
#![feature(lang_items)]
243244
#![feature(let_chains)]
244245
#![feature(let_else)]

library/std/src/sys/unix/mod.rs

+56-35
Original file line numberDiff line numberDiff line change
@@ -77,35 +77,65 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
7777
}
7878

7979
unsafe fn sanitize_standard_fds() {
80-
#[cfg(not(miri))]
81-
// The standard fds are always available in Miri.
82-
cfg_if::cfg_if! {
83-
if #[cfg(not(any(
84-
target_os = "emscripten",
85-
target_os = "fuchsia",
86-
target_os = "vxworks",
87-
// The poll on Darwin doesn't set POLLNVAL for closed fds.
88-
target_os = "macos",
89-
target_os = "ios",
90-
target_os = "redox",
91-
target_os = "l4re",
92-
)))] {
93-
use crate::sys::os::errno;
94-
let pfds: &mut [_] = &mut [
95-
libc::pollfd { fd: 0, events: 0, revents: 0 },
96-
libc::pollfd { fd: 1, events: 0, revents: 0 },
97-
libc::pollfd { fd: 2, events: 0, revents: 0 },
98-
];
99-
while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 {
100-
if errno() == libc::EINTR {
101-
continue;
80+
// fast path with a single syscall for systems with poll()
81+
#[cfg(not(any(
82+
miri,
83+
target_os = "emscripten",
84+
target_os = "fuchsia",
85+
target_os = "vxworks",
86+
// The poll on Darwin doesn't set POLLNVAL for closed fds.
87+
target_os = "macos",
88+
target_os = "ios",
89+
target_os = "redox",
90+
target_os = "l4re",
91+
)))]
92+
'poll: {
93+
use crate::sys::os::errno;
94+
let pfds: &mut [_] = &mut [
95+
libc::pollfd { fd: 0, events: 0, revents: 0 },
96+
libc::pollfd { fd: 1, events: 0, revents: 0 },
97+
libc::pollfd { fd: 2, events: 0, revents: 0 },
98+
];
99+
100+
while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 {
101+
match errno() {
102+
libc::EINTR => continue,
103+
libc::EINVAL | libc::EAGAIN | libc::ENOMEM => {
104+
// RLIMIT_NOFILE or temporary allocation failures
105+
// may be preventing use of poll(), fall back to fcntl
106+
break 'poll;
102107
}
108+
_ => libc::abort(),
109+
}
110+
}
111+
for pfd in pfds {
112+
if pfd.revents & libc::POLLNVAL == 0 {
113+
continue;
114+
}
115+
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
116+
// If the stream is closed but we failed to reopen it, abort the
117+
// process. Otherwise we wouldn't preserve the safety of
118+
// operations on the corresponding Rust object Stdin, Stdout, or
119+
// Stderr.
103120
libc::abort();
104121
}
105-
for pfd in pfds {
106-
if pfd.revents & libc::POLLNVAL == 0 {
107-
continue;
108-
}
122+
}
123+
return;
124+
}
125+
126+
// fallback in case poll isn't available or limited by RLIMIT_NOFILE
127+
#[cfg(not(any(
128+
// The standard fds are always available in Miri.
129+
miri,
130+
target_os = "emscripten",
131+
target_os = "fuchsia",
132+
target_os = "vxworks",
133+
target_os = "l4re",
134+
)))]
135+
{
136+
use crate::sys::os::errno;
137+
for fd in 0..3 {
138+
if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
109139
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
110140
// If the stream is closed but we failed to reopen it, abort the
111141
// process. Otherwise we wouldn't preserve the safety of
@@ -114,15 +144,6 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
114144
libc::abort();
115145
}
116146
}
117-
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] {
118-
use crate::sys::os::errno;
119-
for fd in 0..3 {
120-
if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
121-
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
122-
libc::abort();
123-
}
124-
}
125-
}
126147
}
127148
}
128149
}

src/test/ui/process/nofile-limit.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Check that statically linked binary executes successfully
2+
// with RLIMIT_NOFILE resource lowered to zero. Regression
3+
// test for issue #96621.
4+
//
5+
// run-pass
6+
// dont-check-compiler-stderr
7+
// only-linux
8+
// no-prefer-dynamic
9+
// compile-flags: -Ctarget-feature=+crt-static -Crpath=no
10+
#![feature(exit_status_error)]
11+
#![feature(rustc_private)]
12+
extern crate libc;
13+
14+
use std::os::unix::process::CommandExt;
15+
use std::process::Command;
16+
17+
fn main() {
18+
let mut args = std::env::args();
19+
let this = args.next().unwrap();
20+
match args.next().as_deref() {
21+
None => {
22+
let mut cmd = Command::new(this);
23+
cmd.arg("Ok!");
24+
unsafe {
25+
cmd.pre_exec(|| {
26+
let rlim = libc::rlimit {
27+
rlim_cur: 0,
28+
rlim_max: 0,
29+
};
30+
if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) == -1 {
31+
Err(std::io::Error::last_os_error())
32+
} else {
33+
Ok(())
34+
}
35+
})
36+
};
37+
let output = cmd.output().unwrap();
38+
println!("{:?}", output);
39+
output.status.exit_ok().unwrap();
40+
assert!(output.stdout.starts_with(b"Ok!"));
41+
}
42+
Some(word) => {
43+
println!("{}", word);
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)