]> git.lizzy.rs Git - rust.git/blob - src/shims/posix/fs.rs
Move shim argument checks before isolation check
[rust.git] / src / shims / posix / fs.rs
1 use std::borrow::Cow;
2 use std::collections::BTreeMap;
3 use std::convert::{TryFrom, 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, Read, Seek, SeekFrom, Write};
8 use std::path::Path;
9 use std::time::SystemTime;
10
11 use log::trace;
12
13 use rustc_data_structures::fx::FxHashMap;
14 use rustc_middle::ty;
15 use rustc_target::abi::{Align, LayoutOf, Size};
16
17 use crate::*;
18 use helpers::{check_arg_count, immty_from_int_checked, immty_from_uint_checked};
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 as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle>;
29
30     fn read<'tcx>(
31         &mut self,
32         communicate_allowed: bool,
33         bytes: &mut [u8],
34     ) -> InterpResult<'tcx, io::Result<usize>>;
35     fn write<'tcx>(
36         &mut self,
37         communicate_allowed: bool,
38         bytes: &[u8],
39     ) -> InterpResult<'tcx, io::Result<usize>>;
40     fn seek<'tcx>(
41         &mut self,
42         communicate_allowed: bool,
43         offset: SeekFrom,
44     ) -> InterpResult<'tcx, io::Result<u64>>;
45     fn close<'tcx>(
46         self: Box<Self>,
47         _communicate_allowed: bool,
48     ) -> InterpResult<'tcx, io::Result<i32>>;
49
50     fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
51 }
52
53 impl FileDescriptor for FileHandle {
54     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
55         Ok(&self)
56     }
57
58     fn read<'tcx>(
59         &mut self,
60         communicate_allowed: bool,
61         bytes: &mut [u8],
62     ) -> InterpResult<'tcx, io::Result<usize>> {
63         assert!(communicate_allowed, "isolation should have prevented even opening a file");
64         Ok(self.file.read(bytes))
65     }
66
67     fn write<'tcx>(
68         &mut self,
69         communicate_allowed: bool,
70         bytes: &[u8],
71     ) -> InterpResult<'tcx, io::Result<usize>> {
72         assert!(communicate_allowed, "isolation should have prevented even opening a file");
73         Ok(self.file.write(bytes))
74     }
75
76     fn seek<'tcx>(
77         &mut self,
78         communicate_allowed: bool,
79         offset: SeekFrom,
80     ) -> InterpResult<'tcx, io::Result<u64>> {
81         assert!(communicate_allowed, "isolation should have prevented even opening a file");
82         Ok(self.file.seek(offset))
83     }
84
85     fn close<'tcx>(
86         self: Box<Self>,
87         communicate_allowed: bool,
88     ) -> InterpResult<'tcx, io::Result<i32>> {
89         assert!(communicate_allowed, "isolation should have prevented even opening a file");
90         // We sync the file if it was opened in a mode different than read-only.
91         if self.writable {
92             // `File::sync_all` does the checks that are done when closing a file. We do this to
93             // to handle possible errors correctly.
94             let result = self.file.sync_all().map(|_| 0i32);
95             // Now we actually close the file.
96             drop(self);
97             // And return the result.
98             Ok(result)
99         } else {
100             // We drop the file, this closes it but ignores any errors
101             // produced when closing it. This is done because
102             // `File::sync_all` cannot be done over files like
103             // `/dev/urandom` which are read-only. Check
104             // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
105             // for a deeper discussion.
106             drop(self);
107             Ok(Ok(0))
108         }
109     }
110
111     fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
112         let duplicated = self.file.try_clone()?;
113         Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
114     }
115 }
116
117 impl FileDescriptor for io::Stdin {
118     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
119         throw_unsup_format!("stdin cannot be used as FileHandle");
120     }
121
122     fn read<'tcx>(
123         &mut self,
124         communicate_allowed: bool,
125         bytes: &mut [u8],
126     ) -> InterpResult<'tcx, io::Result<usize>> {
127         if !communicate_allowed {
128             // We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
129             helpers::isolation_abort_error("`read` from stdin")?;
130         }
131         Ok(Read::read(self, bytes))
132     }
133
134     fn write<'tcx>(
135         &mut self,
136         _communicate_allowed: bool,
137         _bytes: &[u8],
138     ) -> InterpResult<'tcx, io::Result<usize>> {
139         throw_unsup_format!("cannot write to stdin");
140     }
141
142     fn seek<'tcx>(
143         &mut self,
144         _communicate_allowed: bool,
145         _offset: SeekFrom,
146     ) -> InterpResult<'tcx, io::Result<u64>> {
147         throw_unsup_format!("cannot seek on stdin");
148     }
149
150     fn close<'tcx>(
151         self: Box<Self>,
152         _communicate_allowed: bool,
153     ) -> InterpResult<'tcx, io::Result<i32>> {
154         throw_unsup_format!("stdin cannot be closed");
155     }
156
157     fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
158         Ok(Box::new(io::stdin()))
159     }
160 }
161
162 impl FileDescriptor for io::Stdout {
163     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
164         throw_unsup_format!("stdout cannot be used as FileHandle");
165     }
166
167     fn read<'tcx>(
168         &mut self,
169         _communicate_allowed: bool,
170         _bytes: &mut [u8],
171     ) -> InterpResult<'tcx, io::Result<usize>> {
172         throw_unsup_format!("cannot read from stdout");
173     }
174
175     fn write<'tcx>(
176         &mut self,
177         _communicate_allowed: bool,
178         bytes: &[u8],
179     ) -> InterpResult<'tcx, io::Result<usize>> {
180         // We allow writing to stderr even with isolation enabled.
181         let result = Write::write(self, bytes);
182         // Stdout is buffered, flush to make sure it appears on the
183         // screen.  This is the write() syscall of the interpreted
184         // program, we want it to correspond to a write() syscall on
185         // the host -- there is no good in adding extra buffering
186         // here.
187         io::stdout().flush().unwrap();
188
189         Ok(result)
190     }
191
192     fn seek<'tcx>(
193         &mut self,
194         _communicate_allowed: bool,
195         _offset: SeekFrom,
196     ) -> InterpResult<'tcx, io::Result<u64>> {
197         throw_unsup_format!("cannot seek on stdout");
198     }
199
200     fn close<'tcx>(
201         self: Box<Self>,
202         _communicate_allowed: bool,
203     ) -> InterpResult<'tcx, io::Result<i32>> {
204         throw_unsup_format!("stdout cannot be closed");
205     }
206
207     fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
208         Ok(Box::new(io::stdout()))
209     }
210 }
211
212 impl FileDescriptor for io::Stderr {
213     fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
214         throw_unsup_format!("stderr cannot be used as FileHandle");
215     }
216
217     fn read<'tcx>(
218         &mut self,
219         _communicate_allowed: bool,
220         _bytes: &mut [u8],
221     ) -> InterpResult<'tcx, io::Result<usize>> {
222         throw_unsup_format!("cannot read from stderr");
223     }
224
225     fn write<'tcx>(
226         &mut 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(self, bytes))
233     }
234
235     fn seek<'tcx>(
236         &mut self,
237         _communicate_allowed: bool,
238         _offset: SeekFrom,
239     ) -> InterpResult<'tcx, io::Result<u64>> {
240         throw_unsup_format!("cannot seek on stderr");
241     }
242
243     fn close<'tcx>(
244         self: Box<Self>,
245         _communicate_allowed: bool,
246     ) -> InterpResult<'tcx, io::Result<i32>> {
247         throw_unsup_format!("stderr cannot be closed");
248     }
249
250     fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
251         Ok(Box::new(io::stderr()))
252     }
253 }
254
255 #[derive(Debug)]
256 pub struct FileHandler {
257     handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
258 }
259
260 impl<'tcx> Default for FileHandler {
261     fn default() -> Self {
262         let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
263         handles.insert(0i32, Box::new(io::stdin()));
264         handles.insert(1i32, Box::new(io::stdout()));
265         handles.insert(2i32, Box::new(io::stderr()));
266         FileHandler { handles }
267     }
268 }
269
270 impl<'tcx> FileHandler {
271     fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
272         self.insert_fd_with_min_fd(file_handle, 0)
273     }
274
275     fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
276         // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
277         // between used FDs, the find_map combinator will return it. If the first such unused FD
278         // is after all other used FDs, the find_map combinator will return None, and we will use
279         // the FD following the greatest FD thus far.
280         let candidate_new_fd =
281             self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
282                 if *fd != counter {
283                     // There was a gap in the fds stored, return the first unused one
284                     // (note that this relies on BTreeMap iterating in key order)
285                     Some(counter)
286                 } else {
287                     // This fd is used, keep going
288                     None
289                 }
290             });
291         let new_fd = candidate_new_fd.unwrap_or_else(|| {
292             // find_map ran out of BTreeMap entries before finding a free fd, use one plus the
293             // maximum fd in the map
294             self.handles
295                 .last_key_value()
296                 .map(|(fd, _)| fd.checked_add(1).unwrap())
297                 .unwrap_or(min_fd)
298         });
299
300         self.handles.try_insert(new_fd, file_handle).unwrap();
301         new_fd
302     }
303 }
304
305 impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
306 trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
307     fn macos_stat_write_buf(
308         &mut self,
309         metadata: FileMetadata,
310         buf_op: &OpTy<'tcx, Tag>,
311     ) -> InterpResult<'tcx, i32> {
312         let this = self.eval_context_mut();
313
314         let mode: u16 = metadata.mode.to_u16()?;
315
316         let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0));
317         let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0));
318         let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0));
319
320         let dev_t_layout = this.libc_ty_layout("dev_t")?;
321         let mode_t_layout = this.libc_ty_layout("mode_t")?;
322         let nlink_t_layout = this.libc_ty_layout("nlink_t")?;
323         let ino_t_layout = this.libc_ty_layout("ino_t")?;
324         let uid_t_layout = this.libc_ty_layout("uid_t")?;
325         let gid_t_layout = this.libc_ty_layout("gid_t")?;
326         let time_t_layout = this.libc_ty_layout("time_t")?;
327         let long_layout = this.libc_ty_layout("c_long")?;
328         let off_t_layout = this.libc_ty_layout("off_t")?;
329         let blkcnt_t_layout = this.libc_ty_layout("blkcnt_t")?;
330         let blksize_t_layout = this.libc_ty_layout("blksize_t")?;
331         let uint32_t_layout = this.libc_ty_layout("uint32_t")?;
332
333         let imms = [
334             immty_from_uint_checked(0u128, dev_t_layout)?, // st_dev
335             immty_from_uint_checked(mode, mode_t_layout)?, // st_mode
336             immty_from_uint_checked(0u128, nlink_t_layout)?, // st_nlink
337             immty_from_uint_checked(0u128, ino_t_layout)?, // st_ino
338             immty_from_uint_checked(0u128, uid_t_layout)?, // st_uid
339             immty_from_uint_checked(0u128, gid_t_layout)?, // st_gid
340             immty_from_uint_checked(0u128, dev_t_layout)?, // st_rdev
341             immty_from_uint_checked(0u128, uint32_t_layout)?, // padding
342             immty_from_uint_checked(access_sec, time_t_layout)?, // st_atime
343             immty_from_uint_checked(access_nsec, long_layout)?, // st_atime_nsec
344             immty_from_uint_checked(modified_sec, time_t_layout)?, // st_mtime
345             immty_from_uint_checked(modified_nsec, long_layout)?, // st_mtime_nsec
346             immty_from_uint_checked(0u128, time_t_layout)?, // st_ctime
347             immty_from_uint_checked(0u128, long_layout)?,  // st_ctime_nsec
348             immty_from_uint_checked(created_sec, time_t_layout)?, // st_birthtime
349             immty_from_uint_checked(created_nsec, long_layout)?, // st_birthtime_nsec
350             immty_from_uint_checked(metadata.size, off_t_layout)?, // st_size
351             immty_from_uint_checked(0u128, blkcnt_t_layout)?, // st_blocks
352             immty_from_uint_checked(0u128, blksize_t_layout)?, // st_blksize
353             immty_from_uint_checked(0u128, uint32_t_layout)?, // st_flags
354             immty_from_uint_checked(0u128, uint32_t_layout)?, // st_gen
355         ];
356
357         let buf = this.deref_operand(buf_op)?;
358         this.write_packed_immediates(&buf, &imms)?;
359
360         Ok(0)
361     }
362
363     /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
364     /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
365     /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
366     /// types (like `read`, that returns an `i64`).
367     fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
368         let this = self.eval_context_mut();
369         let ebadf = this.eval_libc("EBADF")?;
370         this.set_last_error(ebadf)?;
371         Ok((-1).into())
372     }
373
374     fn file_type_to_d_type(
375         &mut self,
376         file_type: std::io::Result<FileType>,
377     ) -> InterpResult<'tcx, i32> {
378         let this = self.eval_context_mut();
379         match file_type {
380             Ok(file_type) => {
381                 if file_type.is_dir() {
382                     Ok(this.eval_libc("DT_DIR")?.to_u8()?.into())
383                 } else if file_type.is_file() {
384                     Ok(this.eval_libc("DT_REG")?.to_u8()?.into())
385                 } else if file_type.is_symlink() {
386                     Ok(this.eval_libc("DT_LNK")?.to_u8()?.into())
387                 } else {
388                     // Certain file types are only supported when the host is a Unix system.
389                     // (i.e. devices and sockets) If it is, check those cases, if not, fall back to
390                     // DT_UNKNOWN sooner.
391
392                     #[cfg(unix)]
393                     {
394                         use std::os::unix::fs::FileTypeExt;
395                         if file_type.is_block_device() {
396                             Ok(this.eval_libc("DT_BLK")?.to_u8()?.into())
397                         } else if file_type.is_char_device() {
398                             Ok(this.eval_libc("DT_CHR")?.to_u8()?.into())
399                         } else if file_type.is_fifo() {
400                             Ok(this.eval_libc("DT_FIFO")?.to_u8()?.into())
401                         } else if file_type.is_socket() {
402                             Ok(this.eval_libc("DT_SOCK")?.to_u8()?.into())
403                         } else {
404                             Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
405                         }
406                     }
407                     #[cfg(not(unix))]
408                     Ok(this.eval_libc("DT_UNKNOWN")?.to_u8()?.into())
409                 }
410             }
411             Err(e) =>
412                 return match e.raw_os_error() {
413                     Some(error) => Ok(error),
414                     None =>
415                         throw_unsup_format!(
416                             "the error {} couldn't be converted to a return value",
417                             e
418                         ),
419                 },
420         }
421     }
422 }
423
424 #[derive(Debug)]
425 pub struct DirHandler {
426     /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
427     /// and closedir.
428     ///
429     /// When opendir is called, a directory iterator is created on the host for the target
430     /// directory, and an entry is stored in this hash map, indexed by an ID which represents
431     /// the directory stream. When readdir is called, the directory stream ID is used to look up
432     /// the corresponding ReadDir iterator from this map, and information from the next
433     /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from
434     /// the map.
435     streams: FxHashMap<u64, ReadDir>,
436     /// ID number to be used by the next call to opendir
437     next_id: u64,
438 }
439
440 impl DirHandler {
441     fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
442         let id = self.next_id;
443         self.next_id += 1;
444         self.streams.try_insert(id, read_dir).unwrap();
445         id
446     }
447 }
448
449 impl Default for DirHandler {
450     fn default() -> DirHandler {
451         DirHandler {
452             streams: FxHashMap::default(),
453             // Skip 0 as an ID, because it looks like a null pointer to libc
454             next_id: 1,
455         }
456     }
457 }
458
459 fn maybe_sync_file(
460     file: &File,
461     writable: bool,
462     operation: fn(&File) -> std::io::Result<()>,
463 ) -> std::io::Result<i32> {
464     if !writable && cfg!(windows) {
465         // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
466         // for writing. (FlushFileBuffers requires that the file handle have the
467         // GENERIC_WRITE right)
468         Ok(0i32)
469     } else {
470         let result = operation(file);
471         result.map(|_| 0i32)
472     }
473 }
474
475 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
476 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
477     fn open(
478         &mut self,
479         path_op: &OpTy<'tcx, Tag>,
480         flag_op: &OpTy<'tcx, Tag>,
481         mode_op: &OpTy<'tcx, Tag>,
482     ) -> InterpResult<'tcx, i32> {
483         let this = self.eval_context_mut();
484
485         let flag = this.read_scalar(flag_op)?.to_i32()?;
486
487         // Get the mode.  On macOS, the argument type `mode_t` is actually `u16`, but
488         // C integer promotion rules mean that on the ABI level, it gets passed as `u32`
489         // (see https://github.com/rust-lang/rust/issues/71915).
490         let mode = this.read_scalar(mode_op)?.to_u32()?;
491         if mode != 0o666 {
492             throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
493         }
494
495         let mut options = OpenOptions::new();
496
497         let o_rdonly = this.eval_libc_i32("O_RDONLY")?;
498         let o_wronly = this.eval_libc_i32("O_WRONLY")?;
499         let o_rdwr = this.eval_libc_i32("O_RDWR")?;
500         // The first two bits of the flag correspond to the access mode in linux, macOS and
501         // windows. We need to check that in fact the access mode flags for the current target
502         // only use these two bits, otherwise we are in an unsupported target and should error.
503         if (o_rdonly | o_wronly | o_rdwr) & !0b11 != 0 {
504             throw_unsup_format!("access mode flags on this target are unsupported");
505         }
506         let mut writable = true;
507
508         // Now we check the access mode
509         let access_mode = flag & 0b11;
510
511         if access_mode == o_rdonly {
512             writable = false;
513             options.read(true);
514         } else if access_mode == o_wronly {
515             options.write(true);
516         } else if access_mode == o_rdwr {
517             options.read(true).write(true);
518         } else {
519             throw_unsup_format!("unsupported access mode {:#x}", access_mode);
520         }
521         // We need to check that there aren't unsupported options in `flag`. For this we try to
522         // reproduce the content of `flag` in the `mirror` variable using only the supported
523         // options.
524         let mut mirror = access_mode;
525
526         let o_append = this.eval_libc_i32("O_APPEND")?;
527         if flag & o_append != 0 {
528             options.append(true);
529             mirror |= o_append;
530         }
531         let o_trunc = this.eval_libc_i32("O_TRUNC")?;
532         if flag & o_trunc != 0 {
533             options.truncate(true);
534             mirror |= o_trunc;
535         }
536         let o_creat = this.eval_libc_i32("O_CREAT")?;
537         if flag & o_creat != 0 {
538             mirror |= o_creat;
539
540             let o_excl = this.eval_libc_i32("O_EXCL")?;
541             if flag & o_excl != 0 {
542                 mirror |= o_excl;
543                 options.create_new(true);
544             } else {
545                 options.create(true);
546             }
547         }
548         let o_cloexec = this.eval_libc_i32("O_CLOEXEC")?;
549         if flag & o_cloexec != 0 {
550             // We do not need to do anything for this flag because `std` already sets it.
551             // (Technically we do not support *not* setting this flag, but we ignore that.)
552             mirror |= o_cloexec;
553         }
554         // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
555         // then we throw an error.
556         if flag != mirror {
557             throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
558         }
559
560         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
561
562         // Reject if isolation is enabled.
563         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
564             this.reject_in_isolation("`open`", reject_with)?;
565             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
566             return Ok(-1);
567         }
568
569         let fd = options.open(&path).map(|file| {
570             let fh = &mut this.machine.file_handler;
571             fh.insert_fd(Box::new(FileHandle { file, writable }))
572         });
573
574         this.try_unwrap_io_result(fd)
575     }
576
577     fn fcntl(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
578         let this = self.eval_context_mut();
579
580         if args.len() < 2 {
581             throw_ub_format!(
582                 "incorrect number of arguments for fcntl: got {}, expected at least 2",
583                 args.len()
584             );
585         }
586         let fd = this.read_scalar(&args[0])?.to_i32()?;
587         let cmd = this.read_scalar(&args[1])?.to_i32()?;
588
589         // Reject if isolation is enabled.
590         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
591             this.reject_in_isolation("`fcntl`", reject_with)?;
592             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
593             return Ok(-1);
594         }
595
596         // We only support getting the flags for a descriptor.
597         if cmd == this.eval_libc_i32("F_GETFD")? {
598             // Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
599             // `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
600             // always sets this flag when opening a file. However we still need to check that the
601             // file itself is open.
602             let &[_, _] = check_arg_count(args)?;
603             if this.machine.file_handler.handles.contains_key(&fd) {
604                 Ok(this.eval_libc_i32("FD_CLOEXEC")?)
605             } else {
606                 this.handle_not_found()
607             }
608         } else if cmd == this.eval_libc_i32("F_DUPFD")?
609             || cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")?
610         {
611             // Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
612             // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
613             // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
614             // thus they can share the same implementation here.
615             let &[_, _, ref start] = check_arg_count(args)?;
616             let start = this.read_scalar(start)?.to_i32()?;
617
618             let fh = &mut this.machine.file_handler;
619
620             match fh.handles.get_mut(&fd) {
621                 Some(file_descriptor) => {
622                     let dup_result = file_descriptor.dup();
623                     match dup_result {
624                         Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
625                         Err(e) => {
626                             this.set_last_error_from_io_error(e.kind())?;
627                             Ok(-1)
628                         }
629                     }
630                 }
631                 None => return this.handle_not_found(),
632             }
633         } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
634             let &[_, _] = check_arg_count(args)?;
635             if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
636                 // FIXME: Support fullfsync for all FDs
637                 let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
638                 let io_result = maybe_sync_file(&file, *writable, File::sync_all);
639                 this.try_unwrap_io_result(io_result)
640             } else {
641                 this.handle_not_found()
642             }
643         } else {
644             throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
645         }
646     }
647
648     fn close(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
649         let this = self.eval_context_mut();
650
651         let fd = this.read_scalar(fd_op)?.to_i32()?;
652
653         if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
654             let result = file_descriptor.close(this.machine.communicate())?;
655             this.try_unwrap_io_result(result)
656         } else {
657             this.handle_not_found()
658         }
659     }
660
661     fn read(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
662         let this = self.eval_context_mut();
663
664         // Isolation check is done via `FileDescriptor` trait.
665
666         trace!("Reading from FD {}, size {}", fd, count);
667
668         // Check that the *entire* buffer is actually valid memory.
669         this.memory.check_ptr_access_align(
670             buf,
671             Size::from_bytes(count),
672             Align::ONE,
673             CheckInAllocMsg::MemoryAccessTest,
674         )?;
675
676         // We cap the number of read bytes to the largest value that we are able to fit in both the
677         // host's and target's `isize`. This saves us from having to handle overflows later.
678         let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
679         let communicate = this.machine.communicate();
680
681         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
682             trace!("read: FD mapped to {:?}", file_descriptor);
683             // We want to read at most `count` bytes. We are sure that `count` is not negative
684             // because it was a target's `usize`. Also we are sure that its smaller than
685             // `usize::MAX` because it is a host's `isize`.
686             let mut bytes = vec![0; count as usize];
687             // `File::read` never returns a value larger than `count`,
688             // so this cannot fail.
689             let result =
690                 file_descriptor.read(communicate, &mut bytes)?.map(|c| i64::try_from(c).unwrap());
691
692             match result {
693                 Ok(read_bytes) => {
694                     // If reading to `bytes` did not fail, we write those bytes to the buffer.
695                     this.memory.write_bytes(buf, bytes)?;
696                     Ok(read_bytes)
697                 }
698                 Err(e) => {
699                     this.set_last_error_from_io_error(e.kind())?;
700                     Ok(-1)
701                 }
702             }
703         } else {
704             trace!("read: FD not found");
705             this.handle_not_found()
706         }
707     }
708
709     fn write(&mut self, fd: i32, buf: Pointer<Option<Tag>>, count: u64) -> InterpResult<'tcx, i64> {
710         let this = self.eval_context_mut();
711
712         // Isolation check is done via `FileDescriptor` trait.
713
714         // Check that the *entire* buffer is actually valid memory.
715         this.memory.check_ptr_access_align(
716             buf,
717             Size::from_bytes(count),
718             Align::ONE,
719             CheckInAllocMsg::MemoryAccessTest,
720         )?;
721
722         // We cap the number of written bytes to the largest value that we are able to fit in both the
723         // host's and target's `isize`. This saves us from having to handle overflows later.
724         let count = count.min(this.machine_isize_max() as u64).min(isize::MAX as u64);
725         let communicate = this.machine.communicate();
726
727         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
728             let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
729             let result =
730                 file_descriptor.write(communicate, &bytes)?.map(|c| i64::try_from(c).unwrap());
731             this.try_unwrap_io_result(result)
732         } else {
733             this.handle_not_found()
734         }
735     }
736
737     fn lseek64(
738         &mut self,
739         fd_op: &OpTy<'tcx, Tag>,
740         offset_op: &OpTy<'tcx, Tag>,
741         whence_op: &OpTy<'tcx, Tag>,
742     ) -> InterpResult<'tcx, i64> {
743         let this = self.eval_context_mut();
744
745         // Isolation check is done via `FileDescriptor` trait.
746
747         let fd = this.read_scalar(fd_op)?.to_i32()?;
748         let offset = this.read_scalar(offset_op)?.to_i64()?;
749         let whence = this.read_scalar(whence_op)?.to_i32()?;
750
751         let seek_from = if whence == this.eval_libc_i32("SEEK_SET")? {
752             SeekFrom::Start(u64::try_from(offset).unwrap())
753         } else if whence == this.eval_libc_i32("SEEK_CUR")? {
754             SeekFrom::Current(offset)
755         } else if whence == this.eval_libc_i32("SEEK_END")? {
756             SeekFrom::End(offset)
757         } else {
758             let einval = this.eval_libc("EINVAL")?;
759             this.set_last_error(einval)?;
760             return Ok(-1);
761         };
762
763         let communicate = this.machine.communicate();
764         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
765             let result = file_descriptor
766                 .seek(communicate, seek_from)?
767                 .map(|offset| i64::try_from(offset).unwrap());
768             this.try_unwrap_io_result(result)
769         } else {
770             this.handle_not_found()
771         }
772     }
773
774     fn unlink(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
775         let this = self.eval_context_mut();
776
777         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
778
779         // Reject if isolation is enabled.
780         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
781             this.reject_in_isolation("`unlink`", reject_with)?;
782             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
783             return Ok(-1);
784         }
785
786         let result = remove_file(path).map(|_| 0);
787         this.try_unwrap_io_result(result)
788     }
789
790     fn symlink(
791         &mut self,
792         target_op: &OpTy<'tcx, Tag>,
793         linkpath_op: &OpTy<'tcx, Tag>,
794     ) -> InterpResult<'tcx, i32> {
795         #[cfg(unix)]
796         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
797             std::os::unix::fs::symlink(src, dst)
798         }
799
800         #[cfg(windows)]
801         fn create_link(src: &Path, dst: &Path) -> std::io::Result<()> {
802             use std::os::windows::fs;
803             if src.is_dir() { fs::symlink_dir(src, dst) } else { fs::symlink_file(src, dst) }
804         }
805
806         let this = self.eval_context_mut();
807         let target = this.read_path_from_c_str(this.read_pointer(target_op)?)?;
808         let linkpath = this.read_path_from_c_str(this.read_pointer(linkpath_op)?)?;
809
810         // Reject if isolation is enabled.
811         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
812             this.reject_in_isolation("`symlink`", reject_with)?;
813             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
814             return Ok(-1);
815         }
816
817         let result = create_link(&target, &linkpath).map(|_| 0);
818         this.try_unwrap_io_result(result)
819     }
820
821     fn macos_stat(
822         &mut self,
823         path_op: &OpTy<'tcx, Tag>,
824         buf_op: &OpTy<'tcx, Tag>,
825     ) -> InterpResult<'tcx, i32> {
826         let this = self.eval_context_mut();
827         this.assert_target_os("macos", "stat");
828
829         let path_scalar = this.read_pointer(path_op)?;
830         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
831
832         // Reject if isolation is enabled.
833         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
834             this.reject_in_isolation("`stat`", reject_with)?;
835             let eacc = this.eval_libc("EACCES")?;
836             this.set_last_error(eacc)?;
837             return Ok(-1);
838         }
839
840         // `stat` always follows symlinks.
841         let metadata = match FileMetadata::from_path(this, &path, true)? {
842             Some(metadata) => metadata,
843             None => return Ok(-1),
844         };
845
846         this.macos_stat_write_buf(metadata, buf_op)
847     }
848
849     // `lstat` is used to get symlink metadata.
850     fn macos_lstat(
851         &mut self,
852         path_op: &OpTy<'tcx, Tag>,
853         buf_op: &OpTy<'tcx, Tag>,
854     ) -> InterpResult<'tcx, i32> {
855         let this = self.eval_context_mut();
856         this.assert_target_os("macos", "lstat");
857
858         let path_scalar = this.read_pointer(path_op)?;
859         let path = this.read_path_from_c_str(path_scalar)?.into_owned();
860
861         // Reject if isolation is enabled.
862         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
863             this.reject_in_isolation("`lstat`", reject_with)?;
864             let eacc = this.eval_libc("EACCES")?;
865             this.set_last_error(eacc)?;
866             return Ok(-1);
867         }
868
869         let metadata = match FileMetadata::from_path(this, &path, false)? {
870             Some(metadata) => metadata,
871             None => return Ok(-1),
872         };
873
874         this.macos_stat_write_buf(metadata, buf_op)
875     }
876
877     fn macos_fstat(
878         &mut self,
879         fd_op: &OpTy<'tcx, Tag>,
880         buf_op: &OpTy<'tcx, Tag>,
881     ) -> InterpResult<'tcx, i32> {
882         let this = self.eval_context_mut();
883
884         this.assert_target_os("macos", "fstat");
885
886         let fd = this.read_scalar(fd_op)?.to_i32()?;
887
888         // Reject if isolation is enabled.
889         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
890             this.reject_in_isolation("`fstat`", reject_with)?;
891             // Set error code as "EBADF" (bad fd)
892             return this.handle_not_found();
893         }
894
895         let metadata = match FileMetadata::from_fd(this, fd)? {
896             Some(metadata) => metadata,
897             None => return Ok(-1),
898         };
899         this.macos_stat_write_buf(metadata, buf_op)
900     }
901
902     fn linux_statx(
903         &mut self,
904         dirfd_op: &OpTy<'tcx, Tag>,    // Should be an `int`
905         pathname_op: &OpTy<'tcx, Tag>, // Should be a `const char *`
906         flags_op: &OpTy<'tcx, Tag>,    // Should be an `int`
907         _mask_op: &OpTy<'tcx, Tag>,    // Should be an `unsigned int`
908         statxbuf_op: &OpTy<'tcx, Tag>, // Should be a `struct statx *`
909     ) -> InterpResult<'tcx, i32> {
910         let this = self.eval_context_mut();
911
912         this.assert_target_os("linux", "statx");
913
914         let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
915         let pathname_ptr = this.read_pointer(pathname_op)?;
916
917         // If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
918         if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
919             let efault = this.eval_libc("EFAULT")?;
920             this.set_last_error(efault)?;
921             return Ok(-1);
922         }
923
924         // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a
925         // proper `MemPlace` and then write the results of this function to it. However, the
926         // `syscall` function is untyped. This means that all the `statx` parameters are provided
927         // as `isize`s instead of having the proper types. Thus, we have to recover the layout of
928         // `statxbuf_op` by using the `libc::statx` struct type.
929         let statxbuf_place = {
930             // FIXME: This long path is required because `libc::statx` is an struct and also a
931             // function and `resolve_path` is returning the latter.
932             let statx_ty = this
933                 .resolve_path(&["libc", "unix", "linux_like", "linux", "gnu", "statx"])
934                 .ty(*this.tcx, ty::ParamEnv::reveal_all());
935             let statx_layout = this.layout_of(statx_ty)?;
936             MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout)
937         };
938
939         let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
940         // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
941         let flags = this.read_scalar(flags_op)?.to_i32()?;
942         let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
943         let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
944         // We only support:
945         // * interpreting `path` as an absolute directory,
946         // * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or
947         // * interpreting `dirfd` as any file descriptor when `path` is empty and AT_EMPTY_PATH is
948         // set.
949         // Other behaviors cannot be tested from `libstd` and thus are not implemented. If you
950         // found this error, please open an issue reporting it.
951         if !(path.is_absolute()
952             || dirfd == this.eval_libc_i32("AT_FDCWD")?
953             || (path.as_os_str().is_empty() && empty_path_flag))
954         {
955             throw_unsup_format!(
956                 "using statx is only supported with absolute paths, relative paths with the file \
957                 descriptor `AT_FDCWD`, and empty paths with the `AT_EMPTY_PATH` flag set and any \
958                 file descriptor"
959             )
960         }
961
962         // Reject if isolation is enabled.
963         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
964             this.reject_in_isolation("`statx`", reject_with)?;
965             let ecode = if path.is_absolute() || dirfd == this.eval_libc_i32("AT_FDCWD")? {
966                 // since `path` is provided, either absolute or
967                 // relative to CWD, `EACCES` is the most relevant.
968                 this.eval_libc("EACCES")?
969             } else {
970                 // `dirfd` is set to target file, and `path` is empty
971                 // (or we would have hit the `throw_unsup_format`
972                 // above). `EACCES` would violate the spec.
973                 assert!(empty_path_flag);
974                 this.eval_libc("EBADF")?
975             };
976             this.set_last_error(ecode)?;
977             return Ok(-1);
978         }
979
980         // the `_mask_op` paramter specifies the file information that the caller requested.
981         // However `statx` is allowed to return information that was not requested or to not
982         // return information that was requested. This `mask` represents the information we can
983         // actually provide for any target.
984         let mut mask =
985             this.eval_libc("STATX_TYPE")?.to_u32()? | this.eval_libc("STATX_SIZE")?.to_u32()?;
986
987         // If the `AT_SYMLINK_NOFOLLOW` flag is set, we query the file's metadata without following
988         // symbolic links.
989         let follow_symlink = flags & this.eval_libc("AT_SYMLINK_NOFOLLOW")?.to_i32()? == 0;
990
991         // If the path is empty, and the AT_EMPTY_PATH flag is set, we query the open file
992         // represented by dirfd, whether it's a directory or otherwise.
993         let metadata = if path.as_os_str().is_empty() && empty_path_flag {
994             FileMetadata::from_fd(this, dirfd)?
995         } else {
996             FileMetadata::from_path(this, &path, follow_symlink)?
997         };
998         let metadata = match metadata {
999             Some(metadata) => metadata,
1000             None => return Ok(-1),
1001         };
1002
1003         // The `mode` field specifies the type of the file and the permissions over the file for
1004         // the owner, its group and other users. Given that we can only provide the file type
1005         // without using platform specific methods, we only set the bits corresponding to the file
1006         // type. This should be an `__u16` but `libc` provides its values as `u32`.
1007         let mode: u16 = metadata
1008             .mode
1009             .to_u32()?
1010             .try_into()
1011             .unwrap_or_else(|_| bug!("libc contains bad value for constant"));
1012
1013         // We need to set the corresponding bits of `mask` if the access, creation and modification
1014         // times were available. Otherwise we let them be zero.
1015         let (access_sec, access_nsec) = metadata
1016             .accessed
1017             .map(|tup| {
1018                 mask |= this.eval_libc("STATX_ATIME")?.to_u32()?;
1019                 InterpResult::Ok(tup)
1020             })
1021             .unwrap_or(Ok((0, 0)))?;
1022
1023         let (created_sec, created_nsec) = metadata
1024             .created
1025             .map(|tup| {
1026                 mask |= this.eval_libc("STATX_BTIME")?.to_u32()?;
1027                 InterpResult::Ok(tup)
1028             })
1029             .unwrap_or(Ok((0, 0)))?;
1030
1031         let (modified_sec, modified_nsec) = metadata
1032             .modified
1033             .map(|tup| {
1034                 mask |= this.eval_libc("STATX_MTIME")?.to_u32()?;
1035                 InterpResult::Ok(tup)
1036             })
1037             .unwrap_or(Ok((0, 0)))?;
1038
1039         let __u32_layout = this.libc_ty_layout("__u32")?;
1040         let __u64_layout = this.libc_ty_layout("__u64")?;
1041         let __u16_layout = this.libc_ty_layout("__u16")?;
1042
1043         // Now we transform all this fields into `ImmTy`s and write them to `statxbuf`. We write a
1044         // zero for the unavailable fields.
1045         let imms = [
1046             immty_from_uint_checked(mask, __u32_layout)?, // stx_mask
1047             immty_from_uint_checked(0u128, __u32_layout)?, // stx_blksize
1048             immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
1049             immty_from_uint_checked(0u128, __u32_layout)?, // stx_nlink
1050             immty_from_uint_checked(0u128, __u32_layout)?, // stx_uid
1051             immty_from_uint_checked(0u128, __u32_layout)?, // stx_gid
1052             immty_from_uint_checked(mode, __u16_layout)?, // stx_mode
1053             immty_from_uint_checked(0u128, __u16_layout)?, // statx padding
1054             immty_from_uint_checked(0u128, __u64_layout)?, // stx_ino
1055             immty_from_uint_checked(metadata.size, __u64_layout)?, // stx_size
1056             immty_from_uint_checked(0u128, __u64_layout)?, // stx_blocks
1057             immty_from_uint_checked(0u128, __u64_layout)?, // stx_attributes
1058             immty_from_uint_checked(access_sec, __u64_layout)?, // stx_atime.tv_sec
1059             immty_from_uint_checked(access_nsec, __u32_layout)?, // stx_atime.tv_nsec
1060             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1061             immty_from_uint_checked(created_sec, __u64_layout)?, // stx_btime.tv_sec
1062             immty_from_uint_checked(created_nsec, __u32_layout)?, // stx_btime.tv_nsec
1063             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1064             immty_from_uint_checked(0u128, __u64_layout)?, // stx_ctime.tv_sec
1065             immty_from_uint_checked(0u128, __u32_layout)?, // stx_ctime.tv_nsec
1066             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1067             immty_from_uint_checked(modified_sec, __u64_layout)?, // stx_mtime.tv_sec
1068             immty_from_uint_checked(modified_nsec, __u32_layout)?, // stx_mtime.tv_nsec
1069             immty_from_uint_checked(0u128, __u32_layout)?, // statx_timestamp padding
1070             immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_major
1071             immty_from_uint_checked(0u128, __u64_layout)?, // stx_rdev_minor
1072             immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_major
1073             immty_from_uint_checked(0u128, __u64_layout)?, // stx_dev_minor
1074         ];
1075
1076         this.write_packed_immediates(&statxbuf_place, &imms)?;
1077
1078         Ok(0)
1079     }
1080
1081     fn rename(
1082         &mut self,
1083         oldpath_op: &OpTy<'tcx, Tag>,
1084         newpath_op: &OpTy<'tcx, Tag>,
1085     ) -> InterpResult<'tcx, i32> {
1086         let this = self.eval_context_mut();
1087
1088         let oldpath_ptr = this.read_pointer(oldpath_op)?;
1089         let newpath_ptr = this.read_pointer(newpath_op)?;
1090
1091         if this.ptr_is_null(oldpath_ptr)? || this.ptr_is_null(newpath_ptr)? {
1092             let efault = this.eval_libc("EFAULT")?;
1093             this.set_last_error(efault)?;
1094             return Ok(-1);
1095         }
1096
1097         let oldpath = this.read_path_from_c_str(oldpath_ptr)?;
1098         let newpath = this.read_path_from_c_str(newpath_ptr)?;
1099
1100         // Reject if isolation is enabled.
1101         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1102             this.reject_in_isolation("`rename`", reject_with)?;
1103             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1104             return Ok(-1);
1105         }
1106
1107         let result = rename(oldpath, newpath).map(|_| 0);
1108
1109         this.try_unwrap_io_result(result)
1110     }
1111
1112     fn mkdir(
1113         &mut self,
1114         path_op: &OpTy<'tcx, Tag>,
1115         mode_op: &OpTy<'tcx, Tag>,
1116     ) -> InterpResult<'tcx, i32> {
1117         let this = self.eval_context_mut();
1118
1119         #[cfg_attr(not(unix), allow(unused_variables))]
1120         let mode = if this.tcx.sess.target.os == "macos" {
1121             u32::from(this.read_scalar(mode_op)?.to_u16()?)
1122         } else {
1123             this.read_scalar(mode_op)?.to_u32()?
1124         };
1125
1126         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1127
1128         // Reject if isolation is enabled.
1129         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1130             this.reject_in_isolation("`mkdir`", reject_with)?;
1131             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1132             return Ok(-1);
1133         }
1134
1135         #[cfg_attr(not(unix), allow(unused_mut))]
1136         let mut builder = DirBuilder::new();
1137
1138         // If the host supports it, forward on the mode of the directory
1139         // (i.e. permission bits and the sticky bit)
1140         #[cfg(unix)]
1141         {
1142             use std::os::unix::fs::DirBuilderExt;
1143             builder.mode(mode.into());
1144         }
1145
1146         let result = builder.create(path).map(|_| 0i32);
1147
1148         this.try_unwrap_io_result(result)
1149     }
1150
1151     fn rmdir(&mut self, path_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1152         let this = self.eval_context_mut();
1153
1154         let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
1155
1156         // Reject if isolation is enabled.
1157         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1158             this.reject_in_isolation("`rmdir`", reject_with)?;
1159             this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
1160             return Ok(-1);
1161         }
1162
1163         let result = remove_dir(path).map(|_| 0i32);
1164
1165         this.try_unwrap_io_result(result)
1166     }
1167
1168     fn opendir(&mut self, name_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
1169         let this = self.eval_context_mut();
1170
1171         let name = this.read_path_from_c_str(this.read_pointer(name_op)?)?;
1172
1173         // Reject if isolation is enabled.
1174         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1175             this.reject_in_isolation("`opendir`", reject_with)?;
1176             let eacc = this.eval_libc("EACCES")?;
1177             this.set_last_error(eacc)?;
1178             return Ok(Scalar::null_ptr(this));
1179         }
1180
1181         let result = read_dir(name);
1182
1183         match result {
1184             Ok(dir_iter) => {
1185                 let id = this.machine.dir_handler.insert_new(dir_iter);
1186
1187                 // The libc API for opendir says that this method returns a pointer to an opaque
1188                 // structure, but we are returning an ID number. Thus, pass it as a scalar of
1189                 // pointer width.
1190                 Ok(Scalar::from_machine_usize(id, this))
1191             }
1192             Err(e) => {
1193                 this.set_last_error_from_io_error(e.kind())?;
1194                 Ok(Scalar::null_ptr(this))
1195             }
1196         }
1197     }
1198
1199     fn linux_readdir64_r(
1200         &mut self,
1201         dirp_op: &OpTy<'tcx, Tag>,
1202         entry_op: &OpTy<'tcx, Tag>,
1203         result_op: &OpTy<'tcx, Tag>,
1204     ) -> InterpResult<'tcx, i32> {
1205         let this = self.eval_context_mut();
1206
1207         this.assert_target_os("linux", "readdir64_r");
1208
1209         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1210
1211         // Reject if isolation is enabled.
1212         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1213             this.reject_in_isolation("`readdir64_r`", reject_with)?;
1214             // Set error code as "EBADF" (bad fd)
1215             return this.handle_not_found();
1216         }
1217
1218         let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1219             err_unsup_format!("the DIR pointer passed to readdir64_r did not come from opendir")
1220         })?;
1221         match dir_iter.next() {
1222             Some(Ok(dir_entry)) => {
1223                 // Write into entry, write pointer to result, return 0 on success.
1224                 // The name is written with write_os_str_to_c_str, while the rest of the
1225                 // dirent64 struct is written using write_packed_immediates.
1226
1227                 // For reference:
1228                 // pub struct dirent64 {
1229                 //     pub d_ino: ino64_t,
1230                 //     pub d_off: off64_t,
1231                 //     pub d_reclen: c_ushort,
1232                 //     pub d_type: c_uchar,
1233                 //     pub d_name: [c_char; 256],
1234                 // }
1235
1236                 let entry_place = this.deref_operand(entry_op)?;
1237                 let name_place = this.mplace_field(&entry_place, 4)?;
1238
1239                 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1240                 let (name_fits, _) = this.write_os_str_to_c_str(
1241                     &file_name,
1242                     name_place.ptr,
1243                     name_place.layout.size.bytes(),
1244                 )?;
1245                 if !name_fits {
1246                     throw_unsup_format!(
1247                         "a directory entry had a name too large to fit in libc::dirent64"
1248                     );
1249                 }
1250
1251                 let entry_place = this.deref_operand(entry_op)?;
1252                 let ino64_t_layout = this.libc_ty_layout("ino64_t")?;
1253                 let off64_t_layout = this.libc_ty_layout("off64_t")?;
1254                 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
1255                 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
1256
1257                 // If the host is a Unix system, fill in the inode number with its real value.
1258                 // If not, use 0 as a fallback value.
1259                 #[cfg(unix)]
1260                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1261                 #[cfg(not(unix))]
1262                 let ino = 0u64;
1263
1264                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1265
1266                 let imms = [
1267                     immty_from_uint_checked(ino, ino64_t_layout)?, // d_ino
1268                     immty_from_uint_checked(0u128, off64_t_layout)?, // d_off
1269                     immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
1270                     immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
1271                 ];
1272                 this.write_packed_immediates(&entry_place, &imms)?;
1273
1274                 let result_place = this.deref_operand(result_op)?;
1275                 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1276
1277                 Ok(0)
1278             }
1279             None => {
1280                 // end of stream: return 0, assign *result=NULL
1281                 this.write_null(&this.deref_operand(result_op)?.into())?;
1282                 Ok(0)
1283             }
1284             Some(Err(e)) =>
1285                 match e.raw_os_error() {
1286                     // return positive error number on error
1287                     Some(error) => Ok(error),
1288                     None => {
1289                         throw_unsup_format!(
1290                             "the error {} couldn't be converted to a return value",
1291                             e
1292                         )
1293                     }
1294                 },
1295         }
1296     }
1297
1298     fn macos_readdir_r(
1299         &mut self,
1300         dirp_op: &OpTy<'tcx, Tag>,
1301         entry_op: &OpTy<'tcx, Tag>,
1302         result_op: &OpTy<'tcx, Tag>,
1303     ) -> InterpResult<'tcx, i32> {
1304         let this = self.eval_context_mut();
1305
1306         this.assert_target_os("macos", "readdir_r");
1307
1308         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1309
1310         // Reject if isolation is enabled.
1311         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1312             this.reject_in_isolation("`readdir_r`", reject_with)?;
1313             // Set error code as "EBADF" (bad fd)
1314             return this.handle_not_found();
1315         }
1316
1317         let dir_iter = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| {
1318             err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
1319         })?;
1320         match dir_iter.next() {
1321             Some(Ok(dir_entry)) => {
1322                 // Write into entry, write pointer to result, return 0 on success.
1323                 // The name is written with write_os_str_to_c_str, while the rest of the
1324                 // dirent struct is written using write_packed_Immediates.
1325
1326                 // For reference:
1327                 // pub struct dirent {
1328                 //     pub d_ino: u64,
1329                 //     pub d_seekoff: u64,
1330                 //     pub d_reclen: u16,
1331                 //     pub d_namlen: u16,
1332                 //     pub d_type: u8,
1333                 //     pub d_name: [c_char; 1024],
1334                 // }
1335
1336                 let entry_place = this.deref_operand(entry_op)?;
1337                 let name_place = this.mplace_field(&entry_place, 5)?;
1338
1339                 let file_name = dir_entry.file_name(); // not a Path as there are no separators!
1340                 let (name_fits, file_name_len) = this.write_os_str_to_c_str(
1341                     &file_name,
1342                     name_place.ptr,
1343                     name_place.layout.size.bytes(),
1344                 )?;
1345                 if !name_fits {
1346                     throw_unsup_format!(
1347                         "a directory entry had a name too large to fit in libc::dirent"
1348                     );
1349                 }
1350
1351                 let entry_place = this.deref_operand(entry_op)?;
1352                 let ino_t_layout = this.libc_ty_layout("ino_t")?;
1353                 let off_t_layout = this.libc_ty_layout("off_t")?;
1354                 let c_ushort_layout = this.libc_ty_layout("c_ushort")?;
1355                 let c_uchar_layout = this.libc_ty_layout("c_uchar")?;
1356
1357                 // If the host is a Unix system, fill in the inode number with its real value.
1358                 // If not, use 0 as a fallback value.
1359                 #[cfg(unix)]
1360                 let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry);
1361                 #[cfg(not(unix))]
1362                 let ino = 0u64;
1363
1364                 let file_type = this.file_type_to_d_type(dir_entry.file_type())?;
1365
1366                 let imms = [
1367                     immty_from_uint_checked(ino, ino_t_layout)?,      // d_ino
1368                     immty_from_uint_checked(0u128, off_t_layout)?,    // d_seekoff
1369                     immty_from_uint_checked(0u128, c_ushort_layout)?, // d_reclen
1370                     immty_from_uint_checked(file_name_len, c_ushort_layout)?, // d_namlen
1371                     immty_from_int_checked(file_type, c_uchar_layout)?, // d_type
1372                 ];
1373                 this.write_packed_immediates(&entry_place, &imms)?;
1374
1375                 let result_place = this.deref_operand(result_op)?;
1376                 this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?;
1377
1378                 Ok(0)
1379             }
1380             None => {
1381                 // end of stream: return 0, assign *result=NULL
1382                 this.write_null(&this.deref_operand(result_op)?.into())?;
1383                 Ok(0)
1384             }
1385             Some(Err(e)) =>
1386                 match e.raw_os_error() {
1387                     // return positive error number on error
1388                     Some(error) => Ok(error),
1389                     None => {
1390                         throw_unsup_format!(
1391                             "the error {} couldn't be converted to a return value",
1392                             e
1393                         )
1394                     }
1395                 },
1396         }
1397     }
1398
1399     fn closedir(&mut self, dirp_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1400         let this = self.eval_context_mut();
1401
1402         let dirp = this.read_scalar(dirp_op)?.to_machine_usize(this)?;
1403
1404         // Reject if isolation is enabled.
1405         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1406             this.reject_in_isolation("`closedir`", reject_with)?;
1407             // Set error code as "EBADF" (bad fd)
1408             return this.handle_not_found();
1409         }
1410
1411         if let Some(dir_iter) = this.machine.dir_handler.streams.remove(&dirp) {
1412             drop(dir_iter);
1413             Ok(0)
1414         } else {
1415             this.handle_not_found()
1416         }
1417     }
1418
1419     fn ftruncate64(
1420         &mut self,
1421         fd_op: &OpTy<'tcx, Tag>,
1422         length_op: &OpTy<'tcx, Tag>,
1423     ) -> InterpResult<'tcx, i32> {
1424         let this = self.eval_context_mut();
1425
1426         let fd = this.read_scalar(fd_op)?.to_i32()?;
1427         let length = this.read_scalar(length_op)?.to_i64()?;
1428
1429         // Reject if isolation is enabled.
1430         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1431             this.reject_in_isolation("`ftruncate64`", reject_with)?;
1432             // Set error code as "EBADF" (bad fd)
1433             return this.handle_not_found();
1434         }
1435
1436         if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
1437             // FIXME: Support ftruncate64 for all FDs
1438             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1439             if *writable {
1440                 if let Ok(length) = length.try_into() {
1441                     let result = file.set_len(length);
1442                     this.try_unwrap_io_result(result.map(|_| 0i32))
1443                 } else {
1444                     let einval = this.eval_libc("EINVAL")?;
1445                     this.set_last_error(einval)?;
1446                     Ok(-1)
1447                 }
1448             } else {
1449                 // The file is not writable
1450                 let einval = this.eval_libc("EINVAL")?;
1451                 this.set_last_error(einval)?;
1452                 Ok(-1)
1453             }
1454         } else {
1455             this.handle_not_found()
1456         }
1457     }
1458
1459     fn fsync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1460         // On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1461         // underlying disk to finish writing. In the interest of host compatibility,
1462         // we conservatively implement this with `sync_all`, which
1463         // *does* wait for the disk.
1464
1465         let this = self.eval_context_mut();
1466
1467         let fd = this.read_scalar(fd_op)?.to_i32()?;
1468
1469         // Reject if isolation is enabled.
1470         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1471             this.reject_in_isolation("`fsync`", reject_with)?;
1472             // Set error code as "EBADF" (bad fd)
1473             return this.handle_not_found();
1474         }
1475
1476         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1477             // FIXME: Support fsync for all FDs
1478             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1479             let io_result = maybe_sync_file(&file, *writable, File::sync_all);
1480             this.try_unwrap_io_result(io_result)
1481         } else {
1482             this.handle_not_found()
1483         }
1484     }
1485
1486     fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1487         let this = self.eval_context_mut();
1488
1489         let fd = this.read_scalar(fd_op)?.to_i32()?;
1490
1491         // Reject if isolation is enabled.
1492         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1493             this.reject_in_isolation("`fdatasync`", reject_with)?;
1494             // Set error code as "EBADF" (bad fd)
1495             return this.handle_not_found();
1496         }
1497
1498         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1499             // FIXME: Support fdatasync for all FDs
1500             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1501             let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1502             this.try_unwrap_io_result(io_result)
1503         } else {
1504             this.handle_not_found()
1505         }
1506     }
1507
1508     fn sync_file_range(
1509         &mut self,
1510         fd_op: &OpTy<'tcx, Tag>,
1511         offset_op: &OpTy<'tcx, Tag>,
1512         nbytes_op: &OpTy<'tcx, Tag>,
1513         flags_op: &OpTy<'tcx, Tag>,
1514     ) -> InterpResult<'tcx, i32> {
1515         let this = self.eval_context_mut();
1516
1517         let fd = this.read_scalar(fd_op)?.to_i32()?;
1518         let offset = this.read_scalar(offset_op)?.to_i64()?;
1519         let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1520         let flags = this.read_scalar(flags_op)?.to_i32()?;
1521
1522         if offset < 0 || nbytes < 0 {
1523             let einval = this.eval_libc("EINVAL")?;
1524             this.set_last_error(einval)?;
1525             return Ok(-1);
1526         }
1527         let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1528             | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1529             | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1530         if flags & allowed_flags != flags {
1531             let einval = this.eval_libc("EINVAL")?;
1532             this.set_last_error(einval)?;
1533             return Ok(-1);
1534         }
1535
1536         // Reject if isolation is enabled.
1537         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1538             this.reject_in_isolation("`sync_file_range`", reject_with)?;
1539             // Set error code as "EBADF" (bad fd)
1540             return this.handle_not_found();
1541         }
1542
1543         if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
1544             // FIXME: Support sync_data_range for all FDs
1545             let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
1546             let io_result = maybe_sync_file(&file, *writable, File::sync_data);
1547             this.try_unwrap_io_result(io_result)
1548         } else {
1549             this.handle_not_found()
1550         }
1551     }
1552
1553     fn readlink(
1554         &mut self,
1555         pathname_op: &OpTy<'tcx, Tag>,
1556         buf_op: &OpTy<'tcx, Tag>,
1557         bufsize_op: &OpTy<'tcx, Tag>,
1558     ) -> InterpResult<'tcx, i64> {
1559         let this = self.eval_context_mut();
1560
1561         let pathname = this.read_path_from_c_str(this.read_pointer(pathname_op)?)?;
1562         let buf = this.read_pointer(buf_op)?;
1563         let bufsize = this.read_scalar(bufsize_op)?.to_machine_usize(this)?;
1564
1565         // Reject if isolation is enabled.
1566         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1567             this.reject_in_isolation("`readlink`", reject_with)?;
1568             let eacc = this.eval_libc("EACCES")?;
1569             this.set_last_error(eacc)?;
1570             return Ok(-1);
1571         }
1572
1573         let result = std::fs::read_link(pathname);
1574         match result {
1575             Ok(resolved) => {
1576                 let resolved = this.convert_path_separator(
1577                     Cow::Borrowed(resolved.as_ref()),
1578                     crate::shims::os_str::PathConversion::HostToTarget,
1579                 );
1580                 let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?;
1581                 let bufsize: usize = bufsize.try_into().unwrap();
1582                 if path_bytes.len() > bufsize {
1583                     path_bytes = &path_bytes[..bufsize]
1584                 }
1585                 // 'readlink' truncates the resolved path if
1586                 // the provided buffer is not large enough.
1587                 this.memory.write_bytes(buf, path_bytes.iter().copied())?;
1588                 Ok(path_bytes.len().try_into().unwrap())
1589             }
1590             Err(e) => {
1591                 this.set_last_error_from_io_error(e.kind())?;
1592                 Ok(-1)
1593             }
1594         }
1595     }
1596 }
1597
1598 /// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
1599 /// `time` is Ok. Returns `None` if `time` is an error. Fails if `time` happens before the unix
1600 /// epoch.
1601 fn extract_sec_and_nsec<'tcx>(
1602     time: std::io::Result<SystemTime>,
1603 ) -> InterpResult<'tcx, Option<(u64, u32)>> {
1604     time.ok()
1605         .map(|time| {
1606             let duration = system_time_to_duration(&time)?;
1607             Ok((duration.as_secs(), duration.subsec_nanos()))
1608         })
1609         .transpose()
1610 }
1611
1612 /// Stores a file's metadata in order to avoid code duplication in the different metadata related
1613 /// shims.
1614 struct FileMetadata {
1615     mode: Scalar<Tag>,
1616     size: u64,
1617     created: Option<(u64, u32)>,
1618     accessed: Option<(u64, u32)>,
1619     modified: Option<(u64, u32)>,
1620 }
1621
1622 impl FileMetadata {
1623     fn from_path<'tcx, 'mir>(
1624         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1625         path: &Path,
1626         follow_symlink: bool,
1627     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1628         let metadata =
1629             if follow_symlink { std::fs::metadata(path) } else { std::fs::symlink_metadata(path) };
1630
1631         FileMetadata::from_meta(ecx, metadata)
1632     }
1633
1634     fn from_fd<'tcx, 'mir>(
1635         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1636         fd: i32,
1637     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1638         let option = ecx.machine.file_handler.handles.get(&fd);
1639         let file = match option {
1640             Some(file_descriptor) => &file_descriptor.as_file_handle()?.file,
1641             None => return ecx.handle_not_found().map(|_: i32| None),
1642         };
1643         let metadata = file.metadata();
1644
1645         FileMetadata::from_meta(ecx, metadata)
1646     }
1647
1648     fn from_meta<'tcx, 'mir>(
1649         ecx: &mut MiriEvalContext<'mir, 'tcx>,
1650         metadata: Result<std::fs::Metadata, std::io::Error>,
1651     ) -> InterpResult<'tcx, Option<FileMetadata>> {
1652         let metadata = match metadata {
1653             Ok(metadata) => metadata,
1654             Err(e) => {
1655                 ecx.set_last_error_from_io_error(e.kind())?;
1656                 return Ok(None);
1657             }
1658         };
1659
1660         let file_type = metadata.file_type();
1661
1662         let mode_name = if file_type.is_file() {
1663             "S_IFREG"
1664         } else if file_type.is_dir() {
1665             "S_IFDIR"
1666         } else {
1667             "S_IFLNK"
1668         };
1669
1670         let mode = ecx.eval_libc(mode_name)?;
1671
1672         let size = metadata.len();
1673
1674         let created = extract_sec_and_nsec(metadata.created())?;
1675         let accessed = extract_sec_and_nsec(metadata.accessed())?;
1676         let modified = extract_sec_and_nsec(metadata.modified())?;
1677
1678         // FIXME: Provide more fields using platform specific methods.
1679         Ok(Some(FileMetadata { mode, size, created, accessed, modified }))
1680     }
1681 }