1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, DirBuilder, File, FileType, OpenOptions, TryLockError};
6use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7use std::path::{self, Path};
8use std::time::SystemTime;
9
10use rustc_abi::Size;
11use rustc_data_structures::either::Either;
12use rustc_data_structures::fx::FxHashMap;
13use rustc_target::spec::Os;
14
15use self::shims::time::system_time_to_duration;
16use crate::shims::files::FileHandle;
17use crate::shims::os_str::bytes_to_os_str;
18use crate::shims::sig::check_min_vararg_count;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22#[derive(Debug)]
24struct OpenDir {
25 special_entries: Vec<&'static str>,
28 read_dir: fs::ReadDir,
30 entry: Option<Pointer>,
33}
34
35impl OpenDir {
36 fn new(read_dir: fs::ReadDir) -> Self {
37 Self { special_entries: vec!["..", "."], read_dir, entry: None }
38 }
39
40 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
41 if let Some(special) = self.special_entries.pop() {
42 return Some(Ok(Either::Right(special)));
43 }
44 let entry = self.read_dir.next()?;
45 Some(entry.map(Either::Left))
46 }
47}
48
49#[derive(Debug)]
50struct DirEntry {
51 name: OsString,
52 ino: u64,
53 d_type: i32,
54}
55
56impl UnixFileDescription for FileHandle {
57 fn pread<'tcx>(
58 &self,
59 communicate_allowed: bool,
60 offset: u64,
61 ptr: Pointer,
62 len: usize,
63 ecx: &mut MiriInterpCx<'tcx>,
64 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
65 ) -> InterpResult<'tcx> {
66 assert!(communicate_allowed, "isolation should have prevented even opening a file");
67 if !self.readable {
68 return finish.call(ecx, Err(LibcError("EBADF")));
69 }
70
71 let mut bytes = vec![0; len];
72 let file = &mut &self.file;
76 let mut f = || {
77 let cursor_pos = file.stream_position()?;
78 file.seek(SeekFrom::Start(offset))?;
79 let res = file.read(&mut bytes);
80 file.seek(SeekFrom::Start(cursor_pos))
82 .expect("failed to restore file position, this shouldn't be possible");
83 res
84 };
85 let result = match f() {
86 Ok(read_size) => {
87 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
91 Ok(read_size)
92 }
93 Err(e) => Err(IoError::HostError(e)),
94 };
95 finish.call(ecx, result)
96 }
97
98 fn pwrite<'tcx>(
99 &self,
100 communicate_allowed: bool,
101 ptr: Pointer,
102 len: usize,
103 offset: u64,
104 ecx: &mut MiriInterpCx<'tcx>,
105 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
106 ) -> InterpResult<'tcx> {
107 assert!(communicate_allowed, "isolation should have prevented even opening a file");
108 if !self.writable {
109 return finish.call(ecx, Err(LibcError("EBADF")));
110 }
111
112 let file = &mut &self.file;
116 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
117 let mut f = || {
118 let cursor_pos = file.stream_position()?;
119 file.seek(SeekFrom::Start(offset))?;
120 let res = file.write(bytes);
121 file.seek(SeekFrom::Start(cursor_pos))
123 .expect("failed to restore file position, this shouldn't be possible");
124 res
125 };
126 let result = f();
127 finish.call(ecx, result.map_err(IoError::HostError))
128 }
129
130 fn flock<'tcx>(
131 &self,
132 communicate_allowed: bool,
133 op: FlockOp,
134 ) -> InterpResult<'tcx, io::Result<()>> {
135 assert!(communicate_allowed, "isolation should have prevented even opening a file");
136
137 use FlockOp::*;
138 let (res, nonblocking) = match op {
140 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
141 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
142 Unlock => {
143 return interp_ok(self.file.unlock());
144 }
145 };
146
147 match res {
148 Ok(()) => interp_ok(Ok(())),
149 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
150 Err(TryLockError::WouldBlock) =>
151 if nonblocking {
152 interp_ok(Err(ErrorKind::WouldBlock.into()))
153 } else {
154 throw_unsup_format!("blocking `flock` is not currently supported");
155 },
156 }
157 }
158}
159
160#[derive(Debug)]
164pub struct DirTable {
165 streams: FxHashMap<u64, OpenDir>,
175 next_id: u64,
177}
178
179impl DirTable {
180 #[expect(clippy::arithmetic_side_effects)]
181 fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
182 let id = self.next_id;
183 self.next_id += 1;
184 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
185 id
186 }
187}
188
189impl Default for DirTable {
190 fn default() -> DirTable {
191 DirTable {
192 streams: FxHashMap::default(),
193 next_id: 1,
195 }
196 }
197}
198
199impl VisitProvenance for DirTable {
200 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
201 let DirTable { streams, next_id: _ } = self;
202
203 for dir in streams.values() {
204 dir.entry.visit_provenance(visit);
205 }
206 }
207}
208
209fn maybe_sync_file(
210 file: &File,
211 writable: bool,
212 operation: fn(&File) -> std::io::Result<()>,
213) -> std::io::Result<i32> {
214 if !writable && cfg!(windows) {
215 Ok(0i32)
219 } else {
220 let result = operation(file);
221 result.map(|_| 0i32)
222 }
223}
224
225impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
226trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
227 fn write_stat_buf(
228 &mut self,
229 metadata: FileMetadata,
230 buf_op: &OpTy<'tcx>,
231 ) -> InterpResult<'tcx, i32> {
232 let this = self.eval_context_mut();
233
234 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
235 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
236 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
237
238 let buf = this.deref_pointer(buf_op)?;
243
244 this.write_int_fields_named(
245 &[
246 ("st_dev", metadata.dev.unwrap_or(0).into()),
247 ("st_mode", metadata.mode.into()),
248 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
249 ("st_ino", metadata.ino.unwrap_or(0).into()),
250 ("st_uid", metadata.uid.unwrap_or(0).into()),
251 ("st_gid", metadata.gid.unwrap_or(0).into()),
252 ("st_rdev", 0),
253 ("st_atime", access_sec.into()),
254 ("st_atime_nsec", access_nsec.into()),
255 ("st_mtime", modified_sec.into()),
256 ("st_mtime_nsec", modified_nsec.into()),
257 ("st_ctime", 0),
258 ("st_ctime_nsec", 0),
259 ("st_size", metadata.size.into()),
260 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
261 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
262 ],
263 &buf,
264 )?;
265
266 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
267 this.write_int_fields_named(
268 &[
269 ("st_birthtime", created_sec.into()),
270 ("st_birthtime_nsec", created_nsec.into()),
271 ("st_flags", 0),
272 ("st_gen", 0),
273 ],
274 &buf,
275 )?;
276 }
277
278 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
279 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
280 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
282 }
283
284 interp_ok(0)
285 }
286
287 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
288 #[cfg(unix)]
289 use std::os::unix::fs::FileTypeExt;
290
291 let this = self.eval_context_ref();
292 match file_type {
293 Ok(file_type) => {
294 match () {
295 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
296 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
297 _ if file_type.is_symlink() =>
298 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
299 #[cfg(unix)]
301 _ if file_type.is_block_device() =>
302 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
303 #[cfg(unix)]
304 _ if file_type.is_char_device() =>
305 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
306 #[cfg(unix)]
307 _ if file_type.is_fifo() =>
308 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
309 #[cfg(unix)]
310 _ if file_type.is_socket() =>
311 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
312 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
314 }
315 }
316 Err(_) => {
317 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
319 }
320 }
321 }
322
323 fn dir_entry_fields(
324 &self,
325 entry: Either<fs::DirEntry, &'static str>,
326 ) -> InterpResult<'tcx, DirEntry> {
327 let this = self.eval_context_ref();
328 interp_ok(match entry {
329 Either::Left(dir_entry) => {
330 DirEntry {
331 name: dir_entry.file_name(),
332 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
333 #[cfg(unix)]
336 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
337 #[cfg(not(unix))]
338 ino: 0u64,
339 }
340 }
341 Either::Right(special) =>
342 DirEntry {
343 name: special.into(),
344 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
345 ino: 0,
346 },
347 })
348 }
349
350 #[cfg(unix)]
351 fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
352 use std::os::unix::fs::PermissionsExt;
353 interp_ok(fs::Permissions::from_mode(mode))
354 }
355
356 #[cfg(not(unix))]
357 fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> {
358 throw_unsup_format!("setting file permissions is only supported on Unix hosts")
359 }
360}
361
362impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
363pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
364 fn open(
365 &mut self,
366 path_raw: &OpTy<'tcx>,
367 flag: &OpTy<'tcx>,
368 varargs: &[OpTy<'tcx>],
369 ) -> InterpResult<'tcx, Scalar> {
370 let this = self.eval_context_mut();
371
372 let path_raw = this.read_pointer(path_raw)?;
373 let flag = this.read_scalar(flag)?.to_i32()?;
374
375 let path = this.read_path_from_c_str(path_raw)?;
376 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
378 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
379 {
380 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
381 }
382
383 let mut flag = flag;
385
386 let mut options = OpenOptions::new();
387
388 let o_rdonly = this.eval_libc_i32("O_RDONLY");
389 let o_wronly = this.eval_libc_i32("O_WRONLY");
390 let o_rdwr = this.eval_libc_i32("O_RDWR");
391 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
395 throw_unsup_format!("access mode flags on this target are unsupported");
396 }
397 let mut writable = true;
398 let mut readable = true;
399
400 let access_mode = flag & 0b11;
402 flag &= !access_mode;
403
404 if access_mode == o_rdonly {
405 writable = false;
406 options.read(true);
407 } else if access_mode == o_wronly {
408 readable = false;
409 options.write(true);
410 } else if access_mode == o_rdwr {
411 options.read(true).write(true);
412 } else {
413 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
414 }
415
416 let o_append = this.eval_libc_i32("O_APPEND");
417 if flag & o_append == o_append {
418 flag &= !o_append;
419 options.append(true);
420 }
421 let o_trunc = this.eval_libc_i32("O_TRUNC");
422 if flag & o_trunc == o_trunc {
423 flag &= !o_trunc;
424 options.truncate(true);
425 }
426 let o_creat = this.eval_libc_i32("O_CREAT");
427 if flag & o_creat == o_creat {
428 flag &= !o_creat;
429 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
433 let mode = this.read_scalar(mode)?.to_u32()?;
434
435 #[cfg(unix)]
436 {
437 use std::os::unix::fs::OpenOptionsExt;
439 options.mode(mode);
440 }
441 #[cfg(not(unix))]
442 {
443 if mode != 0o666 {
445 throw_unsup_format!(
446 "non-default mode 0o{:o} is not supported on non-Unix hosts",
447 mode
448 );
449 }
450 }
451
452 let o_excl = this.eval_libc_i32("O_EXCL");
453 if flag & o_excl == o_excl {
454 flag &= !o_excl;
455 options.create_new(true);
456 } else {
457 options.create(true);
458 }
459 }
460 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
461 if flag & o_cloexec == o_cloexec {
462 flag &= !o_cloexec;
463 }
466 if this.tcx.sess.target.os == Os::Linux {
467 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
468 if flag & o_tmpfile == o_tmpfile {
469 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
471 }
472 }
473
474 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
475 if flag & o_nofollow == o_nofollow {
476 flag &= !o_nofollow;
477 #[cfg(unix)]
478 {
479 use std::os::unix::fs::OpenOptionsExt;
480 options.custom_flags(libc::O_NOFOLLOW);
481 }
482 #[cfg(not(unix))]
486 {
487 if path.is_symlink() {
490 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
491 }
492 }
493 }
494
495 if flag != 0 {
497 throw_unsup_format!("unsupported flags {:#x}", flag);
498 }
499
500 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
502 this.reject_in_isolation("`open`", reject_with)?;
503 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
504 }
505
506 let fd = options
507 .open(path)
508 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
509
510 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
511 }
512
513 fn lseek(
514 &mut self,
515 fd_num: i32,
516 offset: i128,
517 whence: i32,
518 dest: &MPlaceTy<'tcx>,
519 ) -> InterpResult<'tcx> {
520 let this = self.eval_context_mut();
521
522 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
525 if offset < 0 {
526 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
528 } else {
529 SeekFrom::Start(u64::try_from(offset).unwrap())
530 }
531 } else if whence == this.eval_libc_i32("SEEK_CUR") {
532 SeekFrom::Current(i64::try_from(offset).unwrap())
533 } else if whence == this.eval_libc_i32("SEEK_END") {
534 SeekFrom::End(i64::try_from(offset).unwrap())
535 } else {
536 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
537 };
538
539 let communicate = this.machine.communicate();
540
541 let Some(fd) = this.machine.fds.get(fd_num) else {
542 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
543 };
544 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
545 drop(fd);
546
547 let result = this.try_unwrap_io_result(result)?;
548 this.write_int(result, dest)?;
549 interp_ok(())
550 }
551
552 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
553 let this = self.eval_context_mut();
554
555 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
556
557 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
559 this.reject_in_isolation("`unlink`", reject_with)?;
560 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
561 }
562
563 let result = fs::remove_file(path).map(|_| 0);
564 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
565 }
566
567 fn symlink(
568 &mut self,
569 target_op: &OpTy<'tcx>,
570 linkpath_op: &OpTy<'tcx>,
571 ) -> InterpResult<'tcx, Scalar> {
572 #[cfg(unix)]
573 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
574 std::os::unix::fs::symlink(src, dst)
575 }
576
577 #[cfg(windows)]
578 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
579 use std::os::windows::fs;
580 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
581 }
582
583 let this = self.eval_context_mut();
584 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
585 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
586
587 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
589 this.reject_in_isolation("`symlink`", reject_with)?;
590 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
591 }
592
593 let result = create_link(&target, &linkpath).map(|_| 0);
594 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
595 }
596
597 fn linkat(
598 &mut self,
599 oldfd_op: &OpTy<'tcx>,
600 oldpath_op: &OpTy<'tcx>,
601 newfd_op: &OpTy<'tcx>,
602 newpath_op: &OpTy<'tcx>,
603 flags_op: &OpTy<'tcx>,
604 ) -> InterpResult<'tcx, Scalar> {
605 let this = self.eval_context_mut();
606
607 let flags = this.read_scalar(flags_op)?.to_i32()?;
609 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
610 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
611 let oldpath_ptr = this.read_pointer(oldpath_op)?;
612 let newpath_ptr = this.read_pointer(newpath_op)?;
613
614 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
616
617 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
619 this.reject_in_isolation("`linkat`", reject_with)?;
620 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
621 }
622
623 if flags != 0 {
625 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
626 }
627
628 if oldfd != at_fdcwd {
630 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
631 }
632 if oldpath_ptr == Pointer::null() {
633 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
634 }
635 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
636
637 if newfd != at_fdcwd {
639 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
640 }
641 if newpath_ptr == Pointer::null() {
642 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
643 }
644 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
645
646 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
647 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
648 }
649
650 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
651 let this = self.eval_context_mut();
652
653 if !matches!(
654 &this.tcx.sess.target.os,
655 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
656 ) {
657 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
658 }
659
660 let path_scalar = this.read_pointer(path_op)?;
661 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
662
663 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
665 this.reject_in_isolation("`stat`", reject_with)?;
666 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
667 }
668
669 let metadata = match FileMetadata::from_path(this, &path, true)? {
671 Ok(metadata) => metadata,
672 Err(err) => return this.set_errno_and_return_neg1_i32(err),
673 };
674
675 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
676 }
677
678 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
680 let this = self.eval_context_mut();
681
682 if !matches!(
683 &this.tcx.sess.target.os,
684 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
685 ) {
686 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
687 }
688
689 let path_scalar = this.read_pointer(path_op)?;
690 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
691
692 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
694 this.reject_in_isolation("`lstat`", reject_with)?;
695 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
696 }
697
698 let metadata = match FileMetadata::from_path(this, &path, false)? {
699 Ok(metadata) => metadata,
700 Err(err) => return this.set_errno_and_return_neg1_i32(err),
701 };
702
703 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
704 }
705
706 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
707 let this = self.eval_context_mut();
708
709 if !matches!(
710 &this.tcx.sess.target.os,
711 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
712 ) {
713 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
714 }
715
716 let fd = this.read_scalar(fd_op)?.to_i32()?;
717
718 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
720 this.reject_in_isolation("`fstat`", reject_with)?;
721 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
723 }
724
725 let metadata = match FileMetadata::from_fd_num(this, fd)? {
726 Ok(metadata) => metadata,
727 Err(err) => return this.set_errno_and_return_neg1_i32(err),
728 };
729 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
730 }
731
732 fn linux_statx(
733 &mut self,
734 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
740 let this = self.eval_context_mut();
741
742 this.assert_target_os(Os::Linux, "statx");
743
744 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
745 let pathname_ptr = this.read_pointer(pathname_op)?;
746 let flags = this.read_scalar(flags_op)?.to_i32()?;
747 let _mask = this.read_scalar(mask_op)?.to_u32()?;
748 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
749
750 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
752 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
753 }
754
755 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
756
757 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
758 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
760 let empty_path_flag = flags & at_empty_path == at_empty_path;
761 if !(path.is_absolute()
769 || dirfd == this.eval_libc_i32("AT_FDCWD")
770 || (path.as_os_str().is_empty() && empty_path_flag))
771 {
772 throw_unsup_format!(
773 "using statx is only supported with absolute paths, relative paths with the file \
774 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
775 file descriptor"
776 )
777 }
778
779 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
781 this.reject_in_isolation("`statx`", reject_with)?;
782 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
783 LibcError("EACCES")
786 } else {
787 assert!(empty_path_flag);
791 LibcError("EBADF")
792 };
793 return this.set_errno_and_return_neg1_i32(ecode);
794 }
795
796 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
799
800 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
803 FileMetadata::from_fd_num(this, dirfd)?
804 } else {
805 FileMetadata::from_path(this, &path, follow_symlink)?
806 };
807 let metadata = match metadata {
808 Ok(metadata) => metadata,
809 Err(err) => return this.set_errno_and_return_neg1_i32(err),
810 };
811
812 let mut mask = this.eval_libc_u32("STATX_TYPE")
817 | this.eval_libc_u32("STATX_MODE")
818 | this.eval_libc_u32("STATX_SIZE");
819
820 if metadata.ino.is_some() {
822 mask |= this.eval_libc_u32("STATX_INO");
823 }
824 if metadata.nlink.is_some() {
825 mask |= this.eval_libc_u32("STATX_NLINK");
826 }
827 if metadata.uid.is_some() {
828 mask |= this.eval_libc_u32("STATX_UID");
829 }
830 if metadata.gid.is_some() {
831 mask |= this.eval_libc_u32("STATX_GID");
832 }
833 if metadata.blocks.is_some() {
834 mask |= this.eval_libc_u32("STATX_BLOCKS");
835 }
836
837 let (access_sec, access_nsec) = metadata
840 .accessed
841 .map(|tup| {
842 mask |= this.eval_libc_u32("STATX_ATIME");
843 interp_ok(tup)
844 })
845 .unwrap_or_else(|| interp_ok((0, 0)))?;
846
847 let (created_sec, created_nsec) = metadata
848 .created
849 .map(|tup| {
850 mask |= this.eval_libc_u32("STATX_BTIME");
851 interp_ok(tup)
852 })
853 .unwrap_or_else(|| interp_ok((0, 0)))?;
854
855 let (modified_sec, modified_nsec) = metadata
856 .modified
857 .map(|tup| {
858 mask |= this.eval_libc_u32("STATX_MTIME");
859 interp_ok(tup)
860 })
861 .unwrap_or_else(|| interp_ok((0, 0)))?;
862
863 this.write_int_fields_named(
865 &[
866 ("stx_mask", mask.into()),
867 ("stx_mode", metadata.mode.into()),
868 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
869 ("stx_attributes", 0),
870 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
871 ("stx_uid", metadata.uid.unwrap_or(0).into()),
872 ("stx_gid", metadata.gid.unwrap_or(0).into()),
873 ("stx_ino", metadata.ino.unwrap_or(0).into()),
874 ("stx_size", metadata.size.into()),
875 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
876 ("stx_attributes_mask", 0),
877 ("stx_rdev_major", 0),
878 ("stx_rdev_minor", 0),
879 ("stx_dev_major", 0),
880 ("stx_dev_minor", 0),
881 ],
882 &statxbuf,
883 )?;
884 #[rustfmt::skip]
885 this.write_int_fields_named(
886 &[
887 ("tv_sec", access_sec.into()),
888 ("tv_nsec", access_nsec.into()),
889 ],
890 &this.project_field_named(&statxbuf, "stx_atime")?,
891 )?;
892 #[rustfmt::skip]
893 this.write_int_fields_named(
894 &[
895 ("tv_sec", created_sec.into()),
896 ("tv_nsec", created_nsec.into()),
897 ],
898 &this.project_field_named(&statxbuf, "stx_btime")?,
899 )?;
900 #[rustfmt::skip]
901 this.write_int_fields_named(
902 &[
903 ("tv_sec", 0.into()),
904 ("tv_nsec", 0.into()),
905 ],
906 &this.project_field_named(&statxbuf, "stx_ctime")?,
907 )?;
908 #[rustfmt::skip]
909 this.write_int_fields_named(
910 &[
911 ("tv_sec", modified_sec.into()),
912 ("tv_nsec", modified_nsec.into()),
913 ],
914 &this.project_field_named(&statxbuf, "stx_mtime")?,
915 )?;
916
917 interp_ok(Scalar::from_i32(0))
918 }
919
920 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
921 let this = self.eval_context_mut();
922
923 let path_ptr = this.read_pointer(path_op)?;
924 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
925
926 if this.ptr_is_null(path_ptr)? {
927 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
928 }
929 let path = this.read_path_from_c_str(path_ptr)?;
930
931 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
933 this.reject_in_isolation("`chmod`", reject_with)?;
934 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
935 }
936
937 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
938 if let Err(err) = fs::set_permissions(path, permissions) {
939 return this.set_errno_and_return_neg1_i32(err);
940 }
941
942 interp_ok(Scalar::from_i32(0))
943 }
944
945 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
946 let this = self.eval_context_mut();
947
948 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
949 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
950
951 let Some(fd) = this.machine.fds.get(fd_num) else {
952 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
953 };
954 let Some(file) = fd.downcast::<FileHandle>() else {
955 throw_unsup_format!("`fchmod` is only supported on regular files")
957 };
958 if !file.writable && !file.readable {
959 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
962 }
963 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
964
965 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
966 if let Err(err) = file.file.set_permissions(permissions) {
967 return this.set_errno_and_return_neg1_i32(err);
968 }
969
970 interp_ok(Scalar::from_i32(0))
971 }
972
973 fn rename(
974 &mut self,
975 oldpath_op: &OpTy<'tcx>,
976 newpath_op: &OpTy<'tcx>,
977 ) -> InterpResult<'tcx, Scalar> {
978 let this = self.eval_context_mut();
979
980 let oldpath_ptr = this.read_pointer(oldpath_op)?;
981 let newpath_ptr = this.read_pointer(newpath_op)?;
982
983 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
984 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
985 }
986
987 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
988 let newpath = this.read_path_from_c_str(newpath_ptr)?;
989
990 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
992 this.reject_in_isolation("`rename`", reject_with)?;
993 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
994 }
995
996 let result = fs::rename(oldpath, newpath).map(|_| 0);
997
998 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
999 }
1000
1001 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1002 let this = self.eval_context_mut();
1003
1004 #[cfg_attr(not(unix), allow(unused_variables))]
1005 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1006 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1007 } else {
1008 this.read_scalar(mode_op)?.to_u32()?
1009 };
1010
1011 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1012
1013 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1015 this.reject_in_isolation("`mkdir`", reject_with)?;
1016 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1017 }
1018
1019 #[cfg_attr(not(unix), allow(unused_mut))]
1020 let mut builder = DirBuilder::new();
1021
1022 #[cfg(unix)]
1025 {
1026 use std::os::unix::fs::DirBuilderExt;
1027 builder.mode(mode);
1028 }
1029
1030 let result = builder.create(path).map(|_| 0i32);
1031
1032 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1033 }
1034
1035 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1036 let this = self.eval_context_mut();
1037
1038 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1039
1040 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1042 this.reject_in_isolation("`rmdir`", reject_with)?;
1043 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1044 }
1045
1046 let result = fs::remove_dir(path).map(|_| 0i32);
1047
1048 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1049 }
1050
1051 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1052 let this = self.eval_context_mut();
1053
1054 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1055
1056 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1058 this.reject_in_isolation("`opendir`", reject_with)?;
1059 this.set_last_error(LibcError("EACCES"))?;
1060 return interp_ok(Scalar::null_ptr(this));
1061 }
1062
1063 let result = fs::read_dir(name);
1064
1065 match result {
1066 Ok(dir_iter) => {
1067 let id = this.machine.dirs.insert_new(dir_iter);
1068
1069 interp_ok(Scalar::from_target_usize(id, this))
1073 }
1074 Err(e) => {
1075 this.set_last_error(e)?;
1076 interp_ok(Scalar::null_ptr(this))
1077 }
1078 }
1079 }
1080
1081 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1082 let this = self.eval_context_mut();
1083
1084 if !matches!(
1085 &this.tcx.sess.target.os,
1086 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
1087 ) {
1088 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
1089 }
1090
1091 let dirp = this.read_target_usize(dirp_op)?;
1092
1093 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1095 this.reject_in_isolation("`readdir`", reject_with)?;
1096 this.set_last_error(LibcError("EBADF"))?;
1097 this.write_null(dest)?;
1098 return interp_ok(());
1099 }
1100
1101 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1102 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1103 })?;
1104
1105 let entry = match open_dir.next_host_entry() {
1106 Some(Ok(dir_entry)) => {
1107 let dir_entry = this.dir_entry_fields(dir_entry)?;
1108
1109 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1146 let dirent_layout = this.layout_of(dirent_ty)?;
1147 let fields = &dirent_layout.fields;
1148 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1149
1150 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1154 let name_len = u64::try_from(name_bytes.len()).unwrap();
1155 let size = d_name_offset.strict_add(name_len);
1156
1157 let entry = this.allocate_ptr(
1158 Size::from_bytes(size),
1159 dirent_layout.align.abi,
1160 MiriMemoryKind::Runtime.into(),
1161 AllocInit::Uninit,
1162 )?;
1163 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1164
1165 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1168 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1169
1170 let ino_name =
1172 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1173 this.write_int_fields_named(
1174 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1175 &entry,
1176 )?;
1177
1178 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1180 this.write_null(&d_off)?;
1181 }
1182 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1183 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1184 }
1185 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1186 this.write_int(dir_entry.d_type, &d_type)?;
1187 }
1188
1189 Some(entry.ptr())
1190 }
1191 None => {
1192 None
1194 }
1195 Some(Err(e)) => {
1196 this.set_last_error(e)?;
1197 None
1198 }
1199 };
1200
1201 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1202 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1203 if let Some(old_entry) = old_entry {
1204 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1205 }
1206
1207 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1208 interp_ok(())
1209 }
1210
1211 fn macos_readdir_r(
1212 &mut self,
1213 dirp_op: &OpTy<'tcx>,
1214 entry_op: &OpTy<'tcx>,
1215 result_op: &OpTy<'tcx>,
1216 ) -> InterpResult<'tcx, Scalar> {
1217 let this = self.eval_context_mut();
1218
1219 this.assert_target_os(Os::MacOs, "readdir_r");
1220
1221 let dirp = this.read_target_usize(dirp_op)?;
1222 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1223
1224 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1226 this.reject_in_isolation("`readdir_r`", reject_with)?;
1227 return interp_ok(this.eval_libc("EBADF"));
1229 }
1230
1231 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1232 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1233 })?;
1234 interp_ok(match open_dir.next_host_entry() {
1235 Some(Ok(dir_entry)) => {
1236 let dir_entry = this.dir_entry_fields(dir_entry)?;
1237 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1252
1253 let name_place = this.project_field_named(&entry_place, "d_name")?;
1255 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1256 &dir_entry.name,
1257 name_place.ptr(),
1258 name_place.layout.size.bytes(),
1259 )?;
1260 if !name_fits {
1261 throw_unsup_format!(
1262 "a directory entry had a name too large to fit in libc::dirent"
1263 );
1264 }
1265
1266 this.write_int_fields_named(
1268 &[
1269 ("d_reclen", entry_place.layout.size.bytes().into()),
1270 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1271 ("d_type", dir_entry.d_type.into()),
1272 ("d_ino", dir_entry.ino.into()),
1273 ("d_seekoff", 0),
1274 ],
1275 &entry_place,
1276 )?;
1277 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1278
1279 Scalar::from_i32(0)
1280 }
1281 None => {
1282 this.write_null(&result_place)?;
1284 Scalar::from_i32(0)
1285 }
1286 Some(Err(e)) => {
1287 this.io_error_to_errnum(e)?
1289 }
1290 })
1291 }
1292
1293 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1294 let this = self.eval_context_mut();
1295
1296 let dirp = this.read_target_usize(dirp_op)?;
1297
1298 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1300 this.reject_in_isolation("`closedir`", reject_with)?;
1301 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1302 }
1303
1304 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1305 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1306 };
1307 if let Some(entry) = open_dir.entry.take() {
1308 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1309 }
1310 drop(open_dir);
1312
1313 interp_ok(Scalar::from_i32(0))
1314 }
1315
1316 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1317 let this = self.eval_context_mut();
1318
1319 let Some(fd) = this.machine.fds.get(fd_num) else {
1320 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1321 };
1322 let Some(file) = fd.downcast::<FileHandle>() else {
1323 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1326 };
1327 if !file.writable {
1328 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1330 }
1331 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1332
1333 if let Ok(length) = length.try_into() {
1334 let result = file.file.set_len(length);
1335 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1336 interp_ok(Scalar::from_i32(result))
1337 } else {
1338 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1339 }
1340 }
1341
1342 fn posix_fallocate(
1345 &mut self,
1346 fd_num: i32,
1347 offset: i64,
1348 len: i64,
1349 ) -> InterpResult<'tcx, Scalar> {
1350 let this = self.eval_context_mut();
1351
1352 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1354 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1355 return interp_ok(this.eval_libc("EBADF"));
1357 }
1358
1359 if offset < 0 || len <= 0 {
1361 return interp_ok(this.eval_libc("EINVAL"));
1362 }
1363
1364 let Some(fd) = this.machine.fds.get(fd_num) else {
1366 return interp_ok(this.eval_libc("EBADF"));
1367 };
1368 let Some(file) = fd.downcast::<FileHandle>() else {
1369 return interp_ok(this.eval_libc("ENODEV"));
1371 };
1372
1373 if !file.writable {
1374 return interp_ok(this.eval_libc("EBADF"));
1376 }
1377
1378 let current_size = match file.file.metadata() {
1379 Ok(metadata) => metadata.len(),
1380 Err(err) => return this.io_error_to_errnum(err),
1381 };
1382 let new_size = match offset.checked_add(len) {
1384 Some(new_size) => u64::try_from(new_size).unwrap(),
1386 None => return interp_ok(this.eval_libc("EFBIG")), };
1388 if current_size < new_size {
1391 interp_ok(match file.file.set_len(new_size) {
1392 Ok(()) => Scalar::from_i32(0),
1393 Err(e) => this.io_error_to_errnum(e)?,
1394 })
1395 } else {
1396 interp_ok(Scalar::from_i32(0))
1397 }
1398 }
1399
1400 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1401 let this = self.eval_context_mut();
1407
1408 let fd = this.read_scalar(fd_op)?.to_i32()?;
1409
1410 self.ffullsync_fd(fd)
1411 }
1412
1413 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1414 let this = self.eval_context_mut();
1415 let Some(fd) = this.machine.fds.get(fd_num) else {
1416 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1417 };
1418 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1420 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1421 })?;
1422 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1423
1424 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1425 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1426 }
1427
1428 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1429 let this = self.eval_context_mut();
1430
1431 let fd = this.read_scalar(fd_op)?.to_i32()?;
1432
1433 let Some(fd) = this.machine.fds.get(fd) else {
1434 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1435 };
1436 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1438 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1439 })?;
1440 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1441
1442 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1443 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1444 }
1445
1446 fn sync_file_range(
1447 &mut self,
1448 fd_op: &OpTy<'tcx>,
1449 offset_op: &OpTy<'tcx>,
1450 nbytes_op: &OpTy<'tcx>,
1451 flags_op: &OpTy<'tcx>,
1452 ) -> InterpResult<'tcx, Scalar> {
1453 let this = self.eval_context_mut();
1454
1455 let fd = this.read_scalar(fd_op)?.to_i32()?;
1456 let offset = this.read_scalar(offset_op)?.to_i64()?;
1457 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1458 let flags = this.read_scalar(flags_op)?.to_i32()?;
1459
1460 if offset < 0 || nbytes < 0 {
1461 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1462 }
1463 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1464 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1465 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1466 if flags & allowed_flags != flags {
1467 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1468 }
1469
1470 let Some(fd) = this.machine.fds.get(fd) else {
1471 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1472 };
1473 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1475 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1476 })?;
1477 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1478
1479 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1480 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1481 }
1482
1483 fn readlink(
1484 &mut self,
1485 pathname_op: &OpTy<'tcx>,
1486 buf_op: &OpTy<'tcx>,
1487 bufsize_op: &OpTy<'tcx>,
1488 ) -> InterpResult<'tcx, i64> {
1489 let this = self.eval_context_mut();
1490
1491 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1492 let buf = this.read_pointer(buf_op)?;
1493 let bufsize = this.read_target_usize(bufsize_op)?;
1494
1495 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1497 this.reject_in_isolation("`readlink`", reject_with)?;
1498 this.set_last_error(LibcError("EACCES"))?;
1499 return interp_ok(-1);
1500 }
1501
1502 let result = std::fs::read_link(pathname);
1503 match result {
1504 Ok(resolved) => {
1505 let resolved = this.convert_path(
1509 Cow::Borrowed(resolved.as_ref()),
1510 crate::shims::os_str::PathConversion::HostToTarget,
1511 );
1512 let mut path_bytes = resolved.as_encoded_bytes();
1513 let bufsize: usize = bufsize.try_into().unwrap();
1514 if path_bytes.len() > bufsize {
1515 path_bytes = &path_bytes[..bufsize]
1516 }
1517 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1518 interp_ok(path_bytes.len().try_into().unwrap())
1519 }
1520 Err(e) => {
1521 this.set_last_error(e)?;
1522 interp_ok(-1)
1523 }
1524 }
1525 }
1526
1527 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1528 let this = self.eval_context_mut();
1529 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1532 let error = if let Some(fd) = this.machine.fds.get(fd) {
1533 if fd.is_tty(this.machine.communicate()) {
1534 return interp_ok(Scalar::from_i32(1));
1535 } else {
1536 LibcError("ENOTTY")
1537 }
1538 } else {
1539 LibcError("EBADF")
1541 };
1542 this.set_last_error(error)?;
1543 interp_ok(Scalar::from_i32(0))
1544 }
1545
1546 fn realpath(
1547 &mut self,
1548 path_op: &OpTy<'tcx>,
1549 processed_path_op: &OpTy<'tcx>,
1550 ) -> InterpResult<'tcx, Scalar> {
1551 let this = self.eval_context_mut();
1552 this.assert_target_os_is_unix("realpath");
1553
1554 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1555 let processed_ptr = this.read_pointer(processed_path_op)?;
1556
1557 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1559 this.reject_in_isolation("`realpath`", reject_with)?;
1560 this.set_last_error(LibcError("EACCES"))?;
1561 return interp_ok(Scalar::from_target_usize(0, this));
1562 }
1563
1564 let result = std::fs::canonicalize(pathname);
1565 match result {
1566 Ok(resolved) => {
1567 let path_max = this
1568 .eval_libc_i32("PATH_MAX")
1569 .try_into()
1570 .expect("PATH_MAX does not fit in u64");
1571 let dest = if this.ptr_is_null(processed_ptr)? {
1572 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1582 } else {
1583 let (wrote_path, _) =
1584 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1585
1586 if !wrote_path {
1587 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1591 return interp_ok(Scalar::from_target_usize(0, this));
1592 }
1593 processed_ptr
1594 };
1595
1596 interp_ok(Scalar::from_maybe_pointer(dest, this))
1597 }
1598 Err(e) => {
1599 this.set_last_error(e)?;
1600 interp_ok(Scalar::from_target_usize(0, this))
1601 }
1602 }
1603 }
1604 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1605 use rand::seq::IndexedRandom;
1606
1607 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1609
1610 let this = self.eval_context_mut();
1611 this.assert_target_os_is_unix("mkstemp");
1612
1613 let max_attempts = this.eval_libc_u32("TMP_MAX");
1623
1624 let template_ptr = this.read_pointer(template_op)?;
1627 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1628 let template_bytes = template.as_mut_slice();
1629
1630 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1632 this.reject_in_isolation("`mkstemp`", reject_with)?;
1633 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1634 }
1635
1636 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1638
1639 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1644 let end_pos = template_bytes.len();
1645 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1646
1647 if last_six_char_bytes != suffix_bytes {
1649 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1650 }
1651
1652 const SUBSTITUTIONS: &[char; 62] = &[
1656 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1657 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1658 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1659 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1660 ];
1661
1662 let mut fopts = OpenOptions::new();
1665 fopts.read(true).write(true).create_new(true);
1666
1667 cfg_select! {
1668 unix =>
1669 {
1670 use std::os::unix::fs::OpenOptionsExt;
1671 fopts.mode(0o600);
1673 fopts.custom_flags(libc::O_EXCL);
1674 }
1675 windows =>
1676 {
1677 use std::os::windows::fs::OpenOptionsExt;
1678 fopts.share_mode(0);
1680 }
1681 _ => {
1682 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1683 }
1684 }
1685
1686 for _ in 0..max_attempts {
1688 let rng = this.machine.rng.get_mut();
1689
1690 let unique_suffix =
1692 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1693
1694 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1696
1697 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1699
1700 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1702 match file {
1703 Ok(f) => {
1704 let fd = this.machine.fds.insert_new(FileHandle {
1705 file: f,
1706 writable: true,
1707 readable: true,
1708 });
1709 return interp_ok(Scalar::from_i32(fd));
1710 }
1711 Err(e) =>
1712 match e.kind() {
1713 ErrorKind::AlreadyExists => continue,
1715 _ => {
1717 return this.set_errno_and_return_neg1_i32(e);
1720 }
1721 },
1722 }
1723 }
1724
1725 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1727 }
1728}
1729
1730fn extract_sec_and_nsec<'tcx>(
1734 time: std::io::Result<SystemTime>,
1735) -> InterpResult<'tcx, Option<(u64, u32)>> {
1736 match time.ok() {
1737 Some(time) => {
1738 let duration = system_time_to_duration(&time)?;
1739 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1740 }
1741 None => interp_ok(None),
1742 }
1743}
1744
1745fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1746 #[cfg(unix)]
1747 use std::os::unix::fs::FileTypeExt;
1748
1749 if file_type.is_file() {
1750 "S_IFREG"
1751 } else if file_type.is_dir() {
1752 "S_IFDIR"
1753 } else if file_type.is_symlink() {
1754 "S_IFLNK"
1755 } else {
1756 #[cfg(unix)]
1758 {
1759 if file_type.is_socket() {
1760 return "S_IFSOCK";
1761 } else if file_type.is_fifo() {
1762 return "S_IFIFO";
1763 } else if file_type.is_char_device() {
1764 return "S_IFCHR";
1765 } else if file_type.is_block_device() {
1766 return "S_IFBLK";
1767 }
1768 }
1769 "S_IFREG"
1770 }
1771}
1772
1773struct FileMetadata {
1781 mode: u32,
1783 size: u64,
1784 created: Option<(u64, u32)>,
1785 accessed: Option<(u64, u32)>,
1786 modified: Option<(u64, u32)>,
1787 dev: Option<u64>,
1788 ino: Option<u64>,
1789 nlink: Option<u64>,
1790 uid: Option<u32>,
1791 gid: Option<u32>,
1792 blksize: Option<u64>,
1793 blocks: Option<u64>,
1794}
1795
1796impl FileMetadata {
1797 fn from_path<'tcx>(
1798 ecx: &mut MiriInterpCx<'tcx>,
1799 path: &Path,
1800 follow_symlink: bool,
1801 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1802 let metadata =
1803 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1804
1805 FileMetadata::from_meta(ecx, metadata)
1806 }
1807
1808 fn from_fd_num<'tcx>(
1809 ecx: &mut MiriInterpCx<'tcx>,
1810 fd_num: i32,
1811 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1812 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1813 return interp_ok(Err(LibcError("EBADF")));
1814 };
1815 match fd.metadata()? {
1816 Either::Left(host) => Self::from_meta(ecx, host),
1817 Either::Right(name) => Self::synthetic(ecx, name),
1818 }
1819 }
1820
1821 fn synthetic<'tcx>(
1822 ecx: &mut MiriInterpCx<'tcx>,
1823 mode_name: &str,
1824 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1825 let mode = ecx.eval_libc(mode_name);
1826 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1827 let mode = mode | 0o666;
1829 interp_ok(Ok(FileMetadata {
1830 mode,
1831 size: 0,
1832 created: None,
1833 accessed: None,
1834 modified: None,
1835 dev: None,
1836 uid: None,
1837 gid: None,
1838 blksize: None,
1839 blocks: None,
1840 ino: None,
1841 nlink: None,
1842 }))
1843 }
1844
1845 fn from_meta<'tcx>(
1846 ecx: &mut MiriInterpCx<'tcx>,
1847 metadata: Result<std::fs::Metadata, std::io::Error>,
1848 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1849 let metadata = match metadata {
1850 Ok(metadata) => metadata,
1851 Err(e) => {
1852 return interp_ok(Err(e.into()));
1853 }
1854 };
1855
1856 let file_type = metadata.file_type();
1857 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1858 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1859
1860 let size = metadata.len();
1861
1862 let created = extract_sec_and_nsec(metadata.created())?;
1863 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1864 let modified = extract_sec_and_nsec(metadata.modified())?;
1865
1866 cfg_select! {
1869 unix => {
1870 use std::os::unix::fs::MetadataExt;
1871 use std::os::unix::fs::PermissionsExt;
1872
1873 let dev = metadata.dev();
1874 let ino = metadata.ino();
1875 let nlink = metadata.nlink();
1876 let uid = metadata.uid();
1877 let gid = metadata.gid();
1878 let blksize = metadata.blksize();
1879 let blocks = metadata.blocks();
1880
1881 mode |= metadata.permissions().mode();
1882
1883 interp_ok(Ok(FileMetadata {
1884 mode,
1885 size,
1886 created,
1887 accessed,
1888 modified,
1889 dev: Some(dev),
1890 ino: Some(ino),
1891 nlink: Some(nlink),
1892 uid: Some(uid),
1893 gid: Some(gid),
1894 blksize: Some(blksize),
1895 blocks: Some(blocks),
1896 }))
1897 }
1898 _ => {
1899 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1901
1902 interp_ok(Ok(FileMetadata {
1903 mode,
1904 size,
1905 created,
1906 accessed,
1907 modified,
1908 dev: None,
1909 ino: None,
1910 nlink: None,
1911 uid: None,
1912 gid: None,
1913 blksize: None,
1914 blocks: None,
1915 }))
1916 },
1917 }
1918 }
1919}