Skip to main content

miri/shims/
files.rs

1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{Dir, File};
4use std::io::{ErrorKind, IsTerminal, Read, 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 which is the same
18/// for all `dup`licates and is never reused.
19#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
20pub struct FdId(usize);
21
22impl FdId {
23    pub fn to_usize(self) -> usize {
24        self.0
25    }
26
27    /// Create a new fd id from a `usize` without checking if this fd exists.
28    pub fn new_unchecked(id: usize) -> Self {
29        Self(id)
30    }
31}
32
33#[derive(Debug, Clone)]
34struct FdIdWith<T: ?Sized> {
35    id: FdId,
36    inner: T,
37}
38
39/// A refcounted pointer to a file description, also tracking the
40/// globally unique ID of this file description.
41#[repr(transparent)]
42#[derive(CoercePointee, Debug)]
43// Sadly `CoercePointee` does not let us keep the `FdId` *outside* the `Rc`.
44pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
45
46impl<T: ?Sized> Clone for FileDescriptionRef<T> {
47    fn clone(&self) -> Self {
48        FileDescriptionRef(self.0.clone())
49    }
50}
51
52impl<T: ?Sized> Deref for FileDescriptionRef<T> {
53    type Target = T;
54    fn deref(&self) -> &T {
55        &self.0.inner
56    }
57}
58
59impl<T: ?Sized> FileDescriptionRef<T> {
60    pub fn id(&self) -> FdId {
61        self.0.id
62    }
63}
64
65impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
66    fn eq(&self, other: &Self) -> bool {
67        self.0.id == other.0.id
68    }
69}
70
71impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
72
73/// Holds a weak reference to the actual file description.
74#[derive(Debug)]
75pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
76
77impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
78    fn clone(&self) -> Self {
79        WeakFileDescriptionRef(self.0.clone())
80    }
81}
82
83impl<T: ?Sized> FileDescriptionRef<T> {
84    pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
85        WeakFileDescriptionRef(Rc::downgrade(&this.0))
86    }
87}
88
89impl<T: ?Sized> WeakFileDescriptionRef<T> {
90    pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
91        self.0.upgrade().map(FileDescriptionRef)
92    }
93
94    /// Returns whether the file description that this weak reference points to
95    /// has been closed, i.e., there are no more strong references.
96    pub fn is_closed(&self) -> bool {
97        self.0.strong_count() == 0
98    }
99}
100
101impl<T> VisitProvenance for FileDescriptionRef<T> {
102    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
103        // All our FileDescription instances do not have any provenance.
104    }
105}
106
107/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
108/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
109/// but that does not allow upcasting.
110pub trait FileDescriptionExt: 'static {
111    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
112}
113
114impl<T: FileDescription + 'static> FileDescriptionExt for T {
115    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
116        self.0
117    }
118}
119
120pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
121pub type WeakDynFileDescriptionRef = WeakFileDescriptionRef<dyn FileDescription>;
122
123impl FileDescriptionRef<dyn FileDescription> {
124    pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
125        let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
126        Some(FileDescriptionRef(inner))
127    }
128}
129
130/// Represents an open file description.
131pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
132    fn name(&self) -> &'static str;
133
134    /// Reads as much as possible into the given buffer `ptr`.
135    /// `len` indicates how many bytes we should try to read.
136    ///
137    /// When the read is done, `finish` will be called. Note that `read` itself may return before
138    /// that happens! Everything that should happen "after" the `read` needs to happen inside
139    /// `finish`.
140    fn read<'tcx>(
141        self: FileDescriptionRef<Self>,
142        _communicate_allowed: bool,
143        _ptr: Pointer,
144        _len: usize,
145        _ecx: &mut MiriInterpCx<'tcx>,
146        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
147    ) -> InterpResult<'tcx> {
148        throw_unsup_format!("cannot read from {}", self.name());
149    }
150
151    /// Writes as much as possible from the given buffer `ptr`.
152    /// `len` indicates how many bytes we should try to write.
153    ///
154    /// When the write is done, `finish` will be called. Note that `write` itself may return before
155    /// that happens! Everything that should happen "after" the `write` needs to happen inside
156    /// `finish`.
157    fn write<'tcx>(
158        self: FileDescriptionRef<Self>,
159        _communicate_allowed: bool,
160        _ptr: Pointer,
161        _len: usize,
162        _ecx: &mut MiriInterpCx<'tcx>,
163        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
164    ) -> InterpResult<'tcx> {
165        throw_unsup_format!("cannot write to {}", self.name());
166    }
167
168    /// Determines whether this FD non-deterministically has its reads and writes shortened.
169    fn short_fd_operations(&self) -> bool {
170        // We only enable this for FD kinds where we think short accesses gain useful test coverage.
171        false
172    }
173
174    /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
175    /// Returns the new position from the start of the stream.
176    fn seek<'tcx>(
177        &self,
178        _communicate_allowed: bool,
179        _offset: SeekFrom,
180    ) -> InterpResult<'tcx, io::Result<u64>> {
181        throw_unsup_format!("cannot seek on {}", self.name());
182    }
183
184    /// Returns the metadata for this FD, if available.
185    /// This is either host metadata, or a non-file-backed-FD type.
186    /// The latter is for new represented as a string storing a `libc` name so we only
187    /// support that kind of metadata on Unix targets.
188    fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
189        throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
190    }
191
192    fn is_tty(&self, _communicate_allowed: bool) -> bool {
193        // Most FDs are not tty's and the consequence of a wrong `false` are minor,
194        // so we use a default impl here.
195        false
196    }
197
198    fn as_unix<'tcx>(
199        self: FileDescriptionRef<Self>,
200        _ecx: &MiriInterpCx<'tcx>,
201    ) -> FileDescriptionRef<dyn UnixFileDescription> {
202        panic!("Not a unix file descriptor: {}", self.name());
203    }
204
205    /// Implementation of fcntl(F_GETFL) for this FD.
206    fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
207        throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
208    }
209
210    /// Implementation of fcntl(F_SETFL) for this FD.
211    fn set_flags<'tcx>(
212        &self,
213        _flag: i32,
214        _ecx: &mut MiriInterpCx<'tcx>,
215    ) -> InterpResult<'tcx, Scalar> {
216        throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
217    }
218
219    /// Get the `ReadinessWatched` of the file description.
220    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
221        None
222    }
223
224    /// Get the current I/O readiness of the file description.
225    fn readiness(&self) -> Readiness {
226        panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
227    }
228}
229
230#[derive(Debug)]
231struct Stdin {
232    stdin: io::Stdin,
233    watched: ReadinessWatched,
234}
235
236impl Stdin {
237    fn new() -> Self {
238        Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
239    }
240}
241
242impl FileDescription for Stdin {
243    fn name(&self) -> &'static str {
244        "stdin"
245    }
246
247    fn read<'tcx>(
248        self: FileDescriptionRef<Self>,
249        communicate_allowed: bool,
250        ptr: Pointer,
251        len: usize,
252        ecx: &mut MiriInterpCx<'tcx>,
253        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
254    ) -> InterpResult<'tcx> {
255        if !communicate_allowed {
256            // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
257            helpers::isolation_abort_error("`read` from stdin")?;
258        }
259
260        // FIXME: this can block on the host, halting the entire interpreter.
261        let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?;
262        finish.call(ecx, result)
263    }
264
265    fn is_tty(&self, communicate_allowed: bool) -> bool {
266        communicate_allowed && self.stdin.is_terminal()
267    }
268
269    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
270        Some(&self.watched)
271    }
272
273    fn readiness(&self) -> Readiness {
274        // Stdin is readable (we never return EWOULDBLOCK above) and also writable (since that never
275        // blocks either). This matches what we see on Linux.
276        let mut readiness = Readiness::EMPTY;
277        readiness.readable = true;
278        readiness.writable = true;
279        readiness
280    }
281}
282
283#[derive(Debug)]
284struct Stdout {
285    stdout: io::Stdout,
286    watched: ReadinessWatched,
287}
288
289impl Stdout {
290    fn new() -> Self {
291        Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
292    }
293}
294
295impl FileDescription for Stdout {
296    fn name(&self) -> &'static str {
297        "stdout"
298    }
299
300    fn write<'tcx>(
301        self: FileDescriptionRef<Self>,
302        _communicate_allowed: bool,
303        ptr: Pointer,
304        len: usize,
305        ecx: &mut MiriInterpCx<'tcx>,
306        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
307    ) -> InterpResult<'tcx> {
308        // We allow writing to stdout even with isolation enabled.
309        let result = ecx.write_to_host(&self.stdout, len, ptr)?;
310        // Stdout is buffered, flush to make sure it appears on the
311        // screen.  This is the write() syscall of the interpreted
312        // program, we want it to correspond to a write() syscall on
313        // the host -- there is no good in adding extra buffering
314        // here.
315        io::stdout().flush().unwrap();
316
317        finish.call(ecx, result)
318    }
319
320    fn is_tty(&self, communicate_allowed: bool) -> bool {
321        communicate_allowed && self.stdout.is_terminal()
322    }
323
324    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
325        Some(&self.watched)
326    }
327
328    fn readiness(&self) -> Readiness {
329        // stdout can always be written (we never return EWOULDBLOCK there) and never be read.
330        let mut readiness = Readiness::EMPTY;
331        readiness.writable = true;
332        readiness
333    }
334}
335
336#[derive(Debug)]
337struct Stderr {
338    stderr: io::Stderr,
339    watched: ReadinessWatched,
340}
341
342impl Stderr {
343    fn new() -> Self {
344        Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
345    }
346}
347
348impl FileDescription for Stderr {
349    fn name(&self) -> &'static str {
350        "stderr"
351    }
352
353    fn write<'tcx>(
354        self: FileDescriptionRef<Self>,
355        _communicate_allowed: bool,
356        ptr: Pointer,
357        len: usize,
358        ecx: &mut MiriInterpCx<'tcx>,
359        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
360    ) -> InterpResult<'tcx> {
361        // We allow writing to stderr even with isolation enabled.
362        let result = ecx.write_to_host(&self.stderr, len, ptr)?;
363        // No need to flush, stderr is not buffered.
364        finish.call(ecx, result)
365    }
366
367    fn is_tty(&self, communicate_allowed: bool) -> bool {
368        communicate_allowed && self.stderr.is_terminal()
369    }
370
371    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
372        Some(&self.watched)
373    }
374
375    fn readiness(&self) -> Readiness {
376        // stderr can always be written (we never return EWOULDBLOCK there) and never be read.
377        let mut readiness = Readiness::EMPTY;
378        readiness.writable = true;
379        readiness
380    }
381}
382
383/// Like /dev/null
384#[derive(Debug)]
385pub struct NullOutput {
386    watched: ReadinessWatched,
387}
388
389impl NullOutput {
390    fn new() -> Self {
391        Self { watched: ReadinessWatched::default() }
392    }
393}
394
395impl FileDescription for NullOutput {
396    fn name(&self) -> &'static str {
397        "null output"
398    }
399
400    fn write<'tcx>(
401        self: FileDescriptionRef<Self>,
402        _communicate_allowed: bool,
403        _ptr: Pointer,
404        len: usize,
405        ecx: &mut MiriInterpCx<'tcx>,
406        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
407    ) -> InterpResult<'tcx> {
408        // We just don't write anything, but report to the user that we did.
409        finish.call(ecx, Ok(len))
410    }
411
412    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
413        Some(&self.watched)
414    }
415
416    fn readiness(&self) -> Readiness {
417        // null output can always be written (we never return EWOULDBLOCK there) and never be read.
418        let mut readiness = Readiness::EMPTY;
419        readiness.writable = true;
420        readiness
421    }
422}
423
424#[derive(Debug)]
425pub struct FileHandle {
426    pub(crate) file: File,
427    pub(crate) readable: bool,
428    pub(crate) writable: bool,
429}
430
431impl FileDescription for FileHandle {
432    fn name(&self) -> &'static str {
433        "file"
434    }
435
436    fn read<'tcx>(
437        self: FileDescriptionRef<Self>,
438        communicate_allowed: bool,
439        ptr: Pointer,
440        len: usize,
441        ecx: &mut MiriInterpCx<'tcx>,
442        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
443    ) -> InterpResult<'tcx> {
444        assert!(communicate_allowed, "isolation should have prevented even opening a file");
445
446        if !self.readable {
447            return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
448        }
449
450        let mut file = &self.file;
451        let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
452        finish.call(ecx, result)
453    }
454
455    fn write<'tcx>(
456        self: FileDescriptionRef<Self>,
457        communicate_allowed: bool,
458        ptr: Pointer,
459        len: usize,
460        ecx: &mut MiriInterpCx<'tcx>,
461        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
462    ) -> InterpResult<'tcx> {
463        assert!(communicate_allowed, "isolation should have prevented even opening a file");
464
465        if !self.writable {
466            // Linux hosts return EBADF here which we can't translate via the platform-independent
467            // code since it does not map to any `io::ErrorKind` -- so if we don't do anything
468            // special, we'd throw an "unsupported error code" here. Windows returns something that
469            // gets translated to `PermissionDenied`. That seems like a good value so let's just use
470            // this everywhere, even if it means behavior on Unix targets does not match the real
471            // thing.
472            return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
473        }
474        let result = ecx.write_to_host(&self.file, len, ptr)?;
475        finish.call(ecx, result)
476    }
477
478    fn seek<'tcx>(
479        &self,
480        communicate_allowed: bool,
481        offset: SeekFrom,
482    ) -> InterpResult<'tcx, io::Result<u64>> {
483        assert!(communicate_allowed, "isolation should have prevented even opening a file");
484        interp_ok((&mut &self.file).seek(offset))
485    }
486
487    fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
488        interp_ok(Either::Left(self.file.metadata()))
489    }
490
491    fn is_tty(&self, communicate_allowed: bool) -> bool {
492        communicate_allowed && self.file.is_terminal()
493    }
494
495    fn short_fd_operations(&self) -> bool {
496        // While short accesses on file-backed FDs are very rare (at least for sufficiently small
497        // accesses), they can realistically happen when a signal interrupts the syscall.
498        // FIXME: we should return `false` if this is a named pipe...
499        true
500    }
501
502    fn as_unix<'tcx>(
503        self: FileDescriptionRef<Self>,
504        ecx: &MiriInterpCx<'tcx>,
505    ) -> FileDescriptionRef<dyn UnixFileDescription> {
506        assert!(
507            ecx.target_os_is_unix(),
508            "unix file operations are only available for unix targets"
509        );
510        self
511    }
512}
513
514#[derive(Debug)]
515pub struct DirHandle {
516    pub(crate) dir: Dir,
517}
518
519impl FileDescription for DirHandle {
520    fn name(&self) -> &'static str {
521        "directory"
522    }
523
524    fn metadata<'tcx>(
525        &self,
526    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
527        interp_ok(Either::Left(self.dir.metadata()))
528    }
529}
530
531/// Internal type of a file-descriptor - this is what [`FdTable`] expects
532pub type FdNum = i32;
533
534/// The file descriptor table
535#[derive(Debug)]
536pub struct FdTable {
537    fds: BTreeMap<FdNum, DynFileDescriptionRef>,
538    /// Unique identifier for file description, used to differentiate between various file description.
539    next_file_description_id: FdId,
540}
541
542impl VisitProvenance for FdTable {
543    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
544        // All our FileDescription instances do not have any provenance.
545    }
546}
547
548impl FdTable {
549    fn new() -> Self {
550        FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
551    }
552    pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
553        let mut fds = FdTable::new();
554        fds.insert_new(Stdin::new());
555        if mute_stdout_stderr {
556            assert_eq!(fds.insert_new(NullOutput::new()), 1);
557            assert_eq!(fds.insert_new(NullOutput::new()), 2);
558        } else {
559            assert_eq!(fds.insert_new(Stdout::new()), 1);
560            assert_eq!(fds.insert_new(Stderr::new()), 2);
561        }
562        fds
563    }
564
565    pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
566        let file_handle =
567            FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
568        self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
569        file_handle
570    }
571
572    /// Insert a new file description to the FdTable.
573    pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
574        let fd_ref = self.new_ref(fd);
575        self.insert(fd_ref)
576    }
577
578    /// Insert an alias to an existing file description to the FdTable.
579    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
580        self.insert_with_min_num(fd_ref, 0)
581    }
582
583    /// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
584    pub fn insert_with_min_num(
585        &mut self,
586        file_handle: DynFileDescriptionRef,
587        min_fd_num: FdNum,
588    ) -> FdNum {
589        let mut candidate = min_fd_num;
590        for (&fd_num, _) in self.fds.range(min_fd_num..) {
591            if fd_num == candidate {
592                // This one is taken. Try the next one.
593                candidate = candidate.strict_add(1);
594            } else {
595                // We found a gap! Use this candidate.
596                break;
597            }
598        }
599        // If we exhaust the loop, the table is a solid block starting at `min_fd_num` until the
600        // end, and `candidate` is now the first number after that block -- exactly what we need.
601
602        self.fds.try_insert(candidate, file_handle).unwrap();
603        candidate
604    }
605
606    pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
607        let fd = self.fds.get(&fd_num)?;
608        Some(fd.clone())
609    }
610
611    pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
612        self.fds.remove(&fd_num)
613    }
614
615    pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
616        self.fds.contains_key(&fd_num)
617    }
618}
619
620impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
621pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
622    /// Read data from a host `Read` type, store the result into machine memory,
623    /// and return whether that worked.
624    fn read_from_host(
625        &mut self,
626        mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
627        len: usize,
628        ptr: Pointer,
629    ) -> InterpResult<'tcx, Result<usize, IoError>> {
630        let this = self.eval_context_mut();
631
632        let mut bytes = vec![0; len];
633        let result = read_cb(&mut bytes);
634        match result {
635            Ok(read_size) => {
636                // If reading to `bytes` did not fail, we write those bytes to the buffer.
637                // Crucially, if fewer than `bytes.len()` bytes were read, only write
638                // that much into the output buffer!
639                this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
640                interp_ok(Ok(read_size))
641            }
642            Err(e) => interp_ok(Err(IoError::HostError(e))),
643        }
644    }
645
646    /// Write data to a host `Write` type, with the bytes taken from machine memory.
647    fn write_to_host(
648        &mut self,
649        mut file: impl io::Write,
650        len: usize,
651        ptr: Pointer,
652    ) -> InterpResult<'tcx, Result<usize, IoError>> {
653        let this = self.eval_context_mut();
654
655        let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
656        let result = file.write(bytes);
657        interp_ok(result.map_err(IoError::HostError))
658    }
659}