1 #![allow(non_camel_case_types, dead_code)]
2
3 use std::io;
4 use std::os::fd::AsFd;
5 use std::slice;
6 use std::time::Duration;
7
8 use nix::libc::c_int;
9 use nix::poll::{PollFd, PollFlags};
10 #[cfg(target_os = "linux")]
11 use nix::sys::signal::SigSet;
12 #[cfg(any(target_os = "linux", test))]
13 use nix::sys::time::TimeSpec;
14
wait_read_fd<F: AsFd>(fd: F, timeout: Duration) -> io::Result<()>15 pub fn wait_read_fd<F: AsFd>(fd: F, timeout: Duration) -> io::Result<()> {
16 wait_fd(fd, PollFlags::POLLIN, timeout)
17 }
18
wait_write_fd<F: AsFd>(fd: F, timeout: Duration) -> io::Result<()>19 pub fn wait_write_fd<F: AsFd>(fd: F, timeout: Duration) -> io::Result<()> {
20 wait_fd(fd, PollFlags::POLLOUT, timeout)
21 }
22
wait_fd<F: AsFd>(fd: F, events: PollFlags, timeout: Duration) -> io::Result<()>23 fn wait_fd<F: AsFd>(fd: F, events: PollFlags, timeout: Duration) -> io::Result<()> {
24 use nix::errno::Errno::{EIO, EPIPE};
25
26 let mut fd = PollFd::new(fd.as_fd(), events);
27
28 let wait = match poll_clamped(&mut fd, timeout) {
29 Ok(r) => r,
30 Err(e) => return Err(io::Error::from(crate::Error::from(e))),
31 };
32 // All errors generated by poll or ppoll are already caught by the nix wrapper around libc, so
33 // here we only need to check if there's at least 1 event
34 if wait != 1 {
35 return Err(io::Error::new(
36 io::ErrorKind::TimedOut,
37 "Operation timed out",
38 ));
39 }
40
41 // Check the result of ppoll() by looking at the revents field
42 match fd.revents() {
43 Some(e) if e == events => return Ok(()),
44 // If there was a hangout or invalid request
45 Some(e) if e.contains(PollFlags::POLLHUP) || e.contains(PollFlags::POLLNVAL) => {
46 return Err(io::Error::new(io::ErrorKind::BrokenPipe, EPIPE.desc()));
47 }
48 Some(_) | None => (),
49 }
50
51 Err(io::Error::new(io::ErrorKind::Other, EIO.desc()))
52 }
53
54 /// Poll with a duration clamped to the maximum value representable by the `TimeSpec` used by
55 /// `ppoll`.
56 #[cfg(target_os = "linux")]
poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int>57 fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int> {
58 let spec = clamped_time_spec(timeout);
59 nix::poll::ppoll(slice::from_mut(fd), Some(spec), Some(SigSet::empty()))
60 }
61
62 #[cfg(any(target_os = "linux", test))]
63 // The type time_t is deprecaten on musl. The nix crate internally uses this type and makes an
64 // exeption for the deprecation for musl. And so do we.
65 //
66 // See https://github.com/rust-lang/libc/issues/1848 which is referenced from every exemption used
67 // in nix.
68 #[cfg_attr(target_env = "musl", allow(deprecated))]
clamped_time_spec(duration: Duration) -> TimeSpec69 fn clamped_time_spec(duration: Duration) -> TimeSpec {
70 use nix::libc::c_long;
71 use nix::sys::time::time_t;
72
73 // We need to clamp manually as TimeSpec::from_duration translates durations with more than
74 // i64::MAX seconds to negative timespans. This happens due to casting to i64 and is still the
75 // case as of nix 0.29.
76 let secs_limit = time_t::MAX as u64;
77 let secs = duration.as_secs();
78 if secs <= secs_limit {
79 TimeSpec::new(secs as time_t, duration.subsec_nanos() as c_long)
80 } else {
81 TimeSpec::new(time_t::MAX, 999_999_999)
82 }
83 }
84
85 // Poll with a duration clamped to the maximum millisecond value representable by the `c_int` used
86 // by `poll`.
87 #[cfg(not(target_os = "linux"))]
poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int>88 fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result<c_int> {
89 let millis = clamped_millis_c_int(timeout);
90 let poll_timeout = nix::poll::PollTimeout::try_from(millis).unwrap();
91 nix::poll::poll(slice::from_mut(fd), poll_timeout)
92 }
93
94 #[cfg(any(not(target_os = "linux"), test))]
clamped_millis_c_int(duration: Duration) -> c_int95 fn clamped_millis_c_int(duration: Duration) -> c_int {
96 let secs_limit = (c_int::MAX as u64) / 1000;
97 let secs = duration.as_secs();
98
99 if secs <= secs_limit {
100 secs as c_int * 1000 + duration.subsec_millis() as c_int
101 } else {
102 c_int::MAX
103 }
104 }
105
106 #[cfg(test)]
107 mod tests {
108 use super::*;
109 use crate::tests::timeout::MONOTONIC_DURATIONS;
110
111 #[test]
clamped_millis_c_int_is_monotonic()112 fn clamped_millis_c_int_is_monotonic() {
113 let mut last = clamped_millis_c_int(Duration::ZERO);
114
115 for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() {
116 let next = clamped_millis_c_int(*d);
117 assert!(
118 next >= last,
119 "{next} >= {last} failed for {d:?} at index {i}"
120 );
121 last = next;
122 }
123 }
124
125 #[test]
clamped_millis_c_int_zero_is_zero()126 fn clamped_millis_c_int_zero_is_zero() {
127 assert_eq!(0, clamped_millis_c_int(Duration::ZERO));
128 }
129
130 #[test]
clamped_time_spec_is_monotonic()131 fn clamped_time_spec_is_monotonic() {
132 let mut last = clamped_time_spec(Duration::ZERO);
133
134 for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() {
135 let next = clamped_time_spec(*d);
136 assert!(
137 next >= last,
138 "{next} >= {last} failed for {d:?} at index {i}"
139 );
140 last = next;
141 }
142 }
143
144 #[test]
clamped_time_spec_zero_is_zero()145 fn clamped_time_spec_zero_is_zero() {
146 let spec = clamped_time_spec(Duration::ZERO);
147 assert_eq!(0, spec.tv_sec());
148 assert_eq!(0, spec.tv_nsec());
149 }
150 }
151