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