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}