2 use std::collections::BTreeMap;
3 use std::convert::TryInto;
5 read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
7 use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
8 use std::path::{Path, PathBuf};
9 use std::time::SystemTime;
13 use rustc_data_structures::fx::FxHashMap;
14 use rustc_middle::ty::{self, layout::LayoutOf};
15 use rustc_target::abi::{Align, Size};
17 use crate::shims::os_str::bytes_to_os_str;
19 use shims::os_str::os_str_to_bytes;
20 use shims::time::system_time_to_duration;
28 trait FileDescriptor: std::fmt::Debug {
29 fn name(&self) -> &'static str;
31 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
32 throw_unsup_format!("{} cannot be used as FileHandle", self.name());
37 _communicate_allowed: bool,
39 ) -> InterpResult<'tcx, io::Result<usize>> {
40 throw_unsup_format!("cannot read from {}", self.name());
45 _communicate_allowed: bool,
47 ) -> InterpResult<'tcx, io::Result<usize>> {
48 throw_unsup_format!("cannot write to {}", self.name());
53 _communicate_allowed: bool,
55 ) -> InterpResult<'tcx, io::Result<u64>> {
56 throw_unsup_format!("cannot seek on {}", self.name());
61 _communicate_allowed: bool,
62 ) -> InterpResult<'tcx, io::Result<i32>> {
63 throw_unsup_format!("cannot close {}", self.name());
66 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
68 fn is_tty(&self) -> bool;
71 fn as_unix_host_fd(&self) -> Option<i32> {
76 impl FileDescriptor for FileHandle {
77 fn name(&self) -> &'static str {
81 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
87 communicate_allowed: bool,
89 ) -> InterpResult<'tcx, io::Result<usize>> {
90 assert!(communicate_allowed, "isolation should have prevented even opening a file");
91 Ok(self.file.read(bytes))
96 communicate_allowed: bool,
98 ) -> InterpResult<'tcx, io::Result<usize>> {
99 assert!(communicate_allowed, "isolation should have prevented even opening a file");
100 Ok((&mut &self.file).write(bytes))
105 communicate_allowed: bool,
107 ) -> InterpResult<'tcx, io::Result<u64>> {
108 assert!(communicate_allowed, "isolation should have prevented even opening a file");
109 Ok(self.file.seek(offset))
114 communicate_allowed: bool,
115 ) -> InterpResult<'tcx, io::Result<i32>> {
116 assert!(communicate_allowed, "isolation should have prevented even opening a file");
117 // We sync the file if it was opened in a mode different than read-only.
119 // `File::sync_all` does the checks that are done when closing a file. We do this to
120 // to handle possible errors correctly.
121 let result = self.file.sync_all().map(|_| 0i32);
122 // Now we actually close the file.
124 // And return the result.
127 // We drop the file, this closes it but ignores any errors
128 // produced when closing it. This is done because
129 // `File::sync_all` cannot be done over files like
130 // `/dev/urandom` which are read-only. Check
131 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
132 // for a deeper discussion.
138 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
139 let duplicated = self.file.try_clone()?;
140 Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
144 fn as_unix_host_fd(&self) -> Option<i32> {
145 use std::os::unix::io::AsRawFd;
146 Some(self.file.as_raw_fd())
149 fn is_tty(&self) -> bool {
150 self.file.is_terminal()
154 impl FileDescriptor for io::Stdin {
155 fn name(&self) -> &'static str {
161 communicate_allowed: bool,
163 ) -> InterpResult<'tcx, io::Result<usize>> {
164 if !communicate_allowed {
165 // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
166 helpers::isolation_abort_error("`read` from stdin")?;
168 Ok(Read::read(self, bytes))
171 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
172 Ok(Box::new(io::stdin()))
176 fn as_unix_host_fd(&self) -> Option<i32> {
177 Some(libc::STDIN_FILENO)
180 fn is_tty(&self) -> bool {
185 impl FileDescriptor for io::Stdout {
186 fn name(&self) -> &'static str {
192 _communicate_allowed: bool,
194 ) -> InterpResult<'tcx, io::Result<usize>> {
195 // We allow writing to stderr even with isolation enabled.
196 let result = Write::write(&mut { self }, bytes);
197 // Stdout is buffered, flush to make sure it appears on the
198 // screen. This is the write() syscall of the interpreted
199 // program, we want it to correspond to a write() syscall on
200 // the host -- there is no good in adding extra buffering
202 io::stdout().flush().unwrap();
207 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
208 Ok(Box::new(io::stdout()))
212 fn as_unix_host_fd(&self) -> Option<i32> {
213 Some(libc::STDOUT_FILENO)
216 fn is_tty(&self) -> bool {
221 impl FileDescriptor for io::Stderr {
222 fn name(&self) -> &'static str {
228 _communicate_allowed: bool,
230 ) -> InterpResult<'tcx, io::Result<usize>> {
231 // We allow writing to stderr even with isolation enabled.
232 // No need to flush, stderr is not buffered.
233 Ok(Write::write(&mut { self }, bytes))
236 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
237 Ok(Box::new(io::stderr()))
241 fn as_unix_host_fd(&self) -> Option<i32> {
242 Some(libc::STDERR_FILENO)
245 fn is_tty(&self) -> bool {
253 impl FileDescriptor for NullOutput {
254 fn name(&self) -> &'static str {
260 _communicate_allowed: bool,
262 ) -> InterpResult<'tcx, io::Result<usize>> {
263 // We just don't write anything, but report to the user that we did.
267 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
268 Ok(Box::new(NullOutput))
271 fn is_tty(&self) -> bool {
277 pub struct FileHandler {
278 handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
281 impl VisitTags for FileHandler {
282 fn visit_tags(&self, _visit: &mut dyn FnMut(SbTag)) {
283 // All our FileDescriptor do not have any tags.
288 pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
289 let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
290 handles.insert(0i32, Box::new(io::stdin()));
291 if mute_stdout_stderr {
292 handles.insert(1i32, Box::new(NullOutput));
293 handles.insert(2i32, Box::new(NullOutput));
295 handles.insert(1i32, Box::new(io::stdout()));
296 handles.insert(2i32, Box::new(io::stderr()));
298 FileHandler { handles }
301 fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
302 self.insert_fd_with_min_fd(file_handle, 0)
305 fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
306 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
307 // between used FDs, the find_map combinator will return it. If the first such unused FD
308 // is after all other used FDs, the find_map combinator will return None, and we will use
309 // the FD following the greatest FD thus far.
310 let candidate_new_fd =
311 self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
313 // There was a gap in the fds stored, return the first unused one
314 // (note that this relies on BTreeMap iterating in key order)
317 // This fd is used, keep going
321 let new_fd = candidate_new_fd.unwrap_or_else(|| {
322 // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
323 // maximum fd in the map
326 .map(|(fd, _)| fd.checked_add(1).unwrap())
330 self.handles.try_insert(new_fd, file_handle).unwrap();
335 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
336 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
337 fn macos_stat_write_buf(
339 metadata: FileMetadata,
340 buf_op: &OpTy<'tcx, Provenance>,
341 ) -> InterpResult<'tcx, i32> {
342 let this = self.eval_context_mut();
344 let mode: u16 = metadata.mode.to_u16()?;
346 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
347 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
348 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
350 let buf = this.deref_operand(buf_op)?;
351 this.write_int_fields_named(
354 ("st_mode", mode.into()),
360 ("st_atime", access_sec.into()),
361 ("st_atime_nsec", access_nsec.into()),
362 ("st_mtime", modified_sec.into()),
363 ("st_mtime_nsec", modified_nsec.into()),
365 ("st_ctime_nsec", 0),
366 ("st_birthtime", created_sec.into()),
367 ("st_birthtime_nsec", created_nsec.into()),
368 ("st_size", metadata.size.into()),
380 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
381 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
382 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
383 /// types (like `read`, that returns an `i64`).
384 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
385 let this = self.eval_context_mut();
386 let ebadf = this.eval_libc("EBADF")?;
387 this.set_last_error(ebadf)?;
391 fn file_type_to_d_type(
393 file_type: std::io::Result<FileType>,
394 ) -> InterpResult<'tcx, i32> {
395 let this = self.eval_context_mut();
398 if file_type.is_dir() {
399 Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
400 } else if file_type.is_file() {
401 Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
402 } else if file_type.is_symlink() {
403 Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
405 // Certain file types are only supported when the host is a Unix system.
406 // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
407 // DT_UNKNOWN sooner.
411 use std::os::unix::fs::FileTypeExt;
412 if file_type.is_block_device() {
413 Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
414 } else if file_type.is_char_device() {
415 Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
416 } else if file_type.is_fifo() {
417 Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
418 } else if file_type.is_socket() {
419 Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
421 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
425 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
429 match e.raw_os_error() {
430 Some(error) => Ok(error),
433 "the error {} couldn't be converted to a return value",
441 /// An open directory, tracked by DirHandler.
444 /// The directory reader on the host.
446 /// The most recent entry returned by readdir()
447 entry: Pointer<Option<Provenance>>,
451 fn new(read_dir: ReadDir) -> Self {
452 // We rely on `free` being a NOP on null pointers.
453 Self { read_dir, entry: Pointer::null() }
458 pub struct DirHandler {
459 /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
462 /// When opendir is called, a directory iterator is created on the host for the target
463 /// directory, and an entry is stored in this hash map, indexed by an ID which represents
464 /// the directory stream. When readdir is called, the directory stream ID is used to look up
465 /// the corresponding ReadDir iterator from this map, and information from the next
466 /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
468 streams: FxHashMap<u64, OpenDir>,
469 /// ID number to be used by the next call to opendir
474 #[allow(clippy::integer_arithmetic)]
475 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
476 let id = self.next_id;
478 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
483 impl Default for DirHandler {
484 fn default() -> DirHandler {
486 streams: FxHashMap::default(),
487 // Skip 0 as an ID, because it looks like a null pointer to libc
493 impl VisitTags for DirHandler {
494 fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
495 let DirHandler { streams, next_id: _ } = self;
497 for dir in streams.values() {
498 dir.entry.visit_tags(visit);
506 operation: fn(&File) -> std::io::Result<()>,
507 ) -> std::io::Result<i32> {
508 if !writable && cfg!(windows) {
509 // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
510 // for writing. (FlushFileBuffers requires that the file handle have the
511 // GENERIC_WRITE right)
514 let result = operation(file);
519 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
520 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
521 fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
524 "incorrect number of arguments for `open`: got {}, expected at least 2",
529 let this = self.eval_context_mut();
531 let path = this.read_pointer(&args[0])?;
532 let flag = this.read_scalar(&args[1])?.to_i32()?;
534 let mut options = OpenOptions::new();
536 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
537 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
538 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
539 // The first two bits of the flag correspond to the access mode in linux, macOS and
540 // windows. We need to check that in fact the access mode flags for the current target
541 // only use these two bits, otherwise we are in an unsupported target and should error.
542 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
543 throw_unsup_format!("access mode flags on this target are unsupported");
545 let mut writable = true;
547 // Now we check the access mode
548 let access_mode = flag & 0b11;
550 if access_mode == o_rdonly {
553 } else if access_mode == o_wronly {
555 } else if access_mode == o_rdwr {
556 options.read(true).write(true);
558 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
560 // We need to check that there aren't unsupported options in `flag`. For this we try to
561 // reproduce the content of `flag` in the `mirror` variable using only the supported
563 let mut mirror = access_mode;
565 let o_append = this.eval_libc_i32("O_APPEND")?;
566 if flag & o_append != 0 {
567 options.append(true);
570 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
571 if flag & o_trunc != 0 {
572 options.truncate(true);
575 let o_creat = this.eval_libc_i32("O_CREAT")?;
576 if flag & o_creat != 0 {
577 // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
578 // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
579 // (see https://github.com/rust-lang/rust/issues/71915).
580 let mode = if let Some(arg) = args.get(2) {
581 this.read_scalar(arg)?.to_u32()?
584 "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3",
590 throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
595 let o_excl = this.eval_libc_i32("O_EXCL")?;
596 if flag & o_excl != 0 {
598 options.create_new(true);
600 options.create(true);
603 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
604 if flag & o_cloexec != 0 {
605 // We do not need to do anything for this flag because `std` already sets it.
606 // (Technically we do not support *not* setting this flag, but we ignore that.)
609 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
610 // then we throw an error.
612 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
615 let path = this.read_path_from_c_str(path)?;
617 // Reject if isolation is enabled.
618 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
619 this.reject_in_isolation("`open`", reject_with)?;
620 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
624 let fd = options.open(path).map(|file| {
625 let fh = &mut this.machine.file_handler;
626 fh.insert_fd(Box::new(FileHandle { file, writable }))
629 this.try_unwrap_io_result(fd)
632 fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
633 let this = self.eval_context_mut();
637 "incorrect number of arguments for fcntl: got {}, expected at least 2",
641 let fd = this.read_scalar(&args[0])?.to_i32()?;
642 let cmd = this.read_scalar(&args[1])?.to_i32()?;
644 // Reject if isolation is enabled.
645 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
646 this.reject_in_isolation("`fcntl`", reject_with)?;
647 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
651 // We only support getting the flags for a descriptor.
652 if cmd == this.eval_libc_i32("F_GETFD")? {
653 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
654 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
655 // always sets this flag when opening a file. However we still need to check that the
656 // file itself is open.
657 if this.machine.file_handler.handles.contains_key(&fd) {
658 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
660 this.handle_not_found()
662 } else if cmd == this.eval_libc_i32("F_DUPFD")?
663 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
665 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
666 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
667 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
668 // thus they can share the same implementation here.
671 "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
675 let start = this.read_scalar(&args[2])?.to_i32()?;
677 let fh = &mut this.machine.file_handler;
679 match fh.handles.get_mut(&fd) {
680 Some(file_descriptor) => {
681 let dup_result = file_descriptor.dup();
683 Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
685 this.set_last_error_from_io_error(e.kind())?;
690 None => this.handle_not_found(),
692 } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
693 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
694 // FIXME: Support fullfsync for all FDs
695 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
696 let io_result = maybe_sync_file(file, *writable, File::sync_all);
697 this.try_unwrap_io_result(io_result)
699 this.handle_not_found()
702 throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
706 fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
707 let this = self.eval_context_mut();
709 let fd = this.read_scalar(fd_op)?.to_i32()?;
712 if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
713 let result = file_descriptor.close(this.machine.communicate())?;
714 this.try_unwrap_io_result(result)?
716 this.handle_not_found()?
724 buf: Pointer<Option<Provenance>>,
726 ) -> InterpResult<'tcx, i64> {
727 let this = self.eval_context_mut();
729 // Isolation check is done via `FileDescriptor` trait.
731 trace!("Reading from FD {}, size {}", fd, count);
733 // Check that the *entire* buffer is actually valid memory.
734 this.check_ptr_access_align(
736 Size::from_bytes(count),
738 CheckInAllocMsg::MemoryAccessTest,
741 // We cap the number of read bytes to the largest value that we are able to fit in both the
742 // host's and target's `isize`. This saves us from having to handle overflows later.
744 .min(u64::try_from(this.machine_isize_max()).unwrap())
745 .min(u64::try_from(isize::MAX).unwrap());
746 let communicate = this.machine.communicate();
748 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
749 trace!("read: FD mapped to {:?}", file_descriptor);
750 // We want to read at most `count` bytes. We are sure that `count` is not negative
751 // because it was a target's `usize`. Also we are sure that its smaller than
752 // `usize::MAX` because it is bounded by the host's `isize`.
753 let mut bytes = vec![0; usize::try_from(count).unwrap()];
754 // `File::read` never returns a value larger than `count`,
755 // so this cannot fail.
757 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
761 // If reading to `bytes` did not fail, we write those bytes to the buffer.
762 this.write_bytes_ptr(buf, bytes)?;
766 this.set_last_error_from_io_error(e.kind())?;
771 trace!("read: FD not found");
772 this.handle_not_found()
779 buf: Pointer<Option<Provenance>>,
781 ) -> InterpResult<'tcx, i64> {
782 let this = self.eval_context_mut();
784 // Isolation check is done via `FileDescriptor` trait.
786 // Check that the *entire* buffer is actually valid memory.
787 this.check_ptr_access_align(
789 Size::from_bytes(count),
791 CheckInAllocMsg::MemoryAccessTest,
794 // We cap the number of written bytes to the largest value that we are able to fit in both the
795 // host's and target's `isize`. This saves us from having to handle overflows later.
797 .min(u64::try_from(this.machine_isize_max()).unwrap())
798 .min(u64::try_from(isize::MAX).unwrap());
799 let communicate = this.machine.communicate();
801 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
802 let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
804 file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
805 this.try_unwrap_io_result(result)
807 this.handle_not_found()
813 fd_op: &OpTy<'tcx, Provenance>,
814 offset_op: &OpTy<'tcx, Provenance>,
815 whence_op: &OpTy<'tcx, Provenance>,
816 ) -> InterpResult<'tcx, Scalar<Provenance>> {
817 let this = self.eval_context_mut();
819 // Isolation check is done via `FileDescriptor` trait.
821 let fd = this.read_scalar(fd_op)?.to_i32()?;
822 let offset = this.read_scalar(offset_op)?.to_i64()?;
823 let whence = this.read_scalar(whence_op)?.to_i32()?;
825 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
826 SeekFrom::Start(u64::try_from(offset).unwrap())
827 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
828 SeekFrom::Current(offset)
829 } else if whence == this.eval_libc_i32("SEEK_END")? {
830 SeekFrom::End(offset)
832 let einval = this.eval_libc("EINVAL")?;
833 this.set_last_error(einval)?;
834 return Ok(Scalar::from_i64(-1));
837 let communicate = this.machine.communicate();
839 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
840 let result = file_descriptor
841 .seek(communicate, seek_from)?
842 .map(|offset| i64::try_from(offset).unwrap());
843 this.try_unwrap_io_result(result)?
845 this.handle_not_found()?
850 fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
851 let this = self.eval_context_mut();
853 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
855 // Reject if isolation is enabled.
856 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
857 this.reject_in_isolation("`unlink`", reject_with)?;
858 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
862 let result = remove_file(path).map(|_| 0);
863 this.try_unwrap_io_result(result)
868 target_op: &OpTy<'tcx, Provenance>,
869 linkpath_op: &OpTy<'tcx, Provenance>,
870 ) -> InterpResult<'tcx, i32> {
872 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
873 std::os::unix::fs::symlink(src, dst)
877 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
878 use std::os::windows::fs;
879 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
882 let this = self.eval_context_mut();
883 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
884 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
886 // Reject if isolation is enabled.
887 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
888 this.reject_in_isolation("`symlink`", reject_with)?;
889 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
893 let result = create_link(&target, &linkpath).map(|_| 0);
894 this.try_unwrap_io_result(result)
899 path_op: &OpTy<'tcx, Provenance>,
900 buf_op: &OpTy<'tcx, Provenance>,
901 ) -> InterpResult<'tcx, Scalar<Provenance>> {
902 let this = self.eval_context_mut();
903 this.assert_target_os("macos", "stat");
905 let path_scalar = this.read_pointer(path_op)?;
906 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
908 // Reject if isolation is enabled.
909 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
910 this.reject_in_isolation("`stat`", reject_with)?;
911 let eacc = this.eval_libc("EACCES")?;
912 this.set_last_error(eacc)?;
913 return Ok(Scalar::from_i32(-1));
916 // `stat` always follows symlinks.
917 let metadata = match FileMetadata::from_path(this, &path, true)? {
918 Some(metadata) => metadata,
919 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
922 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
925 // `lstat` is used to get symlink metadata.
928 path_op: &OpTy<'tcx, Provenance>,
929 buf_op: &OpTy<'tcx, Provenance>,
930 ) -> InterpResult<'tcx, Scalar<Provenance>> {
931 let this = self.eval_context_mut();
932 this.assert_target_os("macos", "lstat");
934 let path_scalar = this.read_pointer(path_op)?;
935 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
937 // Reject if isolation is enabled.
938 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
939 this.reject_in_isolation("`lstat`", reject_with)?;
940 let eacc = this.eval_libc("EACCES")?;
941 this.set_last_error(eacc)?;
942 return Ok(Scalar::from_i32(-1));
945 let metadata = match FileMetadata::from_path(this, &path, false)? {
946 Some(metadata) => metadata,
947 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
950 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
955 fd_op: &OpTy<'tcx, Provenance>,
956 buf_op: &OpTy<'tcx, Provenance>,
957 ) -> InterpResult<'tcx, Scalar<Provenance>> {
958 let this = self.eval_context_mut();
960 this.assert_target_os("macos", "fstat");
962 let fd = this.read_scalar(fd_op)?.to_i32()?;
964 // Reject if isolation is enabled.
965 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
966 this.reject_in_isolation("`fstat`", reject_with)?;
967 // Set error code as "EBADF" (bad fd)
968 return Ok(Scalar::from_i32(this.handle_not_found()?));
971 let metadata = match FileMetadata::from_fd(this, fd)? {
972 Some(metadata) => metadata,
973 None => return Ok(Scalar::from_i32(-1)),
975 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
980 dirfd_op: &OpTy<'tcx, Provenance>, // Should be an `int`
981 pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
982 flags_op: &OpTy<'tcx, Provenance>, // Should be an `int`
983 mask_op: &OpTy<'tcx, Provenance>, // Should be an `unsigned int`
984 statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
985 ) -> InterpResult<'tcx, i32> {
986 let this = self.eval_context_mut();
988 this.assert_target_os("linux", "statx");
990 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
991 let pathname_ptr = this.read_pointer(pathname_op)?;
992 let flags = this.read_scalar(flags_op)?.to_i32()?;
993 let _mask = this.read_scalar(mask_op)?.to_u32()?;
994 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
996 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
997 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
998 let efault = this.eval_libc("EFAULT")?;
999 this.set_last_error(efault)?;
1003 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
1004 // proper `MemPlace` and then write the results of this function to it. However, the
1005 // `syscall` function is untyped. This means that all the `statx` parameters are provided
1006 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
1007 // `statxbuf_op` by using the `libc::statx` struct type.
1009 // FIXME: This long path is required because `libc::statx` is an struct and also a
1010 // function and `resolve_path` is returning the latter.
1012 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])
1013 .ty(*this.tcx, ty::ParamEnv::reveal_all());
1014 let statx_layout = this.layout_of(statx_ty)?;
1015 MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
1018 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1019 // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
1020 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
1022 // * interpreting `path` as an absolute directory,
1023 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1024 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1026 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1027 // found this error, please open an issue reporting it.
1028 if !(path.is_absolute()
1029 || dirfd == this.eval_libc_i32("AT_FDCWD")?
1030 || (path.as_os_str().is_empty() && empty_path_flag))
1032 throw_unsup_format!(
1033 "using statx is only supported with absolute paths, relative paths with the file \
1034 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1039 // Reject if isolation is enabled.
1040 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1041 this.reject_in_isolation("`statx`", reject_with)?;
1042 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1043 // since `path` is provided, either absolute or
1044 // relative to CWD, `EACCES` is the most relevant.
1045 this.eval_libc("EACCES")?
1047 // `dirfd` is set to target file, and `path` is empty
1048 // (or we would have hit the `throw_unsup_format`
1049 // above). `EACCES` would violate the spec.
1050 assert!(empty_path_flag);
1051 this.eval_libc("EBADF")?
1053 this.set_last_error(ecode)?;
1057 // the `_mask_op` paramter specifies the file information that the caller requested.
1058 // However `statx` is allowed to return information that was not requested or to not
1059 // return information that was requested. This `mask` represents the information we can
1060 // actually provide for any target.
1062 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1064 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1066 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1068 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1069 // represented by dirfd, whether it's a directory or otherwise.
1070 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1071 FileMetadata::from_fd(this, dirfd)?
1073 FileMetadata::from_path(this, &path, follow_symlink)?
1075 let metadata = match metadata {
1076 Some(metadata) => metadata,
1077 None => return Ok(-1),
1080 // The `mode` field specifies the type of the file and the permissions over the file for
1081 // the owner, its group and other users. Given that we can only provide the file type
1082 // without using platform specific methods, we only set the bits corresponding to the file
1083 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1084 let mode: u16 = metadata
1088 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1090 // We need to set the corresponding bits of `mask` if the access, creation and modification
1091 // times were available. Otherwise we let them be zero.
1092 let (access_sec, access_nsec) = metadata
1095 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1096 InterpResult::Ok(tup)
1098 .unwrap_or_else(|| Ok((0, 0)))?;
1100 let (created_sec, created_nsec) = metadata
1103 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1104 InterpResult::Ok(tup)
1106 .unwrap_or_else(|| Ok((0, 0)))?;
1108 let (modified_sec, modified_nsec) = metadata
1111 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1112 InterpResult::Ok(tup)
1114 .unwrap_or_else(|| Ok((0, 0)))?;
1116 // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1117 this.write_int_fields_named(
1119 ("stx_mask", mask.into()),
1121 ("stx_attributes", 0),
1125 ("stx_mode", mode.into()),
1127 ("stx_size", metadata.size.into()),
1129 ("stx_attributes_mask", 0),
1130 ("stx_rdev_major", 0),
1131 ("stx_rdev_minor", 0),
1132 ("stx_dev_major", 0),
1133 ("stx_dev_minor", 0),
1138 this.write_int_fields_named(
1140 ("tv_sec", access_sec.into()),
1141 ("tv_nsec", access_nsec.into()),
1143 &this.mplace_field_named(&statxbuf, "stx_atime")?,
1146 this.write_int_fields_named(
1148 ("tv_sec", created_sec.into()),
1149 ("tv_nsec", created_nsec.into()),
1151 &this.mplace_field_named(&statxbuf, "stx_btime")?,
1154 this.write_int_fields_named(
1156 ("tv_sec", 0.into()),
1157 ("tv_nsec", 0.into()),
1159 &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1162 this.write_int_fields_named(
1164 ("tv_sec", modified_sec.into()),
1165 ("tv_nsec", modified_nsec.into()),
1167 &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1175 oldpath_op: &OpTy<'tcx, Provenance>,
1176 newpath_op: &OpTy<'tcx, Provenance>,
1177 ) -> InterpResult<'tcx, i32> {
1178 let this = self.eval_context_mut();
1180 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1181 let newpath_ptr = this.read_pointer(newpath_op)?;
1183 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1184 let efault = this.eval_libc("EFAULT")?;
1185 this.set_last_error(efault)?;
1189 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1190 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1192 // Reject if isolation is enabled.
1193 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1194 this.reject_in_isolation("`rename`", reject_with)?;
1195 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1199 let result = rename(oldpath, newpath).map(|_| 0);
1201 this.try_unwrap_io_result(result)
1206 path_op: &OpTy<'tcx, Provenance>,
1207 mode_op: &OpTy<'tcx, Provenance>,
1208 ) -> InterpResult<'tcx, i32> {
1209 let this = self.eval_context_mut();
1211 #[cfg_attr(not(unix), allow(unused_variables))]
1212 let mode = if this.tcx.sess.target.os == "macos" {
1213 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1215 this.read_scalar(mode_op)?.to_u32()?
1218 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1220 // Reject if isolation is enabled.
1221 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1222 this.reject_in_isolation("`mkdir`", reject_with)?;
1223 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1227 #[cfg_attr(not(unix), allow(unused_mut))]
1228 let mut builder = DirBuilder::new();
1230 // If the host supports it, forward on the mode of the directory
1231 // (i.e. permission bits and the sticky bit)
1234 use std::os::unix::fs::DirBuilderExt;
1238 let result = builder.create(path).map(|_| 0i32);
1240 this.try_unwrap_io_result(result)
1243 fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1244 let this = self.eval_context_mut();
1246 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1248 // Reject if isolation is enabled.
1249 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1250 this.reject_in_isolation("`rmdir`", reject_with)?;
1251 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1255 let result = remove_dir(path).map(|_| 0i32);
1257 this.try_unwrap_io_result(result)
1262 name_op: &OpTy<'tcx, Provenance>,
1263 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1264 let this = self.eval_context_mut();
1266 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1268 // Reject if isolation is enabled.
1269 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1270 this.reject_in_isolation("`opendir`", reject_with)?;
1271 let eacc = this.eval_libc("EACCES")?;
1272 this.set_last_error(eacc)?;
1273 return Ok(Scalar::null_ptr(this));
1276 let result = read_dir(name);
1280 let id = this.machine.dir_handler.insert_new(dir_iter);
1282 // The libc API for opendir says that this method returns a pointer to an opaque
1283 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1285 Ok(Scalar::from_machine_usize(id, this))
1288 this.set_last_error_from_io_error(e.kind())?;
1289 Ok(Scalar::null_ptr(this))
1296 dirp_op: &OpTy<'tcx, Provenance>,
1297 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1298 let this = self.eval_context_mut();
1300 this.assert_target_os("linux", "readdir64");
1302 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1304 // Reject if isolation is enabled.
1305 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1306 this.reject_in_isolation("`readdir`", reject_with)?;
1307 let eacc = this.eval_libc("EBADF")?;
1308 this.set_last_error(eacc)?;
1309 return Ok(Scalar::null_ptr(this));
1312 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1313 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1316 let entry = match open_dir.read_dir.next() {
1317 Some(Ok(dir_entry)) => {
1318 // Write the directory entry into a newly allocated buffer.
1319 // The name is written with write_bytes, while the rest of the
1320 // dirent64 struct is written using write_int_fields.
1323 // pub struct dirent64 {
1324 // pub d_ino: ino64_t,
1325 // pub d_off: off64_t,
1326 // pub d_reclen: c_ushort,
1327 // pub d_type: c_uchar,
1328 // pub d_name: [c_char; 256],
1331 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1332 name.push("\0"); // Add a NUL terminator
1333 let name_bytes = os_str_to_bytes(&name)?;
1334 let name_len = u64::try_from(name_bytes.len()).unwrap();
1336 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1337 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1338 let size = d_name_offset.checked_add(name_len).unwrap();
1341 this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1343 // If the host is a Unix system, fill in the inode number with its real value.
1344 // If not, use 0 as a fallback value.
1346 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1350 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1352 this.write_int_fields_named(
1354 ("d_ino", ino.into()),
1356 ("d_reclen", size.into()),
1357 ("d_type", file_type.into()),
1359 &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1362 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1363 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1368 // end of stream: return NULL
1372 this.set_last_error_from_io_error(e.kind())?;
1377 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1378 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1379 this.free(old_entry, MiriMemoryKind::Runtime)?;
1381 Ok(Scalar::from_maybe_pointer(entry, this))
1386 dirp_op: &OpTy<'tcx, Provenance>,
1387 entry_op: &OpTy<'tcx, Provenance>,
1388 result_op: &OpTy<'tcx, Provenance>,
1389 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1390 let this = self.eval_context_mut();
1392 this.assert_target_os("macos", "readdir_r");
1394 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1396 // Reject if isolation is enabled.
1397 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1398 this.reject_in_isolation("`readdir_r`", reject_with)?;
1399 // Set error code as "EBADF" (bad fd)
1400 return Ok(Scalar::from_i32(this.handle_not_found()?));
1403 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1404 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1406 Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1407 Some(Ok(dir_entry)) => {
1408 // Write into entry, write pointer to result, return 0 on success.
1409 // The name is written with write_os_str_to_c_str, while the rest of the
1410 // dirent struct is written using write_int_fields.
1413 // pub struct dirent {
1415 // pub d_seekoff: u64,
1416 // pub d_reclen: u16,
1417 // pub d_namlen: u16,
1419 // pub d_name: [c_char; 1024],
1422 let entry_place = this.deref_operand(entry_op)?;
1423 let name_place = this.mplace_field(&entry_place, 5)?;
1425 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1426 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1429 name_place.layout.size.bytes(),
1431 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1433 throw_unsup_format!(
1434 "a directory entry had a name too large to fit in libc::dirent"
1438 let entry_place = this.deref_operand(entry_op)?;
1440 // If the host is a Unix system, fill in the inode number with its real value.
1441 // If not, use 0 as a fallback value.
1443 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1447 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1449 this.write_int_fields_named(
1451 ("d_ino", ino.into()),
1454 ("d_namlen", file_name_len.into()),
1455 ("d_type", file_type.into()),
1460 let result_place = this.deref_operand(result_op)?;
1461 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1466 // end of stream: return 0, assign *result=NULL
1467 this.write_null(&this.deref_operand(result_op)?.into())?;
1471 match e.raw_os_error() {
1472 // return positive error number on error
1473 Some(error) => error,
1475 throw_unsup_format!(
1476 "the error {} couldn't be converted to a return value",
1484 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1485 let this = self.eval_context_mut();
1487 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1489 // Reject if isolation is enabled.
1490 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1491 this.reject_in_isolation("`closedir`", reject_with)?;
1492 // Set error code as "EBADF" (bad fd)
1493 return this.handle_not_found();
1496 if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1497 this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1501 this.handle_not_found()
1507 fd_op: &OpTy<'tcx, Provenance>,
1508 length_op: &OpTy<'tcx, Provenance>,
1509 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1510 let this = self.eval_context_mut();
1512 let fd = this.read_scalar(fd_op)?.to_i32()?;
1513 let length = this.read_scalar(length_op)?.to_i64()?;
1515 // Reject if isolation is enabled.
1516 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1517 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1518 // Set error code as "EBADF" (bad fd)
1519 return Ok(Scalar::from_i32(this.handle_not_found()?));
1522 Ok(Scalar::from_i32(
1523 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1524 // FIXME: Support ftruncate64 for all FDs
1525 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1527 if let Ok(length) = length.try_into() {
1528 let result = file.set_len(length);
1529 this.try_unwrap_io_result(result.map(|_| 0i32))?
1531 let einval = this.eval_libc("EINVAL")?;
1532 this.set_last_error(einval)?;
1536 // The file is not writable
1537 let einval = this.eval_libc("EINVAL")?;
1538 this.set_last_error(einval)?;
1542 this.handle_not_found()?
1547 fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1548 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1549 // underlying disk to finish writing. In the interest of host compatibility,
1550 // we conservatively implement this with `sync_all`, which
1551 // *does* wait for the disk.
1553 let this = self.eval_context_mut();
1555 let fd = this.read_scalar(fd_op)?.to_i32()?;
1557 // Reject if isolation is enabled.
1558 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1559 this.reject_in_isolation("`fsync`", reject_with)?;
1560 // Set error code as "EBADF" (bad fd)
1561 return this.handle_not_found();
1564 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1565 // FIXME: Support fsync for all FDs
1566 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1567 let io_result = maybe_sync_file(file, *writable, File::sync_all);
1568 this.try_unwrap_io_result(io_result)
1570 this.handle_not_found()
1574 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1575 let this = self.eval_context_mut();
1577 let fd = this.read_scalar(fd_op)?.to_i32()?;
1579 // Reject if isolation is enabled.
1580 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1581 this.reject_in_isolation("`fdatasync`", reject_with)?;
1582 // Set error code as "EBADF" (bad fd)
1583 return this.handle_not_found();
1586 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1587 // FIXME: Support fdatasync for all FDs
1588 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1589 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1590 this.try_unwrap_io_result(io_result)
1592 this.handle_not_found()
1598 fd_op: &OpTy<'tcx, Provenance>,
1599 offset_op: &OpTy<'tcx, Provenance>,
1600 nbytes_op: &OpTy<'tcx, Provenance>,
1601 flags_op: &OpTy<'tcx, Provenance>,
1602 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1603 let this = self.eval_context_mut();
1605 let fd = this.read_scalar(fd_op)?.to_i32()?;
1606 let offset = this.read_scalar(offset_op)?.to_i64()?;
1607 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1608 let flags = this.read_scalar(flags_op)?.to_i32()?;
1610 if offset < 0 || nbytes < 0 {
1611 let einval = this.eval_libc("EINVAL")?;
1612 this.set_last_error(einval)?;
1613 return Ok(Scalar::from_i32(-1));
1615 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1616 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1617 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1618 if flags & allowed_flags != flags {
1619 let einval = this.eval_libc("EINVAL")?;
1620 this.set_last_error(einval)?;
1621 return Ok(Scalar::from_i32(-1));
1624 // Reject if isolation is enabled.
1625 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1626 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1627 // Set error code as "EBADF" (bad fd)
1628 return Ok(Scalar::from_i32(this.handle_not_found()?));
1631 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1632 // FIXME: Support sync_data_range for all FDs
1633 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1634 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1635 Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1637 Ok(Scalar::from_i32(this.handle_not_found()?))
1643 pathname_op: &OpTy<'tcx, Provenance>,
1644 buf_op: &OpTy<'tcx, Provenance>,
1645 bufsize_op: &OpTy<'tcx, Provenance>,
1646 ) -> InterpResult<'tcx, i64> {
1647 let this = self.eval_context_mut();
1649 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1650 let buf = this.read_pointer(buf_op)?;
1651 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1653 // Reject if isolation is enabled.
1654 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1655 this.reject_in_isolation("`readlink`", reject_with)?;
1656 let eacc = this.eval_libc("EACCES")?;
1657 this.set_last_error(eacc)?;
1661 let result = std::fs::read_link(pathname);
1664 // 'readlink' truncates the resolved path if the provided buffer is not large
1665 // enough, and does *not* add a null terminator. That means we cannot use the usual
1666 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1667 let resolved = this.convert_path_separator(
1668 Cow::Borrowed(resolved.as_ref()),
1669 crate::shims::os_str::PathConversion::HostToTarget,
1671 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1672 let bufsize: usize = bufsize.try_into().unwrap();
1673 if path_bytes.len() > bufsize {
1674 path_bytes = &path_bytes[..bufsize]
1676 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1677 Ok(path_bytes.len().try_into().unwrap())
1680 this.set_last_error_from_io_error(e.kind())?;
1686 #[cfg_attr(not(unix), allow(unused))]
1689 miri_fd: &OpTy<'tcx, Provenance>,
1690 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1691 let this = self.eval_context_mut();
1692 // "returns 1 if fd is an open file descriptor referring to a terminal;
1693 // otherwise 0 is returned, and errno is set to indicate the error"
1694 if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1695 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1696 if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1697 return Ok(Scalar::from_i32(1));
1700 // Fallback when the FD was not found or isolation is enabled.
1701 let enotty = this.eval_libc("ENOTTY")?;
1702 this.set_last_error(enotty)?;
1703 Ok(Scalar::from_i32(0))
1708 path_op: &OpTy<'tcx, Provenance>,
1709 processed_path_op: &OpTy<'tcx, Provenance>,
1710 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1711 let this = self.eval_context_mut();
1712 this.assert_target_os_is_unix("realpath");
1714 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1715 let processed_ptr = this.read_pointer(processed_path_op)?;
1717 // Reject if isolation is enabled.
1718 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1719 this.reject_in_isolation("`realpath`", reject_with)?;
1720 let eacc = this.eval_libc("EACCES")?;
1721 this.set_last_error(eacc)?;
1722 return Ok(Scalar::from_machine_usize(0, this));
1725 let result = std::fs::canonicalize(pathname);
1729 .eval_libc_i32("PATH_MAX")?
1731 .expect("PATH_MAX does not fit in u64");
1732 let dest = if this.ptr_is_null(processed_ptr)? {
1733 // POSIX says behavior when passing a null pointer is implementation-defined,
1734 // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1737 // "If resolved_path is specified as NULL, then realpath() uses
1738 // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1739 // the resolved pathname, and returns a pointer to this buffer. The
1740 // caller should deallocate this buffer using free(3)."
1741 // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1742 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1744 let (wrote_path, _) =
1745 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1748 // Note that we do not explicitly handle `FILENAME_MAX`
1749 // (different from `PATH_MAX` above) as it is Linux-specific and
1750 // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1751 let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1752 this.set_last_error(enametoolong)?;
1753 return Ok(Scalar::from_machine_usize(0, this));
1758 Ok(Scalar::from_maybe_pointer(dest, this))
1761 this.set_last_error_from_io_error(e.kind())?;
1762 Ok(Scalar::from_machine_usize(0, this))
1766 fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1767 use rand::seq::SliceRandom;
1769 // POSIX defines the template string.
1770 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1772 let this = self.eval_context_mut();
1773 this.assert_target_os_is_unix("mkstemp");
1775 // POSIX defines the maximum number of attempts before failure.
1777 // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1778 // POSIX says this about `TMP_MAX`:
1779 // * Minimum number of unique filenames generated by `tmpnam()`.
1780 // * Maximum number of times an application can call `tmpnam()` reliably.
1781 // * The value of `TMP_MAX` is at least 25.
1782 // * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1783 // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1784 let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1786 // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1787 // (and the target is unix, so a byte slice is the right representation).
1788 let template_ptr = this.read_pointer(template_op)?;
1789 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1790 let template_bytes = template.as_mut_slice();
1792 // Reject if isolation is enabled.
1793 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1794 this.reject_in_isolation("`mkstemp`", reject_with)?;
1795 let eacc = this.eval_libc("EACCES")?;
1796 this.set_last_error(eacc)?;
1800 // Get the bytes of the suffix we expect in _target_ encoding.
1801 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1803 // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1804 // that represents the expected suffix.
1806 // Now we figure out the index of the slice we expect to contain the suffix.
1807 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1808 let end_pos = template_bytes.len();
1809 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1811 // If we don't find the suffix, it is an error.
1812 if last_six_char_bytes != suffix_bytes {
1813 let einval = this.eval_libc("EINVAL")?;
1814 this.set_last_error(einval)?;
1818 // At this point we know we have 6 ASCII 'X' characters as a suffix.
1820 // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1821 const SUBSTITUTIONS: &[char; 62] = &[
1822 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1823 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1824 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1825 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1828 // The file is opened with specific options, which Rust does not expose in a portable way.
1829 // So we use specific APIs depending on the host OS.
1830 let mut fopts = OpenOptions::new();
1831 fopts.read(true).write(true).create_new(true);
1835 use std::os::unix::fs::OpenOptionsExt;
1837 // Do not allow others to read or modify this file.
1838 fopts.custom_flags(libc::O_EXCL);
1842 use std::os::windows::fs::OpenOptionsExt;
1843 // Do not allow others to read or modify this file.
1844 fopts.share_mode(0);
1847 // If the generated file already exists, we will try again `max_attempts` many times.
1848 for _ in 0..max_attempts {
1849 let rng = this.machine.rng.get_mut();
1851 // Generate a random unique suffix.
1852 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1854 // Replace the template string with the random string.
1855 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1857 // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1858 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1860 // To actually open the file, turn this into a host OsString.
1861 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1863 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1865 let file = fopts.open(possibly_unique);
1869 let fh = &mut this.machine.file_handler;
1870 let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1875 // If the random file already exists, keep trying.
1876 ErrorKind::AlreadyExists => continue,
1877 // Any other errors are returned to the caller.
1879 // "On error, -1 is returned, and errno is set to
1880 // indicate the error"
1881 this.set_last_error_from_io_error(e.kind())?;
1888 // We ran out of attempts to create the file, return an error.
1889 let eexist = this.eval_libc("EEXIST")?;
1890 this.set_last_error(eexist)?;
1895 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1896 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1898 fn extract_sec_and_nsec<'tcx>(
1899 time: std::io::Result<SystemTime>,
1900 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1903 let duration = system_time_to_duration(&time)?;
1904 Ok((duration.as_secs(), duration.subsec_nanos()))
1909 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1911 struct FileMetadata {
1912 mode: Scalar<Provenance>,
1914 created: Option<(u64, u32)>,
1915 accessed: Option<(u64, u32)>,
1916 modified: Option<(u64, u32)>,
1920 fn from_path<'tcx, 'mir>(
1921 ecx: &mut MiriInterpCx<'mir, 'tcx>,
1923 follow_symlink: bool,
1924 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1926 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1928 FileMetadata::from_meta(ecx, metadata)
1931 fn from_fd<'tcx, 'mir>(
1932 ecx: &mut MiriInterpCx<'mir, 'tcx>,
1934 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1935 let option = ecx.machine.file_handler.handles.get(&fd);
1936 let file = match option {
1937 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1938 None => return ecx.handle_not_found().map(|_: i32| None),
1940 let metadata = file.metadata();
1942 FileMetadata::from_meta(ecx, metadata)
1945 fn from_meta<'tcx, 'mir>(
1946 ecx: &mut MiriInterpCx<'mir, 'tcx>,
1947 metadata: Result<std::fs::Metadata, std::io::Error>,
1948 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1949 let metadata = match metadata {
1950 Ok(metadata) => metadata,
1952 ecx.set_last_error_from_io_error(e.kind())?;
1957 let file_type = metadata.file_type();
1959 let mode_name = if file_type.is_file() {
1961 } else if file_type.is_dir() {
1967 let mode = ecx.eval_libc(mode_name)?;
1969 let size = metadata.len();
1971 let created = extract_sec_and_nsec(metadata.created())?;
1972 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1973 let modified = extract_sec_and_nsec(metadata.modified())?;
1975 // FIXME: Provide more fields using platform specific methods.
1976 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))