about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/sys/unix/mod.rs62
-rw-r--r--src/test/ui/no-stdio.rs22
2 files changed, 82 insertions, 2 deletions
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: FnOnce() -> 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: FnOnce() -> R>(f: F) -> R {
     type DWORD = u32;
@@ -77,10 +87,18 @@ unsafe fn without_stdio<R, F: FnOnce() -> 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);
 }