1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{
6 self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file,
7 rename,
8};
9use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
10use std::path::{Path, PathBuf};
11use std::time::SystemTime;
12
13use rustc_abi::Size;
14use rustc_data_structures::either::Either;
15use rustc_data_structures::fx::FxHashMap;
16use rustc_target::spec::Os;
17
18use self::shims::time::system_time_to_duration;
19use crate::shims::files::FileHandle;
20use crate::shims::os_str::bytes_to_os_str;
21use crate::shims::sig::check_min_vararg_count;
22use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
23use crate::*;
24
25#[derive(Debug)]
27struct OpenDir {
28 special_entries: Vec<&'static str>,
31 read_dir: fs::ReadDir,
33 entry: Option<Pointer>,
36}
37
38impl OpenDir {
39 fn new(read_dir: fs::ReadDir) -> Self {
40 Self { special_entries: vec!["..", "."], read_dir, entry: None }
41 }
42
43 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
44 if let Some(special) = self.special_entries.pop() {
45 return Some(Ok(Either::Right(special)));
46 }
47 let entry = self.read_dir.next()?;
48 Some(entry.map(Either::Left))
49 }
50}
51
52#[derive(Debug)]
53struct DirEntry {
54 name: OsString,
55 ino: u64,
56 d_type: i32,
57}
58
59impl UnixFileDescription for FileHandle {
60 fn pread<'tcx>(
61 &self,
62 communicate_allowed: bool,
63 offset: u64,
64 ptr: Pointer,
65 len: usize,
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 mut bytes = vec![0; len];
71 let file = &mut &self.file;
75 let mut f = || {
76 let cursor_pos = file.stream_position()?;
77 file.seek(SeekFrom::Start(offset))?;
78 let res = file.read(&mut 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 = match f() {
85 Ok(read_size) => {
86 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
90 Ok(read_size)
91 }
92 Err(e) => Err(IoError::HostError(e)),
93 };
94 finish.call(ecx, result)
95 }
96
97 fn pwrite<'tcx>(
98 &self,
99 communicate_allowed: bool,
100 ptr: Pointer,
101 len: usize,
102 offset: u64,
103 ecx: &mut MiriInterpCx<'tcx>,
104 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
105 ) -> InterpResult<'tcx> {
106 assert!(communicate_allowed, "isolation should have prevented even opening a file");
107 let file = &mut &self.file;
111 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
112 let mut f = || {
113 let cursor_pos = file.stream_position()?;
114 file.seek(SeekFrom::Start(offset))?;
115 let res = file.write(bytes);
116 file.seek(SeekFrom::Start(cursor_pos))
118 .expect("failed to restore file position, this shouldn't be possible");
119 res
120 };
121 let result = f();
122 finish.call(ecx, result.map_err(IoError::HostError))
123 }
124
125 fn flock<'tcx>(
126 &self,
127 communicate_allowed: bool,
128 op: FlockOp,
129 ) -> InterpResult<'tcx, io::Result<()>> {
130 assert!(communicate_allowed, "isolation should have prevented even opening a file");
131
132 use FlockOp::*;
133 let (res, nonblocking) = match op {
135 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
136 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
137 Unlock => {
138 return interp_ok(self.file.unlock());
139 }
140 };
141
142 match res {
143 Ok(()) => interp_ok(Ok(())),
144 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
145 Err(TryLockError::WouldBlock) =>
146 if nonblocking {
147 interp_ok(Err(ErrorKind::WouldBlock.into()))
148 } else {
149 throw_unsup_format!("blocking `flock` is not currently supported");
150 },
151 }
152 }
153}
154
155#[derive(Debug)]
159pub struct DirTable {
160 streams: FxHashMap<u64, OpenDir>,
170 next_id: u64,
172}
173
174impl DirTable {
175 #[expect(clippy::arithmetic_side_effects)]
176 fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
177 let id = self.next_id;
178 self.next_id += 1;
179 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
180 id
181 }
182}
183
184impl Default for DirTable {
185 fn default() -> DirTable {
186 DirTable {
187 streams: FxHashMap::default(),
188 next_id: 1,
190 }
191 }
192}
193
194impl VisitProvenance for DirTable {
195 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
196 let DirTable { streams, next_id: _ } = self;
197
198 for dir in streams.values() {
199 dir.entry.visit_provenance(visit);
200 }
201 }
202}
203
204fn maybe_sync_file(
205 file: &File,
206 writable: bool,
207 operation: fn(&File) -> std::io::Result<()>,
208) -> std::io::Result<i32> {
209 if !writable && cfg!(windows) {
210 Ok(0i32)
214 } else {
215 let result = operation(file);
216 result.map(|_| 0i32)
217 }
218}
219
220impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
221trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
222 fn write_stat_buf(
223 &mut self,
224 metadata: FileMetadata,
225 buf_op: &OpTy<'tcx>,
226 ) -> InterpResult<'tcx, i32> {
227 let this = self.eval_context_mut();
228
229 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
230 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
231 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
232 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
233
234 let buf = this.deref_pointer(buf_op)?;
239 this.write_int_fields_named(
240 &[
241 ("st_dev", metadata.dev.into()),
242 ("st_mode", mode.try_into().unwrap()),
243 ("st_nlink", 0),
244 ("st_ino", 0),
245 ("st_uid", metadata.uid.into()),
246 ("st_gid", metadata.gid.into()),
247 ("st_rdev", 0),
248 ("st_atime", access_sec.into()),
249 ("st_atime_nsec", access_nsec.into()),
250 ("st_mtime", modified_sec.into()),
251 ("st_mtime_nsec", modified_nsec.into()),
252 ("st_ctime", 0),
253 ("st_ctime_nsec", 0),
254 ("st_size", metadata.size.into()),
255 ("st_blocks", 0),
256 ("st_blksize", 0),
257 ],
258 &buf,
259 )?;
260
261 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
262 this.write_int_fields_named(
263 &[
264 ("st_birthtime", created_sec.into()),
265 ("st_birthtime_nsec", created_nsec.into()),
266 ("st_flags", 0),
267 ("st_gen", 0),
268 ],
269 &buf,
270 )?;
271 }
272
273 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
274 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
275 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
277 }
278
279 interp_ok(0)
280 }
281
282 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
283 #[cfg(unix)]
284 use std::os::unix::fs::FileTypeExt;
285
286 let this = self.eval_context_ref();
287 match file_type {
288 Ok(file_type) => {
289 match () {
290 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
291 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
292 _ if file_type.is_symlink() =>
293 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
294 #[cfg(unix)]
296 _ if file_type.is_block_device() =>
297 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
298 #[cfg(unix)]
299 _ if file_type.is_char_device() =>
300 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
301 #[cfg(unix)]
302 _ if file_type.is_fifo() =>
303 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
304 #[cfg(unix)]
305 _ if file_type.is_socket() =>
306 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
307 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
309 }
310 }
311 Err(_) => {
312 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
314 }
315 }
316 }
317
318 fn dir_entry_fields(
319 &self,
320 entry: Either<fs::DirEntry, &'static str>,
321 ) -> InterpResult<'tcx, DirEntry> {
322 let this = self.eval_context_ref();
323 interp_ok(match entry {
324 Either::Left(dir_entry) => {
325 DirEntry {
326 name: dir_entry.file_name(),
327 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
328 #[cfg(unix)]
331 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
332 #[cfg(not(unix))]
333 ino: 0u64,
334 }
335 }
336 Either::Right(special) =>
337 DirEntry {
338 name: special.into(),
339 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
340 ino: 0,
341 },
342 })
343 }
344}
345
346impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
347pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
348 fn open(
349 &mut self,
350 path_raw: &OpTy<'tcx>,
351 flag: &OpTy<'tcx>,
352 varargs: &[OpTy<'tcx>],
353 ) -> InterpResult<'tcx, Scalar> {
354 let this = self.eval_context_mut();
355
356 let path_raw = this.read_pointer(path_raw)?;
357 let path = this.read_path_from_c_str(path_raw)?;
358 let flag = this.read_scalar(flag)?.to_i32()?;
359
360 let mut options = OpenOptions::new();
361
362 let o_rdonly = this.eval_libc_i32("O_RDONLY");
363 let o_wronly = this.eval_libc_i32("O_WRONLY");
364 let o_rdwr = this.eval_libc_i32("O_RDWR");
365 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
369 throw_unsup_format!("access mode flags on this target are unsupported");
370 }
371 let mut writable = true;
372
373 let access_mode = flag & 0b11;
375
376 if access_mode == o_rdonly {
377 writable = false;
378 options.read(true);
379 } else if access_mode == o_wronly {
380 options.write(true);
381 } else if access_mode == o_rdwr {
382 options.read(true).write(true);
383 } else {
384 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
385 }
386 let mut mirror = access_mode;
390
391 let o_append = this.eval_libc_i32("O_APPEND");
392 if flag & o_append == o_append {
393 options.append(true);
394 mirror |= o_append;
395 }
396 let o_trunc = this.eval_libc_i32("O_TRUNC");
397 if flag & o_trunc == o_trunc {
398 options.truncate(true);
399 mirror |= o_trunc;
400 }
401 let o_creat = this.eval_libc_i32("O_CREAT");
402 if flag & o_creat == o_creat {
403 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
407 let mode = this.read_scalar(mode)?.to_u32()?;
408
409 #[cfg(unix)]
410 {
411 use std::os::unix::fs::OpenOptionsExt;
413 options.mode(mode);
414 }
415 #[cfg(not(unix))]
416 {
417 if mode != 0o666 {
419 throw_unsup_format!(
420 "non-default mode 0o{:o} is not supported on non-Unix hosts",
421 mode
422 );
423 }
424 }
425
426 mirror |= o_creat;
427
428 let o_excl = this.eval_libc_i32("O_EXCL");
429 if flag & o_excl == o_excl {
430 mirror |= o_excl;
431 options.create_new(true);
432 } else {
433 options.create(true);
434 }
435 }
436 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
437 if flag & o_cloexec == o_cloexec {
438 mirror |= o_cloexec;
441 }
442 if this.tcx.sess.target.os == Os::Linux {
443 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
444 if flag & o_tmpfile == o_tmpfile {
445 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
447 }
448 }
449
450 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
451 if flag & o_nofollow == o_nofollow {
452 #[cfg(unix)]
453 {
454 use std::os::unix::fs::OpenOptionsExt;
455 options.custom_flags(libc::O_NOFOLLOW);
456 }
457 #[cfg(not(unix))]
461 {
462 if path.is_symlink() {
465 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
466 }
467 }
468 mirror |= o_nofollow;
469 }
470
471 if flag != mirror {
474 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
475 }
476
477 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
479 this.reject_in_isolation("`open`", reject_with)?;
480 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
481 }
482
483 let fd = options
484 .open(path)
485 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
486
487 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
488 }
489
490 fn lseek(
491 &mut self,
492 fd_num: i32,
493 offset: i128,
494 whence: i32,
495 dest: &MPlaceTy<'tcx>,
496 ) -> InterpResult<'tcx> {
497 let this = self.eval_context_mut();
498
499 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
502 if offset < 0 {
503 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
505 } else {
506 SeekFrom::Start(u64::try_from(offset).unwrap())
507 }
508 } else if whence == this.eval_libc_i32("SEEK_CUR") {
509 SeekFrom::Current(i64::try_from(offset).unwrap())
510 } else if whence == this.eval_libc_i32("SEEK_END") {
511 SeekFrom::End(i64::try_from(offset).unwrap())
512 } else {
513 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
514 };
515
516 let communicate = this.machine.communicate();
517
518 let Some(fd) = this.machine.fds.get(fd_num) else {
519 return this.set_last_error_and_return(LibcError("EBADF"), dest);
520 };
521 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
522 drop(fd);
523
524 let result = this.try_unwrap_io_result(result)?;
525 this.write_int(result, dest)?;
526 interp_ok(())
527 }
528
529 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
530 let this = self.eval_context_mut();
531
532 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
533
534 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
536 this.reject_in_isolation("`unlink`", reject_with)?;
537 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
538 }
539
540 let result = remove_file(path).map(|_| 0);
541 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
542 }
543
544 fn symlink(
545 &mut self,
546 target_op: &OpTy<'tcx>,
547 linkpath_op: &OpTy<'tcx>,
548 ) -> InterpResult<'tcx, Scalar> {
549 #[cfg(unix)]
550 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
551 std::os::unix::fs::symlink(src, dst)
552 }
553
554 #[cfg(windows)]
555 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
556 use std::os::windows::fs;
557 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
558 }
559
560 let this = self.eval_context_mut();
561 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
562 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
563
564 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
566 this.reject_in_isolation("`symlink`", reject_with)?;
567 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
568 }
569
570 let result = create_link(&target, &linkpath).map(|_| 0);
571 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
572 }
573
574 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
575 let this = self.eval_context_mut();
576
577 if !matches!(
578 &this.tcx.sess.target.os,
579 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
580 ) {
581 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
582 }
583
584 let path_scalar = this.read_pointer(path_op)?;
585 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
586
587 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
589 this.reject_in_isolation("`stat`", reject_with)?;
590 return this.set_last_error_and_return_i32(LibcError("EACCES"));
591 }
592
593 let metadata = match FileMetadata::from_path(this, &path, true)? {
595 Ok(metadata) => metadata,
596 Err(err) => return this.set_last_error_and_return_i32(err),
597 };
598
599 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
600 }
601
602 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
604 let this = self.eval_context_mut();
605
606 if !matches!(
607 &this.tcx.sess.target.os,
608 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
609 ) {
610 panic!(
611 "`macos_fbsd_solaris_lstat` should not be called on {}",
612 this.tcx.sess.target.os
613 );
614 }
615
616 let path_scalar = this.read_pointer(path_op)?;
617 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
618
619 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
621 this.reject_in_isolation("`lstat`", reject_with)?;
622 return this.set_last_error_and_return_i32(LibcError("EACCES"));
623 }
624
625 let metadata = match FileMetadata::from_path(this, &path, false)? {
626 Ok(metadata) => metadata,
627 Err(err) => return this.set_last_error_and_return_i32(err),
628 };
629
630 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
631 }
632
633 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
634 let this = self.eval_context_mut();
635
636 if !matches!(
637 &this.tcx.sess.target.os,
638 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
639 ) {
640 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
641 }
642
643 let fd = this.read_scalar(fd_op)?.to_i32()?;
644
645 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
647 this.reject_in_isolation("`fstat`", reject_with)?;
648 return this.set_last_error_and_return_i32(LibcError("EBADF"));
650 }
651
652 let metadata = match FileMetadata::from_fd_num(this, fd)? {
653 Ok(metadata) => metadata,
654 Err(err) => return this.set_last_error_and_return_i32(err),
655 };
656 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
657 }
658
659 fn linux_statx(
660 &mut self,
661 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
667 let this = self.eval_context_mut();
668
669 this.assert_target_os(Os::Linux, "statx");
670
671 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
672 let pathname_ptr = this.read_pointer(pathname_op)?;
673 let flags = this.read_scalar(flags_op)?.to_i32()?;
674 let _mask = this.read_scalar(mask_op)?.to_u32()?;
675 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
676
677 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
679 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
680 }
681
682 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
683
684 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
685 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
687 let empty_path_flag = flags & at_empty_path == at_empty_path;
688 if !(path.is_absolute()
696 || dirfd == this.eval_libc_i32("AT_FDCWD")
697 || (path.as_os_str().is_empty() && empty_path_flag))
698 {
699 throw_unsup_format!(
700 "using statx is only supported with absolute paths, relative paths with the file \
701 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
702 file descriptor"
703 )
704 }
705
706 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
708 this.reject_in_isolation("`statx`", reject_with)?;
709 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
710 LibcError("EACCES")
713 } else {
714 assert!(empty_path_flag);
718 LibcError("EBADF")
719 };
720 return this.set_last_error_and_return_i32(ecode);
721 }
722
723 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
728
729 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
732
733 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
736 FileMetadata::from_fd_num(this, dirfd)?
737 } else {
738 FileMetadata::from_path(this, &path, follow_symlink)?
739 };
740 let metadata = match metadata {
741 Ok(metadata) => metadata,
742 Err(err) => return this.set_last_error_and_return_i32(err),
743 };
744
745 let mode: u16 = metadata
750 .mode
751 .to_u32()?
752 .try_into()
753 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
754
755 let (access_sec, access_nsec) = metadata
758 .accessed
759 .map(|tup| {
760 mask |= this.eval_libc_u32("STATX_ATIME");
761 interp_ok(tup)
762 })
763 .unwrap_or_else(|| interp_ok((0, 0)))?;
764
765 let (created_sec, created_nsec) = metadata
766 .created
767 .map(|tup| {
768 mask |= this.eval_libc_u32("STATX_BTIME");
769 interp_ok(tup)
770 })
771 .unwrap_or_else(|| interp_ok((0, 0)))?;
772
773 let (modified_sec, modified_nsec) = metadata
774 .modified
775 .map(|tup| {
776 mask |= this.eval_libc_u32("STATX_MTIME");
777 interp_ok(tup)
778 })
779 .unwrap_or_else(|| interp_ok((0, 0)))?;
780
781 this.write_int_fields_named(
783 &[
784 ("stx_mask", mask.into()),
785 ("stx_blksize", 0),
786 ("stx_attributes", 0),
787 ("stx_nlink", 0),
788 ("stx_uid", 0),
789 ("stx_gid", 0),
790 ("stx_mode", mode.into()),
791 ("stx_ino", 0),
792 ("stx_size", metadata.size.into()),
793 ("stx_blocks", 0),
794 ("stx_attributes_mask", 0),
795 ("stx_rdev_major", 0),
796 ("stx_rdev_minor", 0),
797 ("stx_dev_major", 0),
798 ("stx_dev_minor", 0),
799 ],
800 &statxbuf,
801 )?;
802 #[rustfmt::skip]
803 this.write_int_fields_named(
804 &[
805 ("tv_sec", access_sec.into()),
806 ("tv_nsec", access_nsec.into()),
807 ],
808 &this.project_field_named(&statxbuf, "stx_atime")?,
809 )?;
810 #[rustfmt::skip]
811 this.write_int_fields_named(
812 &[
813 ("tv_sec", created_sec.into()),
814 ("tv_nsec", created_nsec.into()),
815 ],
816 &this.project_field_named(&statxbuf, "stx_btime")?,
817 )?;
818 #[rustfmt::skip]
819 this.write_int_fields_named(
820 &[
821 ("tv_sec", 0.into()),
822 ("tv_nsec", 0.into()),
823 ],
824 &this.project_field_named(&statxbuf, "stx_ctime")?,
825 )?;
826 #[rustfmt::skip]
827 this.write_int_fields_named(
828 &[
829 ("tv_sec", modified_sec.into()),
830 ("tv_nsec", modified_nsec.into()),
831 ],
832 &this.project_field_named(&statxbuf, "stx_mtime")?,
833 )?;
834
835 interp_ok(Scalar::from_i32(0))
836 }
837
838 fn rename(
839 &mut self,
840 oldpath_op: &OpTy<'tcx>,
841 newpath_op: &OpTy<'tcx>,
842 ) -> InterpResult<'tcx, Scalar> {
843 let this = self.eval_context_mut();
844
845 let oldpath_ptr = this.read_pointer(oldpath_op)?;
846 let newpath_ptr = this.read_pointer(newpath_op)?;
847
848 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
849 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
850 }
851
852 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
853 let newpath = this.read_path_from_c_str(newpath_ptr)?;
854
855 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
857 this.reject_in_isolation("`rename`", reject_with)?;
858 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
859 }
860
861 let result = rename(oldpath, newpath).map(|_| 0);
862
863 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
864 }
865
866 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
867 let this = self.eval_context_mut();
868
869 #[cfg_attr(not(unix), allow(unused_variables))]
870 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
871 u32::from(this.read_scalar(mode_op)?.to_u16()?)
872 } else {
873 this.read_scalar(mode_op)?.to_u32()?
874 };
875
876 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
877
878 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
880 this.reject_in_isolation("`mkdir`", reject_with)?;
881 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
882 }
883
884 #[cfg_attr(not(unix), allow(unused_mut))]
885 let mut builder = DirBuilder::new();
886
887 #[cfg(unix)]
890 {
891 use std::os::unix::fs::DirBuilderExt;
892 builder.mode(mode);
893 }
894
895 let result = builder.create(path).map(|_| 0i32);
896
897 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
898 }
899
900 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
901 let this = self.eval_context_mut();
902
903 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
904
905 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
907 this.reject_in_isolation("`rmdir`", reject_with)?;
908 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
909 }
910
911 let result = remove_dir(path).map(|_| 0i32);
912
913 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
914 }
915
916 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
917 let this = self.eval_context_mut();
918
919 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
920
921 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
923 this.reject_in_isolation("`opendir`", reject_with)?;
924 this.set_last_error(LibcError("EACCES"))?;
925 return interp_ok(Scalar::null_ptr(this));
926 }
927
928 let result = read_dir(name);
929
930 match result {
931 Ok(dir_iter) => {
932 let id = this.machine.dirs.insert_new(dir_iter);
933
934 interp_ok(Scalar::from_target_usize(id, this))
938 }
939 Err(e) => {
940 this.set_last_error(e)?;
941 interp_ok(Scalar::null_ptr(this))
942 }
943 }
944 }
945
946 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
947 let this = self.eval_context_mut();
948
949 if !matches!(
950 &this.tcx.sess.target.os,
951 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
952 ) {
953 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
954 }
955
956 let dirp = this.read_target_usize(dirp_op)?;
957
958 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
960 this.reject_in_isolation("`readdir`", reject_with)?;
961 this.set_last_error(LibcError("EBADF"))?;
962 this.write_null(dest)?;
963 return interp_ok(());
964 }
965
966 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
967 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
968 })?;
969
970 let entry = match open_dir.next_host_entry() {
971 Some(Ok(dir_entry)) => {
972 let dir_entry = this.dir_entry_fields(dir_entry)?;
973
974 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1011 let dirent_layout = this.layout_of(dirent_ty)?;
1012 let fields = &dirent_layout.fields;
1013 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1014
1015 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1019 let name_len = u64::try_from(name_bytes.len()).unwrap();
1020 let size = d_name_offset.strict_add(name_len);
1021
1022 let entry = this.allocate_ptr(
1023 Size::from_bytes(size),
1024 dirent_layout.align.abi,
1025 MiriMemoryKind::Runtime.into(),
1026 AllocInit::Uninit,
1027 )?;
1028 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1029
1030 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1033 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1034
1035 let ino_name =
1037 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1038 this.write_int_fields_named(
1039 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1040 &entry,
1041 )?;
1042
1043 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1045 this.write_null(&d_off)?;
1046 }
1047 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1048 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1049 }
1050 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1051 this.write_int(dir_entry.d_type, &d_type)?;
1052 }
1053
1054 Some(entry.ptr())
1055 }
1056 None => {
1057 None
1059 }
1060 Some(Err(e)) => {
1061 this.set_last_error(e)?;
1062 None
1063 }
1064 };
1065
1066 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1067 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1068 if let Some(old_entry) = old_entry {
1069 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1070 }
1071
1072 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1073 interp_ok(())
1074 }
1075
1076 fn macos_readdir_r(
1077 &mut self,
1078 dirp_op: &OpTy<'tcx>,
1079 entry_op: &OpTy<'tcx>,
1080 result_op: &OpTy<'tcx>,
1081 ) -> InterpResult<'tcx, Scalar> {
1082 let this = self.eval_context_mut();
1083
1084 this.assert_target_os(Os::MacOs, "readdir_r");
1085
1086 let dirp = this.read_target_usize(dirp_op)?;
1087 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1088
1089 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1091 this.reject_in_isolation("`readdir_r`", reject_with)?;
1092 return interp_ok(this.eval_libc("EBADF"));
1094 }
1095
1096 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1097 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1098 })?;
1099 interp_ok(match open_dir.next_host_entry() {
1100 Some(Ok(dir_entry)) => {
1101 let dir_entry = this.dir_entry_fields(dir_entry)?;
1102 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1117
1118 let name_place = this.project_field_named(&entry_place, "d_name")?;
1120 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1121 &dir_entry.name,
1122 name_place.ptr(),
1123 name_place.layout.size.bytes(),
1124 )?;
1125 if !name_fits {
1126 throw_unsup_format!(
1127 "a directory entry had a name too large to fit in libc::dirent"
1128 );
1129 }
1130
1131 this.write_int_fields_named(
1133 &[
1134 ("d_reclen", entry_place.layout.size.bytes().into()),
1135 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1136 ("d_type", dir_entry.d_type.into()),
1137 ("d_ino", dir_entry.ino.into()),
1138 ("d_seekoff", 0),
1139 ],
1140 &entry_place,
1141 )?;
1142 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1143
1144 Scalar::from_i32(0)
1145 }
1146 None => {
1147 this.write_null(&result_place)?;
1149 Scalar::from_i32(0)
1150 }
1151 Some(Err(e)) => {
1152 this.io_error_to_errnum(e)?
1154 }
1155 })
1156 }
1157
1158 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1159 let this = self.eval_context_mut();
1160
1161 let dirp = this.read_target_usize(dirp_op)?;
1162
1163 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1165 this.reject_in_isolation("`closedir`", reject_with)?;
1166 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1167 }
1168
1169 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1170 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1171 };
1172 if let Some(entry) = open_dir.entry.take() {
1173 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1174 }
1175 drop(open_dir);
1177
1178 interp_ok(Scalar::from_i32(0))
1179 }
1180
1181 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1182 let this = self.eval_context_mut();
1183
1184 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1186 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1187 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1189 }
1190
1191 let Some(fd) = this.machine.fds.get(fd_num) else {
1192 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1193 };
1194
1195 let Some(file) = fd.downcast::<FileHandle>() else {
1196 return interp_ok(this.eval_libc("EINVAL"));
1199 };
1200
1201 if file.writable {
1202 if let Ok(length) = length.try_into() {
1203 let result = file.file.set_len(length);
1204 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1205 interp_ok(Scalar::from_i32(result))
1206 } else {
1207 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1208 }
1209 } else {
1210 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1212 }
1213 }
1214
1215 fn posix_fallocate(
1218 &mut self,
1219 fd_num: i32,
1220 offset: i64,
1221 len: i64,
1222 ) -> InterpResult<'tcx, Scalar> {
1223 let this = self.eval_context_mut();
1224
1225 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1227 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1228 return interp_ok(this.eval_libc("EBADF"));
1230 }
1231
1232 if offset < 0 || len <= 0 {
1234 return interp_ok(this.eval_libc("EINVAL"));
1235 }
1236
1237 let Some(fd) = this.machine.fds.get(fd_num) else {
1239 return interp_ok(this.eval_libc("EBADF"));
1240 };
1241 let Some(file) = fd.downcast::<FileHandle>() else {
1242 return interp_ok(this.eval_libc("ENODEV"));
1244 };
1245
1246 if !file.writable {
1247 return interp_ok(this.eval_libc("EBADF"));
1249 }
1250
1251 let current_size = match file.file.metadata() {
1252 Ok(metadata) => metadata.len(),
1253 Err(err) => return this.io_error_to_errnum(err),
1254 };
1255 let new_size = match offset.checked_add(len) {
1257 Some(new_size) => u64::try_from(new_size).unwrap(),
1259 None => return interp_ok(this.eval_libc("EFBIG")), };
1261 if current_size < new_size {
1264 interp_ok(match file.file.set_len(new_size) {
1265 Ok(()) => Scalar::from_i32(0),
1266 Err(e) => this.io_error_to_errnum(e)?,
1267 })
1268 } else {
1269 interp_ok(Scalar::from_i32(0))
1270 }
1271 }
1272
1273 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1274 let this = self.eval_context_mut();
1280
1281 let fd = this.read_scalar(fd_op)?.to_i32()?;
1282
1283 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1285 this.reject_in_isolation("`fsync`", reject_with)?;
1286 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1288 }
1289
1290 self.ffullsync_fd(fd)
1291 }
1292
1293 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1294 let this = self.eval_context_mut();
1295 let Some(fd) = this.machine.fds.get(fd_num) else {
1296 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1297 };
1298 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1300 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1301 })?;
1302 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1303 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1304 }
1305
1306 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1307 let this = self.eval_context_mut();
1308
1309 let fd = this.read_scalar(fd_op)?.to_i32()?;
1310
1311 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1313 this.reject_in_isolation("`fdatasync`", reject_with)?;
1314 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1316 }
1317
1318 let Some(fd) = this.machine.fds.get(fd) else {
1319 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1320 };
1321 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1323 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1324 })?;
1325 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1326 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1327 }
1328
1329 fn sync_file_range(
1330 &mut self,
1331 fd_op: &OpTy<'tcx>,
1332 offset_op: &OpTy<'tcx>,
1333 nbytes_op: &OpTy<'tcx>,
1334 flags_op: &OpTy<'tcx>,
1335 ) -> InterpResult<'tcx, Scalar> {
1336 let this = self.eval_context_mut();
1337
1338 let fd = this.read_scalar(fd_op)?.to_i32()?;
1339 let offset = this.read_scalar(offset_op)?.to_i64()?;
1340 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1341 let flags = this.read_scalar(flags_op)?.to_i32()?;
1342
1343 if offset < 0 || nbytes < 0 {
1344 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1345 }
1346 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1347 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1348 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1349 if flags & allowed_flags != flags {
1350 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1351 }
1352
1353 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1355 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1356 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1358 }
1359
1360 let Some(fd) = this.machine.fds.get(fd) else {
1361 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1362 };
1363 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1365 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1366 })?;
1367 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1368 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1369 }
1370
1371 fn readlink(
1372 &mut self,
1373 pathname_op: &OpTy<'tcx>,
1374 buf_op: &OpTy<'tcx>,
1375 bufsize_op: &OpTy<'tcx>,
1376 ) -> InterpResult<'tcx, i64> {
1377 let this = self.eval_context_mut();
1378
1379 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1380 let buf = this.read_pointer(buf_op)?;
1381 let bufsize = this.read_target_usize(bufsize_op)?;
1382
1383 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1385 this.reject_in_isolation("`readlink`", reject_with)?;
1386 this.set_last_error(LibcError("EACCES"))?;
1387 return interp_ok(-1);
1388 }
1389
1390 let result = std::fs::read_link(pathname);
1391 match result {
1392 Ok(resolved) => {
1393 let resolved = this.convert_path(
1397 Cow::Borrowed(resolved.as_ref()),
1398 crate::shims::os_str::PathConversion::HostToTarget,
1399 );
1400 let mut path_bytes = resolved.as_encoded_bytes();
1401 let bufsize: usize = bufsize.try_into().unwrap();
1402 if path_bytes.len() > bufsize {
1403 path_bytes = &path_bytes[..bufsize]
1404 }
1405 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1406 interp_ok(path_bytes.len().try_into().unwrap())
1407 }
1408 Err(e) => {
1409 this.set_last_error(e)?;
1410 interp_ok(-1)
1411 }
1412 }
1413 }
1414
1415 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1416 let this = self.eval_context_mut();
1417 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1420 let error = if let Some(fd) = this.machine.fds.get(fd) {
1421 if fd.is_tty(this.machine.communicate()) {
1422 return interp_ok(Scalar::from_i32(1));
1423 } else {
1424 LibcError("ENOTTY")
1425 }
1426 } else {
1427 LibcError("EBADF")
1429 };
1430 this.set_last_error(error)?;
1431 interp_ok(Scalar::from_i32(0))
1432 }
1433
1434 fn realpath(
1435 &mut self,
1436 path_op: &OpTy<'tcx>,
1437 processed_path_op: &OpTy<'tcx>,
1438 ) -> InterpResult<'tcx, Scalar> {
1439 let this = self.eval_context_mut();
1440 this.assert_target_os_is_unix("realpath");
1441
1442 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1443 let processed_ptr = this.read_pointer(processed_path_op)?;
1444
1445 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1447 this.reject_in_isolation("`realpath`", reject_with)?;
1448 this.set_last_error(LibcError("EACCES"))?;
1449 return interp_ok(Scalar::from_target_usize(0, this));
1450 }
1451
1452 let result = std::fs::canonicalize(pathname);
1453 match result {
1454 Ok(resolved) => {
1455 let path_max = this
1456 .eval_libc_i32("PATH_MAX")
1457 .try_into()
1458 .expect("PATH_MAX does not fit in u64");
1459 let dest = if this.ptr_is_null(processed_ptr)? {
1460 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1470 } else {
1471 let (wrote_path, _) =
1472 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1473
1474 if !wrote_path {
1475 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1479 return interp_ok(Scalar::from_target_usize(0, this));
1480 }
1481 processed_ptr
1482 };
1483
1484 interp_ok(Scalar::from_maybe_pointer(dest, this))
1485 }
1486 Err(e) => {
1487 this.set_last_error(e)?;
1488 interp_ok(Scalar::from_target_usize(0, this))
1489 }
1490 }
1491 }
1492 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1493 use rand::seq::IndexedRandom;
1494
1495 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1497
1498 let this = self.eval_context_mut();
1499 this.assert_target_os_is_unix("mkstemp");
1500
1501 let max_attempts = this.eval_libc_u32("TMP_MAX");
1511
1512 let template_ptr = this.read_pointer(template_op)?;
1515 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1516 let template_bytes = template.as_mut_slice();
1517
1518 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1520 this.reject_in_isolation("`mkstemp`", reject_with)?;
1521 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1522 }
1523
1524 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1526
1527 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1532 let end_pos = template_bytes.len();
1533 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1534
1535 if last_six_char_bytes != suffix_bytes {
1537 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1538 }
1539
1540 const SUBSTITUTIONS: &[char; 62] = &[
1544 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1545 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1546 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1547 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1548 ];
1549
1550 let mut fopts = OpenOptions::new();
1553 fopts.read(true).write(true).create_new(true);
1554
1555 #[cfg(unix)]
1556 {
1557 use std::os::unix::fs::OpenOptionsExt;
1558 fopts.mode(0o600);
1560 fopts.custom_flags(libc::O_EXCL);
1561 }
1562 #[cfg(windows)]
1563 {
1564 use std::os::windows::fs::OpenOptionsExt;
1565 fopts.share_mode(0);
1567 }
1568
1569 for _ in 0..max_attempts {
1571 let rng = this.machine.rng.get_mut();
1572
1573 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1575
1576 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1578
1579 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1581
1582 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1584
1585 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1586
1587 let file = fopts.open(possibly_unique);
1588
1589 match file {
1590 Ok(f) => {
1591 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1592 return interp_ok(Scalar::from_i32(fd));
1593 }
1594 Err(e) =>
1595 match e.kind() {
1596 ErrorKind::AlreadyExists => continue,
1598 _ => {
1600 return this.set_last_error_and_return_i32(e);
1603 }
1604 },
1605 }
1606 }
1607
1608 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1610 }
1611}
1612
1613fn extract_sec_and_nsec<'tcx>(
1617 time: std::io::Result<SystemTime>,
1618) -> InterpResult<'tcx, Option<(u64, u32)>> {
1619 match time.ok() {
1620 Some(time) => {
1621 let duration = system_time_to_duration(&time)?;
1622 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1623 }
1624 None => interp_ok(None),
1625 }
1626}
1627
1628struct FileMetadata {
1631 mode: Scalar,
1632 size: u64,
1633 created: Option<(u64, u32)>,
1634 accessed: Option<(u64, u32)>,
1635 modified: Option<(u64, u32)>,
1636 dev: u64,
1637 uid: u32,
1638 gid: u32,
1639}
1640
1641impl FileMetadata {
1642 fn from_path<'tcx>(
1643 ecx: &mut MiriInterpCx<'tcx>,
1644 path: &Path,
1645 follow_symlink: bool,
1646 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1647 let metadata =
1648 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1649
1650 FileMetadata::from_meta(ecx, metadata)
1651 }
1652
1653 fn from_fd_num<'tcx>(
1654 ecx: &mut MiriInterpCx<'tcx>,
1655 fd_num: i32,
1656 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1657 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1658 return interp_ok(Err(LibcError("EBADF")));
1659 };
1660
1661 let metadata = fd.metadata()?;
1662 drop(fd);
1663 FileMetadata::from_meta(ecx, metadata)
1664 }
1665
1666 fn from_meta<'tcx>(
1667 ecx: &mut MiriInterpCx<'tcx>,
1668 metadata: Result<std::fs::Metadata, std::io::Error>,
1669 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1670 let metadata = match metadata {
1671 Ok(metadata) => metadata,
1672 Err(e) => {
1673 return interp_ok(Err(e.into()));
1674 }
1675 };
1676
1677 let file_type = metadata.file_type();
1678
1679 let mode_name = if file_type.is_file() {
1680 "S_IFREG"
1681 } else if file_type.is_dir() {
1682 "S_IFDIR"
1683 } else {
1684 "S_IFLNK"
1685 };
1686
1687 let mode = ecx.eval_libc(mode_name);
1688
1689 let size = metadata.len();
1690
1691 let created = extract_sec_and_nsec(metadata.created())?;
1692 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1693 let modified = extract_sec_and_nsec(metadata.modified())?;
1694
1695 cfg_select! {
1698 unix => {
1699 use std::os::unix::fs::MetadataExt;
1700 let dev = metadata.dev();
1701 let uid = metadata.uid();
1702 let gid = metadata.gid();
1703 }
1704 _ => {
1705 let dev = 0;
1706 let uid = 0;
1707 let gid = 0;
1708 }
1709 }
1710
1711 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1712 }
1713}