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