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::helpers::check_min_vararg_count;
17use crate::shims::files::FileHandle;
18use crate::shims::os_str::bytes_to_os_str;
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", 0),
136 ("st_mode", mode.try_into().unwrap()),
137 ("st_nlink", 0),
138 ("st_ino", 0),
139 ("st_uid", 0),
140 ("st_gid", 0),
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 linux_solarish_readdir64(
904 &mut self,
905 dirent_type: &str,
906 dirp_op: &OpTy<'tcx>,
907 ) -> InterpResult<'tcx, Scalar> {
908 let this = self.eval_context_mut();
909
910 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
911 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
912 }
913
914 let dirp = this.read_target_usize(dirp_op)?;
915
916 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
918 this.reject_in_isolation("`readdir`", reject_with)?;
919 this.set_last_error(LibcError("EBADF"))?;
920 return interp_ok(Scalar::null_ptr(this));
921 }
922
923 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
924 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
925 })?;
926
927 let entry = match open_dir.read_dir.next() {
928 Some(Ok(dir_entry)) => {
929 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
954 let name_len = u64::try_from(name_bytes.len()).unwrap();
955
956 let dirent_layout = this.libc_ty_layout(dirent_type);
957 let fields = &dirent_layout.fields;
958 let last_field = fields.count().strict_sub(1);
959 let d_name_offset = fields.offset(last_field).bytes();
960 let size = d_name_offset.strict_add(name_len);
961
962 let entry = this.allocate_ptr(
963 Size::from_bytes(size),
964 dirent_layout.align.abi,
965 MiriMemoryKind::Runtime.into(),
966 AllocInit::Uninit,
967 )?;
968 let entry: Pointer = entry.into();
969
970 #[cfg(unix)]
973 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
974 #[cfg(not(unix))]
975 let ino = 0u64;
976
977 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
978 this.write_int_fields_named(
979 &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
980 &this.ptr_to_mplace(entry, dirent_layout),
981 )?;
982
983 if let Some(d_type) = this
984 .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
985 {
986 this.write_int(file_type, &d_type)?;
987 }
988
989 let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
990 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
991
992 Some(entry)
993 }
994 None => {
995 None
997 }
998 Some(Err(e)) => {
999 this.set_last_error(e)?;
1000 None
1001 }
1002 };
1003
1004 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1005 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1006 if let Some(old_entry) = old_entry {
1007 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1008 }
1009
1010 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1011 }
1012
1013 fn macos_fbsd_readdir_r(
1014 &mut self,
1015 dirp_op: &OpTy<'tcx>,
1016 entry_op: &OpTy<'tcx>,
1017 result_op: &OpTy<'tcx>,
1018 ) -> InterpResult<'tcx, Scalar> {
1019 let this = self.eval_context_mut();
1020
1021 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1022 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1023 }
1024
1025 let dirp = this.read_target_usize(dirp_op)?;
1026 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1027
1028 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1030 this.reject_in_isolation("`readdir_r`", reject_with)?;
1031 return interp_ok(this.eval_libc("EBADF"));
1033 }
1034
1035 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1036 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1037 })?;
1038 interp_ok(match open_dir.read_dir.next() {
1039 Some(Ok(dir_entry)) => {
1040 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1055 let name_place = this.project_field_named(&entry_place, "d_name")?;
1056
1057 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1059 &file_name,
1060 name_place.ptr(),
1061 name_place.layout.size.bytes(),
1062 )?;
1063 let file_name_len = file_name_buf_len.strict_sub(1);
1064 if !name_fits {
1065 throw_unsup_format!(
1066 "a directory entry had a name too large to fit in libc::dirent"
1067 );
1068 }
1069
1070 #[cfg(unix)]
1073 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1074 #[cfg(not(unix))]
1075 let ino = 0u64;
1076
1077 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1078
1079 this.write_int_fields_named(
1081 &[
1082 ("d_reclen", 0),
1083 ("d_namlen", file_name_len.into()),
1084 ("d_type", file_type.into()),
1085 ],
1086 &entry_place,
1087 )?;
1088 match &*this.tcx.sess.target.os {
1090 "macos" => {
1091 #[rustfmt::skip]
1092 this.write_int_fields_named(
1093 &[
1094 ("d_ino", ino.into()),
1095 ("d_seekoff", 0),
1096 ],
1097 &entry_place,
1098 )?;
1099 }
1100 "freebsd" => {
1101 #[rustfmt::skip]
1102 this.write_int_fields_named(
1103 &[
1104 ("d_fileno", ino.into()),
1105 ("d_off", 0),
1106 ],
1107 &entry_place,
1108 )?;
1109 }
1110 _ => unreachable!(),
1111 }
1112 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1113
1114 Scalar::from_i32(0)
1115 }
1116 None => {
1117 this.write_null(&result_place)?;
1119 Scalar::from_i32(0)
1120 }
1121 Some(Err(e)) => {
1122 this.io_error_to_errnum(e)?
1124 }
1125 })
1126 }
1127
1128 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1129 let this = self.eval_context_mut();
1130
1131 let dirp = this.read_target_usize(dirp_op)?;
1132
1133 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1135 this.reject_in_isolation("`closedir`", reject_with)?;
1136 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1137 }
1138
1139 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1140 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1141 };
1142 if let Some(entry) = open_dir.entry.take() {
1143 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1144 }
1145 drop(open_dir);
1147
1148 interp_ok(Scalar::from_i32(0))
1149 }
1150
1151 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1152 let this = self.eval_context_mut();
1153
1154 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1156 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1157 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1159 }
1160
1161 let Some(fd) = this.machine.fds.get(fd_num) else {
1162 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1163 };
1164
1165 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1167 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1168 })?;
1169
1170 if file.writable {
1171 if let Ok(length) = length.try_into() {
1172 let result = file.file.set_len(length);
1173 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1174 interp_ok(Scalar::from_i32(result))
1175 } else {
1176 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1177 }
1178 } else {
1179 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1181 }
1182 }
1183
1184 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1185 let this = self.eval_context_mut();
1191
1192 let fd = this.read_scalar(fd_op)?.to_i32()?;
1193
1194 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1196 this.reject_in_isolation("`fsync`", reject_with)?;
1197 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1199 }
1200
1201 self.ffullsync_fd(fd)
1202 }
1203
1204 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1205 let this = self.eval_context_mut();
1206 let Some(fd) = this.machine.fds.get(fd_num) else {
1207 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1208 };
1209 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1211 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1212 })?;
1213 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1214 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1215 }
1216
1217 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1218 let this = self.eval_context_mut();
1219
1220 let fd = this.read_scalar(fd_op)?.to_i32()?;
1221
1222 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1224 this.reject_in_isolation("`fdatasync`", reject_with)?;
1225 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1227 }
1228
1229 let Some(fd) = this.machine.fds.get(fd) else {
1230 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1231 };
1232 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1234 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1235 })?;
1236 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1237 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1238 }
1239
1240 fn sync_file_range(
1241 &mut self,
1242 fd_op: &OpTy<'tcx>,
1243 offset_op: &OpTy<'tcx>,
1244 nbytes_op: &OpTy<'tcx>,
1245 flags_op: &OpTy<'tcx>,
1246 ) -> InterpResult<'tcx, Scalar> {
1247 let this = self.eval_context_mut();
1248
1249 let fd = this.read_scalar(fd_op)?.to_i32()?;
1250 let offset = this.read_scalar(offset_op)?.to_i64()?;
1251 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1252 let flags = this.read_scalar(flags_op)?.to_i32()?;
1253
1254 if offset < 0 || nbytes < 0 {
1255 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1256 }
1257 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1258 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1259 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1260 if flags & allowed_flags != flags {
1261 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1262 }
1263
1264 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1266 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1267 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1269 }
1270
1271 let Some(fd) = this.machine.fds.get(fd) else {
1272 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1273 };
1274 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1276 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1277 })?;
1278 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1279 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1280 }
1281
1282 fn readlink(
1283 &mut self,
1284 pathname_op: &OpTy<'tcx>,
1285 buf_op: &OpTy<'tcx>,
1286 bufsize_op: &OpTy<'tcx>,
1287 ) -> InterpResult<'tcx, i64> {
1288 let this = self.eval_context_mut();
1289
1290 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1291 let buf = this.read_pointer(buf_op)?;
1292 let bufsize = this.read_target_usize(bufsize_op)?;
1293
1294 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1296 this.reject_in_isolation("`readlink`", reject_with)?;
1297 this.set_last_error(LibcError("EACCES"))?;
1298 return interp_ok(-1);
1299 }
1300
1301 let result = std::fs::read_link(pathname);
1302 match result {
1303 Ok(resolved) => {
1304 let resolved = this.convert_path(
1308 Cow::Borrowed(resolved.as_ref()),
1309 crate::shims::os_str::PathConversion::HostToTarget,
1310 );
1311 let mut path_bytes = resolved.as_encoded_bytes();
1312 let bufsize: usize = bufsize.try_into().unwrap();
1313 if path_bytes.len() > bufsize {
1314 path_bytes = &path_bytes[..bufsize]
1315 }
1316 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1317 interp_ok(path_bytes.len().try_into().unwrap())
1318 }
1319 Err(e) => {
1320 this.set_last_error(e)?;
1321 interp_ok(-1)
1322 }
1323 }
1324 }
1325
1326 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1327 let this = self.eval_context_mut();
1328 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1331 let error = if let Some(fd) = this.machine.fds.get(fd) {
1332 if fd.is_tty(this.machine.communicate()) {
1333 return interp_ok(Scalar::from_i32(1));
1334 } else {
1335 LibcError("ENOTTY")
1336 }
1337 } else {
1338 LibcError("EBADF")
1340 };
1341 this.set_last_error(error)?;
1342 interp_ok(Scalar::from_i32(0))
1343 }
1344
1345 fn realpath(
1346 &mut self,
1347 path_op: &OpTy<'tcx>,
1348 processed_path_op: &OpTy<'tcx>,
1349 ) -> InterpResult<'tcx, Scalar> {
1350 let this = self.eval_context_mut();
1351 this.assert_target_os_is_unix("realpath");
1352
1353 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1354 let processed_ptr = this.read_pointer(processed_path_op)?;
1355
1356 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1358 this.reject_in_isolation("`realpath`", reject_with)?;
1359 this.set_last_error(LibcError("EACCES"))?;
1360 return interp_ok(Scalar::from_target_usize(0, this));
1361 }
1362
1363 let result = std::fs::canonicalize(pathname);
1364 match result {
1365 Ok(resolved) => {
1366 let path_max = this
1367 .eval_libc_i32("PATH_MAX")
1368 .try_into()
1369 .expect("PATH_MAX does not fit in u64");
1370 let dest = if this.ptr_is_null(processed_ptr)? {
1371 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1381 } else {
1382 let (wrote_path, _) =
1383 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1384
1385 if !wrote_path {
1386 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1390 return interp_ok(Scalar::from_target_usize(0, this));
1391 }
1392 processed_ptr
1393 };
1394
1395 interp_ok(Scalar::from_maybe_pointer(dest, this))
1396 }
1397 Err(e) => {
1398 this.set_last_error(e)?;
1399 interp_ok(Scalar::from_target_usize(0, this))
1400 }
1401 }
1402 }
1403 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1404 use rand::seq::IndexedRandom;
1405
1406 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1408
1409 let this = self.eval_context_mut();
1410 this.assert_target_os_is_unix("mkstemp");
1411
1412 let max_attempts = this.eval_libc_u32("TMP_MAX");
1422
1423 let template_ptr = this.read_pointer(template_op)?;
1426 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1427 let template_bytes = template.as_mut_slice();
1428
1429 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1431 this.reject_in_isolation("`mkstemp`", reject_with)?;
1432 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1433 }
1434
1435 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1437
1438 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1443 let end_pos = template_bytes.len();
1444 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1445
1446 if last_six_char_bytes != suffix_bytes {
1448 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1449 }
1450
1451 const SUBSTITUTIONS: &[char; 62] = &[
1455 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1456 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1457 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1458 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1459 ];
1460
1461 let mut fopts = OpenOptions::new();
1464 fopts.read(true).write(true).create_new(true);
1465
1466 #[cfg(unix)]
1467 {
1468 use std::os::unix::fs::OpenOptionsExt;
1469 fopts.mode(0o600);
1471 fopts.custom_flags(libc::O_EXCL);
1472 }
1473 #[cfg(windows)]
1474 {
1475 use std::os::windows::fs::OpenOptionsExt;
1476 fopts.share_mode(0);
1478 }
1479
1480 for _ in 0..max_attempts {
1482 let rng = this.machine.rng.get_mut();
1483
1484 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1486
1487 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1489
1490 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1492
1493 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1495
1496 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1497
1498 let file = fopts.open(possibly_unique);
1499
1500 match file {
1501 Ok(f) => {
1502 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1503 return interp_ok(Scalar::from_i32(fd));
1504 }
1505 Err(e) =>
1506 match e.kind() {
1507 ErrorKind::AlreadyExists => continue,
1509 _ => {
1511 return this.set_last_error_and_return_i32(e);
1514 }
1515 },
1516 }
1517 }
1518
1519 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1521 }
1522}
1523
1524fn extract_sec_and_nsec<'tcx>(
1528 time: std::io::Result<SystemTime>,
1529) -> InterpResult<'tcx, Option<(u64, u32)>> {
1530 match time.ok() {
1531 Some(time) => {
1532 let duration = system_time_to_duration(&time)?;
1533 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1534 }
1535 None => interp_ok(None),
1536 }
1537}
1538
1539struct FileMetadata {
1542 mode: Scalar,
1543 size: u64,
1544 created: Option<(u64, u32)>,
1545 accessed: Option<(u64, u32)>,
1546 modified: Option<(u64, u32)>,
1547}
1548
1549impl FileMetadata {
1550 fn from_path<'tcx>(
1551 ecx: &mut MiriInterpCx<'tcx>,
1552 path: &Path,
1553 follow_symlink: bool,
1554 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1555 let metadata =
1556 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1557
1558 FileMetadata::from_meta(ecx, metadata)
1559 }
1560
1561 fn from_fd_num<'tcx>(
1562 ecx: &mut MiriInterpCx<'tcx>,
1563 fd_num: i32,
1564 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1565 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1566 return interp_ok(Err(LibcError("EBADF")));
1567 };
1568
1569 let metadata = fd.metadata()?;
1570 drop(fd);
1571 FileMetadata::from_meta(ecx, metadata)
1572 }
1573
1574 fn from_meta<'tcx>(
1575 ecx: &mut MiriInterpCx<'tcx>,
1576 metadata: Result<std::fs::Metadata, std::io::Error>,
1577 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1578 let metadata = match metadata {
1579 Ok(metadata) => metadata,
1580 Err(e) => {
1581 return interp_ok(Err(e.into()));
1582 }
1583 };
1584
1585 let file_type = metadata.file_type();
1586
1587 let mode_name = if file_type.is_file() {
1588 "S_IFREG"
1589 } else if file_type.is_dir() {
1590 "S_IFDIR"
1591 } else {
1592 "S_IFLNK"
1593 };
1594
1595 let mode = ecx.eval_libc(mode_name);
1596
1597 let size = metadata.len();
1598
1599 let created = extract_sec_and_nsec(metadata.created())?;
1600 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1601 let modified = extract_sec_and_nsec(metadata.modified())?;
1602
1603 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified }))
1605 }
1606}