Skip to main content

miri/shims/unix/
fd.rs

1//! General management of file descriptors, and support for
2//! standard file descriptors (stdin/stdout/stderr).
3
4use std::io;
5use std::io::ErrorKind;
6
7use rand::RngExt;
8use rustc_abi::{Align, Size};
9use rustc_target::spec::Os;
10
11use crate::shims::FileDescriptionRef;
12use crate::shims::files::{DynFileDescriptionRef, FileDescription};
13use crate::shims::sig::check_min_vararg_count;
14use crate::shims::unix::socket::UnixSocketFileDescription;
15use crate::shims::unix::*;
16use crate::*;
17
18#[derive(Debug, Clone, Copy, Eq, PartialEq)]
19pub enum FlockOp {
20    SharedLock { nonblocking: bool },
21    ExclusiveLock { nonblocking: bool },
22    Unlock,
23}
24
25/// Represents unix-specific file descriptions.
26pub trait UnixFileDescription: FileDescription {
27    /// Reads as much as possible into the given buffer `ptr` from a given offset.
28    /// `len` indicates how many bytes we should try to read.
29    /// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
30    fn pread<'tcx>(
31        &self,
32        _communicate_allowed: bool,
33        _offset: u64,
34        _ptr: Pointer,
35        _len: usize,
36        _ecx: &mut MiriInterpCx<'tcx>,
37        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
38    ) -> InterpResult<'tcx> {
39        throw_unsup_format!("cannot pread from {}", self.name());
40    }
41
42    /// Writes as much as possible from the given buffer `ptr` starting at a given offset.
43    /// `ptr` is the pointer to the user supplied read buffer.
44    /// `len` indicates how many bytes we should try to write.
45    /// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
46    fn pwrite<'tcx>(
47        &self,
48        _communicate_allowed: bool,
49        _ptr: Pointer,
50        _len: usize,
51        _offset: u64,
52        _ecx: &mut MiriInterpCx<'tcx>,
53        _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
54    ) -> InterpResult<'tcx> {
55        throw_unsup_format!("cannot pwrite to {}", self.name());
56    }
57
58    fn flock<'tcx>(
59        &self,
60        _communicate_allowed: bool,
61        _op: FlockOp,
62    ) -> InterpResult<'tcx, io::Result<()>> {
63        throw_unsup_format!("cannot flock {}", self.name());
64    }
65
66    /// Modifies device parameters.
67    /// `op` is the device-dependent operation code. It's either a `c_long` or `c_int`, depending on
68    /// the target and whether it uses glibc or musl.
69    /// `arg` is the optional third argument which exists depending on the operation code. It's either
70    /// an integer or a pointer.
71    fn ioctl<'tcx>(
72        &self,
73        _op: Scalar,
74        _arg: Option<&OpTy<'tcx>>,
75        _ecx: &mut MiriInterpCx<'tcx>,
76    ) -> InterpResult<'tcx, i32> {
77        throw_unsup_format!("cannot use ioctl on {}", self.name());
78    }
79
80    /// Returns this file description as a Unix socket, if it represents one.
81    fn as_socket<'tcx>(
82        self: FileDescriptionRef<Self>,
83        _ecx: &MiriInterpCx<'tcx>,
84    ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
85        None
86    }
87}
88
89impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
90pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
91    fn dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
92        let this = self.eval_context_mut();
93
94        let Some(fd) = this.machine.fds.get(old_fd_num) else {
95            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
96        };
97        interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
98    }
99
100    fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
101        let this = self.eval_context_mut();
102
103        let Some(fd) = this.machine.fds.get(old_fd_num) else {
104            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
105        };
106        if new_fd_num != old_fd_num {
107            // Close new_fd if it is previously opened.
108            // If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive.
109            if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
110                // Ignore close error (not interpreter's) according to dup2() doc.
111                old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
112            }
113        }
114        interp_ok(Scalar::from_i32(new_fd_num))
115    }
116
117    fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
118        let this = self.eval_context_mut();
119        let Some(fd) = this.machine.fds.get(fd_num) else {
120            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
121        };
122
123        // We need to check that there aren't unsupported options in `op`.
124        let lock_sh = this.eval_libc_i32("LOCK_SH");
125        let lock_ex = this.eval_libc_i32("LOCK_EX");
126        let lock_nb = this.eval_libc_i32("LOCK_NB");
127        let lock_un = this.eval_libc_i32("LOCK_UN");
128
129        use FlockOp::*;
130        let parsed_op = if op == lock_sh {
131            SharedLock { nonblocking: false }
132        } else if op == lock_sh | lock_nb {
133            SharedLock { nonblocking: true }
134        } else if op == lock_ex {
135            ExclusiveLock { nonblocking: false }
136        } else if op == lock_ex | lock_nb {
137            ExclusiveLock { nonblocking: true }
138        } else if op == lock_un {
139            Unlock
140        } else {
141            throw_unsup_format!("unsupported flags {:#x}", op);
142        };
143
144        let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
145        // return `0` if flock is successful
146        let result = result.map(|()| 0i32);
147        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
148    }
149
150    fn ioctl(
151        &mut self,
152        fd: &OpTy<'tcx>,
153        op: &OpTy<'tcx>,
154        varargs: &[OpTy<'tcx>],
155    ) -> InterpResult<'tcx, Scalar> {
156        let this = self.eval_context_mut();
157
158        let fd = this.read_scalar(fd)?.to_i32()?;
159        let op = this.read_scalar(op)?;
160        // There is at most one relevant variadic argument.
161        // It exists depending on the device and the opcode and thus we can't
162        // use `check_min_vararg_count` here.
163        let arg = varargs.first();
164
165        let Some(fd) = this.machine.fds.get(fd) else {
166            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
167        };
168
169        // Handle common opcodes.
170        let fioclex = this.eval_libc("FIOCLEX");
171        let fionclex = this.eval_libc("FIONCLEX");
172        if op == fioclex || op == fionclex {
173            // Since we don't support `exec`, those are NOPs.
174            return interp_ok(Scalar::from_i32(0));
175        }
176
177        // Since some ioctl operations use the return value as an output parameter, we cannot strictly use the convention of
178        // zero indicating success and -1 indicating an error.
179        let return_value = fd.as_unix(this).ioctl(op, arg, this)?;
180        interp_ok(Scalar::from_i32(return_value))
181    }
182
183    fn fcntl(
184        &mut self,
185        fd_num: &OpTy<'tcx>,
186        cmd: &OpTy<'tcx>,
187        varargs: &[OpTy<'tcx>],
188    ) -> InterpResult<'tcx, Scalar> {
189        let this = self.eval_context_mut();
190
191        let fd_num = this.read_scalar(fd_num)?.to_i32()?;
192        let cmd = this.read_scalar(cmd)?.to_i32()?;
193
194        let f_getfd = this.eval_libc_i32("F_GETFD");
195        let f_dupfd = this.eval_libc_i32("F_DUPFD");
196        let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
197        let f_getfl = this.eval_libc_i32("F_GETFL");
198        let f_setfl = this.eval_libc_i32("F_SETFL");
199
200        // We only support getting the flags for a descriptor.
201        match cmd {
202            cmd if cmd == f_getfd => {
203                // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
204                // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
205                // always sets this flag when opening a file. However we still need to check that the
206                // file itself is open.
207                if !this.machine.fds.is_fd_num(fd_num) {
208                    this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
209                } else {
210                    interp_ok(this.eval_libc("FD_CLOEXEC"))
211                }
212            }
213            cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
214                // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
215                // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
216                // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
217                // thus they can share the same implementation here.
218                let cmd_name = if cmd == f_dupfd {
219                    "fcntl(fd, F_DUPFD, ...)"
220                } else {
221                    "fcntl(fd, F_DUPFD_CLOEXEC, ...)"
222                };
223
224                let [start] = check_min_vararg_count(cmd_name, varargs)?;
225                let start = this.read_scalar(start)?.to_i32()?;
226
227                if let Some(fd) = this.machine.fds.get(fd_num) {
228                    interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
229                } else {
230                    this.set_errno_and_return_neg1_i32(LibcError("EBADF"))
231                }
232            }
233            cmd if cmd == f_getfl => {
234                // Check if this is a valid open file descriptor.
235                let Some(fd) = this.machine.fds.get(fd_num) else {
236                    return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
237                };
238
239                fd.get_flags(this)
240            }
241            cmd if cmd == f_setfl => {
242                // Check if this is a valid open file descriptor.
243                let Some(fd) = this.machine.fds.get(fd_num) else {
244                    return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
245                };
246
247                let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
248                let flag = this.read_scalar(flag)?.to_i32()?;
249
250                // Ignore flags that never get stored by SETFL.
251                // "File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file
252                // creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC)
253                // in arg are ignored."
254                let ignored_flags = this.eval_libc_i32("O_RDONLY")
255                    | this.eval_libc_i32("O_WRONLY")
256                    | this.eval_libc_i32("O_RDWR")
257                    | this.eval_libc_i32("O_CREAT")
258                    | this.eval_libc_i32("O_EXCL")
259                    | this.eval_libc_i32("O_NOCTTY")
260                    | this.eval_libc_i32("O_TRUNC");
261
262                fd.set_flags(flag & !ignored_flags, this)
263            }
264            cmd if this.tcx.sess.target.os == Os::MacOs
265                && cmd == this.eval_libc_i32("F_FULLFSYNC") =>
266            {
267                // Reject if isolation is enabled.
268                if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
269                    this.reject_in_isolation("`fcntl`", reject_with)?;
270                    return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
271                }
272
273                this.ffullsync_fd(fd_num)
274            }
275            cmd => {
276                throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
277            }
278        }
279    }
280
281    fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
282        let this = self.eval_context_mut();
283
284        let fd_num = this.read_scalar(fd_op)?.to_i32()?;
285
286        let Some(fd) = this.machine.fds.remove(fd_num) else {
287            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
288        };
289        let result = fd.close_ref(this.machine.communicate(), this)?;
290        // return `0` if close is successful
291        let result = result.map(|()| 0i32);
292        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
293    }
294
295    /// Read data from `fd` into buffer specified by `buf` and `count`.
296    ///
297    /// If `offset` is `None`, reads data from current cursor position associated with `fd`
298    /// and updates cursor position on completion. Otherwise, reads from the specified offset
299    /// and keeps the cursor unchanged.
300    fn read(
301        &mut self,
302        fd_num: i32,
303        buf: Pointer,
304        count: u64,
305        offset: Option<i128>,
306        dest: &MPlaceTy<'tcx>,
307    ) -> InterpResult<'tcx> {
308        let this = self.eval_context_mut();
309
310        // Isolation check is done via `FileDescription` trait.
311
312        trace!("Reading from FD {}, size {}", fd_num, count);
313
314        // Check that the *entire* buffer is actually valid memory.
315        this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
316
317        // We cap the number of read bytes to the largest value that we are able to fit in both the
318        // host's and target's `isize`. This saves us from having to handle overflows later.
319        let count = count
320            .min(u64::try_from(this.target_isize_max()).unwrap())
321            .min(u64::try_from(isize::MAX).unwrap());
322        let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
323
324        // Get the FD.
325        let Some(fd) = this.machine.fds.get(fd_num) else {
326            trace!("read: FD not found");
327            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
328        };
329
330        trace!("read: FD mapped to {fd:?}");
331        // We want to read at most `count` bytes. We are sure that `count` is not negative
332        // because it was a target's `usize`. Also we are sure that it's smaller than
333        // `usize::MAX` because it is bounded by the host's `isize`.
334
335        let dest = dest.clone();
336        this.read_from_fd(
337            fd,
338            buf,
339            count,
340            offset,
341            callback!(
342                @capture<'tcx> {
343                    count: usize,
344                    dest: MPlaceTy<'tcx>,
345                }
346                |this, result: Result<usize, IoError>| {
347                    match result {
348                        Ok(read_size) => {
349                            assert!(read_size <= count);
350                            // This must fit since `count` fits.
351                            this.write_int(u64::try_from(read_size).unwrap(), &dest)
352                        }
353                        Err(e) => this.set_errno_and_return_neg1(e, &dest)
354                }}
355            ),
356        )
357    }
358
359    fn write(
360        &mut self,
361        fd_num: i32,
362        buf: Pointer,
363        count: u64,
364        offset: Option<i128>,
365        dest: &MPlaceTy<'tcx>,
366    ) -> InterpResult<'tcx> {
367        let this = self.eval_context_mut();
368
369        // Isolation check is done via `FileDescription` trait.
370
371        // Check that the *entire* buffer is actually valid memory.
372        this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;
373
374        // We cap the number of written bytes to the largest value that we are able to fit in both the
375        // host's and target's `isize`. This saves us from having to handle overflows later.
376        let count = count
377            .min(u64::try_from(this.target_isize_max()).unwrap())
378            .min(u64::try_from(isize::MAX).unwrap());
379        let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
380
381        // We temporarily dup the FD to be able to retain mutable access to `this`.
382        let Some(fd) = this.machine.fds.get(fd_num) else {
383            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
384        };
385
386        let dest = dest.clone();
387        this.write_to_fd(
388            fd,
389            buf,
390            count,
391            offset,
392            callback!(
393                @capture<'tcx> {
394                    count: usize,
395                    dest: MPlaceTy<'tcx>,
396                }
397                |this, result: Result<usize, IoError>| {
398                    match result {
399                        Ok(write_size) => {
400                            assert!(write_size <= count);
401                            // This must fit since `count` fits.
402                            this.write_int(u64::try_from(write_size).unwrap(), &dest)
403                        }
404                        Err(e) => this.set_errno_and_return_neg1(e, &dest)
405
406                }}
407            ),
408        )
409    }
410
411    /// Vectored reads are implemented by first reading bytes from `fd`
412    /// into a temporary buffer which has the combined size of all buffers in
413    /// `iov`. After that we split the bytes of the combined buffer into the
414    /// buffers of `iov`. This ensures that the vectored read occurs atomically.
415    fn readv(
416        &mut self,
417        fd: &OpTy<'tcx>,
418        iov: &OpTy<'tcx>,
419        iovcnt: &OpTy<'tcx>,
420        offset: Option<&OpTy<'tcx>>,
421        dest: &MPlaceTy<'tcx>,
422    ) -> InterpResult<'tcx> {
423        let this = self.eval_context_mut();
424
425        let fd = this.read_scalar(fd)?.to_i32()?;
426        let iov_ptr = this.read_pointer(iov)?;
427        let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
428        // `readv` is the same as `preadv` without an offset.
429        let offset = if let Some(offset) = offset {
430            if matches!(this.tcx.sess.target.os, Os::Solaris) {
431                throw_unsup_format!(
432                    "preadv: vectored reads with offsets aren't supported on Solaris"
433                )
434            }
435            Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
436        } else {
437            None
438        };
439
440        // Check that the FD exists.
441        let Some(fd) = this.machine.fds.get(fd) else {
442            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
443        };
444
445        let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
446        let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
447
448        // Read list of buffers from `iov`.
449        let mut buffers = Vec::new();
450
451        let mut array = this.project_array_fields(&iov_ptr_mplace)?;
452        while let Some((_idx, iovec)) = array.next(this)? {
453            let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
454            let iov_len: u64 = this
455                .read_scalar(&iov_len_field)?
456                .to_int(iov_len_field.layout.size)?
457                .try_into()
458                .unwrap();
459
460            let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
461            let iov_base_ptr = this.read_pointer(&iov_base_field)?;
462
463            buffers.push((iov_base_ptr, iov_len));
464        }
465
466        let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
467
468        // Allocate a temporary buffer which has the combined size of all buffers provided in `iov`.
469        let tmp_ptr: Pointer = this
470            .allocate_ptr(
471                Size::from_bytes(total_bytes),
472                Align::ONE,
473                MemoryKind::Stack,
474                AllocInit::Uninit,
475            )?
476            .into();
477
478        let dest = dest.clone();
479        this.read_from_fd(
480            fd,
481            tmp_ptr,
482            usize::try_from(total_bytes).unwrap(),
483            offset,
484            callback!(
485                @capture<'tcx> {
486                    tmp_ptr: Pointer,
487                    buffers: Vec<(Pointer, u64)>,
488                    dest: MPlaceTy<'tcx>
489                } |this, result: Result<usize, IoError>| {
490                    let bytes_read = match result {
491                        Ok(size) => {
492                            this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest)?;
493                            u64::try_from(size).unwrap()
494                        },
495                        Err(e) => {
496                            this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
497                            return this.set_errno_and_return_neg1(e, &dest)
498                        }
499                    };
500                    let mut remaining_bytes = bytes_read;
501
502                    // Split the bytes from the temporary buffer into the buffers provided in `iov`.
503                    // We start at the first buffer and fill them in order, until we reach the end of the
504                    // initialized bytes in the temporary buffer.
505                    for (buffer_ptr, buffer_len) in buffers {
506                        // Offset temporary buffer by the amount of bytes we already copied into previous buffers.
507                        let tmp_ptr_with_offset =
508                            this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_read.strict_sub(remaining_bytes)).unwrap())?;
509
510                        // Copy at most as many bytes as the buffer fits but without reading
511                        // any uninitialized bytes from the temporary buffer.
512                        let copy_amount = buffer_len.min(remaining_bytes);
513                        this.mem_copy(
514                            tmp_ptr_with_offset,
515                            buffer_ptr,
516                            Size::from_bytes(copy_amount),
517                            // The buffers are guaranteed to not overlap because we just newly allocated
518                            // the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be
519                            // within those boundaries.
520                            true,
521                        )?;
522
523                        remaining_bytes = remaining_bytes.strict_sub(copy_amount);
524                        if remaining_bytes == 0 {
525                            // We don't have anything left to copy; exit the loop.
526                            break;
527                        }
528                    }
529
530                    this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)
531                }),
532        )
533    }
534
535    /// Vectored writes are implemented by first writing the bytes from all
536    /// buffers of `iov` into a combined temporary buffer and then writing this
537    /// combined buffer into `fd`. This ensures that the vectored write occurs atomically.
538    fn writev(
539        &mut self,
540        fd: &OpTy<'tcx>,
541        iov: &OpTy<'tcx>,
542        iovcnt: &OpTy<'tcx>,
543        offset: Option<&OpTy<'tcx>>,
544        dest: &MPlaceTy<'tcx>,
545    ) -> InterpResult<'tcx> {
546        let this = self.eval_context_mut();
547
548        let fd = this.read_scalar(fd)?.to_i32()?;
549        let iov_ptr = this.read_pointer(iov)?;
550        let iovcnt: u64 = this.read_scalar(iovcnt)?.to_i32()?.try_into().unwrap();
551        // `writev` is the same as `pwritev` without an offset.
552        let offset = if let Some(offset) = offset {
553            if matches!(this.tcx.sess.target.os, Os::Solaris) {
554                throw_unsup_format!(
555                    "pwritev: vectored writes with offsets aren't supported on Solaris"
556                )
557            }
558            Some(this.read_scalar(offset)?.to_int(offset.layout.size)?)
559        } else {
560            None
561        };
562
563        // Check that the FD exists.
564        let Some(fd) = this.machine.fds.get(fd) else {
565            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
566        };
567
568        let iovec_layout = this.libc_array_ty_layout("iovec", iovcnt);
569        let iov_ptr_mplace = this.ptr_to_mplace(iov_ptr, iovec_layout);
570
571        // Read list of buffers from `iov`.
572        let mut buffers = Vec::new();
573
574        let mut array = this.project_array_fields(&iov_ptr_mplace)?;
575        while let Some((_idx, iovec)) = array.next(this)? {
576            let iov_len_field = this.project_field_named(&iovec, "iov_len")?;
577            let iov_len: u64 = this
578                .read_scalar(&iov_len_field)?
579                .to_int(iov_len_field.layout.size)?
580                .try_into()
581                .unwrap();
582
583            let iov_base_field = this.project_field_named(&iovec, "iov_base")?;
584            let iov_base_ptr = this.read_pointer(&iov_base_field)?;
585
586            buffers.push((iov_base_ptr, iov_len));
587        }
588
589        let total_bytes = buffers.iter().map(|(_, len)| len).sum::<u64>();
590
591        // Allocate a temporary buffer which has the combined size of all buffers provided in `iov`.
592        let tmp_ptr: Pointer = this
593            .allocate_ptr(
594                Size::from_bytes(total_bytes),
595                Align::ONE,
596                MemoryKind::Stack,
597                AllocInit::Uninit,
598            )?
599            .into();
600
601        // Copy the bytes from all buffers provided in `iov` into the temporary buffer.
602        // We start at the first buffer and then continue buffer by buffer.
603        let mut bytes_copied: u64 = 0;
604        for (buffer_ptr, buffer_len) in buffers {
605            // Offset temporary buffer by the amount of bytes we already copied from previous buffers.
606            let tmp_ptr_with_offset =
607                this.ptr_offset_inbounds(tmp_ptr, i64::try_from(bytes_copied).unwrap())?;
608
609            this.mem_copy(
610                buffer_ptr,
611                tmp_ptr_with_offset,
612                Size::from_bytes(buffer_len),
613                // The buffers are guaranteed to not overlap because we just newly allocated
614                // the `tmp_ptr`, and `tmp_ptr_with_offset` is guaranteed to be
615                // within those boundaries.
616                true,
617            )?;
618
619            bytes_copied = bytes_copied.strict_add(buffer_len);
620        }
621
622        let dest = dest.clone();
623        // Write bytes from the temporary buffer. This ensures the write is atomic.
624        this.write_to_fd(
625            fd,
626            tmp_ptr,
627            usize::try_from(total_bytes).unwrap(),
628            offset,
629            callback!(
630                @capture<'tcx> {
631                    tmp_ptr: Pointer,
632                    dest: MPlaceTy<'tcx>,
633                }
634                |this, result: Result<usize, IoError>| {
635                    this.deallocate_ptr(tmp_ptr, None, MemoryKind::Stack)?;
636                    match result {
637                        Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
638                        Err(e) => this.set_errno_and_return_neg1(e, &dest)
639                    }
640            }),
641        )
642    }
643}
644
645impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
646trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
647    /// Read `len` bytes from the `fd` file description at `offset` into the buffer
648    /// pointed to by `ptr`.
649    /// If `offset` is [`Some`], the read occurs at the given absolute position rather
650    /// than the current file position (`read_at` semantics rather than `read`).
651    /// `finish` will be invoked when the read is done (which might be way after
652    /// this function returns as the read may block).
653    fn read_from_fd(
654        &mut self,
655        fd: DynFileDescriptionRef,
656        ptr: Pointer,
657        len: usize,
658        offset: Option<i128>,
659        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
660    ) -> InterpResult<'tcx> {
661        let this = self.eval_context_mut();
662
663        // Handle the zero-sized case. The man page says:
664        // > If count is zero, read() may detect the errors described below.  In the absence of any
665        // > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
666        // > and has no other effects.
667        if len == 0 {
668            return finish.call(this, Ok(0));
669        }
670
671        // Non-deterministically decide to further reduce the length, simulating a partial read (but
672        // never to 0, that would indicate EOF).
673        let len = if this.machine.short_fd_operations
674            && fd.short_fd_operations()
675            && len >= 2
676            && this.machine.rng.get_mut().random()
677        {
678            len / 2 // since `len` is at least 2, the result is still at least 1
679        } else {
680            len
681        };
682
683        match offset {
684            None => fd.read(this.machine.communicate(), ptr, len, this, finish)?,
685            Some(offset) => {
686                let Ok(offset) = u64::try_from(offset) else {
687                    return finish.call(this, Err(LibcError("EINVAL")));
688                };
689                fd.as_unix(this).pread(
690                    this.machine.communicate(),
691                    offset,
692                    ptr,
693                    len,
694                    this,
695                    finish,
696                )?
697            }
698        };
699        interp_ok(())
700    }
701
702    /// Write `len` bytes at `offset` from the buffer pointed to by `ptr` into the `fd`
703    /// file description.
704    /// If `offset` is [`Some`], the write occurs at the given absolute position rather
705    /// than the current file position (`write_at` semantics rather than `write`).
706    /// `finish` will be invoked when the write is done (which might be way after
707    /// this function returns as the write may block).
708    fn write_to_fd(
709        &mut self,
710        fd: DynFileDescriptionRef,
711        ptr: Pointer,
712        len: usize,
713        offset: Option<i128>,
714        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
715    ) -> InterpResult<'tcx> {
716        let this = self.eval_context_mut();
717
718        // Handle the zero-sized case. The man page says:
719        // > If count is zero and fd refers to a regular file, then write() may return a failure
720        // > status if one of the errors below is detected.  If no errors are detected, or error
721        // > detection is not performed, 0 is returned without causing any other effect.   If  count
722        // > is  zero  and  fd refers to a file other than a regular file, the results are not
723        // > specified.
724        if len == 0 {
725            // For now let's not open the can of worms of what exactly "not specified" could mean...
726            return finish.call(this, Ok(0));
727        }
728
729        // Non-deterministically decide to further reduce the length, simulating a partial write.
730        // We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
731        // but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
732        let len = if this.machine.short_fd_operations
733            && fd.short_fd_operations()
734            && len >= 2
735            && this.machine.rng.get_mut().random()
736        {
737            len / 2
738        } else {
739            len
740        };
741
742        match offset {
743            None => fd.write(this.machine.communicate(), ptr, len, this, finish)?,
744            Some(offset) => {
745                let Ok(offset) = u64::try_from(offset) else {
746                    return finish.call(this, Err(LibcError("EINVAL")));
747                };
748                fd.as_unix(this).pwrite(
749                    this.machine.communicate(),
750                    ptr,
751                    len,
752                    offset,
753                    this,
754                    finish,
755                )?
756            }
757        };
758        interp_ok(())
759    }
760}