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 now represented as a string storing the `libc` name of the mode 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    /// Converts this FD into a unix file description. Must succeed for all FD types that can be
199    /// created on Unix targets!
200    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
201        panic!("Not a unix file description: {}", self.name());
202    }
203
204    /// Get the `ReadinessWatched` of the file description.
205    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
206        None
207    }
208
209    /// Get the current I/O readiness of the file description.
210    fn readiness(&self) -> Readiness {
211        panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
212    }
213}
214
215#[derive(Debug)]
216pub struct Stdin {
217    stdin: io::Stdin,
218    watched: ReadinessWatched,
219}
220
221impl Stdin {
222    fn new() -> Self {
223        Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
224    }
225}
226
227impl FileDescription for Stdin {
228    fn name(&self) -> &'static str {
229        "stdin"
230    }
231
232    fn read<'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        if !communicate_allowed {
241            // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
242            helpers::isolation_abort_error("`read` from stdin")?;
243        }
244
245        // FIXME: this can block on the host, halting the entire interpreter.
246        let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?;
247        finish.call(ecx, result)
248    }
249
250    fn is_tty(&self, communicate_allowed: bool) -> bool {
251        communicate_allowed && self.stdin.is_terminal()
252    }
253
254    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
255        Some(&self.watched)
256    }
257
258    fn readiness(&self) -> Readiness {
259        // Stdin is readable (we never return EWOULDBLOCK above) and also writable (since that never
260        // blocks either). This matches what we see on Linux.
261        let mut readiness = Readiness::EMPTY;
262        readiness.readable = true;
263        readiness.writable = true;
264        readiness
265    }
266
267    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
268        self
269    }
270}
271
272#[derive(Debug)]
273pub struct Stdout {
274    stdout: io::Stdout,
275    watched: ReadinessWatched,
276}
277
278impl Stdout {
279    fn new() -> Self {
280        Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
281    }
282}
283
284impl FileDescription for Stdout {
285    fn name(&self) -> &'static str {
286        "stdout"
287    }
288
289    fn write<'tcx>(
290        self: FileDescriptionRef<Self>,
291        _communicate_allowed: bool,
292        ptr: Pointer,
293        len: usize,
294        ecx: &mut MiriInterpCx<'tcx>,
295        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
296    ) -> InterpResult<'tcx> {
297        // We allow writing to stdout even with isolation enabled.
298        let result = ecx.write_to_host(&self.stdout, len, ptr)?;
299        // Stdout is buffered, flush to make sure it appears on the
300        // screen.  This is the write() syscall of the interpreted
301        // program, we want it to correspond to a write() syscall on
302        // the host -- there is no good in adding extra buffering
303        // here.
304        io::stdout().flush().unwrap();
305
306        finish.call(ecx, result)
307    }
308
309    fn is_tty(&self, communicate_allowed: bool) -> bool {
310        communicate_allowed && self.stdout.is_terminal()
311    }
312
313    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
314        Some(&self.watched)
315    }
316
317    fn readiness(&self) -> Readiness {
318        // stdout can always be written (we never return EWOULDBLOCK there) and never be read.
319        let mut readiness = Readiness::EMPTY;
320        readiness.writable = true;
321        readiness
322    }
323
324    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
325        self
326    }
327}
328
329#[derive(Debug)]
330pub struct Stderr {
331    stderr: io::Stderr,
332    watched: ReadinessWatched,
333}
334
335impl Stderr {
336    fn new() -> Self {
337        Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
338    }
339}
340
341impl FileDescription for Stderr {
342    fn name(&self) -> &'static str {
343        "stderr"
344    }
345
346    fn write<'tcx>(
347        self: FileDescriptionRef<Self>,
348        _communicate_allowed: bool,
349        ptr: Pointer,
350        len: usize,
351        ecx: &mut MiriInterpCx<'tcx>,
352        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
353    ) -> InterpResult<'tcx> {
354        // We allow writing to stderr even with isolation enabled.
355        let result = ecx.write_to_host(&self.stderr, len, ptr)?;
356        // No need to flush, stderr is not buffered.
357        finish.call(ecx, result)
358    }
359
360    fn is_tty(&self, communicate_allowed: bool) -> bool {
361        communicate_allowed && self.stderr.is_terminal()
362    }
363
364    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
365        Some(&self.watched)
366    }
367
368    fn readiness(&self) -> Readiness {
369        // stderr can always be written (we never return EWOULDBLOCK there) and never be read.
370        let mut readiness = Readiness::EMPTY;
371        readiness.writable = true;
372        readiness
373    }
374
375    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
376        self
377    }
378}
379
380/// Like /dev/null
381#[derive(Debug)]
382pub struct NullOutput {
383    watched: ReadinessWatched,
384}
385
386impl NullOutput {
387    fn new() -> Self {
388        Self { watched: ReadinessWatched::default() }
389    }
390}
391
392impl FileDescription for NullOutput {
393    fn name(&self) -> &'static str {
394        "null output"
395    }
396
397    fn write<'tcx>(
398        self: FileDescriptionRef<Self>,
399        _communicate_allowed: bool,
400        _ptr: Pointer,
401        len: usize,
402        ecx: &mut MiriInterpCx<'tcx>,
403        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
404    ) -> InterpResult<'tcx> {
405        // We just don't write anything, but report to the user that we did.
406        finish.call(ecx, Ok(len))
407    }
408
409    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
410        Some(&self.watched)
411    }
412
413    fn readiness(&self) -> Readiness {
414        // null output can always be written (we never return EWOULDBLOCK there) and never be read.
415        let mut readiness = Readiness::EMPTY;
416        readiness.writable = true;
417        readiness
418    }
419
420    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
421        self
422    }
423}
424
425#[derive(Debug)]
426pub struct FileHandle {
427    pub(crate) file: File,
428    pub(crate) readable: bool,
429    pub(crate) writable: bool,
430}
431
432impl FileDescription for FileHandle {
433    fn name(&self) -> &'static str {
434        "file"
435    }
436
437    fn read<'tcx>(
438        self: FileDescriptionRef<Self>,
439        communicate_allowed: bool,
440        ptr: Pointer,
441        len: usize,
442        ecx: &mut MiriInterpCx<'tcx>,
443        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
444    ) -> InterpResult<'tcx> {
445        assert!(communicate_allowed, "isolation should have prevented even opening a file");
446
447        if !self.readable {
448            // Unix returns EBADF, Windows something that translates to `PermissionDenied`.
449            let err = if ecx.target_os_is_unix() {
450                LibcError("EBADF")
451            } else {
452                ErrorKind::PermissionDenied.into()
453            };
454            return finish.call(ecx, Err(err));
455        }
456
457        let mut file = &self.file;
458        let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
459        finish.call(ecx, result)
460    }
461
462    fn write<'tcx>(
463        self: FileDescriptionRef<Self>,
464        communicate_allowed: bool,
465        ptr: Pointer,
466        len: usize,
467        ecx: &mut MiriInterpCx<'tcx>,
468        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
469    ) -> InterpResult<'tcx> {
470        assert!(communicate_allowed, "isolation should have prevented even opening a file");
471
472        if !self.writable {
473            // Unix returns EBADF, Windows something that translates to `PermissionDenied`.
474            let err = if ecx.target_os_is_unix() {
475                LibcError("EBADF")
476            } else {
477                ErrorKind::PermissionDenied.into()
478            };
479            return finish.call(ecx, Err(err));
480        }
481        let result = ecx.write_to_host(&self.file, len, ptr)?;
482        finish.call(ecx, result)
483    }
484
485    fn seek<'tcx>(
486        &self,
487        communicate_allowed: bool,
488        offset: SeekFrom,
489    ) -> InterpResult<'tcx, io::Result<u64>> {
490        assert!(communicate_allowed, "isolation should have prevented even opening a file");
491        interp_ok((&mut &self.file).seek(offset))
492    }
493
494    fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
495        interp_ok(Either::Left(self.file.metadata()))
496    }
497
498    fn is_tty(&self, communicate_allowed: bool) -> bool {
499        communicate_allowed && self.file.is_terminal()
500    }
501
502    fn short_fd_operations(&self) -> bool {
503        // While short accesses on file-backed FDs are very rare (at least for sufficiently small
504        // accesses), they can realistically happen when a signal interrupts the syscall.
505        // FIXME: we should return `false` if this is a named pipe...
506        true
507    }
508
509    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
510        self
511    }
512}
513
514#[derive(Debug)]
515pub struct DirHandle {
516    pub(super) dir: Dir,
517    #[cfg(bootstrap)]
518    pub(super) fallback: std::path::PathBuf,
519}
520
521impl FileDescription for DirHandle {
522    fn name(&self) -> &'static str {
523        "directory"
524    }
525
526    fn metadata<'tcx>(
527        &self,
528    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
529        #[cfg(bootstrap)]
530        return interp_ok(Either::Left(self.dir.metadata()));
531        #[cfg(not(bootstrap))]
532        return interp_ok(Either::Left(self.dir.self_metadata()));
533    }
534
535    fn read<'tcx>(
536        self: FileDescriptionRef<Self>,
537        _communicate_allowed: bool,
538        _ptr: Pointer,
539        _len: usize,
540        ecx: &mut MiriInterpCx<'tcx>,
541        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
542    ) -> InterpResult<'tcx> {
543        if ecx.target_os_is_unix() {
544            finish.call(ecx, Err(LibcError("EISDIR")))
545        } else {
546            // No idea what this should do on Windows.
547            throw_unsup_format!("reading directories is not supported on this target");
548        }
549    }
550
551    fn write<'tcx>(
552        self: FileDescriptionRef<Self>,
553        _communicate_allowed: bool,
554        _ptr: Pointer,
555        _len: usize,
556        ecx: &mut MiriInterpCx<'tcx>,
557        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
558    ) -> InterpResult<'tcx> {
559        if ecx.target_os_is_unix() {
560            // Directories are opened for reading, so writing returns EBADF.
561            finish.call(ecx, Err(LibcError("EBADF")))
562        } else {
563            // No idea what this should do on Windows.
564            throw_unsup_format!("writing directories is not supported on this target");
565        }
566    }
567
568    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
569        self
570    }
571}
572
573/// Internal type of a file-descriptor - this is what [`FdTable`] expects
574pub type FdNum = i32;
575
576/// The file descriptor table
577#[derive(Debug)]
578pub struct FdTable {
579    fds: BTreeMap<FdNum, DynFileDescriptionRef>,
580    /// Unique identifier for file description, used to differentiate between various file description.
581    next_file_description_id: FdId,
582}
583
584impl VisitProvenance for FdTable {
585    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
586        // All our FileDescription instances do not have any provenance.
587    }
588}
589
590impl FdTable {
591    fn new() -> Self {
592        FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
593    }
594    pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
595        let mut fds = FdTable::new();
596        fds.insert_new(Stdin::new());
597        if mute_stdout_stderr {
598            assert_eq!(fds.insert_new(NullOutput::new()), 1);
599            assert_eq!(fds.insert_new(NullOutput::new()), 2);
600        } else {
601            assert_eq!(fds.insert_new(Stdout::new()), 1);
602            assert_eq!(fds.insert_new(Stderr::new()), 2);
603        }
604        fds
605    }
606
607    pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
608        let file_handle =
609            FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
610        self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
611        file_handle
612    }
613
614    /// Insert a new file description to the FdTable.
615    pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
616        let fd_ref = self.new_ref(fd);
617        self.insert(fd_ref)
618    }
619
620    /// Insert an alias to an existing file description to the FdTable.
621    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
622        self.insert_with_min_num(fd_ref, 0)
623    }
624
625    /// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
626    pub fn insert_with_min_num(
627        &mut self,
628        file_handle: DynFileDescriptionRef,
629        min_fd_num: FdNum,
630    ) -> FdNum {
631        let mut candidate = min_fd_num;
632        for (&fd_num, _) in self.fds.range(min_fd_num..) {
633            if fd_num == candidate {
634                // This one is taken. Try the next one.
635                candidate = candidate.strict_add(1);
636            } else {
637                // We found a gap! Use this candidate.
638                break;
639            }
640        }
641        // If we exhaust the loop, the table is a solid block starting at `min_fd_num` until the
642        // end, and `candidate` is now the first number after that block -- exactly what we need.
643
644        self.fds.try_insert(candidate, file_handle).unwrap();
645        candidate
646    }
647
648    pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
649        let fd = self.fds.get(&fd_num)?;
650        Some(fd.clone())
651    }
652
653    pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
654        self.fds.remove(&fd_num)
655    }
656
657    pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
658        self.fds.contains_key(&fd_num)
659    }
660}
661
662impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
663pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
664    /// Read data from a host `Read` type, store the result into machine memory,
665    /// and return whether that worked.
666    fn read_from_host(
667        &mut self,
668        mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
669        len: usize,
670        ptr: Pointer,
671    ) -> InterpResult<'tcx, Result<usize, IoError>> {
672        let this = self.eval_context_mut();
673
674        let mut bytes = vec![0; len];
675        let result = read_cb(&mut bytes);
676        match result {
677            Ok(read_size) => {
678                // If reading to `bytes` did not fail, we write those bytes to the buffer.
679                // Crucially, if fewer than `bytes.len()` bytes were read, only write
680                // that much into the output buffer!
681                this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
682                interp_ok(Ok(read_size))
683            }
684            Err(e) => interp_ok(Err(IoError::HostError(e))),
685        }
686    }
687
688    /// Write data to a host `Write` type, with the bytes taken from machine memory.
689    fn write_to_host(
690        &mut self,
691        mut file: impl io::Write,
692        len: usize,
693        ptr: Pointer,
694    ) -> InterpResult<'tcx, Result<usize, IoError>> {
695        let this = self.eval_context_mut();
696
697        let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
698        let result = file.write(bytes);
699        interp_ok(result.map_err(IoError::HostError))
700    }
701}
702
703/// Open something for which we don't know ahead of time whether it is a file or a directory.
704///
705/// Note that on Windows hosts, `custom_flags` is ignored if `root` is `Some`!
706pub fn open_file_or_dir(
707    root: Option<&fs::Dir>,
708    path: &std::path::Path,
709    opts: &fs::OpenOptions,
710) -> io::Result<Either<fs::File, fs::Dir>> {
711    // On Unix, `open`/`openat` works for files and directories.
712    // On Windows, that fails with `PermissionDenied`, so we need to open an `fs::Dir` instead.
713    // (But that may succeed even on a file! So we still have to check the metadata.)
714    let file = match root {
715        Some(root) => root.open_file_with(path, opts),
716        None => opts.open(path),
717    };
718    #[cfg(windows)]
719    let file = file.or_else(|err| {
720        use std::os::windows::io::OwnedHandle;
721
722        if err.kind() == io::ErrorKind::PermissionDenied {
723            // Retry via `fs::Dir`. Opening a directory via `Dir::open_file` does not work so we
724            // have to use `open_dir`.
725            let dir = match root {
726                Some(root) => root.open_dir_with(path, opts),
727                None => Dir::open_with(path, opts),
728            };
729            match dir {
730                // Convert back to file so it always has the same type.
731                Ok(dir) => Ok(File::from(OwnedHandle::from(dir))),
732                Err(err2) => {
733                    // If this 2nd attempt says "not a directory", preserve the original error. It's
734                    // possible that we lost a race twice if this keeps being changed from directory
735                    // to file and back. However, in that case it was entirely non-existent for a
736                    // little while, so returning an error is still fine.
737                    Err(if err2.kind() == io::ErrorKind::NotADirectory { err } else { err2 })
738                }
739            }
740        } else {
741            Err(err)
742        }
743    });
744    let file = file?;
745
746    let metadata = file.metadata().expect("just-opened file/dir should have metadata");
747    if metadata.is_dir() {
748        assert!(!metadata.is_symlink()); // Rust makes this mutually exclusive with `is_dir`
749        // Convert to dir.
750        cfg_select! {
751            unix => {
752                use std::os::fd::OwnedFd;
753                Ok(Either::Right(OwnedFd::from(file).into()))
754            }
755            windows => {
756                use std::os::windows::io::OwnedHandle;
757                Ok(Either::Right(OwnedHandle::from(file).into()))
758            }
759        }
760    } else {
761        Ok(Either::Left(file))
762    }
763}