]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/unix/fs.rs
0a26657f57c85cc921db96efbedecdf1fe03842f
[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 == o_append {
566             options.append(true);
567             mirror |= o_append;
568         }
569         let o_trunc = this.eval_libc_i32("O_TRUNC")?;
570         if flag & o_trunc == o_trunc {
571             options.truncate(true);
572             mirror |= o_trunc;
573         }
574         let o_creat = this.eval_libc_i32("O_CREAT")?;
575         if flag & o_creat == o_creat {
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 == o_excl {
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 == o_cloexec {
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 this.tcx.sess.target.os == "linux" {
609             let o_tmpfile = this.eval_libc_i32("O_TMPFILE")?;
610             if flag & o_tmpfile == o_tmpfile {
611                 // if the flag contains `O_TMPFILE` then we return a graceful error
612                 let eopnotsupp = this.eval_libc("EOPNOTSUPP")?;
613                 this.set_last_error(eopnotsupp)?;
614                 return Ok(-1);
615             }
616         }
617         // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
618         // then we throw an error.
619         if flag != mirror {
620             throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
621         }
622
623         let path = this.read_path_from_c_str(path)?;
624
625         // Reject if isolation is enabled.
626         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
627             this.reject_in_isolation("`open`", reject_with)?;
628             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
629             return Ok(-1);
630         }
631
632         let fd = options.open(path).map(|file| {
633             let fh = &mut this.machine.file_handler;
634             fh.insert_fd(Box::new(FileHandle { file, writable }))
635         });
636
637         this.try_unwrap_io_result(fd)
638     }
639
640     fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
641         let this = self.eval_context_mut();
642
643         if args.len() < 2 {
644             throw_ub_format!(
645                 "incorrect number of arguments for fcntl: got {}, expected at least 2",
646                 args.len()
647             );
648         }
649         let fd = this.read_scalar(&args[0])?.to_i32()?;
650         let cmd = this.read_scalar(&args[1])?.to_i32()?;
651
652         // Reject if isolation is enabled.
653         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
654             this.reject_in_isolation("`fcntl`", reject_with)?;
655             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
656             return Ok(-1);
657         }
658
659         // We only support getting the flags for a descriptor.
660         if cmd == this.eval_libc_i32("F_GETFD")? {
661             // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
662             // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
663             // always sets this flag when opening a file. However we still need to check that the
664             // file itself is open.
665             if this.machine.file_handler.handles.contains_key(&fd) {
666                 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
667             } else {
668                 this.handle_not_found()
669             }
670         } else if cmd == this.eval_libc_i32("F_DUPFD")?
671             || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
672         {
673             // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
674             // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
675             // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
676             // thus they can share the same implementation here.
677             if args.len() < 3 {
678                 throw_ub_format!(
679                     "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
680                     args.len()
681                 );
682             }
683             let start = this.read_scalar(&args[2])?.to_i32()?;
684
685             let fh = &mut this.machine.file_handler;
686
687             match fh.handles.get_mut(&fd) {
688                 Some(file_descriptor) => {
689                     let dup_result = file_descriptor.dup();
690                     match dup_result {
691                         Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
692                         Err(e) => {
693                             this.set_last_error_from_io_error(e.kind())?;
694                             Ok(-1)
695                         }
696                     }
697                 }
698                 None => this.handle_not_found(),
699             }
700         } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
701             if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
702                 // FIXME: Support fullfsync for all FDs
703                 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
704                 let io_result = maybe_sync_file(file, *writable, File::sync_all);
705                 this.try_unwrap_io_result(io_result)
706             } else {
707                 this.handle_not_found()
708             }
709         } else {
710             throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
711         }
712     }
713
714     fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
715         let this = self.eval_context_mut();
716
717         let fd = this.read_scalar(fd_op)?.to_i32()?;
718
719         Ok(Scalar::from_i32(
720             if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
721                 let result = file_descriptor.close(this.machine.communicate())?;
722                 this.try_unwrap_io_result(result)?
723             } else {
724                 this.handle_not_found()?
725             },
726         ))
727     }
728
729     fn read(
730         &mut self,
731         fd: i32,
732         buf: Pointer<Option<Provenance>>,
733         count: u64,
734     ) -> InterpResult<'tcx, i64> {
735         let this = self.eval_context_mut();
736
737         // Isolation check is done via `FileDescriptor` trait.
738
739         trace!("Reading from FD {}, size {}", fd, count);
740
741         // Check that the *entire* buffer is actually valid memory.
742         this.check_ptr_access_align(
743             buf,
744             Size::from_bytes(count),
745             Align::ONE,
746             CheckInAllocMsg::MemoryAccessTest,
747         )?;
748
749         // We cap the number of read bytes to the largest value that we are able to fit in both the
750         // host's and target's `isize`. This saves us from having to handle overflows later.
751         let count = count
752             .min(u64::try_from(this.machine_isize_max()).unwrap())
753             .min(u64::try_from(isize::MAX).unwrap());
754         let communicate = this.machine.communicate();
755
756         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
757             trace!("read: FD mapped to {:?}", file_descriptor);
758             // We want to read at most `count` bytes. We are sure that `count` is not negative
759             // because it was a target's `usize`. Also we are sure that its smaller than
760             // `usize::MAX` because it is bounded by the host's `isize`.
761             let mut bytes = vec![0; usize::try_from(count).unwrap()];
762             // `File::read` never returns a value larger than `count`,
763             // so this cannot fail.
764             let result =
765                 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
766
767             match result {
768                 Ok(read_bytes) => {
769                     // If reading to `bytes` did not fail, we write those bytes to the buffer.
770                     this.write_bytes_ptr(buf, bytes)?;
771                     Ok(read_bytes)
772                 }
773                 Err(e) => {
774                     this.set_last_error_from_io_error(e.kind())?;
775                     Ok(-1)
776                 }
777             }
778         } else {
779             trace!("read: FD not found");
780             this.handle_not_found()
781         }
782     }
783
784     fn write(
785         &mut self,
786         fd: i32,
787         buf: Pointer<Option<Provenance>>,
788         count: u64,
789     ) -> InterpResult<'tcx, i64> {
790         let this = self.eval_context_mut();
791
792         // Isolation check is done via `FileDescriptor` trait.
793
794         // Check that the *entire* buffer is actually valid memory.
795         this.check_ptr_access_align(
796             buf,
797             Size::from_bytes(count),
798             Align::ONE,
799             CheckInAllocMsg::MemoryAccessTest,
800         )?;
801
802         // We cap the number of written bytes to the largest value that we are able to fit in both the
803         // host's and target's `isize`. This saves us from having to handle overflows later.
804         let count = count
805             .min(u64::try_from(this.machine_isize_max()).unwrap())
806             .min(u64::try_from(isize::MAX).unwrap());
807         let communicate = this.machine.communicate();
808
809         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
810             let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
811             let result =
812                 file_descriptor.write(communicate, bytes)?.map(|c| i64::try_from(c).unwrap());
813             this.try_unwrap_io_result(result)
814         } else {
815             this.handle_not_found()
816         }
817     }
818
819     fn lseek64(
820         &mut self,
821         fd_op: &OpTy<'tcx, Provenance>,
822         offset_op: &OpTy<'tcx, Provenance>,
823         whence_op: &OpTy<'tcx, Provenance>,
824     ) -> InterpResult<'tcx, Scalar<Provenance>> {
825         let this = self.eval_context_mut();
826
827         // Isolation check is done via `FileDescriptor` trait.
828
829         let fd = this.read_scalar(fd_op)?.to_i32()?;
830         let offset = this.read_scalar(offset_op)?.to_i64()?;
831         let whence = this.read_scalar(whence_op)?.to_i32()?;
832
833         let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
834             SeekFrom::Start(u64::try_from(offset).unwrap())
835         } else if whence == this.eval_libc_i32("SEEK_CUR")? {
836             SeekFrom::Current(offset)
837         } else if whence == this.eval_libc_i32("SEEK_END")? {
838             SeekFrom::End(offset)
839         } else {
840             let einval = this.eval_libc("EINVAL")?;
841             this.set_last_error(einval)?;
842             return Ok(Scalar::from_i64(-1));
843         };
844
845         let communicate = this.machine.communicate();
846         Ok(Scalar::from_i64(
847             if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
848                 let result = file_descriptor
849                     .seek(communicate, seek_from)?
850                     .map(|offset| i64::try_from(offset).unwrap());
851                 this.try_unwrap_io_result(result)?
852             } else {
853                 this.handle_not_found()?
854             },
855         ))
856     }
857
858     fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
859         let this = self.eval_context_mut();
860
861         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
862
863         // Reject if isolation is enabled.
864         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
865             this.reject_in_isolation("`unlink`", reject_with)?;
866             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
867             return Ok(-1);
868         }
869
870         let result = remove_file(path).map(|_| 0);
871         this.try_unwrap_io_result(result)
872     }
873
874     fn symlink(
875         &mut self,
876         target_op: &OpTy<'tcx, Provenance>,
877         linkpath_op: &OpTy<'tcx, Provenance>,
878     ) -> InterpResult<'tcx, i32> {
879         #[cfg(unix)]
880         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
881             std::os::unix::fs::symlink(src, dst)
882         }
883
884         #[cfg(windows)]
885         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
886             use std::os::windows::fs;
887             if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
888         }
889
890         let this = self.eval_context_mut();
891         let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
892         let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
893
894         // Reject if isolation is enabled.
895         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
896             this.reject_in_isolation("`symlink`", reject_with)?;
897             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
898             return Ok(-1);
899         }
900
901         let result = create_link(&target, &linkpath).map(|_| 0);
902         this.try_unwrap_io_result(result)
903     }
904
905     fn macos_stat(
906         &mut self,
907         path_op: &OpTy<'tcx, Provenance>,
908         buf_op: &OpTy<'tcx, Provenance>,
909     ) -> InterpResult<'tcx, Scalar<Provenance>> {
910         let this = self.eval_context_mut();
911         this.assert_target_os("macos", "stat");
912
913         let path_scalar = this.read_pointer(path_op)?;
914         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
915
916         // Reject if isolation is enabled.
917         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
918             this.reject_in_isolation("`stat`", reject_with)?;
919             let eacc = this.eval_libc("EACCES")?;
920             this.set_last_error(eacc)?;
921             return Ok(Scalar::from_i32(-1));
922         }
923
924         // `stat` always follows symlinks.
925         let metadata = match FileMetadata::from_path(this, &path, true)? {
926             Some(metadata) => metadata,
927             None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
928         };
929
930         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
931     }
932
933     // `lstat` is used to get symlink metadata.
934     fn macos_lstat(
935         &mut self,
936         path_op: &OpTy<'tcx, Provenance>,
937         buf_op: &OpTy<'tcx, Provenance>,
938     ) -> InterpResult<'tcx, Scalar<Provenance>> {
939         let this = self.eval_context_mut();
940         this.assert_target_os("macos", "lstat");
941
942         let path_scalar = this.read_pointer(path_op)?;
943         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
944
945         // Reject if isolation is enabled.
946         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
947             this.reject_in_isolation("`lstat`", reject_with)?;
948             let eacc = this.eval_libc("EACCES")?;
949             this.set_last_error(eacc)?;
950             return Ok(Scalar::from_i32(-1));
951         }
952
953         let metadata = match FileMetadata::from_path(this, &path, false)? {
954             Some(metadata) => metadata,
955             None => return Ok(Scalar::from_i32(-1)), // `FileMetadata` has set errno
956         };
957
958         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
959     }
960
961     fn macos_fstat(
962         &mut self,
963         fd_op: &OpTy<'tcx, Provenance>,
964         buf_op: &OpTy<'tcx, Provenance>,
965     ) -> InterpResult<'tcx, Scalar<Provenance>> {
966         let this = self.eval_context_mut();
967
968         this.assert_target_os("macos", "fstat");
969
970         let fd = this.read_scalar(fd_op)?.to_i32()?;
971
972         // Reject if isolation is enabled.
973         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
974             this.reject_in_isolation("`fstat`", reject_with)?;
975             // Set error code as "EBADF" (bad fd)
976             return Ok(Scalar::from_i32(this.handle_not_found()?));
977         }
978
979         let metadata = match FileMetadata::from_fd(this, fd)? {
980             Some(metadata) => metadata,
981             None => return Ok(Scalar::from_i32(-1)),
982         };
983         Ok(Scalar::from_i32(this.macos_stat_write_buf(metadata, buf_op)?))
984     }
985
986     fn linux_statx(
987         &mut self,
988         dirfd_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
989         pathname_op: &OpTy<'tcx, Provenance>, // Should be a `const char *`
990         flags_op: &OpTy<'tcx, Provenance>,    // Should be an `int`
991         mask_op: &OpTy<'tcx, Provenance>,     // Should be an `unsigned int`
992         statxbuf_op: &OpTy<'tcx, Provenance>, // Should be a `struct statx *`
993     ) -> InterpResult<'tcx, i32> {
994         let this = self.eval_context_mut();
995
996         this.assert_target_os("linux", "statx");
997
998         let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
999         let pathname_ptr = this.read_pointer(pathname_op)?;
1000         let flags = this.read_scalar(flags_op)?.to_i32()?;
1001         let _mask = this.read_scalar(mask_op)?.to_u32()?;
1002         let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
1003
1004         // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
1005         if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
1006             let efault = this.eval_libc("EFAULT")?;
1007             this.set_last_error(efault)?;
1008             return Ok(-1);
1009         }
1010
1011         // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
1012         // proper `MemPlace` and then write the results of this function to it. However, the
1013         // `syscall` function is untyped. This means that all the `statx` parameters are provided
1014         // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
1015         // `statxbuf_op` by using the `libc::statx` struct type.
1016         let statxbuf = {
1017             let statx_layout = this.libc_ty_layout("statx")?;
1018             MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
1019         };
1020
1021         let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
1022         // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
1023         let at_ampty_path = this.eval_libc_i32("AT_EMPTY_PATH")?;
1024         let empty_path_flag = flags & at_ampty_path == at_ampty_path;
1025         // We only support:
1026         // * interpreting `path` as an absolute directory,
1027         // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
1028         // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
1029         // set.
1030         // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
1031         // found this error, please open an issue reporting it.
1032         if !(path.is_absolute()
1033             || dirfd == this.eval_libc_i32("AT_FDCWD")?
1034             || (path.as_os_str().is_empty() && empty_path_flag))
1035         {
1036             throw_unsup_format!(
1037                 "using statx is only supported with absolute paths, relative paths with the file \
1038                 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
1039                 file descriptor"
1040             )
1041         }
1042
1043         // Reject if isolation is enabled.
1044         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1045             this.reject_in_isolation("`statx`", reject_with)?;
1046             let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
1047                 // since `path` is provided, either absolute or
1048                 // relative to CWD, `EACCES` is the most relevant.
1049                 this.eval_libc("EACCES")?
1050             } else {
1051                 // `dirfd` is set to target file, and `path` is empty
1052                 // (or we would have hit the `throw_unsup_format`
1053                 // above). `EACCES` would violate the spec.
1054                 assert!(empty_path_flag);
1055                 this.eval_libc("EBADF")?
1056             };
1057             this.set_last_error(ecode)?;
1058             return Ok(-1);
1059         }
1060
1061         // the `_mask_op` paramter specifies the file information that the caller requested.
1062         // However `statx` is allowed to return information that was not requested or to not
1063         // return information that was requested. This `mask` represents the information we can
1064         // actually provide for any target.
1065         let mut mask =
1066             this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
1067
1068         // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
1069         // symbolic links.
1070         let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
1071
1072         // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
1073         // represented by dirfd, whether it's a directory or otherwise.
1074         let metadata = if path.as_os_str().is_empty() && empty_path_flag {
1075             FileMetadata::from_fd(this, dirfd)?
1076         } else {
1077             FileMetadata::from_path(this, &path, follow_symlink)?
1078         };
1079         let metadata = match metadata {
1080             Some(metadata) => metadata,
1081             None => return Ok(-1),
1082         };
1083
1084         // The `mode` field specifies the type of the file and the permissions over the file for
1085         // the owner, its group and other users. Given that we can only provide the file type
1086         // without using platform specific methods, we only set the bits corresponding to the file
1087         // type. This should be an `__u16` but `libc` provides its values as `u32`.
1088         let mode: u16 = metadata
1089             .mode
1090             .to_u32()?
1091             .try_into()
1092             .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1093
1094         // We need to set the corresponding bits of `mask` if the access, creation and modification
1095         // times were available. Otherwise we let them be zero.
1096         let (access_sec, access_nsec) = metadata
1097             .accessed
1098             .map(|tup| {
1099                 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1100                 InterpResult::Ok(tup)
1101             })
1102             .unwrap_or_else(|| Ok((0, 0)))?;
1103
1104         let (created_sec, created_nsec) = metadata
1105             .created
1106             .map(|tup| {
1107                 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1108                 InterpResult::Ok(tup)
1109             })
1110             .unwrap_or_else(|| Ok((0, 0)))?;
1111
1112         let (modified_sec, modified_nsec) = metadata
1113             .modified
1114             .map(|tup| {
1115                 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1116                 InterpResult::Ok(tup)
1117             })
1118             .unwrap_or_else(|| Ok((0, 0)))?;
1119
1120         // Now we write everything to `statxbuf`. We write a zero for the unavailable fields.
1121         this.write_int_fields_named(
1122             &[
1123                 ("stx_mask", mask.into()),
1124                 ("stx_blksize", 0),
1125                 ("stx_attributes", 0),
1126                 ("stx_nlink", 0),
1127                 ("stx_uid", 0),
1128                 ("stx_gid", 0),
1129                 ("stx_mode", mode.into()),
1130                 ("stx_ino", 0),
1131                 ("stx_size", metadata.size.into()),
1132                 ("stx_blocks", 0),
1133                 ("stx_attributes_mask", 0),
1134                 ("stx_rdev_major", 0),
1135                 ("stx_rdev_minor", 0),
1136                 ("stx_dev_major", 0),
1137                 ("stx_dev_minor", 0),
1138             ],
1139             &statxbuf,
1140         )?;
1141         #[rustfmt::skip]
1142         this.write_int_fields_named(
1143             &[
1144                 ("tv_sec", access_sec.into()),
1145                 ("tv_nsec", access_nsec.into()),
1146             ],
1147             &this.mplace_field_named(&statxbuf, "stx_atime")?,
1148         )?;
1149         #[rustfmt::skip]
1150         this.write_int_fields_named(
1151             &[
1152                 ("tv_sec", created_sec.into()),
1153                 ("tv_nsec", created_nsec.into()),
1154             ],
1155             &this.mplace_field_named(&statxbuf, "stx_btime")?,
1156         )?;
1157         #[rustfmt::skip]
1158         this.write_int_fields_named(
1159             &[
1160                 ("tv_sec", 0.into()),
1161                 ("tv_nsec", 0.into()),
1162             ],
1163             &this.mplace_field_named(&statxbuf, "stx_ctime")?,
1164         )?;
1165         #[rustfmt::skip]
1166         this.write_int_fields_named(
1167             &[
1168                 ("tv_sec", modified_sec.into()),
1169                 ("tv_nsec", modified_nsec.into()),
1170             ],
1171             &this.mplace_field_named(&statxbuf, "stx_mtime")?,
1172         )?;
1173
1174         Ok(0)
1175     }
1176
1177     fn rename(
1178         &mut self,
1179         oldpath_op: &OpTy<'tcx, Provenance>,
1180         newpath_op: &OpTy<'tcx, Provenance>,
1181     ) -> InterpResult<'tcx, i32> {
1182         let this = self.eval_context_mut();
1183
1184         let oldpath_ptr = this.read_pointer(oldpath_op)?;
1185         let newpath_ptr = this.read_pointer(newpath_op)?;
1186
1187         if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1188             let efault = this.eval_libc("EFAULT")?;
1189             this.set_last_error(efault)?;
1190             return Ok(-1);
1191         }
1192
1193         let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1194         let newpath = this.read_path_from_c_str(newpath_ptr)?;
1195
1196         // Reject if isolation is enabled.
1197         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1198             this.reject_in_isolation("`rename`", reject_with)?;
1199             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1200             return Ok(-1);
1201         }
1202
1203         let result = rename(oldpath, newpath).map(|_| 0);
1204
1205         this.try_unwrap_io_result(result)
1206     }
1207
1208     fn mkdir(
1209         &mut self,
1210         path_op: &OpTy<'tcx, Provenance>,
1211         mode_op: &OpTy<'tcx, Provenance>,
1212     ) -> InterpResult<'tcx, i32> {
1213         let this = self.eval_context_mut();
1214
1215         #[cfg_attr(not(unix), allow(unused_variables))]
1216         let mode = if this.tcx.sess.target.os == "macos" {
1217             u32::from(this.read_scalar(mode_op)?.to_u16()?)
1218         } else {
1219             this.read_scalar(mode_op)?.to_u32()?
1220         };
1221
1222         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1223
1224         // Reject if isolation is enabled.
1225         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1226             this.reject_in_isolation("`mkdir`", reject_with)?;
1227             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1228             return Ok(-1);
1229         }
1230
1231         #[cfg_attr(not(unix), allow(unused_mut))]
1232         let mut builder = DirBuilder::new();
1233
1234         // If the host supports it, forward on the mode of the directory
1235         // (i.e. permission bits and the sticky bit)
1236         #[cfg(unix)]
1237         {
1238             use std::os::unix::fs::DirBuilderExt;
1239             builder.mode(mode);
1240         }
1241
1242         let result = builder.create(path).map(|_| 0i32);
1243
1244         this.try_unwrap_io_result(result)
1245     }
1246
1247     fn rmdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1248         let this = self.eval_context_mut();
1249
1250         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1251
1252         // Reject if isolation is enabled.
1253         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1254             this.reject_in_isolation("`rmdir`", reject_with)?;
1255             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1256             return Ok(-1);
1257         }
1258
1259         let result = remove_dir(path).map(|_| 0i32);
1260
1261         this.try_unwrap_io_result(result)
1262     }
1263
1264     fn opendir(
1265         &mut self,
1266         name_op: &OpTy<'tcx, Provenance>,
1267     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1268         let this = self.eval_context_mut();
1269
1270         let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1271
1272         // Reject if isolation is enabled.
1273         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1274             this.reject_in_isolation("`opendir`", reject_with)?;
1275             let eacc = this.eval_libc("EACCES")?;
1276             this.set_last_error(eacc)?;
1277             return Ok(Scalar::null_ptr(this));
1278         }
1279
1280         let result = read_dir(name);
1281
1282         match result {
1283             Ok(dir_iter) => {
1284                 let id = this.machine.dir_handler.insert_new(dir_iter);
1285
1286                 // The libc API for opendir says that this method returns a pointer to an opaque
1287                 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1288                 // pointer width.
1289                 Ok(Scalar::from_machine_usize(id, this))
1290             }
1291             Err(e) => {
1292                 this.set_last_error_from_io_error(e.kind())?;
1293                 Ok(Scalar::null_ptr(this))
1294             }
1295         }
1296     }
1297
1298     fn linux_readdir64(
1299         &mut self,
1300         dirp_op: &OpTy<'tcx, Provenance>,
1301     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1302         let this = self.eval_context_mut();
1303
1304         this.assert_target_os("linux", "readdir64");
1305
1306         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1307
1308         // Reject if isolation is enabled.
1309         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1310             this.reject_in_isolation("`readdir`", reject_with)?;
1311             let eacc = this.eval_libc("EBADF")?;
1312             this.set_last_error(eacc)?;
1313             return Ok(Scalar::null_ptr(this));
1314         }
1315
1316         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1317             err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
1318         })?;
1319
1320         let entry = match open_dir.read_dir.next() {
1321             Some(Ok(dir_entry)) => {
1322                 // Write the directory entry into a newly allocated buffer.
1323                 // The name is written with write_bytes, while the rest of the
1324                 // dirent64 struct is written using write_int_fields.
1325
1326                 // For reference:
1327                 // pub struct dirent64 {
1328                 //     pub d_ino: ino64_t,
1329                 //     pub d_off: off64_t,
1330                 //     pub d_reclen: c_ushort,
1331                 //     pub d_type: c_uchar,
1332                 //     pub d_name: [c_char; 256],
1333                 // }
1334
1335                 let mut name = dir_entry.file_name(); // not a Path as there are no separators!
1336                 name.push("\0"); // Add a NUL terminator
1337                 let name_bytes = os_str_to_bytes(&name)?;
1338                 let name_len = u64::try_from(name_bytes.len()).unwrap();
1339
1340                 let dirent64_layout = this.libc_ty_layout("dirent64")?;
1341                 let d_name_offset = dirent64_layout.fields.offset(4 /* d_name */).bytes();
1342                 let size = d_name_offset.checked_add(name_len).unwrap();
1343
1344                 let entry =
1345                     this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::Runtime)?;
1346
1347                 // If the host is a Unix system, fill in the inode number with its real value.
1348                 // If not, use 0 as a fallback value.
1349                 #[cfg(unix)]
1350                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1351                 #[cfg(not(unix))]
1352                 let ino = 0u64;
1353
1354                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1355
1356                 this.write_int_fields_named(
1357                     &[
1358                         ("d_ino", ino.into()),
1359                         ("d_off", 0),
1360                         ("d_reclen", size.into()),
1361                         ("d_type", file_type.into()),
1362                     ],
1363                     &MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
1364                 )?;
1365
1366                 let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
1367                 this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
1368
1369                 entry
1370             }
1371             None => {
1372                 // end of stream: return NULL
1373                 Pointer::null()
1374             }
1375             Some(Err(e)) => {
1376                 this.set_last_error_from_io_error(e.kind())?;
1377                 Pointer::null()
1378             }
1379         };
1380
1381         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap();
1382         let old_entry = std::mem::replace(&mut open_dir.entry, entry);
1383         this.free(old_entry, MiriMemoryKind::Runtime)?;
1384
1385         Ok(Scalar::from_maybe_pointer(entry, this))
1386     }
1387
1388     fn macos_readdir_r(
1389         &mut self,
1390         dirp_op: &OpTy<'tcx, Provenance>,
1391         entry_op: &OpTy<'tcx, Provenance>,
1392         result_op: &OpTy<'tcx, Provenance>,
1393     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1394         let this = self.eval_context_mut();
1395
1396         this.assert_target_os("macos", "readdir_r");
1397
1398         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1399
1400         // Reject if isolation is enabled.
1401         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1402             this.reject_in_isolation("`readdir_r`", reject_with)?;
1403             // Set error code as "EBADF" (bad fd)
1404             return Ok(Scalar::from_i32(this.handle_not_found()?));
1405         }
1406
1407         let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1408             err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1409         })?;
1410         Ok(Scalar::from_i32(match open_dir.read_dir.next() {
1411             Some(Ok(dir_entry)) => {
1412                 // Write into entry, write pointer to result, return 0 on success.
1413                 // The name is written with write_os_str_to_c_str, while the rest of the
1414                 // dirent struct is written using write_int_fields.
1415
1416                 // For reference:
1417                 // pub struct dirent {
1418                 //     pub d_ino: u64,
1419                 //     pub d_seekoff: u64,
1420                 //     pub d_reclen: u16,
1421                 //     pub d_namlen: u16,
1422                 //     pub d_type: u8,
1423                 //     pub d_name: [c_char; 1024],
1424                 // }
1425
1426                 let entry_place = this.deref_operand(entry_op)?;
1427                 let name_place = this.mplace_field(&entry_place, 5)?;
1428
1429                 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1430                 let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str(
1431                     &file_name,
1432                     name_place.ptr,
1433                     name_place.layout.size.bytes(),
1434                 )?;
1435                 let file_name_len = file_name_buf_len.checked_sub(1).unwrap();
1436                 if !name_fits {
1437                     throw_unsup_format!(
1438                         "a directory entry had a name too large to fit in libc::dirent"
1439                     );
1440                 }
1441
1442                 let entry_place = this.deref_operand(entry_op)?;
1443
1444                 // If the host is a Unix system, fill in the inode number with its real value.
1445                 // If not, use 0 as a fallback value.
1446                 #[cfg(unix)]
1447                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1448                 #[cfg(not(unix))]
1449                 let ino = 0u64;
1450
1451                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1452
1453                 this.write_int_fields_named(
1454                     &[
1455                         ("d_ino", ino.into()),
1456                         ("d_seekoff", 0),
1457                         ("d_reclen", 0),
1458                         ("d_namlen", file_name_len.into()),
1459                         ("d_type", file_type.into()),
1460                     ],
1461                     &entry_place,
1462                 )?;
1463
1464                 let result_place = this.deref_operand(result_op)?;
1465                 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1466
1467                 0
1468             }
1469             None => {
1470                 // end of stream: return 0, assign *result=NULL
1471                 this.write_null(&this.deref_operand(result_op)?.into())?;
1472                 0
1473             }
1474             Some(Err(e)) =>
1475                 match e.raw_os_error() {
1476                     // return positive error number on error
1477                     Some(error) => error,
1478                     None => {
1479                         throw_unsup_format!(
1480                             "the error {} couldn't be converted to a return value",
1481                             e
1482                         )
1483                     }
1484                 },
1485         }))
1486     }
1487
1488     fn closedir(&mut self, dirp_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1489         let this = self.eval_context_mut();
1490
1491         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1492
1493         // Reject if isolation is enabled.
1494         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1495             this.reject_in_isolation("`closedir`", reject_with)?;
1496             // Set error code as "EBADF" (bad fd)
1497             return this.handle_not_found();
1498         }
1499
1500         if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) {
1501             this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
1502             drop(open_dir);
1503             Ok(0)
1504         } else {
1505             this.handle_not_found()
1506         }
1507     }
1508
1509     fn ftruncate64(
1510         &mut self,
1511         fd_op: &OpTy<'tcx, Provenance>,
1512         length_op: &OpTy<'tcx, Provenance>,
1513     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1514         let this = self.eval_context_mut();
1515
1516         let fd = this.read_scalar(fd_op)?.to_i32()?;
1517         let length = this.read_scalar(length_op)?.to_i64()?;
1518
1519         // Reject if isolation is enabled.
1520         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1521             this.reject_in_isolation("`ftruncate64`", reject_with)?;
1522             // Set error code as "EBADF" (bad fd)
1523             return Ok(Scalar::from_i32(this.handle_not_found()?));
1524         }
1525
1526         Ok(Scalar::from_i32(
1527             if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1528                 // FIXME: Support ftruncate64 for all FDs
1529                 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1530                 if *writable {
1531                     if let Ok(length) = length.try_into() {
1532                         let result = file.set_len(length);
1533                         this.try_unwrap_io_result(result.map(|_| 0i32))?
1534                     } else {
1535                         let einval = this.eval_libc("EINVAL")?;
1536                         this.set_last_error(einval)?;
1537                         -1
1538                     }
1539                 } else {
1540                     // The file is not writable
1541                     let einval = this.eval_libc("EINVAL")?;
1542                     this.set_last_error(einval)?;
1543                     -1
1544                 }
1545             } else {
1546                 this.handle_not_found()?
1547             },
1548         ))
1549     }
1550
1551     fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1552         // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1553         // underlying disk to finish writing. In the interest of host compatibility,
1554         // we conservatively implement this with `sync_all`, which
1555         // *does* wait for the disk.
1556
1557         let this = self.eval_context_mut();
1558
1559         let fd = this.read_scalar(fd_op)?.to_i32()?;
1560
1561         // Reject if isolation is enabled.
1562         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1563             this.reject_in_isolation("`fsync`", reject_with)?;
1564             // Set error code as "EBADF" (bad fd)
1565             return this.handle_not_found();
1566         }
1567
1568         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1569             // FIXME: Support fsync for all FDs
1570             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1571             let io_result = maybe_sync_file(file, *writable, File::sync_all);
1572             this.try_unwrap_io_result(io_result)
1573         } else {
1574             this.handle_not_found()
1575         }
1576     }
1577
1578     fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1579         let this = self.eval_context_mut();
1580
1581         let fd = this.read_scalar(fd_op)?.to_i32()?;
1582
1583         // Reject if isolation is enabled.
1584         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1585             this.reject_in_isolation("`fdatasync`", reject_with)?;
1586             // Set error code as "EBADF" (bad fd)
1587             return this.handle_not_found();
1588         }
1589
1590         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1591             // FIXME: Support fdatasync for all FDs
1592             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1593             let io_result = maybe_sync_file(file, *writable, File::sync_data);
1594             this.try_unwrap_io_result(io_result)
1595         } else {
1596             this.handle_not_found()
1597         }
1598     }
1599
1600     fn sync_file_range(
1601         &mut self,
1602         fd_op: &OpTy<'tcx, Provenance>,
1603         offset_op: &OpTy<'tcx, Provenance>,
1604         nbytes_op: &OpTy<'tcx, Provenance>,
1605         flags_op: &OpTy<'tcx, Provenance>,
1606     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1607         let this = self.eval_context_mut();
1608
1609         let fd = this.read_scalar(fd_op)?.to_i32()?;
1610         let offset = this.read_scalar(offset_op)?.to_i64()?;
1611         let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1612         let flags = this.read_scalar(flags_op)?.to_i32()?;
1613
1614         if offset < 0 || nbytes < 0 {
1615             let einval = this.eval_libc("EINVAL")?;
1616             this.set_last_error(einval)?;
1617             return Ok(Scalar::from_i32(-1));
1618         }
1619         let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1620             | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1621             | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1622         if flags & allowed_flags != flags {
1623             let einval = this.eval_libc("EINVAL")?;
1624             this.set_last_error(einval)?;
1625             return Ok(Scalar::from_i32(-1));
1626         }
1627
1628         // Reject if isolation is enabled.
1629         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1630             this.reject_in_isolation("`sync_file_range`", reject_with)?;
1631             // Set error code as "EBADF" (bad fd)
1632             return Ok(Scalar::from_i32(this.handle_not_found()?));
1633         }
1634
1635         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1636             // FIXME: Support sync_data_range for all FDs
1637             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1638             let io_result = maybe_sync_file(file, *writable, File::sync_data);
1639             Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
1640         } else {
1641             Ok(Scalar::from_i32(this.handle_not_found()?))
1642         }
1643     }
1644
1645     fn readlink(
1646         &mut self,
1647         pathname_op: &OpTy<'tcx, Provenance>,
1648         buf_op: &OpTy<'tcx, Provenance>,
1649         bufsize_op: &OpTy<'tcx, Provenance>,
1650     ) -> InterpResult<'tcx, i64> {
1651         let this = self.eval_context_mut();
1652
1653         let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1654         let buf = this.read_pointer(buf_op)?;
1655         let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1656
1657         // Reject if isolation is enabled.
1658         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1659             this.reject_in_isolation("`readlink`", reject_with)?;
1660             let eacc = this.eval_libc("EACCES")?;
1661             this.set_last_error(eacc)?;
1662             return Ok(-1);
1663         }
1664
1665         let result = std::fs::read_link(pathname);
1666         match result {
1667             Ok(resolved) => {
1668                 // 'readlink' truncates the resolved path if the provided buffer is not large
1669                 // enough, and does *not* add a null terminator. That means we cannot use the usual
1670                 // `write_path_to_c_str` and have to re-implement parts of it ourselves.
1671                 let resolved = this.convert_path_separator(
1672                     Cow::Borrowed(resolved.as_ref()),
1673                     crate::shims::os_str::PathConversion::HostToTarget,
1674                 );
1675                 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1676                 let bufsize: usize = bufsize.try_into().unwrap();
1677                 if path_bytes.len() > bufsize {
1678                     path_bytes = &path_bytes[..bufsize]
1679                 }
1680                 this.write_bytes_ptr(buf, path_bytes.iter().copied())?;
1681                 Ok(path_bytes.len().try_into().unwrap())
1682             }
1683             Err(e) => {
1684                 this.set_last_error_from_io_error(e.kind())?;
1685                 Ok(-1)
1686             }
1687         }
1688     }
1689
1690     #[cfg_attr(not(unix), allow(unused))]
1691     fn isatty(
1692         &mut self,
1693         miri_fd: &OpTy<'tcx, Provenance>,
1694     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1695         let this = self.eval_context_mut();
1696         // "returns 1 if fd is an open file descriptor referring to a terminal;
1697         // otherwise 0 is returned, and errno is set to indicate the error"
1698         if matches!(this.machine.isolated_op, IsolatedOp::Allow) {
1699             let fd = this.read_scalar(miri_fd)?.to_i32()?;
1700             if this.machine.file_handler.handles.get(&fd).map(|fd| fd.is_tty()) == Some(true) {
1701                 return Ok(Scalar::from_i32(1));
1702             }
1703         }
1704         // Fallback when the FD was not found or isolation is enabled.
1705         let enotty = this.eval_libc("ENOTTY")?;
1706         this.set_last_error(enotty)?;
1707         Ok(Scalar::from_i32(0))
1708     }
1709
1710     fn realpath(
1711         &mut self,
1712         path_op: &OpTy<'tcx, Provenance>,
1713         processed_path_op: &OpTy<'tcx, Provenance>,
1714     ) -> InterpResult<'tcx, Scalar<Provenance>> {
1715         let this = self.eval_context_mut();
1716         this.assert_target_os_is_unix("realpath");
1717
1718         let pathname = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1719         let processed_ptr = this.read_pointer(processed_path_op)?;
1720
1721         // Reject if isolation is enabled.
1722         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1723             this.reject_in_isolation("`realpath`", reject_with)?;
1724             let eacc = this.eval_libc("EACCES")?;
1725             this.set_last_error(eacc)?;
1726             return Ok(Scalar::from_machine_usize(0, this));
1727         }
1728
1729         let result = std::fs::canonicalize(pathname);
1730         match result {
1731             Ok(resolved) => {
1732                 let path_max = this
1733                     .eval_libc_i32("PATH_MAX")?
1734                     .try_into()
1735                     .expect("PATH_MAX does not fit in u64");
1736                 let dest = if this.ptr_is_null(processed_ptr)? {
1737                     // POSIX says behavior when passing a null pointer is implementation-defined,
1738                     // but GNU/linux, freebsd, netbsd, bionic/android, and macos all treat a null pointer
1739                     // similarly to:
1740                     //
1741                     // "If resolved_path is specified as NULL, then realpath() uses
1742                     // malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold
1743                     // the resolved pathname, and returns a pointer to this buffer.  The
1744                     // caller should deallocate this buffer using free(3)."
1745                     // <https://man7.org/linux/man-pages/man3/realpath.3.html>
1746                     this.alloc_path_as_c_str(&resolved, MiriMemoryKind::C.into())?
1747                 } else {
1748                     let (wrote_path, _) =
1749                         this.write_path_to_c_str(&resolved, processed_ptr, path_max)?;
1750
1751                     if !wrote_path {
1752                         // Note that we do not explicitly handle `FILENAME_MAX`
1753                         // (different from `PATH_MAX` above) as it is Linux-specific and
1754                         // seems like a bit of a mess anyway: <https://eklitzke.org/path-max-is-tricky>.
1755                         let enametoolong = this.eval_libc("ENAMETOOLONG")?;
1756                         this.set_last_error(enametoolong)?;
1757                         return Ok(Scalar::from_machine_usize(0, this));
1758                     }
1759                     processed_ptr
1760                 };
1761
1762                 Ok(Scalar::from_maybe_pointer(dest, this))
1763             }
1764             Err(e) => {
1765                 this.set_last_error_from_io_error(e.kind())?;
1766                 Ok(Scalar::from_machine_usize(0, this))
1767             }
1768         }
1769     }
1770     fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1771         use rand::seq::SliceRandom;
1772
1773         // POSIX defines the template string.
1774         const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1775
1776         let this = self.eval_context_mut();
1777         this.assert_target_os_is_unix("mkstemp");
1778
1779         // POSIX defines the maximum number of attempts before failure.
1780         //
1781         // `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1782         // POSIX says this about `TMP_MAX`:
1783         // * Minimum number of unique filenames generated by `tmpnam()`.
1784         // * Maximum number of times an application can call `tmpnam()` reliably.
1785         //   * The value of `TMP_MAX` is at least 25.
1786         //   * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1787         // See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1788         let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1789
1790         // Get the raw bytes from the template -- as a byte slice, this is a string in the target
1791         // (and the target is unix, so a byte slice is the right representation).
1792         let template_ptr = this.read_pointer(template_op)?;
1793         let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1794         let template_bytes = template.as_mut_slice();
1795
1796         // Reject if isolation is enabled.
1797         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1798             this.reject_in_isolation("`mkstemp`", reject_with)?;
1799             let eacc = this.eval_libc("EACCES")?;
1800             this.set_last_error(eacc)?;
1801             return Ok(-1);
1802         }
1803
1804         // Get the bytes of the suffix we expect in _target_ encoding.
1805         let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1806
1807         // At this point we have one `&[u8]` that represents the template and one `&[u8]`
1808         // that represents the expected suffix.
1809
1810         // Now we figure out the index of the slice we expect to contain the suffix.
1811         let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1812         let end_pos = template_bytes.len();
1813         let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1814
1815         // If we don't find the suffix, it is an error.
1816         if last_six_char_bytes != suffix_bytes {
1817             let einval = this.eval_libc("EINVAL")?;
1818             this.set_last_error(einval)?;
1819             return Ok(-1);
1820         }
1821
1822         // At this point we know we have 6 ASCII 'X' characters as a suffix.
1823
1824         // From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1825         const SUBSTITUTIONS: &[char; 62] = &[
1826             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1827             'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1828             'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1829             'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1830         ];
1831
1832         // The file is opened with specific options, which Rust does not expose in a portable way.
1833         // So we use specific APIs depending on the host OS.
1834         let mut fopts = OpenOptions::new();
1835         fopts.read(true).write(true).create_new(true);
1836
1837         #[cfg(unix)]
1838         {
1839             use std::os::unix::fs::OpenOptionsExt;
1840             fopts.mode(0o600);
1841             // Do not allow others to read or modify this file.
1842             fopts.custom_flags(libc::O_EXCL);
1843         }
1844         #[cfg(windows)]
1845         {
1846             use std::os::windows::fs::OpenOptionsExt;
1847             // Do not allow others to read or modify this file.
1848             fopts.share_mode(0);
1849         }
1850
1851         // If the generated file already exists, we will try again `max_attempts` many times.
1852         for _ in 0..max_attempts {
1853             let rng = this.machine.rng.get_mut();
1854
1855             // Generate a random unique suffix.
1856             let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1857
1858             // Replace the template string with the random string.
1859             template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1860
1861             // Write the modified template back to the passed in pointer to maintain POSIX semantics.
1862             this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1863
1864             // To actually open the file, turn this into a host OsString.
1865             let p = bytes_to_os_str(template_bytes)?.to_os_string();
1866
1867             let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1868
1869             let file = fopts.open(possibly_unique);
1870
1871             match file {
1872                 Ok(f) => {
1873                     let fh = &mut this.machine.file_handler;
1874                     let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1875                     return Ok(fd);
1876                 }
1877                 Err(e) =>
1878                     match e.kind() {
1879                         // If the random file already exists, keep trying.
1880                         ErrorKind::AlreadyExists => continue,
1881                         // Any other errors are returned to the caller.
1882                         _ => {
1883                             // "On error, -1 is returned, and errno is set to
1884                             // indicate the error"
1885                             this.set_last_error_from_io_error(e.kind())?;
1886                             return Ok(-1);
1887                         }
1888                     },
1889             }
1890         }
1891
1892         // We ran out of attempts to create the file, return an error.
1893         let eexist = this.eval_libc("EEXIST")?;
1894         this.set_last_error(eexist)?;
1895         Ok(-1)
1896     }
1897 }
1898
1899 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1900 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1901 /// epoch.
1902 fn extract_sec_and_nsec<'tcx>(
1903     time: std::io::Result<SystemTime>,
1904 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1905     time.ok()
1906         .map(|time| {
1907             let duration = system_time_to_duration(&time)?;
1908             Ok((duration.as_secs(), duration.subsec_nanos()))
1909         })
1910         .transpose()
1911 }
1912
1913 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1914 /// shims.
1915 struct FileMetadata {
1916     mode: Scalar<Provenance>,
1917     size: u64,
1918     created: Option<(u64, u32)>,
1919     accessed: Option<(u64, u32)>,
1920     modified: Option<(u64, u32)>,
1921 }
1922
1923 impl FileMetadata {
1924     fn from_path<'tcx>(
1925         ecx: &mut MiriInterpCx<'_, 'tcx>,
1926         path: &Path,
1927         follow_symlink: bool,
1928     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1929         let metadata =
1930             if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1931
1932         FileMetadata::from_meta(ecx, metadata)
1933     }
1934
1935     fn from_fd<'tcx>(
1936         ecx: &mut MiriInterpCx<'_, 'tcx>,
1937         fd: i32,
1938     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1939         let option = ecx.machine.file_handler.handles.get(&fd);
1940         let file = match option {
1941             Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1942             None => return ecx.handle_not_found().map(|_: i32| None),
1943         };
1944         let metadata = file.metadata();
1945
1946         FileMetadata::from_meta(ecx, metadata)
1947     }
1948
1949     fn from_meta<'tcx>(
1950         ecx: &mut MiriInterpCx<'_, 'tcx>,
1951         metadata: Result<std::fs::Metadata, std::io::Error>,
1952     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1953         let metadata = match metadata {
1954             Ok(metadata) => metadata,
1955             Err(e) => {
1956                 ecx.set_last_error_from_io_error(e.kind())?;
1957                 return Ok(None);
1958             }
1959         };
1960
1961         let file_type = metadata.file_type();
1962
1963         let mode_name = if file_type.is_file() {
1964             "S_IFREG"
1965         } else if file_type.is_dir() {
1966             "S_IFDIR"
1967         } else {
1968             "S_IFLNK"
1969         };
1970
1971         let mode = ecx.eval_libc(mode_name)?;
1972
1973         let size = metadata.len();
1974
1975         let created = extract_sec_and_nsec(metadata.created())?;
1976         let accessed = extract_sec_and_nsec(metadata.accessed())?;
1977         let modified = extract_sec_and_nsec(metadata.modified())?;
1978
1979         // FIXME: Provide more fields using platform specific methods.
1980         Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
1981     }
1982 }