Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions examples/port.rs
Original file line number Diff line number Diff line change
@@ -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 <path>` 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.")
}
2 changes: 2 additions & 0 deletions tests/event/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
174 changes: 174 additions & 0 deletions tests/event/port.rs
Original file line number Diff line number Diff line change
@@ -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::<libc::port_event>();
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::<libc::port_event>();
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::<libc::port_event>();
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(),
}
}
}
}
}