1use std::borrow::Cow;
4use std::fs::{
5 DirBuilder, File, FileType, Metadata, OpenOptions, ReadDir, read_dir, remove_dir, remove_file,
6 rename,
7};
8use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
9use std::path::{Path, PathBuf};
10use std::time::SystemTime;
11
12use rustc_abi::Size;
13use rustc_data_structures::fx::FxHashMap;
14
15use self::shims::time::system_time_to_duration;
16use crate::helpers::check_min_vararg_count;
17use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
18use crate::shims::os_str::bytes_to_os_str;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22#[derive(Debug)]
23struct FileHandle {
24 file: File,
25 writable: bool,
26}
27
28impl FileDescription for FileHandle {
29 fn name(&self) -> &'static str {
30 "file"
31 }
32
33 fn read<'tcx>(
34 self: FileDescriptionRef<Self>,
35 communicate_allowed: bool,
36 ptr: Pointer,
37 len: usize,
38 ecx: &mut MiriInterpCx<'tcx>,
39 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
40 ) -> InterpResult<'tcx> {
41 assert!(communicate_allowed, "isolation should have prevented even opening a file");
42
43 let result = ecx.read_from_host(&self.file, len, ptr)?;
44 finish.call(ecx, result)
45 }
46
47 fn write<'tcx>(
48 self: FileDescriptionRef<Self>,
49 communicate_allowed: bool,
50 ptr: Pointer,
51 len: usize,
52 ecx: &mut MiriInterpCx<'tcx>,
53 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
54 ) -> InterpResult<'tcx> {
55 assert!(communicate_allowed, "isolation should have prevented even opening a file");
56
57 let result = ecx.write_to_host(&self.file, len, ptr)?;
58 finish.call(ecx, result)
59 }
60
61 fn seek<'tcx>(
62 &self,
63 communicate_allowed: bool,
64 offset: SeekFrom,
65 ) -> InterpResult<'tcx, io::Result<u64>> {
66 assert!(communicate_allowed, "isolation should have prevented even opening a file");
67 interp_ok((&mut &self.file).seek(offset))
68 }
69
70 fn close<'tcx>(
71 self,
72 communicate_allowed: bool,
73 _ecx: &mut MiriInterpCx<'tcx>,
74 ) -> InterpResult<'tcx, io::Result<()>> {
75 assert!(communicate_allowed, "isolation should have prevented even opening a file");
76 if self.writable {
78 let result = self.file.sync_all();
81 drop(self.file);
83 interp_ok(result)
84 } else {
85 drop(self.file);
92 interp_ok(Ok(()))
93 }
94 }
95
96 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
97 interp_ok(self.file.metadata())
98 }
99
100 fn is_tty(&self, communicate_allowed: bool) -> bool {
101 communicate_allowed && self.file.is_terminal()
102 }
103
104 fn as_unix(&self) -> &dyn UnixFileDescription {
105 self
106 }
107}
108
109impl UnixFileDescription for FileHandle {
110 fn pread<'tcx>(
111 &self,
112 communicate_allowed: bool,
113 offset: u64,
114 ptr: Pointer,
115 len: usize,
116 ecx: &mut MiriInterpCx<'tcx>,
117 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
118 ) -> InterpResult<'tcx> {
119 assert!(communicate_allowed, "isolation should have prevented even opening a file");
120 let mut bytes = vec![0; len];
121 let file = &mut &self.file;
125 let mut f = || {
126 let cursor_pos = file.stream_position()?;
127 file.seek(SeekFrom::Start(offset))?;
128 let res = file.read(&mut bytes);
129 file.seek(SeekFrom::Start(cursor_pos))
131 .expect("failed to restore file position, this shouldn't be possible");
132 res
133 };
134 let result = match f() {
135 Ok(read_size) => {
136 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
140 Ok(read_size)
141 }
142 Err(e) => Err(IoError::HostError(e)),
143 };
144 finish.call(ecx, result)
145 }
146
147 fn pwrite<'tcx>(
148 &self,
149 communicate_allowed: bool,
150 ptr: Pointer,
151 len: usize,
152 offset: u64,
153 ecx: &mut MiriInterpCx<'tcx>,
154 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
155 ) -> InterpResult<'tcx> {
156 assert!(communicate_allowed, "isolation should have prevented even opening a file");
157 let file = &mut &self.file;
161 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
162 let mut f = || {
163 let cursor_pos = file.stream_position()?;
164 file.seek(SeekFrom::Start(offset))?;
165 let res = file.write(bytes);
166 file.seek(SeekFrom::Start(cursor_pos))
168 .expect("failed to restore file position, this shouldn't be possible");
169 res
170 };
171 let result = f();
172 finish.call(ecx, result.map_err(IoError::HostError))
173 }
174
175 fn flock<'tcx>(
176 &self,
177 communicate_allowed: bool,
178 op: FlockOp,
179 ) -> InterpResult<'tcx, io::Result<()>> {
180 assert!(communicate_allowed, "isolation should have prevented even opening a file");
181 #[cfg(target_family = "unix")]
182 {
183 use std::os::fd::AsRawFd;
184
185 use FlockOp::*;
186 let (host_op, lock_nb) = match op {
188 SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking),
189 ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking),
190 Unlock => (libc::LOCK_UN, false),
191 };
192
193 let fd = self.file.as_raw_fd();
194 let ret = unsafe { libc::flock(fd, host_op) };
195 let res = match ret {
196 0 => Ok(()),
197 -1 => {
198 let err = io::Error::last_os_error();
199 if !lock_nb && err.kind() == io::ErrorKind::WouldBlock {
200 throw_unsup_format!("blocking `flock` is not currently supported");
201 }
202 Err(err)
203 }
204 ret => panic!("Unexpected return value from flock: {ret}"),
205 };
206 interp_ok(res)
207 }
208
209 #[cfg(target_family = "windows")]
210 {
211 use std::os::windows::io::AsRawHandle;
212
213 use windows_sys::Win32::Foundation::{
214 ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE,
215 };
216 use windows_sys::Win32::Storage::FileSystem::{
217 LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
218 };
219
220 let fh = self.file.as_raw_handle() as HANDLE;
221
222 use FlockOp::*;
223 let (ret, lock_nb) = match op {
224 SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
225 let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
227 if matches!(op, ExclusiveLock { .. }) {
228 flags |= LOCKFILE_EXCLUSIVE_LOCK;
229 }
230 let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
231 (ret, nonblocking)
232 }
233 Unlock => {
234 let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
235 (ret, false)
236 }
237 };
238
239 let res = match ret {
240 TRUE => Ok(()),
241 FALSE => {
242 let mut err = io::Error::last_os_error();
243 let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
246 if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
247 if lock_nb {
248 let desc = format!("LockFileEx wouldblock error: {err}");
251 err = io::Error::new(io::ErrorKind::WouldBlock, desc);
252 } else {
253 throw_unsup_format!("blocking `flock` is not currently supported");
254 }
255 }
256 Err(err)
257 }
258 _ => panic!("Unexpected return value: {ret}"),
259 };
260 interp_ok(res)
261 }
262
263 #[cfg(not(any(target_family = "unix", target_family = "windows")))]
264 {
265 let _ = op;
266 compile_error!("flock is supported only on UNIX and Windows hosts");
267 }
268 }
269}
270
271impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
272trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
273 fn macos_fbsd_solarish_write_stat_buf(
274 &mut self,
275 metadata: FileMetadata,
276 buf_op: &OpTy<'tcx>,
277 ) -> InterpResult<'tcx, i32> {
278 let this = self.eval_context_mut();
279
280 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
281 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
282 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
283 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
284
285 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
286 this.write_int_fields_named(
287 &[
288 ("st_dev", 0),
289 ("st_mode", mode.try_into().unwrap()),
290 ("st_nlink", 0),
291 ("st_ino", 0),
292 ("st_uid", 0),
293 ("st_gid", 0),
294 ("st_rdev", 0),
295 ("st_atime", access_sec.into()),
296 ("st_mtime", modified_sec.into()),
297 ("st_ctime", 0),
298 ("st_size", metadata.size.into()),
299 ("st_blocks", 0),
300 ("st_blksize", 0),
301 ],
302 &buf,
303 )?;
304
305 if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
306 this.write_int_fields_named(
307 &[
308 ("st_atime_nsec", access_nsec.into()),
309 ("st_mtime_nsec", modified_nsec.into()),
310 ("st_ctime_nsec", 0),
311 ("st_birthtime", created_sec.into()),
312 ("st_birthtime_nsec", created_nsec.into()),
313 ("st_flags", 0),
314 ("st_gen", 0),
315 ],
316 &buf,
317 )?;
318 }
319
320 if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
321 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
322 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
324 }
325
326 interp_ok(0)
327 }
328
329 fn file_type_to_d_type(
330 &mut self,
331 file_type: std::io::Result<FileType>,
332 ) -> InterpResult<'tcx, i32> {
333 #[cfg(unix)]
334 use std::os::unix::fs::FileTypeExt;
335
336 let this = self.eval_context_mut();
337 match file_type {
338 Ok(file_type) => {
339 match () {
340 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
341 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
342 _ if file_type.is_symlink() =>
343 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
344 #[cfg(unix)]
346 _ if file_type.is_block_device() =>
347 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
348 #[cfg(unix)]
349 _ if file_type.is_char_device() =>
350 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
351 #[cfg(unix)]
352 _ if file_type.is_fifo() =>
353 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
354 #[cfg(unix)]
355 _ if file_type.is_socket() =>
356 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
357 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
359 }
360 }
361 Err(_) => {
362 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
364 }
365 }
366 }
367}
368
369#[derive(Debug)]
371struct OpenDir {
372 read_dir: ReadDir,
374 entry: Option<Pointer>,
377}
378
379impl OpenDir {
380 fn new(read_dir: ReadDir) -> Self {
381 Self { read_dir, entry: None }
382 }
383}
384
385#[derive(Debug)]
389pub struct DirTable {
390 streams: FxHashMap<u64, OpenDir>,
400 next_id: u64,
402}
403
404impl DirTable {
405 #[expect(clippy::arithmetic_side_effects)]
406 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
407 let id = self.next_id;
408 self.next_id += 1;
409 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
410 id
411 }
412}
413
414impl Default for DirTable {
415 fn default() -> DirTable {
416 DirTable {
417 streams: FxHashMap::default(),
418 next_id: 1,
420 }
421 }
422}
423
424impl VisitProvenance for DirTable {
425 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
426 let DirTable { streams, next_id: _ } = self;
427
428 for dir in streams.values() {
429 dir.entry.visit_provenance(visit);
430 }
431 }
432}
433
434fn maybe_sync_file(
435 file: &File,
436 writable: bool,
437 operation: fn(&File) -> std::io::Result<()>,
438) -> std::io::Result<i32> {
439 if !writable && cfg!(windows) {
440 Ok(0i32)
444 } else {
445 let result = operation(file);
446 result.map(|_| 0i32)
447 }
448}
449
450impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
451pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
452 fn open(
453 &mut self,
454 path_raw: &OpTy<'tcx>,
455 flag: &OpTy<'tcx>,
456 varargs: &[OpTy<'tcx>],
457 ) -> InterpResult<'tcx, Scalar> {
458 let this = self.eval_context_mut();
459
460 let path_raw = this.read_pointer(path_raw)?;
461 let path = this.read_path_from_c_str(path_raw)?;
462 let flag = this.read_scalar(flag)?.to_i32()?;
463
464 let mut options = OpenOptions::new();
465
466 let o_rdonly = this.eval_libc_i32("O_RDONLY");
467 let o_wronly = this.eval_libc_i32("O_WRONLY");
468 let o_rdwr = this.eval_libc_i32("O_RDWR");
469 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
473 throw_unsup_format!("access mode flags on this target are unsupported");
474 }
475 let mut writable = true;
476
477 let access_mode = flag & 0b11;
479
480 if access_mode == o_rdonly {
481 writable = false;
482 options.read(true);
483 } else if access_mode == o_wronly {
484 options.write(true);
485 } else if access_mode == o_rdwr {
486 options.read(true).write(true);
487 } else {
488 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
489 }
490 let mut mirror = access_mode;
494
495 let o_append = this.eval_libc_i32("O_APPEND");
496 if flag & o_append == o_append {
497 options.append(true);
498 mirror |= o_append;
499 }
500 let o_trunc = this.eval_libc_i32("O_TRUNC");
501 if flag & o_trunc == o_trunc {
502 options.truncate(true);
503 mirror |= o_trunc;
504 }
505 let o_creat = this.eval_libc_i32("O_CREAT");
506 if flag & o_creat == o_creat {
507 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
511 let mode = this.read_scalar(mode)?.to_u32()?;
512
513 #[cfg(unix)]
514 {
515 use std::os::unix::fs::OpenOptionsExt;
517 options.mode(mode);
518 }
519 #[cfg(not(unix))]
520 {
521 if mode != 0o666 {
523 throw_unsup_format!(
524 "non-default mode 0o{:o} is not supported on non-Unix hosts",
525 mode
526 );
527 }
528 }
529
530 mirror |= o_creat;
531
532 let o_excl = this.eval_libc_i32("O_EXCL");
533 if flag & o_excl == o_excl {
534 mirror |= o_excl;
535 options.create_new(true);
536 } else {
537 options.create(true);
538 }
539 }
540 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
541 if flag & o_cloexec == o_cloexec {
542 mirror |= o_cloexec;
545 }
546 if this.tcx.sess.target.os == "linux" {
547 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
548 if flag & o_tmpfile == o_tmpfile {
549 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
551 }
552 }
553
554 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
555 if flag & o_nofollow == o_nofollow {
556 #[cfg(unix)]
557 {
558 use std::os::unix::fs::OpenOptionsExt;
559 options.custom_flags(libc::O_NOFOLLOW);
560 }
561 #[cfg(not(unix))]
565 {
566 if path.is_symlink() {
569 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
570 }
571 }
572 mirror |= o_nofollow;
573 }
574
575 if flag != mirror {
578 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
579 }
580
581 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
583 this.reject_in_isolation("`open`", reject_with)?;
584 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
585 }
586
587 let fd = options
588 .open(path)
589 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
590
591 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
592 }
593
594 fn lseek64(&mut self, fd_num: i32, offset: i128, whence: i32) -> InterpResult<'tcx, Scalar> {
595 let this = self.eval_context_mut();
596
597 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
600 if offset < 0 {
601 return this.set_last_error_and_return_i64(LibcError("EINVAL"));
603 } else {
604 SeekFrom::Start(u64::try_from(offset).unwrap())
605 }
606 } else if whence == this.eval_libc_i32("SEEK_CUR") {
607 SeekFrom::Current(i64::try_from(offset).unwrap())
608 } else if whence == this.eval_libc_i32("SEEK_END") {
609 SeekFrom::End(i64::try_from(offset).unwrap())
610 } else {
611 return this.set_last_error_and_return_i64(LibcError("EINVAL"));
612 };
613
614 let communicate = this.machine.communicate();
615
616 let Some(fd) = this.machine.fds.get(fd_num) else {
617 return this.set_last_error_and_return_i64(LibcError("EBADF"));
618 };
619 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
620 drop(fd);
621
622 let result = this.try_unwrap_io_result(result)?;
623 interp_ok(Scalar::from_i64(result))
624 }
625
626 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
627 let this = self.eval_context_mut();
628
629 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
630
631 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
633 this.reject_in_isolation("`unlink`", reject_with)?;
634 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
635 }
636
637 let result = remove_file(path).map(|_| 0);
638 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
639 }
640
641 fn symlink(
642 &mut self,
643 target_op: &OpTy<'tcx>,
644 linkpath_op: &OpTy<'tcx>,
645 ) -> InterpResult<'tcx, Scalar> {
646 #[cfg(unix)]
647 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
648 std::os::unix::fs::symlink(src, dst)
649 }
650
651 #[cfg(windows)]
652 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
653 use std::os::windows::fs;
654 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
655 }
656
657 let this = self.eval_context_mut();
658 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
659 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
660
661 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
663 this.reject_in_isolation("`symlink`", reject_with)?;
664 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
665 }
666
667 let result = create_link(&target, &linkpath).map(|_| 0);
668 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
669 }
670
671 fn macos_fbsd_solarish_stat(
672 &mut self,
673 path_op: &OpTy<'tcx>,
674 buf_op: &OpTy<'tcx>,
675 ) -> InterpResult<'tcx, Scalar> {
676 let this = self.eval_context_mut();
677
678 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
679 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
680 }
681
682 let path_scalar = this.read_pointer(path_op)?;
683 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
684
685 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
687 this.reject_in_isolation("`stat`", reject_with)?;
688 return this.set_last_error_and_return_i32(LibcError("EACCES"));
689 }
690
691 let metadata = match FileMetadata::from_path(this, &path, true)? {
693 Ok(metadata) => metadata,
694 Err(err) => return this.set_last_error_and_return_i32(err),
695 };
696
697 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
698 }
699
700 fn macos_fbsd_solarish_lstat(
702 &mut self,
703 path_op: &OpTy<'tcx>,
704 buf_op: &OpTy<'tcx>,
705 ) -> InterpResult<'tcx, Scalar> {
706 let this = self.eval_context_mut();
707
708 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
709 panic!(
710 "`macos_fbsd_solaris_lstat` should not be called on {}",
711 this.tcx.sess.target.os
712 );
713 }
714
715 let path_scalar = this.read_pointer(path_op)?;
716 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
717
718 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
720 this.reject_in_isolation("`lstat`", reject_with)?;
721 return this.set_last_error_and_return_i32(LibcError("EACCES"));
722 }
723
724 let metadata = match FileMetadata::from_path(this, &path, false)? {
725 Ok(metadata) => metadata,
726 Err(err) => return this.set_last_error_and_return_i32(err),
727 };
728
729 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
730 }
731
732 fn macos_fbsd_solarish_fstat(
733 &mut self,
734 fd_op: &OpTy<'tcx>,
735 buf_op: &OpTy<'tcx>,
736 ) -> InterpResult<'tcx, Scalar> {
737 let this = self.eval_context_mut();
738
739 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
740 panic!(
741 "`macos_fbsd_solaris_fstat` should not be called on {}",
742 this.tcx.sess.target.os
743 );
744 }
745
746 let fd = this.read_scalar(fd_op)?.to_i32()?;
747
748 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
750 this.reject_in_isolation("`fstat`", reject_with)?;
751 return this.set_last_error_and_return_i32(LibcError("EBADF"));
753 }
754
755 let metadata = match FileMetadata::from_fd_num(this, fd)? {
756 Ok(metadata) => metadata,
757 Err(err) => return this.set_last_error_and_return_i32(err),
758 };
759 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
760 }
761
762 fn linux_statx(
763 &mut self,
764 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
770 let this = self.eval_context_mut();
771
772 this.assert_target_os("linux", "statx");
773
774 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
775 let pathname_ptr = this.read_pointer(pathname_op)?;
776 let flags = this.read_scalar(flags_op)?.to_i32()?;
777 let _mask = this.read_scalar(mask_op)?.to_u32()?;
778 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
779
780 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
782 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
783 }
784
785 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
786
787 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
788 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
790 let empty_path_flag = flags & at_empty_path == at_empty_path;
791 if !(path.is_absolute()
799 || dirfd == this.eval_libc_i32("AT_FDCWD")
800 || (path.as_os_str().is_empty() && empty_path_flag))
801 {
802 throw_unsup_format!(
803 "using statx is only supported with absolute paths, relative paths with the file \
804 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
805 file descriptor"
806 )
807 }
808
809 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
811 this.reject_in_isolation("`statx`", reject_with)?;
812 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
813 LibcError("EACCES")
816 } else {
817 assert!(empty_path_flag);
821 LibcError("EBADF")
822 };
823 return this.set_last_error_and_return_i32(ecode);
824 }
825
826 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
831
832 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
835
836 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
839 FileMetadata::from_fd_num(this, dirfd)?
840 } else {
841 FileMetadata::from_path(this, &path, follow_symlink)?
842 };
843 let metadata = match metadata {
844 Ok(metadata) => metadata,
845 Err(err) => return this.set_last_error_and_return_i32(err),
846 };
847
848 let mode: u16 = metadata
853 .mode
854 .to_u32()?
855 .try_into()
856 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
857
858 let (access_sec, access_nsec) = metadata
861 .accessed
862 .map(|tup| {
863 mask |= this.eval_libc_u32("STATX_ATIME");
864 interp_ok(tup)
865 })
866 .unwrap_or_else(|| interp_ok((0, 0)))?;
867
868 let (created_sec, created_nsec) = metadata
869 .created
870 .map(|tup| {
871 mask |= this.eval_libc_u32("STATX_BTIME");
872 interp_ok(tup)
873 })
874 .unwrap_or_else(|| interp_ok((0, 0)))?;
875
876 let (modified_sec, modified_nsec) = metadata
877 .modified
878 .map(|tup| {
879 mask |= this.eval_libc_u32("STATX_MTIME");
880 interp_ok(tup)
881 })
882 .unwrap_or_else(|| interp_ok((0, 0)))?;
883
884 this.write_int_fields_named(
886 &[
887 ("stx_mask", mask.into()),
888 ("stx_blksize", 0),
889 ("stx_attributes", 0),
890 ("stx_nlink", 0),
891 ("stx_uid", 0),
892 ("stx_gid", 0),
893 ("stx_mode", mode.into()),
894 ("stx_ino", 0),
895 ("stx_size", metadata.size.into()),
896 ("stx_blocks", 0),
897 ("stx_attributes_mask", 0),
898 ("stx_rdev_major", 0),
899 ("stx_rdev_minor", 0),
900 ("stx_dev_major", 0),
901 ("stx_dev_minor", 0),
902 ],
903 &statxbuf,
904 )?;
905 #[rustfmt::skip]
906 this.write_int_fields_named(
907 &[
908 ("tv_sec", access_sec.into()),
909 ("tv_nsec", access_nsec.into()),
910 ],
911 &this.project_field_named(&statxbuf, "stx_atime")?,
912 )?;
913 #[rustfmt::skip]
914 this.write_int_fields_named(
915 &[
916 ("tv_sec", created_sec.into()),
917 ("tv_nsec", created_nsec.into()),
918 ],
919 &this.project_field_named(&statxbuf, "stx_btime")?,
920 )?;
921 #[rustfmt::skip]
922 this.write_int_fields_named(
923 &[
924 ("tv_sec", 0.into()),
925 ("tv_nsec", 0.into()),
926 ],
927 &this.project_field_named(&statxbuf, "stx_ctime")?,
928 )?;
929 #[rustfmt::skip]
930 this.write_int_fields_named(
931 &[
932 ("tv_sec", modified_sec.into()),
933 ("tv_nsec", modified_nsec.into()),
934 ],
935 &this.project_field_named(&statxbuf, "stx_mtime")?,
936 )?;
937
938 interp_ok(Scalar::from_i32(0))
939 }
940
941 fn rename(
942 &mut self,
943 oldpath_op: &OpTy<'tcx>,
944 newpath_op: &OpTy<'tcx>,
945 ) -> InterpResult<'tcx, Scalar> {
946 let this = self.eval_context_mut();
947
948 let oldpath_ptr = this.read_pointer(oldpath_op)?;
949 let newpath_ptr = this.read_pointer(newpath_op)?;
950
951 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
952 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
953 }
954
955 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
956 let newpath = this.read_path_from_c_str(newpath_ptr)?;
957
958 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
960 this.reject_in_isolation("`rename`", reject_with)?;
961 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
962 }
963
964 let result = rename(oldpath, newpath).map(|_| 0);
965
966 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
967 }
968
969 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
970 let this = self.eval_context_mut();
971
972 #[cfg_attr(not(unix), allow(unused_variables))]
973 let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
974 u32::from(this.read_scalar(mode_op)?.to_u16()?)
975 } else {
976 this.read_scalar(mode_op)?.to_u32()?
977 };
978
979 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
980
981 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
983 this.reject_in_isolation("`mkdir`", reject_with)?;
984 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
985 }
986
987 #[cfg_attr(not(unix), allow(unused_mut))]
988 let mut builder = DirBuilder::new();
989
990 #[cfg(unix)]
993 {
994 use std::os::unix::fs::DirBuilderExt;
995 builder.mode(mode);
996 }
997
998 let result = builder.create(path).map(|_| 0i32);
999
1000 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1001 }
1002
1003 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1004 let this = self.eval_context_mut();
1005
1006 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1007
1008 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1010 this.reject_in_isolation("`rmdir`", reject_with)?;
1011 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
1012 }
1013
1014 let result = remove_dir(path).map(|_| 0i32);
1015
1016 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1017 }
1018
1019 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1020 let this = self.eval_context_mut();
1021
1022 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1023
1024 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1026 this.reject_in_isolation("`opendir`", reject_with)?;
1027 this.set_last_error(LibcError("EACCES"))?;
1028 return interp_ok(Scalar::null_ptr(this));
1029 }
1030
1031 let result = read_dir(name);
1032
1033 match result {
1034 Ok(dir_iter) => {
1035 let id = this.machine.dirs.insert_new(dir_iter);
1036
1037 interp_ok(Scalar::from_target_usize(id, this))
1041 }
1042 Err(e) => {
1043 this.set_last_error(e)?;
1044 interp_ok(Scalar::null_ptr(this))
1045 }
1046 }
1047 }
1048
1049 fn linux_solarish_readdir64(
1050 &mut self,
1051 dirent_type: &str,
1052 dirp_op: &OpTy<'tcx>,
1053 ) -> InterpResult<'tcx, Scalar> {
1054 let this = self.eval_context_mut();
1055
1056 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
1057 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
1058 }
1059
1060 let dirp = this.read_target_usize(dirp_op)?;
1061
1062 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1064 this.reject_in_isolation("`readdir`", reject_with)?;
1065 this.set_last_error(LibcError("EBADF"))?;
1066 return interp_ok(Scalar::null_ptr(this));
1067 }
1068
1069 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1070 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1071 })?;
1072
1073 let entry = match open_dir.read_dir.next() {
1074 Some(Ok(dir_entry)) => {
1075 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
1100 let name_len = u64::try_from(name_bytes.len()).unwrap();
1101
1102 let dirent_layout = this.libc_ty_layout(dirent_type);
1103 let fields = &dirent_layout.fields;
1104 let last_field = fields.count().strict_sub(1);
1105 let d_name_offset = fields.offset(last_field).bytes();
1106 let size = d_name_offset.strict_add(name_len);
1107
1108 let entry = this.allocate_ptr(
1109 Size::from_bytes(size),
1110 dirent_layout.align.abi,
1111 MiriMemoryKind::Runtime.into(),
1112 AllocInit::Uninit,
1113 )?;
1114 let entry: Pointer = entry.into();
1115
1116 #[cfg(unix)]
1119 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1120 #[cfg(not(unix))]
1121 let ino = 0u64;
1122
1123 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1124 this.write_int_fields_named(
1125 &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
1126 &this.ptr_to_mplace(entry, dirent_layout),
1127 )?;
1128
1129 if let Some(d_type) = this
1130 .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
1131 {
1132 this.write_int(file_type, &d_type)?;
1133 }
1134
1135 let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
1136 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1137
1138 Some(entry)
1139 }
1140 None => {
1141 None
1143 }
1144 Some(Err(e)) => {
1145 this.set_last_error(e)?;
1146 None
1147 }
1148 };
1149
1150 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1151 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1152 if let Some(old_entry) = old_entry {
1153 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1154 }
1155
1156 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1157 }
1158
1159 fn macos_fbsd_readdir_r(
1160 &mut self,
1161 dirp_op: &OpTy<'tcx>,
1162 entry_op: &OpTy<'tcx>,
1163 result_op: &OpTy<'tcx>,
1164 ) -> InterpResult<'tcx, Scalar> {
1165 let this = self.eval_context_mut();
1166
1167 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1168 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1169 }
1170
1171 let dirp = this.read_target_usize(dirp_op)?;
1172 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1173
1174 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1176 this.reject_in_isolation("`readdir_r`", reject_with)?;
1177 return interp_ok(this.eval_libc("EBADF"));
1179 }
1180
1181 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1182 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1183 })?;
1184 interp_ok(match open_dir.read_dir.next() {
1185 Some(Ok(dir_entry)) => {
1186 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1201 let name_place = this.project_field_named(&entry_place, "d_name")?;
1202
1203 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1205 &file_name,
1206 name_place.ptr(),
1207 name_place.layout.size.bytes(),
1208 )?;
1209 let file_name_len = file_name_buf_len.strict_sub(1);
1210 if !name_fits {
1211 throw_unsup_format!(
1212 "a directory entry had a name too large to fit in libc::dirent"
1213 );
1214 }
1215
1216 #[cfg(unix)]
1219 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1220 #[cfg(not(unix))]
1221 let ino = 0u64;
1222
1223 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1224
1225 this.write_int_fields_named(
1227 &[
1228 ("d_reclen", 0),
1229 ("d_namlen", file_name_len.into()),
1230 ("d_type", file_type.into()),
1231 ],
1232 &entry_place,
1233 )?;
1234 match &*this.tcx.sess.target.os {
1236 "macos" => {
1237 #[rustfmt::skip]
1238 this.write_int_fields_named(
1239 &[
1240 ("d_ino", ino.into()),
1241 ("d_seekoff", 0),
1242 ],
1243 &entry_place,
1244 )?;
1245 }
1246 "freebsd" => {
1247 #[rustfmt::skip]
1248 this.write_int_fields_named(
1249 &[
1250 ("d_fileno", ino.into()),
1251 ("d_off", 0),
1252 ],
1253 &entry_place,
1254 )?;
1255 }
1256 _ => unreachable!(),
1257 }
1258 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1259
1260 Scalar::from_i32(0)
1261 }
1262 None => {
1263 this.write_null(&result_place)?;
1265 Scalar::from_i32(0)
1266 }
1267 Some(Err(e)) => {
1268 this.io_error_to_errnum(e)?
1270 }
1271 })
1272 }
1273
1274 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1275 let this = self.eval_context_mut();
1276
1277 let dirp = this.read_target_usize(dirp_op)?;
1278
1279 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1281 this.reject_in_isolation("`closedir`", reject_with)?;
1282 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1283 }
1284
1285 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1286 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1287 };
1288 if let Some(entry) = open_dir.entry.take() {
1289 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1290 }
1291 drop(open_dir);
1293
1294 interp_ok(Scalar::from_i32(0))
1295 }
1296
1297 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1298 let this = self.eval_context_mut();
1299
1300 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1302 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1303 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1305 }
1306
1307 let Some(fd) = this.machine.fds.get(fd_num) else {
1308 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1309 };
1310
1311 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1313 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1314 })?;
1315
1316 if file.writable {
1317 if let Ok(length) = length.try_into() {
1318 let result = file.file.set_len(length);
1319 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1320 interp_ok(Scalar::from_i32(result))
1321 } else {
1322 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1323 }
1324 } else {
1325 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1327 }
1328 }
1329
1330 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1331 let this = self.eval_context_mut();
1337
1338 let fd = this.read_scalar(fd_op)?.to_i32()?;
1339
1340 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1342 this.reject_in_isolation("`fsync`", reject_with)?;
1343 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1345 }
1346
1347 self.ffullsync_fd(fd)
1348 }
1349
1350 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1351 let this = self.eval_context_mut();
1352 let Some(fd) = this.machine.fds.get(fd_num) else {
1353 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1354 };
1355 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1357 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1358 })?;
1359 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1360 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1361 }
1362
1363 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1364 let this = self.eval_context_mut();
1365
1366 let fd = this.read_scalar(fd_op)?.to_i32()?;
1367
1368 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1370 this.reject_in_isolation("`fdatasync`", reject_with)?;
1371 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1373 }
1374
1375 let Some(fd) = this.machine.fds.get(fd) else {
1376 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1377 };
1378 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1380 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1381 })?;
1382 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1383 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1384 }
1385
1386 fn sync_file_range(
1387 &mut self,
1388 fd_op: &OpTy<'tcx>,
1389 offset_op: &OpTy<'tcx>,
1390 nbytes_op: &OpTy<'tcx>,
1391 flags_op: &OpTy<'tcx>,
1392 ) -> InterpResult<'tcx, Scalar> {
1393 let this = self.eval_context_mut();
1394
1395 let fd = this.read_scalar(fd_op)?.to_i32()?;
1396 let offset = this.read_scalar(offset_op)?.to_i64()?;
1397 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1398 let flags = this.read_scalar(flags_op)?.to_i32()?;
1399
1400 if offset < 0 || nbytes < 0 {
1401 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1402 }
1403 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1404 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1405 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1406 if flags & allowed_flags != flags {
1407 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1408 }
1409
1410 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1412 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1413 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1415 }
1416
1417 let Some(fd) = this.machine.fds.get(fd) else {
1418 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1419 };
1420 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1422 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1423 })?;
1424 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1425 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1426 }
1427
1428 fn readlink(
1429 &mut self,
1430 pathname_op: &OpTy<'tcx>,
1431 buf_op: &OpTy<'tcx>,
1432 bufsize_op: &OpTy<'tcx>,
1433 ) -> InterpResult<'tcx, i64> {
1434 let this = self.eval_context_mut();
1435
1436 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1437 let buf = this.read_pointer(buf_op)?;
1438 let bufsize = this.read_target_usize(bufsize_op)?;
1439
1440 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1442 this.reject_in_isolation("`readlink`", reject_with)?;
1443 this.set_last_error(LibcError("EACCES"))?;
1444 return interp_ok(-1);
1445 }
1446
1447 let result = std::fs::read_link(pathname);
1448 match result {
1449 Ok(resolved) => {
1450 let resolved = this.convert_path(
1454 Cow::Borrowed(resolved.as_ref()),
1455 crate::shims::os_str::PathConversion::HostToTarget,
1456 );
1457 let mut path_bytes = resolved.as_encoded_bytes();
1458 let bufsize: usize = bufsize.try_into().unwrap();
1459 if path_bytes.len() > bufsize {
1460 path_bytes = &path_bytes[..bufsize]
1461 }
1462 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1463 interp_ok(path_bytes.len().try_into().unwrap())
1464 }
1465 Err(e) => {
1466 this.set_last_error(e)?;
1467 interp_ok(-1)
1468 }
1469 }
1470 }
1471
1472 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1473 let this = self.eval_context_mut();
1474 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1477 let error = if let Some(fd) = this.machine.fds.get(fd) {
1478 if fd.is_tty(this.machine.communicate()) {
1479 return interp_ok(Scalar::from_i32(1));
1480 } else {
1481 LibcError("ENOTTY")
1482 }
1483 } else {
1484 LibcError("EBADF")
1486 };
1487 this.set_last_error(error)?;
1488 interp_ok(Scalar::from_i32(0))
1489 }
1490
1491 fn realpath(
1492 &mut self,
1493 path_op: &OpTy<'tcx>,
1494 processed_path_op: &OpTy<'tcx>,
1495 ) -> InterpResult<'tcx, Scalar> {
1496 let this = self.eval_context_mut();
1497 this.assert_target_os_is_unix("realpath");
1498
1499 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1500 let processed_ptr = this.read_pointer(processed_path_op)?;
1501
1502 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1504 this.reject_in_isolation("`realpath`", reject_with)?;
1505 this.set_last_error(LibcError("EACCES"))?;
1506 return interp_ok(Scalar::from_target_usize(0, this));
1507 }
1508
1509 let result = std::fs::canonicalize(pathname);
1510 match result {
1511 Ok(resolved) => {
1512 let path_max = this
1513 .eval_libc_i32("PATH_MAX")
1514 .try_into()
1515 .expect("PATH_MAX does not fit in u64");
1516 let dest = if this.ptr_is_null(processed_ptr)? {
1517 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1527 } else {
1528 let (wrote_path, _) =
1529 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1530
1531 if !wrote_path {
1532 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1536 return interp_ok(Scalar::from_target_usize(0, this));
1537 }
1538 processed_ptr
1539 };
1540
1541 interp_ok(Scalar::from_maybe_pointer(dest, this))
1542 }
1543 Err(e) => {
1544 this.set_last_error(e)?;
1545 interp_ok(Scalar::from_target_usize(0, this))
1546 }
1547 }
1548 }
1549 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1550 use rand::seq::IndexedRandom;
1551
1552 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1554
1555 let this = self.eval_context_mut();
1556 this.assert_target_os_is_unix("mkstemp");
1557
1558 let max_attempts = this.eval_libc_u32("TMP_MAX");
1568
1569 let template_ptr = this.read_pointer(template_op)?;
1572 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1573 let template_bytes = template.as_mut_slice();
1574
1575 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1577 this.reject_in_isolation("`mkstemp`", reject_with)?;
1578 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1579 }
1580
1581 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1583
1584 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1589 let end_pos = template_bytes.len();
1590 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1591
1592 if last_six_char_bytes != suffix_bytes {
1594 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1595 }
1596
1597 const SUBSTITUTIONS: &[char; 62] = &[
1601 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1602 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1603 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1604 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1605 ];
1606
1607 let mut fopts = OpenOptions::new();
1610 fopts.read(true).write(true).create_new(true);
1611
1612 #[cfg(unix)]
1613 {
1614 use std::os::unix::fs::OpenOptionsExt;
1615 fopts.mode(0o600);
1617 fopts.custom_flags(libc::O_EXCL);
1618 }
1619 #[cfg(windows)]
1620 {
1621 use std::os::windows::fs::OpenOptionsExt;
1622 fopts.share_mode(0);
1624 }
1625
1626 for _ in 0..max_attempts {
1628 let rng = this.machine.rng.get_mut();
1629
1630 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1632
1633 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1635
1636 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1638
1639 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1641
1642 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1643
1644 let file = fopts.open(possibly_unique);
1645
1646 match file {
1647 Ok(f) => {
1648 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1649 return interp_ok(Scalar::from_i32(fd));
1650 }
1651 Err(e) =>
1652 match e.kind() {
1653 ErrorKind::AlreadyExists => continue,
1655 _ => {
1657 return this.set_last_error_and_return_i32(e);
1660 }
1661 },
1662 }
1663 }
1664
1665 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1667 }
1668}
1669
1670fn extract_sec_and_nsec<'tcx>(
1674 time: std::io::Result<SystemTime>,
1675) -> InterpResult<'tcx, Option<(u64, u32)>> {
1676 match time.ok() {
1677 Some(time) => {
1678 let duration = system_time_to_duration(&time)?;
1679 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1680 }
1681 None => interp_ok(None),
1682 }
1683}
1684
1685struct FileMetadata {
1688 mode: Scalar,
1689 size: u64,
1690 created: Option<(u64, u32)>,
1691 accessed: Option<(u64, u32)>,
1692 modified: Option<(u64, u32)>,
1693}
1694
1695impl FileMetadata {
1696 fn from_path<'tcx>(
1697 ecx: &mut MiriInterpCx<'tcx>,
1698 path: &Path,
1699 follow_symlink: bool,
1700 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1701 let metadata =
1702 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1703
1704 FileMetadata::from_meta(ecx, metadata)
1705 }
1706
1707 fn from_fd_num<'tcx>(
1708 ecx: &mut MiriInterpCx<'tcx>,
1709 fd_num: i32,
1710 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1711 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1712 return interp_ok(Err(LibcError("EBADF")));
1713 };
1714
1715 let metadata = fd.metadata()?;
1716 drop(fd);
1717 FileMetadata::from_meta(ecx, metadata)
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
1733 let mode_name = if file_type.is_file() {
1734 "S_IFREG"
1735 } else if file_type.is_dir() {
1736 "S_IFDIR"
1737 } else {
1738 "S_IFLNK"
1739 };
1740
1741 let mode = ecx.eval_libc(mode_name);
1742
1743 let size = metadata.len();
1744
1745 let created = extract_sec_and_nsec(metadata.created())?;
1746 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1747 let modified = extract_sec_and_nsec(metadata.modified())?;
1748
1749 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified }))
1751 }
1752}