1use std::borrow::Cow;
4use std::fs::{
5 DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
6};
7use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
8use std::path::{Path, PathBuf};
9use std::time::SystemTime;
10
11use rustc_abi::Size;
12use rustc_data_structures::fx::FxHashMap;
13
14use self::shims::time::system_time_to_duration;
15use crate::helpers::check_min_vararg_count;
16use crate::shims::files::FileHandle;
17use crate::shims::os_str::bytes_to_os_str;
18use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
19use crate::*;
20
21impl UnixFileDescription for FileHandle {
22 fn pread<'tcx>(
23 &self,
24 communicate_allowed: bool,
25 offset: u64,
26 ptr: Pointer,
27 len: usize,
28 ecx: &mut MiriInterpCx<'tcx>,
29 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
30 ) -> InterpResult<'tcx> {
31 assert!(communicate_allowed, "isolation should have prevented even opening a file");
32 let mut bytes = vec![0; len];
33 let file = &mut &self.file;
37 let mut f = || {
38 let cursor_pos = file.stream_position()?;
39 file.seek(SeekFrom::Start(offset))?;
40 let res = file.read(&mut bytes);
41 file.seek(SeekFrom::Start(cursor_pos))
43 .expect("failed to restore file position, this shouldn't be possible");
44 res
45 };
46 let result = match f() {
47 Ok(read_size) => {
48 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
52 Ok(read_size)
53 }
54 Err(e) => Err(IoError::HostError(e)),
55 };
56 finish.call(ecx, result)
57 }
58
59 fn pwrite<'tcx>(
60 &self,
61 communicate_allowed: bool,
62 ptr: Pointer,
63 len: usize,
64 offset: u64,
65 ecx: &mut MiriInterpCx<'tcx>,
66 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
67 ) -> InterpResult<'tcx> {
68 assert!(communicate_allowed, "isolation should have prevented even opening a file");
69 let file = &mut &self.file;
73 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
74 let mut f = || {
75 let cursor_pos = file.stream_position()?;
76 file.seek(SeekFrom::Start(offset))?;
77 let res = file.write(bytes);
78 file.seek(SeekFrom::Start(cursor_pos))
80 .expect("failed to restore file position, this shouldn't be possible");
81 res
82 };
83 let result = f();
84 finish.call(ecx, result.map_err(IoError::HostError))
85 }
86
87 fn flock<'tcx>(
88 &self,
89 communicate_allowed: bool,
90 op: FlockOp,
91 ) -> InterpResult<'tcx, io::Result<()>> {
92 assert!(communicate_allowed, "isolation should have prevented even opening a file");
93 cfg_select! {
94 all(target_family = "unix", not(target_os = "solaris")) => {
95 use std::os::fd::AsRawFd;
96
97 use FlockOp::*;
98 let (host_op, lock_nb) = match op {
100 SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
101 ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
102 Unlock => (libc::LOCK_UN, false),
103 };
104
105 let fd = self.file.as_raw_fd();
106 let ret = unsafe { libc::flock(fd, host_op) };
107 let res = match ret {
108 0 => Ok(()),
109 -1 => {
110 let err = io::Error::last_os_error();
111 if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
112 throw_unsup_format!("blocking `flock` is not currently supported");
113 }
114 Err(err)
115 }
116 ret => panic!("Unexpected return value from flock: {ret}"),
117 };
118 interp_ok(res)
119 }
120 target_family = "windows" => {
121 use std::os::windows::io::AsRawHandle;
122
123 use windows_sys::Win32::Foundation::{
124 ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE,
125 };
126 use windows_sys::Win32::Storage::FileSystem::{
127 LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
128 };
129
130 let fh = self.file.as_raw_handle();
131
132 use FlockOp::*;
133 let (ret, lock_nb) = match op {
134 SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
135 let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
137 if matches!(op, ExclusiveLock { .. }) {
138 flags |= LOCKFILE_EXCLUSIVE_LOCK;
139 }
140 let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
141 (ret, nonblocking)
142 }
143 Unlock => {
144 let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
145 (ret, false)
146 }
147 };
148
149 let res = match ret {
150 TRUE => Ok(()),
151 FALSE => {
152 let mut err = io::Error::last_os_error();
153 let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
156 if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
157 if lock_nb {
158 let desc = format!("LockFileEx wouldblock error: {err}");
161 err = io::Error::new(io::ErrorKind::WouldBlock, desc);
162 } else {
163 throw_unsup_format!("blocking `flock` is not currently supported");
164 }
165 }
166 Err(err)
167 }
168 _ => panic!("Unexpected return value: {ret}"),
169 };
170 interp_ok(res)
171 }
172 _ => {
173 let _ = op;
174 throw_unsup_format!(
175 "flock is supported only on UNIX (except Solaris) and Windows hosts"
176 );
177 }
178 }
179 }
180}
181
182impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
183trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
184 fn macos_fbsd_solarish_write_stat_buf(
185 &mut self,
186 metadata: FileMetadata,
187 buf_op: &OpTy<'tcx>,
188 ) -> InterpResult<'tcx, i32> {
189 let this = self.eval_context_mut();
190
191 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
192 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
193 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
194 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
195
196 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
197 this.write_int_fields_named(
198 &[
199 ("st_dev", 0),
200 ("st_mode", mode.try_into().unwrap()),
201 ("st_nlink", 0),
202 ("st_ino", 0),
203 ("st_uid", 0),
204 ("st_gid", 0),
205 ("st_rdev", 0),
206 ("st_atime", access_sec.into()),
207 ("st_mtime", modified_sec.into()),
208 ("st_ctime", 0),
209 ("st_size", metadata.size.into()),
210 ("st_blocks", 0),
211 ("st_blksize", 0),
212 ],
213 &buf,
214 )?;
215
216 if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
217 this.write_int_fields_named(
218 &[
219 ("st_atime_nsec", access_nsec.into()),
220 ("st_mtime_nsec", modified_nsec.into()),
221 ("st_ctime_nsec", 0),
222 ("st_birthtime", created_sec.into()),
223 ("st_birthtime_nsec", created_nsec.into()),
224 ("st_flags", 0),
225 ("st_gen", 0),
226 ],
227 &buf,
228 )?;
229 }
230
231 if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
232 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
233 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
235 }
236
237 interp_ok(0)
238 }
239
240 fn file_type_to_d_type(
241 &mut self,
242 file_type: std::io::Result<FileType>,
243 ) -> InterpResult<'tcx, i32> {
244 #[cfg(unix)]
245 use std::os::unix::fs::FileTypeExt;
246
247 let this = self.eval_context_mut();
248 match file_type {
249 Ok(file_type) => {
250 match () {
251 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
252 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
253 _ if file_type.is_symlink() =>
254 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
255 #[cfg(unix)]
257 _ if file_type.is_block_device() =>
258 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
259 #[cfg(unix)]
260 _ if file_type.is_char_device() =>
261 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
262 #[cfg(unix)]
263 _ if file_type.is_fifo() =>
264 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
265 #[cfg(unix)]
266 _ if file_type.is_socket() =>
267 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
268 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
270 }
271 }
272 Err(_) => {
273 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
275 }
276 }
277 }
278}
279
280#[derive(Debug)]
282struct OpenDir {
283 read_dir: ReadDir,
285 entry: Option<Pointer>,
288}
289
290impl OpenDir {
291 fn new(read_dir: ReadDir) -> Self {
292 Self { read_dir, entry: None }
293 }
294}
295
296#[derive(Debug)]
300pub struct DirTable {
301 streams: FxHashMap<u64, OpenDir>,
311 next_id: u64,
313}
314
315impl DirTable {
316 #[expect(clippy::arithmetic_side_effects)]
317 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
318 let id = self.next_id;
319 self.next_id += 1;
320 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
321 id
322 }
323}
324
325impl Default for DirTable {
326 fn default() -> DirTable {
327 DirTable {
328 streams: FxHashMap::default(),
329 next_id: 1,
331 }
332 }
333}
334
335impl VisitProvenance for DirTable {
336 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
337 let DirTable { streams, next_id: _ } = self;
338
339 for dir in streams.values() {
340 dir.entry.visit_provenance(visit);
341 }
342 }
343}
344
345fn maybe_sync_file(
346 file: &File,
347 writable: bool,
348 operation: fn(&File) -> std::io::Result<()>,
349) -> std::io::Result<i32> {
350 if !writable && cfg!(windows) {
351 Ok(0i32)
355 } else {
356 let result = operation(file);
357 result.map(|_| 0i32)
358 }
359}
360
361impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
362pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
363 fn open(
364 &mut self,
365 path_raw: &OpTy<'tcx>,
366 flag: &OpTy<'tcx>,
367 varargs: &[OpTy<'tcx>],
368 ) -> InterpResult<'tcx, Scalar> {
369 let this = self.eval_context_mut();
370
371 let path_raw = this.read_pointer(path_raw)?;
372 let path = this.read_path_from_c_str(path_raw)?;
373 let flag = this.read_scalar(flag)?.to_i32()?;
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
391 if access_mode == o_rdonly {
392 writable = false;
393 options.read(true);
394 } else if access_mode == o_wronly {
395 options.write(true);
396 } else if access_mode == o_rdwr {
397 options.read(true).write(true);
398 } else {
399 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
400 }
401 let mut mirror = access_mode;
405
406 let o_append = this.eval_libc_i32("O_APPEND");
407 if flag & o_append == o_append {
408 options.append(true);
409 mirror |= o_append;
410 }
411 let o_trunc = this.eval_libc_i32("O_TRUNC");
412 if flag & o_trunc == o_trunc {
413 options.truncate(true);
414 mirror |= o_trunc;
415 }
416 let o_creat = this.eval_libc_i32("O_CREAT");
417 if flag & o_creat == o_creat {
418 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
422 let mode = this.read_scalar(mode)?.to_u32()?;
423
424 #[cfg(unix)]
425 {
426 use std::os::unix::fs::OpenOptionsExt;
428 options.mode(mode);
429 }
430 #[cfg(not(unix))]
431 {
432 if mode != 0o666 {
434 throw_unsup_format!(
435 "non-default mode 0o{:o} is not supported on non-Unix hosts",
436 mode
437 );
438 }
439 }
440
441 mirror |= o_creat;
442
443 let o_excl = this.eval_libc_i32("O_EXCL");
444 if flag & o_excl == o_excl {
445 mirror |= o_excl;
446 options.create_new(true);
447 } else {
448 options.create(true);
449 }
450 }
451 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
452 if flag & o_cloexec == o_cloexec {
453 mirror |= o_cloexec;
456 }
457 if this.tcx.sess.target.os == "linux" {
458 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
459 if flag & o_tmpfile == o_tmpfile {
460 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
462 }
463 }
464
465 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
466 if flag & o_nofollow == o_nofollow {
467 #[cfg(unix)]
468 {
469 use std::os::unix::fs::OpenOptionsExt;
470 options.custom_flags(libc::O_NOFOLLOW);
471 }
472 #[cfg(not(unix))]
476 {
477 if path.is_symlink() {
480 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
481 }
482 }
483 mirror |= o_nofollow;
484 }
485
486 if flag != mirror {
489 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
490 }
491
492 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
494 this.reject_in_isolation("`open`", reject_with)?;
495 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
496 }
497
498 let fd = options
499 .open(path)
500 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
501
502 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
503 }
504
505 fn lseek64(
506 &mut self,
507 fd_num: i32,
508 offset: i128,
509 whence: i32,
510 dest: &MPlaceTy<'tcx>,
511 ) -> InterpResult<'tcx> {
512 let this = self.eval_context_mut();
513
514 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
517 if offset < 0 {
518 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
520 } else {
521 SeekFrom::Start(u64::try_from(offset).unwrap())
522 }
523 } else if whence == this.eval_libc_i32("SEEK_CUR") {
524 SeekFrom::Current(i64::try_from(offset).unwrap())
525 } else if whence == this.eval_libc_i32("SEEK_END") {
526 SeekFrom::End(i64::try_from(offset).unwrap())
527 } else {
528 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
529 };
530
531 let communicate = this.machine.communicate();
532
533 let Some(fd) = this.machine.fds.get(fd_num) else {
534 return this.set_last_error_and_return(LibcError("EBADF"), dest);
535 };
536 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
537 drop(fd);
538
539 let result = this.try_unwrap_io_result(result)?;
540 this.write_int(result, dest)?;
541 interp_ok(())
542 }
543
544 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
545 let this = self.eval_context_mut();
546
547 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
548
549 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
551 this.reject_in_isolation("`unlink`", reject_with)?;
552 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
553 }
554
555 let result = remove_file(path).map(|_| 0);
556 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
557 }
558
559 fn symlink(
560 &mut self,
561 target_op: &OpTy<'tcx>,
562 linkpath_op: &OpTy<'tcx>,
563 ) -> InterpResult<'tcx, Scalar> {
564 #[cfg(unix)]
565 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
566 std::os::unix::fs::symlink(src, dst)
567 }
568
569 #[cfg(windows)]
570 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
571 use std::os::windows::fs;
572 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
573 }
574
575 let this = self.eval_context_mut();
576 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
577 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
578
579 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
581 this.reject_in_isolation("`symlink`", reject_with)?;
582 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
583 }
584
585 let result = create_link(&target, &linkpath).map(|_| 0);
586 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
587 }
588
589 fn macos_fbsd_solarish_stat(
590 &mut self,
591 path_op: &OpTy<'tcx>,
592 buf_op: &OpTy<'tcx>,
593 ) -> InterpResult<'tcx, Scalar> {
594 let this = self.eval_context_mut();
595
596 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
597 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
598 }
599
600 let path_scalar = this.read_pointer(path_op)?;
601 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
602
603 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
605 this.reject_in_isolation("`stat`", reject_with)?;
606 return this.set_last_error_and_return_i32(LibcError("EACCES"));
607 }
608
609 let metadata = match FileMetadata::from_path(this, &path, true)? {
611 Ok(metadata) => metadata,
612 Err(err) => return this.set_last_error_and_return_i32(err),
613 };
614
615 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
616 }
617
618 fn macos_fbsd_solarish_lstat(
620 &mut self,
621 path_op: &OpTy<'tcx>,
622 buf_op: &OpTy<'tcx>,
623 ) -> InterpResult<'tcx, Scalar> {
624 let this = self.eval_context_mut();
625
626 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
627 panic!(
628 "`macos_fbsd_solaris_lstat` should not be called on {}",
629 this.tcx.sess.target.os
630 );
631 }
632
633 let path_scalar = this.read_pointer(path_op)?;
634 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
635
636 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
638 this.reject_in_isolation("`lstat`", reject_with)?;
639 return this.set_last_error_and_return_i32(LibcError("EACCES"));
640 }
641
642 let metadata = match FileMetadata::from_path(this, &path, false)? {
643 Ok(metadata) => metadata,
644 Err(err) => return this.set_last_error_and_return_i32(err),
645 };
646
647 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
648 }
649
650 fn macos_fbsd_solarish_fstat(
651 &mut self,
652 fd_op: &OpTy<'tcx>,
653 buf_op: &OpTy<'tcx>,
654 ) -> InterpResult<'tcx, Scalar> {
655 let this = self.eval_context_mut();
656
657 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
658 panic!(
659 "`macos_fbsd_solaris_fstat` should not be called on {}",
660 this.tcx.sess.target.os
661 );
662 }
663
664 let fd = this.read_scalar(fd_op)?.to_i32()?;
665
666 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
668 this.reject_in_isolation("`fstat`", reject_with)?;
669 return this.set_last_error_and_return_i32(LibcError("EBADF"));
671 }
672
673 let metadata = match FileMetadata::from_fd_num(this, fd)? {
674 Ok(metadata) => metadata,
675 Err(err) => return this.set_last_error_and_return_i32(err),
676 };
677 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
678 }
679
680 fn linux_statx(
681 &mut self,
682 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
688 let this = self.eval_context_mut();
689
690 this.assert_target_os("linux", "statx");
691
692 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
693 let pathname_ptr = this.read_pointer(pathname_op)?;
694 let flags = this.read_scalar(flags_op)?.to_i32()?;
695 let _mask = this.read_scalar(mask_op)?.to_u32()?;
696 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
697
698 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
700 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
701 }
702
703 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
704
705 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
706 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
708 let empty_path_flag = flags & at_empty_path == at_empty_path;
709 if !(path.is_absolute()
717 || dirfd == this.eval_libc_i32("AT_FDCWD")
718 || (path.as_os_str().is_empty() && empty_path_flag))
719 {
720 throw_unsup_format!(
721 "using statx is only supported with absolute paths, relative paths with the file \
722 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
723 file descriptor"
724 )
725 }
726
727 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
729 this.reject_in_isolation("`statx`", reject_with)?;
730 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
731 LibcError("EACCES")
734 } else {
735 assert!(empty_path_flag);
739 LibcError("EBADF")
740 };
741 return this.set_last_error_and_return_i32(ecode);
742 }
743
744 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
749
750 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
753
754 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
757 FileMetadata::from_fd_num(this, dirfd)?
758 } else {
759 FileMetadata::from_path(this, &path, follow_symlink)?
760 };
761 let metadata = match metadata {
762 Ok(metadata) => metadata,
763 Err(err) => return this.set_last_error_and_return_i32(err),
764 };
765
766 let mode: u16 = metadata
771 .mode
772 .to_u32()?
773 .try_into()
774 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
775
776 let (access_sec, access_nsec) = metadata
779 .accessed
780 .map(|tup| {
781 mask |= this.eval_libc_u32("STATX_ATIME");
782 interp_ok(tup)
783 })
784 .unwrap_or_else(|| interp_ok((0, 0)))?;
785
786 let (created_sec, created_nsec) = metadata
787 .created
788 .map(|tup| {
789 mask |= this.eval_libc_u32("STATX_BTIME");
790 interp_ok(tup)
791 })
792 .unwrap_or_else(|| interp_ok((0, 0)))?;
793
794 let (modified_sec, modified_nsec) = metadata
795 .modified
796 .map(|tup| {
797 mask |= this.eval_libc_u32("STATX_MTIME");
798 interp_ok(tup)
799 })
800 .unwrap_or_else(|| interp_ok((0, 0)))?;
801
802 this.write_int_fields_named(
804 &[
805 ("stx_mask", mask.into()),
806 ("stx_blksize", 0),
807 ("stx_attributes", 0),
808 ("stx_nlink", 0),
809 ("stx_uid", 0),
810 ("stx_gid", 0),
811 ("stx_mode", mode.into()),
812 ("stx_ino", 0),
813 ("stx_size", metadata.size.into()),
814 ("stx_blocks", 0),
815 ("stx_attributes_mask", 0),
816 ("stx_rdev_major", 0),
817 ("stx_rdev_minor", 0),
818 ("stx_dev_major", 0),
819 ("stx_dev_minor", 0),
820 ],
821 &statxbuf,
822 )?;
823 #[rustfmt::skip]
824 this.write_int_fields_named(
825 &[
826 ("tv_sec", access_sec.into()),
827 ("tv_nsec", access_nsec.into()),
828 ],
829 &this.project_field_named(&statxbuf, "stx_atime")?,
830 )?;
831 #[rustfmt::skip]
832 this.write_int_fields_named(
833 &[
834 ("tv_sec", created_sec.into()),
835 ("tv_nsec", created_nsec.into()),
836 ],
837 &this.project_field_named(&statxbuf, "stx_btime")?,
838 )?;
839 #[rustfmt::skip]
840 this.write_int_fields_named(
841 &[
842 ("tv_sec", 0.into()),
843 ("tv_nsec", 0.into()),
844 ],
845 &this.project_field_named(&statxbuf, "stx_ctime")?,
846 )?;
847 #[rustfmt::skip]
848 this.write_int_fields_named(
849 &[
850 ("tv_sec", modified_sec.into()),
851 ("tv_nsec", modified_nsec.into()),
852 ],
853 &this.project_field_named(&statxbuf, "stx_mtime")?,
854 )?;
855
856 interp_ok(Scalar::from_i32(0))
857 }
858
859 fn rename(
860 &mut self,
861 oldpath_op: &OpTy<'tcx>,
862 newpath_op: &OpTy<'tcx>,
863 ) -> InterpResult<'tcx, Scalar> {
864 let this = self.eval_context_mut();
865
866 let oldpath_ptr = this.read_pointer(oldpath_op)?;
867 let newpath_ptr = this.read_pointer(newpath_op)?;
868
869 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
870 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
871 }
872
873 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
874 let newpath = this.read_path_from_c_str(newpath_ptr)?;
875
876 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
878 this.reject_in_isolation("`rename`", reject_with)?;
879 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
880 }
881
882 let result = rename(oldpath, newpath).map(|_| 0);
883
884 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
885 }
886
887 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
888 let this = self.eval_context_mut();
889
890 #[cfg_attr(not(unix), allow(unused_variables))]
891 let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
892 u32::from(this.read_scalar(mode_op)?.to_u16()?)
893 } else {
894 this.read_scalar(mode_op)?.to_u32()?
895 };
896
897 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
898
899 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
901 this.reject_in_isolation("`mkdir`", reject_with)?;
902 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
903 }
904
905 #[cfg_attr(not(unix), allow(unused_mut))]
906 let mut builder = DirBuilder::new();
907
908 #[cfg(unix)]
911 {
912 use std::os::unix::fs::DirBuilderExt;
913 builder.mode(mode);
914 }
915
916 let result = builder.create(path).map(|_| 0i32);
917
918 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
919 }
920
921 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
922 let this = self.eval_context_mut();
923
924 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
925
926 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
928 this.reject_in_isolation("`rmdir`", reject_with)?;
929 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
930 }
931
932 let result = remove_dir(path).map(|_| 0i32);
933
934 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
935 }
936
937 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
938 let this = self.eval_context_mut();
939
940 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
941
942 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
944 this.reject_in_isolation("`opendir`", reject_with)?;
945 this.set_last_error(LibcError("EACCES"))?;
946 return interp_ok(Scalar::null_ptr(this));
947 }
948
949 let result = read_dir(name);
950
951 match result {
952 Ok(dir_iter) => {
953 let id = this.machine.dirs.insert_new(dir_iter);
954
955 interp_ok(Scalar::from_target_usize(id, this))
959 }
960 Err(e) => {
961 this.set_last_error(e)?;
962 interp_ok(Scalar::null_ptr(this))
963 }
964 }
965 }
966
967 fn linux_solarish_readdir64(
968 &mut self,
969 dirent_type: &str,
970 dirp_op: &OpTy<'tcx>,
971 ) -> InterpResult<'tcx, Scalar> {
972 let this = self.eval_context_mut();
973
974 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
975 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
976 }
977
978 let dirp = this.read_target_usize(dirp_op)?;
979
980 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
982 this.reject_in_isolation("`readdir`", reject_with)?;
983 this.set_last_error(LibcError("EBADF"))?;
984 return interp_ok(Scalar::null_ptr(this));
985 }
986
987 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
988 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
989 })?;
990
991 let entry = match open_dir.read_dir.next() {
992 Some(Ok(dir_entry)) => {
993 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
1018 let name_len = u64::try_from(name_bytes.len()).unwrap();
1019
1020 let dirent_layout = this.libc_ty_layout(dirent_type);
1021 let fields = &dirent_layout.fields;
1022 let last_field = fields.count().strict_sub(1);
1023 let d_name_offset = fields.offset(last_field).bytes();
1024 let size = d_name_offset.strict_add(name_len);
1025
1026 let entry = this.allocate_ptr(
1027 Size::from_bytes(size),
1028 dirent_layout.align.abi,
1029 MiriMemoryKind::Runtime.into(),
1030 AllocInit::Uninit,
1031 )?;
1032 let entry: Pointer = entry.into();
1033
1034 #[cfg(unix)]
1037 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1038 #[cfg(not(unix))]
1039 let ino = 0u64;
1040
1041 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1042 this.write_int_fields_named(
1043 &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
1044 &this.ptr_to_mplace(entry, dirent_layout),
1045 )?;
1046
1047 if let Some(d_type) = this
1048 .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
1049 {
1050 this.write_int(file_type, &d_type)?;
1051 }
1052
1053 let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
1054 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1055
1056 Some(entry)
1057 }
1058 None => {
1059 None
1061 }
1062 Some(Err(e)) => {
1063 this.set_last_error(e)?;
1064 None
1065 }
1066 };
1067
1068 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1069 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1070 if let Some(old_entry) = old_entry {
1071 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1072 }
1073
1074 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1075 }
1076
1077 fn macos_fbsd_readdir_r(
1078 &mut self,
1079 dirp_op: &OpTy<'tcx>,
1080 entry_op: &OpTy<'tcx>,
1081 result_op: &OpTy<'tcx>,
1082 ) -> InterpResult<'tcx, Scalar> {
1083 let this = self.eval_context_mut();
1084
1085 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1086 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1087 }
1088
1089 let dirp = this.read_target_usize(dirp_op)?;
1090 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1091
1092 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1094 this.reject_in_isolation("`readdir_r`", reject_with)?;
1095 return interp_ok(this.eval_libc("EBADF"));
1097 }
1098
1099 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1100 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1101 })?;
1102 interp_ok(match open_dir.read_dir.next() {
1103 Some(Ok(dir_entry)) => {
1104 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1119 let name_place = this.project_field_named(&entry_place, "d_name")?;
1120
1121 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1123 &file_name,
1124 name_place.ptr(),
1125 name_place.layout.size.bytes(),
1126 )?;
1127 let file_name_len = file_name_buf_len.strict_sub(1);
1128 if !name_fits {
1129 throw_unsup_format!(
1130 "a directory entry had a name too large to fit in libc::dirent"
1131 );
1132 }
1133
1134 #[cfg(unix)]
1137 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1138 #[cfg(not(unix))]
1139 let ino = 0u64;
1140
1141 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1142
1143 this.write_int_fields_named(
1145 &[
1146 ("d_reclen", 0),
1147 ("d_namlen", file_name_len.into()),
1148 ("d_type", file_type.into()),
1149 ],
1150 &entry_place,
1151 )?;
1152 match &*this.tcx.sess.target.os {
1154 "macos" => {
1155 #[rustfmt::skip]
1156 this.write_int_fields_named(
1157 &[
1158 ("d_ino", ino.into()),
1159 ("d_seekoff", 0),
1160 ],
1161 &entry_place,
1162 )?;
1163 }
1164 "freebsd" => {
1165 #[rustfmt::skip]
1166 this.write_int_fields_named(
1167 &[
1168 ("d_fileno", ino.into()),
1169 ("d_off", 0),
1170 ],
1171 &entry_place,
1172 )?;
1173 }
1174 _ => unreachable!(),
1175 }
1176 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1177
1178 Scalar::from_i32(0)
1179 }
1180 None => {
1181 this.write_null(&result_place)?;
1183 Scalar::from_i32(0)
1184 }
1185 Some(Err(e)) => {
1186 this.io_error_to_errnum(e)?
1188 }
1189 })
1190 }
1191
1192 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1193 let this = self.eval_context_mut();
1194
1195 let dirp = this.read_target_usize(dirp_op)?;
1196
1197 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1199 this.reject_in_isolation("`closedir`", reject_with)?;
1200 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1201 }
1202
1203 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1204 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1205 };
1206 if let Some(entry) = open_dir.entry.take() {
1207 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1208 }
1209 drop(open_dir);
1211
1212 interp_ok(Scalar::from_i32(0))
1213 }
1214
1215 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1216 let this = self.eval_context_mut();
1217
1218 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1220 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1221 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1223 }
1224
1225 let Some(fd) = this.machine.fds.get(fd_num) else {
1226 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1227 };
1228
1229 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1231 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1232 })?;
1233
1234 if file.writable {
1235 if let Ok(length) = length.try_into() {
1236 let result = file.file.set_len(length);
1237 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1238 interp_ok(Scalar::from_i32(result))
1239 } else {
1240 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1241 }
1242 } else {
1243 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1245 }
1246 }
1247
1248 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1249 let this = self.eval_context_mut();
1255
1256 let fd = this.read_scalar(fd_op)?.to_i32()?;
1257
1258 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1260 this.reject_in_isolation("`fsync`", reject_with)?;
1261 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1263 }
1264
1265 self.ffullsync_fd(fd)
1266 }
1267
1268 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1269 let this = self.eval_context_mut();
1270 let Some(fd) = this.machine.fds.get(fd_num) else {
1271 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1272 };
1273 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1275 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1276 })?;
1277 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1278 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1279 }
1280
1281 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1282 let this = self.eval_context_mut();
1283
1284 let fd = this.read_scalar(fd_op)?.to_i32()?;
1285
1286 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1288 this.reject_in_isolation("`fdatasync`", reject_with)?;
1289 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1291 }
1292
1293 let Some(fd) = this.machine.fds.get(fd) else {
1294 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1295 };
1296 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1298 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1299 })?;
1300 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1301 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1302 }
1303
1304 fn sync_file_range(
1305 &mut self,
1306 fd_op: &OpTy<'tcx>,
1307 offset_op: &OpTy<'tcx>,
1308 nbytes_op: &OpTy<'tcx>,
1309 flags_op: &OpTy<'tcx>,
1310 ) -> InterpResult<'tcx, Scalar> {
1311 let this = self.eval_context_mut();
1312
1313 let fd = this.read_scalar(fd_op)?.to_i32()?;
1314 let offset = this.read_scalar(offset_op)?.to_i64()?;
1315 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1316 let flags = this.read_scalar(flags_op)?.to_i32()?;
1317
1318 if offset < 0 || nbytes < 0 {
1319 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1320 }
1321 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1322 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1323 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1324 if flags & allowed_flags != flags {
1325 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1326 }
1327
1328 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1330 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1331 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1333 }
1334
1335 let Some(fd) = this.machine.fds.get(fd) else {
1336 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1337 };
1338 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1340 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1341 })?;
1342 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1343 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1344 }
1345
1346 fn readlink(
1347 &mut self,
1348 pathname_op: &OpTy<'tcx>,
1349 buf_op: &OpTy<'tcx>,
1350 bufsize_op: &OpTy<'tcx>,
1351 ) -> InterpResult<'tcx, i64> {
1352 let this = self.eval_context_mut();
1353
1354 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1355 let buf = this.read_pointer(buf_op)?;
1356 let bufsize = this.read_target_usize(bufsize_op)?;
1357
1358 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1360 this.reject_in_isolation("`readlink`", reject_with)?;
1361 this.set_last_error(LibcError("EACCES"))?;
1362 return interp_ok(-1);
1363 }
1364
1365 let result = std::fs::read_link(pathname);
1366 match result {
1367 Ok(resolved) => {
1368 let resolved = this.convert_path(
1372 Cow::Borrowed(resolved.as_ref()),
1373 crate::shims::os_str::PathConversion::HostToTarget,
1374 );
1375 let mut path_bytes = resolved.as_encoded_bytes();
1376 let bufsize: usize = bufsize.try_into().unwrap();
1377 if path_bytes.len() > bufsize {
1378 path_bytes = &path_bytes[..bufsize]
1379 }
1380 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1381 interp_ok(path_bytes.len().try_into().unwrap())
1382 }
1383 Err(e) => {
1384 this.set_last_error(e)?;
1385 interp_ok(-1)
1386 }
1387 }
1388 }
1389
1390 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1391 let this = self.eval_context_mut();
1392 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1395 let error = if let Some(fd) = this.machine.fds.get(fd) {
1396 if fd.is_tty(this.machine.communicate()) {
1397 return interp_ok(Scalar::from_i32(1));
1398 } else {
1399 LibcError("ENOTTY")
1400 }
1401 } else {
1402 LibcError("EBADF")
1404 };
1405 this.set_last_error(error)?;
1406 interp_ok(Scalar::from_i32(0))
1407 }
1408
1409 fn realpath(
1410 &mut self,
1411 path_op: &OpTy<'tcx>,
1412 processed_path_op: &OpTy<'tcx>,
1413 ) -> InterpResult<'tcx, Scalar> {
1414 let this = self.eval_context_mut();
1415 this.assert_target_os_is_unix("realpath");
1416
1417 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1418 let processed_ptr = this.read_pointer(processed_path_op)?;
1419
1420 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1422 this.reject_in_isolation("`realpath`", reject_with)?;
1423 this.set_last_error(LibcError("EACCES"))?;
1424 return interp_ok(Scalar::from_target_usize(0, this));
1425 }
1426
1427 let result = std::fs::canonicalize(pathname);
1428 match result {
1429 Ok(resolved) => {
1430 let path_max = this
1431 .eval_libc_i32("PATH_MAX")
1432 .try_into()
1433 .expect("PATH_MAX does not fit in u64");
1434 let dest = if this.ptr_is_null(processed_ptr)? {
1435 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1445 } else {
1446 let (wrote_path, _) =
1447 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1448
1449 if !wrote_path {
1450 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1454 return interp_ok(Scalar::from_target_usize(0, this));
1455 }
1456 processed_ptr
1457 };
1458
1459 interp_ok(Scalar::from_maybe_pointer(dest, this))
1460 }
1461 Err(e) => {
1462 this.set_last_error(e)?;
1463 interp_ok(Scalar::from_target_usize(0, this))
1464 }
1465 }
1466 }
1467 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1468 use rand::seq::IndexedRandom;
1469
1470 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1472
1473 let this = self.eval_context_mut();
1474 this.assert_target_os_is_unix("mkstemp");
1475
1476 let max_attempts = this.eval_libc_u32("TMP_MAX");
1486
1487 let template_ptr = this.read_pointer(template_op)?;
1490 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1491 let template_bytes = template.as_mut_slice();
1492
1493 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1495 this.reject_in_isolation("`mkstemp`", reject_with)?;
1496 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1497 }
1498
1499 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1501
1502 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1507 let end_pos = template_bytes.len();
1508 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1509
1510 if last_six_char_bytes != suffix_bytes {
1512 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1513 }
1514
1515 const SUBSTITUTIONS: &[char; 62] = &[
1519 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1520 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1521 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1522 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1523 ];
1524
1525 let mut fopts = OpenOptions::new();
1528 fopts.read(true).write(true).create_new(true);
1529
1530 #[cfg(unix)]
1531 {
1532 use std::os::unix::fs::OpenOptionsExt;
1533 fopts.mode(0o600);
1535 fopts.custom_flags(libc::O_EXCL);
1536 }
1537 #[cfg(windows)]
1538 {
1539 use std::os::windows::fs::OpenOptionsExt;
1540 fopts.share_mode(0);
1542 }
1543
1544 for _ in 0..max_attempts {
1546 let rng = this.machine.rng.get_mut();
1547
1548 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1550
1551 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1553
1554 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1556
1557 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1559
1560 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1561
1562 let file = fopts.open(possibly_unique);
1563
1564 match file {
1565 Ok(f) => {
1566 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1567 return interp_ok(Scalar::from_i32(fd));
1568 }
1569 Err(e) =>
1570 match e.kind() {
1571 ErrorKind::AlreadyExists => continue,
1573 _ => {
1575 return this.set_last_error_and_return_i32(e);
1578 }
1579 },
1580 }
1581 }
1582
1583 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1585 }
1586}
1587
1588fn extract_sec_and_nsec<'tcx>(
1592 time: std::io::Result<SystemTime>,
1593) -> InterpResult<'tcx, Option<(u64, u32)>> {
1594 match time.ok() {
1595 Some(time) => {
1596 let duration = system_time_to_duration(&time)?;
1597 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1598 }
1599 None => interp_ok(None),
1600 }
1601}
1602
1603struct FileMetadata {
1606 mode: Scalar,
1607 size: u64,
1608 created: Option<(u64, u32)>,
1609 accessed: Option<(u64, u32)>,
1610 modified: Option<(u64, u32)>,
1611}
1612
1613impl FileMetadata {
1614 fn from_path<'tcx>(
1615 ecx: &mut MiriInterpCx<'tcx>,
1616 path: &Path,
1617 follow_symlink: bool,
1618 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1619 let metadata =
1620 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1621
1622 FileMetadata::from_meta(ecx, metadata)
1623 }
1624
1625 fn from_fd_num<'tcx>(
1626 ecx: &mut MiriInterpCx<'tcx>,
1627 fd_num: i32,
1628 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1629 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1630 return interp_ok(Err(LibcError("EBADF")));
1631 };
1632
1633 let metadata = fd.metadata()?;
1634 drop(fd);
1635 FileMetadata::from_meta(ecx, metadata)
1636 }
1637
1638 fn from_meta<'tcx>(
1639 ecx: &mut MiriInterpCx<'tcx>,
1640 metadata: Result<std::fs::Metadata, std::io::Error>,
1641 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1642 let metadata = match metadata {
1643 Ok(metadata) => metadata,
1644 Err(e) => {
1645 return interp_ok(Err(e.into()));
1646 }
1647 };
1648
1649 let file_type = metadata.file_type();
1650
1651 let mode_name = if file_type.is_file() {
1652 "S_IFREG"
1653 } else if file_type.is_dir() {
1654 "S_IFDIR"
1655 } else {
1656 "S_IFLNK"
1657 };
1658
1659 let mode = ecx.eval_libc(mode_name);
1660
1661 let size = metadata.len();
1662
1663 let created = extract_sec_and_nsec(metadata.created())?;
1664 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1665 let modified = extract_sec_and_nsec(metadata.modified())?;
1666
1667 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified }))
1669 }
1670}