From e14a2b6d8f470d7c6b782f8ea064d818fbd3392a Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Sat, 5 Apr 2014 01:23:47 -0700 Subject: [PATCH 1/2] Add run-make test for libnative file I/O and O_NONBLOCK This tests #13336. --- src/test/run-make/libnative-nonblock/Makefile | 8 +++++ .../run-make/libnative-nonblock/mknblock.c | 20 +++++++++++ src/test/run-make/libnative-nonblock/write.rs | 33 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/test/run-make/libnative-nonblock/Makefile create mode 100644 src/test/run-make/libnative-nonblock/mknblock.c create mode 100644 src/test/run-make/libnative-nonblock/write.rs diff --git a/src/test/run-make/libnative-nonblock/Makefile b/src/test/run-make/libnative-nonblock/Makefile new file mode 100644 index 0000000000000..e1d10b356135c --- /dev/null +++ b/src/test/run-make/libnative-nonblock/Makefile @@ -0,0 +1,8 @@ +-include ../tools.mk + +all: + $(RUSTC) write.rs + $(CC) -o $(TMPDIR)/mknblock mknblock.c + mkfifo $(TMPDIR)/fifo + /bin/sh -c "(sleep 1; cat) < $(TMPDIR)/fifo > /dev/null" & + /bin/sh -c "($(TMPDIR)/mknblock; $(TMPDIR)/write) > $(TMPDIR)/fifo" diff --git a/src/test/run-make/libnative-nonblock/mknblock.c b/src/test/run-make/libnative-nonblock/mknblock.c new file mode 100644 index 0000000000000..910257efe6890 --- /dev/null +++ b/src/test/run-make/libnative-nonblock/mknblock.c @@ -0,0 +1,20 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include +#include + +int main() { + if (fcntl(1, F_SETFL, O_NONBLOCK) == -1) { + perror("fcntl"); + return 1; + } + return 0; +} diff --git a/src/test/run-make/libnative-nonblock/write.rs b/src/test/run-make/libnative-nonblock/write.rs new file mode 100644 index 0000000000000..6b42af1a7c9fa --- /dev/null +++ b/src/test/run-make/libnative-nonblock/write.rs @@ -0,0 +1,33 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::io; +use std::io::IoResult; +use std::os; + +fn run() -> IoResult<()> { + let mut out = io::stdio::stdout_raw(); + for _ in range(0u, 1024) { + let mut buf = ['x' as u8, ..1024]; + buf[1023] = '\n' as u8; + try!(out.write(buf)); + } + Ok(()) +} + +fn main() { + match run() { + Err(e) => { + (writeln!(&mut io::stderr(), "Error: {}", e)).unwrap(); + os::set_exit_status(1); + } + Ok(()) => () + } +} From ec4954b41a8708054544489d611805cd62ae36c3 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Sat, 5 Apr 2014 02:22:08 -0700 Subject: [PATCH 2/2] libnative: Convert non-blocking fds to blocking when necessary When doing a read or write on a file descriptor, if it returns EAGAIN or EWOULDBLOCK, we need to turn off the O_NONBLOCK flag and try again. This can happen because TTYs and FIFOs share the file descriptor flags with other processes. --- src/liblibc/lib.rs | 17 +++++++++++ src/libnative/io/file_unix.rs | 57 ++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/liblibc/lib.rs b/src/liblibc/lib.rs index fc7044ed88ad5..8f2115d2c0599 100644 --- a/src/liblibc/lib.rs +++ b/src/liblibc/lib.rs @@ -2378,6 +2378,15 @@ pub mod consts { pub static CLOCK_MONOTONIC: c_int = 1; pub static WNOHANG: c_int = 1; + + pub static F_GETFL: c_int = 3; + pub static F_SETFL: c_int = 4; + #[cfg(target_arch = "mips")] + pub static O_NONBLOCK: c_int = 0x0080; + #[cfg(target_arch = "arm")] + #[cfg(target_arch = "x86")] + #[cfg(target_arch = "x86_64")] + pub static O_NONBLOCK: c_int = 0o04000; } pub mod posix08 { } @@ -2828,6 +2837,10 @@ pub mod consts { pub static CLOCK_MONOTONIC: c_int = 4; pub static WNOHANG: c_int = 1; + + pub static F_GETFL: c_int = 3; + pub static F_SETFL: c_int = 4; + pub static O_NONBLOCK: c_int = 0x0004; } pub mod posix08 { } @@ -3217,6 +3230,10 @@ pub mod consts { pub static PTHREAD_STACK_MIN: size_t = 8192; pub static WNOHANG: c_int = 1; + + pub static F_GETFL: c_int = 3; + pub static F_SETFL: c_int = 4; + pub static O_NONBLOCK: c_int = 0x0004; } pub mod posix08 { } diff --git a/src/libnative/io/file_unix.rs b/src/libnative/io/file_unix.rs index 56460166b48a4..a8539a104b1aa 100644 --- a/src/libnative/io/file_unix.rs +++ b/src/libnative/io/file_unix.rs @@ -17,6 +17,7 @@ use std::io; use libc::{c_int, c_void}; use libc; use std::mem; +use std::os; use std::rt::rtio; use std::slice; @@ -54,9 +55,11 @@ impl FileDesc { // rtio traits in scope pub fn inner_read(&mut self, buf: &mut [u8]) -> Result { let ret = retry(|| unsafe { - libc::read(self.fd(), - buf.as_mut_ptr() as *mut libc::c_void, - buf.len() as libc::size_t) as libc::c_int + blocking(self.fd(), |fd| { + libc::read(fd, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) as i64 + }) as libc::c_int }); if ret == 0 { Err(io::standard_error(io::EndOfFile)) @@ -69,8 +72,10 @@ impl FileDesc { pub fn inner_write(&mut self, buf: &[u8]) -> Result<(), IoError> { let ret = keep_going(buf, |buf, len| { unsafe { - libc::write(self.fd(), buf as *libc::c_void, - len as libc::size_t) as i64 + blocking(self.fd(), |fd| { + libc::write(fd, buf as *libc::c_void, + len as libc::size_t) as i64 + }) } }); if ret < 0 { @@ -87,6 +92,48 @@ impl FileDesc { } } +// libc read/write can return EAGAIN or EWOULDBLOCK if the fd has O_NONBLOCK set. +// However, we're expecting blocking reads/writes. So if we get either of those errors +// we need to unset O_NONBLOCK and try again. +unsafe fn blocking(fd: fd_t, f: |fd_t| -> i64) -> i64 { + loop { + match f(fd) { + -1 => { + let e = os::errno() as int; + if e == libc::EAGAIN as int || e == libc::EWOULDBLOCK as int { + // the fd is marked as nonblock. Turn it off. + let mut flags = libc::fcntl(fd, libc::F_GETFL); + if flags == -1 { return -1 } + if flags & libc::O_NONBLOCK == 0 { + // O_NONBLOCK doesn't seem to be set. Did something else turn it off? + // Or does this system provide an O_NDELAY (which isn't POSIX) that differs + // from O_NONBLOCK? Try again without the loop. + return f(fd); + // Alternatively, something else could have unset the O_NONBLOCK flag in + // between our read/write call and the fcntl. But for that to happen, and + // then for something to subsequently turn it back on again before our + // single retry above, would be quite bizarre and should never happen in + // practice. + } + flags &= !libc::O_NONBLOCK; + if libc::fcntl(fd, libc::F_SETFL, flags) == -1 { + // fcntl() failed. POSIX says this should only happen due to EBADF, which + // would be the same error any subsequent read/write returns. But Linux + // seems to define EPERM as well. Just to be safe, lets re-issue the + // read/write and return. In the EPERM case it's plausible that the + // read/write would then return EAGAIN, but I don't believe we can actually + // hit the EPERM error code so that's a moot point. + return f(fd); + } + } else { + return -1 + } + } + n => return n + } + } +} + impl io::Reader for FileDesc { fn read(&mut self, buf: &mut [u8]) -> io::IoResult { self.inner_read(buf)