1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, DirBuilder, File, FileTimes, OpenOptions, TryLockError};
6use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7use std::path::{self, Path};
8use std::time::SystemTime;
9
10use rand::RngExt;
11use rustc_abi::Size;
12use rustc_data_structures::either::Either;
13use rustc_data_structures::fx::FxHashMap;
14use rustc_target::spec::Os;
15
16use self::shims::time::system_time_to_duration;
17use crate::shims::files::{
18 BIG_ACCESS, DirHandle, DynFileDescriptionRef, EvalContextExt as _, FdNum, FileHandle,
19 open_file_or_dir,
20};
21use crate::shims::os_str::{PathConversion, bytes_to_os_str};
22use crate::shims::sig::Varargs;
23use crate::shims::unix::fd::{EvalContextExt as _, FlockOp, UnixFileDescription};
24use crate::shims::{FdId, FileDescription};
25use crate::*;
26
27#[derive(Debug)]
29struct DirStream {
30 read_dir: fs::ReadDir,
32 fd_num: FdNum,
36 fd_id: FdId,
37 special_entries: Vec<&'static str>,
40 entry: Option<(Pointer, &'static str)>,
43}
44
45impl DirStream {
46 fn new(read_dir: fs::ReadDir, fd_num: FdNum, fd_id: FdId) -> Self {
47 Self { read_dir, fd_num, fd_id, special_entries: vec!["..", "."], entry: None }
48 }
49
50 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
51 if let Some(special) = self.special_entries.pop() {
52 return Some(Ok(Either::Right(special)));
53 }
54 let entry = self.read_dir.next()?;
55 Some(entry.map(Either::Left))
56 }
57}
58
59#[derive(Debug)]
60struct DirEntry {
61 name: OsString,
62 ino: u64,
63 d_type: i32,
64}
65
66#[derive(Copy, Clone)]
68enum TimeUpdate {
69 Omit,
70 Set(SystemTime),
71}
72
73impl UnixFileDescription for FileHandle {
74 fn pread<'tcx>(
75 &self,
76 communicate_allowed: bool,
77 ptr: Pointer,
78 len: u64,
79 offset: u64,
80 ecx: &mut MiriInterpCx<'tcx>,
81 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
82 ) -> InterpResult<'tcx> {
83 assert!(communicate_allowed, "isolation should have prevented even opening a file");
84 if !self.readable {
85 return finish.call(ecx, Err(LibcError("EBADF")));
86 }
87
88 let file = &mut &self.file;
89 let result = ecx.read_from_host(ptr, len, |buf| {
92 let cursor_pos = file.stream_position()?;
93 file.seek(SeekFrom::Start(offset))?;
94 let res = file.read(buf);
95 file.seek(SeekFrom::Start(cursor_pos))
97 .expect("failed to restore file position, this shouldn't be possible");
98 res
99 })?;
100 finish.call(ecx, result)
101 }
102
103 fn pwrite<'tcx>(
104 &self,
105 communicate_allowed: bool,
106 ptr: Pointer,
107 len: u64,
108 offset: u64,
109 ecx: &mut MiriInterpCx<'tcx>,
110 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
111 ) -> InterpResult<'tcx> {
112 assert!(communicate_allowed, "isolation should have prevented even opening a file");
113 if !self.writable {
114 return finish.call(ecx, Err(LibcError("EBADF")));
115 }
116
117 let file = &mut &self.file;
118 let result = ecx.write_to_host(ptr, len, |buf| {
121 let cursor_pos = file.stream_position()?;
122 file.seek(SeekFrom::Start(offset))?;
123 let res = file.write(buf);
124 file.seek(SeekFrom::Start(cursor_pos))
126 .expect("failed to restore file position, this shouldn't be possible");
127 res
128 })?;
129 finish.call(ecx, result)
130 }
131
132 fn flock<'tcx>(
133 &self,
134 communicate_allowed: bool,
135 op: FlockOp,
136 ) -> InterpResult<'tcx, io::Result<()>> {
137 assert!(communicate_allowed, "isolation should have prevented even opening a file");
138
139 use FlockOp::*;
140 let (res, nonblocking) = match op {
142 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
143 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
144 Unlock => {
145 return interp_ok(self.file.unlock());
146 }
147 };
148
149 match res {
150 Ok(()) => interp_ok(Ok(())),
151 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
152 Err(TryLockError::WouldBlock) =>
153 if nonblocking {
154 interp_ok(Err(ErrorKind::WouldBlock.into()))
155 } else {
156 throw_unsup_format!("blocking `flock` is not currently supported");
157 },
158 }
159 }
160
161 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
162 interp_ok(match (self.readable, self.writable) {
163 (true, true) => ecx.eval_libc("O_RDWR"),
164 (true, false) => ecx.eval_libc("O_RDONLY"),
165 (false, true) => ecx.eval_libc("O_WRONLY"),
166 _ => unreachable!(),
167 })
168 }
169}
170
171impl UnixFileDescription for DirHandle {
172 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
173 interp_ok(ecx.eval_libc("O_RDONLY"))
175 }
176}
177
178#[derive(Debug)]
182pub struct DirTable {
183 streams: FxHashMap<u64, DirStream>,
193 next_id: u64,
195}
196
197impl DirTable {
198 #[expect(clippy::arithmetic_side_effects)]
199 fn insert_new(&mut self, stream: DirStream) -> u64 {
200 let id = self.next_id;
201 self.next_id += 1;
202 self.streams.try_insert(id, stream).unwrap();
203 id
204 }
205}
206
207impl Default for DirTable {
208 fn default() -> DirTable {
209 DirTable {
210 streams: FxHashMap::default(),
211 next_id: 1,
213 }
214 }
215}
216
217impl VisitProvenance for DirTable {
218 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
219 let DirTable { streams, next_id: _ } = self;
220
221 for dir in streams.values() {
222 dir.entry.visit_provenance(visit);
223 }
224 }
225}
226
227fn maybe_sync_file(
228 file: &FileHandle,
229 operation: fn(&File) -> std::io::Result<()>,
230) -> std::io::Result<i32> {
231 if !file.writable && cfg!(windows) {
232 Ok(0i32)
236 } else {
237 let result = operation(&file.file);
238 result.map(|_| 0i32)
239 }
240}
241
242fn host_file_times_from_updates(access: TimeUpdate, modified: TimeUpdate) -> FileTimes {
244 let mut filetimes = FileTimes::new();
245 if let TimeUpdate::Set(access) = access {
246 filetimes = filetimes.set_accessed(access);
247 }
248 if let TimeUpdate::Set(modified) = modified {
249 filetimes = filetimes.set_modified(modified);
250 }
251 filetimes
252}
253
254fn same_host_file(file1: &fs::Metadata, file2: &fs::Metadata) -> bool {
255 cfg_select! {
256 unix => {
257 use std::os::unix::fs::MetadataExt;
258 file1.dev() == file2.dev() && file1.ino() == file2.ino()
259 }
260 windows => {
261 use std::os::windows::fs::MetadataExt;
262 file1.volume_serial_number().unwrap() == file2.volume_serial_number().unwrap()
263 && file1.file_index().unwrap() == file2.file_index().unwrap()
264 }
265 }
266}
267
268impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
269trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
270 fn parse_utimens_timespec(
273 &self,
274 tp: &MPlaceTy<'tcx>,
275 ) -> InterpResult<'tcx, Option<TimeUpdate>> {
276 let this = self.eval_context_ref();
277 assert!(this.machine.communicate(), "isolation should have prevented reaching this");
279
280 let nsec_place = this.project_field_named(tp, "tv_nsec")?;
282 let nsec = this.read_scalar(&nsec_place)?.to_target_isize(this)?;
283
284 if nsec == this.eval_libc("UTIME_OMIT").to_target_isize(this)? {
285 return interp_ok(Some(TimeUpdate::Omit));
286 }
287 if nsec == this.eval_libc("UTIME_NOW").to_target_isize(this)? {
288 return interp_ok(Some(TimeUpdate::Set(SystemTime::now())));
289 }
290
291 let Some(duration) = this.read_timespec(tp)? else {
292 return interp_ok(None);
293 };
294 interp_ok(SystemTime::UNIX_EPOCH.checked_add(duration).map(TimeUpdate::Set))
295 }
296
297 fn parse_utimens_times(
301 &self,
302 times: &MPlaceTy<'tcx>,
303 ) -> InterpResult<'tcx, Option<(TimeUpdate, TimeUpdate)>> {
304 let this = self.eval_context_ref();
305
306 if this.ptr_is_null(times.ptr())? {
307 let now = TimeUpdate::Set(SystemTime::now());
308 interp_ok(Some((now, now)))
309 } else {
310 let access_place = this.project_c_array_field(times, 0)?;
311 let modified_place = this.project_c_array_field(times, 1)?;
312 let Some(access) = this.parse_utimens_timespec(&access_place)? else {
313 return interp_ok(None);
314 };
315 let Some(modified) = this.parse_utimens_timespec(&modified_place)? else {
316 return interp_ok(None);
317 };
318 interp_ok(Some((access, modified)))
319 }
320 }
321
322 fn write_stat_buf(
323 &mut self,
324 metadata: FileMetadata,
325 buf_op: &OpTy<'tcx>,
326 ) -> InterpResult<'tcx, i32> {
327 let this = self.eval_context_mut();
328
329 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
330 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
331 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
332
333 let buf = this.deref_pointer(buf_op)?;
334
335 this.write_int_fields_named(
336 &[
337 ("st_dev", metadata.dev.unwrap_or(0).into()),
338 ("st_mode", metadata.mode.into()),
339 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
340 ("st_ino", metadata.ino.unwrap_or(0).into()),
341 ("st_uid", metadata.uid.unwrap_or(0).into()),
342 ("st_gid", metadata.gid.unwrap_or(0).into()),
343 ("st_rdev", 0),
344 ("st_atime", access_sec.into()),
345 ("st_atime_nsec", access_nsec.into()),
346 ("st_mtime", modified_sec.into()),
347 ("st_mtime_nsec", modified_nsec.into()),
348 ("st_ctime", 0),
349 ("st_ctime_nsec", 0),
350 ("st_size", metadata.size.into()),
351 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
352 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
353 ],
354 &buf,
355 )?;
356
357 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
358 this.write_int_fields_named(
359 &[
360 ("st_birthtime", created_sec.into()),
361 ("st_birthtime_nsec", created_nsec.into()),
362 ("st_flags", 0),
363 ("st_gen", 0),
364 ],
365 &buf,
366 )?;
367 }
368
369 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
370 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
371 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
373 }
374
375 interp_ok(0)
376 }
377
378 #[cfg(windows)]
379 fn file_type_to_d_type(&self, file_type: fs::FileType) -> InterpResult<'tcx, i32> {
380 let this = self.eval_context_ref();
381 match () {
382 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
383 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
384 _ if file_type.is_symlink() => interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
385 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
387 }
388 }
389
390 fn dir_entry_fields(
391 &self,
392 entry: Either<fs::DirEntry, &'static str>,
393 ) -> InterpResult<'tcx, DirEntry> {
394 let this = self.eval_context_ref();
395 interp_ok(match entry {
396 Either::Left(dir_entry) => {
397 cfg_select! {
398 unix => {
399 use std::os::unix::fs::DirEntryExt;
400 DirEntry {
401 name: dir_entry.file_name(),
402 d_type: {
403 this.eval_libc("DT_UNKNOWN").to_u8()?.into()
407 },
408 ino: dir_entry.ino(),
409 }
410 }
411 windows => {
412 DirEntry {
413 name: dir_entry.file_name(),
414 d_type: this.file_type_to_d_type(dir_entry.file_type().unwrap())?,
416 ino: 0u64,
417 }
418 }
419 }
420 }
421 Either::Right(special) =>
422 DirEntry {
423 name: special.into(),
424 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
425 ino: 0,
426 },
427 })
428 }
429
430 fn host_permissions_from_mode(
431 &self,
432 mode: u32,
433 old_permissions: impl FnOnce() -> io::Result<fs::Permissions>,
434 ) -> InterpResult<'tcx, io::Result<fs::Permissions>> {
435 cfg_select! {
436 unix => {
437 use std::os::unix::fs::PermissionsExt;
438
439 let _ = old_permissions;
440 interp_ok(Ok(fs::Permissions::from_mode(mode)))
441 }
442 _ => {
443 let ecx = self.eval_context_ref();
444
445 let mut permissions = match old_permissions() {
446 Ok(p) => p,
447 Err(err) => return interp_ok(Err(err)),
448 };
449 let s_ifreg = ecx.eval_libc("S_IFREG");
452 let s_ifreg = u32::try_from(s_ifreg.to_bits(s_ifreg.size())?).unwrap();
453 match mode & !s_ifreg {
454 0o666 => {
455 permissions.set_readonly(false);
456 }
457 0o444 => {
458 permissions.set_readonly(true);
459 }
460 _ => {
461 throw_unsup_format!(
462 "changing permissions to mode 0o{:o} is not supported on non-Unix hosts",
463 mode
464 );
465 }
466 }
467 interp_ok(Ok(permissions))
468 }
469 }
470 }
471}
472
473impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
474pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
475 fn open(
476 &mut self,
477 dirfd: Option<&OpTy<'tcx>>,
478 path_raw: &OpTy<'tcx>,
479 flag: &OpTy<'tcx>,
480 varargs: Varargs<'tcx, '_>,
481 ) -> InterpResult<'tcx, Scalar> {
482 #[cfg(unix)]
483 use std::os::unix::fs::OpenOptionsExt;
484 #[cfg(windows)]
485 use std::os::windows::fs::OpenOptionsExt;
486
487 let ecx = self.eval_context_mut();
488 let open_name = if dirfd.is_some() { "openat" } else { "open" };
489
490 let dirfd = match dirfd {
491 Some(dirfd) => Some(ecx.read_scalar(dirfd)?.to_i32()?),
492 None => None,
493 };
494 let path_raw = ecx.read_pointer(path_raw)?;
495 let flag = ecx.read_scalar(flag)?.to_i32()?;
496
497 let path = ecx.read_path_from_c_str(path_raw)?;
498 if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
500 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
501 {
502 ecx.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
503 }
504
505 let read_mode = |ecx: &MiriInterpCx<'tcx>,
507 options: &mut OpenOptions,
508 fn_name: &str|
509 -> InterpResult<'tcx> {
510 let ([mode], _) = ecx.check_varargs(
511 if ecx.libc_ty_layout("mode_t").size.bytes() >= 4 {
512 shim_varargs![libc::mode_t]
514 } else {
515 shim_varargs![i32]
518 },
519 varargs,
520 fn_name,
521 )?;
522 let mode = ecx.read_scalar(&mode)?.to_u32()?;
523
524 cfg_select! {
525 unix => {
526 options.mode(mode);
528 }
529 _ => {
530 let _unused = options;
531
532 let s_ifreg = ecx.eval_libc("S_IFREG");
535 let s_ifreg = u32::try_from(s_ifreg.to_bits(s_ifreg.size())?).unwrap();
536 if mode & !s_ifreg != 0o666 {
537 throw_unsup_format!(
538 "{open_name}: non-default mode 0o{:o} is not supported on non-Unix hosts",
539 mode
540 );
541 }
542 }
543 }
544
545 interp_ok(())
546 };
547
548 let mut flag = flag;
550
551 let mut options = OpenOptions::new();
552 let mut custom_flags = 0;
553
554 let o_rdonly = ecx.eval_libc_i32("O_RDONLY");
555 let o_wronly = ecx.eval_libc_i32("O_WRONLY");
556 let o_rdwr = ecx.eval_libc_i32("O_RDWR");
557 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
561 throw_unsup_format!("{open_name}: access mode flags on this target are unsupported");
562 }
563 let mut writable = true;
564 let mut readable = true;
565
566 let access_mode = flag & ecx.eval_libc_i32("O_ACCMODE");
568 flag &= !access_mode;
569
570 if access_mode == o_rdonly {
571 writable = false;
572 options.read(true);
573 } else if access_mode == o_wronly {
574 readable = false;
575 options.write(true);
576 } else if access_mode == o_rdwr {
577 options.read(true).write(true);
578 } else {
579 throw_unsup_format!("{open_name}: unsupported access mode {:#x}", access_mode);
580 }
581
582 if ecx.tcx.sess.target.os == Os::Linux {
583 let o_largefile = ecx.eval_libc_i32("O_LARGEFILE");
584 if flag & o_largefile == o_largefile {
585 flag &= !o_largefile;
586 }
588 let o_tmpfile = ecx.eval_libc_i32("O_TMPFILE");
589 if flag & o_tmpfile == o_tmpfile {
591 read_mode(
593 ecx,
594 &mut options,
595 if dirfd.is_some() {
596 "openat(dirfd, pathname, O_TMPFILE, ...)"
597 } else {
598 "open(pathname, O_TMPFILE, ...)"
599 },
600 )?;
601
602 return ecx.set_errno_and_return_neg1_i32(LibcError("EOPNOTSUPP"));
604 }
605 }
606 let o_directory = ecx.eval_libc_i32("O_DIRECTORY");
607 let mut want_directory = false;
608 if flag & o_directory == o_directory {
609 flag &= !o_directory;
610 want_directory = true;
611 #[cfg(unix)]
616 {
617 custom_flags |= libc::O_DIRECTORY;
618 }
619 }
620 let o_append = ecx.eval_libc_i32("O_APPEND");
621 let append = flag & o_append == o_append;
622 if append {
623 flag &= !o_append;
624 options.append(true);
625 }
626 let o_trunc = ecx.eval_libc_i32("O_TRUNC");
627 if flag & o_trunc == o_trunc {
628 flag &= !o_trunc;
629 options.truncate(true);
630 }
631 let o_creat = ecx.eval_libc_i32("O_CREAT");
632 if flag & o_creat == o_creat {
633 flag &= !o_creat;
634 read_mode(
636 ecx,
637 &mut options,
638 if dirfd.is_some() {
639 "openat(dirfd, pathname, O_CREAT, ...)"
640 } else {
641 "open(pathname, O_CREAT, ...)"
642 },
643 )?;
644
645 if want_directory {
646 return ecx.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
648 }
649
650 let o_excl = ecx.eval_libc_i32("O_EXCL");
652 if flag & o_excl == o_excl {
653 flag &= !o_excl;
654 options.create_new(true);
655 } else {
656 options.create(true);
657 }
658 }
659 let o_cloexec = ecx.eval_libc_i32("O_CLOEXEC");
660 if flag & o_cloexec == o_cloexec {
661 flag &= !o_cloexec;
662 }
665 let o_nofollow = ecx.eval_libc_i32("O_NOFOLLOW");
666 let mut nofollow = false;
667 if flag & o_nofollow == o_nofollow {
668 flag &= !o_nofollow;
669 nofollow = true;
670 cfg_select! {
671 unix => {
672 custom_flags |= libc::O_NOFOLLOW;
673 }
674 windows => {
675 if dirfd.is_some() {
676 throw_unsup_format!(
677 "openat: `O_NOFOLLOW` is not supported on Windows hosts"
678 );
679 }
680 custom_flags |=
681 windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
682 }
683 }
684 }
685
686 if flag != 0 {
688 throw_unsup_format!("{open_name}: unsupported flags: {flag:#x}");
689 }
690
691 if let IsolatedOp::Reject(reject_with) = ecx.machine.isolated_op {
693 ecx.reject_in_isolation(&format!("`{open_name}`"), reject_with)?;
694 return ecx.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
695 }
696
697 let dirfd = match dirfd {
698 Some(dirfd) => {
699 if path.is_absolute() || dirfd == ecx.eval_libc_i32("AT_FDCWD") {
700 None
701 } else {
702 let Some(fd) = ecx.machine.fds.get(dirfd) else {
704 return ecx.set_errno_and_return_neg1_i32(LibcError("EBADF"));
705 };
706 let Some(dir) = fd.downcast::<DirHandle>() else {
707 return ecx.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
708 };
709 Some(dir)
710 }
711 }
712 None => None,
713 };
714
715 if path.is_empty() {
717 return ecx.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
718 }
719 let path = if cfg!(windows) && dirfd.is_some() && path.to_str() == Some(".") {
722 Path::new("")
723 } else {
724 &path
725 };
726
727 let f_or_d = open_file_or_dir(
729 dirfd.as_ref().map(|d| &d.dir),
730 path,
731 options.custom_flags(custom_flags),
732 );
733 match f_or_d {
734 Err(err) => ecx.set_errno_and_return_neg1_i32(err),
735 Ok(Either::Right(dir)) => {
736 if writable {
738 return ecx.set_errno_and_return_neg1_i32(LibcError("EISDIR"));
741 }
742
743 let Some(fd) = ecx.machine.fds.insert_new(DirHandle { dir }) else {
744 return ecx.set_errno_and_return_neg1_i32(LibcError("EMFILE"));
745 };
746 interp_ok(Scalar::from_i32(fd))
747 }
748 Ok(Either::Left(file)) => {
749 if want_directory {
750 return ecx.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
751 }
752 if file.metadata().unwrap().is_symlink() {
753 if nofollow {
754 return ecx.set_errno_and_return_neg1_i32(LibcError("ELOOP"));
757 }
758 panic!("we should not get a symlink here");
759 }
760
761 let Some(fd) =
762 ecx.machine.fds.insert_new(FileHandle { file, writable, readable, append })
763 else {
764 return ecx.set_errno_and_return_neg1_i32(LibcError("EMFILE"));
765 };
766 interp_ok(Scalar::from_i32(fd))
767 }
768 }
769 }
770
771 fn lseek(
772 &mut self,
773 fd_num: i32,
774 offset: i128,
775 whence: i32,
776 dest: &MPlaceTy<'tcx>,
777 ) -> InterpResult<'tcx> {
778 let this = self.eval_context_mut();
779
780 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
783 if offset < 0 {
784 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
786 } else {
787 SeekFrom::Start(u64::try_from(offset).unwrap())
788 }
789 } else if whence == this.eval_libc_i32("SEEK_CUR") {
790 SeekFrom::Current(i64::try_from(offset).unwrap())
791 } else if whence == this.eval_libc_i32("SEEK_END") {
792 SeekFrom::End(i64::try_from(offset).unwrap())
793 } else {
794 return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
795 };
796
797 let communicate = this.machine.communicate();
798
799 let Some(fd) = this.machine.fds.get(fd_num) else {
800 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
801 };
802 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
803 drop(fd);
804
805 let result = this.try_unwrap_io_result(result)?;
806 this.write_int(result, dest)?;
807 interp_ok(())
808 }
809
810 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
811 let this = self.eval_context_mut();
812
813 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
814
815 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
817 this.reject_in_isolation("`unlink`", reject_with)?;
818 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
819 }
820
821 let result = cfg_select! {
822 unix => fs::remove_file(path),
823 windows => {{
824 use std::os::windows::fs::FileTypeExt;
828 let metadata = path.symlink_metadata();
829 if metadata.is_ok_and(|m| m.file_type().is_symlink_dir()) {
830 fs::remove_dir(path)
831 } else {
832 fs::remove_file(path)
833 }
834 }}
835 };
836 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0))?))
837 }
838
839 fn symlink(
840 &mut self,
841 target_op: &OpTy<'tcx>,
842 linkpath_op: &OpTy<'tcx>,
843 ) -> InterpResult<'tcx, Scalar> {
844 fn create_link(target: &Path, link: &Path) -> std::io::Result<()> {
845 cfg_select! {
846 unix => std::os::unix::fs::symlink(target, link),
847 windows => {
848 use std::os::windows::fs;
849
850 let target_is_dir = link.parent().is_some_and(|dir| dir.join(target).is_dir());
853 if target_is_dir {
856 fs::symlink_dir(target, link)
857 } else {
858 fs::symlink_file(target, link)
859 }
860 }
861 }
862 }
863
864 let this = self.eval_context_mut();
865 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
866 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
867
868 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
870 this.reject_in_isolation("`symlink`", reject_with)?;
871 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
872 }
873
874 let result = create_link(&target, &linkpath).map(|_| 0);
875 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
876 }
877
878 fn linkat(
879 &mut self,
880 oldfd_op: &OpTy<'tcx>,
881 oldpath_op: &OpTy<'tcx>,
882 newfd_op: &OpTy<'tcx>,
883 newpath_op: &OpTy<'tcx>,
884 flags_op: &OpTy<'tcx>,
885 ) -> InterpResult<'tcx, Scalar> {
886 let this = self.eval_context_mut();
887
888 let flags = this.read_scalar(flags_op)?.to_i32()?;
890 let oldfd = this.read_scalar(oldfd_op)?.to_i32()?;
891 let newfd = this.read_scalar(newfd_op)?.to_i32()?;
892 let oldpath_ptr = this.read_pointer(oldpath_op)?;
893 let newpath_ptr = this.read_pointer(newpath_op)?;
894
895 let at_fdcwd = this.eval_libc_i32("AT_FDCWD");
897
898 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
900 this.reject_in_isolation("`linkat`", reject_with)?;
901 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
902 }
903
904 if flags != 0 {
906 throw_unsup_format!("unsupported linkat flags {:#x}", flags);
907 }
908
909 if oldfd != at_fdcwd {
911 throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported");
912 }
913 if this.ptr_is_null(oldpath_ptr)? {
914 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
915 }
916 let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned();
917
918 if newfd != at_fdcwd {
920 throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported");
921 }
922 if this.ptr_is_null(newpath_ptr)? {
923 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
924 }
925 let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned();
926
927 let result = fs::hard_link(&oldpath, &newpath).map(|()| 0);
928 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
929 }
930
931 fn stat(&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!("`stat` 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("`stat`", reject_with)?;
947 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
948 }
949
950 let metadata = match FileMetadata::from_host(this, fs::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)?))
956 }
957
958 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
960 let this = self.eval_context_mut();
961
962 if !matches!(
963 &this.tcx.sess.target.os,
964 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
965 ) {
966 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
967 }
968
969 let path_scalar = this.read_pointer(path_op)?;
970 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
971
972 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
974 this.reject_in_isolation("`lstat`", reject_with)?;
975 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
976 }
977
978 let metadata = match FileMetadata::from_host(this, fs::symlink_metadata(path))? {
979 Ok(metadata) => metadata,
980 Err(err) => return this.set_errno_and_return_neg1_i32(err),
981 };
982
983 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
984 }
985
986 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
987 let this = self.eval_context_mut();
988
989 if !matches!(
990 &this.tcx.sess.target.os,
991 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
992 ) {
993 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
994 }
995
996 let fd = this.read_scalar(fd_op)?.to_i32()?;
997
998 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1000 this.reject_in_isolation("`fstat`", reject_with)?;
1001 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1003 }
1004
1005 let metadata = match FileMetadata::from_fd_num(this, fd)? {
1006 Ok(metadata) => metadata,
1007 Err(err) => return this.set_errno_and_return_neg1_i32(err),
1008 };
1009 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
1010 }
1011
1012 fn fstatat(
1013 &mut self,
1014 dirfd_op: &OpTy<'tcx>,
1015 path_op: &OpTy<'tcx>,
1016 buf_op: &OpTy<'tcx>,
1017 flags_op: &OpTy<'tcx>,
1018 ) -> InterpResult<'tcx, Scalar> {
1019 let this = self.eval_context_mut();
1020
1021 if !matches!(
1022 &this.tcx.sess.target.os,
1023 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
1024 ) {
1025 panic!("`fstatat` should not be called on {}", this.tcx.sess.target.os);
1026 }
1027
1028 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1029 let path = this.read_pointer(path_op)?;
1030 let flags = this.read_scalar(flags_op)?.to_i32()?;
1031
1032 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1034 this.reject_in_isolation("`fstatat`", reject_with)?;
1035 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1036 }
1037
1038 let mut flags = flags;
1040 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1042 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1043 flags &= !at_symlink_nofollow;
1044 if flags != 0 {
1046 throw_unsup_format!("unsupported flags for `fstatat`: {flags:#x}")
1047 }
1048
1049 let path = this.read_path_from_c_str(path)?.into_owned();
1050 let metadata = FileMetadata::at(
1052 this,
1053 dirfd,
1054 &path,
1055 symlink_nofollow_flag,
1056 false,
1057 )?;
1058
1059 let metadata = match metadata {
1060 Ok(metadata) => metadata,
1061 Err(err) => return this.set_errno_and_return_neg1_i32(err),
1062 };
1063 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
1064 }
1065
1066 fn linux_statx(
1067 &mut self,
1068 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
1074 let this = self.eval_context_mut();
1075 this.assert_target_os_is_linux_like("statx");
1076
1077 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1078 let pathname_ptr = this.read_pointer(pathname_op)?;
1079 let flags = this.read_scalar(flags_op)?.to_i32()?;
1080 let _mask = this.read_scalar(mask_op)?.to_u32()?;
1081 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
1082
1083 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
1085 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1086 }
1087
1088 let statxbuf = this.deref_pointer(statxbuf_op)?;
1089 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1090
1091 let mut flags = flags;
1093 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
1095 let empty_path_flag = flags & at_empty_path == at_empty_path;
1096 flags &= !at_empty_path;
1097 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1099 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1100 flags &= !at_symlink_nofollow;
1101 if flags != 0 {
1103 throw_unsup_format!("unsupported flags for `statx`: {flags:#x}")
1104 }
1105
1106 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1108 this.reject_in_isolation("`statx`", reject_with)?;
1109 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1110 }
1111
1112 let metadata =
1114 FileMetadata::at(this, dirfd, &path, symlink_nofollow_flag, empty_path_flag)?;
1115
1116 let metadata = match metadata {
1117 Ok(metadata) => metadata,
1118 Err(err) => return this.set_errno_and_return_neg1_i32(err),
1119 };
1120
1121 let mut mask = this.eval_libc_u32("STATX_TYPE")
1126 | this.eval_libc_u32("STATX_MODE")
1127 | this.eval_libc_u32("STATX_SIZE");
1128
1129 if metadata.ino.is_some() {
1131 mask |= this.eval_libc_u32("STATX_INO");
1132 }
1133 if metadata.nlink.is_some() {
1134 mask |= this.eval_libc_u32("STATX_NLINK");
1135 }
1136 if metadata.uid.is_some() {
1137 mask |= this.eval_libc_u32("STATX_UID");
1138 }
1139 if metadata.gid.is_some() {
1140 mask |= this.eval_libc_u32("STATX_GID");
1141 }
1142 if metadata.blocks.is_some() {
1143 mask |= this.eval_libc_u32("STATX_BLOCKS");
1144 }
1145
1146 let (access_sec, access_nsec) = metadata
1149 .accessed
1150 .map(|tup| {
1151 mask |= this.eval_libc_u32("STATX_ATIME");
1152 interp_ok(tup)
1153 })
1154 .unwrap_or_else(|| interp_ok((0, 0)))?;
1155
1156 let (created_sec, created_nsec) = metadata
1157 .created
1158 .map(|tup| {
1159 mask |= this.eval_libc_u32("STATX_BTIME");
1160 interp_ok(tup)
1161 })
1162 .unwrap_or_else(|| interp_ok((0, 0)))?;
1163
1164 let (modified_sec, modified_nsec) = metadata
1165 .modified
1166 .map(|tup| {
1167 mask |= this.eval_libc_u32("STATX_MTIME");
1168 interp_ok(tup)
1169 })
1170 .unwrap_or_else(|| interp_ok((0, 0)))?;
1171
1172 this.write_int_fields_named(
1174 &[
1175 ("stx_mask", mask.into()),
1176 ("stx_mode", metadata.mode.into()),
1177 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
1178 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
1179 ("stx_uid", metadata.uid.unwrap_or(0).into()),
1180 ("stx_gid", metadata.gid.unwrap_or(0).into()),
1181 ("stx_ino", metadata.ino.unwrap_or(0).into()),
1182 ("stx_size", metadata.size.into()),
1183 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
1184 ("stx_attributes_mask", 0),
1187 ("stx_attributes", 0),
1188 ("stx_rdev_major", 0),
1189 ("stx_rdev_minor", 0),
1190 ("stx_dev_major", 0),
1191 ("stx_dev_minor", 0),
1192 ],
1193 &statxbuf,
1194 )?;
1195 #[rustfmt::skip]
1196 this.write_int_fields_named(
1197 &[
1198 ("tv_sec", access_sec.into()),
1199 ("tv_nsec", access_nsec.into()),
1200 ],
1201 &this.project_field_named(&statxbuf, "stx_atime")?,
1202 )?;
1203 #[rustfmt::skip]
1204 this.write_int_fields_named(
1205 &[
1206 ("tv_sec", created_sec.into()),
1207 ("tv_nsec", created_nsec.into()),
1208 ],
1209 &this.project_field_named(&statxbuf, "stx_btime")?,
1210 )?;
1211 #[rustfmt::skip]
1212 this.write_int_fields_named(
1213 &[
1214 ("tv_sec", 0.into()),
1215 ("tv_nsec", 0.into()),
1216 ],
1217 &this.project_field_named(&statxbuf, "stx_ctime")?,
1218 )?;
1219 #[rustfmt::skip]
1220 this.write_int_fields_named(
1221 &[
1222 ("tv_sec", modified_sec.into()),
1223 ("tv_nsec", modified_nsec.into()),
1224 ],
1225 &this.project_field_named(&statxbuf, "stx_mtime")?,
1226 )?;
1227
1228 interp_ok(Scalar::from_i32(0))
1232 }
1233
1234 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1235 let this = self.eval_context_mut();
1236
1237 let path_ptr = this.read_pointer(path_op)?;
1238 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
1239
1240 if this.ptr_is_null(path_ptr)? {
1241 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1242 }
1243 let path = this.read_path_from_c_str(path_ptr)?;
1244
1245 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1247 this.reject_in_isolation("`chmod`", reject_with)?;
1248 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1249 }
1250
1251 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap(), || {
1252 fs::metadata(&path).map(|m| m.permissions())
1257 })?;
1258 let res = permissions.and_then(|permissions| fs::set_permissions(path, permissions));
1259 if let Err(err) = res {
1260 return this.set_errno_and_return_neg1_i32(err);
1261 }
1262
1263 interp_ok(Scalar::from_i32(0))
1264 }
1265
1266 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1267 let this = self.eval_context_mut();
1268
1269 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1270 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
1271
1272 let Some(fd) = this.machine.fds.get(fd_num) else {
1273 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1274 };
1275 let Some(file) = fd.downcast::<FileHandle>() else {
1276 throw_unsup_format!("`fchmod` is only supported on regular files")
1278 };
1279 if !file.writable && !file.readable {
1280 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1283 }
1284 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1285
1286 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap(), || {
1287 file.file.metadata().map(|m| m.permissions())
1290 })?;
1291 let res = permissions.and_then(|permissions| file.file.set_permissions(permissions));
1292 if let Err(err) = res {
1293 return this.set_errno_and_return_neg1_i32(err);
1294 }
1295
1296 interp_ok(Scalar::from_i32(0))
1297 }
1298
1299 fn rename(
1300 &mut self,
1301 oldpath_op: &OpTy<'tcx>,
1302 newpath_op: &OpTy<'tcx>,
1303 ) -> InterpResult<'tcx, Scalar> {
1304 let this = self.eval_context_mut();
1305
1306 let oldpath_ptr = this.read_pointer(oldpath_op)?;
1307 let newpath_ptr = this.read_pointer(newpath_op)?;
1308
1309 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1310 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1311 }
1312
1313 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1314 let newpath = this.read_path_from_c_str(newpath_ptr)?;
1315
1316 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1318 this.reject_in_isolation("`rename`", reject_with)?;
1319 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1320 }
1321
1322 let result = fs::rename(oldpath, newpath).map(|_| 0);
1323
1324 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1325 }
1326
1327 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1328 let this = self.eval_context_mut();
1329
1330 #[cfg_attr(not(unix), allow(unused_variables))]
1331 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1332 u32::from(this.read_scalar(mode_op)?.to_u16()?)
1333 } else {
1334 this.read_scalar(mode_op)?.to_u32()?
1335 };
1336
1337 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1338
1339 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1341 this.reject_in_isolation("`mkdir`", reject_with)?;
1342 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1343 }
1344
1345 #[cfg_attr(not(unix), allow(unused_mut))]
1346 let mut builder = DirBuilder::new();
1347
1348 #[cfg(unix)]
1351 {
1352 use std::os::unix::fs::DirBuilderExt;
1353 builder.mode(mode);
1354 }
1355
1356 let result = builder.create(path).map(|_| 0i32);
1357
1358 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
1359 }
1360
1361 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1362 let this = self.eval_context_mut();
1363
1364 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1365
1366 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1368 this.reject_in_isolation("`rmdir`", reject_with)?;
1369 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
1370 }
1371
1372 let result = cfg_select! {
1373 unix => fs::remove_dir(path),
1374 windows => {{
1375 use std::os::windows::fs::FileTypeExt;
1379 let metadata = path.symlink_metadata();
1380 if metadata.is_ok_and(|m| m.file_type().is_symlink_dir()) {
1381 return this.set_errno_and_return_neg1_i32(LibcError("ENOTDIR"));
1382 } else {
1383 fs::remove_dir(path)
1384 }
1385 }}
1386 };
1387
1388 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|_| 0i32))?))
1389 }
1390
1391 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1392 let this = self.eval_context_mut();
1393
1394 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1395
1396 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1398 this.reject_in_isolation("`opendir`", reject_with)?;
1399 this.set_last_error(LibcError("EACCES"))?;
1400 return interp_ok(Scalar::null_ptr(this));
1401 }
1402
1403 let result = fs::read_dir(&name);
1404
1405 match result {
1406 Ok(read_dir) => {
1407 let Ok(dir) = read_dir.dir() else {
1410 throw_unsup_format!(
1411 "cannot `opendir` this directory: failed to create directory handle"
1412 );
1413 };
1414 let dir = this.machine.fds.new_ref(DirHandle { dir });
1415 let dir_fd_id = dir.id();
1416 let Some(dir_fd_num) = this.machine.fds.insert(dir) else {
1417 return this.set_errno_and_return_neg1_i32(LibcError("EMFILE"));
1418 };
1419
1420 let stream = DirStream::new(read_dir, dir_fd_num, dir_fd_id);
1421 let id = this.machine.dirs.insert_new(stream);
1422
1423 interp_ok(Scalar::from_target_usize(id, this))
1428 }
1429 Err(e) => {
1430 this.set_last_error(e)?;
1431 interp_ok(Scalar::null_ptr(this))
1432 }
1433 }
1434 }
1435
1436 fn readdir(
1437 &mut self,
1438 dirp_op: &OpTy<'tcx>,
1439 dest: &MPlaceTy<'tcx>,
1440 buf_type: &'static str,
1441 ) -> InterpResult<'tcx> {
1442 let this = self.eval_context_mut();
1443
1444 if !matches!(
1445 &this.tcx.sess.target.os,
1446 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd | Os::MacOs
1447 ) {
1448 throw_unsup_format!("`readdir` is not yet supported on {}", this.tcx.sess.target.os);
1449 }
1450
1451 let dirp = this.read_target_usize(dirp_op)?;
1452
1453 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1455 this.reject_in_isolation("`readdir`", reject_with)?;
1456 this.set_last_error(LibcError("EBADF"))?;
1457 this.write_int(0, dest)?;
1458 return interp_ok(());
1459 }
1460
1461 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1462 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1463 })?;
1464
1465 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1467 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1468 }
1469
1470 let entry = match open_dir.next_host_entry() {
1471 Some(Ok(dir_entry)) => {
1472 let dir_entry = this.dir_entry_fields(dir_entry)?;
1473
1474 let dirent_layout = this.libc_ty_layout(buf_type);
1516 let fields = &dirent_layout.fields;
1517 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1518
1519 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1523 let name_len = u64::try_from(name_bytes.len()).unwrap();
1524 let size = d_name_offset.strict_add(name_len);
1525
1526 let entry = this.allocate_ptr(
1527 Size::from_bytes(size),
1528 dirent_layout.align,
1529 MiriMemoryKind::Runtime.into(),
1530 AllocInit::Uninit,
1531 )?;
1532 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1533
1534 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1537 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1538
1539 let ino_name =
1541 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1542 this.write_int_fields_named(
1543 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1544 &entry,
1545 )?;
1546
1547 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1549 this.write_int(0, &d_off)?;
1550 }
1551 if let Some(d_seekoff) = this.try_project_field_named(&entry, "d_seekoff")? {
1552 this.write_int(0, &d_seekoff)?;
1553 }
1554 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1555 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1556 }
1557 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1558 this.write_int(dir_entry.d_type, &d_type)?;
1559 }
1560
1561 Some(entry.ptr())
1562 }
1563 None => {
1564 None
1566 }
1567 Some(Err(e)) => {
1568 this.set_last_error(e)?;
1569 None
1570 }
1571 };
1572
1573 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1574 if let Some((old_entry, old_type)) =
1575 std::mem::replace(&mut open_dir.entry, entry.map(|e| (e, buf_type)))
1576 {
1577 if old_type != buf_type {
1578 throw_ub_format!("mixing different `readdir` variants on the same `DIR` instance");
1579 }
1580 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1581 }
1582
1583 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1584 interp_ok(())
1585 }
1586
1587 fn dirfd(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1588 let this = self.eval_context_mut();
1589
1590 let dirp = this.read_target_usize(dirp_op)?;
1591
1592 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1593 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1594 })?;
1595
1596 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1598 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1599 }
1600
1601 let fd_num = open_dir.fd_num;
1602 this.write_int(fd_num, dest)?;
1603 interp_ok(())
1604 }
1605
1606 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1607 let this = self.eval_context_mut();
1608
1609 let dirp = this.read_target_usize(dirp_op)?;
1610
1611 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1613 this.reject_in_isolation("`closedir`", reject_with)?;
1614 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1615 }
1616
1617 let mut open_dir = this.machine.dirs.streams.remove(&dirp).ok_or_else(|| {
1618 err_ub_format!("the DIR pointer passed to `closedir` did not come from opendir")
1619 })?;
1620
1621 if this.machine.fds.get(open_dir.fd_num).is_none_or(|fd| fd.id() != open_dir.fd_id) {
1623 throw_ub_format!("the backing FD for this DIR stream has been tampered with");
1624 }
1625 this.close(open_dir.fd_num)?;
1627
1628 if let Some((entry, _type)) = open_dir.entry.take() {
1629 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1630 }
1631 drop(open_dir);
1633
1634 interp_ok(Scalar::from_i32(0))
1635 }
1636
1637 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1638 let this = self.eval_context_mut();
1639
1640 let Some(fd) = this.machine.fds.get(fd_num) else {
1641 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1642 };
1643 let Some(file) = fd.downcast::<FileHandle>() else {
1644 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1647 };
1648 if !file.writable {
1649 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1651 }
1652 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1653
1654 if let Ok(length) = length.try_into() {
1655 let result = file.file.set_len(length);
1656 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1657 interp_ok(Scalar::from_i32(result))
1658 } else {
1659 this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
1660 }
1661 }
1662
1663 fn posix_fallocate(
1666 &mut self,
1667 fd_num: i32,
1668 offset: i64,
1669 len: i64,
1670 ) -> InterpResult<'tcx, Scalar> {
1671 let this = self.eval_context_mut();
1672
1673 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1675 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1676 return interp_ok(this.eval_libc("EBADF"));
1678 }
1679
1680 match this.fallocate_impl(fd_num, offset, len)? {
1681 Ok(()) => interp_ok(Scalar::from_i32(0)),
1682 Err(e) => this.io_error_to_errnum(e),
1683 }
1684 }
1685
1686 fn linux_fallocate(
1687 &mut self,
1688 fd: i32,
1689 mode: i32,
1690 offset: i64,
1691 size: i64,
1692 ) -> InterpResult<'tcx, Scalar> {
1693 let this = self.eval_context_mut();
1695 this.assert_target_os_is_linux_like("fallocate");
1696
1697 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1699 this.reject_in_isolation("`fallocate`", reject_with)?;
1700 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1702 }
1703
1704 if mode != 0 {
1707 throw_unsup_format!("unsupported flags for `fallocate` in `mode` argument: {mode:#x}")
1708 }
1709
1710 match this.fallocate_impl(fd, offset, size)? {
1711 Ok(()) => interp_ok(Scalar::from_i32(0)),
1712 Err(e) => this.set_errno_and_return_neg1_i32(e),
1713 }
1714 }
1715
1716 fn fallocate_impl(
1718 &self,
1719 fd_num: i32,
1720 offset: i64,
1721 len: i64,
1722 ) -> InterpResult<'tcx, Result<(), IoError>> {
1723 let this = self.eval_context_ref();
1724
1725 if offset < 0 || len <= 0 {
1727 return interp_ok(Err(LibcError("EINVAL")));
1728 }
1729
1730 let Some(fd) = this.machine.fds.get(fd_num) else {
1731 return interp_ok(Err(LibcError("EBADF")));
1732 };
1733 let Some(file) = fd.downcast::<FileHandle>() else {
1734 return interp_ok(Err(LibcError("ENODEV")));
1736 };
1737
1738 if !file.writable {
1739 return interp_ok(Err(LibcError("EBADF")));
1740 }
1741
1742 let current_size = match file.file.metadata() {
1743 Ok(metadata) => metadata.len(),
1744 Err(err) => return interp_ok(Err(err.into())),
1745 };
1746
1747 let new_size = match offset.checked_add(len) {
1749 Some(new_size) => u64::try_from(new_size).unwrap(),
1751 None => return interp_ok(Err(LibcError("EFBIG"))), };
1753
1754 if current_size < new_size {
1757 match file.file.set_len(new_size) {
1758 Ok(()) => interp_ok(Ok(())),
1759 Err(err) => interp_ok(Err(err.into())),
1760 }
1761 } else {
1762 interp_ok(Ok(()))
1763 }
1764 }
1765
1766 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1767 let this = self.eval_context_mut();
1773
1774 let fd = this.read_scalar(fd_op)?.to_i32()?;
1775
1776 this.ffullsync_fd(fd)
1777 }
1778
1779 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1780 let this = self.eval_context_mut();
1781 let Some(fd) = this.machine.fds.get(fd_num) else {
1782 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1783 };
1784 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1786 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1787 })?;
1788 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1789
1790 let io_result = maybe_sync_file(&file, File::sync_all);
1791 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1792 }
1793
1794 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1795 let this = self.eval_context_mut();
1796
1797 let fd = this.read_scalar(fd_op)?.to_i32()?;
1798
1799 let Some(fd) = this.machine.fds.get(fd) else {
1800 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1801 };
1802 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1804 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1805 })?;
1806 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1807
1808 let io_result = maybe_sync_file(&file, File::sync_data);
1809 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1810 }
1811
1812 fn futimens(
1815 &mut self,
1816 fd_op: &OpTy<'tcx>,
1817 times_op: &OpTy<'tcx>,
1818 ) -> InterpResult<'tcx, Scalar> {
1819 let this = self.eval_context_mut();
1820
1821 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
1822 let times = this.deref_pointer(times_op)?;
1823
1824 let Some(fd) = this.machine.fds.get(fd_num) else {
1825 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1826 };
1827 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1828 err_unsup_format!("`futimens` is only supported on file-backed file descriptors")
1829 })?;
1830 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1831
1832 let Some((access, modified)) = this.parse_utimens_times(×)? else {
1833 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1834 };
1835 let result = file.file.set_times(host_file_times_from_updates(access, modified));
1836 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1837 }
1838
1839 fn utimensat(
1842 &mut self,
1843 dirfd_op: &OpTy<'tcx>,
1844 path_op: &OpTy<'tcx>,
1845 times_op: &OpTy<'tcx>,
1846 flags_op: &OpTy<'tcx>,
1847 ) -> InterpResult<'tcx, Scalar> {
1848 let this = self.eval_context_mut();
1849
1850 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
1851 let path_ptr = this.read_pointer(path_op)?;
1852 let times = this.deref_pointer(times_op)?;
1853 let flags = this.read_scalar(flags_op)?.to_i32()?;
1854
1855 if this.ptr_is_null(path_ptr)? {
1856 if dirfd != this.eval_libc_i32("AT_FDCWD") {
1859 throw_unsup_format!(
1860 "`utimensat` with a NULL path is only supported for `AT_FDCWD`"
1861 );
1862 }
1863 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1864 }
1865 let path = this.read_path_from_c_str(path_ptr)?.into_owned();
1866
1867 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1868 this.reject_in_isolation("`utimensat`", reject_with)?;
1869 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
1870 }
1871
1872 let mut flags = flags;
1873 let at_symlink_nofollow = this.eval_libc_i32("AT_SYMLINK_NOFOLLOW");
1875 let symlink_nofollow_flag = flags & at_symlink_nofollow == at_symlink_nofollow;
1876 flags &= !at_symlink_nofollow;
1877
1878 if flags != 0 {
1879 throw_unsup_format!("unsupported flags for `utimensat`: {flags:#x}")
1880 }
1881
1882 if !(path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")) {
1883 throw_unsup_format!("`utimensat` with a path relative to a `dirfd` is not supported");
1884 }
1885
1886 let Some((access, modified)) = this.parse_utimens_times(×)? else {
1887 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1888 };
1889 if matches!((access, modified), (TimeUpdate::Omit, TimeUpdate::Omit)) {
1890 return interp_ok(Scalar::from_i32(0));
1894 }
1895 let filetimes = host_file_times_from_updates(access, modified);
1896 let result = if symlink_nofollow_flag {
1897 fs::set_times_nofollow(path, filetimes)
1898 } else {
1899 fs::set_times(path, filetimes)
1900 };
1901 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result.map(|()| 0i32))?))
1902 }
1903
1904 fn linux_sync_file_range(
1905 &mut self,
1906 fd_op: &OpTy<'tcx>,
1907 offset_op: &OpTy<'tcx>,
1908 nbytes_op: &OpTy<'tcx>,
1909 flags_op: &OpTy<'tcx>,
1910 ) -> InterpResult<'tcx, Scalar> {
1911 let this = self.eval_context_mut();
1912 this.assert_target_os_is_linux_like("sync_file_range");
1913
1914 let fd = this.read_scalar(fd_op)?.to_i32()?;
1915 let offset = this.read_scalar(offset_op)?.to_i64()?;
1916 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1917 let flags = this.read_scalar(flags_op)?.to_i32()?;
1918
1919 if offset < 0 || nbytes < 0 {
1920 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1921 }
1922 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1923 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1924 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1925 if flags & allowed_flags != flags {
1926 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1927 }
1928
1929 let Some(fd) = this.machine.fds.get(fd) else {
1930 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1931 };
1932 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1934 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1935 })?;
1936 assert!(this.machine.communicate(), "isolation should have prevented even opening a file");
1937
1938 let io_result = maybe_sync_file(&file, File::sync_data);
1939 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1940 }
1941
1942 fn readlink(
1943 &mut self,
1944 pathname_op: &OpTy<'tcx>,
1945 buf_op: &OpTy<'tcx>,
1946 bufsize_op: &OpTy<'tcx>,
1947 ) -> InterpResult<'tcx, i64> {
1948 let this = self.eval_context_mut();
1949
1950 let pathname = this.read_os_str_from_c_str(this.read_pointer(pathname_op)?)?;
1951 let buf = this.read_pointer(buf_op)?;
1952 let bufsize = this.read_target_usize(bufsize_op)?;
1953
1954 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1956 this.reject_in_isolation("`readlink`", reject_with)?;
1957 this.set_last_error(LibcError("EACCES"))?;
1958 return interp_ok(-1);
1959 }
1960
1961 let self_exe = match this.tcx.sess.target.os {
1964 Os::Linux | Os::Android => Some("/proc/self/exe"),
1965 Os::Solaris | Os::Illumos => Some("/proc/self/path/a.out"),
1966 _ => None,
1967 };
1968 let result = if self_exe.is_some() && pathname.to_str() == self_exe {
1969 this.machine.current_exe.clone().ok_or(ErrorKind::NotFound.into())
1970 } else if matches!(
1971 this.tcx.sess.target.os,
1972 Os::Linux | Os::Android | Os::Illumos | Os::Solaris
1973 ) && path::absolute(pathname).is_ok_and(|path| path.starts_with("/proc"))
1974 {
1975 this.set_last_error(ErrorKind::NotFound)?;
1978 return interp_ok(-1);
1979 } else {
1980 let pathname = this.convert_path(Cow::Borrowed(pathname), PathConversion::TargetToHost);
1983 std::fs::read_link(pathname)
1984 };
1985
1986 match result {
1987 Ok(resolved) => {
1988 let resolved = this
1992 .convert_path(Cow::Borrowed(resolved.as_ref()), PathConversion::HostToTarget);
1993 let mut path_bytes = resolved.as_encoded_bytes();
1994 let bufsize: usize = bufsize.try_into().unwrap();
1995 if path_bytes.len() > bufsize {
1996 path_bytes = &path_bytes[..bufsize]
1997 }
1998 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1999 interp_ok(path_bytes.len().try_into().unwrap())
2000 }
2001 Err(e) => {
2002 this.set_last_error(e)?;
2003 interp_ok(-1)
2004 }
2005 }
2006 }
2007
2008 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
2009 let this = self.eval_context_mut();
2010 let fd = this.read_scalar(miri_fd)?.to_i32()?;
2013 let error = if let Some(fd) = this.machine.fds.get(fd) {
2014 if fd.is_tty(this.machine.communicate()) {
2015 return interp_ok(Scalar::from_i32(1));
2016 } else {
2017 LibcError("ENOTTY")
2018 }
2019 } else {
2020 LibcError("EBADF")
2022 };
2023 this.set_last_error(error)?;
2024 interp_ok(Scalar::from_i32(0))
2025 }
2026
2027 fn realpath(
2028 &mut self,
2029 path_op: &OpTy<'tcx>,
2030 processed_path_op: &OpTy<'tcx>,
2031 ) -> InterpResult<'tcx, Scalar> {
2032 let this = self.eval_context_mut();
2033 this.assert_target_os_is_unix("realpath");
2034
2035 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
2036 let processed_ptr = this.read_pointer(processed_path_op)?;
2037
2038 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
2040 this.reject_in_isolation("`realpath`", reject_with)?;
2041 this.set_last_error(LibcError("EACCES"))?;
2042 return interp_ok(Scalar::from_target_usize(0, this));
2043 }
2044
2045 let result = std::fs::canonicalize(pathname);
2046 match result {
2047 Ok(resolved) => {
2048 let path_max = this
2049 .eval_libc_i32("PATH_MAX")
2050 .try_into()
2051 .expect("PATH_MAX does not fit in u64");
2052 let dest = if this.ptr_is_null(processed_ptr)? {
2053 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
2063 } else {
2064 let (wrote_path, _) =
2065 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
2066
2067 if !wrote_path {
2068 this.set_last_error(LibcError("ENAMETOOLONG"))?;
2072 return interp_ok(Scalar::from_target_usize(0, this));
2073 }
2074 processed_ptr
2075 };
2076
2077 interp_ok(Scalar::from_maybe_pointer(dest, this))
2078 }
2079 Err(e) => {
2080 this.set_last_error(e)?;
2081 interp_ok(Scalar::from_target_usize(0, this))
2082 }
2083 }
2084 }
2085 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
2086 use rand::seq::IndexedRandom;
2087
2088 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
2090
2091 let this = self.eval_context_mut();
2092 this.assert_target_os_is_unix("mkstemp");
2093
2094 let max_attempts = this.eval_libc_u32("TMP_MAX");
2104
2105 let template_ptr = this.read_pointer(template_op)?;
2108 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
2109 let template_bytes = template.as_mut_slice();
2110
2111 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
2113 this.reject_in_isolation("`mkstemp`", reject_with)?;
2114 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
2115 }
2116
2117 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
2119
2120 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
2125 let end_pos = template_bytes.len();
2126 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
2127
2128 if last_six_char_bytes != suffix_bytes {
2130 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
2131 }
2132
2133 const SUBSTITUTIONS: &[char; 62] = &[
2137 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
2138 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
2139 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
2140 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
2141 ];
2142
2143 let mut fopts = OpenOptions::new();
2146 fopts.read(true).write(true).create_new(true);
2147
2148 cfg_select! {
2149 unix => {
2150 use std::os::unix::fs::OpenOptionsExt;
2151 fopts.mode(0o600);
2153 fopts.custom_flags(libc::O_EXCL);
2154 }
2155 windows => {
2156 use std::os::windows::fs::OpenOptionsExt;
2157 fopts.share_mode(0);
2159 }
2160 _ => {
2161 throw_unsup_format!("`mkstemp` is not supported on this host OS");
2162 }
2163 }
2164
2165 for _ in 0..max_attempts {
2167 let rng = this.machine.rng.get_mut();
2168
2169 let unique_suffix =
2171 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
2172
2173 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
2175
2176 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
2178
2179 let file = fopts.open(bytes_to_os_str(template_bytes)?);
2181 match file {
2182 Ok(f) => {
2183 let Some(fd) = this.machine.fds.insert_new(FileHandle {
2184 file: f,
2185 writable: true,
2186 readable: true,
2187 append: false,
2188 }) else {
2189 return this.set_errno_and_return_neg1_i32(LibcError("EMFILE"));
2190 };
2191 return interp_ok(Scalar::from_i32(fd));
2192 }
2193 Err(e) =>
2194 match e.kind() {
2195 ErrorKind::AlreadyExists => continue,
2197 _ => {
2199 return this.set_errno_and_return_neg1_i32(e);
2202 }
2203 },
2204 }
2205 }
2206
2207 this.set_errno_and_return_neg1_i32(LibcError("EEXIST"))
2209 }
2210
2211 fn linux_copy_file_range(
2213 &mut self,
2214 fd_in_op: &OpTy<'tcx>,
2215 off_in_op: &OpTy<'tcx>,
2216 fd_out_op: &OpTy<'tcx>,
2217 off_out_op: &OpTy<'tcx>,
2218 len_op: &OpTy<'tcx>,
2219 flags_op: &OpTy<'tcx>,
2220 dest: &MPlaceTy<'tcx>,
2221 ) -> InterpResult<'tcx> {
2222 let ecx = self.eval_context_mut();
2223 ecx.assert_target_os_is_linux_like("copy_file_range");
2224
2225 let fd_in = ecx.read_scalar(fd_in_op)?.to_i32()?;
2226 let off_in = ecx.read_pointer(off_in_op)?;
2227 let fd_out = ecx.read_scalar(fd_out_op)?.to_i32()?;
2228 let off_out = ecx.read_pointer(off_out_op)?;
2229 let len = ecx.read_target_usize(len_op)?;
2230 let flags = ecx.read_scalar(flags_op)?.to_u32()?;
2231
2232 let (Some(fd_in), Some(fd_out)) = (ecx.machine.fds.get(fd_in), ecx.machine.fds.get(fd_out))
2233 else {
2234 return ecx.set_errno_and_return_neg1(LibcError("EBADF"), dest);
2235 };
2236 if flags != 0 {
2237 throw_unsup_format!("`copy_file_range` with non-zero flags is not supported");
2238 }
2239 if !ecx.ptr_is_null(off_in)? || !ecx.ptr_is_null(off_out)? {
2240 throw_unsup_format!("`copy_file_range` with non-null offset pointers is not supported");
2241 }
2242 let is_dir = |fd: &DynFileDescriptionRef| fd.clone().downcast::<DirHandle>().is_some();
2245 let as_regular_file = |fd: DynFileDescriptionRef| {
2246 let file = fd.downcast::<FileHandle>()?;
2247 file.file.metadata().is_ok_and(|metadata| metadata.is_file()).then_some(file)
2249 };
2250 if is_dir(&fd_in) || is_dir(&fd_out) {
2251 return ecx.set_errno_and_return_neg1(LibcError("EISDIR"), dest);
2252 }
2253 let (Some(file_in), Some(file_out)) = (as_regular_file(fd_in), as_regular_file(fd_out))
2254 else {
2255 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
2256 };
2257 if !file_in.readable || !file_out.writable || file_out.append {
2258 return ecx.set_errno_and_return_neg1(LibcError("EBADF"), dest);
2260 }
2261 assert!(ecx.machine.communicate(), "isolation should have prevented even opening a file");
2262
2263 let len = if ecx.machine.short_fd_operations
2266 && (file_in.short_fd_operations() || file_out.short_fd_operations())
2267 && len >= 2
2268 && let rng = ecx.machine.rng.get_mut()
2269 && (rng.random() || (len >= BIG_ACCESS && rng.random()))
2271 {
2272 let mut input = &file_in.file;
2274 let remaining_bytes =
2275 try { input.stream_len()?.saturating_sub(input.stream_position()?).clamp_to(2..) };
2276 let len = len.clamp_to(..=remaining_bytes.unwrap_or(u64::MAX));
2277 len / 2 } else {
2279 len
2280 };
2281
2282 let copy = || -> io::Result<u64> {
2283 let (mut input, mut output) = (&file_in.file, &file_out.file);
2284 let (pos_in, pos_out) = (input.stream_position()?, output.stream_position()?);
2285
2286 let (meta_in, meta_out) = (input.metadata()?, output.metadata()?);
2288 if same_host_file(&meta_in, &meta_out)
2289 && (pos_in.checked_add(len).is_none_or(|end_in| end_in > pos_out)
2290 && pos_out.checked_add(len).is_none_or(|end_out| end_out > pos_in))
2291 {
2292 return Err(io::ErrorKind::InvalidInput.into());
2293 }
2294
2295 io::copy(&mut input.take(len), &mut output).inspect_err(|_| {
2296 let _ = input.seek(SeekFrom::Start(pos_in));
2299 let _ = output.seek(SeekFrom::Start(pos_out));
2300 })
2301 };
2302 ecx.return_io_result_via_errno(copy(), dest)
2303 }
2304}
2305
2306fn extract_sec_and_nsec<'tcx>(
2310 time: std::io::Result<SystemTime>,
2311) -> InterpResult<'tcx, Option<(u64, u32)>> {
2312 match time.ok() {
2313 Some(time) => {
2314 let duration = system_time_to_duration(&time)?;
2315 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
2316 }
2317 None => interp_ok(None),
2318 }
2319}
2320
2321fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
2322 if file_type.is_file() {
2323 "S_IFREG"
2324 } else if file_type.is_dir() {
2325 "S_IFDIR"
2326 } else if file_type.is_symlink() {
2327 "S_IFLNK"
2328 } else {
2329 #[cfg(unix)]
2331 {
2332 use std::os::unix::fs::FileTypeExt;
2333 if file_type.is_socket() {
2334 return "S_IFSOCK";
2335 } else if file_type.is_fifo() {
2336 return "S_IFIFO";
2337 } else if file_type.is_char_device() {
2338 return "S_IFCHR";
2339 } else if file_type.is_block_device() {
2340 return "S_IFBLK";
2341 }
2342 }
2343 "S_IFREG"
2344 }
2345}
2346
2347struct FileMetadata {
2355 mode: u32,
2357 size: u64,
2358 created: Option<(u64, u32)>,
2359 accessed: Option<(u64, u32)>,
2360 modified: Option<(u64, u32)>,
2361 dev: Option<u64>,
2362 ino: Option<u64>,
2363 nlink: Option<u64>,
2364 uid: Option<u32>,
2365 gid: Option<u32>,
2366 blksize: Option<u64>,
2367 blocks: Option<u64>,
2368}
2369
2370impl FileMetadata {
2371 fn at<'tcx>(
2373 ecx: &mut MiriInterpCx<'tcx>,
2374 dirfd: FdNum,
2375 path: &Path,
2376 symlink_nofollow_flag: bool,
2377 empty_path_flag: bool,
2378 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2379 if path.is_empty() {
2382 if !empty_path_flag {
2384 return interp_ok(Err(LibcError("ENOENT")));
2385 }
2386 FileMetadata::from_fd_num(ecx, dirfd)
2387 } else if path.is_absolute() || dirfd == ecx.eval_libc_i32("AT_FDCWD") {
2388 FileMetadata::from_host(
2390 ecx,
2391 if symlink_nofollow_flag { path.symlink_metadata() } else { path.metadata() },
2392 )
2393 } else {
2394 let Some(fd) = ecx.machine.fds.get(dirfd) else {
2396 return interp_ok(Err(LibcError("EBADF")));
2397 };
2398 let Some(dir) = fd.downcast::<DirHandle>() else {
2399 return interp_ok(Err(LibcError("ENOTDIR")));
2400 };
2401
2402 let path =
2405 if cfg!(windows) && path.to_str() == Some(".") { Path::new("") } else { path };
2406 let metadata = if symlink_nofollow_flag {
2407 dir.dir.symlink_metadata(path)
2408 } else {
2409 dir.dir.metadata(path)
2410 };
2411 FileMetadata::from_host(ecx, metadata)
2412 }
2413 }
2414
2415 fn from_fd_num<'tcx>(
2416 ecx: &mut MiriInterpCx<'tcx>,
2417 fd_num: FdNum,
2418 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2419 let Some(fd) = ecx.machine.fds.get(fd_num) else {
2420 return interp_ok(Err(LibcError("EBADF")));
2421 };
2422 match fd.metadata(ecx.machine.communicate())? {
2423 Either::Left(host) => Self::from_host(ecx, host),
2424 Either::Right(mode_name) => Self::synthetic(ecx, mode_name),
2425 }
2426 }
2427
2428 fn synthetic<'tcx>(
2429 ecx: &mut MiriInterpCx<'tcx>,
2430 mode_name: &str,
2431 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2432 let mode = ecx.eval_libc(mode_name);
2433 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
2434 let mode = mode | 0o666;
2436 interp_ok(Ok(FileMetadata {
2437 mode,
2438 size: 0,
2439 created: None,
2440 accessed: None,
2441 modified: None,
2442 dev: None,
2443 uid: None,
2444 gid: None,
2445 blksize: None,
2446 blocks: None,
2447 ino: None,
2448 nlink: None,
2449 }))
2450 }
2451
2452 fn from_host<'tcx>(
2453 ecx: &mut MiriInterpCx<'tcx>,
2454 metadata: Result<std::fs::Metadata, std::io::Error>,
2455 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
2456 let metadata = match metadata {
2457 Ok(metadata) => metadata,
2458 Err(e) => {
2459 return interp_ok(Err(e.into()));
2460 }
2461 };
2462
2463 let file_type = metadata.file_type();
2464 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
2465 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
2466
2467 let size = metadata.len();
2468
2469 let created = extract_sec_and_nsec(metadata.created())?;
2470 let accessed = extract_sec_and_nsec(metadata.accessed())?;
2471 let modified = extract_sec_and_nsec(metadata.modified())?;
2472
2473 cfg_select! {
2476 unix => {
2477 use std::os::unix::fs::{MetadataExt, PermissionsExt};
2478
2479 let dev = metadata.dev();
2480 let ino = metadata.ino();
2481 let nlink = metadata.nlink();
2482 let uid = metadata.uid();
2483 let gid = metadata.gid();
2484 let blksize = metadata.blksize();
2485 let blocks = metadata.blocks();
2486
2487 mode |= metadata.permissions().mode();
2488
2489 interp_ok(Ok(FileMetadata {
2490 mode,
2491 size,
2492 created,
2493 accessed,
2494 modified,
2495 dev: Some(dev),
2496 ino: Some(ino),
2497 nlink: Some(nlink),
2498 uid: Some(uid),
2499 gid: Some(gid),
2500 blksize: Some(blksize),
2501 blocks: Some(blocks),
2502 }))
2503 }
2504 _ => {
2505 mode |= if metadata.permissions().readonly() { 0o444 } else { 0o666 };
2507
2508 interp_ok(Ok(FileMetadata {
2509 mode,
2510 size,
2511 created,
2512 accessed,
2513 modified,
2514 dev: None,
2515 ino: None,
2516 nlink: None,
2517 uid: None,
2518 gid: None,
2519 blksize: None,
2520 blocks: None,
2521 }))
2522 }
2523 }
2524 }
2525}