//! 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"); // readv: one read filling several buffers in turn. let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR); write(fd, b"abcdefgh"); lseek(fd, 0, SEEK_SET); let (mut a, mut b) = ([0u8; 3], [0u8; 5]); let n = readv(fd, &mut [&mut a, &mut b]); c.check(n == 8 && &a == b"abc" && &b == b"defgh", "readv fills the buffers in order"); close(fd); unlink("/tmp/nbtest.txt"); // select and ppoll on a pipe: not readable, then readable. let (r, w) = pipe2(0).unwrap(); let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]); fd_set(&mut rs, r); let t0 = now_us(); c.check(select(r + 1, &mut rs, &mut ws, 20_000) == 0 && now_us() - t0 >= 20_000, "select: times out after 20 ms on an empty pipe"); write(w, b"!"); let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]); fd_set(&mut rs, r); fd_set(&mut ws, w); c.check(select(w.max(r) + 1, &mut rs, &mut ws, 0) == 2 && fd_isset(&rs, r) && fd_isset(&ws, w), "select: pipe readable + writable"); let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }]; c.check(ppoll_us(&mut p, 0) == 1 && p[0].revents & POLLIN != 0, "ppoll: POLLIN"); close(r); close(w); // getcwd and uname, which libc start-up and `uname -a` use. let mut cwd = [0u8; 64]; let n = getcwd(&mut cwd); c.check(n > 0 && cwd[0] == b'/', "getcwd gives an absolute path"); let mut ub = [0u8; 6 * 65]; let u = uname(&mut ub); c.check(u[0] == "Rustbelt" && u[3].starts_with("stage 19"), "uname: Rustbelt, stage 19"); 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() }