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(this.machine.readiness_interests.new_watcher());
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.clone(), interests)?;
106            // The Linux implementation also counts invalid interests as fulfilled.
107            let total = count.strict_add(invalid_interests);
108
109            // FIXME: Until <https://github.com/rust-lang/miri/issues/5152> is fixed,
110            // we destroy the watcher here instead of in `write_ready_events` because
111            // here we know that we only have a single strong reference.
112            let inner = Rc::into_inner(watcher).unwrap();
113            inner.destroy(this);
114
115            return this.write_scalar(Scalar::from_u32(total), dest);
116        }
117
118        // None of the interests are currently fulfilled; we thus need to
119        // block the thread until any interest gets fulfilled.
120        watcher.add_blocked_thread(this.machine.threads.active_thread());
121
122        let deadline = if timeout.is_positive() {
123            let timeout_duration = Duration::from_millis(u64::try_from(timeout).unwrap());
124            Some(this.machine.monotonic_clock.now().add_lossy(timeout_duration).into())
125        } else {
126            // Negative timeout means block indefinitely.
127            None
128        };
129
130        let dest = dest.clone();
131        this.block_thread(
132            BlockReason::Readiness,
133            deadline,
134            callback!(
135                @capture<'tcx> {
136                    watcher: Rc<ReadinessWatcher>,
137                    interests: BTreeMap<FdNum, PollInterest<'tcx>>,
138                    dest: MPlaceTy<'tcx>,
139                } |this, reason: UnblockKind| {
140                    if let UnblockKind::TimedOut = reason {
141                        // FIXME(miri#5152): we are not destroying the watcher.
142                        return this.write_null(&dest);
143                    }
144
145                    let count = this.write_ready_events(watcher, interests)?;
146                    this.write_scalar(Scalar::from_u32(count), &dest)
147                }
148            ),
149        );
150
151        interp_ok(())
152    }
153}
154
155impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
156trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
157    /// For all ready interests on the watcher, write the appropriate
158    /// readiness into the `revents` field of the associated poll interest.
159    fn write_ready_events(
160        &mut self,
161        watcher: Rc<ReadinessWatcher>,
162        interests: BTreeMap<FdNum, PollInterest<'tcx>>,
163    ) -> InterpResult<'tcx, u32> {
164        let this = self.eval_context_mut();
165
166        // Counts the number of poll interests that are fulfilled.
167        let mut fulfilled_interests = 0u32;
168
169        // Iterate over all ready interests of the watcher and
170        // write the output readiness of all related poll interests.
171        for ready in watcher.get_ready_interests(watcher.ready_count(), this)? {
172            let fd_num = FdNum::try_from(ready.data).expect("Data is always a file descriptor");
173            let interest = interests.get(&fd_num).expect("Interest should exist");
174
175            fulfilled_interests = fulfilled_interests.strict_add(1);
176
177            let poll_events = this.readiness_to_poll_bitflag(ready.active());
178            this.write_scalar(Scalar::from_u16(poll_events), &interest.revents_place)?;
179        }
180
181        // FIXME: At this point the watcher should be destroyed. However, because
182        // multiple strong references still exist at this point, that is impossible.
183        // See <https://github.com/rust-lang/miri/issues/5152>
184
185        interp_ok(fulfilled_interests)
186    }
187
188    /// Convert a [`Readiness`] instance into the corresponding poll
189    /// readiness bitflag.
190    fn readiness_to_poll_bitflag(&self, readiness: &Readiness) -> u16 {
191        let this = self.eval_context_ref();
192
193        let pollin = this.eval_libc_u16("POLLIN");
194        let pollout = this.eval_libc_u16("POLLOUT");
195        let pollhup = this.eval_libc_u16("POLLHUP");
196        let pollerr = this.eval_libc_u16("POLLERR");
197
198        let mut bitflag = 0;
199        if readiness.readable {
200            bitflag |= pollin;
201        }
202        if readiness.writable {
203            bitflag |= pollout;
204        }
205        if readiness.write_closed {
206            bitflag |= pollhup;
207        }
208        if readiness.error {
209            bitflag |= pollerr;
210        }
211
212        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
213            // POLLRDHUP only exists on Linux, Android, FreeBSD, and Illumos.
214            let pollrdhup = this.eval_libc_u16("POLLRDHUP");
215            if readiness.read_closed {
216                bitflag |= pollrdhup;
217            }
218        }
219
220        bitflag
221    }
222
223    /// Convert a poll readiness bitflag into the corresponding [`Readiness`] instance.
224    ///
225    /// This always sets the `write_closed` and `error` readiness since they are
226    /// implicitly registered for any interest with `poll`.
227    fn poll_bitflag_to_readiness(&self, mut bitflag: u16) -> InterpResult<'tcx, Readiness> {
228        let this = self.eval_context_ref();
229
230        let pollin = this.eval_libc_u16("POLLIN");
231        let pollout = this.eval_libc_u16("POLLOUT");
232        let pollhup = this.eval_libc_u16("POLLHUP");
233        let pollerr = this.eval_libc_u16("POLLERR");
234        let pollnval = this.eval_libc_u16("POLLNVAL");
235
236        // The POLLHUP and POLLERR interests are always set.
237        let mut readiness = Readiness { write_closed: true, error: true, ..Readiness::EMPTY };
238
239        if bitflag & pollin == pollin {
240            readiness.readable = true;
241            bitflag &= !pollin;
242        }
243        if bitflag & pollout == pollout {
244            readiness.writable = true;
245            bitflag &= !pollout;
246        }
247        if bitflag & pollhup == pollhup {
248            bitflag &= !pollhup;
249        }
250        if bitflag & pollerr == pollerr {
251            bitflag &= !pollerr;
252        }
253        if bitflag & pollnval == pollnval {
254            // POLLNVAL is ignored when it's provided as a relevant event.
255            bitflag &= !pollnval;
256        }
257
258        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
259            // POLLRDHUP only exists on Linux, Android, FreeBSD, and Illumos.
260            let pollrdhup = this.eval_libc_u16("POLLRDHUP");
261            if bitflag & pollrdhup == pollrdhup {
262                readiness.read_closed = true;
263                bitflag &= !pollrdhup;
264            }
265        }
266
267        if bitflag != 0 {
268            throw_unsup_format!(
269                "poll: poll event {bitflag:#x} is unsupported. Only POLLIN, \
270                POLLOUT, POLLERR, POLLHUP, POLLNVAL and POLLRDHUP are supported."
271            );
272        }
273
274        interp_ok(readiness)
275    }
276}