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;
14 use rustc_middle::ty::{self, layout::LayoutOf};
15 use rustc_target::abi::{Align, Size};
18 use helpers::check_arg_count;
19 use shims::os_str::os_str_to_bytes;
20 use shims::time::system_time_to_duration;
28 trait FileDescriptor: std::fmt::Debug {
29 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle>;
33 communicate_allowed: bool,
35 ) -> InterpResult<'tcx, io::Result<usize>>;
38 communicate_allowed: bool,
40 ) -> InterpResult<'tcx, io::Result<usize>>;
43 communicate_allowed: bool,
45 ) -> InterpResult<'tcx, io::Result<u64>>;
48 _communicate_allowed: bool,
49 ) -> InterpResult<'tcx, io::Result<i32>>;
51 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
54 impl FileDescriptor for FileHandle {
55 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
61 communicate_allowed: bool,
63 ) -> InterpResult<'tcx, io::Result<usize>> {
64 assert!(communicate_allowed, "isolation should have prevented even opening a file");
65 Ok(self.file.read(bytes))
70 communicate_allowed: bool,
72 ) -> InterpResult<'tcx, io::Result<usize>> {
73 assert!(communicate_allowed, "isolation should have prevented even opening a file");
74 Ok(self.file.write(bytes))
79 communicate_allowed: bool,
81 ) -> InterpResult<'tcx, io::Result<u64>> {
82 assert!(communicate_allowed, "isolation should have prevented even opening a file");
83 Ok(self.file.seek(offset))
88 communicate_allowed: bool,
89 ) -> InterpResult<'tcx, io::Result<i32>> {
90 assert!(communicate_allowed, "isolation should have prevented even opening a file");
91 // We sync the file if it was opened in a mode different than read-only.
93 // `File::sync_all` does the checks that are done when closing a file. We do this to
94 // to handle possible errors correctly.
95 let result = self.file.sync_all().map(|_| 0i32);
96 // Now we actually close the file.
98 // And return the result.
101 // We drop the file, this closes it but ignores any errors
102 // produced when closing it. This is done because
103 // `File::sync_all` cannot be done over files like
104 // `/dev/urandom` which are read-only. Check
105 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
106 // for a deeper discussion.
112 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
113 let duplicated = self.file.try_clone()?;
114 Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
118 impl FileDescriptor for io::Stdin {
119 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
120 throw_unsup_format!("stdin cannot be used as FileHandle");
125 communicate_allowed: bool,
127 ) -> InterpResult<'tcx, io::Result<usize>> {
128 if !communicate_allowed {
129 // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
130 helpers::isolation_abort_error("`read` from stdin")?;
132 Ok(Read::read(self, bytes))
137 _communicate_allowed: bool,
139 ) -> InterpResult<'tcx, io::Result<usize>> {
140 throw_unsup_format!("cannot write to stdin");
145 _communicate_allowed: bool,
147 ) -> InterpResult<'tcx, io::Result<u64>> {
148 throw_unsup_format!("cannot seek on stdin");
153 _communicate_allowed: bool,
154 ) -> InterpResult<'tcx, io::Result<i32>> {
155 throw_unsup_format!("stdin cannot be closed");
158 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
159 Ok(Box::new(io::stdin()))
163 impl FileDescriptor for io::Stdout {
164 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
165 throw_unsup_format!("stdout cannot be used as FileHandle");
170 _communicate_allowed: bool,
172 ) -> InterpResult<'tcx, io::Result<usize>> {
173 throw_unsup_format!("cannot read from stdout");
178 _communicate_allowed: bool,
180 ) -> InterpResult<'tcx, io::Result<usize>> {
181 // We allow writing to stderr even with isolation enabled.
182 let result = Write::write(self, bytes);
183 // Stdout is buffered, flush to make sure it appears on the
184 // screen. This is the write() syscall of the interpreted
185 // program, we want it to correspond to a write() syscall on
186 // the host -- there is no good in adding extra buffering
188 io::stdout().flush().unwrap();
195 _communicate_allowed: bool,
197 ) -> InterpResult<'tcx, io::Result<u64>> {
198 throw_unsup_format!("cannot seek on stdout");
203 _communicate_allowed: bool,
204 ) -> InterpResult<'tcx, io::Result<i32>> {
205 throw_unsup_format!("stdout cannot be closed");
208 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
209 Ok(Box::new(io::stdout()))
213 impl FileDescriptor for io::Stderr {
214 fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
215 throw_unsup_format!("stderr cannot be used as FileHandle");
220 _communicate_allowed: bool,
222 ) -> InterpResult<'tcx, io::Result<usize>> {
223 throw_unsup_format!("cannot read from stderr");
228 _communicate_allowed: bool,
230 ) -> InterpResult<'tcx, io::Result<usize>> {
231 // We allow writing to stderr even with isolation enabled.
232 // No need to flush, stderr is not buffered.
233 Ok(Write::write(self, bytes))
238 _communicate_allowed: bool,
240 ) -> InterpResult<'tcx, io::Result<u64>> {
241 throw_unsup_format!("cannot seek on stderr");
246 _communicate_allowed: bool,
247 ) -> InterpResult<'tcx, io::Result<i32>> {
248 throw_unsup_format!("stderr cannot be closed");
251 fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
252 Ok(Box::new(io::stderr()))
257 pub struct FileHandler {
258 handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
261 impl<'tcx> Default for FileHandler {
262 fn default() -> Self {
263 let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
264 handles.insert(0i32, Box::new(io::stdin()));
265 handles.insert(1i32, Box::new(io::stdout()));
266 handles.insert(2i32, Box::new(io::stderr()));
267 FileHandler { handles }
271 impl<'tcx> FileHandler {
272 fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
273 self.insert_fd_with_min_fd(file_handle, 0)
276 fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
277 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
278 // between used FDs, the find_map combinator will return it. If the first such unused FD
279 // is after all other used FDs, the find_map combinator will return None, and we will use
280 // the FD following the greatest FD thus far.
281 let candidate_new_fd =
282 self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
284 // There was a gap in the fds stored, return the first unused one
285 // (note that this relies on BTreeMap iterating in key order)
288 // This fd is used, keep going
292 let new_fd = candidate_new_fd.unwrap_or_else(|| {
293 // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
294 // maximum fd in the map
297 .map(|(fd, _)| fd.checked_add(1).unwrap())
301 self.handles.try_insert(new_fd, file_handle).unwrap();
306 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
307 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
308 fn macos_stat_write_buf(
310 metadata: FileMetadata,
311 buf_op: &OpTy<'tcx, Tag>,
312 ) -> InterpResult<'tcx, i32> {
313 let this = self.eval_context_mut();
315 let mode: u16 = metadata.mode.to_u16()?;
317 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
318 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
319 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
321 let buf = this.deref_operand(buf_op)?;
322 this.write_int_fields_named(
325 ("st_mode", mode.into()),
331 ("st_atime", access_sec.into()),
332 ("st_atime_nsec", access_nsec.into()),
333 ("st_mtime", modified_sec.into()),
334 ("st_mtime_nsec", modified_nsec.into()),
336 ("st_ctime_nsec", 0),
337 ("st_birthtime", created_sec.into()),
338 ("st_birthtime_nsec", created_nsec.into()),
339 ("st_size", metadata.size.into()),
351 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
352 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
353 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
354 /// types (like `read`, that returns an `i64`).
355 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
356 let this = self.eval_context_mut();
357 let ebadf = this.eval_libc("EBADF")?;
358 this.set_last_error(ebadf)?;
362 fn file_type_to_d_type(
364 file_type: std::io::Result<FileType>,
365 ) -> InterpResult<'tcx, i32> {
366 let this = self.eval_context_mut();
369 if file_type.is_dir() {
370 Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
371 } else if file_type.is_file() {
372 Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
373 } else if file_type.is_symlink() {
374 Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
376 // Certain file types are only supported when the host is a Unix system.
377 // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
378 // DT_UNKNOWN sooner.
382 use std::os::unix::fs::FileTypeExt;
383 if file_type.is_block_device() {
384 Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
385 } else if file_type.is_char_device() {
386 Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
387 } else if file_type.is_fifo() {
388 Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
389 } else if file_type.is_socket() {
390 Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
392 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
396 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
400 return match e.raw_os_error() {
401 Some(error) => Ok(error),
404 "the error {} couldn't be converted to a return value",
412 /// An open directory, tracked by DirHandler.
415 /// The directory reader on the host.
417 /// The most recent entry returned by readdir()
418 entry: Pointer<Option<Tag>>,
422 fn new(read_dir: ReadDir) -> Self {
423 // We rely on `free` being a NOP on null pointers.
424 Self { read_dir, entry: Pointer::null() }
429 pub struct DirHandler {
430 /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
433 /// When opendir is called, a directory iterator is created on the host for the target
434 /// directory, and an entry is stored in this hash map, indexed by an ID which represents
435 /// the directory stream. When readdir is called, the directory stream ID is used to look up
436 /// the corresponding ReadDir iterator from this map, and information from the next
437 /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
439 streams: FxHashMap<u64, OpenDir>,
440 /// ID number to be used by the next call to opendir
445 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
446 let id = self.next_id;
448 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
453 impl Default for DirHandler {
454 fn default() -> DirHandler {
456 streams: FxHashMap::default(),
457 // Skip 0 as an ID, because it looks like a null pointer to libc
466 operation: fn(&File) -> std::io::Result<()>,
467 ) -> std::io::Result<i32> {
468 if !writable && cfg!(windows) {
469 // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
470 // for writing. (FlushFileBuffers requires that the file handle have the
471 // GENERIC_WRITE right)
474 let result = operation(file);
479 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
480 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
481 fn open(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
482 if args.len() < 2 || args.len() > 3 {
484 "incorrect number of arguments for `open`: got {}, expected 2 or 3",
489 let this = self.eval_context_mut();
491 let path_op = &args[0];
492 let flag = this.read_scalar(&args[1])?.to_i32()?;
494 let mut options = OpenOptions::new();
496 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
497 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
498 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
499 // The first two bits of the flag correspond to the access mode in linux, macOS and
500 // windows. We need to check that in fact the access mode flags for the current target
501 // only use these two bits, otherwise we are in an unsupported target and should error.
502 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
503 throw_unsup_format!("access mode flags on this target are unsupported");
505 let mut writable = true;
507 // Now we check the access mode
508 let access_mode = flag & 0b11;
510 if access_mode == o_rdonly {
513 } else if access_mode == o_wronly {
515 } else if access_mode == o_rdwr {
516 options.read(true).write(true);
518 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
520 // We need to check that there aren't unsupported options in `flag`. For this we try to
521 // reproduce the content of `flag` in the `mirror` variable using only the supported
523 let mut mirror = access_mode;
525 let o_append = this.eval_libc_i32("O_APPEND")?;
526 if flag & o_append != 0 {
527 options.append(true);
530 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
531 if flag & o_trunc != 0 {
532 options.truncate(true);
535 let o_creat = this.eval_libc_i32("O_CREAT")?;
536 if flag & o_creat != 0 {
537 // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
538 // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
539 // (see https://github.com/rust-lang/rust/issues/71915).
540 let mode = if let Some(arg) = args.get(2) {
541 this.read_scalar(arg)?.to_u32()?
544 "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected 3",
550 throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
555 let o_excl = this.eval_libc_i32("O_EXCL")?;
556 if flag & o_excl != 0 {
558 options.create_new(true);
560 options.create(true);
563 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
564 if flag & o_cloexec != 0 {
565 // We do not need to do anything for this flag because `std` already sets it.
566 // (Technically we do not support *not* setting this flag, but we ignore that.)
569 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
570 // then we throw an error.
572 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
575 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
577 // Reject if isolation is enabled.
578 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
579 this.reject_in_isolation("`open`", reject_with)?;
580 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
584 let fd = options.open(&path).map(|file| {
585 let fh = &mut this.machine.file_handler;
586 fh.insert_fd(Box::new(FileHandle { file, writable }))
589 this.try_unwrap_io_result(fd)
592 fn fcntl(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
593 let this = self.eval_context_mut();
597 "incorrect number of arguments for fcntl: got {}, expected at least 2",
601 let fd = this.read_scalar(&args[0])?.to_i32()?;
602 let cmd = this.read_scalar(&args[1])?.to_i32()?;
604 // Reject if isolation is enabled.
605 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
606 this.reject_in_isolation("`fcntl`", reject_with)?;
607 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
611 // We only support getting the flags for a descriptor.
612 if cmd == this.eval_libc_i32("F_GETFD")? {
613 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
614 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
615 // always sets this flag when opening a file. However we still need to check that the
616 // file itself is open.
617 let &[_, _] = check_arg_count(args)?;
618 if this.machine.file_handler.handles.contains_key(&fd) {
619 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
621 this.handle_not_found()
623 } else if cmd == this.eval_libc_i32("F_DUPFD")?
624 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
626 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
627 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
628 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
629 // thus they can share the same implementation here.
630 let &[_, _, ref start] = check_arg_count(args)?;
631 let start = this.read_scalar(start)?.to_i32()?;
633 let fh = &mut this.machine.file_handler;
635 match fh.handles.get_mut(&fd) {
636 Some(file_descriptor) => {
637 let dup_result = file_descriptor.dup();
639 Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
641 this.set_last_error_from_io_error(e.kind())?;
646 None => return this.handle_not_found(),
648 } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
649 let &[_, _] = check_arg_count(args)?;
650 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
651 // FIXME: Support fullfsync for all FDs
652 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
653 let io_result = maybe_sync_file(&file, *writable, File::sync_all);
654 this.try_unwrap_io_result(io_result)
656 this.handle_not_found()
659 throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
663 fn close(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
664 let this = self.eval_context_mut();
666 let fd = this.read_scalar(fd_op)?.to_i32()?;
668 if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
669 let result = file_descriptor.close(this.machine.communicate())?;
670 this.try_unwrap_io_result(result)
672 this.handle_not_found()
676 fn read(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
677 let this = self.eval_context_mut();
679 // Isolation check is done via `FileDescriptor` trait.
681 trace!("Reading from FD {}, size {}", fd, count);
683 // Check that the *entire* buffer is actually valid memory.
684 this.memory.check_ptr_access_align(
686 Size::from_bytes(count),
688 CheckInAllocMsg::MemoryAccessTest,
691 // We cap the number of read bytes to the largest value that we are able to fit in both the
692 // host's and target's `isize`. This saves us from having to handle overflows later.
693 let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
694 let communicate = this.machine.communicate();
696 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
697 trace!("read: FD mapped to {:?}", file_descriptor);
698 // We want to read at most `count` bytes. We are sure that `count` is not negative
699 // because it was a target's `usize`. Also we are sure that its smaller than
700 // `usize::MAX` because it is a host's `isize`.
701 let mut bytes = vec![0; count as usize];
702 // `File::read` never returns a value larger than `count`,
703 // so this cannot fail.
705 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
709 // If reading to `bytes` did not fail, we write those bytes to the buffer.
710 this.memory.write_bytes(buf, bytes)?;
714 this.set_last_error_from_io_error(e.kind())?;
719 trace!("read: FD not found");
720 this.handle_not_found()
724 fn write(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
725 let this = self.eval_context_mut();
727 // Isolation check is done via `FileDescriptor` trait.
729 // Check that the *entire* buffer is actually valid memory.
730 this.memory.check_ptr_access_align(
732 Size::from_bytes(count),
734 CheckInAllocMsg::MemoryAccessTest,
737 // We cap the number of written bytes to the largest value that we are able to fit in both the
738 // host's and target's `isize`. This saves us from having to handle overflows later.
739 let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
740 let communicate = this.machine.communicate();
742 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
743 let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
745 file_descriptor.write(communicate, &bytes)?.map(|c| i64::try_from(c).unwrap());
746 this.try_unwrap_io_result(result)
748 this.handle_not_found()
754 fd_op: &OpTy<'tcx, Tag>,
755 offset_op: &OpTy<'tcx, Tag>,
756 whence_op: &OpTy<'tcx, Tag>,
757 ) -> InterpResult<'tcx, i64> {
758 let this = self.eval_context_mut();
760 // Isolation check is done via `FileDescriptor` trait.
762 let fd = this.read_scalar(fd_op)?.to_i32()?;
763 let offset = this.read_scalar(offset_op)?.to_i64()?;
764 let whence = this.read_scalar(whence_op)?.to_i32()?;
766 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
767 SeekFrom::Start(u64::try_from(offset).unwrap())
768 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
769 SeekFrom::Current(offset)
770 } else if whence == this.eval_libc_i32("SEEK_END")? {
771 SeekFrom::End(offset)
773 let einval = this.eval_libc("EINVAL")?;
774 this.set_last_error(einval)?;
778 let communicate = this.machine.communicate();
779 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
780 let result = file_descriptor
781 .seek(communicate, seek_from)?
782 .map(|offset| i64::try_from(offset).unwrap());
783 this.try_unwrap_io_result(result)
785 this.handle_not_found()
789 fn unlink(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
790 let this = self.eval_context_mut();
792 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
794 // Reject if isolation is enabled.
795 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
796 this.reject_in_isolation("`unlink`", reject_with)?;
797 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
801 let result = remove_file(path).map(|_| 0);
802 this.try_unwrap_io_result(result)
807 target_op: &OpTy<'tcx, Tag>,
808 linkpath_op: &OpTy<'tcx, Tag>,
809 ) -> InterpResult<'tcx, i32> {
811 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
812 std::os::unix::fs::symlink(src, dst)
816 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
817 use std::os::windows::fs;
818 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
821 let this = self.eval_context_mut();
822 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
823 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
825 // Reject if isolation is enabled.
826 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
827 this.reject_in_isolation("`symlink`", reject_with)?;
828 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
832 let result = create_link(&target, &linkpath).map(|_| 0);
833 this.try_unwrap_io_result(result)
838 path_op: &OpTy<'tcx, Tag>,
839 buf_op: &OpTy<'tcx, Tag>,
840 ) -> InterpResult<'tcx, i32> {
841 let this = self.eval_context_mut();
842 this.assert_target_os("macos", "stat");
844 let path_scalar = this.read_pointer(path_op)?;
845 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
847 // Reject if isolation is enabled.
848 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
849 this.reject_in_isolation("`stat`", reject_with)?;
850 let eacc = this.eval_libc("EACCES")?;
851 this.set_last_error(eacc)?;
855 // `stat` always follows symlinks.
856 let metadata = match FileMetadata::from_path(this, &path, true)? {
857 Some(metadata) => metadata,
858 None => return Ok(-1),
861 this.macos_stat_write_buf(metadata, buf_op)
864 // `lstat` is used to get symlink metadata.
867 path_op: &OpTy<'tcx, Tag>,
868 buf_op: &OpTy<'tcx, Tag>,
869 ) -> InterpResult<'tcx, i32> {
870 let this = self.eval_context_mut();
871 this.assert_target_os("macos", "lstat");
873 let path_scalar = this.read_pointer(path_op)?;
874 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
876 // Reject if isolation is enabled.
877 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
878 this.reject_in_isolation("`lstat`", reject_with)?;
879 let eacc = this.eval_libc("EACCES")?;
880 this.set_last_error(eacc)?;
884 let metadata = match FileMetadata::from_path(this, &path, false)? {
885 Some(metadata) => metadata,
886 None => return Ok(-1),
889 this.macos_stat_write_buf(metadata, buf_op)
894 fd_op: &OpTy<'tcx, Tag>,
895 buf_op: &OpTy<'tcx, Tag>,
896 ) -> InterpResult<'tcx, i32> {
897 let this = self.eval_context_mut();
899 this.assert_target_os("macos", "fstat");
901 let fd = this.read_scalar(fd_op)?.to_i32()?;
903 // Reject if isolation is enabled.
904 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
905 this.reject_in_isolation("`fstat`", reject_with)?;
906 // Set error code as "EBADF" (bad fd)
907 return this.handle_not_found();
910 let metadata = match FileMetadata::from_fd(this, fd)? {
911 Some(metadata) => metadata,
912 None => return Ok(-1),
914 this.macos_stat_write_buf(metadata, buf_op)
919 dirfd_op: &OpTy<'tcx, Tag>, // Should be an `int`
920 pathname_op: &OpTy<'tcx, Tag>, // Should be a `const char *`
921 flags_op: &OpTy<'tcx, Tag>, // Should be an `int`
922 _mask_op: &OpTy<'tcx, Tag>, // Should be an `unsigned int`
923 statxbuf_op: &OpTy<'tcx, Tag>, // Should be a `struct statx *`
924 ) -> InterpResult<'tcx, i32> {
925 let this = self.eval_context_mut();
927 this.assert_target_os("linux", "statx");
929 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
930 let pathname_ptr = this.read_pointer(pathname_op)?;
932 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
933 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
934 let efault = this.eval_libc("EFAULT")?;
935 this.set_last_error(efault)?;
939 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
940 // proper `MemPlace` and then write the results of this function to it. However, the
941 // `syscall` function is untyped. This means that all the `statx` parameters are provided
942 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
943 // `statxbuf_op` by using the `libc::statx` struct type.
945 // FIXME: This long path is required because `libc::statx` is an struct and also a
946 // function and `resolve_path` is returning the latter.
948 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])
949 .ty(*this.tcx, ty::ParamEnv::reveal_all());
950 let statx_layout = this.layout_of(statx_ty)?;
951 MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
954 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
955 // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
956 let flags = this.read_scalar(flags_op)?.to_i32()?;
957 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
958 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
960 // * interpreting `path` as an absolute directory,
961 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
962 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
964 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
965 // found this error, please open an issue reporting it.
966 if !(path.is_absolute()
967 || dirfd == this.eval_libc_i32("AT_FDCWD")?
968 || (path.as_os_str().is_empty() && empty_path_flag))
971 "using statx is only supported with absolute paths, relative paths with the file \
972 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
977 // Reject if isolation is enabled.
978 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
979 this.reject_in_isolation("`statx`", reject_with)?;
980 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
981 // since `path` is provided, either absolute or
982 // relative to CWD, `EACCES` is the most relevant.
983 this.eval_libc("EACCES")?
985 // `dirfd` is set to target file, and `path` is empty
986 // (or we would have hit the `throw_unsup_format`
987 // above). `EACCES` would violate the spec.
988 assert!(empty_path_flag);
989 this.eval_libc("EBADF")?
991 this.set_last_error(ecode)?;
995 // the `_mask_op` paramter specifies the file information that the caller requested.
996 // However `statx` is allowed to return information that was not requested or to not
997 // return information that was requested. This `mask` represents the information we can
998 // actually provide for any target.
1000 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1002 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1004 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1006 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1007 // represented by dirfd, whether it's a directory or otherwise.
1008 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1009 FileMetadata::from_fd(this, dirfd)?
1011 FileMetadata::from_path(this, &path, follow_symlink)?
1013 let metadata = match metadata {
1014 Some(metadata) => metadata,
1015 None => return Ok(-1),
1018 // The `mode` field specifies the type of the file and the permissions over the file for
1019 // the owner, its group and other users. Given that we can only provide the file type
1020 // without using platform specific methods, we only set the bits corresponding to the file
1021 // type. This should be an `__u16` but `libc` provides its values as `u32`.
1022 let mode: u16 = metadata
1026 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1028 // We need to set the corresponding bits of `mask` if the access, creation and modification
1029 // times were available. Otherwise we let them be zero.
1030 let (access_sec, access_nsec) = metadata
1033 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1034 InterpResult::Ok(tup)
1036 .unwrap_or(Ok((0, 0)))?;
1038 let (created_sec, created_nsec) = metadata
1041 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1042 InterpResult::Ok(tup)
1044 .unwrap_or(Ok((0, 0)))?;
1046 let (modified_sec, modified_nsec) = metadata
1049 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1050 InterpResult::Ok(tup)
1052 .unwrap_or(Ok((0, 0)))?;
1054 // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1055 this.write_int_fields_named(
1057 ("stx_mask", mask.into()),
1059 ("stx_attributes", 0),
1063 ("stx_mode", mode.into()),
1065 ("stx_size", metadata.size.into()),
1067 ("stx_attributes_mask", 0),
1068 ("stx_rdev_major", 0),
1069 ("stx_rdev_minor", 0),
1070 ("stx_dev_major", 0),
1071 ("stx_dev_minor", 0),
1075 this.write_int_fields(
1077 access_sec.into(), // stx_atime.tv_sec
1078 access_nsec.into(), // stx_atime.tv_nsec
1080 &this.mplace_field_named(&statxbuf, "stx_atime")?,
1082 this.write_int_fields(
1084 created_sec.into(), // stx_btime.tv_sec
1085 created_nsec.into(), // stx_btime.tv_nsec
1087 &this.mplace_field_named(&statxbuf, "stx_btime")?,
1089 this.write_int_fields(
1091 0.into(), // stx_ctime.tv_sec
1092 0.into(), // stx_ctime.tv_nsec
1094 &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1096 this.write_int_fields(
1098 modified_sec.into(), // stx_mtime.tv_sec
1099 modified_nsec.into(), // stx_mtime.tv_nsec
1101 &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1109 oldpath_op: &OpTy<'tcx, Tag>,
1110 newpath_op: &OpTy<'tcx, Tag>,
1111 ) -> InterpResult<'tcx, i32> {
1112 let this = self.eval_context_mut();
1114 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1115 let newpath_ptr = this.read_pointer(newpath_op)?;
1117 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1118 let efault = this.eval_libc("EFAULT")?;
1119 this.set_last_error(efault)?;
1123 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1124 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1126 // Reject if isolation is enabled.
1127 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1128 this.reject_in_isolation("`rename`", reject_with)?;
1129 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1133 let result = rename(oldpath, newpath).map(|_| 0);
1135 this.try_unwrap_io_result(result)
1140 path_op: &OpTy<'tcx, Tag>,
1141 mode_op: &OpTy<'tcx, Tag>,
1142 ) -> InterpResult<'tcx, i32> {
1143 let this = self.eval_context_mut();
1145 #[cfg_attr(not(unix), allow(unused_variables))]
1146 let mode = if this.tcx.sess.target.os == "macos" {
1147 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1149 this.read_scalar(mode_op)?.to_u32()?
1152 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1154 // Reject if isolation is enabled.
1155 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1156 this.reject_in_isolation("`mkdir`", reject_with)?;
1157 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1161 #[cfg_attr(not(unix), allow(unused_mut))]
1162 let mut builder = DirBuilder::new();
1164 // If the host supports it, forward on the mode of the directory
1165 // (i.e. permission bits and the sticky bit)
1168 use std::os::unix::fs::DirBuilderExt;
1169 builder.mode(mode.into());
1172 let result = builder.create(path).map(|_| 0i32);
1174 this.try_unwrap_io_result(result)
1177 fn rmdir(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1178 let this = self.eval_context_mut();
1180 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1182 // Reject if isolation is enabled.
1183 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1184 this.reject_in_isolation("`rmdir`", reject_with)?;
1185 this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1189 let result = remove_dir(path).map(|_| 0i32);
1191 this.try_unwrap_io_result(result)
1194 fn opendir(&mut self, name_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
1195 let this = self.eval_context_mut();
1197 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1199 // Reject if isolation is enabled.
1200 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1201 this.reject_in_isolation("`opendir`", reject_with)?;
1202 let eacc = this.eval_libc("EACCES")?;
1203 this.set_last_error(eacc)?;
1204 return Ok(Scalar::null_ptr(this));
1207 let result = read_dir(name);
1211 let id = this.machine.dir_handler.insert_new(dir_iter);
1213 // The libc API for opendir says that this method returns a pointer to an opaque
1214 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1216 Ok(Scalar::from_machine_usize(id, this))
1219 this.set_last_error_from_io_error(e.kind())?;
1220 Ok(Scalar::null_ptr(this))
1225 fn linux_readdir64(&mut self, dirp_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
1226 let this = self.eval_context_mut();
1228 this.assert_target_os("linux", "readdir64");
1230 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1232 // Reject if isolation is enabled.
1233 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1234 this.reject_in_isolation("`readdir`", reject_with)?;
1235 let eacc = this.eval_libc("EBADF")?;
1236 this.set_last_error(eacc)?;
1237 return Ok(Scalar::null_ptr(this));
1240 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1241 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1244 let entry = match open_dir.read_dir.next() {
1245 Some(Ok(dir_entry)) => {
1246 // Write the directory entry into a newly allocated buffer.
1247 // The name is written with write_bytes, while the rest of the
1248 // dirent64 struct is written using write_int_fields.
1251 // pub struct dirent64 {
1252 // pub d_ino: ino64_t,
1253 // pub d_off: off64_t,
1254 // pub d_reclen: c_ushort,
1255 // pub d_type: c_uchar,
1256 // pub d_name: [c_char; 256],
1259 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1260 name.push("\0"); // Add a NUL terminator
1261 let name_bytes = os_str_to_bytes(&name)?;
1262 let name_len = u64::try_from(name_bytes.len()).unwrap();
1264 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1265 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1266 let size = d_name_offset.checked_add(name_len).unwrap();
1269 this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1271 // If the host is a Unix system, fill in the inode number with its real value.
1272 // If not, use 0 as a fallback value.
1274 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1278 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1280 this.write_int_fields(
1282 ino.into(), // d_ino
1284 size.into(), // d_reclen
1285 file_type.into(), // d_type
1287 &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1290 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1291 this.memory.write_bytes(name_ptr, name_bytes.iter().copied())?;
1296 // end of stream: return NULL
1300 this.set_last_error_from_io_error(e.kind())?;
1305 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1306 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1307 this.free(old_entry, MiriMemoryKind::Runtime)?;
1309 Ok(Scalar::from_maybe_pointer(entry, this))
1314 dirp_op: &OpTy<'tcx, Tag>,
1315 entry_op: &OpTy<'tcx, Tag>,
1316 result_op: &OpTy<'tcx, Tag>,
1317 ) -> InterpResult<'tcx, i32> {
1318 let this = self.eval_context_mut();
1320 this.assert_target_os("macos", "readdir_r");
1322 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1324 // Reject if isolation is enabled.
1325 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1326 this.reject_in_isolation("`readdir_r`", reject_with)?;
1327 // Set error code as "EBADF" (bad fd)
1328 return this.handle_not_found();
1331 let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1332 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1334 match open_dir.read_dir.next() {
1335 Some(Ok(dir_entry)) => {
1336 // Write into entry, write pointer to result, return 0 on success.
1337 // The name is written with write_os_str_to_c_str, while the rest of the
1338 // dirent struct is written using write_int_fields.
1341 // pub struct dirent {
1343 // pub d_seekoff: u64,
1344 // pub d_reclen: u16,
1345 // pub d_namlen: u16,
1347 // pub d_name: [c_char; 1024],
1350 let entry_place = this.deref_operand(entry_op)?;
1351 let name_place = this.mplace_field(&entry_place, 5)?;
1353 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1354 let (name_fits, file_name_len) = this.write_os_str_to_c_str(
1357 name_place.layout.size.bytes(),
1360 throw_unsup_format!(
1361 "a directory entry had a name too large to fit in libc::dirent"
1365 let entry_place = this.deref_operand(entry_op)?;
1367 // If the host is a Unix system, fill in the inode number with its real value.
1368 // If not, use 0 as a fallback value.
1370 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1374 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1376 this.write_int_fields(
1378 ino.into(), // d_ino
1381 file_name_len.into(), // d_namlen
1382 file_type.into(), // d_type
1387 let result_place = this.deref_operand(result_op)?;
1388 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1393 // end of stream: return 0, assign *result=NULL
1394 this.write_null(&this.deref_operand(result_op)?.into())?;
1398 match e.raw_os_error() {
1399 // return positive error number on error
1400 Some(error) => Ok(error),
1402 throw_unsup_format!(
1403 "the error {} couldn't be converted to a return value",
1411 fn closedir(&mut self, dirp_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1412 let this = self.eval_context_mut();
1414 let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1416 // Reject if isolation is enabled.
1417 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1418 this.reject_in_isolation("`closedir`", reject_with)?;
1419 // Set error code as "EBADF" (bad fd)
1420 return this.handle_not_found();
1423 if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1424 this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1428 this.handle_not_found()
1434 fd_op: &OpTy<'tcx, Tag>,
1435 length_op: &OpTy<'tcx, Tag>,
1436 ) -> InterpResult<'tcx, i32> {
1437 let this = self.eval_context_mut();
1439 let fd = this.read_scalar(fd_op)?.to_i32()?;
1440 let length = this.read_scalar(length_op)?.to_i64()?;
1442 // Reject if isolation is enabled.
1443 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1444 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1445 // Set error code as "EBADF" (bad fd)
1446 return this.handle_not_found();
1449 if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1450 // FIXME: Support ftruncate64 for all FDs
1451 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1453 if let Ok(length) = length.try_into() {
1454 let result = file.set_len(length);
1455 this.try_unwrap_io_result(result.map(|_| 0i32))
1457 let einval = this.eval_libc("EINVAL")?;
1458 this.set_last_error(einval)?;
1462 // The file is not writable
1463 let einval = this.eval_libc("EINVAL")?;
1464 this.set_last_error(einval)?;
1468 this.handle_not_found()
1472 fn fsync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1473 // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1474 // underlying disk to finish writing. In the interest of host compatibility,
1475 // we conservatively implement this with `sync_all`, which
1476 // *does* wait for the disk.
1478 let this = self.eval_context_mut();
1480 let fd = this.read_scalar(fd_op)?.to_i32()?;
1482 // Reject if isolation is enabled.
1483 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1484 this.reject_in_isolation("`fsync`", reject_with)?;
1485 // Set error code as "EBADF" (bad fd)
1486 return this.handle_not_found();
1489 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1490 // FIXME: Support fsync for all FDs
1491 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1492 let io_result = maybe_sync_file(&file, *writable, File::sync_all);
1493 this.try_unwrap_io_result(io_result)
1495 this.handle_not_found()
1499 fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1500 let this = self.eval_context_mut();
1502 let fd = this.read_scalar(fd_op)?.to_i32()?;
1504 // Reject if isolation is enabled.
1505 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1506 this.reject_in_isolation("`fdatasync`", reject_with)?;
1507 // Set error code as "EBADF" (bad fd)
1508 return this.handle_not_found();
1511 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1512 // FIXME: Support fdatasync for all FDs
1513 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1514 let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1515 this.try_unwrap_io_result(io_result)
1517 this.handle_not_found()
1523 fd_op: &OpTy<'tcx, Tag>,
1524 offset_op: &OpTy<'tcx, Tag>,
1525 nbytes_op: &OpTy<'tcx, Tag>,
1526 flags_op: &OpTy<'tcx, Tag>,
1527 ) -> InterpResult<'tcx, i32> {
1528 let this = self.eval_context_mut();
1530 let fd = this.read_scalar(fd_op)?.to_i32()?;
1531 let offset = this.read_scalar(offset_op)?.to_i64()?;
1532 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1533 let flags = this.read_scalar(flags_op)?.to_i32()?;
1535 if offset < 0 || nbytes < 0 {
1536 let einval = this.eval_libc("EINVAL")?;
1537 this.set_last_error(einval)?;
1540 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1541 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1542 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1543 if flags & allowed_flags != flags {
1544 let einval = this.eval_libc("EINVAL")?;
1545 this.set_last_error(einval)?;
1549 // Reject if isolation is enabled.
1550 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1551 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1552 // Set error code as "EBADF" (bad fd)
1553 return this.handle_not_found();
1556 if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1557 // FIXME: Support sync_data_range for all FDs
1558 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1559 let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1560 this.try_unwrap_io_result(io_result)
1562 this.handle_not_found()
1568 pathname_op: &OpTy<'tcx, Tag>,
1569 buf_op: &OpTy<'tcx, Tag>,
1570 bufsize_op: &OpTy<'tcx, Tag>,
1571 ) -> InterpResult<'tcx, i64> {
1572 let this = self.eval_context_mut();
1574 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1575 let buf = this.read_pointer(buf_op)?;
1576 let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1578 // Reject if isolation is enabled.
1579 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1580 this.reject_in_isolation("`readlink`", reject_with)?;
1581 let eacc = this.eval_libc("EACCES")?;
1582 this.set_last_error(eacc)?;
1586 let result = std::fs::read_link(pathname);
1589 let resolved = this.convert_path_separator(
1590 Cow::Borrowed(resolved.as_ref()),
1591 crate::shims::os_str::PathConversion::HostToTarget,
1593 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1594 let bufsize: usize = bufsize.try_into().unwrap();
1595 if path_bytes.len() > bufsize {
1596 path_bytes = &path_bytes[..bufsize]
1598 // 'readlink' truncates the resolved path if
1599 // the provided buffer is not large enough.
1600 this.memory.write_bytes(buf, path_bytes.iter().copied())?;
1601 Ok(path_bytes.len().try_into().unwrap())
1604 this.set_last_error_from_io_error(e.kind())?;
1611 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1612 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1614 fn extract_sec_and_nsec<'tcx>(
1615 time: std::io::Result<SystemTime>,
1616 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1619 let duration = system_time_to_duration(&time)?;
1620 Ok((duration.as_secs(), duration.subsec_nanos()))
1625 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1627 struct FileMetadata {
1630 created: Option<(u64, u32)>,
1631 accessed: Option<(u64, u32)>,
1632 modified: Option<(u64, u32)>,
1636 fn from_path<'tcx, 'mir>(
1637 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1639 follow_symlink: bool,
1640 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1642 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1644 FileMetadata::from_meta(ecx, metadata)
1647 fn from_fd<'tcx, 'mir>(
1648 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1650 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1651 let option = ecx.machine.file_handler.handles.get(&fd);
1652 let file = match option {
1653 Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1654 None => return ecx.handle_not_found().map(|_: i32| None),
1656 let metadata = file.metadata();
1658 FileMetadata::from_meta(ecx, metadata)
1661 fn from_meta<'tcx, 'mir>(
1662 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1663 metadata: Result<std::fs::Metadata, std::io::Error>,
1664 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1665 let metadata = match metadata {
1666 Ok(metadata) => metadata,
1668 ecx.set_last_error_from_io_error(e.kind())?;
1673 let file_type = metadata.file_type();
1675 let mode_name = if file_type.is_file() {
1677 } else if file_type.is_dir() {
1683 let mode = ecx.eval_libc(mode_name)?;
1685 let size = metadata.len();
1687 let created = extract_sec_and_nsec(metadata.created())?;
1688 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1689 let modified = extract_sec_and_nsec(metadata.modified())?;
1691 // FIXME: Provide more fields using platform specific methods.
1692 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))