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_target::abi::{Align, Size};
16 use crate::shims::os_str::bytes_to_os_str;
18 use shims::os_str::os_str_to_bytes;
19 use shims::time::system_time_to_duration;
27 trait FileDescriptor: std::fmt::Debug {
28 fn name(&self) -> &'static str;
30 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
31 throw_unsup_format!("{} cannot be used as FileHandle", self.name());
36 _communicate_allowed: bool,
38 ) -> InterpResult<'tcx, io::Result<usize>> {
39 throw_unsup_format!("cannot read from {}", self.name());
44 _communicate_allowed: bool,
46 ) -> InterpResult<'tcx, io::Result<usize>> {
47 throw_unsup_format!("cannot write to {}", self.name());
52 _communicate_allowed: bool,
54 ) -> InterpResult<'tcx, io::Result<u64>> {
55 throw_unsup_format!("cannot seek on {}", self.name());
60 _communicate_allowed: bool,
61 ) -> InterpResult<'tcx, io::Result<i32>> {
62 throw_unsup_format!("cannot close {}", self.name());
65 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
67 fn is_tty(&self) -> bool;
70 fn as_unix_host_fd(&self) -> Option<i32> {
75 impl FileDescriptor for FileHandle {
76 fn name(&self) -> &'static str {
80 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
86 communicate_allowed: bool,
88 ) -> InterpResult<'tcx, io::Result<usize>> {
89 assert!(communicate_allowed, "isolation should have prevented even opening a file");
90 Ok(self.file.read(bytes))
95 communicate_allowed: bool,
97 ) -> InterpResult<'tcx, io::Result<usize>> {
98 assert!(communicate_allowed, "isolation should have prevented even opening a file");
99 Ok((&mut &self.file).write(bytes))
104 communicate_allowed: bool,
106 ) -> InterpResult<'tcx, io::Result<u64>> {
107 assert!(communicate_allowed, "isolation should have prevented even opening a file");
108 Ok(self.file.seek(offset))
113 communicate_allowed: bool,
114 ) -> InterpResult<'tcx, io::Result<i32>> {
115 assert!(communicate_allowed, "isolation should have prevented even opening a file");
116 // We sync the file if it was opened in a mode different than read-only.
118 // `File::sync_all` does the checks that are done when closing a file. We do this to
119 // to handle possible errors correctly.
120 let result = self.file.sync_all().map(|_| 0i32);
121 // Now we actually close the file.
123 // And return the result.
126 // We drop the file, this closes it but ignores any errors
127 // produced when closing it. This is done because
128 // `File::sync_all` cannot be done over files like
129 // `/dev/urandom` which are read-only. Check
130 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
131 // for a deeper discussion.
137 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
138 let duplicated = self.file.try_clone()?;
139 Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
143 fn as_unix_host_fd(&self) -> Option<i32> {
144 use std::os::unix::io::AsRawFd;
145 Some(self.file.as_raw_fd())
148 fn is_tty(&self) -> bool {
149 self.file.is_terminal()
153 impl FileDescriptor for io::Stdin {
154 fn name(&self) -> &'static str {
160 communicate_allowed: bool,
162 ) -> InterpResult<'tcx, io::Result<usize>> {
163 if !communicate_allowed {
164 // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
165 helpers::isolation_abort_error("`read` from stdin")?;
167 Ok(Read::read(self, bytes))
170 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
171 Ok(Box::new(io::stdin()))
175 fn as_unix_host_fd(&self) -> Option<i32> {
176 Some(libc::STDIN_FILENO)
179 fn is_tty(&self) -> bool {
184 impl FileDescriptor for io::Stdout {
185 fn name(&self) -> &'static str {
191 _communicate_allowed: bool,
193 ) -> InterpResult<'tcx, io::Result<usize>> {
194 // We allow writing to stderr even with isolation enabled.
195 let result = Write::write(&mut { self }, bytes);
196 // Stdout is buffered, flush to make sure it appears on the
197 // screen. This is the write() syscall of the interpreted
198 // program, we want it to correspond to a write() syscall on
199 // the host -- there is no good in adding extra buffering
201 io::stdout().flush().unwrap();
206 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
207 Ok(Box::new(io::stdout()))
211 fn as_unix_host_fd(&self) -> Option<i32> {
212 Some(libc::STDOUT_FILENO)
215 fn is_tty(&self) -> bool {
220 impl FileDescriptor for io::Stderr {
221 fn name(&self) -> &'static str {
227 _communicate_allowed: bool,
229 ) -> InterpResult<'tcx, io::Result<usize>> {
230 // We allow writing to stderr even with isolation enabled.
231 // No need to flush, stderr is not buffered.
232 Ok(Write::write(&mut { self }, bytes))
235 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
236 Ok(Box::new(io::stderr()))
240 fn as_unix_host_fd(&self) -> Option<i32> {
241 Some(libc::STDERR_FILENO)
244 fn is_tty(&self) -> bool {
252 impl FileDescriptor for NullOutput {
253 fn name(&self) -> &'static str {
259 _communicate_allowed: bool,
261 ) -> InterpResult<'tcx, io::Result<usize>> {
262 // We just don't write anything, but report to the user that we did.
266 fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
267 Ok(Box::new(NullOutput))
270 fn is_tty(&self) -> bool {
276 pub struct FileHandler {
277 handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
280 impl VisitTags for FileHandler {
281 fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
282 // All our FileDescriptor do not have any tags.
287 pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
288 let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
289 handles.insert(0i32, Box::new(io::stdin()));
290 if mute_stdout_stderr {
291 handles.insert(1i32, Box::new(NullOutput));
292 handles.insert(2i32, Box::new(NullOutput));
294 handles.insert(1i32, Box::new(io::stdout()));
295 handles.insert(2i32, Box::new(io::stderr()));
297 FileHandler { handles }
300 fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
301 self.insert_fd_with_min_fd(file_handle, 0)
304 fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
305 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
306 // between used FDs, the find_map combinator will return it. If the first such unused FD
307 // is after all other used FDs, the find_map combinator will return None, and we will use
308 // the FD following the greatest FD thus far.
309 let candidate_new_fd =
310 self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
312 // There was a gap in the fds stored, return the first unused one
313 // (note that this relies on BTreeMap iterating in key order)
316 // This fd is used, keep going
320 let new_fd = candidate_new_fd.unwrap_or_else(|| {
321 // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
322 // maximum fd in the map
325 .map(|(fd, _)| fd.checked_add(1).unwrap())
329 self.handles.try_insert(new_fd, file_handle).unwrap();
334 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
335 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
336 fn macos_stat_write_buf(
338 metadata: FileMetadata,
339 buf_op: &OpTy<'tcx, Provenance>,
340 ) -> InterpResult<'tcx, i32> {
341 let this = self.eval_context_mut();
343 let mode: u16 = metadata.mode.to_u16()?;
345 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
346 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
347 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
349 let buf = this.deref_operand(buf_op)?;
350 this.write_int_fields_named(
353 ("st_mode", mode.into()),
359 ("st_atime", access_sec.into()),
360 ("st_atime_nsec", access_nsec.into()),
361 ("st_mtime", modified_sec.into()),
362 ("st_mtime_nsec", modified_nsec.into()),
364 ("st_ctime_nsec", 0),
365 ("st_birthtime", created_sec.into()),
366 ("st_birthtime_nsec", created_nsec.into()),
367 ("st_size", metadata.size.into()),
379 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
380 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
381 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
382 /// types (like `read`, that returns an `i64`).
383 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
384 let this = self.eval_context_mut();
385 let ebadf = this.eval_libc("EBADF")?;
386 this.set_last_error(ebadf)?;
390 fn file_type_to_d_type(
392 file_type: std::io::Result<FileType>,
393 ) -> InterpResult<'tcx, i32> {
394 let this = self.eval_context_mut();
397 if file_type.is_dir() {
398 Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
399 } else if file_type.is_file() {
400 Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
401 } else if file_type.is_symlink() {
402 Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
404 // Certain file types are only supported when the host is a Unix system.
405 // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
406 // DT_UNKNOWN sooner.
410 use std::os::unix::fs::FileTypeExt;
411 if file_type.is_block_device() {
412 Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
413 } else if file_type.is_char_device() {
414 Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
415 } else if file_type.is_fifo() {
416 Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
417 } else if file_type.is_socket() {
418 Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
420 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
424 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
428 match e.raw_os_error() {
429 Some(error) => Ok(error),
432 "the error {} couldn't be converted to a return value",
440 /// An open directory, tracked by DirHandler.
443 /// The directory reader on the host.
445 /// The most recent entry returned by readdir()
446 entry: Pointer<Option<Provenance>>,
450 fn new(read_dir: ReadDir) -> Self {
451 // We rely on `free` being a NOP on null pointers.
452 Self { read_dir, entry: Pointer::null() }
457 pub struct DirHandler {
458 /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
461 /// When opendir is called, a directory iterator is created on the host for the target
462 /// directory, and an entry is stored in this hash map, indexed by an ID which represents
463 /// the directory stream. When readdir is called, the directory stream ID is used to look up
464 /// the corresponding ReadDir iterator from this map, and information from the next
465 /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
467 streams: FxHashMap<u64, OpenDir>,
468 /// ID number to be used by the next call to opendir
473 #[allow(clippy::integer_arithmetic)]
474 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
475 let id = self.next_id;
477 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
482 impl Default for DirHandler {
483 fn default() -> DirHandler {
485 streams: FxHashMap::default(),
486 // Skip 0 as an ID, because it looks like a null pointer to libc
492 impl VisitTags for DirHandler {
493 fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
494 let DirHandler { streams, next_id: _ } = self;
496 for dir in streams.values() {
497 dir.entry.visit_tags(visit);
505 operation: fn(&File) -> std::io::Result<()>,
506 ) -> std::io::Result<i32> {
507 if !writable && cfg!(windows) {
508 // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
509 // for writing. (FlushFileBuffers requires that the file handle have the
510 // GENERIC_WRITE right)
513 let result = operation(file);
518 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
519 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
520 fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
523 "incorrect number of arguments for `open`: got {}, expected at least 2",
528 let this = self.eval_context_mut();
530 let path = this.read_pointer(&args[0])?;
531 let flag = this.read_scalar(&args[1])?.to_i32()?;
533 let mut options = OpenOptions::new();
535 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
536 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
537 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
538 // The first two bits of the flag correspond to the access mode in linux, macOS and
539 // windows. We need to check that in fact the access mode flags for the current target
540 // only use these two bits, otherwise we are in an unsupported target and should error.
541 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
542 throw_unsup_format!("access mode flags on this target are unsupported");
544 let mut writable = true;
546 // Now we check the access mode
547 let access_mode = flag & 0b11;
549 if access_mode == o_rdonly {
552 } else if access_mode == o_wronly {
554 } else if access_mode == o_rdwr {
555 options.read(true).write(true);
557 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
559 // We need to check that there aren't unsupported options in `flag`. For this we try to
560 // reproduce the content of `flag` in the `mirror` variable using only the supported
562 let mut mirror = access_mode;
564 let o_append = this.eval_libc_i32("O_APPEND")?;
565 if flag & o_append != 0 {
566 options.append(true);
569 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
570 if flag & o_trunc != 0 {
571 options.truncate(true);
574 let o_creat = this.eval_libc_i32("O_CREAT")?;
575 if flag & o_creat != 0 {
576 // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
577 // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
578 // (see https://github.com/rust-lang/rust/issues/71915).
579 let mode = if let Some(arg) = args.get(2) {
580 this.read_scalar(arg)?.to_u32()?
583 "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3",
589 throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
594 let o_excl = this.eval_libc_i32("O_EXCL")?;
595 if flag & o_excl != 0 {
597 options.create_new(true);
599 options.create(true);
602 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
603 if flag & o_cloexec != 0 {
604 // We do not need to do anything for this flag because `std` already sets it.
605 // (Technically we do not support *not* setting this flag, but we ignore that.)
608 if this.tcx.sess.target.os == "linux" {
609 let o_tmpfile = this.eval_libc_i32("O_TMPFILE")?;
610 if flag & o_tmpfile != 0 {
611 // if the flag contains `O_TMPFILE` then we return a graceful error
612 let eopnotsupp = this.eval_libc("EOPNOTSUPP")?;
613 this.set_last_error(eopnotsupp)?;
617 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
618 // then we throw an error.
620 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
623 let path = this.read_path_from_c_str(path)?;
625 // Reject if isolation is enabled.
626 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
627 this.reject_in_isolation("`open`", reject_with)?;
628 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
632 let fd = options.open(path).map(|file| {
633 let fh = &mut this.machine.file_handler;
634 fh.insert_fd(Box::new(FileHandle { file, writable }))
637 this.try_unwrap_io_result(fd)
640 fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
641 let this = self.eval_context_mut();
645 "incorrect number of arguments for fcntl: got {}, expected at least 2",
649 let fd = this.read_scalar(&args[0])?.to_i32()?;
650 let cmd = this.read_scalar(&args[1])?.to_i32()?;
652 // Reject if isolation is enabled.
653 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
654 this.reject_in_isolation("`fcntl`", reject_with)?;
655 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
659 // We only support getting the flags for a descriptor.
660 if cmd == this.eval_libc_i32("F_GETFD")? {
661 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
662 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
663 // always sets this flag when opening a file. However we still need to check that the
664 // file itself is open.
665 if this.machine.file_handler.handles.contains_key(&fd) {
666 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
668 this.handle_not_found()
670 } else if cmd == this.eval_libc_i32("F_DUPFD")?
671 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
673 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
674 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
675 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
676 // thus they can share the same implementation here.
679 "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
683 let start = this.read_scalar(&args[2])?.to_i32()?;
685 let fh = &mut this.machine.file_handler;
687 match fh.handles.get_mut(&fd) {
688 Some(file_descriptor) => {
689 let dup_result = file_descriptor.dup();
691 Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
693 this.set_last_error_from_io_error(e.kind())?;
698 None => this.handle_not_found(),
700 } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
701 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
702 // FIXME: Support fullfsync for all FDs
703 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
704 let io_result = maybe_sync_file(file, *writable, File::sync_all);
705 this.try_unwrap_io_result(io_result)
707 this.handle_not_found()
710 throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
714 fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
715 let this = self.eval_context_mut();
717 let fd = this.read_scalar(fd_op)?.to_i32()?;
720 if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
721 let result = file_descriptor.close(this.machine.communicate())?;
722 this.try_unwrap_io_result(result)?
724 this.handle_not_found()?
732 buf: Pointer<Option<Provenance>>,
734 ) -> InterpResult<'tcx, i64> {
735 let this = self.eval_context_mut();
737 // Isolation check is done via `FileDescriptor` trait.
739 trace!("Reading from FD {}, size {}", fd, count);
741 // Check that the *entire* buffer is actually valid memory.
742 this.check_ptr_access_align(
744 Size::from_bytes(count),
746 CheckInAllocMsg::MemoryAccessTest,
749 // We cap the number of read bytes to the largest value that we are able to fit in both the
750 // host's and target's `isize`. This saves us from having to handle overflows later.
752 .min(u64::try_from(this.machine_isize_max()).unwrap())
753 .min(u64::try_from(isize::MAX).unwrap());
754 let communicate = this.machine.communicate();
756 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
757 trace!("read: FD mapped to {:?}", file_descriptor);
758 // We want to read at most `count` bytes. We are sure that `count` is not negative
759 // because it was a target's `usize`. Also we are sure that its smaller than
760 // `usize::MAX` because it is bounded by the host's `isize`.
761 let mut bytes = vec![0; usize::try_from(count).unwrap()];
762 // `File::read` never returns a value larger than `count`,
763 // so this cannot fail.
765 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
769 // If reading to `bytes` did not fail, we write those bytes to the buffer.
770 this.write_bytes_ptr(buf, bytes)?;
774 this.set_last_error_from_io_error(e.kind())?;
779 trace!("read: FD not found");
780 this.handle_not_found()
787 buf: Pointer<Option<Provenance>>,
789 ) -> InterpResult<'tcx, i64> {
790 let this = self.eval_context_mut();
792 // Isolation check is done via `FileDescriptor` trait.
794 // Check that the *entire* buffer is actually valid memory.
795 this.check_ptr_access_align(
797 Size::from_bytes(count),
799 CheckInAllocMsg::MemoryAccessTest,
802 // We cap the number of written bytes to the largest value that we are able to fit in both the
803 // host's and target's `isize`. This saves us from having to handle overflows later.
805 .min(u64::try_from(this.machine_isize_max()).unwrap())
806 .min(u64::try_from(isize::MAX).unwrap());
807 let communicate = this.machine.communicate();
809 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
810 let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
812 file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
813 this.try_unwrap_io_result(result)
815 this.handle_not_found()
821 fd_op: &OpTy<'tcx, Provenance>,
822 offset_op: &OpTy<'tcx, Provenance>,
823 whence_op: &OpTy<'tcx, Provenance>,
824 ) -> InterpResult<'tcx, Scalar<Provenance>> {
825 let this = self.eval_context_mut();
827 // Isolation check is done via `FileDescriptor` trait.
829 let fd = this.read_scalar(fd_op)?.to_i32()?;
830 let offset = this.read_scalar(offset_op)?.to_i64()?;
831 let whence = this.read_scalar(whence_op)?.to_i32()?;
833 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
834 SeekFrom::Start(u64::try_from(offset).unwrap())
835 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
836 SeekFrom::Current(offset)
837 } else if whence == this.eval_libc_i32("SEEK_END")? {
838 SeekFrom::End(offset)
840 let einval = this.eval_libc("EINVAL")?;
841 this.set_last_error(einval)?;
842 return Ok(Scalar::from_i64(-1));
845 let communicate = this.machine.communicate();
847 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
848 let result = file_descriptor
849 .seek(communicate, seek_from)?
850 .map(|offset| i64::try_from(offset).unwrap());
851 this.try_unwrap_io_result(result)?
853 this.handle_not_found()?
858 fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
859 let this = self.eval_context_mut();
861 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
863 // Reject if isolation is enabled.
864 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
865 this.reject_in_isolation("`unlink`", reject_with)?;
866 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
870 let result = remove_file(path).map(|_| 0);
871 this.try_unwrap_io_result(result)
876 target_op: &OpTy<'tcx, Provenance>,
877 linkpath_op: &OpTy<'tcx, Provenance>,
878 ) -> InterpResult<'tcx, i32> {
880 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
881 std::os::unix::fs::symlink(src, dst)
885 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
886 use std::os::windows::fs;
887 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
890 let this = self.eval_context_mut();
891 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
892 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
894 // Reject if isolation is enabled.
895 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
896 this.reject_in_isolation("`symlink`", reject_with)?;
897 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
901 let result = create_link(&target, &linkpath).map(|_| 0);
902 this.try_unwrap_io_result(result)
907 path_op: &OpTy<'tcx, Provenance>,
908 buf_op: &OpTy<'tcx, Provenance>,
909 ) -> InterpResult<'tcx, Scalar<Provenance>> {
910 let this = self.eval_context_mut();
911 this.assert_target_os("macos", "stat");
913 let path_scalar = this.read_pointer(path_op)?;
914 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
916 // Reject if isolation is enabled.
917 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
918 this.reject_in_isolation("`stat`", reject_with)?;
919 let eacc = this.eval_libc("EACCES")?;
920 this.set_last_error(eacc)?;
921 return Ok(Scalar::from_i32(-1));
924 // `stat` always follows symlinks.
925 let metadata = match FileMetadata::from_path(this, &path, true)? {
926 Some(metadata) => metadata,
927 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
930 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
933 // `lstat` is used to get symlink metadata.
936 path_op: &OpTy<'tcx, Provenance>,
937 buf_op: &OpTy<'tcx, Provenance>,
938 ) -> InterpResult<'tcx, Scalar<Provenance>> {
939 let this = self.eval_context_mut();
940 this.assert_target_os("macos", "lstat");
942 let path_scalar = this.read_pointer(path_op)?;
943 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
945 // Reject if isolation is enabled.
946 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
947 this.reject_in_isolation("`lstat`", reject_with)?;
948 let eacc = this.eval_libc("EACCES")?;
949 this.set_last_error(eacc)?;
950 return Ok(Scalar::from_i32(-1));
953 let metadata = match FileMetadata::from_path(this, &path, false)? {
954 Some(metadata) => metadata,
955 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
958 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
963 fd_op: &OpTy<'tcx, Provenance>,
964 buf_op: &OpTy<'tcx, Provenance>,
965 ) -> InterpResult<'tcx, Scalar<Provenance>> {
966 let this = self.eval_context_mut();
968 this.assert_target_os("macos", "fstat");
970 let fd = this.read_scalar(fd_op)?.to_i32()?;
972 // Reject if isolation is enabled.
973 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
974 this.reject_in_isolation("`fstat`", reject_with)?;
975 // Set error code as "EBADF" (bad fd)
976 return Ok(Scalar::from_i32(this.handle_not_found()?));
979 let metadata = match FileMetadata::from_fd(this, fd)? {
980 Some(metadata) => metadata,
981 None => return Ok(Scalar::from_i32(-1)),
983 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
988 dirfd_op: &OpTy<'tcx, Provenance>, // Should be an `int`
989 pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
990 flags_op: &OpTy<'tcx, Provenance>, // Should be an `int`
991 mask_op: &OpTy<'tcx, Provenance>, // Should be an `unsigned int`
992 statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
993 ) -> InterpResult<'tcx, i32> {
994 let this = self.eval_context_mut();
996 this.assert_target_os("linux", "statx");
998 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
999 let pathname_ptr = this.read_pointer(pathname_op)?;
1000 let flags = this.read_scalar(flags_op)?.to_i32()?;
1001 let _mask = this.read_scalar(mask_op)?.to_u32()?;
1002 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
1004 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
1005 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
1006 let efault = this.eval_libc("EFAULT")?;
1007 this.set_last_error(efault)?;
1011 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
1012 // proper `MemPlace` and then write the results of this function to it. However, the
1013 // `syscall` function is untyped. This means that all the `statx` parameters are provided
1014 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
1015 // `statxbuf_op` by using the `libc::statx` struct type.
1017 let statx_layout = this.libc_ty_layout("statx")?;
1018 MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
1021 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1022 // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
1023 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
1025 // * interpreting `path` as an absolute directory,
1026 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1027 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1029 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1030 // found this error, please open an issue reporting it.
1031 if !(path.is_absolute()
1032 || dirfd == this.eval_libc_i32("AT_FDCWD")?
1033 || (path.as_os_str().is_empty() && empty_path_flag))
1035 throw_unsup_format!(
1036 "using statx is only supported with absolute paths, relative paths with the file \
1037 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1042 // Reject if isolation is enabled.
1043 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1044 this.reject_in_isolation("`statx`", reject_with)?;
1045 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1046 // since `path` is provided, either absolute or
1047 // relative to CWD, `EACCES` is the most relevant.
1048 this.eval_libc("EACCES")?
1050 // `dirfd` is set to target file, and `path` is empty
1051 // (or we would have hit the `throw_unsup_format`
1052 // above). `EACCES` would violate the spec.
1053 assert!(empty_path_flag);
1054 this.eval_libc("EBADF")?
1056 this.set_last_error(ecode)?;
1060 // the `_mask_op` paramter specifies the file information that the caller requested.
1061 // However `statx` is allowed to return information that was not requested or to not
1062 // return information that was requested. This `mask` represents the information we can
1063 // actually provide for any target.
1065 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1067 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1069 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1071 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1072 // represented by dirfd, whether it's a directory or otherwise.
1073 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1074 FileMetadata::from_fd(this, dirfd)?
1076 FileMetadata::from_path(this, &path, follow_symlink)?
1078 let metadata = match metadata {
1079 Some(metadata) => metadata,
1080 None => return Ok(-1),
1083 // The `mode` field specifies the type of the file and the permissions over the file for
1084 // the owner, its group and other users. Given that we can only provide the file type
1085 // without using platform specific methods, we only set the bits corresponding to the file
1086 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1087 let mode: u16 = metadata
1091 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1093 // We need to set the corresponding bits of `mask` if the access, creation and modification
1094 // times were available. Otherwise we let them be zero.
1095 let (access_sec, access_nsec) = metadata
1098 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1099 InterpResult::Ok(tup)
1101 .unwrap_or_else(|| Ok((0, 0)))?;
1103 let (created_sec, created_nsec) = metadata
1106 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1107 InterpResult::Ok(tup)
1109 .unwrap_or_else(|| Ok((0, 0)))?;
1111 let (modified_sec, modified_nsec) = metadata
1114 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1115 InterpResult::Ok(tup)
1117 .unwrap_or_else(|| Ok((0, 0)))?;
1119 // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1120 this.write_int_fields_named(
1122 ("stx_mask", mask.into()),
1124 ("stx_attributes", 0),
1128 ("stx_mode", mode.into()),
1130 ("stx_size", metadata.size.into()),
1132 ("stx_attributes_mask", 0),
1133 ("stx_rdev_major", 0),
1134 ("stx_rdev_minor", 0),
1135 ("stx_dev_major", 0),
1136 ("stx_dev_minor", 0),
1141 this.write_int_fields_named(
1143 ("tv_sec", access_sec.into()),
1144 ("tv_nsec", access_nsec.into()),
1146 &this.mplace_field_named(&statxbuf, "stx_atime")?,
1149 this.write_int_fields_named(
1151 ("tv_sec", created_sec.into()),
1152 ("tv_nsec", created_nsec.into()),
1154 &this.mplace_field_named(&statxbuf, "stx_btime")?,
1157 this.write_int_fields_named(
1159 ("tv_sec", 0.into()),
1160 ("tv_nsec", 0.into()),
1162 &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1165 this.write_int_fields_named(
1167 ("tv_sec", modified_sec.into()),
1168 ("tv_nsec", modified_nsec.into()),
1170 &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1178 oldpath_op: &OpTy<'tcx, Provenance>,
1179 newpath_op: &OpTy<'tcx, Provenance>,
1180 ) -> InterpResult<'tcx, i32> {
1181 let this = self.eval_context_mut();
1183 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1184 let newpath_ptr = this.read_pointer(newpath_op)?;
1186 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1187 let efault = this.eval_libc("EFAULT")?;
1188 this.set_last_error(efault)?;
1192 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1193 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1195 // Reject if isolation is enabled.
1196 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1197 this.reject_in_isolation("`rename`", reject_with)?;
1198 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1202 let result = rename(oldpath, newpath).map(|_| 0);
1204 this.try_unwrap_io_result(result)
1209 path_op: &OpTy<'tcx, Provenance>,
1210 mode_op: &OpTy<'tcx, Provenance>,
1211 ) -> InterpResult<'tcx, i32> {
1212 let this = self.eval_context_mut();
1214 #[cfg_attr(not(unix), allow(unused_variables))]
1215 let mode = if this.tcx.sess.target.os == "macos" {
1216 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1218 this.read_scalar(mode_op)?.to_u32()?
1221 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1223 // Reject if isolation is enabled.
1224 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1225 this.reject_in_isolation("`mkdir`", reject_with)?;
1226 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1230 #[cfg_attr(not(unix), allow(unused_mut))]
1231 let mut builder = DirBuilder::new();
1233 // If the host supports it, forward on the mode of the directory
1234 // (i.e. permission bits and the sticky bit)
1237 use std::os::unix::fs::DirBuilderExt;
1241 let result = builder.create(path).map(|_| 0i32);
1243 this.try_unwrap_io_result(result)
1246 fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1247 let this = self.eval_context_mut();
1249 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1251 // Reject if isolation is enabled.
1252 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1253 this.reject_in_isolation("`rmdir`", reject_with)?;
1254 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1258 let result = remove_dir(path).map(|_| 0i32);
1260 this.try_unwrap_io_result(result)
1265 name_op: &OpTy<'tcx, Provenance>,
1266 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1267 let this = self.eval_context_mut();
1269 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1271 // Reject if isolation is enabled.
1272 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1273 this.reject_in_isolation("`opendir`", reject_with)?;
1274 let eacc = this.eval_libc("EACCES")?;
1275 this.set_last_error(eacc)?;
1276 return Ok(Scalar::null_ptr(this));
1279 let result = read_dir(name);
1283 let id = this.machine.dir_handler.insert_new(dir_iter);
1285 // The libc API for opendir says that this method returns a pointer to an opaque
1286 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1288 Ok(Scalar::from_machine_usize(id, this))
1291 this.set_last_error_from_io_error(e.kind())?;
1292 Ok(Scalar::null_ptr(this))
1299 dirp_op: &OpTy<'tcx, Provenance>,
1300 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1301 let this = self.eval_context_mut();
1303 this.assert_target_os("linux", "readdir64");
1305 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1307 // Reject if isolation is enabled.
1308 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1309 this.reject_in_isolation("`readdir`", reject_with)?;
1310 let eacc = this.eval_libc("EBADF")?;
1311 this.set_last_error(eacc)?;
1312 return Ok(Scalar::null_ptr(this));
1315 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1316 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1319 let entry = match open_dir.read_dir.next() {
1320 Some(Ok(dir_entry)) => {
1321 // Write the directory entry into a newly allocated buffer.
1322 // The name is written with write_bytes, while the rest of the
1323 // dirent64 struct is written using write_int_fields.
1326 // pub struct dirent64 {
1327 // pub d_ino: ino64_t,
1328 // pub d_off: off64_t,
1329 // pub d_reclen: c_ushort,
1330 // pub d_type: c_uchar,
1331 // pub d_name: [c_char; 256],
1334 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1335 name.push("\0"); // Add a NUL terminator
1336 let name_bytes = os_str_to_bytes(&name)?;
1337 let name_len = u64::try_from(name_bytes.len()).unwrap();
1339 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1340 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1341 let size = d_name_offset.checked_add(name_len).unwrap();
1344 this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1346 // If the host is a Unix system, fill in the inode number with its real value.
1347 // If not, use 0 as a fallback value.
1349 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1353 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1355 this.write_int_fields_named(
1357 ("d_ino", ino.into()),
1359 ("d_reclen", size.into()),
1360 ("d_type", file_type.into()),
1362 &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1365 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1366 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1371 // end of stream: return NULL
1375 this.set_last_error_from_io_error(e.kind())?;
1380 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1381 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1382 this.free(old_entry, MiriMemoryKind::Runtime)?;
1384 Ok(Scalar::from_maybe_pointer(entry, this))
1389 dirp_op: &OpTy<'tcx, Provenance>,
1390 entry_op: &OpTy<'tcx, Provenance>,
1391 result_op: &OpTy<'tcx, Provenance>,
1392 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1393 let this = self.eval_context_mut();
1395 this.assert_target_os("macos", "readdir_r");
1397 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1399 // Reject if isolation is enabled.
1400 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1401 this.reject_in_isolation("`readdir_r`", reject_with)?;
1402 // Set error code as "EBADF" (bad fd)
1403 return Ok(Scalar::from_i32(this.handle_not_found()?));
1406 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1407 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1409 Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1410 Some(Ok(dir_entry)) => {
1411 // Write into entry, write pointer to result, return 0 on success.
1412 // The name is written with write_os_str_to_c_str, while the rest of the
1413 // dirent struct is written using write_int_fields.
1416 // pub struct dirent {
1418 // pub d_seekoff: u64,
1419 // pub d_reclen: u16,
1420 // pub d_namlen: u16,
1422 // pub d_name: [c_char; 1024],
1425 let entry_place = this.deref_operand(entry_op)?;
1426 let name_place = this.mplace_field(&entry_place, 5)?;
1428 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1429 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1432 name_place.layout.size.bytes(),
1434 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1436 throw_unsup_format!(
1437 "a directory entry had a name too large to fit in libc::dirent"
1441 let entry_place = this.deref_operand(entry_op)?;
1443 // If the host is a Unix system, fill in the inode number with its real value.
1444 // If not, use 0 as a fallback value.
1446 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1450 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1452 this.write_int_fields_named(
1454 ("d_ino", ino.into()),
1457 ("d_namlen", file_name_len.into()),
1458 ("d_type", file_type.into()),
1463 let result_place = this.deref_operand(result_op)?;
1464 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1469 // end of stream: return 0, assign *result=NULL
1470 this.write_null(&this.deref_operand(result_op)?.into())?;
1474 match e.raw_os_error() {
1475 // return positive error number on error
1476 Some(error) => error,
1478 throw_unsup_format!(
1479 "the error {} couldn't be converted to a return value",
1487 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1488 let this = self.eval_context_mut();
1490 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1492 // Reject if isolation is enabled.
1493 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1494 this.reject_in_isolation("`closedir`", reject_with)?;
1495 // Set error code as "EBADF" (bad fd)
1496 return this.handle_not_found();
1499 if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1500 this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1504 this.handle_not_found()
1510 fd_op: &OpTy<'tcx, Provenance>,
1511 length_op: &OpTy<'tcx, Provenance>,
1512 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1513 let this = self.eval_context_mut();
1515 let fd = this.read_scalar(fd_op)?.to_i32()?;
1516 let length = this.read_scalar(length_op)?.to_i64()?;
1518 // Reject if isolation is enabled.
1519 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1520 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1521 // Set error code as "EBADF" (bad fd)
1522 return Ok(Scalar::from_i32(this.handle_not_found()?));
1525 Ok(Scalar::from_i32(
1526 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1527 // FIXME: Support ftruncate64 for all FDs
1528 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1530 if let Ok(length) = length.try_into() {
1531 let result = file.set_len(length);
1532 this.try_unwrap_io_result(result.map(|_| 0i32))?
1534 let einval = this.eval_libc("EINVAL")?;
1535 this.set_last_error(einval)?;
1539 // The file is not writable
1540 let einval = this.eval_libc("EINVAL")?;
1541 this.set_last_error(einval)?;
1545 this.handle_not_found()?
1550 fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1551 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1552 // underlying disk to finish writing. In the interest of host compatibility,
1553 // we conservatively implement this with `sync_all`, which
1554 // *does* wait for the disk.
1556 let this = self.eval_context_mut();
1558 let fd = this.read_scalar(fd_op)?.to_i32()?;
1560 // Reject if isolation is enabled.
1561 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1562 this.reject_in_isolation("`fsync`", reject_with)?;
1563 // Set error code as "EBADF" (bad fd)
1564 return this.handle_not_found();
1567 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1568 // FIXME: Support fsync for all FDs
1569 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1570 let io_result = maybe_sync_file(file, *writable, File::sync_all);
1571 this.try_unwrap_io_result(io_result)
1573 this.handle_not_found()
1577 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1578 let this = self.eval_context_mut();
1580 let fd = this.read_scalar(fd_op)?.to_i32()?;
1582 // Reject if isolation is enabled.
1583 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1584 this.reject_in_isolation("`fdatasync`", reject_with)?;
1585 // Set error code as "EBADF" (bad fd)
1586 return this.handle_not_found();
1589 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1590 // FIXME: Support fdatasync for all FDs
1591 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1592 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1593 this.try_unwrap_io_result(io_result)
1595 this.handle_not_found()
1601 fd_op: &OpTy<'tcx, Provenance>,
1602 offset_op: &OpTy<'tcx, Provenance>,
1603 nbytes_op: &OpTy<'tcx, Provenance>,
1604 flags_op: &OpTy<'tcx, Provenance>,
1605 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1606 let this = self.eval_context_mut();
1608 let fd = this.read_scalar(fd_op)?.to_i32()?;
1609 let offset = this.read_scalar(offset_op)?.to_i64()?;
1610 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1611 let flags = this.read_scalar(flags_op)?.to_i32()?;
1613 if offset < 0 || nbytes < 0 {
1614 let einval = this.eval_libc("EINVAL")?;
1615 this.set_last_error(einval)?;
1616 return Ok(Scalar::from_i32(-1));
1618 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1619 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1620 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1621 if flags & allowed_flags != flags {
1622 let einval = this.eval_libc("EINVAL")?;
1623 this.set_last_error(einval)?;
1624 return Ok(Scalar::from_i32(-1));
1627 // Reject if isolation is enabled.
1628 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1629 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1630 // Set error code as "EBADF" (bad fd)
1631 return Ok(Scalar::from_i32(this.handle_not_found()?));
1634 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1635 // FIXME: Support sync_data_range for all FDs
1636 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1637 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1638 Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1640 Ok(Scalar::from_i32(this.handle_not_found()?))
1646 pathname_op: &OpTy<'tcx, Provenance>,
1647 buf_op: &OpTy<'tcx, Provenance>,
1648 bufsize_op: &OpTy<'tcx, Provenance>,
1649 ) -> InterpResult<'tcx, i64> {
1650 let this = self.eval_context_mut();
1652 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1653 let buf = this.read_pointer(buf_op)?;
1654 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1656 // Reject if isolation is enabled.
1657 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1658 this.reject_in_isolation("`readlink`", reject_with)?;
1659 let eacc = this.eval_libc("EACCES")?;
1660 this.set_last_error(eacc)?;
1664 let result = std::fs::read_link(pathname);
1667 // 'readlink' truncates the resolved path if the provided buffer is not large
1668 // enough, and does *not* add a null terminator. That means we cannot use the usual
1669 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1670 let resolved = this.convert_path_separator(
1671 Cow::Borrowed(resolved.as_ref()),
1672 crate::shims::os_str::PathConversion::HostToTarget,
1674 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1675 let bufsize: usize = bufsize.try_into().unwrap();
1676 if path_bytes.len() > bufsize {
1677 path_bytes = &path_bytes[..bufsize]
1679 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1680 Ok(path_bytes.len().try_into().unwrap())
1683 this.set_last_error_from_io_error(e.kind())?;
1689 #[cfg_attr(not(unix), allow(unused))]
1692 miri_fd: &OpTy<'tcx, Provenance>,
1693 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1694 let this = self.eval_context_mut();
1695 // "returns 1 if fd is an open file descriptor referring to a terminal;
1696 // otherwise 0 is returned, and errno is set to indicate the error"
1697 if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1698 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1699 if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1700 return Ok(Scalar::from_i32(1));
1703 // Fallback when the FD was not found or isolation is enabled.
1704 let enotty = this.eval_libc("ENOTTY")?;
1705 this.set_last_error(enotty)?;
1706 Ok(Scalar::from_i32(0))
1711 path_op: &OpTy<'tcx, Provenance>,
1712 processed_path_op: &OpTy<'tcx, Provenance>,
1713 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1714 let this = self.eval_context_mut();
1715 this.assert_target_os_is_unix("realpath");
1717 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1718 let processed_ptr = this.read_pointer(processed_path_op)?;
1720 // Reject if isolation is enabled.
1721 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1722 this.reject_in_isolation("`realpath`", reject_with)?;
1723 let eacc = this.eval_libc("EACCES")?;
1724 this.set_last_error(eacc)?;
1725 return Ok(Scalar::from_machine_usize(0, this));
1728 let result = std::fs::canonicalize(pathname);
1732 .eval_libc_i32("PATH_MAX")?
1734 .expect("PATH_MAX does not fit in u64");
1735 let dest = if this.ptr_is_null(processed_ptr)? {
1736 // POSIX says behavior when passing a null pointer is implementation-defined,
1737 // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1740 // "If resolved_path is specified as NULL, then realpath() uses
1741 // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1742 // the resolved pathname, and returns a pointer to this buffer. The
1743 // caller should deallocate this buffer using free(3)."
1744 // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1745 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1747 let (wrote_path, _) =
1748 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1751 // Note that we do not explicitly handle `FILENAME_MAX`
1752 // (different from `PATH_MAX` above) as it is Linux-specific and
1753 // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1754 let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1755 this.set_last_error(enametoolong)?;
1756 return Ok(Scalar::from_machine_usize(0, this));
1761 Ok(Scalar::from_maybe_pointer(dest, this))
1764 this.set_last_error_from_io_error(e.kind())?;
1765 Ok(Scalar::from_machine_usize(0, this))
1769 fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1770 use rand::seq::SliceRandom;
1772 // POSIX defines the template string.
1773 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1775 let this = self.eval_context_mut();
1776 this.assert_target_os_is_unix("mkstemp");
1778 // POSIX defines the maximum number of attempts before failure.
1780 // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1781 // POSIX says this about `TMP_MAX`:
1782 // * Minimum number of unique filenames generated by `tmpnam()`.
1783 // * Maximum number of times an application can call `tmpnam()` reliably.
1784 // * The value of `TMP_MAX` is at least 25.
1785 // * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1786 // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1787 let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1789 // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1790 // (and the target is unix, so a byte slice is the right representation).
1791 let template_ptr = this.read_pointer(template_op)?;
1792 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1793 let template_bytes = template.as_mut_slice();
1795 // Reject if isolation is enabled.
1796 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1797 this.reject_in_isolation("`mkstemp`", reject_with)?;
1798 let eacc = this.eval_libc("EACCES")?;
1799 this.set_last_error(eacc)?;
1803 // Get the bytes of the suffix we expect in _target_ encoding.
1804 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1806 // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1807 // that represents the expected suffix.
1809 // Now we figure out the index of the slice we expect to contain the suffix.
1810 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1811 let end_pos = template_bytes.len();
1812 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1814 // If we don't find the suffix, it is an error.
1815 if last_six_char_bytes != suffix_bytes {
1816 let einval = this.eval_libc("EINVAL")?;
1817 this.set_last_error(einval)?;
1821 // At this point we know we have 6 ASCII 'X' characters as a suffix.
1823 // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1824 const SUBSTITUTIONS: &[char; 62] = &[
1825 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1826 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1827 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1828 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1831 // The file is opened with specific options, which Rust does not expose in a portable way.
1832 // So we use specific APIs depending on the host OS.
1833 let mut fopts = OpenOptions::new();
1834 fopts.read(true).write(true).create_new(true);
1838 use std::os::unix::fs::OpenOptionsExt;
1840 // Do not allow others to read or modify this file.
1841 fopts.custom_flags(libc::O_EXCL);
1845 use std::os::windows::fs::OpenOptionsExt;
1846 // Do not allow others to read or modify this file.
1847 fopts.share_mode(0);
1850 // If the generated file already exists, we will try again `max_attempts` many times.
1851 for _ in 0..max_attempts {
1852 let rng = this.machine.rng.get_mut();
1854 // Generate a random unique suffix.
1855 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1857 // Replace the template string with the random string.
1858 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1860 // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1861 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1863 // To actually open the file, turn this into a host OsString.
1864 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1866 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1868 let file = fopts.open(possibly_unique);
1872 let fh = &mut this.machine.file_handler;
1873 let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1878 // If the random file already exists, keep trying.
1879 ErrorKind::AlreadyExists => continue,
1880 // Any other errors are returned to the caller.
1882 // "On error, -1 is returned, and errno is set to
1883 // indicate the error"
1884 this.set_last_error_from_io_error(e.kind())?;
1891 // We ran out of attempts to create the file, return an error.
1892 let eexist = this.eval_libc("EEXIST")?;
1893 this.set_last_error(eexist)?;
1898 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1899 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1901 fn extract_sec_and_nsec<'tcx>(
1902 time: std::io::Result<SystemTime>,
1903 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1906 let duration = system_time_to_duration(&time)?;
1907 Ok((duration.as_secs(), duration.subsec_nanos()))
1912 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1914 struct FileMetadata {
1915 mode: Scalar<Provenance>,
1917 created: Option<(u64, u32)>,
1918 accessed: Option<(u64, u32)>,
1919 modified: Option<(u64, u32)>,
1924 ecx: &mut MiriInterpCx<'_, 'tcx>,
1926 follow_symlink: bool,
1927 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1929 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1931 FileMetadata::from_meta(ecx, metadata)
1935 ecx: &mut MiriInterpCx<'_, 'tcx>,
1937 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1938 let option = ecx.machine.file_handler.handles.get(&fd);
1939 let file = match option {
1940 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1941 None => return ecx.handle_not_found().map(|_: i32| None),
1943 let metadata = file.metadata();
1945 FileMetadata::from_meta(ecx, metadata)
1949 ecx: &mut MiriInterpCx<'_, 'tcx>,
1950 metadata: Result<std::fs::Metadata, std::io::Error>,
1951 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1952 let metadata = match metadata {
1953 Ok(metadata) => metadata,
1955 ecx.set_last_error_from_io_error(e.kind())?;
1960 let file_type = metadata.file_type();
1962 let mode_name = if file_type.is_file() {
1964 } else if file_type.is_dir() {
1970 let mode = ecx.eval_libc(mode_name)?;
1972 let size = metadata.len();
1974 let created = extract_sec_and_nsec(metadata.created())?;
1975 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1976 let modified = extract_sec_and_nsec(metadata.modified())?;
1978 // FIXME: Provide more fields using platform specific methods.
1979 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))