2 use std::collections::BTreeMap;
3 use std::convert::{TryFrom, TryInto};
5 read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
7 use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
9 use std::time::SystemTime;
13 use rustc_data_structures::fx::FxHashMap;
15 use rustc_target::abi::{Align, LayoutOf, Size};
18 use helpers::{check_arg_count, immty_from_int_checked, immty_from_uint_checked};
19 use shims::time::system_time_to_duration;
27 trait FileDescriptor: std::fmt::Debug {
28 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle>;
32 communicate_allowed: bool,
34 ) -> InterpResult<'tcx, io::Result<usize>>;
37 communicate_allowed: bool,
39 ) -> InterpResult<'tcx, io::Result<usize>>;
42 communicate_allowed: bool,
44 ) -> InterpResult<'tcx, io::Result<u64>>;
47 _communicate_allowed: bool,
48 ) -> InterpResult<'tcx, io::Result<i32>>;
50 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
53 impl FileDescriptor for FileHandle {
54 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
60 communicate_allowed: bool,
62 ) -> InterpResult<'tcx, io::Result<usize>> {
63 assert!(communicate_allowed, "isolation should have prevented even opening a file");
64 Ok(self.file.read(bytes))
69 communicate_allowed: bool,
71 ) -> InterpResult<'tcx, io::Result<usize>> {
72 assert!(communicate_allowed, "isolation should have prevented even opening a file");
73 Ok(self.file.write(bytes))
78 communicate_allowed: bool,
80 ) -> InterpResult<'tcx, io::Result<u64>> {
81 assert!(communicate_allowed, "isolation should have prevented even opening a file");
82 Ok(self.file.seek(offset))
87 communicate_allowed: bool,
88 ) -> InterpResult<'tcx, io::Result<i32>> {
89 assert!(communicate_allowed, "isolation should have prevented even opening a file");
90 // We sync the file if it was opened in a mode different than read-only.
92 // `File::sync_all` does the checks that are done when closing a file. We do this to
93 // to handle possible errors correctly.
94 let result = self.file.sync_all().map(|_| 0i32);
95 // Now we actually close the file.
97 // And return the result.
100 // We drop the file, this closes it but ignores any errors
101 // produced when closing it. This is done because
102 // `File::sync_all` cannot be done over files like
103 // `/dev/urandom` which are read-only. Check
104 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
105 // for a deeper discussion.
111 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
112 let duplicated = self.file.try_clone()?;
113 Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
117 impl FileDescriptor for io::Stdin {
118 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
119 throw_unsup_format!("stdin cannot be used as FileHandle");
124 communicate_allowed: bool,
126 ) -> InterpResult<'tcx, io::Result<usize>> {
127 if !communicate_allowed {
128 // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
129 helpers::isolation_abort_error("`read` from stdin")?;
131 Ok(Read::read(self, bytes))
136 _communicate_allowed: bool,
138 ) -> InterpResult<'tcx, io::Result<usize>> {
139 throw_unsup_format!("cannot write to stdin");
144 _communicate_allowed: bool,
146 ) -> InterpResult<'tcx, io::Result<u64>> {
147 throw_unsup_format!("cannot seek on stdin");
152 _communicate_allowed: bool,
153 ) -> InterpResult<'tcx, io::Result<i32>> {
154 throw_unsup_format!("stdin cannot be closed");
157 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
158 Ok(Box::new(io::stdin()))
162 impl FileDescriptor for io::Stdout {
163 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
164 throw_unsup_format!("stdout cannot be used as FileHandle");
169 _communicate_allowed: bool,
171 ) -> InterpResult<'tcx, io::Result<usize>> {
172 throw_unsup_format!("cannot read from stdout");
177 _communicate_allowed: bool,
179 ) -> InterpResult<'tcx, io::Result<usize>> {
180 // We allow writing to stderr even with isolation enabled.
181 let result = Write::write(self, bytes);
182 // Stdout is buffered, flush to make sure it appears on the
183 // screen. This is the write() syscall of the interpreted
184 // program, we want it to correspond to a write() syscall on
185 // the host -- there is no good in adding extra buffering
187 io::stdout().flush().unwrap();
194 _communicate_allowed: bool,
196 ) -> InterpResult<'tcx, io::Result<u64>> {
197 throw_unsup_format!("cannot seek on stdout");
202 _communicate_allowed: bool,
203 ) -> InterpResult<'tcx, io::Result<i32>> {
204 throw_unsup_format!("stdout cannot be closed");
207 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
208 Ok(Box::new(io::stdout()))
212 impl FileDescriptor for io::Stderr {
213 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
214 throw_unsup_format!("stderr cannot be used as FileHandle");
219 _communicate_allowed: bool,
221 ) -> InterpResult<'tcx, io::Result<usize>> {
222 throw_unsup_format!("cannot read from stderr");
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(self, bytes))
237 _communicate_allowed: bool,
239 ) -> InterpResult<'tcx, io::Result<u64>> {
240 throw_unsup_format!("cannot seek on stderr");
245 _communicate_allowed: bool,
246 ) -> InterpResult<'tcx, io::Result<i32>> {
247 throw_unsup_format!("stderr cannot be closed");
250 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
251 Ok(Box::new(io::stderr()))
256 pub struct FileHandler {
257 handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
260 impl<'tcx> Default for FileHandler {
261 fn default() -> Self {
262 let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
263 handles.insert(0i32, Box::new(io::stdin()));
264 handles.insert(1i32, Box::new(io::stdout()));
265 handles.insert(2i32, Box::new(io::stderr()));
266 FileHandler { handles }
270 impl<'tcx> FileHandler {
271 fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
272 self.insert_fd_with_min_fd(file_handle, 0)
275 fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
276 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
277 // between used FDs, the find_map combinator will return it. If the first such unused FD
278 // is after all other used FDs, the find_map combinator will return None, and we will use
279 // the FD following the greatest FD thus far.
280 let candidate_new_fd =
281 self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
283 // There was a gap in the fds stored, return the first unused one
284 // (note that this relies on BTreeMap iterating in key order)
287 // This fd is used, keep going
291 let new_fd = candidate_new_fd.unwrap_or_else(|| {
292 // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
293 // maximum fd in the map
296 .map(|(fd, _)| fd.checked_add(1).unwrap())
300 self.handles.try_insert(new_fd, file_handle).unwrap();
305 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
306 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
307 fn macos_stat_write_buf(
309 metadata: FileMetadata,
310 buf_op: &OpTy<'tcx, Tag>,
311 ) -> InterpResult<'tcx, i32> {
312 let this = self.eval_context_mut();
314 let mode: u16 = metadata.mode.to_u16()?;
316 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
317 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
318 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
320 let dev_t_layout = this.libc_ty_layout("dev_t")?;
321 let mode_t_layout = this.libc_ty_layout("mode_t")?;
322 let nlink_t_layout = this.libc_ty_layout("nlink_t")?;
323 let ino_t_layout = this.libc_ty_layout("ino_t")?;
324 let uid_t_layout = this.libc_ty_layout("uid_t")?;
325 let gid_t_layout = this.libc_ty_layout("gid_t")?;
326 let time_t_layout = this.libc_ty_layout("time_t")?;
327 let long_layout = this.libc_ty_layout("c_long")?;
328 let off_t_layout = this.libc_ty_layout("off_t")?;
329 let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?;
330 let blksize_t_layout = this.libc_ty_layout("blksize_t")?;
331 let uint32_t_layout = this.libc_ty_layout("uint32_t")?;
334 immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
335 immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
336 immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
337 immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
338 immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
339 immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
340 immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
341 immty_from_uint_checked(0u128, uint32_t_layout)?, // padding
342 immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
343 immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
344 immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
345 immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
346 immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
347 immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec
348 immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
349 immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
350 immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
351 immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
352 immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
353 immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
354 immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
357 let buf = this.deref_operand(buf_op)?;
358 this.write_packed_immediates(&buf, &imms)?;
363 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
364 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
365 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
366 /// types (like `read`, that returns an `i64`).
367 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
368 let this = self.eval_context_mut();
369 let ebadf = this.eval_libc("EBADF")?;
370 this.set_last_error(ebadf)?;
374 fn file_type_to_d_type(
376 file_type: std::io::Result<FileType>,
377 ) -> InterpResult<'tcx, i32> {
378 let this = self.eval_context_mut();
381 if file_type.is_dir() {
382 Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
383 } else if file_type.is_file() {
384 Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
385 } else if file_type.is_symlink() {
386 Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
388 // Certain file types are only supported when the host is a Unix system.
389 // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
390 // DT_UNKNOWN sooner.
394 use std::os::unix::fs::FileTypeExt;
395 if file_type.is_block_device() {
396 Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
397 } else if file_type.is_char_device() {
398 Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
399 } else if file_type.is_fifo() {
400 Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
401 } else if file_type.is_socket() {
402 Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
404 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
408 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
412 return match e.raw_os_error() {
413 Some(error) => Ok(error),
416 "the error {} couldn't be converted to a return value",
425 pub struct DirHandler {
426 /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
429 /// When opendir is called, a directory iterator is created on the host for the target
430 /// directory, and an entry is stored in this hash map, indexed by an ID which represents
431 /// the directory stream. When readdir is called, the directory stream ID is used to look up
432 /// the corresponding ReadDir iterator from this map, and information from the next
433 /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
435 streams: FxHashMap<u64, ReadDir>,
436 /// ID number to be used by the next call to opendir
441 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
442 let id = self.next_id;
444 self.streams.try_insert(id, read_dir).unwrap();
449 impl Default for DirHandler {
450 fn default() -> DirHandler {
452 streams: FxHashMap::default(),
453 // Skip 0 as an ID, because it looks like a null pointer to libc
462 operation: fn(&File) -> std::io::Result<()>,
463 ) -> std::io::Result<i32> {
464 if !writable && cfg!(windows) {
465 // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
466 // for writing. (FlushFileBuffers requires that the file handle have the
467 // GENERIC_WRITE right)
470 let result = operation(file);
475 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
476 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
479 path_op: &OpTy<'tcx, Tag>,
480 flag_op: &OpTy<'tcx, Tag>,
481 mode_op: &OpTy<'tcx, Tag>,
482 ) -> InterpResult<'tcx, i32> {
483 let this = self.eval_context_mut();
485 let flag = this.read_scalar(flag_op)?.to_i32()?;
487 // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
488 // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
489 // (see https://github.com/rust-lang/rust/issues/71915).
490 let mode = this.read_scalar(mode_op)?.to_u32()?;
492 throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
495 let mut options = OpenOptions::new();
497 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
498 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
499 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
500 // The first two bits of the flag correspond to the access mode in linux, macOS and
501 // windows. We need to check that in fact the access mode flags for the current target
502 // only use these two bits, otherwise we are in an unsupported target and should error.
503 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
504 throw_unsup_format!("access mode flags on this target are unsupported");
506 let mut writable = true;
508 // Now we check the access mode
509 let access_mode = flag & 0b11;
511 if access_mode == o_rdonly {
514 } else if access_mode == o_wronly {
516 } else if access_mode == o_rdwr {
517 options.read(true).write(true);
519 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
521 // We need to check that there aren't unsupported options in `flag`. For this we try to
522 // reproduce the content of `flag` in the `mirror` variable using only the supported
524 let mut mirror = access_mode;
526 let o_append = this.eval_libc_i32("O_APPEND")?;
527 if flag & o_append != 0 {
528 options.append(true);
531 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
532 if flag & o_trunc != 0 {
533 options.truncate(true);
536 let o_creat = this.eval_libc_i32("O_CREAT")?;
537 if flag & o_creat != 0 {
540 let o_excl = this.eval_libc_i32("O_EXCL")?;
541 if flag & o_excl != 0 {
543 options.create_new(true);
545 options.create(true);
548 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
549 if flag & o_cloexec != 0 {
550 // We do not need to do anything for this flag because `std` already sets it.
551 // (Technically we do not support *not* setting this flag, but we ignore that.)
554 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
555 // then we throw an error.
557 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
560 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
562 // Reject if isolation is enabled.
563 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
564 this.reject_in_isolation("`open`", reject_with)?;
565 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
569 let fd = options.open(&path).map(|file| {
570 let fh = &mut this.machine.file_handler;
571 fh.insert_fd(Box::new(FileHandle { file, writable }))
574 this.try_unwrap_io_result(fd)
577 fn fcntl(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
578 let this = self.eval_context_mut();
582 "incorrect number of arguments for fcntl: got {}, expected at least 2",
586 let fd = this.read_scalar(&args[0])?.to_i32()?;
587 let cmd = this.read_scalar(&args[1])?.to_i32()?;
589 // Reject if isolation is enabled.
590 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
591 this.reject_in_isolation("`fcntl`", reject_with)?;
592 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
596 // We only support getting the flags for a descriptor.
597 if cmd == this.eval_libc_i32("F_GETFD")? {
598 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
599 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
600 // always sets this flag when opening a file. However we still need to check that the
601 // file itself is open.
602 let &[_, _] = check_arg_count(args)?;
603 if this.machine.file_handler.handles.contains_key(&fd) {
604 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
606 this.handle_not_found()
608 } else if cmd == this.eval_libc_i32("F_DUPFD")?
609 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
611 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
612 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
613 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
614 // thus they can share the same implementation here.
615 let &[_, _, ref start] = check_arg_count(args)?;
616 let start = this.read_scalar(start)?.to_i32()?;
618 let fh = &mut this.machine.file_handler;
620 match fh.handles.get_mut(&fd) {
621 Some(file_descriptor) => {
622 let dup_result = file_descriptor.dup();
624 Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
626 this.set_last_error_from_io_error(e.kind())?;
631 None => return this.handle_not_found(),
633 } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
634 let &[_, _] = check_arg_count(args)?;
635 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
636 // FIXME: Support fullfsync for all FDs
637 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
638 let io_result = maybe_sync_file(&file, *writable, File::sync_all);
639 this.try_unwrap_io_result(io_result)
641 this.handle_not_found()
644 throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
648 fn close(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
649 let this = self.eval_context_mut();
651 let fd = this.read_scalar(fd_op)?.to_i32()?;
653 if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
654 let result = file_descriptor.close(this.machine.communicate())?;
655 this.try_unwrap_io_result(result)
657 this.handle_not_found()
661 fn read(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
662 let this = self.eval_context_mut();
664 // Isolation check is done via `FileDescriptor` trait.
666 trace!("Reading from FD {}, size {}", fd, count);
668 // Check that the *entire* buffer is actually valid memory.
669 this.memory.check_ptr_access_align(
671 Size::from_bytes(count),
673 CheckInAllocMsg::MemoryAccessTest,
676 // We cap the number of read bytes to the largest value that we are able to fit in both the
677 // host's and target's `isize`. This saves us from having to handle overflows later.
678 let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
679 let communicate = this.machine.communicate();
681 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
682 trace!("read: FD mapped to {:?}", file_descriptor);
683 // We want to read at most `count` bytes. We are sure that `count` is not negative
684 // because it was a target's `usize`. Also we are sure that its smaller than
685 // `usize::MAX` because it is a host's `isize`.
686 let mut bytes = vec![0; count as usize];
687 // `File::read` never returns a value larger than `count`,
688 // so this cannot fail.
690 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
694 // If reading to `bytes` did not fail, we write those bytes to the buffer.
695 this.memory.write_bytes(buf, bytes)?;
699 this.set_last_error_from_io_error(e.kind())?;
704 trace!("read: FD not found");
705 this.handle_not_found()
709 fn write(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
710 let this = self.eval_context_mut();
712 // Isolation check is done via `FileDescriptor` trait.
714 // Check that the *entire* buffer is actually valid memory.
715 this.memory.check_ptr_access_align(
717 Size::from_bytes(count),
719 CheckInAllocMsg::MemoryAccessTest,
722 // We cap the number of written bytes to the largest value that we are able to fit in both the
723 // host's and target's `isize`. This saves us from having to handle overflows later.
724 let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
725 let communicate = this.machine.communicate();
727 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
728 let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
730 file_descriptor.write(communicate, &bytes)?.map(|c| i64::try_from(c).unwrap());
731 this.try_unwrap_io_result(result)
733 this.handle_not_found()
739 fd_op: &OpTy<'tcx, Tag>,
740 offset_op: &OpTy<'tcx, Tag>,
741 whence_op: &OpTy<'tcx, Tag>,
742 ) -> InterpResult<'tcx, i64> {
743 let this = self.eval_context_mut();
745 // Isolation check is done via `FileDescriptor` trait.
747 let fd = this.read_scalar(fd_op)?.to_i32()?;
748 let offset = this.read_scalar(offset_op)?.to_i64()?;
749 let whence = this.read_scalar(whence_op)?.to_i32()?;
751 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
752 SeekFrom::Start(u64::try_from(offset).unwrap())
753 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
754 SeekFrom::Current(offset)
755 } else if whence == this.eval_libc_i32("SEEK_END")? {
756 SeekFrom::End(offset)
758 let einval = this.eval_libc("EINVAL")?;
759 this.set_last_error(einval)?;
763 let communicate = this.machine.communicate();
764 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
765 let result = file_descriptor
766 .seek(communicate, seek_from)?
767 .map(|offset| i64::try_from(offset).unwrap());
768 this.try_unwrap_io_result(result)
770 this.handle_not_found()
774 fn unlink(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
775 let this = self.eval_context_mut();
777 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
779 // Reject if isolation is enabled.
780 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
781 this.reject_in_isolation("`unlink`", reject_with)?;
782 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
786 let result = remove_file(path).map(|_| 0);
787 this.try_unwrap_io_result(result)
792 target_op: &OpTy<'tcx, Tag>,
793 linkpath_op: &OpTy<'tcx, Tag>,
794 ) -> InterpResult<'tcx, i32> {
796 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
797 std::os::unix::fs::symlink(src, dst)
801 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
802 use std::os::windows::fs;
803 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
806 let this = self.eval_context_mut();
807 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
808 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
810 // Reject if isolation is enabled.
811 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
812 this.reject_in_isolation("`symlink`", reject_with)?;
813 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
817 let result = create_link(&target, &linkpath).map(|_| 0);
818 this.try_unwrap_io_result(result)
823 path_op: &OpTy<'tcx, Tag>,
824 buf_op: &OpTy<'tcx, Tag>,
825 ) -> InterpResult<'tcx, i32> {
826 let this = self.eval_context_mut();
827 this.assert_target_os("macos", "stat");
829 let path_scalar = this.read_pointer(path_op)?;
830 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
832 // Reject if isolation is enabled.
833 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
834 this.reject_in_isolation("`stat`", reject_with)?;
835 let eacc = this.eval_libc("EACCES")?;
836 this.set_last_error(eacc)?;
840 // `stat` always follows symlinks.
841 let metadata = match FileMetadata::from_path(this, &path, true)? {
842 Some(metadata) => metadata,
843 None => return Ok(-1),
846 this.macos_stat_write_buf(metadata, buf_op)
849 // `lstat` is used to get symlink metadata.
852 path_op: &OpTy<'tcx, Tag>,
853 buf_op: &OpTy<'tcx, Tag>,
854 ) -> InterpResult<'tcx, i32> {
855 let this = self.eval_context_mut();
856 this.assert_target_os("macos", "lstat");
858 let path_scalar = this.read_pointer(path_op)?;
859 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
861 // Reject if isolation is enabled.
862 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
863 this.reject_in_isolation("`lstat`", reject_with)?;
864 let eacc = this.eval_libc("EACCES")?;
865 this.set_last_error(eacc)?;
869 let metadata = match FileMetadata::from_path(this, &path, false)? {
870 Some(metadata) => metadata,
871 None => return Ok(-1),
874 this.macos_stat_write_buf(metadata, buf_op)
879 fd_op: &OpTy<'tcx, Tag>,
880 buf_op: &OpTy<'tcx, Tag>,
881 ) -> InterpResult<'tcx, i32> {
882 let this = self.eval_context_mut();
884 this.assert_target_os("macos", "fstat");
886 let fd = this.read_scalar(fd_op)?.to_i32()?;
888 // Reject if isolation is enabled.
889 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
890 this.reject_in_isolation("`fstat`", reject_with)?;
891 // Set error code as "EBADF" (bad fd)
892 return this.handle_not_found();
895 let metadata = match FileMetadata::from_fd(this, fd)? {
896 Some(metadata) => metadata,
897 None => return Ok(-1),
899 this.macos_stat_write_buf(metadata, buf_op)
904 dirfd_op: &OpTy<'tcx, Tag>, // Should be an `int`
905 pathname_op: &OpTy<'tcx, Tag>, // Should be a `const char *`
906 flags_op: &OpTy<'tcx, Tag>, // Should be an `int`
907 _mask_op: &OpTy<'tcx, Tag>, // Should be an `unsigned int`
908 statxbuf_op: &OpTy<'tcx, Tag>, // Should be a `struct statx *`
909 ) -> InterpResult<'tcx, i32> {
910 let this = self.eval_context_mut();
912 this.assert_target_os("linux", "statx");
914 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
915 let pathname_ptr = this.read_pointer(pathname_op)?;
917 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
918 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
919 let efault = this.eval_libc("EFAULT")?;
920 this.set_last_error(efault)?;
924 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
925 // proper `MemPlace` and then write the results of this function to it. However, the
926 // `syscall` function is untyped. This means that all the `statx` parameters are provided
927 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
928 // `statxbuf_op` by using the `libc::statx` struct type.
929 let statxbuf_place = {
930 // FIXME: This long path is required because `libc::statx` is an struct and also a
931 // function and `resolve_path` is returning the latter.
933 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])
934 .ty(*this.tcx, ty::ParamEnv::reveal_all());
935 let statx_layout = this.layout_of(statx_ty)?;
936 MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
939 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
940 // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
941 let flags = this.read_scalar(flags_op)?.to_i32()?;
942 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
943 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
945 // * interpreting `path` as an absolute directory,
946 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
947 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
949 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
950 // found this error, please open an issue reporting it.
951 if !(path.is_absolute()
952 || dirfd == this.eval_libc_i32("AT_FDCWD")?
953 || (path.as_os_str().is_empty() && empty_path_flag))
956 "using statx is only supported with absolute paths, relative paths with the file \
957 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
962 // Reject if isolation is enabled.
963 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
964 this.reject_in_isolation("`statx`", reject_with)?;
965 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
966 // since `path` is provided, either absolute or
967 // relative to CWD, `EACCES` is the most relevant.
968 this.eval_libc("EACCES")?
970 // `dirfd` is set to target file, and `path` is empty
971 // (or we would have hit the `throw_unsup_format`
972 // above). `EACCES` would violate the spec.
973 assert!(empty_path_flag);
974 this.eval_libc("EBADF")?
976 this.set_last_error(ecode)?;
980 // the `_mask_op` paramter specifies the file information that the caller requested.
981 // However `statx` is allowed to return information that was not requested or to not
982 // return information that was requested. This `mask` represents the information we can
983 // actually provide for any target.
985 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
987 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
989 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
991 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
992 // represented by dirfd, whether it's a directory or otherwise.
993 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
994 FileMetadata::from_fd(this, dirfd)?
996 FileMetadata::from_path(this, &path, follow_symlink)?
998 let metadata = match metadata {
999 Some(metadata) => metadata,
1000 None => return Ok(-1),
1003 // The `mode` field specifies the type of the file and the permissions over the file for
1004 // the owner, its group and other users. Given that we can only provide the file type
1005 // without using platform specific methods, we only set the bits corresponding to the file
1006 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1007 let mode: u16 = metadata
1011 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1013 // We need to set the corresponding bits of `mask` if the access, creation and modification
1014 // times were available. Otherwise we let them be zero.
1015 let (access_sec, access_nsec) = metadata
1018 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1019 InterpResult::Ok(tup)
1021 .unwrap_or(Ok((0, 0)))?;
1023 let (created_sec, created_nsec) = metadata
1026 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1027 InterpResult::Ok(tup)
1029 .unwrap_or(Ok((0, 0)))?;
1031 let (modified_sec, modified_nsec) = metadata
1034 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1035 InterpResult::Ok(tup)
1037 .unwrap_or(Ok((0, 0)))?;
1039 let __u32_layout = this.libc_ty_layout("__u32")?;
1040 let __u64_layout = this.libc_ty_layout("__u64")?;
1041 let __u16_layout = this.libc_ty_layout("__u16")?;
1043 // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
1044 // zero for the unavailable fields.
1046 immty_from_uint_checked(mask, __u32_layout)?, // stx_mask
1047 immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize
1048 immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
1049 immty_from_uint_checked(0u128, __u32_layout)?, // stx_nlink
1050 immty_from_uint_checked(0u128, __u32_layout)?, // stx_uid
1051 immty_from_uint_checked(0u128, __u32_layout)?, // stx_gid
1052 immty_from_uint_checked(mode, __u16_layout)?, // stx_mode
1053 immty_from_uint_checked(0u128, __u16_layout)?, // statx padding
1054 immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino
1055 immty_from_uint_checked(metadata.size, __u64_layout)?, // stx_size
1056 immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks
1057 immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
1058 immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec
1059 immty_from_uint_checked(access_nsec, __u32_layout)?, // stx_atime.tv_nsec
1060 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1061 immty_from_uint_checked(created_sec, __u64_layout)?, // stx_btime.tv_sec
1062 immty_from_uint_checked(created_nsec, __u32_layout)?, // stx_btime.tv_nsec
1063 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1064 immty_from_uint_checked(0u128, __u64_layout)?, // stx_ctime.tv_sec
1065 immty_from_uint_checked(0u128, __u32_layout)?, // stx_ctime.tv_nsec
1066 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1067 immty_from_uint_checked(modified_sec, __u64_layout)?, // stx_mtime.tv_sec
1068 immty_from_uint_checked(modified_nsec, __u32_layout)?, // stx_mtime.tv_nsec
1069 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1070 immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_major
1071 immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_minor
1072 immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_major
1073 immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
1076 this.write_packed_immediates(&statxbuf_place, &imms)?;
1083 oldpath_op: &OpTy<'tcx, Tag>,
1084 newpath_op: &OpTy<'tcx, Tag>,
1085 ) -> InterpResult<'tcx, i32> {
1086 let this = self.eval_context_mut();
1088 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1089 let newpath_ptr = this.read_pointer(newpath_op)?;
1091 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1092 let efault = this.eval_libc("EFAULT")?;
1093 this.set_last_error(efault)?;
1097 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1098 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1100 // Reject if isolation is enabled.
1101 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1102 this.reject_in_isolation("`rename`", reject_with)?;
1103 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1107 let result = rename(oldpath, newpath).map(|_| 0);
1109 this.try_unwrap_io_result(result)
1114 path_op: &OpTy<'tcx, Tag>,
1115 mode_op: &OpTy<'tcx, Tag>,
1116 ) -> InterpResult<'tcx, i32> {
1117 let this = self.eval_context_mut();
1119 #[cfg_attr(not(unix), allow(unused_variables))]
1120 let mode = if this.tcx.sess.target.os == "macos" {
1121 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1123 this.read_scalar(mode_op)?.to_u32()?
1126 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1128 // Reject if isolation is enabled.
1129 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1130 this.reject_in_isolation("`mkdir`", reject_with)?;
1131 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1135 #[cfg_attr(not(unix), allow(unused_mut))]
1136 let mut builder = DirBuilder::new();
1138 // If the host supports it, forward on the mode of the directory
1139 // (i.e. permission bits and the sticky bit)
1142 use std::os::unix::fs::DirBuilderExt;
1143 builder.mode(mode.into());
1146 let result = builder.create(path).map(|_| 0i32);
1148 this.try_unwrap_io_result(result)
1151 fn rmdir(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1152 let this = self.eval_context_mut();
1154 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1156 // Reject if isolation is enabled.
1157 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1158 this.reject_in_isolation("`rmdir`", reject_with)?;
1159 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1163 let result = remove_dir(path).map(|_| 0i32);
1165 this.try_unwrap_io_result(result)
1168 fn opendir(&mut self, name_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
1169 let this = self.eval_context_mut();
1171 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1173 // Reject if isolation is enabled.
1174 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1175 this.reject_in_isolation("`opendir`", reject_with)?;
1176 let eacc = this.eval_libc("EACCES")?;
1177 this.set_last_error(eacc)?;
1178 return Ok(Scalar::null_ptr(this));
1181 let result = read_dir(name);
1185 let id = this.machine.dir_handler.insert_new(dir_iter);
1187 // The libc API for opendir says that this method returns a pointer to an opaque
1188 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1190 Ok(Scalar::from_machine_usize(id, this))
1193 this.set_last_error_from_io_error(e.kind())?;
1194 Ok(Scalar::null_ptr(this))
1199 fn linux_readdir64_r(
1201 dirp_op: &OpTy<'tcx, Tag>,
1202 entry_op: &OpTy<'tcx, Tag>,
1203 result_op: &OpTy<'tcx, Tag>,
1204 ) -> InterpResult<'tcx, i32> {
1205 let this = self.eval_context_mut();
1207 this.assert_target_os("linux", "readdir64_r");
1209 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1211 // Reject if isolation is enabled.
1212 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1213 this.reject_in_isolation("`readdir64_r`", reject_with)?;
1214 // Set error code as "EBADF" (bad fd)
1215 return this.handle_not_found();
1218 let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1219 err_unsup_format!("the DIR pointer passed to readdir64_r did not come from opendir")
1221 match dir_iter.next() {
1222 Some(Ok(dir_entry)) => {
1223 // Write into entry, write pointer to result, return 0 on success.
1224 // The name is written with write_os_str_to_c_str, while the rest of the
1225 // dirent64 struct is written using write_packed_immediates.
1228 // pub struct dirent64 {
1229 // pub d_ino: ino64_t,
1230 // pub d_off: off64_t,
1231 // pub d_reclen: c_ushort,
1232 // pub d_type: c_uchar,
1233 // pub d_name: [c_char; 256],
1236 let entry_place = this.deref_operand(entry_op)?;
1237 let name_place = this.mplace_field(&entry_place, 4)?;
1239 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1240 let (name_fits, _) = this.write_os_str_to_c_str(
1243 name_place.layout.size.bytes(),
1246 throw_unsup_format!(
1247 "a directory entry had a name too large to fit in libc::dirent64"
1251 let entry_place = this.deref_operand(entry_op)?;
1252 let ino64_t_layout = this.libc_ty_layout("ino64_t")?;
1253 let off64_t_layout = this.libc_ty_layout("off64_t")?;
1254 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
1255 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
1257 // If the host is a Unix system, fill in the inode number with its real value.
1258 // If not, use 0 as a fallback value.
1260 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1264 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1267 immty_from_uint_checked(ino, ino64_t_layout)?, // d_ino
1268 immty_from_uint_checked(0u128, off64_t_layout)?, // d_off
1269 immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
1270 immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
1272 this.write_packed_immediates(&entry_place, &imms)?;
1274 let result_place = this.deref_operand(result_op)?;
1275 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1280 // end of stream: return 0, assign *result=NULL
1281 this.write_null(&this.deref_operand(result_op)?.into())?;
1285 match e.raw_os_error() {
1286 // return positive error number on error
1287 Some(error) => Ok(error),
1289 throw_unsup_format!(
1290 "the error {} couldn't be converted to a return value",
1300 dirp_op: &OpTy<'tcx, Tag>,
1301 entry_op: &OpTy<'tcx, Tag>,
1302 result_op: &OpTy<'tcx, Tag>,
1303 ) -> InterpResult<'tcx, i32> {
1304 let this = self.eval_context_mut();
1306 this.assert_target_os("macos", "readdir_r");
1308 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1310 // Reject if isolation is enabled.
1311 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1312 this.reject_in_isolation("`readdir_r`", reject_with)?;
1313 // Set error code as "EBADF" (bad fd)
1314 return this.handle_not_found();
1317 let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1318 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1320 match dir_iter.next() {
1321 Some(Ok(dir_entry)) => {
1322 // Write into entry, write pointer to result, return 0 on success.
1323 // The name is written with write_os_str_to_c_str, while the rest of the
1324 // dirent struct is written using write_packed_Immediates.
1327 // pub struct dirent {
1329 // pub d_seekoff: u64,
1330 // pub d_reclen: u16,
1331 // pub d_namlen: u16,
1333 // pub d_name: [c_char; 1024],
1336 let entry_place = this.deref_operand(entry_op)?;
1337 let name_place = this.mplace_field(&entry_place, 5)?;
1339 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1340 let (name_fits, file_name_len) = this.write_os_str_to_c_str(
1343 name_place.layout.size.bytes(),
1346 throw_unsup_format!(
1347 "a directory entry had a name too large to fit in libc::dirent"
1351 let entry_place = this.deref_operand(entry_op)?;
1352 let ino_t_layout = this.libc_ty_layout("ino_t")?;
1353 let off_t_layout = this.libc_ty_layout("off_t")?;
1354 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
1355 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
1357 // If the host is a Unix system, fill in the inode number with its real value.
1358 // If not, use 0 as a fallback value.
1360 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1364 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1367 immty_from_uint_checked(ino, ino_t_layout)?, // d_ino
1368 immty_from_uint_checked(0u128, off_t_layout)?, // d_seekoff
1369 immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
1370 immty_from_uint_checked(file_name_len, c_ushort_layout)?, // d_namlen
1371 immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
1373 this.write_packed_immediates(&entry_place, &imms)?;
1375 let result_place = this.deref_operand(result_op)?;
1376 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1381 // end of stream: return 0, assign *result=NULL
1382 this.write_null(&this.deref_operand(result_op)?.into())?;
1386 match e.raw_os_error() {
1387 // return positive error number on error
1388 Some(error) => Ok(error),
1390 throw_unsup_format!(
1391 "the error {} couldn't be converted to a return value",
1399 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1400 let this = self.eval_context_mut();
1402 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1404 // Reject if isolation is enabled.
1405 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1406 this.reject_in_isolation("`closedir`", reject_with)?;
1407 // Set error code as "EBADF" (bad fd)
1408 return this.handle_not_found();
1411 if let Some(dir_iter) = this.machine.dir_handler.streams.remove(&dirp) {
1415 this.handle_not_found()
1421 fd_op: &OpTy<'tcx, Tag>,
1422 length_op: &OpTy<'tcx, Tag>,
1423 ) -> InterpResult<'tcx, i32> {
1424 let this = self.eval_context_mut();
1426 let fd = this.read_scalar(fd_op)?.to_i32()?;
1427 let length = this.read_scalar(length_op)?.to_i64()?;
1429 // Reject if isolation is enabled.
1430 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1431 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1432 // Set error code as "EBADF" (bad fd)
1433 return this.handle_not_found();
1436 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1437 // FIXME: Support ftruncate64 for all FDs
1438 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1440 if let Ok(length) = length.try_into() {
1441 let result = file.set_len(length);
1442 this.try_unwrap_io_result(result.map(|_| 0i32))
1444 let einval = this.eval_libc("EINVAL")?;
1445 this.set_last_error(einval)?;
1449 // The file is not writable
1450 let einval = this.eval_libc("EINVAL")?;
1451 this.set_last_error(einval)?;
1455 this.handle_not_found()
1459 fn fsync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1460 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1461 // underlying disk to finish writing. In the interest of host compatibility,
1462 // we conservatively implement this with `sync_all`, which
1463 // *does* wait for the disk.
1465 let this = self.eval_context_mut();
1467 let fd = this.read_scalar(fd_op)?.to_i32()?;
1469 // Reject if isolation is enabled.
1470 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1471 this.reject_in_isolation("`fsync`", reject_with)?;
1472 // Set error code as "EBADF" (bad fd)
1473 return this.handle_not_found();
1476 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1477 // FIXME: Support fsync for all FDs
1478 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1479 let io_result = maybe_sync_file(&file, *writable, File::sync_all);
1480 this.try_unwrap_io_result(io_result)
1482 this.handle_not_found()
1486 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1487 let this = self.eval_context_mut();
1489 let fd = this.read_scalar(fd_op)?.to_i32()?;
1491 // Reject if isolation is enabled.
1492 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1493 this.reject_in_isolation("`fdatasync`", reject_with)?;
1494 // Set error code as "EBADF" (bad fd)
1495 return this.handle_not_found();
1498 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1499 // FIXME: Support fdatasync for all FDs
1500 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1501 let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1502 this.try_unwrap_io_result(io_result)
1504 this.handle_not_found()
1510 fd_op: &OpTy<'tcx, Tag>,
1511 offset_op: &OpTy<'tcx, Tag>,
1512 nbytes_op: &OpTy<'tcx, Tag>,
1513 flags_op: &OpTy<'tcx, Tag>,
1514 ) -> InterpResult<'tcx, i32> {
1515 let this = self.eval_context_mut();
1517 let fd = this.read_scalar(fd_op)?.to_i32()?;
1518 let offset = this.read_scalar(offset_op)?.to_i64()?;
1519 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1520 let flags = this.read_scalar(flags_op)?.to_i32()?;
1522 if offset < 0 || nbytes < 0 {
1523 let einval = this.eval_libc("EINVAL")?;
1524 this.set_last_error(einval)?;
1527 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1528 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1529 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1530 if flags & allowed_flags != flags {
1531 let einval = this.eval_libc("EINVAL")?;
1532 this.set_last_error(einval)?;
1536 // Reject if isolation is enabled.
1537 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1538 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1539 // Set error code as "EBADF" (bad fd)
1540 return this.handle_not_found();
1543 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1544 // FIXME: Support sync_data_range for all FDs
1545 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1546 let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1547 this.try_unwrap_io_result(io_result)
1549 this.handle_not_found()
1555 pathname_op: &OpTy<'tcx, Tag>,
1556 buf_op: &OpTy<'tcx, Tag>,
1557 bufsize_op: &OpTy<'tcx, Tag>,
1558 ) -> InterpResult<'tcx, i64> {
1559 let this = self.eval_context_mut();
1561 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1562 let buf = this.read_pointer(buf_op)?;
1563 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1565 // Reject if isolation is enabled.
1566 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1567 this.reject_in_isolation("`readlink`", reject_with)?;
1568 let eacc = this.eval_libc("EACCES")?;
1569 this.set_last_error(eacc)?;
1573 let result = std::fs::read_link(pathname);
1576 let resolved = this.convert_path_separator(
1577 Cow::Borrowed(resolved.as_ref()),
1578 crate::shims::os_str::PathConversion::HostToTarget,
1580 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1581 let bufsize: usize = bufsize.try_into().unwrap();
1582 if path_bytes.len() > bufsize {
1583 path_bytes = &path_bytes[..bufsize]
1585 // 'readlink' truncates the resolved path if
1586 // the provided buffer is not large enough.
1587 this.memory.write_bytes(buf, path_bytes.iter().copied())?;
1588 Ok(path_bytes.len().try_into().unwrap())
1591 this.set_last_error_from_io_error(e.kind())?;
1598 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1599 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1601 fn extract_sec_and_nsec<'tcx>(
1602 time: std::io::Result<SystemTime>,
1603 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1606 let duration = system_time_to_duration(&time)?;
1607 Ok((duration.as_secs(), duration.subsec_nanos()))
1612 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1614 struct FileMetadata {
1617 created: Option<(u64, u32)>,
1618 accessed: Option<(u64, u32)>,
1619 modified: Option<(u64, u32)>,
1623 fn from_path<'tcx, 'mir>(
1624 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1626 follow_symlink: bool,
1627 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1629 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1631 FileMetadata::from_meta(ecx, metadata)
1634 fn from_fd<'tcx, 'mir>(
1635 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1637 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1638 let option = ecx.machine.file_handler.handles.get(&fd);
1639 let file = match option {
1640 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1641 None => return ecx.handle_not_found().map(|_: i32| None),
1643 let metadata = file.metadata();
1645 FileMetadata::from_meta(ecx, metadata)
1648 fn from_meta<'tcx, 'mir>(
1649 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1650 metadata: Result<std::fs::Metadata, std::io::Error>,
1651 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1652 let metadata = match metadata {
1653 Ok(metadata) => metadata,
1655 ecx.set_last_error_from_io_error(e.kind())?;
1660 let file_type = metadata.file_type();
1662 let mode_name = if file_type.is_file() {
1664 } else if file_type.is_dir() {
1670 let mode = ecx.eval_libc(mode_name)?;
1672 let size = metadata.len();
1674 let created = extract_sec_and_nsec(metadata.created())?;
1675 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1676 let modified = extract_sec_and_nsec(metadata.modified())?;
1678 // FIXME: Provide more fields using platform specific methods.
1679 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))