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 | Os::MacOs
1121 ) {
1122 throw_unsup_format!("`readdir` is not yet supported 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();
1190 let dirent_layout = this.layout_of(dirent_ty)?;
1191 let fields = &dirent_layout.fields;
1192 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1193
1194 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1198 let name_len = u64::try_from(name_bytes.len()).unwrap();
1199 let size = d_name_offset.strict_add(name_len);
1200
1201 let entry = this.allocate_ptr(
1202 Size::from_bytes(size),
1203 dirent_layout.align.abi,
1204 MiriMemoryKind::Runtime.into(),
1205 AllocInit::Uninit,
1206 )?;
1207 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1208
1209 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1212 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1213
1214 let ino_name =
1216 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1217 this.write_int_fields_named(
1218 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1219 &entry,
1220 )?;
1221
1222 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1224 this.write_null(&d_off)?;
1225 }
1226 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1227 this.write_null(&d_seekoff)?;
1228 }
1229 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1230 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1231 }
1232 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1233 this.write_int(dir_entry.d_type, &d_type)?;
1234 }
1235
1236 Some(entry.ptr())
1237 }
1238 None => {
1239 None
1241 }
1242 Some(Err(e)) => {
1243 this.set_last_error(e)?;
1244 None
1245 }
1246 };
1247
1248 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1249 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1250 if let Some(old_entry) = old_entry {
1251 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1252 }
1253
1254 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1255 interp_ok(())
1256 }
1257
1258 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1259 let this = self.eval_context_mut();
1260
1261 let dirp = this.read_target_usize(dirp_op)?;
1262
1263 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1265 this.reject_in_isolation("`closedir`", reject_with)?;
1266 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1267 }
1268
1269 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1270 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1271 };
1272 if let Some(entry) = open_dir.entry.take() {
1273 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1274 }
1275 drop(open_dir);
1277
1278 interp_ok(Scalar::from_i32(0))
1279 }
1280
1281 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1282 let this = self.eval_context_mut();
1283
1284 let Some(fd) = this.machine.fds.get(fd_num) else {
1285 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1286 };
1287 let Some(file) = fd.downcast::<FileHandle>() else {
1288 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1291 };
1292 if !file.writable {
1293 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1295 }
1296 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1297
1298 if let Ok(length) = length.try_into() {
1299 let result = file.file.set_len(length);
1300 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1301 interp_ok(Scalar::from_i32(result))
1302 } else {
1303 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1304 }
1305 }
1306
1307 fn posix_fallocate(
1310 &mut self,
1311 fd_num: i32,
1312 offset: i64,
1313 len: i64,
1314 ) -> InterpResult<'tcx, Scalar> {
1315 let this = self.eval_context_mut();
1316
1317 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1319 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1320 return interp_ok(this.eval_libc("EBADF"));
1322 }
1323
1324 match this.fallocate_impl(fd_num, offset, len)? {
1325 Ok(()) => interp_ok(Scalar::from_i32(0)),
1326 Err(e) => this.io_error_to_errnum(e),
1327 }
1328 }
1329
1330 fn linux_fallocate(
1331 &mut self,
1332 fd: i32,
1333 mode: i32,
1334 offset: i64,
1335 size: i64,
1336 ) -> InterpResult<'tcx, Scalar> {
1337 let this = self.eval_context_mut();
1339
1340 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1342 this.reject_in_isolation("`fallocate`", reject_with)?;
1343 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1345 }
1346
1347 if mode != 0 {
1350 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1351 }
1352
1353 match this.fallocate_impl(fd, offset, size)? {
1354 Ok(()) => interp_ok(Scalar::from_i32(0)),
1355 Err(e) => this.set_errno_and_return_neg1_i32(e),
1356 }
1357 }
1358
1359 fn fallocate_impl(
1361 &mut self,
1362 fd_num: i32,
1363 offset: i64,
1364 len: i64,
1365 ) -> InterpResult<'tcx, Result<(), IoError>> {
1366 let this = self.eval_context_mut();
1367
1368 if offset < 0 || len <= 0 {
1370 return interp_ok(Err(LibcError("EINVAL")));
1371 }
1372
1373 let Some(fd) = this.machine.fds.get(fd_num) else {
1374 return interp_ok(Err(LibcError("EBADF")));
1375 };
1376 let Some(file) = fd.downcast::<FileHandle>() else {
1377 return interp_ok(Err(LibcError("ENODEV")));
1379 };
1380
1381 if !file.writable {
1382 return interp_ok(Err(LibcError("EBADF")));
1383 }
1384
1385 let current_size = match file.file.metadata() {
1386 Ok(metadata) => metadata.len(),
1387 Err(err) => return interp_ok(Err(err.into())),
1388 };
1389
1390 let new_size = match offset.checked_add(len) {
1392 Some(new_size) => u64::try_from(new_size).unwrap(),
1394 None => return interp_ok(Err(LibcError("EFBIG"))), };
1396
1397 if current_size < new_size {
1400 match file.file.set_len(new_size) {
1401 Ok(()) => interp_ok(Ok(())),
1402 Err(err) => interp_ok(Err(err.into())),
1403 }
1404 } else {
1405 interp_ok(Ok(()))
1406 }
1407 }
1408
1409 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1410 let this = self.eval_context_mut();
1416
1417 let fd = this.read_scalar(fd_op)?.to_i32()?;
1418
1419 self.ffullsync_fd(fd)
1420 }
1421
1422 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1423 let this = self.eval_context_mut();
1424 let Some(fd) = this.machine.fds.get(fd_num) else {
1425 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1426 };
1427 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1429 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1430 })?;
1431 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1432
1433 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1434 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1435 }
1436
1437 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1438 let this = self.eval_context_mut();
1439
1440 let fd = this.read_scalar(fd_op)?.to_i32()?;
1441
1442 let Some(fd) = this.machine.fds.get(fd) else {
1443 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1444 };
1445 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1447 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1448 })?;
1449 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1450
1451 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1452 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1453 }
1454
1455 fn futimens(
1458 &mut self,
1459 fd_op: &OpTy<'tcx>,
1460 times_op: &OpTy<'tcx>,
1461 ) -> InterpResult<'tcx, Scalar> {
1462 let this = self.eval_context_mut();
1463
1464 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1465 let times_ptr = this.read_pointer(times_op)?;
1466
1467 let Some(fd) = this.machine.fds.get(fd_num) else {
1468 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1469 };
1470 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1471 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1472 })?;
1473 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1474
1475 let (access, modified) = if this.ptr_is_null(times_ptr)? {
1476 let now = TimeUpdate::Set(SystemTime::now());
1477 (now, now)
1478 } else {
1479 let timespec = this.libc_ty_layout("timespec");
1480 let access_place = this.deref_pointer_as(times_op, timespec)?;
1481 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1482 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1483 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1484 };
1485 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1486 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1487 };
1488 (access, modified)
1489 };
1490
1491 let mut filetimes = FileTimes::new();
1492 if let TimeUpdate::Set(access) = access {
1493 filetimes = filetimes.set_accessed(access);
1494 }
1495 if let TimeUpdate::Set(modified) = modified {
1496 filetimes = filetimes.set_modified(modified);
1497 }
1498 let result = file.file.set_times(filetimes);
1499 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1500 }
1501
1502 fn sync_file_range(
1503 &mut self,
1504 fd_op: &OpTy<'tcx>,
1505 offset_op: &OpTy<'tcx>,
1506 nbytes_op: &OpTy<'tcx>,
1507 flags_op: &OpTy<'tcx>,
1508 ) -> InterpResult<'tcx, Scalar> {
1509 let this = self.eval_context_mut();
1510
1511 let fd = this.read_scalar(fd_op)?.to_i32()?;
1512 let offset = this.read_scalar(offset_op)?.to_i64()?;
1513 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1514 let flags = this.read_scalar(flags_op)?.to_i32()?;
1515
1516 if offset < 0 || nbytes < 0 {
1517 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1518 }
1519 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1520 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1521 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1522 if flags & allowed_flags != flags {
1523 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1524 }
1525
1526 let Some(fd) = this.machine.fds.get(fd) else {
1527 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1528 };
1529 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1531 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1532 })?;
1533 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1534
1535 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1536 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1537 }
1538
1539 fn readlink(
1540 &mut self,
1541 pathname_op: &OpTy<'tcx>,
1542 buf_op: &OpTy<'tcx>,
1543 bufsize_op: &OpTy<'tcx>,
1544 ) -> InterpResult<'tcx, i64> {
1545 let this = self.eval_context_mut();
1546
1547 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1548 let buf = this.read_pointer(buf_op)?;
1549 let bufsize = this.read_target_usize(bufsize_op)?;
1550
1551 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1553 this.reject_in_isolation("`readlink`", reject_with)?;
1554 this.set_last_error(LibcError("EACCES"))?;
1555 return interp_ok(-1);
1556 }
1557
1558 let result = std::fs::read_link(pathname);
1559 match result {
1560 Ok(resolved) => {
1561 let resolved = this.convert_path(
1565 Cow::Borrowed(resolved.as_ref()),
1566 crate::shims::os_str::PathConversion::HostToTarget,
1567 );
1568 let mut path_bytes = resolved.as_encoded_bytes();
1569 let bufsize: usize = bufsize.try_into().unwrap();
1570 if path_bytes.len() > bufsize {
1571 path_bytes = &path_bytes[..bufsize]
1572 }
1573 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1574 interp_ok(path_bytes.len().try_into().unwrap())
1575 }
1576 Err(e) => {
1577 this.set_last_error(e)?;
1578 interp_ok(-1)
1579 }
1580 }
1581 }
1582
1583 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1584 let this = self.eval_context_mut();
1585 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1588 let error = if let Some(fd) = this.machine.fds.get(fd) {
1589 if fd.is_tty(this.machine.communicate()) {
1590 return interp_ok(Scalar::from_i32(1));
1591 } else {
1592 LibcError("ENOTTY")
1593 }
1594 } else {
1595 LibcError("EBADF")
1597 };
1598 this.set_last_error(error)?;
1599 interp_ok(Scalar::from_i32(0))
1600 }
1601
1602 fn realpath(
1603 &mut self,
1604 path_op: &OpTy<'tcx>,
1605 processed_path_op: &OpTy<'tcx>,
1606 ) -> InterpResult<'tcx, Scalar> {
1607 let this = self.eval_context_mut();
1608 this.assert_target_os_is_unix("realpath");
1609
1610 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1611 let processed_ptr = this.read_pointer(processed_path_op)?;
1612
1613 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1615 this.reject_in_isolation("`realpath`", reject_with)?;
1616 this.set_last_error(LibcError("EACCES"))?;
1617 return interp_ok(Scalar::from_target_usize(0, this));
1618 }
1619
1620 let result = std::fs::canonicalize(pathname);
1621 match result {
1622 Ok(resolved) => {
1623 let path_max = this
1624 .eval_libc_i32("PATH_MAX")
1625 .try_into()
1626 .expect("PATH_MAX does not fit in u64");
1627 let dest = if this.ptr_is_null(processed_ptr)? {
1628 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1638 } else {
1639 let (wrote_path, _) =
1640 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1641
1642 if !wrote_path {
1643 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1647 return interp_ok(Scalar::from_target_usize(0, this));
1648 }
1649 processed_ptr
1650 };
1651
1652 interp_ok(Scalar::from_maybe_pointer(dest, this))
1653 }
1654 Err(e) => {
1655 this.set_last_error(e)?;
1656 interp_ok(Scalar::from_target_usize(0, this))
1657 }
1658 }
1659 }
1660 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1661 use rand::seq::IndexedRandom;
1662
1663 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1665
1666 let this = self.eval_context_mut();
1667 this.assert_target_os_is_unix("mkstemp");
1668
1669 let max_attempts = this.eval_libc_u32("TMP_MAX");
1679
1680 let template_ptr = this.read_pointer(template_op)?;
1683 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1684 let template_bytes = template.as_mut_slice();
1685
1686 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1688 this.reject_in_isolation("`mkstemp`", reject_with)?;
1689 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1690 }
1691
1692 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1694
1695 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1700 let end_pos = template_bytes.len();
1701 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1702
1703 if last_six_char_bytes != suffix_bytes {
1705 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1706 }
1707
1708 const SUBSTITUTIONS: &[char; 62] = &[
1712 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1713 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1714 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1715 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1716 ];
1717
1718 let mut fopts = OpenOptions::new();
1721 fopts.read(true).write(true).create_new(true);
1722
1723 cfg_select! {
1724 unix => {
1725 use std::os::unix::fs::OpenOptionsExt;
1726 fopts.mode(0o600);
1728 fopts.custom_flags(libc::O_EXCL);
1729 }
1730 windows => {
1731 use std::os::windows::fs::OpenOptionsExt;
1732 fopts.share_mode(0);
1734 }
1735 _ => {
1736 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1737 }
1738 }
1739
1740 for _ in 0..max_attempts {
1742 let rng = this.machine.rng.get_mut();
1743
1744 let unique_suffix =
1746 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1747
1748 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1750
1751 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1753
1754 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1756 match file {
1757 Ok(f) => {
1758 let fd = this.machine.fds.insert_new(FileHandle {
1759 file: f,
1760 writable: true,
1761 readable: true,
1762 });
1763 return interp_ok(Scalar::from_i32(fd));
1764 }
1765 Err(e) =>
1766 match e.kind() {
1767 ErrorKind::AlreadyExists => continue,
1769 _ => {
1771 return this.set_errno_and_return_neg1_i32(e);
1774 }
1775 },
1776 }
1777 }
1778
1779 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1781 }
1782}
1783
1784fn extract_sec_and_nsec<'tcx>(
1788 time: std::io::Result<SystemTime>,
1789) -> InterpResult<'tcx, Option<(u64, u32)>> {
1790 match time.ok() {
1791 Some(time) => {
1792 let duration = system_time_to_duration(&time)?;
1793 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1794 }
1795 None => interp_ok(None),
1796 }
1797}
1798
1799fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1800 #[cfg(unix)]
1801 use std::os::unix::fs::FileTypeExt;
1802
1803 if file_type.is_file() {
1804 "S_IFREG"
1805 } else if file_type.is_dir() {
1806 "S_IFDIR"
1807 } else if file_type.is_symlink() {
1808 "S_IFLNK"
1809 } else {
1810 #[cfg(unix)]
1812 {
1813 if file_type.is_socket() {
1814 return "S_IFSOCK";
1815 } else if file_type.is_fifo() {
1816 return "S_IFIFO";
1817 } else if file_type.is_char_device() {
1818 return "S_IFCHR";
1819 } else if file_type.is_block_device() {
1820 return "S_IFBLK";
1821 }
1822 }
1823 "S_IFREG"
1824 }
1825}
1826
1827struct FileMetadata {
1835 mode: u32,
1837 size: u64,
1838 created: Option<(u64, u32)>,
1839 accessed: Option<(u64, u32)>,
1840 modified: Option<(u64, u32)>,
1841 dev: Option<u64>,
1842 ino: Option<u64>,
1843 nlink: Option<u64>,
1844 uid: Option<u32>,
1845 gid: Option<u32>,
1846 blksize: Option<u64>,
1847 blocks: Option<u64>,
1848}
1849
1850impl FileMetadata {
1851 fn from_path<'tcx>(
1852 ecx: &mut MiriInterpCx<'tcx>,
1853 path: &Path,
1854 follow_symlink: bool,
1855 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1856 let metadata =
1857 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1858
1859 FileMetadata::from_meta(ecx, metadata)
1860 }
1861
1862 fn from_fd_num<'tcx>(
1863 ecx: &mut MiriInterpCx<'tcx>,
1864 fd_num: i32,
1865 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1866 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1867 return interp_ok(Err(LibcError("EBADF")));
1868 };
1869 match fd.metadata()? {
1870 Either::Left(host) => Self::from_meta(ecx, host),
1871 Either::Right(name) => Self::synthetic(ecx, name),
1872 }
1873 }
1874
1875 fn synthetic<'tcx>(
1876 ecx: &mut MiriInterpCx<'tcx>,
1877 mode_name: &str,
1878 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1879 let mode = ecx.eval_libc(mode_name);
1880 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1881 let mode = mode | 0o666;
1883 interp_ok(Ok(FileMetadata {
1884 mode,
1885 size: 0,
1886 created: None,
1887 accessed: None,
1888 modified: None,
1889 dev: None,
1890 uid: None,
1891 gid: None,
1892 blksize: None,
1893 blocks: None,
1894 ino: None,
1895 nlink: None,
1896 }))
1897 }
1898
1899 fn from_meta<'tcx>(
1900 ecx: &mut MiriInterpCx<'tcx>,
1901 metadata: Result<std::fs::Metadata, std::io::Error>,
1902 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1903 let metadata = match metadata {
1904 Ok(metadata) => metadata,
1905 Err(e) => {
1906 return interp_ok(Err(e.into()));
1907 }
1908 };
1909
1910 let file_type = metadata.file_type();
1911 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1912 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1913
1914 let size = metadata.len();
1915
1916 let created = extract_sec_and_nsec(metadata.created())?;
1917 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1918 let modified = extract_sec_and_nsec(metadata.modified())?;
1919
1920 cfg_select! {
1923 unix => {
1924 use std::os::unix::fs::{MetadataExt, PermissionsExt};
1925
1926 let dev = metadata.dev();
1927 let ino = metadata.ino();
1928 let nlink = metadata.nlink();
1929 let uid = metadata.uid();
1930 let gid = metadata.gid();
1931 let blksize = metadata.blksize();
1932 let blocks = metadata.blocks();
1933
1934 mode |= metadata.permissions().mode();
1935
1936 interp_ok(Ok(FileMetadata {
1937 mode,
1938 size,
1939 created,
1940 accessed,
1941 modified,
1942 dev: Some(dev),
1943 ino: Some(ino),
1944 nlink: Some(nlink),
1945 uid: Some(uid),
1946 gid: Some(gid),
1947 blksize: Some(blksize),
1948 blocks: Some(blocks),
1949 }))
1950 }
1951 _ => {
1952 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1954
1955 interp_ok(Ok(FileMetadata {
1956 mode,
1957 size,
1958 created,
1959 accessed,
1960 modified,
1961 dev: None,
1962 ino: None,
1963 nlink: None,
1964 uid: None,
1965 gid: None,
1966 blksize: None,
1967 blocks: None,
1968 }))
1969 }
1970 }
1971 }
1972}