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::{self, 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 flag = this.read_scalar(flag)?.to_i32()?;
358
359 let path = this.read_path_from_c_str(path_raw)?;
360 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
362 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
363 {
364 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
365 }
366
367 let mut flag = flag;
369
370 let mut options = OpenOptions::new();
371
372 let o_rdonly = this.eval_libc_i32("O_RDONLY");
373 let o_wronly = this.eval_libc_i32("O_WRONLY");
374 let o_rdwr = this.eval_libc_i32("O_RDWR");
375 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
379 throw_unsup_format!("access mode flags on this target are unsupported");
380 }
381 let mut writable = true;
382
383 let access_mode = flag & 0b11;
385 flag &= !access_mode;
386
387 if access_mode == o_rdonly {
388 writable = false;
389 options.read(true);
390 } else if access_mode == o_wronly {
391 options.write(true);
392 } else if access_mode == o_rdwr {
393 options.read(true).write(true);
394 } else {
395 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
396 }
397
398 let o_append = this.eval_libc_i32("O_APPEND");
399 if flag & o_append == o_append {
400 flag &= !o_append;
401 options.append(true);
402 }
403 let o_trunc = this.eval_libc_i32("O_TRUNC");
404 if flag & o_trunc == o_trunc {
405 flag &= !o_trunc;
406 options.truncate(true);
407 }
408 let o_creat = this.eval_libc_i32("O_CREAT");
409 if flag & o_creat == o_creat {
410 flag &= !o_creat;
411 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
415 let mode = this.read_scalar(mode)?.to_u32()?;
416
417 #[cfg(unix)]
418 {
419 use std::os::unix::fs::OpenOptionsExt;
421 options.mode(mode);
422 }
423 #[cfg(not(unix))]
424 {
425 if mode != 0o666 {
427 throw_unsup_format!(
428 "non-default mode 0o{:o} is not supported on non-Unix hosts",
429 mode
430 );
431 }
432 }
433
434 let o_excl = this.eval_libc_i32("O_EXCL");
435 if flag & o_excl == o_excl {
436 flag &= !o_excl;
437 options.create_new(true);
438 } else {
439 options.create(true);
440 }
441 }
442 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
443 if flag & o_cloexec == o_cloexec {
444 flag &= !o_cloexec;
445 }
448 if this.tcx.sess.target.os == Os::Linux {
449 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
450 if flag & o_tmpfile == o_tmpfile {
451 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
453 }
454 }
455
456 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
457 if flag & o_nofollow == o_nofollow {
458 flag &= !o_nofollow;
459 #[cfg(unix)]
460 {
461 use std::os::unix::fs::OpenOptionsExt;
462 options.custom_flags(libc::O_NOFOLLOW);
463 }
464 #[cfg(not(unix))]
468 {
469 if path.is_symlink() {
472 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
473 }
474 }
475 }
476
477 if flag != 0 {
479 throw_unsup_format!("unsupported flags {:#x}", flag);
480 }
481
482 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
484 this.reject_in_isolation("`open`", reject_with)?;
485 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
486 }
487
488 let fd = options
489 .open(path)
490 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
491
492 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
493 }
494
495 fn lseek(
496 &mut self,
497 fd_num: i32,
498 offset: i128,
499 whence: i32,
500 dest: &MPlaceTy<'tcx>,
501 ) -> InterpResult<'tcx> {
502 let this = self.eval_context_mut();
503
504 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
507 if offset < 0 {
508 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
510 } else {
511 SeekFrom::Start(u64::try_from(offset).unwrap())
512 }
513 } else if whence == this.eval_libc_i32("SEEK_CUR") {
514 SeekFrom::Current(i64::try_from(offset).unwrap())
515 } else if whence == this.eval_libc_i32("SEEK_END") {
516 SeekFrom::End(i64::try_from(offset).unwrap())
517 } else {
518 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
519 };
520
521 let communicate = this.machine.communicate();
522
523 let Some(fd) = this.machine.fds.get(fd_num) else {
524 return this.set_last_error_and_return(LibcError("EBADF"), dest);
525 };
526 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
527 drop(fd);
528
529 let result = this.try_unwrap_io_result(result)?;
530 this.write_int(result, dest)?;
531 interp_ok(())
532 }
533
534 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
535 let this = self.eval_context_mut();
536
537 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
538
539 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
541 this.reject_in_isolation("`unlink`", reject_with)?;
542 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
543 }
544
545 let result = remove_file(path).map(|_| 0);
546 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
547 }
548
549 fn symlink(
550 &mut self,
551 target_op: &OpTy<'tcx>,
552 linkpath_op: &OpTy<'tcx>,
553 ) -> InterpResult<'tcx, Scalar> {
554 #[cfg(unix)]
555 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
556 std::os::unix::fs::symlink(src, dst)
557 }
558
559 #[cfg(windows)]
560 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
561 use std::os::windows::fs;
562 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
563 }
564
565 let this = self.eval_context_mut();
566 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
567 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
568
569 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
571 this.reject_in_isolation("`symlink`", reject_with)?;
572 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
573 }
574
575 let result = create_link(&target, &linkpath).map(|_| 0);
576 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
577 }
578
579 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
580 let this = self.eval_context_mut();
581
582 if !matches!(
583 &this.tcx.sess.target.os,
584 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
585 ) {
586 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
587 }
588
589 let path_scalar = this.read_pointer(path_op)?;
590 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
591
592 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
594 this.reject_in_isolation("`stat`", reject_with)?;
595 return this.set_last_error_and_return_i32(LibcError("EACCES"));
596 }
597
598 let metadata = match FileMetadata::from_path(this, &path, true)? {
600 Ok(metadata) => metadata,
601 Err(err) => return this.set_last_error_and_return_i32(err),
602 };
603
604 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
605 }
606
607 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
609 let this = self.eval_context_mut();
610
611 if !matches!(
612 &this.tcx.sess.target.os,
613 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
614 ) {
615 panic!(
616 "`macos_fbsd_solaris_lstat` should not be called on {}",
617 this.tcx.sess.target.os
618 );
619 }
620
621 let path_scalar = this.read_pointer(path_op)?;
622 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
623
624 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
626 this.reject_in_isolation("`lstat`", reject_with)?;
627 return this.set_last_error_and_return_i32(LibcError("EACCES"));
628 }
629
630 let metadata = match FileMetadata::from_path(this, &path, false)? {
631 Ok(metadata) => metadata,
632 Err(err) => return this.set_last_error_and_return_i32(err),
633 };
634
635 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
636 }
637
638 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
639 let this = self.eval_context_mut();
640
641 if !matches!(
642 &this.tcx.sess.target.os,
643 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
644 ) {
645 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
646 }
647
648 let fd = this.read_scalar(fd_op)?.to_i32()?;
649
650 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
652 this.reject_in_isolation("`fstat`", reject_with)?;
653 return this.set_last_error_and_return_i32(LibcError("EBADF"));
655 }
656
657 let metadata = match FileMetadata::from_fd_num(this, fd)? {
658 Ok(metadata) => metadata,
659 Err(err) => return this.set_last_error_and_return_i32(err),
660 };
661 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
662 }
663
664 fn linux_statx(
665 &mut self,
666 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
672 let this = self.eval_context_mut();
673
674 this.assert_target_os(Os::Linux, "statx");
675
676 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
677 let pathname_ptr = this.read_pointer(pathname_op)?;
678 let flags = this.read_scalar(flags_op)?.to_i32()?;
679 let _mask = this.read_scalar(mask_op)?.to_u32()?;
680 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
681
682 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
684 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
685 }
686
687 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
688
689 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
690 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
692 let empty_path_flag = flags & at_empty_path == at_empty_path;
693 if !(path.is_absolute()
701 || dirfd == this.eval_libc_i32("AT_FDCWD")
702 || (path.as_os_str().is_empty() && empty_path_flag))
703 {
704 throw_unsup_format!(
705 "using statx is only supported with absolute paths, relative paths with the file \
706 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
707 file descriptor"
708 )
709 }
710
711 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
713 this.reject_in_isolation("`statx`", reject_with)?;
714 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
715 LibcError("EACCES")
718 } else {
719 assert!(empty_path_flag);
723 LibcError("EBADF")
724 };
725 return this.set_last_error_and_return_i32(ecode);
726 }
727
728 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
733
734 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
737
738 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
741 FileMetadata::from_fd_num(this, dirfd)?
742 } else {
743 FileMetadata::from_path(this, &path, follow_symlink)?
744 };
745 let metadata = match metadata {
746 Ok(metadata) => metadata,
747 Err(err) => return this.set_last_error_and_return_i32(err),
748 };
749
750 let mode: u16 = metadata
755 .mode
756 .to_u32()?
757 .try_into()
758 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
759
760 let (access_sec, access_nsec) = metadata
763 .accessed
764 .map(|tup| {
765 mask |= this.eval_libc_u32("STATX_ATIME");
766 interp_ok(tup)
767 })
768 .unwrap_or_else(|| interp_ok((0, 0)))?;
769
770 let (created_sec, created_nsec) = metadata
771 .created
772 .map(|tup| {
773 mask |= this.eval_libc_u32("STATX_BTIME");
774 interp_ok(tup)
775 })
776 .unwrap_or_else(|| interp_ok((0, 0)))?;
777
778 let (modified_sec, modified_nsec) = metadata
779 .modified
780 .map(|tup| {
781 mask |= this.eval_libc_u32("STATX_MTIME");
782 interp_ok(tup)
783 })
784 .unwrap_or_else(|| interp_ok((0, 0)))?;
785
786 this.write_int_fields_named(
788 &[
789 ("stx_mask", mask.into()),
790 ("stx_blksize", 0),
791 ("stx_attributes", 0),
792 ("stx_nlink", 0),
793 ("stx_uid", 0),
794 ("stx_gid", 0),
795 ("stx_mode", mode.into()),
796 ("stx_ino", 0),
797 ("stx_size", metadata.size.into()),
798 ("stx_blocks", 0),
799 ("stx_attributes_mask", 0),
800 ("stx_rdev_major", 0),
801 ("stx_rdev_minor", 0),
802 ("stx_dev_major", 0),
803 ("stx_dev_minor", 0),
804 ],
805 &statxbuf,
806 )?;
807 #[rustfmt::skip]
808 this.write_int_fields_named(
809 &[
810 ("tv_sec", access_sec.into()),
811 ("tv_nsec", access_nsec.into()),
812 ],
813 &this.project_field_named(&statxbuf, "stx_atime")?,
814 )?;
815 #[rustfmt::skip]
816 this.write_int_fields_named(
817 &[
818 ("tv_sec", created_sec.into()),
819 ("tv_nsec", created_nsec.into()),
820 ],
821 &this.project_field_named(&statxbuf, "stx_btime")?,
822 )?;
823 #[rustfmt::skip]
824 this.write_int_fields_named(
825 &[
826 ("tv_sec", 0.into()),
827 ("tv_nsec", 0.into()),
828 ],
829 &this.project_field_named(&statxbuf, "stx_ctime")?,
830 )?;
831 #[rustfmt::skip]
832 this.write_int_fields_named(
833 &[
834 ("tv_sec", modified_sec.into()),
835 ("tv_nsec", modified_nsec.into()),
836 ],
837 &this.project_field_named(&statxbuf, "stx_mtime")?,
838 )?;
839
840 interp_ok(Scalar::from_i32(0))
841 }
842
843 fn rename(
844 &mut self,
845 oldpath_op: &OpTy<'tcx>,
846 newpath_op: &OpTy<'tcx>,
847 ) -> InterpResult<'tcx, Scalar> {
848 let this = self.eval_context_mut();
849
850 let oldpath_ptr = this.read_pointer(oldpath_op)?;
851 let newpath_ptr = this.read_pointer(newpath_op)?;
852
853 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
854 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
855 }
856
857 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
858 let newpath = this.read_path_from_c_str(newpath_ptr)?;
859
860 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
862 this.reject_in_isolation("`rename`", reject_with)?;
863 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
864 }
865
866 let result = rename(oldpath, newpath).map(|_| 0);
867
868 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
869 }
870
871 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
872 let this = self.eval_context_mut();
873
874 #[cfg_attr(not(unix), allow(unused_variables))]
875 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
876 u32::from(this.read_scalar(mode_op)?.to_u16()?)
877 } else {
878 this.read_scalar(mode_op)?.to_u32()?
879 };
880
881 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
882
883 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
885 this.reject_in_isolation("`mkdir`", reject_with)?;
886 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
887 }
888
889 #[cfg_attr(not(unix), allow(unused_mut))]
890 let mut builder = DirBuilder::new();
891
892 #[cfg(unix)]
895 {
896 use std::os::unix::fs::DirBuilderExt;
897 builder.mode(mode);
898 }
899
900 let result = builder.create(path).map(|_| 0i32);
901
902 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
903 }
904
905 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
906 let this = self.eval_context_mut();
907
908 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
909
910 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
912 this.reject_in_isolation("`rmdir`", reject_with)?;
913 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
914 }
915
916 let result = remove_dir(path).map(|_| 0i32);
917
918 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
919 }
920
921 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
922 let this = self.eval_context_mut();
923
924 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
925
926 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
928 this.reject_in_isolation("`opendir`", reject_with)?;
929 this.set_last_error(LibcError("EACCES"))?;
930 return interp_ok(Scalar::null_ptr(this));
931 }
932
933 let result = read_dir(name);
934
935 match result {
936 Ok(dir_iter) => {
937 let id = this.machine.dirs.insert_new(dir_iter);
938
939 interp_ok(Scalar::from_target_usize(id, this))
943 }
944 Err(e) => {
945 this.set_last_error(e)?;
946 interp_ok(Scalar::null_ptr(this))
947 }
948 }
949 }
950
951 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
952 let this = self.eval_context_mut();
953
954 if !matches!(
955 &this.tcx.sess.target.os,
956 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
957 ) {
958 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
959 }
960
961 let dirp = this.read_target_usize(dirp_op)?;
962
963 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
965 this.reject_in_isolation("`readdir`", reject_with)?;
966 this.set_last_error(LibcError("EBADF"))?;
967 this.write_null(dest)?;
968 return interp_ok(());
969 }
970
971 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
972 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
973 })?;
974
975 let entry = match open_dir.next_host_entry() {
976 Some(Ok(dir_entry)) => {
977 let dir_entry = this.dir_entry_fields(dir_entry)?;
978
979 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1016 let dirent_layout = this.layout_of(dirent_ty)?;
1017 let fields = &dirent_layout.fields;
1018 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1019
1020 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1024 let name_len = u64::try_from(name_bytes.len()).unwrap();
1025 let size = d_name_offset.strict_add(name_len);
1026
1027 let entry = this.allocate_ptr(
1028 Size::from_bytes(size),
1029 dirent_layout.align.abi,
1030 MiriMemoryKind::Runtime.into(),
1031 AllocInit::Uninit,
1032 )?;
1033 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1034
1035 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1038 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1039
1040 let ino_name =
1042 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1043 this.write_int_fields_named(
1044 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1045 &entry,
1046 )?;
1047
1048 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1050 this.write_null(&d_off)?;
1051 }
1052 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1053 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1054 }
1055 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1056 this.write_int(dir_entry.d_type, &d_type)?;
1057 }
1058
1059 Some(entry.ptr())
1060 }
1061 None => {
1062 None
1064 }
1065 Some(Err(e)) => {
1066 this.set_last_error(e)?;
1067 None
1068 }
1069 };
1070
1071 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1072 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1073 if let Some(old_entry) = old_entry {
1074 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1075 }
1076
1077 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1078 interp_ok(())
1079 }
1080
1081 fn macos_readdir_r(
1082 &mut self,
1083 dirp_op: &OpTy<'tcx>,
1084 entry_op: &OpTy<'tcx>,
1085 result_op: &OpTy<'tcx>,
1086 ) -> InterpResult<'tcx, Scalar> {
1087 let this = self.eval_context_mut();
1088
1089 this.assert_target_os(Os::MacOs, "readdir_r");
1090
1091 let dirp = this.read_target_usize(dirp_op)?;
1092 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1093
1094 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1096 this.reject_in_isolation("`readdir_r`", reject_with)?;
1097 return interp_ok(this.eval_libc("EBADF"));
1099 }
1100
1101 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1102 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1103 })?;
1104 interp_ok(match open_dir.next_host_entry() {
1105 Some(Ok(dir_entry)) => {
1106 let dir_entry = this.dir_entry_fields(dir_entry)?;
1107 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1122
1123 let name_place = this.project_field_named(&entry_place, "d_name")?;
1125 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1126 &dir_entry.name,
1127 name_place.ptr(),
1128 name_place.layout.size.bytes(),
1129 )?;
1130 if !name_fits {
1131 throw_unsup_format!(
1132 "a directory entry had a name too large to fit in libc::dirent"
1133 );
1134 }
1135
1136 this.write_int_fields_named(
1138 &[
1139 ("d_reclen", entry_place.layout.size.bytes().into()),
1140 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1141 ("d_type", dir_entry.d_type.into()),
1142 ("d_ino", dir_entry.ino.into()),
1143 ("d_seekoff", 0),
1144 ],
1145 &entry_place,
1146 )?;
1147 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1148
1149 Scalar::from_i32(0)
1150 }
1151 None => {
1152 this.write_null(&result_place)?;
1154 Scalar::from_i32(0)
1155 }
1156 Some(Err(e)) => {
1157 this.io_error_to_errnum(e)?
1159 }
1160 })
1161 }
1162
1163 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1164 let this = self.eval_context_mut();
1165
1166 let dirp = this.read_target_usize(dirp_op)?;
1167
1168 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1170 this.reject_in_isolation("`closedir`", reject_with)?;
1171 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1172 }
1173
1174 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1175 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1176 };
1177 if let Some(entry) = open_dir.entry.take() {
1178 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1179 }
1180 drop(open_dir);
1182
1183 interp_ok(Scalar::from_i32(0))
1184 }
1185
1186 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> 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("`ftruncate64`", reject_with)?;
1192 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1194 }
1195
1196 let Some(fd) = this.machine.fds.get(fd_num) else {
1197 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1198 };
1199
1200 let Some(file) = fd.downcast::<FileHandle>() else {
1201 return interp_ok(this.eval_libc("EINVAL"));
1204 };
1205
1206 if file.writable {
1207 if let Ok(length) = length.try_into() {
1208 let result = file.file.set_len(length);
1209 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1210 interp_ok(Scalar::from_i32(result))
1211 } else {
1212 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1213 }
1214 } else {
1215 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1217 }
1218 }
1219
1220 fn posix_fallocate(
1223 &mut self,
1224 fd_num: i32,
1225 offset: i64,
1226 len: i64,
1227 ) -> InterpResult<'tcx, Scalar> {
1228 let this = self.eval_context_mut();
1229
1230 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1232 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1233 return interp_ok(this.eval_libc("EBADF"));
1235 }
1236
1237 if offset < 0 || len <= 0 {
1239 return interp_ok(this.eval_libc("EINVAL"));
1240 }
1241
1242 let Some(fd) = this.machine.fds.get(fd_num) else {
1244 return interp_ok(this.eval_libc("EBADF"));
1245 };
1246 let Some(file) = fd.downcast::<FileHandle>() else {
1247 return interp_ok(this.eval_libc("ENODEV"));
1249 };
1250
1251 if !file.writable {
1252 return interp_ok(this.eval_libc("EBADF"));
1254 }
1255
1256 let current_size = match file.file.metadata() {
1257 Ok(metadata) => metadata.len(),
1258 Err(err) => return this.io_error_to_errnum(err),
1259 };
1260 let new_size = match offset.checked_add(len) {
1262 Some(new_size) => u64::try_from(new_size).unwrap(),
1264 None => return interp_ok(this.eval_libc("EFBIG")), };
1266 if current_size < new_size {
1269 interp_ok(match file.file.set_len(new_size) {
1270 Ok(()) => Scalar::from_i32(0),
1271 Err(e) => this.io_error_to_errnum(e)?,
1272 })
1273 } else {
1274 interp_ok(Scalar::from_i32(0))
1275 }
1276 }
1277
1278 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1279 let this = self.eval_context_mut();
1285
1286 let fd = this.read_scalar(fd_op)?.to_i32()?;
1287
1288 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1290 this.reject_in_isolation("`fsync`", reject_with)?;
1291 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1293 }
1294
1295 self.ffullsync_fd(fd)
1296 }
1297
1298 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1299 let this = self.eval_context_mut();
1300 let Some(fd) = this.machine.fds.get(fd_num) else {
1301 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1302 };
1303 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1305 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1306 })?;
1307 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1308 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1309 }
1310
1311 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1312 let this = self.eval_context_mut();
1313
1314 let fd = this.read_scalar(fd_op)?.to_i32()?;
1315
1316 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1318 this.reject_in_isolation("`fdatasync`", 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!("`fdatasync` 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 sync_file_range(
1335 &mut self,
1336 fd_op: &OpTy<'tcx>,
1337 offset_op: &OpTy<'tcx>,
1338 nbytes_op: &OpTy<'tcx>,
1339 flags_op: &OpTy<'tcx>,
1340 ) -> InterpResult<'tcx, Scalar> {
1341 let this = self.eval_context_mut();
1342
1343 let fd = this.read_scalar(fd_op)?.to_i32()?;
1344 let offset = this.read_scalar(offset_op)?.to_i64()?;
1345 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1346 let flags = this.read_scalar(flags_op)?.to_i32()?;
1347
1348 if offset < 0 || nbytes < 0 {
1349 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1350 }
1351 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1352 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1353 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1354 if flags & allowed_flags != flags {
1355 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1356 }
1357
1358 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1360 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1361 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1363 }
1364
1365 let Some(fd) = this.machine.fds.get(fd) else {
1366 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1367 };
1368 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1370 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1371 })?;
1372 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1373 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1374 }
1375
1376 fn readlink(
1377 &mut self,
1378 pathname_op: &OpTy<'tcx>,
1379 buf_op: &OpTy<'tcx>,
1380 bufsize_op: &OpTy<'tcx>,
1381 ) -> InterpResult<'tcx, i64> {
1382 let this = self.eval_context_mut();
1383
1384 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1385 let buf = this.read_pointer(buf_op)?;
1386 let bufsize = this.read_target_usize(bufsize_op)?;
1387
1388 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1390 this.reject_in_isolation("`readlink`", reject_with)?;
1391 this.set_last_error(LibcError("EACCES"))?;
1392 return interp_ok(-1);
1393 }
1394
1395 let result = std::fs::read_link(pathname);
1396 match result {
1397 Ok(resolved) => {
1398 let resolved = this.convert_path(
1402 Cow::Borrowed(resolved.as_ref()),
1403 crate::shims::os_str::PathConversion::HostToTarget,
1404 );
1405 let mut path_bytes = resolved.as_encoded_bytes();
1406 let bufsize: usize = bufsize.try_into().unwrap();
1407 if path_bytes.len() > bufsize {
1408 path_bytes = &path_bytes[..bufsize]
1409 }
1410 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1411 interp_ok(path_bytes.len().try_into().unwrap())
1412 }
1413 Err(e) => {
1414 this.set_last_error(e)?;
1415 interp_ok(-1)
1416 }
1417 }
1418 }
1419
1420 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1421 let this = self.eval_context_mut();
1422 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1425 let error = if let Some(fd) = this.machine.fds.get(fd) {
1426 if fd.is_tty(this.machine.communicate()) {
1427 return interp_ok(Scalar::from_i32(1));
1428 } else {
1429 LibcError("ENOTTY")
1430 }
1431 } else {
1432 LibcError("EBADF")
1434 };
1435 this.set_last_error(error)?;
1436 interp_ok(Scalar::from_i32(0))
1437 }
1438
1439 fn realpath(
1440 &mut self,
1441 path_op: &OpTy<'tcx>,
1442 processed_path_op: &OpTy<'tcx>,
1443 ) -> InterpResult<'tcx, Scalar> {
1444 let this = self.eval_context_mut();
1445 this.assert_target_os_is_unix("realpath");
1446
1447 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1448 let processed_ptr = this.read_pointer(processed_path_op)?;
1449
1450 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1452 this.reject_in_isolation("`realpath`", reject_with)?;
1453 this.set_last_error(LibcError("EACCES"))?;
1454 return interp_ok(Scalar::from_target_usize(0, this));
1455 }
1456
1457 let result = std::fs::canonicalize(pathname);
1458 match result {
1459 Ok(resolved) => {
1460 let path_max = this
1461 .eval_libc_i32("PATH_MAX")
1462 .try_into()
1463 .expect("PATH_MAX does not fit in u64");
1464 let dest = if this.ptr_is_null(processed_ptr)? {
1465 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1475 } else {
1476 let (wrote_path, _) =
1477 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1478
1479 if !wrote_path {
1480 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1484 return interp_ok(Scalar::from_target_usize(0, this));
1485 }
1486 processed_ptr
1487 };
1488
1489 interp_ok(Scalar::from_maybe_pointer(dest, this))
1490 }
1491 Err(e) => {
1492 this.set_last_error(e)?;
1493 interp_ok(Scalar::from_target_usize(0, this))
1494 }
1495 }
1496 }
1497 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1498 use rand::seq::IndexedRandom;
1499
1500 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1502
1503 let this = self.eval_context_mut();
1504 this.assert_target_os_is_unix("mkstemp");
1505
1506 let max_attempts = this.eval_libc_u32("TMP_MAX");
1516
1517 let template_ptr = this.read_pointer(template_op)?;
1520 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1521 let template_bytes = template.as_mut_slice();
1522
1523 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1525 this.reject_in_isolation("`mkstemp`", reject_with)?;
1526 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1527 }
1528
1529 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1531
1532 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1537 let end_pos = template_bytes.len();
1538 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1539
1540 if last_six_char_bytes != suffix_bytes {
1542 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1543 }
1544
1545 const SUBSTITUTIONS: &[char; 62] = &[
1549 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1550 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1551 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1552 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1553 ];
1554
1555 let mut fopts = OpenOptions::new();
1558 fopts.read(true).write(true).create_new(true);
1559
1560 #[cfg(unix)]
1561 {
1562 use std::os::unix::fs::OpenOptionsExt;
1563 fopts.mode(0o600);
1565 fopts.custom_flags(libc::O_EXCL);
1566 }
1567 #[cfg(windows)]
1568 {
1569 use std::os::windows::fs::OpenOptionsExt;
1570 fopts.share_mode(0);
1572 }
1573
1574 for _ in 0..max_attempts {
1576 let rng = this.machine.rng.get_mut();
1577
1578 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1580
1581 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1583
1584 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1586
1587 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1589
1590 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1591
1592 let file = fopts.open(possibly_unique);
1593
1594 match file {
1595 Ok(f) => {
1596 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1597 return interp_ok(Scalar::from_i32(fd));
1598 }
1599 Err(e) =>
1600 match e.kind() {
1601 ErrorKind::AlreadyExists => continue,
1603 _ => {
1605 return this.set_last_error_and_return_i32(e);
1608 }
1609 },
1610 }
1611 }
1612
1613 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1615 }
1616}
1617
1618fn extract_sec_and_nsec<'tcx>(
1622 time: std::io::Result<SystemTime>,
1623) -> InterpResult<'tcx, Option<(u64, u32)>> {
1624 match time.ok() {
1625 Some(time) => {
1626 let duration = system_time_to_duration(&time)?;
1627 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1628 }
1629 None => interp_ok(None),
1630 }
1631}
1632
1633struct FileMetadata {
1636 mode: Scalar,
1637 size: u64,
1638 created: Option<(u64, u32)>,
1639 accessed: Option<(u64, u32)>,
1640 modified: Option<(u64, u32)>,
1641 dev: u64,
1642 uid: u32,
1643 gid: u32,
1644}
1645
1646impl FileMetadata {
1647 fn from_path<'tcx>(
1648 ecx: &mut MiriInterpCx<'tcx>,
1649 path: &Path,
1650 follow_symlink: bool,
1651 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1652 let metadata =
1653 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1654
1655 FileMetadata::from_meta(ecx, metadata)
1656 }
1657
1658 fn from_fd_num<'tcx>(
1659 ecx: &mut MiriInterpCx<'tcx>,
1660 fd_num: i32,
1661 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1662 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1663 return interp_ok(Err(LibcError("EBADF")));
1664 };
1665
1666 let metadata = fd.metadata()?;
1667 drop(fd);
1668 FileMetadata::from_meta(ecx, metadata)
1669 }
1670
1671 fn from_meta<'tcx>(
1672 ecx: &mut MiriInterpCx<'tcx>,
1673 metadata: Result<std::fs::Metadata, std::io::Error>,
1674 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1675 let metadata = match metadata {
1676 Ok(metadata) => metadata,
1677 Err(e) => {
1678 return interp_ok(Err(e.into()));
1679 }
1680 };
1681
1682 let file_type = metadata.file_type();
1683
1684 let mode_name = if file_type.is_file() {
1685 "S_IFREG"
1686 } else if file_type.is_dir() {
1687 "S_IFDIR"
1688 } else {
1689 "S_IFLNK"
1690 };
1691
1692 let mode = ecx.eval_libc(mode_name);
1693
1694 let size = metadata.len();
1695
1696 let created = extract_sec_and_nsec(metadata.created())?;
1697 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1698 let modified = extract_sec_and_nsec(metadata.modified())?;
1699
1700 cfg_select! {
1703 unix => {
1704 use std::os::unix::fs::MetadataExt;
1705 let dev = metadata.dev();
1706 let uid = metadata.uid();
1707 let gid = metadata.gid();
1708 }
1709 _ => {
1710 let dev = 0;
1711 let uid = 0;
1712 let gid = 0;
1713 }
1714 }
1715
1716 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1717 }
1718}