1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, DirBuilder, File, FileTimes, 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::{FieldIdx, 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::Varargs;
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
56#[derive(Copy, Clone)]
58enum TimeUpdate {
59 Omit,
60 Set(SystemTime),
61}
62
63impl UnixFileDescription for FileHandle {
64 fn pread<'tcx>(
65 &self,
66 communicate_allowed: bool,
67 offset: u64,
68 ptr: Pointer,
69 len: usize,
70 ecx: &mut MiriInterpCx<'tcx>,
71 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
72 ) -> InterpResult<'tcx> {
73 assert!(communicate_allowed, "isolation should have prevented even opening a file");
74 if !self.readable {
75 return finish.call(ecx, Err(LibcError("EBADF")));
76 }
77
78 let mut bytes = vec![0; len];
79 let file = &mut &self.file;
83 let mut f = || {
84 let cursor_pos = file.stream_position()?;
85 file.seek(SeekFrom::Start(offset))?;
86 let res = file.read(&mut bytes);
87 file.seek(SeekFrom::Start(cursor_pos))
89 .expect("failed to restore file position, this shouldn't be possible");
90 res
91 };
92 let result = match f() {
93 Ok(read_size) => {
94 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
98 Ok(read_size)
99 }
100 Err(e) => Err(IoError::HostError(e)),
101 };
102 finish.call(ecx, result)
103 }
104
105 fn pwrite<'tcx>(
106 &self,
107 communicate_allowed: bool,
108 ptr: Pointer,
109 len: usize,
110 offset: u64,
111 ecx: &mut MiriInterpCx<'tcx>,
112 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
113 ) -> InterpResult<'tcx> {
114 assert!(communicate_allowed, "isolation should have prevented even opening a file");
115 if !self.writable {
116 return finish.call(ecx, Err(LibcError("EBADF")));
117 }
118
119 let file = &mut &self.file;
123 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
124 let mut f = || {
125 let cursor_pos = file.stream_position()?;
126 file.seek(SeekFrom::Start(offset))?;
127 let res = file.write(bytes);
128 file.seek(SeekFrom::Start(cursor_pos))
130 .expect("failed to restore file position, this shouldn't be possible");
131 res
132 };
133 let result = f();
134 finish.call(ecx, result.map_err(IoError::HostError))
135 }
136
137 fn flock<'tcx>(
138 &self,
139 communicate_allowed: bool,
140 op: FlockOp,
141 ) -> InterpResult<'tcx, io::Result<()>> {
142 assert!(communicate_allowed, "isolation should have prevented even opening a file");
143
144 use FlockOp::*;
145 let (res, nonblocking) = match op {
147 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
148 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
149 Unlock => {
150 return interp_ok(self.file.unlock());
151 }
152 };
153
154 match res {
155 Ok(()) => interp_ok(Ok(())),
156 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
157 Err(TryLockError::WouldBlock) =>
158 if nonblocking {
159 interp_ok(Err(ErrorKind::WouldBlock.into()))
160 } else {
161 throw_unsup_format!("blocking `flock` is not currently supported");
162 },
163 }
164 }
165}
166
167#[derive(Debug)]
171pub struct DirTable {
172 streams: FxHashMap<u64, OpenDir>,
182 next_id: u64,
184}
185
186impl DirTable {
187 #[expect(clippy::arithmetic_side_effects)]
188 fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
189 let id = self.next_id;
190 self.next_id += 1;
191 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
192 id
193 }
194}
195
196impl Default for DirTable {
197 fn default() -> DirTable {
198 DirTable {
199 streams: FxHashMap::default(),
200 next_id: 1,
202 }
203 }
204}
205
206impl VisitProvenance for DirTable {
207 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
208 let DirTable { streams, next_id: _ } = self;
209
210 for dir in streams.values() {
211 dir.entry.visit_provenance(visit);
212 }
213 }
214}
215
216fn maybe_sync_file(
217 file: &File,
218 writable: bool,
219 operation: fn(&File) -> std::io::Result<()>,
220) -> std::io::Result<i32> {
221 if !writable && cfg!(windows) {
222 Ok(0i32)
226 } else {
227 let result = operation(file);
228 result.map(|_| 0i32)
229 }
230}
231
232impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
233trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
234 fn parse_utimens_timespec(
237 &self,
238 tp: &MPlaceTy<'tcx>,
239 ) -> InterpResult<'tcx, Option<TimeUpdate>> {
240 let this = self.eval_context_ref();
241 assert!(this.machine.communicate(), "isolation should have prevented reaching this");
243
244 let nsec_place = this.project_field(tp, FieldIdx::ONE)?;
246 let nsec = this.read_scalar(&nsec_place)?.to_target_isize(this)?;
247
248 if nsec == this.eval_libc("UTIME_OMIT").to_target_isize(this)? {
249 return interp_ok(Some(TimeUpdate::Omit));
250 }
251 if nsec == this.eval_libc("UTIME_NOW").to_target_isize(this)? {
252 return interp_ok(Some(TimeUpdate::Set(SystemTime::now())));
253 }
254
255 let Some(duration) = this.read_timespec(tp)? else {
256 return interp_ok(None);
257 };
258 interp_ok(SystemTime::UNIX_EPOCH.checked_add(duration).map(TimeUpdate::Set))
259 }
260
261 fn write_stat_buf(
262 &mut self,
263 metadata: FileMetadata,
264 buf_op: &OpTy<'tcx>,
265 ) -> InterpResult<'tcx, i32> {
266 let this = self.eval_context_mut();
267
268 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
269 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
270 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
271
272 let buf = this.deref_pointer(buf_op)?;
277
278 this.write_int_fields_named(
279 &[
280 ("st_dev", metadata.dev.unwrap_or(0).into()),
281 ("st_mode", metadata.mode.into()),
282 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
283 ("st_ino", metadata.ino.unwrap_or(0).into()),
284 ("st_uid", metadata.uid.unwrap_or(0).into()),
285 ("st_gid", metadata.gid.unwrap_or(0).into()),
286 ("st_rdev", 0),
287 ("st_atime", access_sec.into()),
288 ("st_atime_nsec", access_nsec.into()),
289 ("st_mtime", modified_sec.into()),
290 ("st_mtime_nsec", modified_nsec.into()),
291 ("st_ctime", 0),
292 ("st_ctime_nsec", 0),
293 ("st_size", metadata.size.into()),
294 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
295 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
296 ],
297 &buf,
298 )?;
299
300 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
301 this.write_int_fields_named(
302 &[
303 ("st_birthtime", created_sec.into()),
304 ("st_birthtime_nsec", created_nsec.into()),
305 ("st_flags", 0),
306 ("st_gen", 0),
307 ],
308 &buf,
309 )?;
310 }
311
312 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
313 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
314 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
316 }
317
318 interp_ok(0)
319 }
320
321 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
322 #[cfg(unix)]
323 use std::os::unix::fs::FileTypeExt;
324
325 let this = self.eval_context_ref();
326 match file_type {
327 Ok(file_type) => {
328 match () {
329 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
330 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
331 _ if file_type.is_symlink() =>
332 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
333 #[cfg(unix)]
335 _ if file_type.is_block_device() =>
336 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
337 #[cfg(unix)]
338 _ if file_type.is_char_device() =>
339 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
340 #[cfg(unix)]
341 _ if file_type.is_fifo() =>
342 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
343 #[cfg(unix)]
344 _ if file_type.is_socket() =>
345 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
346 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
348 }
349 }
350 Err(_) => {
351 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
353 }
354 }
355 }
356
357 fn dir_entry_fields(
358 &self,
359 entry: Either<fs::DirEntry, &'static str>,
360 ) -> InterpResult<'tcx, DirEntry> {
361 let this = self.eval_context_ref();
362 interp_ok(match entry {
363 Either::Left(dir_entry) => {
364 DirEntry {
365 name: dir_entry.file_name(),
366 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
367 #[cfg(unix)]
370 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
371 #[cfg(not(unix))]
372 ino: 0u64,
373 }
374 }
375 Either::Right(special) =>
376 DirEntry {
377 name: special.into(),
378 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
379 ino: 0,
380 },
381 })
382 }
383
384 #[cfg(unix)]
385 fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
386 use std::os::unix::fs::PermissionsExt;
387 interp_ok(fs::Permissions::from_mode(mode))
388 }
389
390 #[cfg(not(unix))]
391 fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> {
392 throw_unsup_format!("setting file permissions is only supported on Unix hosts")
393 }
394}
395
396impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
397pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398 fn open(
399 &mut self,
400 path_raw: &OpTy<'tcx>,
401 flag: &OpTy<'tcx>,
402 varargs: Varargs<'tcx, '_>,
403 ) -> InterpResult<'tcx, Scalar> {
404 let this = self.eval_context_mut();
405
406 let path_raw = this.read_pointer(path_raw)?;
407 let flag = this.read_scalar(flag)?.to_i32()?;
408
409 let path = this.read_path_from_c_str(path_raw)?;
410 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
412 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
413 {
414 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
415 }
416
417 let mut flag = flag;
419
420 let mut options = OpenOptions::new();
421
422 let o_rdonly = this.eval_libc_i32("O_RDONLY");
423 let o_wronly = this.eval_libc_i32("O_WRONLY");
424 let o_rdwr = this.eval_libc_i32("O_RDWR");
425 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
429 throw_unsup_format!("access mode flags on this target are unsupported");
430 }
431 let mut writable = true;
432 let mut readable = true;
433
434 let access_mode = flag & 0b11;
436 flag &= !access_mode;
437
438 if access_mode == o_rdonly {
439 writable = false;
440 options.read(true);
441 } else if access_mode == o_wronly {
442 readable = false;
443 options.write(true);
444 } else if access_mode == o_rdwr {
445 options.read(true).write(true);
446 } else {
447 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
448 }
449
450 let o_append = this.eval_libc_i32("O_APPEND");
451 if flag & o_append == o_append {
452 flag &= !o_append;
453 options.append(true);
454 }
455 let o_trunc = this.eval_libc_i32("O_TRUNC");
456 if flag & o_trunc == o_trunc {
457 flag &= !o_trunc;
458 options.truncate(true);
459 }
460 let o_creat = this.eval_libc_i32("O_CREAT");
461 if flag & o_creat == o_creat {
462 flag &= !o_creat;
463 let ([mode], _) = this.check_varargs(
467 shim_varargs![libc::mode_t],
468 varargs,
469 "open(pathname, O_CREAT, ...)",
470 )?;
471 let mode = this.read_scalar(mode)?.to_u32()?;
472
473 #[cfg(unix)]
474 {
475 use std::os::unix::fs::OpenOptionsExt;
477 options.mode(mode);
478 }
479 #[cfg(not(unix))]
480 {
481 if mode != 0o666 {
483 throw_unsup_format!(
484 "non-default mode 0o{:o} is not supported on non-Unix hosts",
485 mode
486 );
487 }
488 }
489
490 let o_excl = this.eval_libc_i32("O_EXCL");
491 if flag & o_excl == o_excl {
492 flag &= !o_excl;
493 options.create_new(true);
494 } else {
495 options.create(true);
496 }
497 }
498 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
499 if flag & o_cloexec == o_cloexec {
500 flag &= !o_cloexec;
501 }
504 if this.tcx.sess.target.os == Os::Linux {
505 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
506 if flag & o_tmpfile == o_tmpfile {
507 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
509 }
510 }
511
512 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
513 if flag & o_nofollow == o_nofollow {
514 flag &= !o_nofollow;
515 #[cfg(unix)]
516 {
517 use std::os::unix::fs::OpenOptionsExt;
518 options.custom_flags(libc::O_NOFOLLOW);
519 }
520 #[cfg(not(unix))]
524 {
525 if path.is_symlink() {
528 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
529 }
530 }
531 }
532
533 if flag != 0 {
535 throw_unsup_format!("unsupported flags {:#x}", flag);
536 }
537
538 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
540 this.reject_in_isolation("`open`", reject_with)?;
541 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
542 }
543
544 let fd = options
545 .open(path)
546 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
547
548 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
549 }
550
551 fn lseek(
552 &mut self,
553 fd_num: i32,
554 offset: i128,
555 whence: i32,
556 dest: &MPlaceTy<'tcx>,
557 ) -> InterpResult<'tcx> {
558 let this = self.eval_context_mut();
559
560 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
563 if offset < 0 {
564 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
566 } else {
567 SeekFrom::Start(u64::try_from(offset).unwrap())
568 }
569 } else if whence == this.eval_libc_i32("SEEK_CUR") {
570 SeekFrom::Current(i64::try_from(offset).unwrap())
571 } else if whence == this.eval_libc_i32("SEEK_END") {
572 SeekFrom::End(i64::try_from(offset).unwrap())
573 } else {
574 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
575 };
576
577 let communicate = this.machine.communicate();
578
579 let Some(fd) = this.machine.fds.get(fd_num) else {
580 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
581 };
582 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
583 drop(fd);
584
585 let result = this.try_unwrap_io_result(result)?;
586 this.write_int(result, dest)?;
587 interp_ok(())
588 }
589
590 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
591 let this = self.eval_context_mut();
592
593 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
594
595 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
597 this.reject_in_isolation("`unlink`", reject_with)?;
598 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
599 }
600
601 let result = fs::remove_file(path).map(|_| 0);
602 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
603 }
604
605 fn symlink(
606 &mut self,
607 target_op: &OpTy<'tcx>,
608 linkpath_op: &OpTy<'tcx>,
609 ) -> InterpResult<'tcx, Scalar> {
610 #[cfg(unix)]
611 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
612 std::os::unix::fs::symlink(src, dst)
613 }
614
615 #[cfg(windows)]
616 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
617 use std::os::windows::fs;
618 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
619 }
620
621 let this = self.eval_context_mut();
622 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
623 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
624
625 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
627 this.reject_in_isolation("`symlink`", reject_with)?;
628 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
629 }
630
631 let result = create_link(&target, &linkpath).map(|_| 0);
632 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
633 }
634
635 fn linkat(
636 &mut self,
637 oldfd_op: &OpTy<'tcx>,
638 oldpath_op: &OpTy<'tcx>,
639 newfd_op: &OpTy<'tcx>,
640 newpath_op: &OpTy<'tcx>,
641 flags_op: &OpTy<'tcx>,
642 ) -> InterpResult<'tcx, Scalar> {
643 let this = self.eval_context_mut();
644
645 let flags = this.read_scalar(flags_op)?.to_i32()?;
647 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
648 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
649 let oldpath_ptr = this.read_pointer(oldpath_op)?;
650 let newpath_ptr = this.read_pointer(newpath_op)?;
651
652 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
654
655 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
657 this.reject_in_isolation("`linkat`", reject_with)?;
658 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
659 }
660
661 if flags != 0 {
663 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
664 }
665
666 if oldfd != at_fdcwd {
668 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
669 }
670 if oldpath_ptr == Pointer::null() {
671 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
672 }
673 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
674
675 if newfd != at_fdcwd {
677 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
678 }
679 if newpath_ptr == Pointer::null() {
680 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
681 }
682 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
683
684 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
685 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
686 }
687
688 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
689 let this = self.eval_context_mut();
690
691 if !matches!(
692 &this.tcx.sess.target.os,
693 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
694 ) {
695 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
696 }
697
698 let path_scalar = this.read_pointer(path_op)?;
699 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
700
701 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
703 this.reject_in_isolation("`stat`", reject_with)?;
704 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
705 }
706
707 let metadata = match FileMetadata::from_path(this, &path, true)? {
709 Ok(metadata) => metadata,
710 Err(err) => return this.set_errno_and_return_neg1_i32(err),
711 };
712
713 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
714 }
715
716 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
718 let this = self.eval_context_mut();
719
720 if !matches!(
721 &this.tcx.sess.target.os,
722 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
723 ) {
724 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
725 }
726
727 let path_scalar = this.read_pointer(path_op)?;
728 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
729
730 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
732 this.reject_in_isolation("`lstat`", reject_with)?;
733 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
734 }
735
736 let metadata = match FileMetadata::from_path(this, &path, false)? {
737 Ok(metadata) => metadata,
738 Err(err) => return this.set_errno_and_return_neg1_i32(err),
739 };
740
741 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
742 }
743
744 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
745 let this = self.eval_context_mut();
746
747 if !matches!(
748 &this.tcx.sess.target.os,
749 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
750 ) {
751 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
752 }
753
754 let fd = this.read_scalar(fd_op)?.to_i32()?;
755
756 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
758 this.reject_in_isolation("`fstat`", reject_with)?;
759 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
761 }
762
763 let metadata = match FileMetadata::from_fd_num(this, fd)? {
764 Ok(metadata) => metadata,
765 Err(err) => return this.set_errno_and_return_neg1_i32(err),
766 };
767 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
768 }
769
770 fn linux_statx(
771 &mut self,
772 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
778 let this = self.eval_context_mut();
779
780 this.assert_target_os(Os::Linux, "statx");
781
782 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
783 let pathname_ptr = this.read_pointer(pathname_op)?;
784 let flags = this.read_scalar(flags_op)?.to_i32()?;
785 let _mask = this.read_scalar(mask_op)?.to_u32()?;
786 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
787
788 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
790 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
791 }
792
793 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
794
795 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
796 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
798 let empty_path_flag = flags & at_empty_path == at_empty_path;
799 if !(path.is_absolute()
807 || dirfd == this.eval_libc_i32("AT_FDCWD")
808 || (path.as_os_str().is_empty() && empty_path_flag))
809 {
810 throw_unsup_format!(
811 "using statx is only supported with absolute paths, relative paths with the file \
812 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
813 file descriptor"
814 )
815 }
816
817 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
819 this.reject_in_isolation("`statx`", reject_with)?;
820 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
821 LibcError("EACCES")
824 } else {
825 assert!(empty_path_flag);
829 LibcError("EBADF")
830 };
831 return this.set_errno_and_return_neg1_i32(ecode);
832 }
833
834 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
837
838 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
841 FileMetadata::from_fd_num(this, dirfd)?
842 } else {
843 FileMetadata::from_path(this, &path, follow_symlink)?
844 };
845 let metadata = match metadata {
846 Ok(metadata) => metadata,
847 Err(err) => return this.set_errno_and_return_neg1_i32(err),
848 };
849
850 let mut mask = this.eval_libc_u32("STATX_TYPE")
855 | this.eval_libc_u32("STATX_MODE")
856 | this.eval_libc_u32("STATX_SIZE");
857
858 if metadata.ino.is_some() {
860 mask |= this.eval_libc_u32("STATX_INO");
861 }
862 if metadata.nlink.is_some() {
863 mask |= this.eval_libc_u32("STATX_NLINK");
864 }
865 if metadata.uid.is_some() {
866 mask |= this.eval_libc_u32("STATX_UID");
867 }
868 if metadata.gid.is_some() {
869 mask |= this.eval_libc_u32("STATX_GID");
870 }
871 if metadata.blocks.is_some() {
872 mask |= this.eval_libc_u32("STATX_BLOCKS");
873 }
874
875 let (access_sec, access_nsec) = metadata
878 .accessed
879 .map(|tup| {
880 mask |= this.eval_libc_u32("STATX_ATIME");
881 interp_ok(tup)
882 })
883 .unwrap_or_else(|| interp_ok((0, 0)))?;
884
885 let (created_sec, created_nsec) = metadata
886 .created
887 .map(|tup| {
888 mask |= this.eval_libc_u32("STATX_BTIME");
889 interp_ok(tup)
890 })
891 .unwrap_or_else(|| interp_ok((0, 0)))?;
892
893 let (modified_sec, modified_nsec) = metadata
894 .modified
895 .map(|tup| {
896 mask |= this.eval_libc_u32("STATX_MTIME");
897 interp_ok(tup)
898 })
899 .unwrap_or_else(|| interp_ok((0, 0)))?;
900
901 this.write_int_fields_named(
903 &[
904 ("stx_mask", mask.into()),
905 ("stx_mode", metadata.mode.into()),
906 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
907 ("stx_attributes", 0),
908 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
909 ("stx_uid", metadata.uid.unwrap_or(0).into()),
910 ("stx_gid", metadata.gid.unwrap_or(0).into()),
911 ("stx_ino", metadata.ino.unwrap_or(0).into()),
912 ("stx_size", metadata.size.into()),
913 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
914 ("stx_attributes_mask", 0),
915 ("stx_rdev_major", 0),
916 ("stx_rdev_minor", 0),
917 ("stx_dev_major", 0),
918 ("stx_dev_minor", 0),
919 ],
920 &statxbuf,
921 )?;
922 #[rustfmt::skip]
923 this.write_int_fields_named(
924 &[
925 ("tv_sec", access_sec.into()),
926 ("tv_nsec", access_nsec.into()),
927 ],
928 &this.project_field_named(&statxbuf, "stx_atime")?,
929 )?;
930 #[rustfmt::skip]
931 this.write_int_fields_named(
932 &[
933 ("tv_sec", created_sec.into()),
934 ("tv_nsec", created_nsec.into()),
935 ],
936 &this.project_field_named(&statxbuf, "stx_btime")?,
937 )?;
938 #[rustfmt::skip]
939 this.write_int_fields_named(
940 &[
941 ("tv_sec", 0.into()),
942 ("tv_nsec", 0.into()),
943 ],
944 &this.project_field_named(&statxbuf, "stx_ctime")?,
945 )?;
946 #[rustfmt::skip]
947 this.write_int_fields_named(
948 &[
949 ("tv_sec", modified_sec.into()),
950 ("tv_nsec", modified_nsec.into()),
951 ],
952 &this.project_field_named(&statxbuf, "stx_mtime")?,
953 )?;
954
955 interp_ok(Scalar::from_i32(0))
956 }
957
958 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
959 let this = self.eval_context_mut();
960
961 let path_ptr = this.read_pointer(path_op)?;
962 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
963
964 if this.ptr_is_null(path_ptr)? {
965 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
966 }
967 let path = this.read_path_from_c_str(path_ptr)?;
968
969 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
971 this.reject_in_isolation("`chmod`", reject_with)?;
972 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
973 }
974
975 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
976 if let Err(err) = fs::set_permissions(path, permissions) {
977 return this.set_errno_and_return_neg1_i32(err);
978 }
979
980 interp_ok(Scalar::from_i32(0))
981 }
982
983 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
984 let this = self.eval_context_mut();
985
986 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
987 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
988
989 let Some(fd) = this.machine.fds.get(fd_num) else {
990 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
991 };
992 let Some(file) = fd.downcast::<FileHandle>() else {
993 throw_unsup_format!("`fchmod` is only supported on regular files")
995 };
996 if !file.writable && !file.readable {
997 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1000 }
1001 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1002
1003 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1004 if let Err(err) = file.file.set_permissions(permissions) {
1005 return this.set_errno_and_return_neg1_i32(err);
1006 }
1007
1008 interp_ok(Scalar::from_i32(0))
1009 }
1010
1011 fn rename(
1012 &mut self,
1013 oldpath_op: &OpTy<'tcx>,
1014 newpath_op: &OpTy<'tcx>,
1015 ) -> InterpResult<'tcx, Scalar> {
1016 let this = self.eval_context_mut();
1017
1018 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1019 let newpath_ptr = this.read_pointer(newpath_op)?;
1020
1021 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1022 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1023 }
1024
1025 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1026 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1027
1028 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1030 this.reject_in_isolation("`rename`", reject_with)?;
1031 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1032 }
1033
1034 let result = fs::rename(oldpath, newpath).map(|_| 0);
1035
1036 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1037 }
1038
1039 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1040 let this = self.eval_context_mut();
1041
1042 #[cfg_attr(not(unix), allow(unused_variables))]
1043 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1044 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1045 } else {
1046 this.read_scalar(mode_op)?.to_u32()?
1047 };
1048
1049 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1050
1051 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1053 this.reject_in_isolation("`mkdir`", reject_with)?;
1054 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1055 }
1056
1057 #[cfg_attr(not(unix), allow(unused_mut))]
1058 let mut builder = DirBuilder::new();
1059
1060 #[cfg(unix)]
1063 {
1064 use std::os::unix::fs::DirBuilderExt;
1065 builder.mode(mode);
1066 }
1067
1068 let result = builder.create(path).map(|_| 0i32);
1069
1070 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1071 }
1072
1073 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1074 let this = self.eval_context_mut();
1075
1076 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1077
1078 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1080 this.reject_in_isolation("`rmdir`", reject_with)?;
1081 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1082 }
1083
1084 let result = fs::remove_dir(path).map(|_| 0i32);
1085
1086 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1087 }
1088
1089 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1090 let this = self.eval_context_mut();
1091
1092 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1093
1094 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1096 this.reject_in_isolation("`opendir`", reject_with)?;
1097 this.set_last_error(LibcError("EACCES"))?;
1098 return interp_ok(Scalar::null_ptr(this));
1099 }
1100
1101 let result = fs::read_dir(name);
1102
1103 match result {
1104 Ok(dir_iter) => {
1105 let id = this.machine.dirs.insert_new(dir_iter);
1106
1107 interp_ok(Scalar::from_target_usize(id, this))
1111 }
1112 Err(e) => {
1113 this.set_last_error(e)?;
1114 interp_ok(Scalar::null_ptr(this))
1115 }
1116 }
1117 }
1118
1119 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1120 let this = self.eval_context_mut();
1121
1122 if !matches!(
1123 &this.tcx.sess.target.os,
1124 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1125 ) {
1126 throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1127 }
1128
1129 let dirp = this.read_target_usize(dirp_op)?;
1130
1131 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1133 this.reject_in_isolation("`readdir`", reject_with)?;
1134 this.set_last_error(LibcError("EBADF"))?;
1135 this.write_null(dest)?;
1136 return interp_ok(());
1137 }
1138
1139 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1140 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1141 })?;
1142
1143 let entry = match open_dir.next_host_entry() {
1144 Some(Ok(dir_entry)) => {
1145 let dir_entry = this.dir_entry_fields(dir_entry)?;
1146
1147 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1194 let dirent_layout = this.layout_of(dirent_ty)?;
1195 let fields = &dirent_layout.fields;
1196 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1197
1198 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1202 let name_len = u64::try_from(name_bytes.len()).unwrap();
1203 let size = d_name_offset.strict_add(name_len);
1204
1205 let entry = this.allocate_ptr(
1206 Size::from_bytes(size),
1207 dirent_layout.align.abi,
1208 MiriMemoryKind::Runtime.into(),
1209 AllocInit::Uninit,
1210 )?;
1211 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1212
1213 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1216 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1217
1218 let ino_name =
1220 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1221 this.write_int_fields_named(
1222 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1223 &entry,
1224 )?;
1225
1226 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1228 this.write_null(&d_off)?;
1229 }
1230 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1231 this.write_null(&d_seekoff)?;
1232 }
1233 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1234 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1235 }
1236 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1237 this.write_int(dir_entry.d_type, &d_type)?;
1238 }
1239
1240 Some(entry.ptr())
1241 }
1242 None => {
1243 None
1245 }
1246 Some(Err(e)) => {
1247 this.set_last_error(e)?;
1248 None
1249 }
1250 };
1251
1252 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1253 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1254 if let Some(old_entry) = old_entry {
1255 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1256 }
1257
1258 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1259 interp_ok(())
1260 }
1261
1262 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1263 let this = self.eval_context_mut();
1264
1265 let dirp = this.read_target_usize(dirp_op)?;
1266
1267 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1269 this.reject_in_isolation("`closedir`", reject_with)?;
1270 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1271 }
1272
1273 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1274 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1275 };
1276 if let Some(entry) = open_dir.entry.take() {
1277 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1278 }
1279 drop(open_dir);
1281
1282 interp_ok(Scalar::from_i32(0))
1283 }
1284
1285 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1286 let this = self.eval_context_mut();
1287
1288 let Some(fd) = this.machine.fds.get(fd_num) else {
1289 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1290 };
1291 let Some(file) = fd.downcast::<FileHandle>() else {
1292 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1295 };
1296 if !file.writable {
1297 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1299 }
1300 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1301
1302 if let Ok(length) = length.try_into() {
1303 let result = file.file.set_len(length);
1304 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1305 interp_ok(Scalar::from_i32(result))
1306 } else {
1307 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1308 }
1309 }
1310
1311 fn posix_fallocate(
1314 &mut self,
1315 fd_num: i32,
1316 offset: i64,
1317 len: i64,
1318 ) -> InterpResult<'tcx, Scalar> {
1319 let this = self.eval_context_mut();
1320
1321 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1323 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1324 return interp_ok(this.eval_libc("EBADF"));
1326 }
1327
1328 match this.fallocate_impl(fd_num, offset, len)? {
1329 Ok(()) => interp_ok(Scalar::from_i32(0)),
1330 Err(e) => this.io_error_to_errnum(e),
1331 }
1332 }
1333
1334 fn linux_fallocate(
1335 &mut self,
1336 fd: i32,
1337 mode: i32,
1338 offset: i64,
1339 size: i64,
1340 ) -> InterpResult<'tcx, Scalar> {
1341 let this = self.eval_context_mut();
1343
1344 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1346 this.reject_in_isolation("`fallocate`", reject_with)?;
1347 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1349 }
1350
1351 if mode != 0 {
1354 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1355 }
1356
1357 match this.fallocate_impl(fd, offset, size)? {
1358 Ok(()) => interp_ok(Scalar::from_i32(0)),
1359 Err(e) => this.set_errno_and_return_neg1_i32(e),
1360 }
1361 }
1362
1363 fn fallocate_impl(
1365 &mut self,
1366 fd_num: i32,
1367 offset: i64,
1368 len: i64,
1369 ) -> InterpResult<'tcx, Result<(), IoError>> {
1370 let this = self.eval_context_mut();
1371
1372 if offset < 0 || len <= 0 {
1374 return interp_ok(Err(LibcError("EINVAL")));
1375 }
1376
1377 let Some(fd) = this.machine.fds.get(fd_num) else {
1378 return interp_ok(Err(LibcError("EBADF")));
1379 };
1380 let Some(file) = fd.downcast::<FileHandle>() else {
1381 return interp_ok(Err(LibcError("ENODEV")));
1383 };
1384
1385 if !file.writable {
1386 return interp_ok(Err(LibcError("EBADF")));
1387 }
1388
1389 let current_size = match file.file.metadata() {
1390 Ok(metadata) => metadata.len(),
1391 Err(err) => return interp_ok(Err(err.into())),
1392 };
1393
1394 let new_size = match offset.checked_add(len) {
1396 Some(new_size) => u64::try_from(new_size).unwrap(),
1398 None => return interp_ok(Err(LibcError("EFBIG"))), };
1400
1401 if current_size < new_size {
1404 match file.file.set_len(new_size) {
1405 Ok(()) => interp_ok(Ok(())),
1406 Err(err) => interp_ok(Err(err.into())),
1407 }
1408 } else {
1409 interp_ok(Ok(()))
1410 }
1411 }
1412
1413 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1414 let this = self.eval_context_mut();
1420
1421 let fd = this.read_scalar(fd_op)?.to_i32()?;
1422
1423 self.ffullsync_fd(fd)
1424 }
1425
1426 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1427 let this = self.eval_context_mut();
1428 let Some(fd) = this.machine.fds.get(fd_num) else {
1429 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1430 };
1431 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1433 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1434 })?;
1435 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1436
1437 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1438 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1439 }
1440
1441 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1442 let this = self.eval_context_mut();
1443
1444 let fd = this.read_scalar(fd_op)?.to_i32()?;
1445
1446 let Some(fd) = this.machine.fds.get(fd) else {
1447 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1448 };
1449 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1451 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1452 })?;
1453 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1454
1455 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1456 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1457 }
1458
1459 fn futimens(
1462 &mut self,
1463 fd_op: &OpTy<'tcx>,
1464 times_op: &OpTy<'tcx>,
1465 ) -> InterpResult<'tcx, Scalar> {
1466 let this = self.eval_context_mut();
1467
1468 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1469 let times_ptr = this.read_pointer(times_op)?;
1470
1471 let Some(fd) = this.machine.fds.get(fd_num) else {
1472 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1473 };
1474 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1475 err_unsup_format!("`futimens` 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 (access, modified) = if this.ptr_is_null(times_ptr)? {
1480 let now = TimeUpdate::Set(SystemTime::now());
1481 (now, now)
1482 } else {
1483 let timespec = this.libc_ty_layout("timespec");
1484 let access_place = this.deref_pointer_as(times_op, timespec)?;
1485 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1486 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1487 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1488 };
1489 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1490 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1491 };
1492 (access, modified)
1493 };
1494
1495 let mut filetimes = FileTimes::new();
1496 if let TimeUpdate::Set(access) = access {
1497 filetimes = filetimes.set_accessed(access);
1498 }
1499 if let TimeUpdate::Set(modified) = modified {
1500 filetimes = filetimes.set_modified(modified);
1501 }
1502 let result = file.file.set_times(filetimes);
1503 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1504 }
1505
1506 fn sync_file_range(
1507 &mut self,
1508 fd_op: &OpTy<'tcx>,
1509 offset_op: &OpTy<'tcx>,
1510 nbytes_op: &OpTy<'tcx>,
1511 flags_op: &OpTy<'tcx>,
1512 ) -> InterpResult<'tcx, Scalar> {
1513 let this = self.eval_context_mut();
1514
1515 let fd = this.read_scalar(fd_op)?.to_i32()?;
1516 let offset = this.read_scalar(offset_op)?.to_i64()?;
1517 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1518 let flags = this.read_scalar(flags_op)?.to_i32()?;
1519
1520 if offset < 0 || nbytes < 0 {
1521 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1522 }
1523 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1524 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1525 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1526 if flags & allowed_flags != flags {
1527 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1528 }
1529
1530 let Some(fd) = this.machine.fds.get(fd) else {
1531 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1532 };
1533 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1535 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1536 })?;
1537 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1538
1539 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1540 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1541 }
1542
1543 fn readlink(
1544 &mut self,
1545 pathname_op: &OpTy<'tcx>,
1546 buf_op: &OpTy<'tcx>,
1547 bufsize_op: &OpTy<'tcx>,
1548 ) -> InterpResult<'tcx, i64> {
1549 let this = self.eval_context_mut();
1550
1551 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1552 let buf = this.read_pointer(buf_op)?;
1553 let bufsize = this.read_target_usize(bufsize_op)?;
1554
1555 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1557 this.reject_in_isolation("`readlink`", reject_with)?;
1558 this.set_last_error(LibcError("EACCES"))?;
1559 return interp_ok(-1);
1560 }
1561
1562 let result = std::fs::read_link(pathname);
1563 match result {
1564 Ok(resolved) => {
1565 let resolved = this.convert_path(
1569 Cow::Borrowed(resolved.as_ref()),
1570 crate::shims::os_str::PathConversion::HostToTarget,
1571 );
1572 let mut path_bytes = resolved.as_encoded_bytes();
1573 let bufsize: usize = bufsize.try_into().unwrap();
1574 if path_bytes.len() > bufsize {
1575 path_bytes = &path_bytes[..bufsize]
1576 }
1577 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1578 interp_ok(path_bytes.len().try_into().unwrap())
1579 }
1580 Err(e) => {
1581 this.set_last_error(e)?;
1582 interp_ok(-1)
1583 }
1584 }
1585 }
1586
1587 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1588 let this = self.eval_context_mut();
1589 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1592 let error = if let Some(fd) = this.machine.fds.get(fd) {
1593 if fd.is_tty(this.machine.communicate()) {
1594 return interp_ok(Scalar::from_i32(1));
1595 } else {
1596 LibcError("ENOTTY")
1597 }
1598 } else {
1599 LibcError("EBADF")
1601 };
1602 this.set_last_error(error)?;
1603 interp_ok(Scalar::from_i32(0))
1604 }
1605
1606 fn realpath(
1607 &mut self,
1608 path_op: &OpTy<'tcx>,
1609 processed_path_op: &OpTy<'tcx>,
1610 ) -> InterpResult<'tcx, Scalar> {
1611 let this = self.eval_context_mut();
1612 this.assert_target_os_is_unix("realpath");
1613
1614 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1615 let processed_ptr = this.read_pointer(processed_path_op)?;
1616
1617 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1619 this.reject_in_isolation("`realpath`", reject_with)?;
1620 this.set_last_error(LibcError("EACCES"))?;
1621 return interp_ok(Scalar::from_target_usize(0, this));
1622 }
1623
1624 let result = std::fs::canonicalize(pathname);
1625 match result {
1626 Ok(resolved) => {
1627 let path_max = this
1628 .eval_libc_i32("PATH_MAX")
1629 .try_into()
1630 .expect("PATH_MAX does not fit in u64");
1631 let dest = if this.ptr_is_null(processed_ptr)? {
1632 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1642 } else {
1643 let (wrote_path, _) =
1644 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1645
1646 if !wrote_path {
1647 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1651 return interp_ok(Scalar::from_target_usize(0, this));
1652 }
1653 processed_ptr
1654 };
1655
1656 interp_ok(Scalar::from_maybe_pointer(dest, this))
1657 }
1658 Err(e) => {
1659 this.set_last_error(e)?;
1660 interp_ok(Scalar::from_target_usize(0, this))
1661 }
1662 }
1663 }
1664 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1665 use rand::seq::IndexedRandom;
1666
1667 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1669
1670 let this = self.eval_context_mut();
1671 this.assert_target_os_is_unix("mkstemp");
1672
1673 let max_attempts = this.eval_libc_u32("TMP_MAX");
1683
1684 let template_ptr = this.read_pointer(template_op)?;
1687 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1688 let template_bytes = template.as_mut_slice();
1689
1690 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1692 this.reject_in_isolation("`mkstemp`", reject_with)?;
1693 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1694 }
1695
1696 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1698
1699 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1704 let end_pos = template_bytes.len();
1705 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1706
1707 if last_six_char_bytes != suffix_bytes {
1709 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1710 }
1711
1712 const SUBSTITUTIONS: &[char; 62] = &[
1716 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1717 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1718 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1719 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1720 ];
1721
1722 let mut fopts = OpenOptions::new();
1725 fopts.read(true).write(true).create_new(true);
1726
1727 cfg_select! {
1728 unix => {
1729 use std::os::unix::fs::OpenOptionsExt;
1730 fopts.mode(0o600);
1732 fopts.custom_flags(libc::O_EXCL);
1733 }
1734 windows => {
1735 use std::os::windows::fs::OpenOptionsExt;
1736 fopts.share_mode(0);
1738 }
1739 _ => {
1740 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1741 }
1742 }
1743
1744 for _ in 0..max_attempts {
1746 let rng = this.machine.rng.get_mut();
1747
1748 let unique_suffix =
1750 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1751
1752 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1754
1755 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1757
1758 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1760 match file {
1761 Ok(f) => {
1762 let fd = this.machine.fds.insert_new(FileHandle {
1763 file: f,
1764 writable: true,
1765 readable: true,
1766 });
1767 return interp_ok(Scalar::from_i32(fd));
1768 }
1769 Err(e) =>
1770 match e.kind() {
1771 ErrorKind::AlreadyExists => continue,
1773 _ => {
1775 return this.set_errno_and_return_neg1_i32(e);
1778 }
1779 },
1780 }
1781 }
1782
1783 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1785 }
1786}
1787
1788fn extract_sec_and_nsec<'tcx>(
1792 time: std::io::Result<SystemTime>,
1793) -> InterpResult<'tcx, Option<(u64, u32)>> {
1794 match time.ok() {
1795 Some(time) => {
1796 let duration = system_time_to_duration(&time)?;
1797 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1798 }
1799 None => interp_ok(None),
1800 }
1801}
1802
1803fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1804 #[cfg(unix)]
1805 use std::os::unix::fs::FileTypeExt;
1806
1807 if file_type.is_file() {
1808 "S_IFREG"
1809 } else if file_type.is_dir() {
1810 "S_IFDIR"
1811 } else if file_type.is_symlink() {
1812 "S_IFLNK"
1813 } else {
1814 #[cfg(unix)]
1816 {
1817 if file_type.is_socket() {
1818 return "S_IFSOCK";
1819 } else if file_type.is_fifo() {
1820 return "S_IFIFO";
1821 } else if file_type.is_char_device() {
1822 return "S_IFCHR";
1823 } else if file_type.is_block_device() {
1824 return "S_IFBLK";
1825 }
1826 }
1827 "S_IFREG"
1828 }
1829}
1830
1831struct FileMetadata {
1839 mode: u32,
1841 size: u64,
1842 created: Option<(u64, u32)>,
1843 accessed: Option<(u64, u32)>,
1844 modified: Option<(u64, u32)>,
1845 dev: Option<u64>,
1846 ino: Option<u64>,
1847 nlink: Option<u64>,
1848 uid: Option<u32>,
1849 gid: Option<u32>,
1850 blksize: Option<u64>,
1851 blocks: Option<u64>,
1852}
1853
1854impl FileMetadata {
1855 fn from_path<'tcx>(
1856 ecx: &mut MiriInterpCx<'tcx>,
1857 path: &Path,
1858 follow_symlink: bool,
1859 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1860 let metadata =
1861 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1862
1863 FileMetadata::from_meta(ecx, metadata)
1864 }
1865
1866 fn from_fd_num<'tcx>(
1867 ecx: &mut MiriInterpCx<'tcx>,
1868 fd_num: i32,
1869 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1870 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1871 return interp_ok(Err(LibcError("EBADF")));
1872 };
1873 match fd.metadata()? {
1874 Either::Left(host) => Self::from_meta(ecx, host),
1875 Either::Right(name) => Self::synthetic(ecx, name),
1876 }
1877 }
1878
1879 fn synthetic<'tcx>(
1880 ecx: &mut MiriInterpCx<'tcx>,
1881 mode_name: &str,
1882 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1883 let mode = ecx.eval_libc(mode_name);
1884 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1885 let mode = mode | 0o666;
1887 interp_ok(Ok(FileMetadata {
1888 mode,
1889 size: 0,
1890 created: None,
1891 accessed: None,
1892 modified: None,
1893 dev: None,
1894 uid: None,
1895 gid: None,
1896 blksize: None,
1897 blocks: None,
1898 ino: None,
1899 nlink: None,
1900 }))
1901 }
1902
1903 fn from_meta<'tcx>(
1904 ecx: &mut MiriInterpCx<'tcx>,
1905 metadata: Result<std::fs::Metadata, std::io::Error>,
1906 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1907 let metadata = match metadata {
1908 Ok(metadata) => metadata,
1909 Err(e) => {
1910 return interp_ok(Err(e.into()));
1911 }
1912 };
1913
1914 let file_type = metadata.file_type();
1915 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1916 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1917
1918 let size = metadata.len();
1919
1920 let created = extract_sec_and_nsec(metadata.created())?;
1921 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1922 let modified = extract_sec_and_nsec(metadata.modified())?;
1923
1924 cfg_select! {
1927 unix => {
1928 use std::os::unix::fs::{MetadataExt, PermissionsExt};
1929
1930 let dev = metadata.dev();
1931 let ino = metadata.ino();
1932 let nlink = metadata.nlink();
1933 let uid = metadata.uid();
1934 let gid = metadata.gid();
1935 let blksize = metadata.blksize();
1936 let blocks = metadata.blocks();
1937
1938 mode |= metadata.permissions().mode();
1939
1940 interp_ok(Ok(FileMetadata {
1941 mode,
1942 size,
1943 created,
1944 accessed,
1945 modified,
1946 dev: Some(dev),
1947 ino: Some(ino),
1948 nlink: Some(nlink),
1949 uid: Some(uid),
1950 gid: Some(gid),
1951 blksize: Some(blksize),
1952 blocks: Some(blocks),
1953 }))
1954 }
1955 _ => {
1956 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1958
1959 interp_ok(Ok(FileMetadata {
1960 mode,
1961 size,
1962 created,
1963 accessed,
1964 modified,
1965 dev: None,
1966 ino: None,
1967 nlink: None,
1968 uid: None,
1969 gid: None,
1970 blksize: None,
1971 blocks: None,
1972 }))
1973 }
1974 }
1975 }
1976}