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
233 let buf = this.deref_pointer(buf_op)?;
238
239 let mode_t_size = this.libc_ty_layout("mode_t").size;
242 let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap();
243
244 this.write_int_fields_named(
245 &[
246 ("st_dev", metadata.dev.into()),
247 ("st_mode", mode.into()),
248 ("st_nlink", 0),
249 ("st_ino", 0),
250 ("st_uid", metadata.uid.into()),
251 ("st_gid", metadata.gid.into()),
252 ("st_rdev", 0),
253 ("st_atime", access_sec.into()),
254 ("st_atime_nsec", access_nsec.into()),
255 ("st_mtime", modified_sec.into()),
256 ("st_mtime_nsec", modified_nsec.into()),
257 ("st_ctime", 0),
258 ("st_ctime_nsec", 0),
259 ("st_size", metadata.size.into()),
260 ("st_blocks", 0),
261 ("st_blksize", 0),
262 ],
263 &buf,
264 )?;
265
266 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
267 this.write_int_fields_named(
268 &[
269 ("st_birthtime", created_sec.into()),
270 ("st_birthtime_nsec", created_nsec.into()),
271 ("st_flags", 0),
272 ("st_gen", 0),
273 ],
274 &buf,
275 )?;
276 }
277
278 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
279 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
280 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
282 }
283
284 interp_ok(0)
285 }
286
287 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
288 #[cfg(unix)]
289 use std::os::unix::fs::FileTypeExt;
290
291 let this = self.eval_context_ref();
292 match file_type {
293 Ok(file_type) => {
294 match () {
295 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
296 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
297 _ if file_type.is_symlink() =>
298 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
299 #[cfg(unix)]
301 _ if file_type.is_block_device() =>
302 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
303 #[cfg(unix)]
304 _ if file_type.is_char_device() =>
305 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
306 #[cfg(unix)]
307 _ if file_type.is_fifo() =>
308 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
309 #[cfg(unix)]
310 _ if file_type.is_socket() =>
311 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
312 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
314 }
315 }
316 Err(_) => {
317 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
319 }
320 }
321 }
322
323 fn dir_entry_fields(
324 &self,
325 entry: Either<fs::DirEntry, &'static str>,
326 ) -> InterpResult<'tcx, DirEntry> {
327 let this = self.eval_context_ref();
328 interp_ok(match entry {
329 Either::Left(dir_entry) => {
330 DirEntry {
331 name: dir_entry.file_name(),
332 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
333 #[cfg(unix)]
336 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
337 #[cfg(not(unix))]
338 ino: 0u64,
339 }
340 }
341 Either::Right(special) =>
342 DirEntry {
343 name: special.into(),
344 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
345 ino: 0,
346 },
347 })
348 }
349}
350
351impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
352pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
353 fn open(
354 &mut self,
355 path_raw: &OpTy<'tcx>,
356 flag: &OpTy<'tcx>,
357 varargs: &[OpTy<'tcx>],
358 ) -> InterpResult<'tcx, Scalar> {
359 let this = self.eval_context_mut();
360
361 let path_raw = this.read_pointer(path_raw)?;
362 let flag = this.read_scalar(flag)?.to_i32()?;
363
364 let path = this.read_path_from_c_str(path_raw)?;
365 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
367 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
368 {
369 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
370 }
371
372 let mut flag = flag;
374
375 let mut options = OpenOptions::new();
376
377 let o_rdonly = this.eval_libc_i32("O_RDONLY");
378 let o_wronly = this.eval_libc_i32("O_WRONLY");
379 let o_rdwr = this.eval_libc_i32("O_RDWR");
380 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
384 throw_unsup_format!("access mode flags on this target are unsupported");
385 }
386 let mut writable = true;
387
388 let access_mode = flag & 0b11;
390 flag &= !access_mode;
391
392 if access_mode == o_rdonly {
393 writable = false;
394 options.read(true);
395 } else if access_mode == o_wronly {
396 options.write(true);
397 } else if access_mode == o_rdwr {
398 options.read(true).write(true);
399 } else {
400 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
401 }
402
403 let o_append = this.eval_libc_i32("O_APPEND");
404 if flag & o_append == o_append {
405 flag &= !o_append;
406 options.append(true);
407 }
408 let o_trunc = this.eval_libc_i32("O_TRUNC");
409 if flag & o_trunc == o_trunc {
410 flag &= !o_trunc;
411 options.truncate(true);
412 }
413 let o_creat = this.eval_libc_i32("O_CREAT");
414 if flag & o_creat == o_creat {
415 flag &= !o_creat;
416 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
420 let mode = this.read_scalar(mode)?.to_u32()?;
421
422 #[cfg(unix)]
423 {
424 use std::os::unix::fs::OpenOptionsExt;
426 options.mode(mode);
427 }
428 #[cfg(not(unix))]
429 {
430 if mode != 0o666 {
432 throw_unsup_format!(
433 "non-default mode 0o{:o} is not supported on non-Unix hosts",
434 mode
435 );
436 }
437 }
438
439 let o_excl = this.eval_libc_i32("O_EXCL");
440 if flag & o_excl == o_excl {
441 flag &= !o_excl;
442 options.create_new(true);
443 } else {
444 options.create(true);
445 }
446 }
447 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
448 if flag & o_cloexec == o_cloexec {
449 flag &= !o_cloexec;
450 }
453 if this.tcx.sess.target.os == Os::Linux {
454 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
455 if flag & o_tmpfile == o_tmpfile {
456 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
458 }
459 }
460
461 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
462 if flag & o_nofollow == o_nofollow {
463 flag &= !o_nofollow;
464 #[cfg(unix)]
465 {
466 use std::os::unix::fs::OpenOptionsExt;
467 options.custom_flags(libc::O_NOFOLLOW);
468 }
469 #[cfg(not(unix))]
473 {
474 if path.is_symlink() {
477 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
478 }
479 }
480 }
481
482 if flag != 0 {
484 throw_unsup_format!("unsupported flags {:#x}", flag);
485 }
486
487 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
489 this.reject_in_isolation("`open`", reject_with)?;
490 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
491 }
492
493 let fd = options
494 .open(path)
495 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
496
497 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
498 }
499
500 fn lseek(
501 &mut self,
502 fd_num: i32,
503 offset: i128,
504 whence: i32,
505 dest: &MPlaceTy<'tcx>,
506 ) -> InterpResult<'tcx> {
507 let this = self.eval_context_mut();
508
509 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
512 if offset < 0 {
513 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
515 } else {
516 SeekFrom::Start(u64::try_from(offset).unwrap())
517 }
518 } else if whence == this.eval_libc_i32("SEEK_CUR") {
519 SeekFrom::Current(i64::try_from(offset).unwrap())
520 } else if whence == this.eval_libc_i32("SEEK_END") {
521 SeekFrom::End(i64::try_from(offset).unwrap())
522 } else {
523 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
524 };
525
526 let communicate = this.machine.communicate();
527
528 let Some(fd) = this.machine.fds.get(fd_num) else {
529 return this.set_last_error_and_return(LibcError("EBADF"), dest);
530 };
531 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
532 drop(fd);
533
534 let result = this.try_unwrap_io_result(result)?;
535 this.write_int(result, dest)?;
536 interp_ok(())
537 }
538
539 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
540 let this = self.eval_context_mut();
541
542 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
543
544 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
546 this.reject_in_isolation("`unlink`", reject_with)?;
547 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
548 }
549
550 let result = remove_file(path).map(|_| 0);
551 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
552 }
553
554 fn symlink(
555 &mut self,
556 target_op: &OpTy<'tcx>,
557 linkpath_op: &OpTy<'tcx>,
558 ) -> InterpResult<'tcx, Scalar> {
559 #[cfg(unix)]
560 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
561 std::os::unix::fs::symlink(src, dst)
562 }
563
564 #[cfg(windows)]
565 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
566 use std::os::windows::fs;
567 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
568 }
569
570 let this = self.eval_context_mut();
571 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
572 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
573
574 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
576 this.reject_in_isolation("`symlink`", reject_with)?;
577 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
578 }
579
580 let result = create_link(&target, &linkpath).map(|_| 0);
581 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
582 }
583
584 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
585 let this = self.eval_context_mut();
586
587 if !matches!(
588 &this.tcx.sess.target.os,
589 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
590 ) {
591 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
592 }
593
594 let path_scalar = this.read_pointer(path_op)?;
595 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
596
597 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
599 this.reject_in_isolation("`stat`", reject_with)?;
600 return this.set_last_error_and_return_i32(LibcError("EACCES"));
601 }
602
603 let metadata = match FileMetadata::from_path(this, &path, true)? {
605 Ok(metadata) => metadata,
606 Err(err) => return this.set_last_error_and_return_i32(err),
607 };
608
609 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
610 }
611
612 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
614 let this = self.eval_context_mut();
615
616 if !matches!(
617 &this.tcx.sess.target.os,
618 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
619 ) {
620 panic!(
621 "`macos_fbsd_solaris_lstat` should not be called on {}",
622 this.tcx.sess.target.os
623 );
624 }
625
626 let path_scalar = this.read_pointer(path_op)?;
627 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
628
629 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
631 this.reject_in_isolation("`lstat`", reject_with)?;
632 return this.set_last_error_and_return_i32(LibcError("EACCES"));
633 }
634
635 let metadata = match FileMetadata::from_path(this, &path, false)? {
636 Ok(metadata) => metadata,
637 Err(err) => return this.set_last_error_and_return_i32(err),
638 };
639
640 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
641 }
642
643 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
644 let this = self.eval_context_mut();
645
646 if !matches!(
647 &this.tcx.sess.target.os,
648 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
649 ) {
650 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
651 }
652
653 let fd = this.read_scalar(fd_op)?.to_i32()?;
654
655 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
657 this.reject_in_isolation("`fstat`", reject_with)?;
658 return this.set_last_error_and_return_i32(LibcError("EBADF"));
660 }
661
662 let metadata = match FileMetadata::from_fd_num(this, fd)? {
663 Ok(metadata) => metadata,
664 Err(err) => return this.set_last_error_and_return_i32(err),
665 };
666 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
667 }
668
669 fn linux_statx(
670 &mut self,
671 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
677 let this = self.eval_context_mut();
678
679 this.assert_target_os(Os::Linux, "statx");
680
681 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
682 let pathname_ptr = this.read_pointer(pathname_op)?;
683 let flags = this.read_scalar(flags_op)?.to_i32()?;
684 let _mask = this.read_scalar(mask_op)?.to_u32()?;
685 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
686
687 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
689 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
690 }
691
692 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
693
694 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
695 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
697 let empty_path_flag = flags & at_empty_path == at_empty_path;
698 if !(path.is_absolute()
706 || dirfd == this.eval_libc_i32("AT_FDCWD")
707 || (path.as_os_str().is_empty() && empty_path_flag))
708 {
709 throw_unsup_format!(
710 "using statx is only supported with absolute paths, relative paths with the file \
711 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
712 file descriptor"
713 )
714 }
715
716 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
718 this.reject_in_isolation("`statx`", reject_with)?;
719 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
720 LibcError("EACCES")
723 } else {
724 assert!(empty_path_flag);
728 LibcError("EBADF")
729 };
730 return this.set_last_error_and_return_i32(ecode);
731 }
732
733 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
738
739 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
742
743 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
746 FileMetadata::from_fd_num(this, dirfd)?
747 } else {
748 FileMetadata::from_path(this, &path, follow_symlink)?
749 };
750 let metadata = match metadata {
751 Ok(metadata) => metadata,
752 Err(err) => return this.set_last_error_and_return_i32(err),
753 };
754
755 let mode_t_size = this.libc_ty_layout("mode_t").size;
758 let mode: u16 = metadata
759 .mode
760 .to_uint(mode_t_size)?
761 .try_into()
762 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
763
764 let (access_sec, access_nsec) = metadata
767 .accessed
768 .map(|tup| {
769 mask |= this.eval_libc_u32("STATX_ATIME");
770 interp_ok(tup)
771 })
772 .unwrap_or_else(|| interp_ok((0, 0)))?;
773
774 let (created_sec, created_nsec) = metadata
775 .created
776 .map(|tup| {
777 mask |= this.eval_libc_u32("STATX_BTIME");
778 interp_ok(tup)
779 })
780 .unwrap_or_else(|| interp_ok((0, 0)))?;
781
782 let (modified_sec, modified_nsec) = metadata
783 .modified
784 .map(|tup| {
785 mask |= this.eval_libc_u32("STATX_MTIME");
786 interp_ok(tup)
787 })
788 .unwrap_or_else(|| interp_ok((0, 0)))?;
789
790 this.write_int_fields_named(
792 &[
793 ("stx_mask", mask.into()),
794 ("stx_blksize", 0),
795 ("stx_attributes", 0),
796 ("stx_nlink", 0),
797 ("stx_uid", 0),
798 ("stx_gid", 0),
799 ("stx_mode", mode.into()),
800 ("stx_ino", 0),
801 ("stx_size", metadata.size.into()),
802 ("stx_blocks", 0),
803 ("stx_attributes_mask", 0),
804 ("stx_rdev_major", 0),
805 ("stx_rdev_minor", 0),
806 ("stx_dev_major", 0),
807 ("stx_dev_minor", 0),
808 ],
809 &statxbuf,
810 )?;
811 #[rustfmt::skip]
812 this.write_int_fields_named(
813 &[
814 ("tv_sec", access_sec.into()),
815 ("tv_nsec", access_nsec.into()),
816 ],
817 &this.project_field_named(&statxbuf, "stx_atime")?,
818 )?;
819 #[rustfmt::skip]
820 this.write_int_fields_named(
821 &[
822 ("tv_sec", created_sec.into()),
823 ("tv_nsec", created_nsec.into()),
824 ],
825 &this.project_field_named(&statxbuf, "stx_btime")?,
826 )?;
827 #[rustfmt::skip]
828 this.write_int_fields_named(
829 &[
830 ("tv_sec", 0.into()),
831 ("tv_nsec", 0.into()),
832 ],
833 &this.project_field_named(&statxbuf, "stx_ctime")?,
834 )?;
835 #[rustfmt::skip]
836 this.write_int_fields_named(
837 &[
838 ("tv_sec", modified_sec.into()),
839 ("tv_nsec", modified_nsec.into()),
840 ],
841 &this.project_field_named(&statxbuf, "stx_mtime")?,
842 )?;
843
844 interp_ok(Scalar::from_i32(0))
845 }
846
847 fn rename(
848 &mut self,
849 oldpath_op: &OpTy<'tcx>,
850 newpath_op: &OpTy<'tcx>,
851 ) -> InterpResult<'tcx, Scalar> {
852 let this = self.eval_context_mut();
853
854 let oldpath_ptr = this.read_pointer(oldpath_op)?;
855 let newpath_ptr = this.read_pointer(newpath_op)?;
856
857 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
858 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
859 }
860
861 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
862 let newpath = this.read_path_from_c_str(newpath_ptr)?;
863
864 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
866 this.reject_in_isolation("`rename`", reject_with)?;
867 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
868 }
869
870 let result = rename(oldpath, newpath).map(|_| 0);
871
872 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
873 }
874
875 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
876 let this = self.eval_context_mut();
877
878 #[cfg_attr(not(unix), allow(unused_variables))]
879 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
880 u32::from(this.read_scalar(mode_op)?.to_u16()?)
881 } else {
882 this.read_scalar(mode_op)?.to_u32()?
883 };
884
885 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
886
887 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
889 this.reject_in_isolation("`mkdir`", reject_with)?;
890 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
891 }
892
893 #[cfg_attr(not(unix), allow(unused_mut))]
894 let mut builder = DirBuilder::new();
895
896 #[cfg(unix)]
899 {
900 use std::os::unix::fs::DirBuilderExt;
901 builder.mode(mode);
902 }
903
904 let result = builder.create(path).map(|_| 0i32);
905
906 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
907 }
908
909 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
910 let this = self.eval_context_mut();
911
912 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
913
914 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
916 this.reject_in_isolation("`rmdir`", reject_with)?;
917 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
918 }
919
920 let result = remove_dir(path).map(|_| 0i32);
921
922 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
923 }
924
925 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
926 let this = self.eval_context_mut();
927
928 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
929
930 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
932 this.reject_in_isolation("`opendir`", reject_with)?;
933 this.set_last_error(LibcError("EACCES"))?;
934 return interp_ok(Scalar::null_ptr(this));
935 }
936
937 let result = read_dir(name);
938
939 match result {
940 Ok(dir_iter) => {
941 let id = this.machine.dirs.insert_new(dir_iter);
942
943 interp_ok(Scalar::from_target_usize(id, this))
947 }
948 Err(e) => {
949 this.set_last_error(e)?;
950 interp_ok(Scalar::null_ptr(this))
951 }
952 }
953 }
954
955 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
956 let this = self.eval_context_mut();
957
958 if !matches!(
959 &this.tcx.sess.target.os,
960 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
961 ) {
962 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
963 }
964
965 let dirp = this.read_target_usize(dirp_op)?;
966
967 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
969 this.reject_in_isolation("`readdir`", reject_with)?;
970 this.set_last_error(LibcError("EBADF"))?;
971 this.write_null(dest)?;
972 return interp_ok(());
973 }
974
975 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
976 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
977 })?;
978
979 let entry = match open_dir.next_host_entry() {
980 Some(Ok(dir_entry)) => {
981 let dir_entry = this.dir_entry_fields(dir_entry)?;
982
983 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1020 let dirent_layout = this.layout_of(dirent_ty)?;
1021 let fields = &dirent_layout.fields;
1022 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1023
1024 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1028 let name_len = u64::try_from(name_bytes.len()).unwrap();
1029 let size = d_name_offset.strict_add(name_len);
1030
1031 let entry = this.allocate_ptr(
1032 Size::from_bytes(size),
1033 dirent_layout.align.abi,
1034 MiriMemoryKind::Runtime.into(),
1035 AllocInit::Uninit,
1036 )?;
1037 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1038
1039 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1042 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1043
1044 let ino_name =
1046 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1047 this.write_int_fields_named(
1048 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1049 &entry,
1050 )?;
1051
1052 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1054 this.write_null(&d_off)?;
1055 }
1056 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1057 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1058 }
1059 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1060 this.write_int(dir_entry.d_type, &d_type)?;
1061 }
1062
1063 Some(entry.ptr())
1064 }
1065 None => {
1066 None
1068 }
1069 Some(Err(e)) => {
1070 this.set_last_error(e)?;
1071 None
1072 }
1073 };
1074
1075 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1076 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1077 if let Some(old_entry) = old_entry {
1078 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1079 }
1080
1081 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1082 interp_ok(())
1083 }
1084
1085 fn macos_readdir_r(
1086 &mut self,
1087 dirp_op: &OpTy<'tcx>,
1088 entry_op: &OpTy<'tcx>,
1089 result_op: &OpTy<'tcx>,
1090 ) -> InterpResult<'tcx, Scalar> {
1091 let this = self.eval_context_mut();
1092
1093 this.assert_target_os(Os::MacOs, "readdir_r");
1094
1095 let dirp = this.read_target_usize(dirp_op)?;
1096 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1097
1098 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1100 this.reject_in_isolation("`readdir_r`", reject_with)?;
1101 return interp_ok(this.eval_libc("EBADF"));
1103 }
1104
1105 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1106 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1107 })?;
1108 interp_ok(match open_dir.next_host_entry() {
1109 Some(Ok(dir_entry)) => {
1110 let dir_entry = this.dir_entry_fields(dir_entry)?;
1111 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1126
1127 let name_place = this.project_field_named(&entry_place, "d_name")?;
1129 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1130 &dir_entry.name,
1131 name_place.ptr(),
1132 name_place.layout.size.bytes(),
1133 )?;
1134 if !name_fits {
1135 throw_unsup_format!(
1136 "a directory entry had a name too large to fit in libc::dirent"
1137 );
1138 }
1139
1140 this.write_int_fields_named(
1142 &[
1143 ("d_reclen", entry_place.layout.size.bytes().into()),
1144 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1145 ("d_type", dir_entry.d_type.into()),
1146 ("d_ino", dir_entry.ino.into()),
1147 ("d_seekoff", 0),
1148 ],
1149 &entry_place,
1150 )?;
1151 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1152
1153 Scalar::from_i32(0)
1154 }
1155 None => {
1156 this.write_null(&result_place)?;
1158 Scalar::from_i32(0)
1159 }
1160 Some(Err(e)) => {
1161 this.io_error_to_errnum(e)?
1163 }
1164 })
1165 }
1166
1167 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1168 let this = self.eval_context_mut();
1169
1170 let dirp = this.read_target_usize(dirp_op)?;
1171
1172 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1174 this.reject_in_isolation("`closedir`", reject_with)?;
1175 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1176 }
1177
1178 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1179 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1180 };
1181 if let Some(entry) = open_dir.entry.take() {
1182 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1183 }
1184 drop(open_dir);
1186
1187 interp_ok(Scalar::from_i32(0))
1188 }
1189
1190 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1191 let this = self.eval_context_mut();
1192
1193 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1195 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1196 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1198 }
1199
1200 let Some(fd) = this.machine.fds.get(fd_num) else {
1201 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1202 };
1203
1204 let Some(file) = fd.downcast::<FileHandle>() else {
1205 return interp_ok(this.eval_libc("EINVAL"));
1208 };
1209
1210 if file.writable {
1211 if let Ok(length) = length.try_into() {
1212 let result = file.file.set_len(length);
1213 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1214 interp_ok(Scalar::from_i32(result))
1215 } else {
1216 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1217 }
1218 } else {
1219 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1221 }
1222 }
1223
1224 fn posix_fallocate(
1227 &mut self,
1228 fd_num: i32,
1229 offset: i64,
1230 len: i64,
1231 ) -> InterpResult<'tcx, Scalar> {
1232 let this = self.eval_context_mut();
1233
1234 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1236 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1237 return interp_ok(this.eval_libc("EBADF"));
1239 }
1240
1241 if offset < 0 || len <= 0 {
1243 return interp_ok(this.eval_libc("EINVAL"));
1244 }
1245
1246 let Some(fd) = this.machine.fds.get(fd_num) else {
1248 return interp_ok(this.eval_libc("EBADF"));
1249 };
1250 let Some(file) = fd.downcast::<FileHandle>() else {
1251 return interp_ok(this.eval_libc("ENODEV"));
1253 };
1254
1255 if !file.writable {
1256 return interp_ok(this.eval_libc("EBADF"));
1258 }
1259
1260 let current_size = match file.file.metadata() {
1261 Ok(metadata) => metadata.len(),
1262 Err(err) => return this.io_error_to_errnum(err),
1263 };
1264 let new_size = match offset.checked_add(len) {
1266 Some(new_size) => u64::try_from(new_size).unwrap(),
1268 None => return interp_ok(this.eval_libc("EFBIG")), };
1270 if current_size < new_size {
1273 interp_ok(match file.file.set_len(new_size) {
1274 Ok(()) => Scalar::from_i32(0),
1275 Err(e) => this.io_error_to_errnum(e)?,
1276 })
1277 } else {
1278 interp_ok(Scalar::from_i32(0))
1279 }
1280 }
1281
1282 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1283 let this = self.eval_context_mut();
1289
1290 let fd = this.read_scalar(fd_op)?.to_i32()?;
1291
1292 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1294 this.reject_in_isolation("`fsync`", reject_with)?;
1295 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1297 }
1298
1299 self.ffullsync_fd(fd)
1300 }
1301
1302 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1303 let this = self.eval_context_mut();
1304 let Some(fd) = this.machine.fds.get(fd_num) else {
1305 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1306 };
1307 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1309 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1310 })?;
1311 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1312 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1313 }
1314
1315 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1316 let this = self.eval_context_mut();
1317
1318 let fd = this.read_scalar(fd_op)?.to_i32()?;
1319
1320 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1322 this.reject_in_isolation("`fdatasync`", reject_with)?;
1323 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1325 }
1326
1327 let Some(fd) = this.machine.fds.get(fd) else {
1328 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1329 };
1330 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1332 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1333 })?;
1334 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1335 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1336 }
1337
1338 fn sync_file_range(
1339 &mut self,
1340 fd_op: &OpTy<'tcx>,
1341 offset_op: &OpTy<'tcx>,
1342 nbytes_op: &OpTy<'tcx>,
1343 flags_op: &OpTy<'tcx>,
1344 ) -> InterpResult<'tcx, Scalar> {
1345 let this = self.eval_context_mut();
1346
1347 let fd = this.read_scalar(fd_op)?.to_i32()?;
1348 let offset = this.read_scalar(offset_op)?.to_i64()?;
1349 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1350 let flags = this.read_scalar(flags_op)?.to_i32()?;
1351
1352 if offset < 0 || nbytes < 0 {
1353 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1354 }
1355 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1356 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1357 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1358 if flags & allowed_flags != flags {
1359 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1360 }
1361
1362 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1364 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1365 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1367 }
1368
1369 let Some(fd) = this.machine.fds.get(fd) else {
1370 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1371 };
1372 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1374 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1375 })?;
1376 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1377 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1378 }
1379
1380 fn readlink(
1381 &mut self,
1382 pathname_op: &OpTy<'tcx>,
1383 buf_op: &OpTy<'tcx>,
1384 bufsize_op: &OpTy<'tcx>,
1385 ) -> InterpResult<'tcx, i64> {
1386 let this = self.eval_context_mut();
1387
1388 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1389 let buf = this.read_pointer(buf_op)?;
1390 let bufsize = this.read_target_usize(bufsize_op)?;
1391
1392 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1394 this.reject_in_isolation("`readlink`", reject_with)?;
1395 this.set_last_error(LibcError("EACCES"))?;
1396 return interp_ok(-1);
1397 }
1398
1399 let result = std::fs::read_link(pathname);
1400 match result {
1401 Ok(resolved) => {
1402 let resolved = this.convert_path(
1406 Cow::Borrowed(resolved.as_ref()),
1407 crate::shims::os_str::PathConversion::HostToTarget,
1408 );
1409 let mut path_bytes = resolved.as_encoded_bytes();
1410 let bufsize: usize = bufsize.try_into().unwrap();
1411 if path_bytes.len() > bufsize {
1412 path_bytes = &path_bytes[..bufsize]
1413 }
1414 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1415 interp_ok(path_bytes.len().try_into().unwrap())
1416 }
1417 Err(e) => {
1418 this.set_last_error(e)?;
1419 interp_ok(-1)
1420 }
1421 }
1422 }
1423
1424 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1425 let this = self.eval_context_mut();
1426 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1429 let error = if let Some(fd) = this.machine.fds.get(fd) {
1430 if fd.is_tty(this.machine.communicate()) {
1431 return interp_ok(Scalar::from_i32(1));
1432 } else {
1433 LibcError("ENOTTY")
1434 }
1435 } else {
1436 LibcError("EBADF")
1438 };
1439 this.set_last_error(error)?;
1440 interp_ok(Scalar::from_i32(0))
1441 }
1442
1443 fn realpath(
1444 &mut self,
1445 path_op: &OpTy<'tcx>,
1446 processed_path_op: &OpTy<'tcx>,
1447 ) -> InterpResult<'tcx, Scalar> {
1448 let this = self.eval_context_mut();
1449 this.assert_target_os_is_unix("realpath");
1450
1451 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1452 let processed_ptr = this.read_pointer(processed_path_op)?;
1453
1454 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1456 this.reject_in_isolation("`realpath`", reject_with)?;
1457 this.set_last_error(LibcError("EACCES"))?;
1458 return interp_ok(Scalar::from_target_usize(0, this));
1459 }
1460
1461 let result = std::fs::canonicalize(pathname);
1462 match result {
1463 Ok(resolved) => {
1464 let path_max = this
1465 .eval_libc_i32("PATH_MAX")
1466 .try_into()
1467 .expect("PATH_MAX does not fit in u64");
1468 let dest = if this.ptr_is_null(processed_ptr)? {
1469 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1479 } else {
1480 let (wrote_path, _) =
1481 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1482
1483 if !wrote_path {
1484 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1488 return interp_ok(Scalar::from_target_usize(0, this));
1489 }
1490 processed_ptr
1491 };
1492
1493 interp_ok(Scalar::from_maybe_pointer(dest, this))
1494 }
1495 Err(e) => {
1496 this.set_last_error(e)?;
1497 interp_ok(Scalar::from_target_usize(0, this))
1498 }
1499 }
1500 }
1501 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1502 use rand::seq::IndexedRandom;
1503
1504 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1506
1507 let this = self.eval_context_mut();
1508 this.assert_target_os_is_unix("mkstemp");
1509
1510 let max_attempts = this.eval_libc_u32("TMP_MAX");
1520
1521 let template_ptr = this.read_pointer(template_op)?;
1524 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1525 let template_bytes = template.as_mut_slice();
1526
1527 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1529 this.reject_in_isolation("`mkstemp`", reject_with)?;
1530 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1531 }
1532
1533 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1535
1536 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1541 let end_pos = template_bytes.len();
1542 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1543
1544 if last_six_char_bytes != suffix_bytes {
1546 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1547 }
1548
1549 const SUBSTITUTIONS: &[char; 62] = &[
1553 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1554 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1555 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1556 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1557 ];
1558
1559 let mut fopts = OpenOptions::new();
1562 fopts.read(true).write(true).create_new(true);
1563
1564 #[cfg(unix)]
1565 {
1566 use std::os::unix::fs::OpenOptionsExt;
1567 fopts.mode(0o600);
1569 fopts.custom_flags(libc::O_EXCL);
1570 }
1571 #[cfg(windows)]
1572 {
1573 use std::os::windows::fs::OpenOptionsExt;
1574 fopts.share_mode(0);
1576 }
1577
1578 for _ in 0..max_attempts {
1580 let rng = this.machine.rng.get_mut();
1581
1582 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1584
1585 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1587
1588 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1590
1591 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1593
1594 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1595
1596 let file = fopts.open(possibly_unique);
1597
1598 match file {
1599 Ok(f) => {
1600 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1601 return interp_ok(Scalar::from_i32(fd));
1602 }
1603 Err(e) =>
1604 match e.kind() {
1605 ErrorKind::AlreadyExists => continue,
1607 _ => {
1609 return this.set_last_error_and_return_i32(e);
1612 }
1613 },
1614 }
1615 }
1616
1617 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1619 }
1620}
1621
1622fn extract_sec_and_nsec<'tcx>(
1626 time: std::io::Result<SystemTime>,
1627) -> InterpResult<'tcx, Option<(u64, u32)>> {
1628 match time.ok() {
1629 Some(time) => {
1630 let duration = system_time_to_duration(&time)?;
1631 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1632 }
1633 None => interp_ok(None),
1634 }
1635}
1636
1637fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1638 #[cfg(unix)]
1639 use std::os::unix::fs::FileTypeExt;
1640
1641 if file_type.is_file() {
1642 "S_IFREG"
1643 } else if file_type.is_dir() {
1644 "S_IFDIR"
1645 } else if file_type.is_symlink() {
1646 "S_IFLNK"
1647 } else {
1648 #[cfg(unix)]
1650 {
1651 if file_type.is_socket() {
1652 return "S_IFSOCK";
1653 } else if file_type.is_fifo() {
1654 return "S_IFIFO";
1655 } else if file_type.is_char_device() {
1656 return "S_IFCHR";
1657 } else if file_type.is_block_device() {
1658 return "S_IFBLK";
1659 }
1660 }
1661 "S_IFREG"
1662 }
1663}
1664
1665struct FileMetadata {
1668 mode: Scalar,
1669 size: u64,
1670 created: Option<(u64, u32)>,
1671 accessed: Option<(u64, u32)>,
1672 modified: Option<(u64, u32)>,
1673 dev: u64,
1674 uid: u32,
1675 gid: u32,
1676}
1677
1678impl FileMetadata {
1679 fn from_path<'tcx>(
1680 ecx: &mut MiriInterpCx<'tcx>,
1681 path: &Path,
1682 follow_symlink: bool,
1683 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1684 let metadata =
1685 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1686
1687 FileMetadata::from_meta(ecx, metadata)
1688 }
1689
1690 fn from_fd_num<'tcx>(
1691 ecx: &mut MiriInterpCx<'tcx>,
1692 fd_num: i32,
1693 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1694 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1695 return interp_ok(Err(LibcError("EBADF")));
1696 };
1697 match fd.metadata()? {
1698 Either::Left(host) => Self::from_meta(ecx, host),
1699 Either::Right(name) => Self::synthetic(ecx, name),
1700 }
1701 }
1702
1703 fn synthetic<'tcx>(
1704 ecx: &mut MiriInterpCx<'tcx>,
1705 mode_name: &str,
1706 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1707 let mode = ecx.eval_libc(mode_name);
1708 interp_ok(Ok(FileMetadata {
1709 mode,
1710 size: 0,
1711 created: None,
1712 accessed: None,
1713 modified: None,
1714 dev: 0,
1715 uid: 0,
1716 gid: 0,
1717 }))
1718 }
1719
1720 fn from_meta<'tcx>(
1721 ecx: &mut MiriInterpCx<'tcx>,
1722 metadata: Result<std::fs::Metadata, std::io::Error>,
1723 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1724 let metadata = match metadata {
1725 Ok(metadata) => metadata,
1726 Err(e) => {
1727 return interp_ok(Err(e.into()));
1728 }
1729 };
1730
1731 let file_type = metadata.file_type();
1732 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1733
1734 let size = metadata.len();
1735
1736 let created = extract_sec_and_nsec(metadata.created())?;
1737 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1738 let modified = extract_sec_and_nsec(metadata.modified())?;
1739
1740 cfg_select! {
1743 unix => {
1744 use std::os::unix::fs::MetadataExt;
1745 let dev = metadata.dev();
1746 let uid = metadata.uid();
1747 let gid = metadata.gid();
1748 }
1749 _ => {
1750 let dev = 0;
1751 let uid = 0;
1752 let gid = 0;
1753 }
1754 }
1755
1756 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1757 }
1758}