]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/unix/fs.rs
Auto merge of #104535 - mikebenfield:discr-fix, r=pnkfelix
[rust.git] / src / tools / miri / src / shims / unix / fs.rs
1 use std::borrow::Cow;
2 use std::collections::BTreeMap;
3 use std::convert::TryInto;
4 use std::fs::{
5     read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
6 };
7 use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
8 use std::path::{Path, PathBuf};
9 use std::time::SystemTime;
10
11 use log::trace;
12
13 use rustc_data_structures::fx::FxHashMap;
14 use rustc_target::abi::{Align, Size};
15
16 use crate::shims::os_str::bytes_to_os_str;
17 use crate::*;
18 use shims::os_str::os_str_to_bytes;
19 use shims::time::system_time_to_duration;
20
21 #[derive(Debug)]
22 struct FileHandle {
23     file: File,
24     writable: bool,
25 }
26
27 trait FileDescriptor: std::fmt::Debug {
28     fn name(&self) -> &'static str;
29
30     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
31         throw_unsup_format!("{} cannot be used as FileHandle", self.name());
32     }
33
34     fn read<'tcx>(
35         &mut self,
36         _communicate_allowed: bool,
37         _bytes: &mut [u8],
38     ) -> InterpResult<'tcx, io::Result<usize>> {
39         throw_unsup_format!("cannot read from {}", self.name());
40     }
41
42     fn write<'tcx>(
43         &self,
44         _communicate_allowed: bool,
45         _bytes: &[u8],
46     ) -> InterpResult<'tcx, io::Result<usize>> {
47         throw_unsup_format!("cannot write to {}", self.name());
48     }
49
50     fn seek<'tcx>(
51         &mut self,
52         _communicate_allowed: bool,
53         _offset: SeekFrom,
54     ) -> InterpResult<'tcx, io::Result<u64>> {
55         throw_unsup_format!("cannot seek on {}", self.name());
56     }
57
58     fn close<'tcx>(
59         self: Box<Self>,
60         _communicate_allowed: bool,
61     ) -> InterpResult<'tcx, io::Result<i32>> {
62         throw_unsup_format!("cannot close {}", self.name());
63     }
64
65     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
66
67     fn is_tty(&self) -> bool;
68
69     #[cfg(unix)]
70     fn as_unix_host_fd(&self) -> Option<i32> {
71         None
72     }
73 }
74
75 impl FileDescriptor for FileHandle {
76     fn name(&self) -> &'static str {
77         "FILE"
78     }
79
80     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
81         Ok(self)
82     }
83
84     fn read<'tcx>(
85         &mut self,
86         communicate_allowed: bool,
87         bytes: &mut [u8],
88     ) -> InterpResult<'tcx, io::Result<usize>> {
89         assert!(communicate_allowed, "isolation should have prevented even opening a file");
90         Ok(self.file.read(bytes))
91     }
92
93     fn write<'tcx>(
94         &self,
95         communicate_allowed: bool,
96         bytes: &[u8],
97     ) -> InterpResult<'tcx, io::Result<usize>> {
98         assert!(communicate_allowed, "isolation should have prevented even opening a file");
99         Ok((&mut &self.file).write(bytes))
100     }
101
102     fn seek<'tcx>(
103         &mut self,
104         communicate_allowed: bool,
105         offset: SeekFrom,
106     ) -> InterpResult<'tcx, io::Result<u64>> {
107         assert!(communicate_allowed, "isolation should have prevented even opening a file");
108         Ok(self.file.seek(offset))
109     }
110
111     fn close<'tcx>(
112         self: Box<Self>,
113         communicate_allowed: bool,
114     ) -> InterpResult<'tcx, io::Result<i32>> {
115         assert!(communicate_allowed, "isolation should have prevented even opening a file");
116         // We sync the file if it was opened in a mode different than read-only.
117         if self.writable {
118             // `File::sync_all` does the checks that are done when closing a file. We do this to
119             // to handle possible errors correctly.
120             let result = self.file.sync_all().map(|_| 0i32);
121             // Now we actually close the file.
122             drop(self);
123             // And return the result.
124             Ok(result)
125         } else {
126             // We drop the file, this closes it but ignores any errors
127             // produced when closing it. This is done because
128             // `File::sync_all` cannot be done over files like
129             // `/dev/urandom` which are read-only. Check
130             // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
131             // for a deeper discussion.
132             drop(self);
133             Ok(Ok(0))
134         }
135     }
136
137     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
138         let duplicated = self.file.try_clone()?;
139         Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
140     }
141
142     #[cfg(unix)]
143     fn as_unix_host_fd(&self) -> Option<i32> {
144         use std::os::unix::io::AsRawFd;
145         Some(self.file.as_raw_fd())
146     }
147
148     fn is_tty(&self) -> bool {
149         self.file.is_terminal()
150     }
151 }
152
153 impl FileDescriptor for io::Stdin {
154     fn name(&self) -> &'static str {
155         "stdin"
156     }
157
158     fn read<'tcx>(
159         &mut self,
160         communicate_allowed: bool,
161         bytes: &mut [u8],
162     ) -> InterpResult<'tcx, io::Result<usize>> {
163         if !communicate_allowed {
164             // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
165             helpers::isolation_abort_error("`read` from stdin")?;
166         }
167         Ok(Read::read(self, bytes))
168     }
169
170     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
171         Ok(Box::new(io::stdin()))
172     }
173
174     #[cfg(unix)]
175     fn as_unix_host_fd(&self) -> Option<i32> {
176         Some(libc::STDIN_FILENO)
177     }
178
179     fn is_tty(&self) -> bool {
180         self.is_terminal()
181     }
182 }
183
184 impl FileDescriptor for io::Stdout {
185     fn name(&self) -> &'static str {
186         "stdout"
187     }
188
189     fn write<'tcx>(
190         &self,
191         _communicate_allowed: bool,
192         bytes: &[u8],
193     ) -> InterpResult<'tcx, io::Result<usize>> {
194         // We allow writing to stderr even with isolation enabled.
195         let result = Write::write(&mut { self }, bytes);
196         // Stdout is buffered, flush to make sure it appears on the
197         // screen.  This is the write() syscall of the interpreted
198         // program, we want it to correspond to a write() syscall on
199         // the host -- there is no good in adding extra buffering
200         // here.
201         io::stdout().flush().unwrap();
202
203         Ok(result)
204     }
205
206     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
207         Ok(Box::new(io::stdout()))
208     }
209
210     #[cfg(unix)]
211     fn as_unix_host_fd(&self) -> Option<i32> {
212         Some(libc::STDOUT_FILENO)
213     }
214
215     fn is_tty(&self) -> bool {
216         self.is_terminal()
217     }
218 }
219
220 impl FileDescriptor for io::Stderr {
221     fn name(&self) -> &'static str {
222         "stderr"
223     }
224
225     fn write<'tcx>(
226         &self,
227         _communicate_allowed: bool,
228         bytes: &[u8],
229     ) -> InterpResult<'tcx, io::Result<usize>> {
230         // We allow writing to stderr even with isolation enabled.
231         // No need to flush, stderr is not buffered.
232         Ok(Write::write(&mut { self }, bytes))
233     }
234
235     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
236         Ok(Box::new(io::stderr()))
237     }
238
239     #[cfg(unix)]
240     fn as_unix_host_fd(&self) -> Option<i32> {
241         Some(libc::STDERR_FILENO)
242     }
243
244     fn is_tty(&self) -> bool {
245         self.is_terminal()
246     }
247 }
248
249 #[derive(Debug)]
250 struct NullOutput;
251
252 impl FileDescriptor for NullOutput {
253     fn name(&self) -> &'static str {
254         "stderr and stdout"
255     }
256
257     fn write<'tcx>(
258         &self,
259         _communicate_allowed: bool,
260         bytes: &[u8],
261     ) -> InterpResult<'tcx, io::Result<usize>> {
262         // We just don't write anything, but report to the user that we did.
263         Ok(Ok(bytes.len()))
264     }
265
266     fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
267         Ok(Box::new(NullOutput))
268     }
269
270     fn is_tty(&self) -> bool {
271         false
272     }
273 }
274
275 #[derive(Debug)]
276 pub struct FileHandler {
277     handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
278 }
279
280 impl VisitTags for FileHandler {
281     fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
282         // All our FileDescriptor do not have any tags.
283     }
284 }
285
286 impl FileHandler {
287     pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
288         let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
289         handles.insert(0i32, Box::new(io::stdin()));
290         if mute_stdout_stderr {
291             handles.insert(1i32, Box::new(NullOutput));
292             handles.insert(2i32, Box::new(NullOutput));
293         } else {
294             handles.insert(1i32, Box::new(io::stdout()));
295             handles.insert(2i32, Box::new(io::stderr()));
296         }
297         FileHandler { handles }
298     }
299
300     fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
301         self.insert_fd_with_min_fd(file_handle, 0)
302     }
303
304     fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
305         // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
306         // between used FDs, the find_map combinator will return it. If the first such unused FD
307         // is after all other used FDs, the find_map combinator will return None, and we will use
308         // the FD following the greatest FD thus far.
309         let candidate_new_fd =
310             self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
311                 if *fd != counter {
312                     // There was a gap in the fds stored, return the first unused one
313                     // (note that this relies on BTreeMap iterating in key order)
314                     Some(counter)
315                 } else {
316                     // This fd is used, keep going
317                     None
318                 }
319             });
320         let new_fd = candidate_new_fd.unwrap_or_else(|| {
321             // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
322             // maximum fd in the map
323             self.handles
324                 .last_key_value()
325                 .map(|(fd, _)| fd.checked_add(1).unwrap())
326                 .unwrap_or(min_fd)
327         });
328
329         self.handles.try_insert(new_fd, file_handle).unwrap();
330         new_fd
331     }
332 }
333
334 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
335 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
336     fn macos_stat_write_buf(
337         &mut self,
338         metadata: FileMetadata,
339         buf_op: &OpTy<'tcx, Provenance>,
340     ) -> InterpResult<'tcx, i32> {
341         let this = self.eval_context_mut();
342
343         let mode: u16 = metadata.mode.to_u16()?;
344
345         let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
346         let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
347         let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
348
349         let buf = this.deref_operand(buf_op)?;
350         this.write_int_fields_named(
351             &[
352                 ("st_dev", 0),
353                 ("st_mode", mode.into()),
354                 ("st_nlink", 0),
355                 ("st_ino", 0),
356                 ("st_uid", 0),
357                 ("st_gid", 0),
358                 ("st_rdev", 0),
359                 ("st_atime", access_sec.into()),
360                 ("st_atime_nsec", access_nsec.into()),
361                 ("st_mtime", modified_sec.into()),
362                 ("st_mtime_nsec", modified_nsec.into()),
363                 ("st_ctime", 0),
364                 ("st_ctime_nsec", 0),
365                 ("st_birthtime", created_sec.into()),
366                 ("st_birthtime_nsec", created_nsec.into()),
367                 ("st_size", metadata.size.into()),
368                 ("st_blocks", 0),
369                 ("st_blksize", 0),
370                 ("st_flags", 0),
371                 ("st_gen", 0),
372             ],
373             &buf,
374         )?;
375
376         Ok(0)
377     }
378
379     /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
380     /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
381     /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
382     /// types (like `read`, that returns an `i64`).
383     fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
384         let this = self.eval_context_mut();
385         let ebadf = this.eval_libc("EBADF")?;
386         this.set_last_error(ebadf)?;
387         Ok((-1).into())
388     }
389
390     fn file_type_to_d_type(
391         &mut self,
392         file_type: std::io::Result<FileType>,
393     ) -> InterpResult<'tcx, i32> {
394         let this = self.eval_context_mut();
395         match file_type {
396             Ok(file_type) => {
397                 if file_type.is_dir() {
398                     Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
399                 } else if file_type.is_file() {
400                     Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
401                 } else if file_type.is_symlink() {
402                     Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
403                 } else {
404                     // Certain file types are only supported when the host is a Unix system.
405                     // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
406                     // DT_UNKNOWN sooner.
407
408                     #[cfg(unix)]
409                     {
410                         use std::os::unix::fs::FileTypeExt;
411                         if file_type.is_block_device() {
412                             Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
413                         } else if file_type.is_char_device() {
414                             Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
415                         } else if file_type.is_fifo() {
416                             Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
417                         } else if file_type.is_socket() {
418                             Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
419                         } else {
420                             Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
421                         }
422                     }
423                     #[cfg(not(unix))]
424                     Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
425                 }
426             }
427             Err(e) =>
428                 match e.raw_os_error() {
429                     Some(error) => Ok(error),
430                     None =>
431                         throw_unsup_format!(
432                             "the error {} couldn't be converted to a return value",
433                             e
434                         ),
435                 },
436         }
437     }
438 }
439
440 /// An open directory, tracked by DirHandler.
441 #[derive(Debug)]
442 pub struct OpenDir {
443     /// The directory reader on the host.
444     read_dir: ReadDir,
445     /// The most recent entry returned by readdir()
446     entry: Pointer<Option<Provenance>>,
447 }
448
449 impl OpenDir {
450     fn new(read_dir: ReadDir) -> Self {
451         // We rely on `free` being a NOP on null pointers.
452         Self { read_dir, entry: Pointer::null() }
453     }
454 }
455
456 #[derive(Debug)]
457 pub struct DirHandler {
458     /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
459     /// and closedir.
460     ///
461     /// When opendir is called, a directory iterator is created on the host for the target
462     /// directory, and an entry is stored in this hash map, indexed by an ID which represents
463     /// the directory stream. When readdir is called, the directory stream ID is used to look up
464     /// the corresponding ReadDir iterator from this map, and information from the next
465     /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
466     /// the map.
467     streams: FxHashMap<u64, OpenDir>,
468     /// ID number to be used by the next call to opendir
469     next_id: u64,
470 }
471
472 impl DirHandler {
473     #[allow(clippy::integer_arithmetic)]
474     fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
475         let id = self.next_id;
476         self.next_id += 1;
477         self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap();
478         id
479     }
480 }
481
482 impl Default for DirHandler {
483     fn default() -> DirHandler {
484         DirHandler {
485             streams: FxHashMap::default(),
486             // Skip 0 as an ID, because it looks like a null pointer to libc
487             next_id: 1,
488         }
489     }
490 }
491
492 impl VisitTags for DirHandler {
493     fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
494         let DirHandler { streams, next_id: _ } = self;
495
496         for dir in streams.values() {
497             dir.entry.visit_tags(visit);
498         }
499     }
500 }
501
502 fn maybe_sync_file(
503     file: &File,
504     writable: bool,
505     operation: fn(&File) -> std::io::Result<()>,
506 ) -> std::io::Result<i32> {
507     if !writable && cfg!(windows) {
508         // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
509         // for writing. (FlushFileBuffers requires that the file handle have the
510         // GENERIC_WRITE right)
511         Ok(0i32)
512     } else {
513         let result = operation(file);
514         result.map(|_| 0i32)
515     }
516 }
517
518 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
519 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
520     fn open(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
521         if args.len() < 2 {
522             throw_ub_format!(
523                 "incorrect number of arguments for `open`: got {}, expected at least 2",
524                 args.len()
525             );
526         }
527
528         let this = self.eval_context_mut();
529
530         let path = this.read_pointer(&args[0])?;
531         let flag = this.read_scalar(&args[1])?.to_i32()?;
532
533         let mut options = OpenOptions::new();
534
535         let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
536         let o_wronly = this.eval_libc_i32("O_WRONLY")?;
537         let o_rdwr = this.eval_libc_i32("O_RDWR")?;
538         // The first two bits of the flag correspond to the access mode in linux, macOS and
539         // windows. We need to check that in fact the access mode flags for the current target
540         // only use these two bits, otherwise we are in an unsupported target and should error.
541         if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
542             throw_unsup_format!("access mode flags on this target are unsupported");
543         }
544         let mut writable = true;
545
546         // Now we check the access mode
547         let access_mode = flag & 0b11;
548
549         if access_mode == o_rdonly {
550             writable = false;
551             options.read(true);
552         } else if access_mode == o_wronly {
553             options.write(true);
554         } else if access_mode == o_rdwr {
555             options.read(true).write(true);
556         } else {
557             throw_unsup_format!("unsupported access mode {:#x}", access_mode);
558         }
559         // We need to check that there aren't unsupported options in `flag`. For this we try to
560         // reproduce the content of `flag` in the `mirror` variable using only the supported
561         // options.
562         let mut mirror = access_mode;
563
564         let o_append = this.eval_libc_i32("O_APPEND")?;
565         if flag & o_append != 0 {
566             options.append(true);
567             mirror |= o_append;
568         }
569         let o_trunc = this.eval_libc_i32("O_TRUNC")?;
570         if flag & o_trunc != 0 {
571             options.truncate(true);
572             mirror |= o_trunc;
573         }
574         let o_creat = this.eval_libc_i32("O_CREAT")?;
575         if flag & o_creat != 0 {
576             // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but
577             // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
578             // (see https://github.com/rust-lang/rust/issues/71915).
579             let mode = if let Some(arg) = args.get(2) {
580                 this.read_scalar(arg)?.to_u32()?
581             } else {
582                 throw_ub_format!(
583                     "incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3",
584                     args.len()
585                 );
586             };
587
588             if mode != 0o666 {
589                 throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
590             }
591
592             mirror |= o_creat;
593
594             let o_excl = this.eval_libc_i32("O_EXCL")?;
595             if flag & o_excl != 0 {
596                 mirror |= o_excl;
597                 options.create_new(true);
598             } else {
599                 options.create(true);
600             }
601         }
602         let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
603         if flag & o_cloexec != 0 {
604             // We do not need to do anything for this flag because `std` already sets it.
605             // (Technically we do not support *not* setting this flag, but we ignore that.)
606             mirror |= o_cloexec;
607         }
608         // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
609         // then we throw an error.
610         if flag != mirror {
611             throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
612         }
613
614         let path = this.read_path_from_c_str(path)?;
615
616         // Reject if isolation is enabled.
617         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
618             this.reject_in_isolation("`open`", reject_with)?;
619             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
620             return Ok(-1);
621         }
622
623         let fd = options.open(path).map(|file| {
624             let fh = &mut this.machine.file_handler;
625             fh.insert_fd(Box::new(FileHandle { file, writable }))
626         });
627
628         this.try_unwrap_io_result(fd)
629     }
630
631     fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
632         let this = self.eval_context_mut();
633
634         if args.len() < 2 {
635             throw_ub_format!(
636                 "incorrect number of arguments for fcntl: got {}, expected at least 2",
637                 args.len()
638             );
639         }
640         let fd = this.read_scalar(&args[0])?.to_i32()?;
641         let cmd = this.read_scalar(&args[1])?.to_i32()?;
642
643         // Reject if isolation is enabled.
644         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
645             this.reject_in_isolation("`fcntl`", reject_with)?;
646             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
647             return Ok(-1);
648         }
649
650         // We only support getting the flags for a descriptor.
651         if cmd == this.eval_libc_i32("F_GETFD")? {
652             // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
653             // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
654             // always sets this flag when opening a file. However we still need to check that the
655             // file itself is open.
656             if this.machine.file_handler.handles.contains_key(&fd) {
657                 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
658             } else {
659                 this.handle_not_found()
660             }
661         } else if cmd == this.eval_libc_i32("F_DUPFD")?
662             || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
663         {
664             // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
665             // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
666             // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
667             // thus they can share the same implementation here.
668             if args.len() < 3 {
669                 throw_ub_format!(
670                     "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
671                     args.len()
672                 );
673             }
674             let start = this.read_scalar(&args[2])?.to_i32()?;
675
676             let fh = &mut this.machine.file_handler;
677
678             match fh.handles.get_mut(&fd) {
679                 Some(file_descriptor) => {
680                     let dup_result = file_descriptor.dup();
681                     match dup_result {
682                         Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
683                         Err(e) => {
684                             this.set_last_error_from_io_error(e.kind())?;
685                             Ok(-1)
686                         }
687                     }
688                 }
689                 None => this.handle_not_found(),
690             }
691         } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
692             if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
693                 // FIXME: Support fullfsync for all FDs
694                 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
695                 let io_result = maybe_sync_file(file, *writable, File::sync_all);
696                 this.try_unwrap_io_result(io_result)
697             } else {
698                 this.handle_not_found()
699             }
700         } else {
701             throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
702         }
703     }
704
705     fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
706         let this = self.eval_context_mut();
707
708         let fd = this.read_scalar(fd_op)?.to_i32()?;
709
710         Ok(Scalar::from_i32(
711             if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
712                 let result = file_descriptor.close(this.machine.communicate())?;
713                 this.try_unwrap_io_result(result)?
714             } else {
715                 this.handle_not_found()?
716             },
717         ))
718     }
719
720     fn read(
721         &mut self,
722         fd: i32,
723         buf: Pointer<Option<Provenance>>,
724         count: u64,
725     ) -> InterpResult<'tcx, i64> {
726         let this = self.eval_context_mut();
727
728         // Isolation check is done via `FileDescriptor` trait.
729
730         trace!("Reading from FD {}, size {}", fd, count);
731
732         // Check that the *entire* buffer is actually valid memory.
733         this.check_ptr_access_align(
734             buf,
735             Size::from_bytes(count),
736             Align::ONE,
737             CheckInAllocMsg::MemoryAccessTest,
738         )?;
739
740         // We cap the number of read bytes to the largest value that we are able to fit in both the
741         // host's and target's `isize`. This saves us from having to handle overflows later.
742         let count = count
743             .min(u64::try_from(this.machine_isize_max()).unwrap())
744             .min(u64::try_from(isize::MAX).unwrap());
745         let communicate = this.machine.communicate();
746
747         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
748             trace!("read: FD mapped to {:?}", file_descriptor);
749             // We want to read at most `count` bytes. We are sure that `count` is not negative
750             // because it was a target's `usize`. Also we are sure that its smaller than
751             // `usize::MAX` because it is bounded by the host's `isize`.
752             let mut bytes = vec![0; usize::try_from(count).unwrap()];
753             // `File::read` never returns a value larger than `count`,
754             // so this cannot fail.
755             let result =
756                 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
757
758             match result {
759                 Ok(read_bytes) => {
760                     // If reading to `bytes` did not fail, we write those bytes to the buffer.
761                     this.write_bytes_ptr(buf, bytes)?;
762                     Ok(read_bytes)
763                 }
764                 Err(e) => {
765                     this.set_last_error_from_io_error(e.kind())?;
766                     Ok(-1)
767                 }
768             }
769         } else {
770             trace!("read: FD not found");
771             this.handle_not_found()
772         }
773     }
774
775     fn write(
776         &mut self,
777         fd: i32,
778         buf: Pointer<Option<Provenance>>,
779         count: u64,
780     ) -> InterpResult<'tcx, i64> {
781         let this = self.eval_context_mut();
782
783         // Isolation check is done via `FileDescriptor` trait.
784
785         // Check that the *entire* buffer is actually valid memory.
786         this.check_ptr_access_align(
787             buf,
788             Size::from_bytes(count),
789             Align::ONE,
790             CheckInAllocMsg::MemoryAccessTest,
791         )?;
792
793         // We cap the number of written bytes to the largest value that we are able to fit in both the
794         // host's and target's `isize`. This saves us from having to handle overflows later.
795         let count = count
796             .min(u64::try_from(this.machine_isize_max()).unwrap())
797             .min(u64::try_from(isize::MAX).unwrap());
798         let communicate = this.machine.communicate();
799
800         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
801             let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
802             let result =
803                 file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
804             this.try_unwrap_io_result(result)
805         } else {
806             this.handle_not_found()
807         }
808     }
809
810     fn lseek64(
811         &mut self,
812         fd_op: &OpTy<'tcx, Provenance>,
813         offset_op: &OpTy<'tcx, Provenance>,
814         whence_op: &OpTy<'tcx, Provenance>,
815     ) -> InterpResult<'tcx, Scalar<Provenance>> {
816         let this = self.eval_context_mut();
817
818         // Isolation check is done via `FileDescriptor` trait.
819
820         let fd = this.read_scalar(fd_op)?.to_i32()?;
821         let offset = this.read_scalar(offset_op)?.to_i64()?;
822         let whence = this.read_scalar(whence_op)?.to_i32()?;
823
824         let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
825             SeekFrom::Start(u64::try_from(offset).unwrap())
826         } else if whence == this.eval_libc_i32("SEEK_CUR")? {
827             SeekFrom::Current(offset)
828         } else if whence == this.eval_libc_i32("SEEK_END")? {
829             SeekFrom::End(offset)
830         } else {
831             let einval = this.eval_libc("EINVAL")?;
832             this.set_last_error(einval)?;
833             return Ok(Scalar::from_i64(-1));
834         };
835
836         let communicate = this.machine.communicate();
837         Ok(Scalar::from_i64(
838             if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
839                 let result = file_descriptor
840                     .seek(communicate, seek_from)?
841                     .map(|offset| i64::try_from(offset).unwrap());
842                 this.try_unwrap_io_result(result)?
843             } else {
844                 this.handle_not_found()?
845             },
846         ))
847     }
848
849     fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
850         let this = self.eval_context_mut();
851
852         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
853
854         // Reject if isolation is enabled.
855         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
856             this.reject_in_isolation("`unlink`", reject_with)?;
857             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
858             return Ok(-1);
859         }
860
861         let result = remove_file(path).map(|_| 0);
862         this.try_unwrap_io_result(result)
863     }
864
865     fn symlink(
866         &mut self,
867         target_op: &OpTy<'tcx, Provenance>,
868         linkpath_op: &OpTy<'tcx, Provenance>,
869     ) -> InterpResult<'tcx, i32> {
870         #[cfg(unix)]
871         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
872             std::os::unix::fs::symlink(src, dst)
873         }
874
875         #[cfg(windows)]
876         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
877             use std::os::windows::fs;
878             if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
879         }
880
881         let this = self.eval_context_mut();
882         let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
883         let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
884
885         // Reject if isolation is enabled.
886         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
887             this.reject_in_isolation("`symlink`", reject_with)?;
888             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
889             return Ok(-1);
890         }
891
892         let result = create_link(&target, &linkpath).map(|_| 0);
893         this.try_unwrap_io_result(result)
894     }
895
896     fn macos_stat(
897         &mut self,
898         path_op: &OpTy<'tcx, Provenance>,
899         buf_op: &OpTy<'tcx, Provenance>,
900     ) -> InterpResult<'tcx, Scalar<Provenance>> {
901         let this = self.eval_context_mut();
902         this.assert_target_os("macos", "stat");
903
904         let path_scalar = this.read_pointer(path_op)?;
905         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
906
907         // Reject if isolation is enabled.
908         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
909             this.reject_in_isolation("`stat`", reject_with)?;
910             let eacc = this.eval_libc("EACCES")?;
911             this.set_last_error(eacc)?;
912             return Ok(Scalar::from_i32(-1));
913         }
914
915         // `stat` always follows symlinks.
916         let metadata = match FileMetadata::from_path(this, &path, true)? {
917             Some(metadata) => metadata,
918             None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
919         };
920
921         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
922     }
923
924     // `lstat` is used to get symlink metadata.
925     fn macos_lstat(
926         &mut self,
927         path_op: &OpTy<'tcx, Provenance>,
928         buf_op: &OpTy<'tcx, Provenance>,
929     ) -> InterpResult<'tcx, Scalar<Provenance>> {
930         let this = self.eval_context_mut();
931         this.assert_target_os("macos", "lstat");
932
933         let path_scalar = this.read_pointer(path_op)?;
934         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
935
936         // Reject if isolation is enabled.
937         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
938             this.reject_in_isolation("`lstat`", reject_with)?;
939             let eacc = this.eval_libc("EACCES")?;
940             this.set_last_error(eacc)?;
941             return Ok(Scalar::from_i32(-1));
942         }
943
944         let metadata = match FileMetadata::from_path(this, &path, false)? {
945             Some(metadata) => metadata,
946             None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
947         };
948
949         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
950     }
951
952     fn macos_fstat(
953         &mut self,
954         fd_op: &OpTy<'tcx, Provenance>,
955         buf_op: &OpTy<'tcx, Provenance>,
956     ) -> InterpResult<'tcx, Scalar<Provenance>> {
957         let this = self.eval_context_mut();
958
959         this.assert_target_os("macos", "fstat");
960
961         let fd = this.read_scalar(fd_op)?.to_i32()?;
962
963         // Reject if isolation is enabled.
964         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
965             this.reject_in_isolation("`fstat`", reject_with)?;
966             // Set error code as "EBADF" (bad fd)
967             return Ok(Scalar::from_i32(this.handle_not_found()?));
968         }
969
970         let metadata = match FileMetadata::from_fd(this, fd)? {
971             Some(metadata) => metadata,
972             None => return Ok(Scalar::from_i32(-1)),
973         };
974         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
975     }
976
977     fn linux_statx(
978         &mut self,
979         dirfd_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
980         pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
981         flags_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
982         mask_op: &OpTy<'tcx, Provenance>,     // Should be an `unsigned int`
983         statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
984     ) -> InterpResult<'tcx, i32> {
985         let this = self.eval_context_mut();
986
987         this.assert_target_os("linux", "statx");
988
989         let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
990         let pathname_ptr = this.read_pointer(pathname_op)?;
991         let flags = this.read_scalar(flags_op)?.to_i32()?;
992         let _mask = this.read_scalar(mask_op)?.to_u32()?;
993         let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
994
995         // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
996         if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
997             let efault = this.eval_libc("EFAULT")?;
998             this.set_last_error(efault)?;
999             return Ok(-1);
1000         }
1001
1002         // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
1003         // proper `MemPlace` and then write the results of this function to it. However, the
1004         // `syscall` function is untyped. This means that all the `statx` parameters are provided
1005         // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
1006         // `statxbuf_op` by using the `libc::statx` struct type.
1007         let statxbuf = {
1008             let statx_layout = this.libc_ty_layout("statx")?;
1009             MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
1010         };
1011
1012         let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1013         // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
1014         let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
1015         // We only support:
1016         // * interpreting `path` as an absolute directory,
1017         // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1018         // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1019         // set.
1020         // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1021         // found this error, please open an issue reporting it.
1022         if !(path.is_absolute()
1023             || dirfd == this.eval_libc_i32("AT_FDCWD")?
1024             || (path.as_os_str().is_empty() && empty_path_flag))
1025         {
1026             throw_unsup_format!(
1027                 "using statx is only supported with absolute paths, relative paths with the file \
1028                 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1029                 file descriptor"
1030             )
1031         }
1032
1033         // Reject if isolation is enabled.
1034         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1035             this.reject_in_isolation("`statx`", reject_with)?;
1036             let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1037                 // since `path` is provided, either absolute or
1038                 // relative to CWD, `EACCES` is the most relevant.
1039                 this.eval_libc("EACCES")?
1040             } else {
1041                 // `dirfd` is set to target file, and `path` is empty
1042                 // (or we would have hit the `throw_unsup_format`
1043                 // above). `EACCES` would violate the spec.
1044                 assert!(empty_path_flag);
1045                 this.eval_libc("EBADF")?
1046             };
1047             this.set_last_error(ecode)?;
1048             return Ok(-1);
1049         }
1050
1051         // the `_mask_op` paramter specifies the file information that the caller requested.
1052         // However `statx` is allowed to return information that was not requested or to not
1053         // return information that was requested. This `mask` represents the information we can
1054         // actually provide for any target.
1055         let mut mask =
1056             this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1057
1058         // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1059         // symbolic links.
1060         let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1061
1062         // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1063         // represented by dirfd, whether it's a directory or otherwise.
1064         let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1065             FileMetadata::from_fd(this, dirfd)?
1066         } else {
1067             FileMetadata::from_path(this, &path, follow_symlink)?
1068         };
1069         let metadata = match metadata {
1070             Some(metadata) => metadata,
1071             None => return Ok(-1),
1072         };
1073
1074         // The `mode` field specifies the type of the file and the permissions over the file for
1075         // the owner, its group and other users. Given that we can only provide the file type
1076         // without using platform specific methods, we only set the bits corresponding to the file
1077         // type. This should be an `__u16` but `libc` provides its values as `u32`.
1078         let mode: u16 = metadata
1079             .mode
1080             .to_u32()?
1081             .try_into()
1082             .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1083
1084         // We need to set the corresponding bits of `mask` if the access, creation and modification
1085         // times were available. Otherwise we let them be zero.
1086         let (access_sec, access_nsec) = metadata
1087             .accessed
1088             .map(|tup| {
1089                 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1090                 InterpResult::Ok(tup)
1091             })
1092             .unwrap_or_else(|| Ok((0, 0)))?;
1093
1094         let (created_sec, created_nsec) = metadata
1095             .created
1096             .map(|tup| {
1097                 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1098                 InterpResult::Ok(tup)
1099             })
1100             .unwrap_or_else(|| Ok((0, 0)))?;
1101
1102         let (modified_sec, modified_nsec) = metadata
1103             .modified
1104             .map(|tup| {
1105                 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1106                 InterpResult::Ok(tup)
1107             })
1108             .unwrap_or_else(|| Ok((0, 0)))?;
1109
1110         // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1111         this.write_int_fields_named(
1112             &[
1113                 ("stx_mask", mask.into()),
1114                 ("stx_blksize", 0),
1115                 ("stx_attributes", 0),
1116                 ("stx_nlink", 0),
1117                 ("stx_uid", 0),
1118                 ("stx_gid", 0),
1119                 ("stx_mode", mode.into()),
1120                 ("stx_ino", 0),
1121                 ("stx_size", metadata.size.into()),
1122                 ("stx_blocks", 0),
1123                 ("stx_attributes_mask", 0),
1124                 ("stx_rdev_major", 0),
1125                 ("stx_rdev_minor", 0),
1126                 ("stx_dev_major", 0),
1127                 ("stx_dev_minor", 0),
1128             ],
1129             &statxbuf,
1130         )?;
1131         #[rustfmt::skip]
1132         this.write_int_fields_named(
1133             &[
1134                 ("tv_sec", access_sec.into()),
1135                 ("tv_nsec", access_nsec.into()),
1136             ],
1137             &this.mplace_field_named(&statxbuf, "stx_atime")?,
1138         )?;
1139         #[rustfmt::skip]
1140         this.write_int_fields_named(
1141             &[
1142                 ("tv_sec", created_sec.into()),
1143                 ("tv_nsec", created_nsec.into()),
1144             ],
1145             &this.mplace_field_named(&statxbuf, "stx_btime")?,
1146         )?;
1147         #[rustfmt::skip]
1148         this.write_int_fields_named(
1149             &[
1150                 ("tv_sec", 0.into()),
1151                 ("tv_nsec", 0.into()),
1152             ],
1153             &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1154         )?;
1155         #[rustfmt::skip]
1156         this.write_int_fields_named(
1157             &[
1158                 ("tv_sec", modified_sec.into()),
1159                 ("tv_nsec", modified_nsec.into()),
1160             ],
1161             &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1162         )?;
1163
1164         Ok(0)
1165     }
1166
1167     fn rename(
1168         &mut self,
1169         oldpath_op: &OpTy<'tcx, Provenance>,
1170         newpath_op: &OpTy<'tcx, Provenance>,
1171     ) -> InterpResult<'tcx, i32> {
1172         let this = self.eval_context_mut();
1173
1174         let oldpath_ptr = this.read_pointer(oldpath_op)?;
1175         let newpath_ptr = this.read_pointer(newpath_op)?;
1176
1177         if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1178             let efault = this.eval_libc("EFAULT")?;
1179             this.set_last_error(efault)?;
1180             return Ok(-1);
1181         }
1182
1183         let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1184         let newpath = this.read_path_from_c_str(newpath_ptr)?;
1185
1186         // Reject if isolation is enabled.
1187         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1188             this.reject_in_isolation("`rename`", reject_with)?;
1189             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1190             return Ok(-1);
1191         }
1192
1193         let result = rename(oldpath, newpath).map(|_| 0);
1194
1195         this.try_unwrap_io_result(result)
1196     }
1197
1198     fn mkdir(
1199         &mut self,
1200         path_op: &OpTy<'tcx, Provenance>,
1201         mode_op: &OpTy<'tcx, Provenance>,
1202     ) -> InterpResult<'tcx, i32> {
1203         let this = self.eval_context_mut();
1204
1205         #[cfg_attr(not(unix), allow(unused_variables))]
1206         let mode = if this.tcx.sess.target.os == "macos" {
1207             u32::from(this.read_scalar(mode_op)?.to_u16()?)
1208         } else {
1209             this.read_scalar(mode_op)?.to_u32()?
1210         };
1211
1212         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1213
1214         // Reject if isolation is enabled.
1215         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1216             this.reject_in_isolation("`mkdir`", reject_with)?;
1217             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1218             return Ok(-1);
1219         }
1220
1221         #[cfg_attr(not(unix), allow(unused_mut))]
1222         let mut builder = DirBuilder::new();
1223
1224         // If the host supports it, forward on the mode of the directory
1225         // (i.e. permission bits and the sticky bit)
1226         #[cfg(unix)]
1227         {
1228             use std::os::unix::fs::DirBuilderExt;
1229             builder.mode(mode);
1230         }
1231
1232         let result = builder.create(path).map(|_| 0i32);
1233
1234         this.try_unwrap_io_result(result)
1235     }
1236
1237     fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1238         let this = self.eval_context_mut();
1239
1240         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1241
1242         // Reject if isolation is enabled.
1243         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1244             this.reject_in_isolation("`rmdir`", reject_with)?;
1245             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1246             return Ok(-1);
1247         }
1248
1249         let result = remove_dir(path).map(|_| 0i32);
1250
1251         this.try_unwrap_io_result(result)
1252     }
1253
1254     fn opendir(
1255         &mut self,
1256         name_op: &OpTy<'tcx, Provenance>,
1257     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1258         let this = self.eval_context_mut();
1259
1260         let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1261
1262         // Reject if isolation is enabled.
1263         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1264             this.reject_in_isolation("`opendir`", reject_with)?;
1265             let eacc = this.eval_libc("EACCES")?;
1266             this.set_last_error(eacc)?;
1267             return Ok(Scalar::null_ptr(this));
1268         }
1269
1270         let result = read_dir(name);
1271
1272         match result {
1273             Ok(dir_iter) => {
1274                 let id = this.machine.dir_handler.insert_new(dir_iter);
1275
1276                 // The libc API for opendir says that this method returns a pointer to an opaque
1277                 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1278                 // pointer width.
1279                 Ok(Scalar::from_machine_usize(id, this))
1280             }
1281             Err(e) => {
1282                 this.set_last_error_from_io_error(e.kind())?;
1283                 Ok(Scalar::null_ptr(this))
1284             }
1285         }
1286     }
1287
1288     fn linux_readdir64(
1289         &mut self,
1290         dirp_op: &OpTy<'tcx, Provenance>,
1291     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1292         let this = self.eval_context_mut();
1293
1294         this.assert_target_os("linux", "readdir64");
1295
1296         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1297
1298         // Reject if isolation is enabled.
1299         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1300             this.reject_in_isolation("`readdir`", reject_with)?;
1301             let eacc = this.eval_libc("EBADF")?;
1302             this.set_last_error(eacc)?;
1303             return Ok(Scalar::null_ptr(this));
1304         }
1305
1306         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1307             err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1308         })?;
1309
1310         let entry = match open_dir.read_dir.next() {
1311             Some(Ok(dir_entry)) => {
1312                 // Write the directory entry into a newly allocated buffer.
1313                 // The name is written with write_bytes, while the rest of the
1314                 // dirent64 struct is written using write_int_fields.
1315
1316                 // For reference:
1317                 // pub struct dirent64 {
1318                 //     pub d_ino: ino64_t,
1319                 //     pub d_off: off64_t,
1320                 //     pub d_reclen: c_ushort,
1321                 //     pub d_type: c_uchar,
1322                 //     pub d_name: [c_char; 256],
1323                 // }
1324
1325                 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1326                 name.push("\0"); // Add a NUL terminator
1327                 let name_bytes = os_str_to_bytes(&name)?;
1328                 let name_len = u64::try_from(name_bytes.len()).unwrap();
1329
1330                 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1331                 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1332                 let size = d_name_offset.checked_add(name_len).unwrap();
1333
1334                 let entry =
1335                     this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1336
1337                 // If the host is a Unix system, fill in the inode number with its real value.
1338                 // If not, use 0 as a fallback value.
1339                 #[cfg(unix)]
1340                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1341                 #[cfg(not(unix))]
1342                 let ino = 0u64;
1343
1344                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1345
1346                 this.write_int_fields_named(
1347                     &[
1348                         ("d_ino", ino.into()),
1349                         ("d_off", 0),
1350                         ("d_reclen", size.into()),
1351                         ("d_type", file_type.into()),
1352                     ],
1353                     &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1354                 )?;
1355
1356                 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1357                 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1358
1359                 entry
1360             }
1361             None => {
1362                 // end of stream: return NULL
1363                 Pointer::null()
1364             }
1365             Some(Err(e)) => {
1366                 this.set_last_error_from_io_error(e.kind())?;
1367                 Pointer::null()
1368             }
1369         };
1370
1371         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1372         let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1373         this.free(old_entry, MiriMemoryKind::Runtime)?;
1374
1375         Ok(Scalar::from_maybe_pointer(entry, this))
1376     }
1377
1378     fn macos_readdir_r(
1379         &mut self,
1380         dirp_op: &OpTy<'tcx, Provenance>,
1381         entry_op: &OpTy<'tcx, Provenance>,
1382         result_op: &OpTy<'tcx, Provenance>,
1383     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1384         let this = self.eval_context_mut();
1385
1386         this.assert_target_os("macos", "readdir_r");
1387
1388         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1389
1390         // Reject if isolation is enabled.
1391         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1392             this.reject_in_isolation("`readdir_r`", reject_with)?;
1393             // Set error code as "EBADF" (bad fd)
1394             return Ok(Scalar::from_i32(this.handle_not_found()?));
1395         }
1396
1397         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1398             err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1399         })?;
1400         Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1401             Some(Ok(dir_entry)) => {
1402                 // Write into entry, write pointer to result, return 0 on success.
1403                 // The name is written with write_os_str_to_c_str, while the rest of the
1404                 // dirent struct is written using write_int_fields.
1405
1406                 // For reference:
1407                 // pub struct dirent {
1408                 //     pub d_ino: u64,
1409                 //     pub d_seekoff: u64,
1410                 //     pub d_reclen: u16,
1411                 //     pub d_namlen: u16,
1412                 //     pub d_type: u8,
1413                 //     pub d_name: [c_char; 1024],
1414                 // }
1415
1416                 let entry_place = this.deref_operand(entry_op)?;
1417                 let name_place = this.mplace_field(&entry_place, 5)?;
1418
1419                 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1420                 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1421                     &file_name,
1422                     name_place.ptr,
1423                     name_place.layout.size.bytes(),
1424                 )?;
1425                 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1426                 if !name_fits {
1427                     throw_unsup_format!(
1428                         "a directory entry had a name too large to fit in libc::dirent"
1429                     );
1430                 }
1431
1432                 let entry_place = this.deref_operand(entry_op)?;
1433
1434                 // If the host is a Unix system, fill in the inode number with its real value.
1435                 // If not, use 0 as a fallback value.
1436                 #[cfg(unix)]
1437                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1438                 #[cfg(not(unix))]
1439                 let ino = 0u64;
1440
1441                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1442
1443                 this.write_int_fields_named(
1444                     &[
1445                         ("d_ino", ino.into()),
1446                         ("d_seekoff", 0),
1447                         ("d_reclen", 0),
1448                         ("d_namlen", file_name_len.into()),
1449                         ("d_type", file_type.into()),
1450                     ],
1451                     &entry_place,
1452                 )?;
1453
1454                 let result_place = this.deref_operand(result_op)?;
1455                 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1456
1457                 0
1458             }
1459             None => {
1460                 // end of stream: return 0, assign *result=NULL
1461                 this.write_null(&this.deref_operand(result_op)?.into())?;
1462                 0
1463             }
1464             Some(Err(e)) =>
1465                 match e.raw_os_error() {
1466                     // return positive error number on error
1467                     Some(error) => error,
1468                     None => {
1469                         throw_unsup_format!(
1470                             "the error {} couldn't be converted to a return value",
1471                             e
1472                         )
1473                     }
1474                 },
1475         }))
1476     }
1477
1478     fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1479         let this = self.eval_context_mut();
1480
1481         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1482
1483         // Reject if isolation is enabled.
1484         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1485             this.reject_in_isolation("`closedir`", reject_with)?;
1486             // Set error code as "EBADF" (bad fd)
1487             return this.handle_not_found();
1488         }
1489
1490         if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1491             this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1492             drop(open_dir);
1493             Ok(0)
1494         } else {
1495             this.handle_not_found()
1496         }
1497     }
1498
1499     fn ftruncate64(
1500         &mut self,
1501         fd_op: &OpTy<'tcx, Provenance>,
1502         length_op: &OpTy<'tcx, Provenance>,
1503     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1504         let this = self.eval_context_mut();
1505
1506         let fd = this.read_scalar(fd_op)?.to_i32()?;
1507         let length = this.read_scalar(length_op)?.to_i64()?;
1508
1509         // Reject if isolation is enabled.
1510         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1511             this.reject_in_isolation("`ftruncate64`", reject_with)?;
1512             // Set error code as "EBADF" (bad fd)
1513             return Ok(Scalar::from_i32(this.handle_not_found()?));
1514         }
1515
1516         Ok(Scalar::from_i32(
1517             if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1518                 // FIXME: Support ftruncate64 for all FDs
1519                 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1520                 if *writable {
1521                     if let Ok(length) = length.try_into() {
1522                         let result = file.set_len(length);
1523                         this.try_unwrap_io_result(result.map(|_| 0i32))?
1524                     } else {
1525                         let einval = this.eval_libc("EINVAL")?;
1526                         this.set_last_error(einval)?;
1527                         -1
1528                     }
1529                 } else {
1530                     // The file is not writable
1531                     let einval = this.eval_libc("EINVAL")?;
1532                     this.set_last_error(einval)?;
1533                     -1
1534                 }
1535             } else {
1536                 this.handle_not_found()?
1537             },
1538         ))
1539     }
1540
1541     fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1542         // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1543         // underlying disk to finish writing. In the interest of host compatibility,
1544         // we conservatively implement this with `sync_all`, which
1545         // *does* wait for the disk.
1546
1547         let this = self.eval_context_mut();
1548
1549         let fd = this.read_scalar(fd_op)?.to_i32()?;
1550
1551         // Reject if isolation is enabled.
1552         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1553             this.reject_in_isolation("`fsync`", reject_with)?;
1554             // Set error code as "EBADF" (bad fd)
1555             return this.handle_not_found();
1556         }
1557
1558         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1559             // FIXME: Support fsync for all FDs
1560             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1561             let io_result = maybe_sync_file(file, *writable, File::sync_all);
1562             this.try_unwrap_io_result(io_result)
1563         } else {
1564             this.handle_not_found()
1565         }
1566     }
1567
1568     fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1569         let this = self.eval_context_mut();
1570
1571         let fd = this.read_scalar(fd_op)?.to_i32()?;
1572
1573         // Reject if isolation is enabled.
1574         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1575             this.reject_in_isolation("`fdatasync`", reject_with)?;
1576             // Set error code as "EBADF" (bad fd)
1577             return this.handle_not_found();
1578         }
1579
1580         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1581             // FIXME: Support fdatasync for all FDs
1582             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1583             let io_result = maybe_sync_file(file, *writable, File::sync_data);
1584             this.try_unwrap_io_result(io_result)
1585         } else {
1586             this.handle_not_found()
1587         }
1588     }
1589
1590     fn sync_file_range(
1591         &mut self,
1592         fd_op: &OpTy<'tcx, Provenance>,
1593         offset_op: &OpTy<'tcx, Provenance>,
1594         nbytes_op: &OpTy<'tcx, Provenance>,
1595         flags_op: &OpTy<'tcx, Provenance>,
1596     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1597         let this = self.eval_context_mut();
1598
1599         let fd = this.read_scalar(fd_op)?.to_i32()?;
1600         let offset = this.read_scalar(offset_op)?.to_i64()?;
1601         let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1602         let flags = this.read_scalar(flags_op)?.to_i32()?;
1603
1604         if offset < 0 || nbytes < 0 {
1605             let einval = this.eval_libc("EINVAL")?;
1606             this.set_last_error(einval)?;
1607             return Ok(Scalar::from_i32(-1));
1608         }
1609         let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1610             | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1611             | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1612         if flags & allowed_flags != flags {
1613             let einval = this.eval_libc("EINVAL")?;
1614             this.set_last_error(einval)?;
1615             return Ok(Scalar::from_i32(-1));
1616         }
1617
1618         // Reject if isolation is enabled.
1619         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1620             this.reject_in_isolation("`sync_file_range`", reject_with)?;
1621             // Set error code as "EBADF" (bad fd)
1622             return Ok(Scalar::from_i32(this.handle_not_found()?));
1623         }
1624
1625         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1626             // FIXME: Support sync_data_range for all FDs
1627             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1628             let io_result = maybe_sync_file(file, *writable, File::sync_data);
1629             Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1630         } else {
1631             Ok(Scalar::from_i32(this.handle_not_found()?))
1632         }
1633     }
1634
1635     fn readlink(
1636         &mut self,
1637         pathname_op: &OpTy<'tcx, Provenance>,
1638         buf_op: &OpTy<'tcx, Provenance>,
1639         bufsize_op: &OpTy<'tcx, Provenance>,
1640     ) -> InterpResult<'tcx, i64> {
1641         let this = self.eval_context_mut();
1642
1643         let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1644         let buf = this.read_pointer(buf_op)?;
1645         let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1646
1647         // Reject if isolation is enabled.
1648         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1649             this.reject_in_isolation("`readlink`", reject_with)?;
1650             let eacc = this.eval_libc("EACCES")?;
1651             this.set_last_error(eacc)?;
1652             return Ok(-1);
1653         }
1654
1655         let result = std::fs::read_link(pathname);
1656         match result {
1657             Ok(resolved) => {
1658                 // 'readlink' truncates the resolved path if the provided buffer is not large
1659                 // enough, and does *not* add a null terminator. That means we cannot use the usual
1660                 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1661                 let resolved = this.convert_path_separator(
1662                     Cow::Borrowed(resolved.as_ref()),
1663                     crate::shims::os_str::PathConversion::HostToTarget,
1664                 );
1665                 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1666                 let bufsize: usize = bufsize.try_into().unwrap();
1667                 if path_bytes.len() > bufsize {
1668                     path_bytes = &path_bytes[..bufsize]
1669                 }
1670                 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1671                 Ok(path_bytes.len().try_into().unwrap())
1672             }
1673             Err(e) => {
1674                 this.set_last_error_from_io_error(e.kind())?;
1675                 Ok(-1)
1676             }
1677         }
1678     }
1679
1680     #[cfg_attr(not(unix), allow(unused))]
1681     fn isatty(
1682         &mut self,
1683         miri_fd: &OpTy<'tcx, Provenance>,
1684     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1685         let this = self.eval_context_mut();
1686         // "returns 1 if fd is an open file descriptor referring to a terminal;
1687         // otherwise 0 is returned, and errno is set to indicate the error"
1688         if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1689             let fd = this.read_scalar(miri_fd)?.to_i32()?;
1690             if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1691                 return Ok(Scalar::from_i32(1));
1692             }
1693         }
1694         // Fallback when the FD was not found or isolation is enabled.
1695         let enotty = this.eval_libc("ENOTTY")?;
1696         this.set_last_error(enotty)?;
1697         Ok(Scalar::from_i32(0))
1698     }
1699
1700     fn realpath(
1701         &mut self,
1702         path_op: &OpTy<'tcx, Provenance>,
1703         processed_path_op: &OpTy<'tcx, Provenance>,
1704     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1705         let this = self.eval_context_mut();
1706         this.assert_target_os_is_unix("realpath");
1707
1708         let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1709         let processed_ptr = this.read_pointer(processed_path_op)?;
1710
1711         // Reject if isolation is enabled.
1712         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1713             this.reject_in_isolation("`realpath`", reject_with)?;
1714             let eacc = this.eval_libc("EACCES")?;
1715             this.set_last_error(eacc)?;
1716             return Ok(Scalar::from_machine_usize(0, this));
1717         }
1718
1719         let result = std::fs::canonicalize(pathname);
1720         match result {
1721             Ok(resolved) => {
1722                 let path_max = this
1723                     .eval_libc_i32("PATH_MAX")?
1724                     .try_into()
1725                     .expect("PATH_MAX does not fit in u64");
1726                 let dest = if this.ptr_is_null(processed_ptr)? {
1727                     // POSIX says behavior when passing a null pointer is implementation-defined,
1728                     // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1729                     // similarly to:
1730                     //
1731                     // "If resolved_path is specified as NULL, then realpath() uses
1732                     // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1733                     // the resolved pathname, and returns a pointer to this buffer.  The
1734                     // caller should deallocate this buffer using free(3)."
1735                     // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1736                     this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1737                 } else {
1738                     let (wrote_path, _) =
1739                         this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1740
1741                     if !wrote_path {
1742                         // Note that we do not explicitly handle `FILENAME_MAX`
1743                         // (different from `PATH_MAX` above) as it is Linux-specific and
1744                         // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1745                         let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1746                         this.set_last_error(enametoolong)?;
1747                         return Ok(Scalar::from_machine_usize(0, this));
1748                     }
1749                     processed_ptr
1750                 };
1751
1752                 Ok(Scalar::from_maybe_pointer(dest, this))
1753             }
1754             Err(e) => {
1755                 this.set_last_error_from_io_error(e.kind())?;
1756                 Ok(Scalar::from_machine_usize(0, this))
1757             }
1758         }
1759     }
1760     fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1761         use rand::seq::SliceRandom;
1762
1763         // POSIX defines the template string.
1764         const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1765
1766         let this = self.eval_context_mut();
1767         this.assert_target_os_is_unix("mkstemp");
1768
1769         // POSIX defines the maximum number of attempts before failure.
1770         //
1771         // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1772         // POSIX says this about `TMP_MAX`:
1773         // * Minimum number of unique filenames generated by `tmpnam()`.
1774         // * Maximum number of times an application can call `tmpnam()` reliably.
1775         //   * The value of `TMP_MAX` is at least 25.
1776         //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1777         // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1778         let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1779
1780         // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1781         // (and the target is unix, so a byte slice is the right representation).
1782         let template_ptr = this.read_pointer(template_op)?;
1783         let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1784         let template_bytes = template.as_mut_slice();
1785
1786         // Reject if isolation is enabled.
1787         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1788             this.reject_in_isolation("`mkstemp`", reject_with)?;
1789             let eacc = this.eval_libc("EACCES")?;
1790             this.set_last_error(eacc)?;
1791             return Ok(-1);
1792         }
1793
1794         // Get the bytes of the suffix we expect in _target_ encoding.
1795         let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1796
1797         // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1798         // that represents the expected suffix.
1799
1800         // Now we figure out the index of the slice we expect to contain the suffix.
1801         let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1802         let end_pos = template_bytes.len();
1803         let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1804
1805         // If we don't find the suffix, it is an error.
1806         if last_six_char_bytes != suffix_bytes {
1807             let einval = this.eval_libc("EINVAL")?;
1808             this.set_last_error(einval)?;
1809             return Ok(-1);
1810         }
1811
1812         // At this point we know we have 6 ASCII 'X' characters as a suffix.
1813
1814         // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1815         const SUBSTITUTIONS: &[char; 62] = &[
1816             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1817             'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1818             'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1819             'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1820         ];
1821
1822         // The file is opened with specific options, which Rust does not expose in a portable way.
1823         // So we use specific APIs depending on the host OS.
1824         let mut fopts = OpenOptions::new();
1825         fopts.read(true).write(true).create_new(true);
1826
1827         #[cfg(unix)]
1828         {
1829             use std::os::unix::fs::OpenOptionsExt;
1830             fopts.mode(0o600);
1831             // Do not allow others to read or modify this file.
1832             fopts.custom_flags(libc::O_EXCL);
1833         }
1834         #[cfg(windows)]
1835         {
1836             use std::os::windows::fs::OpenOptionsExt;
1837             // Do not allow others to read or modify this file.
1838             fopts.share_mode(0);
1839         }
1840
1841         // If the generated file already exists, we will try again `max_attempts` many times.
1842         for _ in 0..max_attempts {
1843             let rng = this.machine.rng.get_mut();
1844
1845             // Generate a random unique suffix.
1846             let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1847
1848             // Replace the template string with the random string.
1849             template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1850
1851             // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1852             this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1853
1854             // To actually open the file, turn this into a host OsString.
1855             let p = bytes_to_os_str(template_bytes)?.to_os_string();
1856
1857             let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1858
1859             let file = fopts.open(possibly_unique);
1860
1861             match file {
1862                 Ok(f) => {
1863                     let fh = &mut this.machine.file_handler;
1864                     let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1865                     return Ok(fd);
1866                 }
1867                 Err(e) =>
1868                     match e.kind() {
1869                         // If the random file already exists, keep trying.
1870                         ErrorKind::AlreadyExists => continue,
1871                         // Any other errors are returned to the caller.
1872                         _ => {
1873                             // "On error, -1 is returned, and errno is set to
1874                             // indicate the error"
1875                             this.set_last_error_from_io_error(e.kind())?;
1876                             return Ok(-1);
1877                         }
1878                     },
1879             }
1880         }
1881
1882         // We ran out of attempts to create the file, return an error.
1883         let eexist = this.eval_libc("EEXIST")?;
1884         this.set_last_error(eexist)?;
1885         Ok(-1)
1886     }
1887 }
1888
1889 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1890 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1891 /// epoch.
1892 fn extract_sec_and_nsec<'tcx>(
1893     time: std::io::Result<SystemTime>,
1894 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1895     time.ok()
1896         .map(|time| {
1897             let duration = system_time_to_duration(&time)?;
1898             Ok((duration.as_secs(), duration.subsec_nanos()))
1899         })
1900         .transpose()
1901 }
1902
1903 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1904 /// shims.
1905 struct FileMetadata {
1906     mode: Scalar<Provenance>,
1907     size: u64,
1908     created: Option<(u64, u32)>,
1909     accessed: Option<(u64, u32)>,
1910     modified: Option<(u64, u32)>,
1911 }
1912
1913 impl FileMetadata {
1914     fn from_path<'tcx>(
1915         ecx: &mut MiriInterpCx<'_, 'tcx>,
1916         path: &Path,
1917         follow_symlink: bool,
1918     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1919         let metadata =
1920             if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1921
1922         FileMetadata::from_meta(ecx, metadata)
1923     }
1924
1925     fn from_fd<'tcx>(
1926         ecx: &mut MiriInterpCx<'_, 'tcx>,
1927         fd: i32,
1928     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1929         let option = ecx.machine.file_handler.handles.get(&fd);
1930         let file = match option {
1931             Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1932             None => return ecx.handle_not_found().map(|_: i32| None),
1933         };
1934         let metadata = file.metadata();
1935
1936         FileMetadata::from_meta(ecx, metadata)
1937     }
1938
1939     fn from_meta<'tcx>(
1940         ecx: &mut MiriInterpCx<'_, 'tcx>,
1941         metadata: Result<std::fs::Metadata, std::io::Error>,
1942     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1943         let metadata = match metadata {
1944             Ok(metadata) => metadata,
1945             Err(e) => {
1946                 ecx.set_last_error_from_io_error(e.kind())?;
1947                 return Ok(None);
1948             }
1949         };
1950
1951         let file_type = metadata.file_type();
1952
1953         let mode_name = if file_type.is_file() {
1954             "S_IFREG"
1955         } else if file_type.is_dir() {
1956             "S_IFDIR"
1957         } else {
1958             "S_IFLNK"
1959         };
1960
1961         let mode = ecx.eval_libc(mode_name)?;
1962
1963         let size = metadata.len();
1964
1965         let created = extract_sec_and_nsec(metadata.created())?;
1966         let accessed = extract_sec_and_nsec(metadata.accessed())?;
1967         let modified = extract_sec_and_nsec(metadata.modified())?;
1968
1969         // FIXME: Provide more fields using platform specific methods.
1970         Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
1971     }
1972 }