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