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(
465 if this.libc_ty_layout("mode_t").size.bytes() >= 4 {
466 shim_varargs![libc::mode_t]
468 } else {
469 shim_varargs![i32]
472 },
473 varargs,
474 "open(pathname, O_CREAT, ...)",
475 )?;
476 let mode = this.read_scalar(mode)?.to_u32()?;
477
478 #[cfg(unix)]
479 {
480 use std::os::unix::fs::OpenOptionsExt;
482 options.mode(mode);
483 }
484 #[cfg(not(unix))]
485 {
486 if mode != 0o666 {
488 throw_unsup_format!(
489 "non-default mode 0o{:o} is not supported on non-Unix hosts",
490 mode
491 );
492 }
493 }
494
495 let o_excl = this.eval_libc_i32("O_EXCL");
496 if flag & o_excl == o_excl {
497 flag &= !o_excl;
498 options.create_new(true);
499 } else {
500 options.create(true);
501 }
502 }
503 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
504 if flag & o_cloexec == o_cloexec {
505 flag &= !o_cloexec;
506 }
509 if this.tcx.sess.target.os == Os::Linux {
510 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
511 if flag & o_tmpfile == o_tmpfile {
512 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
514 }
515 }
516
517 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
518 if flag & o_nofollow == o_nofollow {
519 flag &= !o_nofollow;
520 #[cfg(unix)]
521 {
522 use std::os::unix::fs::OpenOptionsExt;
523 options.custom_flags(libc::O_NOFOLLOW);
524 }
525 #[cfg(not(unix))]
529 {
530 if path.is_symlink() {
533 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
534 }
535 }
536 }
537
538 if flag != 0 {
540 throw_unsup_format!("unsupported flags {:#x}", flag);
541 }
542
543 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
545 this.reject_in_isolation("`open`", reject_with)?;
546 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
547 }
548
549 let fd = options
550 .open(path)
551 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
552
553 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
554 }
555
556 fn lseek(
557 &mut self,
558 fd_num: i32,
559 offset: i128,
560 whence: i32,
561 dest: &MPlaceTy<'tcx>,
562 ) -> InterpResult<'tcx> {
563 let this = self.eval_context_mut();
564
565 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
568 if offset < 0 {
569 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
571 } else {
572 SeekFrom::Start(u64::try_from(offset).unwrap())
573 }
574 } else if whence == this.eval_libc_i32("SEEK_CUR") {
575 SeekFrom::Current(i64::try_from(offset).unwrap())
576 } else if whence == this.eval_libc_i32("SEEK_END") {
577 SeekFrom::End(i64::try_from(offset).unwrap())
578 } else {
579 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
580 };
581
582 let communicate = this.machine.communicate();
583
584 let Some(fd) = this.machine.fds.get(fd_num) else {
585 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
586 };
587 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
588 drop(fd);
589
590 let result = this.try_unwrap_io_result(result)?;
591 this.write_int(result, dest)?;
592 interp_ok(())
593 }
594
595 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
596 let this = self.eval_context_mut();
597
598 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
599
600 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
602 this.reject_in_isolation("`unlink`", reject_with)?;
603 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
604 }
605
606 let result = fs::remove_file(path).map(|_| 0);
607 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
608 }
609
610 fn symlink(
611 &mut self,
612 target_op: &OpTy<'tcx>,
613 linkpath_op: &OpTy<'tcx>,
614 ) -> InterpResult<'tcx, Scalar> {
615 #[cfg(unix)]
616 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
617 std::os::unix::fs::symlink(src, dst)
618 }
619
620 #[cfg(windows)]
621 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
622 use std::os::windows::fs;
623 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
624 }
625
626 let this = self.eval_context_mut();
627 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
628 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
629
630 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
632 this.reject_in_isolation("`symlink`", reject_with)?;
633 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
634 }
635
636 let result = create_link(&target, &linkpath).map(|_| 0);
637 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
638 }
639
640 fn linkat(
641 &mut self,
642 oldfd_op: &OpTy<'tcx>,
643 oldpath_op: &OpTy<'tcx>,
644 newfd_op: &OpTy<'tcx>,
645 newpath_op: &OpTy<'tcx>,
646 flags_op: &OpTy<'tcx>,
647 ) -> InterpResult<'tcx, Scalar> {
648 let this = self.eval_context_mut();
649
650 let flags = this.read_scalar(flags_op)?.to_i32()?;
652 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
653 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
654 let oldpath_ptr = this.read_pointer(oldpath_op)?;
655 let newpath_ptr = this.read_pointer(newpath_op)?;
656
657 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
659
660 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
662 this.reject_in_isolation("`linkat`", reject_with)?;
663 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
664 }
665
666 if flags != 0 {
668 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
669 }
670
671 if oldfd != at_fdcwd {
673 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
674 }
675 if oldpath_ptr == Pointer::null() {
676 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
677 }
678 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
679
680 if newfd != at_fdcwd {
682 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
683 }
684 if newpath_ptr == Pointer::null() {
685 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
686 }
687 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
688
689 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
690 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
691 }
692
693 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
694 let this = self.eval_context_mut();
695
696 if !matches!(
697 &this.tcx.sess.target.os,
698 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
699 ) {
700 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
701 }
702
703 let path_scalar = this.read_pointer(path_op)?;
704 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
705
706 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
708 this.reject_in_isolation("`stat`", reject_with)?;
709 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
710 }
711
712 let metadata = match FileMetadata::from_path(this, &path, true)? {
714 Ok(metadata) => metadata,
715 Err(err) => return this.set_errno_and_return_neg1_i32(err),
716 };
717
718 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
719 }
720
721 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
723 let this = self.eval_context_mut();
724
725 if !matches!(
726 &this.tcx.sess.target.os,
727 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
728 ) {
729 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
730 }
731
732 let path_scalar = this.read_pointer(path_op)?;
733 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
734
735 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
737 this.reject_in_isolation("`lstat`", reject_with)?;
738 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
739 }
740
741 let metadata = match FileMetadata::from_path(this, &path, false)? {
742 Ok(metadata) => metadata,
743 Err(err) => return this.set_errno_and_return_neg1_i32(err),
744 };
745
746 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
747 }
748
749 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
750 let this = self.eval_context_mut();
751
752 if !matches!(
753 &this.tcx.sess.target.os,
754 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
755 ) {
756 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
757 }
758
759 let fd = this.read_scalar(fd_op)?.to_i32()?;
760
761 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
763 this.reject_in_isolation("`fstat`", reject_with)?;
764 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
766 }
767
768 let metadata = match FileMetadata::from_fd_num(this, fd)? {
769 Ok(metadata) => metadata,
770 Err(err) => return this.set_errno_and_return_neg1_i32(err),
771 };
772 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
773 }
774
775 fn linux_statx(
776 &mut self,
777 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
783 let this = self.eval_context_mut();
784
785 this.assert_target_os(Os::Linux, "statx");
786
787 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
788 let pathname_ptr = this.read_pointer(pathname_op)?;
789 let flags = this.read_scalar(flags_op)?.to_i32()?;
790 let _mask = this.read_scalar(mask_op)?.to_u32()?;
791 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
792
793 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
795 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
796 }
797
798 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
799
800 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
801 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
803 let empty_path_flag = flags & at_empty_path == at_empty_path;
804 if !(path.is_absolute()
812 || dirfd == this.eval_libc_i32("AT_FDCWD")
813 || (path.as_os_str().is_empty() && empty_path_flag))
814 {
815 throw_unsup_format!(
816 "using statx is only supported with absolute paths, relative paths with the file \
817 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
818 file descriptor"
819 )
820 }
821
822 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
824 this.reject_in_isolation("`statx`", reject_with)?;
825 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
826 LibcError("EACCES")
829 } else {
830 assert!(empty_path_flag);
834 LibcError("EBADF")
835 };
836 return this.set_errno_and_return_neg1_i32(ecode);
837 }
838
839 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
842
843 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
846 FileMetadata::from_fd_num(this, dirfd)?
847 } else {
848 FileMetadata::from_path(this, &path, follow_symlink)?
849 };
850 let metadata = match metadata {
851 Ok(metadata) => metadata,
852 Err(err) => return this.set_errno_and_return_neg1_i32(err),
853 };
854
855 let mut mask = this.eval_libc_u32("STATX_TYPE")
860 | this.eval_libc_u32("STATX_MODE")
861 | this.eval_libc_u32("STATX_SIZE");
862
863 if metadata.ino.is_some() {
865 mask |= this.eval_libc_u32("STATX_INO");
866 }
867 if metadata.nlink.is_some() {
868 mask |= this.eval_libc_u32("STATX_NLINK");
869 }
870 if metadata.uid.is_some() {
871 mask |= this.eval_libc_u32("STATX_UID");
872 }
873 if metadata.gid.is_some() {
874 mask |= this.eval_libc_u32("STATX_GID");
875 }
876 if metadata.blocks.is_some() {
877 mask |= this.eval_libc_u32("STATX_BLOCKS");
878 }
879
880 let (access_sec, access_nsec) = metadata
883 .accessed
884 .map(|tup| {
885 mask |= this.eval_libc_u32("STATX_ATIME");
886 interp_ok(tup)
887 })
888 .unwrap_or_else(|| interp_ok((0, 0)))?;
889
890 let (created_sec, created_nsec) = metadata
891 .created
892 .map(|tup| {
893 mask |= this.eval_libc_u32("STATX_BTIME");
894 interp_ok(tup)
895 })
896 .unwrap_or_else(|| interp_ok((0, 0)))?;
897
898 let (modified_sec, modified_nsec) = metadata
899 .modified
900 .map(|tup| {
901 mask |= this.eval_libc_u32("STATX_MTIME");
902 interp_ok(tup)
903 })
904 .unwrap_or_else(|| interp_ok((0, 0)))?;
905
906 this.write_int_fields_named(
908 &[
909 ("stx_mask", mask.into()),
910 ("stx_mode", metadata.mode.into()),
911 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
912 ("stx_attributes", 0),
913 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
914 ("stx_uid", metadata.uid.unwrap_or(0).into()),
915 ("stx_gid", metadata.gid.unwrap_or(0).into()),
916 ("stx_ino", metadata.ino.unwrap_or(0).into()),
917 ("stx_size", metadata.size.into()),
918 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
919 ("stx_attributes_mask", 0),
920 ("stx_rdev_major", 0),
921 ("stx_rdev_minor", 0),
922 ("stx_dev_major", 0),
923 ("stx_dev_minor", 0),
924 ],
925 &statxbuf,
926 )?;
927 #[rustfmt::skip]
928 this.write_int_fields_named(
929 &[
930 ("tv_sec", access_sec.into()),
931 ("tv_nsec", access_nsec.into()),
932 ],
933 &this.project_field_named(&statxbuf, "stx_atime")?,
934 )?;
935 #[rustfmt::skip]
936 this.write_int_fields_named(
937 &[
938 ("tv_sec", created_sec.into()),
939 ("tv_nsec", created_nsec.into()),
940 ],
941 &this.project_field_named(&statxbuf, "stx_btime")?,
942 )?;
943 #[rustfmt::skip]
944 this.write_int_fields_named(
945 &[
946 ("tv_sec", 0.into()),
947 ("tv_nsec", 0.into()),
948 ],
949 &this.project_field_named(&statxbuf, "stx_ctime")?,
950 )?;
951 #[rustfmt::skip]
952 this.write_int_fields_named(
953 &[
954 ("tv_sec", modified_sec.into()),
955 ("tv_nsec", modified_nsec.into()),
956 ],
957 &this.project_field_named(&statxbuf, "stx_mtime")?,
958 )?;
959
960 interp_ok(Scalar::from_i32(0))
961 }
962
963 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
964 let this = self.eval_context_mut();
965
966 let path_ptr = this.read_pointer(path_op)?;
967 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
968
969 if this.ptr_is_null(path_ptr)? {
970 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
971 }
972 let path = this.read_path_from_c_str(path_ptr)?;
973
974 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
976 this.reject_in_isolation("`chmod`", reject_with)?;
977 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
978 }
979
980 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
981 if let Err(err) = fs::set_permissions(path, permissions) {
982 return this.set_errno_and_return_neg1_i32(err);
983 }
984
985 interp_ok(Scalar::from_i32(0))
986 }
987
988 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
989 let this = self.eval_context_mut();
990
991 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
992 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
993
994 let Some(fd) = this.machine.fds.get(fd_num) else {
995 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
996 };
997 let Some(file) = fd.downcast::<FileHandle>() else {
998 throw_unsup_format!("`fchmod` is only supported on regular files")
1000 };
1001 if !file.writable && !file.readable {
1002 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1005 }
1006 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1007
1008 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1009 if let Err(err) = file.file.set_permissions(permissions) {
1010 return this.set_errno_and_return_neg1_i32(err);
1011 }
1012
1013 interp_ok(Scalar::from_i32(0))
1014 }
1015
1016 fn rename(
1017 &mut self,
1018 oldpath_op: &OpTy<'tcx>,
1019 newpath_op: &OpTy<'tcx>,
1020 ) -> InterpResult<'tcx, Scalar> {
1021 let this = self.eval_context_mut();
1022
1023 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1024 let newpath_ptr = this.read_pointer(newpath_op)?;
1025
1026 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1027 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1028 }
1029
1030 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1031 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1032
1033 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1035 this.reject_in_isolation("`rename`", reject_with)?;
1036 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1037 }
1038
1039 let result = fs::rename(oldpath, newpath).map(|_| 0);
1040
1041 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1042 }
1043
1044 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1045 let this = self.eval_context_mut();
1046
1047 #[cfg_attr(not(unix), allow(unused_variables))]
1048 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1049 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1050 } else {
1051 this.read_scalar(mode_op)?.to_u32()?
1052 };
1053
1054 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1055
1056 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1058 this.reject_in_isolation("`mkdir`", reject_with)?;
1059 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1060 }
1061
1062 #[cfg_attr(not(unix), allow(unused_mut))]
1063 let mut builder = DirBuilder::new();
1064
1065 #[cfg(unix)]
1068 {
1069 use std::os::unix::fs::DirBuilderExt;
1070 builder.mode(mode);
1071 }
1072
1073 let result = builder.create(path).map(|_| 0i32);
1074
1075 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1076 }
1077
1078 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1079 let this = self.eval_context_mut();
1080
1081 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1082
1083 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1085 this.reject_in_isolation("`rmdir`", reject_with)?;
1086 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1087 }
1088
1089 let result = fs::remove_dir(path).map(|_| 0i32);
1090
1091 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1092 }
1093
1094 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1095 let this = self.eval_context_mut();
1096
1097 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1098
1099 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1101 this.reject_in_isolation("`opendir`", reject_with)?;
1102 this.set_last_error(LibcError("EACCES"))?;
1103 return interp_ok(Scalar::null_ptr(this));
1104 }
1105
1106 let result = fs::read_dir(name);
1107
1108 match result {
1109 Ok(dir_iter) => {
1110 let id = this.machine.dirs.insert_new(dir_iter);
1111
1112 interp_ok(Scalar::from_target_usize(id, this))
1116 }
1117 Err(e) => {
1118 this.set_last_error(e)?;
1119 interp_ok(Scalar::null_ptr(this))
1120 }
1121 }
1122 }
1123
1124 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1125 let this = self.eval_context_mut();
1126
1127 if !matches!(
1128 &this.tcx.sess.target.os,
1129 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1130 ) {
1131 throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1132 }
1133
1134 let dirp = this.read_target_usize(dirp_op)?;
1135
1136 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1138 this.reject_in_isolation("`readdir`", reject_with)?;
1139 this.set_last_error(LibcError("EBADF"))?;
1140 this.write_null(dest)?;
1141 return interp_ok(());
1142 }
1143
1144 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1145 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1146 })?;
1147
1148 let entry = match open_dir.next_host_entry() {
1149 Some(Ok(dir_entry)) => {
1150 let dir_entry = this.dir_entry_fields(dir_entry)?;
1151
1152 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1199 let dirent_layout = this.layout_of(dirent_ty)?;
1200 let fields = &dirent_layout.fields;
1201 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1202
1203 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1207 let name_len = u64::try_from(name_bytes.len()).unwrap();
1208 let size = d_name_offset.strict_add(name_len);
1209
1210 let entry = this.allocate_ptr(
1211 Size::from_bytes(size),
1212 dirent_layout.align.abi,
1213 MiriMemoryKind::Runtime.into(),
1214 AllocInit::Uninit,
1215 )?;
1216 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1217
1218 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1221 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1222
1223 let ino_name =
1225 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1226 this.write_int_fields_named(
1227 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1228 &entry,
1229 )?;
1230
1231 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1233 this.write_null(&d_off)?;
1234 }
1235 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1236 this.write_null(&d_seekoff)?;
1237 }
1238 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1239 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1240 }
1241 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1242 this.write_int(dir_entry.d_type, &d_type)?;
1243 }
1244
1245 Some(entry.ptr())
1246 }
1247 None => {
1248 None
1250 }
1251 Some(Err(e)) => {
1252 this.set_last_error(e)?;
1253 None
1254 }
1255 };
1256
1257 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1258 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1259 if let Some(old_entry) = old_entry {
1260 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1261 }
1262
1263 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1264 interp_ok(())
1265 }
1266
1267 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1268 let this = self.eval_context_mut();
1269
1270 let dirp = this.read_target_usize(dirp_op)?;
1271
1272 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1274 this.reject_in_isolation("`closedir`", reject_with)?;
1275 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1276 }
1277
1278 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1279 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1280 };
1281 if let Some(entry) = open_dir.entry.take() {
1282 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1283 }
1284 drop(open_dir);
1286
1287 interp_ok(Scalar::from_i32(0))
1288 }
1289
1290 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1291 let this = self.eval_context_mut();
1292
1293 let Some(fd) = this.machine.fds.get(fd_num) else {
1294 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1295 };
1296 let Some(file) = fd.downcast::<FileHandle>() else {
1297 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1300 };
1301 if !file.writable {
1302 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1304 }
1305 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1306
1307 if let Ok(length) = length.try_into() {
1308 let result = file.file.set_len(length);
1309 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1310 interp_ok(Scalar::from_i32(result))
1311 } else {
1312 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1313 }
1314 }
1315
1316 fn posix_fallocate(
1319 &mut self,
1320 fd_num: i32,
1321 offset: i64,
1322 len: i64,
1323 ) -> InterpResult<'tcx, Scalar> {
1324 let this = self.eval_context_mut();
1325
1326 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1328 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1329 return interp_ok(this.eval_libc("EBADF"));
1331 }
1332
1333 match this.fallocate_impl(fd_num, offset, len)? {
1334 Ok(()) => interp_ok(Scalar::from_i32(0)),
1335 Err(e) => this.io_error_to_errnum(e),
1336 }
1337 }
1338
1339 fn linux_fallocate(
1340 &mut self,
1341 fd: i32,
1342 mode: i32,
1343 offset: i64,
1344 size: i64,
1345 ) -> InterpResult<'tcx, Scalar> {
1346 let this = self.eval_context_mut();
1348
1349 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1351 this.reject_in_isolation("`fallocate`", reject_with)?;
1352 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1354 }
1355
1356 if mode != 0 {
1359 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1360 }
1361
1362 match this.fallocate_impl(fd, offset, size)? {
1363 Ok(()) => interp_ok(Scalar::from_i32(0)),
1364 Err(e) => this.set_errno_and_return_neg1_i32(e),
1365 }
1366 }
1367
1368 fn fallocate_impl(
1370 &mut self,
1371 fd_num: i32,
1372 offset: i64,
1373 len: i64,
1374 ) -> InterpResult<'tcx, Result<(), IoError>> {
1375 let this = self.eval_context_mut();
1376
1377 if offset < 0 || len <= 0 {
1379 return interp_ok(Err(LibcError("EINVAL")));
1380 }
1381
1382 let Some(fd) = this.machine.fds.get(fd_num) else {
1383 return interp_ok(Err(LibcError("EBADF")));
1384 };
1385 let Some(file) = fd.downcast::<FileHandle>() else {
1386 return interp_ok(Err(LibcError("ENODEV")));
1388 };
1389
1390 if !file.writable {
1391 return interp_ok(Err(LibcError("EBADF")));
1392 }
1393
1394 let current_size = match file.file.metadata() {
1395 Ok(metadata) => metadata.len(),
1396 Err(err) => return interp_ok(Err(err.into())),
1397 };
1398
1399 let new_size = match offset.checked_add(len) {
1401 Some(new_size) => u64::try_from(new_size).unwrap(),
1403 None => return interp_ok(Err(LibcError("EFBIG"))), };
1405
1406 if current_size < new_size {
1409 match file.file.set_len(new_size) {
1410 Ok(()) => interp_ok(Ok(())),
1411 Err(err) => interp_ok(Err(err.into())),
1412 }
1413 } else {
1414 interp_ok(Ok(()))
1415 }
1416 }
1417
1418 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1419 let this = self.eval_context_mut();
1425
1426 let fd = this.read_scalar(fd_op)?.to_i32()?;
1427
1428 self.ffullsync_fd(fd)
1429 }
1430
1431 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1432 let this = self.eval_context_mut();
1433 let Some(fd) = this.machine.fds.get(fd_num) 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!("`fsync` 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_all);
1443 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1444 }
1445
1446 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1447 let this = self.eval_context_mut();
1448
1449 let fd = this.read_scalar(fd_op)?.to_i32()?;
1450
1451 let Some(fd) = this.machine.fds.get(fd) else {
1452 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1453 };
1454 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1456 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1457 })?;
1458 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1459
1460 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1461 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1462 }
1463
1464 fn futimens(
1467 &mut self,
1468 fd_op: &OpTy<'tcx>,
1469 times_op: &OpTy<'tcx>,
1470 ) -> InterpResult<'tcx, Scalar> {
1471 let this = self.eval_context_mut();
1472
1473 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1474 let times_ptr = this.read_pointer(times_op)?;
1475
1476 let Some(fd) = this.machine.fds.get(fd_num) else {
1477 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1478 };
1479 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1480 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1481 })?;
1482 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1483
1484 let (access, modified) = if this.ptr_is_null(times_ptr)? {
1485 let now = TimeUpdate::Set(SystemTime::now());
1486 (now, now)
1487 } else {
1488 let timespec = this.libc_ty_layout("timespec");
1489 let access_place = this.deref_pointer_as(times_op, timespec)?;
1490 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1491 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1492 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1493 };
1494 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1495 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1496 };
1497 (access, modified)
1498 };
1499
1500 let mut filetimes = FileTimes::new();
1501 if let TimeUpdate::Set(access) = access {
1502 filetimes = filetimes.set_accessed(access);
1503 }
1504 if let TimeUpdate::Set(modified) = modified {
1505 filetimes = filetimes.set_modified(modified);
1506 }
1507 let result = file.file.set_times(filetimes);
1508 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1509 }
1510
1511 fn sync_file_range(
1512 &mut self,
1513 fd_op: &OpTy<'tcx>,
1514 offset_op: &OpTy<'tcx>,
1515 nbytes_op: &OpTy<'tcx>,
1516 flags_op: &OpTy<'tcx>,
1517 ) -> InterpResult<'tcx, Scalar> {
1518 let this = self.eval_context_mut();
1519
1520 let fd = this.read_scalar(fd_op)?.to_i32()?;
1521 let offset = this.read_scalar(offset_op)?.to_i64()?;
1522 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1523 let flags = this.read_scalar(flags_op)?.to_i32()?;
1524
1525 if offset < 0 || nbytes < 0 {
1526 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1527 }
1528 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1529 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1530 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1531 if flags & allowed_flags != flags {
1532 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1533 }
1534
1535 let Some(fd) = this.machine.fds.get(fd) else {
1536 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1537 };
1538 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1540 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1541 })?;
1542 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1543
1544 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1545 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1546 }
1547
1548 fn readlink(
1549 &mut self,
1550 pathname_op: &OpTy<'tcx>,
1551 buf_op: &OpTy<'tcx>,
1552 bufsize_op: &OpTy<'tcx>,
1553 ) -> InterpResult<'tcx, i64> {
1554 let this = self.eval_context_mut();
1555
1556 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1557 let buf = this.read_pointer(buf_op)?;
1558 let bufsize = this.read_target_usize(bufsize_op)?;
1559
1560 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1562 this.reject_in_isolation("`readlink`", reject_with)?;
1563 this.set_last_error(LibcError("EACCES"))?;
1564 return interp_ok(-1);
1565 }
1566
1567 let result = std::fs::read_link(pathname);
1568 match result {
1569 Ok(resolved) => {
1570 let resolved = this.convert_path(
1574 Cow::Borrowed(resolved.as_ref()),
1575 crate::shims::os_str::PathConversion::HostToTarget,
1576 );
1577 let mut path_bytes = resolved.as_encoded_bytes();
1578 let bufsize: usize = bufsize.try_into().unwrap();
1579 if path_bytes.len() > bufsize {
1580 path_bytes = &path_bytes[..bufsize]
1581 }
1582 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1583 interp_ok(path_bytes.len().try_into().unwrap())
1584 }
1585 Err(e) => {
1586 this.set_last_error(e)?;
1587 interp_ok(-1)
1588 }
1589 }
1590 }
1591
1592 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1593 let this = self.eval_context_mut();
1594 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1597 let error = if let Some(fd) = this.machine.fds.get(fd) {
1598 if fd.is_tty(this.machine.communicate()) {
1599 return interp_ok(Scalar::from_i32(1));
1600 } else {
1601 LibcError("ENOTTY")
1602 }
1603 } else {
1604 LibcError("EBADF")
1606 };
1607 this.set_last_error(error)?;
1608 interp_ok(Scalar::from_i32(0))
1609 }
1610
1611 fn realpath(
1612 &mut self,
1613 path_op: &OpTy<'tcx>,
1614 processed_path_op: &OpTy<'tcx>,
1615 ) -> InterpResult<'tcx, Scalar> {
1616 let this = self.eval_context_mut();
1617 this.assert_target_os_is_unix("realpath");
1618
1619 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1620 let processed_ptr = this.read_pointer(processed_path_op)?;
1621
1622 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1624 this.reject_in_isolation("`realpath`", reject_with)?;
1625 this.set_last_error(LibcError("EACCES"))?;
1626 return interp_ok(Scalar::from_target_usize(0, this));
1627 }
1628
1629 let result = std::fs::canonicalize(pathname);
1630 match result {
1631 Ok(resolved) => {
1632 let path_max = this
1633 .eval_libc_i32("PATH_MAX")
1634 .try_into()
1635 .expect("PATH_MAX does not fit in u64");
1636 let dest = if this.ptr_is_null(processed_ptr)? {
1637 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1647 } else {
1648 let (wrote_path, _) =
1649 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1650
1651 if !wrote_path {
1652 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1656 return interp_ok(Scalar::from_target_usize(0, this));
1657 }
1658 processed_ptr
1659 };
1660
1661 interp_ok(Scalar::from_maybe_pointer(dest, this))
1662 }
1663 Err(e) => {
1664 this.set_last_error(e)?;
1665 interp_ok(Scalar::from_target_usize(0, this))
1666 }
1667 }
1668 }
1669 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1670 use rand::seq::IndexedRandom;
1671
1672 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1674
1675 let this = self.eval_context_mut();
1676 this.assert_target_os_is_unix("mkstemp");
1677
1678 let max_attempts = this.eval_libc_u32("TMP_MAX");
1688
1689 let template_ptr = this.read_pointer(template_op)?;
1692 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1693 let template_bytes = template.as_mut_slice();
1694
1695 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1697 this.reject_in_isolation("`mkstemp`", reject_with)?;
1698 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1699 }
1700
1701 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1703
1704 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1709 let end_pos = template_bytes.len();
1710 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1711
1712 if last_six_char_bytes != suffix_bytes {
1714 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1715 }
1716
1717 const SUBSTITUTIONS: &[char; 62] = &[
1721 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1722 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1723 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1724 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1725 ];
1726
1727 let mut fopts = OpenOptions::new();
1730 fopts.read(true).write(true).create_new(true);
1731
1732 cfg_select! {
1733 unix => {
1734 use std::os::unix::fs::OpenOptionsExt;
1735 fopts.mode(0o600);
1737 fopts.custom_flags(libc::O_EXCL);
1738 }
1739 windows => {
1740 use std::os::windows::fs::OpenOptionsExt;
1741 fopts.share_mode(0);
1743 }
1744 _ => {
1745 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1746 }
1747 }
1748
1749 for _ in 0..max_attempts {
1751 let rng = this.machine.rng.get_mut();
1752
1753 let unique_suffix =
1755 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1756
1757 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1759
1760 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1762
1763 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1765 match file {
1766 Ok(f) => {
1767 let fd = this.machine.fds.insert_new(FileHandle {
1768 file: f,
1769 writable: true,
1770 readable: true,
1771 });
1772 return interp_ok(Scalar::from_i32(fd));
1773 }
1774 Err(e) =>
1775 match e.kind() {
1776 ErrorKind::AlreadyExists => continue,
1778 _ => {
1780 return this.set_errno_and_return_neg1_i32(e);
1783 }
1784 },
1785 }
1786 }
1787
1788 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1790 }
1791}
1792
1793fn extract_sec_and_nsec<'tcx>(
1797 time: std::io::Result<SystemTime>,
1798) -> InterpResult<'tcx, Option<(u64, u32)>> {
1799 match time.ok() {
1800 Some(time) => {
1801 let duration = system_time_to_duration(&time)?;
1802 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1803 }
1804 None => interp_ok(None),
1805 }
1806}
1807
1808fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1809 #[cfg(unix)]
1810 use std::os::unix::fs::FileTypeExt;
1811
1812 if file_type.is_file() {
1813 "S_IFREG"
1814 } else if file_type.is_dir() {
1815 "S_IFDIR"
1816 } else if file_type.is_symlink() {
1817 "S_IFLNK"
1818 } else {
1819 #[cfg(unix)]
1821 {
1822 if file_type.is_socket() {
1823 return "S_IFSOCK";
1824 } else if file_type.is_fifo() {
1825 return "S_IFIFO";
1826 } else if file_type.is_char_device() {
1827 return "S_IFCHR";
1828 } else if file_type.is_block_device() {
1829 return "S_IFBLK";
1830 }
1831 }
1832 "S_IFREG"
1833 }
1834}
1835
1836struct FileMetadata {
1844 mode: u32,
1846 size: u64,
1847 created: Option<(u64, u32)>,
1848 accessed: Option<(u64, u32)>,
1849 modified: Option<(u64, u32)>,
1850 dev: Option<u64>,
1851 ino: Option<u64>,
1852 nlink: Option<u64>,
1853 uid: Option<u32>,
1854 gid: Option<u32>,
1855 blksize: Option<u64>,
1856 blocks: Option<u64>,
1857}
1858
1859impl FileMetadata {
1860 fn from_path<'tcx>(
1861 ecx: &mut MiriInterpCx<'tcx>,
1862 path: &Path,
1863 follow_symlink: bool,
1864 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1865 let metadata =
1866 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1867
1868 FileMetadata::from_meta(ecx, metadata)
1869 }
1870
1871 fn from_fd_num<'tcx>(
1872 ecx: &mut MiriInterpCx<'tcx>,
1873 fd_num: i32,
1874 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1875 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1876 return interp_ok(Err(LibcError("EBADF")));
1877 };
1878 match fd.metadata()? {
1879 Either::Left(host) => Self::from_meta(ecx, host),
1880 Either::Right(name) => Self::synthetic(ecx, name),
1881 }
1882 }
1883
1884 fn synthetic<'tcx>(
1885 ecx: &mut MiriInterpCx<'tcx>,
1886 mode_name: &str,
1887 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1888 let mode = ecx.eval_libc(mode_name);
1889 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1890 let mode = mode | 0o666;
1892 interp_ok(Ok(FileMetadata {
1893 mode,
1894 size: 0,
1895 created: None,
1896 accessed: None,
1897 modified: None,
1898 dev: None,
1899 uid: None,
1900 gid: None,
1901 blksize: None,
1902 blocks: None,
1903 ino: None,
1904 nlink: None,
1905 }))
1906 }
1907
1908 fn from_meta<'tcx>(
1909 ecx: &mut MiriInterpCx<'tcx>,
1910 metadata: Result<std::fs::Metadata, std::io::Error>,
1911 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1912 let metadata = match metadata {
1913 Ok(metadata) => metadata,
1914 Err(e) => {
1915 return interp_ok(Err(e.into()));
1916 }
1917 };
1918
1919 let file_type = metadata.file_type();
1920 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1921 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1922
1923 let size = metadata.len();
1924
1925 let created = extract_sec_and_nsec(metadata.created())?;
1926 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1927 let modified = extract_sec_and_nsec(metadata.modified())?;
1928
1929 cfg_select! {
1932 unix => {
1933 use std::os::unix::fs::{MetadataExt, PermissionsExt};
1934
1935 let dev = metadata.dev();
1936 let ino = metadata.ino();
1937 let nlink = metadata.nlink();
1938 let uid = metadata.uid();
1939 let gid = metadata.gid();
1940 let blksize = metadata.blksize();
1941 let blocks = metadata.blocks();
1942
1943 mode |= metadata.permissions().mode();
1944
1945 interp_ok(Ok(FileMetadata {
1946 mode,
1947 size,
1948 created,
1949 accessed,
1950 modified,
1951 dev: Some(dev),
1952 ino: Some(ino),
1953 nlink: Some(nlink),
1954 uid: Some(uid),
1955 gid: Some(gid),
1956 blksize: Some(blksize),
1957 blocks: Some(blocks),
1958 }))
1959 }
1960 _ => {
1961 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1963
1964 interp_ok(Ok(FileMetadata {
1965 mode,
1966 size,
1967 created,
1968 accessed,
1969 modified,
1970 dev: None,
1971 ino: None,
1972 nlink: None,
1973 uid: None,
1974 gid: None,
1975 blksize: None,
1976 blocks: None,
1977 }))
1978 }
1979 }
1980 }
1981}