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_match! {
182 all(target_family = "unix", not(target_os = "solaris")) => {
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 target_family = "windows" => {
209 use std::os::windows::io::AsRawHandle;
210
211 use windows_sys::Win32::Foundation::{
212 ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE,
213 };
214 use windows_sys::Win32::Storage::FileSystem::{
215 LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile,
216 };
217
218 let fh = self.file.as_raw_handle() as HANDLE;
219
220 use FlockOp::*;
221 let (ret, lock_nb) = match op {
222 SharedLock { nonblocking } | ExclusiveLock { nonblocking } => {
223 let mut flags = LOCKFILE_FAIL_IMMEDIATELY;
225 if matches!(op, ExclusiveLock { .. }) {
226 flags |= LOCKFILE_EXCLUSIVE_LOCK;
227 }
228 let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) };
229 (ret, nonblocking)
230 }
231 Unlock => {
232 let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) };
233 (ret, false)
234 }
235 };
236
237 let res = match ret {
238 TRUE => Ok(()),
239 FALSE => {
240 let mut err = io::Error::last_os_error();
241 let code: u32 = err.raw_os_error().unwrap().try_into().unwrap();
244 if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) {
245 if lock_nb {
246 let desc = format!("LockFileEx wouldblock error: {err}");
249 err = io::Error::new(io::ErrorKind::WouldBlock, desc);
250 } else {
251 throw_unsup_format!("blocking `flock` is not currently supported");
252 }
253 }
254 Err(err)
255 }
256 _ => panic!("Unexpected return value: {ret}"),
257 };
258 interp_ok(res)
259 }
260 _ => {
261 let _ = op;
262 throw_unsup_format!(
263 "flock is supported only on UNIX (except Solaris) and Windows hosts"
264 );
265 }
266 }
267 }
268}
269
270impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
271trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
272 fn macos_fbsd_solarish_write_stat_buf(
273 &mut self,
274 metadata: FileMetadata,
275 buf_op: &OpTy<'tcx>,
276 ) -> InterpResult<'tcx, i32> {
277 let this = self.eval_context_mut();
278
279 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
280 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
281 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
282 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
283
284 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
285 this.write_int_fields_named(
286 &[
287 ("st_dev", 0),
288 ("st_mode", mode.try_into().unwrap()),
289 ("st_nlink", 0),
290 ("st_ino", 0),
291 ("st_uid", 0),
292 ("st_gid", 0),
293 ("st_rdev", 0),
294 ("st_atime", access_sec.into()),
295 ("st_mtime", modified_sec.into()),
296 ("st_ctime", 0),
297 ("st_size", metadata.size.into()),
298 ("st_blocks", 0),
299 ("st_blksize", 0),
300 ],
301 &buf,
302 )?;
303
304 if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
305 this.write_int_fields_named(
306 &[
307 ("st_atime_nsec", access_nsec.into()),
308 ("st_mtime_nsec", modified_nsec.into()),
309 ("st_ctime_nsec", 0),
310 ("st_birthtime", created_sec.into()),
311 ("st_birthtime_nsec", created_nsec.into()),
312 ("st_flags", 0),
313 ("st_gen", 0),
314 ],
315 &buf,
316 )?;
317 }
318
319 if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
320 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
321 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
323 }
324
325 interp_ok(0)
326 }
327
328 fn file_type_to_d_type(
329 &mut self,
330 file_type: std::io::Result<FileType>,
331 ) -> InterpResult<'tcx, i32> {
332 #[cfg(unix)]
333 use std::os::unix::fs::FileTypeExt;
334
335 let this = self.eval_context_mut();
336 match file_type {
337 Ok(file_type) => {
338 match () {
339 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
340 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
341 _ if file_type.is_symlink() =>
342 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
343 #[cfg(unix)]
345 _ if file_type.is_block_device() =>
346 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
347 #[cfg(unix)]
348 _ if file_type.is_char_device() =>
349 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
350 #[cfg(unix)]
351 _ if file_type.is_fifo() =>
352 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
353 #[cfg(unix)]
354 _ if file_type.is_socket() =>
355 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
356 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
358 }
359 }
360 Err(_) => {
361 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
363 }
364 }
365 }
366}
367
368#[derive(Debug)]
370struct OpenDir {
371 read_dir: ReadDir,
373 entry: Option<Pointer>,
376}
377
378impl OpenDir {
379 fn new(read_dir: ReadDir) -> Self {
380 Self { read_dir, entry: None }
381 }
382}
383
384#[derive(Debug)]
388pub struct DirTable {
389 streams: FxHashMap<u64, OpenDir>,
399 next_id: u64,
401}
402
403impl DirTable {
404 #[expect(clippy::arithmetic_side_effects)]
405 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
406 let id = self.next_id;
407 self.next_id += 1;
408 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
409 id
410 }
411}
412
413impl Default for DirTable {
414 fn default() -> DirTable {
415 DirTable {
416 streams: FxHashMap::default(),
417 next_id: 1,
419 }
420 }
421}
422
423impl VisitProvenance for DirTable {
424 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
425 let DirTable { streams, next_id: _ } = self;
426
427 for dir in streams.values() {
428 dir.entry.visit_provenance(visit);
429 }
430 }
431}
432
433fn maybe_sync_file(
434 file: &File,
435 writable: bool,
436 operation: fn(&File) -> std::io::Result<()>,
437) -> std::io::Result<i32> {
438 if !writable && cfg!(windows) {
439 Ok(0i32)
443 } else {
444 let result = operation(file);
445 result.map(|_| 0i32)
446 }
447}
448
449impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
450pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
451 fn open(
452 &mut self,
453 path_raw: &OpTy<'tcx>,
454 flag: &OpTy<'tcx>,
455 varargs: &[OpTy<'tcx>],
456 ) -> InterpResult<'tcx, Scalar> {
457 let this = self.eval_context_mut();
458
459 let path_raw = this.read_pointer(path_raw)?;
460 let path = this.read_path_from_c_str(path_raw)?;
461 let flag = this.read_scalar(flag)?.to_i32()?;
462
463 let mut options = OpenOptions::new();
464
465 let o_rdonly = this.eval_libc_i32("O_RDONLY");
466 let o_wronly = this.eval_libc_i32("O_WRONLY");
467 let o_rdwr = this.eval_libc_i32("O_RDWR");
468 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
472 throw_unsup_format!("access mode flags on this target are unsupported");
473 }
474 let mut writable = true;
475
476 let access_mode = flag & 0b11;
478
479 if access_mode == o_rdonly {
480 writable = false;
481 options.read(true);
482 } else if access_mode == o_wronly {
483 options.write(true);
484 } else if access_mode == o_rdwr {
485 options.read(true).write(true);
486 } else {
487 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
488 }
489 let mut mirror = access_mode;
493
494 let o_append = this.eval_libc_i32("O_APPEND");
495 if flag & o_append == o_append {
496 options.append(true);
497 mirror |= o_append;
498 }
499 let o_trunc = this.eval_libc_i32("O_TRUNC");
500 if flag & o_trunc == o_trunc {
501 options.truncate(true);
502 mirror |= o_trunc;
503 }
504 let o_creat = this.eval_libc_i32("O_CREAT");
505 if flag & o_creat == o_creat {
506 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
510 let mode = this.read_scalar(mode)?.to_u32()?;
511
512 #[cfg(unix)]
513 {
514 use std::os::unix::fs::OpenOptionsExt;
516 options.mode(mode);
517 }
518 #[cfg(not(unix))]
519 {
520 if mode != 0o666 {
522 throw_unsup_format!(
523 "non-default mode 0o{:o} is not supported on non-Unix hosts",
524 mode
525 );
526 }
527 }
528
529 mirror |= o_creat;
530
531 let o_excl = this.eval_libc_i32("O_EXCL");
532 if flag & o_excl == o_excl {
533 mirror |= o_excl;
534 options.create_new(true);
535 } else {
536 options.create(true);
537 }
538 }
539 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
540 if flag & o_cloexec == o_cloexec {
541 mirror |= o_cloexec;
544 }
545 if this.tcx.sess.target.os == "linux" {
546 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
547 if flag & o_tmpfile == o_tmpfile {
548 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
550 }
551 }
552
553 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
554 if flag & o_nofollow == o_nofollow {
555 #[cfg(unix)]
556 {
557 use std::os::unix::fs::OpenOptionsExt;
558 options.custom_flags(libc::O_NOFOLLOW);
559 }
560 #[cfg(not(unix))]
564 {
565 if path.is_symlink() {
568 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
569 }
570 }
571 mirror |= o_nofollow;
572 }
573
574 if flag != mirror {
577 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
578 }
579
580 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
582 this.reject_in_isolation("`open`", reject_with)?;
583 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
584 }
585
586 let fd = options
587 .open(path)
588 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
589
590 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
591 }
592
593 fn lseek64(&mut self, fd_num: i32, offset: i128, whence: i32) -> InterpResult<'tcx, Scalar> {
594 let this = self.eval_context_mut();
595
596 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
599 if offset < 0 {
600 return this.set_last_error_and_return_i64(LibcError("EINVAL"));
602 } else {
603 SeekFrom::Start(u64::try_from(offset).unwrap())
604 }
605 } else if whence == this.eval_libc_i32("SEEK_CUR") {
606 SeekFrom::Current(i64::try_from(offset).unwrap())
607 } else if whence == this.eval_libc_i32("SEEK_END") {
608 SeekFrom::End(i64::try_from(offset).unwrap())
609 } else {
610 return this.set_last_error_and_return_i64(LibcError("EINVAL"));
611 };
612
613 let communicate = this.machine.communicate();
614
615 let Some(fd) = this.machine.fds.get(fd_num) else {
616 return this.set_last_error_and_return_i64(LibcError("EBADF"));
617 };
618 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
619 drop(fd);
620
621 let result = this.try_unwrap_io_result(result)?;
622 interp_ok(Scalar::from_i64(result))
623 }
624
625 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
626 let this = self.eval_context_mut();
627
628 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
629
630 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
632 this.reject_in_isolation("`unlink`", reject_with)?;
633 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
634 }
635
636 let result = remove_file(path).map(|_| 0);
637 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
638 }
639
640 fn symlink(
641 &mut self,
642 target_op: &OpTy<'tcx>,
643 linkpath_op: &OpTy<'tcx>,
644 ) -> InterpResult<'tcx, Scalar> {
645 #[cfg(unix)]
646 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
647 std::os::unix::fs::symlink(src, dst)
648 }
649
650 #[cfg(windows)]
651 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
652 use std::os::windows::fs;
653 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
654 }
655
656 let this = self.eval_context_mut();
657 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
658 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
659
660 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
662 this.reject_in_isolation("`symlink`", reject_with)?;
663 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
664 }
665
666 let result = create_link(&target, &linkpath).map(|_| 0);
667 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
668 }
669
670 fn macos_fbsd_solarish_stat(
671 &mut self,
672 path_op: &OpTy<'tcx>,
673 buf_op: &OpTy<'tcx>,
674 ) -> InterpResult<'tcx, Scalar> {
675 let this = self.eval_context_mut();
676
677 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
678 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
679 }
680
681 let path_scalar = this.read_pointer(path_op)?;
682 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
683
684 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
686 this.reject_in_isolation("`stat`", reject_with)?;
687 return this.set_last_error_and_return_i32(LibcError("EACCES"));
688 }
689
690 let metadata = match FileMetadata::from_path(this, &path, true)? {
692 Ok(metadata) => metadata,
693 Err(err) => return this.set_last_error_and_return_i32(err),
694 };
695
696 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
697 }
698
699 fn macos_fbsd_solarish_lstat(
701 &mut self,
702 path_op: &OpTy<'tcx>,
703 buf_op: &OpTy<'tcx>,
704 ) -> InterpResult<'tcx, Scalar> {
705 let this = self.eval_context_mut();
706
707 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
708 panic!(
709 "`macos_fbsd_solaris_lstat` should not be called on {}",
710 this.tcx.sess.target.os
711 );
712 }
713
714 let path_scalar = this.read_pointer(path_op)?;
715 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
716
717 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
719 this.reject_in_isolation("`lstat`", reject_with)?;
720 return this.set_last_error_and_return_i32(LibcError("EACCES"));
721 }
722
723 let metadata = match FileMetadata::from_path(this, &path, false)? {
724 Ok(metadata) => metadata,
725 Err(err) => return this.set_last_error_and_return_i32(err),
726 };
727
728 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
729 }
730
731 fn macos_fbsd_solarish_fstat(
732 &mut self,
733 fd_op: &OpTy<'tcx>,
734 buf_op: &OpTy<'tcx>,
735 ) -> InterpResult<'tcx, Scalar> {
736 let this = self.eval_context_mut();
737
738 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
739 panic!(
740 "`macos_fbsd_solaris_fstat` should not be called on {}",
741 this.tcx.sess.target.os
742 );
743 }
744
745 let fd = this.read_scalar(fd_op)?.to_i32()?;
746
747 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
749 this.reject_in_isolation("`fstat`", reject_with)?;
750 return this.set_last_error_and_return_i32(LibcError("EBADF"));
752 }
753
754 let metadata = match FileMetadata::from_fd_num(this, fd)? {
755 Ok(metadata) => metadata,
756 Err(err) => return this.set_last_error_and_return_i32(err),
757 };
758 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
759 }
760
761 fn linux_statx(
762 &mut self,
763 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
769 let this = self.eval_context_mut();
770
771 this.assert_target_os("linux", "statx");
772
773 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
774 let pathname_ptr = this.read_pointer(pathname_op)?;
775 let flags = this.read_scalar(flags_op)?.to_i32()?;
776 let _mask = this.read_scalar(mask_op)?.to_u32()?;
777 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
778
779 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
781 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
782 }
783
784 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
785
786 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
787 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
789 let empty_path_flag = flags & at_empty_path == at_empty_path;
790 if !(path.is_absolute()
798 || dirfd == this.eval_libc_i32("AT_FDCWD")
799 || (path.as_os_str().is_empty() && empty_path_flag))
800 {
801 throw_unsup_format!(
802 "using statx is only supported with absolute paths, relative paths with the file \
803 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
804 file descriptor"
805 )
806 }
807
808 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
810 this.reject_in_isolation("`statx`", reject_with)?;
811 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
812 LibcError("EACCES")
815 } else {
816 assert!(empty_path_flag);
820 LibcError("EBADF")
821 };
822 return this.set_last_error_and_return_i32(ecode);
823 }
824
825 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
830
831 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
834
835 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
838 FileMetadata::from_fd_num(this, dirfd)?
839 } else {
840 FileMetadata::from_path(this, &path, follow_symlink)?
841 };
842 let metadata = match metadata {
843 Ok(metadata) => metadata,
844 Err(err) => return this.set_last_error_and_return_i32(err),
845 };
846
847 let mode: u16 = metadata
852 .mode
853 .to_u32()?
854 .try_into()
855 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
856
857 let (access_sec, access_nsec) = metadata
860 .accessed
861 .map(|tup| {
862 mask |= this.eval_libc_u32("STATX_ATIME");
863 interp_ok(tup)
864 })
865 .unwrap_or_else(|| interp_ok((0, 0)))?;
866
867 let (created_sec, created_nsec) = metadata
868 .created
869 .map(|tup| {
870 mask |= this.eval_libc_u32("STATX_BTIME");
871 interp_ok(tup)
872 })
873 .unwrap_or_else(|| interp_ok((0, 0)))?;
874
875 let (modified_sec, modified_nsec) = metadata
876 .modified
877 .map(|tup| {
878 mask |= this.eval_libc_u32("STATX_MTIME");
879 interp_ok(tup)
880 })
881 .unwrap_or_else(|| interp_ok((0, 0)))?;
882
883 this.write_int_fields_named(
885 &[
886 ("stx_mask", mask.into()),
887 ("stx_blksize", 0),
888 ("stx_attributes", 0),
889 ("stx_nlink", 0),
890 ("stx_uid", 0),
891 ("stx_gid", 0),
892 ("stx_mode", mode.into()),
893 ("stx_ino", 0),
894 ("stx_size", metadata.size.into()),
895 ("stx_blocks", 0),
896 ("stx_attributes_mask", 0),
897 ("stx_rdev_major", 0),
898 ("stx_rdev_minor", 0),
899 ("stx_dev_major", 0),
900 ("stx_dev_minor", 0),
901 ],
902 &statxbuf,
903 )?;
904 #[rustfmt::skip]
905 this.write_int_fields_named(
906 &[
907 ("tv_sec", access_sec.into()),
908 ("tv_nsec", access_nsec.into()),
909 ],
910 &this.project_field_named(&statxbuf, "stx_atime")?,
911 )?;
912 #[rustfmt::skip]
913 this.write_int_fields_named(
914 &[
915 ("tv_sec", created_sec.into()),
916 ("tv_nsec", created_nsec.into()),
917 ],
918 &this.project_field_named(&statxbuf, "stx_btime")?,
919 )?;
920 #[rustfmt::skip]
921 this.write_int_fields_named(
922 &[
923 ("tv_sec", 0.into()),
924 ("tv_nsec", 0.into()),
925 ],
926 &this.project_field_named(&statxbuf, "stx_ctime")?,
927 )?;
928 #[rustfmt::skip]
929 this.write_int_fields_named(
930 &[
931 ("tv_sec", modified_sec.into()),
932 ("tv_nsec", modified_nsec.into()),
933 ],
934 &this.project_field_named(&statxbuf, "stx_mtime")?,
935 )?;
936
937 interp_ok(Scalar::from_i32(0))
938 }
939
940 fn rename(
941 &mut self,
942 oldpath_op: &OpTy<'tcx>,
943 newpath_op: &OpTy<'tcx>,
944 ) -> InterpResult<'tcx, Scalar> {
945 let this = self.eval_context_mut();
946
947 let oldpath_ptr = this.read_pointer(oldpath_op)?;
948 let newpath_ptr = this.read_pointer(newpath_op)?;
949
950 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
951 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
952 }
953
954 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
955 let newpath = this.read_path_from_c_str(newpath_ptr)?;
956
957 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
959 this.reject_in_isolation("`rename`", reject_with)?;
960 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
961 }
962
963 let result = rename(oldpath, newpath).map(|_| 0);
964
965 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
966 }
967
968 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
969 let this = self.eval_context_mut();
970
971 #[cfg_attr(not(unix), allow(unused_variables))]
972 let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
973 u32::from(this.read_scalar(mode_op)?.to_u16()?)
974 } else {
975 this.read_scalar(mode_op)?.to_u32()?
976 };
977
978 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
979
980 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
982 this.reject_in_isolation("`mkdir`", reject_with)?;
983 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
984 }
985
986 #[cfg_attr(not(unix), allow(unused_mut))]
987 let mut builder = DirBuilder::new();
988
989 #[cfg(unix)]
992 {
993 use std::os::unix::fs::DirBuilderExt;
994 builder.mode(mode);
995 }
996
997 let result = builder.create(path).map(|_| 0i32);
998
999 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1000 }
1001
1002 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1003 let this = self.eval_context_mut();
1004
1005 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1006
1007 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1009 this.reject_in_isolation("`rmdir`", reject_with)?;
1010 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
1011 }
1012
1013 let result = remove_dir(path).map(|_| 0i32);
1014
1015 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1016 }
1017
1018 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1019 let this = self.eval_context_mut();
1020
1021 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1022
1023 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1025 this.reject_in_isolation("`opendir`", reject_with)?;
1026 this.set_last_error(LibcError("EACCES"))?;
1027 return interp_ok(Scalar::null_ptr(this));
1028 }
1029
1030 let result = read_dir(name);
1031
1032 match result {
1033 Ok(dir_iter) => {
1034 let id = this.machine.dirs.insert_new(dir_iter);
1035
1036 interp_ok(Scalar::from_target_usize(id, this))
1040 }
1041 Err(e) => {
1042 this.set_last_error(e)?;
1043 interp_ok(Scalar::null_ptr(this))
1044 }
1045 }
1046 }
1047
1048 fn linux_solarish_readdir64(
1049 &mut self,
1050 dirent_type: &str,
1051 dirp_op: &OpTy<'tcx>,
1052 ) -> InterpResult<'tcx, Scalar> {
1053 let this = self.eval_context_mut();
1054
1055 if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
1056 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
1057 }
1058
1059 let dirp = this.read_target_usize(dirp_op)?;
1060
1061 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1063 this.reject_in_isolation("`readdir`", reject_with)?;
1064 this.set_last_error(LibcError("EBADF"))?;
1065 return interp_ok(Scalar::null_ptr(this));
1066 }
1067
1068 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1069 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1070 })?;
1071
1072 let entry = match open_dir.read_dir.next() {
1073 Some(Ok(dir_entry)) => {
1074 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
1099 let name_len = u64::try_from(name_bytes.len()).unwrap();
1100
1101 let dirent_layout = this.libc_ty_layout(dirent_type);
1102 let fields = &dirent_layout.fields;
1103 let last_field = fields.count().strict_sub(1);
1104 let d_name_offset = fields.offset(last_field).bytes();
1105 let size = d_name_offset.strict_add(name_len);
1106
1107 let entry = this.allocate_ptr(
1108 Size::from_bytes(size),
1109 dirent_layout.align.abi,
1110 MiriMemoryKind::Runtime.into(),
1111 AllocInit::Uninit,
1112 )?;
1113 let entry: Pointer = entry.into();
1114
1115 #[cfg(unix)]
1118 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1119 #[cfg(not(unix))]
1120 let ino = 0u64;
1121
1122 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1123 this.write_int_fields_named(
1124 &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
1125 &this.ptr_to_mplace(entry, dirent_layout),
1126 )?;
1127
1128 if let Some(d_type) = this
1129 .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
1130 {
1131 this.write_int(file_type, &d_type)?;
1132 }
1133
1134 let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
1135 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1136
1137 Some(entry)
1138 }
1139 None => {
1140 None
1142 }
1143 Some(Err(e)) => {
1144 this.set_last_error(e)?;
1145 None
1146 }
1147 };
1148
1149 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1150 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1151 if let Some(old_entry) = old_entry {
1152 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1153 }
1154
1155 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1156 }
1157
1158 fn macos_fbsd_readdir_r(
1159 &mut self,
1160 dirp_op: &OpTy<'tcx>,
1161 entry_op: &OpTy<'tcx>,
1162 result_op: &OpTy<'tcx>,
1163 ) -> InterpResult<'tcx, Scalar> {
1164 let this = self.eval_context_mut();
1165
1166 if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1167 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1168 }
1169
1170 let dirp = this.read_target_usize(dirp_op)?;
1171 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1172
1173 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1175 this.reject_in_isolation("`readdir_r`", reject_with)?;
1176 return interp_ok(this.eval_libc("EBADF"));
1178 }
1179
1180 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1181 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1182 })?;
1183 interp_ok(match open_dir.read_dir.next() {
1184 Some(Ok(dir_entry)) => {
1185 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1200 let name_place = this.project_field_named(&entry_place, "d_name")?;
1201
1202 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1204 &file_name,
1205 name_place.ptr(),
1206 name_place.layout.size.bytes(),
1207 )?;
1208 let file_name_len = file_name_buf_len.strict_sub(1);
1209 if !name_fits {
1210 throw_unsup_format!(
1211 "a directory entry had a name too large to fit in libc::dirent"
1212 );
1213 }
1214
1215 #[cfg(unix)]
1218 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1219 #[cfg(not(unix))]
1220 let ino = 0u64;
1221
1222 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1223
1224 this.write_int_fields_named(
1226 &[
1227 ("d_reclen", 0),
1228 ("d_namlen", file_name_len.into()),
1229 ("d_type", file_type.into()),
1230 ],
1231 &entry_place,
1232 )?;
1233 match &*this.tcx.sess.target.os {
1235 "macos" => {
1236 #[rustfmt::skip]
1237 this.write_int_fields_named(
1238 &[
1239 ("d_ino", ino.into()),
1240 ("d_seekoff", 0),
1241 ],
1242 &entry_place,
1243 )?;
1244 }
1245 "freebsd" => {
1246 #[rustfmt::skip]
1247 this.write_int_fields_named(
1248 &[
1249 ("d_fileno", ino.into()),
1250 ("d_off", 0),
1251 ],
1252 &entry_place,
1253 )?;
1254 }
1255 _ => unreachable!(),
1256 }
1257 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1258
1259 Scalar::from_i32(0)
1260 }
1261 None => {
1262 this.write_null(&result_place)?;
1264 Scalar::from_i32(0)
1265 }
1266 Some(Err(e)) => {
1267 this.io_error_to_errnum(e)?
1269 }
1270 })
1271 }
1272
1273 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1274 let this = self.eval_context_mut();
1275
1276 let dirp = this.read_target_usize(dirp_op)?;
1277
1278 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1280 this.reject_in_isolation("`closedir`", reject_with)?;
1281 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1282 }
1283
1284 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1285 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1286 };
1287 if let Some(entry) = open_dir.entry.take() {
1288 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1289 }
1290 drop(open_dir);
1292
1293 interp_ok(Scalar::from_i32(0))
1294 }
1295
1296 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1297 let this = self.eval_context_mut();
1298
1299 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1301 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1302 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1304 }
1305
1306 let Some(fd) = this.machine.fds.get(fd_num) else {
1307 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1308 };
1309
1310 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1312 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1313 })?;
1314
1315 if file.writable {
1316 if let Ok(length) = length.try_into() {
1317 let result = file.file.set_len(length);
1318 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1319 interp_ok(Scalar::from_i32(result))
1320 } else {
1321 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1322 }
1323 } else {
1324 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1326 }
1327 }
1328
1329 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1330 let this = self.eval_context_mut();
1336
1337 let fd = this.read_scalar(fd_op)?.to_i32()?;
1338
1339 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1341 this.reject_in_isolation("`fsync`", reject_with)?;
1342 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1344 }
1345
1346 self.ffullsync_fd(fd)
1347 }
1348
1349 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1350 let this = self.eval_context_mut();
1351 let Some(fd) = this.machine.fds.get(fd_num) else {
1352 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1353 };
1354 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1356 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1357 })?;
1358 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1359 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1360 }
1361
1362 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1363 let this = self.eval_context_mut();
1364
1365 let fd = this.read_scalar(fd_op)?.to_i32()?;
1366
1367 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1369 this.reject_in_isolation("`fdatasync`", reject_with)?;
1370 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1372 }
1373
1374 let Some(fd) = this.machine.fds.get(fd) else {
1375 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1376 };
1377 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1379 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1380 })?;
1381 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1382 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1383 }
1384
1385 fn sync_file_range(
1386 &mut self,
1387 fd_op: &OpTy<'tcx>,
1388 offset_op: &OpTy<'tcx>,
1389 nbytes_op: &OpTy<'tcx>,
1390 flags_op: &OpTy<'tcx>,
1391 ) -> InterpResult<'tcx, Scalar> {
1392 let this = self.eval_context_mut();
1393
1394 let fd = this.read_scalar(fd_op)?.to_i32()?;
1395 let offset = this.read_scalar(offset_op)?.to_i64()?;
1396 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1397 let flags = this.read_scalar(flags_op)?.to_i32()?;
1398
1399 if offset < 0 || nbytes < 0 {
1400 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1401 }
1402 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1403 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1404 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1405 if flags & allowed_flags != flags {
1406 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1407 }
1408
1409 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1411 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1412 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1414 }
1415
1416 let Some(fd) = this.machine.fds.get(fd) else {
1417 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1418 };
1419 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1421 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1422 })?;
1423 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1424 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1425 }
1426
1427 fn readlink(
1428 &mut self,
1429 pathname_op: &OpTy<'tcx>,
1430 buf_op: &OpTy<'tcx>,
1431 bufsize_op: &OpTy<'tcx>,
1432 ) -> InterpResult<'tcx, i64> {
1433 let this = self.eval_context_mut();
1434
1435 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1436 let buf = this.read_pointer(buf_op)?;
1437 let bufsize = this.read_target_usize(bufsize_op)?;
1438
1439 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1441 this.reject_in_isolation("`readlink`", reject_with)?;
1442 this.set_last_error(LibcError("EACCES"))?;
1443 return interp_ok(-1);
1444 }
1445
1446 let result = std::fs::read_link(pathname);
1447 match result {
1448 Ok(resolved) => {
1449 let resolved = this.convert_path(
1453 Cow::Borrowed(resolved.as_ref()),
1454 crate::shims::os_str::PathConversion::HostToTarget,
1455 );
1456 let mut path_bytes = resolved.as_encoded_bytes();
1457 let bufsize: usize = bufsize.try_into().unwrap();
1458 if path_bytes.len() > bufsize {
1459 path_bytes = &path_bytes[..bufsize]
1460 }
1461 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1462 interp_ok(path_bytes.len().try_into().unwrap())
1463 }
1464 Err(e) => {
1465 this.set_last_error(e)?;
1466 interp_ok(-1)
1467 }
1468 }
1469 }
1470
1471 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1472 let this = self.eval_context_mut();
1473 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1476 let error = if let Some(fd) = this.machine.fds.get(fd) {
1477 if fd.is_tty(this.machine.communicate()) {
1478 return interp_ok(Scalar::from_i32(1));
1479 } else {
1480 LibcError("ENOTTY")
1481 }
1482 } else {
1483 LibcError("EBADF")
1485 };
1486 this.set_last_error(error)?;
1487 interp_ok(Scalar::from_i32(0))
1488 }
1489
1490 fn realpath(
1491 &mut self,
1492 path_op: &OpTy<'tcx>,
1493 processed_path_op: &OpTy<'tcx>,
1494 ) -> InterpResult<'tcx, Scalar> {
1495 let this = self.eval_context_mut();
1496 this.assert_target_os_is_unix("realpath");
1497
1498 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1499 let processed_ptr = this.read_pointer(processed_path_op)?;
1500
1501 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1503 this.reject_in_isolation("`realpath`", reject_with)?;
1504 this.set_last_error(LibcError("EACCES"))?;
1505 return interp_ok(Scalar::from_target_usize(0, this));
1506 }
1507
1508 let result = std::fs::canonicalize(pathname);
1509 match result {
1510 Ok(resolved) => {
1511 let path_max = this
1512 .eval_libc_i32("PATH_MAX")
1513 .try_into()
1514 .expect("PATH_MAX does not fit in u64");
1515 let dest = if this.ptr_is_null(processed_ptr)? {
1516 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1526 } else {
1527 let (wrote_path, _) =
1528 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1529
1530 if !wrote_path {
1531 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1535 return interp_ok(Scalar::from_target_usize(0, this));
1536 }
1537 processed_ptr
1538 };
1539
1540 interp_ok(Scalar::from_maybe_pointer(dest, this))
1541 }
1542 Err(e) => {
1543 this.set_last_error(e)?;
1544 interp_ok(Scalar::from_target_usize(0, this))
1545 }
1546 }
1547 }
1548 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1549 use rand::seq::IndexedRandom;
1550
1551 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1553
1554 let this = self.eval_context_mut();
1555 this.assert_target_os_is_unix("mkstemp");
1556
1557 let max_attempts = this.eval_libc_u32("TMP_MAX");
1567
1568 let template_ptr = this.read_pointer(template_op)?;
1571 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1572 let template_bytes = template.as_mut_slice();
1573
1574 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1576 this.reject_in_isolation("`mkstemp`", reject_with)?;
1577 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1578 }
1579
1580 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1582
1583 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1588 let end_pos = template_bytes.len();
1589 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1590
1591 if last_six_char_bytes != suffix_bytes {
1593 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1594 }
1595
1596 const SUBSTITUTIONS: &[char; 62] = &[
1600 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1601 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1602 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1603 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1604 ];
1605
1606 let mut fopts = OpenOptions::new();
1609 fopts.read(true).write(true).create_new(true);
1610
1611 #[cfg(unix)]
1612 {
1613 use std::os::unix::fs::OpenOptionsExt;
1614 fopts.mode(0o600);
1616 fopts.custom_flags(libc::O_EXCL);
1617 }
1618 #[cfg(windows)]
1619 {
1620 use std::os::windows::fs::OpenOptionsExt;
1621 fopts.share_mode(0);
1623 }
1624
1625 for _ in 0..max_attempts {
1627 let rng = this.machine.rng.get_mut();
1628
1629 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1631
1632 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1634
1635 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1637
1638 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1640
1641 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1642
1643 let file = fopts.open(possibly_unique);
1644
1645 match file {
1646 Ok(f) => {
1647 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1648 return interp_ok(Scalar::from_i32(fd));
1649 }
1650 Err(e) =>
1651 match e.kind() {
1652 ErrorKind::AlreadyExists => continue,
1654 _ => {
1656 return this.set_last_error_and_return_i32(e);
1659 }
1660 },
1661 }
1662 }
1663
1664 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1666 }
1667}
1668
1669fn extract_sec_and_nsec<'tcx>(
1673 time: std::io::Result<SystemTime>,
1674) -> InterpResult<'tcx, Option<(u64, u32)>> {
1675 match time.ok() {
1676 Some(time) => {
1677 let duration = system_time_to_duration(&time)?;
1678 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1679 }
1680 None => interp_ok(None),
1681 }
1682}
1683
1684struct FileMetadata {
1687 mode: Scalar,
1688 size: u64,
1689 created: Option<(u64, u32)>,
1690 accessed: Option<(u64, u32)>,
1691 modified: Option<(u64, u32)>,
1692}
1693
1694impl FileMetadata {
1695 fn from_path<'tcx>(
1696 ecx: &mut MiriInterpCx<'tcx>,
1697 path: &Path,
1698 follow_symlink: bool,
1699 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1700 let metadata =
1701 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1702
1703 FileMetadata::from_meta(ecx, metadata)
1704 }
1705
1706 fn from_fd_num<'tcx>(
1707 ecx: &mut MiriInterpCx<'tcx>,
1708 fd_num: i32,
1709 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1710 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1711 return interp_ok(Err(LibcError("EBADF")));
1712 };
1713
1714 let metadata = fd.metadata()?;
1715 drop(fd);
1716 FileMetadata::from_meta(ecx, metadata)
1717 }
1718
1719 fn from_meta<'tcx>(
1720 ecx: &mut MiriInterpCx<'tcx>,
1721 metadata: Result<std::fs::Metadata, std::io::Error>,
1722 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1723 let metadata = match metadata {
1724 Ok(metadata) => metadata,
1725 Err(e) => {
1726 return interp_ok(Err(e.into()));
1727 }
1728 };
1729
1730 let file_type = metadata.file_type();
1731
1732 let mode_name = if file_type.is_file() {
1733 "S_IFREG"
1734 } else if file_type.is_dir() {
1735 "S_IFDIR"
1736 } else {
1737 "S_IFLNK"
1738 };
1739
1740 let mode = ecx.eval_libc(mode_name);
1741
1742 let size = metadata.len();
1743
1744 let created = extract_sec_and_nsec(metadata.created())?;
1745 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1746 let modified = extract_sec_and_nsec(metadata.modified())?;
1747
1748 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified }))
1750 }
1751}