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 stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
531 let this = self.eval_context_mut();
532
533 if !matches!(
534 &this.tcx.sess.target.os,
535 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
536 ) {
537 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
538 }
539
540 let path_scalar = this.read_pointer(path_op)?;
541 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
542
543 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
545 this.reject_in_isolation("`stat`", reject_with)?;
546 return this.set_last_error_and_return_i32(LibcError("EACCES"));
547 }
548
549 let metadata = match FileMetadata::from_path(this, &path, true)? {
551 Ok(metadata) => metadata,
552 Err(err) => return this.set_last_error_and_return_i32(err),
553 };
554
555 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
556 }
557
558 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
560 let this = self.eval_context_mut();
561
562 if !matches!(
563 &this.tcx.sess.target.os,
564 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
565 ) {
566 panic!(
567 "`macos_fbsd_solaris_lstat` should not be called on {}",
568 this.tcx.sess.target.os
569 );
570 }
571
572 let path_scalar = this.read_pointer(path_op)?;
573 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
574
575 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
577 this.reject_in_isolation("`lstat`", reject_with)?;
578 return this.set_last_error_and_return_i32(LibcError("EACCES"));
579 }
580
581 let metadata = match FileMetadata::from_path(this, &path, false)? {
582 Ok(metadata) => metadata,
583 Err(err) => return this.set_last_error_and_return_i32(err),
584 };
585
586 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
587 }
588
589 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
590 let this = self.eval_context_mut();
591
592 if !matches!(
593 &this.tcx.sess.target.os,
594 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
595 ) {
596 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
597 }
598
599 let fd = this.read_scalar(fd_op)?.to_i32()?;
600
601 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
603 this.reject_in_isolation("`fstat`", reject_with)?;
604 return this.set_last_error_and_return_i32(LibcError("EBADF"));
606 }
607
608 let metadata = match FileMetadata::from_fd_num(this, fd)? {
609 Ok(metadata) => metadata,
610 Err(err) => return this.set_last_error_and_return_i32(err),
611 };
612 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
613 }
614
615 fn linux_statx(
616 &mut self,
617 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
623 let this = self.eval_context_mut();
624
625 this.assert_target_os(Os::Linux, "statx");
626
627 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
628 let pathname_ptr = this.read_pointer(pathname_op)?;
629 let flags = this.read_scalar(flags_op)?.to_i32()?;
630 let _mask = this.read_scalar(mask_op)?.to_u32()?;
631 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
632
633 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
635 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
636 }
637
638 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
639
640 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
641 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
643 let empty_path_flag = flags & at_empty_path == at_empty_path;
644 if !(path.is_absolute()
652 || dirfd == this.eval_libc_i32("AT_FDCWD")
653 || (path.as_os_str().is_empty() && empty_path_flag))
654 {
655 throw_unsup_format!(
656 "using statx is only supported with absolute paths, relative paths with the file \
657 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
658 file descriptor"
659 )
660 }
661
662 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
664 this.reject_in_isolation("`statx`", reject_with)?;
665 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
666 LibcError("EACCES")
669 } else {
670 assert!(empty_path_flag);
674 LibcError("EBADF")
675 };
676 return this.set_last_error_and_return_i32(ecode);
677 }
678
679 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
684
685 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
688
689 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
692 FileMetadata::from_fd_num(this, dirfd)?
693 } else {
694 FileMetadata::from_path(this, &path, follow_symlink)?
695 };
696 let metadata = match metadata {
697 Ok(metadata) => metadata,
698 Err(err) => return this.set_last_error_and_return_i32(err),
699 };
700
701 let mode: u16 = metadata
706 .mode
707 .to_u32()?
708 .try_into()
709 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
710
711 let (access_sec, access_nsec) = metadata
714 .accessed
715 .map(|tup| {
716 mask |= this.eval_libc_u32("STATX_ATIME");
717 interp_ok(tup)
718 })
719 .unwrap_or_else(|| interp_ok((0, 0)))?;
720
721 let (created_sec, created_nsec) = metadata
722 .created
723 .map(|tup| {
724 mask |= this.eval_libc_u32("STATX_BTIME");
725 interp_ok(tup)
726 })
727 .unwrap_or_else(|| interp_ok((0, 0)))?;
728
729 let (modified_sec, modified_nsec) = metadata
730 .modified
731 .map(|tup| {
732 mask |= this.eval_libc_u32("STATX_MTIME");
733 interp_ok(tup)
734 })
735 .unwrap_or_else(|| interp_ok((0, 0)))?;
736
737 this.write_int_fields_named(
739 &[
740 ("stx_mask", mask.into()),
741 ("stx_blksize", 0),
742 ("stx_attributes", 0),
743 ("stx_nlink", 0),
744 ("stx_uid", 0),
745 ("stx_gid", 0),
746 ("stx_mode", mode.into()),
747 ("stx_ino", 0),
748 ("stx_size", metadata.size.into()),
749 ("stx_blocks", 0),
750 ("stx_attributes_mask", 0),
751 ("stx_rdev_major", 0),
752 ("stx_rdev_minor", 0),
753 ("stx_dev_major", 0),
754 ("stx_dev_minor", 0),
755 ],
756 &statxbuf,
757 )?;
758 #[rustfmt::skip]
759 this.write_int_fields_named(
760 &[
761 ("tv_sec", access_sec.into()),
762 ("tv_nsec", access_nsec.into()),
763 ],
764 &this.project_field_named(&statxbuf, "stx_atime")?,
765 )?;
766 #[rustfmt::skip]
767 this.write_int_fields_named(
768 &[
769 ("tv_sec", created_sec.into()),
770 ("tv_nsec", created_nsec.into()),
771 ],
772 &this.project_field_named(&statxbuf, "stx_btime")?,
773 )?;
774 #[rustfmt::skip]
775 this.write_int_fields_named(
776 &[
777 ("tv_sec", 0.into()),
778 ("tv_nsec", 0.into()),
779 ],
780 &this.project_field_named(&statxbuf, "stx_ctime")?,
781 )?;
782 #[rustfmt::skip]
783 this.write_int_fields_named(
784 &[
785 ("tv_sec", modified_sec.into()),
786 ("tv_nsec", modified_nsec.into()),
787 ],
788 &this.project_field_named(&statxbuf, "stx_mtime")?,
789 )?;
790
791 interp_ok(Scalar::from_i32(0))
792 }
793
794 fn rename(
795 &mut self,
796 oldpath_op: &OpTy<'tcx>,
797 newpath_op: &OpTy<'tcx>,
798 ) -> InterpResult<'tcx, Scalar> {
799 let this = self.eval_context_mut();
800
801 let oldpath_ptr = this.read_pointer(oldpath_op)?;
802 let newpath_ptr = this.read_pointer(newpath_op)?;
803
804 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
805 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
806 }
807
808 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
809 let newpath = this.read_path_from_c_str(newpath_ptr)?;
810
811 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
813 this.reject_in_isolation("`rename`", reject_with)?;
814 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
815 }
816
817 let result = rename(oldpath, newpath).map(|_| 0);
818
819 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
820 }
821
822 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
823 let this = self.eval_context_mut();
824
825 #[cfg_attr(not(unix), allow(unused_variables))]
826 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
827 u32::from(this.read_scalar(mode_op)?.to_u16()?)
828 } else {
829 this.read_scalar(mode_op)?.to_u32()?
830 };
831
832 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
833
834 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
836 this.reject_in_isolation("`mkdir`", reject_with)?;
837 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
838 }
839
840 #[cfg_attr(not(unix), allow(unused_mut))]
841 let mut builder = DirBuilder::new();
842
843 #[cfg(unix)]
846 {
847 use std::os::unix::fs::DirBuilderExt;
848 builder.mode(mode);
849 }
850
851 let result = builder.create(path).map(|_| 0i32);
852
853 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
854 }
855
856 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
857 let this = self.eval_context_mut();
858
859 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
860
861 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
863 this.reject_in_isolation("`rmdir`", reject_with)?;
864 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
865 }
866
867 let result = remove_dir(path).map(|_| 0i32);
868
869 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
870 }
871
872 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
873 let this = self.eval_context_mut();
874
875 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
876
877 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
879 this.reject_in_isolation("`opendir`", reject_with)?;
880 this.set_last_error(LibcError("EACCES"))?;
881 return interp_ok(Scalar::null_ptr(this));
882 }
883
884 let result = read_dir(name);
885
886 match result {
887 Ok(dir_iter) => {
888 let id = this.machine.dirs.insert_new(dir_iter);
889
890 interp_ok(Scalar::from_target_usize(id, this))
894 }
895 Err(e) => {
896 this.set_last_error(e)?;
897 interp_ok(Scalar::null_ptr(this))
898 }
899 }
900 }
901
902 fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
903 let this = self.eval_context_mut();
904
905 if !matches!(
906 &this.tcx.sess.target.os,
907 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
908 ) {
909 panic!("`readdir64` should not be called on {}", this.tcx.sess.target.os);
910 }
911
912 let dirp = this.read_target_usize(dirp_op)?;
913
914 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
916 this.reject_in_isolation("`readdir`", reject_with)?;
917 this.set_last_error(LibcError("EBADF"))?;
918 return interp_ok(Scalar::null_ptr(this));
919 }
920
921 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
922 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
923 })?;
924
925 let entry = match open_dir.read_dir.next() {
926 Some(Ok(dir_entry)) => {
927 #[cfg(unix)]
930 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
931 #[cfg(not(unix))]
932 let ino = 0u64;
933
934 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
968 let name_len = u64::try_from(name_bytes.len()).unwrap();
969
970 let dirent_layout = this.libc_ty_layout(dirent_type);
971 let fields = &dirent_layout.fields;
972 let last_field = fields.count().strict_sub(1);
973 let d_name_offset = fields.offset(last_field).bytes();
974 let size = d_name_offset.strict_add(name_len);
975
976 let entry = this.allocate_ptr(
977 Size::from_bytes(size),
978 dirent_layout.align.abi,
979 MiriMemoryKind::Runtime.into(),
980 AllocInit::Uninit,
981 )?;
982 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
983
984 let ino_name =
986 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
987 this.write_int_fields_named(
988 &[(ino_name, ino.into()), ("d_reclen", size.into())],
989 &entry,
990 )?;
991
992 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
994 this.write_null(&d_off)?;
995 }
996
997 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
998 this.write_int(name_len.strict_sub(1), &d_namlen)?;
999 }
1000
1001 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1002 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1003 this.write_int(file_type, &d_type)?;
1004 }
1005
1006 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1008 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1009
1010 Some(entry.ptr())
1011 }
1012 None => {
1013 None
1015 }
1016 Some(Err(e)) => {
1017 this.set_last_error(e)?;
1018 None
1019 }
1020 };
1021
1022 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1023 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1024 if let Some(old_entry) = old_entry {
1025 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1026 }
1027
1028 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1029 }
1030
1031 fn macos_readdir_r(
1032 &mut self,
1033 dirp_op: &OpTy<'tcx>,
1034 entry_op: &OpTy<'tcx>,
1035 result_op: &OpTy<'tcx>,
1036 ) -> InterpResult<'tcx, Scalar> {
1037 let this = self.eval_context_mut();
1038
1039 this.assert_target_os(Os::MacOs, "readdir_r");
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(
1096 &[
1097 ("d_reclen", 0),
1098 ("d_namlen", file_name_len.into()),
1099 ("d_type", file_type.into()),
1100 ("d_ino", ino.into()),
1101 ("d_seekoff", 0),
1102 ],
1103 &entry_place,
1104 )?;
1105 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1106
1107 Scalar::from_i32(0)
1108 }
1109 None => {
1110 this.write_null(&result_place)?;
1112 Scalar::from_i32(0)
1113 }
1114 Some(Err(e)) => {
1115 this.io_error_to_errnum(e)?
1117 }
1118 })
1119 }
1120
1121 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1122 let this = self.eval_context_mut();
1123
1124 let dirp = this.read_target_usize(dirp_op)?;
1125
1126 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1128 this.reject_in_isolation("`closedir`", reject_with)?;
1129 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1130 }
1131
1132 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1133 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1134 };
1135 if let Some(entry) = open_dir.entry.take() {
1136 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1137 }
1138 drop(open_dir);
1140
1141 interp_ok(Scalar::from_i32(0))
1142 }
1143
1144 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1145 let this = self.eval_context_mut();
1146
1147 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1149 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1150 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1152 }
1153
1154 let Some(fd) = this.machine.fds.get(fd_num) else {
1155 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1156 };
1157
1158 let Some(file) = fd.downcast::<FileHandle>() else {
1159 return interp_ok(this.eval_libc("EINVAL"));
1162 };
1163
1164 if file.writable {
1165 if let Ok(length) = length.try_into() {
1166 let result = file.file.set_len(length);
1167 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1168 interp_ok(Scalar::from_i32(result))
1169 } else {
1170 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1171 }
1172 } else {
1173 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1175 }
1176 }
1177
1178 fn posix_fallocate(
1181 &mut self,
1182 fd_num: i32,
1183 offset: i64,
1184 len: i64,
1185 ) -> InterpResult<'tcx, Scalar> {
1186 let this = self.eval_context_mut();
1187
1188 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1190 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1191 return interp_ok(this.eval_libc("EBADF"));
1193 }
1194
1195 if offset < 0 || len <= 0 {
1197 return interp_ok(this.eval_libc("EINVAL"));
1198 }
1199
1200 let Some(fd) = this.machine.fds.get(fd_num) else {
1202 return interp_ok(this.eval_libc("EBADF"));
1203 };
1204 let Some(file) = fd.downcast::<FileHandle>() else {
1205 return interp_ok(this.eval_libc("ENODEV"));
1207 };
1208
1209 if !file.writable {
1210 return interp_ok(this.eval_libc("EBADF"));
1212 }
1213
1214 let current_size = match file.file.metadata() {
1215 Ok(metadata) => metadata.len(),
1216 Err(err) => return this.io_error_to_errnum(err),
1217 };
1218 let new_size = match offset.checked_add(len) {
1220 Some(new_size) => u64::try_from(new_size).unwrap(),
1222 None => return interp_ok(this.eval_libc("EFBIG")), };
1224 if current_size < new_size {
1227 interp_ok(match file.file.set_len(new_size) {
1228 Ok(()) => Scalar::from_i32(0),
1229 Err(e) => this.io_error_to_errnum(e)?,
1230 })
1231 } else {
1232 interp_ok(Scalar::from_i32(0))
1233 }
1234 }
1235
1236 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1237 let this = self.eval_context_mut();
1243
1244 let fd = this.read_scalar(fd_op)?.to_i32()?;
1245
1246 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1248 this.reject_in_isolation("`fsync`", reject_with)?;
1249 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1251 }
1252
1253 self.ffullsync_fd(fd)
1254 }
1255
1256 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1257 let this = self.eval_context_mut();
1258 let Some(fd) = this.machine.fds.get(fd_num) else {
1259 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1260 };
1261 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1263 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1264 })?;
1265 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1266 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1267 }
1268
1269 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1270 let this = self.eval_context_mut();
1271
1272 let fd = this.read_scalar(fd_op)?.to_i32()?;
1273
1274 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1276 this.reject_in_isolation("`fdatasync`", reject_with)?;
1277 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1279 }
1280
1281 let Some(fd) = this.machine.fds.get(fd) else {
1282 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1283 };
1284 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1286 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1287 })?;
1288 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1289 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1290 }
1291
1292 fn sync_file_range(
1293 &mut self,
1294 fd_op: &OpTy<'tcx>,
1295 offset_op: &OpTy<'tcx>,
1296 nbytes_op: &OpTy<'tcx>,
1297 flags_op: &OpTy<'tcx>,
1298 ) -> InterpResult<'tcx, Scalar> {
1299 let this = self.eval_context_mut();
1300
1301 let fd = this.read_scalar(fd_op)?.to_i32()?;
1302 let offset = this.read_scalar(offset_op)?.to_i64()?;
1303 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1304 let flags = this.read_scalar(flags_op)?.to_i32()?;
1305
1306 if offset < 0 || nbytes < 0 {
1307 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1308 }
1309 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1310 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1311 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1312 if flags & allowed_flags != flags {
1313 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1314 }
1315
1316 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1318 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1319 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1321 }
1322
1323 let Some(fd) = this.machine.fds.get(fd) else {
1324 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1325 };
1326 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1328 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1329 })?;
1330 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1331 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1332 }
1333
1334 fn readlink(
1335 &mut self,
1336 pathname_op: &OpTy<'tcx>,
1337 buf_op: &OpTy<'tcx>,
1338 bufsize_op: &OpTy<'tcx>,
1339 ) -> InterpResult<'tcx, i64> {
1340 let this = self.eval_context_mut();
1341
1342 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1343 let buf = this.read_pointer(buf_op)?;
1344 let bufsize = this.read_target_usize(bufsize_op)?;
1345
1346 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1348 this.reject_in_isolation("`readlink`", reject_with)?;
1349 this.set_last_error(LibcError("EACCES"))?;
1350 return interp_ok(-1);
1351 }
1352
1353 let result = std::fs::read_link(pathname);
1354 match result {
1355 Ok(resolved) => {
1356 let resolved = this.convert_path(
1360 Cow::Borrowed(resolved.as_ref()),
1361 crate::shims::os_str::PathConversion::HostToTarget,
1362 );
1363 let mut path_bytes = resolved.as_encoded_bytes();
1364 let bufsize: usize = bufsize.try_into().unwrap();
1365 if path_bytes.len() > bufsize {
1366 path_bytes = &path_bytes[..bufsize]
1367 }
1368 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1369 interp_ok(path_bytes.len().try_into().unwrap())
1370 }
1371 Err(e) => {
1372 this.set_last_error(e)?;
1373 interp_ok(-1)
1374 }
1375 }
1376 }
1377
1378 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1379 let this = self.eval_context_mut();
1380 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1383 let error = if let Some(fd) = this.machine.fds.get(fd) {
1384 if fd.is_tty(this.machine.communicate()) {
1385 return interp_ok(Scalar::from_i32(1));
1386 } else {
1387 LibcError("ENOTTY")
1388 }
1389 } else {
1390 LibcError("EBADF")
1392 };
1393 this.set_last_error(error)?;
1394 interp_ok(Scalar::from_i32(0))
1395 }
1396
1397 fn realpath(
1398 &mut self,
1399 path_op: &OpTy<'tcx>,
1400 processed_path_op: &OpTy<'tcx>,
1401 ) -> InterpResult<'tcx, Scalar> {
1402 let this = self.eval_context_mut();
1403 this.assert_target_os_is_unix("realpath");
1404
1405 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1406 let processed_ptr = this.read_pointer(processed_path_op)?;
1407
1408 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1410 this.reject_in_isolation("`realpath`", reject_with)?;
1411 this.set_last_error(LibcError("EACCES"))?;
1412 return interp_ok(Scalar::from_target_usize(0, this));
1413 }
1414
1415 let result = std::fs::canonicalize(pathname);
1416 match result {
1417 Ok(resolved) => {
1418 let path_max = this
1419 .eval_libc_i32("PATH_MAX")
1420 .try_into()
1421 .expect("PATH_MAX does not fit in u64");
1422 let dest = if this.ptr_is_null(processed_ptr)? {
1423 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1433 } else {
1434 let (wrote_path, _) =
1435 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1436
1437 if !wrote_path {
1438 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1442 return interp_ok(Scalar::from_target_usize(0, this));
1443 }
1444 processed_ptr
1445 };
1446
1447 interp_ok(Scalar::from_maybe_pointer(dest, this))
1448 }
1449 Err(e) => {
1450 this.set_last_error(e)?;
1451 interp_ok(Scalar::from_target_usize(0, this))
1452 }
1453 }
1454 }
1455 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1456 use rand::seq::IndexedRandom;
1457
1458 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1460
1461 let this = self.eval_context_mut();
1462 this.assert_target_os_is_unix("mkstemp");
1463
1464 let max_attempts = this.eval_libc_u32("TMP_MAX");
1474
1475 let template_ptr = this.read_pointer(template_op)?;
1478 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1479 let template_bytes = template.as_mut_slice();
1480
1481 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1483 this.reject_in_isolation("`mkstemp`", reject_with)?;
1484 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1485 }
1486
1487 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1489
1490 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1495 let end_pos = template_bytes.len();
1496 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1497
1498 if last_six_char_bytes != suffix_bytes {
1500 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1501 }
1502
1503 const SUBSTITUTIONS: &[char; 62] = &[
1507 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1508 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1509 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1510 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1511 ];
1512
1513 let mut fopts = OpenOptions::new();
1516 fopts.read(true).write(true).create_new(true);
1517
1518 #[cfg(unix)]
1519 {
1520 use std::os::unix::fs::OpenOptionsExt;
1521 fopts.mode(0o600);
1523 fopts.custom_flags(libc::O_EXCL);
1524 }
1525 #[cfg(windows)]
1526 {
1527 use std::os::windows::fs::OpenOptionsExt;
1528 fopts.share_mode(0);
1530 }
1531
1532 for _ in 0..max_attempts {
1534 let rng = this.machine.rng.get_mut();
1535
1536 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1538
1539 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1541
1542 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1544
1545 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1547
1548 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1549
1550 let file = fopts.open(possibly_unique);
1551
1552 match file {
1553 Ok(f) => {
1554 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1555 return interp_ok(Scalar::from_i32(fd));
1556 }
1557 Err(e) =>
1558 match e.kind() {
1559 ErrorKind::AlreadyExists => continue,
1561 _ => {
1563 return this.set_last_error_and_return_i32(e);
1566 }
1567 },
1568 }
1569 }
1570
1571 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1573 }
1574}
1575
1576fn extract_sec_and_nsec<'tcx>(
1580 time: std::io::Result<SystemTime>,
1581) -> InterpResult<'tcx, Option<(u64, u32)>> {
1582 match time.ok() {
1583 Some(time) => {
1584 let duration = system_time_to_duration(&time)?;
1585 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1586 }
1587 None => interp_ok(None),
1588 }
1589}
1590
1591struct FileMetadata {
1594 mode: Scalar,
1595 size: u64,
1596 created: Option<(u64, u32)>,
1597 accessed: Option<(u64, u32)>,
1598 modified: Option<(u64, u32)>,
1599 dev: u64,
1600 uid: u32,
1601 gid: u32,
1602}
1603
1604impl FileMetadata {
1605 fn from_path<'tcx>(
1606 ecx: &mut MiriInterpCx<'tcx>,
1607 path: &Path,
1608 follow_symlink: bool,
1609 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1610 let metadata =
1611 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1612
1613 FileMetadata::from_meta(ecx, metadata)
1614 }
1615
1616 fn from_fd_num<'tcx>(
1617 ecx: &mut MiriInterpCx<'tcx>,
1618 fd_num: i32,
1619 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1620 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1621 return interp_ok(Err(LibcError("EBADF")));
1622 };
1623
1624 let metadata = fd.metadata()?;
1625 drop(fd);
1626 FileMetadata::from_meta(ecx, metadata)
1627 }
1628
1629 fn from_meta<'tcx>(
1630 ecx: &mut MiriInterpCx<'tcx>,
1631 metadata: Result<std::fs::Metadata, std::io::Error>,
1632 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1633 let metadata = match metadata {
1634 Ok(metadata) => metadata,
1635 Err(e) => {
1636 return interp_ok(Err(e.into()));
1637 }
1638 };
1639
1640 let file_type = metadata.file_type();
1641
1642 let mode_name = if file_type.is_file() {
1643 "S_IFREG"
1644 } else if file_type.is_dir() {
1645 "S_IFDIR"
1646 } else {
1647 "S_IFLNK"
1648 };
1649
1650 let mode = ecx.eval_libc(mode_name);
1651
1652 let size = metadata.len();
1653
1654 let created = extract_sec_and_nsec(metadata.created())?;
1655 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1656 let modified = extract_sec_and_nsec(metadata.modified())?;
1657
1658 cfg_select! {
1661 unix => {
1662 use std::os::unix::fs::MetadataExt;
1663 let dev = metadata.dev();
1664 let uid = metadata.uid();
1665 let gid = metadata.gid();
1666 }
1667 _ => {
1668 let dev = 0;
1669 let uid = 0;
1670 let gid = 0;
1671 }
1672 }
1673
1674 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1675 }
1676}