Skip to main content

miri/shims/unix/
fs.rs

1//! File and file system access
2
3use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, DirBuilder, File, FileTimes, FileType, OpenOptions, TryLockError};
6use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7use std::path::{self, Path};
8use std::time::SystemTime;
9
10use rustc_abi::{FieldIdx, Size};
11use rustc_data_structures::either::Either;
12use rustc_data_structures::fx::FxHashMap;
13use rustc_target::spec::Os;
14
15use self::shims::time::system_time_to_duration;
16use crate::shims::files::FileHandle;
17use crate::shims::os_str::bytes_to_os_str;
18use crate::shims::sig::Varargs;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22/// An open directory, tracked by DirHandler.
23#[derive(Debug)]
24struct OpenDir {
25    /// The "special" entries that must still be yielded by the iterator.
26    /// Used for `.` and `..`.
27    special_entries: Vec<&'static str>,
28    /// The directory reader on the host.
29    read_dir: fs::ReadDir,
30    /// The most recent entry returned by readdir().
31    /// Will be freed by the next call.
32    entry: Option<Pointer>,
33}
34
35impl OpenDir {
36    fn new(read_dir: fs::ReadDir) -> Self {
37        Self { special_entries: vec!["..", "."], read_dir, entry: None }
38    }
39
40    fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
41        if let Some(special) = self.special_entries.pop() {
42            return Some(Ok(Either::Right(special)));
43        }
44        let entry = self.read_dir.next()?;
45        Some(entry.map(Either::Left))
46    }
47}
48
49#[derive(Debug)]
50struct DirEntry {
51    name: OsString,
52    ino: u64,
53    d_type: i32,
54}
55
56/// What a `futimens` `timespec` asks for: leave the timestamp alone (`UTIME_OMIT`) or set it.
57#[derive(Copy, Clone)]
58enum TimeUpdate {
59    Omit,
60    Set(SystemTime),
61}
62
63impl UnixFileDescription for FileHandle {
64    fn pread<'tcx>(
65        &self,
66        communicate_allowed: bool,
67        offset: u64,
68        ptr: Pointer,
69        len: usize,
70        ecx: &mut MiriInterpCx<'tcx>,
71        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
72    ) -> InterpResult<'tcx> {
73        assert!(communicate_allowed, "isolation should have prevented even opening a file");
74        if !self.readable {
75            return finish.call(ecx, Err(LibcError("EBADF")));
76        }
77
78        let mut bytes = vec![0; len];
79        // Emulates pread using seek + read + seek to restore cursor position.
80        // Correctness of this emulation relies on sequential nature of Miri execution.
81        // The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
82        let file = &mut &self.file;
83        let mut f = || {
84            let cursor_pos = file.stream_position()?;
85            file.seek(SeekFrom::Start(offset))?;
86            let res = file.read(&mut bytes);
87            // Attempt to restore cursor position even if the read has failed
88            file.seek(SeekFrom::Start(cursor_pos))
89                .expect("failed to restore file position, this shouldn't be possible");
90            res
91        };
92        let result = match f() {
93            Ok(read_size) => {
94                // If reading to `bytes` did not fail, we write those bytes to the buffer.
95                // Crucially, if fewer than `bytes.len()` bytes were read, only write
96                // that much into the output buffer!
97                ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
98                Ok(read_size)
99            }
100            Err(e) => Err(IoError::HostError(e)),
101        };
102        finish.call(ecx, result)
103    }
104
105    fn pwrite<'tcx>(
106        &self,
107        communicate_allowed: bool,
108        ptr: Pointer,
109        len: usize,
110        offset: u64,
111        ecx: &mut MiriInterpCx<'tcx>,
112        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
113    ) -> InterpResult<'tcx> {
114        assert!(communicate_allowed, "isolation should have prevented even opening a file");
115        if !self.writable {
116            return finish.call(ecx, Err(LibcError("EBADF")));
117        }
118
119        // Emulates pwrite using seek + write + seek to restore cursor position.
120        // Correctness of this emulation relies on sequential nature of Miri execution.
121        // The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
122        let file = &mut &self.file;
123        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
124        let mut f = || {
125            let cursor_pos = file.stream_position()?;
126            file.seek(SeekFrom::Start(offset))?;
127            let res = file.write(bytes);
128            // Attempt to restore cursor position even if the write has failed
129            file.seek(SeekFrom::Start(cursor_pos))
130                .expect("failed to restore file position, this shouldn't be possible");
131            res
132        };
133        let result = f();
134        finish.call(ecx, result.map_err(IoError::HostError))
135    }
136
137    fn flock<'tcx>(
138        &self,
139        communicate_allowed: bool,
140        op: FlockOp,
141    ) -> InterpResult<'tcx, io::Result<()>> {
142        assert!(communicate_allowed, "isolation should have prevented even opening a file");
143
144        use FlockOp::*;
145        // We must not block the interpreter loop, so we always `try_lock`.
146        let (res, nonblocking) = match op {
147            SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
148            ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
149            Unlock => {
150                return interp_ok(self.file.unlock());
151            }
152        };
153
154        match res {
155            Ok(()) => interp_ok(Ok(())),
156            Err(TryLockError::Error(err)) => interp_ok(Err(err)),
157            Err(TryLockError::WouldBlock) =>
158                if nonblocking {
159                    interp_ok(Err(ErrorKind::WouldBlock.into()))
160                } else {
161                    throw_unsup_format!("blocking `flock` is not currently supported");
162                },
163        }
164    }
165}
166
167/// The table of open directories.
168/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything
169/// is a file, except a directory is not?
170#[derive(Debug)]
171pub struct DirTable {
172    /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
173    /// and closedir.
174    ///
175    /// When opendir is called, a directory iterator is created on the host for the target
176    /// directory, and an entry is stored in this hash map, indexed by an ID which represents
177    /// the directory stream. When readdir is called, the directory stream ID is used to look up
178    /// the corresponding ReadDir iterator from this map, and information from the next
179    /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
180    /// the map.
181    streams: FxHashMap<u64, OpenDir>,
182    /// ID number to be used by the next call to opendir
183    next_id: u64,
184}
185
186impl DirTable {
187    #[expect(clippy::arithmetic_side_effects)]
188    fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
189        let id = self.next_id;
190        self.next_id += 1;
191        self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
192        id
193    }
194}
195
196impl Default for DirTable {
197    fn default() -> DirTable {
198        DirTable {
199            streams: FxHashMap::default(),
200            // Skip 0 as an ID, because it looks like a null pointer to libc
201            next_id: 1,
202        }
203    }
204}
205
206impl VisitProvenance for DirTable {
207    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
208        let DirTable { streams, next_id: _ } = self;
209
210        for dir in streams.values() {
211            dir.entry.visit_provenance(visit);
212        }
213    }
214}
215
216fn maybe_sync_file(
217    file: &File,
218    writable: bool,
219    operation: fn(&File) -> std::io::Result<()>,
220) -> std::io::Result<i32> {
221    if !writable && cfg!(windows) {
222        // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
223        // for writing. (FlushFileBuffers requires that the file handle have the
224        // GENERIC_WRITE right)
225        Ok(0i32)
226    } else {
227        let result = operation(file);
228        result.map(|_| 0i32)
229    }
230}
231
232impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
233trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
234    /// Decode one `futimens` `timespec`, handling the `UTIME_NOW`/`UTIME_OMIT` `tv_nsec` values.
235    /// `None` means the `timespec` is invalid and the caller should report `EINVAL`.
236    fn parse_utimens_timespec(
237        &self,
238        tp: &MPlaceTy<'tcx>,
239    ) -> InterpResult<'tcx, Option<TimeUpdate>> {
240        let this = self.eval_context_ref();
241        // `UTIME_NOW` reads the host clock, which we must not do under isolation.
242        assert!(this.machine.communicate(), "isolation should have prevented reaching this");
243
244        // `tv_nsec` and the `UTIME_*` constants are `c_long`, i.e. the target's `isize`.
245        let nsec_place = this.project_field(tp, FieldIdx::ONE)?;
246        let nsec = this.read_scalar(&nsec_place)?.to_target_isize(this)?;
247
248        if nsec == this.eval_libc("UTIME_OMIT").to_target_isize(this)? {
249            return interp_ok(Some(TimeUpdate::Omit));
250        }
251        if nsec == this.eval_libc("UTIME_NOW").to_target_isize(this)? {
252            return interp_ok(Some(TimeUpdate::Set(SystemTime::now())));
253        }
254
255        let Some(duration) = this.read_timespec(tp)? else {
256            return interp_ok(None);
257        };
258        interp_ok(SystemTime::UNIX_EPOCH.checked_add(duration).map(TimeUpdate::Set))
259    }
260
261    fn write_stat_buf(
262        &mut self,
263        metadata: FileMetadata,
264        buf_op: &OpTy<'tcx>,
265    ) -> InterpResult<'tcx, i32> {
266        let this = self.eval_context_mut();
267
268        let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
269        let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
270        let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
271
272        // We do *not* use `deref_pointer_as` here since determining the right pointee type
273        // is highly non-trivial: it depends on which exact alias of the function was invoked
274        // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level
275        // which can be different between the libc used by std and the libc used by everyone else.
276        let buf = this.deref_pointer(buf_op)?;
277
278        this.write_int_fields_named(
279            &[
280                ("st_dev", metadata.dev.unwrap_or(0).into()),
281                ("st_mode", metadata.mode.into()),
282                ("st_nlink", metadata.nlink.unwrap_or(0).into()),
283                ("st_ino", metadata.ino.unwrap_or(0).into()),
284                ("st_uid", metadata.uid.unwrap_or(0).into()),
285                ("st_gid", metadata.gid.unwrap_or(0).into()),
286                ("st_rdev", 0),
287                ("st_atime", access_sec.into()),
288                ("st_atime_nsec", access_nsec.into()),
289                ("st_mtime", modified_sec.into()),
290                ("st_mtime_nsec", modified_nsec.into()),
291                ("st_ctime", 0),
292                ("st_ctime_nsec", 0),
293                ("st_size", metadata.size.into()),
294                ("st_blocks", metadata.blocks.unwrap_or(0).into()),
295                ("st_blksize", metadata.blksize.unwrap_or(0).into()),
296            ],
297            &buf,
298        )?;
299
300        if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
301            this.write_int_fields_named(
302                &[
303                    ("st_birthtime", created_sec.into()),
304                    ("st_birthtime_nsec", created_nsec.into()),
305                    ("st_flags", 0),
306                    ("st_gen", 0),
307                ],
308                &buf,
309            )?;
310        }
311
312        if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
313            let st_fstype = this.project_field_named(&buf, "st_fstype")?;
314            // This is an array; write 0 into first element so that it encodes the empty string.
315            this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
316        }
317
318        interp_ok(0)
319    }
320
321    fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
322        #[cfg(unix)]
323        use std::os::unix::fs::FileTypeExt;
324
325        let this = self.eval_context_ref();
326        match file_type {
327            Ok(file_type) => {
328                match () {
329                    _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
330                    _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
331                    _ if file_type.is_symlink() =>
332                        interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
333                    // Certain file types are only supported when the host is a Unix system.
334                    #[cfg(unix)]
335                    _ if file_type.is_block_device() =>
336                        interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
337                    #[cfg(unix)]
338                    _ if file_type.is_char_device() =>
339                        interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
340                    #[cfg(unix)]
341                    _ if file_type.is_fifo() =>
342                        interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
343                    #[cfg(unix)]
344                    _ if file_type.is_socket() =>
345                        interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
346                    // Fallback
347                    _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
348                }
349            }
350            Err(_) => {
351                // Fallback on error
352                interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
353            }
354        }
355    }
356
357    fn dir_entry_fields(
358        &self,
359        entry: Either<fs::DirEntry, &'static str>,
360    ) -> InterpResult<'tcx, DirEntry> {
361        let this = self.eval_context_ref();
362        interp_ok(match entry {
363            Either::Left(dir_entry) => {
364                DirEntry {
365                    name: dir_entry.file_name(),
366                    d_type: this.file_type_to_d_type(dir_entry.file_type())?,
367                    // If the host is a Unix system, fill in the inode number with its real value.
368                    // If not, use 0 as a fallback value.
369                    #[cfg(unix)]
370                    ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
371                    #[cfg(not(unix))]
372                    ino: 0u64,
373                }
374            }
375            Either::Right(special) =>
376                DirEntry {
377                    name: special.into(),
378                    d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
379                    ino: 0,
380                },
381        })
382    }
383
384    #[cfg(unix)]
385    fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
386        use std::os::unix::fs::PermissionsExt;
387        interp_ok(fs::Permissions::from_mode(mode))
388    }
389
390    #[cfg(not(unix))]
391    fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> {
392        throw_unsup_format!("setting file permissions is only supported on Unix hosts")
393    }
394}
395
396impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
397pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398    fn open(
399        &mut self,
400        path_raw: &OpTy<'tcx>,
401        flag: &OpTy<'tcx>,
402        varargs: Varargs<'tcx, '_>,
403    ) -> InterpResult<'tcx, Scalar> {
404        let this = self.eval_context_mut();
405
406        let path_raw = this.read_pointer(path_raw)?;
407        let flag = this.read_scalar(flag)?.to_i32()?;
408
409        let path = this.read_path_from_c_str(path_raw)?;
410        // Files in `/proc` won't work properly.
411        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
412            && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
413        {
414            this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
415        }
416
417        // We will "subtract" supported flags from this and at the end check that no bits are left.
418        let mut flag = flag;
419
420        let mut options = OpenOptions::new();
421
422        let o_rdonly = this.eval_libc_i32("O_RDONLY");
423        let o_wronly = this.eval_libc_i32("O_WRONLY");
424        let o_rdwr = this.eval_libc_i32("O_RDWR");
425        // The first two bits of the flag correspond to the access mode in linux, macOS and
426        // windows. We need to check that in fact the access mode flags for the current target
427        // only use these two bits, otherwise we are in an unsupported target and should error.
428        if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
429            throw_unsup_format!("access mode flags on this target are unsupported");
430        }
431        let mut writable = true;
432        let mut readable = true;
433
434        // Now we check the access mode
435        let access_mode = flag & 0b11;
436        flag &= !access_mode;
437
438        if access_mode == o_rdonly {
439            writable = false;
440            options.read(true);
441        } else if access_mode == o_wronly {
442            readable = false;
443            options.write(true);
444        } else if access_mode == o_rdwr {
445            options.read(true).write(true);
446        } else {
447            throw_unsup_format!("unsupported access mode {:#x}", access_mode);
448        }
449
450        let o_append = this.eval_libc_i32("O_APPEND");
451        if flag & o_append == o_append {
452            flag &= !o_append;
453            options.append(true);
454        }
455        let o_trunc = this.eval_libc_i32("O_TRUNC");
456        if flag & o_trunc == o_trunc {
457            flag &= !o_trunc;
458            options.truncate(true);
459        }
460        let o_creat = this.eval_libc_i32("O_CREAT");
461        if flag & o_creat == o_creat {
462            flag &= !o_creat;
463            // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but
464            // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
465            // (see https://github.com/rust-lang/rust/issues/71915).
466            let ([mode], _) = this.check_varargs(
467                shim_varargs![libc::mode_t],
468                varargs,
469                "open(pathname, O_CREAT, ...)",
470            )?;
471            let mode = this.read_scalar(mode)?.to_u32()?;
472
473            #[cfg(unix)]
474            {
475                // Support all modes on UNIX host
476                use std::os::unix::fs::OpenOptionsExt;
477                options.mode(mode);
478            }
479            #[cfg(not(unix))]
480            {
481                // Only support default mode for non-UNIX (i.e. Windows) host
482                if mode != 0o666 {
483                    throw_unsup_format!(
484                        "non-default mode 0o{:o} is not supported on non-Unix hosts",
485                        mode
486                    );
487                }
488            }
489
490            let o_excl = this.eval_libc_i32("O_EXCL");
491            if flag & o_excl == o_excl {
492                flag &= !o_excl;
493                options.create_new(true);
494            } else {
495                options.create(true);
496            }
497        }
498        let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
499        if flag & o_cloexec == o_cloexec {
500            flag &= !o_cloexec;
501            // We do not need to do anything for this flag because `std` already sets it.
502            // (Technically we do not support *not* setting this flag, but we ignore that.)
503        }
504        if this.tcx.sess.target.os == Os::Linux {
505            let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
506            if flag & o_tmpfile == o_tmpfile {
507                // if the flag contains `O_TMPFILE` then we return a graceful error
508                return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
509            }
510        }
511
512        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
513        if flag & o_nofollow == o_nofollow {
514            flag &= !o_nofollow;
515            #[cfg(unix)]
516            {
517                use std::os::unix::fs::OpenOptionsExt;
518                options.custom_flags(libc::O_NOFOLLOW);
519            }
520            // Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:
521            // the path could change between us checking it here and the later call to `open`.
522            // But it's good enough for Miri purposes.
523            #[cfg(not(unix))]
524            {
525                // O_NOFOLLOW only fails when the trailing component is a symlink;
526                // the entire rest of the path can still contain symlinks.
527                if path.is_symlink() {
528                    return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
529                }
530            }
531        }
532
533        // If `flag` has any bits left set, those are not supported.
534        if flag != 0 {
535            throw_unsup_format!("unsupported flags {:#x}", flag);
536        }
537
538        // Reject if isolation is enabled.
539        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
540            this.reject_in_isolation("`open`", reject_with)?;
541            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
542        }
543
544        let fd = options
545            .open(path)
546            .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
547
548        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
549    }
550
551    fn lseek(
552        &mut self,
553        fd_num: i32,
554        offset: i128,
555        whence: i32,
556        dest: &MPlaceTy<'tcx>,
557    ) -> InterpResult<'tcx> {
558        let this = self.eval_context_mut();
559
560        // Isolation check is done via `FileDescription` trait.
561
562        let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
563            if offset < 0 {
564                // Negative offsets return `EINVAL`.
565                return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
566            } else {
567                SeekFrom::Start(u64::try_from(offset).unwrap())
568            }
569        } else if whence == this.eval_libc_i32("SEEK_CUR") {
570            SeekFrom::Current(i64::try_from(offset).unwrap())
571        } else if whence == this.eval_libc_i32("SEEK_END") {
572            SeekFrom::End(i64::try_from(offset).unwrap())
573        } else {
574            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
575        };
576
577        let communicate = this.machine.communicate();
578
579        let Some(fd) = this.machine.fds.get(fd_num) else {
580            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
581        };
582        let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
583        drop(fd);
584
585        let result = this.try_unwrap_io_result(result)?;
586        this.write_int(result, dest)?;
587        interp_ok(())
588    }
589
590    fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
591        let this = self.eval_context_mut();
592
593        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
594
595        // Reject if isolation is enabled.
596        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
597            this.reject_in_isolation("`unlink`", reject_with)?;
598            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
599        }
600
601        let result = fs::remove_file(path).map(|_| 0);
602        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
603    }
604
605    fn symlink(
606        &mut self,
607        target_op: &OpTy<'tcx>,
608        linkpath_op: &OpTy<'tcx>,
609    ) -> InterpResult<'tcx, Scalar> {
610        #[cfg(unix)]
611        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
612            std::os::unix::fs::symlink(src, dst)
613        }
614
615        #[cfg(windows)]
616        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
617            use std::os::windows::fs;
618            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
619        }
620
621        let this = self.eval_context_mut();
622        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
623        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
624
625        // Reject if isolation is enabled.
626        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
627            this.reject_in_isolation("`symlink`", reject_with)?;
628            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
629        }
630
631        let result = create_link(&target, &linkpath).map(|_| 0);
632        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
633    }
634
635    fn linkat(
636        &mut self,
637        oldfd_op: &OpTy<'tcx>,
638        oldpath_op: &OpTy<'tcx>,
639        newfd_op: &OpTy<'tcx>,
640        newpath_op: &OpTy<'tcx>,
641        flags_op: &OpTy<'tcx>,
642    ) -> InterpResult<'tcx, Scalar> {
643        let this = self.eval_context_mut();
644
645        // Load all arguments
646        let flags = this.read_scalar(flags_op)?.to_i32()?;
647        let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
648        let newfd = this.read_scalar(newfd_op)?.to_i32()?;
649        let oldpath_ptr = this.read_pointer(oldpath_op)?;
650        let newpath_ptr = this.read_pointer(newpath_op)?;
651
652        // Relevant libc constants
653        let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
654
655        // Reject if isolation is enabled.
656        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
657            this.reject_in_isolation("`linkat`", reject_with)?;
658            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
659        }
660
661        // Read flags - only support 0.
662        if flags != 0 {
663            throw_unsup_format!("unsupported linkat flags {:#x}", flags);
664        }
665
666        // Resolve oldpath
667        if oldfd != at_fdcwd {
668            throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
669        }
670        if oldpath_ptr == Pointer::null() {
671            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
672        }
673        let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
674
675        // Resolve newpath
676        if newfd != at_fdcwd {
677            throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
678        }
679        if newpath_ptr == Pointer::null() {
680            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
681        }
682        let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
683
684        let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
685        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
686    }
687
688    fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
689        let this = self.eval_context_mut();
690
691        if !matches!(
692            &this.tcx.sess.target.os,
693            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
694        ) {
695            panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
696        }
697
698        let path_scalar = this.read_pointer(path_op)?;
699        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
700
701        // Reject if isolation is enabled.
702        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
703            this.reject_in_isolation("`stat`", reject_with)?;
704            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
705        }
706
707        // `stat` always follows symlinks.
708        let metadata = match FileMetadata::from_path(this, &path, true)? {
709            Ok(metadata) => metadata,
710            Err(err) => return this.set_errno_and_return_neg1_i32(err),
711        };
712
713        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
714    }
715
716    // `lstat` is used to get symlink metadata.
717    fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
718        let this = self.eval_context_mut();
719
720        if !matches!(
721            &this.tcx.sess.target.os,
722            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
723        ) {
724            panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
725        }
726
727        let path_scalar = this.read_pointer(path_op)?;
728        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
729
730        // Reject if isolation is enabled.
731        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
732            this.reject_in_isolation("`lstat`", reject_with)?;
733            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
734        }
735
736        let metadata = match FileMetadata::from_path(this, &path, false)? {
737            Ok(metadata) => metadata,
738            Err(err) => return this.set_errno_and_return_neg1_i32(err),
739        };
740
741        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
742    }
743
744    fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
745        let this = self.eval_context_mut();
746
747        if !matches!(
748            &this.tcx.sess.target.os,
749            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
750        ) {
751            panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
752        }
753
754        let fd = this.read_scalar(fd_op)?.to_i32()?;
755
756        // Reject if isolation is enabled.
757        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
758            this.reject_in_isolation("`fstat`", reject_with)?;
759            // Set error code as "EBADF" (bad fd)
760            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
761        }
762
763        let metadata = match FileMetadata::from_fd_num(this, fd)? {
764            Ok(metadata) => metadata,
765            Err(err) => return this.set_errno_and_return_neg1_i32(err),
766        };
767        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
768    }
769
770    fn linux_statx(
771        &mut self,
772        dirfd_op: &OpTy<'tcx>,    // Should be an `int`
773        pathname_op: &OpTy<'tcx>, // Should be a `const char *`
774        flags_op: &OpTy<'tcx>,    // Should be an `int`
775        mask_op: &OpTy<'tcx>,     // Should be an `unsigned int`
776        statxbuf_op: &OpTy<'tcx>, // Should be a `struct statx *`
777    ) -> InterpResult<'tcx, Scalar> {
778        let this = self.eval_context_mut();
779
780        this.assert_target_os(Os::Linux, "statx");
781
782        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
783        let pathname_ptr = this.read_pointer(pathname_op)?;
784        let flags = this.read_scalar(flags_op)?.to_i32()?;
785        let _mask = this.read_scalar(mask_op)?.to_u32()?;
786        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
787
788        // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
789        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
790            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
791        }
792
793        let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
794
795        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
796        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
797        let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
798        let empty_path_flag = flags & at_empty_path == at_empty_path;
799        // We only support:
800        // * interpreting `path` as an absolute directory,
801        // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
802        // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
803        // set.
804        // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
805        // found this error, please open an issue reporting it.
806        if !(path.is_absolute()
807            || dirfd == this.eval_libc_i32("AT_FDCWD")
808            || (path.as_os_str().is_empty() && empty_path_flag))
809        {
810            throw_unsup_format!(
811                "using statx is only supported with absolute paths, relative paths with the file \
812                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
813                file descriptor"
814            )
815        }
816
817        // Reject if isolation is enabled.
818        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
819            this.reject_in_isolation("`statx`", reject_with)?;
820            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
821                // since `path` is provided, either absolute or
822                // relative to CWD, `EACCES` is the most relevant.
823                LibcError("EACCES")
824            } else {
825                // `dirfd` is set to target file, and `path` is empty
826                // (or we would have hit the `throw_unsup_format`
827                // above). `EACCES` would violate the spec.
828                assert!(empty_path_flag);
829                LibcError("EBADF")
830            };
831            return this.set_errno_and_return_neg1_i32(ecode);
832        }
833
834        // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
835        // symbolic links.
836        let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
837
838        // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
839        // represented by dirfd, whether it's a directory or otherwise.
840        let metadata = if path.as_os_str().is_empty() && empty_path_flag {
841            FileMetadata::from_fd_num(this, dirfd)?
842        } else {
843            FileMetadata::from_path(this, &path, follow_symlink)?
844        };
845        let metadata = match metadata {
846            Ok(metadata) => metadata,
847            Err(err) => return this.set_errno_and_return_neg1_i32(err),
848        };
849
850        // The `_mask_op` parameter specifies the file information that the caller requested.
851        // However, `statx` is allowed to return information that was not requested or to not
852        // return information that was requested. This `mask` represents the information we can
853        // actually provide for any target.
854        let mut mask = this.eval_libc_u32("STATX_TYPE")
855            | this.eval_libc_u32("STATX_MODE")
856            | this.eval_libc_u32("STATX_SIZE");
857
858        // Check which pieces of metadata we acquired, and set the appropriate flags in the mask.
859        if metadata.ino.is_some() {
860            mask |= this.eval_libc_u32("STATX_INO");
861        }
862        if metadata.nlink.is_some() {
863            mask |= this.eval_libc_u32("STATX_NLINK");
864        }
865        if metadata.uid.is_some() {
866            mask |= this.eval_libc_u32("STATX_UID");
867        }
868        if metadata.gid.is_some() {
869            mask |= this.eval_libc_u32("STATX_GID");
870        }
871        if metadata.blocks.is_some() {
872            mask |= this.eval_libc_u32("STATX_BLOCKS");
873        }
874
875        // We need to set the corresponding bits of `mask` if the access, creation and modification
876        // times were available. Otherwise we let them be zero.
877        let (access_sec, access_nsec) = metadata
878            .accessed
879            .map(|tup| {
880                mask |= this.eval_libc_u32("STATX_ATIME");
881                interp_ok(tup)
882            })
883            .unwrap_or_else(|| interp_ok((0, 0)))?;
884
885        let (created_sec, created_nsec) = metadata
886            .created
887            .map(|tup| {
888                mask |= this.eval_libc_u32("STATX_BTIME");
889                interp_ok(tup)
890            })
891            .unwrap_or_else(|| interp_ok((0, 0)))?;
892
893        let (modified_sec, modified_nsec) = metadata
894            .modified
895            .map(|tup| {
896                mask |= this.eval_libc_u32("STATX_MTIME");
897                interp_ok(tup)
898            })
899            .unwrap_or_else(|| interp_ok((0, 0)))?;
900
901        // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
902        this.write_int_fields_named(
903            &[
904                ("stx_mask", mask.into()),
905                ("stx_mode", metadata.mode.into()),
906                ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
907                ("stx_attributes", 0),
908                ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
909                ("stx_uid", metadata.uid.unwrap_or(0).into()),
910                ("stx_gid", metadata.gid.unwrap_or(0).into()),
911                ("stx_ino", metadata.ino.unwrap_or(0).into()),
912                ("stx_size", metadata.size.into()),
913                ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
914                ("stx_attributes_mask", 0),
915                ("stx_rdev_major", 0),
916                ("stx_rdev_minor", 0),
917                ("stx_dev_major", 0),
918                ("stx_dev_minor", 0),
919            ],
920            &statxbuf,
921        )?;
922        #[rustfmt::skip]
923        this.write_int_fields_named(
924            &[
925                ("tv_sec", access_sec.into()),
926                ("tv_nsec", access_nsec.into()),
927            ],
928            &this.project_field_named(&statxbuf, "stx_atime")?,
929        )?;
930        #[rustfmt::skip]
931        this.write_int_fields_named(
932            &[
933                ("tv_sec", created_sec.into()),
934                ("tv_nsec", created_nsec.into()),
935            ],
936            &this.project_field_named(&statxbuf, "stx_btime")?,
937        )?;
938        #[rustfmt::skip]
939        this.write_int_fields_named(
940            &[
941                ("tv_sec", 0.into()),
942                ("tv_nsec", 0.into()),
943            ],
944            &this.project_field_named(&statxbuf, "stx_ctime")?,
945        )?;
946        #[rustfmt::skip]
947        this.write_int_fields_named(
948            &[
949                ("tv_sec", modified_sec.into()),
950                ("tv_nsec", modified_nsec.into()),
951            ],
952            &this.project_field_named(&statxbuf, "stx_mtime")?,
953        )?;
954
955        interp_ok(Scalar::from_i32(0))
956    }
957
958    fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
959        let this = self.eval_context_mut();
960
961        let path_ptr = this.read_pointer(path_op)?;
962        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
963
964        if this.ptr_is_null(path_ptr)? {
965            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
966        }
967        let path = this.read_path_from_c_str(path_ptr)?;
968
969        // Reject if isolation is enabled.
970        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
971            this.reject_in_isolation("`chmod`", reject_with)?;
972            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
973        }
974
975        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
976        if let Err(err) = fs::set_permissions(path, permissions) {
977            return this.set_errno_and_return_neg1_i32(err);
978        }
979
980        interp_ok(Scalar::from_i32(0))
981    }
982
983    fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
984        let this = self.eval_context_mut();
985
986        let fd_num = this.read_scalar(fd_op)?.to_i32()?;
987        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
988
989        let Some(fd) = this.machine.fds.get(fd_num) else {
990            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
991        };
992        let Some(file) = fd.downcast::<FileHandle>() else {
993            // The docs don't talk about what happens for non-regular files...
994            throw_unsup_format!("`fchmod` is only supported on regular files")
995        };
996        if !file.writable && !file.readable {
997            // Apparently, `fchmod` on a read-only file is fine. But let's not allow it on a
998            // path-only file.
999            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1000        }
1001        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1002
1003        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1004        if let Err(err) = file.file.set_permissions(permissions) {
1005            return this.set_errno_and_return_neg1_i32(err);
1006        }
1007
1008        interp_ok(Scalar::from_i32(0))
1009    }
1010
1011    fn rename(
1012        &mut self,
1013        oldpath_op: &OpTy<'tcx>,
1014        newpath_op: &OpTy<'tcx>,
1015    ) -> InterpResult<'tcx, Scalar> {
1016        let this = self.eval_context_mut();
1017
1018        let oldpath_ptr = this.read_pointer(oldpath_op)?;
1019        let newpath_ptr = this.read_pointer(newpath_op)?;
1020
1021        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1022            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1023        }
1024
1025        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1026        let newpath = this.read_path_from_c_str(newpath_ptr)?;
1027
1028        // Reject if isolation is enabled.
1029        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1030            this.reject_in_isolation("`rename`", reject_with)?;
1031            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1032        }
1033
1034        let result = fs::rename(oldpath, newpath).map(|_| 0);
1035
1036        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1037    }
1038
1039    fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1040        let this = self.eval_context_mut();
1041
1042        #[cfg_attr(not(unix), allow(unused_variables))]
1043        let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1044            u32::from(this.read_scalar(mode_op)?.to_u16()?)
1045        } else {
1046            this.read_scalar(mode_op)?.to_u32()?
1047        };
1048
1049        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1050
1051        // Reject if isolation is enabled.
1052        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1053            this.reject_in_isolation("`mkdir`", reject_with)?;
1054            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1055        }
1056
1057        #[cfg_attr(not(unix), allow(unused_mut))]
1058        let mut builder = DirBuilder::new();
1059
1060        // If the host supports it, forward on the mode of the directory
1061        // (i.e. permission bits and the sticky bit)
1062        #[cfg(unix)]
1063        {
1064            use std::os::unix::fs::DirBuilderExt;
1065            builder.mode(mode);
1066        }
1067
1068        let result = builder.create(path).map(|_| 0i32);
1069
1070        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1071    }
1072
1073    fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1074        let this = self.eval_context_mut();
1075
1076        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1077
1078        // Reject if isolation is enabled.
1079        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1080            this.reject_in_isolation("`rmdir`", reject_with)?;
1081            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1082        }
1083
1084        let result = fs::remove_dir(path).map(|_| 0i32);
1085
1086        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1087    }
1088
1089    fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1090        let this = self.eval_context_mut();
1091
1092        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1093
1094        // Reject if isolation is enabled.
1095        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1096            this.reject_in_isolation("`opendir`", reject_with)?;
1097            this.set_last_error(LibcError("EACCES"))?;
1098            return interp_ok(Scalar::null_ptr(this));
1099        }
1100
1101        let result = fs::read_dir(name);
1102
1103        match result {
1104            Ok(dir_iter) => {
1105                let id = this.machine.dirs.insert_new(dir_iter);
1106
1107                // The libc API for opendir says that this method returns a pointer to an opaque
1108                // structure, but we are returning an ID number. Thus, pass it as a scalar of
1109                // pointer width.
1110                interp_ok(Scalar::from_target_usize(id, this))
1111            }
1112            Err(e) => {
1113                this.set_last_error(e)?;
1114                interp_ok(Scalar::null_ptr(this))
1115            }
1116        }
1117    }
1118
1119    fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1120        let this = self.eval_context_mut();
1121
1122        if !matches!(
1123            &this.tcx.sess.target.os,
1124            Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1125        ) {
1126            throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1127        }
1128
1129        let dirp = this.read_target_usize(dirp_op)?;
1130
1131        // Reject if isolation is enabled.
1132        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1133            this.reject_in_isolation("`readdir`", reject_with)?;
1134            this.set_last_error(LibcError("EBADF"))?;
1135            this.write_null(dest)?;
1136            return interp_ok(());
1137        }
1138
1139        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1140            err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1141        })?;
1142
1143        let entry = match open_dir.next_host_entry() {
1144            Some(Ok(dir_entry)) => {
1145                let dir_entry = this.dir_entry_fields(dir_entry)?;
1146
1147                // Write the directory entry into a newly allocated buffer.
1148                // The name is written with write_bytes, while the rest of the
1149                // dirent64 (or dirent) struct is written using write_int_fields.
1150
1151                // For reference:
1152                // On Linux:
1153                // pub struct dirent64 {
1154                //     pub d_ino: ino64_t,
1155                //     pub d_off: off64_t,
1156                //     pub d_reclen: c_ushort,
1157                //     pub d_type: c_uchar,
1158                //     pub d_name: [c_char; 256],
1159                // }
1160                //
1161                // On Solaris:
1162                // pub struct dirent {
1163                //     pub d_ino: ino64_t,
1164                //     pub d_off: off64_t,
1165                //     pub d_reclen: c_ushort,
1166                //     pub d_name: [c_char; 3],
1167                // }
1168                //
1169                // On FreeBSD:
1170                // pub struct dirent {
1171                //     pub d_fileno: uint32_t,
1172                //     pub d_reclen: uint16_t,
1173                //     pub d_type: uint8_t,
1174                //     pub d_namlen: uint8_t,
1175                //     pub d_name: [c_char; 256],
1176                // }
1177                //
1178                // On macOS:
1179                // pub struct dirent {
1180                //     pub d_ino: u64,
1181                //     pub d_seekoff: u64,
1182                //     pub d_reclen: u16,
1183                //     pub d_namlen: u16,
1184                //     pub d_type: u8,
1185                //     pub d_name: [c_char; 1024],
1186                // }
1187
1188                // We just use the pointee type here since determining the right pointee type
1189                // independently is highly non-trivial: it depends on which exact alias of the
1190                // function was invoked (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also
1191                // depends on the ABI level which can be different between the libc used by std and
1192                // the libc used by everyone else.
1193                let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1194                let dirent_layout = this.layout_of(dirent_ty)?;
1195                let fields = &dirent_layout.fields;
1196                let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1197
1198                // Determine the size of the buffer we have to allocate.
1199                let mut name = dir_entry.name; // not a Path as there are no separators!
1200                name.push("\0"); // Add a NUL terminator
1201                let name_bytes = name.as_encoded_bytes();
1202                let name_len = u64::try_from(name_bytes.len()).unwrap();
1203                let size = d_name_offset.strict_add(name_len);
1204
1205                let entry = this.allocate_ptr(
1206                    Size::from_bytes(size),
1207                    dirent_layout.align.abi,
1208                    MiriMemoryKind::Runtime.into(),
1209                    AllocInit::Uninit,
1210                )?;
1211                let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1212
1213                // Write the name.
1214                // The name is not a normal field, we already computed the offset above.
1215                let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1216                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1217
1218                // Write common fields.
1219                let ino_name =
1220                    if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1221                this.write_int_fields_named(
1222                    &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1223                    &entry,
1224                )?;
1225
1226                // Write "optional" fields.
1227                if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1228                    this.write_null(&d_off)?;
1229                }
1230                if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1231                    this.write_null(&d_seekoff)?;
1232                }
1233                if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1234                    this.write_int(name_len.strict_sub(1), &d_namlen)?;
1235                }
1236                if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1237                    this.write_int(dir_entry.d_type, &d_type)?;
1238                }
1239
1240                Some(entry.ptr())
1241            }
1242            None => {
1243                // end of stream: return NULL
1244                None
1245            }
1246            Some(Err(e)) => {
1247                this.set_last_error(e)?;
1248                None
1249            }
1250        };
1251
1252        let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1253        let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1254        if let Some(old_entry) = old_entry {
1255            this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1256        }
1257
1258        this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1259        interp_ok(())
1260    }
1261
1262    fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1263        let this = self.eval_context_mut();
1264
1265        let dirp = this.read_target_usize(dirp_op)?;
1266
1267        // Reject if isolation is enabled.
1268        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1269            this.reject_in_isolation("`closedir`", reject_with)?;
1270            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1271        }
1272
1273        let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1274            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1275        };
1276        if let Some(entry) = open_dir.entry.take() {
1277            this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1278        }
1279        // We drop the `open_dir`, which will close the host dir handle.
1280        drop(open_dir);
1281
1282        interp_ok(Scalar::from_i32(0))
1283    }
1284
1285    fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1286        let this = self.eval_context_mut();
1287
1288        let Some(fd) = this.machine.fds.get(fd_num) else {
1289            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1290        };
1291        let Some(file) = fd.downcast::<FileHandle>() else {
1292            // The docs say that EINVAL is returned when the FD "does not reference a regular file
1293            // or a POSIX shared memory object" (and we don't support shmem objects).
1294            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1295        };
1296        if !file.writable {
1297            // man page says "EBADF or EINVAL", Linux seems to use EINVAL.
1298            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1299        }
1300        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1301
1302        if let Ok(length) = length.try_into() {
1303            let result = file.file.set_len(length);
1304            let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1305            interp_ok(Scalar::from_i32(result))
1306        } else {
1307            this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1308        }
1309    }
1310
1311    /// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead
1312    /// of setting `errno`.
1313    fn posix_fallocate(
1314        &mut self,
1315        fd_num: i32,
1316        offset: i64,
1317        len: i64,
1318    ) -> InterpResult<'tcx, Scalar> {
1319        let this = self.eval_context_mut();
1320
1321        // Reject if isolation is enabled.
1322        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1323            this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1324            // Return error code "EBADF" (bad fd).
1325            return interp_ok(this.eval_libc("EBADF"));
1326        }
1327
1328        match this.fallocate_impl(fd_num, offset, len)? {
1329            Ok(()) => interp_ok(Scalar::from_i32(0)),
1330            Err(e) => this.io_error_to_errnum(e),
1331        }
1332    }
1333
1334    fn linux_fallocate(
1335        &mut self,
1336        fd: i32,
1337        mode: i32,
1338        offset: i64,
1339        size: i64,
1340    ) -> InterpResult<'tcx, Scalar> {
1341        // This is mostly a copy of `posix_fallocate` except that errors are returned via errno.
1342        let this = self.eval_context_mut();
1343
1344        // Reject if isolation is enabled.
1345        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1346            this.reject_in_isolation("`fallocate`", reject_with)?;
1347            // Set error code "EBADF" (bad fd).
1348            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1349        }
1350
1351        // We only support `fallocate` as a replacement for `posix_fallocate` on linux,
1352        // so a non-default `mode` is not supported.
1353        if mode != 0 {
1354            throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1355        }
1356
1357        match this.fallocate_impl(fd, offset, size)? {
1358            Ok(()) => interp_ok(Scalar::from_i32(0)),
1359            Err(e) => this.set_errno_and_return_neg1_i32(e),
1360        }
1361    }
1362
1363    /// Shared logic between `posix_fallocate` and `linux_fallocate`.
1364    fn fallocate_impl(
1365        &mut self,
1366        fd_num: i32,
1367        offset: i64,
1368        len: i64,
1369    ) -> InterpResult<'tcx, Result<(), IoError>> {
1370        let this = self.eval_context_mut();
1371
1372        // EINVAL is returned/set when: "offset was less than 0, or len was less than or equal to 0".
1373        if offset < 0 || len <= 0 {
1374            return interp_ok(Err(LibcError("EINVAL")));
1375        }
1376
1377        let Some(fd) = this.machine.fds.get(fd_num) else {
1378            return interp_ok(Err(LibcError("EBADF")));
1379        };
1380        let Some(file) = fd.downcast::<FileHandle>() else {
1381            // Man page specifies to return ENODEV if `fd` is not a regular file.
1382            return interp_ok(Err(LibcError("ENODEV")));
1383        };
1384
1385        if !file.writable {
1386            return interp_ok(Err(LibcError("EBADF")));
1387        }
1388
1389        let current_size = match file.file.metadata() {
1390            Ok(metadata) => metadata.len(),
1391            Err(err) => return interp_ok(Err(err.into())),
1392        };
1393
1394        // Checked i64 addition, to ensure the result does not exceed the max file size.
1395        let new_size = match offset.checked_add(len) {
1396            // `new_size` is definitely non-negative, so we can cast to `u64`.
1397            Some(new_size) => u64::try_from(new_size).unwrap(),
1398            None => return interp_ok(Err(LibcError("EFBIG"))), // new size too big
1399        };
1400
1401        // If the size of the file is less than offset+size, then the file is increased to this
1402        // size; otherwise the file size is left unchanged.
1403        if current_size < new_size {
1404            match file.file.set_len(new_size) {
1405                Ok(()) => interp_ok(Ok(())),
1406                Err(err) => interp_ok(Err(err.into())),
1407            }
1408        } else {
1409            interp_ok(Ok(()))
1410        }
1411    }
1412
1413    fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1414        // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1415        // underlying disk to finish writing. In the interest of host compatibility,
1416        // we conservatively implement this with `sync_all`, which
1417        // *does* wait for the disk.
1418
1419        let this = self.eval_context_mut();
1420
1421        let fd = this.read_scalar(fd_op)?.to_i32()?;
1422
1423        self.ffullsync_fd(fd)
1424    }
1425
1426    fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1427        let this = self.eval_context_mut();
1428        let Some(fd) = this.machine.fds.get(fd_num) else {
1429            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1430        };
1431        // Only regular files support synchronization.
1432        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1433            err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1434        })?;
1435        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1436
1437        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1438        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1439    }
1440
1441    fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1442        let this = self.eval_context_mut();
1443
1444        let fd = this.read_scalar(fd_op)?.to_i32()?;
1445
1446        let Some(fd) = this.machine.fds.get(fd) else {
1447            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1448        };
1449        // Only regular files support synchronization.
1450        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1451            err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1452        })?;
1453        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1454
1455        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1456        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1457    }
1458
1459    /// `futimens(fd, times)`: set `fd`'s access/modification times. `times` is `[atime, mtime]`, or
1460    /// NULL to set both to now.
1461    fn futimens(
1462        &mut self,
1463        fd_op: &OpTy<'tcx>,
1464        times_op: &OpTy<'tcx>,
1465    ) -> InterpResult<'tcx, Scalar> {
1466        let this = self.eval_context_mut();
1467
1468        let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1469        let times_ptr = this.read_pointer(times_op)?;
1470
1471        let Some(fd) = this.machine.fds.get(fd_num) else {
1472            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1473        };
1474        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1475            err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1476        })?;
1477        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1478
1479        let (access, modified) = if this.ptr_is_null(times_ptr)? {
1480            let now = TimeUpdate::Set(SystemTime::now());
1481            (now, now)
1482        } else {
1483            let timespec = this.libc_ty_layout("timespec");
1484            let access_place = this.deref_pointer_as(times_op, timespec)?;
1485            let modified_place = access_place.offset(timespec.size, timespec, this)?;
1486            let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1487                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1488            };
1489            let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1490                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1491            };
1492            (access, modified)
1493        };
1494
1495        let mut filetimes = FileTimes::new();
1496        if let TimeUpdate::Set(access) = access {
1497            filetimes = filetimes.set_accessed(access);
1498        }
1499        if let TimeUpdate::Set(modified) = modified {
1500            filetimes = filetimes.set_modified(modified);
1501        }
1502        let result = file.file.set_times(filetimes);
1503        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1504    }
1505
1506    fn sync_file_range(
1507        &mut self,
1508        fd_op: &OpTy<'tcx>,
1509        offset_op: &OpTy<'tcx>,
1510        nbytes_op: &OpTy<'tcx>,
1511        flags_op: &OpTy<'tcx>,
1512    ) -> InterpResult<'tcx, Scalar> {
1513        let this = self.eval_context_mut();
1514
1515        let fd = this.read_scalar(fd_op)?.to_i32()?;
1516        let offset = this.read_scalar(offset_op)?.to_i64()?;
1517        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1518        let flags = this.read_scalar(flags_op)?.to_i32()?;
1519
1520        if offset < 0 || nbytes < 0 {
1521            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1522        }
1523        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1524            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1525            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1526        if flags & allowed_flags != flags {
1527            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1528        }
1529
1530        let Some(fd) = this.machine.fds.get(fd) else {
1531            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1532        };
1533        // Only regular files support synchronization.
1534        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1535            err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1536        })?;
1537        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1538
1539        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1540        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1541    }
1542
1543    fn readlink(
1544        &mut self,
1545        pathname_op: &OpTy<'tcx>,
1546        buf_op: &OpTy<'tcx>,
1547        bufsize_op: &OpTy<'tcx>,
1548    ) -> InterpResult<'tcx, i64> {
1549        let this = self.eval_context_mut();
1550
1551        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1552        let buf = this.read_pointer(buf_op)?;
1553        let bufsize = this.read_target_usize(bufsize_op)?;
1554
1555        // Reject if isolation is enabled.
1556        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1557            this.reject_in_isolation("`readlink`", reject_with)?;
1558            this.set_last_error(LibcError("EACCES"))?;
1559            return interp_ok(-1);
1560        }
1561
1562        let result = std::fs::read_link(pathname);
1563        match result {
1564            Ok(resolved) => {
1565                // 'readlink' truncates the resolved path if the provided buffer is not large
1566                // enough, and does *not* add a null terminator. That means we cannot use the usual
1567                // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1568                let resolved = this.convert_path(
1569                    Cow::Borrowed(resolved.as_ref()),
1570                    crate::shims::os_str::PathConversion::HostToTarget,
1571                );
1572                let mut path_bytes = resolved.as_encoded_bytes();
1573                let bufsize: usize = bufsize.try_into().unwrap();
1574                if path_bytes.len() > bufsize {
1575                    path_bytes = &path_bytes[..bufsize]
1576                }
1577                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1578                interp_ok(path_bytes.len().try_into().unwrap())
1579            }
1580            Err(e) => {
1581                this.set_last_error(e)?;
1582                interp_ok(-1)
1583            }
1584        }
1585    }
1586
1587    fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1588        let this = self.eval_context_mut();
1589        // "returns 1 if fd is an open file descriptor referring to a terminal;
1590        // otherwise 0 is returned, and errno is set to indicate the error"
1591        let fd = this.read_scalar(miri_fd)?.to_i32()?;
1592        let error = if let Some(fd) = this.machine.fds.get(fd) {
1593            if fd.is_tty(this.machine.communicate()) {
1594                return interp_ok(Scalar::from_i32(1));
1595            } else {
1596                LibcError("ENOTTY")
1597            }
1598        } else {
1599            // FD does not exist
1600            LibcError("EBADF")
1601        };
1602        this.set_last_error(error)?;
1603        interp_ok(Scalar::from_i32(0))
1604    }
1605
1606    fn realpath(
1607        &mut self,
1608        path_op: &OpTy<'tcx>,
1609        processed_path_op: &OpTy<'tcx>,
1610    ) -> InterpResult<'tcx, Scalar> {
1611        let this = self.eval_context_mut();
1612        this.assert_target_os_is_unix("realpath");
1613
1614        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1615        let processed_ptr = this.read_pointer(processed_path_op)?;
1616
1617        // Reject if isolation is enabled.
1618        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1619            this.reject_in_isolation("`realpath`", reject_with)?;
1620            this.set_last_error(LibcError("EACCES"))?;
1621            return interp_ok(Scalar::from_target_usize(0, this));
1622        }
1623
1624        let result = std::fs::canonicalize(pathname);
1625        match result {
1626            Ok(resolved) => {
1627                let path_max = this
1628                    .eval_libc_i32("PATH_MAX")
1629                    .try_into()
1630                    .expect("PATH_MAX does not fit in u64");
1631                let dest = if this.ptr_is_null(processed_ptr)? {
1632                    // POSIX says behavior when passing a null pointer is implementation-defined,
1633                    // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1634                    // similarly to:
1635                    //
1636                    // "If resolved_path is specified as NULL, then realpath() uses
1637                    // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1638                    // the resolved pathname, and returns a pointer to this buffer.  The
1639                    // caller should deallocate this buffer using free(3)."
1640                    // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1641                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1642                } else {
1643                    let (wrote_path, _) =
1644                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1645
1646                    if !wrote_path {
1647                        // Note that we do not explicitly handle `FILENAME_MAX`
1648                        // (different from `PATH_MAX` above) as it is Linux-specific and
1649                        // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1650                        this.set_last_error(LibcError("ENAMETOOLONG"))?;
1651                        return interp_ok(Scalar::from_target_usize(0, this));
1652                    }
1653                    processed_ptr
1654                };
1655
1656                interp_ok(Scalar::from_maybe_pointer(dest, this))
1657            }
1658            Err(e) => {
1659                this.set_last_error(e)?;
1660                interp_ok(Scalar::from_target_usize(0, this))
1661            }
1662        }
1663    }
1664    fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1665        use rand::seq::IndexedRandom;
1666
1667        // POSIX defines the template string.
1668        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1669
1670        let this = self.eval_context_mut();
1671        this.assert_target_os_is_unix("mkstemp");
1672
1673        // POSIX defines the maximum number of attempts before failure.
1674        //
1675        // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1676        // POSIX says this about `TMP_MAX`:
1677        // * Minimum number of unique filenames generated by `tmpnam()`.
1678        // * Maximum number of times an application can call `tmpnam()` reliably.
1679        //   * The value of `TMP_MAX` is at least 25.
1680        //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1681        // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1682        let max_attempts = this.eval_libc_u32("TMP_MAX");
1683
1684        // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1685        // (and the target is unix, so a byte slice is the right representation).
1686        let template_ptr = this.read_pointer(template_op)?;
1687        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1688        let template_bytes = template.as_mut_slice();
1689
1690        // Reject if isolation is enabled.
1691        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1692            this.reject_in_isolation("`mkstemp`", reject_with)?;
1693            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1694        }
1695
1696        // Get the bytes of the suffix we expect in _target_ encoding.
1697        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1698
1699        // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1700        // that represents the expected suffix.
1701
1702        // Now we figure out the index of the slice we expect to contain the suffix.
1703        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1704        let end_pos = template_bytes.len();
1705        let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1706
1707        // If we don't find the suffix, it is an error.
1708        if last_six_char_bytes != suffix_bytes {
1709            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1710        }
1711
1712        // At this point we know we have 6 ASCII 'X' characters as a suffix.
1713
1714        // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1715        const SUBSTITUTIONS: &[char; 62] = &[
1716            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1717            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1718            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1719            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1720        ];
1721
1722        // The file is opened with specific options, which Rust does not expose in a portable way.
1723        // So we use specific APIs depending on the host OS.
1724        let mut fopts = OpenOptions::new();
1725        fopts.read(true).write(true).create_new(true);
1726
1727        cfg_select! {
1728            unix => {
1729                use std::os::unix::fs::OpenOptionsExt;
1730                // Do not allow others to read or modify this file.
1731                fopts.mode(0o600);
1732                fopts.custom_flags(libc::O_EXCL);
1733            }
1734            windows => {
1735                use std::os::windows::fs::OpenOptionsExt;
1736                // Do not allow others to read or modify this file.
1737                fopts.share_mode(0);
1738            }
1739            _ => {
1740                throw_unsup_format!("`mkstemp` is not supported on this host OS");
1741            }
1742        }
1743
1744        // If the generated file already exists, we will try again `max_attempts` many times.
1745        for _ in 0..max_attempts {
1746            let rng = this.machine.rng.get_mut();
1747
1748            // Generate a random unique suffix.
1749            let unique_suffix =
1750                (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1751
1752            // Replace the template string with the random string.
1753            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1754
1755            // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1756            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1757
1758            // See if we can create and open this file.
1759            let file = fopts.open(bytes_to_os_str(template_bytes)?);
1760            match file {
1761                Ok(f) => {
1762                    let fd = this.machine.fds.insert_new(FileHandle {
1763                        file: f,
1764                        writable: true,
1765                        readable: true,
1766                    });
1767                    return interp_ok(Scalar::from_i32(fd));
1768                }
1769                Err(e) =>
1770                    match e.kind() {
1771                        // If the random file already exists, keep trying.
1772                        ErrorKind::AlreadyExists => continue,
1773                        // Any other errors are returned to the caller.
1774                        _ => {
1775                            // "On error, -1 is returned, and errno is set to
1776                            // indicate the error"
1777                            return this.set_errno_and_return_neg1_i32(e);
1778                        }
1779                    },
1780            }
1781        }
1782
1783        // We ran out of attempts to create the file, return an error.
1784        this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1785    }
1786}
1787
1788/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1789/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1790/// epoch.
1791fn extract_sec_and_nsec<'tcx>(
1792    time: std::io::Result<SystemTime>,
1793) -> InterpResult<'tcx, Option<(u64, u32)>> {
1794    match time.ok() {
1795        Some(time) => {
1796            let duration = system_time_to_duration(&time)?;
1797            interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1798        }
1799        None => interp_ok(None),
1800    }
1801}
1802
1803fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1804    #[cfg(unix)]
1805    use std::os::unix::fs::FileTypeExt;
1806
1807    if file_type.is_file() {
1808        "S_IFREG"
1809    } else if file_type.is_dir() {
1810        "S_IFDIR"
1811    } else if file_type.is_symlink() {
1812        "S_IFLNK"
1813    } else {
1814        // Certain file types are only available when the host is a Unix system.
1815        #[cfg(unix)]
1816        {
1817            if file_type.is_socket() {
1818                return "S_IFSOCK";
1819            } else if file_type.is_fifo() {
1820                return "S_IFIFO";
1821            } else if file_type.is_char_device() {
1822                return "S_IFCHR";
1823            } else if file_type.is_block_device() {
1824                return "S_IFBLK";
1825            }
1826        }
1827        "S_IFREG"
1828    }
1829}
1830
1831/// Stores a file's metadata in order to avoid code duplication in the different metadata related
1832/// shims.
1833///
1834/// Some fields are host/platform-specific. `None` means that Miri does not have a real value for
1835/// this field, for example because the metadata is synthetic or because the host platform does not
1836/// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`;
1837/// legacy `stat` writes zero for `None` to preserve the old fallback behavior.
1838struct FileMetadata {
1839    /// This holds both the file type (dir, regular, symlink, ...) and permissions.
1840    mode: u32,
1841    size: u64,
1842    created: Option<(u64, u32)>,
1843    accessed: Option<(u64, u32)>,
1844    modified: Option<(u64, u32)>,
1845    dev: Option<u64>,
1846    ino: Option<u64>,
1847    nlink: Option<u64>,
1848    uid: Option<u32>,
1849    gid: Option<u32>,
1850    blksize: Option<u64>,
1851    blocks: Option<u64>,
1852}
1853
1854impl FileMetadata {
1855    fn from_path<'tcx>(
1856        ecx: &mut MiriInterpCx<'tcx>,
1857        path: &Path,
1858        follow_symlink: bool,
1859    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1860        let metadata =
1861            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1862
1863        FileMetadata::from_meta(ecx, metadata)
1864    }
1865
1866    fn from_fd_num<'tcx>(
1867        ecx: &mut MiriInterpCx<'tcx>,
1868        fd_num: i32,
1869    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1870        let Some(fd) = ecx.machine.fds.get(fd_num) else {
1871            return interp_ok(Err(LibcError("EBADF")));
1872        };
1873        match fd.metadata()? {
1874            Either::Left(host) => Self::from_meta(ecx, host),
1875            Either::Right(name) => Self::synthetic(ecx, name),
1876        }
1877    }
1878
1879    fn synthetic<'tcx>(
1880        ecx: &mut MiriInterpCx<'tcx>,
1881        mode_name: &str,
1882    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1883        let mode = ecx.eval_libc(mode_name);
1884        let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1885        // We observed 0x777 on sockets and 0x600 on pipes...
1886        let mode = mode | 0o666;
1887        interp_ok(Ok(FileMetadata {
1888            mode,
1889            size: 0,
1890            created: None,
1891            accessed: None,
1892            modified: None,
1893            dev: None,
1894            uid: None,
1895            gid: None,
1896            blksize: None,
1897            blocks: None,
1898            ino: None,
1899            nlink: None,
1900        }))
1901    }
1902
1903    fn from_meta<'tcx>(
1904        ecx: &mut MiriInterpCx<'tcx>,
1905        metadata: Result<std::fs::Metadata, std::io::Error>,
1906    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1907        let metadata = match metadata {
1908            Ok(metadata) => metadata,
1909            Err(e) => {
1910                return interp_ok(Err(e.into()));
1911            }
1912        };
1913
1914        let file_type = metadata.file_type();
1915        let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1916        let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1917
1918        let size = metadata.len();
1919
1920        let created = extract_sec_and_nsec(metadata.created())?;
1921        let accessed = extract_sec_and_nsec(metadata.accessed())?;
1922        let modified = extract_sec_and_nsec(metadata.modified())?;
1923
1924        // FIXME: Provide more fields using platform specific methods.
1925
1926        cfg_select! {
1927            unix => {
1928                use std::os::unix::fs::{MetadataExt, PermissionsExt};
1929
1930                let dev = metadata.dev();
1931                let ino = metadata.ino();
1932                let nlink = metadata.nlink();
1933                let uid = metadata.uid();
1934                let gid = metadata.gid();
1935                let blksize = metadata.blksize();
1936                let blocks = metadata.blocks();
1937
1938                mode |= metadata.permissions().mode();
1939
1940                interp_ok(Ok(FileMetadata {
1941                    mode,
1942                    size,
1943                    created,
1944                    accessed,
1945                    modified,
1946                    dev: Some(dev),
1947                    ino: Some(ino),
1948                    nlink: Some(nlink),
1949                    uid: Some(uid),
1950                    gid: Some(gid),
1951                    blksize: Some(blksize),
1952                    blocks: Some(blocks),
1953                }))
1954            }
1955            _ => {
1956                // Emulate "everyone can read" or "everyone can read and write".
1957                mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1958
1959                interp_ok(Ok(FileMetadata {
1960                    mode,
1961                    size,
1962                    created,
1963                    accessed,
1964                    modified,
1965                    dev: None,
1966                    ino: None,
1967                    nlink: None,
1968                    uid: None,
1969                    gid: None,
1970                    blksize: None,
1971                    blocks: None,
1972                }))
1973            }
1974        }
1975    }
1976}