src/tools/miri/src/shims/unix/fs.rs RUST 1,812 lines View on github.com → Search inside
1//! File and file system access23use std::borrow::Cow;4use std::ffi::OsString;5use std::fs::{6    self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file,7    rename,8};9use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};10use std::path::{self, Path, PathBuf};11use std::time::SystemTime;1213use rustc_abi::Size;14use rustc_data_structures::either::Either;15use rustc_data_structures::fx::FxHashMap;16use rustc_target::spec::Os;1718use self::shims::time::system_time_to_duration;19use crate::shims::files::FileHandle;20use crate::shims::os_str::bytes_to_os_str;21use crate::shims::sig::check_min_vararg_count;22use crate::shims::unix::fd::{FlockOp, UnixFileDescription};23use crate::*;2425/// An open directory, tracked by DirHandler.26#[derive(Debug)]27struct OpenDir {28    /// The "special" entries that must still be yielded by the iterator.29    /// Used for `.` and `..`.30    special_entries: Vec<&'static str>,31    /// The directory reader on the host.32    read_dir: fs::ReadDir,33    /// The most recent entry returned by readdir().34    /// Will be freed by the next call.35    entry: Option<Pointer>,36}3738impl OpenDir {39    fn new(read_dir: fs::ReadDir) -> Self {40        Self { special_entries: vec!["..", "."], read_dir, entry: None }41    }4243    fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {44        if let Some(special) = self.special_entries.pop() {45            return Some(Ok(Either::Right(special)));46        }47        let entry = self.read_dir.next()?;48        Some(entry.map(Either::Left))49    }50}5152#[derive(Debug)]53struct DirEntry {54    name: OsString,55    ino: u64,56    d_type: i32,57}5859impl UnixFileDescription for FileHandle {60    fn pread<'tcx>(61        &self,62        communicate_allowed: bool,63        offset: u64,64        ptr: Pointer,65        len: usize,66        ecx: &mut MiriInterpCx<'tcx>,67        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,68    ) -> InterpResult<'tcx> {69        assert!(communicate_allowed, "isolation should have prevented even opening a file");70        let mut bytes = vec![0; len];71        // Emulates pread using seek + read + seek to restore cursor position.72        // Correctness of this emulation relies on sequential nature of Miri execution.73        // The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.74        let file = &mut &self.file;75        let mut f = || {76            let cursor_pos = file.stream_position()?;77            file.seek(SeekFrom::Start(offset))?;78            let res = file.read(&mut bytes);79            // Attempt to restore cursor position even if the read has failed80            file.seek(SeekFrom::Start(cursor_pos))81                .expect("failed to restore file position, this shouldn't be possible");82            res83        };84        let result = match f() {85            Ok(read_size) => {86                // If reading to `bytes` did not fail, we write those bytes to the buffer.87                // Crucially, if fewer than `bytes.len()` bytes were read, only write88                // that much into the output buffer!89                ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;90                Ok(read_size)91            }92            Err(e) => Err(IoError::HostError(e)),93        };94        finish.call(ecx, result)95    }9697    fn pwrite<'tcx>(98        &self,99        communicate_allowed: bool,100        ptr: Pointer,101        len: usize,102        offset: u64,103        ecx: &mut MiriInterpCx<'tcx>,104        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,105    ) -> InterpResult<'tcx> {106        assert!(communicate_allowed, "isolation should have prevented even opening a file");107        // Emulates pwrite using seek + write + seek to restore cursor position.108        // Correctness of this emulation relies on sequential nature of Miri execution.109        // The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.110        let file = &mut &self.file;111        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;112        let mut f = || {113            let cursor_pos = file.stream_position()?;114            file.seek(SeekFrom::Start(offset))?;115            let res = file.write(bytes);116            // Attempt to restore cursor position even if the write has failed117            file.seek(SeekFrom::Start(cursor_pos))118                .expect("failed to restore file position, this shouldn't be possible");119            res120        };121        let result = f();122        finish.call(ecx, result.map_err(IoError::HostError))123    }124125    fn flock<'tcx>(126        &self,127        communicate_allowed: bool,128        op: FlockOp,129    ) -> InterpResult<'tcx, io::Result<()>> {130        assert!(communicate_allowed, "isolation should have prevented even opening a file");131132        use FlockOp::*;133        // We must not block the interpreter loop, so we always `try_lock`.134        let (res, nonblocking) = match op {135            SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),136            ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),137            Unlock => {138                return interp_ok(self.file.unlock());139            }140        };141142        match res {143            Ok(()) => interp_ok(Ok(())),144            Err(TryLockError::Error(err)) => interp_ok(Err(err)),145            Err(TryLockError::WouldBlock) =>146                if nonblocking {147                    interp_ok(Err(ErrorKind::WouldBlock.into()))148                } else {149                    throw_unsup_format!("blocking `flock` is not currently supported");150                },151        }152    }153}154155/// The table of open directories.156/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything157/// is a file, except a directory is not?158#[derive(Debug)]159pub struct DirTable {160    /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,161    /// and closedir.162    ///163    /// When opendir is called, a directory iterator is created on the host for the target164    /// directory, and an entry is stored in this hash map, indexed by an ID which represents165    /// the directory stream. When readdir is called, the directory stream ID is used to look up166    /// the corresponding ReadDir iterator from this map, and information from the next167    /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from168    /// the map.169    streams: FxHashMap<u64, OpenDir>,170    /// ID number to be used by the next call to opendir171    next_id: u64,172}173174impl DirTable {175    #[expect(clippy::arithmetic_side_effects)]176    fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {177        let id = self.next_id;178        self.next_id += 1;179        self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();180        id181    }182}183184impl Default for DirTable {185    fn default() -> DirTable {186        DirTable {187            streams: FxHashMap::default(),188            // Skip 0 as an ID, because it looks like a null pointer to libc189            next_id: 1,190        }191    }192}193194impl VisitProvenance for DirTable {195    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {196        let DirTable { streams, next_id: _ } = self;197198        for dir in streams.values() {199            dir.entry.visit_provenance(visit);200        }201    }202}203204fn maybe_sync_file(205    file: &File,206    writable: bool,207    operation: fn(&File) -> std::io::Result<()>,208) -> std::io::Result<i32> {209    if !writable && cfg!(windows) {210        // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened211        // for writing. (FlushFileBuffers requires that the file handle have the212        // GENERIC_WRITE right)213        Ok(0i32)214    } else {215        let result = operation(file);216        result.map(|_| 0i32)217    }218}219220impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}221trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {222    fn write_stat_buf(223        &mut self,224        metadata: FileMetadata,225        buf_op: &OpTy<'tcx>,226    ) -> InterpResult<'tcx, i32> {227        let this = self.eval_context_mut();228229        let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));230        let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));231        let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));232233        // We do *not* use `deref_pointer_as` here since determining the right pointee type234        // is highly non-trivial: it depends on which exact alias of the function was invoked235        // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level236        // which can be different between the libc used by std and the libc used by everyone else.237        let buf = this.deref_pointer(buf_op)?;238239        // `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets240        // (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target.241        let mode_t_size = this.libc_ty_layout("mode_t").size;242        let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap();243244        this.write_int_fields_named(245            &[246                ("st_dev", metadata.dev.unwrap_or(0).into()),247                ("st_mode", mode.into()),248                ("st_nlink", metadata.nlink.unwrap_or(0).into()),249                ("st_ino", metadata.ino.unwrap_or(0).into()),250                ("st_uid", metadata.uid.unwrap_or(0).into()),251                ("st_gid", metadata.gid.unwrap_or(0).into()),252                ("st_rdev", 0),253                ("st_atime", access_sec.into()),254                ("st_atime_nsec", access_nsec.into()),255                ("st_mtime", modified_sec.into()),256                ("st_mtime_nsec", modified_nsec.into()),257                ("st_ctime", 0),258                ("st_ctime_nsec", 0),259                ("st_size", metadata.size.into()),260                ("st_blocks", metadata.blocks.unwrap_or(0).into()),261                ("st_blksize", metadata.blksize.unwrap_or(0).into()),262            ],263            &buf,264        )?;265266        if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {267            this.write_int_fields_named(268                &[269                    ("st_birthtime", created_sec.into()),270                    ("st_birthtime_nsec", created_nsec.into()),271                    ("st_flags", 0),272                    ("st_gen", 0),273                ],274                &buf,275            )?;276        }277278        if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {279            let st_fstype = this.project_field_named(&buf, "st_fstype")?;280            // This is an array; write 0 into first element so that it encodes the empty string.281            this.write_int(0, &this.project_index(&st_fstype, 0)?)?;282        }283284        interp_ok(0)285    }286287    fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {288        #[cfg(unix)]289        use std::os::unix::fs::FileTypeExt;290291        let this = self.eval_context_ref();292        match file_type {293            Ok(file_type) => {294                match () {295                    _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),296                    _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),297                    _ if file_type.is_symlink() =>298                        interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),299                    // Certain file types are only supported when the host is a Unix system.300                    #[cfg(unix)]301                    _ if file_type.is_block_device() =>302                        interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),303                    #[cfg(unix)]304                    _ if file_type.is_char_device() =>305                        interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),306                    #[cfg(unix)]307                    _ if file_type.is_fifo() =>308                        interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),309                    #[cfg(unix)]310                    _ if file_type.is_socket() =>311                        interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),312                    // Fallback313                    _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),314                }315            }316            Err(_) => {317                // Fallback on error318                interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())319            }320        }321    }322323    fn dir_entry_fields(324        &self,325        entry: Either<fs::DirEntry, &'static str>,326    ) -> InterpResult<'tcx, DirEntry> {327        let this = self.eval_context_ref();328        interp_ok(match entry {329            Either::Left(dir_entry) => {330                DirEntry {331                    name: dir_entry.file_name(),332                    d_type: this.file_type_to_d_type(dir_entry.file_type())?,333                    // If the host is a Unix system, fill in the inode number with its real value.334                    // If not, use 0 as a fallback value.335                    #[cfg(unix)]336                    ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),337                    #[cfg(not(unix))]338                    ino: 0u64,339                }340            }341            Either::Right(special) =>342                DirEntry {343                    name: special.into(),344                    d_type: this.eval_libc("DT_DIR").to_u8()?.into(),345                    ino: 0,346                },347        })348    }349}350351impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}352pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {353    fn open(354        &mut self,355        path_raw: &OpTy<'tcx>,356        flag: &OpTy<'tcx>,357        varargs: &[OpTy<'tcx>],358    ) -> InterpResult<'tcx, Scalar> {359        let this = self.eval_context_mut();360361        let path_raw = this.read_pointer(path_raw)?;362        let flag = this.read_scalar(flag)?.to_i32()?;363364        let path = this.read_path_from_c_str(path_raw)?;365        // Files in `/proc` won't work properly.366        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)367            && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))368        {369            this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);370        }371372        // We will "subtract" supported flags from this and at the end check that no bits are left.373        let mut flag = flag;374375        let mut options = OpenOptions::new();376377        let o_rdonly = this.eval_libc_i32("O_RDONLY");378        let o_wronly = this.eval_libc_i32("O_WRONLY");379        let o_rdwr = this.eval_libc_i32("O_RDWR");380        // The first two bits of the flag correspond to the access mode in linux, macOS and381        // windows. We need to check that in fact the access mode flags for the current target382        // only use these two bits, otherwise we are in an unsupported target and should error.383        if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {384            throw_unsup_format!("access mode flags on this target are unsupported");385        }386        let mut writable = true;387388        // Now we check the access mode389        let access_mode = flag & 0b11;390        flag &= !access_mode;391392        if access_mode == o_rdonly {393            writable = false;394            options.read(true);395        } else if access_mode == o_wronly {396            options.write(true);397        } else if access_mode == o_rdwr {398            options.read(true).write(true);399        } else {400            throw_unsup_format!("unsupported access mode {:#x}", access_mode);401        }402403        let o_append = this.eval_libc_i32("O_APPEND");404        if flag & o_append == o_append {405            flag &= !o_append;406            options.append(true);407        }408        let o_trunc = this.eval_libc_i32("O_TRUNC");409        if flag & o_trunc == o_trunc {410            flag &= !o_trunc;411            options.truncate(true);412        }413        let o_creat = this.eval_libc_i32("O_CREAT");414        if flag & o_creat == o_creat {415            flag &= !o_creat;416            // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but417            // C integer promotion rules mean that on the ABI level, it gets passed as `u32`418            // (see https://github.com/rust-lang/rust/issues/71915).419            let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;420            let mode = this.read_scalar(mode)?.to_u32()?;421422            #[cfg(unix)]423            {424                // Support all modes on UNIX host425                use std::os::unix::fs::OpenOptionsExt;426                options.mode(mode);427            }428            #[cfg(not(unix))]429            {430                // Only support default mode for non-UNIX (i.e. Windows) host431                if mode != 0o666 {432                    throw_unsup_format!(433                        "non-default mode 0o{:o} is not supported on non-Unix hosts",434                        mode435                    );436                }437            }438439            let o_excl = this.eval_libc_i32("O_EXCL");440            if flag & o_excl == o_excl {441                flag &= !o_excl;442                options.create_new(true);443            } else {444                options.create(true);445            }446        }447        let o_cloexec = this.eval_libc_i32("O_CLOEXEC");448        if flag & o_cloexec == o_cloexec {449            flag &= !o_cloexec;450            // We do not need to do anything for this flag because `std` already sets it.451            // (Technically we do not support *not* setting this flag, but we ignore that.)452        }453        if this.tcx.sess.target.os == Os::Linux {454            let o_tmpfile = this.eval_libc_i32("O_TMPFILE");455            if flag & o_tmpfile == o_tmpfile {456                // if the flag contains `O_TMPFILE` then we return a graceful error457                return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));458            }459        }460461        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");462        if flag & o_nofollow == o_nofollow {463            flag &= !o_nofollow;464            #[cfg(unix)]465            {466                use std::os::unix::fs::OpenOptionsExt;467                options.custom_flags(libc::O_NOFOLLOW);468            }469            // Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:470            // the path could change between us checking it here and the later call to `open`.471            // But it's good enough for Miri purposes.472            #[cfg(not(unix))]473            {474                // O_NOFOLLOW only fails when the trailing component is a symlink;475                // the entire rest of the path can still contain symlinks.476                if path.is_symlink() {477                    return this.set_last_error_and_return_i32(LibcError("ELOOP"));478                }479            }480        }481482        // If `flag` has any bits left set, those are not supported.483        if flag != 0 {484            throw_unsup_format!("unsupported flags {:#x}", flag);485        }486487        // Reject if isolation is enabled.488        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {489            this.reject_in_isolation("`open`", reject_with)?;490            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);491        }492493        let fd = options494            .open(path)495            .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));496497        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))498    }499500    fn lseek(501        &mut self,502        fd_num: i32,503        offset: i128,504        whence: i32,505        dest: &MPlaceTy<'tcx>,506    ) -> InterpResult<'tcx> {507        let this = self.eval_context_mut();508509        // Isolation check is done via `FileDescription` trait.510511        let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {512            if offset < 0 {513                // Negative offsets return `EINVAL`.514                return this.set_last_error_and_return(LibcError("EINVAL"), dest);515            } else {516                SeekFrom::Start(u64::try_from(offset).unwrap())517            }518        } else if whence == this.eval_libc_i32("SEEK_CUR") {519            SeekFrom::Current(i64::try_from(offset).unwrap())520        } else if whence == this.eval_libc_i32("SEEK_END") {521            SeekFrom::End(i64::try_from(offset).unwrap())522        } else {523            return this.set_last_error_and_return(LibcError("EINVAL"), dest);524        };525526        let communicate = this.machine.communicate();527528        let Some(fd) = this.machine.fds.get(fd_num) else {529            return this.set_last_error_and_return(LibcError("EBADF"), dest);530        };531        let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());532        drop(fd);533534        let result = this.try_unwrap_io_result(result)?;535        this.write_int(result, dest)?;536        interp_ok(())537    }538539    fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {540        let this = self.eval_context_mut();541542        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;543544        // Reject if isolation is enabled.545        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {546            this.reject_in_isolation("`unlink`", reject_with)?;547            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);548        }549550        let result = remove_file(path).map(|_| 0);551        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))552    }553554    fn symlink(555        &mut self,556        target_op: &OpTy<'tcx>,557        linkpath_op: &OpTy<'tcx>,558    ) -> InterpResult<'tcx, Scalar> {559        #[cfg(unix)]560        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {561            std::os::unix::fs::symlink(src, dst)562        }563564        #[cfg(windows)]565        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {566            use std::os::windows::fs;567            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }568        }569570        let this = self.eval_context_mut();571        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;572        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;573574        // Reject if isolation is enabled.575        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {576            this.reject_in_isolation("`symlink`", reject_with)?;577            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);578        }579580        let result = create_link(&target, &linkpath).map(|_| 0);581        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))582    }583584    fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {585        let this = self.eval_context_mut();586587        if !matches!(588            &this.tcx.sess.target.os,589            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux590        ) {591            panic!("`stat` should not be called on {}", this.tcx.sess.target.os);592        }593594        let path_scalar = this.read_pointer(path_op)?;595        let path = this.read_path_from_c_str(path_scalar)?.into_owned();596597        // Reject if isolation is enabled.598        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {599            this.reject_in_isolation("`stat`", reject_with)?;600            return this.set_last_error_and_return_i32(LibcError("EACCES"));601        }602603        // `stat` always follows symlinks.604        let metadata = match FileMetadata::from_path(this, &path, true)? {605            Ok(metadata) => metadata,606            Err(err) => return this.set_last_error_and_return_i32(err),607        };608609        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))610    }611612    // `lstat` is used to get symlink metadata.613    fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {614        let this = self.eval_context_mut();615616        if !matches!(617            &this.tcx.sess.target.os,618            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux619        ) {620            panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);621        }622623        let path_scalar = this.read_pointer(path_op)?;624        let path = this.read_path_from_c_str(path_scalar)?.into_owned();625626        // Reject if isolation is enabled.627        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {628            this.reject_in_isolation("`lstat`", reject_with)?;629            return this.set_last_error_and_return_i32(LibcError("EACCES"));630        }631632        let metadata = match FileMetadata::from_path(this, &path, false)? {633            Ok(metadata) => metadata,634            Err(err) => return this.set_last_error_and_return_i32(err),635        };636637        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))638    }639640    fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {641        let this = self.eval_context_mut();642643        if !matches!(644            &this.tcx.sess.target.os,645            Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android646        ) {647            panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);648        }649650        let fd = this.read_scalar(fd_op)?.to_i32()?;651652        // Reject if isolation is enabled.653        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {654            this.reject_in_isolation("`fstat`", reject_with)?;655            // Set error code as "EBADF" (bad fd)656            return this.set_last_error_and_return_i32(LibcError("EBADF"));657        }658659        let metadata = match FileMetadata::from_fd_num(this, fd)? {660            Ok(metadata) => metadata,661            Err(err) => return this.set_last_error_and_return_i32(err),662        };663        interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))664    }665666    fn linux_statx(667        &mut self,668        dirfd_op: &OpTy<'tcx>,    // Should be an `int`669        pathname_op: &OpTy<'tcx>, // Should be a `const char *`670        flags_op: &OpTy<'tcx>,    // Should be an `int`671        mask_op: &OpTy<'tcx>,     // Should be an `unsigned int`672        statxbuf_op: &OpTy<'tcx>, // Should be a `struct statx *`673    ) -> InterpResult<'tcx, Scalar> {674        let this = self.eval_context_mut();675676        this.assert_target_os(Os::Linux, "statx");677678        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;679        let pathname_ptr = this.read_pointer(pathname_op)?;680        let flags = this.read_scalar(flags_op)?.to_i32()?;681        let _mask = this.read_scalar(mask_op)?.to_u32()?;682        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;683684        // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.685        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {686            return this.set_last_error_and_return_i32(LibcError("EFAULT"));687        }688689        let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;690691        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();692        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.693        let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");694        let empty_path_flag = flags & at_empty_path == at_empty_path;695        // We only support:696        // * interpreting `path` as an absolute directory,697        // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or698        // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is699        // set.700        // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you701        // found this error, please open an issue reporting it.702        if !(path.is_absolute()703            || dirfd == this.eval_libc_i32("AT_FDCWD")704            || (path.as_os_str().is_empty() && empty_path_flag))705        {706            throw_unsup_format!(707                "using statx is only supported with absolute paths, relative paths with the file \708                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \709                file descriptor"710            )711        }712713        // Reject if isolation is enabled.714        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {715            this.reject_in_isolation("`statx`", reject_with)?;716            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {717                // since `path` is provided, either absolute or718                // relative to CWD, `EACCES` is the most relevant.719                LibcError("EACCES")720            } else {721                // `dirfd` is set to target file, and `path` is empty722                // (or we would have hit the `throw_unsup_format`723                // above). `EACCES` would violate the spec.724                assert!(empty_path_flag);725                LibcError("EBADF")726            };727            return this.set_last_error_and_return_i32(ecode);728        }729730        // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following731        // symbolic links.732        let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;733734        // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file735        // represented by dirfd, whether it's a directory or otherwise.736        let metadata = if path.as_os_str().is_empty() && empty_path_flag {737            FileMetadata::from_fd_num(this, dirfd)?738        } else {739            FileMetadata::from_path(this, &path, follow_symlink)?740        };741        let metadata = match metadata {742            Ok(metadata) => metadata,743            Err(err) => return this.set_last_error_and_return_i32(err),744        };745746        // The `_mask_op` parameter specifies the file information that the caller requested.747        // However, `statx` is allowed to return information that was not requested or to not748        // return information that was requested. This `mask` represents the information we can749        // actually provide for any target.750        let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");751752        // Check which pieces of metadata we acquired, and set the appropriate flags in the mask.753        if metadata.ino.is_some() {754            mask |= this.eval_libc_u32("STATX_INO");755        }756        if metadata.nlink.is_some() {757            mask |= this.eval_libc_u32("STATX_NLINK");758        }759        if metadata.uid.is_some() {760            mask |= this.eval_libc_u32("STATX_UID");761        }762        if metadata.gid.is_some() {763            mask |= this.eval_libc_u32("STATX_GID");764        }765        if metadata.blocks.is_some() {766            mask |= this.eval_libc_u32("STATX_BLOCKS");767        }768769        // `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in770        // width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size.771        let mode_t_size = this.libc_ty_layout("mode_t").size;772        let mode: u16 = metadata773            .mode774            .to_uint(mode_t_size)?775            .try_into()776            .unwrap_or_else(|_| bug!("libc contains bad value for constant"));777778        // We need to set the corresponding bits of `mask` if the access, creation and modification779        // times were available. Otherwise we let them be zero.780        let (access_sec, access_nsec) = metadata781            .accessed782            .map(|tup| {783                mask |= this.eval_libc_u32("STATX_ATIME");784                interp_ok(tup)785            })786            .unwrap_or_else(|| interp_ok((0, 0)))?;787788        let (created_sec, created_nsec) = metadata789            .created790            .map(|tup| {791                mask |= this.eval_libc_u32("STATX_BTIME");792                interp_ok(tup)793            })794            .unwrap_or_else(|| interp_ok((0, 0)))?;795796        let (modified_sec, modified_nsec) = metadata797            .modified798            .map(|tup| {799                mask |= this.eval_libc_u32("STATX_MTIME");800                interp_ok(tup)801            })802            .unwrap_or_else(|| interp_ok((0, 0)))?;803804        // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.805        this.write_int_fields_named(806            &[807                ("stx_mask", mask.into()),808                ("stx_blksize", metadata.blksize.unwrap_or(0).into()),809                ("stx_attributes", 0),810                ("stx_nlink", metadata.nlink.unwrap_or(0).into()),811                ("stx_uid", metadata.uid.unwrap_or(0).into()),812                ("stx_gid", metadata.gid.unwrap_or(0).into()),813                ("stx_mode", mode.into()),814                ("stx_ino", metadata.ino.unwrap_or(0).into()),815                ("stx_size", metadata.size.into()),816                ("stx_blocks", metadata.blocks.unwrap_or(0).into()),817                ("stx_attributes_mask", 0),818                ("stx_rdev_major", 0),819                ("stx_rdev_minor", 0),820                ("stx_dev_major", 0),821                ("stx_dev_minor", 0),822            ],823            &statxbuf,824        )?;825        #[rustfmt::skip]826        this.write_int_fields_named(827            &[828                ("tv_sec", access_sec.into()),829                ("tv_nsec", access_nsec.into()),830            ],831            &this.project_field_named(&statxbuf, "stx_atime")?,832        )?;833        #[rustfmt::skip]834        this.write_int_fields_named(835            &[836                ("tv_sec", created_sec.into()),837                ("tv_nsec", created_nsec.into()),838            ],839            &this.project_field_named(&statxbuf, "stx_btime")?,840        )?;841        #[rustfmt::skip]842        this.write_int_fields_named(843            &[844                ("tv_sec", 0.into()),845                ("tv_nsec", 0.into()),846            ],847            &this.project_field_named(&statxbuf, "stx_ctime")?,848        )?;849        #[rustfmt::skip]850        this.write_int_fields_named(851            &[852                ("tv_sec", modified_sec.into()),853                ("tv_nsec", modified_nsec.into()),854            ],855            &this.project_field_named(&statxbuf, "stx_mtime")?,856        )?;857858        interp_ok(Scalar::from_i32(0))859    }860861    fn rename(862        &mut self,863        oldpath_op: &OpTy<'tcx>,864        newpath_op: &OpTy<'tcx>,865    ) -> InterpResult<'tcx, Scalar> {866        let this = self.eval_context_mut();867868        let oldpath_ptr = this.read_pointer(oldpath_op)?;869        let newpath_ptr = this.read_pointer(newpath_op)?;870871        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {872            return this.set_last_error_and_return_i32(LibcError("EFAULT"));873        }874875        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;876        let newpath = this.read_path_from_c_str(newpath_ptr)?;877878        // Reject if isolation is enabled.879        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {880            this.reject_in_isolation("`rename`", reject_with)?;881            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);882        }883884        let result = rename(oldpath, newpath).map(|_| 0);885886        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))887    }888889    fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {890        let this = self.eval_context_mut();891892        #[cfg_attr(not(unix), allow(unused_variables))]893        let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {894            u32::from(this.read_scalar(mode_op)?.to_u16()?)895        } else {896            this.read_scalar(mode_op)?.to_u32()?897        };898899        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;900901        // Reject if isolation is enabled.902        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {903            this.reject_in_isolation("`mkdir`", reject_with)?;904            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);905        }906907        #[cfg_attr(not(unix), allow(unused_mut))]908        let mut builder = DirBuilder::new();909910        // If the host supports it, forward on the mode of the directory911        // (i.e. permission bits and the sticky bit)912        #[cfg(unix)]913        {914            use std::os::unix::fs::DirBuilderExt;915            builder.mode(mode);916        }917918        let result = builder.create(path).map(|_| 0i32);919920        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))921    }922923    fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {924        let this = self.eval_context_mut();925926        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;927928        // Reject if isolation is enabled.929        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {930            this.reject_in_isolation("`rmdir`", reject_with)?;931            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);932        }933934        let result = remove_dir(path).map(|_| 0i32);935936        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))937    }938939    fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {940        let this = self.eval_context_mut();941942        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;943944        // Reject if isolation is enabled.945        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {946            this.reject_in_isolation("`opendir`", reject_with)?;947            this.set_last_error(LibcError("EACCES"))?;948            return interp_ok(Scalar::null_ptr(this));949        }950951        let result = read_dir(name);952953        match result {954            Ok(dir_iter) => {955                let id = this.machine.dirs.insert_new(dir_iter);956957                // The libc API for opendir says that this method returns a pointer to an opaque958                // structure, but we are returning an ID number. Thus, pass it as a scalar of959                // pointer width.960                interp_ok(Scalar::from_target_usize(id, this))961            }962            Err(e) => {963                this.set_last_error(e)?;964                interp_ok(Scalar::null_ptr(this))965            }966        }967    }968969    fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {970        let this = self.eval_context_mut();971972        if !matches!(973            &this.tcx.sess.target.os,974            Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd975        ) {976            panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);977        }978979        let dirp = this.read_target_usize(dirp_op)?;980981        // Reject if isolation is enabled.982        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {983            this.reject_in_isolation("`readdir`", reject_with)?;984            this.set_last_error(LibcError("EBADF"))?;985            this.write_null(dest)?;986            return interp_ok(());987        }988989        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {990            err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")991        })?;992993        let entry = match open_dir.next_host_entry() {994            Some(Ok(dir_entry)) => {995                let dir_entry = this.dir_entry_fields(dir_entry)?;996997                // Write the directory entry into a newly allocated buffer.998                // The name is written with write_bytes, while the rest of the999                // dirent64 (or dirent) struct is written using write_int_fields.10001001                // For reference:1002                // On Linux:1003                // pub struct dirent64 {1004                //     pub d_ino: ino64_t,1005                //     pub d_off: off64_t,1006                //     pub d_reclen: c_ushort,1007                //     pub d_type: c_uchar,1008                //     pub d_name: [c_char; 256],1009                // }1010                //1011                // On Solaris:1012                // pub struct dirent {1013                //     pub d_ino: ino64_t,1014                //     pub d_off: off64_t,1015                //     pub d_reclen: c_ushort,1016                //     pub d_name: [c_char; 3],1017                // }1018                //1019                // On FreeBSD:1020                // pub struct dirent {1021                //     pub d_fileno: uint32_t,1022                //     pub d_reclen: uint16_t,1023                //     pub d_type: uint8_t,1024                //     pub d_namlen: uint8_t,1025                //     pub d_name: [c_char; 256],1026                // }10271028                // We just use the pointee type here since determining the right pointee type1029                // independently is highly non-trivial: it depends on which exact alias of the1030                // function was invoked (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also1031                // depends on the ABI level which can be different between the libc used by std and1032                // the libc used by everyone else.1033                let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();1034                let dirent_layout = this.layout_of(dirent_ty)?;1035                let fields = &dirent_layout.fields;1036                let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();10371038                // Determine the size of the buffer we have to allocate.1039                let mut name = dir_entry.name; // not a Path as there are no separators!1040                name.push("\0"); // Add a NUL terminator1041                let name_bytes = name.as_encoded_bytes();1042                let name_len = u64::try_from(name_bytes.len()).unwrap();1043                let size = d_name_offset.strict_add(name_len);10441045                let entry = this.allocate_ptr(1046                    Size::from_bytes(size),1047                    dirent_layout.align.abi,1048                    MiriMemoryKind::Runtime.into(),1049                    AllocInit::Uninit,1050                )?;1051                let entry = this.ptr_to_mplace(entry.into(), dirent_layout);10521053                // Write the name.1054                // The name is not a normal field, we already computed the offset above.1055                let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);1056                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;10571058                // Write common fields.1059                let ino_name =1060                    if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };1061                this.write_int_fields_named(1062                    &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],1063                    &entry,1064                )?;10651066                // Write "optional" fields.1067                if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {1068                    this.write_null(&d_off)?;1069                }1070                if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {1071                    this.write_int(name_len.strict_sub(1), &d_namlen)?;1072                }1073                if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {1074                    this.write_int(dir_entry.d_type, &d_type)?;1075                }10761077                Some(entry.ptr())1078            }1079            None => {1080                // end of stream: return NULL1081                None1082            }1083            Some(Err(e)) => {1084                this.set_last_error(e)?;1085                None1086            }1087        };10881089        let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();1090        let old_entry = std::mem::replace(&mut open_dir.entry, entry);1091        if let Some(old_entry) = old_entry {1092            this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;1093        }10941095        this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;1096        interp_ok(())1097    }10981099    fn macos_readdir_r(1100        &mut self,1101        dirp_op: &OpTy<'tcx>,1102        entry_op: &OpTy<'tcx>,1103        result_op: &OpTy<'tcx>,1104    ) -> InterpResult<'tcx, Scalar> {1105        let this = self.eval_context_mut();11061107        this.assert_target_os(Os::MacOs, "readdir_r");11081109        let dirp = this.read_target_usize(dirp_op)?;1110        let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;11111112        // Reject if isolation is enabled.1113        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1114            this.reject_in_isolation("`readdir_r`", reject_with)?;1115            // Return error code, do *not* set `errno`.1116            return interp_ok(this.eval_libc("EBADF"));1117        }11181119        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {1120            err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")1121        })?;1122        interp_ok(match open_dir.next_host_entry() {1123            Some(Ok(dir_entry)) => {1124                let dir_entry = this.dir_entry_fields(dir_entry)?;1125                // Write into entry, write pointer to result, return 0 on success.1126                // The name is written with write_os_str_to_c_str, while the rest of the1127                // dirent struct is written using write_int_fields.11281129                // For reference, on macOS this looks like:1130                // pub struct dirent {1131                //     pub d_ino: u64,1132                //     pub d_seekoff: u64,1133                //     pub d_reclen: u16,1134                //     pub d_namlen: u16,1135                //     pub d_type: u8,1136                //     pub d_name: [c_char; 1024],1137                // }11381139                let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;11401141                // Write the name.1142                let name_place = this.project_field_named(&entry_place, "d_name")?;1143                let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(1144                    &dir_entry.name,1145                    name_place.ptr(),1146                    name_place.layout.size.bytes(),1147                )?;1148                if !name_fits {1149                    throw_unsup_format!(1150                        "a directory entry had a name too large to fit in libc::dirent"1151                    );1152                }11531154                // Write the other fields.1155                this.write_int_fields_named(1156                    &[1157                        ("d_reclen", entry_place.layout.size.bytes().into()),1158                        ("d_namlen", file_name_buf_len.strict_sub(1).into()),1159                        ("d_type", dir_entry.d_type.into()),1160                        ("d_ino", dir_entry.ino.into()),1161                        ("d_seekoff", 0),1162                    ],1163                    &entry_place,1164                )?;1165                this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;11661167                Scalar::from_i32(0)1168            }1169            None => {1170                // end of stream: return 0, assign *result=NULL1171                this.write_null(&result_place)?;1172                Scalar::from_i32(0)1173            }1174            Some(Err(e)) => {1175                // return positive error number on error (do *not* set last error)1176                this.io_error_to_errnum(e)?1177            }1178        })1179    }11801181    fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1182        let this = self.eval_context_mut();11831184        let dirp = this.read_target_usize(dirp_op)?;11851186        // Reject if isolation is enabled.1187        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1188            this.reject_in_isolation("`closedir`", reject_with)?;1189            return this.set_last_error_and_return_i32(LibcError("EBADF"));1190        }11911192        let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {1193            return this.set_last_error_and_return_i32(LibcError("EBADF"));1194        };1195        if let Some(entry) = open_dir.entry.take() {1196            this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;1197        }1198        // We drop the `open_dir`, which will close the host dir handle.1199        drop(open_dir);12001201        interp_ok(Scalar::from_i32(0))1202    }12031204    fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {1205        let this = self.eval_context_mut();12061207        // Reject if isolation is enabled.1208        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1209            this.reject_in_isolation("`ftruncate64`", reject_with)?;1210            // Set error code as "EBADF" (bad fd)1211            return this.set_last_error_and_return_i32(LibcError("EBADF"));1212        }12131214        let Some(fd) = this.machine.fds.get(fd_num) else {1215            return this.set_last_error_and_return_i32(LibcError("EBADF"));1216        };12171218        let Some(file) = fd.downcast::<FileHandle>() else {1219            // The docs say that EINVAL is returned when the FD "does not reference a regular file1220            // or a POSIX shared memory object" (and we don't support shmem objects).1221            return interp_ok(this.eval_libc("EINVAL"));1222        };12231224        if file.writable {1225            if let Ok(length) = length.try_into() {1226                let result = file.file.set_len(length);1227                let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;1228                interp_ok(Scalar::from_i32(result))1229            } else {1230                this.set_last_error_and_return_i32(LibcError("EINVAL"))1231            }1232        } else {1233            // The file is not writable1234            this.set_last_error_and_return_i32(LibcError("EINVAL"))1235        }1236    }12371238    /// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead1239    /// of setting `errno`.1240    fn posix_fallocate(1241        &mut self,1242        fd_num: i32,1243        offset: i64,1244        len: i64,1245    ) -> InterpResult<'tcx, Scalar> {1246        let this = self.eval_context_mut();12471248        // Reject if isolation is enabled.1249        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1250            this.reject_in_isolation("`posix_fallocate`", reject_with)?;1251            // Return error code "EBADF" (bad fd).1252            return interp_ok(this.eval_libc("EBADF"));1253        }12541255        // EINVAL is returned when: "offset was less than 0, or len was less than or equal to 0".1256        if offset < 0 || len <= 0 {1257            return interp_ok(this.eval_libc("EINVAL"));1258        }12591260        // Get the file handle.1261        let Some(fd) = this.machine.fds.get(fd_num) else {1262            return interp_ok(this.eval_libc("EBADF"));1263        };1264        let Some(file) = fd.downcast::<FileHandle>() else {1265            // Man page specifies to return ENODEV if `fd` is not a regular file.1266            return interp_ok(this.eval_libc("ENODEV"));1267        };12681269        if !file.writable {1270            // The file is not writable.1271            return interp_ok(this.eval_libc("EBADF"));1272        }12731274        let current_size = match file.file.metadata() {1275            Ok(metadata) => metadata.len(),1276            Err(err) => return this.io_error_to_errnum(err),1277        };1278        // Checked i64 addition, to ensure the result does not exceed the max file size.1279        let new_size = match offset.checked_add(len) {1280            // `new_size` is definitely non-negative, so we can cast to `u64`.1281            Some(new_size) => u64::try_from(new_size).unwrap(),1282            None => return interp_ok(this.eval_libc("EFBIG")), // new size too big1283        };1284        // If the size of the file is less than offset+size, then the file is increased to this size;1285        // otherwise the file size is left unchanged.1286        if current_size < new_size {1287            interp_ok(match file.file.set_len(new_size) {1288                Ok(()) => Scalar::from_i32(0),1289                Err(e) => this.io_error_to_errnum(e)?,1290            })1291        } else {1292            interp_ok(Scalar::from_i32(0))1293        }1294    }12951296    fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1297        // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the1298        // underlying disk to finish writing. In the interest of host compatibility,1299        // we conservatively implement this with `sync_all`, which1300        // *does* wait for the disk.13011302        let this = self.eval_context_mut();13031304        let fd = this.read_scalar(fd_op)?.to_i32()?;13051306        // Reject if isolation is enabled.1307        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1308            this.reject_in_isolation("`fsync`", reject_with)?;1309            // Set error code as "EBADF" (bad fd)1310            return this.set_last_error_and_return_i32(LibcError("EBADF"));1311        }13121313        self.ffullsync_fd(fd)1314    }13151316    fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {1317        let this = self.eval_context_mut();1318        let Some(fd) = this.machine.fds.get(fd_num) else {1319            return this.set_last_error_and_return_i32(LibcError("EBADF"));1320        };1321        // Only regular files support synchronization.1322        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1323            err_unsup_format!("`fsync` is only supported on file-backed file descriptors")1324        })?;1325        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);1326        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1327    }13281329    fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1330        let this = self.eval_context_mut();13311332        let fd = this.read_scalar(fd_op)?.to_i32()?;13331334        // Reject if isolation is enabled.1335        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1336            this.reject_in_isolation("`fdatasync`", reject_with)?;1337            // Set error code as "EBADF" (bad fd)1338            return this.set_last_error_and_return_i32(LibcError("EBADF"));1339        }13401341        let Some(fd) = this.machine.fds.get(fd) else {1342            return this.set_last_error_and_return_i32(LibcError("EBADF"));1343        };1344        // Only regular files support synchronization.1345        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1346            err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")1347        })?;1348        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);1349        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1350    }13511352    fn sync_file_range(1353        &mut self,1354        fd_op: &OpTy<'tcx>,1355        offset_op: &OpTy<'tcx>,1356        nbytes_op: &OpTy<'tcx>,1357        flags_op: &OpTy<'tcx>,1358    ) -> InterpResult<'tcx, Scalar> {1359        let this = self.eval_context_mut();13601361        let fd = this.read_scalar(fd_op)?.to_i32()?;1362        let offset = this.read_scalar(offset_op)?.to_i64()?;1363        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;1364        let flags = this.read_scalar(flags_op)?.to_i32()?;13651366        if offset < 0 || nbytes < 0 {1367            return this.set_last_error_and_return_i32(LibcError("EINVAL"));1368        }1369        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")1370            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")1371            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");1372        if flags & allowed_flags != flags {1373            return this.set_last_error_and_return_i32(LibcError("EINVAL"));1374        }13751376        // Reject if isolation is enabled.1377        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1378            this.reject_in_isolation("`sync_file_range`", reject_with)?;1379            // Set error code as "EBADF" (bad fd)1380            return this.set_last_error_and_return_i32(LibcError("EBADF"));1381        }13821383        let Some(fd) = this.machine.fds.get(fd) else {1384            return this.set_last_error_and_return_i32(LibcError("EBADF"));1385        };1386        // Only regular files support synchronization.1387        let file = fd.downcast::<FileHandle>().ok_or_else(|| {1388            err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")1389        })?;1390        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);1391        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))1392    }13931394    fn readlink(1395        &mut self,1396        pathname_op: &OpTy<'tcx>,1397        buf_op: &OpTy<'tcx>,1398        bufsize_op: &OpTy<'tcx>,1399    ) -> InterpResult<'tcx, i64> {1400        let this = self.eval_context_mut();14011402        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;1403        let buf = this.read_pointer(buf_op)?;1404        let bufsize = this.read_target_usize(bufsize_op)?;14051406        // Reject if isolation is enabled.1407        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1408            this.reject_in_isolation("`readlink`", reject_with)?;1409            this.set_last_error(LibcError("EACCES"))?;1410            return interp_ok(-1);1411        }14121413        let result = std::fs::read_link(pathname);1414        match result {1415            Ok(resolved) => {1416                // 'readlink' truncates the resolved path if the provided buffer is not large1417                // enough, and does *not* add a null terminator. That means we cannot use the usual1418                // `write_path_to_c_str` and have to re-implement parts of it ourselves.1419                let resolved = this.convert_path(1420                    Cow::Borrowed(resolved.as_ref()),1421                    crate::shims::os_str::PathConversion::HostToTarget,1422                );1423                let mut path_bytes = resolved.as_encoded_bytes();1424                let bufsize: usize = bufsize.try_into().unwrap();1425                if path_bytes.len() > bufsize {1426                    path_bytes = &path_bytes[..bufsize]1427                }1428                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;1429                interp_ok(path_bytes.len().try_into().unwrap())1430            }1431            Err(e) => {1432                this.set_last_error(e)?;1433                interp_ok(-1)1434            }1435        }1436    }14371438    fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1439        let this = self.eval_context_mut();1440        // "returns 1 if fd is an open file descriptor referring to a terminal;1441        // otherwise 0 is returned, and errno is set to indicate the error"1442        let fd = this.read_scalar(miri_fd)?.to_i32()?;1443        let error = if let Some(fd) = this.machine.fds.get(fd) {1444            if fd.is_tty(this.machine.communicate()) {1445                return interp_ok(Scalar::from_i32(1));1446            } else {1447                LibcError("ENOTTY")1448            }1449        } else {1450            // FD does not exist1451            LibcError("EBADF")1452        };1453        this.set_last_error(error)?;1454        interp_ok(Scalar::from_i32(0))1455    }14561457    fn realpath(1458        &mut self,1459        path_op: &OpTy<'tcx>,1460        processed_path_op: &OpTy<'tcx>,1461    ) -> InterpResult<'tcx, Scalar> {1462        let this = self.eval_context_mut();1463        this.assert_target_os_is_unix("realpath");14641465        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;1466        let processed_ptr = this.read_pointer(processed_path_op)?;14671468        // Reject if isolation is enabled.1469        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1470            this.reject_in_isolation("`realpath`", reject_with)?;1471            this.set_last_error(LibcError("EACCES"))?;1472            return interp_ok(Scalar::from_target_usize(0, this));1473        }14741475        let result = std::fs::canonicalize(pathname);1476        match result {1477            Ok(resolved) => {1478                let path_max = this1479                    .eval_libc_i32("PATH_MAX")1480                    .try_into()1481                    .expect("PATH_MAX does not fit in u64");1482                let dest = if this.ptr_is_null(processed_ptr)? {1483                    // POSIX says behavior when passing a null pointer is implementation-defined,1484                    // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer1485                    // similarly to:1486                    //1487                    // "If resolved_path is specified as NULL, then realpath() uses1488                    // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold1489                    // the resolved pathname, and returns a pointer to this buffer.  The1490                    // caller should deallocate this buffer using free(3)."1491                    // <https://man7.org/linux/man-pages/man3/realpath.3.html>1492                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?1493                } else {1494                    let (wrote_path, _) =1495                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;14961497                    if !wrote_path {1498                        // Note that we do not explicitly handle `FILENAME_MAX`1499                        // (different from `PATH_MAX` above) as it is Linux-specific and1500                        // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.1501                        this.set_last_error(LibcError("ENAMETOOLONG"))?;1502                        return interp_ok(Scalar::from_target_usize(0, this));1503                    }1504                    processed_ptr1505                };15061507                interp_ok(Scalar::from_maybe_pointer(dest, this))1508            }1509            Err(e) => {1510                this.set_last_error(e)?;1511                interp_ok(Scalar::from_target_usize(0, this))1512            }1513        }1514    }1515    fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1516        use rand::seq::IndexedRandom;15171518        // POSIX defines the template string.1519        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";15201521        let this = self.eval_context_mut();1522        this.assert_target_os_is_unix("mkstemp");15231524        // POSIX defines the maximum number of attempts before failure.1525        //1526        // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.1527        // POSIX says this about `TMP_MAX`:1528        // * Minimum number of unique filenames generated by `tmpnam()`.1529        // * Maximum number of times an application can call `tmpnam()` reliably.1530        //   * The value of `TMP_MAX` is at least 25.1531        //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.1532        // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.1533        let max_attempts = this.eval_libc_u32("TMP_MAX");15341535        // Get the raw bytes from the template -- as a byte slice, this is a string in the target1536        // (and the target is unix, so a byte slice is the right representation).1537        let template_ptr = this.read_pointer(template_op)?;1538        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();1539        let template_bytes = template.as_mut_slice();15401541        // Reject if isolation is enabled.1542        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {1543            this.reject_in_isolation("`mkstemp`", reject_with)?;1544            return this.set_last_error_and_return_i32(LibcError("EACCES"));1545        }15461547        // Get the bytes of the suffix we expect in _target_ encoding.1548        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();15491550        // At this point we have one `&[u8]` that represents the template and one `&[u8]`1551        // that represents the expected suffix.15521553        // Now we figure out the index of the slice we expect to contain the suffix.1554        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());1555        let end_pos = template_bytes.len();1556        let last_six_char_bytes = &template_bytes[start_pos..end_pos];15571558        // If we don't find the suffix, it is an error.1559        if last_six_char_bytes != suffix_bytes {1560            return this.set_last_error_and_return_i32(LibcError("EINVAL"));1561        }15621563        // At this point we know we have 6 ASCII 'X' characters as a suffix.15641565        // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>1566        const SUBSTITUTIONS: &[char; 62] = &[1567            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',1568            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',1569            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',1570            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',1571        ];15721573        // The file is opened with specific options, which Rust does not expose in a portable way.1574        // So we use specific APIs depending on the host OS.1575        let mut fopts = OpenOptions::new();1576        fopts.read(true).write(true).create_new(true);15771578        #[cfg(unix)]1579        {1580            use std::os::unix::fs::OpenOptionsExt;1581            // Do not allow others to read or modify this file.1582            fopts.mode(0o600);1583            fopts.custom_flags(libc::O_EXCL);1584        }1585        #[cfg(windows)]1586        {1587            use std::os::windows::fs::OpenOptionsExt;1588            // Do not allow others to read or modify this file.1589            fopts.share_mode(0);1590        }15911592        // If the generated file already exists, we will try again `max_attempts` many times.1593        for _ in 0..max_attempts {1594            let rng = this.machine.rng.get_mut();15951596            // Generate a random unique suffix.1597            let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();15981599            // Replace the template string with the random string.1600            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());16011602            // Write the modified template back to the passed in pointer to maintain POSIX semantics.1603            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;16041605            // To actually open the file, turn this into a host OsString.1606            let p = bytes_to_os_str(template_bytes)?.to_os_string();16071608            let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());16091610            let file = fopts.open(possibly_unique);16111612            match file {1613                Ok(f) => {1614                    let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });1615                    return interp_ok(Scalar::from_i32(fd));1616                }1617                Err(e) =>1618                    match e.kind() {1619                        // If the random file already exists, keep trying.1620                        ErrorKind::AlreadyExists => continue,1621                        // Any other errors are returned to the caller.1622                        _ => {1623                            // "On error, -1 is returned, and errno is set to1624                            // indicate the error"1625                            return this.set_last_error_and_return_i32(e);1626                        }1627                    },1628            }1629        }16301631        // We ran out of attempts to create the file, return an error.1632        this.set_last_error_and_return_i32(LibcError("EEXIST"))1633    }1634}16351636/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when1637/// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix1638/// epoch.1639fn extract_sec_and_nsec<'tcx>(1640    time: std::io::Result<SystemTime>,1641) -> InterpResult<'tcx, Option<(u64, u32)>> {1642    match time.ok() {1643        Some(time) => {1644            let duration = system_time_to_duration(&time)?;1645            interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))1646        }1647        None => interp_ok(None),1648    }1649}16501651fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {1652    #[cfg(unix)]1653    use std::os::unix::fs::FileTypeExt;16541655    if file_type.is_file() {1656        "S_IFREG"1657    } else if file_type.is_dir() {1658        "S_IFDIR"1659    } else if file_type.is_symlink() {1660        "S_IFLNK"1661    } else {1662        // Certain file types are only available when the host is a Unix system.1663        #[cfg(unix)]1664        {1665            if file_type.is_socket() {1666                return "S_IFSOCK";1667            } else if file_type.is_fifo() {1668                return "S_IFIFO";1669            } else if file_type.is_char_device() {1670                return "S_IFCHR";1671            } else if file_type.is_block_device() {1672                return "S_IFBLK";1673            }1674        }1675        "S_IFREG"1676    }1677}16781679/// Stores a file's metadata in order to avoid code duplication in the different metadata related1680/// shims.1681///1682/// Some fields are host/platform-specific. `None` means that Miri does not have a real value for1683/// this field, for example because the metadata is synthetic or because the host platform does not1684/// expose it. `statx` must only advertise the corresponding `STATX_*` bit when the field is `Some`;1685/// legacy `stat` writes zero for `None` to preserve the old fallback behavior.1686struct FileMetadata {1687    mode: Scalar,1688    size: u64,1689    created: Option<(u64, u32)>,1690    accessed: Option<(u64, u32)>,1691    modified: Option<(u64, u32)>,1692    dev: Option<u64>,1693    ino: Option<u64>,1694    nlink: Option<u64>,1695    uid: Option<u32>,1696    gid: Option<u32>,1697    blksize: Option<u64>,1698    blocks: Option<u64>,1699}17001701impl FileMetadata {1702    fn from_path<'tcx>(1703        ecx: &mut MiriInterpCx<'tcx>,1704        path: &Path,1705        follow_symlink: bool,1706    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1707        let metadata =1708            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };17091710        FileMetadata::from_meta(ecx, metadata)1711    }17121713    fn from_fd_num<'tcx>(1714        ecx: &mut MiriInterpCx<'tcx>,1715        fd_num: i32,1716    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1717        let Some(fd) = ecx.machine.fds.get(fd_num) else {1718            return interp_ok(Err(LibcError("EBADF")));1719        };1720        match fd.metadata()? {1721            Either::Left(host) => Self::from_meta(ecx, host),1722            Either::Right(name) => Self::synthetic(ecx, name),1723        }1724    }17251726    fn synthetic<'tcx>(1727        ecx: &mut MiriInterpCx<'tcx>,1728        mode_name: &str,1729    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1730        let mode = ecx.eval_libc(mode_name);1731        interp_ok(Ok(FileMetadata {1732            mode,1733            size: 0,1734            created: None,1735            accessed: None,1736            modified: None,1737            dev: None,1738            uid: None,1739            gid: None,1740            blksize: None,1741            blocks: None,1742            ino: None,1743            nlink: None,1744        }))1745    }17461747    fn from_meta<'tcx>(1748        ecx: &mut MiriInterpCx<'tcx>,1749        metadata: Result<std::fs::Metadata, std::io::Error>,1750    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {1751        let metadata = match metadata {1752            Ok(metadata) => metadata,1753            Err(e) => {1754                return interp_ok(Err(e.into()));1755            }1756        };17571758        let file_type = metadata.file_type();1759        let mode = ecx.eval_libc(file_type_to_mode_name(file_type));17601761        let size = metadata.len();17621763        let created = extract_sec_and_nsec(metadata.created())?;1764        let accessed = extract_sec_and_nsec(metadata.accessed())?;1765        let modified = extract_sec_and_nsec(metadata.modified())?;17661767        // FIXME: Provide more fields using platform specific methods.17681769        cfg_select! {1770            unix => {1771                use std::os::unix::fs::MetadataExt;1772                let dev = metadata.dev();1773                let ino = metadata.ino();1774                let nlink = metadata.nlink();1775                let uid = metadata.uid();1776                let gid = metadata.gid();1777                let blksize = metadata.blksize();1778                let blocks = metadata.blocks();17791780                interp_ok(Ok(FileMetadata {1781                    mode,1782                    size,1783                    created,1784                    accessed,1785                    modified,1786                    dev: Some(dev),1787                    ino: Some(ino),1788                    nlink: Some(nlink),1789                    uid: Some(uid),1790                    gid: Some(gid),1791                    blksize: Some(blksize),1792                    blocks: Some(blocks),1793                }))1794            }1795            _ => interp_ok(Ok(FileMetadata {1796                mode,1797                size,1798                created,1799                accessed,1800                modified,1801                dev: None,1802                ino: None,1803                nlink: None,1804                uid: None,1805                gid: None,1806                blksize: None,1807                blocks: None,1808            })),1809        }1810    }1811}

Code quality findings 26

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: '.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 = metadata.mode.to_uint(mode_t_size)?.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
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 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: 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());
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 file {
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.