1use std::borrow::Cow;
4use std::fs::{
5 DirBuilder, File, FileType, OpenOptions, ReadDir, TryLockError, read_dir, remove_dir,
6 remove_file, rename,
7};
8use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
9use std::path::{Path, PathBuf};
10use std::time::SystemTime;
11
12use rustc_abi::Size;
13use rustc_data_structures::fx::FxHashMap;
14use rustc_target::spec::Os;
15
16use self::shims::time::system_time_to_duration;
17use crate::shims::files::FileHandle;
18use crate::shims::os_str::bytes_to_os_str;
19use crate::shims::sig::check_min_vararg_count;
20use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
21use crate::*;
22
23impl UnixFileDescription for FileHandle {
24 fn pread<'tcx>(
25 &self,
26 communicate_allowed: bool,
27 offset: u64,
28 ptr: Pointer,
29 len: usize,
30 ecx: &mut MiriInterpCx<'tcx>,
31 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
32 ) -> InterpResult<'tcx> {
33 assert!(communicate_allowed, "isolation should have prevented even opening a file");
34 let mut bytes = vec![0; len];
35 let file = &mut &self.file;
39 let mut f = || {
40 let cursor_pos = file.stream_position()?;
41 file.seek(SeekFrom::Start(offset))?;
42 let res = file.read(&mut bytes);
43 file.seek(SeekFrom::Start(cursor_pos))
45 .expect("failed to restore file position, this shouldn't be possible");
46 res
47 };
48 let result = match f() {
49 Ok(read_size) => {
50 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
54 Ok(read_size)
55 }
56 Err(e) => Err(IoError::HostError(e)),
57 };
58 finish.call(ecx, result)
59 }
60
61 fn pwrite<'tcx>(
62 &self,
63 communicate_allowed: bool,
64 ptr: Pointer,
65 len: usize,
66 offset: u64,
67 ecx: &mut MiriInterpCx<'tcx>,
68 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
69 ) -> InterpResult<'tcx> {
70 assert!(communicate_allowed, "isolation should have prevented even opening a file");
71 let file = &mut &self.file;
75 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
76 let mut f = || {
77 let cursor_pos = file.stream_position()?;
78 file.seek(SeekFrom::Start(offset))?;
79 let res = file.write(bytes);
80 file.seek(SeekFrom::Start(cursor_pos))
82 .expect("failed to restore file position, this shouldn't be possible");
83 res
84 };
85 let result = f();
86 finish.call(ecx, result.map_err(IoError::HostError))
87 }
88
89 fn flock<'tcx>(
90 &self,
91 communicate_allowed: bool,
92 op: FlockOp,
93 ) -> InterpResult<'tcx, io::Result<()>> {
94 assert!(communicate_allowed, "isolation should have prevented even opening a file");
95
96 use FlockOp::*;
97 let (res, nonblocking) = match op {
99 SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
100 ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
101 Unlock => {
102 return interp_ok(self.file.unlock());
103 }
104 };
105
106 match res {
107 Ok(()) => interp_ok(Ok(())),
108 Err(TryLockError::Error(err)) => interp_ok(Err(err)),
109 Err(TryLockError::WouldBlock) =>
110 if nonblocking {
111 interp_ok(Err(ErrorKind::WouldBlock.into()))
112 } else {
113 throw_unsup_format!("blocking `flock` is not currently supported");
114 },
115 }
116 }
117}
118
119impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
120trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
121 fn macos_fbsd_solarish_write_stat_buf(
122 &mut self,
123 metadata: FileMetadata,
124 buf_op: &OpTy<'tcx>,
125 ) -> InterpResult<'tcx, i32> {
126 let this = self.eval_context_mut();
127
128 let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
129 let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
130 let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
131 let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
132
133 let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
134 this.write_int_fields_named(
135 &[
136 ("st_dev", metadata.dev.into()),
137 ("st_mode", mode.try_into().unwrap()),
138 ("st_nlink", 0),
139 ("st_ino", 0),
140 ("st_uid", metadata.uid.into()),
141 ("st_gid", metadata.gid.into()),
142 ("st_rdev", 0),
143 ("st_atime", access_sec.into()),
144 ("st_mtime", modified_sec.into()),
145 ("st_ctime", 0),
146 ("st_size", metadata.size.into()),
147 ("st_blocks", 0),
148 ("st_blksize", 0),
149 ],
150 &buf,
151 )?;
152
153 if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
154 this.write_int_fields_named(
155 &[
156 ("st_atime_nsec", access_nsec.into()),
157 ("st_mtime_nsec", modified_nsec.into()),
158 ("st_ctime_nsec", 0),
159 ("st_birthtime", created_sec.into()),
160 ("st_birthtime_nsec", created_nsec.into()),
161 ("st_flags", 0),
162 ("st_gen", 0),
163 ],
164 &buf,
165 )?;
166 }
167
168 if matches!(&this.tcx.sess.target.os, Os::Solaris | Os::Illumos) {
169 let st_fstype = this.project_field_named(&buf, "st_fstype")?;
170 this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
172 }
173
174 interp_ok(0)
175 }
176
177 fn file_type_to_d_type(
178 &mut self,
179 file_type: std::io::Result<FileType>,
180 ) -> InterpResult<'tcx, i32> {
181 #[cfg(unix)]
182 use std::os::unix::fs::FileTypeExt;
183
184 let this = self.eval_context_mut();
185 match file_type {
186 Ok(file_type) => {
187 match () {
188 _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
189 _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
190 _ if file_type.is_symlink() =>
191 interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
192 #[cfg(unix)]
194 _ if file_type.is_block_device() =>
195 interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
196 #[cfg(unix)]
197 _ if file_type.is_char_device() =>
198 interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
199 #[cfg(unix)]
200 _ if file_type.is_fifo() =>
201 interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
202 #[cfg(unix)]
203 _ if file_type.is_socket() =>
204 interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
205 _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
207 }
208 }
209 Err(_) => {
210 interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
212 }
213 }
214 }
215}
216
217#[derive(Debug)]
219struct OpenDir {
220 read_dir: ReadDir,
222 entry: Option<Pointer>,
225}
226
227impl OpenDir {
228 fn new(read_dir: ReadDir) -> Self {
229 Self { read_dir, entry: None }
230 }
231}
232
233#[derive(Debug)]
237pub struct DirTable {
238 streams: FxHashMap<u64, OpenDir>,
248 next_id: u64,
250}
251
252impl DirTable {
253 #[expect(clippy::arithmetic_side_effects)]
254 fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
255 let id = self.next_id;
256 self.next_id += 1;
257 self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
258 id
259 }
260}
261
262impl Default for DirTable {
263 fn default() -> DirTable {
264 DirTable {
265 streams: FxHashMap::default(),
266 next_id: 1,
268 }
269 }
270}
271
272impl VisitProvenance for DirTable {
273 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
274 let DirTable { streams, next_id: _ } = self;
275
276 for dir in streams.values() {
277 dir.entry.visit_provenance(visit);
278 }
279 }
280}
281
282fn maybe_sync_file(
283 file: &File,
284 writable: bool,
285 operation: fn(&File) -> std::io::Result<()>,
286) -> std::io::Result<i32> {
287 if !writable && cfg!(windows) {
288 Ok(0i32)
292 } else {
293 let result = operation(file);
294 result.map(|_| 0i32)
295 }
296}
297
298impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
299pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
300 fn open(
301 &mut self,
302 path_raw: &OpTy<'tcx>,
303 flag: &OpTy<'tcx>,
304 varargs: &[OpTy<'tcx>],
305 ) -> InterpResult<'tcx, Scalar> {
306 let this = self.eval_context_mut();
307
308 let path_raw = this.read_pointer(path_raw)?;
309 let path = this.read_path_from_c_str(path_raw)?;
310 let flag = this.read_scalar(flag)?.to_i32()?;
311
312 let mut options = OpenOptions::new();
313
314 let o_rdonly = this.eval_libc_i32("O_RDONLY");
315 let o_wronly = this.eval_libc_i32("O_WRONLY");
316 let o_rdwr = this.eval_libc_i32("O_RDWR");
317 if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
321 throw_unsup_format!("access mode flags on this target are unsupported");
322 }
323 let mut writable = true;
324
325 let access_mode = flag & 0b11;
327
328 if access_mode == o_rdonly {
329 writable = false;
330 options.read(true);
331 } else if access_mode == o_wronly {
332 options.write(true);
333 } else if access_mode == o_rdwr {
334 options.read(true).write(true);
335 } else {
336 throw_unsup_format!("unsupported access mode {:#x}", access_mode);
337 }
338 let mut mirror = access_mode;
342
343 let o_append = this.eval_libc_i32("O_APPEND");
344 if flag & o_append == o_append {
345 options.append(true);
346 mirror |= o_append;
347 }
348 let o_trunc = this.eval_libc_i32("O_TRUNC");
349 if flag & o_trunc == o_trunc {
350 options.truncate(true);
351 mirror |= o_trunc;
352 }
353 let o_creat = this.eval_libc_i32("O_CREAT");
354 if flag & o_creat == o_creat {
355 let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
359 let mode = this.read_scalar(mode)?.to_u32()?;
360
361 #[cfg(unix)]
362 {
363 use std::os::unix::fs::OpenOptionsExt;
365 options.mode(mode);
366 }
367 #[cfg(not(unix))]
368 {
369 if mode != 0o666 {
371 throw_unsup_format!(
372 "non-default mode 0o{:o} is not supported on non-Unix hosts",
373 mode
374 );
375 }
376 }
377
378 mirror |= o_creat;
379
380 let o_excl = this.eval_libc_i32("O_EXCL");
381 if flag & o_excl == o_excl {
382 mirror |= o_excl;
383 options.create_new(true);
384 } else {
385 options.create(true);
386 }
387 }
388 let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
389 if flag & o_cloexec == o_cloexec {
390 mirror |= o_cloexec;
393 }
394 if this.tcx.sess.target.os == Os::Linux {
395 let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
396 if flag & o_tmpfile == o_tmpfile {
397 return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
399 }
400 }
401
402 let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
403 if flag & o_nofollow == o_nofollow {
404 #[cfg(unix)]
405 {
406 use std::os::unix::fs::OpenOptionsExt;
407 options.custom_flags(libc::O_NOFOLLOW);
408 }
409 #[cfg(not(unix))]
413 {
414 if path.is_symlink() {
417 return this.set_last_error_and_return_i32(LibcError("ELOOP"));
418 }
419 }
420 mirror |= o_nofollow;
421 }
422
423 if flag != mirror {
426 throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
427 }
428
429 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
431 this.reject_in_isolation("`open`", reject_with)?;
432 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
433 }
434
435 let fd = options
436 .open(path)
437 .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
438
439 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
440 }
441
442 fn lseek64(
443 &mut self,
444 fd_num: i32,
445 offset: i128,
446 whence: i32,
447 dest: &MPlaceTy<'tcx>,
448 ) -> InterpResult<'tcx> {
449 let this = self.eval_context_mut();
450
451 let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
454 if offset < 0 {
455 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
457 } else {
458 SeekFrom::Start(u64::try_from(offset).unwrap())
459 }
460 } else if whence == this.eval_libc_i32("SEEK_CUR") {
461 SeekFrom::Current(i64::try_from(offset).unwrap())
462 } else if whence == this.eval_libc_i32("SEEK_END") {
463 SeekFrom::End(i64::try_from(offset).unwrap())
464 } else {
465 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
466 };
467
468 let communicate = this.machine.communicate();
469
470 let Some(fd) = this.machine.fds.get(fd_num) else {
471 return this.set_last_error_and_return(LibcError("EBADF"), dest);
472 };
473 let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
474 drop(fd);
475
476 let result = this.try_unwrap_io_result(result)?;
477 this.write_int(result, dest)?;
478 interp_ok(())
479 }
480
481 fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
482 let this = self.eval_context_mut();
483
484 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
485
486 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
488 this.reject_in_isolation("`unlink`", reject_with)?;
489 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
490 }
491
492 let result = remove_file(path).map(|_| 0);
493 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
494 }
495
496 fn symlink(
497 &mut self,
498 target_op: &OpTy<'tcx>,
499 linkpath_op: &OpTy<'tcx>,
500 ) -> InterpResult<'tcx, Scalar> {
501 #[cfg(unix)]
502 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
503 std::os::unix::fs::symlink(src, dst)
504 }
505
506 #[cfg(windows)]
507 fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
508 use std::os::windows::fs;
509 if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
510 }
511
512 let this = self.eval_context_mut();
513 let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
514 let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
515
516 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
518 this.reject_in_isolation("`symlink`", reject_with)?;
519 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
520 }
521
522 let result = create_link(&target, &linkpath).map(|_| 0);
523 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
524 }
525
526 fn macos_fbsd_solarish_stat(
527 &mut self,
528 path_op: &OpTy<'tcx>,
529 buf_op: &OpTy<'tcx>,
530 ) -> InterpResult<'tcx, Scalar> {
531 let this = self.eval_context_mut();
532
533 if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
534 {
535 panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
536 }
537
538 let path_scalar = this.read_pointer(path_op)?;
539 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
540
541 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
543 this.reject_in_isolation("`stat`", reject_with)?;
544 return this.set_last_error_and_return_i32(LibcError("EACCES"));
545 }
546
547 let metadata = match FileMetadata::from_path(this, &path, true)? {
549 Ok(metadata) => metadata,
550 Err(err) => return this.set_last_error_and_return_i32(err),
551 };
552
553 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
554 }
555
556 fn macos_fbsd_solarish_lstat(
558 &mut self,
559 path_op: &OpTy<'tcx>,
560 buf_op: &OpTy<'tcx>,
561 ) -> InterpResult<'tcx, Scalar> {
562 let this = self.eval_context_mut();
563
564 if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
565 {
566 panic!(
567 "`macos_fbsd_solaris_lstat` should not be called on {}",
568 this.tcx.sess.target.os
569 );
570 }
571
572 let path_scalar = this.read_pointer(path_op)?;
573 let path = this.read_path_from_c_str(path_scalar)?.into_owned();
574
575 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
577 this.reject_in_isolation("`lstat`", reject_with)?;
578 return this.set_last_error_and_return_i32(LibcError("EACCES"));
579 }
580
581 let metadata = match FileMetadata::from_path(this, &path, false)? {
582 Ok(metadata) => metadata,
583 Err(err) => return this.set_last_error_and_return_i32(err),
584 };
585
586 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
587 }
588
589 fn macos_fbsd_solarish_fstat(
590 &mut self,
591 fd_op: &OpTy<'tcx>,
592 buf_op: &OpTy<'tcx>,
593 ) -> InterpResult<'tcx, Scalar> {
594 let this = self.eval_context_mut();
595
596 if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
597 {
598 panic!(
599 "`macos_fbsd_solaris_fstat` should not be called on {}",
600 this.tcx.sess.target.os
601 );
602 }
603
604 let fd = this.read_scalar(fd_op)?.to_i32()?;
605
606 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
608 this.reject_in_isolation("`fstat`", reject_with)?;
609 return this.set_last_error_and_return_i32(LibcError("EBADF"));
611 }
612
613 let metadata = match FileMetadata::from_fd_num(this, fd)? {
614 Ok(metadata) => metadata,
615 Err(err) => return this.set_last_error_and_return_i32(err),
616 };
617 interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
618 }
619
620 fn linux_statx(
621 &mut self,
622 dirfd_op: &OpTy<'tcx>, pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>, mask_op: &OpTy<'tcx>, statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
628 let this = self.eval_context_mut();
629
630 this.assert_target_os(Os::Linux, "statx");
631
632 let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
633 let pathname_ptr = this.read_pointer(pathname_op)?;
634 let flags = this.read_scalar(flags_op)?.to_i32()?;
635 let _mask = this.read_scalar(mask_op)?.to_u32()?;
636 let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
637
638 if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
640 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
641 }
642
643 let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
644
645 let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
646 let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
648 let empty_path_flag = flags & at_empty_path == at_empty_path;
649 if !(path.is_absolute()
657 || dirfd == this.eval_libc_i32("AT_FDCWD")
658 || (path.as_os_str().is_empty() && empty_path_flag))
659 {
660 throw_unsup_format!(
661 "using statx is only supported with absolute paths, relative paths with the file \
662 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
663 file descriptor"
664 )
665 }
666
667 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
669 this.reject_in_isolation("`statx`", reject_with)?;
670 let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
671 LibcError("EACCES")
674 } else {
675 assert!(empty_path_flag);
679 LibcError("EBADF")
680 };
681 return this.set_last_error_and_return_i32(ecode);
682 }
683
684 let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
689
690 let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
693
694 let metadata = if path.as_os_str().is_empty() && empty_path_flag {
697 FileMetadata::from_fd_num(this, dirfd)?
698 } else {
699 FileMetadata::from_path(this, &path, follow_symlink)?
700 };
701 let metadata = match metadata {
702 Ok(metadata) => metadata,
703 Err(err) => return this.set_last_error_and_return_i32(err),
704 };
705
706 let mode: u16 = metadata
711 .mode
712 .to_u32()?
713 .try_into()
714 .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
715
716 let (access_sec, access_nsec) = metadata
719 .accessed
720 .map(|tup| {
721 mask |= this.eval_libc_u32("STATX_ATIME");
722 interp_ok(tup)
723 })
724 .unwrap_or_else(|| interp_ok((0, 0)))?;
725
726 let (created_sec, created_nsec) = metadata
727 .created
728 .map(|tup| {
729 mask |= this.eval_libc_u32("STATX_BTIME");
730 interp_ok(tup)
731 })
732 .unwrap_or_else(|| interp_ok((0, 0)))?;
733
734 let (modified_sec, modified_nsec) = metadata
735 .modified
736 .map(|tup| {
737 mask |= this.eval_libc_u32("STATX_MTIME");
738 interp_ok(tup)
739 })
740 .unwrap_or_else(|| interp_ok((0, 0)))?;
741
742 this.write_int_fields_named(
744 &[
745 ("stx_mask", mask.into()),
746 ("stx_blksize", 0),
747 ("stx_attributes", 0),
748 ("stx_nlink", 0),
749 ("stx_uid", 0),
750 ("stx_gid", 0),
751 ("stx_mode", mode.into()),
752 ("stx_ino", 0),
753 ("stx_size", metadata.size.into()),
754 ("stx_blocks", 0),
755 ("stx_attributes_mask", 0),
756 ("stx_rdev_major", 0),
757 ("stx_rdev_minor", 0),
758 ("stx_dev_major", 0),
759 ("stx_dev_minor", 0),
760 ],
761 &statxbuf,
762 )?;
763 #[rustfmt::skip]
764 this.write_int_fields_named(
765 &[
766 ("tv_sec", access_sec.into()),
767 ("tv_nsec", access_nsec.into()),
768 ],
769 &this.project_field_named(&statxbuf, "stx_atime")?,
770 )?;
771 #[rustfmt::skip]
772 this.write_int_fields_named(
773 &[
774 ("tv_sec", created_sec.into()),
775 ("tv_nsec", created_nsec.into()),
776 ],
777 &this.project_field_named(&statxbuf, "stx_btime")?,
778 )?;
779 #[rustfmt::skip]
780 this.write_int_fields_named(
781 &[
782 ("tv_sec", 0.into()),
783 ("tv_nsec", 0.into()),
784 ],
785 &this.project_field_named(&statxbuf, "stx_ctime")?,
786 )?;
787 #[rustfmt::skip]
788 this.write_int_fields_named(
789 &[
790 ("tv_sec", modified_sec.into()),
791 ("tv_nsec", modified_nsec.into()),
792 ],
793 &this.project_field_named(&statxbuf, "stx_mtime")?,
794 )?;
795
796 interp_ok(Scalar::from_i32(0))
797 }
798
799 fn rename(
800 &mut self,
801 oldpath_op: &OpTy<'tcx>,
802 newpath_op: &OpTy<'tcx>,
803 ) -> InterpResult<'tcx, Scalar> {
804 let this = self.eval_context_mut();
805
806 let oldpath_ptr = this.read_pointer(oldpath_op)?;
807 let newpath_ptr = this.read_pointer(newpath_op)?;
808
809 if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
810 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
811 }
812
813 let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
814 let newpath = this.read_path_from_c_str(newpath_ptr)?;
815
816 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
818 this.reject_in_isolation("`rename`", reject_with)?;
819 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
820 }
821
822 let result = rename(oldpath, newpath).map(|_| 0);
823
824 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
825 }
826
827 fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
828 let this = self.eval_context_mut();
829
830 #[cfg_attr(not(unix), allow(unused_variables))]
831 let mode = if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
832 u32::from(this.read_scalar(mode_op)?.to_u16()?)
833 } else {
834 this.read_scalar(mode_op)?.to_u32()?
835 };
836
837 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
838
839 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
841 this.reject_in_isolation("`mkdir`", reject_with)?;
842 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
843 }
844
845 #[cfg_attr(not(unix), allow(unused_mut))]
846 let mut builder = DirBuilder::new();
847
848 #[cfg(unix)]
851 {
852 use std::os::unix::fs::DirBuilderExt;
853 builder.mode(mode);
854 }
855
856 let result = builder.create(path).map(|_| 0i32);
857
858 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
859 }
860
861 fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
862 let this = self.eval_context_mut();
863
864 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
865
866 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
868 this.reject_in_isolation("`rmdir`", reject_with)?;
869 return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
870 }
871
872 let result = remove_dir(path).map(|_| 0i32);
873
874 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
875 }
876
877 fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
878 let this = self.eval_context_mut();
879
880 let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
881
882 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
884 this.reject_in_isolation("`opendir`", reject_with)?;
885 this.set_last_error(LibcError("EACCES"))?;
886 return interp_ok(Scalar::null_ptr(this));
887 }
888
889 let result = read_dir(name);
890
891 match result {
892 Ok(dir_iter) => {
893 let id = this.machine.dirs.insert_new(dir_iter);
894
895 interp_ok(Scalar::from_target_usize(id, this))
899 }
900 Err(e) => {
901 this.set_last_error(e)?;
902 interp_ok(Scalar::null_ptr(this))
903 }
904 }
905 }
906
907 fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
908 let this = self.eval_context_mut();
909
910 if !matches!(&this.tcx.sess.target.os, Os::Linux | Os::Solaris | Os::Illumos | Os::FreeBsd)
911 {
912 panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
913 }
914
915 let dirp = this.read_target_usize(dirp_op)?;
916
917 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
919 this.reject_in_isolation("`readdir`", reject_with)?;
920 this.set_last_error(LibcError("EBADF"))?;
921 return interp_ok(Scalar::null_ptr(this));
922 }
923
924 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
925 err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
926 })?;
927
928 let entry = match open_dir.read_dir.next() {
929 Some(Ok(dir_entry)) => {
930 #[cfg(unix)]
933 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
934 #[cfg(not(unix))]
935 let ino = 0u64;
936
937 let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
971 let name_len = u64::try_from(name_bytes.len()).unwrap();
972
973 let dirent_layout = this.libc_ty_layout(dirent_type);
974 let fields = &dirent_layout.fields;
975 let last_field = fields.count().strict_sub(1);
976 let d_name_offset = fields.offset(last_field).bytes();
977 let size = d_name_offset.strict_add(name_len);
978
979 let entry = this.allocate_ptr(
980 Size::from_bytes(size),
981 dirent_layout.align.abi,
982 MiriMemoryKind::Runtime.into(),
983 AllocInit::Uninit,
984 )?;
985 let entry = this.ptr_to_mplace(entry.into(), dirent_layout);
986
987 let ino_name =
989 if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" };
990 this.write_int_fields_named(
991 &[(ino_name, ino.into()), ("d_reclen", size.into())],
992 &entry,
993 )?;
994
995 if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? {
997 this.write_null(&d_off)?;
998 }
999
1000 if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? {
1001 this.write_int(name_len.strict_sub(1), &d_namlen)?;
1002 }
1003
1004 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1005 if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? {
1006 this.write_int(file_type, &d_type)?;
1007 }
1008
1009 let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this);
1011 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1012
1013 Some(entry.ptr())
1014 }
1015 None => {
1016 None
1018 }
1019 Some(Err(e)) => {
1020 this.set_last_error(e)?;
1021 None
1022 }
1023 };
1024
1025 let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1026 let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1027 if let Some(old_entry) = old_entry {
1028 this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1029 }
1030
1031 interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1032 }
1033
1034 fn macos_fbsd_readdir_r(
1035 &mut self,
1036 dirp_op: &OpTy<'tcx>,
1037 entry_op: &OpTy<'tcx>,
1038 result_op: &OpTy<'tcx>,
1039 ) -> InterpResult<'tcx, Scalar> {
1040 let this = self.eval_context_mut();
1041
1042 if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) {
1043 panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1044 }
1045
1046 let dirp = this.read_target_usize(dirp_op)?;
1047 let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1048
1049 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1051 this.reject_in_isolation("`readdir_r`", reject_with)?;
1052 return interp_ok(this.eval_libc("EBADF"));
1054 }
1055
1056 let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1057 err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1058 })?;
1059 interp_ok(match open_dir.read_dir.next() {
1060 Some(Ok(dir_entry)) => {
1061 let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1076 let name_place = this.project_field_named(&entry_place, "d_name")?;
1077
1078 let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1080 &file_name,
1081 name_place.ptr(),
1082 name_place.layout.size.bytes(),
1083 )?;
1084 let file_name_len = file_name_buf_len.strict_sub(1);
1085 if !name_fits {
1086 throw_unsup_format!(
1087 "a directory entry had a name too large to fit in libc::dirent"
1088 );
1089 }
1090
1091 #[cfg(unix)]
1094 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1095 #[cfg(not(unix))]
1096 let ino = 0u64;
1097
1098 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1099
1100 this.write_int_fields_named(
1102 &[
1103 ("d_reclen", 0),
1104 ("d_namlen", file_name_len.into()),
1105 ("d_type", file_type.into()),
1106 ],
1107 &entry_place,
1108 )?;
1109 match this.tcx.sess.target.os {
1111 Os::MacOs => {
1112 #[rustfmt::skip]
1113 this.write_int_fields_named(
1114 &[
1115 ("d_ino", ino.into()),
1116 ("d_seekoff", 0),
1117 ],
1118 &entry_place,
1119 )?;
1120 }
1121 Os::FreeBsd => {
1122 #[rustfmt::skip]
1123 this.write_int_fields_named(
1124 &[
1125 ("d_fileno", ino.into()),
1126 ("d_off", 0),
1127 ],
1128 &entry_place,
1129 )?;
1130 }
1131 _ => unreachable!(),
1132 }
1133 this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1134
1135 Scalar::from_i32(0)
1136 }
1137 None => {
1138 this.write_null(&result_place)?;
1140 Scalar::from_i32(0)
1141 }
1142 Some(Err(e)) => {
1143 this.io_error_to_errnum(e)?
1145 }
1146 })
1147 }
1148
1149 fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1150 let this = self.eval_context_mut();
1151
1152 let dirp = this.read_target_usize(dirp_op)?;
1153
1154 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1156 this.reject_in_isolation("`closedir`", reject_with)?;
1157 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1158 }
1159
1160 let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1161 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1162 };
1163 if let Some(entry) = open_dir.entry.take() {
1164 this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1165 }
1166 drop(open_dir);
1168
1169 interp_ok(Scalar::from_i32(0))
1170 }
1171
1172 fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1173 let this = self.eval_context_mut();
1174
1175 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1177 this.reject_in_isolation("`ftruncate64`", reject_with)?;
1178 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1180 }
1181
1182 let Some(fd) = this.machine.fds.get(fd_num) else {
1183 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1184 };
1185
1186 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1188 err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1189 })?;
1190
1191 if file.writable {
1192 if let Ok(length) = length.try_into() {
1193 let result = file.file.set_len(length);
1194 let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1195 interp_ok(Scalar::from_i32(result))
1196 } else {
1197 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1198 }
1199 } else {
1200 this.set_last_error_and_return_i32(LibcError("EINVAL"))
1202 }
1203 }
1204
1205 fn posix_fallocate(
1208 &mut self,
1209 fd_num: i32,
1210 offset: i64,
1211 len: i64,
1212 ) -> InterpResult<'tcx, Scalar> {
1213 let this = self.eval_context_mut();
1214
1215 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1217 this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1218 return interp_ok(this.eval_libc("EBADF"));
1220 }
1221
1222 if offset < 0 || len <= 0 {
1224 return interp_ok(this.eval_libc("EINVAL"));
1225 }
1226
1227 let Some(fd) = this.machine.fds.get(fd_num) else {
1229 return interp_ok(this.eval_libc("EBADF"));
1230 };
1231 let file = match fd.downcast::<FileHandle>() {
1232 Some(file_handle) => file_handle,
1233 None => return interp_ok(this.eval_libc("ENODEV")),
1235 };
1236
1237 if !file.writable {
1238 return interp_ok(this.eval_libc("EBADF"));
1240 }
1241
1242 let current_size = match file.file.metadata() {
1243 Ok(metadata) => metadata.len(),
1244 Err(err) => return this.io_error_to_errnum(err),
1245 };
1246 let new_size = match offset.checked_add(len) {
1248 Some(new_size) => u64::try_from(new_size).unwrap(),
1250 None => return interp_ok(this.eval_libc("EFBIG")), };
1252 if current_size < new_size {
1255 interp_ok(match file.file.set_len(new_size) {
1256 Ok(()) => Scalar::from_i32(0),
1257 Err(e) => this.io_error_to_errnum(e)?,
1258 })
1259 } else {
1260 interp_ok(Scalar::from_i32(0))
1261 }
1262 }
1263
1264 fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1265 let this = self.eval_context_mut();
1271
1272 let fd = this.read_scalar(fd_op)?.to_i32()?;
1273
1274 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1276 this.reject_in_isolation("`fsync`", reject_with)?;
1277 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1279 }
1280
1281 self.ffullsync_fd(fd)
1282 }
1283
1284 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1285 let this = self.eval_context_mut();
1286 let Some(fd) = this.machine.fds.get(fd_num) else {
1287 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1288 };
1289 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1291 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1292 })?;
1293 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1294 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1295 }
1296
1297 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1298 let this = self.eval_context_mut();
1299
1300 let fd = this.read_scalar(fd_op)?.to_i32()?;
1301
1302 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1304 this.reject_in_isolation("`fdatasync`", reject_with)?;
1305 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1307 }
1308
1309 let Some(fd) = this.machine.fds.get(fd) else {
1310 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1311 };
1312 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1314 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1315 })?;
1316 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1317 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1318 }
1319
1320 fn sync_file_range(
1321 &mut self,
1322 fd_op: &OpTy<'tcx>,
1323 offset_op: &OpTy<'tcx>,
1324 nbytes_op: &OpTy<'tcx>,
1325 flags_op: &OpTy<'tcx>,
1326 ) -> InterpResult<'tcx, Scalar> {
1327 let this = self.eval_context_mut();
1328
1329 let fd = this.read_scalar(fd_op)?.to_i32()?;
1330 let offset = this.read_scalar(offset_op)?.to_i64()?;
1331 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1332 let flags = this.read_scalar(flags_op)?.to_i32()?;
1333
1334 if offset < 0 || nbytes < 0 {
1335 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1336 }
1337 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1338 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1339 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1340 if flags & allowed_flags != flags {
1341 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1342 }
1343
1344 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1346 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1347 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1349 }
1350
1351 let Some(fd) = this.machine.fds.get(fd) else {
1352 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1353 };
1354 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1356 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1357 })?;
1358 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1359 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1360 }
1361
1362 fn readlink(
1363 &mut self,
1364 pathname_op: &OpTy<'tcx>,
1365 buf_op: &OpTy<'tcx>,
1366 bufsize_op: &OpTy<'tcx>,
1367 ) -> InterpResult<'tcx, i64> {
1368 let this = self.eval_context_mut();
1369
1370 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1371 let buf = this.read_pointer(buf_op)?;
1372 let bufsize = this.read_target_usize(bufsize_op)?;
1373
1374 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1376 this.reject_in_isolation("`readlink`", reject_with)?;
1377 this.set_last_error(LibcError("EACCES"))?;
1378 return interp_ok(-1);
1379 }
1380
1381 let result = std::fs::read_link(pathname);
1382 match result {
1383 Ok(resolved) => {
1384 let resolved = this.convert_path(
1388 Cow::Borrowed(resolved.as_ref()),
1389 crate::shims::os_str::PathConversion::HostToTarget,
1390 );
1391 let mut path_bytes = resolved.as_encoded_bytes();
1392 let bufsize: usize = bufsize.try_into().unwrap();
1393 if path_bytes.len() > bufsize {
1394 path_bytes = &path_bytes[..bufsize]
1395 }
1396 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1397 interp_ok(path_bytes.len().try_into().unwrap())
1398 }
1399 Err(e) => {
1400 this.set_last_error(e)?;
1401 interp_ok(-1)
1402 }
1403 }
1404 }
1405
1406 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1407 let this = self.eval_context_mut();
1408 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1411 let error = if let Some(fd) = this.machine.fds.get(fd) {
1412 if fd.is_tty(this.machine.communicate()) {
1413 return interp_ok(Scalar::from_i32(1));
1414 } else {
1415 LibcError("ENOTTY")
1416 }
1417 } else {
1418 LibcError("EBADF")
1420 };
1421 this.set_last_error(error)?;
1422 interp_ok(Scalar::from_i32(0))
1423 }
1424
1425 fn realpath(
1426 &mut self,
1427 path_op: &OpTy<'tcx>,
1428 processed_path_op: &OpTy<'tcx>,
1429 ) -> InterpResult<'tcx, Scalar> {
1430 let this = self.eval_context_mut();
1431 this.assert_target_os_is_unix("realpath");
1432
1433 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1434 let processed_ptr = this.read_pointer(processed_path_op)?;
1435
1436 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1438 this.reject_in_isolation("`realpath`", reject_with)?;
1439 this.set_last_error(LibcError("EACCES"))?;
1440 return interp_ok(Scalar::from_target_usize(0, this));
1441 }
1442
1443 let result = std::fs::canonicalize(pathname);
1444 match result {
1445 Ok(resolved) => {
1446 let path_max = this
1447 .eval_libc_i32("PATH_MAX")
1448 .try_into()
1449 .expect("PATH_MAX does not fit in u64");
1450 let dest = if this.ptr_is_null(processed_ptr)? {
1451 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1461 } else {
1462 let (wrote_path, _) =
1463 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1464
1465 if !wrote_path {
1466 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1470 return interp_ok(Scalar::from_target_usize(0, this));
1471 }
1472 processed_ptr
1473 };
1474
1475 interp_ok(Scalar::from_maybe_pointer(dest, this))
1476 }
1477 Err(e) => {
1478 this.set_last_error(e)?;
1479 interp_ok(Scalar::from_target_usize(0, this))
1480 }
1481 }
1482 }
1483 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1484 use rand::seq::IndexedRandom;
1485
1486 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1488
1489 let this = self.eval_context_mut();
1490 this.assert_target_os_is_unix("mkstemp");
1491
1492 let max_attempts = this.eval_libc_u32("TMP_MAX");
1502
1503 let template_ptr = this.read_pointer(template_op)?;
1506 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1507 let template_bytes = template.as_mut_slice();
1508
1509 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1511 this.reject_in_isolation("`mkstemp`", reject_with)?;
1512 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1513 }
1514
1515 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1517
1518 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1523 let end_pos = template_bytes.len();
1524 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1525
1526 if last_six_char_bytes != suffix_bytes {
1528 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1529 }
1530
1531 const SUBSTITUTIONS: &[char; 62] = &[
1535 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1536 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1537 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1538 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1539 ];
1540
1541 let mut fopts = OpenOptions::new();
1544 fopts.read(true).write(true).create_new(true);
1545
1546 #[cfg(unix)]
1547 {
1548 use std::os::unix::fs::OpenOptionsExt;
1549 fopts.mode(0o600);
1551 fopts.custom_flags(libc::O_EXCL);
1552 }
1553 #[cfg(windows)]
1554 {
1555 use std::os::windows::fs::OpenOptionsExt;
1556 fopts.share_mode(0);
1558 }
1559
1560 for _ in 0..max_attempts {
1562 let rng = this.machine.rng.get_mut();
1563
1564 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1566
1567 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1569
1570 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1572
1573 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1575
1576 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1577
1578 let file = fopts.open(possibly_unique);
1579
1580 match file {
1581 Ok(f) => {
1582 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1583 return interp_ok(Scalar::from_i32(fd));
1584 }
1585 Err(e) =>
1586 match e.kind() {
1587 ErrorKind::AlreadyExists => continue,
1589 _ => {
1591 return this.set_last_error_and_return_i32(e);
1594 }
1595 },
1596 }
1597 }
1598
1599 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1601 }
1602}
1603
1604fn extract_sec_and_nsec<'tcx>(
1608 time: std::io::Result<SystemTime>,
1609) -> InterpResult<'tcx, Option<(u64, u32)>> {
1610 match time.ok() {
1611 Some(time) => {
1612 let duration = system_time_to_duration(&time)?;
1613 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1614 }
1615 None => interp_ok(None),
1616 }
1617}
1618
1619struct FileMetadata {
1622 mode: Scalar,
1623 size: u64,
1624 created: Option<(u64, u32)>,
1625 accessed: Option<(u64, u32)>,
1626 modified: Option<(u64, u32)>,
1627 dev: u64,
1628 uid: u32,
1629 gid: u32,
1630}
1631
1632impl FileMetadata {
1633 fn from_path<'tcx>(
1634 ecx: &mut MiriInterpCx<'tcx>,
1635 path: &Path,
1636 follow_symlink: bool,
1637 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1638 let metadata =
1639 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1640
1641 FileMetadata::from_meta(ecx, metadata)
1642 }
1643
1644 fn from_fd_num<'tcx>(
1645 ecx: &mut MiriInterpCx<'tcx>,
1646 fd_num: i32,
1647 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1648 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1649 return interp_ok(Err(LibcError("EBADF")));
1650 };
1651
1652 let metadata = fd.metadata()?;
1653 drop(fd);
1654 FileMetadata::from_meta(ecx, metadata)
1655 }
1656
1657 fn from_meta<'tcx>(
1658 ecx: &mut MiriInterpCx<'tcx>,
1659 metadata: Result<std::fs::Metadata, std::io::Error>,
1660 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1661 let metadata = match metadata {
1662 Ok(metadata) => metadata,
1663 Err(e) => {
1664 return interp_ok(Err(e.into()));
1665 }
1666 };
1667
1668 let file_type = metadata.file_type();
1669
1670 let mode_name = if file_type.is_file() {
1671 "S_IFREG"
1672 } else if file_type.is_dir() {
1673 "S_IFDIR"
1674 } else {
1675 "S_IFLNK"
1676 };
1677
1678 let mode = ecx.eval_libc(mode_name);
1679
1680 let size = metadata.len();
1681
1682 let created = extract_sec_and_nsec(metadata.created())?;
1683 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1684 let modified = extract_sec_and_nsec(metadata.modified())?;
1685
1686 cfg_select! {
1689 unix => {
1690 use std::os::unix::fs::MetadataExt;
1691 let dev = metadata.dev();
1692 let uid = metadata.uid();
1693 let gid = metadata.gid();
1694 }
1695 _ => {
1696 let dev = 0;
1697 let uid = 0;
1698 let gid = 0;
1699 }
1700 }
1701
1702 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1703 }
1704}