miri/shims/
files.rs

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