1 use std::collections::BTreeMap;
2 use std::collections::HashMap;
3 use std::convert::{TryFrom, TryInto};
4 use std::fs::{read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir};
5 use std::io::{Read, Seek, SeekFrom, Write};
6 use std::path::PathBuf;
7 use std::time::SystemTime;
9 use rustc::ty::layout::{Align, LayoutOf, Size};
11 use crate::stacked_borrows::Tag;
13 use helpers::immty_from_uint_checked;
14 use shims::time::system_time_to_duration;
17 pub struct FileHandle {
22 #[derive(Debug, Default)]
23 pub struct FileHandler {
24 handles: BTreeMap<i32, FileHandle>,
27 // fd numbers 0, 1, and 2 are reserved for stdin, stdout, and stderr
28 const MIN_NORMAL_FILE_FD: i32 = 3;
31 fn insert_fd(&mut self, file_handle: FileHandle) -> i32 {
32 self.insert_fd_with_min_fd(file_handle, 0)
35 fn insert_fd_with_min_fd(&mut self, file_handle: FileHandle, min_fd: i32) -> i32 {
36 let min_fd = std::cmp::max(min_fd, MIN_NORMAL_FILE_FD);
38 // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
39 // between used FDs, the find_map combinator will return it. If the first such unused FD
40 // is after all other used FDs, the find_map combinator will return None, and we will use
41 // the FD following the greatest FD thus far.
42 let candidate_new_fd = self
46 .find_map(|((fd, _fh), counter)| {
48 // There was a gap in the fds stored, return the first unused one
49 // (note that this relies on BTreeMap iterating in key order)
52 // This fd is used, keep going
56 let new_fd = candidate_new_fd.unwrap_or_else(|| {
57 // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
58 // maximum fd in the map
59 self.handles.last_entry().map(|entry| entry.key() + 1).unwrap_or(min_fd)
62 self.handles.insert(new_fd, file_handle).unwrap_none();
67 impl<'mir, 'tcx> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
68 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
69 /// Emulate `stat` or `lstat` on the `macos` platform. This function is not intended to be
70 /// called directly from `emulate_foreign_item_by_name`, so it does not check if isolation is
71 /// disabled or if the target platform is the correct one. Please use `macos_stat` or
72 /// `macos_lstat` instead.
73 fn macos_stat_or_lstat(
76 path_op: OpTy<'tcx, Tag>,
77 buf_op: OpTy<'tcx, Tag>,
78 ) -> InterpResult<'tcx, i32> {
79 let this = self.eval_context_mut();
81 let path_scalar = this.read_scalar(path_op)?.not_undef()?;
82 let path: PathBuf = this.read_os_str_from_c_str(path_scalar)?.into();
84 let metadata = match FileMetadata::from_path(this, path, follow_symlink)? {
85 Some(metadata) => metadata,
86 None => return Ok(-1),
88 this.macos_stat_write_buf(metadata, buf_op)
91 fn macos_stat_write_buf(
93 metadata: FileMetadata,
94 buf_op: OpTy<'tcx, Tag>,
95 ) -> InterpResult<'tcx, i32> {
96 let this = self.eval_context_mut();
98 let mode: u16 = metadata.mode.to_u16()?;
100 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
101 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
102 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
104 let dev_t_layout = this.libc_ty_layout("dev_t")?;
105 let mode_t_layout = this.libc_ty_layout("mode_t")?;
106 let nlink_t_layout = this.libc_ty_layout("nlink_t")?;
107 let ino_t_layout = this.libc_ty_layout("ino_t")?;
108 let uid_t_layout = this.libc_ty_layout("uid_t")?;
109 let gid_t_layout = this.libc_ty_layout("gid_t")?;
110 let time_t_layout = this.libc_ty_layout("time_t")?;
111 let long_layout = this.libc_ty_layout("c_long")?;
112 let off_t_layout = this.libc_ty_layout("off_t")?;
113 let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?;
114 let blksize_t_layout = this.libc_ty_layout("blksize_t")?;
115 let uint32_t_layout = this.libc_ty_layout("uint32_t")?;
117 // We need to add 32 bits of padding after `st_rdev` if we are on a 64-bit platform.
118 let pad_layout = if this.tcx.sess.target.ptr_width == 64 {
121 this.layout_of(this.tcx.mk_unit())?
125 immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
126 immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
127 immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
128 immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
129 immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
130 immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
131 immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
132 immty_from_uint_checked(0u128, pad_layout)?, // padding for 64-bit targets
133 immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
134 immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
135 immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
136 immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
137 immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
138 immty_from_uint_checked(0u128, long_layout)?, // st_ctime_nsec
139 immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
140 immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
141 immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
142 immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
143 immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
144 immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
145 immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
148 let buf = this.deref_operand(buf_op)?;
149 this.write_packed_immediates(buf, &imms)?;
154 /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
155 /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
156 /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
157 /// types (like `read`, that returns an `i64`).
158 fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
159 let this = self.eval_context_mut();
160 let ebadf = this.eval_libc("EBADF")?;
161 this.set_last_error(ebadf)?;
165 fn file_type_to_d_type(&mut self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
166 let this = self.eval_context_mut();
169 if file_type.is_dir() {
170 Ok(this.eval_libc("DT_DIR")?.to_u8()? as i32)
171 } else if file_type.is_file() {
172 Ok(this.eval_libc("DT_REG")?.to_u8()? as i32)
173 } else if file_type.is_symlink() {
174 Ok(this.eval_libc("DT_LNK")?.to_u8()? as i32)
178 use std::os::unix::fs::FileTypeExt;
179 if file_type.is_block_device() {
180 Ok(this.eval_libc("DT_BLK")?.to_u8()? as i32)
181 } else if file_type.is_char_device() {
182 Ok(this.eval_libc("DT_CHR")?.to_u8()? as i32)
183 } else if file_type.is_fifo() {
184 Ok(this.eval_libc("DT_FIFO")?.to_u8()? as i32)
185 } else if file_type.is_socket() {
186 Ok(this.eval_libc("DT_SOCK")?.to_u8()? as i32)
188 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
192 Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()? as i32)
195 Err(e) => return match e.raw_os_error() {
196 Some(error) => Ok(error),
197 None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
203 #[derive(Debug, Default)]
204 pub struct DirHandler {
205 /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
208 /// When opendir is called, a new allocation is made, a directory iterator is created on the
209 /// host for the target directory, and an entry is stored in this hash map, indexed by a
210 /// pointer to the allocation which represents the directory stream. When readdir is called,
211 /// the directory stream pointer is used to look up the corresponding ReadDir iterator from
212 /// this HashMap, and information from the next directory entry is returned. When closedir is
213 /// called, the ReadDir iterator is removed from this HashMap, and the allocation that
214 /// represented the directory stream is deallocated.
215 streams: HashMap<Pointer<Tag>, ReadDir>,
218 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
219 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
222 path_op: OpTy<'tcx, Tag>,
223 flag_op: OpTy<'tcx, Tag>,
224 ) -> InterpResult<'tcx, i32> {
225 let this = self.eval_context_mut();
227 this.check_no_isolation("open")?;
229 let flag = this.read_scalar(flag_op)?.to_i32()?;
231 let mut options = OpenOptions::new();
233 let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
234 let o_wronly = this.eval_libc_i32("O_WRONLY")?;
235 let o_rdwr = this.eval_libc_i32("O_RDWR")?;
236 // The first two bits of the flag correspond to the access mode in linux, macOS and
237 // windows. We need to check that in fact the access mode flags for the current platform
238 // only use these two bits, otherwise we are in an unsupported platform and should error.
239 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
240 throw_unsup_format!("Access mode flags on this platform are unsupported");
242 let mut writable = true;
244 // Now we check the access mode
245 let access_mode = flag & 0b11;
247 if access_mode == o_rdonly {
250 } else if access_mode == o_wronly {
252 } else if access_mode == o_rdwr {
253 options.read(true).write(true);
255 throw_unsup_format!("Unsupported access mode {:#x}", access_mode);
257 // We need to check that there aren't unsupported options in `flag`. For this we try to
258 // reproduce the content of `flag` in the `mirror` variable using only the supported
260 let mut mirror = access_mode;
262 let o_append = this.eval_libc_i32("O_APPEND")?;
263 if flag & o_append != 0 {
264 options.append(true);
267 let o_trunc = this.eval_libc_i32("O_TRUNC")?;
268 if flag & o_trunc != 0 {
269 options.truncate(true);
272 let o_creat = this.eval_libc_i32("O_CREAT")?;
273 if flag & o_creat != 0 {
274 options.create(true);
277 let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
278 if flag & o_cloexec != 0 {
279 // We do not need to do anything for this flag because `std` already sets it.
280 // (Technically we do not support *not* setting this flag, but we ignore that.)
283 // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
284 // then we throw an error.
286 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
289 let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
291 let fd = options.open(&path).map(|file| {
292 let fh = &mut this.machine.file_handler;
293 fh.insert_fd(FileHandle { file, writable })
296 this.try_unwrap_io_result(fd)
301 fd_op: OpTy<'tcx, Tag>,
302 cmd_op: OpTy<'tcx, Tag>,
303 start_op: Option<OpTy<'tcx, Tag>>,
304 ) -> InterpResult<'tcx, i32> {
305 let this = self.eval_context_mut();
307 this.check_no_isolation("fcntl")?;
309 let fd = this.read_scalar(fd_op)?.to_i32()?;
310 let cmd = this.read_scalar(cmd_op)?.to_i32()?;
311 // We only support getting the flags for a descriptor.
312 if cmd == this.eval_libc_i32("F_GETFD")? {
313 // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
314 // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
315 // always sets this flag when opening a file. However we still need to check that the
316 // file itself is open.
317 if this.machine.file_handler.handles.contains_key(&fd) {
318 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
320 this.handle_not_found()
322 } else if cmd == this.eval_libc_i32("F_DUPFD")?
323 || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
325 // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
326 // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
327 // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
328 // thus they can share the same implementation here.
329 if fd < MIN_NORMAL_FILE_FD {
330 throw_unsup_format!("Duplicating file descriptors for stdin, stdout, or stderr is not supported")
332 let start_op = start_op.ok_or_else(|| {
334 "fcntl with command F_DUPFD or F_DUPFD_CLOEXEC requires a third argument"
337 let start = this.read_scalar(start_op)?.to_i32()?;
338 let fh = &mut this.machine.file_handler;
339 let (file_result, writable) = match fh.handles.get(&fd) {
340 Some(FileHandle { file, writable }) => (file.try_clone(), *writable),
341 None => return this.handle_not_found(),
343 let fd_result = file_result.map(|duplicated| {
344 fh.insert_fd_with_min_fd(FileHandle { file: duplicated, writable }, start)
346 this.try_unwrap_io_result(fd_result)
348 throw_unsup_format!("The {:#x} command is not supported for `fcntl`)", cmd);
352 fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
353 let this = self.eval_context_mut();
355 this.check_no_isolation("close")?;
357 let fd = this.read_scalar(fd_op)?.to_i32()?;
359 if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.remove(&fd) {
360 // We sync the file if it was opened in a mode different than read-only.
362 // `File::sync_all` does the checks that are done when closing a file. We do this to
363 // to handle possible errors correctly.
364 let result = this.try_unwrap_io_result(file.sync_all().map(|_| 0i32));
365 // Now we actually close the file.
367 // And return the result.
370 // We drop the file, this closes it but ignores any errors produced when closing
371 // it. This is done because `File::sync_all` cannot be done over files like
372 // `/dev/urandom` which are read-only. Check
373 // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439 for a deeper
379 this.handle_not_found()
385 fd_op: OpTy<'tcx, Tag>,
386 buf_op: OpTy<'tcx, Tag>,
387 count_op: OpTy<'tcx, Tag>,
388 ) -> InterpResult<'tcx, i64> {
389 let this = self.eval_context_mut();
391 this.check_no_isolation("read")?;
393 let fd = this.read_scalar(fd_op)?.to_i32()?;
394 let buf = this.read_scalar(buf_op)?.not_undef()?;
395 let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
397 // Check that the *entire* buffer is actually valid memory.
398 this.memory.check_ptr_access(
400 Size::from_bytes(count),
401 Align::from_bytes(1).unwrap(),
404 // We cap the number of read bytes to the largest value that we are able to fit in both the
405 // host's and target's `isize`. This saves us from having to handle overflows later.
406 let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
408 if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
409 // This can never fail because `count` was capped to be smaller than
410 // `isize::max_value()`.
411 let count = isize::try_from(count).unwrap();
412 // We want to read at most `count` bytes. We are sure that `count` is not negative
413 // because it was a target's `usize`. Also we are sure that its smaller than
414 // `usize::max_value()` because it is a host's `isize`.
415 let mut bytes = vec![0; count as usize];
418 // `File::read` never returns a value larger than `count`, so this cannot fail.
419 .map(|c| i64::try_from(c).unwrap());
423 // If reading to `bytes` did not fail, we write those bytes to the buffer.
424 this.memory.write_bytes(buf, bytes)?;
428 this.set_last_error_from_io_error(e)?;
433 this.handle_not_found()
439 fd_op: OpTy<'tcx, Tag>,
440 buf_op: OpTy<'tcx, Tag>,
441 count_op: OpTy<'tcx, Tag>,
442 ) -> InterpResult<'tcx, i64> {
443 let this = self.eval_context_mut();
445 this.check_no_isolation("write")?;
447 let fd = this.read_scalar(fd_op)?.to_i32()?;
448 let buf = this.read_scalar(buf_op)?.not_undef()?;
449 let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
451 // Check that the *entire* buffer is actually valid memory.
452 this.memory.check_ptr_access(
454 Size::from_bytes(count),
455 Align::from_bytes(1).unwrap(),
458 // We cap the number of written bytes to the largest value that we are able to fit in both the
459 // host's and target's `isize`. This saves us from having to handle overflows later.
460 let count = count.min(this.isize_max() as u64).min(isize::max_value() as u64);
462 if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
463 let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
464 let result = file.write(&bytes).map(|c| i64::try_from(c).unwrap());
465 this.try_unwrap_io_result(result)
467 this.handle_not_found()
473 fd_op: OpTy<'tcx, Tag>,
474 offset_op: OpTy<'tcx, Tag>,
475 whence_op: OpTy<'tcx, Tag>,
476 ) -> InterpResult<'tcx, i64> {
477 let this = self.eval_context_mut();
479 this.check_no_isolation("lseek64")?;
481 let fd = this.read_scalar(fd_op)?.to_i32()?;
482 let offset = this.read_scalar(offset_op)?.to_i64()?;
483 let whence = this.read_scalar(whence_op)?.to_i32()?;
485 let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
486 SeekFrom::Start(offset as u64)
487 } else if whence == this.eval_libc_i32("SEEK_CUR")? {
488 SeekFrom::Current(offset)
489 } else if whence == this.eval_libc_i32("SEEK_END")? {
490 SeekFrom::End(offset)
492 let einval = this.eval_libc("EINVAL")?;
493 this.set_last_error(einval)?;
497 if let Some(FileHandle { file, writable: _ }) = this.machine.file_handler.handles.get_mut(&fd) {
498 let result = file.seek(seek_from).map(|offset| offset as i64);
499 this.try_unwrap_io_result(result)
501 this.handle_not_found()
505 fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
506 let this = self.eval_context_mut();
508 this.check_no_isolation("unlink")?;
510 let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
512 let result = remove_file(path).map(|_| 0);
514 this.try_unwrap_io_result(result)
519 target_op: OpTy<'tcx, Tag>,
520 linkpath_op: OpTy<'tcx, Tag>
521 ) -> InterpResult<'tcx, i32> {
522 #[cfg(target_family = "unix")]
523 fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> {
524 std::os::unix::fs::symlink(src, dst)
527 #[cfg(target_family = "windows")]
528 fn create_link(src: PathBuf, dst: PathBuf) -> std::io::Result<()> {
529 use std::os::windows::fs;
531 fs::symlink_dir(src, dst)
533 fs::symlink_file(src, dst)
537 let this = self.eval_context_mut();
539 this.check_no_isolation("symlink")?;
541 let target = this.read_os_str_from_c_str(this.read_scalar(target_op)?.not_undef()?)?.into();
542 let linkpath = this.read_os_str_from_c_str(this.read_scalar(linkpath_op)?.not_undef()?)?.into();
544 this.try_unwrap_io_result(create_link(target, linkpath).map(|_| 0))
549 path_op: OpTy<'tcx, Tag>,
550 buf_op: OpTy<'tcx, Tag>,
551 ) -> InterpResult<'tcx, i32> {
552 let this = self.eval_context_mut();
553 this.check_no_isolation("stat")?;
554 this.assert_platform("macos", "stat");
555 // `stat` always follows symlinks.
556 this.macos_stat_or_lstat(true, path_op, buf_op)
559 // `lstat` is used to get symlink metadata.
562 path_op: OpTy<'tcx, Tag>,
563 buf_op: OpTy<'tcx, Tag>,
564 ) -> InterpResult<'tcx, i32> {
565 let this = self.eval_context_mut();
566 this.check_no_isolation("lstat")?;
567 this.assert_platform("macos", "lstat");
568 this.macos_stat_or_lstat(false, path_op, buf_op)
573 fd_op: OpTy<'tcx, Tag>,
574 buf_op: OpTy<'tcx, Tag>,
575 ) -> InterpResult<'tcx, i32> {
576 let this = self.eval_context_mut();
578 this.check_no_isolation("fstat")?;
579 this.assert_platform("macos", "fstat");
581 let fd = this.read_scalar(fd_op)?.to_i32()?;
583 let metadata = match FileMetadata::from_fd(this, fd)? {
584 Some(metadata) => metadata,
585 None => return Ok(-1),
587 this.macos_stat_write_buf(metadata, buf_op)
592 dirfd_op: OpTy<'tcx, Tag>, // Should be an `int`
593 pathname_op: OpTy<'tcx, Tag>, // Should be a `const char *`
594 flags_op: OpTy<'tcx, Tag>, // Should be an `int`
595 _mask_op: OpTy<'tcx, Tag>, // Should be an `unsigned int`
596 statxbuf_op: OpTy<'tcx, Tag>, // Should be a `struct statx *`
597 ) -> InterpResult<'tcx, i32> {
598 let this = self.eval_context_mut();
600 this.check_no_isolation("statx")?;
601 this.assert_platform("linux", "statx");
603 let statxbuf_scalar = this.read_scalar(statxbuf_op)?.not_undef()?;
604 let pathname_scalar = this.read_scalar(pathname_op)?.not_undef()?;
606 // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
607 if this.is_null(statxbuf_scalar)? || this.is_null(pathname_scalar)? {
608 let efault = this.eval_libc("EFAULT")?;
609 this.set_last_error(efault)?;
613 // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
614 // proper `MemPlace` and then write the results of this function to it. However, the
615 // `syscall` function is untyped. This means that all the `statx` parameters are provided
616 // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
617 // `statxbuf_op` by using the `libc::statx` struct type.
618 let statxbuf_place = {
619 // FIXME: This long path is required because `libc::statx` is an struct and also a
620 // function and `resolve_path` is returning the latter.
622 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])?
623 .monomorphic_ty(*this.tcx);
624 let statxbuf_ty = this.tcx.mk_mut_ptr(statx_ty);
625 let statxbuf_layout = this.layout_of(statxbuf_ty)?;
626 let statxbuf_imm = ImmTy::from_scalar(statxbuf_scalar, statxbuf_layout);
627 this.ref_to_mplace(statxbuf_imm)?
630 let path: PathBuf = this.read_os_str_from_c_str(pathname_scalar)?.into();
631 // `flags` should be a `c_int` but the `syscall` function provides an `isize`.
633 this.read_scalar(flags_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
634 err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
636 let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
637 // `dirfd` should be a `c_int` but the `syscall` function provides an `isize`.
639 this.read_scalar(dirfd_op)?.to_machine_isize(&*this.tcx)?.try_into().map_err(|e| {
640 err_unsup_format!("Failed to convert pointer sized operand to integer: {}", e)
643 // * interpreting `path` as an absolute directory,
644 // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
645 // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
647 // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
648 // found this error, please open an issue reporting it.
650 path.is_absolute() ||
651 dirfd == this.eval_libc_i32("AT_FDCWD")? ||
652 (path.as_os_str().is_empty() && empty_path_flag)
655 "Using statx is only supported with absolute paths, relative paths with the file \
656 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
661 // the `_mask_op` paramter specifies the file information that the caller requested.
662 // However `statx` is allowed to return information that was not requested or to not
663 // return information that was requested. This `mask` represents the information we can
664 // actually provide in any host platform.
666 this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
668 // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
670 let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
672 // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
673 // represented by dirfd, whether it's a directory or otherwise.
674 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
675 FileMetadata::from_fd(this, dirfd)?
677 FileMetadata::from_path(this, path, follow_symlink)?
679 let metadata = match metadata {
680 Some(metadata) => metadata,
681 None => return Ok(-1),
684 // The `mode` field specifies the type of the file and the permissions over the file for
685 // the owner, its group and other users. Given that we can only provide the file type
686 // without using platform specific methods, we only set the bits corresponding to the file
687 // type. This should be an `__u16` but `libc` provides its values as `u32`.
688 let mode: u16 = metadata
692 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
694 // We need to set the corresponding bits of `mask` if the access, creation and modification
695 // times were available. Otherwise we let them be zero.
696 let (access_sec, access_nsec) = metadata.accessed.map(|tup| {
697 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
698 InterpResult::Ok(tup)
699 }).unwrap_or(Ok((0, 0)))?;
701 let (created_sec, created_nsec) = metadata.created.map(|tup| {
702 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
703 InterpResult::Ok(tup)
704 }).unwrap_or(Ok((0, 0)))?;
706 let (modified_sec, modified_nsec) = metadata.modified.map(|tup| {
707 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
708 InterpResult::Ok(tup)
709 }).unwrap_or(Ok((0, 0)))?;
711 let __u32_layout = this.libc_ty_layout("__u32")?;
712 let __u64_layout = this.libc_ty_layout("__u64")?;
713 let __u16_layout = this.libc_ty_layout("__u16")?;
715 // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
716 // zero for the unavailable fields.
718 immty_from_uint_checked(mask, __u32_layout)?, // stx_mask
719 immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize
720 immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
721 immty_from_uint_checked(0u128, __u32_layout)?, // stx_nlink
722 immty_from_uint_checked(0u128, __u32_layout)?, // stx_uid
723 immty_from_uint_checked(0u128, __u32_layout)?, // stx_gid
724 immty_from_uint_checked(mode, __u16_layout)?, // stx_mode
725 immty_from_uint_checked(0u128, __u16_layout)?, // statx padding
726 immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino
727 immty_from_uint_checked(metadata.size, __u64_layout)?, // stx_size
728 immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks
729 immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
730 immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec
731 immty_from_uint_checked(access_nsec, __u32_layout)?, // stx_atime.tv_nsec
732 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
733 immty_from_uint_checked(created_sec, __u64_layout)?, // stx_btime.tv_sec
734 immty_from_uint_checked(created_nsec, __u32_layout)?, // stx_btime.tv_nsec
735 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
736 immty_from_uint_checked(0u128, __u64_layout)?, // stx_ctime.tv_sec
737 immty_from_uint_checked(0u128, __u32_layout)?, // stx_ctime.tv_nsec
738 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
739 immty_from_uint_checked(modified_sec, __u64_layout)?, // stx_mtime.tv_sec
740 immty_from_uint_checked(modified_nsec, __u32_layout)?, // stx_mtime.tv_nsec
741 immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
742 immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_major
743 immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_minor
744 immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_major
745 immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
748 this.write_packed_immediates(statxbuf_place, &imms)?;
755 oldpath_op: OpTy<'tcx, Tag>,
756 newpath_op: OpTy<'tcx, Tag>,
757 ) -> InterpResult<'tcx, i32> {
758 let this = self.eval_context_mut();
760 this.check_no_isolation("rename")?;
762 let oldpath_scalar = this.read_scalar(oldpath_op)?.not_undef()?;
763 let newpath_scalar = this.read_scalar(newpath_op)?.not_undef()?;
765 if this.is_null(oldpath_scalar)? || this.is_null(newpath_scalar)? {
766 let efault = this.eval_libc("EFAULT")?;
767 this.set_last_error(efault)?;
771 let oldpath = this.read_os_str_from_c_str(oldpath_scalar)?;
772 let newpath = this.read_os_str_from_c_str(newpath_scalar)?;
774 let result = rename(oldpath, newpath).map(|_| 0);
776 this.try_unwrap_io_result(result)
781 path_op: OpTy<'tcx, Tag>,
782 mode_op: OpTy<'tcx, Tag>,
783 ) -> InterpResult<'tcx, i32> {
784 let this = self.eval_context_mut();
786 this.check_no_isolation("mkdir")?;
788 let mode = if this.tcx.sess.target.target.target_os.to_lowercase() == "macos" {
789 this.read_scalar(mode_op)?.not_undef()?.to_u16()? as u32
791 this.read_scalar(mode_op)?.to_u32()?
794 let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
796 let mut builder = DirBuilder::new();
797 #[cfg(target_family = "unix")]
799 use std::os::unix::fs::DirBuilderExt;
800 builder.mode(mode.into());
802 #[cfg(not(target_family = "unix"))]
804 let result = builder.create(path).map(|_| 0i32);
806 this.try_unwrap_io_result(result)
811 path_op: OpTy<'tcx, Tag>,
812 ) -> InterpResult<'tcx, i32> {
813 let this = self.eval_context_mut();
815 this.check_no_isolation("rmdir")?;
817 let path = this.read_os_str_from_c_str(this.read_scalar(path_op)?.not_undef()?)?;
819 let result = remove_dir(path).map(|_| 0i32);
821 this.try_unwrap_io_result(result)
824 fn opendir(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
825 let this = self.eval_context_mut();
827 this.check_no_isolation("opendir")?;
829 let name = this.read_os_str_from_c_str(this.read_scalar(name_op)?.not_undef()?)?;
831 let result = read_dir(name);
836 let kind = MiriMemoryKind::Env;
837 let align = this.min_align(size, kind);
838 let dir_ptr = this.memory.allocate(Size::from_bytes(size), align, kind.into());
843 .insert(dir_ptr, dir_iter);
844 if let Some(_) = prev {
845 throw_unsup_format!("The pointer allocated for opendir was already registered by a previous call to opendir")
847 Ok(Scalar::Ptr(dir_ptr))
851 this.set_last_error_from_io_error(e)?;
852 Ok(Scalar::from_int(0, this.memory.pointer_size()))
859 dirp_op: OpTy<'tcx, Tag>,
860 entry_op: OpTy<'tcx, Tag>,
861 result_op: OpTy<'tcx, Tag>,
862 ) -> InterpResult<'tcx, i32> {
863 let this = self.eval_context_mut();
865 this.check_no_isolation("readdir64_r")?;
867 let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
869 let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
870 let dirent64_layout = this.libc_ty_layout("dirent64")?;
871 this.memory.check_ptr_access(
872 Scalar::Ptr(entry_ptr),
873 dirent64_layout.size,
874 dirent64_layout.align.abi,
877 if let Some(dir_iter) = this.machine.dir_handler.streams.get_mut(&dirp) {
878 match dir_iter.next() {
879 Some(Ok(dir_entry)) => {
880 // write into entry, write pointer to result, return 0 on success
881 let entry_place = this.deref_operand(entry_op)?;
882 let ino64_t_layout = this.libc_ty_layout("ino64_t")?;
883 let off64_t_layout = this.libc_ty_layout("off64_t")?;
884 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
885 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
887 let name_offset = dirent64_layout.details.fields.offset(4);
888 let name_ptr = entry_ptr.offset(name_offset, this)?;
891 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
896 let file_name = dir_entry.file_name();
898 let file_name = std::os::unix::ffi::OsStrExt::as_bytes(file_name.as_os_str());
902 let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
905 immty_from_uint_checked(ino, ino64_t_layout)?, // d_ino
906 immty_from_uint_checked(0u128, off64_t_layout)?, // d_off
907 immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
908 immty_from_uint_checked(file_type, c_uchar_layout)?, // d_type
910 this.write_packed_immediates(entry_place, &imms)?;
911 this.memory.write_bytes(Scalar::Ptr(name_ptr), file_name.iter().copied())?;
913 let result_place = this.deref_operand(result_op)?;
914 this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
919 // end of stream: return 0, assign *result=NULL
920 this.write_null(this.deref_operand(result_op)?.into())?;
923 Some(Err(e)) => match e.raw_os_error() {
924 // return positive error number on error
925 Some(error) => Ok(error),
926 None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
930 throw_unsup_format!("The DIR pointer passed to readdir64_r did not come from opendir")
936 dirp_op: OpTy<'tcx, Tag>,
937 entry_op: OpTy<'tcx, Tag>,
938 result_op: OpTy<'tcx, Tag>,
939 ) -> InterpResult<'tcx, i32> {
940 let this = self.eval_context_mut();
942 this.check_no_isolation("readdir_r")?;
944 let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
946 let entry_ptr = this.force_ptr(this.read_scalar(entry_op)?.not_undef()?)?;
947 let dirent_layout = this.libc_ty_layout("dirent")?;
948 this.memory.check_ptr_access(
949 Scalar::Ptr(entry_ptr),
951 dirent_layout.align.abi,
954 if let Some(dir_iter) = this.machine.dir_handler.streams.get_mut(&dirp) {
955 match dir_iter.next() {
956 Some(Ok(dir_entry)) => {
957 // write into entry, write pointer to result, return 0 on success
958 let entry_place = this.deref_operand(entry_op)?;
959 let ino_t_layout = this.libc_ty_layout("ino_t")?;
960 let off_t_layout = this.libc_ty_layout("off_t")?;
961 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
962 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
964 let name_offset = dirent_layout.details.fields.offset(5);
965 let name_ptr = entry_ptr.offset(name_offset, this)?;
968 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
973 let file_name = dir_entry.file_name();
975 let file_name = std::os::unix::ffi::OsStrExt::as_bytes(file_name.as_os_str());
979 let file_type = this.file_type_to_d_type(dir_entry.file_type())? as u128;
982 immty_from_uint_checked(ino, ino_t_layout)?, // d_ino
983 immty_from_uint_checked(0u128, off_t_layout)?, // d_seekoff
984 immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
985 immty_from_uint_checked(file_name.len() as u128, c_ushort_layout)?, // d_namlen
986 immty_from_uint_checked(file_type, c_uchar_layout)?, // d_type
988 this.write_packed_immediates(entry_place, &imms)?;
989 this.memory.write_bytes(Scalar::Ptr(name_ptr), file_name.iter().copied())?;
991 let result_place = this.deref_operand(result_op)?;
992 this.write_scalar(this.read_scalar(entry_op)?, result_place.into())?;
997 // end of stream: return 0, assign *result=NULL
998 this.write_null(this.deref_operand(result_op)?.into())?;
1001 Some(Err(e)) => match e.raw_os_error() {
1002 // return positive error number on error
1003 Some(error) => Ok(error),
1004 None => throw_unsup_format!("The error {} couldn't be converted to a return value", e),
1008 throw_unsup_format!("The DIR pointer passed to readdir_r did not come from opendir")
1012 fn closedir(&mut self, dirp_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1013 let this = self.eval_context_mut();
1015 this.check_no_isolation("closedir")?;
1017 let dirp = this.force_ptr(this.read_scalar(dirp_op)?.not_undef()?)?;
1019 if let Some(dir_iter) = this.machine.dir_handler.streams.remove(&dirp) {
1021 this.memory.deallocate(dirp, None, MiriMemoryKind::Env.into())?;
1024 this.handle_not_found()
1029 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1030 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1032 fn extract_sec_and_nsec<'tcx>(
1033 time: std::io::Result<SystemTime>
1034 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1035 time.ok().map(|time| {
1036 let duration = system_time_to_duration(&time)?;
1037 Ok((duration.as_secs(), duration.subsec_nanos()))
1041 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1043 struct FileMetadata {
1046 created: Option<(u64, u32)>,
1047 accessed: Option<(u64, u32)>,
1048 modified: Option<(u64, u32)>,
1052 fn from_path<'tcx, 'mir>(
1053 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1055 follow_symlink: bool
1056 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1057 let metadata = if follow_symlink {
1058 std::fs::metadata(path)
1060 std::fs::symlink_metadata(path)
1063 FileMetadata::from_meta(ecx, metadata)
1066 fn from_fd<'tcx, 'mir>(
1067 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1069 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1070 let option = ecx.machine.file_handler.handles.get(&fd);
1071 let file = match option {
1072 Some(FileHandle { file, writable: _ }) => file,
1073 None => return ecx.handle_not_found().map(|_: i32| None),
1075 let metadata = file.metadata();
1077 FileMetadata::from_meta(ecx, metadata)
1080 fn from_meta<'tcx, 'mir>(
1081 ecx: &mut MiriEvalContext<'mir, 'tcx>,
1082 metadata: Result<std::fs::Metadata, std::io::Error>,
1083 ) -> InterpResult<'tcx, Option<FileMetadata>> {
1084 let metadata = match metadata {
1085 Ok(metadata) => metadata,
1087 ecx.set_last_error_from_io_error(e)?;
1092 let file_type = metadata.file_type();
1094 let mode_name = if file_type.is_file() {
1096 } else if file_type.is_dir() {
1102 let mode = ecx.eval_libc(mode_name)?;
1104 let size = metadata.len();
1106 let created = extract_sec_and_nsec(metadata.created())?;
1107 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1108 let modified = extract_sec_and_nsec(metadata.modified())?;
1110 // FIXME: Provide more fields using platform specific methods.
1111 Ok(Some(FileMetadata { mode, size, created, accessed, modified }))