src/tools/miri/src/shims/unix/fs.rs RUST 1,973 lines View on github.com → Search inside
1//! File and file system access23use 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;910use rustc_abi::{FieldIdx, Size};11use rustc_data_structures::either::Either;12use rustc_data_structures::fx::FxHashMap;13use rustc_target::spec::Os;1415use 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::check_min_vararg_count;19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};20use crate::*;2122/// 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}3435impl OpenDir {36    fn new(read_dir: fs::ReadDir) -> Self {37        Self { special_entries: vec!["..", "."], read_dir, entry: None }38    }3940    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}4849#[derive(Debug)]50struct DirEntry {51    name: OsString,52    ino: u64,53    d_type: i32,54}5556/// 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}6263impl 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        }7778        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 failed88            file.seek(SeekFrom::Start(cursor_pos))89                .expect("failed to restore file position, this shouldn't be possible");90            res91        };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 write96                // 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    }104105    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        }118119        // 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 failed129            file.seek(SeekFrom::Start(cursor_pos))130                .expect("failed to restore file position, this shouldn't be possible");131            res132        };133        let result = f();134        finish.call(ecx, result.map_err(IoError::HostError))135    }136137    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");143144        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        };153154        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}166167/// The table of open directories.168/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything169/// 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 target176    /// directory, and an entry is stored in this hash map, indexed by an ID which represents177    /// the directory stream. When readdir is called, the directory stream ID is used to look up178    /// the corresponding ReadDir iterator from this map, and information from the next179    /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from180    /// the map.181    streams: FxHashMap<u64, OpenDir>,182    /// ID number to be used by the next call to opendir183    next_id: u64,184}185186impl 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        id193    }194}195196impl 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 libc201            next_id: 1,202        }203    }204}205206impl VisitProvenance for DirTable {207    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {208        let DirTable { streams, next_id: _ } = self;209210        for dir in streams.values() {211            dir.entry.visit_provenance(visit);212        }213    }214}215216fn 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 opened223        // for writing. (FlushFileBuffers requires that the file handle have the224        // GENERIC_WRITE right)225        Ok(0i32)226    } else {227        let result = operation(file);228        result.map(|_| 0i32)229    }230}231232impl<'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");243244        // `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)?;247248        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        }254255        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    }260261    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();267268        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));271272        // We do *not* use `deref_pointer_as` here since determining the right pointee type273        // is highly non-trivial: it depends on which exact alias of the function was invoked274        // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level275        // 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)?;277278        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        )?;299300        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        }311312        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        }317318        interp_ok(0)319    }320321    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;324325        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                    // Fallback347                    _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),348                }349            }350            Err(_) => {351                // Fallback on error352                interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())353            }354        }355    }356357    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    }383384    #[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    }389390    #[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}395396impl<'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: &[OpTy<'tcx>],403    ) -> InterpResult<'tcx, Scalar> {404        let this = self.eval_context_mut();405406        let path_raw = this.read_pointer(path_raw)?;407        let flag = this.read_scalar(flag)?.to_i32()?;408409        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        }416417        // We will "subtract" supported flags from this and at the end check that no bits are left.418        let mut flag = flag;419420        let mut options = OpenOptions::new();421422        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 and426        // windows. We need to check that in fact the access mode flags for the current target427        // 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;433434        // Now we check the access mode435        let access_mode = flag & 0b11;436        flag &= !access_mode;437438        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        }449450        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`, but464            // 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] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;467            let mode = this.read_scalar(mode)?.to_u32()?;468469            #[cfg(unix)]470            {471                // Support all modes on UNIX host472                use std::os::unix::fs::OpenOptionsExt;473                options.mode(mode);474            }475            #[cfg(not(unix))]476            {477                // Only support default mode for non-UNIX (i.e. Windows) host478                if mode != 0o666 {479                    throw_unsup_format!(480                        "non-default mode 0o{:o} is not supported on non-Unix hosts",481                        mode482                    );483                }484            }485486            let o_excl = this.eval_libc_i32("O_EXCL");487            if flag & o_excl == o_excl {488                flag &= !o_excl;489                options.create_new(true);490            } else {491                options.create(true);492            }493        }494        let o_cloexec = this.eval_libc_i32("O_CLOEXEC");495        if flag & o_cloexec == o_cloexec {496            flag &= !o_cloexec;497            // We do not need to do anything for this flag because `std` already sets it.498            // (Technically we do not support *not* setting this flag, but we ignore that.)499        }500        if this.tcx.sess.target.os == Os::Linux {501            let o_tmpfile = this.eval_libc_i32("O_TMPFILE");502            if flag & o_tmpfile == o_tmpfile {503                // if the flag contains `O_TMPFILE` then we return a graceful error504                return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));505            }506        }507508        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");509        if flag & o_nofollow == o_nofollow {510            flag &= !o_nofollow;511            #[cfg(unix)]512            {513                use std::os::unix::fs::OpenOptionsExt;514                options.custom_flags(libc::O_NOFOLLOW);515            }516            // Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:517            // the path could change between us checking it here and the later call to `open`.518            // But it's good enough for Miri purposes.519            #[cfg(not(unix))]520            {521                // O_NOFOLLOW only fails when the trailing component is a symlink;522                // the entire rest of the path can still contain symlinks.523                if path.is_symlink() {524                    return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));525                }526            }527        }528529        // If `flag` has any bits left set, those are not supported.530        if flag != 0 {531            throw_unsup_format!("unsupported flags {:#x}", flag);532        }533534        // Reject if isolation is enabled.535        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {536            this.reject_in_isolation("`open`", reject_with)?;537            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);538        }539540        let fd = options541            .open(path)542            .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));543544        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))545    }546547    fn lseek(548        &mut self,549        fd_num: i32,550        offset: i128,551        whence: i32,552        dest: &MPlaceTy<'tcx>,553    ) -> InterpResult<'tcx> {554        let this = self.eval_context_mut();555556        // Isolation check is done via `FileDescription` trait.557558        let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {559            if offset < 0 {560                // Negative offsets return `EINVAL`.561                return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);562            } else {563                SeekFrom::Start(u64::try_from(offset).unwrap())564            }565        } else if whence == this.eval_libc_i32("SEEK_CUR") {566            SeekFrom::Current(i64::try_from(offset).unwrap())567        } else if whence == this.eval_libc_i32("SEEK_END") {568            SeekFrom::End(i64::try_from(offset).unwrap())569        } else {570            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);571        };572573        let communicate = this.machine.communicate();574575        let Some(fd) = this.machine.fds.get(fd_num) else {576            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);577        };578        let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());579        drop(fd);580581        let result = this.try_unwrap_io_result(result)?;582        this.write_int(result, dest)?;583        interp_ok(())584    }585586    fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {587        let this = self.eval_context_mut();588589        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;590591        // Reject if isolation is enabled.592        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {593            this.reject_in_isolation("`unlink`", reject_with)?;594            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);595        }596597        let result = fs::remove_file(path).map(|_| 0);598        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))599    }600601    fn symlink(602        &mut self,603        target_op: &OpTy<'tcx>,604        linkpath_op: &OpTy<'tcx>,605    ) -> InterpResult<'tcx, Scalar> {606        #[cfg(unix)]607        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {608            std::os::unix::fs::symlink(src, dst)609        }610611        #[cfg(windows)]612        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {613            use std::os::windows::fs;614            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }615        }616617        let this = self.eval_context_mut();618        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;619        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;620621        // Reject if isolation is enabled.622        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {623            this.reject_in_isolation("`symlink`", reject_with)?;624            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);625        }626627        let result = create_link(&target, &linkpath).map(|_| 0);628        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))629    }630631    fn linkat(632        &mut self,633        oldfd_op: &OpTy<'tcx>,634        oldpath_op: &OpTy<'tcx>,635        newfd_op: &OpTy<'tcx>,636        newpath_op: &OpTy<'tcx>,637        flags_op: &OpTy<'tcx>,638    ) -> InterpResult<'tcx, Scalar> {639        let this = self.eval_context_mut();640641        // Load all arguments642        let flags = this.read_scalar(flags_op)?.to_i32()?;643        let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;644        let newfd = this.read_scalar(newfd_op)?.to_i32()?;645        let oldpath_ptr = this.read_pointer(oldpath_op)?;646        let newpath_ptr = this.read_pointer(newpath_op)?;647648        // Relevant libc constants649        let at_fdcwd = this.eval_libc_i32("AT_FDCWD");650651        // Reject if isolation is enabled.652        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {653            this.reject_in_isolation("`linkat`", reject_with)?;654            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);655        }656657        // Read flags - only support 0.658        if flags != 0 {659            throw_unsup_format!("unsupported linkat flags {:#x}", flags);660        }661662        // Resolve oldpath663        if oldfd != at_fdcwd {664            throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");665        }666        if oldpath_ptr == Pointer::null() {667            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));668        }669        let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();670671        // Resolve newpath672        if newfd != at_fdcwd {673            throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");674        }675        if newpath_ptr == Pointer::null() {676            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));677        }678        let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();679680        let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);681        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))682    }683684    fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {685        let this = self.eval_context_mut();686687        if !matches!(688            &this.tcx.sess.target.os,689            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux690        ) {691            panic!("`stat` should not be called on {}", this.tcx.sess.target.os);692        }693694        let path_scalar = this.read_pointer(path_op)?;695        let path = this.read_path_from_c_str(path_scalar)?.into_owned();696697        // Reject if isolation is enabled.698        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {699            this.reject_in_isolation("`stat`", reject_with)?;700            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));701        }702703        // `stat` always follows symlinks.704        let metadata = match FileMetadata::from_path(this, &path, true)? {705            Ok(metadata) => metadata,706            Err(err) => return this.set_errno_and_return_neg1_i32(err),707        };708709        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))710    }711712    // `lstat` is used to get symlink metadata.713    fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {714        let this = self.eval_context_mut();715716        if !matches!(717            &this.tcx.sess.target.os,718            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux719        ) {720            panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);721        }722723        let path_scalar = this.read_pointer(path_op)?;724        let path = this.read_path_from_c_str(path_scalar)?.into_owned();725726        // Reject if isolation is enabled.727        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {728            this.reject_in_isolation("`lstat`", reject_with)?;729            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));730        }731732        let metadata = match FileMetadata::from_path(this, &path, false)? {733            Ok(metadata) => metadata,734            Err(err) => return this.set_errno_and_return_neg1_i32(err),735        };736737        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))738    }739740    fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {741        let this = self.eval_context_mut();742743        if !matches!(744            &this.tcx.sess.target.os,745            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android746        ) {747            panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);748        }749750        let fd = this.read_scalar(fd_op)?.to_i32()?;751752        // Reject if isolation is enabled.753        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {754            this.reject_in_isolation("`fstat`", reject_with)?;755            // Set error code as "EBADF" (bad fd)756            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));757        }758759        let metadata = match FileMetadata::from_fd_num(this, fd)? {760            Ok(metadata) => metadata,761            Err(err) => return this.set_errno_and_return_neg1_i32(err),762        };763        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))764    }765766    fn linux_statx(767        &mut self,768        dirfd_op: &OpTy<'tcx>,    // Should be an `int`769        pathname_op: &OpTy<'tcx>, // Should be a `const char *`770        flags_op: &OpTy<'tcx>,    // Should be an `int`771        mask_op: &OpTy<'tcx>,     // Should be an `unsigned int`772        statxbuf_op: &OpTy<'tcx>, // Should be a `struct statx *`773    ) -> InterpResult<'tcx, Scalar> {774        let this = self.eval_context_mut();775776        this.assert_target_os(Os::Linux, "statx");777778        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;779        let pathname_ptr = this.read_pointer(pathname_op)?;780        let flags = this.read_scalar(flags_op)?.to_i32()?;781        let _mask = this.read_scalar(mask_op)?.to_u32()?;782        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;783784        // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.785        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {786            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));787        }788789        let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;790791        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();792        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.793        let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");794        let empty_path_flag = flags & at_empty_path == at_empty_path;795        // We only support:796        // * interpreting `path` as an absolute directory,797        // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or798        // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is799        // set.800        // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you801        // found this error, please open an issue reporting it.802        if !(path.is_absolute()803            || dirfd == this.eval_libc_i32("AT_FDCWD")804            || (path.as_os_str().is_empty() && empty_path_flag))805        {806            throw_unsup_format!(807                "using statx is only supported with absolute paths, relative paths with the file \808                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \809                file descriptor"810            )811        }812813        // Reject if isolation is enabled.814        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {815            this.reject_in_isolation("`statx`", reject_with)?;816            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {817                // since `path` is provided, either absolute or818                // relative to CWD, `EACCES` is the most relevant.819                LibcError("EACCES")820            } else {821                // `dirfd` is set to target file, and `path` is empty822                // (or we would have hit the `throw_unsup_format`823                // above). `EACCES` would violate the spec.824                assert!(empty_path_flag);825                LibcError("EBADF")826            };827            return this.set_errno_and_return_neg1_i32(ecode);828        }829830        // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following831        // symbolic links.832        let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;833834        // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file835        // represented by dirfd, whether it's a directory or otherwise.836        let metadata = if path.as_os_str().is_empty() && empty_path_flag {837            FileMetadata::from_fd_num(this, dirfd)?838        } else {839            FileMetadata::from_path(this, &path, follow_symlink)?840        };841        let metadata = match metadata {842            Ok(metadata) => metadata,843            Err(err) => return this.set_errno_and_return_neg1_i32(err),844        };845846        // The `_mask_op` parameter specifies the file information that the caller requested.847        // However, `statx` is allowed to return information that was not requested or to not848        // return information that was requested. This `mask` represents the information we can849        // actually provide for any target.850        let mut mask = this.eval_libc_u32("STATX_TYPE")851            | this.eval_libc_u32("STATX_MODE")852            | this.eval_libc_u32("STATX_SIZE");853854        // Check which pieces of metadata we acquired, and set the appropriate flags in the mask.855        if metadata.ino.is_some() {856            mask |= this.eval_libc_u32("STATX_INO");857        }858        if metadata.nlink.is_some() {859            mask |= this.eval_libc_u32("STATX_NLINK");860        }861        if metadata.uid.is_some() {862            mask |= this.eval_libc_u32("STATX_UID");863        }864        if metadata.gid.is_some() {865            mask |= this.eval_libc_u32("STATX_GID");866        }867        if metadata.blocks.is_some() {868            mask |= this.eval_libc_u32("STATX_BLOCKS");869        }870871        // We need to set the corresponding bits of `mask` if the access, creation and modification872        // times were available. Otherwise we let them be zero.873        let (access_sec, access_nsec) = metadata874            .accessed875            .map(|tup| {876                mask |= this.eval_libc_u32("STATX_ATIME");877                interp_ok(tup)878            })879            .unwrap_or_else(|| interp_ok((0, 0)))?;880881        let (created_sec, created_nsec) = metadata882            .created883            .map(|tup| {884                mask |= this.eval_libc_u32("STATX_BTIME");885                interp_ok(tup)886            })887            .unwrap_or_else(|| interp_ok((0, 0)))?;888889        let (modified_sec, modified_nsec) = metadata890            .modified891            .map(|tup| {892                mask |= this.eval_libc_u32("STATX_MTIME");893                interp_ok(tup)894            })895            .unwrap_or_else(|| interp_ok((0, 0)))?;896897        // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.898        this.write_int_fields_named(899            &[900                ("stx_mask", mask.into()),901                ("stx_mode", metadata.mode.into()),902                ("stx_blksize", metadata.blksize.unwrap_or(0).into()),903                ("stx_attributes", 0),904                ("stx_nlink", metadata.nlink.unwrap_or(0).into()),905                ("stx_uid", metadata.uid.unwrap_or(0).into()),906                ("stx_gid", metadata.gid.unwrap_or(0).into()),907                ("stx_ino", metadata.ino.unwrap_or(0).into()),908                ("stx_size", metadata.size.into()),909                ("stx_blocks", metadata.blocks.unwrap_or(0).into()),910                ("stx_attributes_mask", 0),911                ("stx_rdev_major", 0),912                ("stx_rdev_minor", 0),913                ("stx_dev_major", 0),914                ("stx_dev_minor", 0),915            ],916            &statxbuf,917        )?;918        #[rustfmt::skip]919        this.write_int_fields_named(920            &[921                ("tv_sec", access_sec.into()),922                ("tv_nsec", access_nsec.into()),923            ],924            &this.project_field_named(&statxbuf, "stx_atime")?,925        )?;926        #[rustfmt::skip]927        this.write_int_fields_named(928            &[929                ("tv_sec", created_sec.into()),930                ("tv_nsec", created_nsec.into()),931            ],932            &this.project_field_named(&statxbuf, "stx_btime")?,933        )?;934        #[rustfmt::skip]935        this.write_int_fields_named(936            &[937                ("tv_sec", 0.into()),938                ("tv_nsec", 0.into()),939            ],940            &this.project_field_named(&statxbuf, "stx_ctime")?,941        )?;942        #[rustfmt::skip]943        this.write_int_fields_named(944            &[945                ("tv_sec", modified_sec.into()),946                ("tv_nsec", modified_nsec.into()),947            ],948            &this.project_field_named(&statxbuf, "stx_mtime")?,949        )?;950951        interp_ok(Scalar::from_i32(0))952    }953954    fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {955        let this = self.eval_context_mut();956957        let path_ptr = this.read_pointer(path_op)?;958        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;959960        if this.ptr_is_null(path_ptr)? {961            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));962        }963        let path = this.read_path_from_c_str(path_ptr)?;964965        // Reject if isolation is enabled.966        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {967            this.reject_in_isolation("`chmod`", reject_with)?;968            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));969        }970971        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;972        if let Err(err) = fs::set_permissions(path, permissions) {973            return this.set_errno_and_return_neg1_i32(err);974        }975976        interp_ok(Scalar::from_i32(0))977    }978979    fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {980        let this = self.eval_context_mut();981982        let fd_num = this.read_scalar(fd_op)?.to_i32()?;983        let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;984985        let Some(fd) = this.machine.fds.get(fd_num) else {986            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));987        };988        let Some(file) = fd.downcast::<FileHandle>() else {989            // The docs don't talk about what happens for non-regular files...990            throw_unsup_format!("`fchmod` is only supported on regular files")991        };992        if !file.writable && !file.readable {993            // Apparently, `fchmod` on a read-only file is fine. But let's not allow it on a994            // path-only file.995            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));996        }997        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");998999        let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;1000        if let Err(err) = file.file.set_permissions(permissions) {1001            return this.set_errno_and_return_neg1_i32(err);1002        }10031004        interp_ok(Scalar::from_i32(0))1005    }10061007    fn rename(1008        &mut self,1009        oldpath_op: &OpTy<'tcx>,1010        newpath_op: &OpTy<'tcx>,1011    ) -> InterpResult<'tcx, Scalar> {1012        let this = self.eval_context_mut();10131014        let oldpath_ptr = this.read_pointer(oldpath_op)?;1015        let newpath_ptr = this.read_pointer(newpath_op)?;10161017        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {1018            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));1019        }10201021        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;1022        let newpath = this.read_path_from_c_str(newpath_ptr)?;10231024        // Reject if isolation is enabled.1025        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1026            this.reject_in_isolation("`rename`", reject_with)?;1027            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);1028        }10291030        let result = fs::rename(oldpath, newpath).map(|_| 0);10311032        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))1033    }10341035    fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1036        let this = self.eval_context_mut();10371038        #[cfg_attr(not(unix), allow(unused_variables))]1039        let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {1040            u32::from(this.read_scalar(mode_op)?.to_u16()?)1041        } else {1042            this.read_scalar(mode_op)?.to_u32()?1043        };10441045        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;10461047        // Reject if isolation is enabled.1048        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1049            this.reject_in_isolation("`mkdir`", reject_with)?;1050            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);1051        }10521053        #[cfg_attr(not(unix), allow(unused_mut))]1054        let mut builder = DirBuilder::new();10551056        // If the host supports it, forward on the mode of the directory1057        // (i.e. permission bits and the sticky bit)1058        #[cfg(unix)]1059        {1060            use std::os::unix::fs::DirBuilderExt;1061            builder.mode(mode);1062        }10631064        let result = builder.create(path).map(|_| 0i32);10651066        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))1067    }10681069    fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1070        let this = self.eval_context_mut();10711072        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;10731074        // Reject if isolation is enabled.1075        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1076            this.reject_in_isolation("`rmdir`", reject_with)?;1077            return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);1078        }10791080        let result = fs::remove_dir(path).map(|_| 0i32);10811082        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))1083    }10841085    fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1086        let this = self.eval_context_mut();10871088        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;10891090        // Reject if isolation is enabled.1091        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1092            this.reject_in_isolation("`opendir`", reject_with)?;1093            this.set_last_error(LibcError("EACCES"))?;1094            return interp_ok(Scalar::null_ptr(this));1095        }10961097        let result = fs::read_dir(name);10981099        match result {1100            Ok(dir_iter) => {1101                let id = this.machine.dirs.insert_new(dir_iter);11021103                // The libc API for opendir says that this method returns a pointer to an opaque1104                // structure, but we are returning an ID number. Thus, pass it as a scalar of1105                // pointer width.1106                interp_ok(Scalar::from_target_usize(id, this))1107            }1108            Err(e) => {1109                this.set_last_error(e)?;1110                interp_ok(Scalar::null_ptr(this))1111            }1112        }1113    }11141115    fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {1116        let this = self.eval_context_mut();11171118        if !matches!(1119            &this.tcx.sess.target.os,1120            Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs1121        ) {1122            throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);1123        }11241125        let dirp = this.read_target_usize(dirp_op)?;11261127        // Reject if isolation is enabled.1128        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1129            this.reject_in_isolation("`readdir`", reject_with)?;1130            this.set_last_error(LibcError("EBADF"))?;1131            this.write_null(dest)?;1132            return interp_ok(());1133        }11341135        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {1136            err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")1137        })?;11381139        let entry = match open_dir.next_host_entry() {1140            Some(Ok(dir_entry)) => {1141                let dir_entry = this.dir_entry_fields(dir_entry)?;11421143                // Write the directory entry into a newly allocated buffer.1144                // The name is written with write_bytes, while the rest of the1145                // dirent64 (or dirent) struct is written using write_int_fields.11461147                // For reference:1148                // On Linux:1149                // pub struct dirent64 {1150                //     pub d_ino: ino64_t,1151                //     pub d_off: off64_t,1152                //     pub d_reclen: c_ushort,1153                //     pub d_type: c_uchar,1154                //     pub d_name: [c_char; 256],1155                // }1156                //1157                // On Solaris:1158                // pub struct dirent {1159                //     pub d_ino: ino64_t,1160                //     pub d_off: off64_t,1161                //     pub d_reclen: c_ushort,1162                //     pub d_name: [c_char; 3],1163                // }1164                //1165                // On FreeBSD:1166                // pub struct dirent {1167                //     pub d_fileno: uint32_t,1168                //     pub d_reclen: uint16_t,1169                //     pub d_type: uint8_t,1170                //     pub d_namlen: uint8_t,1171                //     pub d_name: [c_char; 256],1172                // }1173                //1174                // On macOS:1175                // pub struct dirent {1176                //     pub d_ino: u64,1177                //     pub d_seekoff: u64,1178                //     pub d_reclen: u16,1179                //     pub d_namlen: u16,1180                //     pub d_type: u8,1181                //     pub d_name: [c_char; 1024],1182                // }11831184                // We just use the pointee type here since determining the right pointee type1185                // independently is highly non-trivial: it depends on which exact alias of the1186                // function was invoked (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also1187                // depends on the ABI level which can be different between the libc used by std and1188                // the libc used by everyone else.1189                let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();1190                let dirent_layout = this.layout_of(dirent_ty)?;1191                let fields = &dirent_layout.fields;1192                let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();11931194                // Determine the size of the buffer we have to allocate.1195                let mut name = dir_entry.name; // not a Path as there are no separators!1196                name.push("\0"); // Add a NUL terminator1197                let name_bytes = name.as_encoded_bytes();1198                let name_len = u64::try_from(name_bytes.len()).unwrap();1199                let size = d_name_offset.strict_add(name_len);12001201                let entry = this.allocate_ptr(1202                    Size::from_bytes(size),1203                    dirent_layout.align.abi,1204                    MiriMemoryKind::Runtime.into(),1205                    AllocInit::Uninit,1206                )?;1207                let entry = this.ptr_to_mplace(entry.into(), dirent_layout);12081209                // Write the name.1210                // The name is not a normal field, we already computed the offset above.1211                let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);1212                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;12131214                // Write common fields.1215                let ino_name =1216                    if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };1217                this.write_int_fields_named(1218                    &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],1219                    &entry,1220                )?;12211222                // Write "optional" fields.1223                if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {1224                    this.write_null(&d_off)?;1225                }1226                if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {1227                    this.write_null(&d_seekoff)?;1228                }1229                if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {1230                    this.write_int(name_len.strict_sub(1), &d_namlen)?;1231                }1232                if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {1233                    this.write_int(dir_entry.d_type, &d_type)?;1234                }12351236                Some(entry.ptr())1237            }1238            None => {1239                // end of stream: return NULL1240                None1241            }1242            Some(Err(e)) => {1243                this.set_last_error(e)?;1244                None1245            }1246        };12471248        let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();1249        let old_entry = std::mem::replace(&mut open_dir.entry, entry);1250        if let Some(old_entry) = old_entry {1251            this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;1252        }12531254        this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;1255        interp_ok(())1256    }12571258    fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1259        let this = self.eval_context_mut();12601261        let dirp = this.read_target_usize(dirp_op)?;12621263        // Reject if isolation is enabled.1264        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1265            this.reject_in_isolation("`closedir`", reject_with)?;1266            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1267        }12681269        let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {1270            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1271        };1272        if let Some(entry) = open_dir.entry.take() {1273            this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;1274        }1275        // We drop the `open_dir`, which will close the host dir handle.1276        drop(open_dir);12771278        interp_ok(Scalar::from_i32(0))1279    }12801281    fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {1282        let this = self.eval_context_mut();12831284        let Some(fd) = this.machine.fds.get(fd_num) else {1285            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1286        };1287        let Some(file) = fd.downcast::<FileHandle>() else {1288            // The docs say that EINVAL is returned when the FD "does not reference a regular file1289            // or a POSIX shared memory object" (and we don't support shmem objects).1290            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1291        };1292        if !file.writable {1293            // man page says "EBADF or EINVAL", Linux seems to use EINVAL.1294            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1295        }1296        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");12971298        if let Ok(length) = length.try_into() {1299            let result = file.file.set_len(length);1300            let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;1301            interp_ok(Scalar::from_i32(result))1302        } else {1303            this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))1304        }1305    }13061307    /// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead1308    /// of setting `errno`.1309    fn posix_fallocate(1310        &mut self,1311        fd_num: i32,1312        offset: i64,1313        len: i64,1314    ) -> InterpResult<'tcx, Scalar> {1315        let this = self.eval_context_mut();13161317        // Reject if isolation is enabled.1318        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1319            this.reject_in_isolation("`posix_fallocate`", reject_with)?;1320            // Return error code "EBADF" (bad fd).1321            return interp_ok(this.eval_libc("EBADF"));1322        }13231324        match this.fallocate_impl(fd_num, offset, len)? {1325            Ok(()) => interp_ok(Scalar::from_i32(0)),1326            Err(e) => this.io_error_to_errnum(e),1327        }1328    }13291330    fn linux_fallocate(1331        &mut self,1332        fd: i32,1333        mode: i32,1334        offset: i64,1335        size: i64,1336    ) -> InterpResult<'tcx, Scalar> {1337        // This is mostly a copy of `posix_fallocate` except that errors are returned via errno.1338        let this = self.eval_context_mut();13391340        // Reject if isolation is enabled.1341        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1342            this.reject_in_isolation("`fallocate`", reject_with)?;1343            // Set error code "EBADF" (bad fd).1344            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1345        }13461347        // We only support `fallocate` as a replacement for `posix_fallocate` on linux,1348        // so a non-default `mode` is not supported.1349        if mode != 0 {1350            throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")1351        }13521353        match this.fallocate_impl(fd, offset, size)? {1354            Ok(()) => interp_ok(Scalar::from_i32(0)),1355            Err(e) => this.set_errno_and_return_neg1_i32(e),1356        }1357    }13581359    /// Shared logic between `posix_fallocate` and `linux_fallocate`.1360    fn fallocate_impl(1361        &mut self,1362        fd_num: i32,1363        offset: i64,1364        len: i64,1365    ) -> InterpResult<'tcx, Result<(), IoError>> {1366        let this = self.eval_context_mut();13671368        // EINVAL is returned/set when: "offset was less than 0, or len was less than or equal to 0".1369        if offset < 0 || len <= 0 {1370            return interp_ok(Err(LibcError("EINVAL")));1371        }13721373        let Some(fd) = this.machine.fds.get(fd_num) else {1374            return interp_ok(Err(LibcError("EBADF")));1375        };1376        let Some(file) = fd.downcast::<FileHandle>() else {1377            // Man page specifies to return ENODEV if `fd` is not a regular file.1378            return interp_ok(Err(LibcError("ENODEV")));1379        };13801381        if !file.writable {1382            return interp_ok(Err(LibcError("EBADF")));1383        }13841385        let current_size = match file.file.metadata() {1386            Ok(metadata) => metadata.len(),1387            Err(err) => return interp_ok(Err(err.into())),1388        };13891390        // Checked i64 addition, to ensure the result does not exceed the max file size.1391        let new_size = match offset.checked_add(len) {1392            // `new_size` is definitely non-negative, so we can cast to `u64`.1393            Some(new_size) => u64::try_from(new_size).unwrap(),1394            None => return interp_ok(Err(LibcError("EFBIG"))), // new size too big1395        };13961397        // If the size of the file is less than offset+size, then the file is increased to this1398        // size; otherwise the file size is left unchanged.1399        if current_size < new_size {1400            match file.file.set_len(new_size) {1401                Ok(()) => interp_ok(Ok(())),1402                Err(err) => interp_ok(Err(err.into())),1403            }1404        } else {1405            interp_ok(Ok(()))1406        }1407    }14081409    fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1410        // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the1411        // underlying disk to finish writing. In the interest of host compatibility,1412        // we conservatively implement this with `sync_all`, which1413        // *does* wait for the disk.14141415        let this = self.eval_context_mut();14161417        let fd = this.read_scalar(fd_op)?.to_i32()?;14181419        self.ffullsync_fd(fd)1420    }14211422    fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {1423        let this = self.eval_context_mut();1424        let Some(fd) = this.machine.fds.get(fd_num) else {1425            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1426        };1427        // Only regular files support synchronization.1428        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1429            err_unsup_format!("`fsync` is only supported on file-backed file descriptors")1430        })?;1431        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");14321433        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);1434        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1435    }14361437    fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1438        let this = self.eval_context_mut();14391440        let fd = this.read_scalar(fd_op)?.to_i32()?;14411442        let Some(fd) = this.machine.fds.get(fd) else {1443            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1444        };1445        // Only regular files support synchronization.1446        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1447            err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")1448        })?;1449        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");14501451        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);1452        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1453    }14541455    /// `futimens(fd, times)`: set `fd`'s access/modification times. `times` is `[atime, mtime]`, or1456    /// NULL to set both to now.1457    fn futimens(1458        &mut self,1459        fd_op: &OpTy<'tcx>,1460        times_op: &OpTy<'tcx>,1461    ) -> InterpResult<'tcx, Scalar> {1462        let this = self.eval_context_mut();14631464        let fd_num = this.read_scalar(fd_op)?.to_i32()?;1465        let times_ptr = this.read_pointer(times_op)?;14661467        let Some(fd) = this.machine.fds.get(fd_num) else {1468            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1469        };1470        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1471            err_unsup_format!("`futimens` is only supported on file-backed file descriptors")1472        })?;1473        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");14741475        let (access, modified) = if this.ptr_is_null(times_ptr)? {1476            let now = TimeUpdate::Set(SystemTime::now());1477            (now, now)1478        } else {1479            let timespec = this.libc_ty_layout("timespec");1480            let access_place = this.deref_pointer_as(times_op, timespec)?;1481            let modified_place = access_place.offset(timespec.size, timespec, this)?;1482            let Some(access) = this.parse_utimens_timespec(&access_place)? else {1483                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1484            };1485            let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {1486                return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1487            };1488            (access, modified)1489        };14901491        let mut filetimes = FileTimes::new();1492        if let TimeUpdate::Set(access) = access {1493            filetimes = filetimes.set_accessed(access);1494        }1495        if let TimeUpdate::Set(modified) = modified {1496            filetimes = filetimes.set_modified(modified);1497        }1498        let result = file.file.set_times(filetimes);1499        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))1500    }15011502    fn sync_file_range(1503        &mut self,1504        fd_op: &OpTy<'tcx>,1505        offset_op: &OpTy<'tcx>,1506        nbytes_op: &OpTy<'tcx>,1507        flags_op: &OpTy<'tcx>,1508    ) -> InterpResult<'tcx, Scalar> {1509        let this = self.eval_context_mut();15101511        let fd = this.read_scalar(fd_op)?.to_i32()?;1512        let offset = this.read_scalar(offset_op)?.to_i64()?;1513        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;1514        let flags = this.read_scalar(flags_op)?.to_i32()?;15151516        if offset < 0 || nbytes < 0 {1517            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1518        }1519        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")1520            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")1521            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");1522        if flags & allowed_flags != flags {1523            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1524        }15251526        let Some(fd) = this.machine.fds.get(fd) else {1527            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1528        };1529        // Only regular files support synchronization.1530        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1531            err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")1532        })?;1533        assert!(this.machine.communicate(), "isolation should have prevented even opening a file");15341535        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);1536        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1537    }15381539    fn readlink(1540        &mut self,1541        pathname_op: &OpTy<'tcx>,1542        buf_op: &OpTy<'tcx>,1543        bufsize_op: &OpTy<'tcx>,1544    ) -> InterpResult<'tcx, i64> {1545        let this = self.eval_context_mut();15461547        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;1548        let buf = this.read_pointer(buf_op)?;1549        let bufsize = this.read_target_usize(bufsize_op)?;15501551        // Reject if isolation is enabled.1552        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1553            this.reject_in_isolation("`readlink`", reject_with)?;1554            this.set_last_error(LibcError("EACCES"))?;1555            return interp_ok(-1);1556        }15571558        let result = std::fs::read_link(pathname);1559        match result {1560            Ok(resolved) => {1561                // 'readlink' truncates the resolved path if the provided buffer is not large1562                // enough, and does *not* add a null terminator. That means we cannot use the usual1563                // `write_path_to_c_str` and have to re-implement parts of it ourselves.1564                let resolved = this.convert_path(1565                    Cow::Borrowed(resolved.as_ref()),1566                    crate::shims::os_str::PathConversion::HostToTarget,1567                );1568                let mut path_bytes = resolved.as_encoded_bytes();1569                let bufsize: usize = bufsize.try_into().unwrap();1570                if path_bytes.len() > bufsize {1571                    path_bytes = &path_bytes[..bufsize]1572                }1573                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;1574                interp_ok(path_bytes.len().try_into().unwrap())1575            }1576            Err(e) => {1577                this.set_last_error(e)?;1578                interp_ok(-1)1579            }1580        }1581    }15821583    fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1584        let this = self.eval_context_mut();1585        // "returns 1 if fd is an open file descriptor referring to a terminal;1586        // otherwise 0 is returned, and errno is set to indicate the error"1587        let fd = this.read_scalar(miri_fd)?.to_i32()?;1588        let error = if let Some(fd) = this.machine.fds.get(fd) {1589            if fd.is_tty(this.machine.communicate()) {1590                return interp_ok(Scalar::from_i32(1));1591            } else {1592                LibcError("ENOTTY")1593            }1594        } else {1595            // FD does not exist1596            LibcError("EBADF")1597        };1598        this.set_last_error(error)?;1599        interp_ok(Scalar::from_i32(0))1600    }16011602    fn realpath(1603        &mut self,1604        path_op: &OpTy<'tcx>,1605        processed_path_op: &OpTy<'tcx>,1606    ) -> InterpResult<'tcx, Scalar> {1607        let this = self.eval_context_mut();1608        this.assert_target_os_is_unix("realpath");16091610        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;1611        let processed_ptr = this.read_pointer(processed_path_op)?;16121613        // Reject if isolation is enabled.1614        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1615            this.reject_in_isolation("`realpath`", reject_with)?;1616            this.set_last_error(LibcError("EACCES"))?;1617            return interp_ok(Scalar::from_target_usize(0, this));1618        }16191620        let result = std::fs::canonicalize(pathname);1621        match result {1622            Ok(resolved) => {1623                let path_max = this1624                    .eval_libc_i32("PATH_MAX")1625                    .try_into()1626                    .expect("PATH_MAX does not fit in u64");1627                let dest = if this.ptr_is_null(processed_ptr)? {1628                    // POSIX says behavior when passing a null pointer is implementation-defined,1629                    // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer1630                    // similarly to:1631                    //1632                    // "If resolved_path is specified as NULL, then realpath() uses1633                    // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold1634                    // the resolved pathname, and returns a pointer to this buffer.  The1635                    // caller should deallocate this buffer using free(3)."1636                    // <https://man7.org/linux/man-pages/man3/realpath.3.html>1637                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?1638                } else {1639                    let (wrote_path, _) =1640                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;16411642                    if !wrote_path {1643                        // Note that we do not explicitly handle `FILENAME_MAX`1644                        // (different from `PATH_MAX` above) as it is Linux-specific and1645                        // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.1646                        this.set_last_error(LibcError("ENAMETOOLONG"))?;1647                        return interp_ok(Scalar::from_target_usize(0, this));1648                    }1649                    processed_ptr1650                };16511652                interp_ok(Scalar::from_maybe_pointer(dest, this))1653            }1654            Err(e) => {1655                this.set_last_error(e)?;1656                interp_ok(Scalar::from_target_usize(0, this))1657            }1658        }1659    }1660    fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1661        use rand::seq::IndexedRandom;16621663        // POSIX defines the template string.1664        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";16651666        let this = self.eval_context_mut();1667        this.assert_target_os_is_unix("mkstemp");16681669        // POSIX defines the maximum number of attempts before failure.1670        //1671        // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.1672        // POSIX says this about `TMP_MAX`:1673        // * Minimum number of unique filenames generated by `tmpnam()`.1674        // * Maximum number of times an application can call `tmpnam()` reliably.1675        //   * The value of `TMP_MAX` is at least 25.1676        //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.1677        // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.1678        let max_attempts = this.eval_libc_u32("TMP_MAX");16791680        // Get the raw bytes from the template -- as a byte slice, this is a string in the target1681        // (and the target is unix, so a byte slice is the right representation).1682        let template_ptr = this.read_pointer(template_op)?;1683        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();1684        let template_bytes = template.as_mut_slice();16851686        // Reject if isolation is enabled.1687        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1688            this.reject_in_isolation("`mkstemp`", reject_with)?;1689            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));1690        }16911692        // Get the bytes of the suffix we expect in _target_ encoding.1693        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();16941695        // At this point we have one `&[u8]` that represents the template and one `&[u8]`1696        // that represents the expected suffix.16971698        // Now we figure out the index of the slice we expect to contain the suffix.1699        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());1700        let end_pos = template_bytes.len();1701        let last_six_char_bytes = &template_bytes[start_pos..end_pos];17021703        // If we don't find the suffix, it is an error.1704        if last_six_char_bytes != suffix_bytes {1705            return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1706        }17071708        // At this point we know we have 6 ASCII 'X' characters as a suffix.17091710        // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>1711        const SUBSTITUTIONS: &[char; 62] = &[1712            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',1713            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',1714            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',1715            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',1716        ];17171718        // The file is opened with specific options, which Rust does not expose in a portable way.1719        // So we use specific APIs depending on the host OS.1720        let mut fopts = OpenOptions::new();1721        fopts.read(true).write(true).create_new(true);17221723        cfg_select! {1724            unix => {1725                use std::os::unix::fs::OpenOptionsExt;1726                // Do not allow others to read or modify this file.1727                fopts.mode(0o600);1728                fopts.custom_flags(libc::O_EXCL);1729            }1730            windows => {1731                use std::os::windows::fs::OpenOptionsExt;1732                // Do not allow others to read or modify this file.1733                fopts.share_mode(0);1734            }1735            _ => {1736                throw_unsup_format!("`mkstemp` is not supported on this host OS");1737            }1738        }17391740        // If the generated file already exists, we will try again `max_attempts` many times.1741        for _ in 0..max_attempts {1742            let rng = this.machine.rng.get_mut();17431744            // Generate a random unique suffix.1745            let unique_suffix =1746                (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();17471748            // Replace the template string with the random string.1749            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());17501751            // Write the modified template back to the passed in pointer to maintain POSIX semantics.1752            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;17531754            // See if we can create and open this file.1755            let file = fopts.open(bytes_to_os_str(template_bytes)?);1756            match file {1757                Ok(f) => {1758                    let fd = this.machine.fds.insert_new(FileHandle {1759                        file: f,1760                        writable: true,1761                        readable: true,1762                    });1763                    return interp_ok(Scalar::from_i32(fd));1764                }1765                Err(e) =>1766                    match e.kind() {1767                        // If the random file already exists, keep trying.1768                        ErrorKind::AlreadyExists => continue,1769                        // Any other errors are returned to the caller.1770                        _ => {1771                            // "On error, -1 is returned, and errno is set to1772                            // indicate the error"1773                            return this.set_errno_and_return_neg1_i32(e);1774                        }1775                    },1776            }1777        }17781779        // We ran out of attempts to create the file, return an error.1780        this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))1781    }1782}17831784/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when1785/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix1786/// epoch.1787fn extract_sec_and_nsec<'tcx>(1788    time: std::io::Result<SystemTime>,1789) -> InterpResult<'tcx, Option<(u64, u32)>> {1790    match time.ok() {1791        Some(time) => {1792            let duration = system_time_to_duration(&time)?;1793            interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))1794        }1795        None => interp_ok(None),1796    }1797}17981799fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {1800    #[cfg(unix)]1801    use std::os::unix::fs::FileTypeExt;18021803    if file_type.is_file() {1804        "S_IFREG"1805    } else if file_type.is_dir() {1806        "S_IFDIR"1807    } else if file_type.is_symlink() {1808        "S_IFLNK"1809    } else {1810        // Certain file types are only available when the host is a Unix system.1811        #[cfg(unix)]1812        {1813            if file_type.is_socket() {1814                return "S_IFSOCK";1815            } else if file_type.is_fifo() {1816                return "S_IFIFO";1817            } else if file_type.is_char_device() {1818                return "S_IFCHR";1819            } else if file_type.is_block_device() {1820                return "S_IFBLK";1821            }1822        }1823        "S_IFREG"1824    }1825}18261827/// Stores a file's metadata in order to avoid code duplication in the different metadata related1828/// shims.1829///1830/// Some fields are host/platform-specific. `None` means that Miri does not have a real value for1831/// this field, for example because the metadata is synthetic or because the host platform does not1832/// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`;1833/// legacy `stat` writes zero for `None` to preserve the old fallback behavior.1834struct FileMetadata {1835    /// This holds both the file type (dir, regular, symlink, ...) and permissions.1836    mode: u32,1837    size: u64,1838    created: Option<(u64, u32)>,1839    accessed: Option<(u64, u32)>,1840    modified: Option<(u64, u32)>,1841    dev: Option<u64>,1842    ino: Option<u64>,1843    nlink: Option<u64>,1844    uid: Option<u32>,1845    gid: Option<u32>,1846    blksize: Option<u64>,1847    blocks: Option<u64>,1848}18491850impl FileMetadata {1851    fn from_path<'tcx>(1852        ecx: &mut MiriInterpCx<'tcx>,1853        path: &Path,1854        follow_symlink: bool,1855    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1856        let metadata =1857            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };18581859        FileMetadata::from_meta(ecx, metadata)1860    }18611862    fn from_fd_num<'tcx>(1863        ecx: &mut MiriInterpCx<'tcx>,1864        fd_num: i32,1865    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1866        let Some(fd) = ecx.machine.fds.get(fd_num) else {1867            return interp_ok(Err(LibcError("EBADF")));1868        };1869        match fd.metadata()? {1870            Either::Left(host) => Self::from_meta(ecx, host),1871            Either::Right(name) => Self::synthetic(ecx, name),1872        }1873    }18741875    fn synthetic<'tcx>(1876        ecx: &mut MiriInterpCx<'tcx>,1877        mode_name: &str,1878    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1879        let mode = ecx.eval_libc(mode_name);1880        let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();1881        // We observed 0x777 on sockets and 0x600 on pipes...1882        let mode = mode | 0o666;1883        interp_ok(Ok(FileMetadata {1884            mode,1885            size: 0,1886            created: None,1887            accessed: None,1888            modified: None,1889            dev: None,1890            uid: None,1891            gid: None,1892            blksize: None,1893            blocks: None,1894            ino: None,1895            nlink: None,1896        }))1897    }18981899    fn from_meta<'tcx>(1900        ecx: &mut MiriInterpCx<'tcx>,1901        metadata: Result<std::fs::Metadata, std::io::Error>,1902    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1903        let metadata = match metadata {1904            Ok(metadata) => metadata,1905            Err(e) => {1906                return interp_ok(Err(e.into()));1907            }1908        };19091910        let file_type = metadata.file_type();1911        let mode = ecx.eval_libc(file_type_to_mode_name(file_type));1912        let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();19131914        let size = metadata.len();19151916        let created = extract_sec_and_nsec(metadata.created())?;1917        let accessed = extract_sec_and_nsec(metadata.accessed())?;1918        let modified = extract_sec_and_nsec(metadata.modified())?;19191920        // FIXME: Provide more fields using platform specific methods.19211922        cfg_select! {1923            unix => {1924                use std::os::unix::fs::{MetadataExt, PermissionsExt};19251926                let dev = metadata.dev();1927                let ino = metadata.ino();1928                let nlink = metadata.nlink();1929                let uid = metadata.uid();1930                let gid = metadata.gid();1931                let blksize = metadata.blksize();1932                let blocks = metadata.blocks();19331934                mode |= metadata.permissions().mode();19351936                interp_ok(Ok(FileMetadata {1937                    mode,1938                    size,1939                    created,1940                    accessed,1941                    modified,1942                    dev: Some(dev),1943                    ino: Some(ino),1944                    nlink: Some(nlink),1945                    uid: Some(uid),1946                    gid: Some(gid),1947                    blksize: Some(blksize),1948                    blocks: Some(blocks),1949                }))1950            }1951            _ => {1952                // Emulate "everyone can read" or "everyone can read and write".1953                mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };19541955                interp_ok(Ok(FileMetadata {1956                    mode,1957                    size,1958                    created,1959                    accessed,1960                    modified,1961                    dev: None,1962                    ino: None,1963                    nlink: None,1964                    uid: None,1965                    gid: None,1966                    blksize: None,1967                    blocks: None,1968                }))1969            }1970        }1971    }1972}

Code quality findings 29

Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("failed to restore file position, this shouldn't be possible");
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("failed to restore file position, this shouldn't be possible");
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
SeekFrom::Start(u64::try_from(offset).unwrap())
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
SeekFrom::Current(i64::try_from(offset).unwrap())
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
SeekFrom::End(i64::try_from(offset).unwrap())
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let name_len = u64::try_from(name_bytes.len()).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
Some(new_size) => u64::try_from(new_size).unwrap(),
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let bufsize: usize = bufsize.try_into().unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
path_bytes = &path_bytes[..bufsize]
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
interp_ok(path_bytes.len().try_into().unwrap())
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("PATH_MAX does not fit in u64");
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let last_six_char_bytes = &template_bytes[start_pos..end_pos];
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
(0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use crate::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use FlockOp::*;
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match file_type {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match () {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match e.kind() {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.