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 macro_rules! cfg_select_dispatch {
94 ($($tokens:tt)*) => {
95 #[cfg(bootstrap)]
96 cfg_match! { $($tokens)* }
97
98 #[cfg(not(bootstrap))]
99 cfg_select! { $($tokens)* }
100 };
101 }
102
103 assert!(communicate_allowed, "isolation should have prevented even opening a file");
104 cfg_select_dispatch! {
105 all(target_family = "unix", not(target_os = "solaris")) => {
106 use std::os::fd::AsRawFd;
107
108 use FlockOp::*;
109 let (host_op, lock_nb) = match op {
111 SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
112 ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
113 Unlock => (libc::LOCK_UN, false),
114 };
115
116 let fd = self.file.as_raw_fd();
117 let ret = unsafe { libc::flock(fd, host_op) };
118 let res = match ret {
119 0 => Ok(()),
120 -1 => {
121 let err = io::Error::last_os_error();
122 if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
123 throw_unsup_format!("blocking `flock` is not currently supported");
124 }
125 Err(err)
126 }
127 ret => panic!("Unexpected return value from flock: {ret}"),
128 };
129 interp_ok(res)
130 }
131 target_family = "windows" => {
132 use std::os::windows::io::AsRawHandle;
133
134 use windows_sys::Win32::Foundation::{
135 ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, TRUE,
136 };
137 use windows_sys::Win32::Storage::FileSystem::{
138 LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
139 };
140
141 let fh = self.file.as_raw_handle();
142
143 use FlockOp::*;
144 let (ret, lock_nb) = match op {
145 SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
146 let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
148 if matches!(op, ExclusiveLock { .. }) {
149 flags |= LOCKFILE_EXCLUSIVE_LOCK;
150 }
151 let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
152 (ret, nonblocking)
153 }
154 Unlock => {
155 let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
156 (ret, false)
157 }
158 };
159
160 let res = match ret {
161 TRUE => Ok(()),
162 FALSE => {
163 let mut err = io::Error::last_os_error();
164 let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
167 if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
168 if lock_nb {
169 let desc = format!("LockFileEx wouldblock error: {err}");
172 err = io::Error::new(io::ErrorKind::WouldBlock, desc);
173 } else {
174 throw_unsup_format!("blocking `flock` is not currently supported");
175 }
176 }
177 Err(err)
178 }
179 _ => panic!("Unexpected return value: {ret}"),
180 };
181 interp_ok(res)
182 }
183 _ => {
184 let _ = op;
185 throw_unsup_format!(
186 "flock is supported only on UNIX (except Solaris) and Windows hosts"
187 );
188 }
189 }
190 }
191}
192
193impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
194trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
195 fn macos_fbsd_solarish_write_stat_buf(
196 &mut self,
197 metadata: FileMetadata,
198 buf_op: &OpTy<'tcx>,
199 ) -> InterpResult<'tcx, i32> {
200 let this = self.eval_context_mut();
201
202 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
203 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
204 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
205 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
206
207 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
208 this.write_int_fields_named(
209 &[
210 ("st_dev", 0),
211 ("st_mode", mode.try_into().unwrap()),
212 ("st_nlink", 0),
213 ("st_ino", 0),
214 ("st_uid", 0),
215 ("st_gid", 0),
216 ("st_rdev", 0),
217 ("st_atime", access_sec.into()),
218 ("st_mtime", modified_sec.into()),
219 ("st_ctime", 0),
220 ("st_size", metadata.size.into()),
221 ("st_blocks", 0),
222 ("st_blksize", 0),
223 ],
224 &buf,
225 )?;
226
227 if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
228 this.write_int_fields_named(
229 &[
230 ("st_atime_nsec", access_nsec.into()),
231 ("st_mtime_nsec", modified_nsec.into()),
232 ("st_ctime_nsec", 0),
233 ("st_birthtime", created_sec.into()),
234 ("st_birthtime_nsec", created_nsec.into()),
235 ("st_flags", 0),
236 ("st_gen", 0),
237 ],
238 &buf,
239 )?;
240 }
241
242 if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
243 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
244 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
246 }
247
248 interp_ok(0)
249 }
250
251 fn file_type_to_d_type(
252 &mut self,
253 file_type: std::io::Result<FileType>,
254 ) -> InterpResult<'tcx, i32> {
255 #[cfg(unix)]
256 use std::os::unix::fs::FileTypeExt;
257
258 let this = self.eval_context_mut();
259 match file_type {
260 Ok(file_type) => {
261 match () {
262 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
263 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
264 _ if file_type.is_symlink() =>
265 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
266 #[cfg(unix)]
268 _ if file_type.is_block_device() =>
269 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
270 #[cfg(unix)]
271 _ if file_type.is_char_device() =>
272 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
273 #[cfg(unix)]
274 _ if file_type.is_fifo() =>
275 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
276 #[cfg(unix)]
277 _ if file_type.is_socket() =>
278 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
279 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
281 }
282 }
283 Err(_) => {
284 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
286 }
287 }
288 }
289}
290
291#[derive(Debug)]
293struct OpenDir {
294 read_dir: ReadDir,
296 entry: Option<Pointer>,
299}
300
301impl OpenDir {
302 fn new(read_dir: ReadDir) -> Self {
303 Self { read_dir, entry: None }
304 }
305}
306
307#[derive(Debug)]
311pub struct DirTable {
312 streams: FxHashMap<u64, OpenDir>,
322 next_id: u64,
324}
325
326impl DirTable {
327 #[expect(clippy::arithmetic_side_effects)]
328 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
329 let id = self.next_id;
330 self.next_id += 1;
331 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
332 id
333 }
334}
335
336impl Default for DirTable {
337 fn default() -> DirTable {
338 DirTable {
339 streams: FxHashMap::default(),
340 next_id: 1,
342 }
343 }
344}
345
346impl VisitProvenance for DirTable {
347 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
348 let DirTable { streams, next_id: _ } = self;
349
350 for dir in streams.values() {
351 dir.entry.visit_provenance(visit);
352 }
353 }
354}
355
356fn maybe_sync_file(
357 file: &File,
358 writable: bool,
359 operation: fn(&File) -> std::io::Result<()>,
360) -> std::io::Result<i32> {
361 if !writable && cfg!(windows) {
362 Ok(0i32)
366 } else {
367 let result = operation(file);
368 result.map(|_| 0i32)
369 }
370}
371
372impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
373pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
374 fn open(
375 &mut self,
376 path_raw: &OpTy<'tcx>,
377 flag: &OpTy<'tcx>,
378 varargs: &[OpTy<'tcx>],
379 ) -> InterpResult<'tcx, Scalar> {
380 let this = self.eval_context_mut();
381
382 let path_raw = this.read_pointer(path_raw)?;
383 let path = this.read_path_from_c_str(path_raw)?;
384 let flag = this.read_scalar(flag)?.to_i32()?;
385
386 let mut options = OpenOptions::new();
387
388 let o_rdonly = this.eval_libc_i32("O_RDONLY");
389 let o_wronly = this.eval_libc_i32("O_WRONLY");
390 let o_rdwr = this.eval_libc_i32("O_RDWR");
391 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
395 throw_unsup_format!("access mode flags on this target are unsupported");
396 }
397 let mut writable = true;
398
399 let access_mode = flag & 0b11;
401
402 if access_mode == o_rdonly {
403 writable = false;
404 options.read(true);
405 } else if access_mode == o_wronly {
406 options.write(true);
407 } else if access_mode == o_rdwr {
408 options.read(true).write(true);
409 } else {
410 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
411 }
412 let mut mirror = access_mode;
416
417 let o_append = this.eval_libc_i32("O_APPEND");
418 if flag & o_append == o_append {
419 options.append(true);
420 mirror |= o_append;
421 }
422 let o_trunc = this.eval_libc_i32("O_TRUNC");
423 if flag & o_trunc == o_trunc {
424 options.truncate(true);
425 mirror |= o_trunc;
426 }
427 let o_creat = this.eval_libc_i32("O_CREAT");
428 if flag & o_creat == o_creat {
429 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
433 let mode = this.read_scalar(mode)?.to_u32()?;
434
435 #[cfg(unix)]
436 {
437 use std::os::unix::fs::OpenOptionsExt;
439 options.mode(mode);
440 }
441 #[cfg(not(unix))]
442 {
443 if mode != 0o666 {
445 throw_unsup_format!(
446 "non-default mode 0o{:o} is not supported on non-Unix hosts",
447 mode
448 );
449 }
450 }
451
452 mirror |= o_creat;
453
454 let o_excl = this.eval_libc_i32("O_EXCL");
455 if flag & o_excl == o_excl {
456 mirror |= o_excl;
457 options.create_new(true);
458 } else {
459 options.create(true);
460 }
461 }
462 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
463 if flag & o_cloexec == o_cloexec {
464 mirror |= o_cloexec;
467 }
468 if this.tcx.sess.target.os == "linux" {
469 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
470 if flag & o_tmpfile == o_tmpfile {
471 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
473 }
474 }
475
476 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
477 if flag & o_nofollow == o_nofollow {
478 #[cfg(unix)]
479 {
480 use std::os::unix::fs::OpenOptionsExt;
481 options.custom_flags(libc::O_NOFOLLOW);
482 }
483 #[cfg(not(unix))]
487 {
488 if path.is_symlink() {
491 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
492 }
493 }
494 mirror |= o_nofollow;
495 }
496
497 if flag != mirror {
500 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
501 }
502
503 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
505 this.reject_in_isolation("`open`", reject_with)?;
506 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
507 }
508
509 let fd = options
510 .open(path)
511 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
512
513 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
514 }
515
516 fn lseek64(
517 &mut self,
518 fd_num: i32,
519 offset: i128,
520 whence: i32,
521 dest: &MPlaceTy<'tcx>,
522 ) -> InterpResult<'tcx> {
523 let this = self.eval_context_mut();
524
525 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
528 if offset < 0 {
529 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
531 } else {
532 SeekFrom::Start(u64::try_from(offset).unwrap())
533 }
534 } else if whence == this.eval_libc_i32("SEEK_CUR") {
535 SeekFrom::Current(i64::try_from(offset).unwrap())
536 } else if whence == this.eval_libc_i32("SEEK_END") {
537 SeekFrom::End(i64::try_from(offset).unwrap())
538 } else {
539 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
540 };
541
542 let communicate = this.machine.communicate();
543
544 let Some(fd) = this.machine.fds.get(fd_num) else {
545 return this.set_last_error_and_return(LibcError("EBADF"), dest);
546 };
547 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
548 drop(fd);
549
550 let result = this.try_unwrap_io_result(result)?;
551 this.write_int(result, dest)?;
552 interp_ok(())
553 }
554
555 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
556 let this = self.eval_context_mut();
557
558 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
559
560 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
562 this.reject_in_isolation("`unlink`", reject_with)?;
563 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
564 }
565
566 let result = remove_file(path).map(|_| 0);
567 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
568 }
569
570 fn symlink(
571 &mut self,
572 target_op: &OpTy<'tcx>,
573 linkpath_op: &OpTy<'tcx>,
574 ) -> InterpResult<'tcx, Scalar> {
575 #[cfg(unix)]
576 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
577 std::os::unix::fs::symlink(src, dst)
578 }
579
580 #[cfg(windows)]
581 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
582 use std::os::windows::fs;
583 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
584 }
585
586 let this = self.eval_context_mut();
587 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
588 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
589
590 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
592 this.reject_in_isolation("`symlink`", reject_with)?;
593 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
594 }
595
596 let result = create_link(&target, &linkpath).map(|_| 0);
597 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
598 }
599
600 fn macos_fbsd_solarish_stat(
601 &mut self,
602 path_op: &OpTy<'tcx>,
603 buf_op: &OpTy<'tcx>,
604 ) -> InterpResult<'tcx, Scalar> {
605 let this = self.eval_context_mut();
606
607 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
608 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
609 }
610
611 let path_scalar = this.read_pointer(path_op)?;
612 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
613
614 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
616 this.reject_in_isolation("`stat`", reject_with)?;
617 return this.set_last_error_and_return_i32(LibcError("EACCES"));
618 }
619
620 let metadata = match FileMetadata::from_path(this, &path, true)? {
622 Ok(metadata) => metadata,
623 Err(err) => return this.set_last_error_and_return_i32(err),
624 };
625
626 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
627 }
628
629 fn macos_fbsd_solarish_lstat(
631 &mut self,
632 path_op: &OpTy<'tcx>,
633 buf_op: &OpTy<'tcx>,
634 ) -> InterpResult<'tcx, Scalar> {
635 let this = self.eval_context_mut();
636
637 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
638 panic!(
639 "`macos_fbsd_solaris_lstat` should not be called on {}",
640 this.tcx.sess.target.os
641 );
642 }
643
644 let path_scalar = this.read_pointer(path_op)?;
645 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
646
647 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
649 this.reject_in_isolation("`lstat`", reject_with)?;
650 return this.set_last_error_and_return_i32(LibcError("EACCES"));
651 }
652
653 let metadata = match FileMetadata::from_path(this, &path, false)? {
654 Ok(metadata) => metadata,
655 Err(err) => return this.set_last_error_and_return_i32(err),
656 };
657
658 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
659 }
660
661 fn macos_fbsd_solarish_fstat(
662 &mut self,
663 fd_op: &OpTy<'tcx>,
664 buf_op: &OpTy<'tcx>,
665 ) -> InterpResult<'tcx, Scalar> {
666 let this = self.eval_context_mut();
667
668 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
669 panic!(
670 "`macos_fbsd_solaris_fstat` should not be called on {}",
671 this.tcx.sess.target.os
672 );
673 }
674
675 let fd = this.read_scalar(fd_op)?.to_i32()?;
676
677 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
679 this.reject_in_isolation("`fstat`", reject_with)?;
680 return this.set_last_error_and_return_i32(LibcError("EBADF"));
682 }
683
684 let metadata = match FileMetadata::from_fd_num(this, fd)? {
685 Ok(metadata) => metadata,
686 Err(err) => return this.set_last_error_and_return_i32(err),
687 };
688 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
689 }
690
691 fn linux_statx(
692 &mut self,
693 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
699 let this = self.eval_context_mut();
700
701 this.assert_target_os("linux", "statx");
702
703 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
704 let pathname_ptr = this.read_pointer(pathname_op)?;
705 let flags = this.read_scalar(flags_op)?.to_i32()?;
706 let _mask = this.read_scalar(mask_op)?.to_u32()?;
707 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
708
709 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
711 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
712 }
713
714 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
715
716 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
717 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
719 let empty_path_flag = flags & at_empty_path == at_empty_path;
720 if !(path.is_absolute()
728 || dirfd == this.eval_libc_i32("AT_FDCWD")
729 || (path.as_os_str().is_empty() && empty_path_flag))
730 {
731 throw_unsup_format!(
732 "using statx is only supported with absolute paths, relative paths with the file \
733 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
734 file descriptor"
735 )
736 }
737
738 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
740 this.reject_in_isolation("`statx`", reject_with)?;
741 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
742 LibcError("EACCES")
745 } else {
746 assert!(empty_path_flag);
750 LibcError("EBADF")
751 };
752 return this.set_last_error_and_return_i32(ecode);
753 }
754
755 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
760
761 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
764
765 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
768 FileMetadata::from_fd_num(this, dirfd)?
769 } else {
770 FileMetadata::from_path(this, &path, follow_symlink)?
771 };
772 let metadata = match metadata {
773 Ok(metadata) => metadata,
774 Err(err) => return this.set_last_error_and_return_i32(err),
775 };
776
777 let mode: u16 = metadata
782 .mode
783 .to_u32()?
784 .try_into()
785 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
786
787 let (access_sec, access_nsec) = metadata
790 .accessed
791 .map(|tup| {
792 mask |= this.eval_libc_u32("STATX_ATIME");
793 interp_ok(tup)
794 })
795 .unwrap_or_else(|| interp_ok((0, 0)))?;
796
797 let (created_sec, created_nsec) = metadata
798 .created
799 .map(|tup| {
800 mask |= this.eval_libc_u32("STATX_BTIME");
801 interp_ok(tup)
802 })
803 .unwrap_or_else(|| interp_ok((0, 0)))?;
804
805 let (modified_sec, modified_nsec) = metadata
806 .modified
807 .map(|tup| {
808 mask |= this.eval_libc_u32("STATX_MTIME");
809 interp_ok(tup)
810 })
811 .unwrap_or_else(|| interp_ok((0, 0)))?;
812
813 this.write_int_fields_named(
815 &[
816 ("stx_mask", mask.into()),
817 ("stx_blksize", 0),
818 ("stx_attributes", 0),
819 ("stx_nlink", 0),
820 ("stx_uid", 0),
821 ("stx_gid", 0),
822 ("stx_mode", mode.into()),
823 ("stx_ino", 0),
824 ("stx_size", metadata.size.into()),
825 ("stx_blocks", 0),
826 ("stx_attributes_mask", 0),
827 ("stx_rdev_major", 0),
828 ("stx_rdev_minor", 0),
829 ("stx_dev_major", 0),
830 ("stx_dev_minor", 0),
831 ],
832 &statxbuf,
833 )?;
834 #[rustfmt::skip]
835 this.write_int_fields_named(
836 &[
837 ("tv_sec", access_sec.into()),
838 ("tv_nsec", access_nsec.into()),
839 ],
840 &this.project_field_named(&statxbuf, "stx_atime")?,
841 )?;
842 #[rustfmt::skip]
843 this.write_int_fields_named(
844 &[
845 ("tv_sec", created_sec.into()),
846 ("tv_nsec", created_nsec.into()),
847 ],
848 &this.project_field_named(&statxbuf, "stx_btime")?,
849 )?;
850 #[rustfmt::skip]
851 this.write_int_fields_named(
852 &[
853 ("tv_sec", 0.into()),
854 ("tv_nsec", 0.into()),
855 ],
856 &this.project_field_named(&statxbuf, "stx_ctime")?,
857 )?;
858 #[rustfmt::skip]
859 this.write_int_fields_named(
860 &[
861 ("tv_sec", modified_sec.into()),
862 ("tv_nsec", modified_nsec.into()),
863 ],
864 &this.project_field_named(&statxbuf, "stx_mtime")?,
865 )?;
866
867 interp_ok(Scalar::from_i32(0))
868 }
869
870 fn rename(
871 &mut self,
872 oldpath_op: &OpTy<'tcx>,
873 newpath_op: &OpTy<'tcx>,
874 ) -> InterpResult<'tcx, Scalar> {
875 let this = self.eval_context_mut();
876
877 let oldpath_ptr = this.read_pointer(oldpath_op)?;
878 let newpath_ptr = this.read_pointer(newpath_op)?;
879
880 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
881 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
882 }
883
884 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
885 let newpath = this.read_path_from_c_str(newpath_ptr)?;
886
887 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
889 this.reject_in_isolation("`rename`", reject_with)?;
890 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
891 }
892
893 let result = rename(oldpath, newpath).map(|_| 0);
894
895 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
896 }
897
898 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
899 let this = self.eval_context_mut();
900
901 #[cfg_attr(not(unix), allow(unused_variables))]
902 let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
903 u32::from(this.read_scalar(mode_op)?.to_u16()?)
904 } else {
905 this.read_scalar(mode_op)?.to_u32()?
906 };
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("`mkdir`", reject_with)?;
913 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
914 }
915
916 #[cfg_attr(not(unix), allow(unused_mut))]
917 let mut builder = DirBuilder::new();
918
919 #[cfg(unix)]
922 {
923 use std::os::unix::fs::DirBuilderExt;
924 builder.mode(mode);
925 }
926
927 let result = builder.create(path).map(|_| 0i32);
928
929 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
930 }
931
932 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
933 let this = self.eval_context_mut();
934
935 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
936
937 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
939 this.reject_in_isolation("`rmdir`", reject_with)?;
940 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
941 }
942
943 let result = remove_dir(path).map(|_| 0i32);
944
945 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
946 }
947
948 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
949 let this = self.eval_context_mut();
950
951 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
952
953 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
955 this.reject_in_isolation("`opendir`", reject_with)?;
956 this.set_last_error(LibcError("EACCES"))?;
957 return interp_ok(Scalar::null_ptr(this));
958 }
959
960 let result = read_dir(name);
961
962 match result {
963 Ok(dir_iter) => {
964 let id = this.machine.dirs.insert_new(dir_iter);
965
966 interp_ok(Scalar::from_target_usize(id, this))
970 }
971 Err(e) => {
972 this.set_last_error(e)?;
973 interp_ok(Scalar::null_ptr(this))
974 }
975 }
976 }
977
978 fn linux_solarish_readdir64(
979 &mut self,
980 dirent_type: &str,
981 dirp_op: &OpTy<'tcx>,
982 ) -> InterpResult<'tcx, Scalar> {
983 let this = self.eval_context_mut();
984
985 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
986 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
987 }
988
989 let dirp = this.read_target_usize(dirp_op)?;
990
991 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
993 this.reject_in_isolation("`readdir`", reject_with)?;
994 this.set_last_error(LibcError("EBADF"))?;
995 return interp_ok(Scalar::null_ptr(this));
996 }
997
998 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
999 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1000 })?;
1001
1002 let entry = match open_dir.read_dir.next() {
1003 Some(Ok(dir_entry)) => {
1004 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
1029 let name_len = u64::try_from(name_bytes.len()).unwrap();
1030
1031 let dirent_layout = this.libc_ty_layout(dirent_type);
1032 let fields = &dirent_layout.fields;
1033 let last_field = fields.count().strict_sub(1);
1034 let d_name_offset = fields.offset(last_field).bytes();
1035 let size = d_name_offset.strict_add(name_len);
1036
1037 let entry = this.allocate_ptr(
1038 Size::from_bytes(size),
1039 dirent_layout.align.abi,
1040 MiriMemoryKind::Runtime.into(),
1041 AllocInit::Uninit,
1042 )?;
1043 let entry: Pointer = entry.into();
1044
1045 #[cfg(unix)]
1048 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1049 #[cfg(not(unix))]
1050 let ino = 0u64;
1051
1052 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1053 this.write_int_fields_named(
1054 &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
1055 &this.ptr_to_mplace(entry, dirent_layout),
1056 )?;
1057
1058 if let Some(d_type) = this
1059 .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
1060 {
1061 this.write_int(file_type, &d_type)?;
1062 }
1063
1064 let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
1065 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1066
1067 Some(entry)
1068 }
1069 None => {
1070 None
1072 }
1073 Some(Err(e)) => {
1074 this.set_last_error(e)?;
1075 None
1076 }
1077 };
1078
1079 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1080 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1081 if let Some(old_entry) = old_entry {
1082 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1083 }
1084
1085 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1086 }
1087
1088 fn macos_fbsd_readdir_r(
1089 &mut self,
1090 dirp_op: &OpTy<'tcx>,
1091 entry_op: &OpTy<'tcx>,
1092 result_op: &OpTy<'tcx>,
1093 ) -> InterpResult<'tcx, Scalar> {
1094 let this = self.eval_context_mut();
1095
1096 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1097 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1098 }
1099
1100 let dirp = this.read_target_usize(dirp_op)?;
1101 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1102
1103 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1105 this.reject_in_isolation("`readdir_r`", reject_with)?;
1106 return interp_ok(this.eval_libc("EBADF"));
1108 }
1109
1110 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1111 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1112 })?;
1113 interp_ok(match open_dir.read_dir.next() {
1114 Some(Ok(dir_entry)) => {
1115 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1130 let name_place = this.project_field_named(&entry_place, "d_name")?;
1131
1132 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1134 &file_name,
1135 name_place.ptr(),
1136 name_place.layout.size.bytes(),
1137 )?;
1138 let file_name_len = file_name_buf_len.strict_sub(1);
1139 if !name_fits {
1140 throw_unsup_format!(
1141 "a directory entry had a name too large to fit in libc::dirent"
1142 );
1143 }
1144
1145 #[cfg(unix)]
1148 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1149 #[cfg(not(unix))]
1150 let ino = 0u64;
1151
1152 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1153
1154 this.write_int_fields_named(
1156 &[
1157 ("d_reclen", 0),
1158 ("d_namlen", file_name_len.into()),
1159 ("d_type", file_type.into()),
1160 ],
1161 &entry_place,
1162 )?;
1163 match &*this.tcx.sess.target.os {
1165 "macos" => {
1166 #[rustfmt::skip]
1167 this.write_int_fields_named(
1168 &[
1169 ("d_ino", ino.into()),
1170 ("d_seekoff", 0),
1171 ],
1172 &entry_place,
1173 )?;
1174 }
1175 "freebsd" => {
1176 #[rustfmt::skip]
1177 this.write_int_fields_named(
1178 &[
1179 ("d_fileno", ino.into()),
1180 ("d_off", 0),
1181 ],
1182 &entry_place,
1183 )?;
1184 }
1185 _ => unreachable!(),
1186 }
1187 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1188
1189 Scalar::from_i32(0)
1190 }
1191 None => {
1192 this.write_null(&result_place)?;
1194 Scalar::from_i32(0)
1195 }
1196 Some(Err(e)) => {
1197 this.io_error_to_errnum(e)?
1199 }
1200 })
1201 }
1202
1203 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1204 let this = self.eval_context_mut();
1205
1206 let dirp = this.read_target_usize(dirp_op)?;
1207
1208 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1210 this.reject_in_isolation("`closedir`", reject_with)?;
1211 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1212 }
1213
1214 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1215 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1216 };
1217 if let Some(entry) = open_dir.entry.take() {
1218 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1219 }
1220 drop(open_dir);
1222
1223 interp_ok(Scalar::from_i32(0))
1224 }
1225
1226 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1227 let this = self.eval_context_mut();
1228
1229 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1231 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1232 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1234 }
1235
1236 let Some(fd) = this.machine.fds.get(fd_num) else {
1237 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1238 };
1239
1240 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1242 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1243 })?;
1244
1245 if file.writable {
1246 if let Ok(length) = length.try_into() {
1247 let result = file.file.set_len(length);
1248 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1249 interp_ok(Scalar::from_i32(result))
1250 } else {
1251 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1252 }
1253 } else {
1254 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1256 }
1257 }
1258
1259 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1260 let this = self.eval_context_mut();
1266
1267 let fd = this.read_scalar(fd_op)?.to_i32()?;
1268
1269 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1271 this.reject_in_isolation("`fsync`", reject_with)?;
1272 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1274 }
1275
1276 self.ffullsync_fd(fd)
1277 }
1278
1279 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1280 let this = self.eval_context_mut();
1281 let Some(fd) = this.machine.fds.get(fd_num) else {
1282 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1283 };
1284 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1286 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1287 })?;
1288 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1289 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1290 }
1291
1292 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1293 let this = self.eval_context_mut();
1294
1295 let fd = this.read_scalar(fd_op)?.to_i32()?;
1296
1297 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1299 this.reject_in_isolation("`fdatasync`", reject_with)?;
1300 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1302 }
1303
1304 let Some(fd) = this.machine.fds.get(fd) 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!("`fdatasync` is only supported on file-backed file descriptors")
1310 })?;
1311 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1312 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1313 }
1314
1315 fn sync_file_range(
1316 &mut self,
1317 fd_op: &OpTy<'tcx>,
1318 offset_op: &OpTy<'tcx>,
1319 nbytes_op: &OpTy<'tcx>,
1320 flags_op: &OpTy<'tcx>,
1321 ) -> InterpResult<'tcx, Scalar> {
1322 let this = self.eval_context_mut();
1323
1324 let fd = this.read_scalar(fd_op)?.to_i32()?;
1325 let offset = this.read_scalar(offset_op)?.to_i64()?;
1326 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1327 let flags = this.read_scalar(flags_op)?.to_i32()?;
1328
1329 if offset < 0 || nbytes < 0 {
1330 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1331 }
1332 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1333 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1334 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1335 if flags & allowed_flags != flags {
1336 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1337 }
1338
1339 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1341 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1342 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1344 }
1345
1346 let Some(fd) = this.machine.fds.get(fd) else {
1347 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1348 };
1349 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1351 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1352 })?;
1353 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1354 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1355 }
1356
1357 fn readlink(
1358 &mut self,
1359 pathname_op: &OpTy<'tcx>,
1360 buf_op: &OpTy<'tcx>,
1361 bufsize_op: &OpTy<'tcx>,
1362 ) -> InterpResult<'tcx, i64> {
1363 let this = self.eval_context_mut();
1364
1365 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1366 let buf = this.read_pointer(buf_op)?;
1367 let bufsize = this.read_target_usize(bufsize_op)?;
1368
1369 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1371 this.reject_in_isolation("`readlink`", reject_with)?;
1372 this.set_last_error(LibcError("EACCES"))?;
1373 return interp_ok(-1);
1374 }
1375
1376 let result = std::fs::read_link(pathname);
1377 match result {
1378 Ok(resolved) => {
1379 let resolved = this.convert_path(
1383 Cow::Borrowed(resolved.as_ref()),
1384 crate::shims::os_str::PathConversion::HostToTarget,
1385 );
1386 let mut path_bytes = resolved.as_encoded_bytes();
1387 let bufsize: usize = bufsize.try_into().unwrap();
1388 if path_bytes.len() > bufsize {
1389 path_bytes = &path_bytes[..bufsize]
1390 }
1391 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1392 interp_ok(path_bytes.len().try_into().unwrap())
1393 }
1394 Err(e) => {
1395 this.set_last_error(e)?;
1396 interp_ok(-1)
1397 }
1398 }
1399 }
1400
1401 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1402 let this = self.eval_context_mut();
1403 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1406 let error = if let Some(fd) = this.machine.fds.get(fd) {
1407 if fd.is_tty(this.machine.communicate()) {
1408 return interp_ok(Scalar::from_i32(1));
1409 } else {
1410 LibcError("ENOTTY")
1411 }
1412 } else {
1413 LibcError("EBADF")
1415 };
1416 this.set_last_error(error)?;
1417 interp_ok(Scalar::from_i32(0))
1418 }
1419
1420 fn realpath(
1421 &mut self,
1422 path_op: &OpTy<'tcx>,
1423 processed_path_op: &OpTy<'tcx>,
1424 ) -> InterpResult<'tcx, Scalar> {
1425 let this = self.eval_context_mut();
1426 this.assert_target_os_is_unix("realpath");
1427
1428 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1429 let processed_ptr = this.read_pointer(processed_path_op)?;
1430
1431 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1433 this.reject_in_isolation("`realpath`", reject_with)?;
1434 this.set_last_error(LibcError("EACCES"))?;
1435 return interp_ok(Scalar::from_target_usize(0, this));
1436 }
1437
1438 let result = std::fs::canonicalize(pathname);
1439 match result {
1440 Ok(resolved) => {
1441 let path_max = this
1442 .eval_libc_i32("PATH_MAX")
1443 .try_into()
1444 .expect("PATH_MAX does not fit in u64");
1445 let dest = if this.ptr_is_null(processed_ptr)? {
1446 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1456 } else {
1457 let (wrote_path, _) =
1458 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1459
1460 if !wrote_path {
1461 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1465 return interp_ok(Scalar::from_target_usize(0, this));
1466 }
1467 processed_ptr
1468 };
1469
1470 interp_ok(Scalar::from_maybe_pointer(dest, this))
1471 }
1472 Err(e) => {
1473 this.set_last_error(e)?;
1474 interp_ok(Scalar::from_target_usize(0, this))
1475 }
1476 }
1477 }
1478 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1479 use rand::seq::IndexedRandom;
1480
1481 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1483
1484 let this = self.eval_context_mut();
1485 this.assert_target_os_is_unix("mkstemp");
1486
1487 let max_attempts = this.eval_libc_u32("TMP_MAX");
1497
1498 let template_ptr = this.read_pointer(template_op)?;
1501 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1502 let template_bytes = template.as_mut_slice();
1503
1504 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1506 this.reject_in_isolation("`mkstemp`", reject_with)?;
1507 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1508 }
1509
1510 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1512
1513 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1518 let end_pos = template_bytes.len();
1519 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1520
1521 if last_six_char_bytes != suffix_bytes {
1523 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1524 }
1525
1526 const SUBSTITUTIONS: &[char; 62] = &[
1530 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1531 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1532 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1533 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1534 ];
1535
1536 let mut fopts = OpenOptions::new();
1539 fopts.read(true).write(true).create_new(true);
1540
1541 #[cfg(unix)]
1542 {
1543 use std::os::unix::fs::OpenOptionsExt;
1544 fopts.mode(0o600);
1546 fopts.custom_flags(libc::O_EXCL);
1547 }
1548 #[cfg(windows)]
1549 {
1550 use std::os::windows::fs::OpenOptionsExt;
1551 fopts.share_mode(0);
1553 }
1554
1555 for _ in 0..max_attempts {
1557 let rng = this.machine.rng.get_mut();
1558
1559 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1561
1562 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1564
1565 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1567
1568 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1570
1571 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1572
1573 let file = fopts.open(possibly_unique);
1574
1575 match file {
1576 Ok(f) => {
1577 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1578 return interp_ok(Scalar::from_i32(fd));
1579 }
1580 Err(e) =>
1581 match e.kind() {
1582 ErrorKind::AlreadyExists => continue,
1584 _ => {
1586 return this.set_last_error_and_return_i32(e);
1589 }
1590 },
1591 }
1592 }
1593
1594 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1596 }
1597}
1598
1599fn extract_sec_and_nsec<'tcx>(
1603 time: std::io::Result<SystemTime>,
1604) -> InterpResult<'tcx, Option<(u64, u32)>> {
1605 match time.ok() {
1606 Some(time) => {
1607 let duration = system_time_to_duration(&time)?;
1608 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1609 }
1610 None => interp_ok(None),
1611 }
1612}
1613
1614struct FileMetadata {
1617 mode: Scalar,
1618 size: u64,
1619 created: Option<(u64, u32)>,
1620 accessed: Option<(u64, u32)>,
1621 modified: Option<(u64, u32)>,
1622}
1623
1624impl FileMetadata {
1625 fn from_path<'tcx>(
1626 ecx: &mut MiriInterpCx<'tcx>,
1627 path: &Path,
1628 follow_symlink: bool,
1629 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1630 let metadata =
1631 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1632
1633 FileMetadata::from_meta(ecx, metadata)
1634 }
1635
1636 fn from_fd_num<'tcx>(
1637 ecx: &mut MiriInterpCx<'tcx>,
1638 fd_num: i32,
1639 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1640 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1641 return interp_ok(Err(LibcError("EBADF")));
1642 };
1643
1644 let metadata = fd.metadata()?;
1645 drop(fd);
1646 FileMetadata::from_meta(ecx, metadata)
1647 }
1648
1649 fn from_meta<'tcx>(
1650 ecx: &mut MiriInterpCx<'tcx>,
1651 metadata: Result<std::fs::Metadata, std::io::Error>,
1652 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1653 let metadata = match metadata {
1654 Ok(metadata) => metadata,
1655 Err(e) => {
1656 return interp_ok(Err(e.into()));
1657 }
1658 };
1659
1660 let file_type = metadata.file_type();
1661
1662 let mode_name = if file_type.is_file() {
1663 "S_IFREG"
1664 } else if file_type.is_dir() {
1665 "S_IFDIR"
1666 } else {
1667 "S_IFLNK"
1668 };
1669
1670 let mode = ecx.eval_libc(mode_name);
1671
1672 let size = metadata.len();
1673
1674 let created = extract_sec_and_nsec(metadata.created())?;
1675 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1676 let modified = extract_sec_and_nsec(metadata.modified())?;
1677
1678 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified }))
1680 }
1681}