//! Test 4 — poll() over multiple fds. //! //! A Wayland compositor multiplexes many fds: the listening socket, //! each connected client, the input device(s), idle timers, signals. //! This test validates that poll() correctly demultiplexes. //! //! Setup: 3 pipes (a, b, c). We write into b only, then poll all 3. //! Only fd_b should report POLLIN. We read it. Then we close a, expecting //! POLLHUP on a-read side. Finally we test timeout-only behaviour. use std::io; use std::os::fd::RawFd; use std::process::ExitCode; fn errno_str() -> String { let e = io::Error::last_os_error(); format!("{e} (errno={})", e.raw_os_error().unwrap_or(0)) } unsafe fn make_pipe() -> Result<(RawFd, RawFd), String> { let mut fds: [RawFd; 2] = [-1, -1]; if libc::pipe(fds.as_mut_ptr()) != 0 { return Err(format!("pipe: {}", errno_str())); } Ok((fds[0], fds[1])) } unsafe fn poll_revents(fds: &mut [libc::pollfd], timeout_ms: i32) -> Result { let r = libc::poll(fds.as_mut_ptr(), fds.len() as _, timeout_ms); if r < 0 { return Err(format!("poll: {}", errno_str())); } Ok(r) } fn run() -> Result<(), String> { println!("[test-04] poll() multi-fd multiplexing"); unsafe { // 3 pipes; we'll only watch the read ends. let (ra, wa) = make_pipe()?; let (rb, wb) = make_pipe()?; let (rc, wc) = make_pipe()?; println!("[test-04] pipes: ra={ra} rb={rb} rc={rc}"); // ---- Subtest A: timeout-only, no fd ready ---- let mut pfds = [ libc::pollfd { fd: ra, events: libc::POLLIN, revents: 0, }, libc::pollfd { fd: rb, events: libc::POLLIN, revents: 0, }, libc::pollfd { fd: rc, events: libc::POLLIN, revents: 0, }, ]; let n = poll_revents(&mut pfds, 50)?; if n != 0 { return Err(format!("expected timeout (0), got {n}")); } println!("[test-04] A: timeout 50ms with no data → poll returned 0 OK"); // ---- Subtest B: write into pipe B only ---- let payload = b"X"; let n = libc::write(wb, payload.as_ptr() as *const _, 1); if n != 1 { return Err(format!("write to wb: {}", errno_str())); } for p in pfds.iter_mut() { p.revents = 0; } let n = poll_revents(&mut pfds, 1000)?; if n != 1 { return Err(format!("expected 1 ready fd, got {n}")); } let ready: Vec<_> = pfds .iter() .enumerate() .filter(|(_, p)| p.revents & libc::POLLIN != 0) .map(|(i, _)| i) .collect(); if ready != [1] { return Err(format!("expected only index 1 (rb) ready, got {ready:?}")); } println!("[test-04] B: poll detected only rb (index 1) ready, as expected"); let mut buf = [0u8; 1]; let r = libc::read(rb, buf.as_mut_ptr() as *mut _, 1); if r != 1 || buf[0] != b'X' { return Err(format!("read rb: r={r} buf={:?}", buf)); } println!("[test-04] B: read 1 byte 'X' from rb OK"); // ---- Subtest C: close write end of A, expect HUP on read end ---- libc::close(wa); for p in pfds.iter_mut() { p.revents = 0; } let n = poll_revents(&mut pfds, 100)?; if n < 1 { return Err(format!( "expected at least 1 ready (POLLHUP on ra), got {n}" )); } let hup_a = pfds[0].revents & (libc::POLLHUP | libc::POLLIN) != 0; if !hup_a { return Err(format!( "expected HUP/IN on ra, revents={:#x}", pfds[0].revents )); } println!( "[test-04] C: closing wa caused POLLHUP/POLLIN on ra (revents={:#x})", pfds[0].revents ); // Cleanup libc::close(ra); libc::close(rb); libc::close(wb); libc::close(rc); libc::close(wc); } Ok(()) } fn main() -> ExitCode { match run() { Ok(()) => { println!("[test-04] PASS: poll() multiplexes correctly across fds and detects HUP"); ExitCode::SUCCESS } Err(e) => { eprintln!("[test-04] FAIL: {e}"); ExitCode::FAILURE } } }