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