1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, Dir, 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::FdId;
17use crate::shims::files::{DirHandle, FdNum, FileHandle};
18use crate::shims::os_str::bytes_to_os_str;
19use crate::shims::sig::Varargs;
20use crate::shims::unix::fd::{EvalContextExt as _, FlockOp, UnixFileDescription};
21use crate::*;
22
23#[derive(Debug)]
25struct DirStream {
26 read_dir: fs::ReadDir,
28 fd_num: FdNum,
32 fd_id: FdId,
33 special_entries: Vec<&'static str>,
36 entry: Option<Pointer>,
39}
40
41impl DirStream {
42 fn new(read_dir: fs::ReadDir, fd_num: FdNum, fd_id: FdId) -> Self {
43 Self { read_dir, fd_num, fd_id, special_entries: vec!["..", "."], entry: None }
44 }
45
46 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
47 if let Some(special) = self.special_entries.pop() {
48 return Some(Ok(Either::Right(special)));
49 }
50 let entry = self.read_dir.next()?;
51 Some(entry.map(Either::Left))
52 }
53}
54
55#[derive(Debug)]
56struct DirEntry {
57 name: OsString,
58 ino: u64,
59 d_type: i32,
60}
61
62#[derive(Copy, Clone)]
64enum TimeUpdate {
65 Omit,
66 Set(SystemTime),
67}
68
69impl UnixFileDescription for FileHandle {
70 fn pread<'tcx>(
71 &self,
72 communicate_allowed: bool,
73 offset: u64,
74 ptr: Pointer,
75 len: usize,
76 ecx: &mut MiriInterpCx<'tcx>,
77 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
78 ) -> InterpResult<'tcx> {
79 assert!(communicate_allowed, "isolation should have prevented even opening a file");
80 if !self.readable {
81 return finish.call(ecx, Err(LibcError("EBADF")));
82 }
83
84 let mut bytes = vec![0; len];
85 let file = &mut &self.file;
89 let mut f = || {
90 let cursor_pos = file.stream_position()?;
91 file.seek(SeekFrom::Start(offset))?;
92 let res = file.read(&mut bytes);
93 file.seek(SeekFrom::Start(cursor_pos))
95 .expect("failed to restore file position, this shouldn't be possible");
96 res
97 };
98 let result = match f() {
99 Ok(read_size) => {
100 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
104 Ok(read_size)
105 }
106 Err(e) => Err(IoError::HostError(e)),
107 };
108 finish.call(ecx, result)
109 }
110
111 fn pwrite<'tcx>(
112 &self,
113 communicate_allowed: bool,
114 ptr: Pointer,
115 len: usize,
116 offset: u64,
117 ecx: &mut MiriInterpCx<'tcx>,
118 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
119 ) -> InterpResult<'tcx> {
120 assert!(communicate_allowed, "isolation should have prevented even opening a file");
121 if !self.writable {
122 return finish.call(ecx, Err(LibcError("EBADF")));
123 }
124
125 let file = &mut &self.file;
129 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
130 let mut f = || {
131 let cursor_pos = file.stream_position()?;
132 file.seek(SeekFrom::Start(offset))?;
133 let res = file.write(bytes);
134 file.seek(SeekFrom::Start(cursor_pos))
136 .expect("failed to restore file position, this shouldn't be possible");
137 res
138 };
139 let result = f();
140 finish.call(ecx, result.map_err(IoError::HostError))
141 }
142
143 fn flock<'tcx>(
144 &self,
145 communicate_allowed: bool,
146 op: FlockOp,
147 ) -> InterpResult<'tcx, io::Result<()>> {
148 assert!(communicate_allowed, "isolation should have prevented even opening a file");
149
150 use FlockOp::*;
151 let (res, nonblocking) = match op {
153 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
154 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
155 Unlock => {
156 return interp_ok(self.file.unlock());
157 }
158 };
159
160 match res {
161 Ok(()) => interp_ok(Ok(())),
162 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
163 Err(TryLockError::WouldBlock) =>
164 if nonblocking {
165 interp_ok(Err(ErrorKind::WouldBlock.into()))
166 } else {
167 throw_unsup_format!("blocking `flock` is not currently supported");
168 },
169 }
170 }
171}
172
173#[derive(Debug)]
177pub struct DirTable {
178 streams: FxHashMap<u64, DirStream>,
188 next_id: u64,
190}
191
192impl DirTable {
193 #[expect(clippy::arithmetic_side_effects)]
194 fn insert_new(&mut self, stream: DirStream) -> u64 {
195 let id = self.next_id;
196 self.next_id += 1;
197 self.streams.try_insert(id, stream).unwrap();
198 id
199 }
200}
201
202impl Default for DirTable {
203 fn default() -> DirTable {
204 DirTable {
205 streams: FxHashMap::default(),
206 next_id: 1,
208 }
209 }
210}
211
212impl VisitProvenance for DirTable {
213 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
214 let DirTable { streams, next_id: _ } = self;
215
216 for dir in streams.values() {
217 dir.entry.visit_provenance(visit);
218 }
219 }
220}
221
222fn maybe_sync_file(
223 file: &File,
224 writable: bool,
225 operation: fn(&File) -> std::io::Result<()>,
226) -> std::io::Result<i32> {
227 if !writable && cfg!(windows) {
228 Ok(0i32)
232 } else {
233 let result = operation(file);
234 result.map(|_| 0i32)
235 }
236}
237
238impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
239trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
240 fn parse_utimens_timespec(
243 &self,
244 tp: &MPlaceTy<'tcx>,
245 ) -> InterpResult<'tcx, Option<TimeUpdate>> {
246 let this = self.eval_context_ref();
247 assert!(this.machine.communicate(), "isolation should have prevented reaching this");
249
250 let nsec_place = this.project_field(tp, FieldIdx::ONE)?;
252 let nsec = this.read_scalar(&nsec_place)?.to_target_isize(this)?;
253
254 if nsec == this.eval_libc("UTIME_OMIT").to_target_isize(this)? {
255 return interp_ok(Some(TimeUpdate::Omit));
256 }
257 if nsec == this.eval_libc("UTIME_NOW").to_target_isize(this)? {
258 return interp_ok(Some(TimeUpdate::Set(SystemTime::now())));
259 }
260
261 let Some(duration) = this.read_timespec(tp)? else {
262 return interp_ok(None);
263 };
264 interp_ok(SystemTime::UNIX_EPOCH.checked_add(duration).map(TimeUpdate::Set))
265 }
266
267 fn write_stat_buf(
268 &mut self,
269 metadata: FileMetadata,
270 buf_op: &OpTy<'tcx>,
271 ) -> InterpResult<'tcx, i32> {
272 let this = self.eval_context_mut();
273
274 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
275 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
276 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
277
278 let buf = this.deref_pointer(buf_op)?;
283
284 this.write_int_fields_named(
285 &[
286 ("st_dev", metadata.dev.unwrap_or(0).into()),
287 ("st_mode", metadata.mode.into()),
288 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
289 ("st_ino", metadata.ino.unwrap_or(0).into()),
290 ("st_uid", metadata.uid.unwrap_or(0).into()),
291 ("st_gid", metadata.gid.unwrap_or(0).into()),
292 ("st_rdev", 0),
293 ("st_atime", access_sec.into()),
294 ("st_atime_nsec", access_nsec.into()),
295 ("st_mtime", modified_sec.into()),
296 ("st_mtime_nsec", modified_nsec.into()),
297 ("st_ctime", 0),
298 ("st_ctime_nsec", 0),
299 ("st_size", metadata.size.into()),
300 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
301 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
302 ],
303 &buf,
304 )?;
305
306 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
307 this.write_int_fields_named(
308 &[
309 ("st_birthtime", created_sec.into()),
310 ("st_birthtime_nsec", created_nsec.into()),
311 ("st_flags", 0),
312 ("st_gen", 0),
313 ],
314 &buf,
315 )?;
316 }
317
318 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
319 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
320 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
322 }
323
324 interp_ok(0)
325 }
326
327 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
328 #[cfg(unix)]
329 use std::os::unix::fs::FileTypeExt;
330
331 let this = self.eval_context_ref();
332 match file_type {
333 Ok(file_type) => {
334 match () {
335 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
336 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
337 _ if file_type.is_symlink() =>
338 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
339 #[cfg(unix)]
341 _ if file_type.is_block_device() =>
342 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
343 #[cfg(unix)]
344 _ if file_type.is_char_device() =>
345 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
346 #[cfg(unix)]
347 _ if file_type.is_fifo() =>
348 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
349 #[cfg(unix)]
350 _ if file_type.is_socket() =>
351 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
352 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
354 }
355 }
356 Err(_) => {
357 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
359 }
360 }
361 }
362
363 fn dir_entry_fields(
364 &self,
365 entry: Either<fs::DirEntry, &'static str>,
366 ) -> InterpResult<'tcx, DirEntry> {
367 let this = self.eval_context_ref();
368 interp_ok(match entry {
369 Either::Left(dir_entry) => {
370 DirEntry {
371 name: dir_entry.file_name(),
372 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
373 #[cfg(unix)]
376 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
377 #[cfg(not(unix))]
378 ino: 0u64,
379 }
380 }
381 Either::Right(special) =>
382 DirEntry {
383 name: special.into(),
384 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
385 ino: 0,
386 },
387 })
388 }
389
390 #[cfg(unix)]
391 fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
392 use std::os::unix::fs::PermissionsExt;
393 interp_ok(fs::Permissions::from_mode(mode))
394 }
395
396 #[cfg(not(unix))]
397 fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> {
398 throw_unsup_format!("setting file permissions is only supported on Unix hosts")
399 }
400}
401
402impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
403pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
404 fn open(
405 &mut self,
406 path_raw: &OpTy<'tcx>,
407 flag: &OpTy<'tcx>,
408 varargs: Varargs<'tcx, '_>,
409 ) -> InterpResult<'tcx, Scalar> {
410 let this = self.eval_context_mut();
411
412 let path_raw = this.read_pointer(path_raw)?;
413 let flag = this.read_scalar(flag)?.to_i32()?;
414
415 let path = this.read_path_from_c_str(path_raw)?;
416 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
418 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
419 {
420 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
421 }
422
423 let mut flag = flag;
425
426 let mut options = OpenOptions::new();
427
428 let o_rdonly = this.eval_libc_i32("O_RDONLY");
429 let o_wronly = this.eval_libc_i32("O_WRONLY");
430 let o_rdwr = this.eval_libc_i32("O_RDWR");
431 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
435 throw_unsup_format!("access mode flags on this target are unsupported");
436 }
437 let mut writable = true;
438 let mut readable = true;
439
440 let access_mode = flag & 0b11;
442 flag &= !access_mode;
443
444 if access_mode == o_rdonly {
445 writable = false;
446 options.read(true);
447 } else if access_mode == o_wronly {
448 readable = false;
449 options.write(true);
450 } else if access_mode == o_rdwr {
451 options.read(true).write(true);
452 } else {
453 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
454 }
455
456 let o_append = this.eval_libc_i32("O_APPEND");
457 if flag & o_append == o_append {
458 flag &= !o_append;
459 options.append(true);
460 }
461 let o_trunc = this.eval_libc_i32("O_TRUNC");
462 if flag & o_trunc == o_trunc {
463 flag &= !o_trunc;
464 options.truncate(true);
465 }
466 let o_creat = this.eval_libc_i32("O_CREAT");
467 if flag & o_creat == o_creat {
468 flag &= !o_creat;
469 let ([mode], _) = this.check_varargs(
471 if this.libc_ty_layout("mode_t").size.bytes() >= 4 {
472 shim_varargs![libc::mode_t]
474 } else {
475 shim_varargs![i32]
478 },
479 varargs,
480 "open(pathname, O_CREAT, ...)",
481 )?;
482 let mode = this.read_scalar(mode)?.to_u32()?;
483
484 #[cfg(unix)]
485 {
486 use std::os::unix::fs::OpenOptionsExt;
488 options.mode(mode);
489 }
490 #[cfg(not(unix))]
491 {
492 if mode != 0o666 {
494 throw_unsup_format!(
495 "non-default mode 0o{:o} is not supported on non-Unix hosts",
496 mode
497 );
498 }
499 }
500
501 let o_excl = this.eval_libc_i32("O_EXCL");
502 if flag & o_excl == o_excl {
503 flag &= !o_excl;
504 options.create_new(true);
505 } else {
506 options.create(true);
507 }
508 }
509 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
510 if flag & o_cloexec == o_cloexec {
511 flag &= !o_cloexec;
512 }
515 if this.tcx.sess.target.os == Os::Linux {
516 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
517 if flag & o_tmpfile == o_tmpfile {
518 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
520 }
521 }
522
523 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
524 if flag & o_nofollow == o_nofollow {
525 flag &= !o_nofollow;
526 #[cfg(unix)]
527 {
528 use std::os::unix::fs::OpenOptionsExt;
529 options.custom_flags(libc::O_NOFOLLOW);
530 }
531 #[cfg(not(unix))]
535 {
536 if path.is_symlink() {
539 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
540 }
541 }
542 }
543
544 if flag != 0 {
546 throw_unsup_format!("unsupported flags for `open`: {flag:#x}");
547 }
548
549 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
551 this.reject_in_isolation("`open`", reject_with)?;
552 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
553 }
554
555 let fd = options
556 .open(path)
557 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable }));
558
559 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
560 }
561
562 fn lseek(
563 &mut self,
564 fd_num: i32,
565 offset: i128,
566 whence: i32,
567 dest: &MPlaceTy<'tcx>,
568 ) -> InterpResult<'tcx> {
569 let this = self.eval_context_mut();
570
571 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
574 if offset < 0 {
575 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
577 } else {
578 SeekFrom::Start(u64::try_from(offset).unwrap())
579 }
580 } else if whence == this.eval_libc_i32("SEEK_CUR") {
581 SeekFrom::Current(i64::try_from(offset).unwrap())
582 } else if whence == this.eval_libc_i32("SEEK_END") {
583 SeekFrom::End(i64::try_from(offset).unwrap())
584 } else {
585 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
586 };
587
588 let communicate = this.machine.communicate();
589
590 let Some(fd) = this.machine.fds.get(fd_num) else {
591 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
592 };
593 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
594 drop(fd);
595
596 let result = this.try_unwrap_io_result(result)?;
597 this.write_int(result, dest)?;
598 interp_ok(())
599 }
600
601 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
602 let this = self.eval_context_mut();
603
604 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
605
606 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
608 this.reject_in_isolation("`unlink`", reject_with)?;
609 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
610 }
611
612 let result = fs::remove_file(path).map(|_| 0);
613 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
614 }
615
616 fn symlink(
617 &mut self,
618 target_op: &OpTy<'tcx>,
619 linkpath_op: &OpTy<'tcx>,
620 ) -> InterpResult<'tcx, Scalar> {
621 #[cfg(unix)]
622 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
623 std::os::unix::fs::symlink(src, dst)
624 }
625
626 #[cfg(windows)]
627 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
628 use std::os::windows::fs;
629 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
630 }
631
632 let this = self.eval_context_mut();
633 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
634 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
635
636 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
638 this.reject_in_isolation("`symlink`", reject_with)?;
639 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
640 }
641
642 let result = create_link(&target, &linkpath).map(|_| 0);
643 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
644 }
645
646 fn linkat(
647 &mut self,
648 oldfd_op: &OpTy<'tcx>,
649 oldpath_op: &OpTy<'tcx>,
650 newfd_op: &OpTy<'tcx>,
651 newpath_op: &OpTy<'tcx>,
652 flags_op: &OpTy<'tcx>,
653 ) -> InterpResult<'tcx, Scalar> {
654 let this = self.eval_context_mut();
655
656 let flags = this.read_scalar(flags_op)?.to_i32()?;
658 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
659 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
660 let oldpath_ptr = this.read_pointer(oldpath_op)?;
661 let newpath_ptr = this.read_pointer(newpath_op)?;
662
663 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
665
666 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
668 this.reject_in_isolation("`linkat`", reject_with)?;
669 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
670 }
671
672 if flags != 0 {
674 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
675 }
676
677 if oldfd != at_fdcwd {
679 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
680 }
681 if oldpath_ptr == Pointer::null() {
682 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
683 }
684 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
685
686 if newfd != at_fdcwd {
688 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
689 }
690 if newpath_ptr == Pointer::null() {
691 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
692 }
693 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
694
695 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
696 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
697 }
698
699 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
700 let this = self.eval_context_mut();
701
702 if !matches!(
703 &this.tcx.sess.target.os,
704 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
705 ) {
706 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
707 }
708
709 let path_scalar = this.read_pointer(path_op)?;
710 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
711
712 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
714 this.reject_in_isolation("`stat`", reject_with)?;
715 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
716 }
717
718 let metadata = match FileMetadata::from_host(this, fs::metadata(path))? {
719 Ok(metadata) => metadata,
720 Err(err) => return this.set_errno_and_return_neg1_i32(err),
721 };
722
723 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
724 }
725
726 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
728 let this = self.eval_context_mut();
729
730 if !matches!(
731 &this.tcx.sess.target.os,
732 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
733 ) {
734 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
735 }
736
737 let path_scalar = this.read_pointer(path_op)?;
738 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
739
740 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
742 this.reject_in_isolation("`lstat`", reject_with)?;
743 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
744 }
745
746 let metadata = match FileMetadata::from_host(this, fs::symlink_metadata(path))? {
747 Ok(metadata) => metadata,
748 Err(err) => return this.set_errno_and_return_neg1_i32(err),
749 };
750
751 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
752 }
753
754 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
755 let this = self.eval_context_mut();
756
757 if !matches!(
758 &this.tcx.sess.target.os,
759 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
760 ) {
761 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
762 }
763
764 let fd = this.read_scalar(fd_op)?.to_i32()?;
765
766 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
768 this.reject_in_isolation("`fstat`", reject_with)?;
769 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
771 }
772
773 let metadata = match FileMetadata::from_fd_num(this, fd)? {
774 Ok(metadata) => metadata,
775 Err(err) => return this.set_errno_and_return_neg1_i32(err),
776 };
777 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
778 }
779
780 fn linux_statx(
781 &mut self,
782 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
788 let this = self.eval_context_mut();
789
790 this.assert_target_os(Os::Linux, "statx");
791
792 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
793 let pathname_ptr = this.read_pointer(pathname_op)?;
794 let flags = this.read_scalar(flags_op)?.to_i32()?;
795 let _mask = this.read_scalar(mask_op)?.to_u32()?;
796 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
797
798 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
800 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
801 }
802
803 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
804 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
805
806 let mut flags = flags;
808 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
810 let empty_path_flag = flags & at_empty_path == at_empty_path;
811 flags &= !at_empty_path;
812 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
814 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
815 flags &= !at_symlink_nofollow;
816 if flags != 0 {
818 throw_unsup_format!("unsupported flags for `statx`: {flags:#x}")
819 }
820
821 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
823 this.reject_in_isolation("`statx`", reject_with)?;
824 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
825 }
826
827 let metadata = if path.is_empty() {
830 if !empty_path_flag {
832 return this.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
833 }
834 FileMetadata::from_fd_num(this, dirfd)?
835 } else if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
836 FileMetadata::from_host(
838 this,
839 if symlink_nofollow_flag { fs::symlink_metadata(path) } else { fs::metadata(path) },
840 )?
841 } else {
842 let Some(fd) = this.machine.fds.get(dirfd) else {
844 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
845 };
846 let Some(_dir) = fd.downcast::<DirHandle>() else {
847 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
848 };
849
850 throw_unsup_format!("statx relative to a directory handle is not supported");
851 };
852
853 let metadata = match metadata {
854 Ok(metadata) => metadata,
855 Err(err) => return this.set_errno_and_return_neg1_i32(err),
856 };
857
858 let mut mask = this.eval_libc_u32("STATX_TYPE")
863 | this.eval_libc_u32("STATX_MODE")
864 | this.eval_libc_u32("STATX_SIZE");
865
866 if metadata.ino.is_some() {
868 mask |= this.eval_libc_u32("STATX_INO");
869 }
870 if metadata.nlink.is_some() {
871 mask |= this.eval_libc_u32("STATX_NLINK");
872 }
873 if metadata.uid.is_some() {
874 mask |= this.eval_libc_u32("STATX_UID");
875 }
876 if metadata.gid.is_some() {
877 mask |= this.eval_libc_u32("STATX_GID");
878 }
879 if metadata.blocks.is_some() {
880 mask |= this.eval_libc_u32("STATX_BLOCKS");
881 }
882
883 let (access_sec, access_nsec) = metadata
886 .accessed
887 .map(|tup| {
888 mask |= this.eval_libc_u32("STATX_ATIME");
889 interp_ok(tup)
890 })
891 .unwrap_or_else(|| interp_ok((0, 0)))?;
892
893 let (created_sec, created_nsec) = metadata
894 .created
895 .map(|tup| {
896 mask |= this.eval_libc_u32("STATX_BTIME");
897 interp_ok(tup)
898 })
899 .unwrap_or_else(|| interp_ok((0, 0)))?;
900
901 let (modified_sec, modified_nsec) = metadata
902 .modified
903 .map(|tup| {
904 mask |= this.eval_libc_u32("STATX_MTIME");
905 interp_ok(tup)
906 })
907 .unwrap_or_else(|| interp_ok((0, 0)))?;
908
909 this.write_int_fields_named(
911 &[
912 ("stx_mask", mask.into()),
913 ("stx_mode", metadata.mode.into()),
914 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
915 ("stx_attributes", 0),
916 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
917 ("stx_uid", metadata.uid.unwrap_or(0).into()),
918 ("stx_gid", metadata.gid.unwrap_or(0).into()),
919 ("stx_ino", metadata.ino.unwrap_or(0).into()),
920 ("stx_size", metadata.size.into()),
921 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
922 ("stx_attributes_mask", 0),
923 ("stx_rdev_major", 0),
924 ("stx_rdev_minor", 0),
925 ("stx_dev_major", 0),
926 ("stx_dev_minor", 0),
927 ],
928 &statxbuf,
929 )?;
930 #[rustfmt::skip]
931 this.write_int_fields_named(
932 &[
933 ("tv_sec", access_sec.into()),
934 ("tv_nsec", access_nsec.into()),
935 ],
936 &this.project_field_named(&statxbuf, "stx_atime")?,
937 )?;
938 #[rustfmt::skip]
939 this.write_int_fields_named(
940 &[
941 ("tv_sec", created_sec.into()),
942 ("tv_nsec", created_nsec.into()),
943 ],
944 &this.project_field_named(&statxbuf, "stx_btime")?,
945 )?;
946 #[rustfmt::skip]
947 this.write_int_fields_named(
948 &[
949 ("tv_sec", 0.into()),
950 ("tv_nsec", 0.into()),
951 ],
952 &this.project_field_named(&statxbuf, "stx_ctime")?,
953 )?;
954 #[rustfmt::skip]
955 this.write_int_fields_named(
956 &[
957 ("tv_sec", modified_sec.into()),
958 ("tv_nsec", modified_nsec.into()),
959 ],
960 &this.project_field_named(&statxbuf, "stx_mtime")?,
961 )?;
962
963 interp_ok(Scalar::from_i32(0))
964 }
965
966 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
967 let this = self.eval_context_mut();
968
969 let path_ptr = this.read_pointer(path_op)?;
970 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
971
972 if this.ptr_is_null(path_ptr)? {
973 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
974 }
975 let path = this.read_path_from_c_str(path_ptr)?;
976
977 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
979 this.reject_in_isolation("`chmod`", reject_with)?;
980 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
981 }
982
983 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
984 if let Err(err) = fs::set_permissions(path, permissions) {
985 return this.set_errno_and_return_neg1_i32(err);
986 }
987
988 interp_ok(Scalar::from_i32(0))
989 }
990
991 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
992 let this = self.eval_context_mut();
993
994 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
995 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
996
997 let Some(fd) = this.machine.fds.get(fd_num) else {
998 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
999 };
1000 let Some(file) = fd.downcast::<FileHandle>() else {
1001 throw_unsup_format!("`fchmod` is only supported on regular files")
1003 };
1004 if !file.writable && !file.readable {
1005 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1008 }
1009 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1010
1011 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1012 if let Err(err) = file.file.set_permissions(permissions) {
1013 return this.set_errno_and_return_neg1_i32(err);
1014 }
1015
1016 interp_ok(Scalar::from_i32(0))
1017 }
1018
1019 fn rename(
1020 &mut self,
1021 oldpath_op: &OpTy<'tcx>,
1022 newpath_op: &OpTy<'tcx>,
1023 ) -> InterpResult<'tcx, Scalar> {
1024 let this = self.eval_context_mut();
1025
1026 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1027 let newpath_ptr = this.read_pointer(newpath_op)?;
1028
1029 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1030 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1031 }
1032
1033 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1034 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1035
1036 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1038 this.reject_in_isolation("`rename`", reject_with)?;
1039 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1040 }
1041
1042 let result = fs::rename(oldpath, newpath).map(|_| 0);
1043
1044 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1045 }
1046
1047 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1048 let this = self.eval_context_mut();
1049
1050 #[cfg_attr(not(unix), allow(unused_variables))]
1051 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1052 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1053 } else {
1054 this.read_scalar(mode_op)?.to_u32()?
1055 };
1056
1057 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1058
1059 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1061 this.reject_in_isolation("`mkdir`", reject_with)?;
1062 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1063 }
1064
1065 #[cfg_attr(not(unix), allow(unused_mut))]
1066 let mut builder = DirBuilder::new();
1067
1068 #[cfg(unix)]
1071 {
1072 use std::os::unix::fs::DirBuilderExt;
1073 builder.mode(mode);
1074 }
1075
1076 let result = builder.create(path).map(|_| 0i32);
1077
1078 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1079 }
1080
1081 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1082 let this = self.eval_context_mut();
1083
1084 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1085
1086 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1088 this.reject_in_isolation("`rmdir`", reject_with)?;
1089 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1090 }
1091
1092 let result = fs::remove_dir(path).map(|_| 0i32);
1093
1094 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1095 }
1096
1097 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1098 let this = self.eval_context_mut();
1099
1100 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1101
1102 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1104 this.reject_in_isolation("`opendir`", reject_with)?;
1105 this.set_last_error(LibcError("EACCES"))?;
1106 return interp_ok(Scalar::null_ptr(this));
1107 }
1108
1109 let result = fs::read_dir(&name);
1110
1111 match result {
1112 Ok(read_dir) => {
1113 let Ok(dir) = Dir::open(name) else {
1121 throw_unsup_format!(
1122 "cannot `opendir` this directory: failed to create directory handle"
1123 );
1124 };
1125 let dir = this.machine.fds.new_ref(DirHandle { dir });
1126 let dir_fd_id = dir.id();
1127 let dir_fd_num = this.machine.fds.insert(dir);
1128
1129 let stream = DirStream::new(read_dir, dir_fd_num, dir_fd_id);
1130 let id = this.machine.dirs.insert_new(stream);
1131
1132 interp_ok(Scalar::from_target_usize(id, this))
1137 }
1138 Err(e) => {
1139 this.set_last_error(e)?;
1140 interp_ok(Scalar::null_ptr(this))
1141 }
1142 }
1143 }
1144
1145 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1146 let this = self.eval_context_mut();
1147
1148 if !matches!(
1149 &this.tcx.sess.target.os,
1150 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1151 ) {
1152 throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1153 }
1154
1155 let dirp = this.read_target_usize(dirp_op)?;
1156
1157 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1159 this.reject_in_isolation("`readdir`", reject_with)?;
1160 this.set_last_error(LibcError("EBADF"))?;
1161 this.write_null(dest)?;
1162 return interp_ok(());
1163 }
1164
1165 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1166 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1167 })?;
1168
1169 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1171 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1172 }
1173
1174 let entry = match open_dir.next_host_entry() {
1175 Some(Ok(dir_entry)) => {
1176 let dir_entry = this.dir_entry_fields(dir_entry)?;
1177
1178 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1225 let dirent_layout = this.layout_of(dirent_ty)?;
1226 let fields = &dirent_layout.fields;
1227 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1228
1229 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1233 let name_len = u64::try_from(name_bytes.len()).unwrap();
1234 let size = d_name_offset.strict_add(name_len);
1235
1236 let entry = this.allocate_ptr(
1237 Size::from_bytes(size),
1238 dirent_layout.align.abi,
1239 MiriMemoryKind::Runtime.into(),
1240 AllocInit::Uninit,
1241 )?;
1242 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1243
1244 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1247 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1248
1249 let ino_name =
1251 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1252 this.write_int_fields_named(
1253 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1254 &entry,
1255 )?;
1256
1257 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1259 this.write_null(&d_off)?;
1260 }
1261 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1262 this.write_null(&d_seekoff)?;
1263 }
1264 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1265 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1266 }
1267 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1268 this.write_int(dir_entry.d_type, &d_type)?;
1269 }
1270
1271 Some(entry.ptr())
1272 }
1273 None => {
1274 None
1276 }
1277 Some(Err(e)) => {
1278 this.set_last_error(e)?;
1279 None
1280 }
1281 };
1282
1283 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1284 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1285 if let Some(old_entry) = old_entry {
1286 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1287 }
1288
1289 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1290 interp_ok(())
1291 }
1292
1293 fn dirfd(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1294 let this = self.eval_context_mut();
1295
1296 let dirp = this.read_target_usize(dirp_op)?;
1297
1298 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1299 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1300 })?;
1301
1302 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1304 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1305 }
1306
1307 let fd_num = open_dir.fd_num;
1308 this.write_int(fd_num, dest)?;
1309 interp_ok(())
1310 }
1311
1312 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1313 let this = self.eval_context_mut();
1314
1315 let dirp = this.read_target_usize(dirp_op)?;
1316
1317 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1319 this.reject_in_isolation("`closedir`", reject_with)?;
1320 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1321 }
1322
1323 let mut open_dir = this.machine.dirs.streams.remove(&dirp).ok_or_else(|| {
1324 err_ub_format!("the DIR pointer passed to `closedir` did not come from opendir")
1325 })?;
1326
1327 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1329 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1330 }
1331 this.close(open_dir.fd_num)?;
1333
1334 if let Some(entry) = open_dir.entry.take() {
1335 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1336 }
1337 drop(open_dir);
1339
1340 interp_ok(Scalar::from_i32(0))
1341 }
1342
1343 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1344 let this = self.eval_context_mut();
1345
1346 let Some(fd) = this.machine.fds.get(fd_num) else {
1347 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1348 };
1349 let Some(file) = fd.downcast::<FileHandle>() else {
1350 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1353 };
1354 if !file.writable {
1355 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1357 }
1358 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1359
1360 if let Ok(length) = length.try_into() {
1361 let result = file.file.set_len(length);
1362 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1363 interp_ok(Scalar::from_i32(result))
1364 } else {
1365 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1366 }
1367 }
1368
1369 fn posix_fallocate(
1372 &mut self,
1373 fd_num: i32,
1374 offset: i64,
1375 len: i64,
1376 ) -> InterpResult<'tcx, Scalar> {
1377 let this = self.eval_context_mut();
1378
1379 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1381 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1382 return interp_ok(this.eval_libc("EBADF"));
1384 }
1385
1386 match this.fallocate_impl(fd_num, offset, len)? {
1387 Ok(()) => interp_ok(Scalar::from_i32(0)),
1388 Err(e) => this.io_error_to_errnum(e),
1389 }
1390 }
1391
1392 fn linux_fallocate(
1393 &mut self,
1394 fd: i32,
1395 mode: i32,
1396 offset: i64,
1397 size: i64,
1398 ) -> InterpResult<'tcx, Scalar> {
1399 let this = self.eval_context_mut();
1401
1402 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1404 this.reject_in_isolation("`fallocate`", reject_with)?;
1405 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1407 }
1408
1409 if mode != 0 {
1412 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode:#x}")
1413 }
1414
1415 match this.fallocate_impl(fd, offset, size)? {
1416 Ok(()) => interp_ok(Scalar::from_i32(0)),
1417 Err(e) => this.set_errno_and_return_neg1_i32(e),
1418 }
1419 }
1420
1421 fn fallocate_impl(
1423 &mut self,
1424 fd_num: i32,
1425 offset: i64,
1426 len: i64,
1427 ) -> InterpResult<'tcx, Result<(), IoError>> {
1428 let this = self.eval_context_mut();
1429
1430 if offset < 0 || len <= 0 {
1432 return interp_ok(Err(LibcError("EINVAL")));
1433 }
1434
1435 let Some(fd) = this.machine.fds.get(fd_num) else {
1436 return interp_ok(Err(LibcError("EBADF")));
1437 };
1438 let Some(file) = fd.downcast::<FileHandle>() else {
1439 return interp_ok(Err(LibcError("ENODEV")));
1441 };
1442
1443 if !file.writable {
1444 return interp_ok(Err(LibcError("EBADF")));
1445 }
1446
1447 let current_size = match file.file.metadata() {
1448 Ok(metadata) => metadata.len(),
1449 Err(err) => return interp_ok(Err(err.into())),
1450 };
1451
1452 let new_size = match offset.checked_add(len) {
1454 Some(new_size) => u64::try_from(new_size).unwrap(),
1456 None => return interp_ok(Err(LibcError("EFBIG"))), };
1458
1459 if current_size < new_size {
1462 match file.file.set_len(new_size) {
1463 Ok(()) => interp_ok(Ok(())),
1464 Err(err) => interp_ok(Err(err.into())),
1465 }
1466 } else {
1467 interp_ok(Ok(()))
1468 }
1469 }
1470
1471 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1472 let this = self.eval_context_mut();
1478
1479 let fd = this.read_scalar(fd_op)?.to_i32()?;
1480
1481 self.ffullsync_fd(fd)
1482 }
1483
1484 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1485 let this = self.eval_context_mut();
1486 let Some(fd) = this.machine.fds.get(fd_num) else {
1487 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1488 };
1489 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1491 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1492 })?;
1493 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1494
1495 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1496 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1497 }
1498
1499 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1500 let this = self.eval_context_mut();
1501
1502 let fd = this.read_scalar(fd_op)?.to_i32()?;
1503
1504 let Some(fd) = this.machine.fds.get(fd) else {
1505 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1506 };
1507 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1509 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1510 })?;
1511 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1512
1513 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1514 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1515 }
1516
1517 fn futimens(
1520 &mut self,
1521 fd_op: &OpTy<'tcx>,
1522 times_op: &OpTy<'tcx>,
1523 ) -> InterpResult<'tcx, Scalar> {
1524 let this = self.eval_context_mut();
1525
1526 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1527 let times_ptr = this.read_pointer(times_op)?;
1528
1529 let Some(fd) = this.machine.fds.get(fd_num) else {
1530 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1531 };
1532 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1533 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1534 })?;
1535 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1536
1537 let (access, modified) = if this.ptr_is_null(times_ptr)? {
1538 let now = TimeUpdate::Set(SystemTime::now());
1539 (now, now)
1540 } else {
1541 let timespec = this.libc_ty_layout("timespec");
1542 let access_place = this.deref_pointer_as(times_op, timespec)?;
1543 let modified_place = access_place.offset(timespec.size, timespec, this)?;
1544 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
1545 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1546 };
1547 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
1548 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1549 };
1550 (access, modified)
1551 };
1552
1553 let mut filetimes = FileTimes::new();
1554 if let TimeUpdate::Set(access) = access {
1555 filetimes = filetimes.set_accessed(access);
1556 }
1557 if let TimeUpdate::Set(modified) = modified {
1558 filetimes = filetimes.set_modified(modified);
1559 }
1560 let result = file.file.set_times(filetimes);
1561 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1562 }
1563
1564 fn sync_file_range(
1565 &mut self,
1566 fd_op: &OpTy<'tcx>,
1567 offset_op: &OpTy<'tcx>,
1568 nbytes_op: &OpTy<'tcx>,
1569 flags_op: &OpTy<'tcx>,
1570 ) -> InterpResult<'tcx, Scalar> {
1571 let this = self.eval_context_mut();
1572
1573 let fd = this.read_scalar(fd_op)?.to_i32()?;
1574 let offset = this.read_scalar(offset_op)?.to_i64()?;
1575 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1576 let flags = this.read_scalar(flags_op)?.to_i32()?;
1577
1578 if offset < 0 || nbytes < 0 {
1579 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1580 }
1581 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1582 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1583 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1584 if flags & allowed_flags != flags {
1585 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1586 }
1587
1588 let Some(fd) = this.machine.fds.get(fd) else {
1589 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1590 };
1591 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1593 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1594 })?;
1595 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1596
1597 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1598 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1599 }
1600
1601 fn readlink(
1602 &mut self,
1603 pathname_op: &OpTy<'tcx>,
1604 buf_op: &OpTy<'tcx>,
1605 bufsize_op: &OpTy<'tcx>,
1606 ) -> InterpResult<'tcx, i64> {
1607 let this = self.eval_context_mut();
1608
1609 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1610 let buf = this.read_pointer(buf_op)?;
1611 let bufsize = this.read_target_usize(bufsize_op)?;
1612
1613 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1615 this.reject_in_isolation("`readlink`", reject_with)?;
1616 this.set_last_error(LibcError("EACCES"))?;
1617 return interp_ok(-1);
1618 }
1619
1620 let result = std::fs::read_link(pathname);
1621 match result {
1622 Ok(resolved) => {
1623 let resolved = this.convert_path(
1627 Cow::Borrowed(resolved.as_ref()),
1628 crate::shims::os_str::PathConversion::HostToTarget,
1629 );
1630 let mut path_bytes = resolved.as_encoded_bytes();
1631 let bufsize: usize = bufsize.try_into().unwrap();
1632 if path_bytes.len() > bufsize {
1633 path_bytes = &path_bytes[..bufsize]
1634 }
1635 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1636 interp_ok(path_bytes.len().try_into().unwrap())
1637 }
1638 Err(e) => {
1639 this.set_last_error(e)?;
1640 interp_ok(-1)
1641 }
1642 }
1643 }
1644
1645 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1646 let this = self.eval_context_mut();
1647 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1650 let error = if let Some(fd) = this.machine.fds.get(fd) {
1651 if fd.is_tty(this.machine.communicate()) {
1652 return interp_ok(Scalar::from_i32(1));
1653 } else {
1654 LibcError("ENOTTY")
1655 }
1656 } else {
1657 LibcError("EBADF")
1659 };
1660 this.set_last_error(error)?;
1661 interp_ok(Scalar::from_i32(0))
1662 }
1663
1664 fn realpath(
1665 &mut self,
1666 path_op: &OpTy<'tcx>,
1667 processed_path_op: &OpTy<'tcx>,
1668 ) -> InterpResult<'tcx, Scalar> {
1669 let this = self.eval_context_mut();
1670 this.assert_target_os_is_unix("realpath");
1671
1672 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1673 let processed_ptr = this.read_pointer(processed_path_op)?;
1674
1675 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1677 this.reject_in_isolation("`realpath`", reject_with)?;
1678 this.set_last_error(LibcError("EACCES"))?;
1679 return interp_ok(Scalar::from_target_usize(0, this));
1680 }
1681
1682 let result = std::fs::canonicalize(pathname);
1683 match result {
1684 Ok(resolved) => {
1685 let path_max = this
1686 .eval_libc_i32("PATH_MAX")
1687 .try_into()
1688 .expect("PATH_MAX does not fit in u64");
1689 let dest = if this.ptr_is_null(processed_ptr)? {
1690 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1700 } else {
1701 let (wrote_path, _) =
1702 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1703
1704 if !wrote_path {
1705 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1709 return interp_ok(Scalar::from_target_usize(0, this));
1710 }
1711 processed_ptr
1712 };
1713
1714 interp_ok(Scalar::from_maybe_pointer(dest, this))
1715 }
1716 Err(e) => {
1717 this.set_last_error(e)?;
1718 interp_ok(Scalar::from_target_usize(0, this))
1719 }
1720 }
1721 }
1722 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1723 use rand::seq::IndexedRandom;
1724
1725 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1727
1728 let this = self.eval_context_mut();
1729 this.assert_target_os_is_unix("mkstemp");
1730
1731 let max_attempts = this.eval_libc_u32("TMP_MAX");
1741
1742 let template_ptr = this.read_pointer(template_op)?;
1745 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1746 let template_bytes = template.as_mut_slice();
1747
1748 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1750 this.reject_in_isolation("`mkstemp`", reject_with)?;
1751 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1752 }
1753
1754 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1756
1757 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1762 let end_pos = template_bytes.len();
1763 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1764
1765 if last_six_char_bytes != suffix_bytes {
1767 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1768 }
1769
1770 const SUBSTITUTIONS: &[char; 62] = &[
1774 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1775 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1776 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1777 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1778 ];
1779
1780 let mut fopts = OpenOptions::new();
1783 fopts.read(true).write(true).create_new(true);
1784
1785 cfg_select! {
1786 unix => {
1787 use std::os::unix::fs::OpenOptionsExt;
1788 fopts.mode(0o600);
1790 fopts.custom_flags(libc::O_EXCL);
1791 }
1792 windows => {
1793 use std::os::windows::fs::OpenOptionsExt;
1794 fopts.share_mode(0);
1796 }
1797 _ => {
1798 throw_unsup_format!("`mkstemp` is not supported on this host OS");
1799 }
1800 }
1801
1802 for _ in 0..max_attempts {
1804 let rng = this.machine.rng.get_mut();
1805
1806 let unique_suffix =
1808 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1809
1810 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1812
1813 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1815
1816 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1818 match file {
1819 Ok(f) => {
1820 let fd = this.machine.fds.insert_new(FileHandle {
1821 file: f,
1822 writable: true,
1823 readable: true,
1824 });
1825 return interp_ok(Scalar::from_i32(fd));
1826 }
1827 Err(e) =>
1828 match e.kind() {
1829 ErrorKind::AlreadyExists => continue,
1831 _ => {
1833 return this.set_errno_and_return_neg1_i32(e);
1836 }
1837 },
1838 }
1839 }
1840
1841 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
1843 }
1844}
1845
1846fn extract_sec_and_nsec<'tcx>(
1850 time: std::io::Result<SystemTime>,
1851) -> InterpResult<'tcx, Option<(u64, u32)>> {
1852 match time.ok() {
1853 Some(time) => {
1854 let duration = system_time_to_duration(&time)?;
1855 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1856 }
1857 None => interp_ok(None),
1858 }
1859}
1860
1861fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1862 #[cfg(unix)]
1863 use std::os::unix::fs::FileTypeExt;
1864
1865 if file_type.is_file() {
1866 "S_IFREG"
1867 } else if file_type.is_dir() {
1868 "S_IFDIR"
1869 } else if file_type.is_symlink() {
1870 "S_IFLNK"
1871 } else {
1872 #[cfg(unix)]
1874 {
1875 if file_type.is_socket() {
1876 return "S_IFSOCK";
1877 } else if file_type.is_fifo() {
1878 return "S_IFIFO";
1879 } else if file_type.is_char_device() {
1880 return "S_IFCHR";
1881 } else if file_type.is_block_device() {
1882 return "S_IFBLK";
1883 }
1884 }
1885 "S_IFREG"
1886 }
1887}
1888
1889struct FileMetadata {
1897 mode: u32,
1899 size: u64,
1900 created: Option<(u64, u32)>,
1901 accessed: Option<(u64, u32)>,
1902 modified: Option<(u64, u32)>,
1903 dev: Option<u64>,
1904 ino: Option<u64>,
1905 nlink: Option<u64>,
1906 uid: Option<u32>,
1907 gid: Option<u32>,
1908 blksize: Option<u64>,
1909 blocks: Option<u64>,
1910}
1911
1912impl FileMetadata {
1913 fn from_fd_num<'tcx>(
1914 ecx: &mut MiriInterpCx<'tcx>,
1915 fd_num: i32,
1916 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1917 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1918 return interp_ok(Err(LibcError("EBADF")));
1919 };
1920 match fd.metadata()? {
1921 Either::Left(host) => Self::from_host(ecx, host),
1922 Either::Right(mode_name) => Self::synthetic(ecx, mode_name),
1923 }
1924 }
1925
1926 fn synthetic<'tcx>(
1927 ecx: &mut MiriInterpCx<'tcx>,
1928 mode_name: &str,
1929 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1930 let mode = ecx.eval_libc(mode_name);
1931 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1932 let mode = mode | 0o666;
1934 interp_ok(Ok(FileMetadata {
1935 mode,
1936 size: 0,
1937 created: None,
1938 accessed: None,
1939 modified: None,
1940 dev: None,
1941 uid: None,
1942 gid: None,
1943 blksize: None,
1944 blocks: None,
1945 ino: None,
1946 nlink: None,
1947 }))
1948 }
1949
1950 fn from_host<'tcx>(
1951 ecx: &mut MiriInterpCx<'tcx>,
1952 metadata: Result<std::fs::Metadata, std::io::Error>,
1953 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1954 let metadata = match metadata {
1955 Ok(metadata) => metadata,
1956 Err(e) => {
1957 return interp_ok(Err(e.into()));
1958 }
1959 };
1960
1961 let file_type = metadata.file_type();
1962 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1963 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1964
1965 let size = metadata.len();
1966
1967 let created = extract_sec_and_nsec(metadata.created())?;
1968 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1969 let modified = extract_sec_and_nsec(metadata.modified())?;
1970
1971 cfg_select! {
1974 unix => {
1975 use std::os::unix::fs::{MetadataExt, PermissionsExt};
1976
1977 let dev = metadata.dev();
1978 let ino = metadata.ino();
1979 let nlink = metadata.nlink();
1980 let uid = metadata.uid();
1981 let gid = metadata.gid();
1982 let blksize = metadata.blksize();
1983 let blocks = metadata.blocks();
1984
1985 mode |= metadata.permissions().mode();
1986
1987 interp_ok(Ok(FileMetadata {
1988 mode,
1989 size,
1990 created,
1991 accessed,
1992 modified,
1993 dev: Some(dev),
1994 ino: Some(ino),
1995 nlink: Some(nlink),
1996 uid: Some(uid),
1997 gid: Some(gid),
1998 blksize: Some(blksize),
1999 blocks: Some(blocks),
2000 }))
2001 }
2002 _ => {
2003 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
2005
2006 interp_ok(Ok(FileMetadata {
2007 mode,
2008 size,
2009 created,
2010 accessed,
2011 modified,
2012 dev: None,
2013 ino: None,
2014 nlink: None,
2015 uid: None,
2016 gid: None,
2017 blksize: None,
2018 blocks: None,
2019 }))
2020 }
2021 }
2022 }
2023}