1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::fs::{
6 self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file,
7 rename,
8};
9use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
10use std::path::{self, Path, PathBuf};
11use std::time::SystemTime;
12
13use rustc_abi::Size;
14use rustc_data_structures::either::Either;
15use rustc_data_structures::fx::FxHashMap;
16use rustc_target::spec::Os;
17
18use self::shims::time::system_time_to_duration;
19use crate::shims::files::FileHandle;
20use crate::shims::os_str::bytes_to_os_str;
21use crate::shims::sig::check_min_vararg_count;
22use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
23use crate::*;
24
25#[derive(Debug)]
27struct OpenDir {
28 special_entries: Vec<&'static str>,
31 read_dir: fs::ReadDir,
33 entry: Option<Pointer>,
36}
37
38impl OpenDir {
39 fn new(read_dir: fs::ReadDir) -> Self {
40 Self { special_entries: vec!["..", "."], read_dir, entry: None }
41 }
42
43 fn next_host_entry(&mut self) -> Option<io::Result<Either<fs::DirEntry, &'static str>>> {
44 if let Some(special) = self.special_entries.pop() {
45 return Some(Ok(Either::Right(special)));
46 }
47 let entry = self.read_dir.next()?;
48 Some(entry.map(Either::Left))
49 }
50}
51
52#[derive(Debug)]
53struct DirEntry {
54 name: OsString,
55 ino: u64,
56 d_type: i32,
57}
58
59impl UnixFileDescription for FileHandle {
60 fn pread<'tcx>(
61 &self,
62 communicate_allowed: bool,
63 offset: u64,
64 ptr: Pointer,
65 len: usize,
66 ecx: &mut MiriInterpCx<'tcx>,
67 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
68 ) -> InterpResult<'tcx> {
69 assert!(communicate_allowed, "isolation should have prevented even opening a file");
70 let mut bytes = vec![0; len];
71 let file = &mut &self.file;
75 let mut f = || {
76 let cursor_pos = file.stream_position()?;
77 file.seek(SeekFrom::Start(offset))?;
78 let res = file.read(&mut bytes);
79 file.seek(SeekFrom::Start(cursor_pos))
81 .expect("failed to restore file position, this shouldn't be possible");
82 res
83 };
84 let result = match f() {
85 Ok(read_size) => {
86 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
90 Ok(read_size)
91 }
92 Err(e) => Err(IoError::HostError(e)),
93 };
94 finish.call(ecx, result)
95 }
96
97 fn pwrite<'tcx>(
98 &self,
99 communicate_allowed: bool,
100 ptr: Pointer,
101 len: usize,
102 offset: u64,
103 ecx: &mut MiriInterpCx<'tcx>,
104 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
105 ) -> InterpResult<'tcx> {
106 assert!(communicate_allowed, "isolation should have prevented even opening a file");
107 let file = &mut &self.file;
111 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
112 let mut f = || {
113 let cursor_pos = file.stream_position()?;
114 file.seek(SeekFrom::Start(offset))?;
115 let res = file.write(bytes);
116 file.seek(SeekFrom::Start(cursor_pos))
118 .expect("failed to restore file position, this shouldn't be possible");
119 res
120 };
121 let result = f();
122 finish.call(ecx, result.map_err(IoError::HostError))
123 }
124
125 fn flock<'tcx>(
126 &self,
127 communicate_allowed: bool,
128 op: FlockOp,
129 ) -> InterpResult<'tcx, io::Result<()>> {
130 assert!(communicate_allowed, "isolation should have prevented even opening a file");
131
132 use FlockOp::*;
133 let (res, nonblocking) = match op {
135 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
136 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
137 Unlock => {
138 return interp_ok(self.file.unlock());
139 }
140 };
141
142 match res {
143 Ok(()) => interp_ok(Ok(())),
144 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
145 Err(TryLockError::WouldBlock) =>
146 if nonblocking {
147 interp_ok(Err(ErrorKind::WouldBlock.into()))
148 } else {
149 throw_unsup_format!("blocking `flock` is not currently supported");
150 },
151 }
152 }
153}
154
155#[derive(Debug)]
159pub struct DirTable {
160 streams: FxHashMap<u64, OpenDir>,
170 next_id: u64,
172}
173
174impl DirTable {
175 #[expect(clippy::arithmetic_side_effects)]
176 fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 {
177 let id = self.next_id;
178 self.next_id += 1;
179 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
180 id
181 }
182}
183
184impl Default for DirTable {
185 fn default() -> DirTable {
186 DirTable {
187 streams: FxHashMap::default(),
188 next_id: 1,
190 }
191 }
192}
193
194impl VisitProvenance for DirTable {
195 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
196 let DirTable { streams, next_id: _ } = self;
197
198 for dir in streams.values() {
199 dir.entry.visit_provenance(visit);
200 }
201 }
202}
203
204fn maybe_sync_file(
205 file: &File,
206 writable: bool,
207 operation: fn(&File) -> std::io::Result<()>,
208) -> std::io::Result<i32> {
209 if !writable && cfg!(windows) {
210 Ok(0i32)
214 } else {
215 let result = operation(file);
216 result.map(|_| 0i32)
217 }
218}
219
220impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
221trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
222 fn write_stat_buf(
223 &mut self,
224 metadata: FileMetadata,
225 buf_op: &OpTy<'tcx>,
226 ) -> InterpResult<'tcx, i32> {
227 let this = self.eval_context_mut();
228
229 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
230 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
231 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
232
233 let buf = this.deref_pointer(buf_op)?;
238
239 let mode_t_size = this.libc_ty_layout("mode_t").size;
242 let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap();
243
244 this.write_int_fields_named(
245 &[
246 ("st_dev", metadata.dev.unwrap_or(0).into()),
247 ("st_mode", mode.into()),
248 ("st_nlink", metadata.nlink.unwrap_or(0).into()),
249 ("st_ino", metadata.ino.unwrap_or(0).into()),
250 ("st_uid", metadata.uid.unwrap_or(0).into()),
251 ("st_gid", metadata.gid.unwrap_or(0).into()),
252 ("st_rdev", 0),
253 ("st_atime", access_sec.into()),
254 ("st_atime_nsec", access_nsec.into()),
255 ("st_mtime", modified_sec.into()),
256 ("st_mtime_nsec", modified_nsec.into()),
257 ("st_ctime", 0),
258 ("st_ctime_nsec", 0),
259 ("st_size", metadata.size.into()),
260 ("st_blocks", metadata.blocks.unwrap_or(0).into()),
261 ("st_blksize", metadata.blksize.unwrap_or(0).into()),
262 ],
263 &buf,
264 )?;
265
266 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
267 this.write_int_fields_named(
268 &[
269 ("st_birthtime", created_sec.into()),
270 ("st_birthtime_nsec", created_nsec.into()),
271 ("st_flags", 0),
272 ("st_gen", 0),
273 ],
274 &buf,
275 )?;
276 }
277
278 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
279 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
280 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
282 }
283
284 interp_ok(0)
285 }
286
287 fn file_type_to_d_type(&self, file_type: std::io::Result<FileType>) -> InterpResult<'tcx, i32> {
288 #[cfg(unix)]
289 use std::os::unix::fs::FileTypeExt;
290
291 let this = self.eval_context_ref();
292 match file_type {
293 Ok(file_type) => {
294 match () {
295 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
296 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
297 _ if file_type.is_symlink() =>
298 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
299 #[cfg(unix)]
301 _ if file_type.is_block_device() =>
302 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
303 #[cfg(unix)]
304 _ if file_type.is_char_device() =>
305 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
306 #[cfg(unix)]
307 _ if file_type.is_fifo() =>
308 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
309 #[cfg(unix)]
310 _ if file_type.is_socket() =>
311 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
312 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
314 }
315 }
316 Err(_) => {
317 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
319 }
320 }
321 }
322
323 fn dir_entry_fields(
324 &self,
325 entry: Either<fs::DirEntry, &'static str>,
326 ) -> InterpResult<'tcx, DirEntry> {
327 let this = self.eval_context_ref();
328 interp_ok(match entry {
329 Either::Left(dir_entry) => {
330 DirEntry {
331 name: dir_entry.file_name(),
332 d_type: this.file_type_to_d_type(dir_entry.file_type())?,
333 #[cfg(unix)]
336 ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry),
337 #[cfg(not(unix))]
338 ino: 0u64,
339 }
340 }
341 Either::Right(special) =>
342 DirEntry {
343 name: special.into(),
344 d_type: this.eval_libc("DT_DIR").to_u8()?.into(),
345 ino: 0,
346 },
347 })
348 }
349}
350
351impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
352pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
353 fn open(
354 &mut self,
355 path_raw: &OpTy<'tcx>,
356 flag: &OpTy<'tcx>,
357 varargs: &[OpTy<'tcx>],
358 ) -> InterpResult<'tcx, Scalar> {
359 let this = self.eval_context_mut();
360
361 let path_raw = this.read_pointer(path_raw)?;
362 let flag = this.read_scalar(flag)?.to_i32()?;
363
364 let path = this.read_path_from_c_str(path_raw)?;
365 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Illumos | Os::Solaris)
367 && path::absolute(&path).is_ok_and(|path| path.starts_with("/proc"))
368 {
369 this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
370 }
371
372 let mut flag = flag;
374
375 let mut options = OpenOptions::new();
376
377 let o_rdonly = this.eval_libc_i32("O_RDONLY");
378 let o_wronly = this.eval_libc_i32("O_WRONLY");
379 let o_rdwr = this.eval_libc_i32("O_RDWR");
380 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
384 throw_unsup_format!("access mode flags on this target are unsupported");
385 }
386 let mut writable = true;
387
388 let access_mode = flag & 0b11;
390 flag &= !access_mode;
391
392 if access_mode == o_rdonly {
393 writable = false;
394 options.read(true);
395 } else if access_mode == o_wronly {
396 options.write(true);
397 } else if access_mode == o_rdwr {
398 options.read(true).write(true);
399 } else {
400 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
401 }
402
403 let o_append = this.eval_libc_i32("O_APPEND");
404 if flag & o_append == o_append {
405 flag &= !o_append;
406 options.append(true);
407 }
408 let o_trunc = this.eval_libc_i32("O_TRUNC");
409 if flag & o_trunc == o_trunc {
410 flag &= !o_trunc;
411 options.truncate(true);
412 }
413 let o_creat = this.eval_libc_i32("O_CREAT");
414 if flag & o_creat == o_creat {
415 flag &= !o_creat;
416 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
420 let mode = this.read_scalar(mode)?.to_u32()?;
421
422 #[cfg(unix)]
423 {
424 use std::os::unix::fs::OpenOptionsExt;
426 options.mode(mode);
427 }
428 #[cfg(not(unix))]
429 {
430 if mode != 0o666 {
432 throw_unsup_format!(
433 "non-default mode 0o{:o} is not supported on non-Unix hosts",
434 mode
435 );
436 }
437 }
438
439 let o_excl = this.eval_libc_i32("O_EXCL");
440 if flag & o_excl == o_excl {
441 flag &= !o_excl;
442 options.create_new(true);
443 } else {
444 options.create(true);
445 }
446 }
447 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
448 if flag & o_cloexec == o_cloexec {
449 flag &= !o_cloexec;
450 }
453 if this.tcx.sess.target.os == Os::Linux {
454 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
455 if flag & o_tmpfile == o_tmpfile {
456 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
458 }
459 }
460
461 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
462 if flag & o_nofollow == o_nofollow {
463 flag &= !o_nofollow;
464 #[cfg(unix)]
465 {
466 use std::os::unix::fs::OpenOptionsExt;
467 options.custom_flags(libc::O_NOFOLLOW);
468 }
469 #[cfg(not(unix))]
473 {
474 if path.is_symlink() {
477 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
478 }
479 }
480 }
481
482 if flag != 0 {
484 throw_unsup_format!("unsupported flags {:#x}", flag);
485 }
486
487 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
489 this.reject_in_isolation("`open`", reject_with)?;
490 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
491 }
492
493 let fd = options
494 .open(path)
495 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
496
497 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
498 }
499
500 fn lseek(
501 &mut self,
502 fd_num: i32,
503 offset: i128,
504 whence: i32,
505 dest: &MPlaceTy<'tcx>,
506 ) -> InterpResult<'tcx> {
507 let this = self.eval_context_mut();
508
509 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
512 if offset < 0 {
513 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
515 } else {
516 SeekFrom::Start(u64::try_from(offset).unwrap())
517 }
518 } else if whence == this.eval_libc_i32("SEEK_CUR") {
519 SeekFrom::Current(i64::try_from(offset).unwrap())
520 } else if whence == this.eval_libc_i32("SEEK_END") {
521 SeekFrom::End(i64::try_from(offset).unwrap())
522 } else {
523 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
524 };
525
526 let communicate = this.machine.communicate();
527
528 let Some(fd) = this.machine.fds.get(fd_num) else {
529 return this.set_last_error_and_return(LibcError("EBADF"), dest);
530 };
531 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
532 drop(fd);
533
534 let result = this.try_unwrap_io_result(result)?;
535 this.write_int(result, dest)?;
536 interp_ok(())
537 }
538
539 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
540 let this = self.eval_context_mut();
541
542 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
543
544 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
546 this.reject_in_isolation("`unlink`", reject_with)?;
547 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
548 }
549
550 let result = remove_file(path).map(|_| 0);
551 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
552 }
553
554 fn symlink(
555 &mut self,
556 target_op: &OpTy<'tcx>,
557 linkpath_op: &OpTy<'tcx>,
558 ) -> InterpResult<'tcx, Scalar> {
559 #[cfg(unix)]
560 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
561 std::os::unix::fs::symlink(src, dst)
562 }
563
564 #[cfg(windows)]
565 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
566 use std::os::windows::fs;
567 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
568 }
569
570 let this = self.eval_context_mut();
571 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
572 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
573
574 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
576 this.reject_in_isolation("`symlink`", reject_with)?;
577 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
578 }
579
580 let result = create_link(&target, &linkpath).map(|_| 0);
581 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
582 }
583
584 fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
585 let this = self.eval_context_mut();
586
587 if !matches!(
588 &this.tcx.sess.target.os,
589 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
590 ) {
591 panic!("`stat` should not be called on {}", this.tcx.sess.target.os);
592 }
593
594 let path_scalar = this.read_pointer(path_op)?;
595 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
596
597 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
599 this.reject_in_isolation("`stat`", reject_with)?;
600 return this.set_last_error_and_return_i32(LibcError("EACCES"));
601 }
602
603 let metadata = match FileMetadata::from_path(this, &path, true)? {
605 Ok(metadata) => metadata,
606 Err(err) => return this.set_last_error_and_return_i32(err),
607 };
608
609 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
610 }
611
612 fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
614 let this = self.eval_context_mut();
615
616 if !matches!(
617 &this.tcx.sess.target.os,
618 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux
619 ) {
620 panic!("`lstat` should not be called on {}", this.tcx.sess.target.os);
621 }
622
623 let path_scalar = this.read_pointer(path_op)?;
624 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
625
626 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
628 this.reject_in_isolation("`lstat`", reject_with)?;
629 return this.set_last_error_and_return_i32(LibcError("EACCES"));
630 }
631
632 let metadata = match FileMetadata::from_path(this, &path, false)? {
633 Ok(metadata) => metadata,
634 Err(err) => return this.set_last_error_and_return_i32(err),
635 };
636
637 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
638 }
639
640 fn fstat(&mut self, fd_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
641 let this = self.eval_context_mut();
642
643 if !matches!(
644 &this.tcx.sess.target.os,
645 Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
646 ) {
647 panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
648 }
649
650 let fd = this.read_scalar(fd_op)?.to_i32()?;
651
652 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
654 this.reject_in_isolation("`fstat`", reject_with)?;
655 return this.set_last_error_and_return_i32(LibcError("EBADF"));
657 }
658
659 let metadata = match FileMetadata::from_fd_num(this, fd)? {
660 Ok(metadata) => metadata,
661 Err(err) => return this.set_last_error_and_return_i32(err),
662 };
663 interp_ok(Scalar::from_i32(this.write_stat_buf(metadata, buf_op)?))
664 }
665
666 fn linux_statx(
667 &mut self,
668 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
674 let this = self.eval_context_mut();
675
676 this.assert_target_os(Os::Linux, "statx");
677
678 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
679 let pathname_ptr = this.read_pointer(pathname_op)?;
680 let flags = this.read_scalar(flags_op)?.to_i32()?;
681 let _mask = this.read_scalar(mask_op)?.to_u32()?;
682 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
683
684 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
686 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
687 }
688
689 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
690
691 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
692 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
694 let empty_path_flag = flags & at_empty_path == at_empty_path;
695 if !(path.is_absolute()
703 || dirfd == this.eval_libc_i32("AT_FDCWD")
704 || (path.as_os_str().is_empty() && empty_path_flag))
705 {
706 throw_unsup_format!(
707 "using statx is only supported with absolute paths, relative paths with the file \
708 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
709 file descriptor"
710 )
711 }
712
713 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
715 this.reject_in_isolation("`statx`", reject_with)?;
716 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
717 LibcError("EACCES")
720 } else {
721 assert!(empty_path_flag);
725 LibcError("EBADF")
726 };
727 return this.set_last_error_and_return_i32(ecode);
728 }
729
730 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
733
734 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
737 FileMetadata::from_fd_num(this, dirfd)?
738 } else {
739 FileMetadata::from_path(this, &path, follow_symlink)?
740 };
741 let metadata = match metadata {
742 Ok(metadata) => metadata,
743 Err(err) => return this.set_last_error_and_return_i32(err),
744 };
745
746 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
751
752 if metadata.ino.is_some() {
754 mask |= this.eval_libc_u32("STATX_INO");
755 }
756 if metadata.nlink.is_some() {
757 mask |= this.eval_libc_u32("STATX_NLINK");
758 }
759 if metadata.uid.is_some() {
760 mask |= this.eval_libc_u32("STATX_UID");
761 }
762 if metadata.gid.is_some() {
763 mask |= this.eval_libc_u32("STATX_GID");
764 }
765 if metadata.blocks.is_some() {
766 mask |= this.eval_libc_u32("STATX_BLOCKS");
767 }
768
769 let mode_t_size = this.libc_ty_layout("mode_t").size;
772 let mode: u16 = metadata
773 .mode
774 .to_uint(mode_t_size)?
775 .try_into()
776 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
777
778 let (access_sec, access_nsec) = metadata
781 .accessed
782 .map(|tup| {
783 mask |= this.eval_libc_u32("STATX_ATIME");
784 interp_ok(tup)
785 })
786 .unwrap_or_else(|| interp_ok((0, 0)))?;
787
788 let (created_sec, created_nsec) = metadata
789 .created
790 .map(|tup| {
791 mask |= this.eval_libc_u32("STATX_BTIME");
792 interp_ok(tup)
793 })
794 .unwrap_or_else(|| interp_ok((0, 0)))?;
795
796 let (modified_sec, modified_nsec) = metadata
797 .modified
798 .map(|tup| {
799 mask |= this.eval_libc_u32("STATX_MTIME");
800 interp_ok(tup)
801 })
802 .unwrap_or_else(|| interp_ok((0, 0)))?;
803
804 this.write_int_fields_named(
806 &[
807 ("stx_mask", mask.into()),
808 ("stx_blksize", metadata.blksize.unwrap_or(0).into()),
809 ("stx_attributes", 0),
810 ("stx_nlink", metadata.nlink.unwrap_or(0).into()),
811 ("stx_uid", metadata.uid.unwrap_or(0).into()),
812 ("stx_gid", metadata.gid.unwrap_or(0).into()),
813 ("stx_mode", mode.into()),
814 ("stx_ino", metadata.ino.unwrap_or(0).into()),
815 ("stx_size", metadata.size.into()),
816 ("stx_blocks", metadata.blocks.unwrap_or(0).into()),
817 ("stx_attributes_mask", 0),
818 ("stx_rdev_major", 0),
819 ("stx_rdev_minor", 0),
820 ("stx_dev_major", 0),
821 ("stx_dev_minor", 0),
822 ],
823 &statxbuf,
824 )?;
825 #[rustfmt::skip]
826 this.write_int_fields_named(
827 &[
828 ("tv_sec", access_sec.into()),
829 ("tv_nsec", access_nsec.into()),
830 ],
831 &this.project_field_named(&statxbuf, "stx_atime")?,
832 )?;
833 #[rustfmt::skip]
834 this.write_int_fields_named(
835 &[
836 ("tv_sec", created_sec.into()),
837 ("tv_nsec", created_nsec.into()),
838 ],
839 &this.project_field_named(&statxbuf, "stx_btime")?,
840 )?;
841 #[rustfmt::skip]
842 this.write_int_fields_named(
843 &[
844 ("tv_sec", 0.into()),
845 ("tv_nsec", 0.into()),
846 ],
847 &this.project_field_named(&statxbuf, "stx_ctime")?,
848 )?;
849 #[rustfmt::skip]
850 this.write_int_fields_named(
851 &[
852 ("tv_sec", modified_sec.into()),
853 ("tv_nsec", modified_nsec.into()),
854 ],
855 &this.project_field_named(&statxbuf, "stx_mtime")?,
856 )?;
857
858 interp_ok(Scalar::from_i32(0))
859 }
860
861 fn rename(
862 &mut self,
863 oldpath_op: &OpTy<'tcx>,
864 newpath_op: &OpTy<'tcx>,
865 ) -> InterpResult<'tcx, Scalar> {
866 let this = self.eval_context_mut();
867
868 let oldpath_ptr = this.read_pointer(oldpath_op)?;
869 let newpath_ptr = this.read_pointer(newpath_op)?;
870
871 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
872 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
873 }
874
875 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
876 let newpath = this.read_path_from_c_str(newpath_ptr)?;
877
878 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
880 this.reject_in_isolation("`rename`", reject_with)?;
881 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
882 }
883
884 let result = rename(oldpath, newpath).map(|_| 0);
885
886 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
887 }
888
889 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
890 let this = self.eval_context_mut();
891
892 #[cfg_attr(not(unix), allow(unused_variables))]
893 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
894 u32::from(this.read_scalar(mode_op)?.to_u16()?)
895 } else {
896 this.read_scalar(mode_op)?.to_u32()?
897 };
898
899 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
900
901 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
903 this.reject_in_isolation("`mkdir`", reject_with)?;
904 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
905 }
906
907 #[cfg_attr(not(unix), allow(unused_mut))]
908 let mut builder = DirBuilder::new();
909
910 #[cfg(unix)]
913 {
914 use std::os::unix::fs::DirBuilderExt;
915 builder.mode(mode);
916 }
917
918 let result = builder.create(path).map(|_| 0i32);
919
920 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
921 }
922
923 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
924 let this = self.eval_context_mut();
925
926 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
927
928 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
930 this.reject_in_isolation("`rmdir`", reject_with)?;
931 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
932 }
933
934 let result = remove_dir(path).map(|_| 0i32);
935
936 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
937 }
938
939 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
940 let this = self.eval_context_mut();
941
942 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
943
944 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
946 this.reject_in_isolation("`opendir`", reject_with)?;
947 this.set_last_error(LibcError("EACCES"))?;
948 return interp_ok(Scalar::null_ptr(this));
949 }
950
951 let result = read_dir(name);
952
953 match result {
954 Ok(dir_iter) => {
955 let id = this.machine.dirs.insert_new(dir_iter);
956
957 interp_ok(Scalar::from_target_usize(id, this))
961 }
962 Err(e) => {
963 this.set_last_error(e)?;
964 interp_ok(Scalar::null_ptr(this))
965 }
966 }
967 }
968
969 fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
970 let this = self.eval_context_mut();
971
972 if !matches!(
973 &this.tcx.sess.target.os,
974 Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
975 ) {
976 panic!("`readdir` should not be called on {}", this.tcx.sess.target.os);
977 }
978
979 let dirp = this.read_target_usize(dirp_op)?;
980
981 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
983 this.reject_in_isolation("`readdir`", reject_with)?;
984 this.set_last_error(LibcError("EBADF"))?;
985 this.write_null(dest)?;
986 return interp_ok(());
987 }
988
989 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
990 err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir")
991 })?;
992
993 let entry = match open_dir.next_host_entry() {
994 Some(Ok(dir_entry)) => {
995 let dir_entry = this.dir_entry_fields(dir_entry)?;
996
997 let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap();
1034 let dirent_layout = this.layout_of(dirent_ty)?;
1035 let fields = &dirent_layout.fields;
1036 let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes();
1037
1038 let mut name = dir_entry.name; name.push("\0"); let name_bytes = name.as_encoded_bytes();
1042 let name_len = u64::try_from(name_bytes.len()).unwrap();
1043 let size = d_name_offset.strict_add(name_len);
1044
1045 let entry = this.allocate_ptr(
1046 Size::from_bytes(size),
1047 dirent_layout.align.abi,
1048 MiriMemoryKind::Runtime.into(),
1049 AllocInit::Uninit,
1050 )?;
1051 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
1052
1053 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1056 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1057
1058 let ino_name =
1060 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
1061 this.write_int_fields_named(
1062 &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())],
1063 &entry,
1064 )?;
1065
1066 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
1068 this.write_null(&d_off)?;
1069 }
1070 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1071 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1072 }
1073 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1074 this.write_int(dir_entry.d_type, &d_type)?;
1075 }
1076
1077 Some(entry.ptr())
1078 }
1079 None => {
1080 None
1082 }
1083 Some(Err(e)) => {
1084 this.set_last_error(e)?;
1085 None
1086 }
1087 };
1088
1089 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1090 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1091 if let Some(old_entry) = old_entry {
1092 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1093 }
1094
1095 this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?;
1096 interp_ok(())
1097 }
1098
1099 fn macos_readdir_r(
1100 &mut self,
1101 dirp_op: &OpTy<'tcx>,
1102 entry_op: &OpTy<'tcx>,
1103 result_op: &OpTy<'tcx>,
1104 ) -> InterpResult<'tcx, Scalar> {
1105 let this = self.eval_context_mut();
1106
1107 this.assert_target_os(Os::MacOs, "readdir_r");
1108
1109 let dirp = this.read_target_usize(dirp_op)?;
1110 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1111
1112 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1114 this.reject_in_isolation("`readdir_r`", reject_with)?;
1115 return interp_ok(this.eval_libc("EBADF"));
1117 }
1118
1119 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1120 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1121 })?;
1122 interp_ok(match open_dir.next_host_entry() {
1123 Some(Ok(dir_entry)) => {
1124 let dir_entry = this.dir_entry_fields(dir_entry)?;
1125 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1140
1141 let name_place = this.project_field_named(&entry_place, "d_name")?;
1143 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1144 &dir_entry.name,
1145 name_place.ptr(),
1146 name_place.layout.size.bytes(),
1147 )?;
1148 if !name_fits {
1149 throw_unsup_format!(
1150 "a directory entry had a name too large to fit in libc::dirent"
1151 );
1152 }
1153
1154 this.write_int_fields_named(
1156 &[
1157 ("d_reclen", entry_place.layout.size.bytes().into()),
1158 ("d_namlen", file_name_buf_len.strict_sub(1).into()),
1159 ("d_type", dir_entry.d_type.into()),
1160 ("d_ino", dir_entry.ino.into()),
1161 ("d_seekoff", 0),
1162 ],
1163 &entry_place,
1164 )?;
1165 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1166
1167 Scalar::from_i32(0)
1168 }
1169 None => {
1170 this.write_null(&result_place)?;
1172 Scalar::from_i32(0)
1173 }
1174 Some(Err(e)) => {
1175 this.io_error_to_errnum(e)?
1177 }
1178 })
1179 }
1180
1181 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1182 let this = self.eval_context_mut();
1183
1184 let dirp = this.read_target_usize(dirp_op)?;
1185
1186 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1188 this.reject_in_isolation("`closedir`", reject_with)?;
1189 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1190 }
1191
1192 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1193 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1194 };
1195 if let Some(entry) = open_dir.entry.take() {
1196 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1197 }
1198 drop(open_dir);
1200
1201 interp_ok(Scalar::from_i32(0))
1202 }
1203
1204 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1205 let this = self.eval_context_mut();
1206
1207 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1209 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1210 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1212 }
1213
1214 let Some(fd) = this.machine.fds.get(fd_num) else {
1215 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1216 };
1217
1218 let Some(file) = fd.downcast::<FileHandle>() else {
1219 return interp_ok(this.eval_libc("EINVAL"));
1222 };
1223
1224 if file.writable {
1225 if let Ok(length) = length.try_into() {
1226 let result = file.file.set_len(length);
1227 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1228 interp_ok(Scalar::from_i32(result))
1229 } else {
1230 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1231 }
1232 } else {
1233 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1235 }
1236 }
1237
1238 fn posix_fallocate(
1241 &mut self,
1242 fd_num: i32,
1243 offset: i64,
1244 len: i64,
1245 ) -> InterpResult<'tcx, Scalar> {
1246 let this = self.eval_context_mut();
1247
1248 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1250 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1251 return interp_ok(this.eval_libc("EBADF"));
1253 }
1254
1255 if offset < 0 || len <= 0 {
1257 return interp_ok(this.eval_libc("EINVAL"));
1258 }
1259
1260 let Some(fd) = this.machine.fds.get(fd_num) else {
1262 return interp_ok(this.eval_libc("EBADF"));
1263 };
1264 let Some(file) = fd.downcast::<FileHandle>() else {
1265 return interp_ok(this.eval_libc("ENODEV"));
1267 };
1268
1269 if !file.writable {
1270 return interp_ok(this.eval_libc("EBADF"));
1272 }
1273
1274 let current_size = match file.file.metadata() {
1275 Ok(metadata) => metadata.len(),
1276 Err(err) => return this.io_error_to_errnum(err),
1277 };
1278 let new_size = match offset.checked_add(len) {
1280 Some(new_size) => u64::try_from(new_size).unwrap(),
1282 None => return interp_ok(this.eval_libc("EFBIG")), };
1284 if current_size < new_size {
1287 interp_ok(match file.file.set_len(new_size) {
1288 Ok(()) => Scalar::from_i32(0),
1289 Err(e) => this.io_error_to_errnum(e)?,
1290 })
1291 } else {
1292 interp_ok(Scalar::from_i32(0))
1293 }
1294 }
1295
1296 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1297 let this = self.eval_context_mut();
1303
1304 let fd = this.read_scalar(fd_op)?.to_i32()?;
1305
1306 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1308 this.reject_in_isolation("`fsync`", reject_with)?;
1309 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1311 }
1312
1313 self.ffullsync_fd(fd)
1314 }
1315
1316 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1317 let this = self.eval_context_mut();
1318 let Some(fd) = this.machine.fds.get(fd_num) else {
1319 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1320 };
1321 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1323 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1324 })?;
1325 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1326 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1327 }
1328
1329 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1330 let this = self.eval_context_mut();
1331
1332 let fd = this.read_scalar(fd_op)?.to_i32()?;
1333
1334 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1336 this.reject_in_isolation("`fdatasync`", reject_with)?;
1337 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1339 }
1340
1341 let Some(fd) = this.machine.fds.get(fd) else {
1342 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1343 };
1344 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1346 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1347 })?;
1348 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1349 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1350 }
1351
1352 fn sync_file_range(
1353 &mut self,
1354 fd_op: &OpTy<'tcx>,
1355 offset_op: &OpTy<'tcx>,
1356 nbytes_op: &OpTy<'tcx>,
1357 flags_op: &OpTy<'tcx>,
1358 ) -> InterpResult<'tcx, Scalar> {
1359 let this = self.eval_context_mut();
1360
1361 let fd = this.read_scalar(fd_op)?.to_i32()?;
1362 let offset = this.read_scalar(offset_op)?.to_i64()?;
1363 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1364 let flags = this.read_scalar(flags_op)?.to_i32()?;
1365
1366 if offset < 0 || nbytes < 0 {
1367 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1368 }
1369 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1370 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1371 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1372 if flags & allowed_flags != flags {
1373 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1374 }
1375
1376 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1378 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1379 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1381 }
1382
1383 let Some(fd) = this.machine.fds.get(fd) else {
1384 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1385 };
1386 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1388 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1389 })?;
1390 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1391 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1392 }
1393
1394 fn readlink(
1395 &mut self,
1396 pathname_op: &OpTy<'tcx>,
1397 buf_op: &OpTy<'tcx>,
1398 bufsize_op: &OpTy<'tcx>,
1399 ) -> InterpResult<'tcx, i64> {
1400 let this = self.eval_context_mut();
1401
1402 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1403 let buf = this.read_pointer(buf_op)?;
1404 let bufsize = this.read_target_usize(bufsize_op)?;
1405
1406 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1408 this.reject_in_isolation("`readlink`", reject_with)?;
1409 this.set_last_error(LibcError("EACCES"))?;
1410 return interp_ok(-1);
1411 }
1412
1413 let result = std::fs::read_link(pathname);
1414 match result {
1415 Ok(resolved) => {
1416 let resolved = this.convert_path(
1420 Cow::Borrowed(resolved.as_ref()),
1421 crate::shims::os_str::PathConversion::HostToTarget,
1422 );
1423 let mut path_bytes = resolved.as_encoded_bytes();
1424 let bufsize: usize = bufsize.try_into().unwrap();
1425 if path_bytes.len() > bufsize {
1426 path_bytes = &path_bytes[..bufsize]
1427 }
1428 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1429 interp_ok(path_bytes.len().try_into().unwrap())
1430 }
1431 Err(e) => {
1432 this.set_last_error(e)?;
1433 interp_ok(-1)
1434 }
1435 }
1436 }
1437
1438 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1439 let this = self.eval_context_mut();
1440 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1443 let error = if let Some(fd) = this.machine.fds.get(fd) {
1444 if fd.is_tty(this.machine.communicate()) {
1445 return interp_ok(Scalar::from_i32(1));
1446 } else {
1447 LibcError("ENOTTY")
1448 }
1449 } else {
1450 LibcError("EBADF")
1452 };
1453 this.set_last_error(error)?;
1454 interp_ok(Scalar::from_i32(0))
1455 }
1456
1457 fn realpath(
1458 &mut self,
1459 path_op: &OpTy<'tcx>,
1460 processed_path_op: &OpTy<'tcx>,
1461 ) -> InterpResult<'tcx, Scalar> {
1462 let this = self.eval_context_mut();
1463 this.assert_target_os_is_unix("realpath");
1464
1465 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1466 let processed_ptr = this.read_pointer(processed_path_op)?;
1467
1468 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1470 this.reject_in_isolation("`realpath`", reject_with)?;
1471 this.set_last_error(LibcError("EACCES"))?;
1472 return interp_ok(Scalar::from_target_usize(0, this));
1473 }
1474
1475 let result = std::fs::canonicalize(pathname);
1476 match result {
1477 Ok(resolved) => {
1478 let path_max = this
1479 .eval_libc_i32("PATH_MAX")
1480 .try_into()
1481 .expect("PATH_MAX does not fit in u64");
1482 let dest = if this.ptr_is_null(processed_ptr)? {
1483 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1493 } else {
1494 let (wrote_path, _) =
1495 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1496
1497 if !wrote_path {
1498 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1502 return interp_ok(Scalar::from_target_usize(0, this));
1503 }
1504 processed_ptr
1505 };
1506
1507 interp_ok(Scalar::from_maybe_pointer(dest, this))
1508 }
1509 Err(e) => {
1510 this.set_last_error(e)?;
1511 interp_ok(Scalar::from_target_usize(0, this))
1512 }
1513 }
1514 }
1515 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1516 use rand::seq::IndexedRandom;
1517
1518 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1520
1521 let this = self.eval_context_mut();
1522 this.assert_target_os_is_unix("mkstemp");
1523
1524 let max_attempts = this.eval_libc_u32("TMP_MAX");
1534
1535 let template_ptr = this.read_pointer(template_op)?;
1538 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1539 let template_bytes = template.as_mut_slice();
1540
1541 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1543 this.reject_in_isolation("`mkstemp`", reject_with)?;
1544 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1545 }
1546
1547 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1549
1550 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1555 let end_pos = template_bytes.len();
1556 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1557
1558 if last_six_char_bytes != suffix_bytes {
1560 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1561 }
1562
1563 const SUBSTITUTIONS: &[char; 62] = &[
1567 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1568 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1569 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1570 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1571 ];
1572
1573 let mut fopts = OpenOptions::new();
1576 fopts.read(true).write(true).create_new(true);
1577
1578 #[cfg(unix)]
1579 {
1580 use std::os::unix::fs::OpenOptionsExt;
1581 fopts.mode(0o600);
1583 fopts.custom_flags(libc::O_EXCL);
1584 }
1585 #[cfg(windows)]
1586 {
1587 use std::os::windows::fs::OpenOptionsExt;
1588 fopts.share_mode(0);
1590 }
1591
1592 for _ in 0..max_attempts {
1594 let rng = this.machine.rng.get_mut();
1595
1596 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1598
1599 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1601
1602 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1604
1605 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1607
1608 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1609
1610 let file = fopts.open(possibly_unique);
1611
1612 match file {
1613 Ok(f) => {
1614 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1615 return interp_ok(Scalar::from_i32(fd));
1616 }
1617 Err(e) =>
1618 match e.kind() {
1619 ErrorKind::AlreadyExists => continue,
1621 _ => {
1623 return this.set_last_error_and_return_i32(e);
1626 }
1627 },
1628 }
1629 }
1630
1631 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1633 }
1634}
1635
1636fn extract_sec_and_nsec<'tcx>(
1640 time: std::io::Result<SystemTime>,
1641) -> InterpResult<'tcx, Option<(u64, u32)>> {
1642 match time.ok() {
1643 Some(time) => {
1644 let duration = system_time_to_duration(&time)?;
1645 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1646 }
1647 None => interp_ok(None),
1648 }
1649}
1650
1651fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str {
1652 #[cfg(unix)]
1653 use std::os::unix::fs::FileTypeExt;
1654
1655 if file_type.is_file() {
1656 "S_IFREG"
1657 } else if file_type.is_dir() {
1658 "S_IFDIR"
1659 } else if file_type.is_symlink() {
1660 "S_IFLNK"
1661 } else {
1662 #[cfg(unix)]
1664 {
1665 if file_type.is_socket() {
1666 return "S_IFSOCK";
1667 } else if file_type.is_fifo() {
1668 return "S_IFIFO";
1669 } else if file_type.is_char_device() {
1670 return "S_IFCHR";
1671 } else if file_type.is_block_device() {
1672 return "S_IFBLK";
1673 }
1674 }
1675 "S_IFREG"
1676 }
1677}
1678
1679struct FileMetadata {
1687 mode: Scalar,
1688 size: u64,
1689 created: Option<(u64, u32)>,
1690 accessed: Option<(u64, u32)>,
1691 modified: Option<(u64, u32)>,
1692 dev: Option<u64>,
1693 ino: Option<u64>,
1694 nlink: Option<u64>,
1695 uid: Option<u32>,
1696 gid: Option<u32>,
1697 blksize: Option<u64>,
1698 blocks: Option<u64>,
1699}
1700
1701impl FileMetadata {
1702 fn from_path<'tcx>(
1703 ecx: &mut MiriInterpCx<'tcx>,
1704 path: &Path,
1705 follow_symlink: bool,
1706 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1707 let metadata =
1708 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1709
1710 FileMetadata::from_meta(ecx, metadata)
1711 }
1712
1713 fn from_fd_num<'tcx>(
1714 ecx: &mut MiriInterpCx<'tcx>,
1715 fd_num: i32,
1716 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1717 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1718 return interp_ok(Err(LibcError("EBADF")));
1719 };
1720 match fd.metadata()? {
1721 Either::Left(host) => Self::from_meta(ecx, host),
1722 Either::Right(name) => Self::synthetic(ecx, name),
1723 }
1724 }
1725
1726 fn synthetic<'tcx>(
1727 ecx: &mut MiriInterpCx<'tcx>,
1728 mode_name: &str,
1729 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1730 let mode = ecx.eval_libc(mode_name);
1731 interp_ok(Ok(FileMetadata {
1732 mode,
1733 size: 0,
1734 created: None,
1735 accessed: None,
1736 modified: None,
1737 dev: None,
1738 uid: None,
1739 gid: None,
1740 blksize: None,
1741 blocks: None,
1742 ino: None,
1743 nlink: None,
1744 }))
1745 }
1746
1747 fn from_meta<'tcx>(
1748 ecx: &mut MiriInterpCx<'tcx>,
1749 metadata: Result<std::fs::Metadata, std::io::Error>,
1750 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1751 let metadata = match metadata {
1752 Ok(metadata) => metadata,
1753 Err(e) => {
1754 return interp_ok(Err(e.into()));
1755 }
1756 };
1757
1758 let file_type = metadata.file_type();
1759 let mode = ecx.eval_libc(file_type_to_mode_name(file_type));
1760
1761 let size = metadata.len();
1762
1763 let created = extract_sec_and_nsec(metadata.created())?;
1764 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1765 let modified = extract_sec_and_nsec(metadata.modified())?;
1766
1767 cfg_select! {
1770 unix => {
1771 use std::os::unix::fs::MetadataExt;
1772 let dev = metadata.dev();
1773 let ino = metadata.ino();
1774 let nlink = metadata.nlink();
1775 let uid = metadata.uid();
1776 let gid = metadata.gid();
1777 let blksize = metadata.blksize();
1778 let blocks = metadata.blocks();
1779
1780 interp_ok(Ok(FileMetadata {
1781 mode,
1782 size,
1783 created,
1784 accessed,
1785 modified,
1786 dev: Some(dev),
1787 ino: Some(ino),
1788 nlink: Some(nlink),
1789 uid: Some(uid),
1790 gid: Some(gid),
1791 blksize: Some(blksize),
1792 blocks: Some(blocks),
1793 }))
1794 }
1795 _ => interp_ok(Ok(FileMetadata {
1796 mode,
1797 size,
1798 created,
1799 accessed,
1800 modified,
1801 dev: None,
1802 ino: None,
1803 nlink: None,
1804 uid: None,
1805 gid: None,
1806 blksize: None,
1807 blocks: None,
1808 })),
1809 }
1810 }
1811}