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.host_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 match this.fallocate_impl(fd_num, offset, len)? {
1394 Ok(()) => interp_ok(Scalar::from_i32(0)),
1395 Err(e) => this.io_error_to_errnum(e),
1396 }
1397 }
1398
1399 fn linux_fallocate(
1400 &mut self,
1401 fd: i32,
1402 mode: i32,
1403 offset: i64,
1404 size: i64,
1405 ) -> InterpResult<'tcx, Scalar> {
1406 let this = self.eval_context_mut();
1408
1409 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1411 this.reject_in_isolation("`fallocate`", reject_with)?;
1412 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1414 }
1415
1416 if mode != 0 {
1419 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode}")
1420 }
1421
1422 match this.fallocate_impl(fd, offset, size)? {
1423 Ok(()) => interp_ok(Scalar::from_i32(0)),
1424 Err(e) => this.set_errno_and_return_neg1_i32(e),
1425 }
1426 }
1427
1428 fn fallocate_impl(
1430 &mut self,
1431 fd_num: i32,
1432 offset: i64,
1433 len: i64,
1434 ) -> InterpResult<'tcx, Result<(), IoError>> {
1435 let this = self.eval_context_mut();
1436
1437 if offset < 0 || len <= 0 {
1439 return interp_ok(Err(LibcError("EINVAL")));
1440 }
1441
1442 let Some(fd) = this.machine.fds.get(fd_num) else {
1443 return interp_ok(Err(LibcError("EBADF")));
1444 };
1445 let Some(file) = fd.downcast::<FileHandle>() else {
1446 return interp_ok(Err(LibcError("ENODEV")));
1448 };
1449
1450 if !file.writable {
1451 return interp_ok(Err(LibcError("EBADF")));
1452 }
1453
1454 let current_size = match file.file.metadata() {
1455 Ok(metadata) => metadata.len(),
1456 Err(err) => return interp_ok(Err(err.into())),
1457 };
1458
1459 let new_size = match offset.checked_add(len) {
1461 Some(new_size) => u64::try_from(new_size).unwrap(),
1463 None => return interp_ok(Err(LibcError("EFBIG"))), };
1465
1466 if current_size < new_size {
1469 match file.file.set_len(new_size) {
1470 Ok(()) => interp_ok(Ok(())),
1471 Err(err) => interp_ok(Err(err.into())),
1472 }
1473 } else {
1474 interp_ok(Ok(()))
1475 }
1476 }
1477
1478 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1479 let this = self.eval_context_mut();
1485
1486 let fd = this.read_scalar(fd_op)?.to_i32()?;
1487
1488 self.ffullsync_fd(fd)
1489 }
1490
1491 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1492 let this = self.eval_context_mut();
1493 let Some(fd) = this.machine.fds.get(fd_num) else {
1494 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1495 };
1496 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1498 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1499 })?;
1500 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1501
1502 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1503 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1504 }
1505
1506 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1507 let this = self.eval_context_mut();
1508
1509 let fd = this.read_scalar(fd_op)?.to_i32()?;
1510
1511 let Some(fd) = this.machine.fds.get(fd) else {
1512 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1513 };
1514 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1516 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1517 })?;
1518 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1519
1520 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1521 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1522 }
1523
1524 fn futimens(
1527 &mut self,
1528 fd_op: &OpTy<'tcx>,
1529 times_op: &OpTy<'tcx>,
1530 ) -> InterpResult<'tcx, Scalar> {
1531 let this = self.eval_context_mut();
1532
1533 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1534 let times_ptr = this.read_pointer(times_op)?;
1535
1536 let Some(fd) = this.machine.fds.get(fd_num) else {
1537 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1538 };
1539 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1540 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1541 })?;
1542 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1543
1544 let (access, modified) = if this.ptr_is_null(times_ptr)? {
1545 let now = TimeUpdate::Set(SystemTime::now());
1546 (now, now)
1547 } else {
1548 let timespec = this.libc_ty_layout("timespec");
1549 let access_place = this.deref_pointer_as(times_op, timespec)?;
1550 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1551 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1552 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1553 };
1554 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1555 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1556 };
1557 (access, modified)
1558 };
1559
1560 let mut filetimes = FileTimes::new();
1561 if let TimeUpdate::Set(access) = access {
1562 filetimes = filetimes.set_accessed(access);
1563 }
1564 if let TimeUpdate::Set(modified) = modified {
1565 filetimes = filetimes.set_modified(modified);
1566 }
1567 let result = file.file.set_times(filetimes);
1568 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1569 }
1570
1571 fn sync_file_range(
1572 &mut self,
1573 fd_op: &OpTy<'tcx>,
1574 offset_op: &OpTy<'tcx>,
1575 nbytes_op: &OpTy<'tcx>,
1576 flags_op: &OpTy<'tcx>,
1577 ) -> InterpResult<'tcx, Scalar> {
1578 let this = self.eval_context_mut();
1579
1580 let fd = this.read_scalar(fd_op)?.to_i32()?;
1581 let offset = this.read_scalar(offset_op)?.to_i64()?;
1582 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1583 let flags = this.read_scalar(flags_op)?.to_i32()?;
1584
1585 if offset < 0 || nbytes < 0 {
1586 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1587 }
1588 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1589 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1590 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1591 if flags & allowed_flags != flags {
1592 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1593 }
1594
1595 let Some(fd) = this.machine.fds.get(fd) else {
1596 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1597 };
1598 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1600 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1601 })?;
1602 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1603
1604 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1605 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1606 }
1607
1608 fn readlink(
1609 &mut self,
1610 pathname_op: &OpTy<'tcx>,
1611 buf_op: &OpTy<'tcx>,
1612 bufsize_op: &OpTy<'tcx>,
1613 ) -> InterpResult<'tcx, i64> {
1614 let this = self.eval_context_mut();
1615
1616 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1617 let buf = this.read_pointer(buf_op)?;
1618 let bufsize = this.read_target_usize(bufsize_op)?;
1619
1620 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1622 this.reject_in_isolation("`readlink`", reject_with)?;
1623 this.set_last_error(LibcError("EACCES"))?;
1624 return interp_ok(-1);
1625 }
1626
1627 let result = std::fs::read_link(pathname);
1628 match result {
1629 Ok(resolved) => {
1630 let resolved = this.convert_path(
1634 Cow::Borrowed(resolved.as_ref()),
1635 crate::shims::os_str::PathConversion::HostToTarget,
1636 );
1637 let mut path_bytes = resolved.as_encoded_bytes();
1638 let bufsize: usize = bufsize.try_into().unwrap();
1639 if path_bytes.len() > bufsize {
1640 path_bytes = &path_bytes[..bufsize]
1641 }
1642 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1643 interp_ok(path_bytes.len().try_into().unwrap())
1644 }
1645 Err(e) => {
1646 this.set_last_error(e)?;
1647 interp_ok(-1)
1648 }
1649 }
1650 }
1651
1652 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1653 let this = self.eval_context_mut();
1654 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1657 let error = if let Some(fd) = this.machine.fds.get(fd) {
1658 if fd.is_tty(this.machine.communicate()) {
1659 return interp_ok(Scalar::from_i32(1));
1660 } else {
1661 LibcError("ENOTTY")
1662 }
1663 } else {
1664 LibcError("EBADF")
1666 };
1667 this.set_last_error(error)?;
1668 interp_ok(Scalar::from_i32(0))
1669 }
1670
1671 fn realpath(
1672 &mut self,
1673 path_op: &OpTy<'tcx>,
1674 processed_path_op: &OpTy<'tcx>,
1675 ) -> InterpResult<'tcx, Scalar> {
1676 let this = self.eval_context_mut();
1677 this.assert_target_os_is_unix("realpath");
1678
1679 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1680 let processed_ptr = this.read_pointer(processed_path_op)?;
1681
1682 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1684 this.reject_in_isolation("`realpath`", reject_with)?;
1685 this.set_last_error(LibcError("EACCES"))?;
1686 return interp_ok(Scalar::from_target_usize(0, this));
1687 }
1688
1689 let result = std::fs::canonicalize(pathname);
1690 match result {
1691 Ok(resolved) => {
1692 let path_max = this
1693 .eval_libc_i32("PATH_MAX")
1694 .try_into()
1695 .expect("PATH_MAX does not fit in u64");
1696 let dest = if this.ptr_is_null(processed_ptr)? {
1697 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1707 } else {
1708 let (wrote_path, _) =
1709 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1710
1711 if !wrote_path {
1712 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1716 return interp_ok(Scalar::from_target_usize(0, this));
1717 }
1718 processed_ptr
1719 };
1720
1721 interp_ok(Scalar::from_maybe_pointer(dest, this))
1722 }
1723 Err(e) => {
1724 this.set_last_error(e)?;
1725 interp_ok(Scalar::from_target_usize(0, this))
1726 }
1727 }
1728 }
1729 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1730 use rand::seq::IndexedRandom;
1731
1732 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1734
1735 let this = self.eval_context_mut();
1736 this.assert_target_os_is_unix("mkstemp");
1737
1738 let max_attempts = this.eval_libc_u32("TMP_MAX");
1748
1749 let template_ptr = this.read_pointer(template_op)?;
1752 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1753 let template_bytes = template.as_mut_slice();
1754
1755 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1757 this.reject_in_isolation("`mkstemp`", reject_with)?;
1758 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1759 }
1760
1761 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1763
1764 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1769 let end_pos = template_bytes.len();
1770 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1771
1772 if last_six_char_bytes != suffix_bytes {
1774 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1775 }
1776
1777 const SUBSTITUTIONS: &[char; 62] = &[
1781 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1782 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1783 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1784 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1785 ];
1786
1787 let mut fopts = OpenOptions::new();
1790 fopts.read(true).write(true).create_new(true);
1791
1792 cfg_select! {
1793 unix =>
1794 {
1795 use std::os::unix::fs::OpenOptionsExt;
1796 fopts.mode(0o600);
1798 fopts.custom_flags(libc::O_EXCL);
1799 }
1800 windows =>
1801 {
1802 use std::os::windows::fs::OpenOptionsExt;
1803 fopts.share_mode(0);
1805 }
1806 _ => {
1807 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1808 }
1809 }
1810
1811 for _ in 0..max_attempts {
1813 let rng = this.machine.rng.get_mut();
1814
1815 let unique_suffix =
1817 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1818
1819 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1821
1822 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1824
1825 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1827 match file {
1828 Ok(f) => {
1829 let fd = this.machine.fds.insert_new(FileHandle {
1830 file: f,
1831 writable: true,
1832 readable: true,
1833 });
1834 return interp_ok(Scalar::from_i32(fd));
1835 }
1836 Err(e) =>
1837 match e.kind() {
1838 ErrorKind::AlreadyExists => continue,
1840 _ => {
1842 return this.set_errno_and_return_neg1_i32(e);
1845 }
1846 },
1847 }
1848 }
1849
1850 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1852 }
1853}
1854
1855fn extract_sec_and_nsec<'tcx>(
1859 time: std::io::Result<SystemTime>,
1860) -> InterpResult<'tcx, Option<(u64, u32)>> {
1861 match time.ok() {
1862 Some(time) => {
1863 let duration = system_time_to_duration(&time)?;
1864 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1865 }
1866 None => interp_ok(None),
1867 }
1868}
1869
1870fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1871 #[cfg(unix)]
1872 use std::os::unix::fs::FileTypeExt;
1873
1874 if file_type.is_file() {
1875 "S_IFREG"
1876 } else if file_type.is_dir() {
1877 "S_IFDIR"
1878 } else if file_type.is_symlink() {
1879 "S_IFLNK"
1880 } else {
1881 #[cfg(unix)]
1883 {
1884 if file_type.is_socket() {
1885 return "S_IFSOCK";
1886 } else if file_type.is_fifo() {
1887 return "S_IFIFO";
1888 } else if file_type.is_char_device() {
1889 return "S_IFCHR";
1890 } else if file_type.is_block_device() {
1891 return "S_IFBLK";
1892 }
1893 }
1894 "S_IFREG"
1895 }
1896}
1897
1898struct FileMetadata {
1906 mode: u32,
1908 size: u64,
1909 created: Option<(u64, u32)>,
1910 accessed: Option<(u64, u32)>,
1911 modified: Option<(u64, u32)>,
1912 dev: Option<u64>,
1913 ino: Option<u64>,
1914 nlink: Option<u64>,
1915 uid: Option<u32>,
1916 gid: Option<u32>,
1917 blksize: Option<u64>,
1918 blocks: Option<u64>,
1919}
1920
1921impl FileMetadata {
1922 fn from_path<'tcx>(
1923 ecx: &mut MiriInterpCx<'tcx>,
1924 path: &Path,
1925 follow_symlink: bool,
1926 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1927 let metadata =
1928 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1929
1930 FileMetadata::from_meta(ecx, metadata)
1931 }
1932
1933 fn from_fd_num<'tcx>(
1934 ecx: &mut MiriInterpCx<'tcx>,
1935 fd_num: i32,
1936 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1937 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1938 return interp_ok(Err(LibcError("EBADF")));
1939 };
1940 match fd.metadata()? {
1941 Either::Left(host) => Self::from_meta(ecx, host),
1942 Either::Right(name) => Self::synthetic(ecx, name),
1943 }
1944 }
1945
1946 fn synthetic<'tcx>(
1947 ecx: &mut MiriInterpCx<'tcx>,
1948 mode_name: &str,
1949 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1950 let mode = ecx.eval_libc(mode_name);
1951 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1952 let mode = mode | 0o666;
1954 interp_ok(Ok(FileMetadata {
1955 mode,
1956 size: 0,
1957 created: None,
1958 accessed: None,
1959 modified: None,
1960 dev: None,
1961 uid: None,
1962 gid: None,
1963 blksize: None,
1964 blocks: None,
1965 ino: None,
1966 nlink: None,
1967 }))
1968 }
1969
1970 fn from_meta<'tcx>(
1971 ecx: &mut MiriInterpCx<'tcx>,
1972 metadata: Result<std::fs::Metadata, std::io::Error>,
1973 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1974 let metadata = match metadata {
1975 Ok(metadata) => metadata,
1976 Err(e) => {
1977 return interp_ok(Err(e.into()));
1978 }
1979 };
1980
1981 let file_type = metadata.file_type();
1982 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1983 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1984
1985 let size = metadata.len();
1986
1987 let created = extract_sec_and_nsec(metadata.created())?;
1988 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1989 let modified = extract_sec_and_nsec(metadata.modified())?;
1990
1991 cfg_select! {
1994 unix => {
1995 use std::os::unix::fs::MetadataExt;
1996 use std::os::unix::fs::PermissionsExt;
1997
1998 let dev = metadata.dev();
1999 let ino = metadata.ino();
2000 let nlink = metadata.nlink();
2001 let uid = metadata.uid();
2002 let gid = metadata.gid();
2003 let blksize = metadata.blksize();
2004 let blocks = metadata.blocks();
2005
2006 mode |= metadata.permissions().mode();
2007
2008 interp_ok(Ok(FileMetadata {
2009 mode,
2010 size,
2011 created,
2012 accessed,
2013 modified,
2014 dev: Some(dev),
2015 ino: Some(ino),
2016 nlink: Some(nlink),
2017 uid: Some(uid),
2018 gid: Some(gid),
2019 blksize: Some(blksize),
2020 blocks: Some(blocks),
2021 }))
2022 }
2023 _ => {
2024 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
2026
2027 interp_ok(Ok(FileMetadata {
2028 mode,
2029 size,
2030 created,
2031 accessed,
2032 modified,
2033 dev: None,
2034 ino: None,
2035 nlink: None,
2036 uid: None,
2037 gid: None,
2038 blksize: None,
2039 blocks: None,
2040 }))
2041 },
2042 }
2043 }
2044}