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};
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")
754 | this.eval_libc_u32("STATX_MODE")
755 | this.eval_libc_u32("STATX_SIZE");
756
757 if metadata.ino.is_some() {
759 mask |= this.eval_libc_u32("STATX_INO");
760 }
761 if metadata.nlink.is_some() {
762 mask |= this.eval_libc_u32("STATX_NLINK");
763 }
764 if metadata.uid.is_some() {
765 mask |= this.eval_libc_u32("STATX_UID");
766 }
767 if metadata.gid.is_some() {
768 mask |= this.eval_libc_u32("STATX_GID");
769 }
770 if metadata.blocks.is_some() {
771 mask |= this.eval_libc_u32("STATX_BLOCKS");
772 }
773
774 let (access_sec, access_nsec) = metadata
777 .accessed
778 .map(|tup| {
779 mask |= this.eval_libc_u32("STATX_ATIME");
780 interp_ok(tup)
781 })
782 .unwrap_or_else(|| interp_ok((0, 0)))?;
783
784 let (created_sec, created_nsec) = metadata
785 .created
786 .map(|tup| {
787 mask |= this.eval_libc_u32("STATX_BTIME");
788 interp_ok(tup)
789 })
790 .unwrap_or_else(|| interp_ok((0, 0)))?;
791
792 let (modified_sec, modified_nsec) = metadata
793 .modified
794 .map(|tup| {
795 mask |= this.eval_libc_u32("STATX_MTIME");
796 interp_ok(tup)
797 })
798 .unwrap_or_else(|| interp_ok((0, 0)))?;
799
800 this.write_int_fields_named(
802 &[
803 ("stx_mask", mask.into()),
804 ("stx_mode", metadata.mode.into()),
805 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
806 ("stx_attributes", 0),
807 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
808 ("stx_uid", metadata.uid.unwrap_or(0).into()),
809 ("stx_gid", metadata.gid.unwrap_or(0).into()),
810 ("stx_ino", metadata.ino.unwrap_or(0).into()),
811 ("stx_size", metadata.size.into()),
812 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
813 ("stx_attributes_mask", 0),
814 ("stx_rdev_major", 0),
815 ("stx_rdev_minor", 0),
816 ("stx_dev_major", 0),
817 ("stx_dev_minor", 0),
818 ],
819 &statxbuf,
820 )?;
821 #[rustfmt::skip]
822 this.write_int_fields_named(
823 &[
824 ("tv_sec", access_sec.into()),
825 ("tv_nsec", access_nsec.into()),
826 ],
827 &this.project_field_named(&statxbuf, "stx_atime")?,
828 )?;
829 #[rustfmt::skip]
830 this.write_int_fields_named(
831 &[
832 ("tv_sec", created_sec.into()),
833 ("tv_nsec", created_nsec.into()),
834 ],
835 &this.project_field_named(&statxbuf, "stx_btime")?,
836 )?;
837 #[rustfmt::skip]
838 this.write_int_fields_named(
839 &[
840 ("tv_sec", 0.into()),
841 ("tv_nsec", 0.into()),
842 ],
843 &this.project_field_named(&statxbuf, "stx_ctime")?,
844 )?;
845 #[rustfmt::skip]
846 this.write_int_fields_named(
847 &[
848 ("tv_sec", modified_sec.into()),
849 ("tv_nsec", modified_nsec.into()),
850 ],
851 &this.project_field_named(&statxbuf, "stx_mtime")?,
852 )?;
853
854 interp_ok(Scalar::from_i32(0))
855 }
856
857 fn chmod(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
858 let this = self.eval_context_mut();
859
860 let path_ptr = this.read_pointer(path_op)?;
861 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
862
863 if this.ptr_is_null(path_ptr)? {
864 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
865 }
866 let path = this.read_path_from_c_str(path_ptr)?;
867
868 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
870 this.reject_in_isolation("`chmod`", reject_with)?;
871 return this.set_last_error_and_return_i32(LibcError("EACCES"));
872 }
873
874 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
875 if let Err(err) = fs::set_permissions(path, permissions) {
876 return this.set_last_error_and_return_i32(err);
877 }
878
879 interp_ok(Scalar::from_i32(0))
880 }
881
882 fn fchmod(&mut self, fd_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
883 let this = self.eval_context_mut();
884
885 let fd_num = this.read_scalar(fd_op)?.to_i32()?;
886 let mode = this.read_scalar(mode_op)?.to_uint(this.libc_ty_layout("mode_t").size)?;
887
888 let Some(fd) = this.machine.fds.get(fd_num) else {
889 return this.set_last_error_and_return_i32(LibcError("EBADF"));
890 };
891 let Some(file) = fd.downcast::<FileHandle>() else {
892 throw_unsup_format!("`fchmod` is only supported on regular files")
894 };
895
896 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
898 this.reject_in_isolation("`fchmod`", reject_with)?;
899 return this.set_last_error_and_return_i32(LibcError("EACCES"));
900 }
901
902 let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?;
903 if let Err(err) = file.file.set_permissions(permissions) {
904 return this.set_last_error_and_return_i32(err);
905 }
906
907 interp_ok(Scalar::from_i32(0))
908 }
909
910 fn rename(
911 &mut self,
912 oldpath_op: &OpTy<'tcx>,
913 newpath_op: &OpTy<'tcx>,
914 ) -> InterpResult<'tcx, Scalar> {
915 let this = self.eval_context_mut();
916
917 let oldpath_ptr = this.read_pointer(oldpath_op)?;
918 let newpath_ptr = this.read_pointer(newpath_op)?;
919
920 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
921 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
922 }
923
924 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
925 let newpath = this.read_path_from_c_str(newpath_ptr)?;
926
927 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
929 this.reject_in_isolation("`rename`", reject_with)?;
930 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
931 }
932
933 let result = fs::rename(oldpath, newpath).map(|_| 0);
934
935 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
936 }
937
938 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
939 let this = self.eval_context_mut();
940
941 #[cfg_attr(not(unix), allow(unused_variables))]
942 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
943 u32::from(this.read_scalar(mode_op)?.to_u16()?)
944 } else {
945 this.read_scalar(mode_op)?.to_u32()?
946 };
947
948 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
949
950 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
952 this.reject_in_isolation("`mkdir`", reject_with)?;
953 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
954 }
955
956 #[cfg_attr(not(unix), allow(unused_mut))]
957 let mut builder = DirBuilder::new();
958
959 #[cfg(unix)]
962 {
963 use std::os::unix::fs::DirBuilderExt;
964 builder.mode(mode);
965 }
966
967 let result = builder.create(path).map(|_| 0i32);
968
969 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
970 }
971
972 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
973 let this = self.eval_context_mut();
974
975 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
976
977 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
979 this.reject_in_isolation("`rmdir`", reject_with)?;
980 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
981 }
982
983 let result = fs::remove_dir(path).map(|_| 0i32);
984
985 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
986 }
987
988 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
989 let this = self.eval_context_mut();
990
991 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
992
993 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
995 this.reject_in_isolation("`opendir`", reject_with)?;
996 this.set_last_error(LibcError("EACCES"))?;
997 return interp_ok(Scalar::null_ptr(this));
998 }
999
1000 let result = fs::read_dir(name);
1001
1002 match result {
1003 Ok(dir_iter) => {
1004 let id = this.machine.dirs.insert_new(dir_iter);
1005
1006 interp_ok(Scalar::from_target_usize(id, this))
1010 }
1011 Err(e) => {
1012 this.set_last_error(e)?;
1013 interp_ok(Scalar::null_ptr(this))
1014 }
1015 }
1016 }
1017
1018 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
1019 let this = self.eval_context_mut();
1020
1021 if !matches!(
1022 &this.tcx.sess.target.os,
1023 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
1024 ) {
1025 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
1026 }
1027
1028 let dirp = this.read_target_usize(dirp_op)?;
1029
1030 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1032 this.reject_in_isolation("`readdir`", reject_with)?;
1033 this.set_last_error(LibcError("EBADF"))?;
1034 this.write_null(dest)?;
1035 return interp_ok(());
1036 }
1037
1038 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1039 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
1040 })?;
1041
1042 let entry = match open_dir.next_host_entry() {
1043 Some(Ok(dir_entry)) => {
1044 let dir_entry = this.dir_entry_fields(dir_entry)?;
1045
1046 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1083 let dirent_layout = this.layout_of(dirent_ty)?;
1084 let fields = &dirent_layout.fields;
1085 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1086
1087 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1091 let name_len = u64::try_from(name_bytes.len()).unwrap();
1092 let size = d_name_offset.strict_add(name_len);
1093
1094 let entry = this.allocate_ptr(
1095 Size::from_bytes(size),
1096 dirent_layout.align.abi,
1097 MiriMemoryKind::Runtime.into(),
1098 AllocInit::Uninit,
1099 )?;
1100 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1101
1102 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1105 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1106
1107 let ino_name =
1109 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1110 this.write_int_fields_named(
1111 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1112 &entry,
1113 )?;
1114
1115 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1117 this.write_null(&d_off)?;
1118 }
1119 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1120 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1121 }
1122 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1123 this.write_int(dir_entry.d_type, &d_type)?;
1124 }
1125
1126 Some(entry.ptr())
1127 }
1128 None => {
1129 None
1131 }
1132 Some(Err(e)) => {
1133 this.set_last_error(e)?;
1134 None
1135 }
1136 };
1137
1138 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1139 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1140 if let Some(old_entry) = old_entry {
1141 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1142 }
1143
1144 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1145 interp_ok(())
1146 }
1147
1148 fn macos_readdir_r(
1149 &mut self,
1150 dirp_op: &OpTy<'tcx>,
1151 entry_op: &OpTy<'tcx>,
1152 result_op: &OpTy<'tcx>,
1153 ) -> InterpResult<'tcx, Scalar> {
1154 let this = self.eval_context_mut();
1155
1156 this.assert_target_os(Os::MacOs, "readdir_r");
1157
1158 let dirp = this.read_target_usize(dirp_op)?;
1159 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1160
1161 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1163 this.reject_in_isolation("`readdir_r`", reject_with)?;
1164 return interp_ok(this.eval_libc("EBADF"));
1166 }
1167
1168 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1169 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1170 })?;
1171 interp_ok(match open_dir.next_host_entry() {
1172 Some(Ok(dir_entry)) => {
1173 let dir_entry = this.dir_entry_fields(dir_entry)?;
1174 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1189
1190 let name_place = this.project_field_named(&entry_place, "d_name")?;
1192 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1193 &dir_entry.name,
1194 name_place.ptr(),
1195 name_place.layout.size.bytes(),
1196 )?;
1197 if !name_fits {
1198 throw_unsup_format!(
1199 "a directory entry had a name too large to fit in libc::dirent"
1200 );
1201 }
1202
1203 this.write_int_fields_named(
1205 &[
1206 ("d_reclen", entry_place.layout.size.bytes().into()),
1207 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1208 ("d_type", dir_entry.d_type.into()),
1209 ("d_ino", dir_entry.ino.into()),
1210 ("d_seekoff", 0),
1211 ],
1212 &entry_place,
1213 )?;
1214 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1215
1216 Scalar::from_i32(0)
1217 }
1218 None => {
1219 this.write_null(&result_place)?;
1221 Scalar::from_i32(0)
1222 }
1223 Some(Err(e)) => {
1224 this.io_error_to_errnum(e)?
1226 }
1227 })
1228 }
1229
1230 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1231 let this = self.eval_context_mut();
1232
1233 let dirp = this.read_target_usize(dirp_op)?;
1234
1235 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1237 this.reject_in_isolation("`closedir`", reject_with)?;
1238 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1239 }
1240
1241 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1242 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1243 };
1244 if let Some(entry) = open_dir.entry.take() {
1245 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1246 }
1247 drop(open_dir);
1249
1250 interp_ok(Scalar::from_i32(0))
1251 }
1252
1253 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1254 let this = self.eval_context_mut();
1255
1256 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1258 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1259 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1261 }
1262
1263 let Some(fd) = this.machine.fds.get(fd_num) else {
1264 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1265 };
1266
1267 let Some(file) = fd.downcast::<FileHandle>() else {
1268 return interp_ok(this.eval_libc("EINVAL"));
1271 };
1272
1273 if file.writable {
1274 if let Ok(length) = length.try_into() {
1275 let result = file.file.set_len(length);
1276 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1277 interp_ok(Scalar::from_i32(result))
1278 } else {
1279 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1280 }
1281 } else {
1282 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1284 }
1285 }
1286
1287 fn posix_fallocate(
1290 &mut self,
1291 fd_num: i32,
1292 offset: i64,
1293 len: i64,
1294 ) -> InterpResult<'tcx, Scalar> {
1295 let this = self.eval_context_mut();
1296
1297 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1299 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1300 return interp_ok(this.eval_libc("EBADF"));
1302 }
1303
1304 if offset < 0 || len <= 0 {
1306 return interp_ok(this.eval_libc("EINVAL"));
1307 }
1308
1309 let Some(fd) = this.machine.fds.get(fd_num) else {
1311 return interp_ok(this.eval_libc("EBADF"));
1312 };
1313 let Some(file) = fd.downcast::<FileHandle>() else {
1314 return interp_ok(this.eval_libc("ENODEV"));
1316 };
1317
1318 if !file.writable {
1319 return interp_ok(this.eval_libc("EBADF"));
1321 }
1322
1323 let current_size = match file.file.metadata() {
1324 Ok(metadata) => metadata.len(),
1325 Err(err) => return this.io_error_to_errnum(err),
1326 };
1327 let new_size = match offset.checked_add(len) {
1329 Some(new_size) => u64::try_from(new_size).unwrap(),
1331 None => return interp_ok(this.eval_libc("EFBIG")), };
1333 if current_size < new_size {
1336 interp_ok(match file.file.set_len(new_size) {
1337 Ok(()) => Scalar::from_i32(0),
1338 Err(e) => this.io_error_to_errnum(e)?,
1339 })
1340 } else {
1341 interp_ok(Scalar::from_i32(0))
1342 }
1343 }
1344
1345 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1346 let this = self.eval_context_mut();
1352
1353 let fd = this.read_scalar(fd_op)?.to_i32()?;
1354
1355 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1357 this.reject_in_isolation("`fsync`", reject_with)?;
1358 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1360 }
1361
1362 self.ffullsync_fd(fd)
1363 }
1364
1365 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1366 let this = self.eval_context_mut();
1367 let Some(fd) = this.machine.fds.get(fd_num) else {
1368 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1369 };
1370 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1372 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1373 })?;
1374 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1375 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1376 }
1377
1378 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1379 let this = self.eval_context_mut();
1380
1381 let fd = this.read_scalar(fd_op)?.to_i32()?;
1382
1383 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1385 this.reject_in_isolation("`fdatasync`", reject_with)?;
1386 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1388 }
1389
1390 let Some(fd) = this.machine.fds.get(fd) else {
1391 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1392 };
1393 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1395 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1396 })?;
1397 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1398 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1399 }
1400
1401 fn sync_file_range(
1402 &mut self,
1403 fd_op: &OpTy<'tcx>,
1404 offset_op: &OpTy<'tcx>,
1405 nbytes_op: &OpTy<'tcx>,
1406 flags_op: &OpTy<'tcx>,
1407 ) -> InterpResult<'tcx, Scalar> {
1408 let this = self.eval_context_mut();
1409
1410 let fd = this.read_scalar(fd_op)?.to_i32()?;
1411 let offset = this.read_scalar(offset_op)?.to_i64()?;
1412 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1413 let flags = this.read_scalar(flags_op)?.to_i32()?;
1414
1415 if offset < 0 || nbytes < 0 {
1416 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1417 }
1418 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1419 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1420 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1421 if flags & allowed_flags != flags {
1422 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1423 }
1424
1425 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1427 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1428 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1430 }
1431
1432 let Some(fd) = this.machine.fds.get(fd) else {
1433 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1434 };
1435 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1437 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1438 })?;
1439 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1440 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1441 }
1442
1443 fn readlink(
1444 &mut self,
1445 pathname_op: &OpTy<'tcx>,
1446 buf_op: &OpTy<'tcx>,
1447 bufsize_op: &OpTy<'tcx>,
1448 ) -> InterpResult<'tcx, i64> {
1449 let this = self.eval_context_mut();
1450
1451 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1452 let buf = this.read_pointer(buf_op)?;
1453 let bufsize = this.read_target_usize(bufsize_op)?;
1454
1455 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1457 this.reject_in_isolation("`readlink`", reject_with)?;
1458 this.set_last_error(LibcError("EACCES"))?;
1459 return interp_ok(-1);
1460 }
1461
1462 let result = std::fs::read_link(pathname);
1463 match result {
1464 Ok(resolved) => {
1465 let resolved = this.convert_path(
1469 Cow::Borrowed(resolved.as_ref()),
1470 crate::shims::os_str::PathConversion::HostToTarget,
1471 );
1472 let mut path_bytes = resolved.as_encoded_bytes();
1473 let bufsize: usize = bufsize.try_into().unwrap();
1474 if path_bytes.len() > bufsize {
1475 path_bytes = &path_bytes[..bufsize]
1476 }
1477 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1478 interp_ok(path_bytes.len().try_into().unwrap())
1479 }
1480 Err(e) => {
1481 this.set_last_error(e)?;
1482 interp_ok(-1)
1483 }
1484 }
1485 }
1486
1487 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1488 let this = self.eval_context_mut();
1489 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1492 let error = if let Some(fd) = this.machine.fds.get(fd) {
1493 if fd.is_tty(this.machine.communicate()) {
1494 return interp_ok(Scalar::from_i32(1));
1495 } else {
1496 LibcError("ENOTTY")
1497 }
1498 } else {
1499 LibcError("EBADF")
1501 };
1502 this.set_last_error(error)?;
1503 interp_ok(Scalar::from_i32(0))
1504 }
1505
1506 fn realpath(
1507 &mut self,
1508 path_op: &OpTy<'tcx>,
1509 processed_path_op: &OpTy<'tcx>,
1510 ) -> InterpResult<'tcx, Scalar> {
1511 let this = self.eval_context_mut();
1512 this.assert_target_os_is_unix("realpath");
1513
1514 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1515 let processed_ptr = this.read_pointer(processed_path_op)?;
1516
1517 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1519 this.reject_in_isolation("`realpath`", reject_with)?;
1520 this.set_last_error(LibcError("EACCES"))?;
1521 return interp_ok(Scalar::from_target_usize(0, this));
1522 }
1523
1524 let result = std::fs::canonicalize(pathname);
1525 match result {
1526 Ok(resolved) => {
1527 let path_max = this
1528 .eval_libc_i32("PATH_MAX")
1529 .try_into()
1530 .expect("PATH_MAX does not fit in u64");
1531 let dest = if this.ptr_is_null(processed_ptr)? {
1532 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1542 } else {
1543 let (wrote_path, _) =
1544 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1545
1546 if !wrote_path {
1547 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1551 return interp_ok(Scalar::from_target_usize(0, this));
1552 }
1553 processed_ptr
1554 };
1555
1556 interp_ok(Scalar::from_maybe_pointer(dest, this))
1557 }
1558 Err(e) => {
1559 this.set_last_error(e)?;
1560 interp_ok(Scalar::from_target_usize(0, this))
1561 }
1562 }
1563 }
1564 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1565 use rand::seq::IndexedRandom;
1566
1567 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1569
1570 let this = self.eval_context_mut();
1571 this.assert_target_os_is_unix("mkstemp");
1572
1573 let max_attempts = this.eval_libc_u32("TMP_MAX");
1583
1584 let template_ptr = this.read_pointer(template_op)?;
1587 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1588 let template_bytes = template.as_mut_slice();
1589
1590 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1592 this.reject_in_isolation("`mkstemp`", reject_with)?;
1593 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1594 }
1595
1596 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1598
1599 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1604 let end_pos = template_bytes.len();
1605 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1606
1607 if last_six_char_bytes != suffix_bytes {
1609 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1610 }
1611
1612 const SUBSTITUTIONS: &[char; 62] = &[
1616 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1617 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1618 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1619 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1620 ];
1621
1622 let mut fopts = OpenOptions::new();
1625 fopts.read(true).write(true).create_new(true);
1626
1627 #[cfg(unix)]
1628 {
1629 use std::os::unix::fs::OpenOptionsExt;
1630 fopts.mode(0o600);
1632 fopts.custom_flags(libc::O_EXCL);
1633 }
1634 #[cfg(windows)]
1635 {
1636 use std::os::windows::fs::OpenOptionsExt;
1637 fopts.share_mode(0);
1639 }
1640
1641 for _ in 0..max_attempts {
1643 let rng = this.machine.rng.get_mut();
1644
1645 let unique_suffix =
1647 (0..6).map(|_| SUBSTITUTIONS.choose(rng).unwrap()).collect::<String>();
1648
1649 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1651
1652 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1654
1655 let file = fopts.open(bytes_to_os_str(template_bytes)?);
1657 match file {
1658 Ok(f) => {
1659 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1660 return interp_ok(Scalar::from_i32(fd));
1661 }
1662 Err(e) =>
1663 match e.kind() {
1664 ErrorKind::AlreadyExists => continue,
1666 _ => {
1668 return this.set_last_error_and_return_i32(e);
1671 }
1672 },
1673 }
1674 }
1675
1676 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1678 }
1679}
1680
1681fn extract_sec_and_nsec<'tcx>(
1685 time: std::io::Result<SystemTime>,
1686) -> InterpResult<'tcx, Option<(u64, u32)>> {
1687 match time.ok() {
1688 Some(time) => {
1689 let duration = system_time_to_duration(&time)?;
1690 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1691 }
1692 None => interp_ok(None),
1693 }
1694}
1695
1696fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1697 #[cfg(unix)]
1698 use std::os::unix::fs::FileTypeExt;
1699
1700 if file_type.is_file() {
1701 "S_IFREG"
1702 } else if file_type.is_dir() {
1703 "S_IFDIR"
1704 } else if file_type.is_symlink() {
1705 "S_IFLNK"
1706 } else {
1707 #[cfg(unix)]
1709 {
1710 if file_type.is_socket() {
1711 return "S_IFSOCK";
1712 } else if file_type.is_fifo() {
1713 return "S_IFIFO";
1714 } else if file_type.is_char_device() {
1715 return "S_IFCHR";
1716 } else if file_type.is_block_device() {
1717 return "S_IFBLK";
1718 }
1719 }
1720 "S_IFREG"
1721 }
1722}
1723
1724struct FileMetadata {
1732 mode: u32,
1734 size: u64,
1735 created: Option<(u64, u32)>,
1736 accessed: Option<(u64, u32)>,
1737 modified: Option<(u64, u32)>,
1738 dev: Option<u64>,
1739 ino: Option<u64>,
1740 nlink: Option<u64>,
1741 uid: Option<u32>,
1742 gid: Option<u32>,
1743 blksize: Option<u64>,
1744 blocks: Option<u64>,
1745}
1746
1747impl FileMetadata {
1748 fn from_path<'tcx>(
1749 ecx: &mut MiriInterpCx<'tcx>,
1750 path: &Path,
1751 follow_symlink: bool,
1752 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1753 let metadata =
1754 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1755
1756 FileMetadata::from_meta(ecx, metadata)
1757 }
1758
1759 fn from_fd_num<'tcx>(
1760 ecx: &mut MiriInterpCx<'tcx>,
1761 fd_num: i32,
1762 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1763 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1764 return interp_ok(Err(LibcError("EBADF")));
1765 };
1766 match fd.metadata()? {
1767 Either::Left(host) => Self::from_meta(ecx, host),
1768 Either::Right(name) => Self::synthetic(ecx, name),
1769 }
1770 }
1771
1772 fn synthetic<'tcx>(
1773 ecx: &mut MiriInterpCx<'tcx>,
1774 mode_name: &str,
1775 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1776 let mode = ecx.eval_libc(mode_name);
1777 let mode: u32 = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1778 let mode = mode | 0o666;
1780 interp_ok(Ok(FileMetadata {
1781 mode,
1782 size: 0,
1783 created: None,
1784 accessed: None,
1785 modified: None,
1786 dev: None,
1787 uid: None,
1788 gid: None,
1789 blksize: None,
1790 blocks: None,
1791 ino: None,
1792 nlink: None,
1793 }))
1794 }
1795
1796 fn from_meta<'tcx>(
1797 ecx: &mut MiriInterpCx<'tcx>,
1798 metadata: Result<std::fs::Metadata, std::io::Error>,
1799 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1800 let metadata = match metadata {
1801 Ok(metadata) => metadata,
1802 Err(e) => {
1803 return interp_ok(Err(e.into()));
1804 }
1805 };
1806
1807 let file_type = metadata.file_type();
1808 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1809 let mut mode = mode.to_uint(ecx.libc_ty_layout("mode_t").size)?.try_into().unwrap();
1810
1811 let size = metadata.len();
1812
1813 let created = extract_sec_and_nsec(metadata.created())?;
1814 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1815 let modified = extract_sec_and_nsec(metadata.modified())?;
1816
1817 cfg_select! {
1820 unix => {
1821 use std::os::unix::fs::MetadataExt;
1822 use std::os::unix::fs::PermissionsExt;
1823
1824 let dev = metadata.dev();
1825 let ino = metadata.ino();
1826 let nlink = metadata.nlink();
1827 let uid = metadata.uid();
1828 let gid = metadata.gid();
1829 let blksize = metadata.blksize();
1830 let blocks = metadata.blocks();
1831
1832 mode |= metadata.permissions().mode();
1833
1834 interp_ok(Ok(FileMetadata {
1835 mode,
1836 size,
1837 created,
1838 accessed,
1839 modified,
1840 dev: Some(dev),
1841 ino: Some(ino),
1842 nlink: Some(nlink),
1843 uid: Some(uid),
1844 gid: Some(gid),
1845 blksize: Some(blksize),
1846 blocks: Some(blocks),
1847 }))
1848 }
1849 _ => {
1850 mode |= if metadata.permissions().readonly() { 0o111 } else { 0o333 };
1852
1853 interp_ok(Ok(FileMetadata {
1854 mode,
1855 size,
1856 created,
1857 accessed,
1858 modified,
1859 dev: None,
1860 ino: None,
1861 nlink: None,
1862 uid: None,
1863 gid: None,
1864 blksize: None,
1865 blocks: None,
1866 }))
1867 },
1868 }
1869 }
1870}