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