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.
464            let ([mode], _) = this.check_varargs(
465                if this.libc_ty_layout("mode_t").size.bytes() >= 4 {
466                    // `mode_t` is big enough, no C integer promotion.
467                    shim_varargs![libc::mode_t]
468                } else {
469                    // Types smaller than int get promoted to int
470                    // (see https://github.com/rust-lang/rust/issues/71915).
471                    shim_varargs![i32]
472                },
473                varargs,
474                "open(pathname, O_CREAT, ...)",
475            )?;
476            let mode = this.read_scalar(mode)?.to_u32()?;
477
478            #[cfg(unix)]
479            {
480                // Support all modes on UNIX host
481                use std::os::unix::fs::OpenOptionsExt;
482                options.mode(mode);
483            }
484            #[cfg(not(unix))]
485            {
486                // Only support default mode for non-UNIX (i.e. Windows) host
487                if mode != 0o666 {
488                    throw_unsup_format!(
489                        "non-default mode 0o{:o} is not supported on non-Unix hosts",
490                        mode
491                    );
492                }
493            }
494
495            let o_excl = this.eval_libc_i32("O_EXCL");
496            if flag & o_excl == o_excl {
497                flag &= !o_excl;
498                options.create_new(true);
499            } else {
500                options.create(true);
501            }
502        }
503        let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
504        if flag & o_cloexec == o_cloexec {
505            flag &= !o_cloexec;
506            // We do not need to do anything for this flag because `std` already sets it.
507            // (Technically we do not support *not* setting this flag, but we ignore that.)
508        }
509        if this.tcx.sess.target.os == Os::Linux {
510            let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
511            if flag & o_tmpfile == o_tmpfile {
512                // if the flag contains `O_TMPFILE` then we return a graceful error
513                return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
514            }
515        }
516
517        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
518        if flag & o_nofollow == o_nofollow {
519            flag &= !o_nofollow;
520            #[cfg(unix)]
521            {
522                use std::os::unix::fs::OpenOptionsExt;
523                options.custom_flags(libc::O_NOFOLLOW);
524            }
525            // Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:
526            // the path could change between us checking it here and the later call to `open`.
527            // But it's good enough for Miri purposes.
528            #[cfg(not(unix))]
529            {
530                // O_NOFOLLOW only fails when the trailing component is a symlink;
531                // the entire rest of the path can still contain symlinks.
532                if path.is_symlink() {
533                    return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
534                }
535            }
536        }
537
538        // If `flag` has any bits left set, those are not supported.
539        if flag != 0 {
540            throw_unsup_format!("unsupported flags {:#x}", flag);
541        }
542
543        // Reject if isolation is enabled.
544        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
545            this.reject_in_isolation("`open`", reject_with)?;
546            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
547        }
548
549        let fd = options
550            .open(path)
551            .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
552
553        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
554    }
555
556    fn lseek(
557        &mut self,
558        fd_num: i32,
559        offset: i128,
560        whence: i32,
561        dest: &MPlaceTy<'tcx>,
562    ) -> InterpResult<'tcx> {
563        let this = self.eval_context_mut();
564
565        // Isolation check is done via `FileDescription` trait.
566
567        let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
568            if offset < 0 {
569                // Negative offsets return `EINVAL`.
570                return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
571            } else {
572                SeekFrom::Start(u64::try_from(offset).unwrap())
573            }
574        } else if whence == this.eval_libc_i32("SEEK_CUR") {
575            SeekFrom::Current(i64::try_from(offset).unwrap())
576        } else if whence == this.eval_libc_i32("SEEK_END") {
577            SeekFrom::End(i64::try_from(offset).unwrap())
578        } else {
579            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
580        };
581
582        let communicate = this.machine.communicate();
583
584        let Some(fd) = this.machine.fds.get(fd_num) else {
585            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
586        };
587        let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
588        drop(fd);
589
590        let result = this.try_unwrap_io_result(result)?;
591        this.write_int(result, dest)?;
592        interp_ok(())
593    }
594
595    fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
596        let this = self.eval_context_mut();
597
598        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
599
600        // Reject if isolation is enabled.
601        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
602            this.reject_in_isolation("`unlink`", reject_with)?;
603            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
604        }
605
606        let result = fs::remove_file(path).map(|_| 0);
607        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
608    }
609
610    fn symlink(
611        &mut self,
612        target_op: &OpTy<'tcx>,
613        linkpath_op: &OpTy<'tcx>,
614    ) -> InterpResult<'tcx, Scalar> {
615        #[cfg(unix)]
616        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
617            std::os::unix::fs::symlink(src, dst)
618        }
619
620        #[cfg(windows)]
621        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
622            use std::os::windows::fs;
623            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
624        }
625
626        let this = self.eval_context_mut();
627        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
628        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
629
630        // Reject if isolation is enabled.
631        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
632            this.reject_in_isolation("`symlink`", reject_with)?;
633            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
634        }
635
636        let result = create_link(&target, &linkpath).map(|_| 0);
637        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
638    }
639
640    fn linkat(
641        &mut self,
642        oldfd_op: &OpTy<'tcx>,
643        oldpath_op: &OpTy<'tcx>,
644        newfd_op: &OpTy<'tcx>,
645        newpath_op: &OpTy<'tcx>,
646        flags_op: &OpTy<'tcx>,
647    ) -> InterpResult<'tcx, Scalar> {
648        let this = self.eval_context_mut();
649
650        // Load all arguments
651        let flags = this.read_scalar(flags_op)?.to_i32()?;
652        let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
653        let newfd = this.read_scalar(newfd_op)?.to_i32()?;
654        let oldpath_ptr = this.read_pointer(oldpath_op)?;
655        let newpath_ptr = this.read_pointer(newpath_op)?;
656
657        // Relevant libc constants
658        let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
659
660        // Reject if isolation is enabled.
661        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
662            this.reject_in_isolation("`linkat`", reject_with)?;
663            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
664        }
665
666        // Read flags - only support 0.
667        if flags != 0 {
668            throw_unsup_format!("unsupported linkat flags {:#x}", flags);
669        }
670
671        // Resolve oldpath
672        if oldfd != at_fdcwd {
673            throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
674        }
675        if oldpath_ptr == Pointer::null() {
676            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
677        }
678        let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
679
680        // Resolve newpath
681        if newfd != at_fdcwd {
682            throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
683        }
684        if newpath_ptr == Pointer::null() {
685            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
686        }
687        let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
688
689        let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
690        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
691    }
692
693    fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
694        let this = self.eval_context_mut();
695
696        if !matches!(
697            &this.tcx.sess.target.os,
698            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
699        ) {
700            panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
701        }
702
703        let path_scalar = this.read_pointer(path_op)?;
704        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
705
706        // Reject if isolation is enabled.
707        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
708            this.reject_in_isolation("`stat`", reject_with)?;
709            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
710        }
711
712        // `stat` always follows symlinks.
713        let metadata = match FileMetadata::from_path(this, &path, true)? {
714            Ok(metadata) => metadata,
715            Err(err) => return this.set_errno_and_return_neg1_i32(err),
716        };
717
718        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
719    }
720
721    // `lstat` is used to get symlink metadata.
722    fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
723        let this = self.eval_context_mut();
724
725        if !matches!(
726            &this.tcx.sess.target.os,
727            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
728        ) {
729            panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
730        }
731
732        let path_scalar = this.read_pointer(path_op)?;
733        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
734
735        // Reject if isolation is enabled.
736        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
737            this.reject_in_isolation("`lstat`", reject_with)?;
738            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
739        }
740
741        let metadata = match FileMetadata::from_path(this, &path, false)? {
742            Ok(metadata) => metadata,
743            Err(err) => return this.set_errno_and_return_neg1_i32(err),
744        };
745
746        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
747    }
748
749    fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
750        let this = self.eval_context_mut();
751
752        if !matches!(
753            &this.tcx.sess.target.os,
754            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
755        ) {
756            panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
757        }
758
759        let fd = this.read_scalar(fd_op)?.to_i32()?;
760
761        // Reject if isolation is enabled.
762        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
763            this.reject_in_isolation("`fstat`", reject_with)?;
764            // Set error code as "EBADF" (bad fd)
765            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
766        }
767
768        let metadata = match FileMetadata::from_fd_num(this, fd)? {
769            Ok(metadata) => metadata,
770            Err(err) => return this.set_errno_and_return_neg1_i32(err),
771        };
772        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
773    }
774
775    fn linux_statx(
776        &mut self,
777        dirfd_op: &OpTy<'tcx>,    // Should be an `int`
778        pathname_op: &OpTy<'tcx>, // Should be a `const char *`
779        flags_op: &OpTy<'tcx>,    // Should be an `int`
780        mask_op: &OpTy<'tcx>,     // Should be an `unsigned int`
781        statxbuf_op: &OpTy<'tcx>, // Should be a `struct statx *`
782    ) -> InterpResult<'tcx, Scalar> {
783        let this = self.eval_context_mut();
784
785        this.assert_target_os(Os::Linux, "statx");
786
787        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
788        let pathname_ptr = this.read_pointer(pathname_op)?;
789        let flags = this.read_scalar(flags_op)?.to_i32()?;
790        let _mask = this.read_scalar(mask_op)?.to_u32()?;
791        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
792
793        // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
794        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
795            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
796        }
797
798        let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
799
800        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
801        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
802        let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
803        let empty_path_flag = flags & at_empty_path == at_empty_path;
804        // We only support:
805        // * interpreting `path` as an absolute directory,
806        // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
807        // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
808        // set.
809        // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
810        // found this error, please open an issue reporting it.
811        if !(path.is_absolute()
812            || dirfd == this.eval_libc_i32("AT_FDCWD")
813            || (path.as_os_str().is_empty() && empty_path_flag))
814        {
815            throw_unsup_format!(
816                "using statx is only supported with absolute paths, relative paths with the file \
817                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
818                file descriptor"
819            )
820        }
821
822        // Reject if isolation is enabled.
823        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
824            this.reject_in_isolation("`statx`", reject_with)?;
825            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
826                // since `path` is provided, either absolute or
827                // relative to CWD, `EACCES` is the most relevant.
828                LibcError("EACCES")
829            } else {
830                // `dirfd` is set to target file, and `path` is empty
831                // (or we would have hit the `throw_unsup_format`
832                // above). `EACCES` would violate the spec.
833                assert!(empty_path_flag);
834                LibcError("EBADF")
835            };
836            return this.set_errno_and_return_neg1_i32(ecode);
837        }
838
839        // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
840        // symbolic links.
841        let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
842
843        // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
844        // represented by dirfd, whether it's a directory or otherwise.
845        let metadata = if path.as_os_str().is_empty() && empty_path_flag {
846            FileMetadata::from_fd_num(this, dirfd)?
847        } else {
848            FileMetadata::from_path(this, &path, follow_symlink)?
849        };
850        let metadata = match metadata {
851            Ok(metadata) => metadata,
852            Err(err) => return this.set_errno_and_return_neg1_i32(err),
853        };
854
855        // The `_mask_op` parameter specifies the file information that the caller requested.
856        // However, `statx` is allowed to return information that was not requested or to not
857        // return information that was requested. This `mask` represents the information we can
858        // actually provide for any target.
859        let mut mask = this.eval_libc_u32("STATX_TYPE")
860            | this.eval_libc_u32("STATX_MODE")
861            | this.eval_libc_u32("STATX_SIZE");
862
863        // Check which pieces of metadata we acquired, and set the appropriate flags in the mask.
864        if metadata.ino.is_some() {
865            mask |= this.eval_libc_u32("STATX_INO");
866        }
867        if metadata.nlink.is_some() {
868            mask |= this.eval_libc_u32("STATX_NLINK");
869        }
870        if metadata.uid.is_some() {
871            mask |= this.eval_libc_u32("STATX_UID");
872        }
873        if metadata.gid.is_some() {
874            mask |= this.eval_libc_u32("STATX_GID");
875        }
876        if metadata.blocks.is_some() {
877            mask |= this.eval_libc_u32("STATX_BLOCKS");
878        }
879
880        // We need to set the corresponding bits of `mask` if the access, creation and modification
881        // times were available. Otherwise we let them be zero.
882        let (access_sec, access_nsec) = metadata
883            .accessed
884            .map(|tup| {
885                mask |= this.eval_libc_u32("STATX_ATIME");
886                interp_ok(tup)
887            })
888            .unwrap_or_else(|| interp_ok((0, 0)))?;
889
890        let (created_sec, created_nsec) = metadata
891            .created
892            .map(|tup| {
893                mask |= this.eval_libc_u32("STATX_BTIME");
894                interp_ok(tup)
895            })
896            .unwrap_or_else(|| interp_ok((0, 0)))?;
897
898        let (modified_sec, modified_nsec) = metadata
899            .modified
900            .map(|tup| {
901                mask |= this.eval_libc_u32("STATX_MTIME");
902                interp_ok(tup)
903            })
904            .unwrap_or_else(|| interp_ok((0, 0)))?;
905
906        // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
907        this.write_int_fields_named(
908            &[
909                ("stx_mask", mask.into()),
910                ("stx_mode", metadata.mode.into()),
911                ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
912                ("stx_attributes", 0),
913                ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
914                ("stx_uid", metadata.uid.unwrap_or(0).into()),
915                ("stx_gid", metadata.gid.unwrap_or(0).into()),
916                ("stx_ino", metadata.ino.unwrap_or(0).into()),
917                ("stx_size", metadata.size.into()),
918                ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
919                ("stx_attributes_mask", 0),
920                ("stx_rdev_major", 0),
921                ("stx_rdev_minor", 0),
922                ("stx_dev_major", 0),
923                ("stx_dev_minor", 0),
924            ],
925            &statxbuf,
926        )?;
927        #[rustfmt::skip]
928        this.write_int_fields_named(
929            &[
930                ("tv_sec", access_sec.into()),
931                ("tv_nsec", access_nsec.into()),
932            ],
933            &this.project_field_named(&statxbuf, "stx_atime")?,
934        )?;
935        #[rustfmt::skip]
936        this.write_int_fields_named(
937            &[
938                ("tv_sec", created_sec.into()),
939                ("tv_nsec", created_nsec.into()),
940            ],
941            &this.project_field_named(&statxbuf, "stx_btime")?,
942        )?;
943        #[rustfmt::skip]
944        this.write_int_fields_named(
945            &[
946                ("tv_sec", 0.into()),
947                ("tv_nsec", 0.into()),
948            ],
949            &this.project_field_named(&statxbuf, "stx_ctime")?,
950        )?;
951        #[rustfmt::skip]
952        this.write_int_fields_named(
953            &[
954                ("tv_sec", modified_sec.into()),
955                ("tv_nsec", modified_nsec.into()),
956            ],
957            &this.project_field_named(&statxbuf, "stx_mtime")?,
958        )?;
959
960        interp_ok(Scalar::from_i32(0))
961    }
962
963    fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
964        let this = self.eval_context_mut();
965
966        let path_ptr = this.read_pointer(path_op)?;
967        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
968
969        if this.ptr_is_null(path_ptr)? {
970            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
971        }
972        let path = this.read_path_from_c_str(path_ptr)?;
973
974        // Reject if isolation is enabled.
975        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
976            this.reject_in_isolation("`chmod`", reject_with)?;
977            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
978        }
979
980        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
981        if let Err(err) = fs::set_permissions(path, permissions) {
982            return this.set_errno_and_return_neg1_i32(err);
983        }
984
985        interp_ok(Scalar::from_i32(0))
986    }
987
988    fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
989        let this = self.eval_context_mut();
990
991        let fd_num = this.read_scalar(fd_op)?.to_i32()?;
992        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
993
994        let Some(fd) = this.machine.fds.get(fd_num) else {
995            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
996        };
997        let Some(file) = fd.downcast::<FileHandle>() else {
998            // The docs don't talk about what happens for non-regular files...
999            throw_unsup_format!("`fchmod` is only supported on regular files")
1000        };
1001        if !file.writable && !file.readable {
1002            // Apparently, `fchmod` on a read-only file is fine. But let's not allow it on a
1003            // path-only file.
1004            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1005        }
1006        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1007
1008        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1009        if let Err(err) = file.file.set_permissions(permissions) {
1010            return this.set_errno_and_return_neg1_i32(err);
1011        }
1012
1013        interp_ok(Scalar::from_i32(0))
1014    }
1015
1016    fn rename(
1017        &mut self,
1018        oldpath_op: &OpTy<'tcx>,
1019        newpath_op: &OpTy<'tcx>,
1020    ) -> InterpResult<'tcx, Scalar> {
1021        let this = self.eval_context_mut();
1022
1023        let oldpath_ptr = this.read_pointer(oldpath_op)?;
1024        let newpath_ptr = this.read_pointer(newpath_op)?;
1025
1026        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1027            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1028        }
1029
1030        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1031        let newpath = this.read_path_from_c_str(newpath_ptr)?;
1032
1033        // Reject if isolation is enabled.
1034        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1035            this.reject_in_isolation("`rename`", reject_with)?;
1036            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1037        }
1038
1039        let result = fs::rename(oldpath, newpath).map(|_| 0);
1040
1041        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1042    }
1043
1044    fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1045        let this = self.eval_context_mut();
1046
1047        #[cfg_attr(not(unix), allow(unused_variables))]
1048        let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1049            u32::from(this.read_scalar(mode_op)?.to_u16()?)
1050        } else {
1051            this.read_scalar(mode_op)?.to_u32()?
1052        };
1053
1054        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1055
1056        // Reject if isolation is enabled.
1057        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1058            this.reject_in_isolation("`mkdir`", reject_with)?;
1059            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1060        }
1061
1062        #[cfg_attr(not(unix), allow(unused_mut))]
1063        let mut builder = DirBuilder::new();
1064
1065        // If the host supports it, forward on the mode of the directory
1066        // (i.e. permission bits and the sticky bit)
1067        #[cfg(unix)]
1068        {
1069            use std::os::unix::fs::DirBuilderExt;
1070            builder.mode(mode);
1071        }
1072
1073        let result = builder.create(path).map(|_| 0i32);
1074
1075        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1076    }
1077
1078    fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1079        let this = self.eval_context_mut();
1080
1081        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1082
1083        // Reject if isolation is enabled.
1084        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1085            this.reject_in_isolation("`rmdir`", reject_with)?;
1086            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1087        }
1088
1089        let result = fs::remove_dir(path).map(|_| 0i32);
1090
1091        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1092    }
1093
1094    fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1095        let this = self.eval_context_mut();
1096
1097        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1098
1099        // Reject if isolation is enabled.
1100        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1101            this.reject_in_isolation("`opendir`", reject_with)?;
1102            this.set_last_error(LibcError("EACCES"))?;
1103            return interp_ok(Scalar::null_ptr(this));
1104        }
1105
1106        let result = fs::read_dir(name);
1107
1108        match result {
1109            Ok(dir_iter) => {
1110                let id = this.machine.dirs.insert_new(dir_iter);
1111
1112                // The libc API for opendir says that this method returns a pointer to an opaque
1113                // structure, but we are returning an ID number. Thus, pass it as a scalar of
1114                // pointer width.
1115                interp_ok(Scalar::from_target_usize(id, this))
1116            }
1117            Err(e) => {
1118                this.set_last_error(e)?;
1119                interp_ok(Scalar::null_ptr(this))
1120            }
1121        }
1122    }
1123
1124    fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1125        let this = self.eval_context_mut();
1126
1127        if !matches!(
1128            &this.tcx.sess.target.os,
1129            Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1130        ) {
1131            throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1132        }
1133
1134        let dirp = this.read_target_usize(dirp_op)?;
1135
1136        // Reject if isolation is enabled.
1137        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1138            this.reject_in_isolation("`readdir`", reject_with)?;
1139            this.set_last_error(LibcError("EBADF"))?;
1140            this.write_null(dest)?;
1141            return interp_ok(());
1142        }
1143
1144        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1145            err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1146        })?;
1147
1148        let entry = match open_dir.next_host_entry() {
1149            Some(Ok(dir_entry)) => {
1150                let dir_entry = this.dir_entry_fields(dir_entry)?;
1151
1152                // Write the directory entry into a newly allocated buffer.
1153                // The name is written with write_bytes, while the rest of the
1154                // dirent64 (or dirent) struct is written using write_int_fields.
1155
1156                // For reference:
1157                // On Linux:
1158                // pub struct dirent64 {
1159                //     pub d_ino: ino64_t,
1160                //     pub d_off: off64_t,
1161                //     pub d_reclen: c_ushort,
1162                //     pub d_type: c_uchar,
1163                //     pub d_name: [c_char; 256],
1164                // }
1165                //
1166                // On Solaris:
1167                // pub struct dirent {
1168                //     pub d_ino: ino64_t,
1169                //     pub d_off: off64_t,
1170                //     pub d_reclen: c_ushort,
1171                //     pub d_name: [c_char; 3],
1172                // }
1173                //
1174                // On FreeBSD:
1175                // pub struct dirent {
1176                //     pub d_fileno: uint32_t,
1177                //     pub d_reclen: uint16_t,
1178                //     pub d_type: uint8_t,
1179                //     pub d_namlen: uint8_t,
1180                //     pub d_name: [c_char; 256],
1181                // }
1182                //
1183                // On macOS:
1184                // pub struct dirent {
1185                //     pub d_ino: u64,
1186                //     pub d_seekoff: u64,
1187                //     pub d_reclen: u16,
1188                //     pub d_namlen: u16,
1189                //     pub d_type: u8,
1190                //     pub d_name: [c_char; 1024],
1191                // }
1192
1193                // We just use the pointee type here since determining the right pointee type
1194                // independently is highly non-trivial: it depends on which exact alias of the
1195                // function was invoked (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also
1196                // depends on the ABI level which can be different between the libc used by std and
1197                // the libc used by everyone else.
1198                let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1199                let dirent_layout = this.layout_of(dirent_ty)?;
1200                let fields = &dirent_layout.fields;
1201                let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1202
1203                // Determine the size of the buffer we have to allocate.
1204                let mut name = dir_entry.name; // not a Path as there are no separators!
1205                name.push("\0"); // Add a NUL terminator
1206                let name_bytes = name.as_encoded_bytes();
1207                let name_len = u64::try_from(name_bytes.len()).unwrap();
1208                let size = d_name_offset.strict_add(name_len);
1209
1210                let entry = this.allocate_ptr(
1211                    Size::from_bytes(size),
1212                    dirent_layout.align.abi,
1213                    MiriMemoryKind::Runtime.into(),
1214                    AllocInit::Uninit,
1215                )?;
1216                let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1217
1218                // Write the name.
1219                // The name is not a normal field, we already computed the offset above.
1220                let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1221                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1222
1223                // Write common fields.
1224                let ino_name =
1225                    if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1226                this.write_int_fields_named(
1227                    &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1228                    &entry,
1229                )?;
1230
1231                // Write "optional" fields.
1232                if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1233                    this.write_null(&d_off)?;
1234                }
1235                if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1236                    this.write_null(&d_seekoff)?;
1237                }
1238                if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1239                    this.write_int(name_len.strict_sub(1), &d_namlen)?;
1240                }
1241                if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1242                    this.write_int(dir_entry.d_type, &d_type)?;
1243                }
1244
1245                Some(entry.ptr())
1246            }
1247            None => {
1248                // end of stream: return NULL
1249                None
1250            }
1251            Some(Err(e)) => {
1252                this.set_last_error(e)?;
1253                None
1254            }
1255        };
1256
1257        let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1258        let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1259        if let Some(old_entry) = old_entry {
1260            this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1261        }
1262
1263        this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1264        interp_ok(())
1265    }
1266
1267    fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1268        let this = self.eval_context_mut();
1269
1270        let dirp = this.read_target_usize(dirp_op)?;
1271
1272        // Reject if isolation is enabled.
1273        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1274            this.reject_in_isolation("`closedir`", reject_with)?;
1275            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1276        }
1277
1278        let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1279            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1280        };
1281        if let Some(entry) = open_dir.entry.take() {
1282            this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1283        }
1284        // We drop the `open_dir`, which will close the host dir handle.
1285        drop(open_dir);
1286
1287        interp_ok(Scalar::from_i32(0))
1288    }
1289
1290    fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1291        let this = self.eval_context_mut();
1292
1293        let Some(fd) = this.machine.fds.get(fd_num) else {
1294            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1295        };
1296        let Some(file) = fd.downcast::<FileHandle>() else {
1297            // The docs say that EINVAL is returned when the FD "does not reference a regular file
1298            // or a POSIX shared memory object" (and we don't support shmem objects).
1299            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1300        };
1301        if !file.writable {
1302            // man page says "EBADF or EINVAL", Linux seems to use EINVAL.
1303            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1304        }
1305        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1306
1307        if let Ok(length) = length.try_into() {
1308            let result = file.file.set_len(length);
1309            let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1310            interp_ok(Scalar::from_i32(result))
1311        } else {
1312            this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1313        }
1314    }
1315
1316    /// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead
1317    /// of setting `errno`.
1318    fn posix_fallocate(
1319        &mut self,
1320        fd_num: i32,
1321        offset: i64,
1322        len: i64,
1323    ) -> InterpResult<'tcx, Scalar> {
1324        let this = self.eval_context_mut();
1325
1326        // Reject if isolation is enabled.
1327        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1328            this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1329            // Return error code "EBADF" (bad fd).
1330            return interp_ok(this.eval_libc("EBADF"));
1331        }
1332
1333        match this.fallocate_impl(fd_num, offset, len)? {
1334            Ok(()) => interp_ok(Scalar::from_i32(0)),
1335            Err(e) => this.io_error_to_errnum(e),
1336        }
1337    }
1338
1339    fn linux_fallocate(
1340        &mut self,
1341        fd: i32,
1342        mode: i32,
1343        offset: i64,
1344        size: i64,
1345    ) -> InterpResult<'tcx, Scalar> {
1346        // This is mostly a copy of `posix_fallocate` except that errors are returned via errno.
1347        let this = self.eval_context_mut();
1348
1349        // Reject if isolation is enabled.
1350        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1351            this.reject_in_isolation("`fallocate`", reject_with)?;
1352            // Set error code "EBADF" (bad fd).
1353            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1354        }
1355
1356        // We only support `fallocate` as a replacement for `posix_fallocate` on linux,
1357        // so a non-default `mode` is not supported.
1358        if mode != 0 {
1359            throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1360        }
1361
1362        match this.fallocate_impl(fd, offset, size)? {
1363            Ok(()) => interp_ok(Scalar::from_i32(0)),
1364            Err(e) => this.set_errno_and_return_neg1_i32(e),
1365        }
1366    }
1367
1368    /// Shared logic between `posix_fallocate` and `linux_fallocate`.
1369    fn fallocate_impl(
1370        &mut self,
1371        fd_num: i32,
1372        offset: i64,
1373        len: i64,
1374    ) -> InterpResult<'tcx, Result<(), IoError>> {
1375        let this = self.eval_context_mut();
1376
1377        // EINVAL is returned/set when: "offset was less than 0, or len was less than or equal to 0".
1378        if offset < 0 || len <= 0 {
1379            return interp_ok(Err(LibcError("EINVAL")));
1380        }
1381
1382        let Some(fd) = this.machine.fds.get(fd_num) else {
1383            return interp_ok(Err(LibcError("EBADF")));
1384        };
1385        let Some(file) = fd.downcast::<FileHandle>() else {
1386            // Man page specifies to return ENODEV if `fd` is not a regular file.
1387            return interp_ok(Err(LibcError("ENODEV")));
1388        };
1389
1390        if !file.writable {
1391            return interp_ok(Err(LibcError("EBADF")));
1392        }
1393
1394        let current_size = match file.file.metadata() {
1395            Ok(metadata) => metadata.len(),
1396            Err(err) => return interp_ok(Err(err.into())),
1397        };
1398
1399        // Checked i64 addition, to ensure the result does not exceed the max file size.
1400        let new_size = match offset.checked_add(len) {
1401            // `new_size` is definitely non-negative, so we can cast to `u64`.
1402            Some(new_size) => u64::try_from(new_size).unwrap(),
1403            None => return interp_ok(Err(LibcError("EFBIG"))), // new size too big
1404        };
1405
1406        // If the size of the file is less than offset+size, then the file is increased to this
1407        // size; otherwise the file size is left unchanged.
1408        if current_size < new_size {
1409            match file.file.set_len(new_size) {
1410                Ok(()) => interp_ok(Ok(())),
1411                Err(err) => interp_ok(Err(err.into())),
1412            }
1413        } else {
1414            interp_ok(Ok(()))
1415        }
1416    }
1417
1418    fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1419        // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1420        // underlying disk to finish writing. In the interest of host compatibility,
1421        // we conservatively implement this with `sync_all`, which
1422        // *does* wait for the disk.
1423
1424        let this = self.eval_context_mut();
1425
1426        let fd = this.read_scalar(fd_op)?.to_i32()?;
1427
1428        self.ffullsync_fd(fd)
1429    }
1430
1431    fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1432        let this = self.eval_context_mut();
1433        let Some(fd) = this.machine.fds.get(fd_num) else {
1434            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1435        };
1436        // Only regular files support synchronization.
1437        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1438            err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1439        })?;
1440        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1441
1442        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1443        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1444    }
1445
1446    fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1447        let this = self.eval_context_mut();
1448
1449        let fd = this.read_scalar(fd_op)?.to_i32()?;
1450
1451        let Some(fd) = this.machine.fds.get(fd) else {
1452            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1453        };
1454        // Only regular files support synchronization.
1455        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1456            err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1457        })?;
1458        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1459
1460        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1461        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1462    }
1463
1464    /// `futimens(fd, times)`: set `fd`'s access/modification times. `times` is `[atime, mtime]`, or
1465    /// NULL to set both to now.
1466    fn futimens(
1467        &mut self,
1468        fd_op: &OpTy<'tcx>,
1469        times_op: &OpTy<'tcx>,
1470    ) -> InterpResult<'tcx, Scalar> {
1471        let this = self.eval_context_mut();
1472
1473        let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1474        let times_ptr = this.read_pointer(times_op)?;
1475
1476        let Some(fd) = this.machine.fds.get(fd_num) else {
1477            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1478        };
1479        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1480            err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1481        })?;
1482        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1483
1484        let (access, modified) = if this.ptr_is_null(times_ptr)? {
1485            let now = TimeUpdate::Set(SystemTime::now());
1486            (now, now)
1487        } else {
1488            let timespec = this.libc_ty_layout("timespec");
1489            let access_place = this.deref_pointer_as(times_op, timespec)?;
1490            let modified_place = access_place.offset(timespec.size, timespec, this)?;
1491            let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1492                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1493            };
1494            let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1495                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1496            };
1497            (access, modified)
1498        };
1499
1500        let mut filetimes = FileTimes::new();
1501        if let TimeUpdate::Set(access) = access {
1502            filetimes = filetimes.set_accessed(access);
1503        }
1504        if let TimeUpdate::Set(modified) = modified {
1505            filetimes = filetimes.set_modified(modified);
1506        }
1507        let result = file.file.set_times(filetimes);
1508        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1509    }
1510
1511    fn sync_file_range(
1512        &mut self,
1513        fd_op: &OpTy<'tcx>,
1514        offset_op: &OpTy<'tcx>,
1515        nbytes_op: &OpTy<'tcx>,
1516        flags_op: &OpTy<'tcx>,
1517    ) -> InterpResult<'tcx, Scalar> {
1518        let this = self.eval_context_mut();
1519
1520        let fd = this.read_scalar(fd_op)?.to_i32()?;
1521        let offset = this.read_scalar(offset_op)?.to_i64()?;
1522        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1523        let flags = this.read_scalar(flags_op)?.to_i32()?;
1524
1525        if offset < 0 || nbytes < 0 {
1526            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1527        }
1528        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1529            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1530            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1531        if flags & allowed_flags != flags {
1532            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1533        }
1534
1535        let Some(fd) = this.machine.fds.get(fd) else {
1536            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1537        };
1538        // Only regular files support synchronization.
1539        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1540            err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1541        })?;
1542        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1543
1544        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1545        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1546    }
1547
1548    fn readlink(
1549        &mut self,
1550        pathname_op: &OpTy<'tcx>,
1551        buf_op: &OpTy<'tcx>,
1552        bufsize_op: &OpTy<'tcx>,
1553    ) -> InterpResult<'tcx, i64> {
1554        let this = self.eval_context_mut();
1555
1556        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1557        let buf = this.read_pointer(buf_op)?;
1558        let bufsize = this.read_target_usize(bufsize_op)?;
1559
1560        // Reject if isolation is enabled.
1561        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1562            this.reject_in_isolation("`readlink`", reject_with)?;
1563            this.set_last_error(LibcError("EACCES"))?;
1564            return interp_ok(-1);
1565        }
1566
1567        let result = std::fs::read_link(pathname);
1568        match result {
1569            Ok(resolved) => {
1570                // 'readlink' truncates the resolved path if the provided buffer is not large
1571                // enough, and does *not* add a null terminator. That means we cannot use the usual
1572                // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1573                let resolved = this.convert_path(
1574                    Cow::Borrowed(resolved.as_ref()),
1575                    crate::shims::os_str::PathConversion::HostToTarget,
1576                );
1577                let mut path_bytes = resolved.as_encoded_bytes();
1578                let bufsize: usize = bufsize.try_into().unwrap();
1579                if path_bytes.len() > bufsize {
1580                    path_bytes = &path_bytes[..bufsize]
1581                }
1582                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1583                interp_ok(path_bytes.len().try_into().unwrap())
1584            }
1585            Err(e) => {
1586                this.set_last_error(e)?;
1587                interp_ok(-1)
1588            }
1589        }
1590    }
1591
1592    fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1593        let this = self.eval_context_mut();
1594        // "returns 1 if fd is an open file descriptor referring to a terminal;
1595        // otherwise 0 is returned, and errno is set to indicate the error"
1596        let fd = this.read_scalar(miri_fd)?.to_i32()?;
1597        let error = if let Some(fd) = this.machine.fds.get(fd) {
1598            if fd.is_tty(this.machine.communicate()) {
1599                return interp_ok(Scalar::from_i32(1));
1600            } else {
1601                LibcError("ENOTTY")
1602            }
1603        } else {
1604            // FD does not exist
1605            LibcError("EBADF")
1606        };
1607        this.set_last_error(error)?;
1608        interp_ok(Scalar::from_i32(0))
1609    }
1610
1611    fn realpath(
1612        &mut self,
1613        path_op: &OpTy<'tcx>,
1614        processed_path_op: &OpTy<'tcx>,
1615    ) -> InterpResult<'tcx, Scalar> {
1616        let this = self.eval_context_mut();
1617        this.assert_target_os_is_unix("realpath");
1618
1619        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1620        let processed_ptr = this.read_pointer(processed_path_op)?;
1621
1622        // Reject if isolation is enabled.
1623        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1624            this.reject_in_isolation("`realpath`", reject_with)?;
1625            this.set_last_error(LibcError("EACCES"))?;
1626            return interp_ok(Scalar::from_target_usize(0, this));
1627        }
1628
1629        let result = std::fs::canonicalize(pathname);
1630        match result {
1631            Ok(resolved) => {
1632                let path_max = this
1633                    .eval_libc_i32("PATH_MAX")
1634                    .try_into()
1635                    .expect("PATH_MAX does not fit in u64");
1636                let dest = if this.ptr_is_null(processed_ptr)? {
1637                    // POSIX says behavior when passing a null pointer is implementation-defined,
1638                    // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1639                    // similarly to:
1640                    //
1641                    // "If resolved_path is specified as NULL, then realpath() uses
1642                    // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1643                    // the resolved pathname, and returns a pointer to this buffer.  The
1644                    // caller should deallocate this buffer using free(3)."
1645                    // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1646                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1647                } else {
1648                    let (wrote_path, _) =
1649                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1650
1651                    if !wrote_path {
1652                        // Note that we do not explicitly handle `FILENAME_MAX`
1653                        // (different from `PATH_MAX` above) as it is Linux-specific and
1654                        // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1655                        this.set_last_error(LibcError("ENAMETOOLONG"))?;
1656                        return interp_ok(Scalar::from_target_usize(0, this));
1657                    }
1658                    processed_ptr
1659                };
1660
1661                interp_ok(Scalar::from_maybe_pointer(dest, this))
1662            }
1663            Err(e) => {
1664                this.set_last_error(e)?;
1665                interp_ok(Scalar::from_target_usize(0, this))
1666            }
1667        }
1668    }
1669    fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1670        use rand::seq::IndexedRandom;
1671
1672        // POSIX defines the template string.
1673        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1674
1675        let this = self.eval_context_mut();
1676        this.assert_target_os_is_unix("mkstemp");
1677
1678        // POSIX defines the maximum number of attempts before failure.
1679        //
1680        // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1681        // POSIX says this about `TMP_MAX`:
1682        // * Minimum number of unique filenames generated by `tmpnam()`.
1683        // * Maximum number of times an application can call `tmpnam()` reliably.
1684        //   * The value of `TMP_MAX` is at least 25.
1685        //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1686        // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1687        let max_attempts = this.eval_libc_u32("TMP_MAX");
1688
1689        // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1690        // (and the target is unix, so a byte slice is the right representation).
1691        let template_ptr = this.read_pointer(template_op)?;
1692        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1693        let template_bytes = template.as_mut_slice();
1694
1695        // Reject if isolation is enabled.
1696        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1697            this.reject_in_isolation("`mkstemp`", reject_with)?;
1698            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1699        }
1700
1701        // Get the bytes of the suffix we expect in _target_ encoding.
1702        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1703
1704        // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1705        // that represents the expected suffix.
1706
1707        // Now we figure out the index of the slice we expect to contain the suffix.
1708        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1709        let end_pos = template_bytes.len();
1710        let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1711
1712        // If we don't find the suffix, it is an error.
1713        if last_six_char_bytes != suffix_bytes {
1714            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1715        }
1716
1717        // At this point we know we have 6 ASCII 'X' characters as a suffix.
1718
1719        // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1720        const SUBSTITUTIONS: &[char; 62] = &[
1721            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1722            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1723            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1724            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1725        ];
1726
1727        // The file is opened with specific options, which Rust does not expose in a portable way.
1728        // So we use specific APIs depending on the host OS.
1729        let mut fopts = OpenOptions::new();
1730        fopts.read(true).write(true).create_new(true);
1731
1732        cfg_select! {
1733            unix => {
1734                use std::os::unix::fs::OpenOptionsExt;
1735                // Do not allow others to read or modify this file.
1736                fopts.mode(0o600);
1737                fopts.custom_flags(libc::O_EXCL);
1738            }
1739            windows => {
1740                use std::os::windows::fs::OpenOptionsExt;
1741                // Do not allow others to read or modify this file.
1742                fopts.share_mode(0);
1743            }
1744            _ => {
1745                throw_unsup_format!("`mkstemp` is not supported on this host OS");
1746            }
1747        }
1748
1749        // If the generated file already exists, we will try again `max_attempts` many times.
1750        for _ in 0..max_attempts {
1751            let rng = this.machine.rng.get_mut();
1752
1753            // Generate a random unique suffix.
1754            let unique_suffix =
1755                (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1756
1757            // Replace the template string with the random string.
1758            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1759
1760            // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1761            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1762
1763            // See if we can create and open this file.
1764            let file = fopts.open(bytes_to_os_str(template_bytes)?);
1765            match file {
1766                Ok(f) => {
1767                    let fd = this.machine.fds.insert_new(FileHandle {
1768                        file: f,
1769                        writable: true,
1770                        readable: true,
1771                    });
1772                    return interp_ok(Scalar::from_i32(fd));
1773                }
1774                Err(e) =>
1775                    match e.kind() {
1776                        // If the random file already exists, keep trying.
1777                        ErrorKind::AlreadyExists => continue,
1778                        // Any other errors are returned to the caller.
1779                        _ => {
1780                            // "On error, -1 is returned, and errno is set to
1781                            // indicate the error"
1782                            return this.set_errno_and_return_neg1_i32(e);
1783                        }
1784                    },
1785            }
1786        }
1787
1788        // We ran out of attempts to create the file, return an error.
1789        this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1790    }
1791}
1792
1793/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1794/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1795/// epoch.
1796fn extract_sec_and_nsec<'tcx>(
1797    time: std::io::Result<SystemTime>,
1798) -> InterpResult<'tcx, Option<(u64, u32)>> {
1799    match time.ok() {
1800        Some(time) => {
1801            let duration = system_time_to_duration(&time)?;
1802            interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1803        }
1804        None => interp_ok(None),
1805    }
1806}
1807
1808fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1809    #[cfg(unix)]
1810    use std::os::unix::fs::FileTypeExt;
1811
1812    if file_type.is_file() {
1813        "S_IFREG"
1814    } else if file_type.is_dir() {
1815        "S_IFDIR"
1816    } else if file_type.is_symlink() {
1817        "S_IFLNK"
1818    } else {
1819        // Certain file types are only available when the host is a Unix system.
1820        #[cfg(unix)]
1821        {
1822            if file_type.is_socket() {
1823                return "S_IFSOCK";
1824            } else if file_type.is_fifo() {
1825                return "S_IFIFO";
1826            } else if file_type.is_char_device() {
1827                return "S_IFCHR";
1828            } else if file_type.is_block_device() {
1829                return "S_IFBLK";
1830            }
1831        }
1832        "S_IFREG"
1833    }
1834}
1835
1836/// Stores a file's metadata in order to avoid code duplication in the different metadata related
1837/// shims.
1838///
1839/// Some fields are host/platform-specific. `None` means that Miri does not have a real value for
1840/// this field, for example because the metadata is synthetic or because the host platform does not
1841/// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`;
1842/// legacy `stat` writes zero for `None` to preserve the old fallback behavior.
1843struct FileMetadata {
1844    /// This holds both the file type (dir, regular, symlink, ...) and permissions.
1845    mode: u32,
1846    size: u64,
1847    created: Option<(u64, u32)>,
1848    accessed: Option<(u64, u32)>,
1849    modified: Option<(u64, u32)>,
1850    dev: Option<u64>,
1851    ino: Option<u64>,
1852    nlink: Option<u64>,
1853    uid: Option<u32>,
1854    gid: Option<u32>,
1855    blksize: Option<u64>,
1856    blocks: Option<u64>,
1857}
1858
1859impl FileMetadata {
1860    fn from_path<'tcx>(
1861        ecx: &mut MiriInterpCx<'tcx>,
1862        path: &Path,
1863        follow_symlink: bool,
1864    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1865        let metadata =
1866            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1867
1868        FileMetadata::from_meta(ecx, metadata)
1869    }
1870
1871    fn from_fd_num<'tcx>(
1872        ecx: &mut MiriInterpCx<'tcx>,
1873        fd_num: i32,
1874    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1875        let Some(fd) = ecx.machine.fds.get(fd_num) else {
1876            return interp_ok(Err(LibcError("EBADF")));
1877        };
1878        match fd.metadata()? {
1879            Either::Left(host) => Self::from_meta(ecx, host),
1880            Either::Right(name) => Self::synthetic(ecx, name),
1881        }
1882    }
1883
1884    fn synthetic<'tcx>(
1885        ecx: &mut MiriInterpCx<'tcx>,
1886        mode_name: &str,
1887    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1888        let mode = ecx.eval_libc(mode_name);
1889        let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1890        // We observed 0x777 on sockets and 0x600 on pipes...
1891        let mode = mode | 0o666;
1892        interp_ok(Ok(FileMetadata {
1893            mode,
1894            size: 0,
1895            created: None,
1896            accessed: None,
1897            modified: None,
1898            dev: None,
1899            uid: None,
1900            gid: None,
1901            blksize: None,
1902            blocks: None,
1903            ino: None,
1904            nlink: None,
1905        }))
1906    }
1907
1908    fn from_meta<'tcx>(
1909        ecx: &mut MiriInterpCx<'tcx>,
1910        metadata: Result<std::fs::Metadata, std::io::Error>,
1911    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1912        let metadata = match metadata {
1913            Ok(metadata) => metadata,
1914            Err(e) => {
1915                return interp_ok(Err(e.into()));
1916            }
1917        };
1918
1919        let file_type = metadata.file_type();
1920        let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1921        let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1922
1923        let size = metadata.len();
1924
1925        let created = extract_sec_and_nsec(metadata.created())?;
1926        let accessed = extract_sec_and_nsec(metadata.accessed())?;
1927        let modified = extract_sec_and_nsec(metadata.modified())?;
1928
1929        // FIXME: Provide more fields using platform specific methods.
1930
1931        cfg_select! {
1932            unix => {
1933                use std::os::unix::fs::{MetadataExt, PermissionsExt};
1934
1935                let dev = metadata.dev();
1936                let ino = metadata.ino();
1937                let nlink = metadata.nlink();
1938                let uid = metadata.uid();
1939                let gid = metadata.gid();
1940                let blksize = metadata.blksize();
1941                let blocks = metadata.blocks();
1942
1943                mode |= metadata.permissions().mode();
1944
1945                interp_ok(Ok(FileMetadata {
1946                    mode,
1947                    size,
1948                    created,
1949                    accessed,
1950                    modified,
1951                    dev: Some(dev),
1952                    ino: Some(ino),
1953                    nlink: Some(nlink),
1954                    uid: Some(uid),
1955                    gid: Some(gid),
1956                    blksize: Some(blksize),
1957                    blocks: Some(blocks),
1958                }))
1959            }
1960            _ => {
1961                // Emulate "everyone can read" or "everyone can read and write".
1962                mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1963
1964                interp_ok(Ok(FileMetadata {
1965                    mode,
1966                    size,
1967                    created,
1968                    accessed,
1969                    modified,
1970                    dev: None,
1971                    ino: None,
1972                    nlink: None,
1973                    uid: None,
1974                    gid: None,
1975                    blksize: None,
1976                    blocks: None,
1977                }))
1978            }
1979        }
1980    }
1981}