Skip to main content

miri/shims/unix/
poll.rs

1use std::collections::BTreeMap;
2use std::rc::Rc;
3use std::time::Duration;
4
5use rustc_target::spec::Os;
6
7use crate::shims::files::{FD_LIMIT, FdNum};
8use crate::*;
9
10/// An interest into a file descriptor together with its
11/// relevant readiness events.
12#[derive(Debug)]
13struct PollInterest<'tcx> {
14    /// Place where the ready events of the interests should be written to.
15    revents_place: MPlaceTy<'tcx>,
16}
17
18impl VisitProvenance for PollInterest<'_> {
19    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
20        self.revents_place.visit_provenance(visit);
21    }
22}
23
24impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
25pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
26    fn poll(
27        &mut self,
28        fds: &OpTy<'tcx>,
29        nfds: &OpTy<'tcx>,
30        timeout: &OpTy<'tcx>,
31        dest: &MPlaceTy<'tcx>,
32    ) -> InterpResult<'tcx> {
33        let ecx = self.eval_context_mut();
34
35        let nfds: u64 = ecx.read_scalar(nfds)?.to_uint(nfds.layout.size)?.try_into().unwrap();
36        let timeout = ecx.read_scalar(timeout)?.to_i32()?;
37
38        // Return `EINVAL`` if `nfds` is greater than `FD_LIMIT`.
39        if nfds > u64::try_from(FD_LIMIT).unwrap() {
40            return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
41        }
42        let fds_arr = ecx.deref_pointer(fds)?;
43
44        // The provided interests indexed by the file descriptor they're for.
45        let mut interests = BTreeMap::<FdNum, PollInterest<'tcx>>::new();
46        // Counts the number of poll interests that are invalid because they're for
47        // a positive file descriptor that doesn't exist.
48        let mut invalid_interests = 0u32;
49
50        let watcher = Rc::new(ReadinessWatcher::default());
51
52        // We iterate over the fds array of the `poll` syscall. For each fd, we check its
53        // output field, the relevant events, and whether they are currently fulfilled.
54        for idx in 0..nfds {
55            let pollfd = ecx.project_c_array_field(&fds_arr, idx)?;
56            let fd_field = ecx.project_field_named(&pollfd, "fd")?;
57            let fd_num = ecx.read_scalar(&fd_field)?.to_i32()?;
58            let events_field = ecx.project_field_named(&pollfd, "events")?;
59            let events = ecx.read_scalar(&events_field)?.to_u16()?;
60            let revents_field = ecx.project_field_named(&pollfd, "revents")?;
61
62            let relevant_events = ecx.poll_bitflag_to_readiness(events)?;
63
64            let revents = if ecx.machine.fds.get(fd_num).is_some() {
65                // A file description for this file descriptor exists; the interest is thus not ignored.
66                let interest = PollInterest { revents_place: revents_field.clone() };
67                if interests.try_insert(fd_num, interest).is_err() {
68                    throw_unsup_format!(
69                        "poll: providing multiple interests for the same file descriptor is unsupported"
70                    )
71                }
72                watcher
73                    .add_interest(
74                        fd_num,
75                        relevant_events,
76                        /* is_edge_triggered */ false,
77                        u64::try_from(fd_num).unwrap(),
78                        ecx,
79                    )?
80                    // We just ensured that no file descriptor is registered twice.
81                    .unwrap();
82
83                // Since we later only update the `revents` field for FDs which receive
84                // an event, we initially zero this field.
85                0
86            } else if fd_num.is_negative() {
87                // Interests for negative file descriptors should be ignored and
88                // their `revents` field should be zeroed.
89                0
90            } else {
91                // Interests for positive, invalid file descriptors should be ignored
92                // and their `revents` field should be set to POLLNVAL.
93
94                // The Linux implementation still counts such interests as "fulfilled"
95                // and thus returns from the `poll` invocation.
96                invalid_interests = invalid_interests.strict_add(1);
97
98                ecx.eval_libc_u16("POLLNVAL")
99            };
100
101            ecx.write_scalar(Scalar::from_u16(revents), &revents_field)?;
102        }
103
104        if timeout == 0 || invalid_interests > 0 || watcher.ready_count() > 0 {
105            // Some interests are already fulfilled or a zero timeout was provided.
106            // We thus don't need to block the thread and can just return here.
107
108            let count = ecx.write_ready_events(watcher, interests)?;
109            // The Linux implementation also counts invalid interests as fulfilled.
110            let total = count.strict_add(invalid_interests);
111
112            return ecx.write_scalar(Scalar::from_u32(total), dest);
113        }
114
115        // None of the interests are currently fulfilled; we thus need to
116        // block the thread until any interest gets fulfilled.
117        watcher.add_blocked_thread(ecx.machine.threads.active_thread());
118
119        let deadline = if timeout.is_positive() {
120            let timeout_duration = Duration::from_millis(u64::try_from(timeout).unwrap());
121            Some(ecx.machine.monotonic_clock.now().add_lossy(timeout_duration).into())
122        } else {
123            // Negative timeout means block indefinitely.
124            None
125        };
126
127        let dest = dest.clone();
128        ecx.block_thread(
129            BlockReason::Readiness,
130            deadline,
131            callback!(
132                @capture<'tcx> {
133                    watcher: Rc<ReadinessWatcher>,
134                    interests: BTreeMap<FdNum, PollInterest<'tcx>>,
135                    dest: MPlaceTy<'tcx>,
136                } |ecx, reason: UnblockKind| {
137                    if let UnblockKind::TimedOut = reason {
138                        return ecx.write_scalar(Scalar::from_u32(0), &dest);
139                    }
140
141                    let count = ecx.write_ready_events(watcher, interests)?;
142                    ecx.write_scalar(Scalar::from_u32(count), &dest)
143                }
144            ),
145        );
146
147        interp_ok(())
148    }
149}
150
151impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
152trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
153    /// For all ready interests on the watcher, write the appropriate
154    /// readiness into the `revents` field of the associated poll interest.
155    fn write_ready_events(
156        &mut self,
157        watcher: Rc<ReadinessWatcher>,
158        interests: BTreeMap<FdNum, PollInterest<'tcx>>,
159    ) -> InterpResult<'tcx, u32> {
160        let this = self.eval_context_mut();
161
162        // Counts the number of poll interests that are fulfilled.
163        let mut fulfilled_interests = 0u32;
164
165        // Iterate over all ready interests of the watcher and
166        // write the output readiness of all related poll interests.
167        for ready in watcher.get_ready_interests(watcher.ready_count(), this)? {
168            let fd_num = FdNum::try_from(ready.data).expect("Data is always a file descriptor");
169            let interest = interests.get(&fd_num).expect("Interest should exist");
170
171            fulfilled_interests = fulfilled_interests.strict_add(1);
172
173            let poll_events = this.readiness_to_poll_bitflag(ready.active());
174            this.write_scalar(Scalar::from_u16(poll_events), &interest.revents_place)?;
175        }
176
177        interp_ok(fulfilled_interests)
178    }
179
180    /// Convert a [`Readiness`] instance into the corresponding poll
181    /// readiness bitflag.
182    fn readiness_to_poll_bitflag(&self, readiness: &Readiness) -> u16 {
183        let this = self.eval_context_ref();
184
185        let pollin = this.eval_libc_u16("POLLIN");
186        let pollout = this.eval_libc_u16("POLLOUT");
187        let pollhup = this.eval_libc_u16("POLLHUP");
188        let pollerr = this.eval_libc_u16("POLLERR");
189
190        let mut bitflag = 0;
191        if readiness.readable {
192            bitflag |= pollin;
193        }
194        if readiness.writable {
195            bitflag |= pollout;
196        }
197        if readiness.write_closed {
198            bitflag |= pollhup;
199        }
200        if readiness.error {
201            bitflag |= pollerr;
202        }
203
204        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
205            // POLLRDHUP only exists on Linux, Android, FreeBSD, and Illumos.
206            let pollrdhup = this.eval_libc_u16("POLLRDHUP");
207            if readiness.read_closed {
208                bitflag |= pollrdhup;
209            }
210        }
211
212        bitflag
213    }
214
215    /// Convert a poll readiness bitflag into the corresponding [`Readiness`] instance.
216    ///
217    /// This always sets the `write_closed` and `error` readiness since they are
218    /// implicitly registered for any interest with `poll`.
219    fn poll_bitflag_to_readiness(&self, mut bitflag: u16) -> InterpResult<'tcx, Readiness> {
220        let this = self.eval_context_ref();
221
222        let pollin = this.eval_libc_u16("POLLIN");
223        let pollout = this.eval_libc_u16("POLLOUT");
224        let pollhup = this.eval_libc_u16("POLLHUP");
225        let pollerr = this.eval_libc_u16("POLLERR");
226        let pollnval = this.eval_libc_u16("POLLNVAL");
227
228        // The POLLHUP and POLLERR interests are always set.
229        let mut readiness = Readiness { write_closed: true, error: true, ..Readiness::EMPTY };
230
231        if bitflag & pollin == pollin {
232            readiness.readable = true;
233            bitflag &= !pollin;
234        }
235        if bitflag & pollout == pollout {
236            readiness.writable = true;
237            bitflag &= !pollout;
238        }
239        if bitflag & pollhup == pollhup {
240            bitflag &= !pollhup;
241        }
242        if bitflag & pollerr == pollerr {
243            bitflag &= !pollerr;
244        }
245        if bitflag & pollnval == pollnval {
246            // POLLNVAL is ignored when it's provided as a relevant event.
247            bitflag &= !pollnval;
248        }
249
250        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
251            // POLLRDHUP only exists on Linux, Android, FreeBSD, and Illumos.
252            let pollrdhup = this.eval_libc_u16("POLLRDHUP");
253            if bitflag & pollrdhup == pollrdhup {
254                readiness.read_closed = true;
255                bitflag &= !pollrdhup;
256            }
257        }
258
259        if bitflag != 0 {
260            throw_unsup_format!(
261                "poll: poll event {bitflag:#x} is unsupported. Only POLLIN, \
262                POLLOUT, POLLERR, POLLHUP, POLLNVAL and POLLRDHUP are supported."
263            );
264        }
265
266        interp_ok(readiness)
267    }
268}