Skip to content

Commit 9630821

Browse files
committed
feat: I/O safety for 'poll'
1 parent 3d3e6b9 commit 9630821

File tree

2 files changed

+54
-20
lines changed

2 files changed

+54
-20
lines changed

src/poll.rs

+44-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Wait for events to trigger on specific file descriptors
2-
use std::os::unix::io::{AsRawFd, RawFd};
2+
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
33

44
use crate::errno::Errno;
55
use crate::Result;
@@ -14,20 +14,36 @@ use crate::Result;
1414
/// retrieved by calling [`revents()`](#method.revents) on the `PollFd`.
1515
#[repr(transparent)]
1616
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
17-
pub struct PollFd {
17+
pub struct PollFd <'fd> {
1818
pollfd: libc::pollfd,
19+
_fd: std::marker::PhantomData<BorrowedFd<'fd>>
1920
}
2021

21-
impl PollFd {
22+
impl<'fd> PollFd<'fd> {
2223
/// Creates a new `PollFd` specifying the events of interest
2324
/// for a given file descriptor.
24-
pub const fn new(fd: RawFd, events: PollFlags) -> PollFd {
25+
//
26+
// Different from other I/O-safe interfaces, here, we have to take `AsFd`
27+
// by reference to prevent the case where the `fd` is closed but it is
28+
// still in use. For example:
29+
//
30+
// ```rust
31+
// let (reader, _) = pipe().unwrap();
32+
//
33+
// // If `PollFd::new()` takes `AsFd` by value, then `reader` will be consumed,
34+
// // but the file descriptor of `reader` will still be in use.
35+
// let pollfd = PollFd::new(reader, flag);
36+
//
37+
// // Do something with `pollfd`, which uses the CLOSED fd.
38+
// ```
39+
pub fn new<Fd: AsFd>(fd: &'fd Fd, events: PollFlags) -> PollFd<'fd> {
2540
PollFd {
2641
pollfd: libc::pollfd {
27-
fd,
42+
fd: fd.as_fd().as_raw_fd(),
2843
events: events.bits(),
2944
revents: PollFlags::empty().bits(),
3045
},
46+
_fd: std::marker::PhantomData,
3147
}
3248
}
3349

@@ -68,9 +84,29 @@ impl PollFd {
6884
}
6985
}
7086

71-
impl AsRawFd for PollFd {
72-
fn as_raw_fd(&self) -> RawFd {
73-
self.pollfd.fd
87+
impl<'fd> AsFd for PollFd<'fd> {
88+
fn as_fd(&self) -> BorrowedFd<'_> {
89+
// Safety:
90+
//
91+
// BorrowedFd::borrow_raw(RawFd) requires that the raw fd being passed
92+
// must remain open for the duration of the returned BorrowedFd, this is
93+
// guaranteed as the returned BorrowedFd has the lifetime parameter same
94+
// as `self`:
95+
// "fn as_fd<'self>(&'self self) -> BorrowedFd<'self>"
96+
// which means that `self` (PollFd) is guaranteed to outlive the returned
97+
// BorrowedFd. (Lifetime: PollFd > BorrowedFd)
98+
//
99+
// And the lifetime parameter of PollFd::new(fd, ...) ensures that `fd`
100+
// (an owned file descriptor) must outlive the returned PollFd:
101+
// "pub fn new<Fd: AsFd>(fd: &'fd Fd, events: PollFlags) -> PollFd<'fd>"
102+
// (Lifetime: Owned fd > PollFd)
103+
//
104+
// With two above relationships, we can conclude that the `Owned file
105+
// descriptor` will outlive the returned BorrowedFd,
106+
// (Lifetime: Owned fd > BorrowedFd)
107+
// i.e., the raw fd being passed will remain valid for the lifetime of
108+
// the returned BorrowedFd.
109+
unsafe { BorrowedFd::borrow_raw(self.pollfd.fd) }
74110
}
75111
}
76112

test/test_poll.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use nix::{
22
errno::Errno,
33
poll::{poll, PollFd, PollFlags},
4-
unistd::{pipe, write},
4+
unistd::{close, pipe, write},
55
};
6+
use std::os::unix::io::{BorrowedFd, FromRawFd, OwnedFd};
67

78
macro_rules! loop_while_eintr {
89
($poll_expr: expr) => {
@@ -19,7 +20,8 @@ macro_rules! loop_while_eintr {
1920
#[test]
2021
fn test_poll() {
2122
let (r, w) = pipe().unwrap();
22-
let mut fds = [PollFd::new(r, PollFlags::POLLIN)];
23+
let r = unsafe { OwnedFd::from_raw_fd(r) };
24+
let mut fds = [PollFd::new(&r, PollFlags::POLLIN)];
2325

2426
// Poll an idle pipe. Should timeout
2527
let nfds = loop_while_eintr!(poll(&mut fds, 100));
@@ -32,6 +34,7 @@ fn test_poll() {
3234
let nfds = poll(&mut fds, 100).unwrap();
3335
assert_eq!(nfds, 1);
3436
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
37+
close(w).unwrap();
3538
}
3639

3740
// ppoll(2) is the same as poll except for how it handles timeouts and signals.
@@ -51,7 +54,8 @@ fn test_ppoll() {
5154

5255
let timeout = TimeSpec::milliseconds(1);
5356
let (r, w) = pipe().unwrap();
54-
let mut fds = [PollFd::new(r, PollFlags::POLLIN)];
57+
let r = unsafe { OwnedFd::from_raw_fd(r) };
58+
let mut fds = [PollFd::new(&r, PollFlags::POLLIN)];
5559

5660
// Poll an idle pipe. Should timeout
5761
let sigset = SigSet::empty();
@@ -65,19 +69,13 @@ fn test_ppoll() {
6569
let nfds = ppoll(&mut fds, Some(timeout), None).unwrap();
6670
assert_eq!(nfds, 1);
6771
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
68-
}
69-
70-
#[test]
71-
fn test_pollfd_fd() {
72-
use std::os::unix::io::AsRawFd;
73-
74-
let pfd = PollFd::new(0x1234, PollFlags::empty());
75-
assert_eq!(pfd.as_raw_fd(), 0x1234);
72+
close(w).unwrap();
7673
}
7774

7875
#[test]
7976
fn test_pollfd_events() {
80-
let mut pfd = PollFd::new(-1, PollFlags::POLLIN);
77+
let fd_zero = unsafe { BorrowedFd::borrow_raw(0) };
78+
let mut pfd = PollFd::new(&fd_zero, PollFlags::POLLIN);
8179
assert_eq!(pfd.events(), PollFlags::POLLIN);
8280
pfd.set_events(PollFlags::POLLOUT);
8381
assert_eq!(pfd.events(), PollFlags::POLLOUT);

0 commit comments

Comments
 (0)