diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index eddf00d3979..b48d2162eca 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -75,6 +75,13 @@ pub use crate::sys_common::os_str_bytes as os_str; #[cfg(not(test))] pub fn init() { + // The standard streams might be closed on application startup. To prevent + // std::io::{stdin, stdout,stderr} objects from using other unrelated file + // resources opened later, we reopen standards streams when they are closed. + unsafe { + sanitize_standard_fds(); + } + // By default, some platforms will send a *signal* when an EPIPE error // would otherwise be delivered. This runtime doesn't install a SIGPIPE // handler, causing it to kill the program, which isn't exactly what we @@ -86,6 +93,61 @@ pub fn init() { reset_sigpipe(); } + // In the case when all file descriptors are open, the poll has been + // observed to perform better than fcntl (on GNU/Linux). + #[cfg(not(any( + miri, + target_os = "emscripten", + target_os = "fuchsia", + // The poll on Darwin doesn't set POLLNVAL for closed fds. + target_os = "macos", + target_os = "ios", + target_os = "redox", + )))] + unsafe fn sanitize_standard_fds() { + 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(); + } + } + } + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] + unsafe fn sanitize_standard_fds() { + 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 { + libc::abort(); + } + } + } + } + #[cfg(any( + // The standard fds are always available in Miri. + miri, + target_os = "emscripten", + target_os = "fuchsia"))] + unsafe fn sanitize_standard_fds() {} + #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia")))] unsafe fn reset_sigpipe() { assert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR); diff --git a/src/test/ui/no-stdio.rs b/src/test/ui/no-stdio.rs index e72b7b26e22..1b0ad930da6 100644 --- a/src/test/ui/no-stdio.rs +++ b/src/test/ui/no-stdio.rs @@ -36,6 +36,16 @@ unsafe fn without_stdio R>(f: F) -> R { return r } +#[cfg(unix)] +fn assert_fd_is_valid(fd: libc::c_int) { + if unsafe { libc::fcntl(fd, libc::F_GETFD) == -1 } { + panic!("file descriptor {} is not valid: {}", fd, io::Error::last_os_error()); + } +} + +#[cfg(windows)] +fn assert_fd_is_valid(_fd: libc::c_int) {} + #[cfg(windows)] unsafe fn without_stdio R>(f: F) -> R { type DWORD = u32; @@ -77,10 +87,18 @@ unsafe fn without_stdio R>(f: F) -> R { fn main() { if env::args().len() > 1 { + // Writing to stdout & stderr should not panic. println!("test"); assert!(io::stdout().write(b"test\n").is_ok()); assert!(io::stderr().write(b"test\n").is_ok()); + + // Stdin should be at EOF. assert_eq!(io::stdin().read(&mut [0; 10]).unwrap(), 0); + + // Standard file descriptors should be valid on UNIX: + assert_fd_is_valid(0); + assert_fd_is_valid(1); + assert_fd_is_valid(2); return } @@ -109,12 +127,12 @@ fn main() { .stdout(Stdio::null()) .stderr(Stdio::null()) .status().unwrap(); - assert!(status.success(), "{:?} isn't a success", status); + assert!(status.success(), "{} isn't a success", status); // Finally, close everything then spawn a child to make sure everything is // *still* ok. let status = unsafe { without_stdio(|| Command::new(&me).arg("next").status()) }.unwrap(); - assert!(status.success(), "{:?} isn't a success", status); + assert!(status.success(), "{} isn't a success", status); }