Skip to main content

miri/shims/
files.rs

1use std::any::Any;
2use std::collections::BTreeMap;
3use std::convert::BoundedCastFromInt;
4use std::fs::{Dir, File};
5use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
6use std::marker::CoercePointee;
7use std::ops::Deref;
8use std::rc::{Rc, Weak};
9use std::{fs, io};
10
11use rustc_abi::Size;
12
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16/// Internal type of a file-descriptor - this is what [`FdTable`] expects
17pub type FdNum = i32;
18
19/// The maximum number of open FDs.
20pub const FD_LIMIT: FdNum = 524288; // default hard limit on Debian
21
22/// Accesses this large have a higher chance of being shortened.
23/// We use twice the usual page size, which means the shortened access is at least a page.
24pub const BIG_ACCESS: u64 = 8 * 1024;
25
26/// A unique id for file descriptions. While we could use the address, considering that
27/// is definitely unique, the address would expose interpreter internal state when used
28/// for sorting things. So instead we generate a unique id per file description which is the same
29/// for all `dup`licates and is never reused.
30#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
31pub struct FdId(usize);
32
33impl FdId {
34    pub fn to_usize(self) -> usize {
35        self.0
36    }
37
38    /// Create a new fd id from a `usize` without checking if this fd exists.
39    pub fn new_unchecked(id: usize) -> Self {
40        Self(id)
41    }
42}
43
44#[derive(Debug, Clone)]
45struct FdIdWith<T: ?Sized> {
46    id: FdId,
47    inner: T,
48}
49
50/// A refcounted pointer to a file description, also tracking the
51/// globally unique ID of this file description.
52#[repr(transparent)]
53#[derive(CoercePointee, Debug)]
54// Sadly `CoercePointee` does not let us keep the `FdId` *outside* the `Rc`.
55pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
56
57impl<T: ?Sized> Clone for FileDescriptionRef<T> {
58    fn clone(&self) -> Self {
59        FileDescriptionRef(self.0.clone())
60    }
61}
62
63impl<T: ?Sized> Deref for FileDescriptionRef<T> {
64    type Target = T;
65    fn deref(&self) -> &T {
66        &self.0.inner
67    }
68}
69
70impl<T: ?Sized> FileDescriptionRef<T> {
71    pub fn id(&self) -> FdId {
72        self.0.id
73    }
74}
75
76impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
77    fn eq(&self, other: &Self) -> bool {
78        self.0.id == other.0.id
79    }
80}
81
82impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
83
84/// Holds a weak reference to the actual file description.
85#[derive(Debug)]
86pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
87
88impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
89    fn clone(&self) -> Self {
90        WeakFileDescriptionRef(self.0.clone())
91    }
92}
93
94impl<T: ?Sized> FileDescriptionRef<T> {
95    pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
96        WeakFileDescriptionRef(Rc::downgrade(&this.0))
97    }
98}
99
100impl<T: ?Sized> WeakFileDescriptionRef<T> {
101    pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
102        self.0.upgrade().map(FileDescriptionRef)
103    }
104
105    /// Returns whether the file description that this weak reference points to
106    /// has been closed, i.e., there are no more strong references.
107    pub fn is_closed(&self) -> bool {
108        self.0.strong_count() == 0
109    }
110}
111
112impl<T> VisitProvenance for FileDescriptionRef<T> {
113    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
114        // All our FileDescription instances do not have any provenance.
115    }
116}
117
118/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
119/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
120/// but that does not allow upcasting.
121pub trait FileDescriptionExt: 'static {
122    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
123}
124
125impl<T: FileDescription + 'static> FileDescriptionExt for T {
126    fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
127        self.0
128    }
129}
130
131pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
132pub type WeakDynFileDescriptionRef = WeakFileDescriptionRef<dyn FileDescription>;
133
134impl FileDescriptionRef<dyn FileDescription> {
135    pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
136        let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
137        Some(FileDescriptionRef(inner))
138    }
139}
140
141/// Represents an open file description.
142pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
143    fn name(&self) -> &'static str;
144
145    /// Reads as much as possible into the given buffer `ptr`.
146    /// `len` indicates how many bytes we should try to read.
147    ///
148    /// When the read is done, `finish` will be called. Note that `read` itself may return before
149    /// that happens! Everything that should happen "after" the `read` needs to happen inside
150    /// `finish`.
151    fn read<'tcx>(
152        self: FileDescriptionRef<Self>,
153        _communicate_allowed: bool,
154        _ptr: Pointer,
155        _len: u64,
156        _ecx: &mut MiriInterpCx<'tcx>,
157        _finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
158    ) -> InterpResult<'tcx> {
159        throw_unsup_format!("cannot read from {}", self.name());
160    }
161
162    /// Writes as much as possible from the given buffer `ptr`.
163    /// `len` indicates how many bytes we should try to write.
164    ///
165    /// When the write is done, `finish` will be called. Note that `write` itself may return before
166    /// that happens! Everything that should happen "after" the `write` needs to happen inside
167    /// `finish`.
168    fn write<'tcx>(
169        self: FileDescriptionRef<Self>,
170        _communicate_allowed: bool,
171        _ptr: Pointer,
172        _len: u64,
173        _ecx: &mut MiriInterpCx<'tcx>,
174        _finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
175    ) -> InterpResult<'tcx> {
176        throw_unsup_format!("cannot write to {}", self.name());
177    }
178
179    /// Determines whether this FD non-deterministically has its reads and writes shortened.
180    fn short_fd_operations(&self) -> bool {
181        // We only enable this for FD kinds where we think short accesses gain useful test coverage.
182        false
183    }
184
185    /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
186    /// Returns the new position from the start of the stream.
187    fn seek<'tcx>(
188        &self,
189        _communicate_allowed: bool,
190        _offset: SeekFrom,
191    ) -> InterpResult<'tcx, io::Result<u64>> {
192        throw_unsup_format!("cannot seek on {}", self.name());
193    }
194
195    /// Returns the metadata for this FD, if available.
196    /// This is either host metadata, or a non-file-backed-FD type.
197    /// The latter is for now represented as a string storing the `libc` name of the mode so we only
198    /// support that kind of metadata on Unix targets.
199    fn metadata<'tcx>(
200        &self,
201        _communicate_allowed: bool,
202    ) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
203        throw_unsup_format!("obtaining metadata is not supported on {}", self.name());
204    }
205
206    fn is_tty(&self, _communicate_allowed: bool) -> bool {
207        // Most FDs are not tty's and the consequence of a wrong `false` are minor,
208        // so we use a default impl here.
209        false
210    }
211
212    /// Converts this FD into a unix file description. Must succeed for all FD types that can be
213    /// created on Unix targets!
214    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
215        panic!("Not a unix file description: {}", self.name());
216    }
217
218    /// Get the `ReadinessWatched` of the file description.
219    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
220        None
221    }
222
223    /// Get the current I/O readiness of the file description.
224    fn readiness(&self) -> Readiness {
225        panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
226    }
227}
228
229#[derive(Debug)]
230pub struct Stdin {
231    stdin: io::Stdin,
232    watched: ReadinessWatched,
233}
234
235impl Stdin {
236    fn new() -> Self {
237        Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
238    }
239}
240
241impl FileDescription for Stdin {
242    fn name(&self) -> &'static str {
243        "stdin"
244    }
245
246    fn read<'tcx>(
247        self: FileDescriptionRef<Self>,
248        communicate_allowed: bool,
249        ptr: Pointer,
250        len: u64,
251        ecx: &mut MiriInterpCx<'tcx>,
252        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
253    ) -> InterpResult<'tcx> {
254        if !communicate_allowed {
255            // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
256            helpers::isolation_abort_error("`read` from stdin")?;
257        }
258
259        // FIXME: this can block on the host, halting the entire interpreter.
260        let result = ecx.read_from_host(ptr, len, |buf| (&mut &self.stdin).read(buf))?;
261        finish.call(ecx, result)
262    }
263
264    fn metadata<'tcx>(
265        &self,
266        communicate_allowed: bool,
267    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
268        let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
269        interp_ok(Either::Right(ftype))
270    }
271
272    fn is_tty(&self, communicate_allowed: bool) -> bool {
273        // Without communication we assume that it is a terminal.
274        !communicate_allowed || self.stdin.is_terminal()
275    }
276
277    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
278        Some(&self.watched)
279    }
280
281    fn readiness(&self) -> Readiness {
282        // Stdin is readable (we never return EWOULDBLOCK above) and also writable (since that never
283        // blocks either). This matches what we see on Linux.
284        let mut readiness = Readiness::EMPTY;
285        readiness.readable = true;
286        readiness.writable = true;
287        readiness
288    }
289
290    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
291        self
292    }
293}
294
295#[derive(Debug)]
296pub struct Stdout {
297    stdout: io::Stdout,
298    watched: ReadinessWatched,
299}
300
301impl Stdout {
302    fn new() -> Self {
303        Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
304    }
305}
306
307impl FileDescription for Stdout {
308    fn name(&self) -> &'static str {
309        "stdout"
310    }
311
312    fn write<'tcx>(
313        self: FileDescriptionRef<Self>,
314        _communicate_allowed: bool,
315        ptr: Pointer,
316        len: u64,
317        ecx: &mut MiriInterpCx<'tcx>,
318        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
319    ) -> InterpResult<'tcx> {
320        // We allow writing to stdout even with isolation enabled.
321        let result = ecx.write_to_host(ptr, len, |buf| (&self.stdout).write(buf))?;
322        // Stdout is buffered, flush to make sure it appears on the
323        // screen.  This is the write() syscall of the interpreted
324        // program, we want it to correspond to a write() syscall on
325        // the host -- there is no good in adding extra buffering
326        // here.
327        io::stdout().flush().unwrap();
328
329        finish.call(ecx, result)
330    }
331
332    fn metadata<'tcx>(
333        &self,
334        communicate_allowed: bool,
335    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
336        let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
337        interp_ok(Either::Right(ftype))
338    }
339
340    fn is_tty(&self, communicate_allowed: bool) -> bool {
341        // Without communication we assume that it is a terminal.
342        !communicate_allowed || self.stdout.is_terminal()
343    }
344
345    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
346        Some(&self.watched)
347    }
348
349    fn readiness(&self) -> Readiness {
350        // stdout can always be written (we never return EWOULDBLOCK there) and never be read.
351        let mut readiness = Readiness::EMPTY;
352        readiness.writable = true;
353        readiness
354    }
355
356    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
357        self
358    }
359}
360
361#[derive(Debug)]
362pub struct Stderr {
363    stderr: io::Stderr,
364    watched: ReadinessWatched,
365}
366
367impl Stderr {
368    fn new() -> Self {
369        Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
370    }
371}
372
373impl FileDescription for Stderr {
374    fn name(&self) -> &'static str {
375        "stderr"
376    }
377
378    fn write<'tcx>(
379        self: FileDescriptionRef<Self>,
380        _communicate_allowed: bool,
381        ptr: Pointer,
382        len: u64,
383        ecx: &mut MiriInterpCx<'tcx>,
384        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
385    ) -> InterpResult<'tcx> {
386        // We allow writing to stderr even with isolation enabled.
387        let result = ecx.write_to_host(ptr, len, |buf| (&self.stderr).write(buf))?;
388        // No need to flush, stderr is not buffered.
389        finish.call(ecx, result)
390    }
391
392    fn metadata<'tcx>(
393        &self,
394        communicate_allowed: bool,
395    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
396        let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
397        interp_ok(Either::Right(ftype))
398    }
399
400    fn is_tty(&self, communicate_allowed: bool) -> bool {
401        // Without communication we assume that it is a terminal.
402        !communicate_allowed || self.stderr.is_terminal()
403    }
404
405    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
406        Some(&self.watched)
407    }
408
409    fn readiness(&self) -> Readiness {
410        // stderr can always be written (we never return EWOULDBLOCK there) and never be read.
411        let mut readiness = Readiness::EMPTY;
412        readiness.writable = true;
413        readiness
414    }
415
416    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
417        self
418    }
419}
420
421/// Like /dev/null
422#[derive(Debug)]
423pub struct NullOutput {
424    watched: ReadinessWatched,
425}
426
427impl NullOutput {
428    fn new() -> Self {
429        Self { watched: ReadinessWatched::default() }
430    }
431}
432
433impl FileDescription for NullOutput {
434    fn name(&self) -> &'static str {
435        "null output"
436    }
437
438    fn write<'tcx>(
439        self: FileDescriptionRef<Self>,
440        _communicate_allowed: bool,
441        _ptr: Pointer,
442        len: u64,
443        ecx: &mut MiriInterpCx<'tcx>,
444        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
445    ) -> InterpResult<'tcx> {
446        // We just don't write anything, but report to the user that we did.
447        finish.call(ecx, Ok(len))
448    }
449
450    fn metadata<'tcx>(
451        &self,
452        _communicate_allowed: bool,
453    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
454        interp_ok(Either::Right("S_IFIFO"))
455    }
456
457    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
458        Some(&self.watched)
459    }
460
461    fn readiness(&self) -> Readiness {
462        // null output can always be written (we never return EWOULDBLOCK there) and never be read.
463        let mut readiness = Readiness::EMPTY;
464        readiness.writable = true;
465        readiness
466    }
467
468    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
469        self
470    }
471}
472
473#[derive(Debug)]
474pub struct FileHandle {
475    pub(crate) file: File,
476    pub(crate) readable: bool,
477    pub(crate) writable: bool,
478    pub(crate) append: bool,
479}
480
481impl FileDescription for FileHandle {
482    fn name(&self) -> &'static str {
483        "file"
484    }
485
486    fn read<'tcx>(
487        self: FileDescriptionRef<Self>,
488        communicate_allowed: bool,
489        ptr: Pointer,
490        len: u64,
491        ecx: &mut MiriInterpCx<'tcx>,
492        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
493    ) -> InterpResult<'tcx> {
494        assert!(communicate_allowed, "isolation should have prevented even opening a file");
495
496        if !self.readable {
497            // Unix returns EBADF, Windows something that translates to `PermissionDenied`.
498            let err = if ecx.target_os_is_unix() {
499                LibcError("EBADF")
500            } else {
501                ErrorKind::PermissionDenied.into()
502            };
503            return finish.call(ecx, Err(err));
504        }
505
506        let mut file = &self.file;
507        let result = ecx.read_from_host(ptr, len, |buf| file.read(buf))?;
508        finish.call(ecx, result)
509    }
510
511    fn write<'tcx>(
512        self: FileDescriptionRef<Self>,
513        communicate_allowed: bool,
514        ptr: Pointer,
515        len: u64,
516        ecx: &mut MiriInterpCx<'tcx>,
517        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
518    ) -> InterpResult<'tcx> {
519        assert!(communicate_allowed, "isolation should have prevented even opening a file");
520
521        if !self.writable {
522            // Unix returns EBADF, Windows something that translates to `PermissionDenied`.
523            let err = if ecx.target_os_is_unix() {
524                LibcError("EBADF")
525            } else {
526                ErrorKind::PermissionDenied.into()
527            };
528            return finish.call(ecx, Err(err));
529        }
530        let result = ecx.write_to_host(ptr, len, |buf| (&self.file).write(buf))?;
531        finish.call(ecx, result)
532    }
533
534    fn seek<'tcx>(
535        &self,
536        communicate_allowed: bool,
537        offset: SeekFrom,
538    ) -> InterpResult<'tcx, io::Result<u64>> {
539        assert!(communicate_allowed, "isolation should have prevented even opening a file");
540        interp_ok((&mut &self.file).seek(offset))
541    }
542
543    fn metadata<'tcx>(
544        &self,
545        communicate_allowed: bool,
546    ) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
547        assert!(communicate_allowed);
548        interp_ok(Either::Left(self.file.metadata()))
549    }
550
551    fn is_tty(&self, communicate_allowed: bool) -> bool {
552        assert!(communicate_allowed);
553        self.file.is_terminal()
554    }
555
556    fn short_fd_operations(&self) -> bool {
557        // While short accesses on file-backed FDs are very rare (at least for sufficiently small
558        // accesses), they can realistically happen when a signal interrupts the syscall.
559        // FIXME: we should return `false` if this is a named pipe...
560        true
561    }
562
563    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
564        self
565    }
566}
567
568#[derive(Debug)]
569pub struct DirHandle {
570    pub(super) dir: Dir,
571    #[cfg(bootstrap)]
572    pub(super) fallback: std::path::PathBuf,
573}
574
575impl FileDescription for DirHandle {
576    fn name(&self) -> &'static str {
577        "directory"
578    }
579
580    fn metadata<'tcx>(
581        &self,
582        communicate_allowed: bool,
583    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
584        assert!(communicate_allowed);
585
586        #[cfg(bootstrap)]
587        return interp_ok(Either::Left(self.dir.metadata()));
588        #[cfg(not(bootstrap))]
589        return interp_ok(Either::Left(self.dir.self_metadata()));
590    }
591
592    fn read<'tcx>(
593        self: FileDescriptionRef<Self>,
594        _communicate_allowed: bool,
595        _ptr: Pointer,
596        _len: u64,
597        ecx: &mut MiriInterpCx<'tcx>,
598        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
599    ) -> InterpResult<'tcx> {
600        if ecx.target_os_is_unix() {
601            finish.call(ecx, Err(LibcError("EISDIR")))
602        } else {
603            // No idea what this should do on Windows.
604            throw_unsup_format!("reading directories is not supported on this target");
605        }
606    }
607
608    fn write<'tcx>(
609        self: FileDescriptionRef<Self>,
610        _communicate_allowed: bool,
611        _ptr: Pointer,
612        _len: u64,
613        ecx: &mut MiriInterpCx<'tcx>,
614        finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
615    ) -> InterpResult<'tcx> {
616        if ecx.target_os_is_unix() {
617            // Directories are opened for reading, so writing returns EBADF.
618            finish.call(ecx, Err(LibcError("EBADF")))
619        } else {
620            // No idea what this should do on Windows.
621            throw_unsup_format!("writing directories is not supported on this target");
622        }
623    }
624
625    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
626        self
627    }
628}
629
630/// The file descriptor table
631#[derive(Debug)]
632pub struct FdTable {
633    fds: BTreeMap<FdNum, DynFileDescriptionRef>,
634    /// Unique identifier for file description, used to differentiate between various file description.
635    next_file_description_id: FdId,
636}
637
638impl VisitProvenance for FdTable {
639    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
640        // All our FileDescription instances do not have any provenance.
641    }
642}
643
644impl FdTable {
645    fn new() -> Self {
646        FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
647    }
648    pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
649        let mut fds = FdTable::new();
650        fds.insert_new(Stdin::new());
651        if mute_stdout_stderr {
652            assert_eq!(fds.insert_new(NullOutput::new()).unwrap(), 1);
653            assert_eq!(fds.insert_new(NullOutput::new()).unwrap(), 2);
654        } else {
655            assert_eq!(fds.insert_new(Stdout::new()).unwrap(), 1);
656            assert_eq!(fds.insert_new(Stderr::new()).unwrap(), 2);
657        }
658        fds
659    }
660
661    pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
662        let file_handle =
663            FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
664        self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
665        file_handle
666    }
667
668    pub fn fd_num_valid(fd_num: FdNum) -> bool {
669        (0..FD_LIMIT).contains(&fd_num)
670    }
671
672    /// Insert a new file description to the FdTable.
673    pub fn insert_new(&mut self, fd: impl FileDescription) -> Option<FdNum> {
674        let fd_ref = self.new_ref(fd);
675        self.insert(fd_ref)
676    }
677
678    /// Insert an alias to an existing file description to the FdTable.
679    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> Option<FdNum> {
680        self.insert_with_min_num(fd_ref, 0)
681    }
682
683    /// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
684    pub fn insert_with_min_num(
685        &mut self,
686        file_handle: DynFileDescriptionRef,
687        min_fd_num: FdNum,
688    ) -> Option<FdNum> {
689        let mut candidate = min_fd_num;
690        for (&fd_num, _) in self.fds.range(min_fd_num..) {
691            if fd_num == candidate {
692                // This one is taken. Try the next one.
693                candidate = candidate.strict_add(1);
694            } else {
695                // We found a gap! Use this candidate.
696                break;
697            }
698        }
699        // If we exhaust the loop, the table is a solid block starting at `min_fd_num` until the
700        // end, and `candidate` is now the first number after that block -- exactly what we need.
701
702        // Ensure the resulting FD number is valid.
703        if !Self::fd_num_valid(candidate) {
704            return None;
705        }
706
707        self.fds.try_insert(candidate, file_handle).unwrap();
708        Some(candidate)
709    }
710
711    pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
712        let fd = self.fds.get(&fd_num)?;
713        Some(fd.clone())
714    }
715
716    pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
717        self.fds.remove(&fd_num)
718    }
719
720    pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
721        self.fds.contains_key(&fd_num)
722    }
723}
724
725impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
726pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
727    /// Read data from a host `Read` type, store the result into machine memory,
728    /// and return whether that worked.
729    fn read_from_host(
730        &mut self,
731        ptr: Pointer,
732        len: u64,
733        read_cb: impl FnOnce(&mut [u8]) -> io::Result<usize>,
734    ) -> InterpResult<'tcx, Result<u64, IoError>> {
735        let ecx = self.eval_context_mut();
736
737        let len = usize::saturating_cast_from(len);
738        let mut bytes = vec![0; len];
739        let result = read_cb(&mut bytes);
740        match result {
741            Ok(read_size) => {
742                // If reading to `bytes` did not fail, we write those bytes to the buffer.
743                // Crucially, if fewer than `bytes.len()` bytes were read, only write
744                // that much into the output buffer!
745                ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
746                interp_ok(Ok(read_size.to_u64()))
747            }
748            Err(e) => interp_ok(Err(IoError::HostError(e))),
749        }
750    }
751
752    /// Write data to a host `Write` type, with the bytes taken from machine memory.
753    fn write_to_host(
754        &self,
755        ptr: Pointer,
756        len: u64,
757        write_cb: impl FnOnce(&[u8]) -> io::Result<usize>,
758    ) -> InterpResult<'tcx, Result<u64, IoError>> {
759        let ecx = self.eval_context_ref();
760
761        let len = usize::saturating_cast_from(len);
762        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
763        let result = write_cb(bytes);
764        interp_ok(result.map_err(IoError::HostError).map(|written_size| written_size.to_u64()))
765    }
766}
767
768/// Open something for which we don't know ahead of time whether it is a file or a directory.
769///
770/// Note that on Windows hosts, `custom_flags` is ignored if `root` is `Some`!
771pub fn open_file_or_dir(
772    root: Option<&fs::Dir>,
773    path: &std::path::Path,
774    opts: &fs::OpenOptions,
775) -> io::Result<Either<fs::File, fs::Dir>> {
776    // On Unix, `open`/`openat` works for files and directories.
777    // On Windows, that fails with `PermissionDenied`, so we need to open an `fs::Dir` instead.
778    // (But that may succeed even on a file! So we still have to check the metadata.)
779    let file = match root {
780        Some(root) => root.open_file_with(path, opts),
781        None => opts.open(path),
782    };
783    #[cfg(windows)]
784    let file = file.or_else(|err| {
785        use std::os::windows::io::OwnedHandle;
786
787        if err.kind() == io::ErrorKind::PermissionDenied {
788            // Retry via `fs::Dir`. Opening a directory via `Dir::open_file` does not work so we
789            // have to use `open_dir`.
790            let dir = match root {
791                Some(root) => root.open_dir_with(path, opts),
792                None => Dir::open_with(path, opts),
793            };
794            match dir {
795                // Convert back to file so it always has the same type.
796                Ok(dir) => Ok(File::from(OwnedHandle::from(dir))),
797                Err(err2) => {
798                    // If this 2nd attempt says "not a directory", preserve the original error. It's
799                    // possible that we lost a race twice if this keeps being changed from directory
800                    // to file and back. However, in that case it was entirely non-existent for a
801                    // little while, so returning an error is still fine.
802                    Err(if err2.kind() == io::ErrorKind::NotADirectory { err } else { err2 })
803                }
804            }
805        } else {
806            Err(err)
807        }
808    });
809    let file = file?;
810
811    let metadata = file.metadata().expect("just-opened file/dir should have metadata");
812    if metadata.is_dir() {
813        assert!(!metadata.is_symlink()); // Rust makes this mutually exclusive with `is_dir`
814        // Convert to dir.
815        cfg_select! {
816            unix => {
817                use std::os::fd::OwnedFd;
818                Ok(Either::Right(OwnedFd::from(file).into()))
819            }
820            windows => {
821                use std::os::windows::io::OwnedHandle;
822                Ok(Either::Right(OwnedHandle::from(file).into()))
823            }
824        }
825    } else {
826        Ok(Either::Left(file))
827    }
828}