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 == o_append {
566 options.append(true);
569 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
570 if flag & o_trunc == o_trunc {
571 options.truncate(true);
574 let o_creat = this.eval_libc_i32("O_CREAT")?;
575 if flag & o_creat == o_creat {
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 == o_excl {
597 options.create_new(true);
599 options.create(true);
602 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
603 if flag & o_cloexec == o_cloexec {
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 == o_tmpfile {
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 at_ampty_path = this.eval_libc_i32("AT_EMPTY_PATH")?;
1024 let empty_path_flag = flags & at_ampty_path == at_ampty_path;
1026 // * interpreting `path` as an absolute directory,
1027 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1028 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1030 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1031 // found this error, please open an issue reporting it.
1032 if !(path.is_absolute()
1033 || dirfd == this.eval_libc_i32("AT_FDCWD")?
1034 || (path.as_os_str().is_empty() && empty_path_flag))
1036 throw_unsup_format!(
1037 "using statx is only supported with absolute paths, relative paths with the file \
1038 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1043 // Reject if isolation is enabled.
1044 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1045 this.reject_in_isolation("`statx`", reject_with)?;
1046 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1047 // since `path` is provided, either absolute or
1048 // relative to CWD, `EACCES` is the most relevant.
1049 this.eval_libc("EACCES")?
1051 // `dirfd` is set to target file, and `path` is empty
1052 // (or we would have hit the `throw_unsup_format`
1053 // above). `EACCES` would violate the spec.
1054 assert!(empty_path_flag);
1055 this.eval_libc("EBADF")?
1057 this.set_last_error(ecode)?;
1061 // the `_mask_op` paramter specifies the file information that the caller requested.
1062 // However `statx` is allowed to return information that was not requested or to not
1063 // return information that was requested. This `mask` represents the information we can
1064 // actually provide for any target.
1066 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1068 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1070 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1072 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1073 // represented by dirfd, whether it's a directory or otherwise.
1074 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1075 FileMetadata::from_fd(this, dirfd)?
1077 FileMetadata::from_path(this, &path, follow_symlink)?
1079 let metadata = match metadata {
1080 Some(metadata) => metadata,
1081 None => return Ok(-1),
1084 // The `mode` field specifies the type of the file and the permissions over the file for
1085 // the owner, its group and other users. Given that we can only provide the file type
1086 // without using platform specific methods, we only set the bits corresponding to the file
1087 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1088 let mode: u16 = metadata
1092 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1094 // We need to set the corresponding bits of `mask` if the access, creation and modification
1095 // times were available. Otherwise we let them be zero.
1096 let (access_sec, access_nsec) = metadata
1099 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1100 InterpResult::Ok(tup)
1102 .unwrap_or_else(|| Ok((0, 0)))?;
1104 let (created_sec, created_nsec) = metadata
1107 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1108 InterpResult::Ok(tup)
1110 .unwrap_or_else(|| Ok((0, 0)))?;
1112 let (modified_sec, modified_nsec) = metadata
1115 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1116 InterpResult::Ok(tup)
1118 .unwrap_or_else(|| Ok((0, 0)))?;
1120 // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1121 this.write_int_fields_named(
1123 ("stx_mask", mask.into()),
1125 ("stx_attributes", 0),
1129 ("stx_mode", mode.into()),
1131 ("stx_size", metadata.size.into()),
1133 ("stx_attributes_mask", 0),
1134 ("stx_rdev_major", 0),
1135 ("stx_rdev_minor", 0),
1136 ("stx_dev_major", 0),
1137 ("stx_dev_minor", 0),
1142 this.write_int_fields_named(
1144 ("tv_sec", access_sec.into()),
1145 ("tv_nsec", access_nsec.into()),
1147 &this.mplace_field_named(&statxbuf, "stx_atime")?,
1150 this.write_int_fields_named(
1152 ("tv_sec", created_sec.into()),
1153 ("tv_nsec", created_nsec.into()),
1155 &this.mplace_field_named(&statxbuf, "stx_btime")?,
1158 this.write_int_fields_named(
1160 ("tv_sec", 0.into()),
1161 ("tv_nsec", 0.into()),
1163 &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1166 this.write_int_fields_named(
1168 ("tv_sec", modified_sec.into()),
1169 ("tv_nsec", modified_nsec.into()),
1171 &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1179 oldpath_op: &OpTy<'tcx, Provenance>,
1180 newpath_op: &OpTy<'tcx, Provenance>,
1181 ) -> InterpResult<'tcx, i32> {
1182 let this = self.eval_context_mut();
1184 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1185 let newpath_ptr = this.read_pointer(newpath_op)?;
1187 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1188 let efault = this.eval_libc("EFAULT")?;
1189 this.set_last_error(efault)?;
1193 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1194 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1196 // Reject if isolation is enabled.
1197 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1198 this.reject_in_isolation("`rename`", reject_with)?;
1199 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1203 let result = rename(oldpath, newpath).map(|_| 0);
1205 this.try_unwrap_io_result(result)
1210 path_op: &OpTy<'tcx, Provenance>,
1211 mode_op: &OpTy<'tcx, Provenance>,
1212 ) -> InterpResult<'tcx, i32> {
1213 let this = self.eval_context_mut();
1215 #[cfg_attr(not(unix), allow(unused_variables))]
1216 let mode = if this.tcx.sess.target.os == "macos" {
1217 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1219 this.read_scalar(mode_op)?.to_u32()?
1222 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1224 // Reject if isolation is enabled.
1225 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1226 this.reject_in_isolation("`mkdir`", reject_with)?;
1227 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1231 #[cfg_attr(not(unix), allow(unused_mut))]
1232 let mut builder = DirBuilder::new();
1234 // If the host supports it, forward on the mode of the directory
1235 // (i.e. permission bits and the sticky bit)
1238 use std::os::unix::fs::DirBuilderExt;
1242 let result = builder.create(path).map(|_| 0i32);
1244 this.try_unwrap_io_result(result)
1247 fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1248 let this = self.eval_context_mut();
1250 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1252 // Reject if isolation is enabled.
1253 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1254 this.reject_in_isolation("`rmdir`", reject_with)?;
1255 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1259 let result = remove_dir(path).map(|_| 0i32);
1261 this.try_unwrap_io_result(result)
1266 name_op: &OpTy<'tcx, Provenance>,
1267 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1268 let this = self.eval_context_mut();
1270 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1272 // Reject if isolation is enabled.
1273 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1274 this.reject_in_isolation("`opendir`", reject_with)?;
1275 let eacc = this.eval_libc("EACCES")?;
1276 this.set_last_error(eacc)?;
1277 return Ok(Scalar::null_ptr(this));
1280 let result = read_dir(name);
1284 let id = this.machine.dir_handler.insert_new(dir_iter);
1286 // The libc API for opendir says that this method returns a pointer to an opaque
1287 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1289 Ok(Scalar::from_machine_usize(id, this))
1292 this.set_last_error_from_io_error(e.kind())?;
1293 Ok(Scalar::null_ptr(this))
1300 dirp_op: &OpTy<'tcx, Provenance>,
1301 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1302 let this = self.eval_context_mut();
1304 this.assert_target_os("linux", "readdir64");
1306 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1308 // Reject if isolation is enabled.
1309 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1310 this.reject_in_isolation("`readdir`", reject_with)?;
1311 let eacc = this.eval_libc("EBADF")?;
1312 this.set_last_error(eacc)?;
1313 return Ok(Scalar::null_ptr(this));
1316 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1317 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1320 let entry = match open_dir.read_dir.next() {
1321 Some(Ok(dir_entry)) => {
1322 // Write the directory entry into a newly allocated buffer.
1323 // The name is written with write_bytes, while the rest of the
1324 // dirent64 struct is written using write_int_fields.
1327 // pub struct dirent64 {
1328 // pub d_ino: ino64_t,
1329 // pub d_off: off64_t,
1330 // pub d_reclen: c_ushort,
1331 // pub d_type: c_uchar,
1332 // pub d_name: [c_char; 256],
1335 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1336 name.push("\0"); // Add a NUL terminator
1337 let name_bytes = os_str_to_bytes(&name)?;
1338 let name_len = u64::try_from(name_bytes.len()).unwrap();
1340 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1341 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1342 let size = d_name_offset.checked_add(name_len).unwrap();
1345 this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1347 // If the host is a Unix system, fill in the inode number with its real value.
1348 // If not, use 0 as a fallback value.
1350 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1354 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1356 this.write_int_fields_named(
1358 ("d_ino", ino.into()),
1360 ("d_reclen", size.into()),
1361 ("d_type", file_type.into()),
1363 &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1366 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1367 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1372 // end of stream: return NULL
1376 this.set_last_error_from_io_error(e.kind())?;
1381 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1382 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1383 this.free(old_entry, MiriMemoryKind::Runtime)?;
1385 Ok(Scalar::from_maybe_pointer(entry, this))
1390 dirp_op: &OpTy<'tcx, Provenance>,
1391 entry_op: &OpTy<'tcx, Provenance>,
1392 result_op: &OpTy<'tcx, Provenance>,
1393 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1394 let this = self.eval_context_mut();
1396 this.assert_target_os("macos", "readdir_r");
1398 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1400 // Reject if isolation is enabled.
1401 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1402 this.reject_in_isolation("`readdir_r`", reject_with)?;
1403 // Set error code as "EBADF" (bad fd)
1404 return Ok(Scalar::from_i32(this.handle_not_found()?));
1407 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1408 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1410 Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1411 Some(Ok(dir_entry)) => {
1412 // Write into entry, write pointer to result, return 0 on success.
1413 // The name is written with write_os_str_to_c_str, while the rest of the
1414 // dirent struct is written using write_int_fields.
1417 // pub struct dirent {
1419 // pub d_seekoff: u64,
1420 // pub d_reclen: u16,
1421 // pub d_namlen: u16,
1423 // pub d_name: [c_char; 1024],
1426 let entry_place = this.deref_operand(entry_op)?;
1427 let name_place = this.mplace_field(&entry_place, 5)?;
1429 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1430 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1433 name_place.layout.size.bytes(),
1435 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1437 throw_unsup_format!(
1438 "a directory entry had a name too large to fit in libc::dirent"
1442 let entry_place = this.deref_operand(entry_op)?;
1444 // If the host is a Unix system, fill in the inode number with its real value.
1445 // If not, use 0 as a fallback value.
1447 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1451 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1453 this.write_int_fields_named(
1455 ("d_ino", ino.into()),
1458 ("d_namlen", file_name_len.into()),
1459 ("d_type", file_type.into()),
1464 let result_place = this.deref_operand(result_op)?;
1465 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1470 // end of stream: return 0, assign *result=NULL
1471 this.write_null(&this.deref_operand(result_op)?.into())?;
1475 match e.raw_os_error() {
1476 // return positive error number on error
1477 Some(error) => error,
1479 throw_unsup_format!(
1480 "the error {} couldn't be converted to a return value",
1488 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1489 let this = self.eval_context_mut();
1491 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1493 // Reject if isolation is enabled.
1494 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1495 this.reject_in_isolation("`closedir`", reject_with)?;
1496 // Set error code as "EBADF" (bad fd)
1497 return this.handle_not_found();
1500 if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1501 this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1505 this.handle_not_found()
1511 fd_op: &OpTy<'tcx, Provenance>,
1512 length_op: &OpTy<'tcx, Provenance>,
1513 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1514 let this = self.eval_context_mut();
1516 let fd = this.read_scalar(fd_op)?.to_i32()?;
1517 let length = this.read_scalar(length_op)?.to_i64()?;
1519 // Reject if isolation is enabled.
1520 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1521 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1522 // Set error code as "EBADF" (bad fd)
1523 return Ok(Scalar::from_i32(this.handle_not_found()?));
1526 Ok(Scalar::from_i32(
1527 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1528 // FIXME: Support ftruncate64 for all FDs
1529 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1531 if let Ok(length) = length.try_into() {
1532 let result = file.set_len(length);
1533 this.try_unwrap_io_result(result.map(|_| 0i32))?
1535 let einval = this.eval_libc("EINVAL")?;
1536 this.set_last_error(einval)?;
1540 // The file is not writable
1541 let einval = this.eval_libc("EINVAL")?;
1542 this.set_last_error(einval)?;
1546 this.handle_not_found()?
1551 fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1552 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1553 // underlying disk to finish writing. In the interest of host compatibility,
1554 // we conservatively implement this with `sync_all`, which
1555 // *does* wait for the disk.
1557 let this = self.eval_context_mut();
1559 let fd = this.read_scalar(fd_op)?.to_i32()?;
1561 // Reject if isolation is enabled.
1562 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1563 this.reject_in_isolation("`fsync`", reject_with)?;
1564 // Set error code as "EBADF" (bad fd)
1565 return this.handle_not_found();
1568 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1569 // FIXME: Support fsync for all FDs
1570 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1571 let io_result = maybe_sync_file(file, *writable, File::sync_all);
1572 this.try_unwrap_io_result(io_result)
1574 this.handle_not_found()
1578 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1579 let this = self.eval_context_mut();
1581 let fd = this.read_scalar(fd_op)?.to_i32()?;
1583 // Reject if isolation is enabled.
1584 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1585 this.reject_in_isolation("`fdatasync`", reject_with)?;
1586 // Set error code as "EBADF" (bad fd)
1587 return this.handle_not_found();
1590 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1591 // FIXME: Support fdatasync for all FDs
1592 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1593 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1594 this.try_unwrap_io_result(io_result)
1596 this.handle_not_found()
1602 fd_op: &OpTy<'tcx, Provenance>,
1603 offset_op: &OpTy<'tcx, Provenance>,
1604 nbytes_op: &OpTy<'tcx, Provenance>,
1605 flags_op: &OpTy<'tcx, Provenance>,
1606 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1607 let this = self.eval_context_mut();
1609 let fd = this.read_scalar(fd_op)?.to_i32()?;
1610 let offset = this.read_scalar(offset_op)?.to_i64()?;
1611 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1612 let flags = this.read_scalar(flags_op)?.to_i32()?;
1614 if offset < 0 || nbytes < 0 {
1615 let einval = this.eval_libc("EINVAL")?;
1616 this.set_last_error(einval)?;
1617 return Ok(Scalar::from_i32(-1));
1619 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1620 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1621 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1622 if flags & allowed_flags != flags {
1623 let einval = this.eval_libc("EINVAL")?;
1624 this.set_last_error(einval)?;
1625 return Ok(Scalar::from_i32(-1));
1628 // Reject if isolation is enabled.
1629 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1630 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1631 // Set error code as "EBADF" (bad fd)
1632 return Ok(Scalar::from_i32(this.handle_not_found()?));
1635 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1636 // FIXME: Support sync_data_range for all FDs
1637 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1638 let io_result = maybe_sync_file(file, *writable, File::sync_data);
1639 Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1641 Ok(Scalar::from_i32(this.handle_not_found()?))
1647 pathname_op: &OpTy<'tcx, Provenance>,
1648 buf_op: &OpTy<'tcx, Provenance>,
1649 bufsize_op: &OpTy<'tcx, Provenance>,
1650 ) -> InterpResult<'tcx, i64> {
1651 let this = self.eval_context_mut();
1653 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1654 let buf = this.read_pointer(buf_op)?;
1655 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1657 // Reject if isolation is enabled.
1658 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1659 this.reject_in_isolation("`readlink`", reject_with)?;
1660 let eacc = this.eval_libc("EACCES")?;
1661 this.set_last_error(eacc)?;
1665 let result = std::fs::read_link(pathname);
1668 // 'readlink' truncates the resolved path if the provided buffer is not large
1669 // enough, and does *not* add a null terminator. That means we cannot use the usual
1670 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1671 let resolved = this.convert_path_separator(
1672 Cow::Borrowed(resolved.as_ref()),
1673 crate::shims::os_str::PathConversion::HostToTarget,
1675 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1676 let bufsize: usize = bufsize.try_into().unwrap();
1677 if path_bytes.len() > bufsize {
1678 path_bytes = &path_bytes[..bufsize]
1680 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1681 Ok(path_bytes.len().try_into().unwrap())
1684 this.set_last_error_from_io_error(e.kind())?;
1690 #[cfg_attr(not(unix), allow(unused))]
1693 miri_fd: &OpTy<'tcx, Provenance>,
1694 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1695 let this = self.eval_context_mut();
1696 // "returns 1 if fd is an open file descriptor referring to a terminal;
1697 // otherwise 0 is returned, and errno is set to indicate the error"
1698 if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1699 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1700 if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1701 return Ok(Scalar::from_i32(1));
1704 // Fallback when the FD was not found or isolation is enabled.
1705 let enotty = this.eval_libc("ENOTTY")?;
1706 this.set_last_error(enotty)?;
1707 Ok(Scalar::from_i32(0))
1712 path_op: &OpTy<'tcx, Provenance>,
1713 processed_path_op: &OpTy<'tcx, Provenance>,
1714 ) -> InterpResult<'tcx, Scalar<Provenance>> {
1715 let this = self.eval_context_mut();
1716 this.assert_target_os_is_unix("realpath");
1718 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1719 let processed_ptr = this.read_pointer(processed_path_op)?;
1721 // Reject if isolation is enabled.
1722 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1723 this.reject_in_isolation("`realpath`", reject_with)?;
1724 let eacc = this.eval_libc("EACCES")?;
1725 this.set_last_error(eacc)?;
1726 return Ok(Scalar::from_machine_usize(0, this));
1729 let result = std::fs::canonicalize(pathname);
1733 .eval_libc_i32("PATH_MAX")?
1735 .expect("PATH_MAX does not fit in u64");
1736 let dest = if this.ptr_is_null(processed_ptr)? {
1737 // POSIX says behavior when passing a null pointer is implementation-defined,
1738 // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1741 // "If resolved_path is specified as NULL, then realpath() uses
1742 // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1743 // the resolved pathname, and returns a pointer to this buffer. The
1744 // caller should deallocate this buffer using free(3)."
1745 // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1746 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1748 let (wrote_path, _) =
1749 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1752 // Note that we do not explicitly handle `FILENAME_MAX`
1753 // (different from `PATH_MAX` above) as it is Linux-specific and
1754 // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1755 let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1756 this.set_last_error(enametoolong)?;
1757 return Ok(Scalar::from_machine_usize(0, this));
1762 Ok(Scalar::from_maybe_pointer(dest, this))
1765 this.set_last_error_from_io_error(e.kind())?;
1766 Ok(Scalar::from_machine_usize(0, this))
1770 fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1771 use rand::seq::SliceRandom;
1773 // POSIX defines the template string.
1774 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1776 let this = self.eval_context_mut();
1777 this.assert_target_os_is_unix("mkstemp");
1779 // POSIX defines the maximum number of attempts before failure.
1781 // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1782 // POSIX says this about `TMP_MAX`:
1783 // * Minimum number of unique filenames generated by `tmpnam()`.
1784 // * Maximum number of times an application can call `tmpnam()` reliably.
1785 // * The value of `TMP_MAX` is at least 25.
1786 // * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1787 // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1788 let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1790 // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1791 // (and the target is unix, so a byte slice is the right representation).
1792 let template_ptr = this.read_pointer(template_op)?;
1793 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1794 let template_bytes = template.as_mut_slice();
1796 // Reject if isolation is enabled.
1797 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1798 this.reject_in_isolation("`mkstemp`", reject_with)?;
1799 let eacc = this.eval_libc("EACCES")?;
1800 this.set_last_error(eacc)?;
1804 // Get the bytes of the suffix we expect in _target_ encoding.
1805 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1807 // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1808 // that represents the expected suffix.
1810 // Now we figure out the index of the slice we expect to contain the suffix.
1811 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1812 let end_pos = template_bytes.len();
1813 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1815 // If we don't find the suffix, it is an error.
1816 if last_six_char_bytes != suffix_bytes {
1817 let einval = this.eval_libc("EINVAL")?;
1818 this.set_last_error(einval)?;
1822 // At this point we know we have 6 ASCII 'X' characters as a suffix.
1824 // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1825 const SUBSTITUTIONS: &[char; 62] = &[
1826 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1827 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1828 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1829 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1832 // The file is opened with specific options, which Rust does not expose in a portable way.
1833 // So we use specific APIs depending on the host OS.
1834 let mut fopts = OpenOptions::new();
1835 fopts.read(true).write(true).create_new(true);
1839 use std::os::unix::fs::OpenOptionsExt;
1841 // Do not allow others to read or modify this file.
1842 fopts.custom_flags(libc::O_EXCL);
1846 use std::os::windows::fs::OpenOptionsExt;
1847 // Do not allow others to read or modify this file.
1848 fopts.share_mode(0);
1851 // If the generated file already exists, we will try again `max_attempts` many times.
1852 for _ in 0..max_attempts {
1853 let rng = this.machine.rng.get_mut();
1855 // Generate a random unique suffix.
1856 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1858 // Replace the template string with the random string.
1859 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1861 // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1862 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1864 // To actually open the file, turn this into a host OsString.
1865 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1867 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1869 let file = fopts.open(possibly_unique);
1873 let fh = &mut this.machine.file_handler;
1874 let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1879 // If the random file already exists, keep trying.
1880 ErrorKind::AlreadyExists => continue,
1881 // Any other errors are returned to the caller.
1883 // "On error, -1 is returned, and errno is set to
1884 // indicate the error"
1885 this.set_last_error_from_io_error(e.kind())?;
1892 // We ran out of attempts to create the file, return an error.
1893 let eexist = this.eval_libc("EEXIST")?;
1894 this.set_last_error(eexist)?;
1899 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1900 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1902 fn extract_sec_and_nsec<'tcx>(
1903 time: std::io::Result<SystemTime>,
1904 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1907 let duration = system_time_to_duration(&time)?;
1908 Ok((duration.as_secs(), duration.subsec_nanos()))
1913 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1915 struct FileMetadata {
1916 mode: Scalar<Provenance>,
1918 created: Option<(u64, u32)>,
1919 accessed: Option<(u64, u32)>,
1920 modified: Option<(u64, u32)>,
1925 ecx: &mut MiriInterpCx<'_, 'tcx>,
1927 follow_symlink: bool,
1928 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1930 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1932 FileMetadata::from_meta(ecx, metadata)
1936 ecx: &mut MiriInterpCx<'_, 'tcx>,
1938 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1939 let option = ecx.machine.file_handler.handles.get(&fd);
1940 let file = match option {
1941 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1942 None => return ecx.handle_not_found().map(|_: i32| None),
1944 let metadata = file.metadata();
1946 FileMetadata::from_meta(ecx, metadata)
1950 ecx: &mut MiriInterpCx<'_, 'tcx>,
1951 metadata: Result<std::fs::Metadata, std::io::Error>,
1952 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1953 let metadata = match metadata {
1954 Ok(metadata) => metadata,
1956 ecx.set_last_error_from_io_error(e.kind())?;
1961 let file_type = metadata.file_type();
1963 let mode_name = if file_type.is_file() {
1965 } else if file_type.is_dir() {
1971 let mode = ecx.eval_libc(mode_name)?;
1973 let size = metadata.len();
1975 let created = extract_sec_and_nsec(metadata.created())?;
1976 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1977 let modified = extract_sec_and_nsec(metadata.modified())?;
1979 // FIXME: Provide more fields using platform specific methods.
1980 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))