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(SbTag)) {
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(SbTag)) {
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 `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
609 // then we throw an error.
611 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
614 let path = this.read_path_from_c_str(path)?;
616 // Reject if isolation is enabled.
617 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
618 this.reject_in_isolation("`open`", reject_with)?;
619 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
623 let fd = options.open(path).map(|file| {
624 let fh = &mut this.machine.file_handler;
625 fh.insert_fd(Box::new(FileHandle { file, writable }))
628 this.try_unwrap_io_result(fd)
631 fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
632 let this = self.eval_context_mut();
636 "incorrect number of arguments for fcntl: got {}, expected at least 2",
640 let fd = this.read_scalar(&args[0])?.to_i32()?;
641 let cmd = this.read_scalar(&args[1])?.to_i32()?;
643 // Reject if isolation is enabled.
644 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
645 this.reject_in_isolation("`fcntl`", reject_with)?;
646 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
650 // We only support getting the flags for a descriptor.
651 if cmd == this.eval_libc_i32("F_GETFD")? {
652 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
653 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
654 // always sets this flag when opening a file. However we still need to check that the
655 // file itself is open.
656 if this.machine.file_handler.handles.contains_key(&fd) {
657 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
659 this.handle_not_found()
661 } else if cmd == this.eval_libc_i32("F_DUPFD")?
662 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
664 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
665 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
666 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
667 // thus they can share the same implementation here.
670 "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
674 let start = this.read_scalar(&args[2])?.to_i32()?;
676 let fh = &mut this.machine.file_handler;
678 match fh.handles.get_mut(&fd) {
679 Some(file_descriptor) => {
680 let dup_result = file_descriptor.dup();
682 Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
684 this.set_last_error_from_io_error(e.kind())?;
689 None => this.handle_not_found(),
691 } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
692 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
693 // FIXME: Support fullfsync for all FDs
694 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
695 let io_result = maybe_sync_file(file, *writable, File::sync_all);
696 this.try_unwrap_io_result(io_result)
698 this.handle_not_found()
701 throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
705 fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
706 let this = self.eval_context_mut();
708 let fd = this.read_scalar(fd_op)?.to_i32()?;
711 if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
712 let result = file_descriptor.close(this.machine.communicate())?;
713 this.try_unwrap_io_result(result)?
715 this.handle_not_found()?
723 buf: Pointer<Option<Provenance>>,
725 ) -> InterpResult<'tcx, i64> {
726 let this = self.eval_context_mut();
728 // Isolation check is done via `FileDescriptor` trait.
730 trace!("Reading from FD {}, size {}", fd, count);
732 // Check that the *entire* buffer is actually valid memory.
733 this.check_ptr_access_align(
735 Size::from_bytes(count),
737 CheckInAllocMsg::MemoryAccessTest,
740 // We cap the number of read bytes to the largest value that we are able to fit in both the
741 // host's and target's `isize`. This saves us from having to handle overflows later.
743 .min(u64::try_from(this.machine_isize_max()).unwrap())
744 .min(u64::try_from(isize::MAX).unwrap());
745 let communicate = this.machine.communicate();
747 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
748 trace!("read: FD mapped to {:?}", file_descriptor);
749 // We want to read at most `count` bytes. We are sure that `count` is not negative
750 // because it was a target's `usize`. Also we are sure that its smaller than
751 // `usize::MAX` because it is bounded by the host's `isize`.
752 let mut bytes = vec![0; usize::try_from(count).unwrap()];
753 // `File::read` never returns a value larger than `count`,
754 // so this cannot fail.
756 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
760 // If reading to `bytes` did not fail, we write those bytes to the buffer.
761 this.write_bytes_ptr(buf, bytes)?;
765 this.set_last_error_from_io_error(e.kind())?;
770 trace!("read: FD not found");
771 this.handle_not_found()
778 buf: Pointer<Option<Provenance>>,
780 ) -> InterpResult<'tcx, i64> {
781 let this = self.eval_context_mut();
783 // Isolation check is done via `FileDescriptor` trait.
785 // Check that the *entire* buffer is actually valid memory.
786 this.check_ptr_access_align(
788 Size::from_bytes(count),
790 CheckInAllocMsg::MemoryAccessTest,
793 // We cap the number of written bytes to the largest value that we are able to fit in both the
794 // host's and target's `isize`. This saves us from having to handle overflows later.
796 .min(u64::try_from(this.machine_isize_max()).unwrap())
797 .min(u64::try_from(isize::MAX).unwrap());
798 let communicate = this.machine.communicate();
800 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
801 let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
803 file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
804 this.try_unwrap_io_result(result)
806 this.handle_not_found()
812 fd_op: &OpTy<'tcx, Provenance>,
813 offset_op: &OpTy<'tcx, Provenance>,
814 whence_op: &OpTy<'tcx, Provenance>,
815 ) -> InterpResult<'tcx, Scalar<Provenance>> {
816 let this = self.eval_context_mut();
818 // Isolation check is done via `FileDescriptor` trait.
820 let fd = this.read_scalar(fd_op)?.to_i32()?;
821 let offset = this.read_scalar(offset_op)?.to_i64()?;
822 let whence = this.read_scalar(whence_op)?.to_i32()?;
824 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
825 SeekFrom::Start(u64::try_from(offset).unwrap())
826 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
827 SeekFrom::Current(offset)
828 } else if whence == this.eval_libc_i32("SEEK_END")? {
829 SeekFrom::End(offset)
831 let einval = this.eval_libc("EINVAL")?;
832 this.set_last_error(einval)?;
833 return Ok(Scalar::from_i64(-1));
836 let communicate = this.machine.communicate();
838 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
839 let result = file_descriptor
840 .seek(communicate, seek_from)?
841 .map(|offset| i64::try_from(offset).unwrap());
842 this.try_unwrap_io_result(result)?
844 this.handle_not_found()?
849 fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
850 let this = self.eval_context_mut();
852 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
854 // Reject if isolation is enabled.
855 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
856 this.reject_in_isolation("`unlink`", reject_with)?;
857 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
861 let result = remove_file(path).map(|_| 0);
862 this.try_unwrap_io_result(result)
867 target_op: &OpTy<'tcx, Provenance>,
868 linkpath_op: &OpTy<'tcx, Provenance>,
869 ) -> InterpResult<'tcx, i32> {
871 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
872 std::os::unix::fs::symlink(src, dst)
876 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
877 use std::os::windows::fs;
878 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
881 let this = self.eval_context_mut();
882 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
883 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
885 // Reject if isolation is enabled.
886 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
887 this.reject_in_isolation("`symlink`", reject_with)?;
888 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
892 let result = create_link(&target, &linkpath).map(|_| 0);
893 this.try_unwrap_io_result(result)
898 path_op: &OpTy<'tcx, Provenance>,
899 buf_op: &OpTy<'tcx, Provenance>,
900 ) -> InterpResult<'tcx, Scalar<Provenance>> {
901 let this = self.eval_context_mut();
902 this.assert_target_os("macos", "stat");
904 let path_scalar = this.read_pointer(path_op)?;
905 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
907 // Reject if isolation is enabled.
908 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
909 this.reject_in_isolation("`stat`", reject_with)?;
910 let eacc = this.eval_libc("EACCES")?;
911 this.set_last_error(eacc)?;
912 return Ok(Scalar::from_i32(-1));
915 // `stat` always follows symlinks.
916 let metadata = match FileMetadata::from_path(this, &path, true)? {
917 Some(metadata) => metadata,
918 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
921 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
924 // `lstat` is used to get symlink metadata.
927 path_op: &OpTy<'tcx, Provenance>,
928 buf_op: &OpTy<'tcx, Provenance>,
929 ) -> InterpResult<'tcx, Scalar<Provenance>> {
930 let this = self.eval_context_mut();
931 this.assert_target_os("macos", "lstat");
933 let path_scalar = this.read_pointer(path_op)?;
934 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
936 // Reject if isolation is enabled.
937 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
938 this.reject_in_isolation("`lstat`", reject_with)?;
939 let eacc = this.eval_libc("EACCES")?;
940 this.set_last_error(eacc)?;
941 return Ok(Scalar::from_i32(-1));
944 let metadata = match FileMetadata::from_path(this, &path, false)? {
945 Some(metadata) => metadata,
946 None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
949 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
954 fd_op: &OpTy<'tcx, Provenance>,
955 buf_op: &OpTy<'tcx, Provenance>,
956 ) -> InterpResult<'tcx, Scalar<Provenance>> {
957 let this = self.eval_context_mut();
959 this.assert_target_os("macos", "fstat");
961 let fd = this.read_scalar(fd_op)?.to_i32()?;
963 // Reject if isolation is enabled.
964 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
965 this.reject_in_isolation("`fstat`", reject_with)?;
966 // Set error code as "EBADF" (bad fd)
967 return Ok(Scalar::from_i32(this.handle_not_found()?));
970 let metadata = match FileMetadata::from_fd(this, fd)? {
971 Some(metadata) => metadata,
972 None => return Ok(Scalar::from_i32(-1)),
974 Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
979 dirfd_op: &OpTy<'tcx, Provenance>, // Should be an `int`
980 pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
981 flags_op: &OpTy<'tcx, Provenance>, // Should be an `int`
982 mask_op: &OpTy<'tcx, Provenance>, // Should be an `unsigned int`
983 statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
984 ) -> InterpResult<'tcx, i32> {
985 let this = self.eval_context_mut();
987 this.assert_target_os("linux", "statx");
989 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
990 let pathname_ptr = this.read_pointer(pathname_op)?;
991 let flags = this.read_scalar(flags_op)?.to_i32()?;
992 let _mask = this.read_scalar(mask_op)?.to_u32()?;
993 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
995 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
996 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
997 let efault = this.eval_libc("EFAULT")?;
998 this.set_last_error(efault)?;
1002 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
1003 // proper `MemPlace` and then write the results of this function to it. However, the
1004 // `syscall` function is untyped. This means that all the `statx` parameters are provided
1005 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
1006 // `statxbuf_op` by using the `libc::statx` struct type.
1008 let statx_layout = this.libc_ty_layout("statx")?;
1009 MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
1012 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1013 // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
1014 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
1016 // * interpreting `path` as an absolute directory,
1017 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1018 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1020 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1021 // found this error, please open an issue reporting it.
1022 if !(path.is_absolute()
1023 || dirfd == this.eval_libc_i32("AT_FDCWD")?
1024 || (path.as_os_str().is_empty() && empty_path_flag))
1026 throw_unsup_format!(
1027 "using statx is only supported with absolute paths, relative paths with the file \
1028 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1033 // Reject if isolation is enabled.
1034 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1035 this.reject_in_isolation("`statx`", reject_with)?;
1036 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1037 // since `path` is provided, either absolute or
1038 // relative to CWD, `EACCES` is the most relevant.
1039 this.eval_libc("EACCES")?
1041 // `dirfd` is set to target file, and `path` is empty
1042 // (or we would have hit the `throw_unsup_format`
1043 // above). `EACCES` would violate the spec.
1044 assert!(empty_path_flag);
1045 this.eval_libc("EBADF")?
1047 this.set_last_error(ecode)?;
1051 // the `_mask_op` paramter specifies the file information that the caller requested.
1052 // However `statx` is allowed to return information that was not requested or to not
1053 // return information that was requested. This `mask` represents the information we can
1054 // actually provide for any target.
1056 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1058 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1060 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1062 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1063 // represented by dirfd, whether it's a directory or otherwise.
1064 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1065 FileMetadata::from_fd(this, dirfd)?
1067 FileMetadata::from_path(this, &path, follow_symlink)?
1069 let metadata = match metadata {
1070 Some(metadata) => metadata,
1071 None => return Ok(-1),
1074 // The `mode` field specifies the type of the file and the permissions over the file for
1075 // the owner, its group and other users. Given that we can only provide the file type
1076 // without using platform specific methods, we only set the bits corresponding to the file
1077 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1078 let mode: u16 = metadata
1082 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1084 // We need to set the corresponding bits of `mask` if the access, creation and modification
1085 // times were available. Otherwise we let them be zero.
1086 let (access_sec, access_nsec) = metadata
1089 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1090 InterpResult::Ok(tup)
1092 .unwrap_or_else(|| Ok((0, 0)))?;
1094 let (created_sec, created_nsec) = metadata
1097 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1098 InterpResult::Ok(tup)
1100 .unwrap_or_else(|| Ok((0, 0)))?;
1102 let (modified_sec, modified_nsec) = metadata
1105 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1106 InterpResult::Ok(tup)
1108 .unwrap_or_else(|| Ok((0, 0)))?;
1110 // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1111 this.write_int_fields_named(
1113 ("stx_mask", mask.into()),
1115 ("stx_attributes", 0),
1119 ("stx_mode", mode.into()),
1121 ("stx_size", metadata.size.into()),
1123 ("stx_attributes_mask", 0),
1124 ("stx_rdev_major", 0),
1125 ("stx_rdev_minor", 0),
1126 ("stx_dev_major", 0),
1127 ("stx_dev_minor", 0),
1132 this.write_int_fields_named(
1134 ("tv_sec", access_sec.into()),
1135 ("tv_nsec", access_nsec.into()),
1137 &this.mplace_field_named(&statxbuf, "stx_atime")?,
1140 this.write_int_fields_named(
1142 ("tv_sec", created_sec.into()),
1143 ("tv_nsec", created_nsec.into()),
1145 &this.mplace_field_named(&statxbuf, "stx_btime")?,
1148 this.write_int_fields_named(
1150 ("tv_sec", 0.into()),
1151 ("tv_nsec", 0.into()),
1153 &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1156 this.write_int_fields_named(
1158 ("tv_sec", modified_sec.into()),
1159 ("tv_nsec", modified_nsec.into()),
1161 &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1169 oldpath_op: &OpTy<'tcx, Provenance>,
1170 newpath_op: &OpTy<'tcx, Provenance>,
1171 ) -> InterpResult<'tcx, i32> {
1172 let this = self.eval_context_mut();
1174 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1175 let newpath_ptr = this.read_pointer(newpath_op)?;
1177 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1178 let efault = this.eval_libc("EFAULT")?;
1179 this.set_last_error(efault)?;
1183 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1184 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1186 // Reject if isolation is enabled.
1187 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1188 this.reject_in_isolation("`rename`", reject_with)?;
1189 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1193 let result = rename(oldpath, newpath).map(|_| 0);
1195 this.try_unwrap_io_result(result)
1200 path_op: &OpTy<'tcx, Provenance>,
1201 mode_op: &OpTy<'tcx, Provenance>,
1202 ) -> InterpResult<'tcx, i32> {
1203 let this = self.eval_context_mut();
1205 #[cfg_attr(not(unix), allow(unused_variables))]
1206 let mode = if this.tcx.sess.target.os == "macos" {
1207 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1209 this.read_scalar(mode_op)?.to_u32()?
1212 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1214 // Reject if isolation is enabled.
1215 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1216 this.reject_in_isolation("`mkdir`", reject_with)?;
1217 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1221 #[cfg_attr(not(unix), allow(unused_mut))]
1222 let mut builder = DirBuilder::new();
1224 // If the host supports it, forward on the mode of the directory
1225 // (i.e. permission bits and the sticky bit)
1228 use std::os::unix::fs::DirBuilderExt;
1232 let result = builder.create(path).map(|_| 0i32);
1234 this.try_unwrap_io_result(result)
1237 fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1238 let this = self.eval_context_mut();
1240 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1242 // Reject if isolation is enabled.
1243 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1244 this.reject_in_isolation("`rmdir`", reject_with)?;
1245 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1249 let result = remove_dir(path).map(|_| 0i32);
1251 this.try_unwrap_io_result(result)
1256 name_op: &OpTy<'tcx, Provenance>,
1257 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1258 let this = self.eval_context_mut();
1260 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1262 // Reject if isolation is enabled.
1263 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1264 this.reject_in_isolation("`opendir`", reject_with)?;
1265 let eacc = this.eval_libc("EACCES")?;
1266 this.set_last_error(eacc)?;
1267 return Ok(Scalar::null_ptr(this));
1270 let result = read_dir(name);
1274 let id = this.machine.dir_handler.insert_new(dir_iter);
1276 // The libc API for opendir says that this method returns a pointer to an opaque
1277 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1279 Ok(Scalar::from_machine_usize(id, this))
1282 this.set_last_error_from_io_error(e.kind())?;
1283 Ok(Scalar::null_ptr(this))
1290 dirp_op: &OpTy<'tcx, Provenance>,
1291 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1292 let this = self.eval_context_mut();
1294 this.assert_target_os("linux", "readdir64");
1296 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1298 // Reject if isolation is enabled.
1299 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1300 this.reject_in_isolation("`readdir`", reject_with)?;
1301 let eacc = this.eval_libc("EBADF")?;
1302 this.set_last_error(eacc)?;
1303 return Ok(Scalar::null_ptr(this));
1306 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1307 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1310 let entry = match open_dir.read_dir.next() {
1311 Some(Ok(dir_entry)) => {
1312 // Write the directory entry into a newly allocated buffer.
1313 // The name is written with write_bytes, while the rest of the
1314 // dirent64 struct is written using write_int_fields.
1317 // pub struct dirent64 {
1318 // pub d_ino: ino64_t,
1319 // pub d_off: off64_t,
1320 // pub d_reclen: c_ushort,
1321 // pub d_type: c_uchar,
1322 // pub d_name: [c_char; 256],
1325 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1326 name.push("\0"); // Add a NUL terminator
1327 let name_bytes = os_str_to_bytes(&name)?;
1328 let name_len = u64::try_from(name_bytes.len()).unwrap();
1330 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1331 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1332 let size = d_name_offset.checked_add(name_len).unwrap();
1335 this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1337 // If the host is a Unix system, fill in the inode number with its real value.
1338 // If not, use 0 as a fallback value.
1340 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1344 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1346 this.write_int_fields_named(
1348 ("d_ino", ino.into()),
1350 ("d_reclen", size.into()),
1351 ("d_type", file_type.into()),
1353 &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1356 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1357 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1362 // end of stream: return NULL
1366 this.set_last_error_from_io_error(e.kind())?;
1371 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1372 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1373 this.free(old_entry, MiriMemoryKind::Runtime)?;
1375 Ok(Scalar::from_maybe_pointer(entry, this))
1380 dirp_op: &OpTy<'tcx, Provenance>,
1381 entry_op: &OpTy<'tcx, Provenance>,
1382 result_op: &OpTy<'tcx, Provenance>,
1383 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1384 let this = self.eval_context_mut();
1386 this.assert_target_os("macos", "readdir_r");
1388 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1390 // Reject if isolation is enabled.
1391 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1392 this.reject_in_isolation("`readdir_r`", reject_with)?;
1393 // Set error code as "EBADF" (bad fd)
1394 return Ok(Scalar::from_i32(this.handle_not_found()?));
1397 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1398 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1400 Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1401 Some(Ok(dir_entry)) => {
1402 // Write into entry, write pointer to result, return 0 on success.
1403 // The name is written with write_os_str_to_c_str, while the rest of the
1404 // dirent struct is written using write_int_fields.
1407 // pub struct dirent {
1409 // pub d_seekoff: u64,
1410 // pub d_reclen: u16,
1411 // pub d_namlen: u16,
1413 // pub d_name: [c_char; 1024],
1416 let entry_place = this.deref_operand(entry_op)?;
1417 let name_place = this.mplace_field(&entry_place, 5)?;
1419 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1420 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1423 name_place.layout.size.bytes(),
1425 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1427 throw_unsup_format!(
1428 "a directory entry had a name too large to fit in libc::dirent"
1432 let entry_place = this.deref_operand(entry_op)?;
1434 // If the host is a Unix system, fill in the inode number with its real value.
1435 // If not, use 0 as a fallback value.
1437 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1441 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1443 this.write_int_fields_named(
1445 ("d_ino", ino.into()),
1448 ("d_namlen", file_name_len.into()),
1449 ("d_type", file_type.into()),
1454 let result_place = this.deref_operand(result_op)?;
1455 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1460 // end of stream: return 0, assign *result=NULL
1461 this.write_null(&this.deref_operand(result_op)?.into())?;
1465 match e.raw_os_error() {
1466 // return positive error number on error
1467 Some(error) => error,
1469 throw_unsup_format!(
1470 "the error {} couldn't be converted to a return value",
1478 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1479 let this = self.eval_context_mut();
1481 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1483 // Reject if isolation is enabled.
1484 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1485 this.reject_in_isolation("`closedir`", reject_with)?;
1486 // Set error code as "EBADF" (bad fd)
1487 return this.handle_not_found();
1490 if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1491 this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1495 this.handle_not_found()
1501 fd_op: &OpTy<'tcx, Provenance>,
1502 length_op: &OpTy<'tcx, Provenance>,
1503 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1504 let this = self.eval_context_mut();
1506 let fd = this.read_scalar(fd_op)?.to_i32()?;
1507 let length = this.read_scalar(length_op)?.to_i64()?;
1509 // Reject if isolation is enabled.
1510 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1511 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1512 // Set error code as "EBADF" (bad fd)
1513 return Ok(Scalar::from_i32(this.handle_not_found()?));
1516 Ok(Scalar::from_i32(
1517 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1518 // FIXME: Support ftruncate64 for all FDs
1519 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1521 if let Ok(length) = length.try_into() {
1522 let result = file.set_len(length);
1523 this.try_unwrap_io_result(result.map(|_| 0i32))?
1525 let einval = this.eval_libc("EINVAL")?;
1526 this.set_last_error(einval)?;
1530 // The file is not writable
1531 let einval = this.eval_libc("EINVAL")?;
1532 this.set_last_error(einval)?;
1536 this.handle_not_found()?
1541 fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1542 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1543 // underlying disk to finish writing. In the interest of host compatibility,
1544 // we conservatively implement this with `sync_all`, which
1545 // *does* wait for the disk.
1547 let this = self.eval_context_mut();
1549 let fd = this.read_scalar(fd_op)?.to_i32()?;
1551 // Reject if isolation is enabled.
1552 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1553 this.reject_in_isolation("`fsync`", reject_with)?;
1554 // Set error code as "EBADF" (bad fd)
1555 return this.handle_not_found();
1558 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1559 // FIXME: Support fsync for all FDs
1560 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1561 let io_result = maybe_sync_file(file, *writable, File::sync_all);
1562 this.try_unwrap_io_result(io_result)
1564 this.handle_not_found()
1568 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1569 let this = self.eval_context_mut();
1571 let fd = this.read_scalar(fd_op)?.to_i32()?;
1573 // Reject if isolation is enabled.
1574 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1575 this.reject_in_isolation("`fdatasync`", reject_with)?;
1576 // Set error code as "EBADF" (bad fd)
1577 return this.handle_not_found();
1580 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1581 // FIXME: Support fdatasync for all FDs
1582 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1583 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1584 this.try_unwrap_io_result(io_result)
1586 this.handle_not_found()
1592 fd_op: &OpTy<'tcx, Provenance>,
1593 offset_op: &OpTy<'tcx, Provenance>,
1594 nbytes_op: &OpTy<'tcx, Provenance>,
1595 flags_op: &OpTy<'tcx, Provenance>,
1596 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1597 let this = self.eval_context_mut();
1599 let fd = this.read_scalar(fd_op)?.to_i32()?;
1600 let offset = this.read_scalar(offset_op)?.to_i64()?;
1601 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1602 let flags = this.read_scalar(flags_op)?.to_i32()?;
1604 if offset < 0 || nbytes < 0 {
1605 let einval = this.eval_libc("EINVAL")?;
1606 this.set_last_error(einval)?;
1607 return Ok(Scalar::from_i32(-1));
1609 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1610 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1611 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1612 if flags & allowed_flags != flags {
1613 let einval = this.eval_libc("EINVAL")?;
1614 this.set_last_error(einval)?;
1615 return Ok(Scalar::from_i32(-1));
1618 // Reject if isolation is enabled.
1619 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1620 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1621 // Set error code as "EBADF" (bad fd)
1622 return Ok(Scalar::from_i32(this.handle_not_found()?));
1625 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1626 // FIXME: Support sync_data_range for all FDs
1627 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1628 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1629 Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1631 Ok(Scalar::from_i32(this.handle_not_found()?))
1637 pathname_op: &OpTy<'tcx, Provenance>,
1638 buf_op: &OpTy<'tcx, Provenance>,
1639 bufsize_op: &OpTy<'tcx, Provenance>,
1640 ) -> InterpResult<'tcx, i64> {
1641 let this = self.eval_context_mut();
1643 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1644 let buf = this.read_pointer(buf_op)?;
1645 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1647 // Reject if isolation is enabled.
1648 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1649 this.reject_in_isolation("`readlink`", reject_with)?;
1650 let eacc = this.eval_libc("EACCES")?;
1651 this.set_last_error(eacc)?;
1655 let result = std::fs::read_link(pathname);
1658 // 'readlink' truncates the resolved path if the provided buffer is not large
1659 // enough, and does *not* add a null terminator. That means we cannot use the usual
1660 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1661 let resolved = this.convert_path_separator(
1662 Cow::Borrowed(resolved.as_ref()),
1663 crate::shims::os_str::PathConversion::HostToTarget,
1665 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1666 let bufsize: usize = bufsize.try_into().unwrap();
1667 if path_bytes.len() > bufsize {
1668 path_bytes = &path_bytes[..bufsize]
1670 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1671 Ok(path_bytes.len().try_into().unwrap())
1674 this.set_last_error_from_io_error(e.kind())?;
1680 #[cfg_attr(not(unix), allow(unused))]
1683 miri_fd: &OpTy<'tcx, Provenance>,
1684 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1685 let this = self.eval_context_mut();
1686 // "returns 1 if fd is an open file descriptor referring to a terminal;
1687 // otherwise 0 is returned, and errno is set to indicate the error"
1688 if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1689 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1690 if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1691 return Ok(Scalar::from_i32(1));
1694 // Fallback when the FD was not found or isolation is enabled.
1695 let enotty = this.eval_libc("ENOTTY")?;
1696 this.set_last_error(enotty)?;
1697 Ok(Scalar::from_i32(0))
1702 path_op: &OpTy<'tcx, Provenance>,
1703 processed_path_op: &OpTy<'tcx, Provenance>,
1704 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1705 let this = self.eval_context_mut();
1706 this.assert_target_os_is_unix("realpath");
1708 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1709 let processed_ptr = this.read_pointer(processed_path_op)?;
1711 // Reject if isolation is enabled.
1712 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1713 this.reject_in_isolation("`realpath`", reject_with)?;
1714 let eacc = this.eval_libc("EACCES")?;
1715 this.set_last_error(eacc)?;
1716 return Ok(Scalar::from_machine_usize(0, this));
1719 let result = std::fs::canonicalize(pathname);
1723 .eval_libc_i32("PATH_MAX")?
1725 .expect("PATH_MAX does not fit in u64");
1726 let dest = if this.ptr_is_null(processed_ptr)? {
1727 // POSIX says behavior when passing a null pointer is implementation-defined,
1728 // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1731 // "If resolved_path is specified as NULL, then realpath() uses
1732 // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1733 // the resolved pathname, and returns a pointer to this buffer. The
1734 // caller should deallocate this buffer using free(3)."
1735 // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1736 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1738 let (wrote_path, _) =
1739 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1742 // Note that we do not explicitly handle `FILENAME_MAX`
1743 // (different from `PATH_MAX` above) as it is Linux-specific and
1744 // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1745 let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1746 this.set_last_error(enametoolong)?;
1747 return Ok(Scalar::from_machine_usize(0, this));
1752 Ok(Scalar::from_maybe_pointer(dest, this))
1755 this.set_last_error_from_io_error(e.kind())?;
1756 Ok(Scalar::from_machine_usize(0, this))
1760 fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1761 use rand::seq::SliceRandom;
1763 // POSIX defines the template string.
1764 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1766 let this = self.eval_context_mut();
1767 this.assert_target_os_is_unix("mkstemp");
1769 // POSIX defines the maximum number of attempts before failure.
1771 // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1772 // POSIX says this about `TMP_MAX`:
1773 // * Minimum number of unique filenames generated by `tmpnam()`.
1774 // * Maximum number of times an application can call `tmpnam()` reliably.
1775 // * The value of `TMP_MAX` is at least 25.
1776 // * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1777 // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1778 let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1780 // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1781 // (and the target is unix, so a byte slice is the right representation).
1782 let template_ptr = this.read_pointer(template_op)?;
1783 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1784 let template_bytes = template.as_mut_slice();
1786 // Reject if isolation is enabled.
1787 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1788 this.reject_in_isolation("`mkstemp`", reject_with)?;
1789 let eacc = this.eval_libc("EACCES")?;
1790 this.set_last_error(eacc)?;
1794 // Get the bytes of the suffix we expect in _target_ encoding.
1795 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1797 // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1798 // that represents the expected suffix.
1800 // Now we figure out the index of the slice we expect to contain the suffix.
1801 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1802 let end_pos = template_bytes.len();
1803 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1805 // If we don't find the suffix, it is an error.
1806 if last_six_char_bytes != suffix_bytes {
1807 let einval = this.eval_libc("EINVAL")?;
1808 this.set_last_error(einval)?;
1812 // At this point we know we have 6 ASCII 'X' characters as a suffix.
1814 // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1815 const SUBSTITUTIONS: &[char; 62] = &[
1816 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1817 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1818 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1819 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1822 // The file is opened with specific options, which Rust does not expose in a portable way.
1823 // So we use specific APIs depending on the host OS.
1824 let mut fopts = OpenOptions::new();
1825 fopts.read(true).write(true).create_new(true);
1829 use std::os::unix::fs::OpenOptionsExt;
1831 // Do not allow others to read or modify this file.
1832 fopts.custom_flags(libc::O_EXCL);
1836 use std::os::windows::fs::OpenOptionsExt;
1837 // Do not allow others to read or modify this file.
1838 fopts.share_mode(0);
1841 // If the generated file already exists, we will try again `max_attempts` many times.
1842 for _ in 0..max_attempts {
1843 let rng = this.machine.rng.get_mut();
1845 // Generate a random unique suffix.
1846 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1848 // Replace the template string with the random string.
1849 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1851 // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1852 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1854 // To actually open the file, turn this into a host OsString.
1855 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1857 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1859 let file = fopts.open(possibly_unique);
1863 let fh = &mut this.machine.file_handler;
1864 let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1869 // If the random file already exists, keep trying.
1870 ErrorKind::AlreadyExists => continue,
1871 // Any other errors are returned to the caller.
1873 // "On error, -1 is returned, and errno is set to
1874 // indicate the error"
1875 this.set_last_error_from_io_error(e.kind())?;
1882 // We ran out of attempts to create the file, return an error.
1883 let eexist = this.eval_libc("EEXIST")?;
1884 this.set_last_error(eexist)?;
1889 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1890 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1892 fn extract_sec_and_nsec<'tcx>(
1893 time: std::io::Result<SystemTime>,
1894 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1897 let duration = system_time_to_duration(&time)?;
1898 Ok((duration.as_secs(), duration.subsec_nanos()))
1903 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1905 struct FileMetadata {
1906 mode: Scalar<Provenance>,
1908 created: Option<(u64, u32)>,
1909 accessed: Option<(u64, u32)>,
1910 modified: Option<(u64, u32)>,
1915 ecx: &mut MiriInterpCx<'_, 'tcx>,
1917 follow_symlink: bool,
1918 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1920 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1922 FileMetadata::from_meta(ecx, metadata)
1926 ecx: &mut MiriInterpCx<'_, 'tcx>,
1928 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1929 let option = ecx.machine.file_handler.handles.get(&fd);
1930 let file = match option {
1931 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1932 None => return ecx.handle_not_found().map(|_: i32| None),
1934 let metadata = file.metadata();
1936 FileMetadata::from_meta(ecx, metadata)
1940 ecx: &mut MiriInterpCx<'_, 'tcx>,
1941 metadata: Result<std::fs::Metadata, std::io::Error>,
1942 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1943 let metadata = match metadata {
1944 Ok(metadata) => metadata,
1946 ecx.set_last_error_from_io_error(e.kind())?;
1951 let file_type = metadata.file_type();
1953 let mode_name = if file_type.is_file() {
1955 } else if file_type.is_dir() {
1961 let mode = ecx.eval_libc(mode_name)?;
1963 let size = metadata.len();
1965 let created = extract_sec_and_nsec(metadata.created())?;
1966 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1967 let modified = extract_sec_and_nsec(metadata.modified())?;
1969 // FIXME: Provide more fields using platform specific methods.
1970 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))