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