1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{self, DirBuilder, File, FileType, OpenOptions, TryLockError};
6use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7use std::path::{self, Path, PathBuf};
8use std::time::SystemTime;
9
10use rustc_abi::Size;
11use rustc_data_structures::either::Either;
12use rustc_data_structures::fx::FxHashMap;
13use rustc_target::spec::Os;
14
15use self::shims::time::system_time_to_duration;
16use crate::shims::files::FileHandle;
17use crate::shims::os_str::bytes_to_os_str;
18use crate::shims::sig::check_min_vararg_count;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22#[derive(Debug)]
24struct OpenDir {
25 special_entries: Vec<&'static str>,
28 read_dir: fs::ReadDir,
30 entry: Option<Pointer>,
33}
34
35impl OpenDir {
36 fn new(read_dir: fs::ReadDir) -> Self {
37 Self { special_entries: vec!["..", "."], read_dir, entry: None }
38 }
39
40 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
41 if let Some(special) = self.special_entries.pop() {
42 return Some(Ok(Either::Right(special)));
43 }
44 let entry = self.read_dir.next()?;
45 Some(entry.map(Either::Left))
46 }
47}
48
49#[derive(Debug)]
50struct DirEntry {
51 name: OsString,
52 ino: u64,
53 d_type: i32,
54}
55
56impl UnixFileDescription for FileHandle {
57 fn pread<'tcx>(
58 &self,
59 communicate_allowed: bool,
60 offset: u64,
61 ptr: Pointer,
62 len: usize,
63 ecx: &mut MiriInterpCx<'tcx>,
64 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
65 ) -> InterpResult<'tcx> {
66 assert!(communicate_allowed, "isolation should have prevented even opening a file");
67 let mut bytes = vec![0; len];
68 let file = &mut &self.file;
72 let mut f = || {
73 let cursor_pos = file.stream_position()?;
74 file.seek(SeekFrom::Start(offset))?;
75 let res = file.read(&mut bytes);
76 file.seek(SeekFrom::Start(cursor_pos))
78 .expect("failed to restore file position, this shouldn't be possible");
79 res
80 };
81 let result = match f() {
82 Ok(read_size) => {
83 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
87 Ok(read_size)
88 }
89 Err(e) => Err(IoError::HostError(e)),
90 };
91 finish.call(ecx, result)
92 }
93
94 fn pwrite<'tcx>(
95 &self,
96 communicate_allowed: bool,
97 ptr: Pointer,
98 len: usize,
99 offset: u64,
100 ecx: &mut MiriInterpCx<'tcx>,
101 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
102 ) -> InterpResult<'tcx> {
103 assert!(communicate_allowed, "isolation should have prevented even opening a file");
104 let file = &mut &self.file;
108 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
109 let mut f = || {
110 let cursor_pos = file.stream_position()?;
111 file.seek(SeekFrom::Start(offset))?;
112 let res = file.write(bytes);
113 file.seek(SeekFrom::Start(cursor_pos))
115 .expect("failed to restore file position, this shouldn't be possible");
116 res
117 };
118 let result = f();
119 finish.call(ecx, result.map_err(IoError::HostError))
120 }
121
122 fn flock<'tcx>(
123 &self,
124 communicate_allowed: bool,
125 op: FlockOp,
126 ) -> InterpResult<'tcx, io::Result<()>> {
127 assert!(communicate_allowed, "isolation should have prevented even opening a file");
128
129 use FlockOp::*;
130 let (res, nonblocking) = match op {
132 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
133 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
134 Unlock => {
135 return interp_ok(self.file.unlock());
136 }
137 };
138
139 match res {
140 Ok(()) => interp_ok(Ok(())),
141 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
142 Err(TryLockError::WouldBlock) =>
143 if nonblocking {
144 interp_ok(Err(ErrorKind::WouldBlock.into()))
145 } else {
146 throw_unsup_format!("blocking `flock` is not currently supported");
147 },
148 }
149 }
150}
151
152#[derive(Debug)]
156pub struct DirTable {
157 streams: FxHashMap<u64, OpenDir>,
167 next_id: u64,
169}
170
171impl DirTable {
172 #[expect(clippy::arithmetic_side_effects)]
173 fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
174 let id = self.next_id;
175 self.next_id += 1;
176 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
177 id
178 }
179}
180
181impl Default for DirTable {
182 fn default() -> DirTable {
183 DirTable {
184 streams: FxHashMap::default(),
185 next_id: 1,
187 }
188 }
189}
190
191impl VisitProvenance for DirTable {
192 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
193 let DirTable { streams, next_id: _ } = self;
194
195 for dir in streams.values() {
196 dir.entry.visit_provenance(visit);
197 }
198 }
199}
200
201fn maybe_sync_file(
202 file: &File,
203 writable: bool,
204 operation: fn(&File) -> std::io::Result<()>,
205) -> std::io::Result<i32> {
206 if !writable && cfg!(windows) {
207 Ok(0i32)
211 } else {
212 let result = operation(file);
213 result.map(|_| 0i32)
214 }
215}
216
217impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
218trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
219 fn write_stat_buf(
220 &mut self,
221 metadata: FileMetadata,
222 buf_op: &OpTy<'tcx>,
223 ) -> InterpResult<'tcx, i32> {
224 let this = self.eval_context_mut();
225
226 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
227 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
228 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
229
230 let buf = this.deref_pointer(buf_op)?;
235
236 this.write_int_fields_named(
237 &[
238 ("st_dev", metadata.dev.unwrap_or(0).into()),
239 ("st_mode", metadata.mode.into()),
240 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
241 ("st_ino", metadata.ino.unwrap_or(0).into()),
242 ("st_uid", metadata.uid.unwrap_or(0).into()),
243 ("st_gid", metadata.gid.unwrap_or(0).into()),
244 ("st_rdev", 0),
245 ("st_atime", access_sec.into()),
246 ("st_atime_nsec", access_nsec.into()),
247 ("st_mtime", modified_sec.into()),
248 ("st_mtime_nsec", modified_nsec.into()),
249 ("st_ctime", 0),
250 ("st_ctime_nsec", 0),
251 ("st_size", metadata.size.into()),
252 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
253 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
254 ],
255 &buf,
256 )?;
257
258 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
259 this.write_int_fields_named(
260 &[
261 ("st_birthtime", created_sec.into()),
262 ("st_birthtime_nsec", created_nsec.into()),
263 ("st_flags", 0),
264 ("st_gen", 0),
265 ],
266 &buf,
267 )?;
268 }
269
270 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
271 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
272 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
274 }
275
276 interp_ok(0)
277 }
278
279 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
280 #[cfg(unix)]
281 use std::os::unix::fs::FileTypeExt;
282
283 let this = self.eval_context_ref();
284 match file_type {
285 Ok(file_type) => {
286 match () {
287 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
288 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
289 _ if file_type.is_symlink() =>
290 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
291 #[cfg(unix)]
293 _ if file_type.is_block_device() =>
294 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
295 #[cfg(unix)]
296 _ if file_type.is_char_device() =>
297 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
298 #[cfg(unix)]
299 _ if file_type.is_fifo() =>
300 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
301 #[cfg(unix)]
302 _ if file_type.is_socket() =>
303 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
304 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
306 }
307 }
308 Err(_) => {
309 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
311 }
312 }
313 }
314
315 fn dir_entry_fields(
316 &self,
317 entry: Either<fs::DirEntry, &'static str>,
318 ) -> InterpResult<'tcx, DirEntry> {
319 let this = self.eval_context_ref();
320 interp_ok(match entry {
321 Either::Left(dir_entry) => {
322 DirEntry {
323 name: dir_entry.file_name(),
324 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
325 #[cfg(unix)]
328 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
329 #[cfg(not(unix))]
330 ino: 0u64,
331 }
332 }
333 Either::Right(special) =>
334 DirEntry {
335 name: special.into(),
336 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
337 ino: 0,
338 },
339 })
340 }
341
342 #[cfg(unix)]
343 fn host_permissions_from_mode(&self, mode: u32) -> InterpResult<'tcx, fs::Permissions> {
344 use std::os::unix::fs::PermissionsExt;
345 interp_ok(fs::Permissions::from_mode(mode))
346 }
347
348 #[cfg(not(unix))]
349 fn host_permissions_from_mode(&self, _mode: u32) -> InterpResult<'tcx, fs::Permissions> {
350 throw_unsup_format!("setting file permissions is only supported on Unix hosts")
351 }
352}
353
354impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
355pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
356 fn open(
357 &mut self,
358 path_raw: &OpTy<'tcx>,
359 flag: &OpTy<'tcx>,
360 varargs: &[OpTy<'tcx>],
361 ) -> InterpResult<'tcx, Scalar> {
362 let this = self.eval_context_mut();
363
364 let path_raw = this.read_pointer(path_raw)?;
365 let flag = this.read_scalar(flag)?.to_i32()?;
366
367 let path = this.read_path_from_c_str(path_raw)?;
368 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
370 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
371 {
372 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
373 }
374
375 let mut flag = flag;
377
378 let mut options = OpenOptions::new();
379
380 let o_rdonly = this.eval_libc_i32("O_RDONLY");
381 let o_wronly = this.eval_libc_i32("O_WRONLY");
382 let o_rdwr = this.eval_libc_i32("O_RDWR");
383 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
387 throw_unsup_format!("access mode flags on this target are unsupported");
388 }
389 let mut writable = true;
390
391 let access_mode = flag & 0b11;
393 flag &= !access_mode;
394
395 if access_mode == o_rdonly {
396 writable = false;
397 options.read(true);
398 } else if access_mode == o_wronly {
399 options.write(true);
400 } else if access_mode == o_rdwr {
401 options.read(true).write(true);
402 } else {
403 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
404 }
405
406 let o_append = this.eval_libc_i32("O_APPEND");
407 if flag & o_append == o_append {
408 flag &= !o_append;
409 options.append(true);
410 }
411 let o_trunc = this.eval_libc_i32("O_TRUNC");
412 if flag & o_trunc == o_trunc {
413 flag &= !o_trunc;
414 options.truncate(true);
415 }
416 let o_creat = this.eval_libc_i32("O_CREAT");
417 if flag & o_creat == o_creat {
418 flag &= !o_creat;
419 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
423 let mode = this.read_scalar(mode)?.to_u32()?;
424
425 #[cfg(unix)]
426 {
427 use std::os::unix::fs::OpenOptionsExt;
429 options.mode(mode);
430 }
431 #[cfg(not(unix))]
432 {
433 if mode != 0o666 {
435 throw_unsup_format!(
436 "non-default mode 0o{:o} is not supported on non-Unix hosts",
437 mode
438 );
439 }
440 }
441
442 let o_excl = this.eval_libc_i32("O_EXCL");
443 if flag & o_excl == o_excl {
444 flag &= !o_excl;
445 options.create_new(true);
446 } else {
447 options.create(true);
448 }
449 }
450 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
451 if flag & o_cloexec == o_cloexec {
452 flag &= !o_cloexec;
453 }
456 if this.tcx.sess.target.os == Os::Linux {
457 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
458 if flag & o_tmpfile == o_tmpfile {
459 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
461 }
462 }
463
464 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
465 if flag & o_nofollow == o_nofollow {
466 flag &= !o_nofollow;
467 #[cfg(unix)]
468 {
469 use std::os::unix::fs::OpenOptionsExt;
470 options.custom_flags(libc::O_NOFOLLOW);
471 }
472 #[cfg(not(unix))]
476 {
477 if path.is_symlink() {
480 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
481 }
482 }
483 }
484
485 if flag != 0 {
487 throw_unsup_format!("unsupported flags {:#x}", flag);
488 }
489
490 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
492 this.reject_in_isolation("`open`", reject_with)?;
493 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
494 }
495
496 let fd = options
497 .open(path)
498 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
499
500 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
501 }
502
503 fn lseek(
504 &mut self,
505 fd_num: i32,
506 offset: i128,
507 whence: i32,
508 dest: &MPlaceTy<'tcx>,
509 ) -> InterpResult<'tcx> {
510 let this = self.eval_context_mut();
511
512 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
515 if offset < 0 {
516 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
518 } else {
519 SeekFrom::Start(u64::try_from(offset).unwrap())
520 }
521 } else if whence == this.eval_libc_i32("SEEK_CUR") {
522 SeekFrom::Current(i64::try_from(offset).unwrap())
523 } else if whence == this.eval_libc_i32("SEEK_END") {
524 SeekFrom::End(i64::try_from(offset).unwrap())
525 } else {
526 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
527 };
528
529 let communicate = this.machine.communicate();
530
531 let Some(fd) = this.machine.fds.get(fd_num) else {
532 return this.set_last_error_and_return(LibcError("EBADF"), dest);
533 };
534 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
535 drop(fd);
536
537 let result = this.try_unwrap_io_result(result)?;
538 this.write_int(result, dest)?;
539 interp_ok(())
540 }
541
542 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
543 let this = self.eval_context_mut();
544
545 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
546
547 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
549 this.reject_in_isolation("`unlink`", reject_with)?;
550 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
551 }
552
553 let result = fs::remove_file(path).map(|_| 0);
554 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
555 }
556
557 fn symlink(
558 &mut self,
559 target_op: &OpTy<'tcx>,
560 linkpath_op: &OpTy<'tcx>,
561 ) -> InterpResult<'tcx, Scalar> {
562 #[cfg(unix)]
563 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
564 std::os::unix::fs::symlink(src, dst)
565 }
566
567 #[cfg(windows)]
568 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
569 use std::os::windows::fs;
570 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
571 }
572
573 let this = self.eval_context_mut();
574 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
575 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
576
577 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
579 this.reject_in_isolation("`symlink`", reject_with)?;
580 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
581 }
582
583 let result = create_link(&target, &linkpath).map(|_| 0);
584 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
585 }
586
587 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
588 let this = self.eval_context_mut();
589
590 if !matches!(
591 &this.tcx.sess.target.os,
592 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
593 ) {
594 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
595 }
596
597 let path_scalar = this.read_pointer(path_op)?;
598 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
599
600 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
602 this.reject_in_isolation("`stat`", reject_with)?;
603 return this.set_last_error_and_return_i32(LibcError("EACCES"));
604 }
605
606 let metadata = match FileMetadata::from_path(this, &path, true)? {
608 Ok(metadata) => metadata,
609 Err(err) => return this.set_last_error_and_return_i32(err),
610 };
611
612 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
613 }
614
615 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
617 let this = self.eval_context_mut();
618
619 if !matches!(
620 &this.tcx.sess.target.os,
621 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
622 ) {
623 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
624 }
625
626 let path_scalar = this.read_pointer(path_op)?;
627 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
628
629 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
631 this.reject_in_isolation("`lstat`", reject_with)?;
632 return this.set_last_error_and_return_i32(LibcError("EACCES"));
633 }
634
635 let metadata = match FileMetadata::from_path(this, &path, false)? {
636 Ok(metadata) => metadata,
637 Err(err) => return this.set_last_error_and_return_i32(err),
638 };
639
640 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
641 }
642
643 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
644 let this = self.eval_context_mut();
645
646 if !matches!(
647 &this.tcx.sess.target.os,
648 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
649 ) {
650 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
651 }
652
653 let fd = this.read_scalar(fd_op)?.to_i32()?;
654
655 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
657 this.reject_in_isolation("`fstat`", reject_with)?;
658 return this.set_last_error_and_return_i32(LibcError("EBADF"));
660 }
661
662 let metadata = match FileMetadata::from_fd_num(this, fd)? {
663 Ok(metadata) => metadata,
664 Err(err) => return this.set_last_error_and_return_i32(err),
665 };
666 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
667 }
668
669 fn linux_statx(
670 &mut self,
671 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
677 let this = self.eval_context_mut();
678
679 this.assert_target_os(Os::Linux, "statx");
680
681 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
682 let pathname_ptr = this.read_pointer(pathname_op)?;
683 let flags = this.read_scalar(flags_op)?.to_i32()?;
684 let _mask = this.read_scalar(mask_op)?.to_u32()?;
685 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
686
687 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
689 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
690 }
691
692 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
693
694 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
695 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
697 let empty_path_flag = flags & at_empty_path == at_empty_path;
698 if !(path.is_absolute()
706 || dirfd == this.eval_libc_i32("AT_FDCWD")
707 || (path.as_os_str().is_empty() && empty_path_flag))
708 {
709 throw_unsup_format!(
710 "using statx is only supported with absolute paths, relative paths with the file \
711 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
712 file descriptor"
713 )
714 }
715
716 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
718 this.reject_in_isolation("`statx`", reject_with)?;
719 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
720 LibcError("EACCES")
723 } else {
724 assert!(empty_path_flag);
728 LibcError("EBADF")
729 };
730 return this.set_last_error_and_return_i32(ecode);
731 }
732
733 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
736
737 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
740 FileMetadata::from_fd_num(this, dirfd)?
741 } else {
742 FileMetadata::from_path(this, &path, follow_symlink)?
743 };
744 let metadata = match metadata {
745 Ok(metadata) => metadata,
746 Err(err) => return this.set_last_error_and_return_i32(err),
747 };
748
749 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
754
755 if metadata.ino.is_some() {
757 mask |= this.eval_libc_u32("STATX_INO");
758 }
759 if metadata.nlink.is_some() {
760 mask |= this.eval_libc_u32("STATX_NLINK");
761 }
762 if metadata.uid.is_some() {
763 mask |= this.eval_libc_u32("STATX_UID");
764 }
765 if metadata.gid.is_some() {
766 mask |= this.eval_libc_u32("STATX_GID");
767 }
768 if metadata.blocks.is_some() {
769 mask |= this.eval_libc_u32("STATX_BLOCKS");
770 }
771
772 let (access_sec, access_nsec) = metadata
775 .accessed
776 .map(|tup| {
777 mask |= this.eval_libc_u32("STATX_ATIME");
778 interp_ok(tup)
779 })
780 .unwrap_or_else(|| interp_ok((0, 0)))?;
781
782 let (created_sec, created_nsec) = metadata
783 .created
784 .map(|tup| {
785 mask |= this.eval_libc_u32("STATX_BTIME");
786 interp_ok(tup)
787 })
788 .unwrap_or_else(|| interp_ok((0, 0)))?;
789
790 let (modified_sec, modified_nsec) = metadata
791 .modified
792 .map(|tup| {
793 mask |= this.eval_libc_u32("STATX_MTIME");
794 interp_ok(tup)
795 })
796 .unwrap_or_else(|| interp_ok((0, 0)))?;
797
798 this.write_int_fields_named(
800 &[
801 ("stx_mask", mask.into()),
802 ("stx_mode", metadata.mode.into()),
803 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
804 ("stx_attributes", 0),
805 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
806 ("stx_uid", metadata.uid.unwrap_or(0).into()),
807 ("stx_gid", metadata.gid.unwrap_or(0).into()),
808 ("stx_ino", metadata.ino.unwrap_or(0).into()),
809 ("stx_size", metadata.size.into()),
810 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
811 ("stx_attributes_mask", 0),
812 ("stx_rdev_major", 0),
813 ("stx_rdev_minor", 0),
814 ("stx_dev_major", 0),
815 ("stx_dev_minor", 0),
816 ],
817 &statxbuf,
818 )?;
819 #[rustfmt::skip]
820 this.write_int_fields_named(
821 &[
822 ("tv_sec", access_sec.into()),
823 ("tv_nsec", access_nsec.into()),
824 ],
825 &this.project_field_named(&statxbuf, "stx_atime")?,
826 )?;
827 #[rustfmt::skip]
828 this.write_int_fields_named(
829 &[
830 ("tv_sec", created_sec.into()),
831 ("tv_nsec", created_nsec.into()),
832 ],
833 &this.project_field_named(&statxbuf, "stx_btime")?,
834 )?;
835 #[rustfmt::skip]
836 this.write_int_fields_named(
837 &[
838 ("tv_sec", 0.into()),
839 ("tv_nsec", 0.into()),
840 ],
841 &this.project_field_named(&statxbuf, "stx_ctime")?,
842 )?;
843 #[rustfmt::skip]
844 this.write_int_fields_named(
845 &[
846 ("tv_sec", modified_sec.into()),
847 ("tv_nsec", modified_nsec.into()),
848 ],
849 &this.project_field_named(&statxbuf, "stx_mtime")?,
850 )?;
851
852 interp_ok(Scalar::from_i32(0))
853 }
854
855 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
856 let this = self.eval_context_mut();
857
858 let path_ptr = this.read_pointer(path_op)?;
859 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
860
861 if this.ptr_is_null(path_ptr)? {
862 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
863 }
864 let path = this.read_path_from_c_str(path_ptr)?;
865
866 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
868 this.reject_in_isolation("`chmod`", reject_with)?;
869 return this.set_last_error_and_return_i32(LibcError("EACCES"));
870 }
871
872 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
873 if let Err(err) = fs::set_permissions(path, permissions) {
874 return this.set_last_error_and_return_i32(err);
875 }
876
877 interp_ok(Scalar::from_i32(0))
878 }
879
880 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
881 let this = self.eval_context_mut();
882
883 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
884 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
885
886 let Some(fd) = this.machine.fds.get(fd_num) else {
887 return this.set_last_error_and_return_i32(LibcError("EBADF"));
888 };
889 let Some(file) = fd.downcast::<FileHandle>() else {
890 throw_unsup_format!("`fchmod` is only supported on regular files")
892 };
893
894 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
896 this.reject_in_isolation("`fchmod`", reject_with)?;
897 return this.set_last_error_and_return_i32(LibcError("EACCES"));
898 }
899
900 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
901 if let Err(err) = file.file.set_permissions(permissions) {
902 return this.set_last_error_and_return_i32(err);
903 }
904
905 interp_ok(Scalar::from_i32(0))
906 }
907
908 fn rename(
909 &mut self,
910 oldpath_op: &OpTy<'tcx>,
911 newpath_op: &OpTy<'tcx>,
912 ) -> InterpResult<'tcx, Scalar> {
913 let this = self.eval_context_mut();
914
915 let oldpath_ptr = this.read_pointer(oldpath_op)?;
916 let newpath_ptr = this.read_pointer(newpath_op)?;
917
918 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
919 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
920 }
921
922 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
923 let newpath = this.read_path_from_c_str(newpath_ptr)?;
924
925 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
927 this.reject_in_isolation("`rename`", reject_with)?;
928 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
929 }
930
931 let result = fs::rename(oldpath, newpath).map(|_| 0);
932
933 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
934 }
935
936 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
937 let this = self.eval_context_mut();
938
939 #[cfg_attr(not(unix), allow(unused_variables))]
940 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
941 u32::from(this.read_scalar(mode_op)?.to_u16()?)
942 } else {
943 this.read_scalar(mode_op)?.to_u32()?
944 };
945
946 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
947
948 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
950 this.reject_in_isolation("`mkdir`", reject_with)?;
951 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
952 }
953
954 #[cfg_attr(not(unix), allow(unused_mut))]
955 let mut builder = DirBuilder::new();
956
957 #[cfg(unix)]
960 {
961 use std::os::unix::fs::DirBuilderExt;
962 builder.mode(mode);
963 }
964
965 let result = builder.create(path).map(|_| 0i32);
966
967 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
968 }
969
970 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
971 let this = self.eval_context_mut();
972
973 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
974
975 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
977 this.reject_in_isolation("`rmdir`", reject_with)?;
978 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
979 }
980
981 let result = fs::remove_dir(path).map(|_| 0i32);
982
983 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
984 }
985
986 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
987 let this = self.eval_context_mut();
988
989 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
990
991 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
993 this.reject_in_isolation("`opendir`", reject_with)?;
994 this.set_last_error(LibcError("EACCES"))?;
995 return interp_ok(Scalar::null_ptr(this));
996 }
997
998 let result = fs::read_dir(name);
999
1000 match result {
1001 Ok(dir_iter) => {
1002 let id = this.machine.dirs.insert_new(dir_iter);
1003
1004 interp_ok(Scalar::from_target_usize(id, this))
1008 }
1009 Err(e) => {
1010 this.set_last_error(e)?;
1011 interp_ok(Scalar::null_ptr(this))
1012 }
1013 }
1014 }
1015
1016 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1017 let this = self.eval_context_mut();
1018
1019 if !matches!(
1020 &this.tcx.sess.target.os,
1021 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
1022 ) {
1023 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
1024 }
1025
1026 let dirp = this.read_target_usize(dirp_op)?;
1027
1028 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1030 this.reject_in_isolation("`readdir`", reject_with)?;
1031 this.set_last_error(LibcError("EBADF"))?;
1032 this.write_null(dest)?;
1033 return interp_ok(());
1034 }
1035
1036 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1037 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1038 })?;
1039
1040 let entry = match open_dir.next_host_entry() {
1041 Some(Ok(dir_entry)) => {
1042 let dir_entry = this.dir_entry_fields(dir_entry)?;
1043
1044 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1081 let dirent_layout = this.layout_of(dirent_ty)?;
1082 let fields = &dirent_layout.fields;
1083 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1084
1085 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1089 let name_len = u64::try_from(name_bytes.len()).unwrap();
1090 let size = d_name_offset.strict_add(name_len);
1091
1092 let entry = this.allocate_ptr(
1093 Size::from_bytes(size),
1094 dirent_layout.align.abi,
1095 MiriMemoryKind::Runtime.into(),
1096 AllocInit::Uninit,
1097 )?;
1098 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1099
1100 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1103 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1104
1105 let ino_name =
1107 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1108 this.write_int_fields_named(
1109 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1110 &entry,
1111 )?;
1112
1113 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1115 this.write_null(&d_off)?;
1116 }
1117 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1118 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1119 }
1120 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1121 this.write_int(dir_entry.d_type, &d_type)?;
1122 }
1123
1124 Some(entry.ptr())
1125 }
1126 None => {
1127 None
1129 }
1130 Some(Err(e)) => {
1131 this.set_last_error(e)?;
1132 None
1133 }
1134 };
1135
1136 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1137 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1138 if let Some(old_entry) = old_entry {
1139 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1140 }
1141
1142 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1143 interp_ok(())
1144 }
1145
1146 fn macos_readdir_r(
1147 &mut self,
1148 dirp_op: &OpTy<'tcx>,
1149 entry_op: &OpTy<'tcx>,
1150 result_op: &OpTy<'tcx>,
1151 ) -> InterpResult<'tcx, Scalar> {
1152 let this = self.eval_context_mut();
1153
1154 this.assert_target_os(Os::MacOs, "readdir_r");
1155
1156 let dirp = this.read_target_usize(dirp_op)?;
1157 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1158
1159 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1161 this.reject_in_isolation("`readdir_r`", reject_with)?;
1162 return interp_ok(this.eval_libc("EBADF"));
1164 }
1165
1166 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1167 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1168 })?;
1169 interp_ok(match open_dir.next_host_entry() {
1170 Some(Ok(dir_entry)) => {
1171 let dir_entry = this.dir_entry_fields(dir_entry)?;
1172 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1187
1188 let name_place = this.project_field_named(&entry_place, "d_name")?;
1190 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1191 &dir_entry.name,
1192 name_place.ptr(),
1193 name_place.layout.size.bytes(),
1194 )?;
1195 if !name_fits {
1196 throw_unsup_format!(
1197 "a directory entry had a name too large to fit in libc::dirent"
1198 );
1199 }
1200
1201 this.write_int_fields_named(
1203 &[
1204 ("d_reclen", entry_place.layout.size.bytes().into()),
1205 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1206 ("d_type", dir_entry.d_type.into()),
1207 ("d_ino", dir_entry.ino.into()),
1208 ("d_seekoff", 0),
1209 ],
1210 &entry_place,
1211 )?;
1212 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1213
1214 Scalar::from_i32(0)
1215 }
1216 None => {
1217 this.write_null(&result_place)?;
1219 Scalar::from_i32(0)
1220 }
1221 Some(Err(e)) => {
1222 this.io_error_to_errnum(e)?
1224 }
1225 })
1226 }
1227
1228 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1229 let this = self.eval_context_mut();
1230
1231 let dirp = this.read_target_usize(dirp_op)?;
1232
1233 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1235 this.reject_in_isolation("`closedir`", reject_with)?;
1236 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1237 }
1238
1239 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1240 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1241 };
1242 if let Some(entry) = open_dir.entry.take() {
1243 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1244 }
1245 drop(open_dir);
1247
1248 interp_ok(Scalar::from_i32(0))
1249 }
1250
1251 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1252 let this = self.eval_context_mut();
1253
1254 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1256 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1257 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1259 }
1260
1261 let Some(fd) = this.machine.fds.get(fd_num) else {
1262 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1263 };
1264
1265 let Some(file) = fd.downcast::<FileHandle>() else {
1266 return interp_ok(this.eval_libc("EINVAL"));
1269 };
1270
1271 if file.writable {
1272 if let Ok(length) = length.try_into() {
1273 let result = file.file.set_len(length);
1274 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1275 interp_ok(Scalar::from_i32(result))
1276 } else {
1277 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1278 }
1279 } else {
1280 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1282 }
1283 }
1284
1285 fn posix_fallocate(
1288 &mut self,
1289 fd_num: i32,
1290 offset: i64,
1291 len: i64,
1292 ) -> InterpResult<'tcx, Scalar> {
1293 let this = self.eval_context_mut();
1294
1295 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1297 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1298 return interp_ok(this.eval_libc("EBADF"));
1300 }
1301
1302 if offset < 0 || len <= 0 {
1304 return interp_ok(this.eval_libc("EINVAL"));
1305 }
1306
1307 let Some(fd) = this.machine.fds.get(fd_num) else {
1309 return interp_ok(this.eval_libc("EBADF"));
1310 };
1311 let Some(file) = fd.downcast::<FileHandle>() else {
1312 return interp_ok(this.eval_libc("ENODEV"));
1314 };
1315
1316 if !file.writable {
1317 return interp_ok(this.eval_libc("EBADF"));
1319 }
1320
1321 let current_size = match file.file.metadata() {
1322 Ok(metadata) => metadata.len(),
1323 Err(err) => return this.io_error_to_errnum(err),
1324 };
1325 let new_size = match offset.checked_add(len) {
1327 Some(new_size) => u64::try_from(new_size).unwrap(),
1329 None => return interp_ok(this.eval_libc("EFBIG")), };
1331 if current_size < new_size {
1334 interp_ok(match file.file.set_len(new_size) {
1335 Ok(()) => Scalar::from_i32(0),
1336 Err(e) => this.io_error_to_errnum(e)?,
1337 })
1338 } else {
1339 interp_ok(Scalar::from_i32(0))
1340 }
1341 }
1342
1343 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1344 let this = self.eval_context_mut();
1350
1351 let fd = this.read_scalar(fd_op)?.to_i32()?;
1352
1353 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1355 this.reject_in_isolation("`fsync`", reject_with)?;
1356 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1358 }
1359
1360 self.ffullsync_fd(fd)
1361 }
1362
1363 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1364 let this = self.eval_context_mut();
1365 let Some(fd) = this.machine.fds.get(fd_num) else {
1366 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1367 };
1368 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1370 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1371 })?;
1372 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1373 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1374 }
1375
1376 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1377 let this = self.eval_context_mut();
1378
1379 let fd = this.read_scalar(fd_op)?.to_i32()?;
1380
1381 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1383 this.reject_in_isolation("`fdatasync`", reject_with)?;
1384 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1386 }
1387
1388 let Some(fd) = this.machine.fds.get(fd) else {
1389 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1390 };
1391 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1393 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1394 })?;
1395 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1396 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1397 }
1398
1399 fn sync_file_range(
1400 &mut self,
1401 fd_op: &OpTy<'tcx>,
1402 offset_op: &OpTy<'tcx>,
1403 nbytes_op: &OpTy<'tcx>,
1404 flags_op: &OpTy<'tcx>,
1405 ) -> InterpResult<'tcx, Scalar> {
1406 let this = self.eval_context_mut();
1407
1408 let fd = this.read_scalar(fd_op)?.to_i32()?;
1409 let offset = this.read_scalar(offset_op)?.to_i64()?;
1410 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1411 let flags = this.read_scalar(flags_op)?.to_i32()?;
1412
1413 if offset < 0 || nbytes < 0 {
1414 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1415 }
1416 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1417 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1418 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1419 if flags & allowed_flags != flags {
1420 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1421 }
1422
1423 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1425 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1426 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1428 }
1429
1430 let Some(fd) = this.machine.fds.get(fd) else {
1431 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1432 };
1433 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1435 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1436 })?;
1437 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1438 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1439 }
1440
1441 fn readlink(
1442 &mut self,
1443 pathname_op: &OpTy<'tcx>,
1444 buf_op: &OpTy<'tcx>,
1445 bufsize_op: &OpTy<'tcx>,
1446 ) -> InterpResult<'tcx, i64> {
1447 let this = self.eval_context_mut();
1448
1449 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1450 let buf = this.read_pointer(buf_op)?;
1451 let bufsize = this.read_target_usize(bufsize_op)?;
1452
1453 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1455 this.reject_in_isolation("`readlink`", reject_with)?;
1456 this.set_last_error(LibcError("EACCES"))?;
1457 return interp_ok(-1);
1458 }
1459
1460 let result = std::fs::read_link(pathname);
1461 match result {
1462 Ok(resolved) => {
1463 let resolved = this.convert_path(
1467 Cow::Borrowed(resolved.as_ref()),
1468 crate::shims::os_str::PathConversion::HostToTarget,
1469 );
1470 let mut path_bytes = resolved.as_encoded_bytes();
1471 let bufsize: usize = bufsize.try_into().unwrap();
1472 if path_bytes.len() > bufsize {
1473 path_bytes = &path_bytes[..bufsize]
1474 }
1475 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1476 interp_ok(path_bytes.len().try_into().unwrap())
1477 }
1478 Err(e) => {
1479 this.set_last_error(e)?;
1480 interp_ok(-1)
1481 }
1482 }
1483 }
1484
1485 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1486 let this = self.eval_context_mut();
1487 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1490 let error = if let Some(fd) = this.machine.fds.get(fd) {
1491 if fd.is_tty(this.machine.communicate()) {
1492 return interp_ok(Scalar::from_i32(1));
1493 } else {
1494 LibcError("ENOTTY")
1495 }
1496 } else {
1497 LibcError("EBADF")
1499 };
1500 this.set_last_error(error)?;
1501 interp_ok(Scalar::from_i32(0))
1502 }
1503
1504 fn realpath(
1505 &mut self,
1506 path_op: &OpTy<'tcx>,
1507 processed_path_op: &OpTy<'tcx>,
1508 ) -> InterpResult<'tcx, Scalar> {
1509 let this = self.eval_context_mut();
1510 this.assert_target_os_is_unix("realpath");
1511
1512 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1513 let processed_ptr = this.read_pointer(processed_path_op)?;
1514
1515 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1517 this.reject_in_isolation("`realpath`", reject_with)?;
1518 this.set_last_error(LibcError("EACCES"))?;
1519 return interp_ok(Scalar::from_target_usize(0, this));
1520 }
1521
1522 let result = std::fs::canonicalize(pathname);
1523 match result {
1524 Ok(resolved) => {
1525 let path_max = this
1526 .eval_libc_i32("PATH_MAX")
1527 .try_into()
1528 .expect("PATH_MAX does not fit in u64");
1529 let dest = if this.ptr_is_null(processed_ptr)? {
1530 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1540 } else {
1541 let (wrote_path, _) =
1542 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1543
1544 if !wrote_path {
1545 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1549 return interp_ok(Scalar::from_target_usize(0, this));
1550 }
1551 processed_ptr
1552 };
1553
1554 interp_ok(Scalar::from_maybe_pointer(dest, this))
1555 }
1556 Err(e) => {
1557 this.set_last_error(e)?;
1558 interp_ok(Scalar::from_target_usize(0, this))
1559 }
1560 }
1561 }
1562 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1563 use rand::seq::IndexedRandom;
1564
1565 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1567
1568 let this = self.eval_context_mut();
1569 this.assert_target_os_is_unix("mkstemp");
1570
1571 let max_attempts = this.eval_libc_u32("TMP_MAX");
1581
1582 let template_ptr = this.read_pointer(template_op)?;
1585 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1586 let template_bytes = template.as_mut_slice();
1587
1588 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1590 this.reject_in_isolation("`mkstemp`", reject_with)?;
1591 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1592 }
1593
1594 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1596
1597 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1602 let end_pos = template_bytes.len();
1603 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1604
1605 if last_six_char_bytes != suffix_bytes {
1607 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1608 }
1609
1610 const SUBSTITUTIONS: &[char; 62] = &[
1614 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1615 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1616 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1617 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1618 ];
1619
1620 let mut fopts = OpenOptions::new();
1623 fopts.read(true).write(true).create_new(true);
1624
1625 #[cfg(unix)]
1626 {
1627 use std::os::unix::fs::OpenOptionsExt;
1628 fopts.mode(0o600);
1630 fopts.custom_flags(libc::O_EXCL);
1631 }
1632 #[cfg(windows)]
1633 {
1634 use std::os::windows::fs::OpenOptionsExt;
1635 fopts.share_mode(0);
1637 }
1638
1639 for _ in 0..max_attempts {
1641 let rng = this.machine.rng.get_mut();
1642
1643 let unique_suffix =
1645 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1646
1647 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1649
1650 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1652
1653 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1655
1656 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1657
1658 let file = fopts.open(possibly_unique);
1659
1660 match file {
1661 Ok(f) => {
1662 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1663 return interp_ok(Scalar::from_i32(fd));
1664 }
1665 Err(e) =>
1666 match e.kind() {
1667 ErrorKind::AlreadyExists => continue,
1669 _ => {
1671 return this.set_last_error_and_return_i32(e);
1674 }
1675 },
1676 }
1677 }
1678
1679 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1681 }
1682}
1683
1684fn extract_sec_and_nsec<'tcx>(
1688 time: std::io::Result<SystemTime>,
1689) -> InterpResult<'tcx, Option<(u64, u32)>> {
1690 match time.ok() {
1691 Some(time) => {
1692 let duration = system_time_to_duration(&time)?;
1693 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1694 }
1695 None => interp_ok(None),
1696 }
1697}
1698
1699fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1700 #[cfg(unix)]
1701 use std::os::unix::fs::FileTypeExt;
1702
1703 if file_type.is_file() {
1704 "S_IFREG"
1705 } else if file_type.is_dir() {
1706 "S_IFDIR"
1707 } else if file_type.is_symlink() {
1708 "S_IFLNK"
1709 } else {
1710 #[cfg(unix)]
1712 {
1713 if file_type.is_socket() {
1714 return "S_IFSOCK";
1715 } else if file_type.is_fifo() {
1716 return "S_IFIFO";
1717 } else if file_type.is_char_device() {
1718 return "S_IFCHR";
1719 } else if file_type.is_block_device() {
1720 return "S_IFBLK";
1721 }
1722 }
1723 "S_IFREG"
1724 }
1725}
1726
1727struct FileMetadata {
1735 mode: u32,
1737 size: u64,
1738 created: Option<(u64, u32)>,
1739 accessed: Option<(u64, u32)>,
1740 modified: Option<(u64, u32)>,
1741 dev: Option<u64>,
1742 ino: Option<u64>,
1743 nlink: Option<u64>,
1744 uid: Option<u32>,
1745 gid: Option<u32>,
1746 blksize: Option<u64>,
1747 blocks: Option<u64>,
1748}
1749
1750impl FileMetadata {
1751 fn from_path<'tcx>(
1752 ecx: &mut MiriInterpCx<'tcx>,
1753 path: &Path,
1754 follow_symlink: bool,
1755 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1756 let metadata =
1757 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1758
1759 FileMetadata::from_meta(ecx, metadata)
1760 }
1761
1762 fn from_fd_num<'tcx>(
1763 ecx: &mut MiriInterpCx<'tcx>,
1764 fd_num: i32,
1765 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1766 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1767 return interp_ok(Err(LibcError("EBADF")));
1768 };
1769 match fd.metadata()? {
1770 Either::Left(host) => Self::from_meta(ecx, host),
1771 Either::Right(name) => Self::synthetic(ecx, name),
1772 }
1773 }
1774
1775 fn synthetic<'tcx>(
1776 ecx: &mut MiriInterpCx<'tcx>,
1777 mode_name: &str,
1778 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1779 let mode = ecx.eval_libc(mode_name);
1780 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1781 let mode = mode | 0o666;
1783 interp_ok(Ok(FileMetadata {
1784 mode,
1785 size: 0,
1786 created: None,
1787 accessed: None,
1788 modified: None,
1789 dev: None,
1790 uid: None,
1791 gid: None,
1792 blksize: None,
1793 blocks: None,
1794 ino: None,
1795 nlink: None,
1796 }))
1797 }
1798
1799 fn from_meta<'tcx>(
1800 ecx: &mut MiriInterpCx<'tcx>,
1801 metadata: Result<std::fs::Metadata, std::io::Error>,
1802 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1803 let metadata = match metadata {
1804 Ok(metadata) => metadata,
1805 Err(e) => {
1806 return interp_ok(Err(e.into()));
1807 }
1808 };
1809
1810 let file_type = metadata.file_type();
1811 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1812 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1813
1814 let size = metadata.len();
1815
1816 let created = extract_sec_and_nsec(metadata.created())?;
1817 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1818 let modified = extract_sec_and_nsec(metadata.modified())?;
1819
1820 cfg_select! {
1823 unix => {
1824 use std::os::unix::fs::MetadataExt;
1825 use std::os::unix::fs::PermissionsExt;
1826
1827 let dev = metadata.dev();
1828 let ino = metadata.ino();
1829 let nlink = metadata.nlink();
1830 let uid = metadata.uid();
1831 let gid = metadata.gid();
1832 let blksize = metadata.blksize();
1833 let blocks = metadata.blocks();
1834
1835 mode |= metadata.permissions().mode();
1836
1837 interp_ok(Ok(FileMetadata {
1838 mode,
1839 size,
1840 created,
1841 accessed,
1842 modified,
1843 dev: Some(dev),
1844 ino: Some(ino),
1845 nlink: Some(nlink),
1846 uid: Some(uid),
1847 gid: Some(gid),
1848 blksize: Some(blksize),
1849 blocks: Some(blocks),
1850 }))
1851 }
1852 _ => {
1853 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1855
1856 interp_ok(Ok(FileMetadata {
1857 mode,
1858 size,
1859 created,
1860 accessed,
1861 modified,
1862 dev: None,
1863 ino: None,
1864 nlink: None,
1865 uid: None,
1866 gid: None,
1867 blksize: None,
1868 blocks: None,
1869 }))
1870 },
1871 }
1872 }
1873}