From d85f733c08c455abd8c1a7b4a30c0ae46ddac04c Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 28 Feb 2025 08:03:26 -0800 Subject: [PATCH] Add an example and testcases for the `event::port` API. Add an example of how to use `event::port` on Solarish platforms, and add a test as well. --- examples/port.rs | 62 ++++++++++++++++ tests/event/main.rs | 2 + tests/event/port.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 examples/port.rs create mode 100644 tests/event/port.rs diff --git a/examples/port.rs b/examples/port.rs new file mode 100644 index 000000000..12da57ecc --- /dev/null +++ b/examples/port.rs @@ -0,0 +1,62 @@ +//! A simple example of the Solarish `port` API. +//! +//! This creates a fifo and then monitors it for data. To use it, +//! run the example, note the path it prints out, and then in +//! another window, run `cat ` and type some text. + +#[cfg(all(solarish, feature = "event", feature = "fs"))] +fn main() -> std::io::Result<()> { + use rustix::buffer::spare_capacity; + use rustix::event::port; + use rustix::fd::AsRawFd; + use rustix::fs; + use std::ptr::null_mut; + + let port = port::create()?; + let mut out = Vec::with_capacity(10); + + let tmpdir = tempfile::tempdir()?; + let fifo_path = tmpdir.path().join("fifo"); + fs::mknodat( + fs::CWD, + &fifo_path, + fs::FileType::Fifo, + fs::Mode::RUSR | fs::Mode::WUSR, + 0, + )?; + + eprintln!("Listening for data on fifo {}", fifo_path.display()); + + let fifo = fs::openat(fs::CWD, &fifo_path, fs::OFlags::RDONLY, fs::Mode::empty())?; + + loop { + // Associate `some_fd` with the port. + unsafe { + port::associate_fd(&port, fifo.as_raw_fd(), port::PollFlags::IN, null_mut())?; + } + + port::getn(&port, spare_capacity(&mut out), 1, None)?; + + for event in out.drain(..) { + dbg!(event.events(), event.object(), event.userdata()); + + let mut buf = [0_u8; 32]; + loop { + match rustix::io::read(&fifo, &mut buf) { + Ok(0) => return Ok(()), + Ok(n) => { + dbg!(&buf[..n]); + break; + } + Err(rustix::io::Errno::INTR) => continue, + Err(err) => Err(err).unwrap(), + } + } + } + } +} + +#[cfg(not(all(solarish, feature = "event", feature = "fs")))] +fn main() -> Result<(), &'static str> { + Err("This example requires --features=event,fs and is only supported on Solarish.") +} diff --git a/tests/event/main.rs b/tests/event/main.rs index ed1b53f46..9a0a8ad05 100644 --- a/tests/event/main.rs +++ b/tests/event/main.rs @@ -14,6 +14,8 @@ mod epoll_timeout; #[cfg(not(target_os = "wasi"))] mod eventfd; mod poll; +#[cfg(solarish)] +mod port; #[cfg(any(bsd, linux_kernel, windows, target_os = "wasi"))] mod select; diff --git a/tests/event/port.rs b/tests/event/port.rs new file mode 100644 index 000000000..dbc9a7b64 --- /dev/null +++ b/tests/event/port.rs @@ -0,0 +1,174 @@ +//! We assume that `port_get` etc. don't mutate the timestamp. +//! +//! illumos and Solaris document that the timestamp is `const`, but it's +//! `mut` in the Rust libc bindings. Test that it isn't actually mutated +//! in practice. + +// Test that the timeout isn't mutated on a timeout. +#[test] +fn test_port_timeout_assumption() { + unsafe { + use rustix::fd::AsRawFd; + use std::ffi::c_void; + + let port = libc::port_create(); + assert_ne!(port, -1); + + let file = std::fs::File::open("Cargo.toml").unwrap(); + let fd = file.as_raw_fd(); + + let r = libc::port_associate( + port, + libc::PORT_SOURCE_FD, + fd as _, + libc::POLLERR.into(), + 7 as *mut c_void, + ); + assert_ne!(r, -1); + + let mut event = std::mem::zeroed::(); + let orig_timeout = libc::timespec { + tv_sec: 0, + tv_nsec: 500_000_000, + }; + let mut timeout = orig_timeout.clone(); + libc_errno::set_errno(libc_errno::Errno(0)); + let r = libc::port_get(port, &mut event, &mut timeout); + assert_eq!(libc_errno::errno().0, libc::ETIME); + assert_eq!(r, -1); + + assert_eq!(timeout, orig_timeout); + } +} + +// Test that the timeout isn't mutated on an immediate wake. +#[test] +fn test_port_event_assumption() { + unsafe { + use rustix::fd::AsRawFd; + use std::ffi::c_void; + + let port = libc::port_create(); + assert_ne!(port, -1); + + let file = std::fs::File::open("Cargo.toml").unwrap(); + let fd = file.as_raw_fd(); + + let r = libc::port_associate( + port, + libc::PORT_SOURCE_FD, + fd as _, + libc::POLLIN.into(), + 7 as *mut c_void, + ); + assert_ne!(r, -1); + + let mut event = std::mem::zeroed::(); + let orig_timeout = libc::timespec { + tv_sec: 1, + tv_nsec: 5678, + }; + let mut timeout = orig_timeout.clone(); + let r = libc::port_get(port, &mut event, &mut timeout); + assert_ne!(r, -1); + + assert_eq!(timeout, orig_timeout); + assert_eq!(event.portev_user, 7 as *mut c_void); + } +} + +// Test that the timeout isn't mutated when data arrives midway +// through a timeout. +#[cfg(feature = "fs")] +#[test] +fn test_port_delay_assumption() { + use rustix::fs; + + let tmpdir = tempfile::tempdir().unwrap(); + let fifo_path = tmpdir.path().join("fifo"); + fs::mknodat( + fs::CWD, + &fifo_path, + fs::FileType::Fifo, + fs::Mode::RUSR + | fs::Mode::WUSR + | fs::Mode::RGRP + | fs::Mode::WGRP + | fs::Mode::ROTH + | fs::Mode::WOTH, + 0, + ) + .unwrap(); + + let fifo_path_clone = fifo_path.clone(); + let _mutater = std::thread::Builder::new() + .name("mutater".to_string()) + .spawn(|| { + let fifo = fs::openat( + fs::CWD, + fifo_path_clone, + fs::OFlags::WRONLY, + fs::Mode::empty(), + ) + .unwrap(); + + for i in 0..10 { + let buf = [b'A' + (i % 26)]; + match rustix::io::write(&fifo, &buf) { + Ok(1) => {} + Ok(n) => panic!("unexpected write of length {}", n), + Err(rustix::io::Errno::PIPE) => return, + Err(err) => Err(err).unwrap(), + } + std::thread::sleep(std::time::Duration::new(0, 4_000_000)); + } + panic!("Loop iterated too many times without completing!"); + }) + .unwrap(); + + let fifo = fs::openat(fs::CWD, &fifo_path, fs::OFlags::RDONLY, fs::Mode::empty()).unwrap(); + + unsafe { + use rustix::fd::AsRawFd; + use std::ffi::c_void; + + let port = libc::port_create(); + assert_ne!(port, -1); + + for i in 0..5 { + let r = libc::port_associate( + port, + libc::PORT_SOURCE_FD, + fifo.as_raw_fd() as _, + libc::POLLIN.into(), + (9 + i) as *mut c_void, + ); + assert_ne!(r, -1); + + let mut event = std::mem::zeroed::(); + let orig_timeout = libc::timespec { + tv_sec: 5, + tv_nsec: 5678, + }; + let mut timeout = orig_timeout.clone(); + let r = libc::port_get(port, &mut event, &mut timeout); + assert_ne!(r, -1, "port_get: {:?}", std::io::Error::last_os_error()); + + assert_eq!(timeout, orig_timeout); + assert_eq!(event.portev_user, (9 + i) as *mut c_void); + + let mut buf = [0_u8; 1]; + loop { + match rustix::io::read(&fifo, &mut buf) { + Ok(1) => { + assert_eq!(buf[0], b'A' + i); + break; + } + Ok(n) => panic!("unexpected read of length {}", n), + Err(rustix::io::Errno::INTR) => continue, + Err(err) => Err(err).unwrap(), + } + } + } + } +}