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