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::check_min_vararg_count;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22#[derive(Debug)]
24struct OpenDir {
25 special_entries: Vec<&'static str>,
28 read_dir: fs::ReadDir,
30 entry: Option<Pointer>,
33}
34
35impl OpenDir {
36 fn new(read_dir: fs::ReadDir) -> Self {
37 Self { special_entries: vec!["..", "."], read_dir, entry: None }
38 }
39
40 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
41 if let Some(special) = self.special_entries.pop() {
42 return Some(Ok(Either::Right(special)));
43 }
44 let entry = self.read_dir.next()?;
45 Some(entry.map(Either::Left))
46 }
47}
48
49#[derive(Debug)]
50struct DirEntry {
51 name: OsString,
52 ino: u64,
53 d_type: i32,
54}
55
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: &[OpTy<'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] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
467 let mode = this.read_scalar(mode)?.to_u32()?;
468
469 #[cfg(unix)]
470 {
471 use std::os::unix::fs::OpenOptionsExt;
473 options.mode(mode);
474 }
475 #[cfg(not(unix))]
476 {
477 if mode != 0o666 {
479 throw_unsup_format!(
480 "non-default mode 0o{:o} is not supported on non-Unix hosts",
481 mode
482 );
483 }
484 }
485
486 let o_excl = this.eval_libc_i32("O_EXCL");
487 if flag & o_excl == o_excl {
488 flag &= !o_excl;
489 options.create_new(true);
490 } else {
491 options.create(true);
492 }
493 }
494 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
495 if flag & o_cloexec == o_cloexec {
496 flag &= !o_cloexec;
497 }
500 if this.tcx.sess.target.os == Os::Linux {
501 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
502 if flag & o_tmpfile == o_tmpfile {
503 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
505 }
506 }
507
508 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
509 if flag & o_nofollow == o_nofollow {
510 flag &= !o_nofollow;
511 #[cfg(unix)]
512 {
513 use std::os::unix::fs::OpenOptionsExt;
514 options.custom_flags(libc::O_NOFOLLOW);
515 }
516 #[cfg(not(unix))]
520 {
521 if path.is_symlink() {
524 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
525 }
526 }
527 }
528
529 if flag != 0 {
531 throw_unsup_format!("unsupported flags {:#x}", flag);
532 }
533
534 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
536 this.reject_in_isolation("`open`", reject_with)?;
537 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
538 }
539
540 let fd = options
541 .open(path)
542 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
543
544 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
545 }
546
547 fn lseek(
548 &mut self,
549 fd_num: i32,
550 offset: i128,
551 whence: i32,
552 dest: &MPlaceTy<'tcx>,
553 ) -> InterpResult<'tcx> {
554 let this = self.eval_context_mut();
555
556 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
559 if offset < 0 {
560 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
562 } else {
563 SeekFrom::Start(u64::try_from(offset).unwrap())
564 }
565 } else if whence == this.eval_libc_i32("SEEK_CUR") {
566 SeekFrom::Current(i64::try_from(offset).unwrap())
567 } else if whence == this.eval_libc_i32("SEEK_END") {
568 SeekFrom::End(i64::try_from(offset).unwrap())
569 } else {
570 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
571 };
572
573 let communicate = this.machine.communicate();
574
575 let Some(fd) = this.machine.fds.get(fd_num) else {
576 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
577 };
578 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
579 drop(fd);
580
581 let result = this.try_unwrap_io_result(result)?;
582 this.write_int(result, dest)?;
583 interp_ok(())
584 }
585
586 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
587 let this = self.eval_context_mut();
588
589 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
590
591 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
593 this.reject_in_isolation("`unlink`", reject_with)?;
594 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
595 }
596
597 let result = fs::remove_file(path).map(|_| 0);
598 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
599 }
600
601 fn symlink(
602 &mut self,
603 target_op: &OpTy<'tcx>,
604 linkpath_op: &OpTy<'tcx>,
605 ) -> InterpResult<'tcx, Scalar> {
606 #[cfg(unix)]
607 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
608 std::os::unix::fs::symlink(src, dst)
609 }
610
611 #[cfg(windows)]
612 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
613 use std::os::windows::fs;
614 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
615 }
616
617 let this = self.eval_context_mut();
618 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
619 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
620
621 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
623 this.reject_in_isolation("`symlink`", reject_with)?;
624 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
625 }
626
627 let result = create_link(&target, &linkpath).map(|_| 0);
628 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
629 }
630
631 fn linkat(
632 &mut self,
633 oldfd_op: &OpTy<'tcx>,
634 oldpath_op: &OpTy<'tcx>,
635 newfd_op: &OpTy<'tcx>,
636 newpath_op: &OpTy<'tcx>,
637 flags_op: &OpTy<'tcx>,
638 ) -> InterpResult<'tcx, Scalar> {
639 let this = self.eval_context_mut();
640
641 let flags = this.read_scalar(flags_op)?.to_i32()?;
643 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
644 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
645 let oldpath_ptr = this.read_pointer(oldpath_op)?;
646 let newpath_ptr = this.read_pointer(newpath_op)?;
647
648 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
650
651 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
653 this.reject_in_isolation("`linkat`", reject_with)?;
654 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
655 }
656
657 if flags != 0 {
659 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
660 }
661
662 if oldfd != at_fdcwd {
664 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
665 }
666 if oldpath_ptr == Pointer::null() {
667 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
668 }
669 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
670
671 if newfd != at_fdcwd {
673 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
674 }
675 if newpath_ptr == Pointer::null() {
676 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
677 }
678 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
679
680 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
681 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
682 }
683
684 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
685 let this = self.eval_context_mut();
686
687 if !matches!(
688 &this.tcx.sess.target.os,
689 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
690 ) {
691 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
692 }
693
694 let path_scalar = this.read_pointer(path_op)?;
695 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
696
697 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
699 this.reject_in_isolation("`stat`", reject_with)?;
700 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
701 }
702
703 let metadata = match FileMetadata::from_path(this, &path, true)? {
705 Ok(metadata) => metadata,
706 Err(err) => return this.set_errno_and_return_neg1_i32(err),
707 };
708
709 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
710 }
711
712 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
714 let this = self.eval_context_mut();
715
716 if !matches!(
717 &this.tcx.sess.target.os,
718 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
719 ) {
720 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
721 }
722
723 let path_scalar = this.read_pointer(path_op)?;
724 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
725
726 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
728 this.reject_in_isolation("`lstat`", reject_with)?;
729 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
730 }
731
732 let metadata = match FileMetadata::from_path(this, &path, false)? {
733 Ok(metadata) => metadata,
734 Err(err) => return this.set_errno_and_return_neg1_i32(err),
735 };
736
737 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
738 }
739
740 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
741 let this = self.eval_context_mut();
742
743 if !matches!(
744 &this.tcx.sess.target.os,
745 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
746 ) {
747 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
748 }
749
750 let fd = this.read_scalar(fd_op)?.to_i32()?;
751
752 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
754 this.reject_in_isolation("`fstat`", reject_with)?;
755 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
757 }
758
759 let metadata = match FileMetadata::from_fd_num(this, fd)? {
760 Ok(metadata) => metadata,
761 Err(err) => return this.set_errno_and_return_neg1_i32(err),
762 };
763 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
764 }
765
766 fn linux_statx(
767 &mut self,
768 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
774 let this = self.eval_context_mut();
775
776 this.assert_target_os(Os::Linux, "statx");
777
778 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
779 let pathname_ptr = this.read_pointer(pathname_op)?;
780 let flags = this.read_scalar(flags_op)?.to_i32()?;
781 let _mask = this.read_scalar(mask_op)?.to_u32()?;
782 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
783
784 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
786 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
787 }
788
789 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
790
791 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
792 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
794 let empty_path_flag = flags & at_empty_path == at_empty_path;
795 if !(path.is_absolute()
803 || dirfd == this.eval_libc_i32("AT_FDCWD")
804 || (path.as_os_str().is_empty() && empty_path_flag))
805 {
806 throw_unsup_format!(
807 "using statx is only supported with absolute paths, relative paths with the file \
808 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
809 file descriptor"
810 )
811 }
812
813 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
815 this.reject_in_isolation("`statx`", reject_with)?;
816 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
817 LibcError("EACCES")
820 } else {
821 assert!(empty_path_flag);
825 LibcError("EBADF")
826 };
827 return this.set_errno_and_return_neg1_i32(ecode);
828 }
829
830 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
833
834 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
837 FileMetadata::from_fd_num(this, dirfd)?
838 } else {
839 FileMetadata::from_path(this, &path, follow_symlink)?
840 };
841 let metadata = match metadata {
842 Ok(metadata) => metadata,
843 Err(err) => return this.set_errno_and_return_neg1_i32(err),
844 };
845
846 let mut mask = this.eval_libc_u32("STATX_TYPE")
851 | this.eval_libc_u32("STATX_MODE")
852 | this.eval_libc_u32("STATX_SIZE");
853
854 if metadata.ino.is_some() {
856 mask |= this.eval_libc_u32("STATX_INO");
857 }
858 if metadata.nlink.is_some() {
859 mask |= this.eval_libc_u32("STATX_NLINK");
860 }
861 if metadata.uid.is_some() {
862 mask |= this.eval_libc_u32("STATX_UID");
863 }
864 if metadata.gid.is_some() {
865 mask |= this.eval_libc_u32("STATX_GID");
866 }
867 if metadata.blocks.is_some() {
868 mask |= this.eval_libc_u32("STATX_BLOCKS");
869 }
870
871 let (access_sec, access_nsec) = metadata
874 .accessed
875 .map(|tup| {
876 mask |= this.eval_libc_u32("STATX_ATIME");
877 interp_ok(tup)
878 })
879 .unwrap_or_else(|| interp_ok((0, 0)))?;
880
881 let (created_sec, created_nsec) = metadata
882 .created
883 .map(|tup| {
884 mask |= this.eval_libc_u32("STATX_BTIME");
885 interp_ok(tup)
886 })
887 .unwrap_or_else(|| interp_ok((0, 0)))?;
888
889 let (modified_sec, modified_nsec) = metadata
890 .modified
891 .map(|tup| {
892 mask |= this.eval_libc_u32("STATX_MTIME");
893 interp_ok(tup)
894 })
895 .unwrap_or_else(|| interp_ok((0, 0)))?;
896
897 this.write_int_fields_named(
899 &[
900 ("stx_mask", mask.into()),
901 ("stx_mode", metadata.mode.into()),
902 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
903 ("stx_attributes", 0),
904 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
905 ("stx_uid", metadata.uid.unwrap_or(0).into()),
906 ("stx_gid", metadata.gid.unwrap_or(0).into()),
907 ("stx_ino", metadata.ino.unwrap_or(0).into()),
908 ("stx_size", metadata.size.into()),
909 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
910 ("stx_attributes_mask", 0),
911 ("stx_rdev_major", 0),
912 ("stx_rdev_minor", 0),
913 ("stx_dev_major", 0),
914 ("stx_dev_minor", 0),
915 ],
916 &statxbuf,
917 )?;
918 #[rustfmt::skip]
919 this.write_int_fields_named(
920 &[
921 ("tv_sec", access_sec.into()),
922 ("tv_nsec", access_nsec.into()),
923 ],
924 &this.project_field_named(&statxbuf, "stx_atime")?,
925 )?;
926 #[rustfmt::skip]
927 this.write_int_fields_named(
928 &[
929 ("tv_sec", created_sec.into()),
930 ("tv_nsec", created_nsec.into()),
931 ],
932 &this.project_field_named(&statxbuf, "stx_btime")?,
933 )?;
934 #[rustfmt::skip]
935 this.write_int_fields_named(
936 &[
937 ("tv_sec", 0.into()),
938 ("tv_nsec", 0.into()),
939 ],
940 &this.project_field_named(&statxbuf, "stx_ctime")?,
941 )?;
942 #[rustfmt::skip]
943 this.write_int_fields_named(
944 &[
945 ("tv_sec", modified_sec.into()),
946 ("tv_nsec", modified_nsec.into()),
947 ],
948 &this.project_field_named(&statxbuf, "stx_mtime")?,
949 )?;
950
951 interp_ok(Scalar::from_i32(0))
952 }
953
954 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
955 let this = self.eval_context_mut();
956
957 let path_ptr = this.read_pointer(path_op)?;
958 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
959
960 if this.ptr_is_null(path_ptr)? {
961 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
962 }
963 let path = this.read_path_from_c_str(path_ptr)?;
964
965 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
967 this.reject_in_isolation("`chmod`", reject_with)?;
968 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
969 }
970
971 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
972 if let Err(err) = fs::set_permissions(path, permissions) {
973 return this.set_errno_and_return_neg1_i32(err);
974 }
975
976 interp_ok(Scalar::from_i32(0))
977 }
978
979 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
980 let this = self.eval_context_mut();
981
982 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
983 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
984
985 let Some(fd) = this.machine.fds.get(fd_num) else {
986 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
987 };
988 let Some(file) = fd.downcast::<FileHandle>() else {
989 throw_unsup_format!("`fchmod` is only supported on regular files")
991 };
992 if !file.writable && !file.readable {
993 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
996 }
997 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
998
999 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1000 if let Err(err) = file.file.set_permissions(permissions) {
1001 return this.set_errno_and_return_neg1_i32(err);
1002 }
1003
1004 interp_ok(Scalar::from_i32(0))
1005 }
1006
1007 fn rename(
1008 &mut self,
1009 oldpath_op: &OpTy<'tcx>,
1010 newpath_op: &OpTy<'tcx>,
1011 ) -> InterpResult<'tcx, Scalar> {
1012 let this = self.eval_context_mut();
1013
1014 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1015 let newpath_ptr = this.read_pointer(newpath_op)?;
1016
1017 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1018 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1019 }
1020
1021 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1022 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1023
1024 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1026 this.reject_in_isolation("`rename`", reject_with)?;
1027 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1028 }
1029
1030 let result = fs::rename(oldpath, newpath).map(|_| 0);
1031
1032 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1033 }
1034
1035 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1036 let this = self.eval_context_mut();
1037
1038 #[cfg_attr(not(unix), allow(unused_variables))]
1039 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1040 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1041 } else {
1042 this.read_scalar(mode_op)?.to_u32()?
1043 };
1044
1045 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1046
1047 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1049 this.reject_in_isolation("`mkdir`", reject_with)?;
1050 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1051 }
1052
1053 #[cfg_attr(not(unix), allow(unused_mut))]
1054 let mut builder = DirBuilder::new();
1055
1056 #[cfg(unix)]
1059 {
1060 use std::os::unix::fs::DirBuilderExt;
1061 builder.mode(mode);
1062 }
1063
1064 let result = builder.create(path).map(|_| 0i32);
1065
1066 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1067 }
1068
1069 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1070 let this = self.eval_context_mut();
1071
1072 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1073
1074 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1076 this.reject_in_isolation("`rmdir`", reject_with)?;
1077 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1078 }
1079
1080 let result = fs::remove_dir(path).map(|_| 0i32);
1081
1082 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1083 }
1084
1085 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1086 let this = self.eval_context_mut();
1087
1088 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1089
1090 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1092 this.reject_in_isolation("`opendir`", reject_with)?;
1093 this.set_last_error(LibcError("EACCES"))?;
1094 return interp_ok(Scalar::null_ptr(this));
1095 }
1096
1097 let result = fs::read_dir(name);
1098
1099 match result {
1100 Ok(dir_iter) => {
1101 let id = this.machine.dirs.insert_new(dir_iter);
1102
1103 interp_ok(Scalar::from_target_usize(id, this))
1107 }
1108 Err(e) => {
1109 this.set_last_error(e)?;
1110 interp_ok(Scalar::null_ptr(this))
1111 }
1112 }
1113 }
1114
1115 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1116 let this = self.eval_context_mut();
1117
1118 if !matches!(
1119 &this.tcx.sess.target.os,
1120 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
1121 ) {
1122 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
1123 }
1124
1125 let dirp = this.read_target_usize(dirp_op)?;
1126
1127 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1129 this.reject_in_isolation("`readdir`", reject_with)?;
1130 this.set_last_error(LibcError("EBADF"))?;
1131 this.write_null(dest)?;
1132 return interp_ok(());
1133 }
1134
1135 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1136 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1137 })?;
1138
1139 let entry = match open_dir.next_host_entry() {
1140 Some(Ok(dir_entry)) => {
1141 let dir_entry = this.dir_entry_fields(dir_entry)?;
1142
1143 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1180 let dirent_layout = this.layout_of(dirent_ty)?;
1181 let fields = &dirent_layout.fields;
1182 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1183
1184 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1188 let name_len = u64::try_from(name_bytes.len()).unwrap();
1189 let size = d_name_offset.strict_add(name_len);
1190
1191 let entry = this.allocate_ptr(
1192 Size::from_bytes(size),
1193 dirent_layout.align.abi,
1194 MiriMemoryKind::Runtime.into(),
1195 AllocInit::Uninit,
1196 )?;
1197 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1198
1199 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1202 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1203
1204 let ino_name =
1206 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1207 this.write_int_fields_named(
1208 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1209 &entry,
1210 )?;
1211
1212 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1214 this.write_null(&d_off)?;
1215 }
1216 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1217 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1218 }
1219 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1220 this.write_int(dir_entry.d_type, &d_type)?;
1221 }
1222
1223 Some(entry.ptr())
1224 }
1225 None => {
1226 None
1228 }
1229 Some(Err(e)) => {
1230 this.set_last_error(e)?;
1231 None
1232 }
1233 };
1234
1235 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1236 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1237 if let Some(old_entry) = old_entry {
1238 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1239 }
1240
1241 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1242 interp_ok(())
1243 }
1244
1245 fn macos_readdir_r(
1246 &mut self,
1247 dirp_op: &OpTy<'tcx>,
1248 entry_op: &OpTy<'tcx>,
1249 result_op: &OpTy<'tcx>,
1250 ) -> InterpResult<'tcx, Scalar> {
1251 let this = self.eval_context_mut();
1252
1253 this.assert_target_os(Os::MacOs, "readdir_r");
1254
1255 let dirp = this.read_target_usize(dirp_op)?;
1256 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1257
1258 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1260 this.reject_in_isolation("`readdir_r`", reject_with)?;
1261 return interp_ok(this.eval_libc("EBADF"));
1263 }
1264
1265 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1266 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1267 })?;
1268 interp_ok(match open_dir.next_host_entry() {
1269 Some(Ok(dir_entry)) => {
1270 let dir_entry = this.dir_entry_fields(dir_entry)?;
1271 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1286
1287 let name_place = this.project_field_named(&entry_place, "d_name")?;
1289 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1290 &dir_entry.name,
1291 name_place.ptr(),
1292 name_place.layout.size.bytes(),
1293 )?;
1294 if !name_fits {
1295 throw_unsup_format!(
1296 "a directory entry had a name too large to fit in libc::dirent"
1297 );
1298 }
1299
1300 this.write_int_fields_named(
1302 &[
1303 ("d_reclen", entry_place.layout.size.bytes().into()),
1304 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1305 ("d_type", dir_entry.d_type.into()),
1306 ("d_ino", dir_entry.ino.into()),
1307 ("d_seekoff", 0),
1308 ],
1309 &entry_place,
1310 )?;
1311 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1312
1313 Scalar::from_i32(0)
1314 }
1315 None => {
1316 this.write_null(&result_place)?;
1318 Scalar::from_i32(0)
1319 }
1320 Some(Err(e)) => {
1321 this.io_error_to_errnum(e)?
1323 }
1324 })
1325 }
1326
1327 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1328 let this = self.eval_context_mut();
1329
1330 let dirp = this.read_target_usize(dirp_op)?;
1331
1332 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1334 this.reject_in_isolation("`closedir`", reject_with)?;
1335 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1336 }
1337
1338 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1339 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1340 };
1341 if let Some(entry) = open_dir.entry.take() {
1342 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1343 }
1344 drop(open_dir);
1346
1347 interp_ok(Scalar::from_i32(0))
1348 }
1349
1350 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1351 let this = self.eval_context_mut();
1352
1353 let Some(fd) = this.machine.fds.get(fd_num) else {
1354 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1355 };
1356 let Some(file) = fd.downcast::<FileHandle>() else {
1357 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1360 };
1361 if !file.writable {
1362 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1364 }
1365 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1366
1367 if let Ok(length) = length.try_into() {
1368 let result = file.file.set_len(length);
1369 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1370 interp_ok(Scalar::from_i32(result))
1371 } else {
1372 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1373 }
1374 }
1375
1376 fn posix_fallocate(
1379 &mut self,
1380 fd_num: i32,
1381 offset: i64,
1382 len: i64,
1383 ) -> InterpResult<'tcx, Scalar> {
1384 let this = self.eval_context_mut();
1385
1386 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1388 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1389 return interp_ok(this.eval_libc("EBADF"));
1391 }
1392
1393 if offset < 0 || len <= 0 {
1395 return interp_ok(this.eval_libc("EINVAL"));
1396 }
1397
1398 let Some(fd) = this.machine.fds.get(fd_num) else {
1400 return interp_ok(this.eval_libc("EBADF"));
1401 };
1402 let Some(file) = fd.downcast::<FileHandle>() else {
1403 return interp_ok(this.eval_libc("ENODEV"));
1405 };
1406
1407 if !file.writable {
1408 return interp_ok(this.eval_libc("EBADF"));
1410 }
1411
1412 let current_size = match file.file.metadata() {
1413 Ok(metadata) => metadata.len(),
1414 Err(err) => return this.io_error_to_errnum(err),
1415 };
1416 let new_size = match offset.checked_add(len) {
1418 Some(new_size) => u64::try_from(new_size).unwrap(),
1420 None => return interp_ok(this.eval_libc("EFBIG")), };
1422 if current_size < new_size {
1425 interp_ok(match file.file.set_len(new_size) {
1426 Ok(()) => Scalar::from_i32(0),
1427 Err(e) => this.io_error_to_errnum(e)?,
1428 })
1429 } else {
1430 interp_ok(Scalar::from_i32(0))
1431 }
1432 }
1433
1434 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1435 let this = self.eval_context_mut();
1441
1442 let fd = this.read_scalar(fd_op)?.to_i32()?;
1443
1444 self.ffullsync_fd(fd)
1445 }
1446
1447 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1448 let this = self.eval_context_mut();
1449 let Some(fd) = this.machine.fds.get(fd_num) else {
1450 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1451 };
1452 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1454 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1455 })?;
1456 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1457
1458 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1459 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1460 }
1461
1462 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1463 let this = self.eval_context_mut();
1464
1465 let fd = this.read_scalar(fd_op)?.to_i32()?;
1466
1467 let Some(fd) = this.machine.fds.get(fd) else {
1468 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1469 };
1470 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1472 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1473 })?;
1474 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1475
1476 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1477 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1478 }
1479
1480 fn futimens(
1483 &mut self,
1484 fd_op: &OpTy<'tcx>,
1485 times_op: &OpTy<'tcx>,
1486 ) -> InterpResult<'tcx, Scalar> {
1487 let this = self.eval_context_mut();
1488
1489 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1490 let times_ptr = this.read_pointer(times_op)?;
1491
1492 let Some(fd) = this.machine.fds.get(fd_num) else {
1493 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1494 };
1495 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1496 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1497 })?;
1498 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1499
1500 let (access, modified) = if this.ptr_is_null(times_ptr)? {
1501 let now = TimeUpdate::Set(SystemTime::now());
1502 (now, now)
1503 } else {
1504 let timespec = this.libc_ty_layout("timespec");
1505 let access_place = this.deref_pointer_as(times_op, timespec)?;
1506 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1507 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1508 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1509 };
1510 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1511 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1512 };
1513 (access, modified)
1514 };
1515
1516 let mut filetimes = FileTimes::new();
1517 if let TimeUpdate::Set(access) = access {
1518 filetimes = filetimes.set_accessed(access);
1519 }
1520 if let TimeUpdate::Set(modified) = modified {
1521 filetimes = filetimes.set_modified(modified);
1522 }
1523 let result = file.file.set_times(filetimes);
1524 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1525 }
1526
1527 fn sync_file_range(
1528 &mut self,
1529 fd_op: &OpTy<'tcx>,
1530 offset_op: &OpTy<'tcx>,
1531 nbytes_op: &OpTy<'tcx>,
1532 flags_op: &OpTy<'tcx>,
1533 ) -> InterpResult<'tcx, Scalar> {
1534 let this = self.eval_context_mut();
1535
1536 let fd = this.read_scalar(fd_op)?.to_i32()?;
1537 let offset = this.read_scalar(offset_op)?.to_i64()?;
1538 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1539 let flags = this.read_scalar(flags_op)?.to_i32()?;
1540
1541 if offset < 0 || nbytes < 0 {
1542 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1543 }
1544 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1545 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1546 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1547 if flags & allowed_flags != flags {
1548 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1549 }
1550
1551 let Some(fd) = this.machine.fds.get(fd) else {
1552 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1553 };
1554 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1556 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1557 })?;
1558 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1559
1560 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1561 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1562 }
1563
1564 fn readlink(
1565 &mut self,
1566 pathname_op: &OpTy<'tcx>,
1567 buf_op: &OpTy<'tcx>,
1568 bufsize_op: &OpTy<'tcx>,
1569 ) -> InterpResult<'tcx, i64> {
1570 let this = self.eval_context_mut();
1571
1572 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1573 let buf = this.read_pointer(buf_op)?;
1574 let bufsize = this.read_target_usize(bufsize_op)?;
1575
1576 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1578 this.reject_in_isolation("`readlink`", reject_with)?;
1579 this.set_last_error(LibcError("EACCES"))?;
1580 return interp_ok(-1);
1581 }
1582
1583 let result = std::fs::read_link(pathname);
1584 match result {
1585 Ok(resolved) => {
1586 let resolved = this.convert_path(
1590 Cow::Borrowed(resolved.as_ref()),
1591 crate::shims::os_str::PathConversion::HostToTarget,
1592 );
1593 let mut path_bytes = resolved.as_encoded_bytes();
1594 let bufsize: usize = bufsize.try_into().unwrap();
1595 if path_bytes.len() > bufsize {
1596 path_bytes = &path_bytes[..bufsize]
1597 }
1598 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1599 interp_ok(path_bytes.len().try_into().unwrap())
1600 }
1601 Err(e) => {
1602 this.set_last_error(e)?;
1603 interp_ok(-1)
1604 }
1605 }
1606 }
1607
1608 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1609 let this = self.eval_context_mut();
1610 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1613 let error = if let Some(fd) = this.machine.fds.get(fd) {
1614 if fd.is_tty(this.machine.communicate()) {
1615 return interp_ok(Scalar::from_i32(1));
1616 } else {
1617 LibcError("ENOTTY")
1618 }
1619 } else {
1620 LibcError("EBADF")
1622 };
1623 this.set_last_error(error)?;
1624 interp_ok(Scalar::from_i32(0))
1625 }
1626
1627 fn realpath(
1628 &mut self,
1629 path_op: &OpTy<'tcx>,
1630 processed_path_op: &OpTy<'tcx>,
1631 ) -> InterpResult<'tcx, Scalar> {
1632 let this = self.eval_context_mut();
1633 this.assert_target_os_is_unix("realpath");
1634
1635 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1636 let processed_ptr = this.read_pointer(processed_path_op)?;
1637
1638 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1640 this.reject_in_isolation("`realpath`", reject_with)?;
1641 this.set_last_error(LibcError("EACCES"))?;
1642 return interp_ok(Scalar::from_target_usize(0, this));
1643 }
1644
1645 let result = std::fs::canonicalize(pathname);
1646 match result {
1647 Ok(resolved) => {
1648 let path_max = this
1649 .eval_libc_i32("PATH_MAX")
1650 .try_into()
1651 .expect("PATH_MAX does not fit in u64");
1652 let dest = if this.ptr_is_null(processed_ptr)? {
1653 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1663 } else {
1664 let (wrote_path, _) =
1665 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1666
1667 if !wrote_path {
1668 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1672 return interp_ok(Scalar::from_target_usize(0, this));
1673 }
1674 processed_ptr
1675 };
1676
1677 interp_ok(Scalar::from_maybe_pointer(dest, this))
1678 }
1679 Err(e) => {
1680 this.set_last_error(e)?;
1681 interp_ok(Scalar::from_target_usize(0, this))
1682 }
1683 }
1684 }
1685 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1686 use rand::seq::IndexedRandom;
1687
1688 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1690
1691 let this = self.eval_context_mut();
1692 this.assert_target_os_is_unix("mkstemp");
1693
1694 let max_attempts = this.eval_libc_u32("TMP_MAX");
1704
1705 let template_ptr = this.read_pointer(template_op)?;
1708 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1709 let template_bytes = template.as_mut_slice();
1710
1711 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1713 this.reject_in_isolation("`mkstemp`", reject_with)?;
1714 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1715 }
1716
1717 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1719
1720 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1725 let end_pos = template_bytes.len();
1726 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1727
1728 if last_six_char_bytes != suffix_bytes {
1730 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1731 }
1732
1733 const SUBSTITUTIONS: &[char; 62] = &[
1737 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1738 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1739 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1740 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1741 ];
1742
1743 let mut fopts = OpenOptions::new();
1746 fopts.read(true).write(true).create_new(true);
1747
1748 cfg_select! {
1749 unix =>
1750 {
1751 use std::os::unix::fs::OpenOptionsExt;
1752 fopts.mode(0o600);
1754 fopts.custom_flags(libc::O_EXCL);
1755 }
1756 windows =>
1757 {
1758 use std::os::windows::fs::OpenOptionsExt;
1759 fopts.share_mode(0);
1761 }
1762 _ => {
1763 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1764 }
1765 }
1766
1767 for _ in 0..max_attempts {
1769 let rng = this.machine.rng.get_mut();
1770
1771 let unique_suffix =
1773 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1774
1775 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1777
1778 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1780
1781 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1783 match file {
1784 Ok(f) => {
1785 let fd = this.machine.fds.insert_new(FileHandle {
1786 file: f,
1787 writable: true,
1788 readable: true,
1789 });
1790 return interp_ok(Scalar::from_i32(fd));
1791 }
1792 Err(e) =>
1793 match e.kind() {
1794 ErrorKind::AlreadyExists => continue,
1796 _ => {
1798 return this.set_errno_and_return_neg1_i32(e);
1801 }
1802 },
1803 }
1804 }
1805
1806 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1808 }
1809}
1810
1811fn extract_sec_and_nsec<'tcx>(
1815 time: std::io::Result<SystemTime>,
1816) -> InterpResult<'tcx, Option<(u64, u32)>> {
1817 match time.ok() {
1818 Some(time) => {
1819 let duration = system_time_to_duration(&time)?;
1820 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1821 }
1822 None => interp_ok(None),
1823 }
1824}
1825
1826fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1827 #[cfg(unix)]
1828 use std::os::unix::fs::FileTypeExt;
1829
1830 if file_type.is_file() {
1831 "S_IFREG"
1832 } else if file_type.is_dir() {
1833 "S_IFDIR"
1834 } else if file_type.is_symlink() {
1835 "S_IFLNK"
1836 } else {
1837 #[cfg(unix)]
1839 {
1840 if file_type.is_socket() {
1841 return "S_IFSOCK";
1842 } else if file_type.is_fifo() {
1843 return "S_IFIFO";
1844 } else if file_type.is_char_device() {
1845 return "S_IFCHR";
1846 } else if file_type.is_block_device() {
1847 return "S_IFBLK";
1848 }
1849 }
1850 "S_IFREG"
1851 }
1852}
1853
1854struct FileMetadata {
1862 mode: u32,
1864 size: u64,
1865 created: Option<(u64, u32)>,
1866 accessed: Option<(u64, u32)>,
1867 modified: Option<(u64, u32)>,
1868 dev: Option<u64>,
1869 ino: Option<u64>,
1870 nlink: Option<u64>,
1871 uid: Option<u32>,
1872 gid: Option<u32>,
1873 blksize: Option<u64>,
1874 blocks: Option<u64>,
1875}
1876
1877impl FileMetadata {
1878 fn from_path<'tcx>(
1879 ecx: &mut MiriInterpCx<'tcx>,
1880 path: &Path,
1881 follow_symlink: bool,
1882 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1883 let metadata =
1884 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1885
1886 FileMetadata::from_meta(ecx, metadata)
1887 }
1888
1889 fn from_fd_num<'tcx>(
1890 ecx: &mut MiriInterpCx<'tcx>,
1891 fd_num: i32,
1892 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1893 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1894 return interp_ok(Err(LibcError("EBADF")));
1895 };
1896 match fd.metadata()? {
1897 Either::Left(host) => Self::from_meta(ecx, host),
1898 Either::Right(name) => Self::synthetic(ecx, name),
1899 }
1900 }
1901
1902 fn synthetic<'tcx>(
1903 ecx: &mut MiriInterpCx<'tcx>,
1904 mode_name: &str,
1905 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1906 let mode = ecx.eval_libc(mode_name);
1907 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1908 let mode = mode | 0o666;
1910 interp_ok(Ok(FileMetadata {
1911 mode,
1912 size: 0,
1913 created: None,
1914 accessed: None,
1915 modified: None,
1916 dev: None,
1917 uid: None,
1918 gid: None,
1919 blksize: None,
1920 blocks: None,
1921 ino: None,
1922 nlink: None,
1923 }))
1924 }
1925
1926 fn from_meta<'tcx>(
1927 ecx: &mut MiriInterpCx<'tcx>,
1928 metadata: Result<std::fs::Metadata, std::io::Error>,
1929 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1930 let metadata = match metadata {
1931 Ok(metadata) => metadata,
1932 Err(e) => {
1933 return interp_ok(Err(e.into()));
1934 }
1935 };
1936
1937 let file_type = metadata.file_type();
1938 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1939 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1940
1941 let size = metadata.len();
1942
1943 let created = extract_sec_and_nsec(metadata.created())?;
1944 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1945 let modified = extract_sec_and_nsec(metadata.modified())?;
1946
1947 cfg_select! {
1950 unix => {
1951 use std::os::unix::fs::MetadataExt;
1952 use std::os::unix::fs::PermissionsExt;
1953
1954 let dev = metadata.dev();
1955 let ino = metadata.ino();
1956 let nlink = metadata.nlink();
1957 let uid = metadata.uid();
1958 let gid = metadata.gid();
1959 let blksize = metadata.blksize();
1960 let blocks = metadata.blocks();
1961
1962 mode |= metadata.permissions().mode();
1963
1964 interp_ok(Ok(FileMetadata {
1965 mode,
1966 size,
1967 created,
1968 accessed,
1969 modified,
1970 dev: Some(dev),
1971 ino: Some(ino),
1972 nlink: Some(nlink),
1973 uid: Some(uid),
1974 gid: Some(gid),
1975 blksize: Some(blksize),
1976 blocks: Some(blocks),
1977 }))
1978 }
1979 _ => {
1980 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1982
1983 interp_ok(Ok(FileMetadata {
1984 mode,
1985 size,
1986 created,
1987 accessed,
1988 modified,
1989 dev: None,
1990 ino: None,
1991 nlink: None,
1992 uid: None,
1993 gid: None,
1994 blksize: None,
1995 blocks: None,
1996 }))
1997 },
1998 }
1999 }
2000}