From e0a53ed63ab9132b031153d1e69591195e97a59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sun, 8 May 2022 00:00:00 +0000 Subject: [PATCH 1/3] 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. --- library/std/src/sys/unix/mod.rs | 37 ++++------------------- src/test/ui/process/nofile-limit.rs | 46 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 src/test/ui/process/nofile-limit.rs diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index 8e909aab7f0ca..d823e51afa238 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -67,48 +67,23 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) { args::init(argc, argv); unsafe fn sanitize_standard_fds() { - #[cfg(not(miri))] - // The standard fds are always available in Miri. cfg_if::cfg_if! { if #[cfg(not(any( + // The standard fds are always available in Miri. + miri, target_os = "emscripten", target_os = "fuchsia", target_os = "vxworks", - // The poll on Darwin doesn't set POLLNVAL for closed fds. - target_os = "macos", - target_os = "ios", - target_os = "redox", target_os = "l4re", )))] { - use crate::sys::os::errno; - let pfds: &mut [_] = &mut [ - libc::pollfd { fd: 0, events: 0, revents: 0 }, - libc::pollfd { fd: 1, events: 0, revents: 0 }, - libc::pollfd { fd: 2, events: 0, revents: 0 }, - ]; - while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { - if errno() == libc::EINTR { - continue; - } - libc::abort(); - } - for pfd in pfds { - if pfd.revents & libc::POLLNVAL == 0 { - continue; - } - if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { - // If the stream is closed but we failed to reopen it, abort the - // process. Otherwise we wouldn't preserve the safety of - // operations on the corresponding Rust object Stdin, Stdout, or - // Stderr. - libc::abort(); - } - } - } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] { use crate::sys::os::errno; for fd in 0..3 { if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF { if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { + // If the stream is closed but we failed to reopen it, abort the + // process. Otherwise we wouldn't preserve the safety of + // operations on the corresponding Rust object Stdin, Stdout, or + // Stderr. libc::abort(); } } diff --git a/src/test/ui/process/nofile-limit.rs b/src/test/ui/process/nofile-limit.rs new file mode 100644 index 0000000000000..549135a46cf10 --- /dev/null +++ b/src/test/ui/process/nofile-limit.rs @@ -0,0 +1,46 @@ +// Check that statically linked binary executes successfully +// with RLIMIT_NOFILE resource lowered to zero. Regression +// test for issue #96621. +// +// run-pass +// dont-check-compiler-stderr +// only-linux +// no-prefer-dynamic +// compile-flags: -Ctarget-feature=+crt-static -Crpath=no +#![feature(exit_status_error)] +#![feature(rustc_private)] +extern crate libc; + +use std::os::unix::process::CommandExt; +use std::process::Command; + +fn main() { + let mut args = std::env::args(); + let this = args.next().unwrap(); + match args.next().as_deref() { + None => { + let mut cmd = Command::new(this); + cmd.arg("Ok!"); + unsafe { + cmd.pre_exec(|| { + let rlim = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } + }) + }; + let output = cmd.output().unwrap(); + println!("{:?}", output); + output.status.exit_ok().unwrap(); + assert!(output.stdout.starts_with(b"Ok!")); + } + Some(word) => { + println!("{}", word); + } + } +} From d3465a8f210403b922d5796b1f24b536f4defedc Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 4 Jun 2022 11:34:02 +0200 Subject: [PATCH 2/3] keep using poll as fast path and only use fcntl as fallback this minimizes the amount of syscalls performed during startup --- library/std/src/lib.rs | 1 + library/std/src/sys/unix/mod.rs | 83 +++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 6dc3fd9858451..55a97be43e25c 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -239,6 +239,7 @@ #![feature(dropck_eyepatch)] #![feature(exhaustive_patterns)] #![feature(intra_doc_pointers)] +#![feature(label_break_value)] #![feature(lang_items)] #![feature(let_chains)] #![feature(linkage)] diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index d823e51afa238..0f06811a1d6f0 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -67,25 +67,70 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) { args::init(argc, argv); unsafe fn sanitize_standard_fds() { - cfg_if::cfg_if! { - if #[cfg(not(any( - // The standard fds are always available in Miri. - miri, - target_os = "emscripten", - target_os = "fuchsia", - target_os = "vxworks", - target_os = "l4re", - )))] { - use crate::sys::os::errno; - for fd in 0..3 { - if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF { - if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { - // If the stream is closed but we failed to reopen it, abort the - // process. Otherwise we wouldn't preserve the safety of - // operations on the corresponding Rust object Stdin, Stdout, or - // Stderr. - libc::abort(); - } + // fast path with a single syscall for systems with poll() + #[cfg(not(any( + miri, + target_os = "emscripten", + target_os = "fuchsia", + target_os = "vxworks", + // The poll on Darwin doesn't set POLLNVAL for closed fds. + target_os = "macos", + target_os = "ios", + target_os = "redox", + target_os = "l4re", + )))] + 'poll: { + use crate::sys::os::errno; + let pfds: &mut [_] = &mut [ + libc::pollfd { fd: 0, events: 0, revents: 0 }, + libc::pollfd { fd: 1, events: 0, revents: 0 }, + libc::pollfd { fd: 2, events: 0, revents: 0 }, + ]; + + while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { + if errno() == libc::EINTR { + continue; + } + if errno() == libc::EINVAL { + // RLIMIT_NOFILE may be preventing use of poll() + break 'poll; + } + libc::abort(); + } + for pfd in pfds { + if pfd.revents & libc::POLLNVAL == 0 { + continue; + } + if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { + // If the stream is closed but we failed to reopen it, abort the + // process. Otherwise we wouldn't preserve the safety of + // operations on the corresponding Rust object Stdin, Stdout, or + // Stderr. + libc::abort(); + } + } + return; + } + + // fallback in case poll isn't available or limited by RLIMIT_NOFILE + #[cfg(not(any( + // The standard fds are always available in Miri. + miri, + target_os = "emscripten", + target_os = "fuchsia", + target_os = "vxworks", + target_os = "l4re", + )))] + { + use crate::sys::os::errno; + for fd in 0..3 { + if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF { + if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { + // If the stream is closed but we failed to reopen it, abort the + // process. Otherwise we wouldn't preserve the safety of + // operations on the corresponding Rust object Stdin, Stdout, or + // Stderr. + libc::abort(); } } } From 2e62fdab76bd92d0d381589fc85602efad93c846 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Tue, 7 Jun 2022 21:30:07 +0200 Subject: [PATCH 3/3] use fcntl fallback for additional poll-specific errors --- library/std/src/sys/unix/mod.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index 0f06811a1d6f0..852f5dec31af5 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -88,14 +88,15 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) { ]; while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { - if errno() == libc::EINTR { - continue; - } - if errno() == libc::EINVAL { - // RLIMIT_NOFILE may be preventing use of poll() - break 'poll; + match errno() { + libc::EINTR => continue, + libc::EINVAL | libc::EAGAIN | libc::ENOMEM => { + // RLIMIT_NOFILE or temporary allocation failures + // may be preventing use of poll(), fall back to fcntl + break 'poll; + } + _ => libc::abort(), } - libc::abort(); } for pfd in pfds { if pfd.revents & libc::POLLNVAL == 0 {