//! nbtest -- checks O_NONBLOCK: "would block" becomes EAGAIN instead. //! //! A blocking read on an empty pipe sleeps until data comes. With //! O_NONBLOCK (set with fcntl(F_SETFL), ioctl(FIONBIO), pipe2 or //! SOCK_NONBLOCK) it returns -EAGAIN at once ("try again later"), which //! is what an event loop wants: it only reads when poll said "ready", and //! must never get stuck on one fd. #![no_std] #![no_main] #[macro_use] #[path = "ulib.rs"] mod ulib; extern crate alloc; use ulib::*; const FIONBIO: u64 = 0x5421; fn main() -> i64 { let mut c = Checks::new("nbtest"); let mut buf = [0u8; 4096]; // Pipes. let (r, w) = pipe2(O_NONBLOCK).unwrap(); c.check(read(r, &mut buf) == -EAGAIN, "pipe: read with nothing there: EAGAIN"); let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }]; c.check(poll(&mut p, 0) == 0, "pipe: poll says not readable"); let mut total = 0; loop { let n = write(w, &buf); if n < 0 { c.check(n == -EAGAIN, "pipe: write to a full pipe: EAGAIN"); break; } total += n; } println!("nbtest: the pipe took {} bytes before it was full", total); c.check(total == 65536, "pipe: holds 64 KiB, like Linux"); let mut p = [PollFd { fd: w as i32, events: POLLOUT, revents: 0 }]; c.check(poll(&mut p, 0) == 0, "pipe: poll says a full pipe isn't writable"); c.check(read(r, &mut buf) == 4096, "pipe: then reading works"); c.check(poll(&mut p, 0) == 1 && p[0].revents & POLLOUT != 0, "pipe: room again -> POLLOUT"); close(w); while read(r, &mut buf) > 0 {} c.check(read(r, &mut buf) == 0, "pipe: writer gone -> end of file, not EAGAIN"); close(r); // fcntl and FIONBIO switch it on and off. let (r, w) = pipe().unwrap(); c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "fcntl F_GETFL: blocking by default"); set_nonblocking(r, true); c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK != 0, "fcntl F_SETFL O_NONBLOCK sticks"); c.check(read(r, &mut buf) == -EAGAIN, "fcntl-made nonblocking pipe: EAGAIN"); let off = 0i32; ioctl(r, FIONBIO, &off as *const i32 as u64); c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "ioctl FIONBIO 0 turns it off"); close(r); close(w); // Sockets (over loopback). let l = socket(SOCK_STREAM, 0); bind(l, 7080); listen(l, 2); set_nonblocking(l, true); c.check(accept(l).0 == -EAGAIN, "socket: accept with nobody calling: EAGAIN"); let s = socket(SOCK_STREAM | O_NONBLOCK, 0); let r = connect(s, Ip([127, 0, 0, 1]), 7080); c.check(r == 0 || r == -EINPROGRESS, "socket: nonblocking connect: EINPROGRESS (or done at once)"); let mut p = [PollFd { fd: s as i32, events: POLLOUT, revents: 0 }]; c.check(poll(&mut p, 2000) == 1 && p[0].revents & POLLOUT != 0, "socket: poll POLLOUT = connected"); let mut p = [PollFd { fd: l as i32, events: POLLIN, revents: 0 }]; c.check(poll(&mut p, 2000) == 1, "socket: listener POLLIN = a connection to accept"); let (a, _) = accept(l); c.check(a >= 0, "socket: accept it"); c.check(read(s, &mut buf) == -EAGAIN, "socket: recv with no data: EAGAIN"); write(a, b"hi"); let mut p = [PollFd { fd: s as i32, events: POLLIN, revents: 0 }]; poll(&mut p, 2000); c.check(read(s, &mut buf) == 2, "socket: data after POLLIN"); close(a); close(s); close(l); // A terminal: the slave side of a pty. if let Ok((m, n)) = openpty() { let slave = open(&alloc::format!("/dev/pts/{}", n), O_RDWR | O_NOCTTY | O_NONBLOCK); c.check(read(slave, &mut buf) == -EAGAIN, "tty: read with no line typed: EAGAIN"); close(slave); close(m); } else { c.check(false, "openpty"); } // Small extras libc needs: pread/pwrite, writev, clocks, nanosleep. let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR); writev(fd, &[b"hello ", b"vectored ", b"world"]); pwrite(fd, b"HELLO", 0); let n = pread(fd, &mut buf, 6); c.check(&buf[..n.max(0) as usize] == b"vectored world", "writev + pread at an offset"); c.check(lseek(fd, 0, SEEK_CUR) == 20, "pread/pwrite leave the file position alone"); lseek(fd, 0, SEEK_SET); let n = read(fd, &mut buf); c.check(&buf[..n.max(0) as usize] == b"HELLO vectored world", "pwrite overwrote the start"); close(fd); unlink("/tmp/nbtest.txt"); let (secs, _) = clock_gettime(0); c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)"); let t0 = now_us(); nanosleep_us(30_000); c.check(now_us() - t0 >= 25_000, "nanosleep 30 ms"); let mut link = [0u8; 64]; let n = readlink("/proc/self/exe", &mut link); c.check(n > 0 && link[..n as usize].ends_with(b"nbtest"), "readlink /proc/self/exe = this program"); c.done() }