• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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