miri/shims/
files.rs

1use std::any::Any;
2use std::collections::BTreeMap;
3use std::io::{IsTerminal, SeekFrom, Write};
4use std::marker::CoercePointee;
5use std::ops::Deref;
6use std::rc::{Rc, Weak};
7use std::{fs, io};
8
9use rustc_abi::Size;
10
11use crate::shims::unix::UnixFileDescription;
12use crate::*;
13
14/// A unique id for file descriptions. While we could use the address, considering that
15/// is definitely unique, the address would expose interpreter internal state when used
16/// for sorting things. So instead we generate a unique id per file description is the name
17/// for all `dup`licates and is never reused.
18#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
19pub struct FdId(usize);
20
21#[derive(Debug, Clone)]
22struct FdIdWith<T: ?Sized> {
23    id: FdId,
24    inner: T,
25}
26
27/// A refcounted pointer to a file description, also tracking the
28/// globally unique ID of this file description.
29#[repr(transparent)]
30#[derive(CoercePointee, Debug)]
31pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
32
33impl<T: ?Sized> Clone for FileDescriptionRef<T> {
34    fn clone(&self) -> Self {
35        FileDescriptionRef(self.0.clone())
36    }
37}
38
39impl<T: ?Sized> Deref for FileDescriptionRef<T> {
40    type Target = T;
41    fn deref(&self) -> &T {
42        &self.0.inner
43    }
44}
45
46impl<T: ?Sized> FileDescriptionRef<T> {
47    pub fn id(&self) -> FdId {
48        self.0.id
49    }
50}
51
52/// Holds a weak reference to the actual file description.
53#[derive(Debug)]
54pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
55
56impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
57    fn clone(&self) -> Self {
58        WeakFileDescriptionRef(self.0.clone())
59    }
60}
61
62impl<T: ?Sized> FileDescriptionRef<T> {
63    pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
64        WeakFileDescriptionRef(Rc::downgrade(&this.0))
65    }
66}
67
68impl<T: ?Sized> WeakFileDescriptionRef<T> {
69    pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
70        self.0.upgrade().map(FileDescriptionRef)
71    }
72}
73
74impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
75    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
76        // A weak reference can never be the only reference to some pointer or place.
77        // Since the actual file description is tracked by strong ref somewhere,
78        // it is ok to make this a NOP operation.
79    }
80}
81
82/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
83/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
84/// but that does not allow upcasting.
85pub trait FileDescriptionExt: 'static {
86    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
87
88    /// We wrap the regular `close` function generically, so both handle `Rc::into_inner`
89    /// and epoll interest management.
90    fn close_ref<'tcx>(
91        self: FileDescriptionRef<Self>,
92        communicate_allowed: bool,
93        ecx: &mut MiriInterpCx<'tcx>,
94    ) -> InterpResult<'tcx, io::Result<()>>;
95}
96
97impl<T: FileDescription + 'static> FileDescriptionExt for T {
98    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
99        self.0
100    }
101
102    fn close_ref<'tcx>(
103        self: FileDescriptionRef<Self>,
104        communicate_allowed: bool,
105        ecx: &mut MiriInterpCx<'tcx>,
106    ) -> InterpResult<'tcx, io::Result<()>> {
107        match Rc::into_inner(self.0) {
108            Some(fd) => {
109                // Remove entry from the global epoll_event_interest table.
110                ecx.machine.epoll_interests.remove(fd.id);
111
112                fd.inner.close(communicate_allowed, ecx)
113            }
114            None => {
115                // Not the last reference.
116                interp_ok(Ok(()))
117            }
118        }
119    }
120}
121
122pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
123
124impl FileDescriptionRef<dyn FileDescription> {
125    pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
126        let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
127        Some(FileDescriptionRef(inner))
128    }
129}
130
131/// Represents an open file description.
132pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
133    fn name(&self) -> &'static str;
134
135    /// Reads as much as possible into the given buffer `ptr`.
136    /// `len` indicates how many bytes we should try to read.
137    /// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
138    fn read<'tcx>(
139        self: FileDescriptionRef<Self>,
140        _communicate_allowed: bool,
141        _ptr: Pointer,
142        _len: usize,
143        _ecx: &mut MiriInterpCx<'tcx>,
144        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
145    ) -> InterpResult<'tcx> {
146        throw_unsup_format!("cannot read from {}", self.name());
147    }
148
149    /// Writes as much as possible from the given buffer `ptr`.
150    /// `len` indicates how many bytes we should try to write.
151    /// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
152    fn write<'tcx>(
153        self: FileDescriptionRef<Self>,
154        _communicate_allowed: bool,
155        _ptr: Pointer,
156        _len: usize,
157        _ecx: &mut MiriInterpCx<'tcx>,
158        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
159    ) -> InterpResult<'tcx> {
160        throw_unsup_format!("cannot write to {}", self.name());
161    }
162
163    /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
164    /// Returns the new position from the start of the stream.
165    fn seek<'tcx>(
166        &self,
167        _communicate_allowed: bool,
168        _offset: SeekFrom,
169    ) -> InterpResult<'tcx, io::Result<u64>> {
170        throw_unsup_format!("cannot seek on {}", self.name());
171    }
172
173    /// Close the file descriptor.
174    fn close<'tcx>(
175        self,
176        _communicate_allowed: bool,
177        _ecx: &mut MiriInterpCx<'tcx>,
178    ) -> InterpResult<'tcx, io::Result<()>>
179    where
180        Self: Sized,
181    {
182        throw_unsup_format!("cannot close {}", self.name());
183    }
184
185    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
186        throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
187    }
188
189    fn is_tty(&self, _communicate_allowed: bool) -> bool {
190        // Most FDs are not tty's and the consequence of a wrong `false` are minor,
191        // so we use a default impl here.
192        false
193    }
194
195    fn as_unix(&self) -> &dyn UnixFileDescription {
196        panic!("Not a unix file descriptor: {}", self.name());
197    }
198}
199
200impl FileDescription for io::Stdin {
201    fn name(&self) -> &'static str {
202        "stdin"
203    }
204
205    fn read<'tcx>(
206        self: FileDescriptionRef<Self>,
207        communicate_allowed: bool,
208        ptr: Pointer,
209        len: usize,
210        ecx: &mut MiriInterpCx<'tcx>,
211        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
212    ) -> InterpResult<'tcx> {
213        if !communicate_allowed {
214            // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
215            helpers::isolation_abort_error("`read` from stdin")?;
216        }
217
218        let result = ecx.read_from_host(&*self, len, ptr)?;
219        finish.call(ecx, result)
220    }
221
222    fn is_tty(&self, communicate_allowed: bool) -> bool {
223        communicate_allowed && self.is_terminal()
224    }
225}
226
227impl FileDescription for io::Stdout {
228    fn name(&self) -> &'static str {
229        "stdout"
230    }
231
232    fn write<'tcx>(
233        self: FileDescriptionRef<Self>,
234        _communicate_allowed: bool,
235        ptr: Pointer,
236        len: usize,
237        ecx: &mut MiriInterpCx<'tcx>,
238        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
239    ) -> InterpResult<'tcx> {
240        // We allow writing to stdout even with isolation enabled.
241        let result = ecx.write_to_host(&*self, len, ptr)?;
242        // Stdout is buffered, flush to make sure it appears on the
243        // screen.  This is the write() syscall of the interpreted
244        // program, we want it to correspond to a write() syscall on
245        // the host -- there is no good in adding extra buffering
246        // here.
247        io::stdout().flush().unwrap();
248
249        finish.call(ecx, result)
250    }
251
252    fn is_tty(&self, communicate_allowed: bool) -> bool {
253        communicate_allowed && self.is_terminal()
254    }
255}
256
257impl FileDescription for io::Stderr {
258    fn name(&self) -> &'static str {
259        "stderr"
260    }
261
262    fn write<'tcx>(
263        self: FileDescriptionRef<Self>,
264        _communicate_allowed: bool,
265        ptr: Pointer,
266        len: usize,
267        ecx: &mut MiriInterpCx<'tcx>,
268        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
269    ) -> InterpResult<'tcx> {
270        // We allow writing to stderr even with isolation enabled.
271        let result = ecx.write_to_host(&*self, len, ptr)?;
272        // No need to flush, stderr is not buffered.
273        finish.call(ecx, result)
274    }
275
276    fn is_tty(&self, communicate_allowed: bool) -> bool {
277        communicate_allowed && self.is_terminal()
278    }
279}
280
281/// Like /dev/null
282#[derive(Debug)]
283pub struct NullOutput;
284
285impl FileDescription for NullOutput {
286    fn name(&self) -> &'static str {
287        "stderr and stdout"
288    }
289
290    fn write<'tcx>(
291        self: FileDescriptionRef<Self>,
292        _communicate_allowed: bool,
293        _ptr: Pointer,
294        len: usize,
295        ecx: &mut MiriInterpCx<'tcx>,
296        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
297    ) -> InterpResult<'tcx> {
298        // We just don't write anything, but report to the user that we did.
299        finish.call(ecx, Ok(len))
300    }
301}
302
303/// The file descriptor table
304#[derive(Debug)]
305pub struct FdTable {
306    pub fds: BTreeMap<i32, DynFileDescriptionRef>,
307    /// Unique identifier for file description, used to differentiate between various file description.
308    next_file_description_id: FdId,
309}
310
311impl VisitProvenance for FdTable {
312    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
313        // All our FileDescription instances do not have any tags.
314    }
315}
316
317impl FdTable {
318    fn new() -> Self {
319        FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
320    }
321    pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
322        let mut fds = FdTable::new();
323        fds.insert_new(io::stdin());
324        if mute_stdout_stderr {
325            assert_eq!(fds.insert_new(NullOutput), 1);
326            assert_eq!(fds.insert_new(NullOutput), 2);
327        } else {
328            assert_eq!(fds.insert_new(io::stdout()), 1);
329            assert_eq!(fds.insert_new(io::stderr()), 2);
330        }
331        fds
332    }
333
334    pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
335        let file_handle =
336            FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
337        self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
338        file_handle
339    }
340
341    /// Insert a new file description to the FdTable.
342    pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
343        let fd_ref = self.new_ref(fd);
344        self.insert(fd_ref)
345    }
346
347    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
348        self.insert_with_min_num(fd_ref, 0)
349    }
350
351    /// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
352    pub fn insert_with_min_num(
353        &mut self,
354        file_handle: DynFileDescriptionRef,
355        min_fd_num: i32,
356    ) -> i32 {
357        // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
358        // between used FDs, the find_map combinator will return it. If the first such unused FD
359        // is after all other used FDs, the find_map combinator will return None, and we will use
360        // the FD following the greatest FD thus far.
361        let candidate_new_fd =
362            self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
363                if *fd_num != counter {
364                    // There was a gap in the fds stored, return the first unused one
365                    // (note that this relies on BTreeMap iterating in key order)
366                    Some(counter)
367                } else {
368                    // This fd is used, keep going
369                    None
370                }
371            });
372        let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
373            // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
374            // maximum fd in the map
375            self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
376        });
377
378        self.fds.try_insert(new_fd_num, file_handle).unwrap();
379        new_fd_num
380    }
381
382    pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
383        let fd = self.fds.get(&fd_num)?;
384        Some(fd.clone())
385    }
386
387    pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
388        self.fds.remove(&fd_num)
389    }
390
391    pub fn is_fd_num(&self, fd_num: i32) -> bool {
392        self.fds.contains_key(&fd_num)
393    }
394}
395
396impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
397pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398    /// Read data from a host `Read` type, store the result into machine memory,
399    /// and return whether that worked.
400    fn read_from_host(
401        &mut self,
402        mut file: impl io::Read,
403        len: usize,
404        ptr: Pointer,
405    ) -> InterpResult<'tcx, Result<usize, IoError>> {
406        let this = self.eval_context_mut();
407
408        let mut bytes = vec![0; len];
409        let result = file.read(&mut bytes);
410        match result {
411            Ok(read_size) => {
412                // If reading to `bytes` did not fail, we write those bytes to the buffer.
413                // Crucially, if fewer than `bytes.len()` bytes were read, only write
414                // that much into the output buffer!
415                this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
416                interp_ok(Ok(read_size))
417            }
418            Err(e) => interp_ok(Err(IoError::HostError(e))),
419        }
420    }
421
422    /// Write data to a host `Write` type, withthe bytes taken from machine memory.
423    fn write_to_host(
424        &mut self,
425        mut file: impl io::Write,
426        len: usize,
427        ptr: Pointer,
428    ) -> InterpResult<'tcx, Result<usize, IoError>> {
429        let this = self.eval_context_mut();
430
431        let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
432        let result = file.write(bytes);
433        interp_ok(result.map_err(IoError::HostError))
434    }
435}