1use std::borrow::Cow;
4use std::fs::{
5 DirBuilder, File, FileType, OpenOptions, ReadDir, TryLockError, read_dir, remove_dir,
6 remove_file, rename,
7};
8use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
9use std::path::{Path, PathBuf};
10use std::time::SystemTime;
11
12use rustc_abi::Size;
13use rustc_data_structures::fx::FxHashMap;
14
15use self::shims::time::system_time_to_duration;
16use crate::shims::files::FileHandle;
17use crate::shims::os_str::bytes_to_os_str;
18use crate::shims::sig::check_min_vararg_count;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22impl UnixFileDescription for FileHandle {
23 fn pread<'tcx>(
24 &self,
25 communicate_allowed: bool,
26 offset: u64,
27 ptr: Pointer,
28 len: usize,
29 ecx: &mut MiriInterpCx<'tcx>,
30 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
31 ) -> InterpResult<'tcx> {
32 assert!(communicate_allowed, "isolation should have prevented even opening a file");
33 let mut bytes = vec![0; len];
34 let file = &mut &self.file;
38 let mut f = || {
39 let cursor_pos = file.stream_position()?;
40 file.seek(SeekFrom::Start(offset))?;
41 let res = file.read(&mut bytes);
42 file.seek(SeekFrom::Start(cursor_pos))
44 .expect("failed to restore file position, this shouldn't be possible");
45 res
46 };
47 let result = match f() {
48 Ok(read_size) => {
49 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
53 Ok(read_size)
54 }
55 Err(e) => Err(IoError::HostError(e)),
56 };
57 finish.call(ecx, result)
58 }
59
60 fn pwrite<'tcx>(
61 &self,
62 communicate_allowed: bool,
63 ptr: Pointer,
64 len: usize,
65 offset: u64,
66 ecx: &mut MiriInterpCx<'tcx>,
67 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
68 ) -> InterpResult<'tcx> {
69 assert!(communicate_allowed, "isolation should have prevented even opening a file");
70 let file = &mut &self.file;
74 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
75 let mut f = || {
76 let cursor_pos = file.stream_position()?;
77 file.seek(SeekFrom::Start(offset))?;
78 let res = file.write(bytes);
79 file.seek(SeekFrom::Start(cursor_pos))
81 .expect("failed to restore file position, this shouldn't be possible");
82 res
83 };
84 let result = f();
85 finish.call(ecx, result.map_err(IoError::HostError))
86 }
87
88 fn flock<'tcx>(
89 &self,
90 communicate_allowed: bool,
91 op: FlockOp,
92 ) -> InterpResult<'tcx, io::Result<()>> {
93 assert!(communicate_allowed, "isolation should have prevented even opening a file");
94
95 use FlockOp::*;
96 let (res, nonblocking) = match op {
98 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
99 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
100 Unlock => {
101 return interp_ok(self.file.unlock());
102 }
103 };
104
105 match res {
106 Ok(()) => interp_ok(Ok(())),
107 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
108 Err(TryLockError::WouldBlock) =>
109 if nonblocking {
110 interp_ok(Err(ErrorKind::WouldBlock.into()))
111 } else {
112 throw_unsup_format!("blocking `flock` is not currently supported");
113 },
114 }
115 }
116}
117
118impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
119trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
120 fn macos_fbsd_solarish_write_stat_buf(
121 &mut self,
122 metadata: FileMetadata,
123 buf_op: &OpTy<'tcx>,
124 ) -> InterpResult<'tcx, i32> {
125 let this = self.eval_context_mut();
126
127 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
128 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
129 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
130 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
131
132 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
133 this.write_int_fields_named(
134 &[
135 ("st_dev", metadata.dev.into()),
136 ("st_mode", mode.try_into().unwrap()),
137 ("st_nlink", 0),
138 ("st_ino", 0),
139 ("st_uid", metadata.uid.into()),
140 ("st_gid", metadata.gid.into()),
141 ("st_rdev", 0),
142 ("st_atime", access_sec.into()),
143 ("st_mtime", modified_sec.into()),
144 ("st_ctime", 0),
145 ("st_size", metadata.size.into()),
146 ("st_blocks", 0),
147 ("st_blksize", 0),
148 ],
149 &buf,
150 )?;
151
152 if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
153 this.write_int_fields_named(
154 &[
155 ("st_atime_nsec", access_nsec.into()),
156 ("st_mtime_nsec", modified_nsec.into()),
157 ("st_ctime_nsec", 0),
158 ("st_birthtime", created_sec.into()),
159 ("st_birthtime_nsec", created_nsec.into()),
160 ("st_flags", 0),
161 ("st_gen", 0),
162 ],
163 &buf,
164 )?;
165 }
166
167 if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
168 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
169 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
171 }
172
173 interp_ok(0)
174 }
175
176 fn file_type_to_d_type(
177 &mut self,
178 file_type: std::io::Result<FileType>,
179 ) -> InterpResult<'tcx, i32> {
180 #[cfg(unix)]
181 use std::os::unix::fs::FileTypeExt;
182
183 let this = self.eval_context_mut();
184 match file_type {
185 Ok(file_type) => {
186 match () {
187 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
188 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
189 _ if file_type.is_symlink() =>
190 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
191 #[cfg(unix)]
193 _ if file_type.is_block_device() =>
194 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
195 #[cfg(unix)]
196 _ if file_type.is_char_device() =>
197 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
198 #[cfg(unix)]
199 _ if file_type.is_fifo() =>
200 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
201 #[cfg(unix)]
202 _ if file_type.is_socket() =>
203 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
204 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
206 }
207 }
208 Err(_) => {
209 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
211 }
212 }
213 }
214}
215
216#[derive(Debug)]
218struct OpenDir {
219 read_dir: ReadDir,
221 entry: Option<Pointer>,
224}
225
226impl OpenDir {
227 fn new(read_dir: ReadDir) -> Self {
228 Self { read_dir, entry: None }
229 }
230}
231
232#[derive(Debug)]
236pub struct DirTable {
237 streams: FxHashMap<u64, OpenDir>,
247 next_id: u64,
249}
250
251impl DirTable {
252 #[expect(clippy::arithmetic_side_effects)]
253 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
254 let id = self.next_id;
255 self.next_id += 1;
256 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
257 id
258 }
259}
260
261impl Default for DirTable {
262 fn default() -> DirTable {
263 DirTable {
264 streams: FxHashMap::default(),
265 next_id: 1,
267 }
268 }
269}
270
271impl VisitProvenance for DirTable {
272 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
273 let DirTable { streams, next_id: _ } = self;
274
275 for dir in streams.values() {
276 dir.entry.visit_provenance(visit);
277 }
278 }
279}
280
281fn maybe_sync_file(
282 file: &File,
283 writable: bool,
284 operation: fn(&File) -> std::io::Result<()>,
285) -> std::io::Result<i32> {
286 if !writable && cfg!(windows) {
287 Ok(0i32)
291 } else {
292 let result = operation(file);
293 result.map(|_| 0i32)
294 }
295}
296
297impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
298pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
299 fn open(
300 &mut self,
301 path_raw: &OpTy<'tcx>,
302 flag: &OpTy<'tcx>,
303 varargs: &[OpTy<'tcx>],
304 ) -> InterpResult<'tcx, Scalar> {
305 let this = self.eval_context_mut();
306
307 let path_raw = this.read_pointer(path_raw)?;
308 let path = this.read_path_from_c_str(path_raw)?;
309 let flag = this.read_scalar(flag)?.to_i32()?;
310
311 let mut options = OpenOptions::new();
312
313 let o_rdonly = this.eval_libc_i32("O_RDONLY");
314 let o_wronly = this.eval_libc_i32("O_WRONLY");
315 let o_rdwr = this.eval_libc_i32("O_RDWR");
316 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
320 throw_unsup_format!("access mode flags on this target are unsupported");
321 }
322 let mut writable = true;
323
324 let access_mode = flag & 0b11;
326
327 if access_mode == o_rdonly {
328 writable = false;
329 options.read(true);
330 } else if access_mode == o_wronly {
331 options.write(true);
332 } else if access_mode == o_rdwr {
333 options.read(true).write(true);
334 } else {
335 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
336 }
337 let mut mirror = access_mode;
341
342 let o_append = this.eval_libc_i32("O_APPEND");
343 if flag & o_append == o_append {
344 options.append(true);
345 mirror |= o_append;
346 }
347 let o_trunc = this.eval_libc_i32("O_TRUNC");
348 if flag & o_trunc == o_trunc {
349 options.truncate(true);
350 mirror |= o_trunc;
351 }
352 let o_creat = this.eval_libc_i32("O_CREAT");
353 if flag & o_creat == o_creat {
354 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
358 let mode = this.read_scalar(mode)?.to_u32()?;
359
360 #[cfg(unix)]
361 {
362 use std::os::unix::fs::OpenOptionsExt;
364 options.mode(mode);
365 }
366 #[cfg(not(unix))]
367 {
368 if mode != 0o666 {
370 throw_unsup_format!(
371 "non-default mode 0o{:o} is not supported on non-Unix hosts",
372 mode
373 );
374 }
375 }
376
377 mirror |= o_creat;
378
379 let o_excl = this.eval_libc_i32("O_EXCL");
380 if flag & o_excl == o_excl {
381 mirror |= o_excl;
382 options.create_new(true);
383 } else {
384 options.create(true);
385 }
386 }
387 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
388 if flag & o_cloexec == o_cloexec {
389 mirror |= o_cloexec;
392 }
393 if this.tcx.sess.target.os == "linux" {
394 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
395 if flag & o_tmpfile == o_tmpfile {
396 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
398 }
399 }
400
401 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
402 if flag & o_nofollow == o_nofollow {
403 #[cfg(unix)]
404 {
405 use std::os::unix::fs::OpenOptionsExt;
406 options.custom_flags(libc::O_NOFOLLOW);
407 }
408 #[cfg(not(unix))]
412 {
413 if path.is_symlink() {
416 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
417 }
418 }
419 mirror |= o_nofollow;
420 }
421
422 if flag != mirror {
425 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
426 }
427
428 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
430 this.reject_in_isolation("`open`", reject_with)?;
431 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
432 }
433
434 let fd = options
435 .open(path)
436 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
437
438 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
439 }
440
441 fn lseek64(
442 &mut self,
443 fd_num: i32,
444 offset: i128,
445 whence: i32,
446 dest: &MPlaceTy<'tcx>,
447 ) -> InterpResult<'tcx> {
448 let this = self.eval_context_mut();
449
450 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
453 if offset < 0 {
454 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
456 } else {
457 SeekFrom::Start(u64::try_from(offset).unwrap())
458 }
459 } else if whence == this.eval_libc_i32("SEEK_CUR") {
460 SeekFrom::Current(i64::try_from(offset).unwrap())
461 } else if whence == this.eval_libc_i32("SEEK_END") {
462 SeekFrom::End(i64::try_from(offset).unwrap())
463 } else {
464 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
465 };
466
467 let communicate = this.machine.communicate();
468
469 let Some(fd) = this.machine.fds.get(fd_num) else {
470 return this.set_last_error_and_return(LibcError("EBADF"), dest);
471 };
472 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
473 drop(fd);
474
475 let result = this.try_unwrap_io_result(result)?;
476 this.write_int(result, dest)?;
477 interp_ok(())
478 }
479
480 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
481 let this = self.eval_context_mut();
482
483 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
484
485 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
487 this.reject_in_isolation("`unlink`", reject_with)?;
488 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
489 }
490
491 let result = remove_file(path).map(|_| 0);
492 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
493 }
494
495 fn symlink(
496 &mut self,
497 target_op: &OpTy<'tcx>,
498 linkpath_op: &OpTy<'tcx>,
499 ) -> InterpResult<'tcx, Scalar> {
500 #[cfg(unix)]
501 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
502 std::os::unix::fs::symlink(src, dst)
503 }
504
505 #[cfg(windows)]
506 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
507 use std::os::windows::fs;
508 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
509 }
510
511 let this = self.eval_context_mut();
512 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
513 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
514
515 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
517 this.reject_in_isolation("`symlink`", reject_with)?;
518 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
519 }
520
521 let result = create_link(&target, &linkpath).map(|_| 0);
522 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
523 }
524
525 fn macos_fbsd_solarish_stat(
526 &mut self,
527 path_op: &OpTy<'tcx>,
528 buf_op: &OpTy<'tcx>,
529 ) -> InterpResult<'tcx, Scalar> {
530 let this = self.eval_context_mut();
531
532 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
533 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
534 }
535
536 let path_scalar = this.read_pointer(path_op)?;
537 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
538
539 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
541 this.reject_in_isolation("`stat`", reject_with)?;
542 return this.set_last_error_and_return_i32(LibcError("EACCES"));
543 }
544
545 let metadata = match FileMetadata::from_path(this, &path, true)? {
547 Ok(metadata) => metadata,
548 Err(err) => return this.set_last_error_and_return_i32(err),
549 };
550
551 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
552 }
553
554 fn macos_fbsd_solarish_lstat(
556 &mut self,
557 path_op: &OpTy<'tcx>,
558 buf_op: &OpTy<'tcx>,
559 ) -> InterpResult<'tcx, Scalar> {
560 let this = self.eval_context_mut();
561
562 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
563 panic!(
564 "`macos_fbsd_solaris_lstat` should not be called on {}",
565 this.tcx.sess.target.os
566 );
567 }
568
569 let path_scalar = this.read_pointer(path_op)?;
570 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
571
572 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
574 this.reject_in_isolation("`lstat`", reject_with)?;
575 return this.set_last_error_and_return_i32(LibcError("EACCES"));
576 }
577
578 let metadata = match FileMetadata::from_path(this, &path, false)? {
579 Ok(metadata) => metadata,
580 Err(err) => return this.set_last_error_and_return_i32(err),
581 };
582
583 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
584 }
585
586 fn macos_fbsd_solarish_fstat(
587 &mut self,
588 fd_op: &OpTy<'tcx>,
589 buf_op: &OpTy<'tcx>,
590 ) -> InterpResult<'tcx, Scalar> {
591 let this = self.eval_context_mut();
592
593 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
594 panic!(
595 "`macos_fbsd_solaris_fstat` should not be called on {}",
596 this.tcx.sess.target.os
597 );
598 }
599
600 let fd = this.read_scalar(fd_op)?.to_i32()?;
601
602 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
604 this.reject_in_isolation("`fstat`", reject_with)?;
605 return this.set_last_error_and_return_i32(LibcError("EBADF"));
607 }
608
609 let metadata = match FileMetadata::from_fd_num(this, fd)? {
610 Ok(metadata) => metadata,
611 Err(err) => return this.set_last_error_and_return_i32(err),
612 };
613 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
614 }
615
616 fn linux_statx(
617 &mut self,
618 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
624 let this = self.eval_context_mut();
625
626 this.assert_target_os("linux", "statx");
627
628 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
629 let pathname_ptr = this.read_pointer(pathname_op)?;
630 let flags = this.read_scalar(flags_op)?.to_i32()?;
631 let _mask = this.read_scalar(mask_op)?.to_u32()?;
632 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
633
634 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
636 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
637 }
638
639 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
640
641 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
642 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
644 let empty_path_flag = flags & at_empty_path == at_empty_path;
645 if !(path.is_absolute()
653 || dirfd == this.eval_libc_i32("AT_FDCWD")
654 || (path.as_os_str().is_empty() && empty_path_flag))
655 {
656 throw_unsup_format!(
657 "using statx is only supported with absolute paths, relative paths with the file \
658 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
659 file descriptor"
660 )
661 }
662
663 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
665 this.reject_in_isolation("`statx`", reject_with)?;
666 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
667 LibcError("EACCES")
670 } else {
671 assert!(empty_path_flag);
675 LibcError("EBADF")
676 };
677 return this.set_last_error_and_return_i32(ecode);
678 }
679
680 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
685
686 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
689
690 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
693 FileMetadata::from_fd_num(this, dirfd)?
694 } else {
695 FileMetadata::from_path(this, &path, follow_symlink)?
696 };
697 let metadata = match metadata {
698 Ok(metadata) => metadata,
699 Err(err) => return this.set_last_error_and_return_i32(err),
700 };
701
702 let mode: u16 = metadata
707 .mode
708 .to_u32()?
709 .try_into()
710 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
711
712 let (access_sec, access_nsec) = metadata
715 .accessed
716 .map(|tup| {
717 mask |= this.eval_libc_u32("STATX_ATIME");
718 interp_ok(tup)
719 })
720 .unwrap_or_else(|| interp_ok((0, 0)))?;
721
722 let (created_sec, created_nsec) = metadata
723 .created
724 .map(|tup| {
725 mask |= this.eval_libc_u32("STATX_BTIME");
726 interp_ok(tup)
727 })
728 .unwrap_or_else(|| interp_ok((0, 0)))?;
729
730 let (modified_sec, modified_nsec) = metadata
731 .modified
732 .map(|tup| {
733 mask |= this.eval_libc_u32("STATX_MTIME");
734 interp_ok(tup)
735 })
736 .unwrap_or_else(|| interp_ok((0, 0)))?;
737
738 this.write_int_fields_named(
740 &[
741 ("stx_mask", mask.into()),
742 ("stx_blksize", 0),
743 ("stx_attributes", 0),
744 ("stx_nlink", 0),
745 ("stx_uid", 0),
746 ("stx_gid", 0),
747 ("stx_mode", mode.into()),
748 ("stx_ino", 0),
749 ("stx_size", metadata.size.into()),
750 ("stx_blocks", 0),
751 ("stx_attributes_mask", 0),
752 ("stx_rdev_major", 0),
753 ("stx_rdev_minor", 0),
754 ("stx_dev_major", 0),
755 ("stx_dev_minor", 0),
756 ],
757 &statxbuf,
758 )?;
759 #[rustfmt::skip]
760 this.write_int_fields_named(
761 &[
762 ("tv_sec", access_sec.into()),
763 ("tv_nsec", access_nsec.into()),
764 ],
765 &this.project_field_named(&statxbuf, "stx_atime")?,
766 )?;
767 #[rustfmt::skip]
768 this.write_int_fields_named(
769 &[
770 ("tv_sec", created_sec.into()),
771 ("tv_nsec", created_nsec.into()),
772 ],
773 &this.project_field_named(&statxbuf, "stx_btime")?,
774 )?;
775 #[rustfmt::skip]
776 this.write_int_fields_named(
777 &[
778 ("tv_sec", 0.into()),
779 ("tv_nsec", 0.into()),
780 ],
781 &this.project_field_named(&statxbuf, "stx_ctime")?,
782 )?;
783 #[rustfmt::skip]
784 this.write_int_fields_named(
785 &[
786 ("tv_sec", modified_sec.into()),
787 ("tv_nsec", modified_nsec.into()),
788 ],
789 &this.project_field_named(&statxbuf, "stx_mtime")?,
790 )?;
791
792 interp_ok(Scalar::from_i32(0))
793 }
794
795 fn rename(
796 &mut self,
797 oldpath_op: &OpTy<'tcx>,
798 newpath_op: &OpTy<'tcx>,
799 ) -> InterpResult<'tcx, Scalar> {
800 let this = self.eval_context_mut();
801
802 let oldpath_ptr = this.read_pointer(oldpath_op)?;
803 let newpath_ptr = this.read_pointer(newpath_op)?;
804
805 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
806 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
807 }
808
809 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
810 let newpath = this.read_path_from_c_str(newpath_ptr)?;
811
812 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
814 this.reject_in_isolation("`rename`", reject_with)?;
815 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
816 }
817
818 let result = rename(oldpath, newpath).map(|_| 0);
819
820 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
821 }
822
823 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
824 let this = self.eval_context_mut();
825
826 #[cfg_attr(not(unix), allow(unused_variables))]
827 let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
828 u32::from(this.read_scalar(mode_op)?.to_u16()?)
829 } else {
830 this.read_scalar(mode_op)?.to_u32()?
831 };
832
833 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
834
835 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
837 this.reject_in_isolation("`mkdir`", reject_with)?;
838 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
839 }
840
841 #[cfg_attr(not(unix), allow(unused_mut))]
842 let mut builder = DirBuilder::new();
843
844 #[cfg(unix)]
847 {
848 use std::os::unix::fs::DirBuilderExt;
849 builder.mode(mode);
850 }
851
852 let result = builder.create(path).map(|_| 0i32);
853
854 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
855 }
856
857 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
858 let this = self.eval_context_mut();
859
860 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
861
862 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
864 this.reject_in_isolation("`rmdir`", reject_with)?;
865 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
866 }
867
868 let result = remove_dir(path).map(|_| 0i32);
869
870 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
871 }
872
873 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
874 let this = self.eval_context_mut();
875
876 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
877
878 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
880 this.reject_in_isolation("`opendir`", reject_with)?;
881 this.set_last_error(LibcError("EACCES"))?;
882 return interp_ok(Scalar::null_ptr(this));
883 }
884
885 let result = read_dir(name);
886
887 match result {
888 Ok(dir_iter) => {
889 let id = this.machine.dirs.insert_new(dir_iter);
890
891 interp_ok(Scalar::from_target_usize(id, this))
895 }
896 Err(e) => {
897 this.set_last_error(e)?;
898 interp_ok(Scalar::null_ptr(this))
899 }
900 }
901 }
902
903 fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
904 let this = self.eval_context_mut();
905
906 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos" | "freebsd") {
907 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
908 }
909
910 let dirp = this.read_target_usize(dirp_op)?;
911
912 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
914 this.reject_in_isolation("`readdir`", reject_with)?;
915 this.set_last_error(LibcError("EBADF"))?;
916 return interp_ok(Scalar::null_ptr(this));
917 }
918
919 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
920 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
921 })?;
922
923 let entry = match open_dir.read_dir.next() {
924 Some(Ok(dir_entry)) => {
925 #[cfg(unix)]
928 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
929 #[cfg(not(unix))]
930 let ino = 0u64;
931
932 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
966 let name_len = u64::try_from(name_bytes.len()).unwrap();
967
968 let dirent_layout = this.libc_ty_layout(dirent_type);
969 let fields = &dirent_layout.fields;
970 let last_field = fields.count().strict_sub(1);
971 let d_name_offset = fields.offset(last_field).bytes();
972 let size = d_name_offset.strict_add(name_len);
973
974 let entry = this.allocate_ptr(
975 Size::from_bytes(size),
976 dirent_layout.align.abi,
977 MiriMemoryKind::Runtime.into(),
978 AllocInit::Uninit,
979 )?;
980 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
981
982 let ino_name =
984 if this.tcx.sess.target.os == "freebsd" { "d_fileno" } else { "d_ino" };
985 this.write_int_fields_named(
986 &[(ino_name, ino.into()), ("d_reclen", size.into())],
987 &entry,
988 )?;
989
990 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
992 this.write_null(&d_off)?;
993 }
994
995 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
996 this.write_int(name_len.strict_sub(1), &d_namlen)?;
997 }
998
999 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1000 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1001 this.write_int(file_type, &d_type)?;
1002 }
1003
1004 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1006 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1007
1008 Some(entry.ptr())
1009 }
1010 None => {
1011 None
1013 }
1014 Some(Err(e)) => {
1015 this.set_last_error(e)?;
1016 None
1017 }
1018 };
1019
1020 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1021 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1022 if let Some(old_entry) = old_entry {
1023 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1024 }
1025
1026 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1027 }
1028
1029 fn macos_fbsd_readdir_r(
1030 &mut self,
1031 dirp_op: &OpTy<'tcx>,
1032 entry_op: &OpTy<'tcx>,
1033 result_op: &OpTy<'tcx>,
1034 ) -> InterpResult<'tcx, Scalar> {
1035 let this = self.eval_context_mut();
1036
1037 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1038 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1039 }
1040
1041 let dirp = this.read_target_usize(dirp_op)?;
1042 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1043
1044 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1046 this.reject_in_isolation("`readdir_r`", reject_with)?;
1047 return interp_ok(this.eval_libc("EBADF"));
1049 }
1050
1051 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1052 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1053 })?;
1054 interp_ok(match open_dir.read_dir.next() {
1055 Some(Ok(dir_entry)) => {
1056 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1071 let name_place = this.project_field_named(&entry_place, "d_name")?;
1072
1073 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1075 &file_name,
1076 name_place.ptr(),
1077 name_place.layout.size.bytes(),
1078 )?;
1079 let file_name_len = file_name_buf_len.strict_sub(1);
1080 if !name_fits {
1081 throw_unsup_format!(
1082 "a directory entry had a name too large to fit in libc::dirent"
1083 );
1084 }
1085
1086 #[cfg(unix)]
1089 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1090 #[cfg(not(unix))]
1091 let ino = 0u64;
1092
1093 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1094
1095 this.write_int_fields_named(
1097 &[
1098 ("d_reclen", 0),
1099 ("d_namlen", file_name_len.into()),
1100 ("d_type", file_type.into()),
1101 ],
1102 &entry_place,
1103 )?;
1104 match &*this.tcx.sess.target.os {
1106 "macos" => {
1107 #[rustfmt::skip]
1108 this.write_int_fields_named(
1109 &[
1110 ("d_ino", ino.into()),
1111 ("d_seekoff", 0),
1112 ],
1113 &entry_place,
1114 )?;
1115 }
1116 "freebsd" => {
1117 #[rustfmt::skip]
1118 this.write_int_fields_named(
1119 &[
1120 ("d_fileno", ino.into()),
1121 ("d_off", 0),
1122 ],
1123 &entry_place,
1124 )?;
1125 }
1126 _ => unreachable!(),
1127 }
1128 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1129
1130 Scalar::from_i32(0)
1131 }
1132 None => {
1133 this.write_null(&result_place)?;
1135 Scalar::from_i32(0)
1136 }
1137 Some(Err(e)) => {
1138 this.io_error_to_errnum(e)?
1140 }
1141 })
1142 }
1143
1144 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1145 let this = self.eval_context_mut();
1146
1147 let dirp = this.read_target_usize(dirp_op)?;
1148
1149 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1151 this.reject_in_isolation("`closedir`", reject_with)?;
1152 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1153 }
1154
1155 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1156 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1157 };
1158 if let Some(entry) = open_dir.entry.take() {
1159 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1160 }
1161 drop(open_dir);
1163
1164 interp_ok(Scalar::from_i32(0))
1165 }
1166
1167 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1168 let this = self.eval_context_mut();
1169
1170 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1172 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1173 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1175 }
1176
1177 let Some(fd) = this.machine.fds.get(fd_num) else {
1178 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1179 };
1180
1181 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1183 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1184 })?;
1185
1186 if file.writable {
1187 if let Ok(length) = length.try_into() {
1188 let result = file.file.set_len(length);
1189 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1190 interp_ok(Scalar::from_i32(result))
1191 } else {
1192 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1193 }
1194 } else {
1195 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1197 }
1198 }
1199
1200 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1201 let this = self.eval_context_mut();
1207
1208 let fd = this.read_scalar(fd_op)?.to_i32()?;
1209
1210 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1212 this.reject_in_isolation("`fsync`", reject_with)?;
1213 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1215 }
1216
1217 self.ffullsync_fd(fd)
1218 }
1219
1220 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1221 let this = self.eval_context_mut();
1222 let Some(fd) = this.machine.fds.get(fd_num) else {
1223 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1224 };
1225 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1227 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1228 })?;
1229 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1230 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1231 }
1232
1233 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1234 let this = self.eval_context_mut();
1235
1236 let fd = this.read_scalar(fd_op)?.to_i32()?;
1237
1238 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1240 this.reject_in_isolation("`fdatasync`", reject_with)?;
1241 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1243 }
1244
1245 let Some(fd) = this.machine.fds.get(fd) else {
1246 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1247 };
1248 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1250 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1251 })?;
1252 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1253 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1254 }
1255
1256 fn sync_file_range(
1257 &mut self,
1258 fd_op: &OpTy<'tcx>,
1259 offset_op: &OpTy<'tcx>,
1260 nbytes_op: &OpTy<'tcx>,
1261 flags_op: &OpTy<'tcx>,
1262 ) -> InterpResult<'tcx, Scalar> {
1263 let this = self.eval_context_mut();
1264
1265 let fd = this.read_scalar(fd_op)?.to_i32()?;
1266 let offset = this.read_scalar(offset_op)?.to_i64()?;
1267 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1268 let flags = this.read_scalar(flags_op)?.to_i32()?;
1269
1270 if offset < 0 || nbytes < 0 {
1271 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1272 }
1273 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1274 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1275 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1276 if flags & allowed_flags != flags {
1277 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1278 }
1279
1280 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1282 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1283 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1285 }
1286
1287 let Some(fd) = this.machine.fds.get(fd) else {
1288 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1289 };
1290 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1292 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1293 })?;
1294 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1295 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1296 }
1297
1298 fn readlink(
1299 &mut self,
1300 pathname_op: &OpTy<'tcx>,
1301 buf_op: &OpTy<'tcx>,
1302 bufsize_op: &OpTy<'tcx>,
1303 ) -> InterpResult<'tcx, i64> {
1304 let this = self.eval_context_mut();
1305
1306 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1307 let buf = this.read_pointer(buf_op)?;
1308 let bufsize = this.read_target_usize(bufsize_op)?;
1309
1310 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1312 this.reject_in_isolation("`readlink`", reject_with)?;
1313 this.set_last_error(LibcError("EACCES"))?;
1314 return interp_ok(-1);
1315 }
1316
1317 let result = std::fs::read_link(pathname);
1318 match result {
1319 Ok(resolved) => {
1320 let resolved = this.convert_path(
1324 Cow::Borrowed(resolved.as_ref()),
1325 crate::shims::os_str::PathConversion::HostToTarget,
1326 );
1327 let mut path_bytes = resolved.as_encoded_bytes();
1328 let bufsize: usize = bufsize.try_into().unwrap();
1329 if path_bytes.len() > bufsize {
1330 path_bytes = &path_bytes[..bufsize]
1331 }
1332 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1333 interp_ok(path_bytes.len().try_into().unwrap())
1334 }
1335 Err(e) => {
1336 this.set_last_error(e)?;
1337 interp_ok(-1)
1338 }
1339 }
1340 }
1341
1342 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1343 let this = self.eval_context_mut();
1344 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1347 let error = if let Some(fd) = this.machine.fds.get(fd) {
1348 if fd.is_tty(this.machine.communicate()) {
1349 return interp_ok(Scalar::from_i32(1));
1350 } else {
1351 LibcError("ENOTTY")
1352 }
1353 } else {
1354 LibcError("EBADF")
1356 };
1357 this.set_last_error(error)?;
1358 interp_ok(Scalar::from_i32(0))
1359 }
1360
1361 fn realpath(
1362 &mut self,
1363 path_op: &OpTy<'tcx>,
1364 processed_path_op: &OpTy<'tcx>,
1365 ) -> InterpResult<'tcx, Scalar> {
1366 let this = self.eval_context_mut();
1367 this.assert_target_os_is_unix("realpath");
1368
1369 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1370 let processed_ptr = this.read_pointer(processed_path_op)?;
1371
1372 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1374 this.reject_in_isolation("`realpath`", reject_with)?;
1375 this.set_last_error(LibcError("EACCES"))?;
1376 return interp_ok(Scalar::from_target_usize(0, this));
1377 }
1378
1379 let result = std::fs::canonicalize(pathname);
1380 match result {
1381 Ok(resolved) => {
1382 let path_max = this
1383 .eval_libc_i32("PATH_MAX")
1384 .try_into()
1385 .expect("PATH_MAX does not fit in u64");
1386 let dest = if this.ptr_is_null(processed_ptr)? {
1387 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1397 } else {
1398 let (wrote_path, _) =
1399 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1400
1401 if !wrote_path {
1402 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1406 return interp_ok(Scalar::from_target_usize(0, this));
1407 }
1408 processed_ptr
1409 };
1410
1411 interp_ok(Scalar::from_maybe_pointer(dest, this))
1412 }
1413 Err(e) => {
1414 this.set_last_error(e)?;
1415 interp_ok(Scalar::from_target_usize(0, this))
1416 }
1417 }
1418 }
1419 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1420 use rand::seq::IndexedRandom;
1421
1422 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1424
1425 let this = self.eval_context_mut();
1426 this.assert_target_os_is_unix("mkstemp");
1427
1428 let max_attempts = this.eval_libc_u32("TMP_MAX");
1438
1439 let template_ptr = this.read_pointer(template_op)?;
1442 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1443 let template_bytes = template.as_mut_slice();
1444
1445 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1447 this.reject_in_isolation("`mkstemp`", reject_with)?;
1448 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1449 }
1450
1451 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1453
1454 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1459 let end_pos = template_bytes.len();
1460 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1461
1462 if last_six_char_bytes != suffix_bytes {
1464 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1465 }
1466
1467 const SUBSTITUTIONS: &[char; 62] = &[
1471 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1472 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1473 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1474 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1475 ];
1476
1477 let mut fopts = OpenOptions::new();
1480 fopts.read(true).write(true).create_new(true);
1481
1482 #[cfg(unix)]
1483 {
1484 use std::os::unix::fs::OpenOptionsExt;
1485 fopts.mode(0o600);
1487 fopts.custom_flags(libc::O_EXCL);
1488 }
1489 #[cfg(windows)]
1490 {
1491 use std::os::windows::fs::OpenOptionsExt;
1492 fopts.share_mode(0);
1494 }
1495
1496 for _ in 0..max_attempts {
1498 let rng = this.machine.rng.get_mut();
1499
1500 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1502
1503 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1505
1506 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1508
1509 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1511
1512 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1513
1514 let file = fopts.open(possibly_unique);
1515
1516 match file {
1517 Ok(f) => {
1518 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1519 return interp_ok(Scalar::from_i32(fd));
1520 }
1521 Err(e) =>
1522 match e.kind() {
1523 ErrorKind::AlreadyExists => continue,
1525 _ => {
1527 return this.set_last_error_and_return_i32(e);
1530 }
1531 },
1532 }
1533 }
1534
1535 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1537 }
1538}
1539
1540fn extract_sec_and_nsec<'tcx>(
1544 time: std::io::Result<SystemTime>,
1545) -> InterpResult<'tcx, Option<(u64, u32)>> {
1546 match time.ok() {
1547 Some(time) => {
1548 let duration = system_time_to_duration(&time)?;
1549 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1550 }
1551 None => interp_ok(None),
1552 }
1553}
1554
1555struct FileMetadata {
1558 mode: Scalar,
1559 size: u64,
1560 created: Option<(u64, u32)>,
1561 accessed: Option<(u64, u32)>,
1562 modified: Option<(u64, u32)>,
1563 dev: u64,
1564 uid: u32,
1565 gid: u32,
1566}
1567
1568impl FileMetadata {
1569 fn from_path<'tcx>(
1570 ecx: &mut MiriInterpCx<'tcx>,
1571 path: &Path,
1572 follow_symlink: bool,
1573 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1574 let metadata =
1575 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1576
1577 FileMetadata::from_meta(ecx, metadata)
1578 }
1579
1580 fn from_fd_num<'tcx>(
1581 ecx: &mut MiriInterpCx<'tcx>,
1582 fd_num: i32,
1583 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1584 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1585 return interp_ok(Err(LibcError("EBADF")));
1586 };
1587
1588 let metadata = fd.metadata()?;
1589 drop(fd);
1590 FileMetadata::from_meta(ecx, metadata)
1591 }
1592
1593 fn from_meta<'tcx>(
1594 ecx: &mut MiriInterpCx<'tcx>,
1595 metadata: Result<std::fs::Metadata, std::io::Error>,
1596 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1597 let metadata = match metadata {
1598 Ok(metadata) => metadata,
1599 Err(e) => {
1600 return interp_ok(Err(e.into()));
1601 }
1602 };
1603
1604 let file_type = metadata.file_type();
1605
1606 let mode_name = if file_type.is_file() {
1607 "S_IFREG"
1608 } else if file_type.is_dir() {
1609 "S_IFDIR"
1610 } else {
1611 "S_IFLNK"
1612 };
1613
1614 let mode = ecx.eval_libc(mode_name);
1615
1616 let size = metadata.len();
1617
1618 let created = extract_sec_and_nsec(metadata.created())?;
1619 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1620 let modified = extract_sec_and_nsec(metadata.modified())?;
1621
1622 cfg_select! {
1625 unix => {
1626 use std::os::unix::fs::MetadataExt;
1627 let dev = metadata.dev();
1628 let uid = metadata.uid();
1629 let gid = metadata.gid();
1630 }
1631 _ => {
1632 let dev = 0;
1633 let uid = 0;
1634 let gid = 0;
1635 }
1636 }
1637
1638 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1639 }
1640}