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::FdId;
17use crate::shims::files::{DirHandle, FdNum, FileHandle, open_file_or_dir};
18use crate::shims::os_str::{PathConversion, 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, &'static str)>,
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 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
173 interp_ok(match (self.readable, self.writable) {
174 (true, true) => ecx.eval_libc("O_RDWR"),
175 (true, false) => ecx.eval_libc("O_RDONLY"),
176 (false, true) => ecx.eval_libc("O_WRONLY"),
177 _ => unreachable!(),
178 })
179 }
180}
181
182impl UnixFileDescription for DirHandle {
183 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
184 interp_ok(ecx.eval_libc("O_RDONLY"))
186 }
187}
188
189#[derive(Debug)]
193pub struct DirTable {
194 streams: FxHashMap<u64, DirStream>,
204 next_id: u64,
206}
207
208impl DirTable {
209 #[expect(clippy::arithmetic_side_effects)]
210 fn insert_new(&mut self, stream: DirStream) -> u64 {
211 let id = self.next_id;
212 self.next_id += 1;
213 self.streams.try_insert(id, stream).unwrap();
214 id
215 }
216}
217
218impl Default for DirTable {
219 fn default() -> DirTable {
220 DirTable {
221 streams: FxHashMap::default(),
222 next_id: 1,
224 }
225 }
226}
227
228impl VisitProvenance for DirTable {
229 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
230 let DirTable { streams, next_id: _ } = self;
231
232 for dir in streams.values() {
233 dir.entry.visit_provenance(visit);
234 }
235 }
236}
237
238fn maybe_sync_file(
239 file: &FileHandle,
240 operation: fn(&File) -> std::io::Result<()>,
241) -> std::io::Result<i32> {
242 if !file.writable && cfg!(windows) {
243 Ok(0i32)
247 } else {
248 let result = operation(&file.file);
249 result.map(|_| 0i32)
250 }
251}
252
253fn host_file_times_from_updates(access: TimeUpdate, modified: TimeUpdate) -> FileTimes {
255 let mut filetimes = FileTimes::new();
256 if let TimeUpdate::Set(access) = access {
257 filetimes = filetimes.set_accessed(access);
258 }
259 if let TimeUpdate::Set(modified) = modified {
260 filetimes = filetimes.set_modified(modified);
261 }
262 filetimes
263}
264
265impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
266trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
267 fn parse_utimens_timespec(
270 &self,
271 tp: &MPlaceTy<'tcx>,
272 ) -> InterpResult<'tcx, Option<TimeUpdate>> {
273 let this = self.eval_context_ref();
274 assert!(this.machine.communicate(), "isolation should have prevented reaching this");
276
277 let nsec_place = this.project_field(tp, FieldIdx::ONE)?;
279 let nsec = this.read_scalar(&nsec_place)?.to_target_isize(this)?;
280
281 if nsec == this.eval_libc("UTIME_OMIT").to_target_isize(this)? {
282 return interp_ok(Some(TimeUpdate::Omit));
283 }
284 if nsec == this.eval_libc("UTIME_NOW").to_target_isize(this)? {
285 return interp_ok(Some(TimeUpdate::Set(SystemTime::now())));
286 }
287
288 let Some(duration) = this.read_timespec(tp)? else {
289 return interp_ok(None);
290 };
291 interp_ok(SystemTime::UNIX_EPOCH.checked_add(duration).map(TimeUpdate::Set))
292 }
293
294 fn parse_utimens_times(
298 &self,
299 times_ptr: Pointer,
300 ) -> InterpResult<'tcx, Option<(TimeUpdate, TimeUpdate)>> {
301 let this = self.eval_context_ref();
302
303 if this.ptr_is_null(times_ptr)? {
304 let now = TimeUpdate::Set(SystemTime::now());
305 interp_ok(Some((now, now)))
306 } else {
307 let timespec = this.libc_ty_layout("timespec");
308 let access_place = this.ptr_to_mplace(times_ptr, timespec);
309 let modified_place = access_place.offset(timespec.size, timespec, this)?;
310 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
311 return interp_ok(None);
312 };
313 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
314 return interp_ok(None);
315 };
316 interp_ok(Some((access, modified)))
317 }
318 }
319
320 fn write_stat_buf(
321 &mut self,
322 metadata: FileMetadata,
323 buf_op: &OpTy<'tcx>,
324 buf_type: &str,
325 ) -> InterpResult<'tcx, i32> {
326 let this = self.eval_context_mut();
327
328 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
329 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
330 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
331
332 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout(buf_type))?;
333
334 this.write_int_fields_named(
335 &[
336 ("st_dev", metadata.dev.unwrap_or(0).into()),
337 ("st_mode", metadata.mode.into()),
338 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
339 ("st_ino", metadata.ino.unwrap_or(0).into()),
340 ("st_uid", metadata.uid.unwrap_or(0).into()),
341 ("st_gid", metadata.gid.unwrap_or(0).into()),
342 ("st_rdev", 0),
343 ("st_atime", access_sec.into()),
344 ("st_atime_nsec", access_nsec.into()),
345 ("st_mtime", modified_sec.into()),
346 ("st_mtime_nsec", modified_nsec.into()),
347 ("st_ctime", 0),
348 ("st_ctime_nsec", 0),
349 ("st_size", metadata.size.into()),
350 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
351 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
352 ],
353 &buf,
354 )?;
355
356 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
357 this.write_int_fields_named(
358 &[
359 ("st_birthtime", created_sec.into()),
360 ("st_birthtime_nsec", created_nsec.into()),
361 ("st_flags", 0),
362 ("st_gen", 0),
363 ],
364 &buf,
365 )?;
366 }
367
368 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
369 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
370 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
372 }
373
374 interp_ok(0)
375 }
376
377 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
378 #[cfg(unix)]
379 use std::os::unix::fs::FileTypeExt;
380
381 let this = self.eval_context_ref();
382 match file_type {
383 Ok(file_type) => {
384 match () {
385 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
386 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
387 _ if file_type.is_symlink() =>
388 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
389 #[cfg(unix)]
391 _ if file_type.is_block_device() =>
392 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
393 #[cfg(unix)]
394 _ if file_type.is_char_device() =>
395 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
396 #[cfg(unix)]
397 _ if file_type.is_fifo() =>
398 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
399 #[cfg(unix)]
400 _ if file_type.is_socket() =>
401 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
402 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
404 }
405 }
406 Err(_) => {
407 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
409 }
410 }
411 }
412
413 fn dir_entry_fields(
414 &self,
415 entry: Either<fs::DirEntry, &'static str>,
416 ) -> InterpResult<'tcx, DirEntry> {
417 let this = self.eval_context_ref();
418 interp_ok(match entry {
419 Either::Left(dir_entry) => {
420 DirEntry {
421 name: dir_entry.file_name(),
422 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
423 #[cfg(unix)]
426 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
427 #[cfg(not(unix))]
428 ino: 0u64,
429 }
430 }
431 Either::Right(special) =>
432 DirEntry {
433 name: special.into(),
434 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
435 ino: 0,
436 },
437 })
438 }
439
440 fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
441 cfg_select! {
442 unix => {
443 use std::os::unix::fs::PermissionsExt;
444 interp_ok(fs::Permissions::from_mode(mode))
445 }
446 _ => {
447 let _ = mode;
448 throw_unsup_format!("setting file permissions is only supported on Unix hosts")
449 }
450 }
451 }
452}
453
454impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
455pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
456 fn open(
457 &mut self,
458 dirfd: Option<&OpTy<'tcx>>,
459 path_raw: &OpTy<'tcx>,
460 flag: &OpTy<'tcx>,
461 varargs: Varargs<'tcx, '_>,
462 ) -> InterpResult<'tcx, Scalar> {
463 #[cfg(unix)]
464 use std::os::unix::fs::OpenOptionsExt;
465 #[cfg(windows)]
466 use std::os::windows::fs::OpenOptionsExt;
467
468 let this = self.eval_context_mut();
469 let open_name = if dirfd.is_some() { "openat" } else { "open" };
470
471 let dirfd = match dirfd {
472 Some(dirfd) => Some(this.read_scalar(dirfd)?.to_i32()?),
473 None => None,
474 };
475 let path_raw = this.read_pointer(path_raw)?;
476 let flag = this.read_scalar(flag)?.to_i32()?;
477
478 let path = this.read_path_from_c_str(path_raw)?;
479 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
481 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
482 {
483 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
484 }
485
486 let read_mode = |this: &MiriInterpCx<'tcx>,
488 options: &mut OpenOptions,
489 fn_name: &str|
490 -> InterpResult<'tcx> {
491 let ([mode], _) = this.check_varargs(
492 if this.libc_ty_layout("mode_t").size.bytes() >= 4 {
493 shim_varargs![libc::mode_t]
495 } else {
496 shim_varargs![i32]
499 },
500 varargs,
501 fn_name,
502 )?;
503 let mode = this.read_scalar(mode)?.to_u32()?;
504
505 cfg_select! {
506 unix => {
507 options.mode(mode);
509 }
510 _ => {
511 let _unused = options;
512
513 if mode != 0o666 {
515 throw_unsup_format!(
516 "{open_name}: non-default mode 0o{:o} is not supported on non-Unix hosts",
517 mode
518 );
519 }
520 }
521 }
522
523 interp_ok(())
524 };
525
526 let mut flag = flag;
528
529 let mut options = OpenOptions::new();
530 let mut custom_flags = 0;
531
532 let o_rdonly = this.eval_libc_i32("O_RDONLY");
533 let o_wronly = this.eval_libc_i32("O_WRONLY");
534 let o_rdwr = this.eval_libc_i32("O_RDWR");
535 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
539 throw_unsup_format!("{open_name}: access mode flags on this target are unsupported");
540 }
541 let mut writable = true;
542 let mut readable = true;
543
544 let access_mode = flag & this.eval_libc_i32("O_ACCMODE");
546 flag &= !access_mode;
547
548 if access_mode == o_rdonly {
549 writable = false;
550 options.read(true);
551 } else if access_mode == o_wronly {
552 readable = false;
553 options.write(true);
554 } else if access_mode == o_rdwr {
555 options.read(true).write(true);
556 } else {
557 throw_unsup_format!("{open_name}: unsupported access mode {:#x}", access_mode);
558 }
559
560 if this.tcx.sess.target.os == Os::Linux {
561 let o_largefile = this.eval_libc_i32("O_LARGEFILE");
562 if flag & o_largefile == o_largefile {
563 flag &= !o_largefile;
564 }
566 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
567 if flag & o_tmpfile == o_tmpfile {
569 read_mode(
571 this,
572 &mut options,
573 if dirfd.is_some() {
574 "openat(dirfd, pathname, O_TMPFILE, ...)"
575 } else {
576 "open(pathname, O_TMPFILE, ...)"
577 },
578 )?;
579
580 return this.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
582 }
583 }
584 let o_directory = this.eval_libc_i32("O_DIRECTORY");
585 let mut want_directory = false;
586 if flag & o_directory == o_directory {
587 flag &= !o_directory;
588 want_directory = true;
589 #[cfg(unix)]
594 {
595 custom_flags |= libc::O_DIRECTORY;
596 }
597 }
598 let o_append = this.eval_libc_i32("O_APPEND");
599 if flag & o_append == o_append {
600 flag &= !o_append;
601 options.append(true);
602 }
603 let o_trunc = this.eval_libc_i32("O_TRUNC");
604 if flag & o_trunc == o_trunc {
605 flag &= !o_trunc;
606 options.truncate(true);
607 }
608 let o_creat = this.eval_libc_i32("O_CREAT");
609 if flag & o_creat == o_creat {
610 flag &= !o_creat;
611 read_mode(
613 this,
614 &mut options,
615 if dirfd.is_some() {
616 "openat(dirfd, pathname, O_CREAT, ...)"
617 } else {
618 "open(pathname, O_CREAT, ...)"
619 },
620 )?;
621
622 if want_directory {
623 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
625 }
626
627 let o_excl = this.eval_libc_i32("O_EXCL");
629 if flag & o_excl == o_excl {
630 flag &= !o_excl;
631 options.create_new(true);
632 } else {
633 options.create(true);
634 }
635 }
636 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
637 if flag & o_cloexec == o_cloexec {
638 flag &= !o_cloexec;
639 }
642 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
643 let mut nofollow = false;
644 if flag & o_nofollow == o_nofollow {
645 flag &= !o_nofollow;
646 nofollow = true;
647 cfg_select! {
648 unix => {
649 custom_flags |= libc::O_NOFOLLOW;
650 }
651 windows => {
652 if dirfd.is_some() {
653 throw_unsup_format!(
654 "openat: `O_NOFOLLOW` is not supported on Windows hosts"
655 );
656 }
657 custom_flags |=
658 windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
659 }
660 }
661 }
662
663 if flag != 0 {
665 throw_unsup_format!("{open_name}: unsupported flags: {flag:#x}");
666 }
667
668 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
670 this.reject_in_isolation(&format!("`{open_name}`"), reject_with)?;
671 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
672 }
673
674 let dirfd = match dirfd {
675 Some(dirfd) => {
676 if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
677 None
678 } else {
679 let Some(fd) = this.machine.fds.get(dirfd) else {
681 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
682 };
683 let Some(dir) = fd.downcast::<DirHandle>() else {
684 return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
685 };
686 Some(dir)
687 }
688 }
689 None => None,
690 };
691
692 options.custom_flags(custom_flags);
694 let f_or_d = match &dirfd {
697 Some(dirfd) if path.to_str() == Some(".") => dirfd.dir.try_clone().map(Either::Right),
701 _ => open_file_or_dir(dirfd.as_ref().map(|d| &d.dir), &path, &options),
702 };
703 match f_or_d {
704 Err(err) => this.set_errno_and_return_neg1_i32(err),
705 Ok(Either::Right(dir)) => {
706 if writable {
708 return this.set_errno_and_return_neg1_i32(LibcError("EISDIR"));
711 }
712
713 let fd = this.machine.fds.insert_new(DirHandle {
714 dir,
715 #[cfg(bootstrap)]
716 fallback: match &dirfd {
717 Some(dirfd) => dirfd.fallback.join(&path).canonicalize().unwrap(),
718 None => path.canonicalize().unwrap(),
719 },
720 });
721 interp_ok(Scalar::from_i32(fd))
722 }
723 Ok(Either::Left(file)) => {
724 if want_directory {
725 return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
726 }
727 if file.metadata().unwrap().is_symlink() {
728 if nofollow {
729 return this.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
732 }
733 panic!("we should not get a symlink here");
734 }
735
736 let fd = this.machine.fds.insert_new(FileHandle { file, writable, readable });
737 interp_ok(Scalar::from_i32(fd))
738 }
739 }
740 }
741
742 fn lseek(
743 &mut self,
744 fd_num: i32,
745 offset: i128,
746 whence: i32,
747 dest: &MPlaceTy<'tcx>,
748 ) -> InterpResult<'tcx> {
749 let this = self.eval_context_mut();
750
751 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
754 if offset < 0 {
755 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
757 } else {
758 SeekFrom::Start(u64::try_from(offset).unwrap())
759 }
760 } else if whence == this.eval_libc_i32("SEEK_CUR") {
761 SeekFrom::Current(i64::try_from(offset).unwrap())
762 } else if whence == this.eval_libc_i32("SEEK_END") {
763 SeekFrom::End(i64::try_from(offset).unwrap())
764 } else {
765 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
766 };
767
768 let communicate = this.machine.communicate();
769
770 let Some(fd) = this.machine.fds.get(fd_num) else {
771 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
772 };
773 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
774 drop(fd);
775
776 let result = this.try_unwrap_io_result(result)?;
777 this.write_int(result, dest)?;
778 interp_ok(())
779 }
780
781 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
782 let this = self.eval_context_mut();
783
784 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
785
786 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
788 this.reject_in_isolation("`unlink`", reject_with)?;
789 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
790 }
791
792 #[rustfmt::skip] let result = cfg_select! {
794 unix => fs::remove_file(path),
795 windows => {{
796 use std::os::windows::fs::FileTypeExt;
800 let metadata = path.symlink_metadata();
801 if metadata.is_ok_and(|m| m.file_type().is_symlink_dir()) {
802 fs::remove_dir(path)
803 } else {
804 fs::remove_file(path)
805 }
806 }}
807 };
808 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0))?))
809 }
810
811 fn symlink(
812 &mut self,
813 target_op: &OpTy<'tcx>,
814 linkpath_op: &OpTy<'tcx>,
815 ) -> InterpResult<'tcx, Scalar> {
816 fn create_link(target: &Path, link: &Path) -> std::io::Result<()> {
817 cfg_select! {
818 unix => std::os::unix::fs::symlink(target, link),
819 windows => {
820 use std::os::windows::fs;
821
822 let target_is_dir = link.parent().is_some_and(|dir| dir.join(target).is_dir());
825 if target_is_dir {
828 fs::symlink_dir(target, link)
829 } else {
830 fs::symlink_file(target, link)
831 }
832 }
833 }
834 }
835
836 let this = self.eval_context_mut();
837 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
838 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
839
840 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
842 this.reject_in_isolation("`symlink`", reject_with)?;
843 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
844 }
845
846 let result = create_link(&target, &linkpath).map(|_| 0);
847 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
848 }
849
850 fn linkat(
851 &mut self,
852 oldfd_op: &OpTy<'tcx>,
853 oldpath_op: &OpTy<'tcx>,
854 newfd_op: &OpTy<'tcx>,
855 newpath_op: &OpTy<'tcx>,
856 flags_op: &OpTy<'tcx>,
857 ) -> InterpResult<'tcx, Scalar> {
858 let this = self.eval_context_mut();
859
860 let flags = this.read_scalar(flags_op)?.to_i32()?;
862 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
863 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
864 let oldpath_ptr = this.read_pointer(oldpath_op)?;
865 let newpath_ptr = this.read_pointer(newpath_op)?;
866
867 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
869
870 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
872 this.reject_in_isolation("`linkat`", reject_with)?;
873 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
874 }
875
876 if flags != 0 {
878 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
879 }
880
881 if oldfd != at_fdcwd {
883 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
884 }
885 if this.ptr_is_null(oldpath_ptr)? {
886 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
887 }
888 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
889
890 if newfd != at_fdcwd {
892 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
893 }
894 if this.ptr_is_null(newpath_ptr)? {
895 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
896 }
897 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
898
899 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
900 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
901 }
902
903 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
904 let this = self.eval_context_mut();
905
906 if !matches!(
907 &this.tcx.sess.target.os,
908 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
909 ) {
910 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
911 }
912
913 let path_scalar = this.read_pointer(path_op)?;
914 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
915
916 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
918 this.reject_in_isolation("`stat`", reject_with)?;
919 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
920 }
921
922 let metadata = match FileMetadata::from_host(this, fs::metadata(path))? {
923 Ok(metadata) => metadata,
924 Err(err) => return this.set_errno_and_return_neg1_i32(err),
925 };
926
927 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op, "stat")?))
928 }
929
930 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
932 let this = self.eval_context_mut();
933
934 if !matches!(
935 &this.tcx.sess.target.os,
936 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
937 ) {
938 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
939 }
940
941 let path_scalar = this.read_pointer(path_op)?;
942 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
943
944 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
946 this.reject_in_isolation("`lstat`", reject_with)?;
947 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
948 }
949
950 let metadata = match FileMetadata::from_host(this, fs::symlink_metadata(path))? {
951 Ok(metadata) => metadata,
952 Err(err) => return this.set_errno_and_return_neg1_i32(err),
953 };
954
955 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op, "stat")?))
956 }
957
958 fn fstat(
959 &mut self,
960 fd_op: &OpTy<'tcx>,
961 buf_op: &OpTy<'tcx>,
962 buf_type: &str,
963 ) -> InterpResult<'tcx, Scalar> {
964 let this = self.eval_context_mut();
965
966 if !matches!(
967 &this.tcx.sess.target.os,
968 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
969 ) {
970 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
971 }
972
973 let fd = this.read_scalar(fd_op)?.to_i32()?;
974
975 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
977 this.reject_in_isolation("`fstat`", reject_with)?;
978 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
980 }
981
982 let metadata = match FileMetadata::from_fd_num(this, fd)? {
983 Ok(metadata) => metadata,
984 Err(err) => return this.set_errno_and_return_neg1_i32(err),
985 };
986 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op, buf_type)?))
987 }
988
989 fn fstatat(
990 &mut self,
991 dirfd_op: &OpTy<'tcx>,
992 path_op: &OpTy<'tcx>,
993 buf_op: &OpTy<'tcx>,
994 flags_op: &OpTy<'tcx>,
995 ) -> InterpResult<'tcx, Scalar> {
996 let this = self.eval_context_mut();
997
998 if !matches!(
999 &this.tcx.sess.target.os,
1000 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
1001 ) {
1002 panic!("`fstatat` should not be called on {}", this.tcx.sess.target.os);
1003 }
1004
1005 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1006 let path = this.read_pointer(path_op)?;
1007 let flags = this.read_scalar(flags_op)?.to_i32()?;
1008
1009 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1011 this.reject_in_isolation("`fstatat`", reject_with)?;
1012 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1013 }
1014
1015 let mut flags = flags;
1017 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1019 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1020 flags &= !at_symlink_nofollow;
1021 if flags != 0 {
1023 throw_unsup_format!("unsupported flags for `fstatat`: {flags:#x}")
1024 }
1025
1026 let path = this.read_path_from_c_str(path)?.into_owned();
1027 let metadata = FileMetadata::at(
1029 this,
1030 dirfd,
1031 &path,
1032 symlink_nofollow_flag,
1033 false,
1034 )?;
1035
1036 let metadata = match metadata {
1037 Ok(metadata) => metadata,
1038 Err(err) => return this.set_errno_and_return_neg1_i32(err),
1039 };
1040 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op, "stat")?))
1041 }
1042
1043 fn linux_statx(
1044 &mut self,
1045 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
1051 let this = self.eval_context_mut();
1052
1053 this.assert_target_os(Os::Linux, "statx");
1054
1055 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1056 let pathname_ptr = this.read_pointer(pathname_op)?;
1057 let flags = this.read_scalar(flags_op)?.to_i32()?;
1058 let _mask = this.read_scalar(mask_op)?.to_u32()?;
1059 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
1060
1061 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
1063 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1064 }
1065
1066 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
1067 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1068
1069 let mut flags = flags;
1071 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
1073 let empty_path_flag = flags & at_empty_path == at_empty_path;
1074 flags &= !at_empty_path;
1075 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1077 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1078 flags &= !at_symlink_nofollow;
1079 if flags != 0 {
1081 throw_unsup_format!("unsupported flags for `statx`: {flags:#x}")
1082 }
1083
1084 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1086 this.reject_in_isolation("`statx`", reject_with)?;
1087 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1088 }
1089
1090 let metadata =
1092 FileMetadata::at(this, dirfd, &path, symlink_nofollow_flag, empty_path_flag)?;
1093
1094 let metadata = match metadata {
1095 Ok(metadata) => metadata,
1096 Err(err) => return this.set_errno_and_return_neg1_i32(err),
1097 };
1098
1099 let mut mask = this.eval_libc_u32("STATX_TYPE")
1104 | this.eval_libc_u32("STATX_MODE")
1105 | this.eval_libc_u32("STATX_SIZE");
1106
1107 if metadata.ino.is_some() {
1109 mask |= this.eval_libc_u32("STATX_INO");
1110 }
1111 if metadata.nlink.is_some() {
1112 mask |= this.eval_libc_u32("STATX_NLINK");
1113 }
1114 if metadata.uid.is_some() {
1115 mask |= this.eval_libc_u32("STATX_UID");
1116 }
1117 if metadata.gid.is_some() {
1118 mask |= this.eval_libc_u32("STATX_GID");
1119 }
1120 if metadata.blocks.is_some() {
1121 mask |= this.eval_libc_u32("STATX_BLOCKS");
1122 }
1123
1124 let (access_sec, access_nsec) = metadata
1127 .accessed
1128 .map(|tup| {
1129 mask |= this.eval_libc_u32("STATX_ATIME");
1130 interp_ok(tup)
1131 })
1132 .unwrap_or_else(|| interp_ok((0, 0)))?;
1133
1134 let (created_sec, created_nsec) = metadata
1135 .created
1136 .map(|tup| {
1137 mask |= this.eval_libc_u32("STATX_BTIME");
1138 interp_ok(tup)
1139 })
1140 .unwrap_or_else(|| interp_ok((0, 0)))?;
1141
1142 let (modified_sec, modified_nsec) = metadata
1143 .modified
1144 .map(|tup| {
1145 mask |= this.eval_libc_u32("STATX_MTIME");
1146 interp_ok(tup)
1147 })
1148 .unwrap_or_else(|| interp_ok((0, 0)))?;
1149
1150 this.write_int_fields_named(
1152 &[
1153 ("stx_mask", mask.into()),
1154 ("stx_mode", metadata.mode.into()),
1155 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
1156 ("stx_attributes", 0),
1157 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
1158 ("stx_uid", metadata.uid.unwrap_or(0).into()),
1159 ("stx_gid", metadata.gid.unwrap_or(0).into()),
1160 ("stx_ino", metadata.ino.unwrap_or(0).into()),
1161 ("stx_size", metadata.size.into()),
1162 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
1163 ("stx_attributes_mask", 0),
1164 ("stx_rdev_major", 0),
1165 ("stx_rdev_minor", 0),
1166 ("stx_dev_major", 0),
1167 ("stx_dev_minor", 0),
1168 ],
1169 &statxbuf,
1170 )?;
1171 #[rustfmt::skip]
1172 this.write_int_fields_named(
1173 &[
1174 ("tv_sec", access_sec.into()),
1175 ("tv_nsec", access_nsec.into()),
1176 ],
1177 &this.project_field_named(&statxbuf, "stx_atime")?,
1178 )?;
1179 #[rustfmt::skip]
1180 this.write_int_fields_named(
1181 &[
1182 ("tv_sec", created_sec.into()),
1183 ("tv_nsec", created_nsec.into()),
1184 ],
1185 &this.project_field_named(&statxbuf, "stx_btime")?,
1186 )?;
1187 #[rustfmt::skip]
1188 this.write_int_fields_named(
1189 &[
1190 ("tv_sec", 0.into()),
1191 ("tv_nsec", 0.into()),
1192 ],
1193 &this.project_field_named(&statxbuf, "stx_ctime")?,
1194 )?;
1195 #[rustfmt::skip]
1196 this.write_int_fields_named(
1197 &[
1198 ("tv_sec", modified_sec.into()),
1199 ("tv_nsec", modified_nsec.into()),
1200 ],
1201 &this.project_field_named(&statxbuf, "stx_mtime")?,
1202 )?;
1203
1204 interp_ok(Scalar::from_i32(0))
1205 }
1206
1207 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1208 let this = self.eval_context_mut();
1209
1210 let path_ptr = this.read_pointer(path_op)?;
1211 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
1212
1213 if this.ptr_is_null(path_ptr)? {
1214 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1215 }
1216 let path = this.read_path_from_c_str(path_ptr)?;
1217
1218 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1220 this.reject_in_isolation("`chmod`", reject_with)?;
1221 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1222 }
1223
1224 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1225 if let Err(err) = fs::set_permissions(path, permissions) {
1226 return this.set_errno_and_return_neg1_i32(err);
1227 }
1228
1229 interp_ok(Scalar::from_i32(0))
1230 }
1231
1232 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1233 let this = self.eval_context_mut();
1234
1235 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1236 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
1237
1238 let Some(fd) = this.machine.fds.get(fd_num) else {
1239 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1240 };
1241 let Some(file) = fd.downcast::<FileHandle>() else {
1242 throw_unsup_format!("`fchmod` is only supported on regular files")
1244 };
1245 if !file.writable && !file.readable {
1246 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1249 }
1250 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1251
1252 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
1253 if let Err(err) = file.file.set_permissions(permissions) {
1254 return this.set_errno_and_return_neg1_i32(err);
1255 }
1256
1257 interp_ok(Scalar::from_i32(0))
1258 }
1259
1260 fn rename(
1261 &mut self,
1262 oldpath_op: &OpTy<'tcx>,
1263 newpath_op: &OpTy<'tcx>,
1264 ) -> InterpResult<'tcx, Scalar> {
1265 let this = self.eval_context_mut();
1266
1267 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1268 let newpath_ptr = this.read_pointer(newpath_op)?;
1269
1270 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1271 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1272 }
1273
1274 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1275 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1276
1277 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1279 this.reject_in_isolation("`rename`", reject_with)?;
1280 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1281 }
1282
1283 let result = fs::rename(oldpath, newpath).map(|_| 0);
1284
1285 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1286 }
1287
1288 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1289 let this = self.eval_context_mut();
1290
1291 #[cfg_attr(not(unix), allow(unused_variables))]
1292 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1293 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1294 } else {
1295 this.read_scalar(mode_op)?.to_u32()?
1296 };
1297
1298 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1299
1300 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1302 this.reject_in_isolation("`mkdir`", reject_with)?;
1303 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1304 }
1305
1306 #[cfg_attr(not(unix), allow(unused_mut))]
1307 let mut builder = DirBuilder::new();
1308
1309 #[cfg(unix)]
1312 {
1313 use std::os::unix::fs::DirBuilderExt;
1314 builder.mode(mode);
1315 }
1316
1317 let result = builder.create(path).map(|_| 0i32);
1318
1319 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1320 }
1321
1322 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1323 let this = self.eval_context_mut();
1324
1325 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1326
1327 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1329 this.reject_in_isolation("`rmdir`", reject_with)?;
1330 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1331 }
1332
1333 #[rustfmt::skip] let result = cfg_select! {
1335 unix => fs::remove_dir(path),
1336 windows => {{
1337 use std::os::windows::fs::FileTypeExt;
1341 let metadata = path.symlink_metadata();
1342 if metadata.is_ok_and(|m| m.file_type().is_symlink_dir()) {
1343 return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
1344 } else {
1345 fs::remove_dir(path)
1346 }
1347 }}
1348 };
1349
1350 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|_| 0i32))?))
1351 }
1352
1353 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1354 let this = self.eval_context_mut();
1355
1356 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1357
1358 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1360 this.reject_in_isolation("`opendir`", reject_with)?;
1361 this.set_last_error(LibcError("EACCES"))?;
1362 return interp_ok(Scalar::null_ptr(this));
1363 }
1364
1365 let result = fs::read_dir(&name);
1366
1367 match result {
1368 Ok(read_dir) => {
1369 let Ok(dir) = fs::Dir::open(&name) else {
1377 throw_unsup_format!(
1378 "cannot `opendir` this directory: failed to create directory handle"
1379 );
1380 };
1381 let dir = this.machine.fds.new_ref(DirHandle {
1382 dir,
1383 #[cfg(bootstrap)]
1384 fallback: name.canonicalize().unwrap(),
1385 });
1386 let dir_fd_id = dir.id();
1387 let dir_fd_num = this.machine.fds.insert(dir);
1388
1389 let stream = DirStream::new(read_dir, dir_fd_num, dir_fd_id);
1390 let id = this.machine.dirs.insert_new(stream);
1391
1392 interp_ok(Scalar::from_target_usize(id, this))
1397 }
1398 Err(e) => {
1399 this.set_last_error(e)?;
1400 interp_ok(Scalar::null_ptr(this))
1401 }
1402 }
1403 }
1404
1405 fn readdir(
1406 &mut self,
1407 dirp_op: &OpTy<'tcx>,
1408 dest: &MPlaceTy<'tcx>,
1409 buf_type: &'static str,
1410 ) -> InterpResult<'tcx> {
1411 let this = self.eval_context_mut();
1412
1413 if !matches!(
1414 &this.tcx.sess.target.os,
1415 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1416 ) {
1417 throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1418 }
1419
1420 let dirp = this.read_target_usize(dirp_op)?;
1421
1422 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1424 this.reject_in_isolation("`readdir`", reject_with)?;
1425 this.set_last_error(LibcError("EBADF"))?;
1426 this.write_null(dest)?;
1427 return interp_ok(());
1428 }
1429
1430 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1431 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1432 })?;
1433
1434 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1436 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1437 }
1438
1439 let entry = match open_dir.next_host_entry() {
1440 Some(Ok(dir_entry)) => {
1441 let dir_entry = this.dir_entry_fields(dir_entry)?;
1442
1443 let dirent_layout = this.libc_ty_layout(buf_type);
1485 let fields = &dirent_layout.fields;
1486 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1487
1488 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1492 let name_len = u64::try_from(name_bytes.len()).unwrap();
1493 let size = d_name_offset.strict_add(name_len);
1494
1495 let entry = this.allocate_ptr(
1496 Size::from_bytes(size),
1497 dirent_layout.align.abi,
1498 MiriMemoryKind::Runtime.into(),
1499 AllocInit::Uninit,
1500 )?;
1501 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1502
1503 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1506 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1507
1508 let ino_name =
1510 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1511 this.write_int_fields_named(
1512 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1513 &entry,
1514 )?;
1515
1516 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1518 this.write_null(&d_off)?;
1519 }
1520 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1521 this.write_null(&d_seekoff)?;
1522 }
1523 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1524 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1525 }
1526 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1527 this.write_int(dir_entry.d_type, &d_type)?;
1528 }
1529
1530 Some(entry.ptr())
1531 }
1532 None => {
1533 None
1535 }
1536 Some(Err(e)) => {
1537 this.set_last_error(e)?;
1538 None
1539 }
1540 };
1541
1542 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1543 if let Some((old_entry, old_type)) =
1544 std::mem::replace(&mut open_dir.entry, entry.map(|e| (e, buf_type)))
1545 {
1546 if old_type != buf_type {
1547 throw_ub_format!("mixing different `readdir` variants on the same `DIR` instance");
1548 }
1549 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1550 }
1551
1552 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1553 interp_ok(())
1554 }
1555
1556 fn dirfd(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1557 let this = self.eval_context_mut();
1558
1559 let dirp = this.read_target_usize(dirp_op)?;
1560
1561 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1562 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1563 })?;
1564
1565 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1567 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1568 }
1569
1570 let fd_num = open_dir.fd_num;
1571 this.write_int(fd_num, dest)?;
1572 interp_ok(())
1573 }
1574
1575 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1576 let this = self.eval_context_mut();
1577
1578 let dirp = this.read_target_usize(dirp_op)?;
1579
1580 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1582 this.reject_in_isolation("`closedir`", reject_with)?;
1583 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1584 }
1585
1586 let mut open_dir = this.machine.dirs.streams.remove(&dirp).ok_or_else(|| {
1587 err_ub_format!("the DIR pointer passed to `closedir` did not come from opendir")
1588 })?;
1589
1590 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1592 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1593 }
1594 this.close(open_dir.fd_num)?;
1596
1597 if let Some((entry, _type)) = open_dir.entry.take() {
1598 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1599 }
1600 drop(open_dir);
1602
1603 interp_ok(Scalar::from_i32(0))
1604 }
1605
1606 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1607 let this = self.eval_context_mut();
1608
1609 let Some(fd) = this.machine.fds.get(fd_num) else {
1610 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1611 };
1612 let Some(file) = fd.downcast::<FileHandle>() else {
1613 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1616 };
1617 if !file.writable {
1618 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1620 }
1621 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1622
1623 if let Ok(length) = length.try_into() {
1624 let result = file.file.set_len(length);
1625 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1626 interp_ok(Scalar::from_i32(result))
1627 } else {
1628 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1629 }
1630 }
1631
1632 fn posix_fallocate(
1635 &mut self,
1636 fd_num: i32,
1637 offset: i64,
1638 len: i64,
1639 ) -> InterpResult<'tcx, Scalar> {
1640 let this = self.eval_context_mut();
1641
1642 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1644 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1645 return interp_ok(this.eval_libc("EBADF"));
1647 }
1648
1649 match this.fallocate_impl(fd_num, offset, len)? {
1650 Ok(()) => interp_ok(Scalar::from_i32(0)),
1651 Err(e) => this.io_error_to_errnum(e),
1652 }
1653 }
1654
1655 fn linux_fallocate(
1656 &mut self,
1657 fd: i32,
1658 mode: i32,
1659 offset: i64,
1660 size: i64,
1661 ) -> InterpResult<'tcx, Scalar> {
1662 let this = self.eval_context_mut();
1664
1665 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1667 this.reject_in_isolation("`fallocate`", reject_with)?;
1668 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1670 }
1671
1672 if mode != 0 {
1675 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode:#x}")
1676 }
1677
1678 match this.fallocate_impl(fd, offset, size)? {
1679 Ok(()) => interp_ok(Scalar::from_i32(0)),
1680 Err(e) => this.set_errno_and_return_neg1_i32(e),
1681 }
1682 }
1683
1684 fn fallocate_impl(
1686 &mut self,
1687 fd_num: i32,
1688 offset: i64,
1689 len: i64,
1690 ) -> InterpResult<'tcx, Result<(), IoError>> {
1691 let this = self.eval_context_mut();
1692
1693 if offset < 0 || len <= 0 {
1695 return interp_ok(Err(LibcError("EINVAL")));
1696 }
1697
1698 let Some(fd) = this.machine.fds.get(fd_num) else {
1699 return interp_ok(Err(LibcError("EBADF")));
1700 };
1701 let Some(file) = fd.downcast::<FileHandle>() else {
1702 return interp_ok(Err(LibcError("ENODEV")));
1704 };
1705
1706 if !file.writable {
1707 return interp_ok(Err(LibcError("EBADF")));
1708 }
1709
1710 let current_size = match file.file.metadata() {
1711 Ok(metadata) => metadata.len(),
1712 Err(err) => return interp_ok(Err(err.into())),
1713 };
1714
1715 let new_size = match offset.checked_add(len) {
1717 Some(new_size) => u64::try_from(new_size).unwrap(),
1719 None => return interp_ok(Err(LibcError("EFBIG"))), };
1721
1722 if current_size < new_size {
1725 match file.file.set_len(new_size) {
1726 Ok(()) => interp_ok(Ok(())),
1727 Err(err) => interp_ok(Err(err.into())),
1728 }
1729 } else {
1730 interp_ok(Ok(()))
1731 }
1732 }
1733
1734 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1735 let this = self.eval_context_mut();
1741
1742 let fd = this.read_scalar(fd_op)?.to_i32()?;
1743
1744 self.ffullsync_fd(fd)
1745 }
1746
1747 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1748 let this = self.eval_context_mut();
1749 let Some(fd) = this.machine.fds.get(fd_num) else {
1750 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1751 };
1752 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1754 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1755 })?;
1756 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1757
1758 let io_result = maybe_sync_file(&file, File::sync_all);
1759 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1760 }
1761
1762 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1763 let this = self.eval_context_mut();
1764
1765 let fd = this.read_scalar(fd_op)?.to_i32()?;
1766
1767 let Some(fd) = this.machine.fds.get(fd) else {
1768 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1769 };
1770 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1772 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1773 })?;
1774 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1775
1776 let io_result = maybe_sync_file(&file, File::sync_data);
1777 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1778 }
1779
1780 fn futimens(
1783 &mut self,
1784 fd_op: &OpTy<'tcx>,
1785 times_op: &OpTy<'tcx>,
1786 ) -> InterpResult<'tcx, Scalar> {
1787 let this = self.eval_context_mut();
1788
1789 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1790 let times_ptr = this.read_pointer(times_op)?;
1791
1792 let Some(fd) = this.machine.fds.get(fd_num) else {
1793 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1794 };
1795 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1796 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1797 })?;
1798 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1799
1800 let Some((access, modified)) = this.parse_utimens_times(times_ptr)? else {
1801 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1802 };
1803 let result = file.file.set_times(host_file_times_from_updates(access, modified));
1804 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1805 }
1806
1807 fn utimensat(
1810 &mut self,
1811 dirfd_op: &OpTy<'tcx>,
1812 path_op: &OpTy<'tcx>,
1813 times_op: &OpTy<'tcx>,
1814 flags_op: &OpTy<'tcx>,
1815 ) -> InterpResult<'tcx, Scalar> {
1816 let this = self.eval_context_mut();
1817
1818 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1819 let path_ptr = this.read_pointer(path_op)?;
1820 let times_ptr = this.read_pointer(times_op)?;
1821 let flags = this.read_scalar(flags_op)?.to_i32()?;
1822
1823 if this.ptr_is_null(path_ptr)? {
1824 if dirfd != this.eval_libc_i32("AT_FDCWD") {
1827 throw_unsup_format!(
1828 "`utimensat` with a NULL path is only supported for `AT_FDCWD`"
1829 );
1830 }
1831 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1832 }
1833 let path = this.read_path_from_c_str(path_ptr)?.into_owned();
1834
1835 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1836 this.reject_in_isolation("`utimensat`", reject_with)?;
1837 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1838 }
1839
1840 let mut flags = flags;
1841 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1843 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1844 flags &= !at_symlink_nofollow;
1845
1846 if flags != 0 {
1847 throw_unsup_format!("unsupported flags for `utimensat`: {flags:#x}")
1848 }
1849
1850 if !(path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")) {
1851 throw_unsup_format!("`utimensat` with a path relative to a `dirfd` is not supported");
1852 }
1853
1854 let Some((access, modified)) = this.parse_utimens_times(times_ptr)? else {
1855 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1856 };
1857 if matches!((access, modified), (TimeUpdate::Omit, TimeUpdate::Omit)) {
1858 return interp_ok(Scalar::from_i32(0));
1862 }
1863 let filetimes = host_file_times_from_updates(access, modified);
1864 let result = if symlink_nofollow_flag {
1865 fs::set_times_nofollow(path, filetimes)
1866 } else {
1867 fs::set_times(path, filetimes)
1868 };
1869 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1870 }
1871
1872 fn sync_file_range(
1873 &mut self,
1874 fd_op: &OpTy<'tcx>,
1875 offset_op: &OpTy<'tcx>,
1876 nbytes_op: &OpTy<'tcx>,
1877 flags_op: &OpTy<'tcx>,
1878 ) -> InterpResult<'tcx, Scalar> {
1879 let this = self.eval_context_mut();
1880
1881 let fd = this.read_scalar(fd_op)?.to_i32()?;
1882 let offset = this.read_scalar(offset_op)?.to_i64()?;
1883 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1884 let flags = this.read_scalar(flags_op)?.to_i32()?;
1885
1886 if offset < 0 || nbytes < 0 {
1887 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1888 }
1889 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1890 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1891 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1892 if flags & allowed_flags != flags {
1893 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1894 }
1895
1896 let Some(fd) = this.machine.fds.get(fd) else {
1897 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1898 };
1899 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1901 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1902 })?;
1903 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1904
1905 let io_result = maybe_sync_file(&file, File::sync_data);
1906 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1907 }
1908
1909 fn readlink(
1910 &mut self,
1911 pathname_op: &OpTy<'tcx>,
1912 buf_op: &OpTy<'tcx>,
1913 bufsize_op: &OpTy<'tcx>,
1914 ) -> InterpResult<'tcx, i64> {
1915 let this = self.eval_context_mut();
1916
1917 let pathname = this.read_os_str_from_c_str(this.read_pointer(pathname_op)?)?;
1918 let buf = this.read_pointer(buf_op)?;
1919 let bufsize = this.read_target_usize(bufsize_op)?;
1920
1921 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1923 this.reject_in_isolation("`readlink`", reject_with)?;
1924 this.set_last_error(LibcError("EACCES"))?;
1925 return interp_ok(-1);
1926 }
1927
1928 let self_exe = match this.tcx.sess.target.os {
1931 Os::Linux | Os::Android => Some("/proc/self/exe"),
1932 Os::Solaris | Os::Illumos => Some("/proc/self/path/a.out"),
1933 _ => None,
1934 };
1935 let result = if self_exe.is_some() && pathname.to_str() == self_exe {
1936 this.machine.current_exe.clone().ok_or(ErrorKind::NotFound.into())
1937 } else if matches!(
1938 this.tcx.sess.target.os,
1939 Os::Linux | Os::Android | Os::Illumos | Os::Solaris
1940 ) && path::absolute(pathname).is_ok_and(|path| path.starts_with("/proc"))
1941 {
1942 this.set_last_error(ErrorKind::NotFound)?;
1945 return interp_ok(-1);
1946 } else {
1947 let pathname = this.convert_path(Cow::Borrowed(pathname), PathConversion::TargetToHost);
1950 std::fs::read_link(pathname)
1951 };
1952
1953 match result {
1954 Ok(resolved) => {
1955 let resolved = this
1959 .convert_path(Cow::Borrowed(resolved.as_ref()), PathConversion::HostToTarget);
1960 let mut path_bytes = resolved.as_encoded_bytes();
1961 let bufsize: usize = bufsize.try_into().unwrap();
1962 if path_bytes.len() > bufsize {
1963 path_bytes = &path_bytes[..bufsize]
1964 }
1965 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1966 interp_ok(path_bytes.len().try_into().unwrap())
1967 }
1968 Err(e) => {
1969 this.set_last_error(e)?;
1970 interp_ok(-1)
1971 }
1972 }
1973 }
1974
1975 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1976 let this = self.eval_context_mut();
1977 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1980 let error = if let Some(fd) = this.machine.fds.get(fd) {
1981 if fd.is_tty(this.machine.communicate()) {
1982 return interp_ok(Scalar::from_i32(1));
1983 } else {
1984 LibcError("ENOTTY")
1985 }
1986 } else {
1987 LibcError("EBADF")
1989 };
1990 this.set_last_error(error)?;
1991 interp_ok(Scalar::from_i32(0))
1992 }
1993
1994 fn realpath(
1995 &mut self,
1996 path_op: &OpTy<'tcx>,
1997 processed_path_op: &OpTy<'tcx>,
1998 ) -> InterpResult<'tcx, Scalar> {
1999 let this = self.eval_context_mut();
2000 this.assert_target_os_is_unix("realpath");
2001
2002 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
2003 let processed_ptr = this.read_pointer(processed_path_op)?;
2004
2005 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
2007 this.reject_in_isolation("`realpath`", reject_with)?;
2008 this.set_last_error(LibcError("EACCES"))?;
2009 return interp_ok(Scalar::from_target_usize(0, this));
2010 }
2011
2012 let result = std::fs::canonicalize(pathname);
2013 match result {
2014 Ok(resolved) => {
2015 let path_max = this
2016 .eval_libc_i32("PATH_MAX")
2017 .try_into()
2018 .expect("PATH_MAX does not fit in u64");
2019 let dest = if this.ptr_is_null(processed_ptr)? {
2020 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
2030 } else {
2031 let (wrote_path, _) =
2032 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
2033
2034 if !wrote_path {
2035 this.set_last_error(LibcError("ENAMETOOLONG"))?;
2039 return interp_ok(Scalar::from_target_usize(0, this));
2040 }
2041 processed_ptr
2042 };
2043
2044 interp_ok(Scalar::from_maybe_pointer(dest, this))
2045 }
2046 Err(e) => {
2047 this.set_last_error(e)?;
2048 interp_ok(Scalar::from_target_usize(0, this))
2049 }
2050 }
2051 }
2052 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
2053 use rand::seq::IndexedRandom;
2054
2055 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
2057
2058 let this = self.eval_context_mut();
2059 this.assert_target_os_is_unix("mkstemp");
2060
2061 let max_attempts = this.eval_libc_u32("TMP_MAX");
2071
2072 let template_ptr = this.read_pointer(template_op)?;
2075 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
2076 let template_bytes = template.as_mut_slice();
2077
2078 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
2080 this.reject_in_isolation("`mkstemp`", reject_with)?;
2081 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
2082 }
2083
2084 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
2086
2087 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
2092 let end_pos = template_bytes.len();
2093 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
2094
2095 if last_six_char_bytes != suffix_bytes {
2097 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
2098 }
2099
2100 const SUBSTITUTIONS: &[char; 62] = &[
2104 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
2105 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
2106 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
2107 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
2108 ];
2109
2110 let mut fopts = OpenOptions::new();
2113 fopts.read(true).write(true).create_new(true);
2114
2115 cfg_select! {
2116 unix => {
2117 use std::os::unix::fs::OpenOptionsExt;
2118 fopts.mode(0o600);
2120 fopts.custom_flags(libc::O_EXCL);
2121 }
2122 windows => {
2123 use std::os::windows::fs::OpenOptionsExt;
2124 fopts.share_mode(0);
2126 }
2127 _ => {
2128 throw_unsup_format!("`mkstemp` is not supported on this host OS");
2129 }
2130 }
2131
2132 for _ in 0..max_attempts {
2134 let rng = this.machine.rng.get_mut();
2135
2136 let unique_suffix =
2138 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
2139
2140 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
2142
2143 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
2145
2146 let file = fopts.open(bytes_to_os_str(template_bytes)?);
2148 match file {
2149 Ok(f) => {
2150 let fd = this.machine.fds.insert_new(FileHandle {
2151 file: f,
2152 writable: true,
2153 readable: true,
2154 });
2155 return interp_ok(Scalar::from_i32(fd));
2156 }
2157 Err(e) =>
2158 match e.kind() {
2159 ErrorKind::AlreadyExists => continue,
2161 _ => {
2163 return this.set_errno_and_return_neg1_i32(e);
2166 }
2167 },
2168 }
2169 }
2170
2171 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
2173 }
2174}
2175
2176fn extract_sec_and_nsec<'tcx>(
2180 time: std::io::Result<SystemTime>,
2181) -> InterpResult<'tcx, Option<(u64, u32)>> {
2182 match time.ok() {
2183 Some(time) => {
2184 let duration = system_time_to_duration(&time)?;
2185 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
2186 }
2187 None => interp_ok(None),
2188 }
2189}
2190
2191fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
2192 if file_type.is_file() {
2193 "S_IFREG"
2194 } else if file_type.is_dir() {
2195 "S_IFDIR"
2196 } else if file_type.is_symlink() {
2197 "S_IFLNK"
2198 } else {
2199 #[cfg(unix)]
2201 {
2202 use std::os::unix::fs::FileTypeExt;
2203 if file_type.is_socket() {
2204 return "S_IFSOCK";
2205 } else if file_type.is_fifo() {
2206 return "S_IFIFO";
2207 } else if file_type.is_char_device() {
2208 return "S_IFCHR";
2209 } else if file_type.is_block_device() {
2210 return "S_IFBLK";
2211 }
2212 }
2213 "S_IFREG"
2214 }
2215}
2216
2217struct FileMetadata {
2225 mode: u32,
2227 size: u64,
2228 created: Option<(u64, u32)>,
2229 accessed: Option<(u64, u32)>,
2230 modified: Option<(u64, u32)>,
2231 dev: Option<u64>,
2232 ino: Option<u64>,
2233 nlink: Option<u64>,
2234 uid: Option<u32>,
2235 gid: Option<u32>,
2236 blksize: Option<u64>,
2237 blocks: Option<u64>,
2238}
2239
2240impl FileMetadata {
2241 fn at<'tcx>(
2243 ecx: &mut MiriInterpCx<'tcx>,
2244 dirfd: FdNum,
2245 path: &Path,
2246 symlink_nofollow_flag: bool,
2247 empty_path_flag: bool,
2248 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2249 if path.is_empty() {
2252 if !empty_path_flag {
2254 return interp_ok(Err(LibcError("ENOENT")));
2255 }
2256 FileMetadata::from_fd_num(ecx, dirfd)
2257 } else if path.is_absolute() || dirfd == ecx.eval_libc_i32("AT_FDCWD") {
2258 FileMetadata::from_host(
2260 ecx,
2261 if symlink_nofollow_flag { path.symlink_metadata() } else { path.metadata() },
2262 )
2263 } else {
2264 let Some(fd) = ecx.machine.fds.get(dirfd) else {
2266 return interp_ok(Err(LibcError("EBADF")));
2267 };
2268 let Some(dir) = fd.downcast::<DirHandle>() else {
2269 return interp_ok(Err(LibcError("ENOTDIR")));
2270 };
2271
2272 #[cfg(not(bootstrap))]
2275 let metadata = if path.to_str() == Some(".") {
2276 dir.dir.self_metadata()
2277 } else if symlink_nofollow_flag {
2278 dir.dir.symlink_metadata(path)
2279 } else {
2280 dir.dir.metadata(path)
2281 };
2282 #[cfg(bootstrap)]
2283 let metadata = if path.to_str() == Some(".") {
2284 dir.dir.metadata()
2285 } else if symlink_nofollow_flag {
2286 dir.fallback.join(path).symlink_metadata()
2287 } else {
2288 dir.fallback.join(path).metadata()
2289 };
2290 FileMetadata::from_host(ecx, metadata)
2291 }
2292 }
2293
2294 fn from_fd_num<'tcx>(
2295 ecx: &mut MiriInterpCx<'tcx>,
2296 fd_num: FdNum,
2297 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2298 let Some(fd) = ecx.machine.fds.get(fd_num) else {
2299 return interp_ok(Err(LibcError("EBADF")));
2300 };
2301 match fd.metadata()? {
2302 Either::Left(host) => Self::from_host(ecx, host),
2303 Either::Right(mode_name) => Self::synthetic(ecx, mode_name),
2304 }
2305 }
2306
2307 fn synthetic<'tcx>(
2308 ecx: &mut MiriInterpCx<'tcx>,
2309 mode_name: &str,
2310 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2311 let mode = ecx.eval_libc(mode_name);
2312 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
2313 let mode = mode | 0o666;
2315 interp_ok(Ok(FileMetadata {
2316 mode,
2317 size: 0,
2318 created: None,
2319 accessed: None,
2320 modified: None,
2321 dev: None,
2322 uid: None,
2323 gid: None,
2324 blksize: None,
2325 blocks: None,
2326 ino: None,
2327 nlink: None,
2328 }))
2329 }
2330
2331 fn from_host<'tcx>(
2332 ecx: &mut MiriInterpCx<'tcx>,
2333 metadata: Result<std::fs::Metadata, std::io::Error>,
2334 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2335 let metadata = match metadata {
2336 Ok(metadata) => metadata,
2337 Err(e) => {
2338 return interp_ok(Err(e.into()));
2339 }
2340 };
2341
2342 let file_type = metadata.file_type();
2343 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
2344 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
2345
2346 let size = metadata.len();
2347
2348 let created = extract_sec_and_nsec(metadata.created())?;
2349 let accessed = extract_sec_and_nsec(metadata.accessed())?;
2350 let modified = extract_sec_and_nsec(metadata.modified())?;
2351
2352 cfg_select! {
2355 unix => {
2356 use std::os::unix::fs::{MetadataExt, PermissionsExt};
2357
2358 let dev = metadata.dev();
2359 let ino = metadata.ino();
2360 let nlink = metadata.nlink();
2361 let uid = metadata.uid();
2362 let gid = metadata.gid();
2363 let blksize = metadata.blksize();
2364 let blocks = metadata.blocks();
2365
2366 mode |= metadata.permissions().mode();
2367
2368 interp_ok(Ok(FileMetadata {
2369 mode,
2370 size,
2371 created,
2372 accessed,
2373 modified,
2374 dev: Some(dev),
2375 ino: Some(ino),
2376 nlink: Some(nlink),
2377 uid: Some(uid),
2378 gid: Some(gid),
2379 blksize: Some(blksize),
2380 blocks: Some(blocks),
2381 }))
2382 }
2383 _ => {
2384 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
2386
2387 interp_ok(Ok(FileMetadata {
2388 mode,
2389 size,
2390 created,
2391 accessed,
2392 modified,
2393 dev: None,
2394 ino: None,
2395 nlink: None,
2396 uid: None,
2397 gid: None,
2398 blksize: None,
2399 blocks: None,
2400 }))
2401 }
2402 }
2403 }
2404}