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 fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1206 let this = self.eval_context_mut();
1212
1213 let fd = this.read_scalar(fd_op)?.to_i32()?;
1214
1215 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1217 this.reject_in_isolation("`fsync`", reject_with)?;
1218 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1220 }
1221
1222 self.ffullsync_fd(fd)
1223 }
1224
1225 fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1226 let this = self.eval_context_mut();
1227 let Some(fd) = this.machine.fds.get(fd_num) else {
1228 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1229 };
1230 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1232 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1233 })?;
1234 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1235 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1236 }
1237
1238 fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1239 let this = self.eval_context_mut();
1240
1241 let fd = this.read_scalar(fd_op)?.to_i32()?;
1242
1243 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1245 this.reject_in_isolation("`fdatasync`", reject_with)?;
1246 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1248 }
1249
1250 let Some(fd) = this.machine.fds.get(fd) else {
1251 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1252 };
1253 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1255 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1256 })?;
1257 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1258 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1259 }
1260
1261 fn sync_file_range(
1262 &mut self,
1263 fd_op: &OpTy<'tcx>,
1264 offset_op: &OpTy<'tcx>,
1265 nbytes_op: &OpTy<'tcx>,
1266 flags_op: &OpTy<'tcx>,
1267 ) -> InterpResult<'tcx, Scalar> {
1268 let this = self.eval_context_mut();
1269
1270 let fd = this.read_scalar(fd_op)?.to_i32()?;
1271 let offset = this.read_scalar(offset_op)?.to_i64()?;
1272 let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1273 let flags = this.read_scalar(flags_op)?.to_i32()?;
1274
1275 if offset < 0 || nbytes < 0 {
1276 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1277 }
1278 let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1279 | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1280 | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1281 if flags & allowed_flags != flags {
1282 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1283 }
1284
1285 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1287 this.reject_in_isolation("`sync_file_range`", reject_with)?;
1288 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1290 }
1291
1292 let Some(fd) = this.machine.fds.get(fd) else {
1293 return this.set_last_error_and_return_i32(LibcError("EBADF"));
1294 };
1295 let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1297 err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1298 })?;
1299 let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1300 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1301 }
1302
1303 fn readlink(
1304 &mut self,
1305 pathname_op: &OpTy<'tcx>,
1306 buf_op: &OpTy<'tcx>,
1307 bufsize_op: &OpTy<'tcx>,
1308 ) -> InterpResult<'tcx, i64> {
1309 let this = self.eval_context_mut();
1310
1311 let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1312 let buf = this.read_pointer(buf_op)?;
1313 let bufsize = this.read_target_usize(bufsize_op)?;
1314
1315 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1317 this.reject_in_isolation("`readlink`", reject_with)?;
1318 this.set_last_error(LibcError("EACCES"))?;
1319 return interp_ok(-1);
1320 }
1321
1322 let result = std::fs::read_link(pathname);
1323 match result {
1324 Ok(resolved) => {
1325 let resolved = this.convert_path(
1329 Cow::Borrowed(resolved.as_ref()),
1330 crate::shims::os_str::PathConversion::HostToTarget,
1331 );
1332 let mut path_bytes = resolved.as_encoded_bytes();
1333 let bufsize: usize = bufsize.try_into().unwrap();
1334 if path_bytes.len() > bufsize {
1335 path_bytes = &path_bytes[..bufsize]
1336 }
1337 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1338 interp_ok(path_bytes.len().try_into().unwrap())
1339 }
1340 Err(e) => {
1341 this.set_last_error(e)?;
1342 interp_ok(-1)
1343 }
1344 }
1345 }
1346
1347 fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1348 let this = self.eval_context_mut();
1349 let fd = this.read_scalar(miri_fd)?.to_i32()?;
1352 let error = if let Some(fd) = this.machine.fds.get(fd) {
1353 if fd.is_tty(this.machine.communicate()) {
1354 return interp_ok(Scalar::from_i32(1));
1355 } else {
1356 LibcError("ENOTTY")
1357 }
1358 } else {
1359 LibcError("EBADF")
1361 };
1362 this.set_last_error(error)?;
1363 interp_ok(Scalar::from_i32(0))
1364 }
1365
1366 fn realpath(
1367 &mut self,
1368 path_op: &OpTy<'tcx>,
1369 processed_path_op: &OpTy<'tcx>,
1370 ) -> InterpResult<'tcx, Scalar> {
1371 let this = self.eval_context_mut();
1372 this.assert_target_os_is_unix("realpath");
1373
1374 let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1375 let processed_ptr = this.read_pointer(processed_path_op)?;
1376
1377 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1379 this.reject_in_isolation("`realpath`", reject_with)?;
1380 this.set_last_error(LibcError("EACCES"))?;
1381 return interp_ok(Scalar::from_target_usize(0, this));
1382 }
1383
1384 let result = std::fs::canonicalize(pathname);
1385 match result {
1386 Ok(resolved) => {
1387 let path_max = this
1388 .eval_libc_i32("PATH_MAX")
1389 .try_into()
1390 .expect("PATH_MAX does not fit in u64");
1391 let dest = if this.ptr_is_null(processed_ptr)? {
1392 this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1402 } else {
1403 let (wrote_path, _) =
1404 this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1405
1406 if !wrote_path {
1407 this.set_last_error(LibcError("ENAMETOOLONG"))?;
1411 return interp_ok(Scalar::from_target_usize(0, this));
1412 }
1413 processed_ptr
1414 };
1415
1416 interp_ok(Scalar::from_maybe_pointer(dest, this))
1417 }
1418 Err(e) => {
1419 this.set_last_error(e)?;
1420 interp_ok(Scalar::from_target_usize(0, this))
1421 }
1422 }
1423 }
1424 fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1425 use rand::seq::IndexedRandom;
1426
1427 const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1429
1430 let this = self.eval_context_mut();
1431 this.assert_target_os_is_unix("mkstemp");
1432
1433 let max_attempts = this.eval_libc_u32("TMP_MAX");
1443
1444 let template_ptr = this.read_pointer(template_op)?;
1447 let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1448 let template_bytes = template.as_mut_slice();
1449
1450 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1452 this.reject_in_isolation("`mkstemp`", reject_with)?;
1453 return this.set_last_error_and_return_i32(LibcError("EACCES"));
1454 }
1455
1456 let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1458
1459 let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1464 let end_pos = template_bytes.len();
1465 let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1466
1467 if last_six_char_bytes != suffix_bytes {
1469 return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1470 }
1471
1472 const SUBSTITUTIONS: &[char; 62] = &[
1476 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1477 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1478 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1479 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1480 ];
1481
1482 let mut fopts = OpenOptions::new();
1485 fopts.read(true).write(true).create_new(true);
1486
1487 #[cfg(unix)]
1488 {
1489 use std::os::unix::fs::OpenOptionsExt;
1490 fopts.mode(0o600);
1492 fopts.custom_flags(libc::O_EXCL);
1493 }
1494 #[cfg(windows)]
1495 {
1496 use std::os::windows::fs::OpenOptionsExt;
1497 fopts.share_mode(0);
1499 }
1500
1501 for _ in 0..max_attempts {
1503 let rng = this.machine.rng.get_mut();
1504
1505 let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1507
1508 template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1510
1511 this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1513
1514 let p = bytes_to_os_str(template_bytes)?.to_os_string();
1516
1517 let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1518
1519 let file = fopts.open(possibly_unique);
1520
1521 match file {
1522 Ok(f) => {
1523 let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1524 return interp_ok(Scalar::from_i32(fd));
1525 }
1526 Err(e) =>
1527 match e.kind() {
1528 ErrorKind::AlreadyExists => continue,
1530 _ => {
1532 return this.set_last_error_and_return_i32(e);
1535 }
1536 },
1537 }
1538 }
1539
1540 this.set_last_error_and_return_i32(LibcError("EEXIST"))
1542 }
1543}
1544
1545fn extract_sec_and_nsec<'tcx>(
1549 time: std::io::Result<SystemTime>,
1550) -> InterpResult<'tcx, Option<(u64, u32)>> {
1551 match time.ok() {
1552 Some(time) => {
1553 let duration = system_time_to_duration(&time)?;
1554 interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1555 }
1556 None => interp_ok(None),
1557 }
1558}
1559
1560struct FileMetadata {
1563 mode: Scalar,
1564 size: u64,
1565 created: Option<(u64, u32)>,
1566 accessed: Option<(u64, u32)>,
1567 modified: Option<(u64, u32)>,
1568 dev: u64,
1569 uid: u32,
1570 gid: u32,
1571}
1572
1573impl FileMetadata {
1574 fn from_path<'tcx>(
1575 ecx: &mut MiriInterpCx<'tcx>,
1576 path: &Path,
1577 follow_symlink: bool,
1578 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1579 let metadata =
1580 if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1581
1582 FileMetadata::from_meta(ecx, metadata)
1583 }
1584
1585 fn from_fd_num<'tcx>(
1586 ecx: &mut MiriInterpCx<'tcx>,
1587 fd_num: i32,
1588 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1589 let Some(fd) = ecx.machine.fds.get(fd_num) else {
1590 return interp_ok(Err(LibcError("EBADF")));
1591 };
1592
1593 let metadata = fd.metadata()?;
1594 drop(fd);
1595 FileMetadata::from_meta(ecx, metadata)
1596 }
1597
1598 fn from_meta<'tcx>(
1599 ecx: &mut MiriInterpCx<'tcx>,
1600 metadata: Result<std::fs::Metadata, std::io::Error>,
1601 ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1602 let metadata = match metadata {
1603 Ok(metadata) => metadata,
1604 Err(e) => {
1605 return interp_ok(Err(e.into()));
1606 }
1607 };
1608
1609 let file_type = metadata.file_type();
1610
1611 let mode_name = if file_type.is_file() {
1612 "S_IFREG"
1613 } else if file_type.is_dir() {
1614 "S_IFDIR"
1615 } else {
1616 "S_IFLNK"
1617 };
1618
1619 let mode = ecx.eval_libc(mode_name);
1620
1621 let size = metadata.len();
1622
1623 let created = extract_sec_and_nsec(metadata.created())?;
1624 let accessed = extract_sec_and_nsec(metadata.accessed())?;
1625 let modified = extract_sec_and_nsec(metadata.modified())?;
1626
1627 cfg_select! {
1630 unix => {
1631 use std::os::unix::fs::MetadataExt;
1632 let dev = metadata.dev();
1633 let uid = metadata.uid();
1634 let gid = metadata.gid();
1635 }
1636 _ => {
1637 let dev = 0;
1638 let uid = 0;
1639 let gid = 0;
1640 }
1641 }
1642
1643 interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1644 }
1645}