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;
14
15use self::shims::time::system_time_to_duration;
16use crate::helpers::check_min_vararg_count;
17use crate::shims::files::FileHandle;
18use crate::shims::os_str::bytes_to_os_str;
19use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
20use crate::*;
21
22impl UnixFileDescription for FileHandle {
23    fn pread<'tcx>(
24        &self,
25        communicate_allowed: bool,
26        offset: u64,
27        ptr: Pointer,
28        len: usize,
29        ecx: &mut MiriInterpCx<'tcx>,
30        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
31    ) -> InterpResult<'tcx> {
32        assert!(communicate_allowed, "isolation should have prevented even opening a file");
33        let mut bytes = vec![0; len];
34        let file = &mut &self.file;
38        let mut f = || {
39            let cursor_pos = file.stream_position()?;
40            file.seek(SeekFrom::Start(offset))?;
41            let res = file.read(&mut bytes);
42            file.seek(SeekFrom::Start(cursor_pos))
44                .expect("failed to restore file position, this shouldn't be possible");
45            res
46        };
47        let result = match f() {
48            Ok(read_size) => {
49                ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
53                Ok(read_size)
54            }
55            Err(e) => Err(IoError::HostError(e)),
56        };
57        finish.call(ecx, result)
58    }
59
60    fn pwrite<'tcx>(
61        &self,
62        communicate_allowed: bool,
63        ptr: Pointer,
64        len: usize,
65        offset: u64,
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 file = &mut &self.file;
74        let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
75        let mut f = || {
76            let cursor_pos = file.stream_position()?;
77            file.seek(SeekFrom::Start(offset))?;
78            let res = file.write(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 = f();
85        finish.call(ecx, result.map_err(IoError::HostError))
86    }
87
88    fn flock<'tcx>(
89        &self,
90        communicate_allowed: bool,
91        op: FlockOp,
92    ) -> InterpResult<'tcx, io::Result<()>> {
93        assert!(communicate_allowed, "isolation should have prevented even opening a file");
94
95        use FlockOp::*;
96        let (res, nonblocking) = match op {
98            SharedLock { nonblocking } => (self.file.try_lock_shared(), nonblocking),
99            ExclusiveLock { nonblocking } => (self.file.try_lock(), nonblocking),
100            Unlock => {
101                return interp_ok(self.file.unlock());
102            }
103        };
104
105        match res {
106            Ok(()) => interp_ok(Ok(())),
107            Err(TryLockError::Error(err)) => interp_ok(Err(err)),
108            Err(TryLockError::WouldBlock) =>
109                if nonblocking {
110                    interp_ok(Err(ErrorKind::WouldBlock.into()))
111                } else {
112                    throw_unsup_format!("blocking `flock` is not currently supported");
113                },
114        }
115    }
116}
117
118impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {}
119trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> {
120    fn macos_fbsd_solarish_write_stat_buf(
121        &mut self,
122        metadata: FileMetadata,
123        buf_op: &OpTy<'tcx>,
124    ) -> InterpResult<'tcx, i32> {
125        let this = self.eval_context_mut();
126
127        let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
128        let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
129        let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
130        let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?;
131
132        let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?;
133        this.write_int_fields_named(
134            &[
135                ("st_dev", metadata.dev.into()),
136                ("st_mode", mode.try_into().unwrap()),
137                ("st_nlink", 0),
138                ("st_ino", 0),
139                ("st_uid", metadata.uid.into()),
140                ("st_gid", metadata.gid.into()),
141                ("st_rdev", 0),
142                ("st_atime", access_sec.into()),
143                ("st_mtime", modified_sec.into()),
144                ("st_ctime", 0),
145                ("st_size", metadata.size.into()),
146                ("st_blocks", 0),
147                ("st_blksize", 0),
148            ],
149            &buf,
150        )?;
151
152        if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
153            this.write_int_fields_named(
154                &[
155                    ("st_atime_nsec", access_nsec.into()),
156                    ("st_mtime_nsec", modified_nsec.into()),
157                    ("st_ctime_nsec", 0),
158                    ("st_birthtime", created_sec.into()),
159                    ("st_birthtime_nsec", created_nsec.into()),
160                    ("st_flags", 0),
161                    ("st_gen", 0),
162                ],
163                &buf,
164            )?;
165        }
166
167        if matches!(&*this.tcx.sess.target.os, "solaris" | "illumos") {
168            let st_fstype = this.project_field_named(&buf, "st_fstype")?;
169            this.write_int(0, &this.project_index(&st_fstype, 0)?)?;
171        }
172
173        interp_ok(0)
174    }
175
176    fn file_type_to_d_type(
177        &mut self,
178        file_type: std::io::Result<FileType>,
179    ) -> InterpResult<'tcx, i32> {
180        #[cfg(unix)]
181        use std::os::unix::fs::FileTypeExt;
182
183        let this = self.eval_context_mut();
184        match file_type {
185            Ok(file_type) => {
186                match () {
187                    _ if file_type.is_dir() => interp_ok(this.eval_libc("DT_DIR").to_u8()?.into()),
188                    _ if file_type.is_file() => interp_ok(this.eval_libc("DT_REG").to_u8()?.into()),
189                    _ if file_type.is_symlink() =>
190                        interp_ok(this.eval_libc("DT_LNK").to_u8()?.into()),
191                    #[cfg(unix)]
193                    _ if file_type.is_block_device() =>
194                        interp_ok(this.eval_libc("DT_BLK").to_u8()?.into()),
195                    #[cfg(unix)]
196                    _ if file_type.is_char_device() =>
197                        interp_ok(this.eval_libc("DT_CHR").to_u8()?.into()),
198                    #[cfg(unix)]
199                    _ if file_type.is_fifo() =>
200                        interp_ok(this.eval_libc("DT_FIFO").to_u8()?.into()),
201                    #[cfg(unix)]
202                    _ if file_type.is_socket() =>
203                        interp_ok(this.eval_libc("DT_SOCK").to_u8()?.into()),
204                    _ => interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into()),
206                }
207            }
208            Err(_) => {
209                interp_ok(this.eval_libc("DT_UNKNOWN").to_u8()?.into())
211            }
212        }
213    }
214}
215
216#[derive(Debug)]
218struct OpenDir {
219    read_dir: ReadDir,
221    entry: Option<Pointer>,
224}
225
226impl OpenDir {
227    fn new(read_dir: ReadDir) -> Self {
228        Self { read_dir, entry: None }
229    }
230}
231
232#[derive(Debug)]
236pub struct DirTable {
237    streams: FxHashMap<u64, OpenDir>,
247    next_id: u64,
249}
250
251impl DirTable {
252    #[expect(clippy::arithmetic_side_effects)]
253    fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
254        let id = self.next_id;
255        self.next_id += 1;
256        self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
257        id
258    }
259}
260
261impl Default for DirTable {
262    fn default() -> DirTable {
263        DirTable {
264            streams: FxHashMap::default(),
265            next_id: 1,
267        }
268    }
269}
270
271impl VisitProvenance for DirTable {
272    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
273        let DirTable { streams, next_id: _ } = self;
274
275        for dir in streams.values() {
276            dir.entry.visit_provenance(visit);
277        }
278    }
279}
280
281fn maybe_sync_file(
282    file: &File,
283    writable: bool,
284    operation: fn(&File) -> std::io::Result<()>,
285) -> std::io::Result<i32> {
286    if !writable && cfg!(windows) {
287        Ok(0i32)
291    } else {
292        let result = operation(file);
293        result.map(|_| 0i32)
294    }
295}
296
297impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
298pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
299    fn open(
300        &mut self,
301        path_raw: &OpTy<'tcx>,
302        flag: &OpTy<'tcx>,
303        varargs: &[OpTy<'tcx>],
304    ) -> InterpResult<'tcx, Scalar> {
305        let this = self.eval_context_mut();
306
307        let path_raw = this.read_pointer(path_raw)?;
308        let path = this.read_path_from_c_str(path_raw)?;
309        let flag = this.read_scalar(flag)?.to_i32()?;
310
311        let mut options = OpenOptions::new();
312
313        let o_rdonly = this.eval_libc_i32("O_RDONLY");
314        let o_wronly = this.eval_libc_i32("O_WRONLY");
315        let o_rdwr = this.eval_libc_i32("O_RDWR");
316        if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
320            throw_unsup_format!("access mode flags on this target are unsupported");
321        }
322        let mut writable = true;
323
324        let access_mode = flag & 0b11;
326
327        if access_mode == o_rdonly {
328            writable = false;
329            options.read(true);
330        } else if access_mode == o_wronly {
331            options.write(true);
332        } else if access_mode == o_rdwr {
333            options.read(true).write(true);
334        } else {
335            throw_unsup_format!("unsupported access mode {:#x}", access_mode);
336        }
337        let mut mirror = access_mode;
341
342        let o_append = this.eval_libc_i32("O_APPEND");
343        if flag & o_append == o_append {
344            options.append(true);
345            mirror |= o_append;
346        }
347        let o_trunc = this.eval_libc_i32("O_TRUNC");
348        if flag & o_trunc == o_trunc {
349            options.truncate(true);
350            mirror |= o_trunc;
351        }
352        let o_creat = this.eval_libc_i32("O_CREAT");
353        if flag & o_creat == o_creat {
354            let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
358            let mode = this.read_scalar(mode)?.to_u32()?;
359
360            #[cfg(unix)]
361            {
362                use std::os::unix::fs::OpenOptionsExt;
364                options.mode(mode);
365            }
366            #[cfg(not(unix))]
367            {
368                if mode != 0o666 {
370                    throw_unsup_format!(
371                        "non-default mode 0o{:o} is not supported on non-Unix hosts",
372                        mode
373                    );
374                }
375            }
376
377            mirror |= o_creat;
378
379            let o_excl = this.eval_libc_i32("O_EXCL");
380            if flag & o_excl == o_excl {
381                mirror |= o_excl;
382                options.create_new(true);
383            } else {
384                options.create(true);
385            }
386        }
387        let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
388        if flag & o_cloexec == o_cloexec {
389            mirror |= o_cloexec;
392        }
393        if this.tcx.sess.target.os == "linux" {
394            let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
395            if flag & o_tmpfile == o_tmpfile {
396                return this.set_last_error_and_return_i32(LibcError("EOPNOTSUPP"));
398            }
399        }
400
401        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
402        if flag & o_nofollow == o_nofollow {
403            #[cfg(unix)]
404            {
405                use std::os::unix::fs::OpenOptionsExt;
406                options.custom_flags(libc::O_NOFOLLOW);
407            }
408            #[cfg(not(unix))]
412            {
413                if path.is_symlink() {
416                    return this.set_last_error_and_return_i32(LibcError("ELOOP"));
417                }
418            }
419            mirror |= o_nofollow;
420        }
421
422        if flag != mirror {
425            throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
426        }
427
428        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
430            this.reject_in_isolation("`open`", reject_with)?;
431            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
432        }
433
434        let fd = options
435            .open(path)
436            .map(|file| this.machine.fds.insert_new(FileHandle { file, writable }));
437
438        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?))
439    }
440
441    fn lseek64(
442        &mut self,
443        fd_num: i32,
444        offset: i128,
445        whence: i32,
446        dest: &MPlaceTy<'tcx>,
447    ) -> InterpResult<'tcx> {
448        let this = self.eval_context_mut();
449
450        let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
453            if offset < 0 {
454                return this.set_last_error_and_return(LibcError("EINVAL"), dest);
456            } else {
457                SeekFrom::Start(u64::try_from(offset).unwrap())
458            }
459        } else if whence == this.eval_libc_i32("SEEK_CUR") {
460            SeekFrom::Current(i64::try_from(offset).unwrap())
461        } else if whence == this.eval_libc_i32("SEEK_END") {
462            SeekFrom::End(i64::try_from(offset).unwrap())
463        } else {
464            return this.set_last_error_and_return(LibcError("EINVAL"), dest);
465        };
466
467        let communicate = this.machine.communicate();
468
469        let Some(fd) = this.machine.fds.get(fd_num) else {
470            return this.set_last_error_and_return(LibcError("EBADF"), dest);
471        };
472        let result = fd.seek(communicate, seek_from)?.map(|offset| i64::try_from(offset).unwrap());
473        drop(fd);
474
475        let result = this.try_unwrap_io_result(result)?;
476        this.write_int(result, dest)?;
477        interp_ok(())
478    }
479
480    fn unlink(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
481        let this = self.eval_context_mut();
482
483        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
484
485        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
487            this.reject_in_isolation("`unlink`", reject_with)?;
488            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
489        }
490
491        let result = remove_file(path).map(|_| 0);
492        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
493    }
494
495    fn symlink(
496        &mut self,
497        target_op: &OpTy<'tcx>,
498        linkpath_op: &OpTy<'tcx>,
499    ) -> InterpResult<'tcx, Scalar> {
500        #[cfg(unix)]
501        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
502            std::os::unix::fs::symlink(src, dst)
503        }
504
505        #[cfg(windows)]
506        fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
507            use std::os::windows::fs;
508            if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
509        }
510
511        let this = self.eval_context_mut();
512        let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
513        let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
514
515        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
517            this.reject_in_isolation("`symlink`", reject_with)?;
518            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
519        }
520
521        let result = create_link(&target, &linkpath).map(|_| 0);
522        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
523    }
524
525    fn macos_fbsd_solarish_stat(
526        &mut self,
527        path_op: &OpTy<'tcx>,
528        buf_op: &OpTy<'tcx>,
529    ) -> InterpResult<'tcx, Scalar> {
530        let this = self.eval_context_mut();
531
532        if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
533            panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
534        }
535
536        let path_scalar = this.read_pointer(path_op)?;
537        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
538
539        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
541            this.reject_in_isolation("`stat`", reject_with)?;
542            return this.set_last_error_and_return_i32(LibcError("EACCES"));
543        }
544
545        let metadata = match FileMetadata::from_path(this, &path, true)? {
547            Ok(metadata) => metadata,
548            Err(err) => return this.set_last_error_and_return_i32(err),
549        };
550
551        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
552    }
553
554    fn macos_fbsd_solarish_lstat(
556        &mut self,
557        path_op: &OpTy<'tcx>,
558        buf_op: &OpTy<'tcx>,
559    ) -> InterpResult<'tcx, Scalar> {
560        let this = self.eval_context_mut();
561
562        if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
563            panic!(
564                "`macos_fbsd_solaris_lstat` should not be called on {}",
565                this.tcx.sess.target.os
566            );
567        }
568
569        let path_scalar = this.read_pointer(path_op)?;
570        let path = this.read_path_from_c_str(path_scalar)?.into_owned();
571
572        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
574            this.reject_in_isolation("`lstat`", reject_with)?;
575            return this.set_last_error_and_return_i32(LibcError("EACCES"));
576        }
577
578        let metadata = match FileMetadata::from_path(this, &path, false)? {
579            Ok(metadata) => metadata,
580            Err(err) => return this.set_last_error_and_return_i32(err),
581        };
582
583        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
584    }
585
586    fn macos_fbsd_solarish_fstat(
587        &mut self,
588        fd_op: &OpTy<'tcx>,
589        buf_op: &OpTy<'tcx>,
590    ) -> InterpResult<'tcx, Scalar> {
591        let this = self.eval_context_mut();
592
593        if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd" | "solaris" | "illumos") {
594            panic!(
595                "`macos_fbsd_solaris_fstat` should not be called on {}",
596                this.tcx.sess.target.os
597            );
598        }
599
600        let fd = this.read_scalar(fd_op)?.to_i32()?;
601
602        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
604            this.reject_in_isolation("`fstat`", reject_with)?;
605            return this.set_last_error_and_return_i32(LibcError("EBADF"));
607        }
608
609        let metadata = match FileMetadata::from_fd_num(this, fd)? {
610            Ok(metadata) => metadata,
611            Err(err) => return this.set_last_error_and_return_i32(err),
612        };
613        interp_ok(Scalar::from_i32(this.macos_fbsd_solarish_write_stat_buf(metadata, buf_op)?))
614    }
615
616    fn linux_statx(
617        &mut self,
618        dirfd_op: &OpTy<'tcx>,    pathname_op: &OpTy<'tcx>, flags_op: &OpTy<'tcx>,    mask_op: &OpTy<'tcx>,     statxbuf_op: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> {
624        let this = self.eval_context_mut();
625
626        this.assert_target_os("linux", "statx");
627
628        let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
629        let pathname_ptr = this.read_pointer(pathname_op)?;
630        let flags = this.read_scalar(flags_op)?.to_i32()?;
631        let _mask = this.read_scalar(mask_op)?.to_u32()?;
632        let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
633
634        if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
636            return this.set_last_error_and_return_i32(LibcError("EFAULT"));
637        }
638
639        let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?;
640
641        let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
642        let at_empty_path = this.eval_libc_i32("AT_EMPTY_PATH");
644        let empty_path_flag = flags & at_empty_path == at_empty_path;
645        if !(path.is_absolute()
653            || dirfd == this.eval_libc_i32("AT_FDCWD")
654            || (path.as_os_str().is_empty() && empty_path_flag))
655        {
656            throw_unsup_format!(
657                "using statx is only supported with absolute paths, relative paths with the file \
658                descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
659                file descriptor"
660            )
661        }
662
663        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
665            this.reject_in_isolation("`statx`", reject_with)?;
666            let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD") {
667                LibcError("EACCES")
670            } else {
671                assert!(empty_path_flag);
675                LibcError("EBADF")
676            };
677            return this.set_last_error_and_return_i32(ecode);
678        }
679
680        let mut mask = this.eval_libc_u32("STATX_TYPE") | this.eval_libc_u32("STATX_SIZE");
685
686        let follow_symlink = flags & this.eval_libc_i32("AT_SYMLINK_NOFOLLOW") == 0;
689
690        let metadata = if path.as_os_str().is_empty() && empty_path_flag {
693            FileMetadata::from_fd_num(this, dirfd)?
694        } else {
695            FileMetadata::from_path(this, &path, follow_symlink)?
696        };
697        let metadata = match metadata {
698            Ok(metadata) => metadata,
699            Err(err) => return this.set_last_error_and_return_i32(err),
700        };
701
702        let mode: u16 = metadata
707            .mode
708            .to_u32()?
709            .try_into()
710            .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
711
712        let (access_sec, access_nsec) = metadata
715            .accessed
716            .map(|tup| {
717                mask |= this.eval_libc_u32("STATX_ATIME");
718                interp_ok(tup)
719            })
720            .unwrap_or_else(|| interp_ok((0, 0)))?;
721
722        let (created_sec, created_nsec) = metadata
723            .created
724            .map(|tup| {
725                mask |= this.eval_libc_u32("STATX_BTIME");
726                interp_ok(tup)
727            })
728            .unwrap_or_else(|| interp_ok((0, 0)))?;
729
730        let (modified_sec, modified_nsec) = metadata
731            .modified
732            .map(|tup| {
733                mask |= this.eval_libc_u32("STATX_MTIME");
734                interp_ok(tup)
735            })
736            .unwrap_or_else(|| interp_ok((0, 0)))?;
737
738        this.write_int_fields_named(
740            &[
741                ("stx_mask", mask.into()),
742                ("stx_blksize", 0),
743                ("stx_attributes", 0),
744                ("stx_nlink", 0),
745                ("stx_uid", 0),
746                ("stx_gid", 0),
747                ("stx_mode", mode.into()),
748                ("stx_ino", 0),
749                ("stx_size", metadata.size.into()),
750                ("stx_blocks", 0),
751                ("stx_attributes_mask", 0),
752                ("stx_rdev_major", 0),
753                ("stx_rdev_minor", 0),
754                ("stx_dev_major", 0),
755                ("stx_dev_minor", 0),
756            ],
757            &statxbuf,
758        )?;
759        #[rustfmt::skip]
760        this.write_int_fields_named(
761            &[
762                ("tv_sec", access_sec.into()),
763                ("tv_nsec", access_nsec.into()),
764            ],
765            &this.project_field_named(&statxbuf, "stx_atime")?,
766        )?;
767        #[rustfmt::skip]
768        this.write_int_fields_named(
769            &[
770                ("tv_sec", created_sec.into()),
771                ("tv_nsec", created_nsec.into()),
772            ],
773            &this.project_field_named(&statxbuf, "stx_btime")?,
774        )?;
775        #[rustfmt::skip]
776        this.write_int_fields_named(
777            &[
778                ("tv_sec", 0.into()),
779                ("tv_nsec", 0.into()),
780            ],
781            &this.project_field_named(&statxbuf, "stx_ctime")?,
782        )?;
783        #[rustfmt::skip]
784        this.write_int_fields_named(
785            &[
786                ("tv_sec", modified_sec.into()),
787                ("tv_nsec", modified_nsec.into()),
788            ],
789            &this.project_field_named(&statxbuf, "stx_mtime")?,
790        )?;
791
792        interp_ok(Scalar::from_i32(0))
793    }
794
795    fn rename(
796        &mut self,
797        oldpath_op: &OpTy<'tcx>,
798        newpath_op: &OpTy<'tcx>,
799    ) -> InterpResult<'tcx, Scalar> {
800        let this = self.eval_context_mut();
801
802        let oldpath_ptr = this.read_pointer(oldpath_op)?;
803        let newpath_ptr = this.read_pointer(newpath_op)?;
804
805        if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
806            return this.set_last_error_and_return_i32(LibcError("EFAULT"));
807        }
808
809        let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
810        let newpath = this.read_path_from_c_str(newpath_ptr)?;
811
812        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
814            this.reject_in_isolation("`rename`", reject_with)?;
815            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
816        }
817
818        let result = rename(oldpath, newpath).map(|_| 0);
819
820        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
821    }
822
823    fn mkdir(&mut self, path_op: &OpTy<'tcx>, mode_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
824        let this = self.eval_context_mut();
825
826        #[cfg_attr(not(unix), allow(unused_variables))]
827        let mode = if matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
828            u32::from(this.read_scalar(mode_op)?.to_u16()?)
829        } else {
830            this.read_scalar(mode_op)?.to_u32()?
831        };
832
833        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
834
835        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
837            this.reject_in_isolation("`mkdir`", reject_with)?;
838            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
839        }
840
841        #[cfg_attr(not(unix), allow(unused_mut))]
842        let mut builder = DirBuilder::new();
843
844        #[cfg(unix)]
847        {
848            use std::os::unix::fs::DirBuilderExt;
849            builder.mode(mode);
850        }
851
852        let result = builder.create(path).map(|_| 0i32);
853
854        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
855    }
856
857    fn rmdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
858        let this = self.eval_context_mut();
859
860        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
861
862        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
864            this.reject_in_isolation("`rmdir`", reject_with)?;
865            return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
866        }
867
868        let result = remove_dir(path).map(|_| 0i32);
869
870        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
871    }
872
873    fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
874        let this = self.eval_context_mut();
875
876        let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
877
878        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
880            this.reject_in_isolation("`opendir`", reject_with)?;
881            this.set_last_error(LibcError("EACCES"))?;
882            return interp_ok(Scalar::null_ptr(this));
883        }
884
885        let result = read_dir(name);
886
887        match result {
888            Ok(dir_iter) => {
889                let id = this.machine.dirs.insert_new(dir_iter);
890
891                interp_ok(Scalar::from_target_usize(id, this))
895            }
896            Err(e) => {
897                this.set_last_error(e)?;
898                interp_ok(Scalar::null_ptr(this))
899            }
900        }
901    }
902
903    fn linux_solarish_readdir64(
904        &mut self,
905        dirent_type: &str,
906        dirp_op: &OpTy<'tcx>,
907    ) -> InterpResult<'tcx, Scalar> {
908        let this = self.eval_context_mut();
909
910        if !matches!(&*this.tcx.sess.target.os, "linux" | "solaris" | "illumos") {
911            panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
912        }
913
914        let dirp = this.read_target_usize(dirp_op)?;
915
916        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
918            this.reject_in_isolation("`readdir`", reject_with)?;
919            this.set_last_error(LibcError("EBADF"))?;
920            return interp_ok(Scalar::null_ptr(this));
921        }
922
923        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
924            err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
925        })?;
926
927        let entry = match open_dir.read_dir.next() {
928            Some(Ok(dir_entry)) => {
929                let mut name = dir_entry.file_name(); name.push("\0"); let name_bytes = name.as_encoded_bytes();
954                let name_len = u64::try_from(name_bytes.len()).unwrap();
955
956                let dirent_layout = this.libc_ty_layout(dirent_type);
957                let fields = &dirent_layout.fields;
958                let last_field = fields.count().strict_sub(1);
959                let d_name_offset = fields.offset(last_field).bytes();
960                let size = d_name_offset.strict_add(name_len);
961
962                let entry = this.allocate_ptr(
963                    Size::from_bytes(size),
964                    dirent_layout.align.abi,
965                    MiriMemoryKind::Runtime.into(),
966                    AllocInit::Uninit,
967                )?;
968                let entry: Pointer = entry.into();
969
970                #[cfg(unix)]
973                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
974                #[cfg(not(unix))]
975                let ino = 0u64;
976
977                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
978                this.write_int_fields_named(
979                    &[("d_ino", ino.into()), ("d_off", 0), ("d_reclen", size.into())],
980                    &this.ptr_to_mplace(entry, dirent_layout),
981                )?;
982
983                if let Some(d_type) = this
984                    .try_project_field_named(&this.ptr_to_mplace(entry, dirent_layout), "d_type")?
985                {
986                    this.write_int(file_type, &d_type)?;
987                }
988
989                let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
990                this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
991
992                Some(entry)
993            }
994            None => {
995                None
997            }
998            Some(Err(e)) => {
999                this.set_last_error(e)?;
1000                None
1001            }
1002        };
1003
1004        let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
1005        let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1006        if let Some(old_entry) = old_entry {
1007            this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?;
1008        }
1009
1010        interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this))
1011    }
1012
1013    fn macos_fbsd_readdir_r(
1014        &mut self,
1015        dirp_op: &OpTy<'tcx>,
1016        entry_op: &OpTy<'tcx>,
1017        result_op: &OpTy<'tcx>,
1018    ) -> InterpResult<'tcx, Scalar> {
1019        let this = self.eval_context_mut();
1020
1021        if !matches!(&*this.tcx.sess.target.os, "macos" | "freebsd") {
1022            panic!("`macos_fbsd_readdir_r` should not be called on {}", this.tcx.sess.target.os);
1023        }
1024
1025        let dirp = this.read_target_usize(dirp_op)?;
1026        let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?;
1027
1028        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1030            this.reject_in_isolation("`readdir_r`", reject_with)?;
1031            return interp_ok(this.eval_libc("EBADF"));
1033        }
1034
1035        let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
1036            err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1037        })?;
1038        interp_ok(match open_dir.read_dir.next() {
1039            Some(Ok(dir_entry)) => {
1040                let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?;
1055                let name_place = this.project_field_named(&entry_place, "d_name")?;
1056
1057                let file_name = dir_entry.file_name(); let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1059                    &file_name,
1060                    name_place.ptr(),
1061                    name_place.layout.size.bytes(),
1062                )?;
1063                let file_name_len = file_name_buf_len.strict_sub(1);
1064                if !name_fits {
1065                    throw_unsup_format!(
1066                        "a directory entry had a name too large to fit in libc::dirent"
1067                    );
1068                }
1069
1070                #[cfg(unix)]
1073                let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1074                #[cfg(not(unix))]
1075                let ino = 0u64;
1076
1077                let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1078
1079                this.write_int_fields_named(
1081                    &[
1082                        ("d_reclen", 0),
1083                        ("d_namlen", file_name_len.into()),
1084                        ("d_type", file_type.into()),
1085                    ],
1086                    &entry_place,
1087                )?;
1088                match &*this.tcx.sess.target.os {
1090                    "macos" => {
1091                        #[rustfmt::skip]
1092                        this.write_int_fields_named(
1093                            &[
1094                                ("d_ino", ino.into()),
1095                                ("d_seekoff", 0),
1096                            ],
1097                            &entry_place,
1098                        )?;
1099                    }
1100                    "freebsd" => {
1101                        #[rustfmt::skip]
1102                        this.write_int_fields_named(
1103                            &[
1104                                ("d_fileno", ino.into()),
1105                                ("d_off", 0),
1106                            ],
1107                            &entry_place,
1108                        )?;
1109                    }
1110                    _ => unreachable!(),
1111                }
1112                this.write_scalar(this.read_scalar(entry_op)?, &result_place)?;
1113
1114                Scalar::from_i32(0)
1115            }
1116            None => {
1117                this.write_null(&result_place)?;
1119                Scalar::from_i32(0)
1120            }
1121            Some(Err(e)) => {
1122                this.io_error_to_errnum(e)?
1124            }
1125        })
1126    }
1127
1128    fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1129        let this = self.eval_context_mut();
1130
1131        let dirp = this.read_target_usize(dirp_op)?;
1132
1133        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1135            this.reject_in_isolation("`closedir`", reject_with)?;
1136            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1137        }
1138
1139        let Some(mut open_dir) = this.machine.dirs.streams.remove(&dirp) else {
1140            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1141        };
1142        if let Some(entry) = open_dir.entry.take() {
1143            this.deallocate_ptr(entry, None, MiriMemoryKind::Runtime.into())?;
1144        }
1145        drop(open_dir);
1147
1148        interp_ok(Scalar::from_i32(0))
1149    }
1150
1151    fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> {
1152        let this = self.eval_context_mut();
1153
1154        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1156            this.reject_in_isolation("`ftruncate64`", reject_with)?;
1157            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1159        }
1160
1161        let Some(fd) = this.machine.fds.get(fd_num) else {
1162            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1163        };
1164
1165        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1167            err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
1168        })?;
1169
1170        if file.writable {
1171            if let Ok(length) = length.try_into() {
1172                let result = file.file.set_len(length);
1173                let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1174                interp_ok(Scalar::from_i32(result))
1175            } else {
1176                this.set_last_error_and_return_i32(LibcError("EINVAL"))
1177            }
1178        } else {
1179            this.set_last_error_and_return_i32(LibcError("EINVAL"))
1181        }
1182    }
1183
1184    fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1185        let this = self.eval_context_mut();
1191
1192        let fd = this.read_scalar(fd_op)?.to_i32()?;
1193
1194        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1196            this.reject_in_isolation("`fsync`", reject_with)?;
1197            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1199        }
1200
1201        self.ffullsync_fd(fd)
1202    }
1203
1204    fn ffullsync_fd(&mut self, fd_num: i32) -> InterpResult<'tcx, Scalar> {
1205        let this = self.eval_context_mut();
1206        let Some(fd) = this.machine.fds.get(fd_num) else {
1207            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1208        };
1209        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1211            err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
1212        })?;
1213        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
1214        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1215    }
1216
1217    fn fdatasync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1218        let this = self.eval_context_mut();
1219
1220        let fd = this.read_scalar(fd_op)?.to_i32()?;
1221
1222        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1224            this.reject_in_isolation("`fdatasync`", reject_with)?;
1225            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1227        }
1228
1229        let Some(fd) = this.machine.fds.get(fd) else {
1230            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1231        };
1232        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1234            err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
1235        })?;
1236        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1237        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1238    }
1239
1240    fn sync_file_range(
1241        &mut self,
1242        fd_op: &OpTy<'tcx>,
1243        offset_op: &OpTy<'tcx>,
1244        nbytes_op: &OpTy<'tcx>,
1245        flags_op: &OpTy<'tcx>,
1246    ) -> InterpResult<'tcx, Scalar> {
1247        let this = self.eval_context_mut();
1248
1249        let fd = this.read_scalar(fd_op)?.to_i32()?;
1250        let offset = this.read_scalar(offset_op)?.to_i64()?;
1251        let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1252        let flags = this.read_scalar(flags_op)?.to_i32()?;
1253
1254        if offset < 0 || nbytes < 0 {
1255            return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1256        }
1257        let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
1258            | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
1259            | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
1260        if flags & allowed_flags != flags {
1261            return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1262        }
1263
1264        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1266            this.reject_in_isolation("`sync_file_range`", reject_with)?;
1267            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1269        }
1270
1271        let Some(fd) = this.machine.fds.get(fd) else {
1272            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1273        };
1274        let file = fd.downcast::<FileHandle>().ok_or_else(|| {
1276            err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
1277        })?;
1278        let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
1279        interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1280    }
1281
1282    fn readlink(
1283        &mut self,
1284        pathname_op: &OpTy<'tcx>,
1285        buf_op: &OpTy<'tcx>,
1286        bufsize_op: &OpTy<'tcx>,
1287    ) -> InterpResult<'tcx, i64> {
1288        let this = self.eval_context_mut();
1289
1290        let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1291        let buf = this.read_pointer(buf_op)?;
1292        let bufsize = this.read_target_usize(bufsize_op)?;
1293
1294        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1296            this.reject_in_isolation("`readlink`", reject_with)?;
1297            this.set_last_error(LibcError("EACCES"))?;
1298            return interp_ok(-1);
1299        }
1300
1301        let result = std::fs::read_link(pathname);
1302        match result {
1303            Ok(resolved) => {
1304                let resolved = this.convert_path(
1308                    Cow::Borrowed(resolved.as_ref()),
1309                    crate::shims::os_str::PathConversion::HostToTarget,
1310                );
1311                let mut path_bytes = resolved.as_encoded_bytes();
1312                let bufsize: usize = bufsize.try_into().unwrap();
1313                if path_bytes.len() > bufsize {
1314                    path_bytes = &path_bytes[..bufsize]
1315                }
1316                this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1317                interp_ok(path_bytes.len().try_into().unwrap())
1318            }
1319            Err(e) => {
1320                this.set_last_error(e)?;
1321                interp_ok(-1)
1322            }
1323        }
1324    }
1325
1326    fn isatty(&mut self, miri_fd: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1327        let this = self.eval_context_mut();
1328        let fd = this.read_scalar(miri_fd)?.to_i32()?;
1331        let error = if let Some(fd) = this.machine.fds.get(fd) {
1332            if fd.is_tty(this.machine.communicate()) {
1333                return interp_ok(Scalar::from_i32(1));
1334            } else {
1335                LibcError("ENOTTY")
1336            }
1337        } else {
1338            LibcError("EBADF")
1340        };
1341        this.set_last_error(error)?;
1342        interp_ok(Scalar::from_i32(0))
1343    }
1344
1345    fn realpath(
1346        &mut self,
1347        path_op: &OpTy<'tcx>,
1348        processed_path_op: &OpTy<'tcx>,
1349    ) -> InterpResult<'tcx, Scalar> {
1350        let this = self.eval_context_mut();
1351        this.assert_target_os_is_unix("realpath");
1352
1353        let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1354        let processed_ptr = this.read_pointer(processed_path_op)?;
1355
1356        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1358            this.reject_in_isolation("`realpath`", reject_with)?;
1359            this.set_last_error(LibcError("EACCES"))?;
1360            return interp_ok(Scalar::from_target_usize(0, this));
1361        }
1362
1363        let result = std::fs::canonicalize(pathname);
1364        match result {
1365            Ok(resolved) => {
1366                let path_max = this
1367                    .eval_libc_i32("PATH_MAX")
1368                    .try_into()
1369                    .expect("PATH_MAX does not fit in u64");
1370                let dest = if this.ptr_is_null(processed_ptr)? {
1371                    this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1381                } else {
1382                    let (wrote_path, _) =
1383                        this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1384
1385                    if !wrote_path {
1386                        this.set_last_error(LibcError("ENAMETOOLONG"))?;
1390                        return interp_ok(Scalar::from_target_usize(0, this));
1391                    }
1392                    processed_ptr
1393                };
1394
1395                interp_ok(Scalar::from_maybe_pointer(dest, this))
1396            }
1397            Err(e) => {
1398                this.set_last_error(e)?;
1399                interp_ok(Scalar::from_target_usize(0, this))
1400            }
1401        }
1402    }
1403    fn mkstemp(&mut self, template_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1404        use rand::seq::IndexedRandom;
1405
1406        const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1408
1409        let this = self.eval_context_mut();
1410        this.assert_target_os_is_unix("mkstemp");
1411
1412        let max_attempts = this.eval_libc_u32("TMP_MAX");
1422
1423        let template_ptr = this.read_pointer(template_op)?;
1426        let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1427        let template_bytes = template.as_mut_slice();
1428
1429        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1431            this.reject_in_isolation("`mkstemp`", reject_with)?;
1432            return this.set_last_error_and_return_i32(LibcError("EACCES"));
1433        }
1434
1435        let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1437
1438        let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1443        let end_pos = template_bytes.len();
1444        let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1445
1446        if last_six_char_bytes != suffix_bytes {
1448            return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1449        }
1450
1451        const SUBSTITUTIONS: &[char; 62] = &[
1455            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1456            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1457            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1458            'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1459        ];
1460
1461        let mut fopts = OpenOptions::new();
1464        fopts.read(true).write(true).create_new(true);
1465
1466        #[cfg(unix)]
1467        {
1468            use std::os::unix::fs::OpenOptionsExt;
1469            fopts.mode(0o600);
1471            fopts.custom_flags(libc::O_EXCL);
1472        }
1473        #[cfg(windows)]
1474        {
1475            use std::os::windows::fs::OpenOptionsExt;
1476            fopts.share_mode(0);
1478        }
1479
1480        for _ in 0..max_attempts {
1482            let rng = this.machine.rng.get_mut();
1483
1484            let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1486
1487            template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1489
1490            this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1492
1493            let p = bytes_to_os_str(template_bytes)?.to_os_string();
1495
1496            let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1497
1498            let file = fopts.open(possibly_unique);
1499
1500            match file {
1501                Ok(f) => {
1502                    let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true });
1503                    return interp_ok(Scalar::from_i32(fd));
1504                }
1505                Err(e) =>
1506                    match e.kind() {
1507                        ErrorKind::AlreadyExists => continue,
1509                        _ => {
1511                            return this.set_last_error_and_return_i32(e);
1514                        }
1515                    },
1516            }
1517        }
1518
1519        this.set_last_error_and_return_i32(LibcError("EEXIST"))
1521    }
1522}
1523
1524fn extract_sec_and_nsec<'tcx>(
1528    time: std::io::Result<SystemTime>,
1529) -> InterpResult<'tcx, Option<(u64, u32)>> {
1530    match time.ok() {
1531        Some(time) => {
1532            let duration = system_time_to_duration(&time)?;
1533            interp_ok(Some((duration.as_secs(), duration.subsec_nanos())))
1534        }
1535        None => interp_ok(None),
1536    }
1537}
1538
1539struct FileMetadata {
1542    mode: Scalar,
1543    size: u64,
1544    created: Option<(u64, u32)>,
1545    accessed: Option<(u64, u32)>,
1546    modified: Option<(u64, u32)>,
1547    dev: u64,
1548    uid: u32,
1549    gid: u32,
1550}
1551
1552impl FileMetadata {
1553    fn from_path<'tcx>(
1554        ecx: &mut MiriInterpCx<'tcx>,
1555        path: &Path,
1556        follow_symlink: bool,
1557    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1558        let metadata =
1559            if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1560
1561        FileMetadata::from_meta(ecx, metadata)
1562    }
1563
1564    fn from_fd_num<'tcx>(
1565        ecx: &mut MiriInterpCx<'tcx>,
1566        fd_num: i32,
1567    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1568        let Some(fd) = ecx.machine.fds.get(fd_num) else {
1569            return interp_ok(Err(LibcError("EBADF")));
1570        };
1571
1572        let metadata = fd.metadata()?;
1573        drop(fd);
1574        FileMetadata::from_meta(ecx, metadata)
1575    }
1576
1577    fn from_meta<'tcx>(
1578        ecx: &mut MiriInterpCx<'tcx>,
1579        metadata: Result<std::fs::Metadata, std::io::Error>,
1580    ) -> InterpResult<'tcx, Result<FileMetadata, IoError>> {
1581        let metadata = match metadata {
1582            Ok(metadata) => metadata,
1583            Err(e) => {
1584                return interp_ok(Err(e.into()));
1585            }
1586        };
1587
1588        let file_type = metadata.file_type();
1589
1590        let mode_name = if file_type.is_file() {
1591            "S_IFREG"
1592        } else if file_type.is_dir() {
1593            "S_IFDIR"
1594        } else {
1595            "S_IFLNK"
1596        };
1597
1598        let mode = ecx.eval_libc(mode_name);
1599
1600        let size = metadata.len();
1601
1602        let created = extract_sec_and_nsec(metadata.created())?;
1603        let accessed = extract_sec_and_nsec(metadata.accessed())?;
1604        let modified = extract_sec_and_nsec(metadata.modified())?;
1605
1606        cfg_select! {
1609            unix => {
1610                use std::os::unix::fs::MetadataExt;
1611                let dev = metadata.dev();
1612                let uid = metadata.uid();
1613                let gid = metadata.gid();
1614            }
1615            _ => {
1616                let dev = 0;
1617                let uid = 0;
1618                let gid = 0;
1619            }
1620        }
1621
1622        interp_ok(Ok(FileMetadata { mode, size, created, accessed, modified, dev, uid, gid }))
1623    }
1624}